From 71e9e422026582b9ed9f56fb90810e1737050658 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Jul 2023 21:05:29 +0200 Subject: [PATCH 01/12] set gcc12 as the default compiler for linux --- app/linux-prebuild.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/linux-prebuild.sh b/app/linux-prebuild.sh index 58286b2388..8d4d4e12c0 100755 --- a/app/linux-prebuild.sh +++ b/app/linux-prebuild.sh @@ -28,6 +28,8 @@ cd "${SCRIPT_DIR}" if [ ! "$system_libs" == true ]; then "${SCRIPT_DIR}"/linux-pre-vcpkg.sh "${args[@]}" + export CC=gcc-12 CXX=g++-12 # Use gcc-12 as the default compiler, since gcc-13 fails to build vcpkg + "${SCRIPT_DIR}"/linux-pre-vcpkg.sh "${args[@]}" fi echo "Compiling native ruby extensions..." From aba724fbcddae41d9ce00cd544bcede1810ce636 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Jul 2023 21:09:43 +0200 Subject: [PATCH 02/12] update documentation to include info about gcc12 for building with linux --- BUILD-LINUX.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BUILD-LINUX.md b/BUILD-LINUX.md index 89eccaa2dc..f2a5293c3e 100644 --- a/BUILD-LINUX.md +++ b/BUILD-LINUX.md @@ -47,13 +47,15 @@ and to run In order to build Sonic Pi's various components, we need to install a few dependencies: -* Build Tools (c++ compiler, cmake, git.) +* Build Tools (c++ compiler , cmake, git.) * Qt + Dev tools (5.15+) * Jack (and pulse-audio-module-jack if you are running Raspberry Pi OS) * Ruby + Dev tools (2.5+) * Elixir + Dev tools (12.0+) * SuperCollider + SC3 plugins +Note: please make sure that you have gcc12 installed. Compiling vcpkg dependencies does not work with gcc13 currently + ### 1.1 Debian The following is a rough list of Debian packages that are needed that can serve as a starting position: From 4c3fb855e04417df9ef848a90594cddeb0fa9594 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Jul 2023 21:10:19 +0200 Subject: [PATCH 03/12] update .gitignore to ignore build artefacts --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index dcd8174811..87d05d2af5 100644 --- a/.gitignore +++ b/.gitignore @@ -154,3 +154,5 @@ etc/synthdefs/designs/overtone/sonic-pi/native # Visual Studio settings folder. .vs +app/.cache/clangd/index +sonic-pi.desktop From 4280ab4033bdc773b6f4a26038835fb993499b35 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Jul 2023 21:11:09 +0200 Subject: [PATCH 04/12] Include QTQuick in the build --- app/gui/qt/SonicPi.pro | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/gui/qt/SonicPi.pro b/app/gui/qt/SonicPi.pro index 2506e979f7..6992dca478 100644 --- a/app/gui/qt/SonicPi.pro +++ b/app/gui/qt/SonicPi.pro @@ -107,3 +107,5 @@ TRANSLATIONS = lang/sonic-pi_bg.ts \ RESOURCES += SonicPi.qrc \ help_files.qrc \ info_files.qrc + +QT += quick From 0eb49f18904d0ecd67b6e67ac91296ae54551007 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Jul 2023 21:33:12 +0200 Subject: [PATCH 05/12] fix unsued return value error while loading qt translations --- app/gui/qt/utils/sonicpi_i18n.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/gui/qt/utils/sonicpi_i18n.cpp b/app/gui/qt/utils/sonicpi_i18n.cpp index 8f10fcb321..d2f1f2337f 100644 --- a/app/gui/qt/utils/sonicpi_i18n.cpp +++ b/app/gui/qt/utils/sonicpi_i18n.cpp @@ -111,13 +111,15 @@ bool SonicPii18n::loadTranslations(QString lang) { std::cout << "[GUI] [i18n] - Loading translations for " << language.toUtf8().constData() << std::endl; i18n = translator.load("sonic-pi_" + language, ":/lang/") || language == "en_GB" || language == "en" || language == "C"; - if (!i18n) { - std::cout << "[GUI] [i18n] - Error: Failed to load language translation for " << language.toUtf8().constData() << std::endl; - language = "en_GB"; - } - app->installTranslator(&translator); + i18n = translator.load("sonic-pi_" + language, ":/lang/") || language == "en_GB" || language == "en" || language == "C"; + bool translator_succes_qt = qtTranslator.load("qt_" + language, QLibraryInfo::path(QLibraryInfo::TranslationsPath)); + if (!i18n || !translator_succes_qt) + { + std::cout << "[GUI] [i18n] - Error: Failed to load language translation for " << language.toUtf8().constData() << std::endl; + language = "en_GB"; + } + app->installTranslator(&translator); - qtTranslator.load("qt_" + language, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app->installTranslator(&qtTranslator); this->currently_loaded_language = language; From 1848b42518fdec3415c93fcdbac4bd88c978724a Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Jul 2023 21:36:34 +0200 Subject: [PATCH 06/12] fix rb_data_object_allow deprecation --- app/server/ruby/vendor/atomic/ext/atomic_reference.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/server/ruby/vendor/atomic/ext/atomic_reference.c b/app/server/ruby/vendor/atomic/ext/atomic_reference.c index fbbbca1096..a162e4f898 100755 --- a/app/server/ruby/vendor/atomic/ext/atomic_reference.c +++ b/app/server/ruby/vendor/atomic/ext/atomic_reference.c @@ -23,8 +23,9 @@ static void ir_mark(void *value) { rb_gc_mark_maybe((VALUE) value); } -static VALUE ir_alloc(VALUE klass) { - return rb_data_object_alloc(klass, (void *) Qnil, ir_mark, NULL); +static VALUE ir_alloc(VALUE klass) +{ + return rb_data_object_wrap(klass, (void*)Qnil, ir_mark, NULL); } static VALUE ir_initialize(int argc, VALUE* argv, VALUE self) { From af915599622e060bae349a0be82cc4958c137578 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Jul 2023 22:01:27 +0200 Subject: [PATCH 07/12] fix deprecation warning for QApplication::setActiveWindow(QWidget*) --- app/gui/qt/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index c18242c50a..b02ebd2fea 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -270,7 +270,8 @@ MainWindow::MainWindow(QApplication& app, QSplashScreen* splash) toggleOSCServer(1); - app.setActiveWindow(editorTabWidget->currentWidget()); + editorTabWidget->currentWidget()->activateWindow(); + showWelcomeScreen(); From dca34a43de6d1037ceac1660125a2b2db1dc9e92 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Jul 2023 22:44:28 +0200 Subject: [PATCH 08/12] Do not set default attributes --- app/gui/qt/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/gui/qt/main.cpp b/app/gui/qt/main.cpp index c5bf1db726..828a605240 100644 --- a/app/gui/qt/main.cpp +++ b/app/gui/qt/main.cpp @@ -57,8 +57,6 @@ int main(int argc, char *argv[]) #if defined(Q_OS_LINUX) //linux code goes here - QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #elif defined(Q_OS_WIN) // windows code goes here From f7d7951c6af91c458789a4e67ad9be48bb77374d Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 10 Jul 2023 11:01:05 +0200 Subject: [PATCH 09/12] Revert "Include QTQuick in the build" This reverts commit 4280ab4033bdc773b6f4a26038835fb993499b35. --- app/gui/qt/SonicPi.pro | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/gui/qt/SonicPi.pro b/app/gui/qt/SonicPi.pro index 6992dca478..2506e979f7 100644 --- a/app/gui/qt/SonicPi.pro +++ b/app/gui/qt/SonicPi.pro @@ -107,5 +107,3 @@ TRANSLATIONS = lang/sonic-pi_bg.ts \ RESOURCES += SonicPi.qrc \ help_files.qrc \ info_files.qrc - -QT += quick From 64d700c26b293ab712ff218329413ac9e4fa925b Mon Sep 17 00:00:00 2001 From: Sam Aaron Date: Mon, 10 Jul 2023 10:46:45 +0100 Subject: [PATCH 10/12] Spider - update active support to 7.0.6 Switches transient gems - thread_safe and atomic are replaced with concurrent_ruby. tzinfo is also pulled in. --- LICENSE.md | 6 +- app/server/ruby/bin/compile-extensions.rb | 10 +- .../vendor/activesupport-7.0.6/CHANGELOG.md | 515 ++++ .../MIT-LICENSE | 4 +- .../vendor/activesupport-7.0.6/README.rdoc | 40 + .../lib/active_support.rb | 53 +- .../lib/active_support/actionable_error.rb | 48 + .../lib/active_support/all.rb | 5 + .../lib/active_support/array_inquirer.rb | 48 + .../lib/active_support/backtrace_cleaner.rb | 52 +- .../lib/active_support/benchmarkable.rb | 12 +- .../lib/active_support/builder.rb | 4 +- .../lib/active_support/cache.rb | 1030 ++++++++ .../lib/active_support/cache/file_store.rb | 127 +- .../active_support/cache/mem_cache_store.rb | 324 +++ .../lib/active_support/cache/memory_store.rb | 149 +- .../lib/active_support/cache/null_store.rb | 26 +- .../active_support/cache/redis_cache_store.rb | 474 ++++ .../cache/strategy/local_cache.rb | 186 ++ .../cache/strategy/local_cache_middleware.rb | 20 +- .../lib/active_support/callbacks.rb | 961 ++++++++ .../lib/active_support/code_generator.rb | 65 + .../lib/active_support/concern.rb | 95 +- .../load_interlock_aware_monitor.rb | 33 + .../active_support/concurrency/share_lock.rb | 226 ++ .../lib/active_support/configurable.rb | 40 +- .../lib/active_support/configuration_file.rb | 51 + .../lib/active_support/core_ext.rb | 6 + .../lib/active_support/core_ext/array.rb | 10 + .../active_support/core_ext/array/access.rb | 100 + .../core_ext/array/conversions.rb | 61 +- .../core_ext/array/deprecated_conversions.rb | 25 + .../active_support/core_ext/array/extract.rb | 21 + .../core_ext/array/extract_options.rb | 2 + .../active_support/core_ext/array/grouping.rb | 44 +- .../active_support/core_ext/array/inquiry.rb | 19 + .../lib/active_support/core_ext/array/wrap.rb | 11 +- .../lib/active_support/core_ext/benchmark.rb | 8 +- .../active_support/core_ext/big_decimal.rb | 3 + .../core_ext/big_decimal/conversions.rb | 14 + .../lib/active_support/core_ext/class.rb | 4 + .../core_ext/class/attribute.rb | 100 +- .../core_ext/class/attribute_accessors.rb | 4 +- .../core_ext/class/subclasses.rb | 41 + .../lib/active_support/core_ext/date.rb | 8 + .../active_support/core_ext/date/acts_like.rb | 4 +- .../lib/active_support/core_ext/date/blank.rb | 14 + .../core_ext/date/calculations.rb | 62 +- .../core_ext/date/conversions.rb | 97 + .../core_ext/date/deprecated_conversions.rb | 26 + .../lib/active_support/core_ext/date/zones.rb | 8 + .../core_ext/date_and_time/calculations.rb | 364 +++ .../core_ext/date_and_time/compatibility.rb | 31 + .../core_ext/date_and_time/zones.rb | 25 +- .../lib/active_support/core_ext/date_time.rb | 8 + .../core_ext/date_time/acts_like.rb | 6 +- .../core_ext/date_time/blank.rb | 14 + .../core_ext/date_time/calculations.rb | 92 +- .../core_ext/date_time/compatibility.rb | 18 + .../core_ext/date_time/conversions.rb | 59 +- .../date_time/deprecated_conversions.rb | 22 + .../lib/active_support/core_ext/digest.rb | 3 + .../active_support/core_ext/digest/uuid.rb | 79 + .../lib/active_support/core_ext/enumerable.rb | 329 +++ .../lib/active_support/core_ext/file.rb | 3 + .../active_support/core_ext/file/atomic.rb | 72 + .../lib/active_support/core_ext/hash.rb | 10 + .../core_ext/hash/conversions.rb | 107 +- .../core_ext/hash/deep_merge.rb | 34 + .../core_ext/hash/deep_transform_values.rb | 46 + .../active_support/core_ext/hash/except.rb | 24 + .../core_ext/hash/indifferent_access.rb | 15 +- .../lib/active_support/core_ext/hash/keys.rb | 123 +- .../core_ext/hash/reverse_merge.rb | 7 +- .../lib/active_support/core_ext/hash/slice.rb | 27 + .../lib/active_support/core_ext/integer.rb | 5 + .../core_ext/integer/inflections.rb | 31 + .../core_ext/integer/multiple.rb | 4 +- .../active_support/core_ext/integer/time.rb | 22 + .../lib/active_support/core_ext/kernel.rb | 5 + .../active_support/core_ext/kernel/concern.rb | 6 +- .../core_ext/kernel/reporting.rb | 45 + .../core_ext/kernel/singleton_class.rb | 4 +- .../lib/active_support/core_ext/load_error.rb | 9 + .../lib/active_support/core_ext/module.rb | 13 + .../core_ext/module/aliasing.rb | 31 + .../core_ext/module/anonymous.rb | 13 +- .../core_ext/module/attr_internal.rb | 17 +- .../core_ext/module/attribute_accessors.rb | 208 ++ .../module/attribute_accessors_per_thread.rb | 157 ++ .../core_ext/module/concerning.rb | 33 +- .../core_ext/module/delegation.rb | 324 +++ .../core_ext/module/deprecation.rb | 6 +- .../core_ext/module/introspection.rb | 63 + .../core_ext/module/redefine_method.rb | 40 + .../core_ext/module/remove_method.rb | 17 + .../lib/active_support/core_ext/name_error.rb | 59 + .../lib/active_support/core_ext/numeric.rb | 6 + .../active_support/core_ext/numeric/bytes.rb | 22 + .../core_ext/numeric/conversions.rb | 143 ++ .../numeric/deprecated_conversions.rb | 60 + .../active_support/core_ext/numeric/time.rb | 66 + .../lib/active_support/core_ext/object.rb | 16 + .../core_ext/object/acts_like.rb | 45 + .../active_support/core_ext/object/blank.rb | 34 +- .../core_ext/object/conversions.rb | 6 + .../core_ext/object/deep_dup.rb | 17 +- .../core_ext/object/duplicable.rb | 60 + .../core_ext/object/inclusion.rb | 8 +- .../core_ext/object/instance_variables.rb | 4 +- .../active_support/core_ext/object/json.rb | 137 +- .../core_ext/object/to_param.rb | 3 + .../core_ext/object/to_query.rb | 87 + .../lib/active_support/core_ext/object/try.rb | 158 ++ .../core_ext/object/with_options.rb | 101 + .../lib/active_support/core_ext/pathname.rb | 3 + .../core_ext/pathname/existence.rb | 21 + .../lib/active_support/core_ext/range.rb | 7 + .../core_ext/range/compare_range.rb | 57 + .../core_ext/range/conversions.rb | 41 + .../core_ext/range/deprecated_conversions.rb | 26 + .../lib/active_support/core_ext/range/each.rb | 24 + .../core_ext/range/include_time_with_zone.rb | 5 + .../active_support/core_ext/range/overlaps.rb | 4 +- .../lib/active_support/core_ext/regexp.rb | 14 + .../active_support/core_ext/securerandom.rb | 45 + .../lib/active_support/core_ext/string.rb | 15 + .../active_support/core_ext/string/access.rb | 33 +- .../core_ext/string/behavior.rb | 8 + .../core_ext/string/conversions.rb | 18 +- .../active_support/core_ext/string/exclude.rb | 2 + .../active_support/core_ext/string/filters.rb | 145 ++ .../active_support/core_ext/string/indent.rb | 10 +- .../core_ext/string/inflections.rb | 123 +- .../active_support/core_ext/string/inquiry.rb | 7 +- .../core_ext/string/multibyte.rb | 27 +- .../core_ext/string/output_safety.rb | 371 +++ .../core_ext/string/starts_ends_with.rb | 6 + .../active_support/core_ext/string/strip.rb | 11 +- .../active_support/core_ext/string/zones.rb | 6 +- .../lib/active_support/core_ext/symbol.rb | 3 + .../core_ext/symbol/starts_ends_with.rb | 6 + .../lib/active_support/core_ext/time.rb | 8 + .../active_support/core_ext/time/acts_like.rb | 4 +- .../core_ext/time/calculations.rb | 229 +- .../core_ext/time/compatibility.rb | 16 + .../core_ext/time/conversions.rb | 75 + .../core_ext/time/deprecated_conversions.rb | 22 + .../lib/active_support/core_ext/time/zones.rb | 97 + .../lib/active_support/core_ext/uri.rb | 5 + .../lib/active_support/current_attributes.rb | 226 ++ .../current_attributes/test_helper.rb | 13 + .../lib/active_support/dependencies.rb | 98 + .../active_support/dependencies/autoload.rb | 4 +- .../active_support/dependencies/interlock.rb | 49 + .../dependencies/require_dependency.rb | 28 + .../lib/active_support/deprecation.rb | 28 +- .../active_support/deprecation/behaviors.rb | 125 + .../deprecation/constant_accessor.rb | 52 + .../active_support/deprecation/disallowed.rb | 56 + .../deprecation/instance_delegator.rb | 18 +- .../deprecation/method_wrappers.rb | 85 + .../deprecation/proxy_wrappers.rb | 177 ++ .../active_support/deprecation/reporting.rb | 157 ++ .../lib/active_support/descendants_tracker.rb | 218 ++ .../lib/active_support/digest.rb | 22 + .../lib/active_support/duration.rb | 514 ++++ .../active_support/duration/iso8601_parser.rb | 123 + .../duration/iso8601_serializer.rb | 67 + .../active_support/encrypted_configuration.rb | 87 + .../lib/active_support/encrypted_file.rb | 129 + .../active_support/environment_inquirer.rb | 20 + .../lib/active_support/error_reporter.rb | 117 + .../evented_file_update_checker.rb | 181 ++ .../lib/active_support/execution_context.rb | 53 + .../execution_context/test_helper.rb | 13 + .../lib/active_support/execution_wrapper.rb | 151 ++ .../lib/active_support/executor.rb | 8 + .../active_support/executor/test_helper.rb | 7 + .../lib/active_support/file_update_checker.rb | 99 +- .../lib/active_support/fork_tracker.rb | 71 + .../lib/active_support/gem_version.rb | 17 + .../lib/active_support/gzip.rb | 12 +- .../hash_with_indifferent_access.rb | 425 ++++ .../active_support/html_safe_translation.rb | 43 + .../lib/active_support/i18n.rb | 17 + .../lib/active_support/i18n_railtie.rb | 67 +- .../lib/active_support/inflections.rb | 24 +- .../lib/active_support/inflector.rb | 9 + .../active_support/inflector/inflections.rb | 100 +- .../lib/active_support/inflector/methods.rb | 375 +++ .../active_support/inflector/transliterate.rb | 147 ++ .../isolated_execution_state.rb | 72 + .../lib/active_support/json.rb | 4 + .../lib/active_support/json/decoding.rb | 64 +- .../lib/active_support/json/encoding.rb | 94 +- .../lib/active_support/key_generator.rb | 58 + .../lib/active_support/lazy_load_hooks.rb | 105 + .../lib/active_support/locale/en.rb | 33 + .../lib/active_support/locale/en.yml | 14 +- .../lib/active_support/log_subscriber.rb | 77 +- .../log_subscriber/test_helper.rb | 30 +- .../lib/active_support/logger.rb | 93 + .../lib/active_support/logger_silence.rb | 21 + .../logger_thread_safe_level.rb | 69 + .../lib/active_support/message_encryptor.rb | 230 ++ .../lib/active_support/message_verifier.rb | 237 ++ .../lib/active_support/messages/metadata.rb | 80 + .../messages/rotation_configuration.rb | 23 + .../lib/active_support/messages/rotator.rb | 57 + .../lib/active_support/multibyte.rb | 8 +- .../lib/active_support/multibyte/chars.rb | 134 +- .../lib/active_support/multibyte/unicode.rb | 70 + .../lib/active_support/notifications.rb | 149 +- .../active_support/notifications/fanout.rb | 285 +++ .../notifications/instrumenter.rb | 172 ++ .../lib/active_support/number_helper.rb | 220 +- .../number_helper/number_converter.rb | 37 +- .../number_to_currency_converter.rb | 46 + .../number_to_delimited_converter.rb | 30 + .../number_to_human_converter.rb | 25 +- .../number_to_human_size_converter.rb | 24 +- .../number_to_percentage_converter.rb | 6 +- .../number_to_phone_converter.rb | 22 +- .../number_to_rounded_converter.rb | 59 + .../number_helper/rounding_helper.rb | 46 + .../lib/active_support/option_merger.rb | 38 + .../lib/active_support/ordered_hash.rb | 14 +- .../lib/active_support/ordered_options.rb | 44 +- .../lib/active_support/parameter_filter.rb | 142 ++ .../lib/active_support/per_thread_registry.rb | 24 +- .../lib/active_support/proxy_object.rb | 2 + .../lib/active_support/rails.rb | 26 + .../lib/active_support/railtie.rb | 152 ++ .../lib/active_support/reloader.rb | 130 + .../lib/active_support/rescuable.rb | 174 ++ .../lib/active_support/ruby_features.rb | 7 + .../active_support/secure_compare_rotator.rb | 51 + .../lib/active_support/security_utils.rb | 38 + .../lib/active_support/string_inquirer.rb | 15 +- .../lib/active_support/subscriber.rb | 163 ++ .../lib/active_support/tagged_logging.rb | 113 + .../lib/active_support/test_case.rb | 155 ++ .../lib/active_support/testing/assertions.rb | 265 ++ .../lib/active_support/testing/autorun.rb | 7 + .../active_support/testing/constant_lookup.rb | 9 +- .../lib/active_support/testing/declarative.rb | 28 +- .../lib/active_support/testing/deprecation.rb | 89 + .../active_support/testing/file_fixtures.rb | 38 + .../lib/active_support/testing/isolation.rb | 111 + .../testing/method_call_assertions.rb | 70 + .../active_support/testing/parallelization.rb | 55 + .../testing/parallelization/server.rb | 82 + .../testing/parallelization/worker.rb | 103 + .../testing/parallelize_executor.rb | 76 + .../testing/setup_and_teardown.rb | 21 +- .../lib/active_support/testing/stream.rb | 41 + .../active_support/testing/tagged_logging.rb | 8 +- .../active_support/testing/time_helpers.rb | 246 ++ .../lib/active_support/time.rb | 20 + .../lib/active_support/time_with_zone.rb | 625 +++++ .../lib/active_support/values/time_zone.rb | 416 +++- .../lib/active_support/version.rb | 10 + .../lib/active_support/xml_mini.rb | 123 +- .../lib/active_support/xml_mini/jdom.rb | 182 ++ .../lib/active_support/xml_mini/libxml.rb | 43 +- .../lib/active_support/xml_mini/libxmlsax.rb | 36 +- .../lib/active_support/xml_mini/nokogiri.rb | 38 +- .../active_support/xml_mini/nokogirisax.rb | 33 +- .../lib/active_support/xml_mini/rexml.rb | 42 +- .../ruby/vendor/activesupport/CHANGELOG.md | 56 - .../ruby/vendor/activesupport/README.rdoc | 35 - app/server/ruby/vendor/activesupport/Rakefile | 32 - .../activesupport/activesupport.gemspec | 28 - .../vendor/activesupport/bin/generate_tables | 144 -- .../activesupport/lib/active_support/all.rb | 3 - .../activesupport/lib/active_support/cache.rb | 718 ------ .../active_support/cache/mem_cache_store.rb | 198 -- .../cache/strategy/local_cache.rb | 160 -- .../lib/active_support/callbacks.rb | 745 ------ .../lib/active_support/concurrency/latch.rb | 27 - .../lib/active_support/core_ext.rb | 3 - .../lib/active_support/core_ext/array.rb | 6 - .../active_support/core_ext/array/access.rb | 56 - .../core_ext/array/prepend_and_append.rb | 7 - .../active_support/core_ext/big_decimal.rb | 1 - .../core_ext/big_decimal/conversions.rb | 16 - .../core_ext/big_decimal/yaml_conversions.rb | 14 - .../lib/active_support/core_ext/class.rb | 3 - .../core_ext/class/delegating_attributes.rb | 45 - .../core_ext/class/subclasses.rb | 42 - .../lib/active_support/core_ext/date.rb | 5 - .../core_ext/date/conversions.rb | 88 - .../lib/active_support/core_ext/date/zones.rb | 6 - .../core_ext/date_and_time/calculations.rb | 251 -- .../lib/active_support/core_ext/date_time.rb | 4 - .../core_ext/date_time/zones.rb | 6 - .../lib/active_support/core_ext/enumerable.rb | 80 - .../lib/active_support/core_ext/file.rb | 1 - .../active_support/core_ext/file/atomic.rb | 63 - .../lib/active_support/core_ext/hash.rb | 8 - .../active_support/core_ext/hash/compact.rb | 20 - .../core_ext/hash/deep_merge.rb | 27 - .../active_support/core_ext/hash/except.rb | 15 - .../lib/active_support/core_ext/hash/slice.rb | 42 - .../lib/active_support/core_ext/integer.rb | 3 - .../core_ext/integer/inflections.rb | 29 - .../active_support/core_ext/integer/time.rb | 44 - .../lib/active_support/core_ext/kernel.rb | 5 - .../core_ext/kernel/agnostics.rb | 11 - .../core_ext/kernel/debugger.rb | 10 - .../core_ext/kernel/reporting.rb | 114 - .../lib/active_support/core_ext/load_error.rb | 25 - .../lib/active_support/core_ext/marshal.rb | 21 - .../lib/active_support/core_ext/module.rb | 11 - .../core_ext/module/aliasing.rb | 69 - .../core_ext/module/attribute_accessors.rb | 212 -- .../core_ext/module/delegation.rb | 209 -- .../core_ext/module/introspection.rb | 62 - .../core_ext/module/method_transplanting.rb | 11 - .../core_ext/module/qualified_const.rb | 52 - .../core_ext/module/reachable.rb | 8 - .../core_ext/module/remove_method.rb | 12 - .../lib/active_support/core_ext/name_error.rb | 18 - .../lib/active_support/core_ext/numeric.rb | 3 - .../core_ext/numeric/conversions.rb | 135 - .../active_support/core_ext/numeric/time.rb | 87 - .../lib/active_support/core_ext/object.rb | 14 - .../core_ext/object/acts_like.rb | 10 - .../core_ext/object/conversions.rb | 4 - .../core_ext/object/duplicable.rb | 90 - .../active_support/core_ext/object/to_json.rb | 5 - .../core_ext/object/to_param.rb | 62 - .../core_ext/object/to_query.rb | 32 - .../lib/active_support/core_ext/object/try.rb | 78 - .../core_ext/object/with_options.rb | 42 - .../lib/active_support/core_ext/range.rb | 4 - .../core_ext/range/conversions.rb | 19 - .../lib/active_support/core_ext/range/each.rb | 23 - .../core_ext/range/include_range.rb | 23 - .../lib/active_support/core_ext/regexp.rb | 5 - .../lib/active_support/core_ext/string.rb | 13 - .../core_ext/string/behavior.rb | 6 - .../active_support/core_ext/string/filters.rb | 65 - .../core_ext/string/output_safety.rb | 243 -- .../core_ext/string/starts_ends_with.rb | 4 - .../lib/active_support/core_ext/struct.rb | 6 - .../lib/active_support/core_ext/thread.rb | 79 - .../lib/active_support/core_ext/time.rb | 5 - .../core_ext/time/conversions.rb | 65 - .../active_support/core_ext/time/marshal.rb | 30 - .../lib/active_support/core_ext/time/zones.rb | 78 - .../lib/active_support/core_ext/uri.rb | 26 - .../lib/active_support/dependencies.rb | 748 ------ .../active_support/deprecation/behaviors.rb | 76 - .../deprecation/method_wrappers.rb | 44 - .../deprecation/proxy_wrappers.rb | 126 - .../active_support/deprecation/reporting.rb | 94 - .../lib/active_support/descendants_tracker.rb | 60 - .../lib/active_support/duration.rb | 114 - .../lib/active_support/file_watcher.rb | 36 - .../lib/active_support/gem_version.rb | 15 - .../hash_with_indifferent_access.rb | 272 --- .../activesupport/lib/active_support/i18n.rb | 13 - .../lib/active_support/inflector.rb | 7 - .../lib/active_support/inflector/methods.rb | 360 --- .../active_support/inflector/transliterate.rb | 97 - .../activesupport/lib/active_support/json.rb | 2 - .../lib/active_support/key_generator.rb | 73 - .../lib/active_support/lazy_load_hooks.rb | 48 - .../lib/active_support/logger.rb | 57 - .../lib/active_support/logger_silence.rb | 24 - .../lib/active_support/message_encryptor.rb | 106 - .../lib/active_support/message_verifier.rb | 73 - .../lib/active_support/multibyte/unicode.rb | 390 --- .../active_support/notifications/fanout.rb | 152 -- .../notifications/instrumenter.rb | 73 - .../number_to_currency_converter.rb | 46 - .../number_to_delimited_converter.rb | 21 - .../number_to_rounded_converter.rb | 91 - .../lib/active_support/option_merger.rb | 25 - .../activesupport/lib/active_support/rails.rb | 27 - .../lib/active_support/railtie.rb | 46 - .../lib/active_support/rescuable.rb | 119 - .../lib/active_support/subscriber.rb | 116 - .../lib/active_support/tagged_logging.rb | 76 - .../lib/active_support/test_case.rb | 66 - .../lib/active_support/testing/assertions.rb | 97 - .../lib/active_support/testing/autorun.rb | 5 - .../lib/active_support/testing/deprecation.rb | 35 - .../lib/active_support/testing/isolation.rb | 91 - .../active_support/testing/time_helpers.rb | 127 - .../activesupport/lib/active_support/time.rb | 20 - .../lib/active_support/time_with_zone.rb | 402 --- .../active_support/values/unicode_tables.dat | Bin 904640 -> 0 bytes .../lib/active_support/version.rb | 8 - .../lib/active_support/xml_mini/jdom.rb | 180 -- .../activesupport/test/abstract_unit.rb | 38 - .../activesupport/test/autoload_test.rb | 70 - .../test/autoloading_fixtures/a/b.rb | 2 - .../test/autoloading_fixtures/a/c/d.rb | 2 - .../test/autoloading_fixtures/a/c/e/f.rb | 2 - .../test/autoloading_fixtures/application.rb | 1 - .../test/autoloading_fixtures/circular1.rb | 6 - .../test/autoloading_fixtures/circular2.rb | 4 - .../test/autoloading_fixtures/class_folder.rb | 3 - .../class_folder/class_folder_subclass.rb | 3 - .../class_folder/inline_class.rb | 2 - .../class_folder/nested_class.rb | 7 - .../test/autoloading_fixtures/conflict.rb | 1 - .../autoloading_fixtures/counting_loader.rb | 5 - .../cross_site_dependency.rb | 2 - .../test/autoloading_fixtures/e.rb | 2 - .../autoloading_fixtures/html/some_class.rb | 4 - .../load_path/loaded_constant.rb | 3 - .../autoloading_fixtures/loads_constant.rb | 5 - .../module_folder/inline_class.rb | 2 - .../module_folder/nested_class.rb | 4 - .../module_folder/nested_sibling.rb | 2 - .../module_with_custom_const_missing/a/b.rb | 1 - .../multiple_constant_file.rb | 2 - .../autoloading_fixtures/raises_name_error.rb | 3 - .../raises_no_method_error.rb | 3 - .../autoloading_fixtures/requires_constant.rb | 5 - .../should_not_be_required.rb | 1 - .../activesupport/test/benchmarkable_test.rb | 62 - .../test/broadcast_logger_test.rb | 82 - .../vendor/activesupport/test/caching_test.rb | 1072 -------- .../test/callback_inheritance_test.rb | 176 -- .../activesupport/test/callbacks_test.rb | 1003 -------- .../activesupport/test/class_cache_test.rb | 78 - .../test/clean_backtrace_test.rb | 70 - .../activesupport/test/clean_logger_test.rb | 29 - .../vendor/activesupport/test/concern_test.rb | 104 - .../activesupport/test/configurable_test.rb | 123 - .../test/constantize_test_cases.rb | 75 - .../test/core_ext/array_ext_test.rb | 451 ---- .../big_decimal/yaml_conversions_test.rb | 11 - .../test/core_ext/bigdecimal_test.rb | 9 - .../activesupport/test/core_ext/blank_test.rb | 36 - .../test/core_ext/class/attribute_test.rb | 91 - .../class/delegating_attributes_test.rb | 122 - .../activesupport/test/core_ext/class_test.rb | 28 - .../test/core_ext/date_and_time_behavior.rb | 241 -- .../test/core_ext/date_ext_test.rb | 387 --- .../test/core_ext/date_time_ext_test.rb | 357 --- .../test/core_ext/deep_dup_test.rb | 53 - .../test/core_ext/duplicable_test.rb | 38 - .../test/core_ext/duration_test.rb | 171 -- .../test/core_ext/enumerable_test.rb | 106 - .../activesupport/test/core_ext/file_test.rb | 68 - .../test/core_ext/hash_ext_test.rb | 1592 ------------ .../test/core_ext/integer_ext_test.rb | 30 - .../test/core_ext/kernel/concern_test.rb | 12 - .../test/core_ext/kernel_test.rb | 140 -- .../test/core_ext/load_error_test.rb | 32 - .../test/core_ext/marshal_test.rb | 124 - .../test/core_ext/module/anonymous_test.rb | 14 - .../core_ext/module/attr_internal_test.rb | 53 - .../module/attribute_accessor_test.rb | 79 - .../module/attribute_aliasing_test.rb | 59 - .../test/core_ext/module/concerning_test.rb | 65 - .../core_ext/module/qualified_const_test.rb | 108 - .../test/core_ext/module/reachable_test.rb | 41 - .../core_ext/module/remove_method_test.rb | 29 - .../test/core_ext/module_test.rb | 498 ---- .../test/core_ext/name_error_test.rb | 21 - .../test/core_ext/numeric_ext_test.rb | 442 ---- .../test/core_ext/object/inclusion_test.rb | 55 - .../test/core_ext/object/json_test.rb | 9 - .../test/core_ext/object/to_param_test.rb | 19 - .../test/core_ext/object/to_query_test.rb | 66 - .../core_ext/object_and_class_ext_test.rb | 156 -- .../test/core_ext/range_ext_test.rb | 119 - .../test/core_ext/regexp_ext_test.rb | 10 - .../test/core_ext/string_ext_test.rb | 806 ------ .../test/core_ext/struct_test.rb | 10 - .../test/core_ext/thread_test.rb | 75 - .../test/core_ext/time_ext_test.rb | 900 ------- .../test/core_ext/time_with_zone_test.rb | 1138 --------- .../test/core_ext/uri_ext_test.rb | 13 - .../test/dependencies/check_warnings.rb | 2 - .../test/dependencies/conflict.rb | 1 - .../test/dependencies/cross_site_depender.rb | 3 - .../test/dependencies/mutual_one.rb | 4 - .../test/dependencies/mutual_two.rb | 4 - .../test/dependencies/raises_exception.rb | 3 - .../raises_exception_without_blame_file.rb | 5 - .../dependencies/requires_nonexistent0.rb | 1 - .../dependencies/requires_nonexistent1.rb | 1 - .../test/dependencies/service_one.rb | 5 - .../test/dependencies/service_two.rb | 2 - .../activesupport/test/dependencies_test.rb | 997 -------- .../test/dependencies_test_helpers.rb | 27 - .../test/deprecation/proxy_wrappers_test.rb | 22 - .../activesupport/test/deprecation_test.rb | 358 --- .../test/descendants_tracker_test_cases.rb | 65 - ...scendants_tracker_with_autoloading_test.rb | 34 - ...ndants_tracker_without_autoloading_test.rb | 17 - .../test/file_update_checker_test.rb | 112 - .../test/fixtures/autoload/another_class.rb | 2 - .../test/fixtures/autoload/some_class.rb | 2 - .../test/fixtures/xml/jdom_doctype.dtd | 1 - .../test/fixtures/xml/jdom_entities.txt | 1 - .../test/fixtures/xml/jdom_include.txt | 1 - .../vendor/activesupport/test/gzip_test.rb | 33 - .../vendor/activesupport/test/i18n_test.rb | 105 - .../activesupport/test/inflector_test.rb | 523 ---- .../test/inflector_test_cases.rb | 321 --- .../activesupport/test/json/decoding_test.rb | 105 - .../activesupport/test/json/encoding_test.rb | 546 ----- .../activesupport/test/key_generator_test.rb | 32 - .../test/lazy_load_hooks_test.rb | 96 - .../activesupport/test/load_paths_test.rb | 16 - .../activesupport/test/log_subscriber_test.rb | 123 - .../vendor/activesupport/test/logger_test.rb | 133 - .../test/message_encryptor_test.rb | 101 - .../test/message_verifier_test.rb | 79 - .../test/multibyte_chars_test.rb | 713 ------ .../test/multibyte_conformance.rb | 129 - .../test/multibyte_test_helpers.rb | 19 - .../test/multibyte_unicode_database_test.rb | 26 - .../evented_notification_test.rb | 87 - .../test/notifications/instrumenter_test.rb | 58 - .../activesupport/test/notifications_test.rb | 266 -- .../test/number_helper_i18n_test.rb | 156 -- .../activesupport/test/number_helper_test.rb | 391 --- .../activesupport/test/option_merger_test.rb | 86 - .../activesupport/test/ordered_hash_test.rb | 322 --- .../test/ordered_options_test.rb | 88 - .../activesupport/test/rescuable_test.rb | 105 - .../activesupport/test/safe_buffer_test.rb | 168 -- .../test/string_inquirer_test.rb | 23 - .../activesupport/test/subscriber_test.rb | 40 - .../activesupport/test/tagged_logging_test.rb | 102 - .../vendor/activesupport/test/test_test.rb | 220 -- .../test/testing/constant_lookup_test.rb | 68 - .../activesupport/test/time_zone_test.rb | 418 ---- .../activesupport/test/transliterate_test.rb | 35 - .../test/xml_mini/jdom_engine_test.rb | 188 -- .../test/xml_mini/libxml_engine_test.rb | 204 -- .../test/xml_mini/libxmlsax_engine_test.rb | 195 -- .../test/xml_mini/nokogiri_engine_test.rb | 217 -- .../test/xml_mini/nokogirisax_engine_test.rb | 218 -- .../test/xml_mini/rexml_engine_test.rb | 37 - .../activesupport/test/xml_mini_test.rb | 296 --- app/server/ruby/vendor/atomic/.gitignore | 8 - app/server/ruby/vendor/atomic/.travis.yml | 10 - app/server/ruby/vendor/atomic/Gemfile | 4 - app/server/ruby/vendor/atomic/LICENSE | 144 -- app/server/ruby/vendor/atomic/README.md | 53 - app/server/ruby/vendor/atomic/Rakefile | 63 - app/server/ruby/vendor/atomic/atomic.gemspec | 24 - .../vendor/atomic/examples/atomic_example.rb | 24 - .../vendor/atomic/examples/bench_atomic.rb | 121 - .../atomic/ext/AtomicReferenceService.java | 24 - app/server/ruby/vendor/atomic/ext/extconf.rb | 47 - app/server/ruby/vendor/atomic/lib/atomic.rb | 26 - .../lib/atomic/concurrent_update_error.rb | 18 - .../atomic/lib/atomic/delegated_update.rb | 37 - .../vendor/atomic/lib/atomic/direct_update.rb | 37 - .../ruby/vendor/atomic/lib/atomic/fallback.rb | 54 - .../ruby/vendor/atomic/lib/atomic/jruby.rb | 14 - .../atomic/lib/atomic/numeric_cas_wrapper.rb | 32 - .../ruby/vendor/atomic/lib/atomic/rbx.rb | 21 - .../ruby/vendor/atomic/lib/atomic/ruby.rb | 34 - .../ruby/vendor/atomic/test/test_atomic.rb | 139 -- .../.github/CONTRIBUTING.md | 134 + .../.github/ISSUE_TEMPLATE.md | 11 + .../.github/workflows/ci.yml | 49 + .../.github/workflows/docs.yml | 48 + .../.github/workflows/experimental.yml | 32 + .../vendor/concurrent-ruby-1.2.2/.gitignore | 33 + .../ruby/vendor/concurrent-ruby-1.2.2/.rspec | 5 + .../vendor/concurrent-ruby-1.2.2/.yardopts | 5 + .../vendor/concurrent-ruby-1.2.2/CHANGELOG.md | 561 +++++ .../concurrent-ruby-1.2.2/CODE_OF_CONDUCT.md | 71 + .../ruby/vendor/concurrent-ruby-1.2.2/Gemfile | 36 + .../vendor/concurrent-ruby-1.2.2/LICENSE.txt | 21 + .../vendor/concurrent-ruby-1.2.2/README.md | 405 +++ .../vendor/concurrent-ruby-1.2.2/Rakefile | 319 +++ .../concurrent-ruby-edge.gemspec | 29 + .../concurrent-ruby-ext.gemspec | 27 + .../concurrent-ruby.gemspec | 30 + .../docs-source/actor/celluloid_benchmark.rb | 105 + .../docs-source/actor/define.in.rb | 36 + .../docs-source/actor/define.out.rb | 37 + .../docs-source/actor/examples.in.rb | 3 + .../docs-source/actor/examples.out.rb | 3 + .../docs-source/actor/format.rb | 76 + .../docs-source/actor/init.rb | 2 + .../docs-source/actor/io.in.rb | 49 + .../docs-source/actor/io.out.rb | 53 + .../docs-source/actor/main.md | 228 ++ .../docs-source/actor/messaging.in.rb | 32 + .../docs-source/actor/messaging.out.rb | 36 + .../docs-source/actor/quick.in.rb | 30 + .../docs-source/actor/quick.out.rb | 34 + .../docs-source/actor/supervision_tree.in.rb | 68 + .../docs-source/actor/supervision_tree.out.rb | 73 + .../docs-source/cancellation.in.md | 158 ++ .../docs-source/cancellation.init.rb | 6 + .../docs-source/cancellation.out.md | 192 ++ .../docs-source/channel.in.md | 225 ++ .../docs-source/channel.init.rb | 6 + .../docs-source/channel.md | 283 +++ .../docs-source/channel.out.md | 313 +++ .../docs-source/dataflow.md | 183 ++ .../docs-source/dataflow_top_stock_calc.md | 109 + .../docs-source/erlang_actor.in.md | 278 +++ .../docs-source/erlang_actor.init.rb | 9 + .../docs-source/erlang_actor.out.md | 300 +++ .../docs-source/future.md | 113 + .../docs-source/future.rb | 37 + .../images/tvar/implementation-absolute.png | Bin 0 -> 109909 bytes .../tvar/implementation-scalability.png | Bin 0 -> 118048 bytes ...mentation-write-proportion-scalability.png | Bin 0 -> 91671 bytes .../docs-source/images/tvar/ruby-absolute.png | Bin 0 -> 102338 bytes .../images/tvar/ruby-scalability.png | Bin 0 -> 96499 bytes .../logo/concurrent-ruby-logo-200x200.png | Bin 0 -> 26508 bytes .../logo/concurrent-ruby-logo-300x300.png | Bin 0 -> 46014 bytes .../logo/concurrent-ruby-logo-400x400.png | Bin 0 -> 70498 bytes .../logo/concurrent-ruby-logo-930x930.png | Bin 0 -> 121734 bytes .../docs-source/medium-example.in.rb | 273 +++ .../docs-source/medium-example.init.rb | 13 + .../docs-source/medium-example.out.rb | 881 +++++++ .../docs-source/promises-main.md | 60 + .../docs-source/promises.in.md | 974 ++++++++ .../docs-source/promises.init.rb | 6 + .../docs-source/promises.out.md | 1140 +++++++++ .../ruby-association-final-report.md | 314 +++ .../ruby-association-intermediate-report.md | 99 + .../docs-source/signpost.md | 12 + .../docs-source/synchronization-notes.md | 64 + .../docs-source/synchronization.md | 5 + .../docs-source/thread_pools.md | 179 ++ .../docs-source/throttle.in.md | 141 ++ .../docs-source/throttle.init.rb | 6 + .../docs-source/throttle.out.md | 145 ++ .../docs-source/top-stock-scala/.gitignore} | 0 .../docs-source/top-stock-scala/README.md | 115 + .../docs-source/top-stock-scala/top-stock.rb | 48 + .../concurrent-ruby-1.2.2/docs-source/tvar.md | 211 ++ .../buffered-channels.rb | 19 + .../a-tour-of-go-channels/channels.rb | 26 + .../default-selection.rb | 43 + .../equivalent-binary-trees.rb | 70 + .../a-tour-of-go-channels/range-and-close.rb | 33 + .../examples/a-tour-of-go-channels/select.rb | 44 + .../examples/actor_stress_test.rb | 140 ++ .../examples/atomic_example.rb | 16 + .../examples/benchmark_async.rb | 227 ++ .../examples/benchmark_atomic.rb | 135 + .../examples/benchmark_atomic_1.rb} | 22 +- .../examples/benchmark_atomic_boolean.rb | 41 + .../examples/benchmark_atomic_fixnum.rb | 41 + .../examples/benchmark_map.rb | 35 + .../examples/benchmark_new_futures.rb | 72 + .../examples/benchmark_read_write_lock.rb | 139 ++ .../examples/benchmark_structs.rb | 100 + .../concurrent-ruby-1.2.2/examples/format.rb | 75 + .../channel-buffering.rb | 20 + .../channel-directions.rb | 31 + .../channel-synchronization.rb | 25 + .../go-by-example-channels/channels.rb | 20 + .../closing-channels.rb | 45 + .../non-blocking-channel-operations.rb | 33 + .../range-over-channels.rb | 21 + .../go-by-example-channels/rate-limiting.rb | 61 + .../examples/go-by-example-channels/select.rb | 32 + .../examples/go-by-example-channels/ticker.rb | 25 + .../go-by-example-channels/timeouts.rb | 34 + .../examples/go-by-example-channels/timers.rb | 24 + .../go-by-example-channels/worker-pools.rb | 43 + .../examples/graph_atomic_bench.rb | 22 +- .../concurrent-ruby-1.2.2/examples/init.rb | 7 + .../examples/stress_ruby_thread_pool.rb | 26 + .../examples/thread_local_memory_usage.rb | 74 + .../examples/thread_local_var_bench.rb | 31 + .../concurrent-ruby-1.2.2/examples/who.rb | 28 + .../ext/concurrent-ruby-ext/atomic_boolean.c | 46 + .../ext/concurrent-ruby-ext/atomic_boolean.h | 16 + .../ext/concurrent-ruby-ext/atomic_fixnum.c | 72 + .../ext/concurrent-ruby-ext/atomic_fixnum.h | 14 + .../concurrent-ruby-ext/atomic_reference.c | 113 + .../concurrent-ruby-ext/atomic_reference.h | 20 + .../ext/concurrent-ruby-ext/extconf.rb | 27 + .../ext/concurrent-ruby-ext/rb_concurrent.c | 58 + .../ConcurrentRubyService.java | 17 + .../ext}/AtomicReferenceLibrary.java | 17 +- .../ext/JRubyMapBackendLibrary.java | 248 ++ .../ext/JavaAtomicBooleanLibrary.java | 93 + .../ext/JavaAtomicFixnumLibrary.java | 113 + .../ext/JavaSemaphoreLibrary.java | 189 ++ .../ext/SynchronizationLibrary.java | 292 +++ .../ext}/jsr166e/ConcurrentHashMap.java | 2 +- .../ext}/jsr166e/ConcurrentHashMapV8.java | 4 +- .../ext}/jsr166e/LongAdder.java | 2 +- .../ext}/jsr166e/Striped64.java | 2 +- .../jsr166e/nounsafe/ConcurrentHashMapV8.java | 8 +- .../ext}/jsr166e/nounsafe/LongAdder.java | 2 +- .../ext}/jsr166e/nounsafe/Striped64.java | 4 +- .../ext}/jsr166y/ThreadLocalRandom.java | 2 +- .../lib/concurrent-ruby.rb | 5 + .../concurrent-ruby-1.2.2/lib/concurrent.rb | 134 + .../lib/concurrent/.gitignore | 0 .../lib/concurrent/agent.rb | 588 +++++ .../lib/concurrent/array.rb | 56 + .../lib/concurrent/async.rb | 449 ++++ .../lib/concurrent/atom.rb | 222 ++ .../lib/concurrent/atomic/atomic_boolean.rb | 127 + .../lib/concurrent/atomic/atomic_fixnum.rb | 144 ++ .../atomic/atomic_markable_reference.rb | 167 ++ .../lib/concurrent/atomic/atomic_reference.rb | 135 + .../lib/concurrent/atomic/count_down_latch.rb | 100 + .../lib/concurrent/atomic/cyclic_barrier.rb | 128 + .../lib/concurrent/atomic/event.rb | 109 + .../lib/concurrent/atomic/fiber_local_var.rb | 109 + .../atomic/java_count_down_latch.rb | 43 + .../lib/concurrent/atomic/locals.rb | 189 ++ .../lib/concurrent/atomic/lock_local_var.rb | 28 + .../concurrent/atomic/mutex_atomic_boolean.rb | 68 + .../concurrent/atomic/mutex_atomic_fixnum.rb | 81 + .../atomic/mutex_count_down_latch.rb | 44 + .../lib/concurrent/atomic/mutex_semaphore.rb | 131 + .../lib/concurrent/atomic/read_write_lock.rb | 255 ++ .../atomic/reentrant_read_write_lock.rb | 379 +++ .../lib/concurrent/atomic/semaphore.rb | 163 ++ .../lib/concurrent/atomic/thread_local_var.rb | 111 + .../atomic_reference/atomic_direct_update.rb | 37 + .../atomic_reference/mutex_atomic.rb | 67 + .../atomic_reference/numeric_cas_wrapper.rb | 28 + .../lib/concurrent/atomics.rb | 10 + .../collection/copy_on_notify_observer_set.rb | 107 + .../collection/copy_on_write_observer_set.rb | 111 + .../java_non_concurrent_priority_queue.rb | 84 + .../concurrent/collection/lock_free_stack.rb | 160 ++ .../map/atomic_reference_map_backend.rb | 927 +++++++ .../collection/map/mri_map_backend.rb | 66 + .../map/non_concurrent_map_backend.rb | 148 ++ .../map/synchronized_map_backend.rb | 82 + .../collection/map/truffleruby_map_backend.rb | 14 + .../non_concurrent_priority_queue.rb | 143 ++ .../ruby_non_concurrent_priority_queue.rb | 160 ++ .../lib/concurrent/concern/deprecation.rb | 34 + .../lib/concurrent/concern/dereferenceable.rb | 73 + .../lib/concurrent/concern/logging.rb | 116 + .../lib/concurrent/concern/obligation.rb | 220 ++ .../lib/concurrent/concern/observable.rb | 110 + .../lib/concurrent/configuration.rb | 105 + .../lib/concurrent/constants.rb | 8 + .../lib/concurrent/dataflow.rb | 81 + .../lib/concurrent/delay.rb | 199 ++ .../lib/concurrent/errors.rb | 74 + .../lib/concurrent/exchanger.rb | 353 +++ .../executor/abstract_executor_service.rb | 131 + .../concurrent/executor/cached_thread_pool.rb | 62 + .../concurrent/executor/executor_service.rb | 185 ++ .../concurrent/executor/fixed_thread_pool.rb | 220 ++ .../concurrent/executor/immediate_executor.rb | 66 + .../executor/indirect_immediate_executor.rb | 44 + .../executor/java_executor_service.rb | 103 + .../executor/java_single_thread_executor.rb | 30 + .../executor/java_thread_pool_executor.rb | 140 ++ .../executor/ruby_executor_service.rb | 82 + .../executor/ruby_single_thread_executor.rb | 21 + .../executor/ruby_thread_pool_executor.rb | 366 +++ .../concurrent/executor/safe_task_executor.rb | 35 + .../executor/serial_executor_service.rb | 34 + .../executor/serialized_execution.rb | 107 + .../serialized_execution_delegator.rb | 28 + .../executor/simple_executor_service.rb | 103 + .../executor/single_thread_executor.rb | 57 + .../executor/thread_pool_executor.rb | 88 + .../lib/concurrent/executor/timer_set.rb | 172 ++ .../lib/concurrent/executors.rb | 20 + .../lib/concurrent/future.rb | 141 ++ .../lib/concurrent/hash.rb | 50 + .../lib/concurrent/immutable_struct.rb | 101 + .../lib/concurrent/ivar.rb | 208 ++ .../lib/concurrent/map.rb | 350 +++ .../lib/concurrent/maybe.rb | 229 ++ .../lib/concurrent/mutable_struct.rb | 239 ++ .../lib/concurrent/mvar.rb | 242 ++ .../lib/concurrent/options.rb | 42 + .../lib/concurrent/promise.rb | 580 +++++ .../lib/concurrent/promises.rb | 2168 +++++++++++++++++ .../lib/concurrent/re_include.rb | 60 + .../lib/concurrent/scheduled_task.rb | 331 +++ .../lib/concurrent/set.rb | 64 + .../lib/concurrent/settable_struct.rb | 139 ++ .../lib/concurrent/synchronization.rb | 13 + .../abstract_lockable_object.rb | 102 + .../synchronization/abstract_object.rb | 22 + .../synchronization/abstract_struct.rb | 171 ++ .../concurrent/synchronization/condition.rb | 62 + .../synchronization/full_memory_barrier.rb | 29 + .../synchronization/jruby_lockable_object.rb | 15 + .../lib/concurrent/synchronization/lock.rb | 38 + .../synchronization/lockable_object.rb | 75 + .../synchronization/mutex_lockable_object.rb | 89 + .../lib/concurrent/synchronization/object.rb | 151 ++ .../synchronization/safe_initialization.rb | 36 + .../concurrent/synchronization/volatile.rb | 101 + .../lib/concurrent/thread_safe/readme.txt | 1 + .../thread_safe/synchronized_delegator.rb | 47 + .../lib/concurrent/thread_safe/util.rb | 16 + .../lib/concurrent/thread_safe/util/adder.rb | 74 + .../thread_safe/util/cheap_lockable.rb | 81 + .../thread_safe/util/data_structures.rb | 52 + .../thread_safe/util/power_of_two_tuple.rb | 38 + .../concurrent/thread_safe/util/striped64.rb | 246 ++ .../concurrent/thread_safe/util/volatile.rb | 75 + .../thread_safe/util/xor_shift_random.rb | 50 + .../lib/concurrent/timer_task.rb | 311 +++ .../lib/concurrent/tuple.rb | 82 + .../lib/concurrent/tvar.rb | 222 ++ .../lib/concurrent/utility/engine.rb | 45 + .../lib/concurrent/utility/monotonic_time.rb | 19 + .../utility/native_extension_loader.rb | 77 + .../lib/concurrent/utility/native_integer.rb | 54 + .../concurrent/utility/processor_counter.rb | 110 + .../lib/concurrent/version.rb | 3 + .../concurrent-ruby-1.2.2/spec/.gitignore | 0 .../spec/concurrent/.gitignore | 0 .../spec/concurrent/actor_spec.rb | 341 +++ .../spec/concurrent/agent_spec.rb | 1217 +++++++++ .../spec/concurrent/array_spec.rb | 96 + .../spec/concurrent/async_spec.rb | 314 +++ .../spec/concurrent/atom_spec.rb | 210 ++ .../concurrent/atomic/atomic_boolean_spec.rb | 186 ++ .../concurrent/atomic/atomic_fixnum_spec.rb | 249 ++ .../atomic/atomic_markable_reference_spec.rb | 154 ++ .../atomic/atomic_reference_spec.rb | 207 ++ .../atomic/count_down_latch_spec.rb | 179 ++ .../concurrent/atomic/cyclic_barrier_spec.rb | 258 ++ .../spec/concurrent/atomic/event_spec.rb | 186 ++ .../concurrent/atomic/fiber_local_var_spec.rb | 123 + .../concurrent/atomic/lock_local_var_spec.rb | 20 + .../concurrent/atomic/read_write_lock_spec.rb | 496 ++++ .../atomic/reentrant_read_write_lock_spec.rb | 561 +++++ .../spec/concurrent/atomic/semaphore_spec.rb | 355 +++ .../atomic/thread_local_var_spec.rb | 130 + .../spec/concurrent/cancellation_spec.rb | 93 + .../concurrent/channel/buffer/base_shared.rb | 134 + .../concurrent/channel/buffer/base_spec.rb | 75 + .../channel/buffer/buffered_shared.rb | 176 ++ .../channel/buffer/buffered_spec.rb | 55 + .../channel/buffer/dropping_spec.rb | 49 + .../concurrent/channel/buffer/sliding_spec.rb | 49 + .../concurrent/channel/buffer/ticker_spec.rb | 61 + .../concurrent/channel/buffer/timer_spec.rb | 45 + .../channel/buffer/timing_buffer_shared.rb | 168 ++ .../channel/buffer/unbuffered_spec.rb | 252 ++ .../concurrent/channel/integration_spec.rb | 252 ++ .../spec/concurrent/channel/tick_spec.rb | 74 + .../spec/concurrent/channel_spec.rb | 665 +++++ .../copy_on_notify_observer_set_spec.rb | 10 + .../copy_on_write_observer_set_spec.rb | 11 + .../non_concurrent_priority_queue_spec.rb | 366 +++ .../collection/observer_set_shared.rb | 240 ++ .../spec/concurrent/collection_each_shared.rb | 61 + .../concern/dereferenceable_shared.rb | 139 ++ .../concurrent/concern/obligation_shared.rb | 100 + .../concurrent/concern/obligation_spec.rb | 331 +++ .../concurrent/concern/observable_shared.rb | 177 ++ .../concurrent/concern/observable_spec.rb | 58 + .../spec/concurrent/configuration_spec.rb | 26 + .../spec/concurrent/dataflow_spec.rb | 240 ++ .../spec/concurrent/delay_spec.rb | 103 + .../spec/concurrent/edge/channel_spec.rb | 389 +++ .../spec/concurrent/edge/erlang_actor_spec.rb | 1031 ++++++++ .../edge/lock_free_linked_set_spec.rb | 201 ++ .../spec/concurrent/exchanger_spec.rb | 263 ++ .../executor/cached_thread_pool_spec.rb | 244 ++ .../concurrent/executor/executor_quits.rb | 14 + .../executor/executor_service_shared.rb | 220 ++ .../executor/fixed_thread_pool_spec.rb | 317 +++ .../executor/global_thread_pool_shared.rb | 34 + .../executor/immediate_executor_spec.rb | 12 + .../indirect_immediate_executor_spec.rb | 26 + .../java_single_thread_executor_spec.rb | 20 + .../java_thread_pool_executor_spec.rb | 79 + .../ruby_single_thread_executor_spec.rb | 17 + .../ruby_thread_pool_executor_spec.rb | 196 ++ .../executor/safe_task_executor_spec.rb | 133 + .../executor/serialized_execution_spec.rb | 13 + .../executor/simple_executor_service_spec.rb | 101 + .../executor/thread_pool_class_cast_spec.rb | 28 + .../executor/thread_pool_executor_shared.rb | 662 +++++ .../concurrent/executor/thread_pool_shared.rb | 113 + .../concurrent/executor/timer_set_spec.rb | 396 +++ .../executor/wrapping_executor_spec.rb | 51 + .../spec/concurrent/future_spec.rb | 417 ++++ .../spec/concurrent/hash_spec.rb | 106 + .../spec/concurrent/immutable_struct_spec.rb | 9 + .../spec/concurrent/ivar_shared.rb | 158 ++ .../spec/concurrent/ivar_spec.rb | 144 ++ .../spec/concurrent/lazy_register_spec.rb | 9 + .../spec/concurrent/map_spec.rb | 938 +++++++ .../spec/concurrent/maybe_spec.rb | 231 ++ .../spec/concurrent/monotonic_time_spec.rb | 32 + .../spec/concurrent/mutable_struct_spec.rb | 174 ++ .../spec/concurrent/mvar_spec.rb | 409 ++++ .../no_concurrent_files_loaded_before_spec.rb | 7 + .../spec/concurrent/options_spec.rb | 39 + .../spec/concurrent/processing_actor_spec.rb | 74 + .../spec/concurrent/promise_spec.rb | 771 ++++++ .../spec/concurrent/promises_spec.rb | 757 ++++++ .../require_all_files_separately.rb | 29 + .../spec/concurrent/scheduled_task_spec.rb | 282 +++ .../spec/concurrent/set_spec.rb | 119 + .../spec/concurrent/settable_struct_spec.rb | 202 ++ .../spec/concurrent/struct_shared.rb | 535 ++++ .../spec/concurrent/synchronization_spec.rb | 245 ++ .../concurrent/thread_arguments_shared.rb | 46 + .../concurrent/thread_safe/map_loops_spec.rb | 508 ++++ .../concurrent/thread_safe/no_unsafe_spec.rb | 29 + .../synchronized_delegator_spec.rb | 85 + .../spec/concurrent/throttle_spec.rb | 96 + .../spec/concurrent/timer_task_spec.rb | 225 ++ .../spec/concurrent/tvar_spec.rb | 176 ++ .../utility/processor_count_spec.rb | 20 + .../concurrent-ruby-1.2.2/spec/spec_helper.rb | 68 + .../spec/support/.gitignore | 0 .../spec/support/example_group_extensions.rb | 67 + .../spec/support/threadsafe_test.rb | 72 + .../support/generate_docs.rb | 46 + .../support/yard_full_types.rb | 54 + .../default/fulldoc/html/css/common.css | 135 + .../default/layout/html/footer.erb | 14 + .../default/layout/html/objects.erb | 34 + .../yard-template/default/module/setup.rb | 21 + .../.github/ISSUE_TEMPLATE/bug_report.md | 30 + .../vendor/i18n-1.14.1/.github/funding.yml | 1 + .../i18n-1.14.1/.github/workflows/ruby.yml | 71 + .../vendor/{i18n => i18n-1.14.1}/.gitignore | 5 + .../ruby/vendor/i18n-1.14.1/CHANGELOG.md | 3 + app/server/ruby/vendor/i18n-1.14.1/Gemfile | 9 + .../vendor/{i18n => i18n-1.14.1}/MIT-LICENSE | 0 app/server/ruby/vendor/i18n-1.14.1/README.md | 123 + .../vendor/{i18n => i18n-1.14.1}/Rakefile | 2 +- .../benchmark/example.yml | 0 .../{i18n => i18n-1.14.1}/benchmark/run.rb | 26 +- .../i18n-1.14.1/gemfiles/Gemfile.rails-5.0.x | 13 + .../i18n-1.14.1/gemfiles/Gemfile.rails-5.1.x | 13 + .../i18n-1.14.1/gemfiles/Gemfile.rails-5.2.x | 13 + .../i18n-1.14.1/gemfiles/Gemfile.rails-6.0.x | 13 + .../i18n-1.14.1/gemfiles/Gemfile.rails-6.1.x | 13 + .../i18n-1.14.1/gemfiles/Gemfile.rails-7.0.x | 13 + .../i18n-1.14.1/gemfiles/Gemfile.rails-main | 13 + .../ruby/vendor/i18n-1.14.1/i18n.gemspec | 31 + .../vendor/{i18n => i18n-1.14.1}/lib/i18n.rb | 251 +- .../{i18n => i18n-1.14.1}/lib/i18n/backend.rb | 6 +- .../i18n-1.14.1/lib/i18n/backend/base.rb | 304 +++ .../lib/i18n/backend/cache.rb | 35 +- .../lib/i18n/backend/cache_file.rb | 36 + .../lib/i18n/backend/cascade.rb | 4 +- .../lib/i18n/backend/chain.rb | 63 +- .../lib/i18n/backend/fallbacks.rb | 60 +- .../lib/i18n/backend/flatten.rb | 21 +- .../lib/i18n/backend/gettext.rb | 47 +- .../i18n/backend/interpolation_compiler.rb | 6 +- .../i18n-1.14.1/lib/i18n/backend/key_value.rb | 204 ++ .../lib/i18n/backend/lazy_loadable.rb | 184 ++ .../lib/i18n/backend/memoize.rb | 14 +- .../lib/i18n/backend/metadata.rb | 16 +- .../lib/i18n/backend/pluralization.rb | 96 + .../lib/i18n/backend/simple.rb | 58 +- .../lib/i18n/backend/transliterator.rb | 108 + .../{i18n => i18n-1.14.1}/lib/i18n/config.rb | 53 +- .../vendor/i18n-1.14.1/lib/i18n/exceptions.rb | 157 ++ .../{i18n => i18n-1.14.1}/lib/i18n/gettext.rb | 11 +- .../lib/i18n/gettext/helpers.rb | 15 +- .../lib/i18n/gettext/po_parser.rb | 14 +- .../lib/i18n/interpolate/ruby.rb | 30 +- .../{i18n => i18n-1.14.1}/lib/i18n/locale.rb | 2 + .../lib/i18n/locale/fallbacks.rb | 41 +- .../lib/i18n/locale/tag.rb | 0 .../lib/i18n/locale/tag/parents.rb | 24 + .../lib/i18n/locale/tag/rfc4646.rb | 0 .../lib/i18n/locale/tag/simple.rb | 4 +- .../vendor/i18n-1.14.1/lib/i18n/middleware.rb | 17 + .../{i18n => i18n-1.14.1}/lib/i18n/tests.rb | 2 + .../lib/i18n/tests/basics.rb | 17 +- .../lib/i18n/tests/defaults.rb | 14 +- .../lib/i18n/tests/interpolation.rb | 44 +- .../lib/i18n/tests/link.rb | 12 +- .../lib/i18n/tests/localization.rb | 0 .../lib/i18n/tests/localization/date.rb | 51 +- .../lib/i18n/tests/localization/date_time.rb | 35 +- .../lib/i18n/tests/localization/procs.rb | 14 +- .../lib/i18n/tests/localization/time.rb | 32 +- .../lib/i18n/tests/lookup.rb | 12 +- .../lib/i18n/tests/pluralization.rb | 2 +- .../lib/i18n/tests/procs.rb | 31 +- .../ruby/vendor/i18n-1.14.1/lib/i18n/utils.rb | 55 + .../vendor/i18n-1.14.1/lib/i18n/version.rb | 5 + .../test/api/all_features_test.rb | 0 .../test/api/cascade_test.rb | 0 .../test/api/chain_test.rb | 0 .../test/api/fallbacks_test.rb | 0 .../test/api/key_value_test.rb | 8 +- .../test/api/lazy_loadable_test.rb | 24 + .../test/api/memoize_test.rb | 8 +- .../test/api/override_test.rb | 11 +- .../test/api/pluralization_test.rb | 0 .../test/api/simple_test.rb | 0 .../test/backend/cache_file_test.rb | 94 + .../test/backend/cache_test.rb | 48 +- .../test/backend/cascade_test.rb | 9 +- .../i18n-1.14.1/test/backend/chain_test.rb | 175 ++ .../test/backend/exceptions_test.rb | 7 +- .../test/backend/fallbacks_test.rb | 430 ++++ .../backend/interpolation_compiler_test.rb | 2 +- .../test/backend/key_value_test.rb | 106 + .../test/backend/lazy_loadable_test.rb | 223 ++ .../test/backend/memoize_test.rb | 39 +- .../test/backend/metadata_test.rb | 1 + .../backend/pluralization_fallback_test.rb | 69 + .../test/backend/pluralization_scope_test.rb | 55 + .../test/backend/pluralization_test.rb | 95 + .../i18n-1.14.1/test/backend/simple_test.rb | 232 ++ .../test/backend/transliterator_test.rb | 15 +- .../test/gettext/api_test.rb | 8 + .../i18n-1.14.1/test/gettext/backend_test.rb | 92 + .../test/i18n/exceptions_test.rb | 41 +- .../test/i18n/gettext_plural_keys_test.rb | 20 + .../test/i18n/interpolate_test.rb | 43 +- .../test/i18n/load_path_test.rb | 5 +- .../i18n-1.14.1/test/i18n/middleware_test.rb | 24 + .../{i18n => i18n-1.14.1}/test/i18n_test.rb | 171 +- .../test/locale/fallbacks_test.rb | 49 +- .../test/locale/tag/rfc4646_test.rb | 1 + .../test/locale/tag/simple_test.rb | 0 .../{i18n => i18n-1.14.1}/test/run_all.rb | 2 +- .../ruby/vendor/i18n-1.14.1/test/run_one.rb | 11 + .../test/test_data/locales/de.po | 0 .../test/test_data/locales/en.json | 7 + .../test/test_data/locales/en.rb | 0 .../test/test_data/locales/en.yaml} | 0 .../i18n-1.14.1/test/test_data/locales/en.yml | 3 + .../i18n-1.14.1/test/test_data/locales/fr.yml | 3 + .../test/test_data/locales/invalid/empty.yml | 0 .../test/test_data/locales/invalid/syntax.yml | 0 .../test/test_data/locales/plurals.rb | 4 +- .../vendor/i18n-1.14.1/test/test_helper.rb | 63 + .../vendor/i18n-1.14.1/test/utils_test.rb | 30 + app/server/ruby/vendor/i18n/.travis.yml | 43 - app/server/ruby/vendor/i18n/Gemfile | 7 - app/server/ruby/vendor/i18n/Gemfile.lock | 23 - app/server/ruby/vendor/i18n/README.md | 105 - .../vendor/i18n/gemfiles/Gemfile.rails-2.3.x | 11 - .../i18n/gemfiles/Gemfile.rails-2.3.x.lock | 30 - .../vendor/i18n/gemfiles/Gemfile.rails-3.0.x | 11 - .../i18n/gemfiles/Gemfile.rails-3.0.x.lock | 30 - .../vendor/i18n/gemfiles/Gemfile.rails-3.1.x | 10 - .../i18n/gemfiles/Gemfile.rails-3.1.x.lock | 30 - .../vendor/i18n/gemfiles/Gemfile.rails-3.2.x | 10 - .../i18n/gemfiles/Gemfile.rails-3.2.x.lock | 31 - .../vendor/i18n/gemfiles/Gemfile.rails-4.0.x | 10 - .../i18n/gemfiles/Gemfile.rails-4.0.x.lock | 37 - .../vendor/i18n/gemfiles/Gemfile.rails-4.1.x | 10 - .../i18n/gemfiles/Gemfile.rails-4.1.x.lock | 38 - app/server/ruby/vendor/i18n/i18n.gemspec | 21 - .../ruby/vendor/i18n/lib/i18n/backend/base.rb | 190 -- .../vendor/i18n/lib/i18n/backend/key_value.rb | 101 - .../i18n/lib/i18n/backend/pluralization.rb | 53 - .../i18n/lib/i18n/backend/transliterator.rb | 99 - .../vendor/i18n/lib/i18n/core_ext/hash.rb | 29 - .../i18n/core_ext/kernel/suppress_warnings.rb | 8 - .../lib/i18n/core_ext/string/interpolate.rb | 105 - .../ruby/vendor/i18n/lib/i18n/exceptions.rb | 128 - .../i18n/lib/i18n/locale/tag/parents.rb | 22 - .../ruby/vendor/i18n/lib/i18n/version.rb | 3 - app/server/ruby/vendor/i18n/test/all.rb | 8 - .../vendor/i18n/test/backend/chain_test.rb | 76 - .../i18n/test/backend/fallbacks_test.rb | 146 -- .../i18n/test/backend/key_value_test.rb | 46 - .../i18n/test/backend/pluralization_test.rb | 44 - .../vendor/i18n/test/backend/simple_test.rb | 83 - .../vendor/i18n/test/core_ext/hash_test.rb | 30 - .../test/core_ext/string/interpolate_test.rb | 99 - .../vendor/i18n/test/gettext/backend_test.rb | 101 - .../ruby/vendor/i18n/test/test_helper.rb | 76 - .../.autotest | 0 .../minitest-5.18.1/.github/workflows/ci.yml | 76 + .../History.rdoc | 105 +- .../Manifest.txt | 2 + .../README.rdoc | 102 +- .../Rakefile | 3 +- .../design_rationale.rb | 0 .../lib/hoe/minitest.rb | 4 - .../lib/minitest.rb | 106 +- .../lib/minitest/assertions.rb | 63 +- .../lib/minitest/autorun.rb | 0 .../lib/minitest/benchmark.rb | 10 +- .../lib/minitest/expectations.rb | 18 + .../lib/minitest/hell.rb | 0 .../lib/minitest/mock.rb | 149 +- .../lib/minitest/parallel.rb | 0 .../lib/minitest/pride.rb | 0 .../lib/minitest/pride_plugin.rb | 2 +- .../lib/minitest/spec.rb | 11 +- .../lib/minitest/test.rb | 56 +- .../minitest-5.18.1/lib/minitest/test_task.rb | 305 +++ .../lib/minitest/unit.rb | 13 +- .../test/minitest/metametameta.rb | 2 +- .../test/minitest/test_minitest_assertions.rb | 158 +- .../test/minitest/test_minitest_benchmark.rb | 4 +- .../test/minitest/test_minitest_mock.rb | 288 ++- .../test/minitest/test_minitest_reporter.rb | 47 +- .../test/minitest/test_minitest_spec.rb | 117 +- .../test/minitest/test_minitest_test.rb | 248 +- .../test/minitest/test_minitest_test_task.rb | 46 + app/server/ruby/vendor/thread_safe/.gitignore | 22 - .../ruby/vendor/thread_safe/.travis.yml | 24 - app/server/ruby/vendor/thread_safe/Gemfile | 4 - app/server/ruby/vendor/thread_safe/LICENSE | 144 -- app/server/ruby/vendor/thread_safe/README.md | 56 - app/server/ruby/vendor/thread_safe/Rakefile | 48 - .../thread_safe/examples/bench_cache.rb | 35 - .../thread_safe/JRubyCacheBackendLibrary.java | 245 -- .../thread_safe/JrubyCacheBackendService.java | 15 - .../vendor/thread_safe/lib/thread_safe.rb | 65 - .../atomic_reference_cache_backend.rb | 922 ------- .../thread_safe/lib/thread_safe/cache.rb | 163 -- .../lib/thread_safe/mri_cache_backend.rb | 62 - .../non_concurrent_cache_backend.rb | 133 - .../thread_safe/synchronized_cache_backend.rb | 76 - .../lib/thread_safe/synchronized_delegator.rb | 60 - .../thread_safe/lib/thread_safe/util.rb | 16 - .../thread_safe/lib/thread_safe/util/adder.rb | 59 - .../lib/thread_safe/util/atomic_reference.rb | 45 - .../lib/thread_safe/util/cheap_lockable.rb | 105 - .../thread_safe/util/power_of_two_tuple.rb | 26 - .../lib/thread_safe/util/striped64.rb | 226 -- .../lib/thread_safe/util/volatile.rb | 62 - .../lib/thread_safe/util/volatile_tuple.rb | 46 - .../lib/thread_safe/util/xor_shift_random.rb | 39 - .../thread_safe/lib/thread_safe/version.rb | 21 - .../test/src/thread_safe/SecurityManager.java | 21 - .../vendor/thread_safe/test/test_array.rb | 18 - .../vendor/thread_safe/test/test_cache.rb | 901 ------- .../thread_safe/test/test_cache_loops.rb | 449 ---- .../ruby/vendor/thread_safe/test/test_hash.rb | 17 - .../vendor/thread_safe/test/test_helper.rb | 113 - .../test/test_synchronized_delegator.rb | 84 - .../vendor/thread_safe/thread_safe.gemspec | 26 - .../ruby/vendor/tzinfo-2.0.6/.editorconfig | 12 + .../tzinfo-2.0.6/.github/workflows/tests.yml | 70 + .../ruby/vendor/tzinfo-2.0.6/.gitignore | 6 + app/server/ruby/vendor/tzinfo-2.0.6/.yardopts | 9 + .../ruby/vendor/tzinfo-2.0.6/CHANGES.md | 1026 ++++++++ app/server/ruby/vendor/tzinfo-2.0.6/Gemfile | 27 + app/server/ruby/vendor/tzinfo-2.0.6/LICENSE | 19 + app/server/ruby/vendor/tzinfo-2.0.6/README.md | 406 +++ app/server/ruby/vendor/tzinfo-2.0.6/Rakefile | 121 + .../vendor/tzinfo-2.0.6/gem-public_cert.pem | 20 + .../ruby/vendor/tzinfo-2.0.6/lib/tzinfo.rb | 81 + .../tzinfo-2.0.6/lib/tzinfo/annual_rules.rb | 71 + .../vendor/tzinfo-2.0.6/lib/tzinfo/country.rb | 208 ++ .../lib/tzinfo/country_timezone.rb | 93 + .../tzinfo-2.0.6/lib/tzinfo/data_source.rb | 446 ++++ .../tzinfo-2.0.6/lib/tzinfo/data_sources.rb | 8 + .../constant_offset_data_timezone_info.rb | 56 + .../lib/tzinfo/data_sources/country_info.rb | 42 + .../tzinfo/data_sources/data_timezone_info.rb | 91 + .../data_sources/linked_timezone_info.rb | 33 + .../data_sources/posix_time_zone_parser.rb | 177 ++ .../tzinfo/data_sources/ruby_data_source.rb | 141 ++ .../lib/tzinfo/data_sources/timezone_info.rb | 47 + .../transitions_data_timezone_info.rb | 214 ++ .../data_sources/zoneinfo_data_source.rb | 592 +++++ .../tzinfo/data_sources/zoneinfo_reader.rb | 482 ++++ .../tzinfo-2.0.6/lib/tzinfo/data_timezone.rb | 44 + .../lib/tzinfo/datetime_with_offset.rb | 153 ++ .../vendor/tzinfo-2.0.6/lib/tzinfo/format1.rb | 10 + .../lib/tzinfo/format1/country_definer.rb | 17 + .../format1/country_index_definition.rb | 64 + .../lib/tzinfo/format1/timezone_definer.rb | 64 + .../lib/tzinfo/format1/timezone_definition.rb | 39 + .../format1/timezone_index_definition.rb | 77 + .../vendor/tzinfo-2.0.6/lib/tzinfo/format2.rb | 10 + .../lib/tzinfo/format2/country_definer.rb | 68 + .../tzinfo/format2/country_index_definer.rb | 68 + .../format2/country_index_definition.rb | 46 + .../lib/tzinfo/format2/timezone_definer.rb | 94 + .../lib/tzinfo/format2/timezone_definition.rb | 73 + .../tzinfo/format2/timezone_index_definer.rb | 45 + .../format2/timezone_index_definition.rb | 55 + .../tzinfo-2.0.6/lib/tzinfo/info_timezone.rb | 35 + .../lib/tzinfo/linked_timezone.rb | 44 + .../lib/tzinfo/offset_timezone_period.rb | 42 + .../lib/tzinfo/ruby_core_support.rb | 38 + .../tzinfo-2.0.6/lib/tzinfo/string_deduper.rb | 118 + .../lib/tzinfo/time_with_offset.rb | 154 ++ .../tzinfo-2.0.6/lib/tzinfo/timestamp.rb | 552 +++++ .../lib/tzinfo/timestamp_with_offset.rb | 85 + .../tzinfo-2.0.6/lib/tzinfo/timezone.rb | 1160 +++++++++ .../lib/tzinfo/timezone_offset.rb | 111 + .../lib/tzinfo/timezone_period.rb | 179 ++ .../tzinfo-2.0.6/lib/tzinfo/timezone_proxy.rb | 96 + .../lib/tzinfo/timezone_transition.rb | 98 + .../lib/tzinfo/transition_rule.rb | 455 ++++ .../lib/tzinfo/transitions_timezone_period.rb | 63 + .../vendor/tzinfo-2.0.6/lib/tzinfo/version.rb | 7 + .../tzinfo-2.0.6/lib/tzinfo/with_offset.rb | 61 + .../tc_constant_offset_data_timezone_info.rb | 61 + .../test/data_sources/tc_country_info.rb | 86 + .../data_sources/tc_data_timezone_info.rb | 62 + .../data_sources/tc_linked_timezone_info.rb | 60 + .../data_sources/tc_posix_time_zone_parser.rb | 282 +++ .../test/data_sources/tc_ruby_data_source.rb | 350 +++ .../test/data_sources/tc_timezone_info.rb | 40 + .../tc_transitions_data_timezone_info.rb | 342 +++ .../data_sources/tc_zoneinfo_data_source.rb | 1574 ++++++++++++ .../test/data_sources/tc_zoneinfo_reader.rb | 2117 ++++++++++++++++ .../test/format1/tc_country_definer.rb | 60 + .../format1/tc_country_index_definition.rb | 181 ++ .../test/format1/tc_timezone_definer.rb | 74 + .../test/format1/tc_timezone_definition.rb | 242 ++ .../format1/tc_timezone_index_definition.rb | 106 + .../test/format2/tc_country_definer.rb | 94 + .../test/format2/tc_country_index_definer.rb | 192 ++ .../format2/tc_country_index_definition.rb | 113 + .../test/format2/tc_timezone_definer.rb | 125 + .../test/format2/tc_timezone_definition.rb | 234 ++ .../test/format2/tc_timezone_index_definer.rb | 65 + .../format2/tc_timezone_index_definition.rb | 127 + .../tzinfo-2.0.6/test/tc_annual_rules.rb | 97 + .../vendor/tzinfo-2.0.6/test/tc_country.rb | 219 ++ .../tzinfo-2.0.6/test/tc_country_timezone.rb | 129 + .../tzinfo-2.0.6/test/tc_data_source.rb | 604 +++++ .../tzinfo-2.0.6/test/tc_data_timezone.rb | 340 +++ .../test/tc_datetime_with_offset.rb | 397 +++ .../tzinfo-2.0.6/test/tc_info_timezone.rb | 25 + .../tzinfo-2.0.6/test/tc_linked_timezone.rb | 204 ++ .../test/tc_offset_timezone_period.rb | 67 + .../tzinfo-2.0.6/test/tc_ruby_core_support.rb | 19 + .../test/tc_ruby_time_timezone.rb | 104 + .../tzinfo-2.0.6/test/tc_string_deduper.rb | 78 + .../tzinfo-2.0.6/test/tc_time_with_offset.rb | 371 +++ .../vendor/tzinfo-2.0.6/test/tc_timestamp.rb | 1306 ++++++++++ .../test/tc_timestamp_with_offset.rb | 202 ++ .../vendor/tzinfo-2.0.6/test/tc_timezone.rb | 1970 +++++++++++++++ .../tzinfo-2.0.6/test/tc_timezone_london.rb | 176 ++ .../test/tc_timezone_melbourne.rb | 175 ++ .../tzinfo-2.0.6/test/tc_timezone_new_york.rb | 175 ++ .../tzinfo-2.0.6/test/tc_timezone_offset.rb | 105 + .../tzinfo-2.0.6/test/tc_timezone_period.rb | 141 ++ .../tzinfo-2.0.6/test/tc_timezone_proxy.rb | 158 ++ .../test/tc_timezone_transition.rb | 99 + .../tzinfo-2.0.6/test/tc_timezone_utc.rb | 47 + .../tzinfo-2.0.6/test/tc_transition_rule.rb | 567 +++++ .../test/tc_transitions_timezone_period.rb | 163 ++ .../vendor/tzinfo-2.0.6/test/tc_tzinfo.rb | 23 + .../vendor/tzinfo-2.0.6/test/tc_version.rb | 10 + .../tzinfo-2.0.6/test/tc_with_offset.rb | 88 + .../vendor/tzinfo-2.0.6/test/test_utils.rb | 544 +++++ .../ruby/vendor/tzinfo-2.0.6/test/ts_all.rb | 10 + .../tzinfo-2.0.6/test/ts_all_ruby_format1.rb | 11 + .../tzinfo-2.0.6/test/ts_all_ruby_format2.rb | 11 + .../tzinfo-2.0.6/test/ts_all_zoneinfo.rb | 14 + .../test/tzinfo-data1/tzinfo/data.rb | 8 + .../America/Argentina/Buenos_Aires.rb | 89 + .../data/definitions/America/New_York.rb | 327 +++ .../data/definitions/Australia/Melbourne.rb | 230 ++ .../tzinfo/data/definitions/EST.rb | 19 + .../tzinfo/data/definitions/Etc/GMT__m__1.rb | 21 + .../tzinfo/data/definitions/Etc/GMT__p__1.rb | 21 + .../tzinfo/data/definitions/Etc/UTC.rb | 21 + .../data/definitions/Europe/Amsterdam.rb | 273 +++ .../tzinfo/data/definitions/Europe/Andorra.rb | 198 ++ .../tzinfo/data/definitions/Europe/London.rb | 333 +++ .../tzinfo/data/definitions/Europe/Paris.rb | 277 +++ .../tzinfo/data/definitions/Europe/Prague.rb | 235 ++ .../definitions/Invalid/Incorrect_Module.rb | 20 + .../tzinfo/data/definitions/UTC.rb | 16 + .../tzinfo/data/indexes/countries.rb | 940 +++++++ .../tzinfo/data/indexes/timezones.rb | 609 +++++ .../test/tzinfo-data1/tzinfo/data/version.rb | 20 + .../test/tzinfo-data2/tzinfo/data.rb | 18 + .../America/Argentina/Buenos_Aires.rb | 90 + .../data/definitions/America/New_York.rb | 328 +++ .../data/definitions/Australia/Melbourne.rb | 231 ++ .../tzinfo/data/definitions/EST.rb | 20 + .../tzinfo/data/definitions/Etc/GMT__m__1.rb | 22 + .../tzinfo/data/definitions/Etc/GMT__p__1.rb | 22 + .../tzinfo/data/definitions/Etc/UTC.rb | 22 + .../data/definitions/Europe/Amsterdam.rb | 274 +++ .../tzinfo/data/definitions/Europe/Andorra.rb | 199 ++ .../tzinfo/data/definitions/Europe/London.rb | 334 +++ .../tzinfo/data/definitions/Europe/Paris.rb | 278 +++ .../tzinfo/data/definitions/Europe/Prague.rb | 236 ++ .../definitions/Invalid/Incorrect_Module.rb | 21 + .../tzinfo/data/definitions/UTC.rb | 17 + .../tzinfo/data/indexes/countries.rb | 968 ++++++++ .../tzinfo/data/indexes/timezones.rb | 612 +++++ .../test/tzinfo-data2/tzinfo/data/version.rb | 21 + .../tzinfo-2.0.6/test/zoneinfo/.gitignore | 2 + .../zoneinfo/America/Argentina/Buenos_Aires | Bin 0 -> 708 bytes .../test/zoneinfo/America/New_York | Bin 0 -> 1744 bytes .../test/zoneinfo/Australia/Melbourne | Bin 0 -> 904 bytes .../vendor/tzinfo-2.0.6/test/zoneinfo/EST | Bin 0 -> 111 bytes .../test/zoneinfo/Europe/Amsterdam | Bin 0 -> 1071 bytes .../tzinfo-2.0.6/test/zoneinfo/Europe/Andorra | Bin 0 -> 389 bytes .../tzinfo-2.0.6/test/zoneinfo/Europe/London | Bin 0 -> 1599 bytes .../tzinfo-2.0.6/test/zoneinfo/Europe/Paris | Bin 0 -> 1105 bytes .../tzinfo-2.0.6/test/zoneinfo/Europe/Prague | Bin 0 -> 723 bytes .../vendor/tzinfo-2.0.6/test/zoneinfo/Factory | Bin 0 -> 113 bytes .../tzinfo-2.0.6/test/zoneinfo/iso3166.tab | 274 +++ .../tzinfo-2.0.6/test/zoneinfo/leapseconds | 78 + .../test/zoneinfo/posix/Europe/London | Bin 0 -> 1599 bytes .../tzinfo-2.0.6/test/zoneinfo/posixrules | Bin 0 -> 1744 bytes .../test/zoneinfo/right/Europe/London | Bin 0 -> 2358 bytes .../tzinfo-2.0.6/test/zoneinfo/zone.tab | 452 ++++ .../tzinfo-2.0.6/test/zoneinfo/zone1970.tab | 384 +++ .../ruby/vendor/tzinfo-2.0.6/tzinfo.gemspec | 29 + 1319 files changed, 109580 insertions(+), 39918 deletions(-) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/CHANGELOG.md rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/MIT-LICENSE (89%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/README.rdoc rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support.rb (60%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/actionable_error.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/all.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/array_inquirer.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/backtrace_cleaner.rb (62%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/benchmarkable.rb (82%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/builder.rb (76%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/cache/file_store.rb (58%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/mem_cache_store.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/cache/memory_store.rb (50%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/cache/null_store.rb (61%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/redis_cache_store.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/strategy/local_cache.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/cache/strategy/local_cache_middleware.rb (74%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/callbacks.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/code_generator.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/concern.rb (51%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concurrency/load_interlock_aware_monitor.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concurrency/share_lock.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/configurable.rb (75%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/configuration_file.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/access.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/array/conversions.rb (80%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/deprecated_conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/extract.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/array/extract_options.rb (96%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/array/grouping.rb (79%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/inquiry.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/array/wrap.rb (79%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/benchmark.rb (65%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/big_decimal.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/big_decimal/conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/class/attribute.rb (50%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/class/attribute_accessors.rb (67%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/subclasses.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/date/acts_like.rb (57%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/blank.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/date/calculations.rb (68%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/deprecated_conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/zones.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/calculations.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/compatibility.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/date_and_time/zones.rb (61%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/date_time/acts_like.rb (68%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/blank.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/date_time/calculations.rb (64%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/compatibility.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/date_time/conversions.rb (61%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/deprecated_conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/digest.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/digest/uuid.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/enumerable.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/file.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/file/atomic.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/hash/conversions.rb (69%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/deep_merge.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/deep_transform_values.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/except.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/hash/indifferent_access.rb (55%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/hash/keys.rb (51%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/hash/reverse_merge.rb (75%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/slice.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/inflections.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/integer/multiple.rb (74%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/time.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/kernel/concern.rb (68%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/reporting.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/kernel/singleton_class.rb (50%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/load_error.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/aliasing.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/module/anonymous.rb (54%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/module/attr_internal.rb (65%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attribute_accessors.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/module/concerning.rb (80%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/delegation.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/module/deprecation.rb (81%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/introspection.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/redefine_method.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/remove_method.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/name_error.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/numeric/bytes.rb (50%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/deprecated_conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/time.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/acts_like.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/object/blank.rb (71%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/conversions.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/object/deep_dup.rb (71%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/duplicable.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/object/inclusion.rb (76%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/object/instance_variables.rb (91%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/object/json.rb (58%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/to_param.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/to_query.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/try.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/with_options.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/pathname.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/pathname/existence.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/compare_range.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/deprecated_conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/each.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/include_time_with_zone.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/range/overlaps.rb (61%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/regexp.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/securerandom.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/access.rb (84%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/behavior.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/conversions.rb (76%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/exclude.rb (90%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/filters.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/indent.rb (79%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/inflections.rb (63%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/inquiry.rb (55%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/multibyte.rb (73%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/output_safety.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/starts_ends_with.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/strip.rb (59%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/string/zones.rb (70%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/symbol.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/symbol/starts_ends_with.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/time/acts_like.rb (57%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/core_ext/time/calculations.rb (51%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/compatibility.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/deprecated_conversions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/zones.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/uri.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/current_attributes.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/current_attributes/test_helper.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/dependencies/autoload.rb (95%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/interlock.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/require_dependency.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/deprecation.rb (58%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/behaviors.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/constant_accessor.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/disallowed.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/deprecation/instance_delegator.rb (51%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/method_wrappers.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/proxy_wrappers.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/reporting.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/descendants_tracker.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/digest.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration/iso8601_parser.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration/iso8601_serializer.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/encrypted_configuration.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/encrypted_file.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/environment_inquirer.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/error_reporter.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/evented_file_update_checker.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/execution_context.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/execution_context/test_helper.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/execution_wrapper.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/executor.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/executor/test_helper.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/file_update_checker.rb (57%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/fork_tracker.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/gem_version.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/gzip.rb (75%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/hash_with_indifferent_access.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/html_safe_translation.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/i18n.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/i18n_railtie.rb (56%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/inflections.rb (84%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/inflector/inflections.rb (74%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/methods.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/transliterate.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/isolated_execution_state.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/json/decoding.rb (54%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/json/encoding.rb (52%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/key_generator.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/lazy_load_hooks.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/locale/en.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/locale/en.yml (91%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/log_subscriber.rb (50%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/log_subscriber/test_helper.rb (83%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger_silence.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger_thread_safe_level.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/message_encryptor.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/message_verifier.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/metadata.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/rotation_configuration.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/rotator.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/multibyte.rb (75%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/multibyte/chars.rb (56%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte/unicode.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/notifications.rb (50%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications/fanout.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications/instrumenter.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/number_helper.rb (65%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/number_helper/number_converter.rb (83%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_currency_converter.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_delimited_converter.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/number_helper/number_to_human_converter.rb (73%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/number_helper/number_to_human_size_converter.rb (60%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/number_helper/number_to_percentage_converter.rb (65%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/number_helper/number_to_phone_converter.rb (60%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_rounded_converter.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/rounding_helper.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/option_merger.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/ordered_hash.rb (71%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/ordered_options.rb (55%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/parameter_filter.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/per_thread_registry.rb (64%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/proxy_object.rb (92%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/rails.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/railtie.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/reloader.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/rescuable.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ruby_features.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/secure_compare_rotator.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/security_utils.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/string_inquirer.rb (60%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/subscriber.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/tagged_logging.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/test_case.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/assertions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/autorun.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/testing/constant_lookup.rb (87%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/testing/declarative.rb (50%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/deprecation.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/file_fixtures.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/isolation.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/method_call_assertions.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization/server.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization/worker.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelize_executor.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/testing/setup_and_teardown.rb (73%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/stream.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/testing/tagged_logging.rb (78%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/time_helpers.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/time.rb create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/time_with_zone.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/values/time_zone.rb (58%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/version.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/xml_mini.rb (63%) create mode 100644 app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/jdom.rb rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/xml_mini/libxml.rb (60%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/xml_mini/libxmlsax.rb (68%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/xml_mini/nokogiri.rb (66%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/xml_mini/nokogirisax.rb (71%) rename app/server/ruby/vendor/{activesupport => activesupport-7.0.6}/lib/active_support/xml_mini/rexml.rb (74%) delete mode 100644 app/server/ruby/vendor/activesupport/CHANGELOG.md delete mode 100644 app/server/ruby/vendor/activesupport/README.rdoc delete mode 100644 app/server/ruby/vendor/activesupport/Rakefile delete mode 100644 app/server/ruby/vendor/activesupport/activesupport.gemspec delete mode 100755 app/server/ruby/vendor/activesupport/bin/generate_tables delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/all.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/cache.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/cache/mem_cache_store.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/cache/strategy/local_cache.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/callbacks.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/concurrency/latch.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/access.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/subclasses.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/conversions.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/zones.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/zones.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/enumerable.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/file.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/file/atomic.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/compact.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/deep_merge.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/except.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/slice.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/inflections.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/time.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/agnostics.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/debugger.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/reporting.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/load_error.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/marshal.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/aliasing.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/delegation.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/introspection.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/method_transplanting.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/qualified_const.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/reachable.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/remove_method.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/name_error.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/conversions.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/time.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/acts_like.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/conversions.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/duplicable.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_json.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_param.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_query.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/try.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/with_options.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/conversions.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/each.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/include_range.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/regexp.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/behavior.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/filters.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/output_safety.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/struct.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/thread.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/conversions.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/marshal.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/zones.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/core_ext/uri.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/dependencies.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/deprecation/behaviors.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/deprecation/method_wrappers.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/deprecation/proxy_wrappers.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/deprecation/reporting.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/descendants_tracker.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/duration.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/file_watcher.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/gem_version.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/hash_with_indifferent_access.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/i18n.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/inflector.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/inflector/methods.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/inflector/transliterate.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/json.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/key_generator.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/lazy_load_hooks.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/logger.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/logger_silence.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/message_encryptor.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/message_verifier.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/multibyte/unicode.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/notifications/fanout.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/notifications/instrumenter.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/option_merger.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/rails.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/railtie.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/rescuable.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/subscriber.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/tagged_logging.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/test_case.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/testing/assertions.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/testing/autorun.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/testing/deprecation.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/testing/isolation.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/testing/time_helpers.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/time.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/time_with_zone.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/values/unicode_tables.dat delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/version.rb delete mode 100644 app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/jdom.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/abstract_unit.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoload_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/b.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/c/d.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/c/e/f.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/application.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/circular1.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/circular2.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/inline_class.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/conflict.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/counting_loader.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/cross_site_dependency.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/e.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/html/some_class.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/loads_constant.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/inline_class.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/multiple_constant_file.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/raises_name_error.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/raises_no_method_error.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/requires_constant.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/autoloading_fixtures/should_not_be_required.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/benchmarkable_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/broadcast_logger_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/caching_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/callback_inheritance_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/callbacks_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/class_cache_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/clean_backtrace_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/clean_logger_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/concern_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/configurable_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/constantize_test_cases.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/array_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/bigdecimal_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/blank_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/class/attribute_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/class/delegating_attributes_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/class_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/date_and_time_behavior.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/date_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/date_time_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/deep_dup_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/duplicable_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/duration_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/enumerable_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/file_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/hash_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/integer_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/kernel/concern_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/kernel_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/load_error_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/marshal_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module/anonymous_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module/attr_internal_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module/attribute_accessor_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module/attribute_aliasing_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module/concerning_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module/qualified_const_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module/reachable_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module/remove_method_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/module_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/name_error_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/numeric_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/object/inclusion_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/object/json_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/object/to_param_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/object/to_query_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/object_and_class_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/range_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/regexp_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/string_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/struct_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/thread_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/time_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/time_with_zone_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/core_ext/uri_ext_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/check_warnings.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/conflict.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/cross_site_depender.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/mutual_one.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/mutual_two.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/raises_exception.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/raises_exception_without_blame_file.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/requires_nonexistent0.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/requires_nonexistent1.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/service_one.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies/service_two.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/dependencies_test_helpers.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/deprecation/proxy_wrappers_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/deprecation_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/descendants_tracker_test_cases.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/descendants_tracker_with_autoloading_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/descendants_tracker_without_autoloading_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/file_update_checker_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/fixtures/autoload/another_class.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/fixtures/autoload/some_class.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_doctype.dtd delete mode 100644 app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_entities.txt delete mode 100644 app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_include.txt delete mode 100644 app/server/ruby/vendor/activesupport/test/gzip_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/i18n_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/inflector_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/inflector_test_cases.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/json/decoding_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/json/encoding_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/key_generator_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/lazy_load_hooks_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/load_paths_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/log_subscriber_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/logger_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/message_encryptor_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/message_verifier_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/multibyte_chars_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/multibyte_conformance.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/multibyte_test_helpers.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/multibyte_unicode_database_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/notifications/evented_notification_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/notifications/instrumenter_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/notifications_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/number_helper_i18n_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/number_helper_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/option_merger_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/ordered_hash_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/ordered_options_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/rescuable_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/safe_buffer_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/string_inquirer_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/subscriber_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/tagged_logging_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/test_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/testing/constant_lookup_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/time_zone_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/transliterate_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/xml_mini/jdom_engine_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/xml_mini/libxml_engine_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/xml_mini/libxmlsax_engine_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/xml_mini/nokogiri_engine_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/xml_mini/nokogirisax_engine_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/xml_mini/rexml_engine_test.rb delete mode 100644 app/server/ruby/vendor/activesupport/test/xml_mini_test.rb delete mode 100755 app/server/ruby/vendor/atomic/.gitignore delete mode 100755 app/server/ruby/vendor/atomic/.travis.yml delete mode 100755 app/server/ruby/vendor/atomic/Gemfile delete mode 100755 app/server/ruby/vendor/atomic/LICENSE delete mode 100755 app/server/ruby/vendor/atomic/README.md delete mode 100755 app/server/ruby/vendor/atomic/Rakefile delete mode 100755 app/server/ruby/vendor/atomic/atomic.gemspec delete mode 100755 app/server/ruby/vendor/atomic/examples/atomic_example.rb delete mode 100755 app/server/ruby/vendor/atomic/examples/bench_atomic.rb delete mode 100755 app/server/ruby/vendor/atomic/ext/AtomicReferenceService.java delete mode 100755 app/server/ruby/vendor/atomic/ext/extconf.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic/concurrent_update_error.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic/delegated_update.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic/direct_update.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic/fallback.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic/jruby.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic/numeric_cas_wrapper.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic/rbx.rb delete mode 100755 app/server/ruby/vendor/atomic/lib/atomic/ruby.rb delete mode 100755 app/server/ruby/vendor/atomic/test/test_atomic.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/CONTRIBUTING.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/ISSUE_TEMPLATE.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/ci.yml create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/docs.yml create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/experimental.yml create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/.gitignore create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/.rspec create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/.yardopts create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/CHANGELOG.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/CODE_OF_CONDUCT.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/Gemfile create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/LICENSE.txt create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/README.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/Rakefile create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby-edge.gemspec create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby-ext.gemspec create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby.gemspec create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/celluloid_benchmark.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/define.in.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/define.out.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/examples.in.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/examples.out.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/format.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/init.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/io.in.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/io.out.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/main.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/messaging.in.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/messaging.out.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/quick.in.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/quick.out.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/supervision_tree.in.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/supervision_tree.out.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.in.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.init.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.out.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.in.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.init.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.out.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/dataflow.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/dataflow_top_stock_calc.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.in.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.init.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.out.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/future.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/future.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/implementation-absolute.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/implementation-scalability.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/implementation-write-proportion-scalability.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/ruby-absolute.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/ruby-scalability.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/logo/concurrent-ruby-logo-200x200.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/logo/concurrent-ruby-logo-300x300.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/logo/concurrent-ruby-logo-400x400.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/logo/concurrent-ruby-logo-930x930.png create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.in.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.init.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.out.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises-main.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.in.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.init.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.out.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/ruby-association-final-report.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/ruby-association-intermediate-report.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/signpost.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/synchronization-notes.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/synchronization.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/thread_pools.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.in.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.init.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.out.md rename app/server/ruby/vendor/{i18n/test/test_data/locales/invalid/empty.yml => concurrent-ruby-1.2.2/docs-source/top-stock-scala/.gitignore} (100%) create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/README.md create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/top-stock.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/tvar.md create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/buffered-channels.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/channels.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/default-selection.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/equivalent-binary-trees.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/range-and-close.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/select.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/actor_stress_test.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/atomic_example.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_async.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic.rb rename app/server/ruby/vendor/{atomic/examples/bench_atomic_1.rb => concurrent-ruby-1.2.2/examples/benchmark_atomic_1.rb} (78%) create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_boolean.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_fixnum.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_map.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_new_futures.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_read_write_lock.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_structs.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/format.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-buffering.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-directions.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-synchronization.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channels.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/closing-channels.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/non-blocking-channel-operations.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/range-over-channels.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/rate-limiting.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/select.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/ticker.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/timeouts.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/timers.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/worker-pools.rb rename app/server/ruby/vendor/{atomic => concurrent-ruby-1.2.2}/examples/graph_atomic_bench.rb (59%) create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/init.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/stress_ruby_thread_pool.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/thread_local_memory_usage.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/thread_local_var_bench.rb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/who.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_boolean.c create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_boolean.h create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_fixnum.c create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_fixnum.h create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_reference.c create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_reference.h create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/extconf.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/rb_concurrent.c create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/ConcurrentRubyService.java rename app/server/ruby/vendor/{atomic/ext/org/jruby/ext/atomic => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/AtomicReferenceLibrary.java (90%) mode change 100755 => 100644 create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java rename app/server/ruby/vendor/{thread_safe/ext/org/jruby/ext/thread_safe => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/jsr166e/ConcurrentHashMap.java (96%) rename app/server/ruby/vendor/{thread_safe/ext/org/jruby/ext/thread_safe => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/jsr166e/ConcurrentHashMapV8.java (99%) rename app/server/ruby/vendor/{thread_safe/ext/org/jruby/ext/thread_safe => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/jsr166e/LongAdder.java (99%) rename app/server/ruby/vendor/{thread_safe/ext/org/jruby/ext/thread_safe => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/jsr166e/Striped64.java (99%) rename app/server/ruby/vendor/{thread_safe/ext/org/jruby/ext/thread_safe => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/jsr166e/nounsafe/ConcurrentHashMapV8.java (99%) rename app/server/ruby/vendor/{thread_safe/ext/org/jruby/ext/thread_safe => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/jsr166e/nounsafe/LongAdder.java (99%) rename app/server/ruby/vendor/{thread_safe/ext/org/jruby/ext/thread_safe => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/jsr166e/nounsafe/Striped64.java (99%) rename app/server/ruby/vendor/{thread_safe/ext/org/jruby/ext/thread_safe => concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext}/jsr166y/ThreadLocalRandom.java (99%) create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent-ruby.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/.gitignore create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/agent.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/array.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/async.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atom.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_boolean.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_fixnum.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_markable_reference.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_reference.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/count_down_latch.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/cyclic_barrier.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/event.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/fiber_local_var.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/java_count_down_latch.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/locals.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/lock_local_var.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_atomic_boolean.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_atomic_fixnum.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_count_down_latch.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_semaphore.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/read_write_lock.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/reentrant_read_write_lock.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/semaphore.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/thread_local_var.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/atomic_direct_update.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/mutex_atomic.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomics.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/copy_on_notify_observer_set.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/copy_on_write_observer_set.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/java_non_concurrent_priority_queue.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/lock_free_stack.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/atomic_reference_map_backend.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/mri_map_backend.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/non_concurrent_map_backend.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/synchronized_map_backend.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/truffleruby_map_backend.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/non_concurrent_priority_queue.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/deprecation.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/dereferenceable.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/logging.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/obligation.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/observable.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/configuration.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/constants.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/dataflow.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/delay.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/errors.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/exchanger.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/abstract_executor_service.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/cached_thread_pool.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/executor_service.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/fixed_thread_pool.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/immediate_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/indirect_immediate_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_executor_service.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_single_thread_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_thread_pool_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_executor_service.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_single_thread_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_thread_pool_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/safe_task_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serial_executor_service.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serialized_execution.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serialized_execution_delegator.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/simple_executor_service.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/single_thread_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/thread_pool_executor.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/timer_set.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executors.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/future.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/hash.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/immutable_struct.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/ivar.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/map.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/maybe.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/mutable_struct.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/mvar.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/options.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/promise.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/promises.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/re_include.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/scheduled_task.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/set.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/settable_struct.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_lockable_object.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_object.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_struct.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/condition.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/full_memory_barrier.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/jruby_lockable_object.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/lock.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/lockable_object.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/mutex_lockable_object.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/object.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/safe_initialization.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/volatile.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/readme.txt create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/synchronized_delegator.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/adder.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/cheap_lockable.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/data_structures.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/power_of_two_tuple.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/striped64.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/volatile.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/xor_shift_random.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/timer_task.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/tuple.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/tvar.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/engine.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/monotonic_time.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/native_extension_loader.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/native_integer.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/processor_counter.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/version.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/.gitignore create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/.gitignore create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/actor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/agent_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/array_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/async_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atom_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_boolean_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_fixnum_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_markable_reference_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_reference_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/count_down_latch_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/cyclic_barrier_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/event_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/fiber_local_var_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/lock_local_var_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/read_write_lock_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/reentrant_read_write_lock_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/semaphore_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/thread_local_var_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/cancellation_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/base_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/base_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/buffered_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/buffered_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/dropping_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/sliding_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/ticker_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/timer_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/timing_buffer_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/unbuffered_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/integration_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/tick_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/copy_on_notify_observer_set_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/copy_on_write_observer_set_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/non_concurrent_priority_queue_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/observer_set_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection_each_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/dereferenceable_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/obligation_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/obligation_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/observable_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/observable_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/configuration_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/dataflow_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/delay_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/channel_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/erlang_actor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/lock_free_linked_set_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/exchanger_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/cached_thread_pool_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/executor_quits.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/executor_service_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/fixed_thread_pool_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/global_thread_pool_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/immediate_executor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/indirect_immediate_executor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/java_single_thread_executor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/java_thread_pool_executor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/ruby_single_thread_executor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/ruby_thread_pool_executor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/safe_task_executor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/serialized_execution_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/simple_executor_service_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_class_cast_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_executor_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/timer_set_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/wrapping_executor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/future_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/hash_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/immutable_struct_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/ivar_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/ivar_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/lazy_register_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/map_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/maybe_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/monotonic_time_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/mutable_struct_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/mvar_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/no_concurrent_files_loaded_before_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/options_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/processing_actor_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/promise_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/promises_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/require_all_files_separately.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/scheduled_task_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/set_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/settable_struct_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/struct_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/synchronization_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_arguments_shared.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/map_loops_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/no_unsafe_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/synchronized_delegator_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/throttle_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/timer_task_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/tvar_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/utility/processor_count_spec.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/spec_helper.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/.gitignore create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/example_group_extensions.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/threadsafe_test.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/support/generate_docs.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/support/yard_full_types.rb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/fulldoc/html/css/common.css create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/layout/html/footer.erb create mode 100755 app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/layout/html/objects.erb create mode 100644 app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/module/setup.rb create mode 100644 app/server/ruby/vendor/i18n-1.14.1/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 app/server/ruby/vendor/i18n-1.14.1/.github/funding.yml create mode 100644 app/server/ruby/vendor/i18n-1.14.1/.github/workflows/ruby.yml rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/.gitignore (52%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/CHANGELOG.md create mode 100644 app/server/ruby/vendor/i18n-1.14.1/Gemfile rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/MIT-LICENSE (100%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/README.md rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/Rakefile (81%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/benchmark/example.yml (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/benchmark/run.rb (79%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.0.x create mode 100644 app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.1.x create mode 100644 app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.2.x create mode 100644 app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-6.0.x create mode 100644 app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-6.1.x create mode 100644 app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-7.0.x create mode 100644 app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-main create mode 100644 app/server/ruby/vendor/i18n-1.14.1/i18n.gemspec rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n.rb (64%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend.rb (84%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/base.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/cache.rb (72%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cache_file.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/cascade.rb (95%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/chain.rb (54%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/fallbacks.rb (53%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/flatten.rb (90%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/gettext.rb (60%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/interpolation_compiler.rb (96%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/key_value.rb create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/lazy_loadable.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/memoize.rb (78%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/metadata.rb (79%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/pluralization.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/backend/simple.rb (56%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/transliterator.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/config.rb (71%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/exceptions.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/gettext.rb (56%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/gettext/helpers.rb (78%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/gettext/po_parser.rb (98%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/interpolate/ruby.rb (55%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/locale.rb (80%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/locale/fallbacks.rb (73%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/locale/tag.rb (100%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/parents.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/locale/tag/rfc4646.rb (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/locale/tag/simple.rb (84%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/middleware.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests.rb (93%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/basics.rb (74%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/defaults.rb (75%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/interpolation.rb (70%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/link.rb (83%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/localization.rb (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/localization/date.rb (54%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/localization/date_time.rb (60%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/localization/procs.rb (87%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/localization/time.rb (63%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/lookup.rb (86%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/pluralization.rb (91%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/lib/i18n/tests/procs.rb (68%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/utils.rb create mode 100644 app/server/ruby/vendor/i18n-1.14.1/lib/i18n/version.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/all_features_test.rb (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/cascade_test.rb (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/chain_test.rb (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/fallbacks_test.rb (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/key_value_test.rb (78%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/api/lazy_loadable_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/memoize_test.rb (90%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/override_test.rb (81%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/pluralization_test.rb (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/api/simple_test.rb (100%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/cache_file_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/backend/cache_test.rb (55%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/backend/cascade_test.rb (90%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/chain_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/backend/exceptions_test.rb (85%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/fallbacks_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/backend/interpolation_compiler_test.rb (98%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/key_value_test.rb create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/lazy_loadable_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/backend/memoize_test.rb (50%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/backend/metadata_test.rb (99%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_fallback_test.rb create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_scope_test.rb create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_test.rb create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/backend/simple_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/backend/transliterator_test.rb (90%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/gettext/api_test.rb (98%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/gettext/backend_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/i18n/exceptions_test.rb (67%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/i18n/gettext_plural_keys_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/i18n/interpolate_test.rb (72%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/i18n/load_path_test.rb (90%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/i18n/middleware_test.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/i18n_test.rb (68%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/locale/fallbacks_test.rb (76%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/locale/tag/rfc4646_test.rb (99%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/locale/tag/simple_test.rb (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/run_all.rb (89%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/run_one.rb rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/test_data/locales/de.po (100%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/test_data/locales/en.json rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/test_data/locales/en.rb (100%) rename app/server/ruby/vendor/{i18n/test/test_data/locales/en.yml => i18n-1.14.1/test/test_data/locales/en.yaml} (100%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/test_data/locales/en.yml create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/test_data/locales/fr.yml create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/test_data/locales/invalid/empty.yml rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/test_data/locales/invalid/syntax.yml (100%) rename app/server/ruby/vendor/{i18n => i18n-1.14.1}/test/test_data/locales/plurals.rb (97%) create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/test_helper.rb create mode 100644 app/server/ruby/vendor/i18n-1.14.1/test/utils_test.rb delete mode 100644 app/server/ruby/vendor/i18n/.travis.yml delete mode 100644 app/server/ruby/vendor/i18n/Gemfile delete mode 100644 app/server/ruby/vendor/i18n/Gemfile.lock delete mode 100644 app/server/ruby/vendor/i18n/README.md delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-2.3.x delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-2.3.x.lock delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-3.0.x delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-3.0.x.lock delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-3.1.x delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-3.1.x.lock delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-3.2.x delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-3.2.x.lock delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-4.0.x delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-4.0.x.lock delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-4.1.x delete mode 100644 app/server/ruby/vendor/i18n/gemfiles/Gemfile.rails-4.1.x.lock delete mode 100644 app/server/ruby/vendor/i18n/i18n.gemspec delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/backend/base.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/backend/key_value.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/backend/pluralization.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/backend/transliterator.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/core_ext/hash.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/core_ext/kernel/suppress_warnings.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/core_ext/string/interpolate.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/exceptions.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/locale/tag/parents.rb delete mode 100644 app/server/ruby/vendor/i18n/lib/i18n/version.rb delete mode 100644 app/server/ruby/vendor/i18n/test/all.rb delete mode 100644 app/server/ruby/vendor/i18n/test/backend/chain_test.rb delete mode 100644 app/server/ruby/vendor/i18n/test/backend/fallbacks_test.rb delete mode 100644 app/server/ruby/vendor/i18n/test/backend/key_value_test.rb delete mode 100644 app/server/ruby/vendor/i18n/test/backend/pluralization_test.rb delete mode 100644 app/server/ruby/vendor/i18n/test/backend/simple_test.rb delete mode 100644 app/server/ruby/vendor/i18n/test/core_ext/hash_test.rb delete mode 100644 app/server/ruby/vendor/i18n/test/core_ext/string/interpolate_test.rb delete mode 100644 app/server/ruby/vendor/i18n/test/gettext/backend_test.rb delete mode 100644 app/server/ruby/vendor/i18n/test/test_helper.rb rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/.autotest (100%) create mode 100644 app/server/ruby/vendor/minitest-5.18.1/.github/workflows/ci.yml rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/History.rdoc (92%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/Manifest.txt (90%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/README.rdoc (89%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/Rakefile (94%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/design_rationale.rb (100%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/hoe/minitest.rb (79%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest.rb (91%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/assertions.rb (91%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/autorun.rb (100%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/benchmark.rb (97%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/expectations.rb (94%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/hell.rb (100%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/mock.rb (57%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/parallel.rb (100%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/pride.rb (100%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/pride_plugin.rb (97%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/spec.rb (94%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/test.rb (83%) create mode 100644 app/server/ruby/vendor/minitest-5.18.1/lib/minitest/test_task.rb rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/lib/minitest/unit.rb (85%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/test/minitest/metametameta.rb (99%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/test/minitest/test_minitest_assertions.rb (90%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/test/minitest/test_minitest_benchmark.rb (95%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/test/minitest/test_minitest_mock.rb (71%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/test/minitest/test_minitest_reporter.rb (86%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/test/minitest/test_minitest_spec.rb (90%) rename app/server/ruby/vendor/{minitest-5.14.4 => minitest-5.18.1}/test/minitest/test_minitest_test.rb (79%) create mode 100644 app/server/ruby/vendor/minitest-5.18.1/test/minitest/test_minitest_test_task.rb delete mode 100644 app/server/ruby/vendor/thread_safe/.gitignore delete mode 100644 app/server/ruby/vendor/thread_safe/.travis.yml delete mode 100644 app/server/ruby/vendor/thread_safe/Gemfile delete mode 100644 app/server/ruby/vendor/thread_safe/LICENSE delete mode 100644 app/server/ruby/vendor/thread_safe/README.md delete mode 100644 app/server/ruby/vendor/thread_safe/Rakefile delete mode 100755 app/server/ruby/vendor/thread_safe/examples/bench_cache.rb delete mode 100644 app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java delete mode 100644 app/server/ruby/vendor/thread_safe/ext/thread_safe/JrubyCacheBackendService.java delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/atomic_reference_cache_backend.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/cache.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/mri_cache_backend.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/non_concurrent_cache_backend.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/synchronized_cache_backend.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/synchronized_delegator.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util/adder.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util/atomic_reference.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util/cheap_lockable.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util/power_of_two_tuple.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util/striped64.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util/volatile.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util/volatile_tuple.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/util/xor_shift_random.rb delete mode 100644 app/server/ruby/vendor/thread_safe/lib/thread_safe/version.rb delete mode 100644 app/server/ruby/vendor/thread_safe/test/src/thread_safe/SecurityManager.java delete mode 100644 app/server/ruby/vendor/thread_safe/test/test_array.rb delete mode 100644 app/server/ruby/vendor/thread_safe/test/test_cache.rb delete mode 100644 app/server/ruby/vendor/thread_safe/test/test_cache_loops.rb delete mode 100644 app/server/ruby/vendor/thread_safe/test/test_hash.rb delete mode 100644 app/server/ruby/vendor/thread_safe/test/test_helper.rb delete mode 100644 app/server/ruby/vendor/thread_safe/test/test_synchronized_delegator.rb delete mode 100644 app/server/ruby/vendor/thread_safe/thread_safe.gemspec create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/.editorconfig create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/.github/workflows/tests.yml create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/.gitignore create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/.yardopts create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/CHANGES.md create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/Gemfile create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/LICENSE create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/README.md create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/Rakefile create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/gem-public_cert.pem create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/annual_rules.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/country.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/country_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_source.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/country_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/data_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/linked_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/posix_time_zone_parser.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/ruby_data_source.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/transitions_data_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/zoneinfo_data_source.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_sources/zoneinfo_reader.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/data_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/datetime_with_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format1.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format1/country_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format1/country_index_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format1/timezone_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format1/timezone_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format1/timezone_index_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format2.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format2/country_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format2/country_index_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format2/country_index_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format2/timezone_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format2/timezone_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format2/timezone_index_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/format2/timezone_index_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/info_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/linked_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/offset_timezone_period.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/ruby_core_support.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/string_deduper.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/time_with_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/timestamp.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/timestamp_with_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/timezone_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/timezone_period.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/timezone_proxy.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/timezone_transition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/transition_rule.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/transitions_timezone_period.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/version.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/lib/tzinfo/with_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_constant_offset_data_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_country_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_data_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_linked_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_posix_time_zone_parser.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_ruby_data_source.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_transitions_data_timezone_info.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_zoneinfo_data_source.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/data_sources/tc_zoneinfo_reader.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format1/tc_country_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format1/tc_country_index_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format1/tc_timezone_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format1/tc_timezone_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format1/tc_timezone_index_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format2/tc_country_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format2/tc_country_index_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format2/tc_country_index_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format2/tc_timezone_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format2/tc_timezone_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format2/tc_timezone_index_definer.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/format2/tc_timezone_index_definition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_annual_rules.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_country.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_country_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_data_source.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_data_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_datetime_with_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_info_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_linked_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_offset_timezone_period.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_ruby_core_support.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_ruby_time_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_string_deduper.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_time_with_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timestamp.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timestamp_with_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone_london.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone_melbourne.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone_new_york.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone_period.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone_proxy.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone_transition.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_timezone_utc.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_transition_rule.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_transitions_timezone_period.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_tzinfo.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_version.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tc_with_offset.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/test_utils.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/ts_all.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/ts_all_ruby_format1.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/ts_all_ruby_format2.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/ts_all_zoneinfo.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/America/New_York.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Australia/Melbourne.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/EST.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Etc/GMT__m__1.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Etc/GMT__p__1.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Etc/UTC.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Europe/Amsterdam.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Europe/Andorra.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Europe/London.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Europe/Paris.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Europe/Prague.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/Invalid/Incorrect_Module.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/definitions/UTC.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/indexes/countries.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/indexes/timezones.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data1/tzinfo/data/version.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/America/New_York.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Australia/Melbourne.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/EST.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Etc/GMT__m__1.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Etc/GMT__p__1.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Etc/UTC.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Europe/Amsterdam.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Europe/Andorra.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Europe/London.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Europe/Paris.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Europe/Prague.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/Invalid/Incorrect_Module.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/definitions/UTC.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/indexes/countries.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/indexes/timezones.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/tzinfo-data2/tzinfo/data/version.rb create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/.gitignore create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/America/Argentina/Buenos_Aires create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/America/New_York create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/Australia/Melbourne create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/EST create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/Europe/Amsterdam create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/Europe/Andorra create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/Europe/London create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/Europe/Paris create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/Europe/Prague create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/Factory create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/iso3166.tab create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/leapseconds create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/posix/Europe/London create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/posixrules create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/right/Europe/London create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/zone.tab create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/test/zoneinfo/zone1970.tab create mode 100644 app/server/ruby/vendor/tzinfo-2.0.6/tzinfo.gemspec diff --git a/LICENSE.md b/LICENSE.md index ea3d7d1195..9d41fc28ce 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -128,8 +128,8 @@ relevant licenses: (contents of [app/server/ruby/vendor/](https://github.com/sonic-pi-net/sonic-pi/tree/stable/app/server/ruby/vendor)) - [ActiveSupport](https://github.com/rails/rails/tree/master/activesupport) - [MIT License](http://opensource.org/licenses/MIT) -- [Atomic](http://github.com/headius/ruby-atomic) - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) - [Blankslate](https://github.com/masover/blankslate) - [MIT License](http://opensource.org/licensesMIT) +- [Concurrent Ruby](https://github.com/ruby-concurrency/concurrent-ruby) - [MIT License](http://opensource.org/licensesMIT) - [gettext](https://github.com/ruby-gettext/gettext) - [Ruby License](https://www.ruby-lang.org/en/about/license.txt) - [i81n](https://github.com/svenfuchs/i18n) - [MIT License](http://opensource.org/licenses/MIT) - [Kramdown](http://kramdown.gettalong.org) - [MIT License](http://opensource.org/licenses/MIT) @@ -141,12 +141,10 @@ relevant licenses: - [Multi JSON](https://github.com/intridea/multi_json) - [MIT License](http://opensource.org/licenses/MIT) - [Rouge](https://github.com/jneen/rouge) - [MIT License](http://opensource.org/licenses/MIT) - [Ruby Beautify](https://github.com/erniebrodeur/ruby-beautify) - [MIT License](http://opensource.org/licenses/MIT) -- [Ruby CoreAudio](https://github.com/nagachika/ruby-coreaudio) - [MIT License](http://opensource.org/licenses/MIT) -- [Ruby Prof](https://github.com/ruby-prof/ruby-prof) - [BSD 2-Clause License](http://opensource.org/licenses/BSD-2-Clause) - [MIT License](http://opensource.org/licenses/MIT) - [Rugged](https://github.com/libgit2/rugged) - [MIT License](http://opensource.org/licenses/MIT) - [Text](https://github.com/threedaymonk/text) - [MIT License](http://opensource.org/licenses/MIT) -- [Thread Safe](https://github.com/ruby-concurrency/thread_safe) - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) - [Tomlrb](https://github.com/fbernier/tomlrb) - [MIT License](http://opensource.org/licenses/MIT) +- [TZ Info](https://github.com/tzinfo/tzinfo) - [MIT License](http://opensource.org/licenses/MIT) - [WaveFile](https://github.com/jstrait/wavefile/) - [MIT License](http://opensource.org/licenses/MIT) ### Dynamically Linked Libraries for Tau IO Server NIFs diff --git a/app/server/ruby/bin/compile-extensions.rb b/app/server/ruby/bin/compile-extensions.rb index 87f983e975..8079ec3a47 100755 --- a/app/server/ruby/bin/compile-extensions.rb +++ b/app/server/ruby/bin/compile-extensions.rb @@ -43,8 +43,14 @@ # Rugged is used for storing the user's ruby music scripts in Git # FFI is used for MIDI lib support native_ext_dirs = [ - File.expand_path(File.dirname(__FILE__) + '/../vendor/rugged-1.6.3/ext/rugged'), - File.expand_path(File.dirname(__FILE__) + '/../vendor/atomic/ext'), + [ + File.expand_path(File.dirname(__FILE__) + '/../vendor/rugged-1.6.3/ext/rugged'), + "rugged" + ], + [ + File.expand_path(File.dirname(__FILE__) + '/../vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext'), + "concurrent" + ] ] diff --git a/app/server/ruby/vendor/activesupport-7.0.6/CHANGELOG.md b/app/server/ruby/vendor/activesupport-7.0.6/CHANGELOG.md new file mode 100644 index 0000000000..1fa648a7d3 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/CHANGELOG.md @@ -0,0 +1,515 @@ +## Rails 7.0.6 (June 29, 2023) ## + +* Fix `EncryptedConfiguration` returning incorrect values for some `Hash` + methods + + *Hartley McGuire* + +* Fix arguments being destructed `Enumerable#many?` with block. + + *Andrew Novoselac* + +* Fix humanize for strings ending with id. + + *fatkodima* + + +## Rails 7.0.5.1 (June 26, 2023) ## + +* No changes. + + +## Rails 7.0.5 (May 24, 2023) ## + +* Fixes TimeWithZone ArgumentError. + + *Niklas Häusele* + + +## Rails 7.0.4.3 (March 13, 2023) ## + +* Implement SafeBuffer#bytesplice + + [CVE-2023-28120] + + +## Rails 7.0.4.2 (January 24, 2023) ## + +* No changes. + + +## Rails 7.0.4.1 (January 17, 2023) ## + +* Avoid regex backtracking in Inflector.underscore + + [CVE-2023-22796] + + +## Rails 7.0.4 (September 09, 2022) ## + +* Ensure `ActiveSupport::Testing::Isolation::Forking` closes pipes + + Previously, `Forking.run_in_isolation` opened two ends of a pipe. The fork + process closed the read end, wrote to it, and then terminated (which + presumably closed the file descriptors on its end). The parent process + closed the write end, read from it, and returned, never closing the read + end. + + This resulted in an accumulation of open file descriptors, which could + cause errors if the limit is reached. + + *Sam Bostock* + +* Redis cache store is now compatible with redis-rb 5.0. + + *Jean Boussier* + +* Fix `NoMethodError` on custom `ActiveSupport::Deprecation` behavior. + + `ActiveSupport::Deprecation.behavior=` was supposed to accept any object + that responds to `call`, but in fact its internal implementation assumed that + this object could respond to `arity`, so it was restricted to only `Proc` objects. + + This change removes this `arity` restriction of custom behaviors. + + *Ryo Nakamura* + + +## Rails 7.0.3.1 (July 12, 2022) ## + +* No changes. + + +## Rails 7.0.3 (May 09, 2022) ## + +* No changes. + + +## Rails 7.0.2.4 (April 26, 2022) ## + +* Fix and add protections for XSS in `ActionView::Helpers` and `ERB::Util`. + + Add the method `ERB::Util.xml_name_escape` to escape dangerous characters + in names of tags and names of attributes, following the specification of XML. + + *Álvaro Martín Fraguas* + +## Rails 7.0.2.3 (March 08, 2022) ## + +* No changes. + + +## Rails 7.0.2.2 (February 11, 2022) ## + +* Fix Reloader method signature to work with the new Executor signature + + +## Rails 7.0.2.1 (February 11, 2022) ## + +* No changes. + + +## Rails 7.0.2 (February 08, 2022) ## + +* Fix `ActiveSupport::EncryptedConfiguration` to be compatible with Psych 4 + + *Stephen Sugden* + +* Improve `File.atomic_write` error handling. + + *Daniel Pepper* + + +## Rails 7.0.1 (January 06, 2022) ## + +* Fix `Class#descendants` and `DescendantsTracker#descendants` compatibility with Ruby 3.1. + + [The native `Class#descendants` was reverted prior to Ruby 3.1 release](https://bugs.ruby-lang.org/issues/14394#note-33), + but `Class#subclasses` was kept, breaking the feature detection. + + *Jean Boussier* + + +## Rails 7.0.0 (December 15, 2021) ## + +* Fix `ActiveSupport::Duration.build` to support negative values. + + The algorithm to collect the `parts` of the `ActiveSupport::Duration` + ignored the sign of the `value` and accumulated incorrect part values. This + impacted `ActiveSupport::Duration#sum` (which is dependent on `parts`) but + not `ActiveSupport::Duration#eql?` (which is dependent on `value`). + + *Caleb Buxton*, *Braden Staudacher* + + +## Rails 7.0.0.rc3 (December 14, 2021) ## + +* No changes. + + +## Rails 7.0.0.rc2 (December 14, 2021) ## + +* No changes. + +## Rails 7.0.0.rc1 (December 06, 2021) ## + +* Deprecate passing a format to `#to_s` in favor of `#to_formatted_s` in `Array`, `Range`, `Date`, `DateTime`, `Time`, + `BigDecimal`, `Float` and, `Integer`. + + *Rafael Mendonça França* + +* Document `ActiveSupport::Testing::Deprecation`. + + *Sam Bostock & Sam Jordan* + +* Add `Pathname#existence`. + + ```ruby + Pathname.new("file").existence&.read + ``` + + *Timo Schilling* + +* Remove deprecate `ActiveSupport::Multibyte::Unicode.default_normalization_form`. + + *Rafael Mendonça França* + +* Remove deprecated support to use `Range#include?` to check the inclusion of a value in + a date time range is deprecated. + + *Rafael Mendonça França* + +* Remove deprecated `URI.parser`. + + *Rafael Mendonça França* + +* Remove deprecated `config.active_support.use_sha1_digests`. + + *Rafael Mendonça França* + +* Invoking `Object#with_options` without a `&block` argument returns the + `ActiveSupport::OptionMerger` instance. + + *Sean Doyle* + +* `Rails.application.executor` hooks can now be called around every test + + This helps to better simulate request or job local state being reset around tests and prevents state + leaking from one test to another. + + However it requires the executor hooks executed in the test environment to be re-entrant. + + To enable this, set `config.active_support.executor_around_test_case = true` (this is the default in Rails 7). + + *Jean Boussier* + +* `ActiveSupport::DescendantsTracker` now mostly delegate to `Class#descendants` on Ruby 3.1 + + Ruby now provides a fast `Class#descendants` making `ActiveSupport::DescendantsTracker` mostly useless. + + As a result the following methods are deprecated: + + - `ActiveSupport::DescendantsTracker.direct_descendants` + - `ActiveSupport::DescendantsTracker#direct_descendants` + + *Jean Boussier* + +* Fix the `Digest::UUID.uuid_from_hash` behavior for namespace IDs that are different from the ones defined on `Digest::UUID`. + + The new behavior will be enabled by setting the + `config.active_support.use_rfc4122_namespaced_uuids` option to `true` + and is the default for new apps. + + The old behavior is the default for upgraded apps and will output a + deprecation warning every time a value that is different than one of + the constants defined on the `Digest::UUID` extension is used as the + namespace ID. + + *Alex Robbin*, *Erich Soares Machado*, *Eugene Kenny* + +* `ActiveSupport::Inflector::Inflections#clear(:acronyms)` is now supported, + and `inflector.clear` / `inflector.clear(:all)` also clears acronyms. + + *Alex Ghiculescu*, *Oliver Peate* + + +## Rails 7.0.0.alpha2 (September 15, 2021) ## + +* No changes. + + +## Rails 7.0.0.alpha1 (September 15, 2021) ## + +* `ActiveSupport::Dependencies` no longer installs a `const_missing` hook. Before this, you could push to the autoload paths and have constants autoloaded. This feature, known as the `classic` autoloader, has been removed. + + *Xavier Noria* + +* Private internal classes of `ActiveSupport::Dependencies` have been deleted, like `ActiveSupport::Dependencies::Reference`, `ActiveSupport::Dependencies::Blamable`, and others. + + *Xavier Noria* + +* The private API of `ActiveSupport::Dependencies` has been deleted. That includes methods like `hook!`, `unhook!`, `depend_on`, `require_or_load`, `mechanism`, and many others. + + *Xavier Noria* + +* Improves the performance of `ActiveSupport::NumberHelper` formatters by avoiding the use of exceptions as flow control. + + *Mike Dalessio* + +* Removed rescue block from `ActiveSupport::Cache::RedisCacheStore#handle_exception` + + Previously, if you provided a `error_handler` to `redis_cache_store`, any errors thrown by + the error handler would be rescued and logged only. Removed the `rescue` clause from `handle_exception` + to allow these to be thrown. + + *Nicholas A. Stuart* + +* Allow entirely opting out of deprecation warnings. + + Previously if you did `app.config.active_support.deprecation = :silence`, some work would + still be done on each call to `ActiveSupport::Deprecation.warn`. In very hot paths, this could + cause performance issues. + + Now, you can make `ActiveSupport::Deprecation.warn` a no-op: + + ```ruby + config.active_support.report_deprecations = false + ``` + + This is the default in production for new apps. It is the equivalent to: + + ```ruby + config.active_support.deprecation = :silence + config.active_support.disallowed_deprecation = :silence + ``` + + but will take a more optimised code path. + + *Alex Ghiculescu* + +* Faster tests by parallelizing only when overhead is justified by the number + of them. + + Running tests in parallel adds overhead in terms of database + setup and fixture loading. Now, Rails will only parallelize test executions when + there are enough tests to make it worth it. + + This threshold is 50 by default, and is configurable via config setting in + your test.rb: + + ```ruby + config.active_support.test_parallelization_threshold = 100 + ``` + + It's also configurable at the test case level: + + ```ruby + class ActiveSupport::TestCase + parallelize threshold: 100 + end + ``` + + *Jorge Manrubia* + +* OpenSSL constants are now used for Digest computations. + + *Dirkjan Bussink* + +* `TimeZone.iso8601` now accepts valid ordinal values similar to Ruby's `Date._iso8601` method. + A valid ordinal value will be converted to an instance of `TimeWithZone` using the `:year` + and `:yday` fragments returned from `Date._iso8601`. + + ```ruby + twz = ActiveSupport::TimeZone["Eastern Time (US & Canada)"].iso8601("21087") + twz.to_a[0, 6] == [0, 0, 0, 28, 03, 2021] + ``` + + *Steve Laing* + +* `Time#change` and methods that call it (e.g. `Time#advance`) will now + return a `Time` with the timezone argument provided, if the caller was + initialized with a timezone argument. + + Fixes [#42467](https://github.com/rails/rails/issues/42467). + + *Alex Ghiculescu* + +* Allow serializing any module or class to JSON by name. + + *Tyler Rick*, *Zachary Scott* + +* Raise `ActiveSupport::EncryptedFile::MissingKeyError` when the + `RAILS_MASTER_KEY` environment variable is blank (e.g. `""`). + + *Sunny Ripert* + +* The `from:` option is added to `ActiveSupport::TestCase#assert_no_changes`. + + It permits asserting on the initial value that is expected not to change. + + ```ruby + assert_no_changes -> { Status.all_good? }, from: true do + post :create, params: { status: { ok: true } } + end + ``` + + *George Claghorn* + +* Deprecate `ActiveSupport::SafeBuffer`'s incorrect implicit conversion of objects into string. + + Except for a few methods like `String#%`, objects must implement `#to_str` + to be implicitly converted to a String in string operations. In some + circumstances `ActiveSupport::SafeBuffer` was incorrectly calling the + explicit conversion method (`#to_s`) on them. This behavior is now + deprecated. + + *Jean Boussier* + +* Allow nested access to keys on `Rails.application.credentials`. + + Previously only top level keys in `credentials.yml.enc` could be accessed with method calls. Now any key can. + + For example, given these secrets: + + ```yml + aws: + access_key_id: 123 + secret_access_key: 345 + ``` + + `Rails.application.credentials.aws.access_key_id` will now return the same thing as + `Rails.application.credentials.aws[:access_key_id]`. + + *Alex Ghiculescu* + +* Added a faster and more compact `ActiveSupport::Cache` serialization format. + + It can be enabled with `config.active_support.cache_format_version = 7.0` or + `config.load_defaults 7.0`. Regardless of the configuration Active Support + 7.0 can read cache entries serialized by Active Support 6.1 which allows to + upgrade without invalidating the cache. However Rails 6.1 can't read the + new format, so all readers must be upgraded before the new format is enabled. + + *Jean Boussier* + +* Add `Enumerable#sole`, per `ActiveRecord::FinderMethods#sole`. Returns the + sole item of the enumerable, raising if no items are found, or if more than + one is. + + *Asherah Connor* + +* Freeze `ActiveSupport::Duration#parts` and remove writer methods. + + Durations are meant to be value objects and should not be mutated. + + *Andrew White* + +* Fix `ActiveSupport::TimeZone#utc_to_local` with fractional seconds. + + When `utc_to_local_returns_utc_offset_times` is false and the time + instance had fractional seconds the new UTC time instance was out by + a factor of 1,000,000 as the `Time.utc` constructor takes a usec + value and not a fractional second value. + + *Andrew White* + +* Add `expires_at` argument to `ActiveSupport::Cache` `write` and `fetch` to set a cache entry TTL as an absolute time. + + ```ruby + Rails.cache.write(key, value, expires_at: Time.now.at_end_of_hour) + ``` + + *Jean Boussier* + +* Deprecate `ActiveSupport::TimeWithZone.name` so that from Rails 7.1 it will use the default implementation. + + *Andrew White* + +* Deprecates Rails custom `Enumerable#sum` and `Array#sum` in favor of Ruby's native implementation which + is considerably faster. + + Ruby requires an initializer for non-numeric type as per examples below: + + ```ruby + %w[foo bar].sum('') + # instead of %w[foo bar].sum + + [[1, 2], [3, 4, 5]].sum([]) + # instead of [[1, 2], [3, 4, 5]].sum + ``` + + *Alberto Mota* + +* Tests parallelization is now disabled when running individual files to prevent the setup overhead. + + It can still be enforced if the environment variable `PARALLEL_WORKERS` is present and set to a value greater than 1. + + *Ricardo Díaz* + +* Fix proxying keyword arguments in `ActiveSupport::CurrentAttributes`. + + *Marcin Kołodziej* + +* Add `Enumerable#maximum` and `Enumerable#minimum` to easily calculate the maximum or minimum from extracted + elements of an enumerable. + + ```ruby + payments = [Payment.new(5), Payment.new(15), Payment.new(10)] + + payments.minimum(:price) # => 5 + payments.maximum(:price) # => 15 + ``` + + This also allows passing enumerables to `fresh_when` and `stale?` in Action Controller. + See PR [#41404](https://github.com/rails/rails/pull/41404) for an example. + + *Ayrton De Craene* + +* `ActiveSupport::Cache::MemCacheStore` now accepts an explicit `nil` for its `addresses` argument. + + ```ruby + config.cache_store = :mem_cache_store, nil + + # is now equivalent to + + config.cache_store = :mem_cache_store + + # and is also equivalent to + + config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211" + + # which is the fallback behavior of Dalli + ``` + + This helps those migrating from `:dalli_store`, where an explicit `nil` was permitted. + + *Michael Overmeyer* + +* Add `Enumerable#in_order_of` to put an Enumerable in a certain order by a key. + + *DHH* + +* `ActiveSupport::Inflector.camelize` behaves expected when provided a symbol `:upper` or `:lower` argument. Matches + `String#camelize` behavior. + + *Alex Ghiculescu* + +* Raises an `ArgumentError` when the first argument of `ActiveSupport::Notification.subscribe` is + invalid. + + *Vipul A M* + +* `HashWithIndifferentAccess#deep_transform_keys` now returns a `HashWithIndifferentAccess` instead of a `Hash`. + + *Nathaniel Woodthorpe* + +* Consume dalli’s `cache_nils` configuration as `ActiveSupport::Cache`'s `skip_nil` when using `MemCacheStore`. + + *Ritikesh G* + +* Add `RedisCacheStore#stats` method similar to `MemCacheStore#stats`. Calls `redis#info` internally. + + *Ritikesh G* + + +Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/app/server/ruby/vendor/activesupport/MIT-LICENSE b/app/server/ruby/vendor/activesupport-7.0.6/MIT-LICENSE similarity index 89% rename from app/server/ruby/vendor/activesupport/MIT-LICENSE rename to app/server/ruby/vendor/activesupport-7.0.6/MIT-LICENSE index d06d4f3b2d..0a0ce3889a 100644 --- a/app/server/ruby/vendor/activesupport/MIT-LICENSE +++ b/app/server/ruby/vendor/activesupport-7.0.6/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2014 David Heinemeier Hansson +Copyright (c) 2005-2022 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/server/ruby/vendor/activesupport-7.0.6/README.rdoc b/app/server/ruby/vendor/activesupport-7.0.6/README.rdoc new file mode 100644 index 0000000000..469b8e9233 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/README.rdoc @@ -0,0 +1,40 @@ += Active Support -- Utility classes and Ruby extensions from Rails + +Active Support is a collection of utility classes and standard library +extensions that were found useful for the Rails framework. These additions +reside in this package so they can be loaded as needed in Ruby projects +outside of Rails. + +You can read more about the extensions in the {Active Support Core Extensions}[https://edgeguides.rubyonrails.org/active_support_core_extensions.html] guide. + +== Download and installation + +The latest version of Active Support can be installed with RubyGems: + + $ gem install activesupport + +Source code can be downloaded as part of the \Rails project on GitHub: + +* https://github.com/rails/rails/tree/main/activesupport + + +== License + +Active Support is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* https://api.rubyonrails.org + +Bug reports for the Ruby on \Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://discuss.rubyonrails.org/c/rubyonrails-core diff --git a/app/server/ruby/vendor/activesupport/lib/active_support.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support.rb similarity index 60% rename from app/server/ruby/vendor/activesupport/lib/active_support.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support.rb index ab0054b339..e04796bfbf 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + #-- -# Copyright (c) 2005-2014 David Heinemeier Hansson +# Copyright (c) 2005-2022 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -21,21 +23,36 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'securerandom' +require "securerandom" require "active_support/dependencies/autoload" require "active_support/version" require "active_support/logger" require "active_support/lazy_load_hooks" +require "active_support/core_ext/date_and_time/compatibility" module ActiveSupport extend ActiveSupport::Autoload autoload :Concern + autoload :CodeGenerator + autoload :ActionableError + autoload :ConfigurationFile + autoload :CurrentAttributes autoload :Dependencies autoload :DescendantsTracker + autoload :ExecutionContext + autoload :ExecutionWrapper + autoload :Executor + autoload :ErrorReporter autoload :FileUpdateChecker + autoload :EventedFileUpdateChecker + autoload :ForkTracker autoload :LogSubscriber + autoload :IsolatedExecutionState autoload :Notifications + autoload :Reloader + autoload :PerThreadRegistry + autoload :SecureCompareRotator eager_autoload do autoload :BacktraceCleaner @@ -45,6 +62,7 @@ module ActiveSupport autoload :Callbacks autoload :Configurable autoload :Deprecation + autoload :Digest autoload :Gzip autoload :Inflector autoload :JSON @@ -57,8 +75,10 @@ module ActiveSupport autoload :OrderedHash autoload :OrderedOptions autoload :StringInquirer + autoload :EnvironmentInquirer autoload :TaggedLogging autoload :XmlMini + autoload :ArrayInquirer end autoload :Rescuable @@ -70,6 +90,35 @@ def self.eager_load! NumberHelper.eager_load! end + + cattr_accessor :test_order # :nodoc: + cattr_accessor :test_parallelization_threshold, default: 50 # :nodoc: + + singleton_class.attr_accessor :error_reporter # :nodoc: + + def self.cache_format_version + Cache.format_version + end + + def self.cache_format_version=(value) + Cache.format_version = value + end + + def self.to_time_preserves_timezone + DateAndTime::Compatibility.preserve_timezone + end + + def self.to_time_preserves_timezone=(value) + DateAndTime::Compatibility.preserve_timezone = value + end + + def self.utc_to_local_returns_utc_offset_times + DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times + end + + def self.utc_to_local_returns_utc_offset_times=(value) + DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times = value + end end autoload :I18n, "active_support/i18n" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/actionable_error.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/actionable_error.rb new file mode 100644 index 0000000000..2b8b2ff558 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/actionable_error.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveSupport + # Actionable errors lets you define actions to resolve an error. + # + # To make an error actionable, include the ActiveSupport::ActionableError + # module and invoke the +action+ class macro to define the action. An action + # needs a name and a block to execute. + module ActionableError + extend Concern + + class NonActionable < StandardError; end + + included do + class_attribute :_actions, default: {} + end + + def self.actions(error) # :nodoc: + case error + when ActionableError, -> it { Class === it && it < ActionableError } + error._actions + else + {} + end + end + + def self.dispatch(error, name) # :nodoc: + actions(error).fetch(name).call + rescue KeyError + raise NonActionable, "Cannot find action \"#{name}\"" + end + + module ClassMethods + # Defines an action that can resolve the error. + # + # class PendingMigrationError < MigrationError + # include ActiveSupport::ActionableError + # + # action "Run pending migrations" do + # ActiveRecord::Tasks::DatabaseTasks.migrate + # end + # end + def action(name, &block) + _actions[name] = block + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/all.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/all.rb new file mode 100644 index 0000000000..4adf446af8 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/all.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/time" +require "active_support/core_ext" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/array_inquirer.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/array_inquirer.rb new file mode 100644 index 0000000000..ecd2389ce1 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/array_inquirer.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveSupport + # Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check + # its string-like contents: + # + # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) + # + # variants.phone? # => true + # variants.tablet? # => true + # variants.desktop? # => false + class ArrayInquirer < Array + # Passes each element of +candidates+ collection to ArrayInquirer collection. + # The method returns true if any element from the ArrayInquirer collection + # is equal to the stringified or symbolized form of any element in the +candidates+ collection. + # + # If +candidates+ collection is not given, method returns true. + # + # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) + # + # variants.any? # => true + # variants.any?(:phone, :tablet) # => true + # variants.any?('phone', 'desktop') # => true + # variants.any?(:desktop, :watch) # => false + def any?(*candidates) + if candidates.none? + super + else + candidates.any? do |candidate| + include?(candidate.to_sym) || include?(candidate.to_s) + end + end + end + + private + def respond_to_missing?(name, include_private = false) + name.end_with?("?") || super + end + + def method_missing(name, *args) + if name.end_with?("?") + any?(name[0..-2]) + else + super + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/backtrace_cleaner.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/backtrace_cleaner.rb similarity index 62% rename from app/server/ruby/vendor/activesupport/lib/active_support/backtrace_cleaner.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/backtrace_cleaner.rb index d58578b7bc..d570f21d58 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/backtrace_cleaner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveSupport # Backtraces often include many lines that are not relevant for the context # under review. This makes it hard to find the signal amongst the backtrace @@ -12,23 +14,26 @@ module ActiveSupport # is to exclude the output of a noisy library from the backtrace, so that you # can focus on the rest. # - # bc = BacktraceCleaner.new - # bc.add_filter { |line| line.gsub(Rails.root, '') } # strip the Rails.root prefix - # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # skip any lines from mongrel or rubygems + # bc = ActiveSupport::BacktraceCleaner.new + # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix + # bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems # bc.clean(exception.backtrace) # perform the cleanup # # To reconfigure an existing BacktraceCleaner (like the default one in Rails) # and show as much data as possible, you can always call - # BacktraceCleaner#remove_silencers!, which will restore the + # BacktraceCleaner#remove_silencers!, which will restore the # backtrace to a pristine state. If you need to reconfigure an existing # BacktraceCleaner so that it does not filter or modify the paths of any lines - # of the backtrace, you can call BacktraceCleaner#remove_filters! + # of the backtrace, you can call BacktraceCleaner#remove_filters! # These two methods will give you a completely untouched backtrace. # - # Inspired by the Quiet Backtrace gem by Thoughtbot. + # Inspired by the Quiet Backtrace gem by thoughtbot. class BacktraceCleaner def initialize @filters, @silencers = [], [] + add_gem_filter + add_gem_silencer + add_stdlib_silencer end # Returns the backtrace after all filters and silencers have been run @@ -59,20 +64,20 @@ def add_filter(&block) # Adds a silencer from the block provided. If the silencer returns +true+ # for a given line, it will be excluded from the clean backtrace. # - # # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb" - # backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ } + # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb" + # backtrace_cleaner.add_silencer { |line| /puma/.match?(line) } def add_silencer(&block) @silencers << block end - # Will remove all silencers, but leave in the filters. This is useful if - # your context of debugging suddenly expands as you suspect a bug in one of + # Removes all silencers, but leaves in the filters. Useful if your + # context of debugging suddenly expands as you suspect a bug in one of # the libraries you use. def remove_silencers! @silencers = [] end - # Removes all filters, but leaves in silencers. Useful if you suddenly + # Removes all filters, but leaves in the silencers. Useful if you suddenly # need to see entire filepaths in the backtrace that you had already # filtered out. def remove_filters! @@ -80,6 +85,25 @@ def remove_filters! end private + FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) / + + def add_gem_filter + gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) } + return if gems_paths.empty? + + gems_regexp = %r{\A(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)} + gems_result = '\3 (\4) \5' + add_filter { |line| line.sub(gems_regexp, gems_result) } + end + + def add_gem_silencer + add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) } + end + + def add_stdlib_silencer + add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) } + end + def filter_backtrace(backtrace) @filters.each do |f| backtrace = backtrace.map { |line| f.call(line) } @@ -97,7 +121,11 @@ def silence(backtrace) end def noise(backtrace) - backtrace - silence(backtrace) + backtrace.select do |line| + @silencers.any? do |s| + s.call(line) + end + end end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/benchmarkable.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/benchmarkable.rb similarity index 82% rename from app/server/ruby/vendor/activesupport/lib/active_support/benchmarkable.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/benchmarkable.rb index 805b7a714f..4060784f67 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/benchmarkable.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/benchmarkable.rb @@ -1,5 +1,7 @@ -require 'active_support/core_ext/benchmark' -require 'active_support/core_ext/hash/keys' +# frozen_string_literal: true + +require "active_support/core_ext/benchmark" +require "active_support/core_ext/hash/keys" module ActiveSupport module Benchmarkable @@ -32,14 +34,14 @@ module Benchmarkable # <% benchmark 'Process data files', level: :info, silence: true do %> # <%= expensive_and_chatty_files_operation %> # <% end %> - def benchmark(message = "Benchmarking", options = {}) + def benchmark(message = "Benchmarking", options = {}, &block) if logger options.assert_valid_keys(:level, :silence) options[:level] ||= :info result = nil - ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield } - logger.send(options[:level], '%s (%.1fms)' % [ message, ms ]) + ms = Benchmark.ms { result = options[:silence] ? logger.silence(&block) : yield } + logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ]) result else yield diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/builder.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/builder.rb similarity index 76% rename from app/server/ruby/vendor/activesupport/lib/active_support/builder.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/builder.rb index 321e462acd..3fa7e6b26d 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/builder.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/builder.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + begin - require 'builder' + require "builder" rescue LoadError => e $stderr.puts "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install" raise e diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache.rb new file mode 100644 index 0000000000..a059c1ec3b --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache.rb @@ -0,0 +1,1030 @@ +# frozen_string_literal: true + +require "zlib" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/numeric/bytes" +require "active_support/core_ext/numeric/time" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/try" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + # See ActiveSupport::Cache::Store for documentation. + module Cache + autoload :FileStore, "active_support/cache/file_store" + autoload :MemoryStore, "active_support/cache/memory_store" + autoload :MemCacheStore, "active_support/cache/mem_cache_store" + autoload :NullStore, "active_support/cache/null_store" + autoload :RedisCacheStore, "active_support/cache/redis_cache_store" + + # These options mean something to all cache implementations. Individual cache + # implementations may support additional options. + UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :expire_in, :expired_in, :race_condition_ttl, :coder, :skip_nil] + + DEFAULT_COMPRESS_LIMIT = 1.kilobyte + + # Mapping of canonical option names to aliases that a store will recognize. + OPTION_ALIASES = { + expires_in: [:expire_in, :expired_in] + }.freeze + + module Strategy + autoload :LocalCache, "active_support/cache/strategy/local_cache" + end + + @format_version = 6.1 + + class << self + attr_accessor :format_version + + # Creates a new Store object according to the given options. + # + # If no arguments are passed to this method, then a new + # ActiveSupport::Cache::MemoryStore object will be returned. + # + # If you pass a Symbol as the first argument, then a corresponding cache + # store class under the ActiveSupport::Cache namespace will be created. + # For example: + # + # ActiveSupport::Cache.lookup_store(:memory_store) + # # => returns a new ActiveSupport::Cache::MemoryStore object + # + # ActiveSupport::Cache.lookup_store(:mem_cache_store) + # # => returns a new ActiveSupport::Cache::MemCacheStore object + # + # Any additional arguments will be passed to the corresponding cache store + # class's constructor: + # + # ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache') + # # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache') + # + # If the first argument is not a Symbol, then it will simply be returned: + # + # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) + # # => returns MyOwnCacheStore.new + def lookup_store(store = nil, *parameters) + case store + when Symbol + options = parameters.extract_options! + # clean this up once Ruby 2.7 support is dropped + # see https://github.com/rails/rails/pull/41522#discussion_r581186602 + if options.empty? + retrieve_store_class(store).new(*parameters) + else + retrieve_store_class(store).new(*parameters, **options) + end + when Array + lookup_store(*store) + when nil + ActiveSupport::Cache::MemoryStore.new + else + store + end + end + + # Expands out the +key+ argument into a key that can be used for the + # cache store. Optionally accepts a namespace, and all keys will be + # scoped within that namespace. + # + # If the +key+ argument provided is an array, or responds to +to_a+, then + # each of elements in the array will be turned into parameters/keys and + # concatenated into a single key. For example: + # + # ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar" + # ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar" + # + # The +key+ argument can also respond to +cache_key+ or +to_param+. + def expand_cache_key(key, namespace = nil) + expanded_cache_key = namespace ? +"#{namespace}/" : +"" + + if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] + expanded_cache_key << "#{prefix}/" + end + + expanded_cache_key << retrieve_cache_key(key) + expanded_cache_key + end + + private + def retrieve_cache_key(key) + case + when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version + when key.respond_to?(:cache_key) then key.cache_key + when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) + else key.to_param + end.to_s + end + + # Obtains the specified cache store class, given the name of the +store+. + # Raises an error when the store class cannot be found. + def retrieve_store_class(store) + # require_relative cannot be used here because the class might be + # provided by another gem, like redis-activesupport for example. + require "active_support/cache/#{store}" + rescue LoadError => e + raise "Could not find cache store adapter for #{store} (#{e})" + else + ActiveSupport::Cache.const_get(store.to_s.camelize) + end + end + + # An abstract cache store class. There are multiple cache store + # implementations, each having its own additional features. See the classes + # under the ActiveSupport::Cache module, e.g. + # ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most + # popular cache store for large production websites. + # + # Some implementations may not support all methods beyond the basic cache + # methods of #fetch, #write, #read, #exist?, and #delete. + # + # ActiveSupport::Cache::Store can store any Ruby object that is supported by + # its +coder+'s +dump+ and +load+ methods. + # + # cache = ActiveSupport::Cache::MemoryStore.new + # + # cache.read('city') # => nil + # cache.write('city', "Duckburgh") + # cache.read('city') # => "Duckburgh" + # + # cache.write('not serializable', Proc.new {}) # => TypeError + # + # Keys are always translated into Strings and are case sensitive. When an + # object is specified as a key and has a +cache_key+ method defined, this + # method will be called to define the key. Otherwise, the +to_param+ + # method will be called. Hashes and Arrays can also be used as keys. The + # elements will be delimited by slashes, and the elements within a Hash + # will be sorted by key so they are consistent. + # + # cache.read('city') == cache.read(:city) # => true + # + # Nil values can be cached. + # + # If your cache is on a shared infrastructure, you can define a namespace + # for your cache entries. If a namespace is defined, it will be prefixed on + # to every key. The namespace can be either a static value or a Proc. If it + # is a Proc, it will be invoked when each key is evaluated so that you can + # use application logic to invalidate keys. + # + # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable + # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace + # + class Store + cattr_accessor :logger, instance_writer: true + + attr_reader :silence, :options + alias :silence? :silence + + class << self + private + def retrieve_pool_options(options) + {}.tap do |pool_options| + pool_options[:size] = options.delete(:pool_size) if options[:pool_size] + pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout] + end + end + + def ensure_connection_pool_added! + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + end + + # Creates a new cache. + # + # ==== Options + # + # * +:namespace+ - Sets the namespace for the cache. This option is + # especially useful if your application shares a cache with other + # applications. + # * +:coder+ - Replaces the default cache entry serialization mechanism + # with a custom one. The +coder+ must respond to +dump+ and +load+. + # Using a custom coder disables automatic compression. + # + # Any other specified options are treated as default options for the + # relevant cache operations, such as #read, #write, and #fetch. + def initialize(options = nil) + @options = options ? normalize_options(options) : {} + @options[:compress] = true unless @options.key?(:compress) + @options[:compress_threshold] = DEFAULT_COMPRESS_LIMIT unless @options.key?(:compress_threshold) + + @coder = @options.delete(:coder) { default_coder } || NullCoder + @coder_supports_compression = @coder.respond_to?(:dump_compressed) + end + + # Silences the logger. + def silence! + @silence = true + self + end + + # Silences the logger within a block. + def mute + previous_silence, @silence = defined?(@silence) && @silence, true + yield + ensure + @silence = previous_silence + end + + # Fetches data from the cache, using the given key. If there is data in + # the cache with the given key, then that data is returned. + # + # If there is no such data in the cache (a cache miss), then +nil+ will be + # returned. However, if a block has been passed, that block will be passed + # the key and executed in the event of a cache miss. The return value of the + # block will be written to the cache under the given cache key, and that + # return value will be returned. + # + # cache.write('today', 'Monday') + # cache.fetch('today') # => "Monday" + # + # cache.fetch('city') # => nil + # cache.fetch('city') do + # 'Duckburgh' + # end + # cache.fetch('city') # => "Duckburgh" + # + # ==== Options + # + # Internally, +fetch+ calls #read_entry, and calls #write_entry on a cache + # miss. Thus, +fetch+ supports the same options as #read and #write. + # Additionally, +fetch+ supports the following options: + # + # * force: true - Forces a cache "miss," meaning we treat the + # cache value as missing even if it's present. Passing a block is + # required when +force+ is true so this always results in a cache write. + # + # cache.write('today', 'Monday') + # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday' + # cache.fetch('today', force: true) # => ArgumentError + # + # The +:force+ option is useful when you're calling some other method to + # ask whether you should force a cache write. Otherwise, it's clearer to + # just call +write+. + # + # * skip_nil: true - Prevents caching a nil result: + # + # cache.fetch('foo') { nil } + # cache.fetch('bar', skip_nil: true) { nil } + # cache.exist?('foo') # => true + # cache.exist?('bar') # => false + # + # * +:race_condition_ttl+ - Specifies the number of seconds during which + # an expired value can be reused while a new value is being generated. + # This can be used to prevent race conditions when cache entries expire, + # by preventing multiple processes from simultaneously regenerating the + # same entry (also known as the dog pile effect). + # + # When a process encounters a cache entry that has expired less than + # +:race_condition_ttl+ seconds ago, it will bump the expiration time by + # +:race_condition_ttl+ seconds before generating a new value. During + # this extended time window, while the process generates a new value, + # other processes will continue to use the old value. After the first + # process writes the new value, other processes will then use it. + # + # If the first process errors out while generating a new value, another + # process can try to generate a new value after the extended time window + # has elapsed. + # + # # Set all values to expire after one minute. + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute) + # + # cache.write('foo', 'original value') + # val_1 = nil + # val_2 = nil + # sleep 60 + # + # Thread.new do + # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do + # sleep 1 + # 'new value 1' + # end + # end + # + # Thread.new do + # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do + # 'new value 2' + # end + # end + # + # cache.fetch('foo') # => "original value" + # sleep 10 # First thread extended the life of cache by another 10 seconds + # cache.fetch('foo') # => "new value 1" + # val_1 # => "new value 1" + # val_2 # => "original value" + # + def fetch(name, options = nil, &block) + if block_given? + options = merged_options(options) + key = normalize_key(name, options) + + entry = nil + instrument(:read, name, options) do |payload| + cached_entry = read_entry(key, **options, event: payload) unless options[:force] + entry = handle_expired_entry(cached_entry, key, options) + entry = nil if entry && entry.mismatched?(normalize_version(name, options)) + payload[:super_operation] = :fetch if payload + payload[:hit] = !!entry if payload + end + + if entry + get_entry_value(entry, name, options) + else + save_block_result_to_cache(name, options, &block) + end + elsif options && options[:force] + raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block." + else + read(name, options) + end + end + + # Reads data from the cache, using the given key. If there is data in + # the cache with the given key, then that data is returned. Otherwise, + # +nil+ is returned. + # + # Note, if data was written with the :expires_in or + # :version options, both of these conditions are applied before + # the data is returned. + # + # ==== Options + # + # * +:version+ - Specifies a version for the cache entry. If the cached + # version does not match the requested version, the read will be treated + # as a cache miss. This feature is used to support recyclable cache keys. + # + # Other options will be handled by the specific cache store implementation. + def read(name, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + version = normalize_version(name, options) + + instrument(:read, name, options) do |payload| + entry = read_entry(key, **options, event: payload) + + if entry + if entry.expired? + delete_entry(key, **options) + payload[:hit] = false if payload + nil + elsif entry.mismatched?(version) + payload[:hit] = false if payload + nil + else + payload[:hit] = true if payload + entry.value + end + else + payload[:hit] = false if payload + nil + end + end + end + + # Reads multiple values at once from the cache. Options can be passed + # in the last argument. + # + # Some cache implementation may optimize this method. + # + # Returns a hash mapping the names provided to the values found. + def read_multi(*names) + options = names.extract_options! + options = merged_options(options) + + instrument :read_multi, names, options do |payload| + read_multi_entries(names, **options, event: payload).tap do |results| + payload[:hits] = results.keys + end + end + end + + # Cache Storage API to write multiple values at once. + def write_multi(hash, options = nil) + options = merged_options(options) + + instrument :write_multi, hash, options do |payload| + entries = hash.each_with_object({}) do |(name, value), memo| + memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options))) + end + + write_multi_entries entries, **options + end + end + + # Fetches data from the cache, using the given keys. If there is data in + # the cache with the given keys, then that data is returned. Otherwise, + # the supplied block is called for each key for which there was no data, + # and the result will be written to the cache and returned. + # Therefore, you need to pass a block that returns the data to be written + # to the cache. If you do not want to write the cache when the cache is + # not found, use #read_multi. + # + # Returns a hash with the data for each of the names. For example: + # + # cache.write("bim", "bam") + # cache.fetch_multi("bim", "unknown_key") do |key| + # "Fallback value for key: #{key}" + # end + # # => { "bim" => "bam", + # # "unknown_key" => "Fallback value for key: unknown_key" } + # + # Options are passed to the underlying cache implementation. For example: + # + # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key| + # "buzz" + # end + # # => {"fizz"=>"buzz"} + # cache.read("fizz") + # # => "buzz" + # sleep(6) + # cache.read("fizz") + # # => nil + def fetch_multi(*names) + raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given? + + options = names.extract_options! + options = merged_options(options) + + instrument :read_multi, names, options do |payload| + reads = read_multi_entries(names, **options) + writes = {} + ordered = names.index_with do |name| + reads.fetch(name) { writes[name] = yield(name) } + end + + payload[:hits] = reads.keys + payload[:super_operation] = :fetch_multi + + write_multi(writes, options) + + ordered + end + end + + # Writes the value to the cache with the key. The value must be supported + # by the +coder+'s +dump+ and +load+ methods. + # + # By default, cache entries larger than 1kB are compressed. Compression + # allows more data to be stored in the same memory footprint, leading to + # fewer cache evictions and higher hit rates. + # + # ==== Options + # + # * compress: false - Disables compression of the cache entry. + # + # * +:compress_threshold+ - The compression threshold, specified in bytes. + # \Cache entries larger than this threshold will be compressed. Defaults + # to +1.kilobyte+. + # + # * +:expires_in+ - Sets a relative expiration time for the cache entry, + # specified in seconds. +:expire_in+ and +:expired_in+ are aliases for + # +:expires_in+. + # + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes) + # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry + # + # * +:expires_at+ - Sets an absolute expiration time for the cache entry. + # + # cache = ActiveSupport::Cache::MemoryStore.new + # cache.write(key, value, expires_at: Time.now.at_end_of_hour) + # + # * +:version+ - Specifies a version for the cache entry. When reading + # from the cache, if the cached version does not match the requested + # version, the read will be treated as a cache miss. This feature is + # used to support recyclable cache keys. + # + # Other options will be handled by the specific cache store implementation. + def write(name, value, options = nil) + options = merged_options(options) + + instrument(:write, name, options) do + entry = Entry.new(value, **options.merge(version: normalize_version(name, options))) + write_entry(normalize_key(name, options), entry, **options) + end + end + + # Deletes an entry in the cache. Returns +true+ if an entry is deleted. + # + # Options are passed to the underlying cache implementation. + def delete(name, options = nil) + options = merged_options(options) + + instrument(:delete, name) do + delete_entry(normalize_key(name, options), **options) + end + end + + # Deletes multiple entries in the cache. + # + # Options are passed to the underlying cache implementation. + def delete_multi(names, options = nil) + options = merged_options(options) + names.map! { |key| normalize_key(key, options) } + + instrument :delete_multi, names do + delete_multi_entries(names, **options) + end + end + + # Returns +true+ if the cache contains an entry for the given key. + # + # Options are passed to the underlying cache implementation. + def exist?(name, options = nil) + options = merged_options(options) + + instrument(:exist?, name) do |payload| + entry = read_entry(normalize_key(name, options), **options, event: payload) + (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false + end + end + + def new_entry(value, options = nil) # :nodoc: + Entry.new(value, **merged_options(options)) + end + + # Deletes all entries with keys matching the pattern. + # + # Options are passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def delete_matched(matcher, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support delete_matched") + end + + # Increments an integer value in the cache. + # + # Options are passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def increment(name, amount = 1, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support increment") + end + + # Decrements an integer value in the cache. + # + # Options are passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def decrement(name, amount = 1, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support decrement") + end + + # Cleans up the cache by removing expired entries. + # + # Options are passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def cleanup(options = nil) + raise NotImplementedError.new("#{self.class.name} does not support cleanup") + end + + # Clears the entire cache. Be careful with this method since it could + # affect other processes if shared cache is being used. + # + # The options hash is passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def clear(options = nil) + raise NotImplementedError.new("#{self.class.name} does not support clear") + end + + private + def default_coder + Coders[Cache.format_version] + end + + # Adds the namespace defined in the options to a pattern designed to + # match keys. Implementations that support delete_matched should call + # this method to translate a pattern that matches names into one that + # matches namespaced keys. + def key_matcher(pattern, options) # :doc: + prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] + if prefix + source = pattern.source + if source.start_with?("^") + source = source[1, source.length] + else + source = ".*#{source[0, source.length]}" + end + Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options) + else + pattern + end + end + + # Reads an entry from the cache implementation. Subclasses must implement + # this method. + def read_entry(key, **options) + raise NotImplementedError.new + end + + # Writes an entry to the cache implementation. Subclasses must implement + # this method. + def write_entry(key, entry, **options) + raise NotImplementedError.new + end + + def serialize_entry(entry, **options) + options = merged_options(options) + if @coder_supports_compression && options[:compress] + @coder.dump_compressed(entry, options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT) + else + @coder.dump(entry) + end + end + + def deserialize_entry(payload) + payload.nil? ? nil : @coder.load(payload) + end + + # Reads multiple entries from the cache implementation. Subclasses MAY + # implement this method. + def read_multi_entries(names, **options) + names.each_with_object({}) do |name, results| + key = normalize_key(name, options) + entry = read_entry(key, **options) + + next unless entry + + version = normalize_version(name, options) + + if entry.expired? + delete_entry(key, **options) + elsif !entry.mismatched?(version) + results[name] = entry.value + end + end + end + + # Writes multiple entries to the cache implementation. Subclasses MAY + # implement this method. + def write_multi_entries(hash, **options) + hash.each do |key, entry| + write_entry key, entry, **options + end + end + + # Deletes an entry from the cache implementation. Subclasses must + # implement this method. + def delete_entry(key, **options) + raise NotImplementedError.new + end + + # Deletes multiples entries in the cache implementation. Subclasses MAY + # implement this method. + def delete_multi_entries(entries, **options) + entries.count { |key| delete_entry(key, **options) } + end + + # Merges the default options with ones specific to a method call. + def merged_options(call_options) + if call_options + call_options = normalize_options(call_options) + if options.empty? + call_options + else + options.merge(call_options) + end + else + options + end + end + + # Normalize aliased options to their canonical form + def normalize_options(options) + options = options.dup + OPTION_ALIASES.each do |canonical_name, aliases| + alias_key = aliases.detect { |key| options.key?(key) } + options[canonical_name] ||= options[alias_key] if alias_key + options.except!(*aliases) + end + + options + end + + # Expands and namespaces the cache key. May be overridden by + # cache stores to do additional normalization. + def normalize_key(key, options = nil) + namespace_key expanded_key(key), options + end + + # Prefix the key with a namespace string: + # + # namespace_key 'foo', namespace: 'cache' + # # => 'cache:foo' + # + # With a namespace block: + # + # namespace_key 'foo', namespace: -> { 'cache' } + # # => 'cache:foo' + def namespace_key(key, options = nil) + options = merged_options(options) + namespace = options[:namespace] + + if namespace.respond_to?(:call) + namespace = namespace.call + end + + if key && key.encoding != Encoding::UTF_8 + key = key.dup.force_encoding(Encoding::UTF_8) + end + + if namespace + "#{namespace}:#{key}" + else + key + end + end + + # Expands key to be a consistent string value. Invokes +cache_key+ if + # object responds to +cache_key+. Otherwise, +to_param+ method will be + # called. If the key is a Hash, then keys will be sorted alphabetically. + def expanded_key(key) + return key.cache_key.to_s if key.respond_to?(:cache_key) + + case key + when Array + if key.size > 1 + key.collect { |element| expanded_key(element) } + else + expanded_key(key.first) + end + when Hash + key.collect { |k, v| "#{k}=#{v}" }.sort! + else + key + end.to_param + end + + def normalize_version(key, options = nil) + (options && options[:version].try(:to_param)) || expanded_version(key) + end + + def expanded_version(key) + case + when key.respond_to?(:cache_version) then key.cache_version.to_param + when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param + when key.respond_to?(:to_a) then expanded_version(key.to_a) + end + end + + def instrument(operation, key, options = nil) + if logger && logger.debug? && !silence? + logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" + end + + payload = { key: key, store: self.class.name } + payload.merge!(options) if options.is_a?(Hash) + ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) } + end + + def handle_expired_entry(entry, key, options) + if entry && entry.expired? + race_ttl = options[:race_condition_ttl].to_i + if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl) + # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache + # for a brief period while the entry is being recalculated. + entry.expires_at = Time.now.to_f + race_ttl + write_entry(key, entry, expires_in: race_ttl * 2) + else + delete_entry(key, **options) + end + entry = nil + end + entry + end + + def get_entry_value(entry, name, options) + instrument(:fetch_hit, name, options) { } + entry.value + end + + def save_block_result_to_cache(name, options) + result = instrument(:generate, name, options) do + yield(name) + end + + write(name, result, options) unless result.nil? && options[:skip_nil] + result + end + end + + module NullCoder # :nodoc: + extend self + + def dump(entry) + entry + end + + def dump_compressed(entry, threshold) + entry.compressed(threshold) + end + + def load(payload) + payload + end + end + + module Coders # :nodoc: + MARK_61 = "\x04\b".b.freeze # The one set by Marshal. + MARK_70_UNCOMPRESSED = "\x00".b.freeze + MARK_70_COMPRESSED = "\x01".b.freeze + + class << self + def [](version) + case version + when 6.1 + Rails61Coder + when 7.0 + Rails70Coder + else + raise ArgumentError, "Unknown ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}" + end + end + end + + module Loader + extend self + + def load(payload) + if !payload.is_a?(String) + ActiveSupport::Cache::Store.logger&.warn %{Payload wasn't a string, was #{payload.class.name} - couldn't unmarshal, so returning nil."} + + return nil + elsif payload.start_with?(MARK_70_UNCOMPRESSED) + members = Marshal.load(payload.byteslice(1..-1)) + elsif payload.start_with?(MARK_70_COMPRESSED) + members = Marshal.load(Zlib::Inflate.inflate(payload.byteslice(1..-1))) + elsif payload.start_with?(MARK_61) + return Marshal.load(payload) + else + ActiveSupport::Cache::Store.logger&.warn %{Invalid cache prefix: #{payload.byteslice(0).inspect}, expected "\\x00" or "\\x01"} + + return nil + end + Entry.unpack(members) + end + end + + module Rails61Coder + include Loader + extend self + + def dump(entry) + Marshal.dump(entry) + end + + def dump_compressed(entry, threshold) + Marshal.dump(entry.compressed(threshold)) + end + end + + module Rails70Coder + include Loader + extend self + + def dump(entry) + MARK_70_UNCOMPRESSED + Marshal.dump(entry.pack) + end + + def dump_compressed(entry, threshold) + payload = Marshal.dump(entry.pack) + if payload.bytesize >= threshold + compressed_payload = Zlib::Deflate.deflate(payload) + if compressed_payload.bytesize < payload.bytesize + return MARK_70_COMPRESSED + compressed_payload + end + end + + MARK_70_UNCOMPRESSED + payload + end + end + end + + # This class is used to represent cache entries. Cache entries have a value, an optional + # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option + # on the cache. The version is used to support the :version option on the cache for rejecting + # mismatches. + # + # Since cache entries in most instances will be serialized, the internals of this class are highly optimized + # using short instance variable names that are lazily defined. + class Entry # :nodoc: + class << self + def unpack(members) + new(members[0], expires_at: members[1], version: members[2]) + end + end + + attr_reader :version + + # Creates a new cache entry for the specified value. Options supported are + # +:compressed+, +:version+, +:expires_at+ and +:expires_in+. + def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **) + @value = value + @version = version + @created_at = 0.0 + @expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f) + @compressed = true if compressed + end + + def value + compressed? ? uncompress(@value) : @value + end + + def mismatched?(version) + @version && version && @version != version + end + + # Checks if the entry is expired. The +expires_in+ parameter can override + # the value set when the entry was created. + def expired? + @expires_in && @created_at + @expires_in <= Time.now.to_f + end + + def expires_at + @expires_in ? @created_at + @expires_in : nil + end + + def expires_at=(value) + if value + @expires_in = value.to_f - @created_at + else + @expires_in = nil + end + end + + # Returns the size of the cached value. This could be less than + # value.bytesize if the data is compressed. + def bytesize + case value + when NilClass + 0 + when String + @value.bytesize + else + @s ||= Marshal.dump(@value).bytesize + end + end + + def compressed? # :nodoc: + defined?(@compressed) + end + + def compressed(compress_threshold) + return self if compressed? + + case @value + when nil, true, false, Numeric + uncompressed_size = 0 + when String + uncompressed_size = @value.bytesize + else + serialized = Marshal.dump(@value) + uncompressed_size = serialized.bytesize + end + + if uncompressed_size >= compress_threshold + serialized ||= Marshal.dump(@value) + compressed = Zlib::Deflate.deflate(serialized) + + if compressed.bytesize < uncompressed_size + return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version) + end + end + self + end + + def local? + false + end + + # Duplicates the value in a class. This is used by cache implementations that don't natively + # serialize entries to protect against accidental cache modifications. + def dup_value! + if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) + if @value.is_a?(String) + @value = @value.dup + else + @value = Marshal.load(Marshal.dump(@value)) + end + end + end + + def pack + members = [value, expires_at, version] + members.pop while !members.empty? && members.last.nil? + members + end + + private + def uncompress(value) + Marshal.load(Zlib::Inflate.inflate(value)) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/cache/file_store.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/file_store.rb similarity index 58% rename from app/server/ruby/vendor/activesupport/lib/active_support/cache/file_store.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/file_store.rb index 8ed60aebac..604ed82095 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/cache/file_store.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/file_store.rb @@ -1,7 +1,8 @@ -require 'active_support/core_ext/marshal' -require 'active_support/core_ext/file/atomic' -require 'active_support/core_ext/string/conversions' -require 'uri/common' +# frozen_string_literal: true + +require "active_support/core_ext/file/atomic" +require "active_support/core_ext/string/conversions" +require "uri/common" module ActiveSupport module Cache @@ -13,30 +14,35 @@ class FileStore < Store attr_reader :cache_path DIR_FORMATTER = "%03X" - FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write) - EXCLUDED_DIRS = ['.', '..'].freeze + FILENAME_MAX_SIZE = 226 # max filename size on file system is 255, minus room for timestamp, pid, and random characters appended by Tempfile (used by atomic write) + FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room + GITKEEP_FILES = [".gitkeep", ".keep"].freeze - def initialize(cache_path, options = nil) + def initialize(cache_path, **options) super(options) @cache_path = cache_path.to_s - extend Strategy::LocalCache + end + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true end # Deletes all items from the cache. In this case it deletes all the entries in the specified - # file store directory except for .gitkeep. Be careful which directory is specified in your + # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your # config file when using +FileStore+ because everything in that directory will be deleted. def clear(options = nil) - root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)} - FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) + root_dirs = (Dir.children(cache_path) - GITKEEP_FILES) + FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) }) + rescue Errno::ENOENT, Errno::ENOTEMPTY end # Preemptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) search_dir(cache_path) do |fname| - key = file_path_key(fname) - entry = read_entry(key, options) - delete_entry(key, options) if entry && entry.expired? + entry = read_entry(fname, **options) + delete_entry(fname, **options) if entry && entry.expired? end end @@ -58,57 +64,59 @@ def delete_matched(matcher, options = nil) matcher = key_matcher(matcher, options) search_dir(cache_path) do |path| key = file_path_key(path) - delete_entry(key, options) if key.match(matcher) + delete_entry(path, **options) if key.match(matcher) end end end - protected - - def read_entry(key, options) - file_name = key_file_path(key) - if File.exist?(file_name) - File.open(file_name) { |f| Marshal.load(f) } + private + def read_entry(key, **options) + if payload = read_serialized_entry(key, **options) + entry = deserialize_entry(payload) + entry if entry.is_a?(Cache::Entry) end - rescue => e - logger.error("FileStoreError (#{e}): #{e.message}") if logger + end + + def read_serialized_entry(key, **) + File.binread(key) if File.exist?(key) + rescue => error + logger.error("FileStoreError (#{error}): #{error.message}") if logger nil end - def write_entry(key, entry, options) - file_name = key_file_path(key) - return false if options[:unless_exist] && File.exist?(file_name) - ensure_cache_path(File.dirname(file_name)) - File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)} + def write_entry(key, entry, **options) + write_serialized_entry(key, serialize_entry(entry, **options), **options) + end + + def write_serialized_entry(key, payload, **options) + return false if options[:unless_exist] && File.exist?(key) + ensure_cache_path(File.dirname(key)) + File.atomic_write(key, cache_path) { |f| f.write(payload) } true end - def delete_entry(key, options) - file_name = key_file_path(key) - if File.exist?(file_name) + def delete_entry(key, **options) + if File.exist?(key) begin - File.delete(file_name) - delete_empty_directories(File.dirname(file_name)) + File.delete(key) + delete_empty_directories(File.dirname(key)) true - rescue => e + rescue # Just in case the error was caused by another process deleting the file first. - raise e if File.exist?(file_name) + raise if File.exist?(key) false end end end - private # Lock a file for a block so only one process can modify it at a time. - def lock_file(file_name, &block) # :nodoc: + def lock_file(file_name, &block) if File.exist?(file_name) - File.open(file_name, 'r+') do |f| - begin - f.flock File::LOCK_EX - yield - ensure - f.flock File::LOCK_UN - end + File.open(file_name, "r+") do |f| + f.flock File::LOCK_EX + yield + ensure + f.flock File::LOCK_UN end else yield @@ -116,20 +124,30 @@ def lock_file(file_name, &block) # :nodoc: end # Translate a key into a file path. - def key_file_path(key) + def normalize_key(key, options) + key = super fname = URI.encode_www_form_component(key) + + if fname.size > FILEPATH_MAX_SIZE + fname = ActiveSupport::Digest.hexdigest(key) + end + hash = Zlib.adler32(fname) hash, dir_1 = hash.divmod(0x1000) dir_2 = hash.modulo(0x1000) - fname_paths = [] # Make sure file name doesn't exceed file system limits. - begin - fname_paths << fname[0, FILENAME_MAX_SIZE] - fname = fname[FILENAME_MAX_SIZE..-1] - end until fname.blank? + if fname.length < FILENAME_MAX_SIZE + fname_paths = fname + else + fname_paths = [] + begin + fname_paths << fname[0, FILENAME_MAX_SIZE] + fname = fname[FILENAME_MAX_SIZE..-1] + end until fname.blank? + end - File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths) + File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths) end # Translate a file path into a key. @@ -141,7 +159,7 @@ def file_path_key(path) # Delete empty directories in the cache. def delete_empty_directories(dir) return if File.realpath(dir) == File.realpath(cache_path) - if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty? + if Dir.children(dir).empty? Dir.delete(dir) rescue nil delete_empty_directories(File.dirname(dir)) end @@ -154,8 +172,7 @@ def ensure_cache_path(path) def search_dir(dir, &callback) return if !File.exist?(dir) - Dir.foreach(dir) do |d| - next if EXCLUDED_DIRS.include?(d) + Dir.each_child(dir) do |d| name = File.join(dir, d) if File.directory?(name) search_dir(name, &callback) @@ -168,7 +185,7 @@ def search_dir(dir, &callback) # Modifies the amount of an already existing integer value that is stored in the cache. # If the key is not found nothing is done. def modify_value(name, amount, options) - file_name = key_file_path(namespaced_key(name, options)) + file_name = normalize_key(name, options) lock_file(file_name) do options = merged_options(options) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/mem_cache_store.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/mem_cache_store.rb new file mode 100644 index 0000000000..c41d724c5e --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/mem_cache_store.rb @@ -0,0 +1,324 @@ +# frozen_string_literal: true + +begin + require "dalli" +rescue LoadError => e + $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end + +require "delegate" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/array/extract_options" + +module ActiveSupport + module Cache + # A cache store implementation which stores data in Memcached: + # https://memcached.org + # + # This is currently the most popular cache store for production websites. + # + # Special features: + # - Clustering and load balancing. One can specify multiple memcached servers, + # and MemCacheStore will load balance between all available servers. If a + # server goes down, then MemCacheStore will ignore it until it comes back up. + # + # MemCacheStore implements the Strategy::LocalCache strategy which implements + # an in-memory cache inside of a block. + class MemCacheStore < Store + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + prepend Strategy::LocalCache + + module DupLocalCache + class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore) + def write_entry(_key, entry) + if entry.is_a?(Entry) + entry.dup_value! + end + super + end + + def fetch_entry(key) + entry = super do + new_entry = yield + if entry.is_a?(Entry) + new_entry.dup_value! + end + new_entry + end + entry = entry.dup + + if entry.is_a?(Entry) + entry.dup_value! + end + + entry + end + end + + private + def local_cache + if ActiveSupport::Cache.format_version == 6.1 + if local_cache = super + DupLocalStore.new(local_cache) + end + else + super + end + end + end + prepend DupLocalCache + + ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n + + # Creates a new Dalli::Client instance with specified addresses and options. + # If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks: + # - ENV["MEMCACHE_SERVERS"] (if defined) + # - "127.0.0.1:11211" (otherwise) + # + # ActiveSupport::Cache::MemCacheStore.build_mem_cache + # # => # + # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290') + # # => # + def self.build_mem_cache(*addresses) # :nodoc: + addresses = addresses.flatten + options = addresses.extract_options! + addresses = nil if addresses.compact.empty? + pool_options = retrieve_pool_options(options) + + if pool_options.empty? + Dalli::Client.new(addresses, options) + else + ensure_connection_pool_added! + ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } + end + end + + # Creates a new MemCacheStore object, with the given memcached server + # addresses. Each address is either a host name, or a host-with-port string + # in the form of "host_name:port". For example: + # + # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229") + # + # If no addresses are provided, but ENV['MEMCACHE_SERVERS'] is defined, it will be used instead. Otherwise, + # MemCacheStore will connect to localhost:11211 (the default memcached port). + def initialize(*addresses) + addresses = addresses.flatten + options = addresses.extract_options! + if options.key?(:cache_nils) + options[:skip_nil] = !options.delete(:cache_nils) + end + super(options) + + unless [String, Dalli::Client, NilClass].include?(addresses.first.class) + raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance." + end + if addresses.first.is_a?(Dalli::Client) + @data = addresses.first + else + mem_cache_options = options.dup + # The value "compress: false" prevents duplicate compression within Dalli. + mem_cache_options[:compress] = false + (UNIVERSAL_OPTIONS - %i(compress)).each { |name| mem_cache_options.delete(name) } + @data = self.class.build_mem_cache(*(addresses + [mem_cache_options])) + end + end + + ## + # :method: write + # :call-seq: write(name, value, options = nil) + # + # Behaves the same as ActiveSupport::Cache::Store#write, but supports + # additional options specific to memcached. + # + # ==== Additional Options + # + # * raw: true - Sends the value directly to the server as raw + # bytes. The value must be a string or number. You can use memcached + # direct operations like +increment+ and +decrement+ only on raw values. + # + # * unless_exist: true - Prevents overwriting an existing cache + # entry. + + # Increment a cached value. This method uses the memcached incr atomic + # operator and can only be used on values written with the +:raw+ option. + # Calling it on a value not stored with +:raw+ will initialize that value + # to zero. + def increment(name, amount = 1, options = nil) + options = merged_options(options) + instrument(:increment, name, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) } + end + end + end + + # Decrement a cached value. This method uses the memcached decr atomic + # operator and can only be used on values written with the +:raw+ option. + # Calling it on a value not stored with +:raw+ will initialize that value + # to zero. + def decrement(name, amount = 1, options = nil) + options = merged_options(options) + instrument(:decrement, name, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) } + end + end + end + + # Clear the entire cache on all memcached servers. This method should + # be used with care when shared cache is being used. + def clear(options = nil) + rescue_error_with(nil) { @data.with { |c| c.flush_all } } + end + + # Get the statistics from the memcached servers. + def stats + @data.with { |c| c.stats } + end + + private + module Coders # :nodoc: + class << self + def [](version) + case version + when 6.1 + Rails61Coder + when 7.0 + Rails70Coder + else + raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}" + end + end + end + + module Loader + def load(payload) + if payload.is_a?(Entry) + payload + else + Cache::Coders::Loader.load(payload) + end + end + end + + module Rails61Coder + include Loader + extend self + + def dump(entry) + entry + end + + def dump_compressed(entry, threshold) + entry.compressed(threshold) + end + end + + module Rails70Coder + include Cache::Coders::Rails70Coder + include Loader + extend self + end + end + + def default_coder + Coders[Cache.format_version] + end + + # Read an entry from the cache. + def read_entry(key, **options) + deserialize_entry(read_serialized_entry(key, **options), **options) + end + + def read_serialized_entry(key, **options) + rescue_error_with(nil) do + @data.with { |c| c.get(key, options) } + end + end + + # Write an entry to the cache. + def write_entry(key, entry, **options) + write_serialized_entry(key, serialize_entry(entry, **options), **options) + end + + def write_serialized_entry(key, payload, **options) + method = options[:unless_exist] ? :add : :set + expires_in = options[:expires_in].to_i + if options[:race_condition_ttl] && expires_in > 0 && !options[:raw] + # Set the memcache expire a few minutes in the future to support race condition ttls on read + expires_in += 5.minutes + end + rescue_error_with false do + # Don't pass compress option to Dalli since we are already dealing with compression. + options.delete(:compress) + @data.with { |c| c.send(method, key, payload, expires_in, **options) } + end + end + + # Reads multiple entries from the cache implementation. + def read_multi_entries(names, **options) + keys_to_names = names.index_by { |name| normalize_key(name, options) } + + raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) } + values = {} + + raw_values.each do |key, value| + entry = deserialize_entry(value, raw: options[:raw]) + + unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options)) + values[keys_to_names[key]] = entry.value + end + end + + values + end + + # Delete an entry from the cache. + def delete_entry(key, **options) + rescue_error_with(false) { @data.with { |c| c.delete(key) } } + end + + def serialize_entry(entry, raw: false, **options) + if raw + entry.value.to_s + else + super(entry, raw: raw, **options) + end + end + + # Memcache keys are binaries. So we need to force their encoding to binary + # before applying the regular expression to ensure we are escaping all + # characters properly. + def normalize_key(key, options) + key = super + if key + key = key.dup.force_encoding(Encoding::ASCII_8BIT) + key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" } + key = "#{key[0, 212]}:hash:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250 + end + key + end + + def deserialize_entry(payload, raw: false, **) + if payload && raw + Entry.new(payload) + else + super(payload) + end + end + + def rescue_error_with(fallback) + yield + rescue Dalli::DalliError => error + ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning) + logger.error("DalliError (#{error}): #{error.message}") if logger + fallback + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/cache/memory_store.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/memory_store.rb similarity index 50% rename from app/server/ruby/vendor/activesupport/lib/active_support/cache/memory_store.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/memory_store.rb index 8a0523d0e2..f03edefcec 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/cache/memory_store.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/memory_store.rb @@ -1,26 +1,56 @@ -require 'monitor' +# frozen_string_literal: true + +require "monitor" module ActiveSupport module Cache # A cache store implementation which stores everything into memory in the # same process. If you're running multiple Ruby on Rails server processes - # (which is the case if you're using mongrel_cluster or Phusion Passenger), + # (which is the case if you're using Phusion Passenger or puma clustered mode), # then this means that Rails server process instances won't be able # to share cache data with each other and this may not be the most # appropriate cache in that scenario. # - # This cache has a bounded size specified by the :size options to the + # This cache has a bounded size specified by the +:size+ options to the # initializer (default is 32Mb). When the cache exceeds the allotted size, # a cleanup will occur which tries to prune the cache down to three quarters # of the maximum size by removing the least recently used entries. # + # Unlike other Cache store implementations, MemoryStore does not compress + # values by default. MemoryStore does not benefit from compression as much + # as other Store implementations, as it does not send data over a network. + # However, when compression is enabled, it still pays the full cost of + # compression in terms of cpu use. + # # MemoryStore is thread-safe. class MemoryStore < Store + module DupCoder # :nodoc: + extend self + + def dump(entry) + entry.dup_value! unless entry.compressed? + entry + end + + def dump_compressed(entry, threshold) + entry = entry.compressed(threshold) + entry.dup_value! unless entry.compressed? + entry + end + + def load(entry) + entry = entry.dup + entry.dup_value! + entry + end + end + def initialize(options = nil) options ||= {} + # Disable compression by default. + options[:compress] ||= false super(options) @data = {} - @key_access = {} @max_size = options[:size] || 32.megabytes @max_prune_time = options[:max_prune_time] || 2 @cache_size = 0 @@ -28,10 +58,15 @@ def initialize(options = nil) @pruning = false end + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + # Delete all data stored in a given cache store. def clear(options = nil) synchronize do @data.clear - @key_access.clear @cache_size = 0 end end @@ -39,11 +74,11 @@ def clear(options = nil) # Preemptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) - instrument(:cleanup, :size => @data.size) do - keys = synchronize{ @data.keys } + instrument(:cleanup, size: @data.size) do + keys = synchronize { @data.keys } keys.each do |key| entry = @data[key] - delete_entry(key, options) if entry && entry.expired? + delete_entry(key, **options) if entry && entry.expired? end end end @@ -54,13 +89,13 @@ def prune(target_size, max_time = nil) return if pruning? @pruning = true begin - start_time = Time.now + start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) cleanup - instrument(:prune, target_size, :from => @cache_size) do - keys = synchronize{ @key_access.keys.sort{|a,b| @key_access[a].to_f <=> @key_access[b].to_f} } + instrument(:prune, target_size, from: @cache_size) do + keys = synchronize { @data.keys } keys.each do |key| - delete_entry(key, options) - return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time) + delete_entry(key, **options) + return if @cache_size <= target_size || (max_time && Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time > max_time) end end ensure @@ -75,45 +110,28 @@ def pruning? # Increment an integer value in the cache. def increment(name, amount = 1, options = nil) - synchronize do - options = merged_options(options) - if num = read(name, options) - num = num.to_i + amount - write(name, num, options) - num - else - nil - end - end + modify_value(name, amount, options) end # Decrement an integer value in the cache. def decrement(name, amount = 1, options = nil) - synchronize do - options = merged_options(options) - if num = read(name, options) - num = num.to_i - amount - write(name, num, options) - num - else - nil - end - end + modify_value(name, -amount, options) end + # Deletes cache entries if the cache key matches a given pattern. def delete_matched(matcher, options = nil) options = merged_options(options) instrument(:delete_matched, matcher.inspect) do matcher = key_matcher(matcher, options) keys = synchronize { @data.keys } keys.each do |key| - delete_entry(key, options) if key.match(matcher) + delete_entry(key, **options) if key.match(matcher) end end end def inspect # :nodoc: - "<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>" + "#<#{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>" end # Synchronize calls to the cache. This should be called wherever the underlying cache implementation @@ -122,49 +140,62 @@ def synchronize(&block) # :nodoc: @monitor.synchronize(&block) end - protected - + private PER_ENTRY_OVERHEAD = 240 - def cached_size(key, entry) - key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD + def default_coder + DupCoder + end + + def cached_size(key, payload) + key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD end - def read_entry(key, options) # :nodoc: - entry = @data[key] + def read_entry(key, **options) + entry = nil synchronize do - if entry - @key_access[key] = Time.now.to_f - else - @key_access.delete(key) + payload = @data.delete(key) + if payload + @data[key] = payload + entry = deserialize_entry(payload) end end entry end - def write_entry(key, entry, options) # :nodoc: - entry.dup_value! + def write_entry(key, entry, **options) + payload = serialize_entry(entry, **options) synchronize do - old_entry = @data[key] - return false if @data.key?(key) && options[:unless_exist] - if old_entry - @cache_size -= (old_entry.size - entry.size) + return false if options[:unless_exist] && @data.key?(key) + + old_payload = @data[key] + if old_payload + @cache_size -= (old_payload.bytesize - payload.bytesize) else - @cache_size += cached_size(key, entry) + @cache_size += cached_size(key, payload) end - @key_access[key] = Time.now.to_f - @data[key] = entry + @data[key] = payload prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size true end end - def delete_entry(key, options) # :nodoc: + def delete_entry(key, **options) synchronize do - @key_access.delete(key) - entry = @data.delete(key) - @cache_size -= cached_size(key, entry) if entry - !!entry + payload = @data.delete(key) + @cache_size -= cached_size(key, payload) if payload + !!payload + end + end + + def modify_value(name, amount, options) + options = merged_options(options) + synchronize do + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/cache/null_store.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/null_store.rb similarity index 61% rename from app/server/ruby/vendor/activesupport/lib/active_support/cache/null_store.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/null_store.rb index 4427eaafcd..e840b26a04 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/cache/null_store.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/null_store.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveSupport module Cache # A cache store implementation which doesn't actually store anything. Useful in @@ -8,9 +10,11 @@ module Cache # be cached inside blocks that utilize this strategy. See # ActiveSupport::Cache::Strategy::LocalCache for more details. class NullStore < Store - def initialize(options = nil) - super(options) - extend Strategy::LocalCache + prepend Strategy::LocalCache + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true end def clear(options = nil) @@ -28,15 +32,23 @@ def decrement(name, amount = 1, options = nil) def delete_matched(matcher, options = nil) end - protected - def read_entry(key, options) # :nodoc: + private + def read_entry(key, **s) + deserialize_entry(read_serialized_entry(key)) + end + + def read_serialized_entry(_key, **) + end + + def write_entry(key, entry, **) + write_serialized_entry(key, serialize_entry(entry)) end - def write_entry(key, entry, options) # :nodoc: + def write_serialized_entry(_key, _payload, **) true end - def delete_entry(key, options) # :nodoc: + def delete_entry(key, **options) false end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/redis_cache_store.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/redis_cache_store.rb new file mode 100644 index 0000000000..4a5a28a47d --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/redis_cache_store.rb @@ -0,0 +1,474 @@ +# frozen_string_literal: true + +begin + gem "redis", ">= 4.0.1" + require "redis" + require "redis/distributed" +rescue LoadError + warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \">= 4.0.1\"`" + raise +end + +# Prefer the hiredis driver but don't require it. +begin + if ::Redis::VERSION < "5" + require "redis/connection/hiredis" + else + require "hiredis-client" + end +rescue LoadError +end + +require "active_support/digest" + +module ActiveSupport + module Cache + module ConnectionPoolLike + def with + yield self + end + end + + ::Redis.include(ConnectionPoolLike) + ::Redis::Distributed.include(ConnectionPoolLike) + + # Redis cache store. + # + # Deployment note: Take care to use a *dedicated Redis cache* rather + # than pointing this at your existing Redis server. It won't cope well + # with mixed usage patterns and it won't expire cache entries by default. + # + # Redis cache server setup guide: https://redis.io/topics/lru-cache + # + # * Supports vanilla Redis, hiredis, and Redis::Distributed. + # * Supports Memcached-like sharding across Redises with Redis::Distributed. + # * Fault tolerant. If the Redis server is unavailable, no exceptions are + # raised. Cache fetches are all misses and writes are dropped. + # * Local cache. Hot in-memory primary cache within block/middleware scope. + # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed + # 4.0.1+ for distributed mget support. + # * +delete_matched+ support for Redis KEYS globs. + class RedisCacheStore < Store + # Keys are truncated with the ActiveSupport digest if they exceed 1kB + MAX_KEY_BYTESIZE = 1024 + + DEFAULT_REDIS_OPTIONS = { + connect_timeout: 20, + read_timeout: 1, + write_timeout: 1, + reconnect_attempts: 0, + } + + DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do + if logger + logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" } + end + end + + # The maximum number of entries to receive per SCAN call. + SCAN_BATCH_SIZE = 1000 + private_constant :SCAN_BATCH_SIZE + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + prepend Strategy::LocalCache + + class << self + # Factory method to create a new Redis instance. + # + # Handles four options: :redis block, :redis instance, single :url + # string, and multiple :url strings. + # + # Option Class Result + # :redis Proc -> options[:redis].call + # :redis Object -> options[:redis] + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + def build_redis(redis: nil, url: nil, **redis_options) # :nodoc: + urls = Array(url) + + if redis.is_a?(Proc) + redis.call + elsif redis + redis + elsif urls.size > 1 + build_redis_distributed_client(urls: urls, **redis_options) + elsif urls.empty? + build_redis_client(**redis_options) + else + build_redis_client(url: urls.first, **redis_options) + end + end + + private + def build_redis_distributed_client(urls:, **redis_options) + ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| + urls.each { |u| dist.add_node url: u } + end + end + + def build_redis_client(**redis_options) + ::Redis.new(DEFAULT_REDIS_OPTIONS.merge(redis_options)) + end + end + + attr_reader :redis_options + attr_reader :max_key_bytesize + + # Creates a new Redis cache store. + # + # Handles four options: :redis block, :redis instance, single :url + # string, and multiple :url strings. + # + # Option Class Result + # :redis Proc -> options[:redis].call + # :redis Object -> options[:redis] + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + # No namespace is set by default. Provide one if the Redis cache + # server is shared with other apps: namespace: 'myapp-cache'. + # + # Compression is enabled by default with a 1kB threshold, so cached + # values larger than 1kB are automatically compressed. Disable by + # passing compress: false or change the threshold by passing + # compress_threshold: 4.kilobytes. + # + # No expiry is set on cache entries by default. Redis is expected to + # be configured with an eviction policy that automatically deletes + # least-recently or -frequently used keys when it reaches max memory. + # See https://redis.io/topics/lru-cache for cache server setup. + # + # Race condition TTL is not set by default. This can be used to avoid + # "thundering herd" cache writes when hot cache entries are expired. + # See ActiveSupport::Cache::Store#fetch for more. + def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, coder: default_coder, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options) + @redis_options = redis_options + + @max_key_bytesize = MAX_KEY_BYTESIZE + @error_handler = error_handler + + super namespace: namespace, + compress: compress, compress_threshold: compress_threshold, + expires_in: expires_in, race_condition_ttl: race_condition_ttl, + coder: coder + end + + def redis + @redis ||= begin + pool_options = self.class.send(:retrieve_pool_options, redis_options) + + if pool_options.any? + self.class.send(:ensure_connection_pool_added!) + ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) } + else + self.class.build_redis(**redis_options) + end + end + end + + def inspect + instance = @redis || @redis_options + "#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>" + end + + # Cache Store API implementation. + # + # Read multiple values at once. Returns a hash of requested keys -> + # fetched values. + def read_multi(*names) + if mget_capable? + instrument(:read_multi, names, options) do |payload| + read_multi_mget(*names).tap do |results| + payload[:hits] = results.keys + end + end + else + super + end + end + + # Cache Store API implementation. + # + # Supports Redis KEYS glob patterns: + # + # h?llo matches hello, hallo and hxllo + # h*llo matches hllo and heeeello + # h[ae]llo matches hello and hallo, but not hillo + # h[^e]llo matches hallo, hbllo, ... but not hello + # h[a-b]llo matches hallo and hbllo + # + # Use \ to escape special characters if you want to match them verbatim. + # + # See https://redis.io/commands/KEYS for more. + # + # Failsafe: Raises errors. + def delete_matched(matcher, options = nil) + instrument :delete_matched, matcher do + unless String === matcher + raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" + end + redis.with do |c| + pattern = namespace_key(matcher, options) + cursor = "0" + # Fetch keys in batches using SCAN to avoid blocking the Redis server. + nodes = c.respond_to?(:nodes) ? c.nodes : [c] + + nodes.each do |node| + begin + cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE) + node.del(*keys) unless keys.empty? + end until cursor == "0" + end + end + end + end + + # Cache Store API implementation. + # + # Increment a cached value. This method uses the Redis incr atomic + # operator and can only be used on values written with the +:raw+ option. + # Calling it on a value not stored with +:raw+ will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def increment(name, amount = 1, options = nil) + instrument :increment, name, amount: amount do + failsafe :increment do + options = merged_options(options) + key = normalize_key(name, options) + + redis.with do |c| + c.incrby(key, amount).tap do + write_key_expiry(c, key, options) + end + end + end + end + end + + # Cache Store API implementation. + # + # Decrement a cached value. This method uses the Redis decr atomic + # operator and can only be used on values written with the +:raw+ option. + # Calling it on a value not stored with +:raw+ will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def decrement(name, amount = 1, options = nil) + instrument :decrement, name, amount: amount do + failsafe :decrement do + options = merged_options(options) + key = normalize_key(name, options) + + redis.with do |c| + c.decrby(key, amount).tap do + write_key_expiry(c, key, options) + end + end + end + end + end + + # Cache Store API implementation. + # + # Removes expired entries. Handled natively by Redis least-recently-/ + # least-frequently-used expiry, so manual cleanup is not supported. + def cleanup(options = nil) + super + end + + # Clear the entire cache on all Redis servers. Safe to use on + # shared servers if the cache is namespaced. + # + # Failsafe: Raises errors. + def clear(options = nil) + failsafe :clear do + if namespace = merged_options(options)[:namespace] + delete_matched "*", namespace: namespace + else + redis.with { |c| c.flushdb } + end + end + end + + # Get info from redis servers. + def stats + redis.with { |c| c.info } + end + + def mget_capable? # :nodoc: + set_redis_capabilities unless defined? @mget_capable + @mget_capable + end + + def mset_capable? # :nodoc: + set_redis_capabilities unless defined? @mset_capable + @mset_capable + end + + private + def set_redis_capabilities + case redis + when Redis::Distributed + @mget_capable = true + @mset_capable = false + else + @mget_capable = true + @mset_capable = true + end + end + + # Store provider interface: + # Read an entry from the cache. + def read_entry(key, **options) + deserialize_entry(read_serialized_entry(key, **options), **options) + end + + def read_serialized_entry(key, raw: false, **options) + failsafe :read_entry do + redis.with { |c| c.get(key) } + end + end + + def read_multi_entries(names, **options) + if mget_capable? + read_multi_mget(*names, **options) + else + super + end + end + + def read_multi_mget(*names) + options = names.extract_options! + options = merged_options(options) + return {} if names == [] + raw = options&.fetch(:raw, false) + + keys = names.map { |name| normalize_key(name, options) } + + values = failsafe(:read_multi_mget, returning: {}) do + redis.with { |c| c.mget(*keys) } + end + + names.zip(values).each_with_object({}) do |(name, value), results| + if value + entry = deserialize_entry(value, raw: raw) + unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) + results[name] = entry.value + end + end + end + end + + # Write an entry to the cache. + # + # Requires Redis 2.6.12+ for extended SET options. + def write_entry(key, entry, raw: false, **options) + write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options) + end + + def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, **options) + # If race condition TTL is in use, ensure that cache entries + # stick around a bit longer after they would have expired + # so we can purposefully serve stale entries. + if race_condition_ttl && expires_in && expires_in > 0 && !raw + expires_in += 5.minutes + end + + modifiers = {} + if unless_exist || expires_in + modifiers[:nx] = unless_exist + modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in + end + + failsafe :write_entry, returning: false do + redis.with { |c| c.set key, payload, **modifiers } + end + end + + def write_key_expiry(client, key, options) + if options[:expires_in] && client.ttl(key).negative? + client.expire key, options[:expires_in].to_i + end + end + + # Delete an entry from the cache. + def delete_entry(key, options) + failsafe :delete_entry, returning: false do + redis.with { |c| c.del key } + end + end + + # Deletes multiple entries in the cache. Returns the number of entries deleted. + def delete_multi_entries(entries, **_options) + redis.with { |c| c.del(entries) } + end + + # Nonstandard store provider API to write multiple values at once. + def write_multi_entries(entries, expires_in: nil, **options) + if entries.any? + if mset_capable? && expires_in.nil? + failsafe :write_multi_entries do + payload = serialize_entries(entries, **options) + redis.with do |c| + c.mapped_mset(payload) + end + end + else + super + end + end + end + + # Truncate keys that exceed 1kB. + def normalize_key(key, options) + truncate_key super&.b + end + + def truncate_key(key) + if key && key.bytesize > max_key_bytesize + suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}" + truncate_at = max_key_bytesize - suffix.bytesize + "#{key.byteslice(0, truncate_at)}#{suffix}" + else + key + end + end + + def deserialize_entry(payload, raw: false, **) + if raw && !payload.nil? + Entry.new(payload) + else + super(payload) + end + end + + def serialize_entry(entry, raw: false, **options) + if raw + entry.value.to_s + else + super(entry, raw: raw, **options) + end + end + + def serialize_entries(entries, **options) + entries.transform_values do |entry| + serialize_entry(entry, **options) + end + end + + def failsafe(method, returning: nil) + yield + rescue ::Redis::BaseError => error + ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning) + @error_handler&.call(method: method, exception: error, returning: returning) + returning + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/strategy/local_cache.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/strategy/local_cache.rb new file mode 100644 index 0000000000..c89cacdd61 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/strategy/local_cache.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inflections" + +module ActiveSupport + module Cache + module Strategy + # Caches that implement LocalCache will be backed by an in-memory cache for the + # duration of a block. Repeated calls to the cache for the same key will hit the + # in-memory cache for faster access. + module LocalCache + autoload :Middleware, "active_support/cache/strategy/local_cache_middleware" + + # Class for storing and registering the local caches. + module LocalCacheRegistry # :nodoc: + extend self + + def cache_for(local_cache_key) + registry = ActiveSupport::IsolatedExecutionState[:active_support_local_cache_registry] ||= {} + registry[local_cache_key] + end + + def set_cache_for(local_cache_key, value) + registry = ActiveSupport::IsolatedExecutionState[:active_support_local_cache_registry] ||= {} + registry[local_cache_key] = value + end + end + + # Simple memory backed cache. This cache is not thread safe and is intended only + # for serving as a temporary memory cache for a single thread. + class LocalStore + def initialize + @data = {} + end + + def clear(options = nil) + @data.clear + end + + def read_entry(key) + @data[key] + end + + def read_multi_entries(keys) + @data.slice(*keys) + end + + def write_entry(key, entry) + @data[key] = entry + true + end + + def delete_entry(key) + !!@data.delete(key) + end + + def fetch_entry(key) # :nodoc: + @data.fetch(key) { @data[key] = yield } + end + end + + # Use a local cache for the duration of block. + def with_local_cache(&block) + use_temporary_local_cache(LocalStore.new, &block) + end + + # Middleware class can be inserted as a Rack handler to be local cache for the + # duration of request. + def middleware + @middleware ||= Middleware.new( + "ActiveSupport::Cache::Strategy::LocalCache", + local_cache_key) + end + + def clear(**options) # :nodoc: + return super unless cache = local_cache + cache.clear(options) + super + end + + def cleanup(**options) # :nodoc: + return super unless cache = local_cache + cache.clear + super + end + + def delete_matched(matcher, options = nil) # :nodoc: + return super unless cache = local_cache + cache.clear + super + end + + def increment(name, amount = 1, **options) # :nodoc: + return super unless local_cache + value = bypass_local_cache { super } + write_cache_value(name, value, raw: true, **options) + value + end + + def decrement(name, amount = 1, **options) # :nodoc: + return super unless local_cache + value = bypass_local_cache { super } + write_cache_value(name, value, raw: true, **options) + value + end + + private + def read_serialized_entry(key, raw: false, **options) + if cache = local_cache + hit = true + entry = cache.fetch_entry(key) do + hit = false + super + end + options[:event][:store] = cache.class.name if hit && options[:event] + entry + else + super + end + end + + def read_multi_entries(keys, **options) + return super unless local_cache + + local_entries = local_cache.read_multi_entries(keys) + local_entries.transform_values! do |payload| + deserialize_entry(payload).value + end + missed_keys = keys - local_entries.keys + + if missed_keys.any? + local_entries.merge!(super(missed_keys, **options)) + else + local_entries + end + end + + def write_serialized_entry(key, payload, **) + if return_value = super + local_cache.write_entry(key, payload) if local_cache + else + local_cache.delete_entry(key) if local_cache + end + return_value + end + + def delete_entry(key, **) + local_cache.delete_entry(key) if local_cache + super + end + + def write_cache_value(name, value, **options) + name = normalize_key(name, options) + cache = local_cache + if value + cache.write_entry(name, serialize_entry(new_entry(value, **options), **options)) + else + cache.delete_entry(name) + end + end + + def local_cache_key + @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym + end + + def local_cache + LocalCacheRegistry.cache_for(local_cache_key) + end + + def bypass_local_cache(&block) + use_temporary_local_cache(nil, &block) + end + + def use_temporary_local_cache(temporary_cache) + save_cache = LocalCacheRegistry.cache_for(local_cache_key) + begin + LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache) + yield + ensure + LocalCacheRegistry.set_cache_for(local_cache_key, save_cache) + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/strategy/local_cache_middleware.rb similarity index 74% rename from app/server/ruby/vendor/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/strategy/local_cache_middleware.rb index 901c2e05a8..62542bdb22 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/cache/strategy/local_cache_middleware.rb @@ -1,9 +1,12 @@ -require 'rack/body_proxy' +# frozen_string_literal: true + +require "rack/body_proxy" +require "rack/utils" + module ActiveSupport module Cache module Strategy module LocalCache - #-- # This class wraps up local storage for middlewares. Only the middleware method should # construct them. @@ -11,9 +14,9 @@ class Middleware # :nodoc: attr_reader :name, :local_cache_key def initialize(name, local_cache_key) - @name = name + @name = name @local_cache_key = local_cache_key - @app = nil + @app = nil end def new(app) @@ -27,10 +30,13 @@ def call(env) response[2] = ::Rack::BodyProxy.new(response[2]) do LocalCacheRegistry.set_cache_for(local_cache_key, nil) end + cleanup_on_body_close = true response - rescue Exception - LocalCacheRegistry.set_cache_for(local_cache_key, nil) - raise + rescue Rack::Utils::InvalidParameterError + [400, {}, []] + ensure + LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless + cleanup_on_body_close end end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/callbacks.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/callbacks.rb new file mode 100644 index 0000000000..4c4bc9fe83 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/callbacks.rb @@ -0,0 +1,961 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/descendants_tracker" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/object/blank" +require "thread" + +module ActiveSupport + # Callbacks are code hooks that are run at key points in an object's life cycle. + # The typical use case is to have a base class define a set of callbacks + # relevant to the other functionality it supplies, so that subclasses can + # install callbacks that enhance or modify the base functionality without + # needing to override or redefine methods of the base class. + # + # Mixing in this module allows you to define the events in the object's + # life cycle that will support callbacks (via ClassMethods#define_callbacks), + # set the instance methods, procs, or callback objects to be called (via + # ClassMethods#set_callback), and run the installed callbacks at the + # appropriate times (via +run_callbacks+). + # + # By default callbacks are halted by throwing +:abort+. + # See ClassMethods#define_callbacks for details. + # + # Three kinds of callbacks are supported: before callbacks, run before a + # certain event; after callbacks, run after the event; and around callbacks, + # blocks that surround the event, triggering it when they yield. Callback code + # can be contained in instance methods, procs or lambdas, or callback objects + # that respond to certain predetermined methods. See ClassMethods#set_callback + # for details. + # + # class Record + # include ActiveSupport::Callbacks + # define_callbacks :save + # + # def save + # run_callbacks :save do + # puts "- save" + # end + # end + # end + # + # class PersonRecord < Record + # set_callback :save, :before, :saving_message + # def saving_message + # puts "saving..." + # end + # + # set_callback :save, :after do |object| + # puts "saved" + # end + # end + # + # person = PersonRecord.new + # person.save + # + # Output: + # saving... + # - save + # saved + module Callbacks + extend Concern + + included do + extend ActiveSupport::DescendantsTracker + class_attribute :__callbacks, instance_writer: false, default: {} + end + + CALLBACK_FILTER_TYPES = [:before, :after, :around] + + # Runs the callbacks for the given event. + # + # Calls the before and around callbacks in the order they were set, yields + # the block (if given one), and then runs the after callbacks in reverse + # order. + # + # If the callback chain was halted, returns +false+. Otherwise returns the + # result of the block, +nil+ if no callbacks have been set, or +true+ + # if callbacks have been set but no block is given. + # + # run_callbacks :save do + # save + # end + # + #-- + # + # As this method is used in many places, and often wraps large portions of + # user code, it has an additional design goal of minimizing its impact on + # the visible call stack. An exception from inside a :before or :after + # callback can be as noisy as it likes -- but when control has passed + # smoothly through and into the supplied block, we want as little evidence + # as possible that we were here. + def run_callbacks(kind) + callbacks = __callbacks[kind.to_sym] + + if callbacks.empty? + yield if block_given? + else + env = Filters::Environment.new(self, false, nil) + next_sequence = callbacks.compile + + # Common case: no 'around' callbacks defined + if next_sequence.final? + next_sequence.invoke_before(env) + env.value = !env.halted && (!block_given? || yield) + next_sequence.invoke_after(env) + env.value + else + invoke_sequence = Proc.new do + skipped = nil + + while true + current = next_sequence + current.invoke_before(env) + if current.final? + env.value = !env.halted && (!block_given? || yield) + elsif current.skip?(env) + (skipped ||= []) << current + next_sequence = next_sequence.nested + next + else + next_sequence = next_sequence.nested + begin + target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) + target.send(method, *arguments, &block) + ensure + next_sequence = current + end + end + current.invoke_after(env) + skipped.pop.invoke_after(env) while skipped&.first + break env.value + end + end + + invoke_sequence.call + end + end + end + + private + # A hook invoked every time a before callback is halted. + # This can be overridden in ActiveSupport::Callbacks implementors in order + # to provide better debugging/logging. + def halted_callback_hook(filter, name) + end + + module Conditionals # :nodoc: + class Value + def initialize(&block) + @block = block + end + def call(target, value); @block.call(value); end + end + end + + module Filters + Environment = Struct.new(:target, :halted, :value) + + class Before + def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter, name) + halted_lambda = chain_config[:terminator] + + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name) + else + halting(callback_sequence, user_callback, halted_lambda, filter, name) + end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name) + callback_sequence.before do |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) + if env.halted + target.send :halted_callback_hook, filter, name + end + end + + env + end + end + private_class_method :halting_and_conditional + + def self.halting(callback_sequence, user_callback, halted_lambda, filter, name) + callback_sequence.before do |env| + target = env.target + value = env.value + halted = env.halted + + unless halted + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) + if env.halted + target.send :halted_callback_hook, filter, name + end + end + + env + end + end + private_class_method :halting + end + + class After + def self.build(callback_sequence, user_callback, user_conditions, chain_config) + if chain_config[:skip_after_callbacks_if_terminated] + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions) + else + halting(callback_sequence, user_callback) + end + else + if user_conditions.any? + conditional callback_sequence, user_callback, user_conditions + else + simple callback_sequence, user_callback + end + end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + end + private_class_method :halting_and_conditional + + def self.halting(callback_sequence, user_callback) + callback_sequence.after do |env| + unless env.halted + user_callback.call env.target, env.value + end + + env + end + end + private_class_method :halting + + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + + if user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + end + private_class_method :conditional + + def self.simple(callback_sequence, user_callback) + callback_sequence.after do |env| + user_callback.call env.target, env.value + + env + end + end + private_class_method :simple + end + end + + class Callback # :nodoc:# + def self.build(chain, filter, kind, options) + if filter.is_a?(String) + raise ArgumentError, <<-MSG.squish + Passing string to define a callback is not supported. See the `.set_callback` + documentation to see supported values. + MSG + end + + new chain.name, filter, kind, options, chain.config + end + + attr_accessor :kind, :name + attr_reader :chain_config, :filter + + def initialize(name, filter, kind, options, chain_config) + @chain_config = chain_config + @name = name + @kind = kind + @filter = filter + @if = check_conditionals(options[:if]) + @unless = check_conditionals(options[:unless]) + end + + def merge_conditional_options(chain, if_option:, unless_option:) + options = { + if: @if.dup, + unless: @unless.dup + } + + options[:if].concat Array(unless_option) + options[:unless].concat Array(if_option) + + self.class.build chain, @filter, @kind, options + end + + def matches?(_kind, _filter) + @kind == _kind && filter == _filter + end + + def duplicates?(other) + case @filter + when Symbol + matches?(other.kind, other.filter) + else + false + end + end + + # Wraps code with filter + def apply(callback_sequence) + user_conditions = conditions_lambdas + user_callback = CallTemplate.build(@filter, self) + + case kind + when :before + Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter, name) + when :after + Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config) + when :around + callback_sequence.around(user_callback, user_conditions) + end + end + + def current_scopes + Array(chain_config[:scope]).map { |s| public_send(s) } + end + + private + EMPTY_ARRAY = [].freeze + private_constant :EMPTY_ARRAY + + def check_conditionals(conditionals) + return EMPTY_ARRAY if conditionals.blank? + + conditionals = Array(conditionals) + if conditionals.any?(String) + raise ArgumentError, <<-MSG.squish + Passing string to be evaluated in :if and :unless conditional + options is not supported. Pass a symbol for an instance method, + or a lambda, proc or block, instead. + MSG + end + + conditionals.freeze + end + + def conditions_lambdas + @if.map { |c| CallTemplate.build(c, self).make_lambda } + + @unless.map { |c| CallTemplate.build(c, self).inverted_lambda } + end + end + + # A future invocation of user-supplied code (either as a callback, + # or a condition filter). + module CallTemplate # :nodoc: + class MethodCall + def initialize(method) + @method_name = method + end + + # Return the parts needed to make this call, with the given + # input values. + # + # Returns an array of the form: + # + # [target, block, method, *arguments] + # + # This array can be used as such: + # + # target.send(method, *arguments, &block) + # + # The actual invocation is left up to the caller to minimize + # call stack pollution. + def expand(target, value, block) + [target, block, @method_name] + end + + def make_lambda + lambda do |target, value, &block| + target.send(@method_name, &block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !target.send(@method_name, &block) + end + end + end + + class ObjectCall + def initialize(target, method) + @override_target = target + @method_name = method + end + + def expand(target, value, block) + [@override_target || target, block, @method_name, target] + end + + def make_lambda + lambda do |target, value, &block| + (@override_target || target).send(@method_name, target, &block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !(@override_target || target).send(@method_name, target, &block) + end + end + end + + class InstanceExec0 + def initialize(block) + @override_block = block + end + + def expand(target, value, block) + [target, @override_block, :instance_exec] + end + + def make_lambda + lambda do |target, value, &block| + target.instance_exec(&@override_block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !target.instance_exec(&@override_block) + end + end + end + + class InstanceExec1 + def initialize(block) + @override_block = block + end + + def expand(target, value, block) + [target, @override_block, :instance_exec, target] + end + + def make_lambda + lambda do |target, value, &block| + target.instance_exec(target, &@override_block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !target.instance_exec(target, &@override_block) + end + end + end + + class InstanceExec2 + def initialize(block) + @override_block = block + end + + def expand(target, value, block) + raise ArgumentError unless block + [target, @override_block || block, :instance_exec, target, block] + end + + def make_lambda + lambda do |target, value, &block| + raise ArgumentError unless block + target.instance_exec(target, block, &@override_block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + raise ArgumentError unless block + !target.instance_exec(target, block, &@override_block) + end + end + end + + class ProcCall + def initialize(target) + @override_target = target + end + + def expand(target, value, block) + [@override_target || target, block, :call, target, value] + end + + def make_lambda + lambda do |target, value, &block| + (@override_target || target).call(target, value, &block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !(@override_target || target).call(target, value, &block) + end + end + end + + # Filters support: + # + # Symbols:: A method to call. + # Procs:: A proc to call with the object. + # Objects:: An object with a before_foo method on it to call. + # + # All of these objects are converted into a CallTemplate and handled + # the same after this point. + def self.build(filter, callback) + case filter + when Symbol + MethodCall.new(filter) + when Conditionals::Value + ProcCall.new(filter) + when ::Proc + if filter.arity > 1 + InstanceExec2.new(filter) + elsif filter.arity > 0 + InstanceExec1.new(filter) + else + InstanceExec0.new(filter) + end + else + ObjectCall.new(filter, callback.current_scopes.join("_").to_sym) + end + end + end + + # Execute before and after filters in a sequence instead of + # chaining them with nested lambda calls, see: + # https://github.com/rails/rails/issues/18011 + class CallbackSequence # :nodoc: + def initialize(nested = nil, call_template = nil, user_conditions = nil) + @nested = nested + @call_template = call_template + @user_conditions = user_conditions + + @before = [] + @after = [] + end + + def before(&before) + @before.unshift(before) + self + end + + def after(&after) + @after.push(after) + self + end + + def around(call_template, user_conditions) + CallbackSequence.new(self, call_template, user_conditions) + end + + def skip?(arg) + arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) } + end + + attr_reader :nested + + def final? + !@call_template + end + + def expand_call_template(arg, block) + @call_template.expand(arg.target, arg.value, block) + end + + def invoke_before(arg) + @before.each { |b| b.call(arg) } + end + + def invoke_after(arg) + @after.each { |a| a.call(arg) } + end + end + + class CallbackChain # :nodoc: + include Enumerable + + attr_reader :name, :config + + def initialize(name, config) + @name = name + @config = { + scope: [:kind], + terminator: default_terminator + }.merge!(config) + @chain = [] + @callbacks = nil + @mutex = Mutex.new + end + + def each(&block); @chain.each(&block); end + def index(o); @chain.index(o); end + def empty?; @chain.empty?; end + + def insert(index, o) + @callbacks = nil + @chain.insert(index, o) + end + + def delete(o) + @callbacks = nil + @chain.delete(o) + end + + def clear + @callbacks = nil + @chain.clear + self + end + + def initialize_copy(other) + @callbacks = nil + @chain = other.chain.dup + @mutex = Mutex.new + end + + def compile + @callbacks || @mutex.synchronize do + final_sequence = CallbackSequence.new + @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + callback.apply callback_sequence + end + end + end + + def append(*callbacks) + callbacks.each { |c| append_one(c) } + end + + def prepend(*callbacks) + callbacks.each { |c| prepend_one(c) } + end + + protected + attr_reader :chain + + private + def append_one(callback) + @callbacks = nil + remove_duplicates(callback) + @chain.push(callback) + end + + def prepend_one(callback) + @callbacks = nil + remove_duplicates(callback) + @chain.unshift(callback) + end + + def remove_duplicates(callback) + @callbacks = nil + @chain.delete_if { |c| callback.duplicates?(c) } + end + + def default_terminator + Proc.new do |target, result_lambda| + terminate = true + catch(:abort) do + result_lambda.call + terminate = false + end + terminate + end + end + end + + module ClassMethods + def normalize_callback_params(filters, block) # :nodoc: + type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before + options = filters.extract_options! + filters.unshift(block) if block + [type, filters, options.dup] + end + + # This is used internally to append, prepend and skip callbacks to the + # CallbackChain. + def __update_callbacks(name) # :nodoc: + ([self] + self.descendants).reverse_each do |target| + chain = target.get_callbacks name + yield target, chain.dup + end + end + + # Install a callback for the given event. + # + # set_callback :save, :before, :before_method + # set_callback :save, :after, :after_method, if: :condition + # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff } + # + # The second argument indicates whether the callback is to be run +:before+, + # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This + # means the first example above can also be written as: + # + # set_callback :save, :before_method + # + # The callback can be specified as a symbol naming an instance method; as a + # proc, lambda, or block; or as an object that responds to a certain method + # determined by the :scope argument to +define_callbacks+. + # + # If a proc, lambda, or block is given, its body is evaluated in the context + # of the current object. It can also optionally accept the current object as + # an argument. + # + # Before and around callbacks are called in the order that they are set; + # after callbacks are called in the reverse order. + # + # Around callbacks can access the return value from the event, if it + # wasn't halted, from the +yield+ call. + # + # ===== Options + # + # * :if - A symbol or an array of symbols, each naming an instance + # method or a proc; the callback will be called only when they all return + # a true value. + # + # If a proc is given, its body is evaluated in the context of the + # current object. It can also optionally accept the current object as + # an argument. + # * :unless - A symbol or an array of symbols, each naming an + # instance method or a proc; the callback will be called only when they + # all return a false value. + # + # If a proc is given, its body is evaluated in the context of the + # current object. It can also optionally accept the current object as + # an argument. + # * :prepend - If +true+, the callback will be prepended to the + # existing chain rather than appended. + def set_callback(name, *filter_list, &block) + type, filters, options = normalize_callback_params(filter_list, block) + + self_chain = get_callbacks name + mapped = filters.map do |filter| + Callback.build(self_chain, filter, type, options) + end + + __update_callbacks(name) do |target, chain| + options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) + target.set_callbacks name, chain + end + end + + # Skip a previously set callback. Like +set_callback+, :if or + # :unless options may be passed in order to control when the + # callback is skipped. + # + # class Writer < PersonRecord + # attr_accessor :age + # skip_callback :save, :before, :saving_message, if: -> { age > 18 } + # end + # + # When if option returns true, callback is skipped. + # + # writer = Writer.new + # writer.age = 20 + # writer.save + # + # Output: + # - save + # saved + # + # When if option returns false, callback is NOT skipped. + # + # young_writer = Writer.new + # young_writer.age = 17 + # young_writer.save + # + # Output: + # saving... + # - save + # saved + # + # An ArgumentError will be raised if the callback has not + # already been set (unless the :raise option is set to false). + def skip_callback(name, *filter_list, &block) + type, filters, options = normalize_callback_params(filter_list, block) + + options[:raise] = true unless options.key?(:raise) + + __update_callbacks(name) do |target, chain| + filters.each do |filter| + callback = chain.find { |c| c.matches?(type, filter) } + + if !callback && options[:raise] + raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined" + end + + if callback && (options.key?(:if) || options.key?(:unless)) + new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless]) + chain.insert(chain.index(callback), new_callback) + end + + chain.delete(callback) + end + target.set_callbacks name, chain + end + end + + # Remove all set callbacks for the given event. + def reset_callbacks(name) + callbacks = get_callbacks name + + self.descendants.each do |target| + chain = target.get_callbacks(name).dup + callbacks.each { |c| chain.delete(c) } + target.set_callbacks name, chain + end + + set_callbacks(name, callbacks.dup.clear) + end + + # Define sets of events in the object life cycle that support callbacks. + # + # define_callbacks :validate + # define_callbacks :initialize, :save, :destroy + # + # ===== Options + # + # * :terminator - Determines when a before filter will halt the + # callback chain, preventing following before and around callbacks from + # being called and the event from being triggered. + # This should be a lambda to be executed. + # The current object and the result lambda of the callback will be provided + # to the terminator lambda. + # + # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false } + # + # In this example, if any before validate callbacks returns +false+, + # any successive before and around callback is not executed. + # + # The default terminator halts the chain when a callback throws +:abort+. + # + # * :skip_after_callbacks_if_terminated - Determines if after + # callbacks should be terminated by the :terminator option. By + # default after callbacks are executed no matter if callback chain was + # terminated or not. This option has no effect if :terminator + # option is set to +nil+. + # + # * :scope - Indicates which methods should be executed when an + # object is used as a callback. + # + # class Audit + # def before(caller) + # puts 'Audit: before is called' + # end + # + # def before_save(caller) + # puts 'Audit: before_save is called' + # end + # end + # + # class Account + # include ActiveSupport::Callbacks + # + # define_callbacks :save + # set_callback :save, :before, Audit.new + # + # def save + # run_callbacks :save do + # puts 'save in main' + # end + # end + # end + # + # In the above case whenever you save an account the method + # Audit#before will be called. On the other hand + # + # define_callbacks :save, scope: [:kind, :name] + # + # would trigger Audit#before_save instead. That's constructed + # by calling #{kind}_#{name} on the given instance. In this + # case "kind" is "before" and "name" is "save". In this context +:kind+ + # and +:name+ have special meanings: +:kind+ refers to the kind of + # callback (before/after/around) and +:name+ refers to the method on + # which callbacks are being defined. + # + # A declaration like + # + # define_callbacks :save, scope: [:name] + # + # would call Audit#save. + # + # ===== Notes + # + # +names+ passed to +define_callbacks+ must not end with + # !, ? or =. + # + # Calling +define_callbacks+ multiple times with the same +names+ will + # overwrite previous callbacks registered with +set_callback+. + def define_callbacks(*names) + options = names.extract_options! + + names.each do |name| + name = name.to_sym + + ([self] + self.descendants).each do |target| + target.set_callbacks name, CallbackChain.new(name, options) + end + + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def _run_#{name}_callbacks(&block) + run_callbacks #{name.inspect}, &block + end + + def self._#{name}_callbacks + get_callbacks(#{name.inspect}) + end + + def self._#{name}_callbacks=(value) + set_callbacks(#{name.inspect}, value) + end + + def _#{name}_callbacks + __callbacks[#{name.inspect}] + end + RUBY + end + end + + protected + def get_callbacks(name) # :nodoc: + __callbacks[name.to_sym] + end + + def set_callbacks(name, callbacks) # :nodoc: + unless singleton_class.method_defined?(:__callbacks, false) + self.__callbacks = __callbacks.dup + end + self.__callbacks[name.to_sym] = callbacks + self.__callbacks + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/code_generator.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/code_generator.rb new file mode 100644 index 0000000000..46f612d2cf --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/code_generator.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module ActiveSupport + class CodeGenerator # :nodoc: + class MethodSet + METHOD_CACHES = Hash.new { |h, k| h[k] = Module.new } + + def initialize(namespace) + @cache = METHOD_CACHES[namespace] + @sources = [] + @methods = {} + end + + def define_cached_method(name, as: name) + name = name.to_sym + as = as.to_sym + @methods.fetch(name) do + unless @cache.method_defined?(as) + yield @sources + end + @methods[name] = as + end + end + + def apply(owner, path, line) + unless @sources.empty? + @cache.module_eval("# frozen_string_literal: true\n" + @sources.join(";"), path, line) + end + @methods.each do |name, as| + owner.define_method(name, @cache.instance_method(as)) + end + end + end + + class << self + def batch(owner, path, line) + if owner.is_a?(CodeGenerator) + yield owner + else + instance = new(owner, path, line) + result = yield instance + instance.execute + result + end + end + end + + def initialize(owner, path, line) + @owner = owner + @path = path + @line = line + @namespaces = Hash.new { |h, k| h[k] = MethodSet.new(k) } + end + + def define_cached_method(name, namespace:, as: name, &block) + @namespaces[namespace].define_cached_method(name, as: as, &block) + end + + def execute + @namespaces.each_value do |method_set| + method_set.apply(@owner, @path, @line - 1) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/concern.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concern.rb similarity index 51% rename from app/server/ruby/vendor/activesupport/lib/active_support/concern.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concern.rb index 9d5cee54e3..95113382d9 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/concern.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concern.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveSupport # A typical module looks like this: # @@ -17,7 +19,7 @@ module ActiveSupport # By using ActiveSupport::Concern the above module could instead be # written as: # - # require 'active_support/concern' + # require "active_support/concern" # # module M # extend ActiveSupport::Concern @@ -74,7 +76,7 @@ module ActiveSupport # is the +Bar+ module, not the +Host+ class. With ActiveSupport::Concern, # module dependencies are properly resolved: # - # require 'active_support/concern' + # require "active_support/concern" # # module Foo # extend ActiveSupport::Concern @@ -95,44 +97,115 @@ module ActiveSupport # end # # class Host - # include Bar # works, Bar takes care now of its dependencies + # include Bar # It works, now Bar takes care of its dependencies # end + # + # === Prepending concerns + # + # Just like include, concerns also support prepend with a corresponding + # prepended do callback. module ClassMethods or class_methods do are + # prepended as well. + # + # prepend is also used for any dependencies. module Concern - class MultipleIncludedBlocks < StandardError #:nodoc: + class MultipleIncludedBlocks < StandardError # :nodoc: def initialize super "Cannot define multiple 'included' blocks for a Concern" end end - def self.extended(base) #:nodoc: + class MultiplePrependBlocks < StandardError # :nodoc: + def initialize + super "Cannot define multiple 'prepended' blocks for a Concern" + end + end + + def self.extended(base) # :nodoc: base.instance_variable_set(:@_dependencies, []) end - def append_features(base) + def append_features(base) # :nodoc: if base.instance_variable_defined?(:@_dependencies) base.instance_variable_get(:@_dependencies) << self - return false + false else return false if base < self - @_dependencies.each { |dep| base.send(:include, dep) } + @_dependencies.each { |dep| base.include(dep) } super base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) end end + def prepend_features(base) # :nodoc: + if base.instance_variable_defined?(:@_dependencies) + base.instance_variable_get(:@_dependencies).unshift self + false + else + return false if base < self + @_dependencies.each { |dep| base.prepend(dep) } + super + base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods) + base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block) + end + end + + # Evaluate given block in context of base class, + # so that you can write class macros here. + # When you define more than one +included+ block, it raises an exception. def included(base = nil, &block) if base.nil? - raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block) + if instance_variable_defined?(:@_included_block) + if @_included_block.source_location != block.source_location + raise MultipleIncludedBlocks + end + else + @_included_block = block + end + else + super + end + end - @_included_block = block + # Evaluate given block in context of base class, + # so that you can write class macros here. + # When you define more than one +prepended+ block, it raises an exception. + def prepended(base = nil, &block) + if base.nil? + if instance_variable_defined?(:@_prepended_block) + if @_prepended_block.source_location != block.source_location + raise MultiplePrependBlocks + end + else + @_prepended_block = block + end else super end end + # Define class methods from given block. + # You can define private class methods as well. + # + # module Example + # extend ActiveSupport::Concern + # + # class_methods do + # def foo; puts 'foo'; end + # + # private + # def bar; puts 'bar'; end + # end + # end + # + # class Buzz + # include Example + # end + # + # Buzz.foo # => "foo" + # Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError) def class_methods(&class_methods_module_definition) - mod = const_defined?(:ClassMethods) ? + mod = const_defined?(:ClassMethods, false) ? const_get(:ClassMethods) : const_set(:ClassMethods, Module.new) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concurrency/load_interlock_aware_monitor.rb new file mode 100644 index 0000000000..9af2bd94b8 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concurrency/load_interlock_aware_monitor.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + # A monitor that will permit dependency loading while blocked waiting for + # the lock. + class LoadInterlockAwareMonitor < Monitor + EXCEPTION_NEVER = { Exception => :never }.freeze + EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze + private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE + + # Enters an exclusive section, but allows dependency loading while blocked + def mon_enter + mon_try_enter || + ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super } + end + + def synchronize(&block) + Thread.handle_interrupt(EXCEPTION_NEVER) do + mon_enter + + begin + Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block) + ensure + mon_exit + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concurrency/share_lock.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concurrency/share_lock.rb new file mode 100644 index 0000000000..fef87c7498 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/concurrency/share_lock.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true + +require "thread" +require "monitor" + +module ActiveSupport + module Concurrency + # A share/exclusive lock, otherwise known as a read/write lock. + # + # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock + class ShareLock + include MonitorMixin + + # We track Thread objects, instead of just using counters, because + # we need exclusive locks to be reentrant, and we need to be able + # to upgrade share locks to exclusive. + + def raw_state # :nodoc: + synchronize do + threads = @sleeping.keys | @sharing.keys | @waiting.keys + threads |= [@exclusive_thread] if @exclusive_thread + + data = {} + + threads.each do |thread| + purpose, compatible = @waiting[thread] + + data[thread] = { + thread: thread, + sharing: @sharing[thread], + exclusive: @exclusive_thread == thread, + purpose: purpose, + compatible: compatible, + waiting: !!@waiting[thread], + sleeper: @sleeping[thread], + } + end + + # NB: Yields while holding our *internal* synchronize lock, + # which is supposed to be used only for a few instructions at + # a time. This allows the caller to inspect additional state + # without things changing out from underneath, but would have + # disastrous effects upon normal operation. Fortunately, this + # method is only intended to be called when things have + # already gone wrong. + yield data + end + end + + def initialize + super() + + @cv = new_cond + + @sharing = Hash.new(0) + @waiting = {} + @sleeping = {} + @exclusive_thread = nil + @exclusive_depth = 0 + end + + # Returns false if +no_wait+ is set and the lock is not + # immediately available. Otherwise, returns true after the lock + # has been acquired. + # + # +purpose+ and +compatible+ work together; while this thread is + # waiting for the exclusive lock, it will yield its share (if any) + # to any other attempt whose +purpose+ appears in this attempt's + # +compatible+ list. This allows a "loose" upgrade, which, being + # less strict, prevents some classes of deadlocks. + # + # For many resources, loose upgrades are sufficient: if a thread + # is awaiting a lock, it is not running any other code. With + # +purpose+ matching, it is possible to yield only to other + # threads whose activity will not interfere. + def start_exclusive(purpose: nil, compatible: [], no_wait: false) + synchronize do + unless @exclusive_thread == Thread.current + if busy_for_exclusive?(purpose) + return false if no_wait + + yield_shares(purpose: purpose, compatible: compatible, block_share: true) do + wait_for(:start_exclusive) { busy_for_exclusive?(purpose) } + end + end + @exclusive_thread = Thread.current + end + @exclusive_depth += 1 + + true + end + end + + # Relinquish the exclusive lock. Must only be called by the thread + # that called start_exclusive (and currently holds the lock). + def stop_exclusive(compatible: []) + synchronize do + raise "invalid unlock" if @exclusive_thread != Thread.current + + @exclusive_depth -= 1 + if @exclusive_depth == 0 + @exclusive_thread = nil + + if eligible_waiters?(compatible) + yield_shares(compatible: compatible, block_share: true) do + wait_for(:stop_exclusive) { @exclusive_thread || eligible_waiters?(compatible) } + end + end + @cv.broadcast + end + end + end + + def start_sharing + synchronize do + if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current + # We already hold a lock; nothing to wait for + elsif @waiting[Thread.current] + # We're nested inside a +yield_shares+ call: we'll resume as + # soon as there isn't an exclusive lock in our way + wait_for(:start_sharing) { @exclusive_thread } + else + # This is an initial / outermost share call: any outstanding + # requests for an exclusive lock get to go first + wait_for(:start_sharing) { busy_for_sharing?(false) } + end + @sharing[Thread.current] += 1 + end + end + + def stop_sharing + synchronize do + if @sharing[Thread.current] > 1 + @sharing[Thread.current] -= 1 + else + @sharing.delete Thread.current + @cv.broadcast + end + end + end + + # Execute the supplied block while holding the Exclusive lock. If + # +no_wait+ is set and the lock is not immediately available, + # returns +nil+ without yielding. Otherwise, returns the result of + # the block. + # + # See +start_exclusive+ for other options. + def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false) + if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait) + begin + yield + ensure + stop_exclusive(compatible: after_compatible) + end + end + end + + # Execute the supplied block while holding the Share lock. + def sharing + start_sharing + begin + yield + ensure + stop_sharing + end + end + + # Temporarily give up all held Share locks while executing the + # supplied block, allowing any +compatible+ exclusive lock request + # to proceed. + def yield_shares(purpose: nil, compatible: [], block_share: false) + loose_shares = previous_wait = nil + synchronize do + if loose_shares = @sharing.delete(Thread.current) + if previous_wait = @waiting[Thread.current] + purpose = nil unless purpose == previous_wait[0] + compatible &= previous_wait[1] + end + compatible |= [false] unless block_share + @waiting[Thread.current] = [purpose, compatible] + end + + @cv.broadcast + end + + begin + yield + ensure + synchronize do + wait_for(:yield_shares) { @exclusive_thread && @exclusive_thread != Thread.current } + + if previous_wait + @waiting[Thread.current] = previous_wait + else + @waiting.delete Thread.current + end + @sharing[Thread.current] = loose_shares if loose_shares + end + end + end + + private + # Must be called within synchronize + def busy_for_exclusive?(purpose) + busy_for_sharing?(purpose) || + @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) + end + + def busy_for_sharing?(purpose) + (@exclusive_thread && @exclusive_thread != Thread.current) || + @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) } + end + + def eligible_waiters?(compatible) + @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } } + end + + def wait_for(method, &block) + @sleeping[Thread.current] = method + @cv.wait_while(&block) + ensure + @sleeping.delete Thread.current + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/configurable.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/configurable.rb similarity index 75% rename from app/server/ruby/vendor/activesupport/lib/active_support/configurable.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/configurable.rb index 3dd44e32d8..c97d5fd9a8 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/configurable.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/configurable.rb @@ -1,10 +1,11 @@ -require 'active_support/concern' -require 'active_support/ordered_options' -require 'active_support/core_ext/array/extract_options' +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/ordered_options" module ActiveSupport # Configurable provides a config method to store and retrieve - # configuration options as an OrderedHash. + # configuration options as an OrderedOptions. module Configurable extend ActiveSupport::Concern @@ -66,8 +67,8 @@ def configure # end # # => NameError: invalid config attribute name # - # To opt out of the instance writer method, pass instance_writer: false. - # To opt out of the instance reader method, pass instance_reader: false. + # To omit the instance writer method, pass instance_writer: false. + # To omit the instance reader method, pass instance_reader: false. # # class User # include ActiveSupport::Configurable @@ -80,7 +81,7 @@ def configure # User.new.allowed_access = true # => NoMethodError # User.new.allowed_access # => NoMethodError # - # Or pass instance_accessor: false, to opt out both instance methods. + # Or pass instance_accessor: false, to omit both instance methods. # # class User # include ActiveSupport::Configurable @@ -93,21 +94,21 @@ def configure # User.new.allowed_access = true # => NoMethodError # User.new.allowed_access # => NoMethodError # - # Also you can pass a block to set up the attribute with a default value. + # Also you can pass default or a block to set up the attribute with a default value. # # class User # include ActiveSupport::Configurable + # config_accessor :allowed_access, default: false # config_accessor :hair_colors do # [:brown, :black, :blonde, :red] # end # end # + # User.allowed_access # => false # User.hair_colors # => [:brown, :black, :blonde, :red] - def config_accessor(*names) - options = names.extract_options! - + def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil) # :doc: names.each do |name| - raise NameError.new('invalid config attribute name') unless name =~ /\A[_A-Za-z]\w*\z/ + raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name) reader, reader_line = "def #{name}; config.#{name}; end", __LINE__ writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__ @@ -115,18 +116,20 @@ def config_accessor(*names) singleton_class.class_eval reader, __FILE__, reader_line singleton_class.class_eval writer, __FILE__, writer_line - unless options[:instance_accessor] == false - class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false - class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false + if instance_accessor + class_eval reader, __FILE__, reader_line if instance_reader + class_eval writer, __FILE__, writer_line if instance_writer end - send("#{name}=", yield) if block_given? + + send("#{name}=", block_given? ? yield : default) end end + private :config_accessor end - # Reads and writes attributes from a configuration OrderedHash. + # Reads and writes attributes from a configuration OrderedOptions. # - # require 'active_support/configurable' + # require "active_support/configurable" # # class User # include ActiveSupport::Configurable @@ -144,4 +147,3 @@ def config end end end - diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/configuration_file.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/configuration_file.rb new file mode 100644 index 0000000000..d0255014d3 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/configuration_file.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module ActiveSupport + # Reads a YAML configuration file, evaluating any ERB, then + # parsing the resulting YAML. + # + # Warns in case of YAML confusing characters, like invisible + # non-breaking spaces. + class ConfigurationFile # :nodoc: + class FormatError < StandardError; end + + def initialize(content_path) + @content_path = content_path.to_s + @content = read content_path + end + + def self.parse(content_path, **options) + new(content_path).parse(**options) + end + + def parse(context: nil, **options) + source = render(context) + if YAML.respond_to?(:unsafe_load) + YAML.unsafe_load(source, **options) || {} + else + YAML.load(source, **options) || {} + end + rescue Psych::SyntaxError => error + raise "YAML syntax error occurred while parsing #{@content_path}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{error.message}" + end + + private + def read(content_path) + require "yaml" + require "erb" + + File.read(content_path).tap do |content| + if content.include?("\u00A0") + warn "#{content_path} contains invisible non-breaking spaces, you may want to remove those" + end + end + end + + def render(context) + erb = ERB.new(@content).tap { |e| e.filename = @content_path } + context ? erb.result(context) : erb.result + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext.rb new file mode 100644 index 0000000000..db2be98dca --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort.each do |path| + next if path.end_with?("core_ext/uri.rb") + require path +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array.rb new file mode 100644 index 0000000000..81c9f694c5 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/array/access" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/array/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"] +require "active_support/core_ext/array/extract" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/grouping" +require "active_support/core_ext/array/inquiry" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/access.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/access.rb new file mode 100644 index 0000000000..8f36669cea --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/access.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +class Array + # Returns the tail of the array from +position+. + # + # %w( a b c d ).from(0) # => ["a", "b", "c", "d"] + # %w( a b c d ).from(2) # => ["c", "d"] + # %w( a b c d ).from(10) # => [] + # %w().from(0) # => [] + # %w( a b c d ).from(-2) # => ["c", "d"] + # %w( a b c ).from(-10) # => [] + def from(position) + self[position, length] || [] + end + + # Returns the beginning of the array up to +position+. + # + # %w( a b c d ).to(0) # => ["a"] + # %w( a b c d ).to(2) # => ["a", "b", "c"] + # %w( a b c d ).to(10) # => ["a", "b", "c", "d"] + # %w().to(0) # => [] + # %w( a b c d ).to(-2) # => ["a", "b", "c"] + # %w( a b c ).to(-10) # => [] + def to(position) + if position >= 0 + take position + 1 + else + self[0..position] + end + end + + # Returns a new array that includes the passed elements. + # + # [ 1, 2, 3 ].including(4, 5) # => [ 1, 2, 3, 4, 5 ] + # [ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ] + def including(*elements) + self + elements.flatten(1) + end + + # Returns a copy of the Array excluding the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"] + # [ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ]) # => [ [ 0, 1 ] ] + # + # Note: This is an optimization of Enumerable#excluding that uses Array#- + # instead of Array#reject for performance reasons. + def excluding(*elements) + self - elements.flatten(1) + end + alias :without :excluding + + # Equal to self[1]. + # + # %w( a b c d e ).second # => "b" + def second + self[1] + end + + # Equal to self[2]. + # + # %w( a b c d e ).third # => "c" + def third + self[2] + end + + # Equal to self[3]. + # + # %w( a b c d e ).fourth # => "d" + def fourth + self[3] + end + + # Equal to self[4]. + # + # %w( a b c d e ).fifth # => "e" + def fifth + self[4] + end + + # Equal to self[41]. Also known as accessing "the reddit". + # + # (1..42).to_a.forty_two # => 42 + def forty_two + self[41] + end + + # Equal to self[-3]. + # + # %w( a b c d e ).third_to_last # => "c" + def third_to_last + self[-3] + end + + # Equal to self[-2]. + # + # %w( a b c d e ).second_to_last # => "d" + def second_to_last + self[-2] + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/conversions.rb similarity index 80% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/conversions.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/conversions.rb index 76ffd23ed1..6835d2116d 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/conversions.rb @@ -1,8 +1,9 @@ -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" class Array # Converts the array to a comma-separated sentence where the last element is @@ -14,12 +15,12 @@ class Array # # ==== Options # - # * :words_connector - The sign or word used to join the elements - # in arrays with two or more elements (default: ", "). - # * :two_words_connector - The sign or word used to join the elements - # in arrays with two elements (default: " and "). + # * :words_connector - The sign or word used to join all but the last + # element in arrays with three or more elements (default: ", "). # * :last_word_connector - The sign or word used to join the last element # in arrays with three or more elements (default: ", and "). + # * :two_words_connector - The sign or word used to join the elements + # in arrays with two elements (default: " and "). # * :locale - If +i18n+ is available, you can set a locale and use # the connector options defined on the 'support.array' namespace in the # corresponding dictionary file. @@ -32,7 +33,7 @@ class Array # ['one', 'two', 'three'].to_sentence # => "one, two, and three" # # ['one', 'two'].to_sentence(passing: 'invalid option') - # # => ArgumentError: Unknown key :passing + # # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale # # ['one', 'two'].to_sentence(two_words_connector: '-') # # => "one-two" @@ -60,11 +61,11 @@ def to_sentence(options = {}) options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) default_connectors = { - :words_connector => ', ', - :two_words_connector => ' and ', - :last_word_connector => ', and ' + words_connector: ", ", + two_words_connector: " and ", + last_word_connector: ", and " } - if defined?(I18n) + if options[:locale] != false && defined?(I18n) i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) default_connectors.merge!(i18n_connectors) end @@ -72,34 +73,38 @@ def to_sentence(options = {}) case length when 0 - '' + +"" when 1 - self[0].to_s.dup + +"#{self[0]}" when 2 - "#{self[0]}#{options[:two_words_connector]}#{self[1]}" + +"#{self[0]}#{options[:two_words_connector]}#{self[1]}" else - "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" + +"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" end end # Extends Array#to_s to convert a collection of elements into a # comma separated id list if :db argument is given as the format. # - # Blog.all.to_formatted_s(:db) # => "1,2,3" - def to_formatted_s(format = :default) + # This method is aliased to to_formatted_s. + # + # Blog.all.to_fs(:db) # => "1,2,3" + # Blog.none.to_fs(:db) # => "null" + # [1,2].to_fs # => "[1, 2]" + def to_fs(format = :default) case format when :db if empty? - 'null' + "null" else - collect { |element| element.id }.join(',') + collect(&:id).join(",") end else to_default_s end end + alias_method :to_formatted_s, :to_fs alias_method :to_default_s, :to_s - alias_method :to_s, :to_formatted_s # Returns a string that represents the array in XML by invoking +to_xml+ # on each element. Active Record collections delegate their representation @@ -177,17 +182,17 @@ def to_formatted_s(format = :default) # # def to_xml(options = {}) - require 'active_support/builder' unless defined?(Builder) + require "active_support/builder" unless defined?(Builder::XmlMarkup) options = options.dup options[:indent] ||= 2 options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) options[:root] ||= \ - if first.class != Hash && all? { |e| e.is_a?(first.class) } + if first.class != Hash && all?(first.class) underscored = ActiveSupport::Inflector.underscore(first.class.name) - ActiveSupport::Inflector.pluralize(underscored).tr('/', '_') + ActiveSupport::Inflector.pluralize(underscored).tr("/", "_") else - 'objects' + "objects" end builder = options[:builder] @@ -195,7 +200,7 @@ def to_xml(options = {}) root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) children = options.delete(:children) || root.singularize - attributes = options[:skip_types] ? {} : { type: 'array' } + attributes = options[:skip_types] ? {} : { type: "array" } if empty? builder.tag!(root, attributes) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/deprecated_conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/deprecated_conversions.rb new file mode 100644 index 0000000000..b2eabdf045 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/deprecated_conversions.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Array + NOT_SET = Object.new # :nodoc: + def to_s(format = NOT_SET) # :nodoc: + case format + when :db + ActiveSupport::Deprecation.warn( + "Array#to_s(#{format.inspect}) is deprecated. Please use Array#to_fs(#{format.inspect}) instead." + ) + if empty? + "null" + else + collect(&:id).join(",") + end + when NOT_SET + to_default_s + else + ActiveSupport::Deprecation.warn( + "Array#to_s(#{format.inspect}) is deprecated. Please use Array#to_fs(#{format.inspect}) instead." + ) + to_default_s + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/extract.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/extract.rb new file mode 100644 index 0000000000..cc5a8a3f88 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/extract.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Array + # Removes and returns the elements for which the block returns a true value. + # If no block is given, an Enumerator is returned instead. + # + # numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + # odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9] + # numbers # => [0, 2, 4, 6, 8] + def extract! + return to_enum(:extract!) { size } unless block_given? + + extracted_elements = [] + + reject! do |element| + extracted_elements << element if yield(element) + end + + extracted_elements + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/extract_options.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/extract_options.rb similarity index 96% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/extract_options.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/extract_options.rb index 9008a0df2a..8c7cb2e780 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/extract_options.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/extract_options.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Hash # By default, only instances of Hash itself are extractable. # Subclasses of Hash may implement this method and return diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/grouping.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/grouping.rb similarity index 79% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/grouping.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/grouping.rb index 3529d57174..36993e01dc 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/grouping.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Array # Splits or iterates over the array in groups of size +number+, # padding any remaining slots with +fill_with+ unless it is +false+. @@ -17,7 +19,12 @@ class Array # ["1", "2"] # ["3", "4"] # ["5"] - def in_groups_of(number, fill_with = nil) + def in_groups_of(number, fill_with = nil, &block) + if number.to_i <= 0 + raise ArgumentError, + "Group size must be a positive integer, was #{number.inspect}" + end + if fill_with == false collection = self else @@ -29,7 +36,7 @@ def in_groups_of(number, fill_with = nil) end if block_given? - collection.each_slice(number) { |slice| yield(slice) } + collection.each_slice(number, &block) else collection.each_slice(number).to_a end @@ -52,7 +59,7 @@ def in_groups_of(number, fill_with = nil) # ["1", "2", "3"] # ["4", "5"] # ["6", "7"] - def in_groups(number, fill_with = nil) + def in_groups(number, fill_with = nil, &block) # size.div number gives minor group size; # size % number gives how many objects need extra accommodation; # each group hold either division or division + 1 items. @@ -72,7 +79,7 @@ def in_groups(number, fill_with = nil) end if block_given? - groups.each { |g| yield(g) } + groups.each(&block) else groups end @@ -83,29 +90,20 @@ def in_groups(number, fill_with = nil) # # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] - def split(value = nil) + def split(value = nil, &block) + arr = dup + result = [] if block_given? - inject([[]]) do |results, element| - if yield(element) - results << [] - else - results.last << element - end - - results + while (idx = arr.index(&block)) + result << arr.shift(idx) + arr.shift end else - results, arr = [[]], self.dup - until arr.empty? - if (idx = arr.index(value)) - results.last.concat(arr.shift(idx)) - arr.shift - results << [] - else - results.last.concat(arr.shift(arr.size)) - end + while (idx = arr.index(value)) + result << arr.shift(idx) + arr.shift end - results end + result << arr end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/inquiry.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/inquiry.rb new file mode 100644 index 0000000000..650b1067f1 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/inquiry.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "active_support/array_inquirer" + +class Array + # Wraps the array in an ActiveSupport::ArrayInquirer object, which gives a + # friendlier way to check its string-like contents. + # + # pets = [:cat, :dog].inquiry + # + # pets.cat? # => true + # pets.ferret? # => false + # + # pets.any?(:cat, :ferret) # => true + # pets.any?(:ferret, :alligator) # => false + def inquiry + ActiveSupport::ArrayInquirer.new(self) + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/wrap.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/wrap.rb similarity index 79% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/wrap.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/wrap.rb index 152eb02218..d62f97edbf 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/array/wrap.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + class Array # Wraps its argument in an array unless it is already an array (or array-like). # # Specifically: # - # * If the argument is +nil+ an empty list is returned. + # * If the argument is +nil+ an empty array is returned. # * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned. # * Otherwise, returns an array with the argument as its single element. # @@ -15,12 +17,13 @@ class Array # # * If the argument responds to +to_ary+ the method is invoked. Kernel#Array # moves on to try +to_a+ if the returned value is +nil+, but Array.wrap returns - # +nil+ right away. + # an array with the argument as its single element right away. # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, Kernel#Array # raises an exception, while Array.wrap does not, it just returns the value. - # * It does not call +to_a+ on the argument, but returns an empty array if argument is +nil+. + # * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+ + # it returns an array with the argument as its single element. # - # The second point is easily explained with some enumerables: + # The last point is easily explained with some enumerables: # # Array(foo: :bar) # => [[:foo, :bar]] # Array.wrap(foo: :bar) # => [{:foo=>:bar}] diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/benchmark.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/benchmark.rb similarity index 65% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/benchmark.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/benchmark.rb index eb25b2bc44..f6e1b72bcf 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/benchmark.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/benchmark.rb @@ -1,4 +1,6 @@ -require 'benchmark' +# frozen_string_literal: true + +require "benchmark" class << Benchmark # Benchmark realtime in milliseconds. @@ -8,7 +10,7 @@ class << Benchmark # # Benchmark.ms { User.all } # # => 0.074 - def ms - 1000 * realtime { yield } + def ms(&block) + 1000 * realtime(&block) end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/big_decimal.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/big_decimal.rb new file mode 100644 index 0000000000..9e6a9d6331 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/big_decimal.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/big_decimal/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/big_decimal/conversions.rb new file mode 100644 index 0000000000..76ad58432f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/big_decimal/conversions.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "bigdecimal" +require "bigdecimal/util" + +module ActiveSupport + module BigDecimalWithDefaultFormat # :nodoc: + def to_s(format = "F") + super(format) + end + end +end + +BigDecimal.prepend(ActiveSupport::BigDecimalWithDefaultFormat) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class.rb new file mode 100644 index 0000000000..1c110fd07b --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/class/subclasses" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/attribute.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/attribute.rb similarity index 50% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/attribute.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/attribute.rb index f2a221c396..ec78845159 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/attribute.rb @@ -1,11 +1,21 @@ -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/array/extract_options' +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" class Class # Declare a class-level attribute whose value is inheritable by subclasses. # Subclasses can change their own value and it will not impact parent class. # + # ==== Options + # + # * :instance_reader - Sets the instance reader method (defaults to true). + # * :instance_writer - Sets the instance writer method (defaults to true). + # * :instance_accessor - Sets both instance methods (defaults to true). + # * :instance_predicate - Sets a predicate method (defaults to true). + # * :default - Sets a default value for the attribute (defaults to nil). + # + # ==== Examples + # # class Base # class_attribute :setting # end @@ -20,14 +30,14 @@ class Class # Base.setting # => true # # In the above case as long as Subclass does not assign a value to setting - # by performing Subclass.setting = _something_ , Subclass.setting + # by performing Subclass.setting = _something_, Subclass.setting # would read value assigned to parent class. Once Subclass assigns a value then # the value assigned by Subclass would be returned. # # This matches normal Ruby method inheritance: think of writing an attribute # on a subclass as overriding the reader method. However, you need to be aware # when using +class_attribute+ with mutable structures as +Array+ or +Hash+. - # In such cases, you don't want to do changes in places but use setters: + # In such cases, you don't want to do changes in place. Instead use setters: # # Base.setting = [] # Base.setting # => [] @@ -68,60 +78,54 @@ class Class # object.setting = false # => NoMethodError # # To opt out of both instance methods, pass instance_accessor: false. - def class_attribute(*attrs) - options = attrs.extract_options! - instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) - instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) - instance_predicate = options.fetch(:instance_predicate, true) + # + # To set a default value for the attribute, pass default:, like so: + # + # class_attribute :settings, default: {} + def class_attribute(*attrs, instance_accessor: true, + instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil) + class_methods, methods = [], [] attrs.each do |name| - define_singleton_method(name) { nil } - define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate + unless name.is_a?(Symbol) || name.is_a?(String) + raise TypeError, "#{name.inspect} is not a symbol nor a string" + end - ivar = "@#{name}" + class_methods << <<~RUBY # In case the method exists and is not public + silence_redefinition_of_method def #{name} + end + RUBY - define_singleton_method("#{name}=") do |val| - singleton_class.class_eval do - remove_possible_method(name) - define_method(name) { val } + methods << <<~RUBY if instance_reader + silence_redefinition_of_method def #{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} end + RUBY - if singleton_class? - class_eval do - remove_possible_method(name) - define_method(name) do - if instance_variable_defined? ivar - instance_variable_get ivar - else - singleton_class.send name - end - end - end + class_methods << <<~RUBY + silence_redefinition_of_method def #{name}=(value) + redefine_method(:#{name}) { value } if singleton_class? + redefine_singleton_method(:#{name}) { value } + value end - val - end + RUBY + + methods << <<~RUBY if instance_writer + silence_redefinition_of_method(:#{name}=) + attr_writer :#{name} + RUBY - if instance_reader - remove_possible_method name - define_method(name) do - if instance_variable_defined?(ivar) - instance_variable_get ivar - else - self.class.public_send name - end + if instance_predicate + class_methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end" + if instance_reader + methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end" end - define_method("#{name}?") { !!public_send(name) } if instance_predicate end - - attr_writer name if instance_writer end - end - private + location = caller_locations(1, 1).first + class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno) - unless respond_to?(:singleton_class?) - def singleton_class? - ancestors.first != self - end - end + attrs.each { |name| public_send("#{name}=", default) } + end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/attribute_accessors.rb similarity index 67% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/attribute_accessors.rb index 84d5e95e7a..a77354e153 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/attribute_accessors.rb @@ -1,4 +1,6 @@ +# frozen_string_literal: true + # cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d, # but we keep this around for libraries that directly require it knowing they # want cattr_*. No need to deprecate. -require 'active_support/core_ext/module/attribute_accessors' +require "active_support/core_ext/module/attribute_accessors" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/subclasses.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/subclasses.rb new file mode 100644 index 0000000000..3f4c24d8c6 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/class/subclasses.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "active_support/ruby_features" + +class Class + if ActiveSupport::RubyFeatures::CLASS_SUBCLASSES + # Returns an array with all classes that are < than its receiver. + # + # class C; end + # C.descendants # => [] + # + # class B < C; end + # C.descendants # => [B] + # + # class A < B; end + # C.descendants # => [B, A] + # + # class D < C; end + # C.descendants # => [B, A, D] + def descendants + subclasses.concat(subclasses.flat_map(&:descendants)) + end + else + def descendants + ObjectSpace.each_object(singleton_class).reject do |k| + k.singleton_class? || k == self + end + end + end + + # Returns an array with the direct children of +self+. + # + # class Foo; end + # class Bar < Foo; end + # class Baz < Bar; end + # + # Foo.subclasses # => [Bar] + def subclasses + descendants.select { |descendant| descendant.superclass == self } + end unless ActiveSupport::RubyFeatures::CLASS_SUBCLASSES +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date.rb new file mode 100644 index 0000000000..b683597f2a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date/acts_like" +require "active_support/core_ext/date/blank" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/conversions" +require "active_support/core_ext/date/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"] +require "active_support/core_ext/date/zones" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/acts_like.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/acts_like.rb similarity index 57% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/acts_like.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/acts_like.rb index cd90cee236..c8077f3774 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/acts_like.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/acts_like.rb @@ -1,4 +1,6 @@ -require 'active_support/core_ext/object/acts_like' +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" class Date # Duck-types as a Date-like class. See Object#acts_like?. diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/blank.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/blank.rb new file mode 100644 index 0000000000..2c5902970a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/blank.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "date" + +class Date # :nodoc: + # No Date is blank: + # + # Date.today.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/calculations.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/calculations.rb similarity index 68% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/calculations.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/calculations.rb index c60e833441..7dea469756 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/calculations.rb @@ -1,9 +1,11 @@ -require 'date' -require 'active_support/duration' -require 'active_support/core_ext/object/acts_like' -require 'active_support/core_ext/date/zones' -require 'active_support/core_ext/time/zones' -require 'active_support/core_ext/date_and_time/calculations' +# frozen_string_literal: true + +require "date" +require "active_support/duration" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" class Date include DateAndTime::Calculations @@ -11,22 +13,22 @@ class Date class << self attr_accessor :beginning_of_week_default - # Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=). + # Returns the week start (e.g. +:monday+) for the current request, if this has been set (via Date.beginning_of_week=). # If Date.beginning_of_week has not been set for the current request, returns the week start specified in config.beginning_of_week. - # If no config.beginning_of_week was specified, returns :monday. + # If no +config.beginning_of_week+ was specified, returns +:monday+. def beginning_of_week - Thread.current[:beginning_of_week] || beginning_of_week_default || :monday + ::ActiveSupport::IsolatedExecutionState[:beginning_of_week] || beginning_of_week_default || :monday end - # Sets Date.beginning_of_week to a week start (e.g. :monday) for current request/thread. + # Sets Date.beginning_of_week to a week start (e.g. +:monday+) for current request/thread. # # This method accepts any of the following day symbols: - # :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday + # +:monday+, +:tuesday+, +:wednesday+, +:thursday+, +:friday+, +:saturday+, +:sunday+ def beginning_of_week=(week_start) - Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start) + ::ActiveSupport::IsolatedExecutionState[:beginning_of_week] = find_beginning_of_week!(week_start) end - # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol. + # Returns week start day symbol (e.g. +:monday+), or raises an +ArgumentError+ for invalid day symbol. def find_beginning_of_week!(week_start) raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start) week_start @@ -85,7 +87,7 @@ def end_of_day end alias :at_end_of_day :end_of_day - def plus_with_duration(other) #:nodoc: + def plus_with_duration(other) # :nodoc: if ActiveSupport::Duration === other other.since(self) else @@ -95,7 +97,7 @@ def plus_with_duration(other) #:nodoc: alias_method :plus_without_duration, :+ alias_method :+, :plus_with_duration - def minus_with_duration(other) #:nodoc: + def minus_with_duration(other) # :nodoc: if ActiveSupport::Duration === other plus_with_duration(-other) else @@ -107,13 +109,29 @@ def minus_with_duration(other) #:nodoc: # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with # any of these keys: :years, :months, :weeks, :days. + # + # The increments are applied in order of time units from largest to smallest. + # In other words, the date is incremented first by +:years+, then by + # +:months+, then by +:weeks+, then by +:days+. This order can affect the + # result around the end of a month. For example, incrementing first by months + # then by days: + # + # Date.new(2004, 9, 30).advance(months: 1, days: 1) + # # => Sun, 31 Oct 2004 + # + # Whereas incrementing first by days then by months yields a different result: + # + # Date.new(2004, 9, 30).advance(days: 1).advance(months: 1) + # # => Mon, 01 Nov 2004 + # def advance(options) - options = options.dup d = self - d = d >> options.delete(:years) * 12 if options[:years] - d = d >> options.delete(:months) if options[:months] - d = d + options.delete(:weeks) * 7 if options[:weeks] - d = d + options.delete(:days) if options[:days] + + d = d >> options[:years] * 12 if options[:years] + d = d >> options[:months] if options[:months] + d = d + options[:weeks] * 7 if options[:weeks] + d = d + options[:days] if options[:days] + d end @@ -129,11 +147,11 @@ def change(options) options.fetch(:day, day) ) end - + # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there. def compare_with_coercion(other) if other.is_a?(Time) - self.to_datetime <=> other + to_datetime <=> other else compare_without_coercion(other) end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/conversions.rb new file mode 100644 index 0000000000..60112a0212 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/conversions.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "date" +require "active_support/inflector/methods" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/module/redefine_method" + +class Date + DATE_FORMATS = { + short: "%d %b", + long: "%B %d, %Y", + db: "%Y-%m-%d", + inspect: "%Y-%m-%d", + number: "%Y%m%d", + long_ordinal: lambda { |date| + day_format = ActiveSupport::Inflector.ordinalize(date.day) + date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" + }, + rfc822: "%d %b %Y", + iso8601: lambda { |date| date.iso8601 } + } + + # Convert to a formatted string. See DATE_FORMATS for predefined formats. + # + # This method is aliased to to_formatted_s. + # + # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 + # + # date.to_fs(:db) # => "2007-11-10" + # date.to_formatted_s(:db) # => "2007-11-10" + # + # date.to_fs(:short) # => "10 Nov" + # date.to_fs(:number) # => "20071110" + # date.to_fs(:long) # => "November 10, 2007" + # date.to_fs(:long_ordinal) # => "November 10th, 2007" + # date.to_fs(:rfc822) # => "10 Nov 2007" + # date.to_fs(:iso8601) # => "2007-11-10" + # + # == Adding your own date formats to to_fs + # You can add your own formats to the Date::DATE_FORMATS hash. + # Use the format name as the hash key and either a strftime string + # or Proc instance that takes a date argument as the value. + # + # # config/initializers/date_formats.rb + # Date::DATE_FORMATS[:month_and_year] = '%B %Y' + # Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") } + def to_fs(format = :default) + if formatter = DATE_FORMATS[format] + if formatter.respond_to?(:call) + formatter.call(self).to_s + else + strftime(formatter) + end + else + to_default_s + end + end + alias_method :to_formatted_s, :to_fs + alias_method :to_default_s, :to_s + + # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005" + def readable_inspect + strftime("%a, %d %b %Y") + end + alias_method :default_inspect, :inspect + alias_method :inspect, :readable_inspect + + silence_redefinition_of_method :to_time + + # Converts a Date instance to a Time, where the time is set to the beginning of the day. + # The timezone can be either +:local+ or +:utc+ (default +:local+). + # + # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 + # + # date.to_time # => 2007-11-10 00:00:00 0800 + # date.to_time(:local) # => 2007-11-10 00:00:00 0800 + # + # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC + # + # NOTE: The +:local+ timezone is Ruby's *process* timezone, i.e. ENV['TZ']. + # If the application's timezone is needed, then use +in_time_zone+ instead. + def to_time(form = :local) + raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form) + ::Time.public_send(form, year, month, day) + end + + silence_redefinition_of_method :xmlschema + + # Returns a string which represents the time in used time zone as DateTime + # defined by XML Schema: + # + # date = Date.new(2015, 05, 23) # => Sat, 23 May 2015 + # date.xmlschema # => "2015-05-23T00:00:00+04:00" + def xmlschema + in_time_zone.xmlschema + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/deprecated_conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/deprecated_conversions.rb new file mode 100644 index 0000000000..f983235f0f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/deprecated_conversions.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "date" + +class Date + NOT_SET = Object.new # :nodoc: + def to_s(format = NOT_SET) # :nodoc: + if formatter = DATE_FORMATS[format] + ActiveSupport::Deprecation.warn( + "Date#to_s(#{format.inspect}) is deprecated. Please use Date#to_fs(#{format.inspect}) instead." + ) + if formatter.respond_to?(:call) + formatter.call(self).to_s + else + strftime(formatter) + end + elsif format == NOT_SET + to_default_s + else + ActiveSupport::Deprecation.warn( + "Date#to_s(#{format.inspect}) is deprecated. Please use Date#to_fs(#{format.inspect}) instead." + ) + to_default_s + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/zones.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/zones.rb new file mode 100644 index 0000000000..2dcf97cff8 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date/zones.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/date_and_time/zones" + +class Date + include DateAndTime::Zones +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/calculations.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/calculations.rb new file mode 100644 index 0000000000..a8e53c426e --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/calculations.rb @@ -0,0 +1,364 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" +require "active_support/core_ext/date_time/conversions" + +module DateAndTime + module Calculations + DAYS_INTO_WEEK = { + sunday: 0, + monday: 1, + tuesday: 2, + wednesday: 3, + thursday: 4, + friday: 5, + saturday: 6 + } + WEEKEND_DAYS = [ 6, 0 ] + + # Returns a new date/time representing yesterday. + def yesterday + advance(days: -1) + end + + # Returns a new date/time representing tomorrow. + def tomorrow + advance(days: 1) + end + + # Returns true if the date/time is today. + def today? + to_date == ::Date.current + end + + # Returns true if the date/time is tomorrow. + def tomorrow? + to_date == ::Date.current.tomorrow + end + alias :next_day? :tomorrow? + + # Returns true if the date/time is yesterday. + def yesterday? + to_date == ::Date.current.yesterday + end + alias :prev_day? :yesterday? + + # Returns true if the date/time is in the past. + def past? + self < self.class.current + end + + # Returns true if the date/time is in the future. + def future? + self > self.class.current + end + + # Returns true if the date/time falls on a Saturday or Sunday. + def on_weekend? + WEEKEND_DAYS.include?(wday) + end + + # Returns true if the date/time does not fall on a Saturday or Sunday. + def on_weekday? + !WEEKEND_DAYS.include?(wday) + end + + # Returns true if the date/time falls before date_or_time. + def before?(date_or_time) + self < date_or_time + end + + # Returns true if the date/time falls after date_or_time. + def after?(date_or_time) + self > date_or_time + end + + # Returns a new date/time the specified number of days ago. + def days_ago(days) + advance(days: -days) + end + + # Returns a new date/time the specified number of days in the future. + def days_since(days) + advance(days: days) + end + + # Returns a new date/time the specified number of weeks ago. + def weeks_ago(weeks) + advance(weeks: -weeks) + end + + # Returns a new date/time the specified number of weeks in the future. + def weeks_since(weeks) + advance(weeks: weeks) + end + + # Returns a new date/time the specified number of months ago. + def months_ago(months) + advance(months: -months) + end + + # Returns a new date/time the specified number of months in the future. + def months_since(months) + advance(months: months) + end + + # Returns a new date/time the specified number of years ago. + def years_ago(years) + advance(years: -years) + end + + # Returns a new date/time the specified number of years in the future. + def years_since(years) + advance(years: years) + end + + # Returns a new date/time at the start of the month. + # + # today = Date.today # => Thu, 18 Jun 2015 + # today.beginning_of_month # => Mon, 01 Jun 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000 + # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000 + def beginning_of_month + first_hour(change(day: 1)) + end + alias :at_beginning_of_month :beginning_of_month + + # Returns a new date/time at the start of the quarter. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.beginning_of_quarter # => Wed, 01 Jul 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000 + def beginning_of_quarter + first_quarter_month = month - (2 + month) % 3 + beginning_of_month.change(month: first_quarter_month) + end + alias :at_beginning_of_quarter :beginning_of_quarter + + # Returns a new date/time at the end of the quarter. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.end_of_quarter # => Wed, 30 Sep 2015 + # + # +DateTime+ objects will have a time set to 23:59:59. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000 + def end_of_quarter + last_quarter_month = month + (12 - month) % 3 + beginning_of_month.change(month: last_quarter_month).end_of_month + end + alias :at_end_of_quarter :end_of_quarter + + # Returns a new date/time at the beginning of the year. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.beginning_of_year # => Thu, 01 Jan 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000 + def beginning_of_year + change(month: 1).beginning_of_month + end + alias :at_beginning_of_year :beginning_of_year + + # Returns a new date/time representing the given day in the next week. + # + # today = Date.today # => Thu, 07 May 2015 + # today.next_week # => Mon, 11 May 2015 + # + # The +given_day_in_next_week+ defaults to the beginning of the week + # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ + # when set. + # + # today = Date.today # => Thu, 07 May 2015 + # today.next_week(:friday) # => Fri, 15 May 2015 + # + # +DateTime+ objects have their time set to 0:00 unless +same_time+ is true. + # + # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000 + # now.next_week # => Mon, 11 May 2015 00:00:00 +0000 + def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) + same_time ? copy_time_to(result) : result + end + + # Returns a new date/time representing the next weekday. + def next_weekday + if next_day.on_weekend? + next_week(:monday, same_time: true) + else + next_day + end + end + + # Short-hand for months_since(3). + def next_quarter + months_since(3) + end + + # Returns a new date/time representing the given day in the previous week. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # DateTime objects have their time set to 0:00 unless +same_time+ is true. + def prev_week(start_day = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) + same_time ? copy_time_to(result) : result + end + alias_method :last_week, :prev_week + + # Returns a new date/time representing the previous weekday. + def prev_weekday + if prev_day.on_weekend? + copy_time_to(beginning_of_week(:friday)) + else + prev_day + end + end + alias_method :last_weekday, :prev_weekday + + # Short-hand for months_ago(1). + def last_month + months_ago(1) + end + + # Short-hand for months_ago(3). + def prev_quarter + months_ago(3) + end + alias_method :last_quarter, :prev_quarter + + # Short-hand for years_ago(1). + def last_year + years_ago(1) + end + + # Returns the number of days to the start of the week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + def days_to_week_start(start_day = Date.beginning_of_week) + start_day_number = DAYS_INTO_WEEK.fetch(start_day) + (wday - start_day_number) % 7 + end + + # Returns a new date/time representing the start of this week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # +DateTime+ objects have their time set to 0:00. + def beginning_of_week(start_day = Date.beginning_of_week) + result = days_ago(days_to_week_start(start_day)) + acts_like?(:time) ? result.midnight : result + end + alias :at_beginning_of_week :beginning_of_week + + # Returns Monday of this week assuming that week starts on Monday. + # +DateTime+ objects have their time set to 0:00. + def monday + beginning_of_week(:monday) + end + + # Returns a new date/time representing the end of this week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # DateTime objects have their time set to 23:59:59. + def end_of_week(start_day = Date.beginning_of_week) + last_hour(days_since(6 - days_to_week_start(start_day))) + end + alias :at_end_of_week :end_of_week + + # Returns Sunday of this week assuming that week starts on Monday. + # +DateTime+ objects have their time set to 23:59:59. + def sunday + end_of_week(:monday) + end + + # Returns a new date/time representing the end of the month. + # DateTime objects will have a time set to 23:59:59. + def end_of_month + last_day = ::Time.days_in_month(month, year) + last_hour(days_since(last_day - day)) + end + alias :at_end_of_month :end_of_month + + # Returns a new date/time representing the end of the year. + # DateTime objects will have a time set to 23:59:59. + def end_of_year + change(month: 12).end_of_month + end + alias :at_end_of_year :end_of_year + + # Returns a Range representing the whole day of the current date/time. + def all_day + beginning_of_day..end_of_day + end + + # Returns a Range representing the whole week of the current date/time. + # Week starts on start_day, default is Date.beginning_of_week or config.beginning_of_week when set. + def all_week(start_day = Date.beginning_of_week) + beginning_of_week(start_day)..end_of_week(start_day) + end + + # Returns a Range representing the whole month of the current date/time. + def all_month + beginning_of_month..end_of_month + end + + # Returns a Range representing the whole quarter of the current date/time. + def all_quarter + beginning_of_quarter..end_of_quarter + end + + # Returns a Range representing the whole year of the current date/time. + def all_year + beginning_of_year..end_of_year + end + + # Returns a new date/time representing the next occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # today.next_occurring(:monday) # => Mon, 18 Dec 2017 + # today.next_occurring(:thursday) # => Thu, 21 Dec 2017 + def next_occurring(day_of_week) + from_now = DAYS_INTO_WEEK.fetch(day_of_week) - wday + from_now += 7 unless from_now > 0 + advance(days: from_now) + end + + # Returns a new date/time representing the previous occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # today.prev_occurring(:monday) # => Mon, 11 Dec 2017 + # today.prev_occurring(:thursday) # => Thu, 07 Dec 2017 + def prev_occurring(day_of_week) + ago = wday - DAYS_INTO_WEEK.fetch(day_of_week) + ago += 7 unless ago > 0 + advance(days: -ago) + end + + private + def first_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time + end + + def last_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time + end + + def days_span(day) + (DAYS_INTO_WEEK.fetch(day) - DAYS_INTO_WEEK.fetch(Date.beginning_of_week)) % 7 + end + + def copy_time_to(other) + other.change(hour: hour, min: min, sec: sec, nsec: try(:nsec)) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/compatibility.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/compatibility.rb new file mode 100644 index 0000000000..bc13cf96f4 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/compatibility.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module DateAndTime + module Compatibility + # If true, +to_time+ preserves the timezone offset of receiver. + # + # NOTE: With Ruby 2.4+ the default for +to_time+ changed from + # converting to the local system time, to preserving the offset + # of the receiver. For backwards compatibility we're overriding + # this behavior, but new apps will have an initializer that sets + # this to true, because the new behavior is preferred. + mattr_accessor :preserve_timezone, instance_writer: false, default: false + + # Change the output of ActiveSupport::TimeZone.utc_to_local. + # + # When +true+, it returns local times with a UTC offset, with +false+ local + # times are returned as UTC. + # + # # Given this zone: + # zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + # + # # With `utc_to_local_returns_utc_offset_times = false`, local time is converted to UTC: + # zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 UTC + # + # # With `utc_to_local_returns_utc_offset_times = true`, local time is returned with UTC offset: + # zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 -0500 + mattr_accessor :utc_to_local_returns_utc_offset_times, instance_writer: false, default: false + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/zones.rb similarity index 61% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_and_time/zones.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/zones.rb index 96c6df9407..fb6a27cb27 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_and_time/zones.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_and_time/zones.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + module DateAndTime module Zones # Returns the simultaneous time in Time.zone if a zone is given or # if Time.zone_default is set. Otherwise, it returns the current time. # # Time.zone = 'Hawaii' # => 'Hawaii' - # DateTime.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 + # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 # # This method is similar to Time#localtime, except that it uses Time.zone as the local zone # instead of the operating system's time zone. @@ -14,8 +16,7 @@ module Zones # and the conversion will be based on that zone instead of Time.zone. # # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 - # DateTime.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 - # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 + # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) time_zone = ::Time.find_zone! zone time = acts_like?(:time) ? self : nil @@ -23,19 +24,17 @@ def in_time_zone(zone = ::Time.zone) if time_zone time_with_zone(time, time_zone) else - time || self.to_time + time || to_time end end private - - def time_with_zone(time, zone) - if time - ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) - else - ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) + def time_with_zone(time, zone) + if time + ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) + else + ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) + end end - end end end - diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time.rb new file mode 100644 index 0000000000..e23a8be6ae --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_time/acts_like" +require "active_support/core_ext/date_time/blank" +require "active_support/core_ext/date_time/calculations" +require "active_support/core_ext/date_time/compatibility" +require "active_support/core_ext/date_time/conversions" +require "active_support/core_ext/date_time/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"] diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/acts_like.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/acts_like.rb similarity index 68% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/acts_like.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/acts_like.rb index 8fbbe0d3e9..5dccdfe219 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/acts_like.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/acts_like.rb @@ -1,5 +1,7 @@ -require 'date' -require 'active_support/core_ext/object/acts_like' +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/object/acts_like" class DateTime # Duck-types as a Date-like class. See Object#acts_like?. diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/blank.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/blank.rb new file mode 100644 index 0000000000..d1c418cb1d --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/blank.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "date" + +class DateTime # :nodoc: + # No DateTime is ever blank: + # + # DateTime.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/calculations.rb similarity index 64% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/calculations.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/calculations.rb index 73ad0aa097..a63506809a 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/calculations.rb @@ -1,4 +1,6 @@ -require 'date' +# frozen_string_literal: true + +require "date" class DateTime class << self @@ -10,7 +12,11 @@ def current end end - # Seconds since midnight: DateTime.now.seconds_since_midnight. + # Returns the number of seconds since 00:00:00. + # + # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0 + # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296 + # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399 def seconds_since_midnight sec + (min * 60) + (hour * 3600) end @@ -24,6 +30,13 @@ def seconds_until_end_of_day end_of_day.to_i - to_i end + # Returns the fraction of a second as a +Rational+ + # + # DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2) + def subsec + sec_fraction + end + # Returns a new DateTime where one or more of the elements have been changed # according to the +options+ parameter. The time options (:hour, # :min, :sec) reset cascadingly, so if only the hour is @@ -36,13 +49,23 @@ def seconds_until_end_of_day # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0) # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0) def change(options) + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_fraction = Rational(new_nsec, 1000000000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + new_fraction = Rational(new_usec, 1000000) + end + + raise ArgumentError, "argument out of range" if new_fraction >= 1 + ::DateTime.civil( options.fetch(:year, year), options.fetch(:month, month), options.fetch(:day, day), options.fetch(:hour, hour), options.fetch(:min, options[:hour] ? 0 : min), - options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction, options.fetch(:offset, offset), options.fetch(:start, start) ) @@ -52,9 +75,23 @@ def change(options) # The +options+ parameter takes a hash with any of these keys: :years, # :months, :weeks, :days, :hours, # :minutes, :seconds. + # + # Just like Date#advance, increments are applied in order of time units from + # largest to smallest. This order can affect the result around the end of a + # month. def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days + end + d = to_date.advance(options) - datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) + datetime_advanced_by_date = change(year: d.year, month: d.month, day: d.day) seconds_to_advance = \ options.fetch(:seconds, 0) + options.fetch(:minutes, 0) * 60 + @@ -63,7 +100,7 @@ def advance(options) if seconds_to_advance.zero? datetime_advanced_by_date else - datetime_advanced_by_date.since seconds_to_advance + datetime_advanced_by_date.since(seconds_to_advance) end end @@ -77,13 +114,13 @@ def ago(seconds) # instance time. Do not use this method in combination with x.months, use # months_since instead! def since(seconds) - self + Rational(seconds.round, 86400) + self + Rational(seconds, 86400) end alias :in :since # Returns a new DateTime representing the start of the day (0:00). def beginning_of_day - change(:hour => 0) + change(hour: 0) end alias :midnight :beginning_of_day alias :at_midnight :beginning_of_day @@ -91,7 +128,7 @@ def beginning_of_day # Returns a new DateTime representing the middle of the day (12:00) def middle_of_day - change(:hour => 12) + change(hour: 12) end alias :midday :middle_of_day alias :noon :middle_of_day @@ -101,42 +138,60 @@ def middle_of_day # Returns a new DateTime representing the end of the day (23:59:59). def end_of_day - change(:hour => 23, :min => 59, :sec => 59) + change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_day :end_of_day # Returns a new DateTime representing the start of the hour (hh:00:00). def beginning_of_hour - change(:min => 0) + change(min: 0) end alias :at_beginning_of_hour :beginning_of_hour # Returns a new DateTime representing the end of the hour (hh:59:59). def end_of_hour - change(:min => 59, :sec => 59) + change(min: 59, sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_hour :end_of_hour # Returns a new DateTime representing the start of the minute (hh:mm:00). def beginning_of_minute - change(:sec => 0) + change(sec: 0) end alias :at_beginning_of_minute :beginning_of_minute # Returns a new DateTime representing the end of the minute (hh:mm:59). def end_of_minute - change(:sec => 59) + change(sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_minute :end_of_minute - # Adjusts DateTime to UTC by adding its offset value; offset is set to 0. + # Returns a Time instance of the simultaneous time in the system timezone. + def localtime(utc_offset = nil) + utc = new_offset(0) + + Time.utc( + utc.year, utc.month, utc.day, + utc.hour, utc.min, utc.sec + utc.sec_fraction + ).getlocal(utc_offset) + end + alias_method :getlocal, :localtime + + # Returns a Time instance of the simultaneous time in the UTC timezone. # # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600 - # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000 + # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC def utc - new_offset(0) + utc = new_offset(0) + + Time.utc( + utc.year, utc.month, utc.day, + utc.hour, utc.min, utc.sec + utc.sec_fraction + ) end + alias_method :getgm, :utc alias_method :getutc, :utc + alias_method :gmtime, :utc # Returns +true+ if offset == 0. def utc? @@ -152,10 +207,9 @@ def utc_offset # ActiveSupport::TimeWithZone instances can be compared with a DateTime. def <=>(other) if other.respond_to? :to_datetime - super other.to_datetime + super other.to_datetime rescue nil else - nil + super end end - end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/compatibility.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/compatibility.rb new file mode 100644 index 0000000000..7600a067cc --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/compatibility.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/redefine_method" + +class DateTime + include DateAndTime::Compatibility + + silence_redefinition_of_method :to_time + + # Either return an instance of +Time+ with the same UTC offset + # as +self+ or an instance of +Time+ representing the same time + # in the local system timezone depending on the setting of + # on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + preserve_timezone ? getlocal(utc_offset) : getlocal + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/conversions.rb similarity index 61% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/conversions.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/conversions.rb index 6ddfb72a0d..be1c472c8e 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/conversions.rb @@ -1,27 +1,29 @@ -require 'date' -require 'active_support/inflector/methods' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date_time/calculations' -require 'active_support/values/time_zone' +# frozen_string_literal: true + +require "date" +require "active_support/inflector/methods" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/calculations" +require "active_support/values/time_zone" class DateTime # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats. # - # This method is aliased to to_s. + # This method is aliased to to_formatted_s. # # === Examples # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000 # - # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00" - # datetime.to_s(:db) # => "2007-12-04 00:00:00" - # datetime.to_s(:number) # => "20071204000000" - # datetime.to_formatted_s(:short) # => "04 Dec 00:00" - # datetime.to_formatted_s(:long) # => "December 04, 2007 00:00" - # datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00" - # datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" - # datetime.to_formatted_s(:iso8601) # => "2007-12-04T00:00:00+00:00" + # datetime.to_fs(:db) # => "2007-12-04 00:00:00" + # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00" + # datetime.to_fs(:number) # => "20071204000000" + # datetime.to_fs(:short) # => "04 Dec 00:00" + # datetime.to_fs(:long) # => "December 04, 2007 00:00" + # datetime.to_fs(:long_ordinal) # => "December 4th, 2007 00:00" + # datetime.to_fs(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" + # datetime.to_fs(:iso8601) # => "2007-12-04T00:00:00+00:00" # - # == Adding your own datetime formats to to_formatted_s + # == Adding your own datetime formats to to_fs # DateTime formats are shared with Time. You can add your own to the # Time::DATE_FORMATS hash. Use the format name as the hash key and # either a strftime string or Proc instance that takes a time or @@ -30,16 +32,18 @@ class DateTime # # config/initializers/time_formats.rb # Time::DATE_FORMATS[:month_and_year] = '%B %Y' # Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") } - def to_formatted_s(format = :default) + def to_fs(format = :default) if formatter = ::Time::DATE_FORMATS[format] formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) else to_default_s end end + alias_method :to_formatted_s, :to_fs alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s) - alias_method :to_s, :to_formatted_s + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. # # datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24)) # datetime.formatted_offset # => "-06:00" @@ -50,7 +54,7 @@ def formatted_offset(colon = true, alternate_utc_string = nil) # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000". def readable_inspect - to_s(:rfc822) + to_fs(:rfc822) end alias_method :default_inspect, :inspect alias_method :inspect, :readable_inspect @@ -62,7 +66,7 @@ def readable_inspect # # => Sun, 01 Jan 2012 00:00:00 +0300 # DateTime.civil_from_format :local, 2012, 12, 17 # # => Mon, 17 Dec 2012 00:00:00 +0000 - def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0) + def self.civil_from_format(utc_or_local, year, month = 1, day = 1, hour = 0, min = 0, sec = 0) if utc_or_local.to_sym == :local offset = ::Time.local(year, month, day).utc_offset.to_r / 86400 else @@ -71,9 +75,9 @@ def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, se civil(year, month, day, hour, min, sec, offset) end - # Converts +self+ to a floating-point number of seconds since the Unix epoch. + # Converts +self+ to a floating-point number of seconds, including fractional microseconds, since the Unix epoch. def to_f - seconds_since_unix_epoch.to_f + seconds_since_unix_epoch.to_f + sec_fraction end # Converts +self+ to an integer number of seconds since the Unix epoch. @@ -92,12 +96,11 @@ def nsec end private + def offset_in_seconds + (offset * 86400).to_i + end - def offset_in_seconds - (offset * 86400).to_i - end - - def seconds_since_unix_epoch - (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight - end + def seconds_since_unix_epoch + (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight + end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/deprecated_conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/deprecated_conversions.rb new file mode 100644 index 0000000000..738083930b --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/date_time/deprecated_conversions.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "date" + +class DateTime + NOT_SET = Object.new # :nodoc: + def to_s(format = NOT_SET) # :nodoc: + if formatter = ::Time::DATE_FORMATS[format] + ActiveSupport::Deprecation.warn( + "DateTime#to_s(#{format.inspect}) is deprecated. Please use DateTime#to_fs(#{format.inspect}) instead." + ) + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + elsif format == NOT_SET + to_default_s + else + ActiveSupport::Deprecation.warn( + "DateTime#to_s(#{format.inspect}) is deprecated. Please use DateTime#to_fs(#{format.inspect}) instead." + ) + to_default_s + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/digest.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/digest.rb new file mode 100644 index 0000000000..ce1427e13a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/digest.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/digest/uuid" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/digest/uuid.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/digest/uuid.rb new file mode 100644 index 0000000000..3546932c4f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/digest/uuid.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "securerandom" +require "openssl" + +module Digest + module UUID + DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc: + URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc: + OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc: + X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc: + + mattr_accessor :use_rfc4122_namespaced_uuids, instance_accessor: false, default: false + + # Generates a v5 non-random UUID (Universally Unique IDentifier). + # + # Using OpenSSL::Digest::MD5 generates version 3 UUIDs; OpenSSL::Digest::SHA1 generates version 5 UUIDs. + # uuid_from_hash always generates the same UUID for a given name and namespace combination. + # + # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt + def self.uuid_from_hash(hash_class, namespace, name) + if hash_class == Digest::MD5 || hash_class == OpenSSL::Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 || hash_class == OpenSSL::Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected OpenSSL::Digest::SHA1 or OpenSSL::Digest::MD5, got #{hash_class.name}." + end + + uuid_namespace = pack_uuid_namespace(namespace) + + hash = hash_class.new + hash.update(uuid_namespace) + hash.update(name) + + ary = hash.digest.unpack("NnnnnN") + ary[2] = (ary[2] & 0x0FFF) | (version << 12) + ary[3] = (ary[3] & 0x3FFF) | 0x8000 + + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + # Convenience method for uuid_from_hash using OpenSSL::Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + uuid_from_hash(OpenSSL::Digest::MD5, uuid_namespace, name) + end + + # Convenience method for uuid_from_hash using OpenSSL::Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + uuid_from_hash(OpenSSL::Digest::SHA1, uuid_namespace, name) + end + + # Convenience method for SecureRandom.uuid. + def self.uuid_v4 + SecureRandom.uuid + end + + def self.pack_uuid_namespace(namespace) + if [DNS_NAMESPACE, OID_NAMESPACE, URL_NAMESPACE, X500_NAMESPACE].include?(namespace) + namespace + elsif use_rfc4122_namespaced_uuids == true + match_data = namespace.match(/\A(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})\z/) + + raise ArgumentError, "Only UUIDs are valid namespace identifiers" unless match_data.present? + + match_data.captures.map { |s| s.to_i(16) }.pack("NnnnnN") + else + ActiveSupport::Deprecation.warn <<~WARNING.squish + Providing a namespace ID that is not one of the constants defined on Digest::UUID generates an incorrect UUID value according to RFC 4122. + To enable the correct behavior, set the Rails.application.config.active_support.use_rfc4122_namespaced_uuids configuration option to true. + WARNING + + namespace + end + end + + private_class_method :pack_uuid_namespace + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/enumerable.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/enumerable.rb new file mode 100644 index 0000000000..2bc58b1e16 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/enumerable.rb @@ -0,0 +1,329 @@ +# frozen_string_literal: true + +module ActiveSupport + module EnumerableCoreExt # :nodoc: + module Constants + private + def const_missing(name) + if name == :SoleItemExpectedError + ::ActiveSupport::EnumerableCoreExt::SoleItemExpectedError + else + super + end + end + end + end +end + +module Enumerable + # Error generated by +sole+ when called on an enumerable that doesn't have + # exactly one item. + class SoleItemExpectedError < StandardError; end + + # HACK: For performance reasons, Enumerable shouldn't have any constants of its own. + # So we move SoleItemExpectedError into ActiveSupport::EnumerableCoreExt. + ActiveSupport::EnumerableCoreExt::SoleItemExpectedError = remove_const(:SoleItemExpectedError) + singleton_class.prepend(ActiveSupport::EnumerableCoreExt::Constants) + + # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements + # when we omit an identity. + + # :stopdoc: + + # We can't use Refinements here because Refinements with Module which will be prepended + # doesn't work well https://bugs.ruby-lang.org/issues/13446 + alias :_original_sum_with_required_identity :sum + private :_original_sum_with_required_identity + + # :startdoc: + + # Calculates the minimum from the extracted elements. + # + # payments = [Payment.new(5), Payment.new(15), Payment.new(10)] + # payments.minimum(:price) # => 5 + def minimum(key) + map(&key).min + end + + # Calculates the maximum from the extracted elements. + # + # payments = [Payment.new(5), Payment.new(15), Payment.new(10)] + # payments.maximum(:price) # => 15 + def maximum(key) + map(&key).max + end + + # Calculates a sum from the elements. + # + # payments.sum { |p| p.price * p.tax_rate } + # payments.sum(&:price) + # + # The latter is a shortcut for: + # + # payments.inject(0) { |sum, p| sum + p.price } + # + # It can also calculate the sum without the use of a block. + # + # [5, 15, 10].sum # => 30 + # ['foo', 'bar'].sum('') # => "foobar" + # [[1, 2], [3, 1, 5]].sum([]) # => [1, 2, 3, 1, 5] + # + # The default sum of an empty list is zero. You can override this default: + # + # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) + def sum(identity = nil, &block) + if identity + _original_sum_with_required_identity(identity, &block) + elsif block_given? + map(&block).sum + # we check `first(1) == []` to check if we have an + # empty Enumerable; checking `empty?` would return + # true for `[nil]`, which we want to deprecate to + # keep consistent with Ruby + elsif first.is_a?(Numeric) || first(1) == [] || first.respond_to?(:coerce) + identity ||= 0 + _original_sum_with_required_identity(identity, &block) + else + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4. + Sum of non-numeric elements requires an initial argument. + MSG + inject(:+) || 0 + end + end + + # Convert an enumerable to a hash, using the block result as the key and the + # element as the value. + # + # people.index_by(&:login) + # # => { "nextangle" => , "chade-" => , ...} + # + # people.index_by { |person| "#{person.first_name} #{person.last_name}" } + # # => { "Chade- Fowlersburg-e" => , "David Heinemeier Hansson" => , ...} + def index_by + if block_given? + result = {} + each { |elem| result[yield(elem)] = elem } + result + else + to_enum(:index_by) { size if respond_to?(:size) } + end + end + + # Convert an enumerable to a hash, using the element as the key and the block + # result as the value. + # + # post = Post.new(title: "hey there", body: "what's up?") + # + # %i( title body ).index_with { |attr_name| post.public_send(attr_name) } + # # => { title: "hey there", body: "what's up?" } + # + # If an argument is passed instead of a block, it will be used as the value + # for all elements: + # + # %i( created_at updated_at ).index_with(Time.now) + # # => { created_at: 2020-03-09 22:31:47, updated_at: 2020-03-09 22:31:47 } + def index_with(default = (no_default = true)) + if block_given? + result = {} + each { |elem| result[elem] = yield(elem) } + result + elsif no_default + to_enum(:index_with) { size if respond_to?(:size) } + else + result = {} + each { |elem| result[elem] = default } + result + end + end + + # Returns +true+ if the enumerable has more than 1 element. Functionally + # equivalent to enum.to_a.size > 1. Can be called with a block too, + # much like any?, so people.many? { |p| p.age > 26 } returns +true+ + # if more than one person is over 26. + def many? + cnt = 0 + if block_given? + any? do |*args| + cnt += 1 if yield(*args) + cnt > 1 + end + else + any? { (cnt += 1) > 1 } + end + end + + # Returns a new array that includes the passed elements. + # + # [ 1, 2, 3 ].including(4, 5) + # # => [ 1, 2, 3, 4, 5 ] + # + # ["David", "Rafael"].including %w[ Aaron Todd ] + # # => ["David", "Rafael", "Aaron", "Todd"] + def including(*elements) + to_a.including(*elements) + end + + # The negative of the Enumerable#include?. Returns +true+ if the + # collection does not include the object. + def exclude?(object) + !include?(object) + end + + # Returns a copy of the enumerable excluding the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd" + # # => ["David", "Rafael"] + # + # ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ] + # # => ["David", "Rafael"] + # + # {foo: 1, bar: 2, baz: 3}.excluding :bar + # # => {foo: 1, baz: 3} + def excluding(*elements) + elements.flatten!(1) + reject { |element| elements.include?(element) } + end + alias :without :excluding + + # Extract the given key from each element in the enumerable. + # + # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) + # # => ["David", "Rafael", "Aaron"] + # + # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) + # # => [[1, "David"], [2, "Rafael"]] + def pluck(*keys) + if keys.many? + map { |element| keys.map { |key| element[key] } } + else + key = keys.first + map { |element| element[key] } + end + end + + # Extract the given key from the first element in the enumerable. + # + # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name) + # # => "David" + # + # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name) + # # => [1, "David"] + def pick(*keys) + return if none? + + if keys.many? + keys.map { |key| first[key] } + else + first[keys.first] + end + end + + # Returns a new +Array+ without the blank items. + # Uses Object#blank? for determining if an item is blank. + # + # [1, "", nil, 2, " ", [], {}, false, true].compact_blank + # # => [1, 2, true] + # + # Set.new([nil, "", 1, false]).compact_blank + # # => [1] + # + # When called on a +Hash+, returns a new +Hash+ without the blank values. + # + # { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank + # # => { b: 1, f: true } + def compact_blank + reject(&:blank?) + end + + # Returns a new +Array+ where the order has been set to that provided in the +series+, based on the +key+ of the + # objects in the original enumerable. + # + # [ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ]) + # # => [ Person.find(1), Person.find(5), Person.find(3) ] + # + # If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored. + # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result. + def in_order_of(key, series) + group_by(&key).values_at(*series).flatten(1).compact + end + + # Returns the sole item in the enumerable. If there are no items, or more + # than one item, raises +Enumerable::SoleItemExpectedError+. + # + # ["x"].sole # => "x" + # Set.new.sole # => Enumerable::SoleItemExpectedError: no item found + # { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found + def sole + case count + when 1 then return first # rubocop:disable Style/RedundantReturn + when 0 then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "no item found" + when 2.. then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "multiple items found" + end + end +end + +class Hash + # Hash#reject has its own definition, so this needs one too. + def compact_blank # :nodoc: + reject { |_k, v| v.blank? } + end + + # Removes all blank values from the +Hash+ in place and returns self. + # Uses Object#blank? for determining if a value is blank. + # + # h = { a: "", b: 1, c: nil, d: [], e: false, f: true } + # h.compact_blank! + # # => { b: 1, f: true } + def compact_blank! + # use delete_if rather than reject! because it always returns self even if nothing changed + delete_if { |_k, v| v.blank? } + end +end + +class Range # :nodoc: + # Optimize range sum to use arithmetic progression if a block is not given and + # we have a range of numeric values. + def sum(identity = nil) + if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer)) + super + else + actual_last = exclude_end? ? (last - 1) : last + if actual_last >= first + sum = identity || 0 + sum + (actual_last - first + 1) * (actual_last + first) / 2 + else + identity || 0 + end + end + end +end + +# Using Refinements here in order not to expose our internal method +using Module.new { + refine Array do + alias :orig_sum :sum + end +} + +class Array # :nodoc: + def sum(init = nil, &block) + if init.is_a?(Numeric) || first.is_a?(Numeric) + init ||= 0 + orig_sum(init, &block) + else + super + end + end + + # Removes all blank elements from the +Array+ in place and returns self. + # Uses Object#blank? for determining if an item is blank. + # + # a = [1, "", nil, 2, " ", [], {}, false, true] + # a.compact_blank! + # # => [1, 2, true] + def compact_blank! + # use delete_if rather than reject! because it always returns self even if nothing changed + delete_if(&:blank?) + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/file.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/file.rb new file mode 100644 index 0000000000..64553bfa4e --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/file.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/file/atomic" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/file/atomic.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/file/atomic.rb new file mode 100644 index 0000000000..b442ea3100 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/file/atomic.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "fileutils" + +class File + # Write to a file atomically. Useful for situations where you don't + # want other processes or threads to see half-written files. + # + # File.atomic_write('important.file') do |file| + # file.write('hello') + # end + # + # This method needs to create a temporary file. By default it will create it + # in the same directory as the destination file. If you don't like this + # behavior you can provide a different directory but it must be on the + # same physical filesystem as the file you're trying to write. + # + # File.atomic_write('/data/something.important', '/data/tmp') do |file| + # file.write('hello') + # end + def self.atomic_write(file_name, temp_dir = dirname(file_name)) + require "tempfile" unless defined?(Tempfile) + + Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file| + temp_file.binmode + return_val = yield temp_file + temp_file.close + + old_stat = if exist?(file_name) + # Get original file permissions + stat(file_name) + else + # If not possible, probe which are the default permissions in the + # destination directory. + probe_stat_in(dirname(file_name)) + end + + if old_stat + # Set correct permissions on new file + begin + chown(old_stat.uid, old_stat.gid, temp_file.path) + # This operation will affect filesystem ACL's + chmod(old_stat.mode, temp_file.path) + rescue Errno::EPERM, Errno::EACCES + # Changing file ownership failed, moving on. + end + end + + # Overwrite original file with temp file + rename(temp_file.path, file_name) + return_val + end + end + + # Private utility method. + def self.probe_stat_in(dir) # :nodoc: + basename = [ + ".permissions_check", + Thread.current.object_id, + Process.pid, + rand(1000000) + ].join(".") + + file_name = join(dir, basename) + FileUtils.touch(file_name) + stat(file_name) + rescue Errno::ENOENT + file_name = nil + ensure + FileUtils.rm_f(file_name) if file_name + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash.rb new file mode 100644 index 0000000000..2f0901d853 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/conversions" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/deep_transform_values" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/slice" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/conversions.rb similarity index 69% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/conversions.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/conversions.rb index 6c3e48a3ca..9a1db92d6d 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/conversions.rb @@ -1,11 +1,12 @@ -require 'active_support/xml_mini' -require 'active_support/time' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/core_ext/string/inflections' +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/try" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/string/inflections" class Hash # Returns a string containing an XML representation of its receiver: @@ -31,7 +32,7 @@ class Hash # with +key+ as :root, and +key+ singularized as second argument. The # callable can add nodes by using options[:builder]. # - # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) }) + # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml # # => "foo" # # * If +value+ responds to +to_xml+ the method is invoked with +key+ as :root. @@ -55,8 +56,7 @@ class Hash # # XML_TYPE_NAMES = { # "Symbol" => "symbol", - # "Fixnum" => "integer", - # "Bignum" => "integer", + # "Integer" => "integer", # "BigDecimal" => "decimal", # "Float" => "float", # "TrueClass" => "boolean", @@ -72,11 +72,11 @@ class Hash # configure your own builder with the :builder option. The method also accepts # options like :dasherize and friends, they are forwarded to the builder. def to_xml(options = {}) - require 'active_support/builder' unless defined?(Builder) + require "active_support/builder" unless defined?(Builder::XmlMarkup) options = options.dup options[:indent] ||= 2 - options[:root] ||= 'hash' + options[:root] ||= "hash" options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) builder = options[:builder] @@ -106,7 +106,25 @@ class << self # # => {"hash"=>{"foo"=>1, "bar"=>2}} # # +DisallowedType+ is raised if the XML contains attributes with type="yaml" or - # type="symbol". Use Hash.from_trusted_xml to parse this XML. + # type="symbol". Use Hash.from_trusted_xml to + # parse this XML. + # + # Custom +disallowed_types+ can also be passed in the form of an + # array. + # + # xml = <<-XML + # + # + # 1 + # "David" + # + # XML + # + # hash = Hash.from_xml(xml, ['integer']) + # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer" + # + # Note that passing custom disallowed types will override the default types, + # which are Symbol and YAML. def from_xml(xml, disallowed_types = nil) ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h end @@ -120,6 +138,8 @@ def from_trusted_xml(xml) module ActiveSupport class XMLConverter # :nodoc: + # Raised if the XML contains attributes with type="yaml" or + # type="symbol". Read Hash#from_xml for more details. class DisallowedType < StandardError def initialize(type) super "Disallowed type attribute: #{type.inspect}" @@ -140,36 +160,36 @@ def to_h private def normalize_keys(params) case params - when Hash - Hash[params.map { |k,v| [k.to_s.tr('-', '_'), normalize_keys(v)] } ] - when Array - params.map { |v| normalize_keys(v) } - else - params + when Hash + Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ] + when Array + params.map { |v| normalize_keys(v) } + else + params end end def deep_to_h(value) case value - when Hash - process_hash(value) - when Array - process_array(value) - when String - value - else - raise "can't typecast #{value.class.name} - #{value.inspect}" + when Hash + process_hash(value) + when Array + process_array(value) + when String + value + else + raise "can't typecast #{value.class.name} - #{value.inspect}" end end def process_hash(value) - if value.include?('type') && !value['type'].is_a?(Hash) && @disallowed_types.include?(value['type']) - raise DisallowedType, value['type'] + if value.include?("type") && !value["type"].is_a?(Hash) && @disallowed_types.include?(value["type"]) + raise DisallowedType, value["type"] end if become_array?(value) - _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) }) - if entries.nil? || value['__content__'].try(:empty?) + _, entries = Array.wrap(value.detect { |k, v| not v.is_a?(String) }) + if entries.nil? || value["__content__"].try(:empty?) [] else case entries @@ -185,28 +205,28 @@ def process_hash(value) process_content(value) elsif become_empty_string?(value) - '' + "" elsif become_hash?(value) - xml_value = Hash[value.map { |k,v| [k, deep_to_h(v)] }] + xml_value = value.transform_values { |v| deep_to_h(v) } # Turn { files: { file: # } } into { files: # } so it is compatible with # how multipart uploaded files from HTML appear - xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value + xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value end end def become_content?(value) - value['type'] == 'file' || (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?)) + value["type"] == "file" || (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?)) end def become_array?(value) - value['type'] == 'array' + value["type"] == "array" end def become_empty_string?(value) # { "string" => true } # No tests fail when the second term is removed. - value['type'] == 'string' && value['nil'] != 'true' + value["type"] == "string" && value["nil"] != "true" end def become_hash?(value) @@ -215,19 +235,19 @@ def become_hash?(value) def nothing?(value) # blank or nil parsed values are represented by nil - value.blank? || value['nil'] == 'true' + value.blank? || value["nil"] == "true" end def garbage?(value) # If the type is the only element which makes it then # this still makes the value nil, except if type is - # a XML node(where type['value'] is a Hash) - value['type'] && !value['type'].is_a?(::Hash) && value.size == 1 + # an XML node(where type['value'] is a Hash) + value["type"] && !value["type"].is_a?(::Hash) && value.size == 1 end def process_content(value) - content = value['__content__'] - if parser = ActiveSupport::XmlMini::PARSING[value['type']] + content = value["__content__"] + if parser = ActiveSupport::XmlMini::PARSING[value["type"]] parser.arity == 1 ? parser.call(content) : parser.call(content, value) else content @@ -238,6 +258,5 @@ def process_array(value) value.map! { |i| deep_to_h(i) } value.length > 1 ? value : value.first end - end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/deep_merge.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/deep_merge.rb new file mode 100644 index 0000000000..9bc50b7bc6 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/deep_merge.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Hash + # Returns a new hash with +self+ and +other_hash+ merged recursively. + # + # h1 = { a: true, b: { c: [1, 2, 3] } } + # h2 = { a: false, b: { x: [3, 4, 5] } } + # + # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } + # + # Like with Hash#merge in the standard library, a block can be provided + # to merge values: + # + # h1 = { a: 100, b: 200, c: { c1: 100 } } + # h2 = { b: 250, c: { c1: 200 } } + # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val } + # # => { a: 100, b: 450, c: { c1: 300 } } + def deep_merge(other_hash, &block) + dup.deep_merge!(other_hash, &block) + end + + # Same as +deep_merge+, but modifies +self+. + def deep_merge!(other_hash, &block) + merge!(other_hash) do |key, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + this_val.deep_merge(other_val, &block) + elsif block_given? + block.call(key, this_val, other_val) + else + other_val + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/deep_transform_values.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/deep_transform_values.rb new file mode 100644 index 0000000000..f7aeae5bed --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/deep_transform_values.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class Hash + # Returns a new hash with all values converted by the block operation. + # This includes the values from the root hash and from all + # nested hashes and arrays. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_transform_values{ |value| value.to_s.upcase } + # # => {person: {name: "ROB", age: "28"}} + def deep_transform_values(&block) + _deep_transform_values_in_object(self, &block) + end + + # Destructively converts all values by using the block operation. + # This includes the values from the root hash and from all + # nested hashes and arrays. + def deep_transform_values!(&block) + _deep_transform_values_in_object!(self, &block) + end + + private + # Support methods for deep transforming nested hashes and arrays. + def _deep_transform_values_in_object(object, &block) + case object + when Hash + object.transform_values { |value| _deep_transform_values_in_object(value, &block) } + when Array + object.map { |e| _deep_transform_values_in_object(e, &block) } + else + yield(object) + end + end + + def _deep_transform_values_in_object!(object, &block) + case object + when Hash + object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) } + when Array + object.map! { |e| _deep_transform_values_in_object!(e, &block) } + else + yield(object) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/except.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/except.rb new file mode 100644 index 0000000000..ec96929b0a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/except.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Hash + # Returns a hash that includes everything except given keys. + # hash = { a: true, b: false, c: nil } + # hash.except(:c) # => { a: true, b: false } + # hash.except(:a, :b) # => { c: nil } + # hash # => { a: true, b: false, c: nil } + # + # This is useful for limiting a set of parameters to everything but a few known toggles: + # @person.update(params[:person].except(:admin)) + def except(*keys) + slice(*self.keys - keys) + end unless method_defined?(:except) + + # Removes the given keys from hash and returns it. + # hash = { a: true, b: false, c: nil } + # hash.except!(:c) # => { a: true, b: false } + # hash # => { a: true, b: false } + def except!(*keys) + keys.each { |key| delete(key) } + self + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/indifferent_access.rb similarity index 55% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/indifferent_access.rb index 970d6faa1d..4437363c49 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/indifferent_access.rb @@ -1,23 +1,24 @@ -require 'active_support/hash_with_indifferent_access' +# frozen_string_literal: true -class Hash +require "active_support/hash_with_indifferent_access" - # Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver: +class Hash + # Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver: # # { a: 1 }.with_indifferent_access['a'] # => 1 def with_indifferent_access - ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self) + ActiveSupport::HashWithIndifferentAccess.new(self) end # Called when object is nested under an object that receives # #with_indifferent_access. This method will be called on the current object # by the enclosing object and is aliased to #with_indifferent_access by - # default. Subclasses of Hash may overwrite this method to return +self+ if - # converting to an ActiveSupport::HashWithIndifferentAccess would not be + # default. Subclasses of Hash may override this method to return +self+ if + # converting to an ActiveSupport::HashWithIndifferentAccess would not be # desirable. # # b = { b: 1 } # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access - # # => {"b"=>32} + # # => {"b"=>1} alias nested_under_indifferent_access with_indifferent_access end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/keys.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/keys.rb similarity index 51% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/keys.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/keys.rb index 3d41aa8572..ad5daccd7c 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/keys.rb @@ -1,41 +1,20 @@ -class Hash - # Returns a new hash with all keys converted using the block operation. - # - # hash = { name: 'Rob', age: '28' } - # - # hash.transform_keys{ |key| key.to_s.upcase } - # # => {"NAME"=>"Rob", "AGE"=>"28"} - def transform_keys - result = {} - each_key do |key| - result[yield(key)] = self[key] - end - result - end - - # Destructively convert all keys using the block operations. - # Same as transform_keys but modifies +self+. - def transform_keys! - keys.each do |key| - self[yield(key)] = delete(key) - end - self - end +# frozen_string_literal: true +class Hash # Returns a new hash with all keys converted to strings. # # hash = { name: 'Rob', age: '28' } # # hash.stringify_keys - # # => { "name" => "Rob", "age" => "28" } + # # => {"name"=>"Rob", "age"=>"28"} def stringify_keys - transform_keys{ |key| key.to_s } + transform_keys(&:to_s) end - # Destructively convert all keys to strings. Same as + # Destructively converts all keys to strings. Same as # +stringify_keys+, but modifies +self+. def stringify_keys! - transform_keys!{ |key| key.to_s } + transform_keys!(&:to_s) end # Returns a new hash with all keys converted to symbols, as long as @@ -44,22 +23,24 @@ def stringify_keys! # hash = { 'name' => 'Rob', 'age' => '28' } # # hash.symbolize_keys - # # => { name: "Rob", age: "28" } + # # => {:name=>"Rob", :age=>"28"} def symbolize_keys - transform_keys{ |key| key.to_sym rescue key } + transform_keys { |key| key.to_sym rescue key } end alias_method :to_options, :symbolize_keys - # Destructively convert all keys to symbols, as long as they respond + # Destructively converts all keys to symbols, as long as they respond # to +to_sym+. Same as +symbolize_keys+, but modifies +self+. def symbolize_keys! - transform_keys!{ |key| key.to_sym rescue key } + transform_keys! { |key| key.to_sym rescue key } end alias_method :to_options!, :symbolize_keys! - # Validate all keys in a hash match *valid_keys, raising ArgumentError - # on a mismatch. Note that keys are NOT treated indifferently, meaning if you - # use strings for keys but assert symbols as keys, this will fail. + # Validates all keys in a hash match *valid_keys, raising + # +ArgumentError+ on a mismatch. + # + # Note that keys are treated differently than HashWithIndifferentAccess, + # meaning that string and symbol keys will not match. # # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age" # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'" @@ -75,66 +56,88 @@ def assert_valid_keys(*valid_keys) # Returns a new hash with all keys converted by the block operation. # This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. # - # hash = { person: { name: 'Rob', age: '28' } } + # hash = { person: { name: 'Rob', age: '28' } } # - # hash.deep_transform_keys{ |key| key.to_s.upcase } - # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}} + # hash.deep_transform_keys{ |key| key.to_s.upcase } + # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}} def deep_transform_keys(&block) - result = {} - each do |key, value| - result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value - end - result + _deep_transform_keys_in_object(self, &block) end - # Destructively convert all keys by using the block operation. + # Destructively converts all keys by using the block operation. # This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. def deep_transform_keys!(&block) - keys.each do |key| - value = delete(key) - self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value - end - self + _deep_transform_keys_in_object!(self, &block) end # Returns a new hash with all keys converted to strings. # This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. # # hash = { person: { name: 'Rob', age: '28' } } # # hash.deep_stringify_keys # # => {"person"=>{"name"=>"Rob", "age"=>"28"}} def deep_stringify_keys - deep_transform_keys{ |key| key.to_s } + deep_transform_keys(&:to_s) end - # Destructively convert all keys to strings. + # Destructively converts all keys to strings. # This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. def deep_stringify_keys! - deep_transform_keys!{ |key| key.to_s } + deep_transform_keys!(&:to_s) end # Returns a new hash with all keys converted to symbols, as long as # they respond to +to_sym+. This includes the keys from the root hash - # and from all nested hashes. + # and from all nested hashes and arrays. # # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } } # # hash.deep_symbolize_keys # # => {:person=>{:name=>"Rob", :age=>"28"}} def deep_symbolize_keys - deep_transform_keys{ |key| key.to_sym rescue key } + deep_transform_keys { |key| key.to_sym rescue key } end - # Destructively convert all keys to symbols, as long as they respond + # Destructively converts all keys to symbols, as long as they respond # to +to_sym+. This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. def deep_symbolize_keys! - deep_transform_keys!{ |key| key.to_sym rescue key } + deep_transform_keys! { |key| key.to_sym rescue key } end + + private + # Support methods for deep transforming nested hashes and arrays. + def _deep_transform_keys_in_object(object, &block) + case object + when Hash + object.each_with_object(self.class.new) do |(key, value), result| + result[yield(key)] = _deep_transform_keys_in_object(value, &block) + end + when Array + object.map { |e| _deep_transform_keys_in_object(e, &block) } + else + object + end + end + + def _deep_transform_keys_in_object!(object, &block) + case object + when Hash + object.keys.each do |key| + value = object.delete(key) + object[yield(key)] = _deep_transform_keys_in_object!(value, &block) + end + object + when Array + object.map! { |e| _deep_transform_keys_in_object!(e, &block) } + else + object + end + end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/reverse_merge.rb similarity index 75% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/reverse_merge.rb index fbb482435d..ef8d592829 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/reverse_merge.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Hash # Merges the caller into +other_hash+. For example, # @@ -12,11 +14,12 @@ class Hash def reverse_merge(other_hash) other_hash.merge(self) end + alias_method :with_defaults, :reverse_merge # Destructive +reverse_merge+. def reverse_merge!(other_hash) - # right wins if there is no left - merge!( other_hash ){|key,left,right| left } + replace(reverse_merge(other_hash)) end alias_method :reverse_update, :reverse_merge! + alias_method :with_defaults!, :reverse_merge! end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/slice.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/slice.rb new file mode 100644 index 0000000000..56bc5de382 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/hash/slice.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Hash + # Replaces the hash with only the given keys. + # Returns a hash containing the removed key/value pairs. + # + # hash = { a: 1, b: 2, c: 3, d: 4 } + # hash.slice!(:a, :b) # => {:c=>3, :d=>4} + # hash # => {:a=>1, :b=>2} + def slice!(*keys) + omit = slice(*self.keys - keys) + hash = slice(*keys) + hash.default = default + hash.default_proc = default_proc if default_proc + replace(hash) + omit + end + + # Removes and returns the key/value pairs matching the given keys. + # + # hash = { a: 1, b: 2, c: 3, d: 4 } + # hash.extract!(:a, :b) # => {:a=>1, :b=>2} + # hash # => {:c=>3, :d=>4} + def extract!(*keys) + keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) } + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer.rb new file mode 100644 index 0000000000..d22701306a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support/core_ext/integer/multiple" +require "active_support/core_ext/integer/inflections" +require "active_support/core_ext/integer/time" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/inflections.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/inflections.rb new file mode 100644 index 0000000000..c987990234 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/inflections.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "active_support/inflector" + +class Integer + # Ordinalize turns a number into an ordinal string used to denote the + # position in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # 1.ordinalize # => "1st" + # 2.ordinalize # => "2nd" + # 1002.ordinalize # => "1002nd" + # 1003.ordinalize # => "1003rd" + # -11.ordinalize # => "-11th" + # -1001.ordinalize # => "-1001st" + def ordinalize + ActiveSupport::Inflector.ordinalize(self) + end + + # Ordinal returns the suffix used to denote the position + # in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # 1.ordinal # => "st" + # 2.ordinal # => "nd" + # 1002.ordinal # => "nd" + # 1003.ordinal # => "rd" + # -11.ordinal # => "th" + # -1001.ordinal # => "st" + def ordinal + ActiveSupport::Inflector.ordinal(self) + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/multiple.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/multiple.rb similarity index 74% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/multiple.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/multiple.rb index c668c7c2eb..bd57a909c5 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/multiple.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/multiple.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Integer # Check whether the integer is evenly divisible by the argument. # @@ -5,6 +7,6 @@ class Integer # 6.multiple_of?(5) # => false # 10.multiple_of?(2) # => true def multiple_of?(number) - number != 0 ? self % number == 0 : zero? + number == 0 ? self == 0 : self % number == 0 end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/time.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/time.rb new file mode 100644 index 0000000000..5efb89cf9f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/integer/time.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/numeric/time" + +class Integer + # Returns a Duration instance matching the number of months provided. + # + # 2.months # => 2 months + def months + ActiveSupport::Duration.months(self) + end + alias :month :months + + # Returns a Duration instance matching the number of years provided. + # + # 2.years # => 2 years + def years + ActiveSupport::Duration.years(self) + end + alias :year :years +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel.rb new file mode 100644 index 0000000000..7708069301 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support/core_ext/kernel/concern" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/kernel/singleton_class" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/concern.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/concern.rb similarity index 68% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/concern.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/concern.rb index bf72caa058..0b2baed780 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/concern.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/concern.rb @@ -1,6 +1,10 @@ -require 'active_support/core_ext/module/concerning' +# frozen_string_literal: true + +require "active_support/core_ext/module/concerning" module Kernel + module_function + # A shortcut to define a toplevel concern, not within a module. # # See Module::Concerning for more. diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/reporting.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/reporting.rb new file mode 100644 index 0000000000..1ae1ae8e48 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/reporting.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Kernel + module_function + + # Sets $VERBOSE to +nil+ for the duration of the block and back to its original + # value afterwards. + # + # silence_warnings do + # value = noisy_call # no warning voiced + # end + # + # noisy_call # warning voiced + def silence_warnings(&block) + with_warnings(nil, &block) + end + + # Sets $VERBOSE to +true+ for the duration of the block and back to its + # original value afterwards. + def enable_warnings(&block) + with_warnings(true, &block) + end + + # Sets $VERBOSE for the duration of the block and back to its original + # value afterwards. + def with_warnings(flag) + old_verbose, $VERBOSE = $VERBOSE, flag + yield + ensure + $VERBOSE = old_verbose + end + + # Blocks and ignores any exception passed as argument if raised within the block. + # + # suppress(ZeroDivisionError) do + # 1/0 + # puts 'This code is NOT reached' + # end + # + # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' + def suppress(*exception_classes) + yield + rescue *exception_classes + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/singleton_class.rb similarity index 50% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/singleton_class.rb index 9bbf1bbd73..31335b68f6 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/kernel/singleton_class.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module Kernel - # class_eval on an object acts like singleton_class.class_eval. + # class_eval on an object acts like +singleton_class.class_eval+. def class_eval(*args, &block) singleton_class.class_eval(*args, &block) end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/load_error.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/load_error.rb new file mode 100644 index 0000000000..03df2ddac4 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/load_error.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class LoadError + # Returns true if the given path name (except perhaps for the ".rb" + # extension) is the missing file which caused the exception to be raised. + def is_missing?(location) + location.delete_suffix(".rb") == path.to_s.delete_suffix(".rb") + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module.rb new file mode 100644 index 0000000000..542af98c04 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors_per_thread" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/module/concerning" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/module/deprecation" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/module/remove_method" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/aliasing.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/aliasing.rb new file mode 100644 index 0000000000..6f64d11627 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/aliasing.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Module + # Allows you to make aliases for attributes, which includes + # getter, setter, and a predicate. + # + # class Content < ActiveRecord::Base + # # has a title attribute + # end + # + # class Email < Content + # alias_attribute :subject, :title + # end + # + # e = Email.find(1) + # e.title # => "Superstars" + # e.subject # => "Superstars" + # e.subject? # => true + # e.subject = "Megastars" + # e.title # => "Megastars" + def alias_attribute(new_name, old_name) + # The following reader methods use an explicit `self` receiver in order to + # support aliases that start with an uppercase letter. Otherwise, they would + # be resolved as constants instead. + module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{new_name}; self.#{old_name}; end # def subject; self.title; end + def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end + def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end + STR + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/anonymous.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/anonymous.rb similarity index 54% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/anonymous.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/anonymous.rb index b0c7b021db..d1c86b8722 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/anonymous.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/anonymous.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Module # A module may or may not have a name. # @@ -7,12 +9,21 @@ class Module # m = Module.new # m.name # => nil # + # +anonymous?+ method returns true if module does not have a name, false otherwise: + # + # Module.new.anonymous? # => true + # + # module M; end + # M.anonymous? # => false + # # A module gets a name when it is first assigned to a constant. Either # via the +module+ or +class+ keyword or by an explicit assignment: # # m = Module.new # creates an anonymous module - # M = m # => m gets a name here as a side-effect + # m.anonymous? # => true + # M = m # m gets a name here as a side-effect # m.name # => "M" + # m.anonymous? # => false def anonymous? name.nil? end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attr_internal.rb similarity index 65% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/attr_internal.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attr_internal.rb index 67f0e0335d..3bd66ff3bc 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attr_internal.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + class Module # Declares an attribute reader backed by an internally-named instance variable. def attr_internal_reader(*attrs) - attrs.each {|attr_name| attr_internal_define(attr_name, :reader)} + attrs.each { |attr_name| attr_internal_define(attr_name, :reader) } end # Declares an attribute writer backed by an internally-named instance variable. def attr_internal_writer(*attrs) - attrs.each {|attr_name| attr_internal_define(attr_name, :writer)} + attrs.each { |attr_name| attr_internal_define(attr_name, :writer) } end # Declares an attribute reader and writer backed by an internally-named instance @@ -18,7 +20,7 @@ def attr_internal_accessor(*attrs) alias_method :attr_internal, :attr_internal_accessor class << self; attr_accessor :attr_internal_naming_format end - self.attr_internal_naming_format = '@_%s' + self.attr_internal_naming_format = "@_%s" private def attr_internal_ivar_name(attr) @@ -26,12 +28,9 @@ def attr_internal_ivar_name(attr) end def attr_internal_define(attr_name, type) - internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '') - # class_eval is necessary on 1.9 or else the methods are made private - class_eval do - # use native attr_* methods as they are faster on some Ruby implementations - send("attr_#{type}", internal_name) - end + internal_name = attr_internal_ivar_name(attr_name).delete_prefix("@") + # use native attr_* methods as they are faster on some Ruby implementations + public_send("attr_#{type}", internal_name) attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer alias_method attr_name, internal_name remove_method internal_name diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attribute_accessors.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attribute_accessors.rb new file mode 100644 index 0000000000..f7903aff76 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attribute_accessors.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +# == Attribute Accessors +# +# Extends the module object with class/module and instance accessors for +# class/module attributes, just like the native attr* accessors for instance +# attributes. +class Module + # Defines a class attribute and creates a class and instance reader methods. + # The underlying class variable is set to +nil+, if it is not previously + # defined. All class and instance methods created will be public, even if + # this method is called with a private or protected access modifier. + # + # module HairColors + # mattr_reader :hair_colors + # end + # + # HairColors.hair_colors # => nil + # HairColors.class_variable_set("@@hair_colors", [:brown, :black]) + # HairColors.hair_colors # => [:brown, :black] + # + # The attribute name must be a valid method name in Ruby. + # + # module Foo + # mattr_reader :"1_Badname" + # end + # # => NameError: invalid attribute name: 1_Badname + # + # To omit the instance reader method, pass + # instance_reader: false or instance_accessor: false. + # + # module HairColors + # mattr_reader :hair_colors, instance_reader: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red] + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil) + raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class? + location ||= caller_locations(1, 1).first + + definition = [] + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) + + definition << "def self.#{sym}; @@#{sym}; end" + + if instance_reader && instance_accessor + definition << "def #{sym}; @@#{sym}; end" + end + + sym_default_value = (block_given? && default.nil?) ? yield : default + class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}") + end + + module_eval(definition.join(";"), location.path, location.lineno) + end + alias :cattr_reader :mattr_reader + + # Defines a class attribute and creates a class and instance writer methods to + # allow assignment to the attribute. All class and instance methods created + # will be public, even if this method is called with a private or protected + # access modifier. + # + # module HairColors + # mattr_writer :hair_colors + # end + # + # class Person + # include HairColors + # end + # + # HairColors.hair_colors = [:brown, :black] + # Person.class_variable_get("@@hair_colors") # => [:brown, :black] + # Person.new.hair_colors = [:blonde, :red] + # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red] + # + # To omit the instance writer method, pass + # instance_writer: false or instance_accessor: false. + # + # module HairColors + # mattr_writer :hair_colors, instance_writer: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:blonde, :red] # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red] + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil) + raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class? + location ||= caller_locations(1, 1).first + + definition = [] + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) + definition << "def self.#{sym}=(val); @@#{sym} = val; end" + + if instance_writer && instance_accessor + definition << "def #{sym}=(val); @@#{sym} = val; end" + end + + sym_default_value = (block_given? && default.nil?) ? yield : default + class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}") + end + + module_eval(definition.join(";"), location.path, location.lineno) + end + alias :cattr_writer :mattr_writer + + # Defines both class and instance accessors for class attributes. + # All class and instance methods created will be public, even if + # this method is called with a private or protected access modifier. + # + # module HairColors + # mattr_accessor :hair_colors + # end + # + # class Person + # include HairColors + # end + # + # HairColors.hair_colors = [:brown, :black, :blonde, :red] + # HairColors.hair_colors # => [:brown, :black, :blonde, :red] + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + # + # If a subclass changes the value then that would also change the value for + # parent class. Similarly if parent class changes the value then that would + # change the value of subclasses too. + # + # class Citizen < Person + # end + # + # Citizen.new.hair_colors << :blue + # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue] + # + # To omit the instance writer method, pass instance_writer: false. + # To omit the instance reader method, pass instance_reader: false. + # + # module HairColors + # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # Or pass instance_accessor: false, to omit both instance methods. + # + # module HairColors + # mattr_accessor :hair_colors, instance_accessor: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red] + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk) + location = caller_locations(1, 1).first + mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk) + mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default, location: location) + end + alias :cattr_accessor :mattr_accessor +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb new file mode 100644 index 0000000000..e407cdef25 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +# == Attribute Accessors per Thread +# +# Extends the module object with class/module and instance accessors for +# class/module attributes, just like the native attr* accessors for instance +# attributes, but does so on a per-thread basis. +# +# So the values are scoped within the Thread.current space under the class name +# of the module. +# +# Note that it can also be scoped per-fiber if +Rails.application.config.active_support.isolation_level+ +# is set to +:fiber+. +class Module + # Defines a per-thread class attribute and creates class and instance reader methods. + # The underlying per-thread class variable is set to +nil+, if it is not previously defined. + # + # module Current + # thread_mattr_reader :user + # end + # + # Current.user = "DHH" + # Current.user # => "DHH" + # Thread.new { Current.user }.value # => nil + # + # The attribute name must be a valid method name in Ruby. + # + # module Foo + # thread_mattr_reader :"1_Badname" + # end + # # => NameError: invalid attribute name: 1_Badname + # + # To omit the instance reader method, pass + # instance_reader: false or instance_accessor: false. + # + # class Current + # thread_mattr_reader :user, instance_reader: false + # end + # + # Current.new.user # => NoMethodError + def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc: + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) + + # The following generated method concatenates `name` because we want it + # to work with inheritance via polymorphism. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym} + @__thread_mattr_#{sym} ||= "attr_\#{name}_#{sym}" + ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] + end + EOS + + if instance_reader && instance_accessor + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym} + self.class.#{sym} + end + EOS + end + + ::ActiveSupport::IsolatedExecutionState["attr_#{name}_#{sym}"] = default unless default.nil? + end + end + alias :thread_cattr_reader :thread_mattr_reader + + # Defines a per-thread class attribute and creates a class and instance writer methods to + # allow assignment to the attribute. + # + # module Current + # thread_mattr_writer :user + # end + # + # Current.user = "DHH" + # Thread.current[:attr_Current_user] # => "DHH" + # + # To omit the instance writer method, pass + # instance_writer: false or instance_accessor: false. + # + # class Current + # thread_mattr_writer :user, instance_writer: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc: + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) + + # The following generated method concatenates `name` because we want it + # to work with inheritance via polymorphism. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym}=(obj) + @__thread_mattr_#{sym} ||= "attr_\#{name}_#{sym}" + ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = obj + end + EOS + + if instance_writer && instance_accessor + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym}=(obj) + self.class.#{sym} = obj + end + EOS + end + + public_send("#{sym}=", default) unless default.nil? + end + end + alias :thread_cattr_writer :thread_mattr_writer + + # Defines both class and instance accessors for class attributes. + # + # class Account + # thread_mattr_accessor :user + # end + # + # Account.user = "DHH" + # Account.user # => "DHH" + # Account.new.user # => "DHH" + # + # Unlike +mattr_accessor+, values are *not* shared with subclasses or parent classes. + # If a subclass changes the value, the parent class' value is not changed. + # If the parent class changes the value, the value of subclasses is not changed. + # + # class Customer < Account + # end + # + # Account.user # => "DHH" + # Customer.user # => nil + # Customer.user = "Rafael" + # Customer.user # => "Rafael" + # Account.user # => "DHH" + # + # To omit the instance writer method, pass instance_writer: false. + # To omit the instance reader method, pass instance_reader: false. + # + # class Current + # thread_mattr_accessor :user, instance_writer: false, instance_reader: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + # Current.new.user # => NoMethodError + # + # Or pass instance_accessor: false, to omit both instance methods. + # + # class Current + # thread_mattr_accessor :user, instance_accessor: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + # Current.new.user # => NoMethodError + def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil) + thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default) + thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor) + end + alias :thread_cattr_accessor :thread_mattr_accessor +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/concerning.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/concerning.rb similarity index 80% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/concerning.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/concerning.rb index 07a392404e..36f5f85937 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/concerning.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/concerning.rb @@ -1,4 +1,6 @@ -require 'active_support/concern' +# frozen_string_literal: true + +require "active_support/concern" class Module # = Bite-sized separation of concerns @@ -20,7 +22,7 @@ class Module # # == Using comments: # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -28,7 +30,6 @@ class Module # has_many :events # # before_create :track_creation - # after_destroy :track_deletion # # private # def track_creation @@ -40,7 +41,7 @@ class Module # # Noisy syntax. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -50,7 +51,6 @@ class Module # included do # has_many :events # before_create :track_creation - # after_destroy :track_deletion # end # # private @@ -63,12 +63,12 @@ class Module # # == Mix-in noise exiled to its own file: # - # Once our chunk of behavior starts pushing the scroll-to-understand it's + # Once our chunk of behavior starts pushing the scroll-to-understand-it # boundary, we give in and move it to a separate file. At this size, the - # overhead feels in good proportion to the size of our extraction, despite - # diluting our at-a-glance sense of how things really work. + # increased overhead can be a reasonable tradeoff even if it reduces our + # at-a-glance perception of how things work. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -80,7 +80,7 @@ class Module # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to # separate bite-sized concerns. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -88,7 +88,6 @@ class Module # included do # has_many :events # before_create :track_creation - # after_destroy :track_deletion # end # # private @@ -99,16 +98,22 @@ class Module # end # # Todo.ancestors - # # => Todo, Todo::EventTracking, Object + # # => [Todo, Todo::EventTracking, ApplicationRecord, Object] # # This small step has some wonderful ripple effects. We can # * grok the behavior of our class in one glance, # * clean up monolithic junk-drawer classes by separating their concerns, and # * stop leaning on protected/private for crude "this is internal stuff" modularity. + # + # === Prepending concerning + # + # concerning supports a prepend: true argument which will prepend the + # concern instead of using include for it. module Concerning # Define a new concern and mix it in. - def concerning(topic, &block) - include concern(topic, &block) + def concerning(topic, prepend: false, &block) + method = prepend ? :prepend : :include + __send__(method, concern(topic, &block)) end # A low-cruft shortcut to define a concern. diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/delegation.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/delegation.rb new file mode 100644 index 0000000000..dcec1b1761 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/delegation.rb @@ -0,0 +1,324 @@ +# frozen_string_literal: true + +require "set" + +class Module + # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ + # option is not used. + class DelegationError < NoMethodError; end + + RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do + else elsif END end ensure false for if in module next nil not or redo rescue retry + return self super then true undef unless until when while yield) + DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block) + DELEGATION_RESERVED_METHOD_NAMES = Set.new( + RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS + ).freeze + + # Provides a +delegate+ class method to easily expose contained objects' + # public methods as your own. + # + # ==== Options + # * :to - Specifies the target object name as a symbol or string + # * :prefix - Prefixes the new method with the target name or a custom prefix + # * :allow_nil - If set to true, prevents a +Module::DelegationError+ + # from being raised + # * :private - If set to true, changes method visibility to private + # + # The macro receives one or more method names (specified as symbols or + # strings) and the name of the target object via the :to option + # (also a symbol or string). + # + # Delegation is particularly useful with Active Record associations: + # + # class Greeter < ActiveRecord::Base + # def hello + # 'hello' + # end + # + # def goodbye + # 'goodbye' + # end + # end + # + # class Foo < ActiveRecord::Base + # belongs_to :greeter + # delegate :hello, to: :greeter + # end + # + # Foo.new.hello # => "hello" + # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for # + # + # Multiple delegates to the same target are allowed: + # + # class Foo < ActiveRecord::Base + # belongs_to :greeter + # delegate :hello, :goodbye, to: :greeter + # end + # + # Foo.new.goodbye # => "goodbye" + # + # Methods can be delegated to instance variables, class variables, or constants + # by providing them as a symbols: + # + # class Foo + # CONSTANT_ARRAY = [0,1,2,3] + # @@class_array = [4,5,6,7] + # + # def initialize + # @instance_array = [8,9,10,11] + # end + # delegate :sum, to: :CONSTANT_ARRAY + # delegate :min, to: :@@class_array + # delegate :max, to: :@instance_array + # end + # + # Foo.new.sum # => 6 + # Foo.new.min # => 4 + # Foo.new.max # => 11 + # + # It's also possible to delegate a method to the class by using +:class+: + # + # class Foo + # def self.hello + # "world" + # end + # + # delegate :hello, to: :class + # end + # + # Foo.new.hello # => "world" + # + # Delegates can optionally be prefixed using the :prefix option. If the value + # is true, the delegate methods are prefixed with the name of the object being + # delegated to. + # + # Person = Struct.new(:name, :address) + # + # class Invoice < Struct.new(:client) + # delegate :name, :address, to: :client, prefix: true + # end + # + # john_doe = Person.new('John Doe', 'Vimmersvej 13') + # invoice = Invoice.new(john_doe) + # invoice.client_name # => "John Doe" + # invoice.client_address # => "Vimmersvej 13" + # + # It is also possible to supply a custom prefix. + # + # class Invoice < Struct.new(:client) + # delegate :name, :address, to: :client, prefix: :customer + # end + # + # invoice = Invoice.new(john_doe) + # invoice.customer_name # => 'John Doe' + # invoice.customer_address # => 'Vimmersvej 13' + # + # The delegated methods are public by default. + # Pass private: true to change that. + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :first_name, to: :profile + # delegate :date_of_birth, to: :profile, private: true + # + # def age + # Date.today.year - date_of_birth.year + # end + # end + # + # User.new.first_name # => "Tomas" + # User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for # + # User.new.age # => 2 + # + # If the target is +nil+ and does not respond to the delegated method a + # +Module::DelegationError+ is raised. If you wish to instead return +nil+, + # use the :allow_nil option. + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :age, to: :profile + # end + # + # User.new.age + # # => Module::DelegationError: User#age delegated to profile.age, but profile is nil + # + # But if not having a profile yet is fine and should not be an error + # condition: + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :age, to: :profile, allow_nil: true + # end + # + # User.new.age # nil + # + # Note that if the target is not +nil+ then the call is attempted regardless of the + # :allow_nil option, and thus an exception is still raised if said object + # does not respond to the method: + # + # class Foo + # def initialize(bar) + # @bar = bar + # end + # + # delegate :name, to: :@bar, allow_nil: true + # end + # + # Foo.new("Bar").name # raises NoMethodError: undefined method `name' + # + # The target method must be public, otherwise it will raise +NoMethodError+. + def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil) + unless to + raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)." + end + + if prefix == true && /^[^a-z_]/.match?(to) + raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." + end + + method_prefix = \ + if prefix + "#{prefix == true ? to : prefix}_" + else + "" + end + + location = caller_locations(1, 1).first + file, line = location.path, location.lineno + + to = to.to_s + to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to) + + method_def = [] + method_names = [] + + methods.map do |method| + method_name = prefix ? "#{method_prefix}#{method}" : method + method_names << method_name.to_sym + + # Attribute writer methods only accept one argument. Makes sure []= + # methods still accept two arguments. + definition = /[^\]]=\z/.match?(method) ? "arg" : "..." + + # The following generated method calls the target exactly once, storing + # the returned value in a dummy variable. + # + # Reason is twofold: On one hand doing less calls is in general better. + # On the other hand it could be that the target has side-effects, + # whereas conceptually, from the user point of view, the delegator should + # be doing one call. + if allow_nil + method = method.to_s + + method_def << + "def #{method_name}(#{definition})" << + " _ = #{to}" << + " if !_.nil? || nil.respond_to?(:#{method})" << + " _.#{method}(#{definition})" << + " end" << + "end" + else + method = method.to_s + method_name = method_name.to_s + + method_def << + "def #{method_name}(#{definition})" << + " _ = #{to}" << + " _.#{method}(#{definition})" << + "rescue NoMethodError => e" << + " if _.nil? && e.name == :#{method}" << + %( raise DelegationError, "#{self}##{method_name} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") << + " else" << + " raise" << + " end" << + "end" + end + end + module_eval(method_def.join(";"), file, line) + private(*method_names) if private + method_names + end + + # When building decorators, a common pattern may emerge: + # + # class Partition + # def initialize(event) + # @event = event + # end + # + # def person + # detail.person || creator + # end + # + # private + # def respond_to_missing?(name, include_private = false) + # @event.respond_to?(name, include_private) + # end + # + # def method_missing(method, *args, &block) + # @event.send(method, *args, &block) + # end + # end + # + # With Module#delegate_missing_to, the above is condensed to: + # + # class Partition + # delegate_missing_to :@event + # + # def initialize(event) + # @event = event + # end + # + # def person + # detail.person || creator + # end + # end + # + # The target can be anything callable within the object, e.g. instance + # variables, methods, constants, etc. + # + # The delegated method must be public on the target, otherwise it will + # raise +DelegationError+. If you wish to instead return +nil+, + # use the :allow_nil option. + # + # The marshal_dump and _dump methods are exempt from + # delegation due to possible interference when calling + # Marshal.dump(object), should the delegation target method + # of object add or remove instance variables. + def delegate_missing_to(target, allow_nil: nil) + target = target.to_s + target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) + + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def respond_to_missing?(name, include_private = false) + # It may look like an oversight, but we deliberately do not pass + # +include_private+, because they do not get delegated. + + return false if name == :marshal_dump || name == :_dump + #{target}.respond_to?(name) || super + end + + def method_missing(method, *args, &block) + if #{target}.respond_to?(method) + #{target}.public_send(method, *args, &block) + else + begin + super + rescue NoMethodError + if #{target}.nil? + if #{allow_nil == true} + nil + else + raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" + end + else + raise + end + end + end + end + ruby2_keywords(:method_missing) + RUBY + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/deprecation.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/deprecation.rb similarity index 81% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/deprecation.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/deprecation.rb index 56d670fbe8..71c42eb357 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/deprecation.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/deprecation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Module # deprecate :foo # deprecate bar: 'message' @@ -13,8 +15,8 @@ class Module # # class MyLib::Deprecator # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil) - # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}" - # Kernel.warn message + # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}" + # Kernel.warn message # end # end def deprecate(*method_names) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/introspection.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/introspection.rb new file mode 100644 index 0000000000..7cdcab59b8 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/introspection.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" +require "active_support/inflector" + +class Module + # Returns the name of the module containing this one. + # + # M::N.module_parent_name # => "M" + def module_parent_name + if defined?(@parent_name) + @parent_name + else + parent_name = name =~ /::[^:]+\z/ ? -$` : nil + @parent_name = parent_name unless frozen? + parent_name + end + end + + # Returns the module which contains this one according to its name. + # + # module M + # module N + # end + # end + # X = M::N + # + # M::N.module_parent # => M + # X.module_parent # => M + # + # The parent of top-level and anonymous modules is Object. + # + # M.module_parent # => Object + # Module.new.module_parent # => Object + def module_parent + module_parent_name ? ActiveSupport::Inflector.constantize(module_parent_name) : Object + end + + # Returns all the parents of this module according to its name, ordered from + # nested outwards. The receiver is not contained within the result. + # + # module M + # module N + # end + # end + # X = M::N + # + # M.module_parents # => [Object] + # M::N.module_parents # => [M, Object] + # X.module_parents # => [M, Object] + def module_parents + parents = [] + if module_parent_name + parts = module_parent_name.split("::") + until parts.empty? + parents << ActiveSupport::Inflector.constantize(parts * "::") + parts.pop + end + end + parents << Object unless parents.include? Object + parents + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/redefine_method.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/redefine_method.rb new file mode 100644 index 0000000000..5bd8e6e973 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/redefine_method.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class Module + # Marks the named method as intended to be redefined, if it exists. + # Suppresses the Ruby method redefinition warning. Prefer + # #redefine_method where possible. + def silence_redefinition_of_method(method) + if method_defined?(method) || private_method_defined?(method) + # This suppresses the "method redefined" warning; the self-alias + # looks odd, but means we don't need to generate a unique name + alias_method method, method + end + end + + # Replaces the existing method definition, if there is one, with the passed + # block as its body. + def redefine_method(method, &block) + visibility = method_visibility(method) + silence_redefinition_of_method(method) + define_method(method, &block) + send(visibility, method) + end + + # Replaces the existing singleton method definition, if there is one, with + # the passed block as its body. + def redefine_singleton_method(method, &block) + singleton_class.redefine_method(method, &block) + end + + def method_visibility(method) # :nodoc: + case + when private_method_defined?(method) + :private + when protected_method_defined?(method) + :protected + else + :public + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/remove_method.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/remove_method.rb new file mode 100644 index 0000000000..97eb5f9eca --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/module/remove_method.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" + +class Module + # Removes the named method, if it exists. + def remove_possible_method(method) + if method_defined?(method) || private_method_defined?(method) + undef_method(method) + end + end + + # Removes the named singleton method, if it exists. + def remove_possible_singleton_method(method) + singleton_class.remove_possible_method(method) + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/name_error.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/name_error.rb new file mode 100644 index 0000000000..18ea2754d1 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/name_error.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +class NameError + # Extract the name of the missing constant from the exception message. + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name + # end + # # => "HelloWorld" + def missing_name + # Since ruby v2.3.0 `did_you_mean` gem is loaded by default. + # It extends NameError#message with spell corrections which are SLOW. + # We should use original_message message instead. + message = respond_to?(:original_message) ? original_message : self.message + return unless message.start_with?("uninitialized constant ") + + receiver = begin + self.receiver + rescue ArgumentError + nil + end + + if receiver == Object + name.to_s + elsif receiver + "#{real_mod_name(receiver)}::#{self.name}" + else + if match = message.match(/((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/) + match[1] + end + end + end + + # Was this exception raised because the given name was missing? + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name?("HelloWorld") + # end + # # => true + def missing_name?(name) + if name.is_a? Symbol + self.name == name + else + missing_name == name.to_s + end + end + + private + UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name) + private_constant :UNBOUND_METHOD_MODULE_NAME + + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind_call(mod) + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric.rb new file mode 100644 index 0000000000..9368cb3fcf --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "active_support/core_ext/numeric/bytes" +require "active_support/core_ext/numeric/time" +require "active_support/core_ext/numeric/conversions" +require "active_support/core_ext/numeric/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"] diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/bytes.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/bytes.rb similarity index 50% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/bytes.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/bytes.rb index deea8e9358..b002eba406 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/bytes.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/bytes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Numeric KILOBYTE = 1024 MEGABYTE = KILOBYTE * 1024 @@ -7,36 +9,56 @@ class Numeric EXABYTE = PETABYTE * 1024 # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes + # + # 2.bytes # => 2 def bytes self end alias :byte :bytes + # Returns the number of bytes equivalent to the kilobytes provided. + # + # 2.kilobytes # => 2048 def kilobytes self * KILOBYTE end alias :kilobyte :kilobytes + # Returns the number of bytes equivalent to the megabytes provided. + # + # 2.megabytes # => 2_097_152 def megabytes self * MEGABYTE end alias :megabyte :megabytes + # Returns the number of bytes equivalent to the gigabytes provided. + # + # 2.gigabytes # => 2_147_483_648 def gigabytes self * GIGABYTE end alias :gigabyte :gigabytes + # Returns the number of bytes equivalent to the terabytes provided. + # + # 2.terabytes # => 2_199_023_255_552 def terabytes self * TERABYTE end alias :terabyte :terabytes + # Returns the number of bytes equivalent to the petabytes provided. + # + # 2.petabytes # => 2_251_799_813_685_248 def petabytes self * PETABYTE end alias :petabyte :petabytes + # Returns the number of bytes equivalent to the exabytes provided. + # + # 2.exabytes # => 2_305_843_009_213_693_952 def exabytes self * EXABYTE end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/conversions.rb new file mode 100644 index 0000000000..af672dd783 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/conversions.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/number_helper" + +module ActiveSupport + module NumericWithFormat + # Provides options for converting numbers into formatted strings. + # Options are provided for phone numbers, currency, percentage, + # precision, positional notation, file size, and pretty printing. + # + # This method is aliased to to_formatted_s. + # + # ==== Options + # + # For details on which formats use which options, see ActiveSupport::NumberHelper + # + # ==== Examples + # + # Phone Numbers: + # 5551234.to_fs(:phone) # => "555-1234" + # 1235551234.to_fs(:phone) # => "123-555-1234" + # 1235551234.to_fs(:phone, area_code: true) # => "(123) 555-1234" + # 1235551234.to_fs(:phone, delimiter: ' ') # => "123 555 1234" + # 1235551234.to_fs(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # 1235551234.to_fs(:phone, country_code: 1) # => "+1-123-555-1234" + # 1235551234.to_fs(:phone, country_code: 1, extension: 1343, delimiter: '.') + # # => "+1.123.555.1234 x 1343" + # + # Currency: + # 1234567890.50.to_fs(:currency) # => "$1,234,567,890.50" + # 1234567890.506.to_fs(:currency) # => "$1,234,567,890.51" + # 1234567890.506.to_fs(:currency, precision: 3) # => "$1,234,567,890.506" + # 1234567890.506.to_fs(:currency, round_mode: :down) # => "$1,234,567,890.50" + # 1234567890.506.to_fs(:currency, locale: :fr) # => "1 234 567 890,51 €" + # -1234567890.50.to_fs(:currency, negative_format: '(%u%n)') + # # => "($1,234,567,890.50)" + # 1234567890.50.to_fs(:currency, unit: '£', separator: ',', delimiter: '') + # # => "£1234567890,50" + # 1234567890.50.to_fs(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => "1234567890,50 £" + # + # Percentage: + # 100.to_fs(:percentage) # => "100.000%" + # 100.to_fs(:percentage, precision: 0) # => "100%" + # 1000.to_fs(:percentage, delimiter: '.', separator: ',') # => "1.000,000%" + # 302.24398923423.to_fs(:percentage, precision: 5) # => "302.24399%" + # 302.24398923423.to_fs(:percentage, round_mode: :down) # => "302.243%" + # 1000.to_fs(:percentage, locale: :fr) # => "1 000,000%" + # 100.to_fs(:percentage, format: '%n %') # => "100.000 %" + # + # Delimited: + # 12345678.to_fs(:delimited) # => "12,345,678" + # 12345678.05.to_fs(:delimited) # => "12,345,678.05" + # 12345678.to_fs(:delimited, delimiter: '.') # => "12.345.678" + # 12345678.to_fs(:delimited, delimiter: ',') # => "12,345,678" + # 12345678.05.to_fs(:delimited, separator: ' ') # => "12,345,678 05" + # 12345678.05.to_fs(:delimited, locale: :fr) # => "12 345 678,05" + # 98765432.98.to_fs(:delimited, delimiter: ' ', separator: ',') + # # => "98 765 432,98" + # + # Rounded: + # 111.2345.to_fs(:rounded) # => "111.235" + # 111.2345.to_fs(:rounded, precision: 2) # => "111.23" + # 111.2345.to_fs(:rounded, precision: 2, round_mode: :up) # => "111.24" + # 13.to_fs(:rounded, precision: 5) # => "13.00000" + # 389.32314.to_fs(:rounded, precision: 0) # => "389" + # 111.2345.to_fs(:rounded, significant: true) # => "111" + # 111.2345.to_fs(:rounded, precision: 1, significant: true) # => "100" + # 13.to_fs(:rounded, precision: 5, significant: true) # => "13.000" + # 111.234.to_fs(:rounded, locale: :fr) # => "111,234" + # 13.to_fs(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => "13" + # 389.32314.to_fs(:rounded, precision: 4, significant: true) # => "389.3" + # 1111.2345.to_fs(:rounded, precision: 2, separator: ',', delimiter: '.') + # # => "1.111,23" + # + # Human-friendly size in Bytes: + # 123.to_fs(:human_size) # => "123 Bytes" + # 1234.to_fs(:human_size) # => "1.21 KB" + # 12345.to_fs(:human_size) # => "12.1 KB" + # 1234567.to_fs(:human_size) # => "1.18 MB" + # 1234567890.to_fs(:human_size) # => "1.15 GB" + # 1234567890123.to_fs(:human_size) # => "1.12 TB" + # 1234567890123456.to_fs(:human_size) # => "1.1 PB" + # 1234567890123456789.to_fs(:human_size) # => "1.07 EB" + # 1234567.to_fs(:human_size, precision: 2) # => "1.2 MB" + # 1234567.to_fs(:human_size, precision: 2, round_mode: :up) # => "1.3 MB" + # 483989.to_fs(:human_size, precision: 2) # => "470 KB" + # 1234567.to_fs(:human_size, precision: 2, separator: ',') # => "1,2 MB" + # 1234567890123.to_fs(:human_size, precision: 5) # => "1.1228 TB" + # 524288000.to_fs(:human_size, precision: 5) # => "500 MB" + # + # Human-friendly format: + # 123.to_fs(:human) # => "123" + # 1234.to_fs(:human) # => "1.23 Thousand" + # 12345.to_fs(:human) # => "12.3 Thousand" + # 1234567.to_fs(:human) # => "1.23 Million" + # 1234567890.to_fs(:human) # => "1.23 Billion" + # 1234567890123.to_fs(:human) # => "1.23 Trillion" + # 1234567890123456.to_fs(:human) # => "1.23 Quadrillion" + # 1234567890123456789.to_fs(:human) # => "1230 Quadrillion" + # 489939.to_fs(:human, precision: 2) # => "490 Thousand" + # 489939.to_fs(:human, precision: 2, round_mode: :down) # => "480 Thousand" + # 489939.to_fs(:human, precision: 4) # => "489.9 Thousand" + # 1234567.to_fs(:human, precision: 4, + # significant: false) # => "1.2346 Million" + # 1234567.to_fs(:human, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + def to_fs(format = nil, options = nil) + return to_s if format.nil? + + case format + when Integer, String + to_s(format) + when :phone + ActiveSupport::NumberHelper.number_to_phone(self, options || {}) + when :currency + ActiveSupport::NumberHelper.number_to_currency(self, options || {}) + when :percentage + ActiveSupport::NumberHelper.number_to_percentage(self, options || {}) + when :delimited + ActiveSupport::NumberHelper.number_to_delimited(self, options || {}) + when :rounded + ActiveSupport::NumberHelper.number_to_rounded(self, options || {}) + when :human + ActiveSupport::NumberHelper.number_to_human(self, options || {}) + when :human_size + ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) + when Symbol + to_s + else + to_s(format) + end + end + alias_method :to_formatted_s, :to_fs + end +end + +Integer.prepend ActiveSupport::NumericWithFormat +Float.prepend ActiveSupport::NumericWithFormat +BigDecimal.prepend ActiveSupport::NumericWithFormat diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/deprecated_conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/deprecated_conversions.rb new file mode 100644 index 0000000000..5f81dc633a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/deprecated_conversions.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module ActiveSupport + module DeprecatedNumericWithFormat # :nodoc: + def to_s(format = nil, options = nil) + return super() if format.nil? + + case format + when Integer, String + super(format) + when :phone + ActiveSupport::Deprecation.warn( + "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_fs(#{format.inspect}) instead." + ) + ActiveSupport::NumberHelper.number_to_phone(self, options || {}) + when :currency + ActiveSupport::Deprecation.warn( + "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_fs(#{format.inspect}) instead." + ) + ActiveSupport::NumberHelper.number_to_currency(self, options || {}) + when :percentage + ActiveSupport::Deprecation.warn( + "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_fs(#{format.inspect}) instead." + ) + ActiveSupport::NumberHelper.number_to_percentage(self, options || {}) + when :delimited + ActiveSupport::Deprecation.warn( + "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_fs(#{format.inspect}) instead." + ) + ActiveSupport::NumberHelper.number_to_delimited(self, options || {}) + when :rounded + ActiveSupport::Deprecation.warn( + "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_fs(#{format.inspect}) instead." + ) + ActiveSupport::NumberHelper.number_to_rounded(self, options || {}) + when :human + ActiveSupport::Deprecation.warn( + "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_fs(#{format.inspect}) instead." + ) + ActiveSupport::NumberHelper.number_to_human(self, options || {}) + when :human_size + ActiveSupport::Deprecation.warn( + "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_fs(#{format.inspect}) instead." + ) + ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) + when Symbol + ActiveSupport::Deprecation.warn( + "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_fs(#{format.inspect}) instead." + ) + super() + else + super(format) + end + end + end +end + +Integer.prepend ActiveSupport::DeprecatedNumericWithFormat +Float.prepend ActiveSupport::DeprecatedNumericWithFormat +BigDecimal.prepend ActiveSupport::DeprecatedNumericWithFormat diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/time.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/time.rb new file mode 100644 index 0000000000..bc4627f7a2 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/numeric/time.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/acts_like" + +class Numeric + # Returns a Duration instance matching the number of seconds provided. + # + # 2.seconds # => 2 seconds + def seconds + ActiveSupport::Duration.seconds(self) + end + alias :second :seconds + + # Returns a Duration instance matching the number of minutes provided. + # + # 2.minutes # => 2 minutes + def minutes + ActiveSupport::Duration.minutes(self) + end + alias :minute :minutes + + # Returns a Duration instance matching the number of hours provided. + # + # 2.hours # => 2 hours + def hours + ActiveSupport::Duration.hours(self) + end + alias :hour :hours + + # Returns a Duration instance matching the number of days provided. + # + # 2.days # => 2 days + def days + ActiveSupport::Duration.days(self) + end + alias :day :days + + # Returns a Duration instance matching the number of weeks provided. + # + # 2.weeks # => 2 weeks + def weeks + ActiveSupport::Duration.weeks(self) + end + alias :week :weeks + + # Returns a Duration instance matching the number of fortnights provided. + # + # 2.fortnights # => 4 weeks + def fortnights + ActiveSupport::Duration.weeks(self * 2) + end + alias :fortnight :fortnights + + # Returns the number of milliseconds equivalent to the seconds provided. + # Used with the standard time durations. + # + # 2.in_milliseconds # => 2000 + # 1.hour.in_milliseconds # => 3600000 + def in_milliseconds + self * 1000 + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object.rb new file mode 100644 index 0000000000..efd34cc692 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/object/deep_dup" +require "active_support/core_ext/object/try" +require "active_support/core_ext/object/inclusion" + +require "active_support/core_ext/object/conversions" +require "active_support/core_ext/object/instance_variables" + +require "active_support/core_ext/object/json" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/with_options" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/acts_like.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/acts_like.rb new file mode 100644 index 0000000000..292826c8c1 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/acts_like.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class Object + # Provides a way to check whether some class acts like some other class based on the existence of + # an appropriately-named marker method. + # + # A class that provides the same interface as SomeClass may define a marker method named + # acts_like_some_class? to signal its compatibility to callers of + # acts_like?(:some_class). + # + # For example, Active Support extends Date to define an acts_like_date? method, + # and extends Time to define acts_like_time?. As a result, developers can call + # x.acts_like?(:time) and x.acts_like?(:date) to test duck-type compatibility, + # and classes that are able to act like Time can also define an acts_like_time? + # method to interoperate. + # + # Note that the marker method is only expected to exist. It isn't called, so its body or return + # value are irrelevant. + # + # ==== Example: A class that provides the same interface as String + # + # This class may define: + # + # class Stringish + # def acts_like_string? + # end + # end + # + # Then client code can query for duck-type-safeness this way: + # + # Stringish.new.acts_like?(:string) # => true + # + def acts_like?(duck) + case duck + when :time + respond_to? :acts_like_time? + when :date + respond_to? :acts_like_date? + when :string + respond_to? :acts_like_string? + else + respond_to? :"acts_like_#{duck}?" + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/blank.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/blank.rb similarity index 71% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/blank.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/blank.rb index 38e43478df..372f725047 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/blank.rb @@ -1,12 +1,14 @@ -# encoding: utf-8 +# frozen_string_literal: true + +require "concurrent/map" class Object # An object is blank if it's false, empty, or a whitespace string. - # For example, '', ' ', +nil+, [], and {} are all blank. + # For example, +nil+, '', ' ', [], {}, and +false+ are all blank. # # This simplifies # - # address.nil? || address.empty? + # !address || address.empty? # # to # @@ -100,6 +102,9 @@ class Hash class String BLANK_RE = /\A[[:space:]]*\z/ + ENCODED_BLANKS = Concurrent::Map.new do |h, enc| + h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING) + end # A string is blank if it's empty or contains whitespaces only: # @@ -114,11 +119,19 @@ class String # # @return [true, false] def blank? - BLANK_RE === self + # The regexp that matches blank strings is expensive. For the case of empty + # strings we can speed up this method (~3.5x) with an empty? call. The + # penalty for the rest of strings is marginal. + empty? || + begin + BLANK_RE.match?(self) + rescue Encoding::CompatibilityError + ENCODED_BLANKS[self.encoding].match?(self) + end end end -class Numeric #:nodoc: +class Numeric # :nodoc: # No number is blank: # # 1.blank? # => false @@ -129,3 +142,14 @@ def blank? false end end + +class Time # :nodoc: + # No Time is blank: + # + # Time.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/conversions.rb new file mode 100644 index 0000000000..624fb8d77c --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/conversions.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/hash/conversions" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/deep_dup.rb similarity index 71% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/deep_dup.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/deep_dup.rb index 2e99f4a1b8..4aca04acca 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/deep_dup.rb @@ -1,4 +1,6 @@ -require 'active_support/core_ext/object/duplicable' +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" class Object # Returns a deep copy of object if it's duplicable. If it's @@ -25,7 +27,7 @@ class Array # array[1][2] # => nil # dup[1][2] # => 4 def deep_dup - map { |it| it.deep_dup } + map(&:deep_dup) end end @@ -39,8 +41,15 @@ class Hash # hash[:a][:c] # => nil # dup[:a][:c] # => "c" def deep_dup - each_with_object(dup) do |(key, value), hash| - hash[key.deep_dup] = value.deep_dup + hash = dup + each_pair do |key, value| + if ::String === key || ::Symbol === key + hash[key] = value.deep_dup + else + hash.delete(key) + hash[key.deep_dup] = value.deep_dup + end end + hash end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/duplicable.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/duplicable.rb new file mode 100644 index 0000000000..6fdf6d810f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/duplicable.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +#-- +# Most objects are cloneable, but not all. For example you can't dup methods: +# +# method(:puts).dup # => TypeError: allocator undefined for Method +# +# Classes may signal their instances are not duplicable removing +dup+/+clone+ +# or raising exceptions from them. So, to dup an arbitrary object you normally +# use an optimistic approach and are ready to catch an exception, say: +# +# arbitrary_object.dup rescue object +# +# Rails dups objects in a few critical spots where they are not that arbitrary. +# That rescue is very expensive (like 40 times slower than a predicate), and it +# is often triggered. +# +# That's why we hardcode the following cases and check duplicable? instead of +# using that rescue idiom. +#++ +class Object + # Can you safely dup this object? + # + # False for method objects; + # true otherwise. + def duplicable? + true + end +end + +class Method + # Methods are not duplicable: + # + # method(:puts).duplicable? # => false + # method(:puts).dup # => TypeError: allocator undefined for Method + def duplicable? + false + end +end + +class UnboundMethod + # Unbound methods are not duplicable: + # + # method(:puts).unbind.duplicable? # => false + # method(:puts).unbind.dup # => TypeError: allocator undefined for UnboundMethod + def duplicable? + false + end +end + +require "singleton" + +module Singleton + # Singleton instances are not duplicable: + # + # Class.new.include(Singleton).instance.dup # TypeError (can't dup instance of singleton + def duplicable? + false + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/inclusion.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/inclusion.rb similarity index 76% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/inclusion.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/inclusion.rb index 55f281b213..6064e92f20 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/inclusion.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Object # Returns true if this object is included in the argument. Argument must be # any object which responds to +#include?+. Usage: @@ -5,7 +7,7 @@ class Object # characters = ["Konata", "Kagami", "Tsukasa"] # "Konata".in?(characters) # => true # - # This will throw an ArgumentError if the argument doesn't respond + # This will throw an +ArgumentError+ if the argument doesn't respond # to +#include?+. def in?(another_object) another_object.include?(self) @@ -18,10 +20,10 @@ def in?(another_object) # # params[:bucket_type].presence_in %w( project calendar ) # - # This will throw an ArgumentError if the argument doesn't respond to +#include?+. + # This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+. # # @return [Object] def presence_in(another_object) - self.in?(another_object) ? self : nil + in?(another_object) ? self : nil end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/instance_variables.rb similarity index 91% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/instance_variables.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/instance_variables.rb index 755e1c6b16..12fdf840b5 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/instance_variables.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Object # Returns a hash with string keys that maps instance variable names without "@" to their # corresponding values. @@ -23,6 +25,6 @@ def instance_values # # C.new(0, 1).instance_variable_names # => ["@y", "@x"] def instance_variable_names - instance_variables.map { |var| var.to_s } + instance_variables.map(&:to_s) end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/json.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/json.rb similarity index 58% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/json.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/json.rb index 5496692373..bd9b6f9019 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/json.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/json.rb @@ -1,21 +1,25 @@ -# Hack to load json gem first so we can overwrite its to_json. -require 'json' -require 'bigdecimal' -require 'active_support/core_ext/big_decimal/conversions' # for #to_s -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/instance_variables' -require 'time' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/module/aliasing' - +# frozen_string_literal: true + +# Hack to load json gem first so we can override its to_json. +require "json" +require "bigdecimal" +require "ipaddr" +require "uri/generic" +require "pathname" +require "active_support/core_ext/big_decimal/conversions" # for #to_s +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/object/instance_variables" +require "time" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/conversions" +require "active_support/core_ext/date/conversions" + +#-- # The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting # their default behavior. That said, we need to define the basic to_json method in all of them, # otherwise they will always use to_json gem implementation, which is backwards incompatible in -# several cases (for instance, the JSON implementation for Hash does not work) with inheritance -# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. +# several cases (for instance, the JSON implementation for Hash does not work) with inheritance. # # On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the # JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always @@ -26,24 +30,33 @@ # bypassed completely. This means that as_json won't be invoked and the JSON gem will simply # ignore any options it does not natively understand. This also means that ::JSON.{generate,dump} # should give exactly the same results with or without active support. -[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].each do |klass| - klass.class_eval do - def to_json_with_active_support_encoder(options = nil) + +module ActiveSupport + module ToJsonWithActiveSupportEncoder # :nodoc: + def to_json(options = nil) if options.is_a?(::JSON::State) # Called from JSON.{generate,dump}, forward it to JSON gem's to_json - self.to_json_without_active_support_encoder(options) + super(options) else # to_json is being invoked directly, use ActiveSupport's encoder ActiveSupport::JSON.encode(self, options) end end + end +end - alias_method_chain :to_json, :active_support_encoder +[Enumerable, Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].reverse_each do |klass| + klass.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder) +end + +class Module + def as_json(options = nil) # :nodoc: + name end end class Object - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: if respond_to?(:to_hash) to_hash.as_json(options) else @@ -52,44 +65,44 @@ def as_json(options = nil) #:nodoc: end end -class Struct #:nodoc: +class Struct # :nodoc: def as_json(options = nil) Hash[members.zip(values)].as_json(options) end end class TrueClass - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: self end end class FalseClass - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: self end end class NilClass - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: self end end class String - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: self end end class Symbol - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: to_s end end class Numeric - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: self end end @@ -97,7 +110,7 @@ def as_json(options = nil) #:nodoc: class Float # Encoding Infinity or NaN to JSON should return "null". The default returns # "Infinity" or "NaN" which are not valid JSON. - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: finite? ? self : nil end end @@ -112,37 +125,43 @@ class BigDecimal # if the other end knows by contract that the data is supposed to be a # BigDecimal, it still has the chance to post-process the string and get the # real value. - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: finite? ? to_s : nil end end class Regexp - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: to_s end end module Enumerable - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: to_a.as_json(options) end end +class IO + def as_json(options = nil) # :nodoc: + to_s + end +end + class Range - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: to_s end end class Array - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: map { |v| options ? v.as_json(options.dup) : v.as_json } end end class Hash - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: # create a subset of the hash by applying :only or :except subset = if options if attrs = options[:only] @@ -156,13 +175,17 @@ def as_json(options = nil) #:nodoc: self end - Hash[subset.map { |k, v| [k.to_s, options ? v.as_json(options.dup) : v.as_json] }] + result = {} + subset.each do |k, v| + result[k.to_s] = options ? v.as_json(options.dup) : v.as_json + end + result end end class Time - def as_json(options = nil) #:nodoc: - if ActiveSupport.use_standard_json_time_format + def as_json(options = nil) # :nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format xmlschema(ActiveSupport::JSON::Encoding.time_precision) else %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) @@ -171,8 +194,8 @@ def as_json(options = nil) #:nodoc: end class Date - def as_json(options = nil) #:nodoc: - if ActiveSupport.use_standard_json_time_format + def as_json(options = nil) # :nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format strftime("%Y-%m-%d") else strftime("%Y/%m/%d") @@ -181,17 +204,41 @@ def as_json(options = nil) #:nodoc: end class DateTime - def as_json(options = nil) #:nodoc: - if ActiveSupport.use_standard_json_time_format + def as_json(options = nil) # :nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format xmlschema(ActiveSupport::JSON::Encoding.time_precision) else - strftime('%Y/%m/%d %H:%M:%S %z') + strftime("%Y/%m/%d %H:%M:%S %z") end end end -class Process::Status #:nodoc: +class URI::Generic # :nodoc: + def as_json(options = nil) + to_s + end +end + +class Pathname # :nodoc: def as_json(options = nil) - { :exitstatus => exitstatus, :pid => pid } + to_s + end +end + +class IPAddr # :nodoc: + def as_json(options = nil) + to_s + end +end + +class Process::Status # :nodoc: + def as_json(options = nil) + { exitstatus: exitstatus, pid: pid } + end +end + +class Exception + def as_json(options = nil) + to_s end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/to_param.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/to_param.rb new file mode 100644 index 0000000000..6d2bdd70f3 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/to_param.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/to_query" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/to_query.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/to_query.rb new file mode 100644 index 0000000000..7cccc03d47 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/to_query.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require "cgi" + +class Object + # Alias of to_s. + def to_param + to_s + end + + # Converts an object into a string suitable for use as a URL query string, + # using the given key as the param name. + def to_query(key) + "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}" + end +end + +class NilClass + # Returns +self+. + def to_param + self + end +end + +class TrueClass + # Returns +self+. + def to_param + self + end +end + +class FalseClass + # Returns +self+. + def to_param + self + end +end + +class Array + # Calls to_param on all its elements and joins the result with + # slashes. This is used by url_for in Action Pack. + def to_param + collect(&:to_param).join "/" + end + + # Converts an array into a string suitable for use as a URL query string, + # using the given +key+ as the param name. + # + # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" + def to_query(key) + prefix = "#{key}[]" + + if empty? + nil.to_query(prefix) + else + collect { |value| value.to_query(prefix) }.join "&" + end + end +end + +class Hash + # Returns a string representation of the receiver suitable for use as a URL + # query string: + # + # {name: 'David', nationality: 'Danish'}.to_query + # # => "name=David&nationality=Danish" + # + # An optional namespace can be passed to enclose key names: + # + # {name: 'David', nationality: 'Danish'}.to_query('user') + # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # + # The string pairs "key=value" that conform the query string + # are sorted lexicographically in ascending order. + def to_query(namespace = nil) + query = filter_map do |key, value| + unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? + value.to_query(namespace ? "#{namespace}[#{key}]" : key) + end + end + + query.sort! unless namespace.to_s.include?("[]") + query.join("&") + end + + alias_method :to_param, :to_query +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/try.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/try.rb new file mode 100644 index 0000000000..c2c76254ae --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/try.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require "delegate" + +module ActiveSupport + module Tryable # :nodoc: + def try(*args, &block) + if args.empty? && block_given? + if block.arity == 0 + instance_eval(&block) + else + yield self + end + elsif respond_to?(args.first) + public_send(*args, &block) + end + end + ruby2_keywords(:try) + + def try!(*args, &block) + if args.empty? && block_given? + if block.arity == 0 + instance_eval(&block) + else + yield self + end + else + public_send(*args, &block) + end + end + ruby2_keywords(:try!) + end +end + +class Object + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(*args, &block) + # + # Invokes the public method whose name goes as first argument just like + # +public_send+ does, except that if the receiver does not respond to it the + # call returns +nil+ rather than raising an exception. + # + # This method is defined to be able to write + # + # @person.try(:name) + # + # instead of + # + # @person.name if @person + # + # +try+ calls can be chained: + # + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) # => nil + # + # instead of + # + # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil + # + # +try+ returns +nil+ when called on +nil+ regardless of whether it responds + # to the method: + # + # nil.try(:to_i) # => nil, rather than 0 + # + # Arguments and blocks are forwarded to the method if invoked: + # + # @posts.try(:each_slice, 2) do |a, b| + # ... + # end + # + # The number of arguments in the signature must match. If the object responds + # to the method the call is attempted and +ArgumentError+ is still raised + # in case of argument mismatch. + # + # If +try+ is called without arguments it yields the receiver to a given + # block unless it is +nil+: + # + # @person.try do |p| + # ... + # end + # + # You can also call try with a block without accepting an argument, and the block + # will be instance_eval'ed instead: + # + # @person.try { upcase.truncate(50) } + # + # Please also note that +try+ is defined on +Object+. Therefore, it won't work + # with instances of classes that do not have +Object+ among their ancestors, + # like direct subclasses of +BasicObject+. + + ## + # :method: try! + # + # :call-seq: + # try!(*args, &block) + # + # Same as #try, but raises a +NoMethodError+ exception if the receiver is + # not +nil+ and does not implement the tried method. + # + # "a".try!(:upcase) # => "A" + # nil.try!(:upcase) # => nil + # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer +end + +class Delegator + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(*args, &block) + # + # See Object#try + + ## + # :method: try! + # + # :call-seq: + # try!(*args, &block) + # + # See Object#try! +end + +class NilClass + # Calling +try+ on +nil+ always returns +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. + # + # nil.try(:name) # => nil + # + # Without +try+ + # @person && @person.children.any? && @person.children.first.name + # + # With +try+ + # @person.try(:children).try(:first).try(:name) + def try(*) + nil + end + + # Calling +try!+ on +nil+ always returns +nil+. + # + # nil.try!(:name) # => nil + def try!(*) + nil + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/with_options.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/with_options.rb new file mode 100644 index 0000000000..11985fa828 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/object/with_options.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require "active_support/option_merger" + +class Object + # An elegant way to factor duplication out of options passed to a series of + # method calls. Each method called in the block, with the block variable as + # the receiver, will have its options merged with the default +options+ hash + # provided. Each method called on the block variable must take an options + # hash as its final argument. + # + # Without with_options, this code contains duplication: + # + # class Account < ActiveRecord::Base + # has_many :customers, dependent: :destroy + # has_many :products, dependent: :destroy + # has_many :invoices, dependent: :destroy + # has_many :expenses, dependent: :destroy + # end + # + # Using with_options, we can remove the duplication: + # + # class Account < ActiveRecord::Base + # with_options dependent: :destroy do |assoc| + # assoc.has_many :customers + # assoc.has_many :products + # assoc.has_many :invoices + # assoc.has_many :expenses + # end + # end + # + # It can also be used with an explicit receiver: + # + # I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n| + # subject i18n.t :subject + # body i18n.t :body, user_name: user.name + # end + # + # When you don't pass an explicit receiver, it executes the whole block + # in merging options context: + # + # class Account < ActiveRecord::Base + # with_options dependent: :destroy do + # has_many :customers + # has_many :products + # has_many :invoices + # has_many :expenses + # end + # end + # + # with_options can also be nested since the call is forwarded to its receiver. + # + # NOTE: Each nesting level will merge inherited defaults in addition to their own. + # + # class Post < ActiveRecord::Base + # with_options if: :persisted?, length: { minimum: 50 } do + # validates :content, if: -> { content.present? } + # end + # end + # + # The code is equivalent to: + # + # validates :content, length: { minimum: 50 }, if: -> { content.present? } + # + # Hence the inherited default for +if+ key is ignored. + # + # NOTE: You cannot call class methods implicitly inside of +with_options+. + # You can access these methods using the class name instead: + # + # class Phone < ActiveRecord::Base + # enum phone_number_type: { home: 0, office: 1, mobile: 2 } + # + # with_options presence: true do + # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys } + # end + # end + # + # When the block argument is omitted, the decorated Object instance is returned: + # + # module MyStyledHelpers + # def styled + # with_options style: "color: red;" + # end + # end + # + # styled.link_to "I'm red", "/" + # # => I'm red + # + # styled.button_tag "I'm red too!" + # # => + # + def with_options(options, &block) + option_merger = ActiveSupport::OptionMerger.new(self, options) + + if block + block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger) + else + option_merger + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/pathname.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/pathname.rb new file mode 100644 index 0000000000..611a43e7e0 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/pathname.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/pathname/existence" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/pathname/existence.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/pathname/existence.rb new file mode 100644 index 0000000000..9bb9b17ab3 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/pathname/existence.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Pathname + # Returns the receiver if the named file exists otherwise returns +nil+. + # pathname.existence is equivalent to + # + # pathname.exist? ? pathname : nil + # + # For example, something like + # + # content = pathname.read if pathname.exist? + # + # becomes + # + # content = pathname.existence&.read + # + # @return [Pathname] + def existence + self if exist? + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range.rb new file mode 100644 index 0000000000..ba6bc9bdca --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/range/conversions" +require "active_support/core_ext/range/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"] +require "active_support/core_ext/range/compare_range" +require "active_support/core_ext/range/overlaps" +require "active_support/core_ext/range/each" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/compare_range.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/compare_range.rb new file mode 100644 index 0000000000..affbbebb42 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/compare_range.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActiveSupport + module CompareWithRange + # Extends the default Range#=== to support range comparisons. + # (1..5) === (1..5) # => true + # (1..5) === (2..3) # => true + # (1..5) === (1...6) # => true + # (1..5) === (2..6) # => false + # + # The native Range#=== behavior is untouched. + # ('a'..'f') === ('c') # => true + # (5..9) === (11) # => false + # + # The given range must be fully bounded, with both start and end. + def ===(value) + if value.is_a?(::Range) + is_backwards_op = value.exclude_end? ? :>= : :> + return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end) + # 1...10 includes 1..9 but it does not include 1..10. + # 1..10 includes 1...11 but it does not include 1...12. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + value_max = !exclude_end? && value.exclude_end? ? value.max : value.last + super(value.first) && (self.end.nil? || value_max.public_send(operator, last)) + else + super + end + end + + # Extends the default Range#include? to support range comparisons. + # (1..5).include?(1..5) # => true + # (1..5).include?(2..3) # => true + # (1..5).include?(1...6) # => true + # (1..5).include?(2..6) # => false + # + # The native Range#include? behavior is untouched. + # ('a'..'f').include?('c') # => true + # (5..9).include?(11) # => false + # + # The given range must be fully bounded, with both start and end. + def include?(value) + if value.is_a?(::Range) + is_backwards_op = value.exclude_end? ? :>= : :> + return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end) + # 1...10 includes 1..9 but it does not include 1..10. + # 1..10 includes 1...11 but it does not include 1...12. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + value_max = !exclude_end? && value.exclude_end? ? value.max : value.last + super(value.first) && (self.end.nil? || value_max.public_send(operator, last)) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::CompareWithRange) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/conversions.rb new file mode 100644 index 0000000000..1c80ea3b1d --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/conversions.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveSupport + module RangeWithFormat + RANGE_FORMATS = { + db: -> (start, stop) do + case start + when String then "BETWEEN '#{start}' AND '#{stop}'" + else + "BETWEEN '#{start.to_fs(:db)}' AND '#{stop.to_fs(:db)}'" + end + end + } + + # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. + # + # This method is aliased to to_formatted_s. + # + # range = (1..100) # => 1..100 + # + # range.to_s # => "1..100" + # range.to_fs(:db) # => "BETWEEN '1' AND '100'" + # + # == Adding your own range formats to to_s + # You can add your own formats to the Range::RANGE_FORMATS hash. + # Use the format name as the hash key and a Proc instance. + # + # # config/initializers/range_formats.rb + # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_fs(:db)} and #{stop.to_fs(:db)}" } + def to_fs(format = :default) + if formatter = RANGE_FORMATS[format] + formatter.call(first, last) + else + to_s + end + end + alias_method :to_formatted_s, :to_fs + end +end + +Range.prepend(ActiveSupport::RangeWithFormat) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/deprecated_conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/deprecated_conversions.rb new file mode 100644 index 0000000000..86b377f2bf --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/deprecated_conversions.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActiveSupport + module DeprecatedRangeWithFormat # :nodoc: + NOT_SET = Object.new # :nodoc: + def to_s(format = NOT_SET) + if formatter = RangeWithFormat::RANGE_FORMATS[format] + ActiveSupport::Deprecation.warn( + "Range#to_s(#{format.inspect}) is deprecated. Please use Range#to_fs(#{format.inspect}) instead." + ) + formatter.call(first, last) + elsif format == NOT_SET + super() + else + ActiveSupport::Deprecation.warn( + "Range#to_s(#{format.inspect}) is deprecated. Please use Range#to_fs(#{format.inspect}) instead." + ) + super() + end + end + alias_method :to_default_s, :to_s + deprecate :to_default_s + end +end + +Range.prepend(ActiveSupport::DeprecatedRangeWithFormat) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/each.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/each.rb new file mode 100644 index 0000000000..1c44cc8449 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/each.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveSupport + module EachTimeWithZone # :nodoc: + def each(&block) + ensure_iteration_allowed + super + end + + def step(n = 1, &block) + ensure_iteration_allowed + super + end + + private + def ensure_iteration_allowed + raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone) + end + end +end + +Range.prepend(ActiveSupport::EachTimeWithZone) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/include_time_with_zone.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/include_time_with_zone.rb new file mode 100644 index 0000000000..114d6a785e --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/include_time_with_zone.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ActiveSupport::Deprecation.warn(<<-MSG.squish) + `active_support/core_ext/range/include_time_with_zone` is deprecated and will be removed in Rails 7.1. +MSG diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/overlaps.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/overlaps.rb similarity index 61% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/overlaps.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/overlaps.rb index 603657c180..c286988d13 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/overlaps.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/range/overlaps.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + class Range # Compare two ranges and see if they overlap each other # (1..5).overlaps?(4..6) # => true # (1..5).overlaps?(7..9) # => false def overlaps?(other) - cover?(other.first) || other.cover?(first) + other.begin == self.begin || cover?(other.begin) || other.cover?(self.begin) end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/regexp.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/regexp.rb new file mode 100644 index 0000000000..15534ff52f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/regexp.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Regexp + # Returns +true+ if the regexp has the multiline flag set. + # + # (/./).multiline? # => false + # (/./m).multiline? # => true + # + # Regexp.new(".").multiline? # => false + # Regexp.new(".", Regexp::MULTILINE).multiline? # => true + def multiline? + options & MULTILINE == MULTILINE + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/securerandom.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..fa6b68b101 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "securerandom" + +module SecureRandom + BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"] + BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a + + # SecureRandom.base58 generates a random base58 string. + # + # The argument _n_ specifies the length of the random string to be generated. + # + # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future. + # + # The result may contain alphanumeric characters except 0, O, I, and l. + # + # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE" + # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7" + def self.base58(n = 16) + SecureRandom.random_bytes(n).unpack("C*").map do |byte| + idx = byte % 64 + idx = SecureRandom.random_number(58) if idx >= 58 + BASE58_ALPHABET[idx] + end.join + end + + # SecureRandom.base36 generates a random base36 string in lowercase. + # + # The argument _n_ specifies the length of the random string to be generated. + # + # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future. + # This method can be used over +base58+ if a deterministic case key is necessary. + # + # The result will contain alphanumeric characters in lowercase. + # + # p SecureRandom.base36 # => "4kugl2pdqmscqtje" + # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7" + def self.base36(n = 16) + SecureRandom.random_bytes(n).unpack("C*").map do |byte| + idx = byte % 64 + idx = SecureRandom.random_number(36) if idx >= 36 + BASE36_ALPHABET[idx] + end.join + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string.rb new file mode 100644 index 0000000000..757d15c51a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/string/multibyte" +require "active_support/core_ext/string/starts_ends_with" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/string/exclude" +require "active_support/core_ext/string/strip" +require "active_support/core_ext/string/inquiry" +require "active_support/core_ext/string/indent" +require "active_support/core_ext/string/zones" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/access.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/access.rb similarity index 84% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/access.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/access.rb index 6018fd9641..f6a14c08bc 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/access.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/access.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + class String - # If you pass a single Fixnum, returns a substring of one character at that + # If you pass a single integer, returns a substring of one character at that # position. The first character of the string is at position 0, the next at # position 1, and so on. If a range is supplied, a substring containing # characters at offsets given by the range is returned. In both cases, if an - # offset is negative, it is counted from the end of the string. Returns nil + # offset is negative, it is counted from the end of the string. Returns +nil+ # if the initial offset falls outside the string. Returns an empty string if # the beginning of the range is greater than the end of the string. # @@ -17,7 +19,7 @@ class String # # If a Regexp is given, the matching portion of the string is returned. # If a String is given, that given string is returned if it occurs in - # the string. In both cases, nil is returned if there is no match. + # the string. In both cases, +nil+ is returned if there is no match. # # str = "hello" # str.at(/lo/) # => "lo" @@ -42,7 +44,7 @@ def at(position) # str.from(0).to(-1) # => "hello" # str.from(1).to(-2) # => "ell" def from(position) - self[position..-1] + self[position, length] end # Returns a substring from the beginning of the string to the given position. @@ -59,12 +61,13 @@ def from(position) # str.from(0).to(-1) # => "hello" # str.from(1).to(-2) # => "ell" def to(position) - self[0..position] + position += size if position < 0 + self[0, position + 1] || +"" end # Returns the first character. If a limit is supplied, returns a substring # from the beginning of the string until it reaches the limit value. If the - # given limit is greater than or equal to the string length, returns self. + # given limit is greater than or equal to the string length, returns a copy of self. # # str = "hello" # str.first # => "h" @@ -73,18 +76,12 @@ def to(position) # str.first(0) # => "" # str.first(6) # => "hello" def first(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - to(limit - 1) - end + self[0, limit] || raise(ArgumentError, "negative limit") end # Returns the last character of the string. If a limit is supplied, returns a substring # from the end of the string until it reaches the limit value (counting backwards). If - # the given limit is greater than or equal to the string length, returns self. + # the given limit is greater than or equal to the string length, returns a copy of self. # # str = "hello" # str.last # => "o" @@ -93,12 +90,6 @@ def first(limit = 1) # str.last(0) # => "" # str.last(6) # => "hello" def last(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - from(-limit) - end + self[[length - limit, 0].max, limit] || raise(ArgumentError, "negative limit") end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/behavior.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/behavior.rb new file mode 100644 index 0000000000..35a5aa7840 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/behavior.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class String + # Enables more predictable duck-typing on String-like classes. See Object#acts_like?. + def acts_like_string? + true + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/conversions.rb similarity index 76% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/conversions.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/conversions.rb index 3e0cb8a7ac..58e3289292 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/conversions.rb @@ -1,12 +1,14 @@ -require 'date' -require 'active_support/core_ext/time/calculations' +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/time/calculations" class String # Converts a string to a Time value. - # The +form+ can be either :utc or :local (default :local). + # The +form+ can be either +:utc+ or +:local+ (default +:local+). # # The time is parsed using Time.parse method. - # If +form+ is :local, then the time is in the system timezone. + # If +form+ is +:local+, then the time is in the system timezone. # If the date part is missing then the current date is used and if # the time part is missing then it is assumed to be 00:00:00. # @@ -14,11 +16,13 @@ class String # "06:12".to_time # => 2012-12-13 06:12:00 +0100 # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100 # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100 - # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC + # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC # "12/13/2012".to_time # => ArgumentError: argument out of range + # "1604326192".to_time # => ArgumentError: argument out of range def to_time(form = :local) parts = Date._parse(self, false) - return if parts.empty? + used_keys = %i(year mon mday hour min sec sec_fraction offset) + return if (parts.keys & used_keys).empty? now = Time.now time = Time.new( @@ -31,7 +35,7 @@ def to_time(form = :local) parts.fetch(:offset, form == :utc ? 0 : nil) ) - form == :utc ? time.utc : time.getlocal + form == :utc ? time.utc : time.to_time end # Converts a string to a Date value. diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/exclude.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/exclude.rb similarity index 90% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/exclude.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/exclude.rb index 0ac684f6ee..8e462689f1 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/exclude.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/exclude.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class String # The inverse of String#include?. Returns true if the string # does not include the other string. diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/filters.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/filters.rb new file mode 100644 index 0000000000..dc163c58be --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/filters.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +class String + # Returns the string, first removing all whitespace on both ends of + # the string, and then changing remaining consecutive whitespace + # groups into one space each. + # + # Note that it handles both ASCII and Unicode whitespace. + # + # %{ Multi-line + # string }.squish # => "Multi-line string" + # " foo bar \n \t boo".squish # => "foo bar boo" + def squish + dup.squish! + end + + # Performs a destructive squish. See String#squish. + # str = " foo bar \n \t boo" + # str.squish! # => "foo bar boo" + # str # => "foo bar boo" + def squish! + gsub!(/[[:space:]]+/, " ") + strip! + self + end + + # Returns a new string with all occurrences of the patterns removed. + # str = "foo bar test" + # str.remove(" test") # => "foo bar" + # str.remove(" test", /bar/) # => "foo " + # str # => "foo bar test" + def remove(*patterns) + dup.remove!(*patterns) + end + + # Alters the string by removing all occurrences of the patterns. + # str = "foo bar test" + # str.remove!(" test", /bar/) # => "foo " + # str # => "foo " + def remove!(*patterns) + patterns.each do |pattern| + gsub! pattern, "" + end + + self + end + + # Truncates a given +text+ after a given length if +text+ is longer than length: + # + # 'Once upon a time in a world far far away'.truncate(27) + # # => "Once upon a time in a wo..." + # + # Pass a string or regexp :separator to truncate +text+ at a natural break: + # + # 'Once upon a time in a world far far away'.truncate(27, separator: ' ') + # # => "Once upon a time in a..." + # + # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/) + # # => "Once upon a time in a..." + # + # The last characters will be replaced with the :omission string (defaults to "...") + # for a total length not exceeding length: + # + # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)') + # # => "And they f... (continued)" + def truncate(truncate_at, options = {}) + return dup unless length > truncate_at + + omission = options[:omission] || "..." + length_with_room_for_omission = truncate_at - omission.length + stop = \ + if options[:separator] + rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission + else + length_with_room_for_omission + end + + +"#{self[0, stop]}#{omission}" + end + + # Truncates +text+ to at most bytesize bytes in length without + # breaking string encoding by splitting multibyte characters or breaking + # grapheme clusters ("perceptual characters") by truncating at combining + # characters. + # + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size + # => 20 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize + # => 80 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20) + # => "🔪🔪🔪🔪…" + # + # The truncated text ends with the :omission string, defaulting + # to "…", for a total length not exceeding bytesize. + def truncate_bytes(truncate_at, omission: "…") + omission ||= "" + + case + when bytesize <= truncate_at + dup + when omission.bytesize > truncate_at + raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes" + when omission.bytesize == truncate_at + omission.dup + else + self.class.new.tap do |cut| + cut_at = truncate_at - omission.bytesize + + each_grapheme_cluster do |grapheme| + if cut.bytesize + grapheme.bytesize <= cut_at + cut << grapheme + else + break + end + end + + cut << omission + end + end + end + + # Truncates a given +text+ after a given number of words (words_count): + # + # 'Once upon a time in a world far far away'.truncate_words(4) + # # => "Once upon a time..." + # + # Pass a string or regexp :separator to specify a different separator of words: + # + # 'Once
upon
a
time
in
a
world'.truncate_words(5, separator: '
') + # # => "Once
upon
a
time
in..." + # + # The last characters will be replaced with the :omission string (defaults to "..."): + # + # 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)') + # # => "And they found that many... (continued)" + def truncate_words(words_count, options = {}) + sep = options[:separator] || /\s+/ + sep = Regexp.escape(sep.to_s) unless Regexp === sep + if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m + $1 + (options[:omission] || "...") + else + dup + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/indent.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/indent.rb similarity index 79% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/indent.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/indent.rb index ce3a69cf5f..af9d181487 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/indent.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/indent.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + class String # Same as +indent+, except it indents the receiver in-place. # # Returns the indented string, or +nil+ if there was nothing to indent. - def indent!(amount, indent_string=nil, indent_empty_lines=false) - indent_string = indent_string || self[/^[ \t]/] || ' ' + def indent!(amount, indent_string = nil, indent_empty_lines = false) + indent_string = indent_string || self[/^[ \t]/] || " " re = indent_empty_lines ? /^/ : /^(?!$)/ gsub!(re, indent_string * amount) end @@ -37,7 +39,7 @@ def indent!(amount, indent_string=nil, indent_empty_lines=false) # "foo\n\nbar".indent(2) # => " foo\n\n bar" # "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar" # - def indent(amount, indent_string=nil, indent_empty_lines=false) - dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)} + def indent(amount, indent_string = nil, indent_empty_lines = false) + dup.tap { |_| _.indent!(amount, indent_string, indent_empty_lines) } end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/inflections.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/inflections.rb similarity index 63% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/inflections.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/inflections.rb index cf9b1a4ec0..fe7bb621c4 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/inflections.rb @@ -1,5 +1,7 @@ -require 'active_support/inflector/methods' -require 'active_support/inflector/transliterate' +# frozen_string_literal: true + +require "active_support/inflector/methods" +require "active_support/inflector/transliterate" # String inflections define new methods on the String class to transform names for different purposes. # For instance, you can figure out the name of a table from the name of a class. @@ -28,10 +30,12 @@ class String # 'apple'.pluralize(2) # => "apples" # 'ley'.pluralize(:es) # => "leyes" # 'ley'.pluralize(1, :es) # => "ley" + # + # See ActiveSupport::Inflector.pluralize. def pluralize(count = nil, locale = :en) locale = count if count.is_a?(Symbol) if count == 1 - self + dup else ActiveSupport::Inflector.pluralize(self, locale) end @@ -51,28 +55,34 @@ def pluralize(count = nil, locale = :en) # 'the blue mailmen'.singularize # => "the blue mailman" # 'CamelOctopi'.singularize # => "CamelOctopus" # 'leyes'.singularize(:es) # => "ley" + # + # See ActiveSupport::Inflector.singularize. def singularize(locale = :en) ActiveSupport::Inflector.singularize(self, locale) end # +constantize+ tries to find a declared constant with the name specified # in the string. It raises a NameError when the name is not in CamelCase - # or is not initialized. See ActiveSupport::Inflector.constantize + # or is not initialized. # # 'Module'.constantize # => Module # 'Class'.constantize # => Class # 'blargle'.constantize # => NameError: wrong constant name blargle + # + # See ActiveSupport::Inflector.constantize. def constantize ActiveSupport::Inflector.constantize(self) end # +safe_constantize+ tries to find a declared constant with the name specified - # in the string. It returns nil when the name is not in CamelCase - # or is not initialized. See ActiveSupport::Inflector.safe_constantize + # in the string. It returns +nil+ when the name is not in CamelCase + # or is not initialized. # # 'Module'.safe_constantize # => Module # 'Class'.safe_constantize # => Class # 'blargle'.safe_constantize # => nil + # + # See ActiveSupport::Inflector.safe_constantize. def safe_constantize ActiveSupport::Inflector.safe_constantize(self) end @@ -86,12 +96,16 @@ def safe_constantize # 'active_record'.camelize(:lower) # => "activeRecord" # 'active_record/errors'.camelize # => "ActiveRecord::Errors" # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors" + # + # See ActiveSupport::Inflector.camelize. def camelize(first_letter = :upper) case first_letter when :upper ActiveSupport::Inflector.camelize(self, true) when :lower ActiveSupport::Inflector.camelize(self, false) + else + raise ArgumentError, "Invalid option, use either :upper or :lower." end end alias_method :camelcase, :camelize @@ -100,12 +114,17 @@ def camelize(first_letter = :upper) # a nicer looking title. +titleize+ is meant for creating pretty output. It is not # used in the Rails internals. # - # +titleize+ is also aliased as +titlecase+. + # The trailing '_id','Id'.. can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. # - # 'man from the boondocks'.titleize # => "Man From The Boondocks" - # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" - def titleize - ActiveSupport::Inflector.titleize(self) + # 'man from the boondocks'.titleize # => "Man From The Boondocks" + # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" + # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id" + # + # See ActiveSupport::Inflector.titleize. + def titleize(keep_id_suffix: false) + ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix) end alias_method :titlecase, :titleize @@ -115,6 +134,8 @@ def titleize # # 'ActiveModel'.underscore # => "active_model" # 'ActiveModel::Errors'.underscore # => "active_model/errors" + # + # See ActiveSupport::Inflector.underscore. def underscore ActiveSupport::Inflector.underscore(self) end @@ -122,14 +143,20 @@ def underscore # Replaces underscores with dashes in the string. # # 'puni_puni'.dasherize # => "puni-puni" + # + # See ActiveSupport::Inflector.dasherize. def dasherize ActiveSupport::Inflector.dasherize(self) end # Removes the module part from the constant expression in the string. # - # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" - # 'Inflections'.demodulize # => "Inflections" + # 'ActiveSupport::Inflector::Inflections'.demodulize # => "Inflections" + # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' + # + # See ActiveSupport::Inflector.demodulize. # # See also +deconstantize+. def demodulize @@ -144,6 +171,8 @@ def demodulize # '::String'.deconstantize # => "" # ''.deconstantize # => "" # + # See ActiveSupport::Inflector.deconstantize. + # # See also +demodulize+. def deconstantize ActiveSupport::Inflector.deconstantize(self) @@ -151,6 +180,11 @@ def deconstantize # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. # + # If the optional parameter +locale+ is specified, + # the word will be parameterized as a word of that language. + # By default, this parameter is set to nil and it will use + # the configured I18n.locale. + # # class Person # def to_param # "#{id}-#{name.parameterize}" @@ -162,31 +196,51 @@ def deconstantize # # <%= link_to(@person.name, person_path) %> # # => Donald E. Knuth - def parameterize(sep = '-') - ActiveSupport::Inflector.parameterize(self, sep) + # + # To preserve the case of the characters in a string, use the +preserve_case+ argument. + # + # class Person + # def to_param + # "#{id}-#{name.parameterize(preserve_case: true)}" + # end + # end + # + # @person = Person.find(1) + # # => # + # + # <%= link_to(@person.name, person_path) %> + # # => Donald E. Knuth + # + # See ActiveSupport::Inflector.parameterize. + def parameterize(separator: "-", preserve_case: false, locale: nil) + ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale) end # Creates the name of a table like Rails does for models to table names. This method # uses the +pluralize+ method on the last word in the string. # # 'RawScaledScorer'.tableize # => "raw_scaled_scorers" - # 'egg_and_ham'.tableize # => "egg_and_hams" + # 'ham_and_egg'.tableize # => "ham_and_eggs" # 'fancyCategory'.tableize # => "fancy_categories" + # + # See ActiveSupport::Inflector.tableize. def tableize ActiveSupport::Inflector.tableize(self) end - # Create a class name from a plural table name like Rails does for table names to models. + # Creates a class name from a plural table name like Rails does for table names to models. # Note that this returns a string and not a class. (To convert to an actual class # follow +classify+ with +constantize+.) # - # 'egg_and_hams'.classify # => "EggAndHam" + # 'ham_and_eggs'.classify # => "HamAndEgg" # 'posts'.classify # => "Post" + # + # See ActiveSupport::Inflector.classify. def classify ActiveSupport::Inflector.classify(self) end - # Capitalizes the first word, turns underscores into spaces, and strips a + # Capitalizes the first word, turns underscores into spaces, and (by default)strips a # trailing '_id' if present. # Like +titleize+, this is meant for creating pretty output. # @@ -194,11 +248,30 @@ def classify # optional parameter +capitalize+ to false. # By default, this parameter is true. # - # 'employee_salary'.humanize # => "Employee salary" - # 'author_id'.humanize # => "Author" - # 'author_id'.humanize(capitalize: false) # => "author" - def humanize(options = {}) - ActiveSupport::Inflector.humanize(self, options) + # The trailing '_id' can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # 'employee_salary'.humanize # => "Employee salary" + # 'author_id'.humanize # => "Author" + # 'author_id'.humanize(capitalize: false) # => "author" + # '_id'.humanize # => "Id" + # 'author_id'.humanize(keep_id_suffix: true) # => "Author id" + # + # See ActiveSupport::Inflector.humanize. + def humanize(capitalize: true, keep_id_suffix: false) + ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix) + end + + # Converts just the first character to uppercase. + # + # 'what a Lovely Day'.upcase_first # => "What a Lovely Day" + # 'w'.upcase_first # => "W" + # ''.upcase_first # => "" + # + # See ActiveSupport::Inflector.upcase_first. + def upcase_first + ActiveSupport::Inflector.upcase_first(self) end # Creates a foreign key name from a class name. @@ -208,6 +281,8 @@ def humanize(options = {}) # 'Message'.foreign_key # => "message_id" # 'Message'.foreign_key(false) # => "messageid" # 'Admin::Post'.foreign_key # => "post_id" + # + # See ActiveSupport::Inflector.foreign_key. def foreign_key(separate_class_name_and_id_with_underscore = true) ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore) end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/inquiry.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/inquiry.rb similarity index 55% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/inquiry.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/inquiry.rb index 1dcd949536..a3b42dad5c 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/inquiry.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/inquiry.rb @@ -1,7 +1,10 @@ -require 'active_support/string_inquirer' +# frozen_string_literal: true + +require "active_support/string_inquirer" +require "active_support/environment_inquirer" class String - # Wraps the current string in the ActiveSupport::StringInquirer class, + # Wraps the current string in the ActiveSupport::StringInquirer class, # which gives you a prettier way to test for equality. # # env = 'production'.inquiry diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/multibyte.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/multibyte.rb similarity index 73% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/multibyte.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/multibyte.rb index a124202936..0542121e39 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/multibyte.rb @@ -1,5 +1,6 @@ -# encoding: utf-8 -require 'active_support/multibyte' +# frozen_string_literal: true + +require "active_support/multibyte" class String # == Multibyte proxy @@ -10,12 +11,13 @@ class String # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string. # - # name = 'Claus Müller' - # name.reverse # => "rell??M sualC" - # name.length # => 13 + # >> "lj".mb_chars.upcase.to_s + # => "LJ" + # + # NOTE: Ruby 2.4 and later support native Unicode case mappings: # - # name.mb_chars.reverse.to_s # => "rellüM sualC" - # name.mb_chars.length # => 12 + # >> "lj".upcase + # => "LJ" # # == Method chaining # @@ -36,11 +38,18 @@ def mb_chars ActiveSupport::Multibyte.proxy_class.new(self) end + # Returns +true+ if string has utf_8 encoding. + # + # utf_8_str = "some string".encode "UTF-8" + # iso_str = "some string".encode "ISO-8859-1" + # + # utf_8_str.is_utf8? # => true + # iso_str.is_utf8? # => false def is_utf8? case encoding - when Encoding::UTF_8 + when Encoding::UTF_8, Encoding::US_ASCII valid_encoding? - when Encoding::ASCII_8BIT, Encoding::US_ASCII + when Encoding::ASCII_8BIT dup.force_encoding(Encoding::UTF_8).valid_encoding? else false diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/output_safety.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/output_safety.rb new file mode 100644 index 0000000000..74b187e85a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/output_safety.rb @@ -0,0 +1,371 @@ +# frozen_string_literal: true + +require "erb" +require "active_support/core_ext/module/redefine_method" +require "active_support/multibyte/unicode" + +class ERB + module Util + HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" } + JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } + HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ + JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u + + # Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name + TAG_NAME_START_REGEXP_SET = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \ + "\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \ + "\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}" + TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}]/ + TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}]/ + TAG_NAME_REPLACEMENT_CHAR = "_" + + # A utility method for escaping HTML tag characters. + # This method is also aliased as h. + # + # puts html_escape('is a > 0 & a < 10?') + # # => is a > 0 & a < 10? + def html_escape(s) + unwrapped_html_escape(s).html_safe + end + + silence_redefinition_of_method :h + alias h html_escape + + module_function :h + + singleton_class.silence_redefinition_of_method :html_escape + module_function :html_escape + + # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer. + # This method is not for public consumption! Seriously! + def unwrapped_html_escape(s) # :nodoc: + s = s.to_s + if s.html_safe? + s + else + CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s)) + end + end + module_function :unwrapped_html_escape + + # A utility method for escaping HTML without affecting existing escaped entities. + # + # html_escape_once('1 < 2 & 3') + # # => "1 < 2 & 3" + # + # html_escape_once('<< Accept & Checkout') + # # => "<< Accept & Checkout" + def html_escape_once(s) + result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) + s.html_safe? ? result.html_safe : result + end + + module_function :html_escape_once + + # A utility method for escaping HTML entities in JSON strings. Specifically, the + # &, > and < characters are replaced with their equivalent unicode escaped form - + # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also + # escaped as they are treated as newline characters in some JavaScript engines. + # These sequences have identical meaning as the original characters inside the + # context of a JSON string, so assuming the input is a valid and well-formed + # JSON value, the output will have equivalent meaning when parsed: + # + # json = JSON.generate({ name: ""}) + # # => "{\"name\":\"\"}" + # + # json_escape(json) + # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}" + # + # JSON.parse(json) == JSON.parse(json_escape(json)) + # # => true + # + # The intended use case for this method is to escape JSON strings before including + # them inside a script tag to avoid XSS vulnerability: + # + # + # + # It is necessary to +raw+ the result of +json_escape+, so that quotation marks + # don't get converted to " entities. +json_escape+ doesn't + # automatically flag the result as HTML safe, since the raw value is unsafe to + # use inside HTML attributes. + # + # If your JSON is being used downstream for insertion into the DOM, be aware of + # whether or not it is being inserted via html(). Most jQuery plugins do this. + # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated + # content returned by your JSON. + # + # If you need to output JSON elsewhere in your HTML, you can just do something + # like this, as any unsafe characters (including quotation marks) will be + # automatically escaped for you: + # + #
...
+ # + # WARNING: this helper only works with valid JSON. Using this on non-JSON values + # will open up serious XSS vulnerabilities. For example, if you replace the + # +current_user.to_json+ in the example above with user input instead, the browser + # will happily eval() that string as JavaScript. + # + # The escaping performed in this method is identical to those performed in the + # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is + # set to true. Because this transformation is idempotent, this helper can be + # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true. + # + # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+ + # is enabled, or if you are unsure where your JSON string originated from, it + # is recommended that you always apply this helper (other libraries, such as the + # JSON gem, do not provide this kind of protection by default; also some gems + # might override +to_json+ to bypass Active Support's encoder). + def json_escape(s) + result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) + s.html_safe? ? result.html_safe : result + end + + module_function :json_escape + + # A utility method for escaping XML names of tags and names of attributes. + # + # xml_name_escape('1 < 2 & 3') + # # => "1___2___3" + # + # It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name + def xml_name_escape(name) + name = name.to_s + return "" if name.blank? + + starting_char = name[0].gsub(TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR) + + return starting_char if name.size == 1 + + following_chars = name[1..-1].gsub(TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR) + + starting_char + following_chars + end + module_function :xml_name_escape + end +end + +class Object + def html_safe? + false + end +end + +class Numeric + def html_safe? + true + end +end + +module ActiveSupport # :nodoc: + class SafeBuffer < String + UNSAFE_STRING_METHODS = %w( + capitalize chomp chop delete delete_prefix delete_suffix + downcase lstrip next reverse rstrip scrub slice squeeze strip + succ swapcase tr tr_s unicode_normalize upcase + ) + + UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub) + + alias_method :original_concat, :concat + private :original_concat + + # Raised when ActiveSupport::SafeBuffer#safe_concat is called on unsafe buffers. + class SafeConcatError < StandardError + def initialize + super "Could not concatenate to the buffer because it is not html safe." + end + end + + def [](*args) + if html_safe? + new_string = super + + return unless new_string + + new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string) + new_safe_buffer.instance_variable_set :@html_safe, true + new_safe_buffer + else + to_str[*args] + end + end + + def safe_concat(value) + raise SafeConcatError unless html_safe? + original_concat(value) + end + + def initialize(str = "") + @html_safe = true + super + end + + def initialize_copy(other) + super + @html_safe = other.html_safe? + end + + def clone_empty + self[0, 0] + end + + def concat(value) + unless value.nil? + super(implicit_html_escape_interpolated_argument(value)) + end + self + end + alias << concat + + def bytesplice(*args, value) + super(*args, implicit_html_escape_interpolated_argument(value)) + end + + def insert(index, value) + super(index, implicit_html_escape_interpolated_argument(value)) + end + + def prepend(value) + super(implicit_html_escape_interpolated_argument(value)) + end + + def replace(value) + super(implicit_html_escape_interpolated_argument(value)) + end + + def []=(*args) + if args.length == 3 + super(args[0], args[1], implicit_html_escape_interpolated_argument(args[2])) + else + super(args[0], implicit_html_escape_interpolated_argument(args[1])) + end + end + + def +(other) + dup.concat(other) + end + + def *(*) + new_string = super + new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string) + new_safe_buffer.instance_variable_set(:@html_safe, @html_safe) + new_safe_buffer + end + + def %(args) + case args + when Hash + escaped_args = args.transform_values { |arg| explicit_html_escape_interpolated_argument(arg) } + else + escaped_args = Array(args).map { |arg| explicit_html_escape_interpolated_argument(arg) } + end + + self.class.new(super(escaped_args)) + end + + def html_safe? + defined?(@html_safe) && @html_safe + end + + def to_s + self + end + + def to_param + to_str + end + + def encode_with(coder) + coder.represent_object nil, to_str + end + + UNSAFE_STRING_METHODS.each do |unsafe_method| + if unsafe_method.respond_to?(unsafe_method) + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}(*args, &block) # def capitalize(*args, &block) + to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block) + end # end + + def #{unsafe_method}!(*args) # def capitalize!(*args) + @html_safe = false # @html_safe = false + super # super + end # end + EOT + end + end + + UNSAFE_STRING_METHODS_WITH_BACKREF.each do |unsafe_method| + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}(*args, &block) # def gsub(*args, &block) + if block # if block + to_str.#{unsafe_method}(*args) { |*params| # to_str.gsub(*args) { |*params| + set_block_back_references(block, $~) # set_block_back_references(block, $~) + block.call(*params) # block.call(*params) + } # } + else # else + to_str.#{unsafe_method}(*args) # to_str.gsub(*args) + end # end + end # end + + def #{unsafe_method}!(*args, &block) # def gsub!(*args, &block) + @html_safe = false # @html_safe = false + if block # if block + super(*args) { |*params| # super(*args) { |*params| + set_block_back_references(block, $~) # set_block_back_references(block, $~) + block.call(*params) # block.call(*params) + } # } + else # else + super # super + end # end + end # end + EOT + end + + private + def explicit_html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) + end + + def implicit_html_escape_interpolated_argument(arg) + if !html_safe? || arg.html_safe? + arg + else + arg_string = begin + arg.to_str + rescue NoMethodError => error + if error.name == :to_str + str = arg.to_s + ActiveSupport::Deprecation.warn <<~MSG.squish + Implicit conversion of #{arg.class} into String by ActiveSupport::SafeBuffer + is deprecated and will be removed in Rails 7.1. + You must explicitly cast it to a String. + MSG + str + else + raise + end + end + CGI.escapeHTML(arg_string) + end + end + + def set_block_back_references(block, match_data) + block.binding.eval("proc { |m| $~ = m }").call(match_data) + rescue ArgumentError + # Can't create binding from C level Proc + end + end +end + +class String + # Marks a string as trusted safe. It will be inserted into HTML with no + # additional escaping performed. It is your responsibility to ensure that the + # string contains no malicious content. This method is equivalent to the + # +raw+ helper in views. It is recommended that you use +sanitize+ instead of + # this method. It should never be called on user input. + def html_safe + ActiveSupport::SafeBuffer.new(self) + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/starts_ends_with.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/starts_ends_with.rb new file mode 100644 index 0000000000..1e216370e4 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/starts_ends_with.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class String + alias :starts_with? :start_with? + alias :ends_with? :end_with? +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/strip.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/strip.rb similarity index 59% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/strip.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/strip.rb index 086c610976..60e9952ee6 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/strip.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/try' +# frozen_string_literal: true class String # Strips indentation in heredocs. @@ -17,10 +17,11 @@ class String # # the user would see the usage message aligned against the left margin. # - # Technically, it looks for the least indented line in the whole string, and removes - # that amount of leading whitespace. + # Technically, it looks for the least indented non-empty line + # in the whole string, and removes that amount of leading whitespace. def strip_heredoc - indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0 - gsub(/^[ \t]{#{indent}}/, '') + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "").tap do |stripped| + stripped.freeze if frozen? + end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/zones.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/zones.rb similarity index 70% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/zones.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/zones.rb index 510c884c18..55dc231464 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/zones.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/string/zones.rb @@ -1,5 +1,7 @@ -require 'active_support/core_ext/string/conversions' -require 'active_support/core_ext/time/zones' +# frozen_string_literal: true + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/time/zones" class String # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/symbol.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/symbol.rb new file mode 100644 index 0000000000..709fed2024 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/symbol.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/symbol/starts_ends_with" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/symbol/starts_ends_with.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/symbol/starts_ends_with.rb new file mode 100644 index 0000000000..4f8521759b --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/symbol/starts_ends_with.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Symbol + alias :starts_with? :start_with? + alias :ends_with? :end_with? +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time.rb new file mode 100644 index 0000000000..9716257080 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/compatibility" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/time/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"] +require "active_support/core_ext/time/zones" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/acts_like.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/acts_like.rb similarity index 57% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/acts_like.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/acts_like.rb index 3f853b7893..8572b49639 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/acts_like.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/acts_like.rb @@ -1,4 +1,6 @@ -require 'active_support/core_ext/object/acts_like' +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" class Time # Duck-types as a Time-like class. See Object#acts_like?. diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/calculations.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/calculations.rb similarity index 51% rename from app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/calculations.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/calculations.rb index 89cd7516cd..ad88957cc9 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/calculations.rb @@ -1,8 +1,12 @@ -require 'active_support/duration' -require 'active_support/core_ext/time/conversions' -require 'active_support/time_with_zone' -require 'active_support/core_ext/time/zones' -require 'active_support/core_ext/date_and_time/calculations' +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/time/conversions" +require "active_support/time_with_zone" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/module/remove_method" class Time include DateAndTime::Calculations @@ -15,9 +19,9 @@ def ===(other) super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone)) end - # Return the number of days in the given month. + # Returns the number of days in the given month. # If no year is specified, it will use the current year. - def days_in_month(month, year = now.year) + def days_in_month(month, year = current.year) if month == 2 && ::Date.gregorian_leap?(year) 29 else @@ -25,6 +29,12 @@ def days_in_month(month, year = now.year) end end + # Returns the number of days in the given year. + # If no year is specified, it will use the current year. + def days_in_year(year = current.year) + days_in_month(2, year) + 337 + end + # Returns Time.zone.now when Time.zone or config.time_zone are set, otherwise just returns Time.now. def current ::Time.zone ? ::Time.zone.now : ::Time.now @@ -32,13 +42,15 @@ def current # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime # instances can be used when called with a single argument - def at_with_coercion(*args) - return at_without_coercion(*args) if args.size != 1 + def at_with_coercion(*args, **kwargs) + return at_without_coercion(*args, **kwargs) if args.size != 1 || !kwargs.empty? # Time.at can be called with a time or numerical value time_or_number = args.first - if time_or_number.is_a?(ActiveSupport::TimeWithZone) || time_or_number.is_a?(DateTime) + if time_or_number.is_a?(ActiveSupport::TimeWithZone) + at_without_coercion(time_or_number.to_r).getlocal + elsif time_or_number.is_a?(DateTime) at_without_coercion(time_or_number.to_f).getlocal else at_without_coercion(time_or_number) @@ -46,11 +58,38 @@ def at_with_coercion(*args) end alias_method :at_without_coercion, :at alias_method :at, :at_with_coercion + + # Creates a +Time+ instance from an RFC 3339 string. + # + # Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000 + # + # If the time or offset components are missing then an +ArgumentError+ will be raised. + # + # Time.rfc3339('1999-12-31') # => ArgumentError: invalid date + def rfc3339(str) + parts = Date._rfc3339(str) + + raise ArgumentError, "invalid date" if parts.empty? + + Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour), + parts.fetch(:min), + parts.fetch(:sec) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset) + ) + end end - # Seconds since midnight: Time.now.seconds_since_midnight + # Returns the number of seconds since 00:00:00. + # + # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0.0 + # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0 + # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0 def seconds_since_midnight - to_i - change(:hour => 0).to_i + (usec / 1.0e+6) + to_i - change(hour: 0).to_i + (usec / 1.0e+6) end # Returns the number of seconds until 23:59:59. @@ -62,32 +101,70 @@ def seconds_until_end_of_day end_of_day.to_i - to_i end + # Returns the fraction of a second as a +Rational+ + # + # Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2) + def sec_fraction + subsec + end + + unless Time.method_defined?(:floor) + def floor(precision = 0) + change(nsec: 0) + subsec.floor(precision) + end + end + + # Restricted Ruby version due to a bug in `Time#ceil` + # See https://bugs.ruby-lang.org/issues/17025 for more details + if RUBY_VERSION <= "2.8" + remove_possible_method :ceil + def ceil(precision = 0) + change(nsec: 0) + subsec.ceil(precision) + end + end + # Returns a new Time where one or more of the elements have been changed according # to the +options+ parameter. The time options (:hour, :min, - # :sec, :usec) reset cascadingly, so if only the hour is passed, - # then minute, sec, and usec is set to 0. If the hour and minute is passed, then - # sec and usec is set to 0. The +options+ parameter takes a hash with any of these - # keys: :year, :month, :day, :hour, :min, - # :sec, :usec. + # :sec, :usec, :nsec) reset cascadingly, so if only + # the hour is passed, then minute, sec, usec, and nsec is set to 0. If the hour + # and minute is passed, then sec, usec, and nsec is set to 0. The +options+ parameter + # takes a hash with any of these keys: :year, :month, :day, + # :hour, :min, :sec, :usec, :nsec, + # :offset. Pass either :usec or :nsec, not both. # # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0) # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0) # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0) def change(options) - new_year = options.fetch(:year, year) - new_month = options.fetch(:month, month) - new_day = options.fetch(:day, day) - new_hour = options.fetch(:hour, hour) - new_min = options.fetch(:min, options[:hour] ? 0 : min) - new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) - new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) - - if utc? - ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) + new_year = options.fetch(:year, year) + new_month = options.fetch(:month, month) + new_day = options.fetch(:day, day) + new_hour = options.fetch(:hour, hour) + new_min = options.fetch(:min, options[:hour] ? 0 : min) + new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_offset = options.fetch(:offset, nil) + + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_usec = Rational(new_nsec, 1000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + end + + raise ArgumentError, "argument out of range" if new_usec >= 1000000 + + new_sec += Rational(new_usec, 1000000) + + if new_offset + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset) + elsif utc? + ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec) + elsif zone&.respond_to?(:utc_to_local) + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone) elsif zone - ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) + ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec) else - ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset) + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset) end end @@ -96,6 +173,16 @@ def change(options) # takes a hash with any of these keys: :years, :months, # :weeks, :days, :hours, :minutes, # :seconds. + # + # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700 + # + # Just like Date#advance, increments are applied in order of time units from + # largest to smallest. This order can affect the result around the end of a + # month. def advance(options) unless options[:weeks].nil? options[:weeks], partial_weeks = options[:weeks].divmod(1) @@ -107,9 +194,8 @@ def advance(options) options[:hours] = options.fetch(:hours, 0) + 24 * partial_days end - d = to_date.advance(options) - d = d.gregorian if d.julian? - time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) + d = to_date.gregorian.advance(options) + time_advanced_by_date = change(year: d.year, month: d.month, day: d.day) seconds_to_advance = \ options.fetch(:seconds, 0) + options.fetch(:minutes, 0) * 60 + @@ -137,8 +223,7 @@ def since(seconds) # Returns a new Time representing the start of the day (0:00) def beginning_of_day - #(self - seconds_since_midnight).change(usec: 0) - change(:hour => 0) + change(hour: 0) end alias :midnight :beginning_of_day alias :at_midnight :beginning_of_day @@ -146,7 +231,7 @@ def beginning_of_day # Returns a new Time representing the middle of the day (12:00) def middle_of_day - change(:hour => 12) + change(hour: 12) end alias :midday :middle_of_day alias :noon :middle_of_day @@ -154,54 +239,49 @@ def middle_of_day alias :at_noon :middle_of_day alias :at_middle_of_day :middle_of_day - # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the day, 23:59:59.999999 def end_of_day change( - :hour => 23, - :min => 59, - :sec => 59, - :usec => Rational(999999999, 1000) + hour: 23, + min: 59, + sec: 59, + usec: Rational(999999999, 1000) ) end alias :at_end_of_day :end_of_day # Returns a new Time representing the start of the hour (x:00) def beginning_of_hour - change(:min => 0) + change(min: 0) end alias :at_beginning_of_hour :beginning_of_hour - # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the hour, x:59:59.999999 def end_of_hour change( - :min => 59, - :sec => 59, - :usec => Rational(999999999, 1000) + min: 59, + sec: 59, + usec: Rational(999999999, 1000) ) end alias :at_end_of_hour :end_of_hour # Returns a new Time representing the start of the minute (x:xx:00) def beginning_of_minute - change(:sec => 0) + change(sec: 0) end alias :at_beginning_of_minute :beginning_of_minute - # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the minute, x:xx:59.999999 def end_of_minute change( - :sec => 59, - :usec => Rational(999999999, 1000) + sec: 59, + usec: Rational(999999999, 1000) ) end alias :at_end_of_minute :end_of_minute - # Returns a Range representing the whole day of the current time. - def all_day - beginning_of_day..end_of_day - end - - def plus_with_duration(other) #:nodoc: + def plus_with_duration(other) # :nodoc: if ActiveSupport::Duration === other other.since(self) else @@ -211,7 +291,7 @@ def plus_with_duration(other) #:nodoc: alias_method :plus_without_duration, :+ alias_method :+, :plus_with_duration - def minus_with_duration(other) #:nodoc: + def minus_with_duration(other) # :nodoc: if ActiveSupport::Duration === other other.until(self) else @@ -229,13 +309,15 @@ def minus_with_coercion(other) other.is_a?(DateTime) ? to_f - other.to_f : minus_without_coercion(other) end alias_method :minus_without_coercion, :- - alias_method :-, :minus_with_coercion + alias_method :-, :minus_with_coercion # rubocop:disable Lint/DuplicateMethods # Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances # can be chronologically compared with a Time def compare_with_coercion(other) - # we're avoiding Time#to_datetime cause it's expensive - if other.is_a?(Time) + # we're avoiding Time#to_datetime and Time#to_time because they're expensive + if other.class == Time + compare_without_coercion(other) + elsif other.is_a?(Time) compare_without_coercion(other.to_time) else to_datetime <=> other @@ -254,4 +336,33 @@ def eql_with_coercion(other) alias_method :eql_without_coercion, :eql? alias_method :eql?, :eql_with_coercion + # Returns a new time the specified number of days ago. + def prev_day(days = 1) + advance(days: -days) + end + + # Returns a new time the specified number of days in the future. + def next_day(days = 1) + advance(days: days) + end + + # Returns a new time the specified number of months ago. + def prev_month(months = 1) + advance(months: -months) + end + + # Returns a new time the specified number of months in the future. + def next_month(months = 1) + advance(months: months) + end + + # Returns a new time the specified number of years ago. + def prev_year(years = 1) + advance(years: -years) + end + + # Returns a new time the specified number of years in the future. + def next_year(years = 1) + advance(years: years) + end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/compatibility.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/compatibility.rb new file mode 100644 index 0000000000..495e4f307b --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/compatibility.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/redefine_method" + +class Time + include DateAndTime::Compatibility + + silence_redefinition_of_method :to_time + + # Either return +self+ or the time in the local system timezone depending + # on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + preserve_timezone ? self : getlocal + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/conversions.rb new file mode 100644 index 0000000000..aeb8e142e1 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/conversions.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require "time" +require "active_support/inflector/methods" +require "active_support/values/time_zone" + +class Time + DATE_FORMATS = { + db: "%Y-%m-%d %H:%M:%S", + inspect: "%Y-%m-%d %H:%M:%S.%9N %z", + number: "%Y%m%d%H%M%S", + nsec: "%Y%m%d%H%M%S%9N", + usec: "%Y%m%d%H%M%S%6N", + time: "%H:%M", + short: "%d %b %H:%M", + long: "%B %d, %Y %H:%M", + long_ordinal: lambda { |time| + day_format = ActiveSupport::Inflector.ordinalize(time.day) + time.strftime("%B #{day_format}, %Y %H:%M") + }, + rfc822: lambda { |time| + offset_format = time.formatted_offset(false) + time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") + }, + iso8601: lambda { |time| time.iso8601 } + } + + # Converts to a formatted string. See DATE_FORMATS for built-in formats. + # + # This method is aliased to to_formatted_s. + # + # time = Time.now # => 2007-01-18 06:10:17 -06:00 + # + # time.to_fs(:time) # => "06:10" + # time.to_formatted_s(:time) # => "06:10" + # + # time.to_fs(:db) # => "2007-01-18 06:10:17" + # time.to_fs(:number) # => "20070118061017" + # time.to_fs(:short) # => "18 Jan 06:10" + # time.to_fs(:long) # => "January 18, 2007 06:10" + # time.to_fs(:long_ordinal) # => "January 18th, 2007 06:10" + # time.to_fs(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" + # time.to_fs(:iso8601) # => "2007-01-18T06:10:17-06:00" + # + # == Adding your own time formats to +to_fs+ + # You can add your own formats to the Time::DATE_FORMATS hash. + # Use the format name as the hash key and either a strftime string + # or Proc instance that takes a time argument as the value. + # + # # config/initializers/time_formats.rb + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' + # Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") } + def to_fs(format = :default) + if formatter = DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + # Change to `to_s` when deprecation is gone. Also deprecate `to_default_s`. + to_default_s + end + end + alias_method :to_formatted_s, :to_fs + alias_method :to_default_s, :to_s + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # Time.local(2000).formatted_offset # => "-06:00" + # Time.local(2000).formatted_offset(false) # => "-0600" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon) + end + + # Aliased to +xmlschema+ for compatibility with +DateTime+ + alias_method :rfc3339, :xmlschema +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/deprecated_conversions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/deprecated_conversions.rb new file mode 100644 index 0000000000..2fe730b778 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/deprecated_conversions.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "time" + +class Time + NOT_SET = Object.new # :nodoc: + def to_s(format = NOT_SET) # :nodoc: + if formatter = DATE_FORMATS[format] + ActiveSupport::Deprecation.warn( + "Time#to_s(#{format.inspect}) is deprecated. Please use Time#to_fs(#{format.inspect}) instead." + ) + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + elsif format == NOT_SET + to_default_s + else + ActiveSupport::Deprecation.warn( + "Time#to_s(#{format.inspect}) is deprecated. Please use Time#to_fs(#{format.inspect}) instead." + ) + to_default_s + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/zones.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/zones.rb new file mode 100644 index 0000000000..5703621263 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/time/zones.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date_and_time/zones" + +class Time + include DateAndTime::Zones + class << self + attr_accessor :zone_default + + # Returns the TimeZone for the current request, if this has been set (via Time.zone=). + # If Time.zone has not been set for the current request, returns the TimeZone specified in config.time_zone. + def zone + ::ActiveSupport::IsolatedExecutionState[:time_zone] || zone_default + end + + # Sets Time.zone to a TimeZone object for the current request/thread. + # + # This method accepts any of the following: + # + # * A Rails TimeZone object. + # * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", -5.hours). + # * A TZInfo::Timezone object. + # * An identifier for a TZInfo::Timezone object (e.g., "America/New_York"). + # + # Here's an example of how you might set Time.zone on a per request basis and reset it when the request is done. + # current_user.time_zone just needs to return a string identifying the user's preferred time zone: + # + # class ApplicationController < ActionController::Base + # around_action :set_time_zone + # + # def set_time_zone + # if logged_in? + # Time.use_zone(current_user.time_zone) { yield } + # else + # yield + # end + # end + # end + def zone=(time_zone) + ::ActiveSupport::IsolatedExecutionState[:time_zone] = find_zone!(time_zone) + end + + # Allows override of Time.zone locally inside supplied block; + # resets Time.zone to existing value when done. + # + # class ApplicationController < ActionController::Base + # around_action :set_time_zone + # + # private + # def set_time_zone + # Time.use_zone(current_user.timezone) { yield } + # end + # end + # + # NOTE: This won't affect any ActiveSupport::TimeWithZone + # objects that have already been created, e.g. any model timestamp + # attributes that have been read before the block will remain in + # the application's default timezone. + def use_zone(time_zone) + new_zone = find_zone!(time_zone) + begin + old_zone, ::Time.zone = ::Time.zone, new_zone + yield + ensure + ::Time.zone = old_zone + end + end + + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by Time.zone=. + # Raises an +ArgumentError+ for invalid time zones. + # + # Time.find_zone! "America/New_York" # => # + # Time.find_zone! "EST" # => # + # Time.find_zone! -5.hours # => # + # Time.find_zone! nil # => nil + # Time.find_zone! false # => false + # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE + def find_zone!(time_zone) + return time_zone unless time_zone + + ActiveSupport::TimeZone[time_zone] || raise(ArgumentError, "Invalid Timezone: #{time_zone}") + end + + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by Time.zone=. + # Returns +nil+ for invalid time zones. + # + # Time.find_zone "America/New_York" # => # + # Time.find_zone "NOT-A-TIMEZONE" # => nil + def find_zone(time_zone) + find_zone!(time_zone) rescue nil + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/uri.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/uri.rb new file mode 100644 index 0000000000..9811477d3d --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/core_ext/uri.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ActiveSupport::Deprecation.warn(<<-MSG.squish) + `active_support/core_ext/uri` is deprecated and will be removed in Rails 7.1. +MSG diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/current_attributes.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/current_attributes.rb new file mode 100644 index 0000000000..b4c6a5d426 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/current_attributes.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true + +require "active_support/callbacks" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically + # before and after each request. This allows you to keep all the per-request attributes easily + # available to the whole system. + # + # The following full app-like example demonstrates how to use a Current class to + # facilitate easy access to the global, per-request attributes without passing them deeply + # around everywhere: + # + # # app/models/current.rb + # class Current < ActiveSupport::CurrentAttributes + # attribute :account, :user + # attribute :request_id, :user_agent, :ip_address + # + # resets { Time.zone = nil } + # + # def user=(user) + # super + # self.account = user.account + # Time.zone = user.time_zone + # end + # end + # + # # app/controllers/concerns/authentication.rb + # module Authentication + # extend ActiveSupport::Concern + # + # included do + # before_action :authenticate + # end + # + # private + # def authenticate + # if authenticated_user = User.find_by(id: cookies.encrypted[:user_id]) + # Current.user = authenticated_user + # else + # redirect_to new_session_url + # end + # end + # end + # + # # app/controllers/concerns/set_current_request_details.rb + # module SetCurrentRequestDetails + # extend ActiveSupport::Concern + # + # included do + # before_action do + # Current.request_id = request.uuid + # Current.user_agent = request.user_agent + # Current.ip_address = request.ip + # end + # end + # end + # + # class ApplicationController < ActionController::Base + # include Authentication + # include SetCurrentRequestDetails + # end + # + # class MessagesController < ApplicationController + # def create + # Current.account.messages.create(message_params) + # end + # end + # + # class Message < ApplicationRecord + # belongs_to :creator, default: -> { Current.user } + # after_create { |message| Event.create(record: message) } + # end + # + # class Event < ApplicationRecord + # before_create do + # self.request_id = Current.request_id + # self.user_agent = Current.user_agent + # self.ip_address = Current.ip_address + # end + # end + # + # A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result. + # Current should only be used for a few, top-level globals, like account, user, and request details. + # The attributes stuck in Current should be used by more or less all actions on all requests. If you start + # sticking controller-specific attributes in there, you're going to create a mess. + class CurrentAttributes + include ActiveSupport::Callbacks + define_callbacks :reset + + class << self + # Returns singleton instance for this class in this thread. If none exists, one is created. + def instance + current_instances[current_instances_key] ||= new + end + + # Declares one or more attributes that will be given both class and instance accessor methods. + def attribute(*names) + ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner| + names.each do |name| + owner.define_cached_method(name, namespace: :current_attributes) do |batch| + batch << + "def #{name}" << + "attributes[:#{name}]" << + "end" + end + owner.define_cached_method("#{name}=", namespace: :current_attributes) do |batch| + batch << + "def #{name}=(value)" << + "attributes[:#{name}] = value" << + "end" + end + end + end + + ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner| + names.each do |name| + owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch| + batch << + "def #{name}" << + "instance.#{name}" << + "end" + end + owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch| + batch << + "def #{name}=(value)" << + "instance.#{name} = value" << + "end" + end + end + end + end + + # Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values. + def before_reset(&block) + set_callback :reset, :before, &block + end + + # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone. + def resets(&block) + set_callback :reset, :after, &block + end + alias_method :after_reset, :resets + + delegate :set, :reset, to: :instance + + def reset_all # :nodoc: + current_instances.each_value(&:reset) + end + + def clear_all # :nodoc: + reset_all + current_instances.clear + end + + private + def generated_attribute_methods + @generated_attribute_methods ||= Module.new.tap { |mod| include mod } + end + + def current_instances + IsolatedExecutionState[:current_attributes_instances] ||= {} + end + + def current_instances_key + @current_instances_key ||= name.to_sym + end + + def method_missing(name, *args, &block) + # Caches the method definition as a singleton method of the receiver. + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance + + send(name, *args, &block) + end + ruby2_keywords(:method_missing) + + def respond_to_missing?(name, _) + super || instance.respond_to?(name) + end + end + + attr_accessor :attributes + + def initialize + @attributes = {} + end + + # Expose one or more attributes within a block. Old values are returned after the block concludes. + # Example demonstrating the common use of needing to set Current attributes outside the request-cycle: + # + # class Chat::PublicationJob < ApplicationJob + # def perform(attributes, room_number, creator) + # Current.set(person: creator) do + # Chat::Publisher.publish(attributes: attributes, room_number: room_number) + # end + # end + # end + def set(set_attributes) + old_attributes = compute_attributes(set_attributes.keys) + assign_attributes(set_attributes) + yield + ensure + assign_attributes(old_attributes) + end + + # Reset all attributes. Should be called before and after actions, when used as a per-request singleton. + def reset + run_callbacks :reset do + self.attributes = {} + end + end + + private + def assign_attributes(new_attributes) + new_attributes.each { |key, value| public_send("#{key}=", value) } + end + + def compute_attributes(keys) + keys.index_with { |key| public_send(key) } + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/current_attributes/test_helper.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/current_attributes/test_helper.rb new file mode 100644 index 0000000000..2016384a80 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/current_attributes/test_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActiveSupport::CurrentAttributes::TestHelper # :nodoc: + def before_setup + ActiveSupport::CurrentAttributes.reset_all + super + end + + def after_teardown + super + ActiveSupport::CurrentAttributes.reset_all + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies.rb new file mode 100644 index 0000000000..bb1fc4697f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require "set" +require "active_support/dependencies/interlock" + +module ActiveSupport # :nodoc: + module Dependencies # :nodoc: + require_relative "dependencies/require_dependency" + + singleton_class.attr_accessor :interlock + @interlock = Interlock.new + + # :doc: + + # Execute the supplied block without interference from any + # concurrent loads. + def self.run_interlock(&block) + interlock.running(&block) + end + + # Execute the supplied block while holding an exclusive lock, + # preventing any other thread from being inside a #run_interlock + # block at the same time. + def self.load_interlock(&block) + interlock.loading(&block) + end + + # Execute the supplied block while holding an exclusive lock, + # preventing any other thread from being inside a #run_interlock + # block at the same time. + def self.unload_interlock(&block) + interlock.unloading(&block) + end + + # :nodoc: + + # The array of directories from which we autoload and reload, if reloading + # is enabled. The public interface to push directories to this collection + # from applications or engines is config.autoload_paths. + # + # This collection is allowed to have intersection with autoload_once_paths. + # Common directories are not reloaded. + singleton_class.attr_accessor :autoload_paths + self.autoload_paths = [] + + # The array of directories from which we autoload and never reload, even if + # reloading is enabled. The public interface to push directories to this + # collection from applications or engines is config.autoload_once_paths. + singleton_class.attr_accessor :autoload_once_paths + self.autoload_once_paths = [] + + # This is a private set that collects all eager load paths during bootstrap. + # Useful for Zeitwerk integration. The public interface to push custom + # directories to this collection from applications or engines is + # config.eager_load_paths. + singleton_class.attr_accessor :_eager_load_paths + self._eager_load_paths = Set.new + + # If reloading is enabled, this private set holds autoloaded classes tracked + # by the descendants tracker. It is populated by an on_load callback in the + # main autoloader. Used to clear state. + singleton_class.attr_accessor :_autoloaded_tracked_classes + self._autoloaded_tracked_classes = Set.new + + # If reloading is enabled, this private attribute stores the main autoloader + # of a Rails application. It is `nil` otherwise. + # + # The public interface for this autoloader is `Rails.autoloaders.main`. + singleton_class.attr_accessor :autoloader + + # Private method that reloads constants autoloaded by the main autoloader. + # + # Rails.application.reloader.reload! is the public interface for application + # reload. That involves more things, like deleting unloaded classes from the + # internal state of the descendants tracker, or reloading routes. + def self.clear + unload_interlock do + _autoloaded_tracked_classes.clear + autoloader.reload + end + end + + # Private method used by require_dependency. + def self.search_for_file(relpath) + relpath += ".rb" unless relpath.end_with?(".rb") + autoload_paths.each do |autoload_path| + abspath = File.join(autoload_path, relpath) + return abspath if File.file?(abspath) + end + nil + end + + # Private method that helps configuring the autoloaders. + def self.eager_load?(path) + _eager_load_paths.member?(path) + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/dependencies/autoload.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/autoload.rb similarity index 95% rename from app/server/ruby/vendor/activesupport/lib/active_support/dependencies/autoload.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/autoload.rb index c0dba5f7fd..1cee85d98f 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/dependencies/autoload.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/autoload.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/inflector/methods" module ActiveSupport @@ -67,7 +69,7 @@ def eager_autoload end def eager_load! - @_autoloads.values.each { |file| require file } + @_autoloads.each_value { |file| require file } end def autoloads diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/interlock.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/interlock.rb new file mode 100644 index 0000000000..e0e32e821c --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/interlock.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "active_support/concurrency/share_lock" + +module ActiveSupport # :nodoc: + module Dependencies # :nodoc: + class Interlock + def initialize # :nodoc: + @lock = ActiveSupport::Concurrency::ShareLock.new + end + + def loading(&block) + @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load], &block) + end + + def unloading(&block) + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], &block) + end + + def start_unloading + @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload]) + end + + def done_unloading + @lock.stop_exclusive(compatible: [:load, :unload]) + end + + def start_running + @lock.start_sharing + end + + def done_running + @lock.stop_sharing + end + + def running(&block) + @lock.sharing(&block) + end + + def permit_concurrent_loads(&block) + @lock.yield_shares(compatible: [:load], &block) + end + + def raw_state(&block) # :nodoc: + @lock.raw_state(&block) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/require_dependency.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/require_dependency.rb new file mode 100644 index 0000000000..403f5fa4c6 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/dependencies/require_dependency.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveSupport::Dependencies::RequireDependency + # Warning: This method is obsolete. The semantics of the autoloader + # match Ruby's and you do not need to be defensive with load order anymore. + # Just refer to classes and modules normally. + # + # Engines that do not control the mode in which their parent application runs + # should call +require_dependency+ where needed in case the runtime mode is + # +:classic+. + def require_dependency(filename) + filename = filename.to_path if filename.respond_to?(:to_path) + + unless filename.is_a?(String) + raise ArgumentError, "the file name must be either a String or implement #to_path -- you passed #{filename.inspect}" + end + + if abspath = ActiveSupport::Dependencies.search_for_file(filename) + require abspath + else + require filename + end + end + + # We could define require_dependency in Object directly, but a module makes + # the extension apparent if you list ancestors. + Object.prepend(self) +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation.rb similarity index 58% rename from app/server/ruby/vendor/activesupport/lib/active_support/deprecation.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation.rb index ab16977bda..d72838683b 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation.rb @@ -1,8 +1,10 @@ -require 'singleton' +# frozen_string_literal: true + +require "singleton" module ActiveSupport # \Deprecation specifies the API used by Rails to deprecate methods, instance - # variables, objects and constants. + # variables, objects, and constants. class Deprecation # active_support.rb sets an autoload for ActiveSupport::Deprecation. # @@ -12,32 +14,38 @@ class Deprecation # a circular require warning for active_support/deprecation.rb. # # So, we define the constant first, and load dependencies later. - require 'active_support/deprecation/instance_delegator' - require 'active_support/deprecation/behaviors' - require 'active_support/deprecation/reporting' - require 'active_support/deprecation/method_wrappers' - require 'active_support/deprecation/proxy_wrappers' - require 'active_support/core_ext/module/deprecation' + require "active_support/deprecation/instance_delegator" + require "active_support/deprecation/behaviors" + require "active_support/deprecation/reporting" + require "active_support/deprecation/disallowed" + require "active_support/deprecation/constant_accessor" + require "active_support/deprecation/method_wrappers" + require "active_support/deprecation/proxy_wrappers" + require "active_support/core_ext/module/deprecation" + require "concurrent/atomic/thread_local_var" include Singleton include InstanceDelegator include Behavior include Reporting + include Disallowed include MethodWrapper # The version number in which the deprecated behavior will be removed, by default. attr_accessor :deprecation_horizon # It accepts two parameters on initialization. The first is a version of library - # and the second is a library name + # and the second is a library name. # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') - def initialize(deprecation_horizon = '4.2', gem_name = 'Rails') + def initialize(deprecation_horizon = "7.1", gem_name = "Rails") self.gem_name = gem_name self.deprecation_horizon = deprecation_horizon # By default, warnings are not silenced and debugging is off. self.silenced = false self.debug = false + @silenced_thread = Concurrent::ThreadLocalVar.new(false) + @explicitly_allowed_warnings = Concurrent::ThreadLocalVar.new(nil) end end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/behaviors.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/behaviors.rb new file mode 100644 index 0000000000..bb31fba028 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/behaviors.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require "active_support/notifications" + +module ActiveSupport + # Raised when ActiveSupport::Deprecation::Behavior#behavior is set with :raise. + # You would set :raise, as a behavior to raise errors and proactively report exceptions from deprecations. + class DeprecationException < StandardError + end + + class Deprecation + # Default warning behaviors per Rails.env. + DEFAULT_BEHAVIORS = { + raise: ->(message, callstack, deprecation_horizon, gem_name) { + e = DeprecationException.new(message) + e.set_backtrace(callstack.map(&:to_s)) + raise e + }, + + stderr: ->(message, callstack, deprecation_horizon, gem_name) { + $stderr.puts(message) + $stderr.puts callstack.join("\n ") if debug + }, + + log: ->(message, callstack, deprecation_horizon, gem_name) { + logger = + if defined?(Rails.logger) && Rails.logger + Rails.logger + else + require "active_support/logger" + ActiveSupport::Logger.new($stderr) + end + logger.warn message + logger.debug callstack.join("\n ") if debug + }, + + notify: ->(message, callstack, deprecation_horizon, gem_name) { + notification_name = "deprecation.#{gem_name.underscore.tr('/', '_')}" + ActiveSupport::Notifications.instrument(notification_name, + message: message, + callstack: callstack, + gem_name: gem_name, + deprecation_horizon: deprecation_horizon) + }, + + silence: ->(message, callstack, deprecation_horizon, gem_name) { }, + } + + # Behavior module allows to determine how to display deprecation messages. + # You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+ + # constant. Available behaviors are: + # + # [+raise+] Raise ActiveSupport::DeprecationException. + # [+stderr+] Log all deprecation warnings to $stderr. + # [+log+] Log all deprecation warnings to +Rails.logger+. + # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. + # [+silence+] Do nothing. On Rails, set config.active_support.report_deprecations = false to disable all behaviors. + # + # Setting behaviors only affects deprecations that happen after boot time. + # For more information you can read the documentation of the +behavior=+ method. + module Behavior + # Whether to print a backtrace along with the warning. + attr_accessor :debug + + # Returns the current behavior or if one isn't set, defaults to +:stderr+. + def behavior + @behavior ||= [DEFAULT_BEHAVIORS[:stderr]] + end + + # Returns the current behavior for disallowed deprecations or if one isn't set, defaults to +:raise+. + def disallowed_behavior + @disallowed_behavior ||= [DEFAULT_BEHAVIORS[:raise]] + end + + # Sets the behavior to the specified value. Can be a single value, array, + # or an object that responds to +call+. + # + # Available behaviors: + # + # [+raise+] Raise ActiveSupport::DeprecationException. + # [+stderr+] Log all deprecation warnings to $stderr. + # [+log+] Log all deprecation warnings to +Rails.logger+. + # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. + # [+silence+] Do nothing. + # + # Setting behaviors only affects deprecations that happen after boot time. + # Deprecation warnings raised by gems are not affected by this setting + # because they happen before Rails boots up. + # + # ActiveSupport::Deprecation.behavior = :stderr + # ActiveSupport::Deprecation.behavior = [:stderr, :log] + # ActiveSupport::Deprecation.behavior = MyCustomHandler + # ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) { + # # custom stuff + # } + # + # If you are using Rails, you can set config.active_support.report_deprecations = false to disable + # all deprecation behaviors. This is similar to the +silence+ option but more performant. + def behavior=(behavior) + @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) } + end + + # Sets the behavior for disallowed deprecations (those configured by + # ActiveSupport::Deprecation.disallowed_warnings=) to the specified + # value. As with +behavior=+, this can be a single value, array, or an + # object that responds to +call+. + def disallowed_behavior=(behavior) + @disallowed_behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) } + end + + private + def arity_coerce(behavior) + unless behavior.respond_to?(:call) + raise ArgumentError, "#{behavior.inspect} is not a valid deprecation behavior." + end + + if behavior.respond_to?(:arity) && behavior.arity == 2 + -> message, callstack, _, _ { behavior.call(message, callstack) } + else + behavior + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/constant_accessor.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/constant_accessor.rb new file mode 100644 index 0000000000..1ed0015812 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/constant_accessor.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + # DeprecatedConstantAccessor transforms a constant into a deprecated one by + # hooking +const_missing+. + # + # It takes the names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. + # + # The deprecated constant now returns the same object as the new one rather + # than a proxy object, so it can be used transparently in +rescue+ blocks + # etc. + # + # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) + # + # # (In a later update, the original implementation of `PLANETS` has been removed.) + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # include ActiveSupport::Deprecation::DeprecatedConstantAccessor + # deprecate_constant 'PLANETS', 'PLANETS_POST_2006' + # + # PLANETS.map { |planet| planet.capitalize } + # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. + # (Backtrace information…) + # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] + module DeprecatedConstantAccessor + def self.included(base) + require "active_support/inflector/methods" + + extension = Module.new do + def const_missing(missing_const_name) + if class_variable_defined?(:@@_deprecated_constants) + if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s]) + replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations) + return ActiveSupport::Inflector.constantize(replacement[:new].to_s) + end + end + super + end + + def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance) + class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants) + class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator } + end + end + base.singleton_class.prepend extension + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/disallowed.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/disallowed.rb new file mode 100644 index 0000000000..0fc6e9a39d --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/disallowed.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + module Disallowed + # Sets the criteria used to identify deprecation messages which should be + # disallowed. Can be an array containing strings, symbols, or regular + # expressions. (Symbols are treated as strings.) These are compared against + # the text of the generated deprecation warning. + # + # Additionally the scalar symbol +:all+ may be used to treat all + # deprecations as disallowed. + # + # Deprecations matching a substring or regular expression will be handled + # using the configured Behavior#disallowed_behavior rather than + # Behavior#behavior. + attr_writer :disallowed_warnings + + # Returns the configured criteria used to identify deprecation messages + # which should be treated as disallowed. + def disallowed_warnings + @disallowed_warnings ||= [] + end + + private + def deprecation_disallowed?(message) + disallowed = ActiveSupport::Deprecation.disallowed_warnings + return false if explicitly_allowed?(message) + return true if disallowed == :all + disallowed.any? do |rule| + case rule + when String, Symbol + message.include?(rule.to_s) + when Regexp + rule.match?(message) + end + end + end + + def explicitly_allowed?(message) + allowances = @explicitly_allowed_warnings.value + return false unless allowances + return true if allowances == :all + allowances = [allowances] unless allowances.kind_of?(Array) + allowances.any? do |rule| + case rule + when String, Symbol + message.include?(rule.to_s) + when Regexp + rule.match?(message) + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/instance_delegator.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/instance_delegator.rb similarity index 51% rename from app/server/ruby/vendor/activesupport/lib/active_support/deprecation/instance_delegator.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/instance_delegator.rb index 8472a58add..59dd30ae30 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/instance_delegator.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/instance_delegator.rb @@ -1,11 +1,13 @@ -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/module/delegation' +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" module ActiveSupport class Deprecation module InstanceDelegator # :nodoc: def self.included(base) base.extend(ClassMethods) + base.singleton_class.prepend(OverrideDelegators) base.public_class_method :new end @@ -19,6 +21,18 @@ def method_added(method_name) singleton_class.delegate(method_name, to: :instance) end end + + module OverrideDelegators # :nodoc: + def warn(message = nil, callstack = nil) + callstack ||= caller_locations(2) + super + end + + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) + caller_backtrace ||= caller_locations(2) + super + end + end end end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/method_wrappers.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/method_wrappers.rb new file mode 100644 index 0000000000..54375982c2 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/method_wrappers.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/module/redefine_method" + +module ActiveSupport + class Deprecation + module MethodWrapper + # Declare that a method has been deprecated. + # + # class Fred + # def aaa; end + # def bbb; end + # def ccc; end + # def ddd; end + # def eee; end + # end + # + # Using the default deprecator: + # ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead') + # # => Fred + # + # Fred.new.aaa + # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10) + # # => nil + # + # Fred.new.bbb + # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11) + # # => nil + # + # Fred.new.ccc + # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12) + # # => nil + # + # Passing in a custom deprecator: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator) + # # => [:ddd] + # + # Fred.new.ddd + # DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15) + # # => nil + # + # Using a custom deprecator directly: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # custom_deprecator.deprecate_methods(Fred, eee: :zzz) + # # => [:eee] + # + # Fred.new.eee + # DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18) + # # => nil + def deprecate_methods(target_module, *method_names) + options = method_names.extract_options! + deprecator = options.delete(:deprecator) || self + method_names += options.keys + mod = nil + + method_names.each do |method_name| + message = options[method_name] + if target_module.method_defined?(method_name) || target_module.private_method_defined?(method_name) + method = target_module.instance_method(method_name) + target_module.module_eval do + redefine_method(method_name) do |*args, &block| + deprecator.deprecation_warning(method_name, message) + method.bind_call(self, *args, &block) + end + ruby2_keywords(method_name) + end + else + mod ||= Module.new + mod.module_eval do + define_method(method_name) do |*args, &block| + deprecator.deprecation_warning(method_name, message) + super(*args, &block) + end + ruby2_keywords(method_name) + end + end + end + + target_module.prepend(mod) if mod + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/proxy_wrappers.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/proxy_wrappers.rb new file mode 100644 index 0000000000..1584f7157a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/proxy_wrappers.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + class DeprecationProxy # :nodoc: + def self.new(*args, &block) + object = args.first + + return object unless object + super + end + + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } + + # Don't give a deprecation warning on inspect since test/unit and error + # logs rely on it for diagnostics. + def inspect + target.inspect + end + + private + def method_missing(called, *args, &block) + warn caller_locations, called, args + target.__send__(called, *args, &block) + end + end + + # DeprecatedObjectProxy transforms an object into a deprecated one. It + # takes an object, a deprecation message, and optionally a deprecator. The + # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified. + # + # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated") + # # => # + # + # deprecated_object.to_s + # DEPRECATION WARNING: This object is now deprecated. + # (Backtrace) + # # => "#" + class DeprecatedObjectProxy < DeprecationProxy + def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance) + @object = object + @message = message + @deprecator = deprecator + end + + private + def target + @object + end + + def warn(callstack, called, args) + @deprecator.warn(@message, callstack) + end + end + + # DeprecatedInstanceVariableProxy transforms an instance variable into a + # deprecated one. It takes an instance of a class, a method on that class + # and an instance variable. It optionally takes a deprecator as the last + # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none + # is specified. + # + # class Example + # def initialize + # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request) + # @_request = :special_request + # end + # + # def request + # @_request + # end + # + # def old_request + # @request + # end + # end + # + # example = Example.new + # # => # + # + # example.old_request.to_s + # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of + # @request.to_s + # (Backtrace information…) + # "special_request" + # + # example.request.to_s + # # => "special_request" + class DeprecatedInstanceVariableProxy < DeprecationProxy + def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance) + @instance = instance + @method = method + @var = var + @deprecator = deprecator + end + + private + def target + @instance.__send__(@method) + end + + def warn(callstack, called, args) + @deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack) + end + end + + # DeprecatedConstantProxy transforms a constant into a deprecated one. It + # takes the names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant + # now returns the value of the new one. + # + # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) + # + # # (In a later update, the original implementation of `PLANETS` has been removed.) + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') + # + # PLANETS.map { |planet| planet.capitalize } + # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. + # (Backtrace information…) + # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] + class DeprecatedConstantProxy < Module + def self.new(*args, **options, &block) + object = args.first + + return object unless object + super + end + + def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.") + Kernel.require "active_support/inflector/methods" + + @old_const = old_const + @new_const = new_const + @deprecator = deprecator + @message = message + end + + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } + + # Don't give a deprecation warning on inspect since test/unit and error + # logs rely on it for diagnostics. + def inspect + target.inspect + end + + # Don't give a deprecation warning on methods that IRB may invoke + # during tab-completion. + delegate :hash, :instance_methods, :name, :respond_to?, to: :target + + # Returns the class of the new constant. + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') + # PLANETS.class # => Array + def class + target.class + end + + private + def target + ActiveSupport::Inflector.constantize(@new_const.to_s) + end + + def const_missing(name) + @deprecator.warn(@message, caller_locations) + target.const_get(name) + end + + def method_missing(called, *args, &block) + @deprecator.warn(@message, caller_locations) + target.__send__(called, *args, &block) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/reporting.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/reporting.rb new file mode 100644 index 0000000000..51514eb3a6 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/deprecation/reporting.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require "rbconfig" + +module ActiveSupport + class Deprecation + module Reporting + # Whether to print a message (silent mode) + attr_writer :silenced + # Name of gem where method is deprecated + attr_accessor :gem_name + + # Outputs a deprecation warning to the output configured by + # ActiveSupport::Deprecation.behavior. + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + def warn(message = nil, callstack = nil) + return if silenced + + callstack ||= caller_locations(2) + deprecation_message(callstack, message).tap do |m| + if deprecation_disallowed?(message) + disallowed_behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) } + else + behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) } + end + end + end + + # Silence deprecation warnings within the block. + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + # + # ActiveSupport::Deprecation.silence do + # ActiveSupport::Deprecation.warn('something broke!') + # end + # # => nil + def silence(&block) + @silenced_thread.bind(true, &block) + end + + # Allow previously disallowed deprecation warnings within the block. + # allowed_warnings can be an array containing strings, symbols, or regular + # expressions. (Symbols are treated as strings). These are compared against + # the text of deprecation warning messages generated within the block. + # Matching warnings will be exempt from the rules set by + # +ActiveSupport::Deprecation.disallowed_warnings+ + # + # The optional if: argument accepts a truthy/falsy value or an object that + # responds to .call. If truthy, then matching warnings will be allowed. + # If falsey then the method yields to the block without allowing the warning. + # + # ActiveSupport::Deprecation.disallowed_behavior = :raise + # ActiveSupport::Deprecation.disallowed_warnings = [ + # "something broke" + # ] + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => ActiveSupport::DeprecationException + # + # ActiveSupport::Deprecation.allow ['something broke'] do + # ActiveSupport::Deprecation.warn('something broke!') + # end + # # => nil + # + # ActiveSupport::Deprecation.allow ['something broke'], if: Rails.env.production? do + # ActiveSupport::Deprecation.warn('something broke!') + # end + # # => ActiveSupport::DeprecationException for dev/test, nil for production + def allow(allowed_warnings = :all, if: true, &block) + conditional = binding.local_variable_get(:if) + conditional = conditional.call if conditional.respond_to?(:call) + if conditional + @explicitly_allowed_warnings.bind(allowed_warnings, &block) + else + yield + end + end + + def silenced + @silenced || @silenced_thread.value + end + + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) + caller_backtrace ||= caller_locations(2) + deprecated_method_warning(deprecated_method_name, message).tap do |msg| + warn(msg, caller_backtrace) + end + end + + private + # Outputs a deprecation warning message + # + # deprecated_method_warning(:method_name) + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}" + # deprecated_method_warning(:method_name, :another_method) + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)" + # deprecated_method_warning(:method_name, "Optional message") + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)" + def deprecated_method_warning(method_name, message = nil) + warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}" + case message + when Symbol then "#{warning} (use #{message} instead)" + when String then "#{warning} (#{message})" + else warning + end + end + + def deprecation_message(callstack, message = nil) + message ||= "You are using deprecated behavior which will be removed from the next major or minor release." + "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}" + end + + def deprecation_caller_message(callstack) + file, line, method = extract_callstack(callstack) + if file + if line && method + "(called from #{method} at #{file}:#{line})" + else + "(called from #{file}:#{line})" + end + end + end + + def extract_callstack(callstack) + return _extract_callstack(callstack) if callstack.first.is_a? String + + offending_line = callstack.find { |frame| + frame.absolute_path && !ignored_callstack(frame.absolute_path) + } || callstack.first + + [offending_line.path, offending_line.lineno, offending_line.label] + end + + def _extract_callstack(callstack) + warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE + offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first + + if offending_line + if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) + md.captures + else + offending_line + end + end + end + + RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" + + def ignored_callstack(path) + path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"]) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/descendants_tracker.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/descendants_tracker.rb new file mode 100644 index 0000000000..ea3ed1f41e --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/descendants_tracker.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +require "weakref" +require "active_support/ruby_features" + +module ActiveSupport + # This module provides an internal implementation to track descendants + # which is faster than iterating through ObjectSpace. + module DescendantsTracker + class << self + def direct_descendants(klass) + ActiveSupport::Deprecation.warn(<<~MSG) + ActiveSupport::DescendantsTracker.direct_descendants is deprecated and will be removed in Rails 7.1. + Use ActiveSupport::DescendantsTracker.subclasses instead. + MSG + subclasses(klass) + end + end + + @clear_disabled = false + + if RubyFeatures::CLASS_SUBCLASSES + @@excluded_descendants = if RUBY_ENGINE == "ruby" + # On MRI `ObjectSpace::WeakMap` keys are weak references. + # So we can simply use WeakMap as a `Set`. + ObjectSpace::WeakMap.new + else + # On TruffleRuby `ObjectSpace::WeakMap` keys are strong references. + # So we use `object_id` as a key and the actual object as a value. + # + # JRuby for now doesn't have Class#descendant, but when it will, it will likely + # have the same WeakMap semantic than Truffle so we future proof this as much as possible. + class WeakSet # :nodoc: + def initialize + @map = ObjectSpace::WeakMap.new + end + + def [](object) + @map.key?(object.object_id) + end + + def []=(object, _present) + @map[object.object_id] = object + end + end + WeakSet.new + end + + class << self + def disable_clear! # :nodoc: + unless @clear_disabled + @clear_disabled = true + remove_method(:subclasses) + @@excluded_descendants = nil + end + end + + def subclasses(klass) + klass.subclasses + end + + def descendants(klass) + klass.descendants + end + + def clear(classes) # :nodoc: + raise "DescendantsTracker.clear was disabled because config.cache_classes = true" if @clear_disabled + + classes.each do |klass| + @@excluded_descendants[klass] = true + klass.descendants.each do |descendant| + @@excluded_descendants[descendant] = true + end + end + end + + def native? # :nodoc: + true + end + end + + def subclasses + subclasses = super + subclasses.reject! { |d| @@excluded_descendants[d] } + subclasses + end + + def descendants + subclasses.concat(subclasses.flat_map(&:descendants)) + end + + def direct_descendants + ActiveSupport::Deprecation.warn(<<~MSG) + ActiveSupport::DescendantsTracker#direct_descendants is deprecated and will be removed in Rails 7.1. + Use #subclasses instead. + MSG + subclasses + end + else + @@direct_descendants = {} + + class << self + def disable_clear! # :nodoc: + @clear_disabled = true + end + + def subclasses(klass) + descendants = @@direct_descendants[klass] + descendants ? descendants.to_a : [] + end + + def descendants(klass) + arr = [] + accumulate_descendants(klass, arr) + arr + end + + def clear(classes) # :nodoc: + raise "DescendantsTracker.clear was disabled because config.cache_classes = true" if @clear_disabled + + @@direct_descendants.each do |klass, direct_descendants_of_klass| + if classes.member?(klass) + @@direct_descendants.delete(klass) + else + direct_descendants_of_klass.reject! do |direct_descendant_of_class| + classes.member?(direct_descendant_of_class) + end + end + end + end + + def native? # :nodoc: + false + end + + # This is the only method that is not thread safe, but is only ever called + # during the eager loading phase. + def store_inherited(klass, descendant) + (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant + end + + private + def accumulate_descendants(klass, acc) + if direct_descendants = @@direct_descendants[klass] + direct_descendants.each do |direct_descendant| + acc << direct_descendant + accumulate_descendants(direct_descendant, acc) + end + end + end + end + + def inherited(base) + DescendantsTracker.store_inherited(self, base) + super + end + + def direct_descendants + ActiveSupport::Deprecation.warn(<<~MSG) + ActiveSupport::DescendantsTracker#direct_descendants is deprecated and will be removed in Rails 7.1. + Use #subclasses instead. + MSG + DescendantsTracker.subclasses(self) + end + + def subclasses + DescendantsTracker.subclasses(self) + end + + def descendants + DescendantsTracker.descendants(self) + end + + # DescendantsArray is an array that contains weak references to classes. + class DescendantsArray # :nodoc: + include Enumerable + + def initialize + @refs = [] + end + + def initialize_copy(orig) + @refs = @refs.dup + end + + def <<(klass) + @refs << WeakRef.new(klass) + end + + def each + @refs.reject! do |ref| + yield ref.__getobj__ + false + rescue WeakRef::RefError + true + end + self + end + + def refs_size + @refs.size + end + + def cleanup! + @refs.delete_if { |ref| !ref.weakref_alive? } + end + + def reject! + @refs.reject! do |ref| + yield ref.__getobj__ + rescue WeakRef::RefError + true + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/digest.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/digest.rb new file mode 100644 index 0000000000..a3d27be643 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/digest.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "openssl" + +module ActiveSupport + class Digest # :nodoc: + class << self + def hash_digest_class + @hash_digest_class ||= OpenSSL::Digest::MD5 + end + + def hash_digest_class=(klass) + raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest) + @hash_digest_class = klass + end + + def hexdigest(arg) + hash_digest_class.hexdigest(arg)[0...32] + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration.rb new file mode 100644 index 0000000000..19986d1d0c --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration.rb @@ -0,0 +1,514 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/string/filters" + +module ActiveSupport + # Provides accurate date and time measurements using Date#advance and + # Time#advance, respectively. It mainly supports the methods on Numeric. + # + # 1.month.ago # equivalent to Time.now.advance(months: -1) + class Duration + class Scalar < Numeric # :nodoc: + attr_reader :value + delegate :to_i, :to_f, :to_s, to: :value + + def initialize(value) + @value = value + end + + def coerce(other) + [Scalar.new(other), self] + end + + def -@ + Scalar.new(-value) + end + + def <=>(other) + if Scalar === other || Duration === other + value <=> other.value + elsif Numeric === other + value <=> other + else + nil + end + end + + def +(other) + if Duration === other + seconds = value + other._parts.fetch(:seconds, 0) + new_parts = other._parts.merge(seconds: seconds) + new_value = value + other.value + + Duration.new(new_value, new_parts, other.variable?) + else + calculate(:+, other) + end + end + + def -(other) + if Duration === other + seconds = value - other._parts.fetch(:seconds, 0) + new_parts = other._parts.transform_values(&:-@) + new_parts = new_parts.merge(seconds: seconds) + new_value = value - other.value + + Duration.new(new_value, new_parts, other.variable?) + else + calculate(:-, other) + end + end + + def *(other) + if Duration === other + new_parts = other._parts.transform_values { |other_value| value * other_value } + new_value = value * other.value + + Duration.new(new_value, new_parts, other.variable?) + else + calculate(:*, other) + end + end + + def /(other) + if Duration === other + value / other.value + else + calculate(:/, other) + end + end + + def %(other) + if Duration === other + Duration.build(value % other.value) + else + calculate(:%, other) + end + end + + def variable? # :nodoc: + false + end + + private + def calculate(op, other) + if Scalar === other + Scalar.new(value.public_send(op, other.value)) + elsif Numeric === other + Scalar.new(value.public_send(op, other)) + else + raise_type_error(other) + end + end + + def raise_type_error(other) + raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" + end + end + + SECONDS_PER_MINUTE = 60 + SECONDS_PER_HOUR = 3600 + SECONDS_PER_DAY = 86400 + SECONDS_PER_WEEK = 604800 + SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year + SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days) + + PARTS_IN_SECONDS = { + seconds: 1, + minutes: SECONDS_PER_MINUTE, + hours: SECONDS_PER_HOUR, + days: SECONDS_PER_DAY, + weeks: SECONDS_PER_WEEK, + months: SECONDS_PER_MONTH, + years: SECONDS_PER_YEAR + }.freeze + + PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze + VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze + + attr_reader :value + + autoload :ISO8601Parser, "active_support/duration/iso8601_parser" + autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer" + + class << self + # Creates a new Duration from string formatted according to ISO 8601 Duration. + # + # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # This method allows negative parts to be present in pattern. + # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+. + def parse(iso8601duration) + parts = ISO8601Parser.new(iso8601duration).parse! + new(calculate_total_seconds(parts), parts) + end + + def ===(other) # :nodoc: + other.is_a?(Duration) + rescue ::NoMethodError + false + end + + def seconds(value) # :nodoc: + new(value, { seconds: value }, false) + end + + def minutes(value) # :nodoc: + new(value * SECONDS_PER_MINUTE, { minutes: value }, false) + end + + def hours(value) # :nodoc: + new(value * SECONDS_PER_HOUR, { hours: value }, false) + end + + def days(value) # :nodoc: + new(value * SECONDS_PER_DAY, { days: value }, true) + end + + def weeks(value) # :nodoc: + new(value * SECONDS_PER_WEEK, { weeks: value }, true) + end + + def months(value) # :nodoc: + new(value * SECONDS_PER_MONTH, { months: value }, true) + end + + def years(value) # :nodoc: + new(value * SECONDS_PER_YEAR, { years: value }, true) + end + + # Creates a new Duration from a seconds value that is converted + # to the individual parts: + # + # ActiveSupport::Duration.build(31556952).parts # => {:years=>1} + # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1} + # + def build(value) + unless value.is_a?(::Numeric) + raise TypeError, "can't build an #{self.name} from a #{value.class.name}" + end + + parts = {} + remainder_sign = value <=> 0 + remainder = value.round(9).abs + variable = false + + PARTS.each do |part| + unless part == :seconds + part_in_seconds = PARTS_IN_SECONDS[part] + parts[part] = remainder.div(part_in_seconds) * remainder_sign + remainder %= part_in_seconds + + unless parts[part].zero? + variable ||= VARIABLE_PARTS.include?(part) + end + end + end unless value == 0 + + parts[:seconds] = remainder * remainder_sign + + new(value, parts, variable) + end + + private + def calculate_total_seconds(parts) + parts.inject(0) do |total, (part, value)| + total + value * PARTS_IN_SECONDS[part] + end + end + end + + def initialize(value, parts, variable = nil) # :nodoc: + @value, @parts = value, parts + @parts.reject! { |k, v| v.zero? } unless value == 0 + @parts.freeze + @variable = variable + + if @variable.nil? + @variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) } + end + end + + # Returns a copy of the parts hash that defines the duration + def parts + @parts.dup + end + + def coerce(other) # :nodoc: + case other + when Scalar + [other, self] + when Duration + [Scalar.new(other.value), self] + else + [Scalar.new(other), self] + end + end + + # Compares one Duration with another or a Numeric to this Duration. + # Numeric values are treated as seconds. + def <=>(other) + if Duration === other + value <=> other.value + elsif Numeric === other + value <=> other + end + end + + # Adds another Duration or a Numeric to this Duration. Numeric values + # are treated as seconds. + def +(other) + if Duration === other + parts = @parts.merge(other._parts) do |_key, value, other_value| + value + other_value + end + Duration.new(value + other.value, parts, @variable || other.variable?) + else + seconds = @parts.fetch(:seconds, 0) + other + Duration.new(value + other, @parts.merge(seconds: seconds), @variable) + end + end + + # Subtracts another Duration or a Numeric from this Duration. Numeric + # values are treated as seconds. + def -(other) + self + (-other) + end + + # Multiplies this Duration by a Numeric and returns a new Duration. + def *(other) + if Scalar === other || Duration === other + Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?) + elsif Numeric === other + Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable) + else + raise_type_error(other) + end + end + + # Divides this Duration by a Numeric and returns a new Duration. + def /(other) + if Scalar === other + Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable) + elsif Duration === other + value / other.value + elsif Numeric === other + Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable) + else + raise_type_error(other) + end + end + + # Returns the modulo of this Duration by another Duration or Numeric. + # Numeric values are treated as seconds. + def %(other) + if Duration === other || Scalar === other + Duration.build(value % other.value) + elsif Numeric === other + Duration.build(value % other) + else + raise_type_error(other) + end + end + + def -@ # :nodoc: + Duration.new(-value, @parts.transform_values(&:-@), @variable) + end + + def +@ # :nodoc: + self + end + + def is_a?(klass) # :nodoc: + Duration == klass || value.is_a?(klass) + end + alias :kind_of? :is_a? + + def instance_of?(klass) # :nodoc: + Duration == klass || value.instance_of?(klass) + end + + # Returns +true+ if +other+ is also a Duration instance with the + # same +value+, or if other == value. + def ==(other) + if Duration === other + other.value == value + else + other == value + end + end + + # Returns the amount of seconds a duration covers as a string. + # For more information check to_i method. + # + # 1.day.to_s # => "86400" + def to_s + @value.to_s + end + + # Returns the number of seconds that this Duration represents. + # + # 1.minute.to_i # => 60 + # 1.hour.to_i # => 3600 + # 1.day.to_i # => 86400 + # + # Note that this conversion makes some assumptions about the + # duration of some periods, e.g. months are always 1/12 of year + # and years are 365.2425 days: + # + # # equivalent to (1.year / 12).to_i + # 1.month.to_i # => 2629746 + # + # # equivalent to 365.2425.days.to_i + # 1.year.to_i # => 31556952 + # + # In such cases, Ruby's core + # Date[https://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and + # Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision + # date and time arithmetic. + def to_i + @value.to_i + end + alias :in_seconds :to_i + + # Returns the amount of minutes a duration covers as a float + # + # 1.day.in_minutes # => 1440.0 + def in_minutes + in_seconds / SECONDS_PER_MINUTE.to_f + end + + # Returns the amount of hours a duration covers as a float + # + # 1.day.in_hours # => 24.0 + def in_hours + in_seconds / SECONDS_PER_HOUR.to_f + end + + # Returns the amount of days a duration covers as a float + # + # 12.hours.in_days # => 0.5 + def in_days + in_seconds / SECONDS_PER_DAY.to_f + end + + # Returns the amount of weeks a duration covers as a float + # + # 2.months.in_weeks # => 8.696 + def in_weeks + in_seconds / SECONDS_PER_WEEK.to_f + end + + # Returns the amount of months a duration covers as a float + # + # 9.weeks.in_months # => 2.07 + def in_months + in_seconds / SECONDS_PER_MONTH.to_f + end + + # Returns the amount of years a duration covers as a float + # + # 30.days.in_years # => 0.082 + def in_years + in_seconds / SECONDS_PER_YEAR.to_f + end + + # Returns +true+ if +other+ is also a Duration instance, which has the + # same parts as this one. + def eql?(other) + Duration === other && other.value.eql?(value) + end + + def hash + @value.hash + end + + # Calculates a new Time or Date that is as far in the future + # as this Duration represents. + def since(time = ::Time.current) + sum(1, time) + end + alias :from_now :since + alias :after :since + + # Calculates a new Time or Date that is as far in the past + # as this Duration represents. + def ago(time = ::Time.current) + sum(-1, time) + end + alias :until :ago + alias :before :ago + + def inspect # :nodoc: + return "#{value} seconds" if @parts.empty? + + @parts. + sort_by { |unit, _ | PARTS.index(unit) }. + map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }. + to_sentence(locale: false) + end + + def as_json(options = nil) # :nodoc: + to_i + end + + def init_with(coder) # :nodoc: + initialize(coder["value"], coder["parts"]) + end + + def encode_with(coder) # :nodoc: + coder.map = { "value" => @value, "parts" => @parts } + end + + # Build ISO 8601 Duration string for this duration. + # The +precision+ parameter can be used to limit seconds' precision of duration. + def iso8601(precision: nil) + ISO8601Serializer.new(self, precision: precision).serialize + end + + def variable? # :nodoc: + @variable + end + + def _parts # :nodoc: + @parts + end + + private + def sum(sign, time = ::Time.current) + unless time.acts_like?(:time) || time.acts_like?(:date) + raise ::ArgumentError, "expected a time or date, got #{time.inspect}" + end + + if @parts.empty? + time.since(sign * value) + else + @parts.inject(time) do |t, (type, number)| + if type == :seconds + t.since(sign * number) + elsif type == :minutes + t.since(sign * number * 60) + elsif type == :hours + t.since(sign * number * 3600) + else + t.advance(type => sign * number) + end + end + end + end + + def respond_to_missing?(method, _) + value.respond_to?(method) + end + + def method_missing(method, *args, &block) + value.public_send(method, *args, &block) + end + + def raise_type_error(other) + raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration/iso8601_parser.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration/iso8601_parser.rb new file mode 100644 index 0000000000..839caab80d --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration/iso8601_parser.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require "strscan" + +module ActiveSupport + class Duration + # Parses a string formatted according to ISO 8601 Duration into the hash. + # + # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # + # This parser allows negative parts to be present in pattern. + class ISO8601Parser # :nodoc: + class ParsingError < ::ArgumentError; end + + PERIOD_OR_COMMA = /\.|,/ + PERIOD = "." + COMMA = "," + + SIGN_MARKER = /\A-|\+|/ + DATE_MARKER = /P/ + TIME_MARKER = /T/ + DATE_COMPONENT = /(-?\d+(?:[.,]\d+)?)(Y|M|D|W)/ + TIME_COMPONENT = /(-?\d+(?:[.,]\d+)?)(H|M|S)/ + + DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days } + TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds } + + DATE_COMPONENTS = [:years, :months, :days] + TIME_COMPONENTS = [:hours, :minutes, :seconds] + + attr_reader :parts, :scanner + attr_accessor :mode, :sign + + def initialize(string) + @scanner = StringScanner.new(string) + @parts = {} + @mode = :start + @sign = 1 + end + + def parse! + while !finished? + case mode + when :start + if scan(SIGN_MARKER) + self.sign = (scanner.matched == "-") ? -1 : 1 + self.mode = :sign + else + raise_parsing_error + end + + when :sign + if scan(DATE_MARKER) + self.mode = :date + else + raise_parsing_error + end + + when :date + if scan(TIME_MARKER) + self.mode = :time + elsif scan(DATE_COMPONENT) + parts[DATE_TO_PART[scanner[2]]] = number * sign + else + raise_parsing_error + end + + when :time + if scan(TIME_COMPONENT) + parts[TIME_TO_PART[scanner[2]]] = number * sign + else + raise_parsing_error + end + + end + end + + validate! + parts + end + + private + def finished? + scanner.eos? + end + + # Parses number which can be a float with either comma or period. + def number + PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i + end + + def scan(pattern) + scanner.scan(pattern) + end + + def raise_parsing_error(reason = nil) + raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip + end + + # Checks for various semantic errors as stated in ISO 8601 standard. + def validate! + raise_parsing_error("is empty duration") if parts.empty? + + # Mixing any of Y, M, D with W is invalid. + if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any? + raise_parsing_error("mixing weeks with other date parts not allowed") + end + + # Specifying an empty T part is invalid. + if mode == :time && (parts.keys & TIME_COMPONENTS).empty? + raise_parsing_error("time part marker is present but time part is empty") + end + + fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 } + unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last) + raise_parsing_error "(only last part can be fractional)" + end + + true + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration/iso8601_serializer.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration/iso8601_serializer.rb new file mode 100644 index 0000000000..9353c649b4 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/duration/iso8601_serializer.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" + +module ActiveSupport + class Duration + # Serializes duration to string according to ISO 8601 Duration format. + class ISO8601Serializer # :nodoc: + DATE_COMPONENTS = %i(years months days) + + def initialize(duration, precision: nil) + @duration = duration + @precision = precision + end + + # Builds and returns output string. + def serialize + parts = normalize + return "PT0S" if parts.empty? + + output = +"P" + output << "#{parts[:years]}Y" if parts.key?(:years) + output << "#{parts[:months]}M" if parts.key?(:months) + output << "#{parts[:days]}D" if parts.key?(:days) + output << "#{parts[:weeks]}W" if parts.key?(:weeks) + time = +"" + time << "#{parts[:hours]}H" if parts.key?(:hours) + time << "#{parts[:minutes]}M" if parts.key?(:minutes) + if parts.key?(:seconds) + time << "#{format_seconds(parts[:seconds])}S" + end + output << "T#{time}" unless time.empty? + output + end + + private + # Return pair of duration's parts and whole duration sign. + # Parts are summarized (as they can become repetitive due to addition, etc). + # Zero parts are removed as not significant. + # If all parts are negative it will negate all of them and return minus as a sign. + def normalize + parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p| + p[k] += v unless v.zero? + end + + # Convert weeks to days and remove weeks if mixed with date parts + if week_mixed_with_date?(parts) + parts[:days] += parts.delete(:weeks) * SECONDS_PER_WEEK / SECONDS_PER_DAY + end + + parts + end + + def week_mixed_with_date?(parts) + parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any? + end + + def format_seconds(seconds) + if @precision + sprintf("%0.0#{@precision}f", seconds) + else + seconds.to_s + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/encrypted_configuration.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/encrypted_configuration.rb new file mode 100644 index 0000000000..b22eb7a30f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/encrypted_configuration.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require "yaml" +require "active_support/encrypted_file" +require "active_support/ordered_options" +require "active_support/core_ext/object/inclusion" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # Provides convenience methods on top of EncryptedFile to access values stored + # as encrypted YAML. + # + # Values can be accessed via +Hash+ methods, such as +fetch+ and +dig+, or via + # dynamic accessor methods, similar to OrderedOptions. + # + # my_config = ActiveSupport::EncryptedConfiguration.new(...) + # my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456" + # + # my_config[:some_secret] + # # => 123 + # my_config.some_secret + # # => 123 + # my_config.dig(:some_namespace, :another_secret) + # # => 456 + # my_config.some_namespace.another_secret + # # => 456 + # my_config.fetch(:foo) + # # => KeyError + # my_config.foo! + # # => KeyError + # + class EncryptedConfiguration < EncryptedFile + delegate :[], :fetch, to: :config + delegate_missing_to :options + + def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:) + super content_path: config_path, key_path: key_path, + env_key: env_key, raise_if_missing_key: raise_if_missing_key + end + + # Reads the file and returns the decrypted content. See EncryptedFile#read. + def read + super + rescue ActiveSupport::EncryptedFile::MissingContentError + # Allow a config to be started without a file present + "" + end + + def write(contents) + deserialize(contents) + + super + end + + # Returns the decrypted content as a Hash with symbolized keys. + # + # my_config = ActiveSupport::EncryptedConfiguration.new(...) + # my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456" + # + # my_config.config + # # => { some_secret: 123, some_namespace: { another_secret: 789 } } + # + def config + @config ||= deserialize(read).deep_symbolize_keys + end + + private + def deep_transform(hash) + return hash unless hash.is_a?(Hash) + + h = ActiveSupport::InheritableOptions.new + hash.each do |k, v| + h[k] = deep_transform(v) + end + h + end + + def options + @options ||= deep_transform(config) + end + + def deserialize(config) + doc = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(config) : YAML.load(config) + doc.presence || {} + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/encrypted_file.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/encrypted_file.rb new file mode 100644 index 0000000000..d2c9e624cc --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/encrypted_file.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require "pathname" +require "tmpdir" +require "active_support/message_encryptor" + +module ActiveSupport + class EncryptedFile + class MissingContentError < RuntimeError + def initialize(content_path) + super "Missing encrypted content file in #{content_path}." + end + end + + class MissingKeyError < RuntimeError + def initialize(key_path:, env_key:) + super \ + "Missing encryption key to decrypt file with. " + + "Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']." + end + end + + class InvalidKeyLengthError < RuntimeError + def initialize + super "Encryption key must be exactly #{EncryptedFile.expected_key_length} characters." + end + end + + CIPHER = "aes-128-gcm" + + def self.generate_key + SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER)) + end + + def self.expected_key_length # :nodoc: + @expected_key_length ||= generate_key.length + end + + + attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key + + def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:) + @content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path } + @key_path = Pathname.new(key_path) + @env_key, @raise_if_missing_key = env_key, raise_if_missing_key + end + + # Returns the encryption key, first trying the environment variable + # specified by +env_key+, then trying the key file specified by +key_path+. + # If +raise_if_missing_key+ is true, raises MissingKeyError if the + # environment variable is not set and the key file does not exist. + def key + read_env_key || read_key_file || handle_missing_key + end + + # Reads the file and returns the decrypted content. + # + # Raises: + # - MissingKeyError if the key is missing and +raise_if_missing_key+ is true. + # - MissingContentError if the encrypted file does not exist or otherwise + # if the key is missing. + # - ActiveSupport::MessageEncryptor::InvalidMessage if the content cannot be + # decrypted or verified. + def read + if !key.nil? && content_path.exist? + decrypt content_path.binread + else + raise MissingContentError, content_path + end + end + + def write(contents) + IO.binwrite "#{content_path}.tmp", encrypt(contents) + FileUtils.mv "#{content_path}.tmp", content_path + end + + def change(&block) + writing read, &block + end + + + private + def writing(contents) + tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}" + tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file) + tmp_path.binwrite contents + + yield tmp_path + + updated_contents = tmp_path.binread + + write(updated_contents) if updated_contents != contents + ensure + FileUtils.rm(tmp_path) if tmp_path&.exist? + end + + + def encrypt(contents) + check_key_length + encryptor.encrypt_and_sign contents + end + + def decrypt(contents) + encryptor.decrypt_and_verify contents + end + + def encryptor + @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER) + end + + + def read_env_key + ENV[env_key].presence + end + + def read_key_file + return @key_file_contents if defined?(@key_file_contents) + @key_file_contents = (key_path.binread.strip if key_path.exist?) + end + + def handle_missing_key + raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key + end + + def check_key_length + raise InvalidKeyLengthError if key&.length != self.class.expected_key_length + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/environment_inquirer.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/environment_inquirer.rb new file mode 100644 index 0000000000..770cdddf1b --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/environment_inquirer.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "active_support/string_inquirer" + +module ActiveSupport + class EnvironmentInquirer < StringInquirer # :nodoc: + DEFAULT_ENVIRONMENTS = ["development", "test", "production"] + def initialize(env) + super(env) + + DEFAULT_ENVIRONMENTS.each do |default| + instance_variable_set :"@#{default}", env == default + end + end + + DEFAULT_ENVIRONMENTS.each do |env| + class_eval "def #{env}?; @#{env}; end" + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/error_reporter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/error_reporter.rb new file mode 100644 index 0000000000..8219a3ecf4 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/error_reporter.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +module ActiveSupport + # +ActiveSupport::ErrorReporter+ is a common interface for error reporting services. + # + # To rescue and report any unhandled error, you can use the +handle+ method: + # + # Rails.error.handle do + # do_something! + # end + # + # If an error is raised, it will be reported and swallowed. + # + # Alternatively if you want to report the error but not swallow it, you can use +record+ + # + # Rails.error.record do + # do_something! + # end + # + # Both methods can be restricted to only handle a specific exception class + # + # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") } + # + # You can also pass some extra context information that may be used by the error subscribers: + # + # Rails.error.handle(context: { section: "admin" }) do + # # ... + # end + # + # Additionally a +severity+ can be passed along to communicate how important the error report is. + # +severity+ can be one of +:error+, +:warning+, or +:info+. Handled errors default to the +:warning+ + # severity, and unhandled ones to +:error+. + # + # Both +handle+ and +record+ pass through the return value from the block. In the case of +handle+ + # rescuing an error, a fallback can be provided. The fallback must be a callable whose result will + # be returned when the block raises and is handled: + # + # user = Rails.error.handle(fallback: -> { User.anonymous }) do + # User.find_by(params) + # end + class ErrorReporter + SEVERITIES = %i(error warning info) + + attr_accessor :logger + + def initialize(*subscribers, logger: nil) + @subscribers = subscribers.flatten + @logger = logger + end + + # Report any unhandled exception, and swallow it. + # + # Rails.error.handle do + # 1 + '1' + # end + # + def handle(error_class = StandardError, severity: :warning, context: {}, fallback: nil) + yield + rescue error_class => error + report(error, handled: true, severity: severity, context: context) + fallback.call if fallback + end + + def record(error_class = StandardError, severity: :error, context: {}) + yield + rescue error_class => error + report(error, handled: false, severity: severity, context: context) + raise + end + + # Register a new error subscriber. The subscriber must respond to + # + # report(Exception, handled: Boolean, context: Hash) + # + # The +report+ method +should+ never raise an error. + def subscribe(subscriber) + unless subscriber.respond_to?(:report) + raise ArgumentError, "Error subscribers must respond to #report" + end + @subscribers << subscriber + end + + # Update the execution context that is accessible to error subscribers + # + # Rails.error.set_context(section: "checkout", user_id: @user.id) + # + # See +ActiveSupport::ExecutionContext.set+ + def set_context(...) + ActiveSupport::ExecutionContext.set(...) + end + + # When the block based +handle+ and +record+ methods are not suitable, you can directly use +report+ + # + # Rails.error.report(error, handled: true) + def report(error, handled:, severity: handled ? :warning : :error, context: {}) + unless SEVERITIES.include?(severity) + raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}" + end + + full_context = ActiveSupport::ExecutionContext.to_h.merge(context) + @subscribers.each do |subscriber| + subscriber.report(error, handled: handled, severity: severity, context: full_context) + rescue => subscriber_error + if logger + logger.fatal( + "Error subscriber raised an error: #{subscriber_error.message} (#{subscriber_error.class})\n" + + subscriber_error.backtrace.join("\n") + ) + else + raise + end + end + + nil + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/evented_file_update_checker.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/evented_file_update_checker.rb new file mode 100644 index 0000000000..42081ab184 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/evented_file_update_checker.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +require "set" +require "pathname" +require "concurrent/atomic/atomic_boolean" +require "listen" +require "active_support/fork_tracker" + +module ActiveSupport + # Allows you to "listen" to changes in a file system. + # The evented file updater does not hit disk when checking for updates. + # Instead, it uses platform-specific file system events to trigger a change + # in state. + # + # The file checker takes an array of files to watch or a hash specifying directories + # and file extensions to watch. It also takes a block that is called when + # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated + # is run and there have been changes to the file system. + # + # Example: + # + # checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" } + # checker.updated? + # # => false + # checker.execute_if_updated + # # => nil + # + # FileUtils.touch("/tmp/foo") + # + # checker.updated? + # # => true + # checker.execute_if_updated + # # => "changed" + # + class EventedFileUpdateChecker # :nodoc: all + def initialize(files, dirs = {}, &block) + unless block + raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker" + end + + @block = block + @core = Core.new(files, dirs) + ObjectSpace.define_finalizer(self, @core.finalizer) + end + + def inspect + "# error + error_reporter.report(error, handled: false) + raise + ensure + instance.complete! + end + end + + def self.perform # :nodoc: + instance = new + instance.run + begin + yield + ensure + instance.complete + end + end + + def self.error_reporter + @error_reporter ||= ActiveSupport::ErrorReporter.new + end + + def self.active_key # :nodoc: + @active_key ||= :"active_execution_wrapper_#{object_id}" + end + + def self.active? # :nodoc: + IsolatedExecutionState.key?(active_key) + end + + def run! # :nodoc: + IsolatedExecutionState[self.class.active_key] = self + run + end + + def run # :nodoc: + run_callbacks(:run) + end + + # Complete this in-flight execution. This method *must* be called + # exactly once on the result of any call to +run!+. + # + # Where possible, prefer +wrap+. + def complete! + complete + ensure + IsolatedExecutionState.delete(self.class.active_key) + end + + def complete # :nodoc: + run_callbacks(:complete) + end + + private + def hook_state + @_hook_state ||= {} + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/executor.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/executor.rb new file mode 100644 index 0000000000..ce391b07ec --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/executor.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "active_support/execution_wrapper" + +module ActiveSupport + class Executor < ExecutionWrapper + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/executor/test_helper.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/executor/test_helper.rb new file mode 100644 index 0000000000..97f489dc2e --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/executor/test_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ActiveSupport::Executor::TestHelper # :nodoc: + def run(...) + Rails.application.executor.perform { super } + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/file_update_checker.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/file_update_checker.rb similarity index 57% rename from app/server/ruby/vendor/activesupport/lib/active_support/file_update_checker.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/file_update_checker.rb index 78b627c286..9b665e7f19 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/file_update_checker.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/file_update_checker.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/calculations" + module ActiveSupport # FileUpdateChecker specifies the API used by Rails to watch files # and control reloading. The API depends on four methods: @@ -23,7 +27,7 @@ module ActiveSupport # I18n.reload! # end # - # ActionDispatch::Reloader.to_prepare do + # ActiveSupport::Reloader.to_prepare do # i18n_reloader.execute_if_updated # end class FileUpdateChecker @@ -35,7 +39,11 @@ class FileUpdateChecker # This method must also receive a block that will be called once a path # changes. The array of files and list of directories cannot be changed # after FileUpdateChecker has been initialized. - def initialize(files, dirs={}, &block) + def initialize(files, dirs = {}, &block) + unless block + raise ArgumentError, "A block is required to initialize a FileUpdateChecker" + end + @files = files.freeze @glob = compile_glob(dirs) @block = block @@ -81,6 +89,7 @@ def execute # Execute the block given if updated. def execute_if_updated if updated? + yield if block_given? execute true else @@ -89,49 +98,65 @@ def execute_if_updated end private + def watched + @watched || begin + all = @files.select { |f| File.exist?(f) } + all.concat(Dir[@glob]) if @glob + all + end + end - def watched - @watched || begin - all = @files.select { |f| File.exist?(f) } - all.concat(Dir[@glob]) if @glob - all + def updated_at(paths) + @updated_at || max_mtime(paths) || Time.at(0) end - end - def updated_at(paths) - @updated_at || max_mtime(paths) || Time.at(0) - end + # This method returns the maximum mtime of the files in +paths+, or +nil+ + # if the array is empty. + # + # Files with a mtime in the future are ignored. Such abnormal situation + # can happen for example if the user changes the clock by hand. It is + # healthy to consider this edge case because with mtimes in the future + # reloading is not triggered. + def max_mtime(paths) + time_now = Time.now + max_mtime = nil - # This method returns the maximum mtime of the files in +paths+, or +nil+ - # if the array is empty. - # - # Files with a mtime in the future are ignored. Such abnormal situation - # can happen for example if the user changes the clock by hand. It is - # healthy to consider this edge case because with mtimes in the future - # reloading is not triggered. - def max_mtime(paths) - time_now = Time.now - paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max - end + # Time comparisons are performed with #compare_without_coercion because + # AS redefines these operators in a way that is much slower and does not + # bring any benefit in this particular code. + # + # Read t1.compare_without_coercion(t2) < 0 as t1 < t2. + paths.each do |path| + mtime = File.mtime(path) - def compile_glob(hash) - hash.freeze # Freeze so changes aren't accidentally pushed - return if hash.empty? + next if time_now.compare_without_coercion(mtime) < 0 - globs = hash.map do |key, value| - "#{escape(key)}/**/*#{compile_ext(value)}" + if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0 + max_mtime = mtime + end + end + + max_mtime end - "{#{globs.join(",")}}" - end - def escape(key) - key.gsub(',','\,') - end + def compile_glob(hash) + hash.freeze # Freeze so changes aren't accidentally pushed + return if hash.empty? - def compile_ext(array) - array = Array(array) - return if array.empty? - ".{#{array.join(",")}}" - end + globs = hash.map do |key, value| + "#{escape(key)}/**/*#{compile_ext(value)}" + end + "{#{globs.join(",")}}" + end + + def escape(key) + key.gsub(",", '\,') + end + + def compile_ext(array) + array = Array(array) + return if array.empty? + ".{#{array.join(",")}}" + end end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/fork_tracker.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/fork_tracker.rb new file mode 100644 index 0000000000..bc1d32b550 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/fork_tracker.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module ActiveSupport + module ForkTracker # :nodoc: + module ModernCoreExt + def _fork + pid = super + if pid == 0 + ForkTracker.check! + end + pid + end + end + + module CoreExt + def fork(...) + if block_given? + super do + ForkTracker.check! + yield + end + else + unless pid = super + ForkTracker.check! + end + pid + end + end + end + + module CoreExtPrivate + include CoreExt + private :fork + end + + @pid = Process.pid + @callbacks = [] + + class << self + def check! + new_pid = Process.pid + if @pid != new_pid + @callbacks.each(&:call) + @pid = new_pid + end + end + + def hook! + if Process.respond_to?(:_fork) # Ruby 3.1+ + ::Process.singleton_class.prepend(ModernCoreExt) + elsif Process.respond_to?(:fork) + ::Object.prepend(CoreExtPrivate) if RUBY_VERSION < "3.0" + ::Kernel.prepend(CoreExtPrivate) + ::Kernel.singleton_class.prepend(CoreExt) + ::Process.singleton_class.prepend(CoreExt) + end + end + + def after_fork(&block) + @callbacks << block + block + end + + def unregister(callback) + @callbacks.delete(callback) + end + end + end +end + +ActiveSupport::ForkTracker.hook! diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/gem_version.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/gem_version.rb new file mode 100644 index 0000000000..e1d15415a1 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveSupport + # Returns the currently loaded version of Active Support as a Gem::Version. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 7 + MINOR = 0 + TINY = 6 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/gzip.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/gzip.rb similarity index 75% rename from app/server/ruby/vendor/activesupport/lib/active_support/gzip.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/gzip.rb index b837c879bb..7ffa6d90a2 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/gzip.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/gzip.rb @@ -1,5 +1,7 @@ -require 'zlib' -require 'stringio' +# frozen_string_literal: true + +require "zlib" +require "stringio" module ActiveSupport # A convenient wrapper for the zlib standard library that allows @@ -9,7 +11,7 @@ module ActiveSupport # # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00" # # ActiveSupport::Gzip.decompress(gzip) - # # => "compress me!" + # # => "compress me!" module Gzip class Stream < StringIO def initialize(*) @@ -21,11 +23,11 @@ def close; rewind; end # Decompresses a gzipped string. def self.decompress(source) - Zlib::GzipReader.new(StringIO.new(source)).read + Zlib::GzipReader.wrap(StringIO.new(source), &:read) end # Compresses a string using gzip. - def self.compress(source, level=Zlib::DEFAULT_COMPRESSION, strategy=Zlib::DEFAULT_STRATEGY) + def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY) output = Stream.new gz = Zlib::GzipWriter.new(output, level, strategy) gz.write(source) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/hash_with_indifferent_access.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/hash_with_indifferent_access.rb new file mode 100644 index 0000000000..aed884f84b --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/hash_with_indifferent_access.rb @@ -0,0 +1,425 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" + +module ActiveSupport + # Implements a hash where keys :foo and "foo" are considered + # to be the same. + # + # rgb = ActiveSupport::HashWithIndifferentAccess.new + # + # rgb[:black] = '#000000' + # rgb[:black] # => '#000000' + # rgb['black'] # => '#000000' + # + # rgb['white'] = '#FFFFFF' + # rgb[:white] # => '#FFFFFF' + # rgb['white'] # => '#FFFFFF' + # + # Internally symbols are mapped to strings when used as keys in the entire + # writing interface (calling []=, merge, etc). This + # mapping belongs to the public interface. For example, given: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1) + # + # You are guaranteed that the key is returned as a string: + # + # hash.keys # => ["a"] + # + # Technically other types of keys are accepted: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1) + # hash[0] = 0 + # hash # => {"a"=>1, 0=>0} + # + # but this class is intended for use cases where strings or symbols are the + # expected keys and it is convenient to understand both as the same. For + # example the +params+ hash in Ruby on Rails. + # + # Note that core extensions define Hash#with_indifferent_access: + # + # rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access + # + # which may be handy. + # + # To access this class outside of Rails, require the core extension with: + # + # require "active_support/core_ext/hash/indifferent_access" + # + # which will, in turn, require this file. + class HashWithIndifferentAccess < Hash + # Returns +true+ so that Array#extract_options! finds members of + # this class. + def extractable_options? + true + end + + def with_indifferent_access + dup + end + + def nested_under_indifferent_access + self + end + + def initialize(constructor = nil) + if constructor.respond_to?(:to_hash) + super() + update(constructor) + + hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash + self.default = hash.default if hash.default + self.default_proc = hash.default_proc if hash.default_proc + elsif constructor.nil? + super() + else + super(constructor) + end + end + + def self.[](*args) + new.merge!(Hash[*args]) + end + + alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) + alias_method :regular_update, :update unless method_defined?(:regular_update) + + # Assigns a new value to the hash: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:key] = 'value' + # + # This value can be later fetched using either +:key+ or 'key'. + def []=(key, value) + regular_writer(convert_key(key), convert_value(value, conversion: :assignment)) + end + + alias_method :store, :[]= + + # Updates the receiver in-place, merging in the hashes passed as arguments: + # + # hash_1 = ActiveSupport::HashWithIndifferentAccess.new + # hash_1[:key] = 'value' + # + # hash_2 = ActiveSupport::HashWithIndifferentAccess.new + # hash_2[:key] = 'New Value!' + # + # hash_1.update(hash_2) # => {"key"=>"New Value!"} + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash.update({ "a" => 1 }, { "b" => 2 }) # => { "a" => 1, "b" => 2 } + # + # The arguments can be either an + # ActiveSupport::HashWithIndifferentAccess or a regular +Hash+. + # In either case the merge respects the semantics of indifferent access. + # + # If the argument is a regular hash with keys +:key+ and "key" only one + # of the values end up in the receiver, but which one is unspecified. + # + # When given a block, the value for duplicated keys will be determined + # by the result of invoking the block with the duplicated key, the value + # in the receiver, and the value in +other_hash+. The rules for duplicated + # keys follow the semantics of indifferent access: + # + # hash_1[:key] = 10 + # hash_2['key'] = 12 + # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22} + def update(*other_hashes, &block) + if other_hashes.size == 1 + update_with_single_argument(other_hashes.first, block) + else + other_hashes.each do |other_hash| + update_with_single_argument(other_hash, block) + end + end + self + end + + alias_method :merge!, :update + + # Checks the hash for a key matching the argument passed in: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash['key'] = 'value' + # hash.key?(:key) # => true + # hash.key?('key') # => true + def key?(key) + super(convert_key(key)) + end + + alias_method :include?, :key? + alias_method :has_key?, :key? + alias_method :member?, :key? + + # Same as Hash#[] where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters['foo'] # => 1 + # counters[:foo] # => 1 + # counters[:zoo] # => nil + def [](key) + super(convert_key(key)) + end + + # Same as Hash#assoc where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.assoc('foo') # => ["foo", 1] + # counters.assoc(:foo) # => ["foo", 1] + # counters.assoc(:zoo) # => nil + def assoc(key) + super(convert_key(key)) + end + + # Same as Hash#fetch where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.fetch('foo') # => 1 + # counters.fetch(:bar, 0) # => 0 + # counters.fetch(:bar) { |key| 0 } # => 0 + # counters.fetch(:zoo) # => KeyError: key not found: "zoo" + def fetch(key, *extras) + super(convert_key(key), *extras) + end + + # Same as Hash#dig where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = { bar: 1 } + # + # counters.dig('foo', 'bar') # => 1 + # counters.dig(:foo, :bar) # => 1 + # counters.dig(:zoo) # => nil + def dig(*args) + args[0] = convert_key(args[0]) if args.size > 0 + super(*args) + end + + # Same as Hash#default where the key passed as argument can be + # either a string or a symbol: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(1) + # hash.default # => 1 + # + # hash = ActiveSupport::HashWithIndifferentAccess.new { |hash, key| key } + # hash.default # => nil + # hash.default('foo') # => 'foo' + # hash.default(:foo) # => 'foo' + def default(*args) + super(*args.map { |arg| convert_key(arg) }) + end + + # Returns an array of the values at the specified indices: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:a] = 'x' + # hash[:b] = 'y' + # hash.values_at('a', 'b') # => ["x", "y"] + def values_at(*keys) + super(*keys.map { |key| convert_key(key) }) + end + + # Returns an array of the values at the specified indices, but also + # raises an exception when one of the keys can't be found. + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:a] = 'x' + # hash[:b] = 'y' + # hash.fetch_values('a', 'b') # => ["x", "y"] + # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"] + # hash.fetch_values('a', 'c') # => KeyError: key not found: "c" + def fetch_values(*indices, &block) + super(*indices.map { |key| convert_key(key) }, &block) + end + + # Returns a shallow copy of the hash. + # + # hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } }) + # dup = hash.dup + # dup[:a][:c] = 'c' + # + # hash[:a][:c] # => "c" + # dup[:a][:c] # => "c" + def dup + self.class.new(self).tap do |new_hash| + set_defaults(new_hash) + end + end + + # This method has the same semantics of +update+, except it does not + # modify the receiver but rather returns a new hash with indifferent + # access with the result of the merge. + def merge(*hashes, &block) + dup.update(*hashes, &block) + end + + # Like +merge+ but the other way around: Merges the receiver into the + # argument and returns a new hash with indifferent access as result: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash['a'] = nil + # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1} + def reverse_merge(other_hash) + super(self.class.new(other_hash)) + end + alias_method :with_defaults, :reverse_merge + + # Same semantics as +reverse_merge+ but modifies the receiver in-place. + def reverse_merge!(other_hash) + super(self.class.new(other_hash)) + end + alias_method :with_defaults!, :reverse_merge! + + # Replaces the contents of this hash with other_hash. + # + # h = { "a" => 100, "b" => 200 } + # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400} + def replace(other_hash) + super(self.class.new(other_hash)) + end + + # Removes the specified key from the hash. + def delete(key) + super(convert_key(key)) + end + + # Returns a hash with indifferent access that includes everything except given keys. + # hash = { a: "x", b: "y", c: 10 }.with_indifferent_access + # hash.except(:a, "b") # => {c: 10}.with_indifferent_access + # hash # => { a: "x", b: "y", c: 10 }.with_indifferent_access + def except(*keys) + slice(*self.keys - keys.map { |key| convert_key(key) }) + end + alias_method :without, :except + + def stringify_keys!; self end + def deep_stringify_keys!; self end + def stringify_keys; dup end + def deep_stringify_keys; dup end + undef :symbolize_keys! + undef :deep_symbolize_keys! + def symbolize_keys; to_hash.symbolize_keys! end + alias_method :to_options, :symbolize_keys + def deep_symbolize_keys; to_hash.deep_symbolize_keys! end + def to_options!; self end + + def select(*args, &block) + return to_enum(:select) unless block_given? + dup.tap { |hash| hash.select!(*args, &block) } + end + + def reject(*args, &block) + return to_enum(:reject) unless block_given? + dup.tap { |hash| hash.reject!(*args, &block) } + end + + def transform_values(*args, &block) + return to_enum(:transform_values) unless block_given? + dup.tap { |hash| hash.transform_values!(*args, &block) } + end + + def transform_keys(*args, &block) + return to_enum(:transform_keys) unless block_given? + dup.tap { |hash| hash.transform_keys!(*args, &block) } + end + + def transform_keys! + return enum_for(:transform_keys!) { size } unless block_given? + keys.each do |key| + self[yield(key)] = delete(key) + end + self + end + + def slice(*keys) + keys.map! { |key| convert_key(key) } + self.class.new(super) + end + + def slice!(*keys) + keys.map! { |key| convert_key(key) } + super + end + + def compact + dup.tap(&:compact!) + end + + # Convert to a regular hash with string keys. + def to_hash + _new_hash = Hash.new + set_defaults(_new_hash) + + each do |key, value| + _new_hash[key] = convert_value(value, conversion: :to_hash) + end + _new_hash + end + + private + if Symbol.method_defined?(:name) + def convert_key(key) + key.kind_of?(Symbol) ? key.name : key + end + else + def convert_key(key) + key.kind_of?(Symbol) ? key.to_s : key + end + end + + def convert_value(value, conversion: nil) + if value.is_a? Hash + if conversion == :to_hash + value.to_hash + else + value.nested_under_indifferent_access + end + elsif value.is_a?(Array) + if conversion != :assignment || value.frozen? + value = value.dup + end + value.map! { |e| convert_value(e, conversion: conversion) } + else + value + end + end + + def set_defaults(target) + if default_proc + target.default_proc = default_proc.dup + else + target.default = default + end + end + + def update_with_single_argument(other_hash, block) + if other_hash.is_a? HashWithIndifferentAccess + regular_update(other_hash, &block) + else + other_hash.to_hash.each_pair do |key, value| + if block && key?(key) + value = block.call(convert_key(key), self[key], value) + end + regular_writer(convert_key(key), convert_value(value)) + end + end + end + end +end + +# :stopdoc: + +HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/html_safe_translation.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/html_safe_translation.rb new file mode 100644 index 0000000000..2d06a0d4a8 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/html_safe_translation.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActiveSupport + module HtmlSafeTranslation # :nodoc: + extend self + + def translate(key, **options) + if html_safe_translation_key?(key) + html_safe_options = html_escape_translation_options(options) + translation = I18n.translate(key, **html_safe_options) + html_safe_translation(translation) + else + I18n.translate(key, **options) + end + end + + private + def html_safe_translation_key?(key) + /(?:_|\b)html\z/.match?(key) + end + + def html_escape_translation_options(options) + options.each do |name, value| + unless i18n_option?(name) || (name == :count && value.is_a?(Numeric)) + options[name] = ERB::Util.html_escape(value.to_s) + end + end + end + + def i18n_option?(name) + (@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name) + end + + + def html_safe_translation(translation) + if translation.respond_to?(:map) + translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element } + else + translation.respond_to?(:html_safe) ? translation.html_safe : translation + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/i18n.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/i18n.rb new file mode 100644 index 0000000000..832a9fa002 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/i18n.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +begin + require "i18n" + require "i18n/backend/fallbacks" +rescue LoadError => e + $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install" + raise e +end +require "active_support/lazy_load_hooks" + +ActiveSupport.run_load_hooks(:i18n) +I18n.load_path << File.expand_path("locale/en.yml", __dir__) +I18n.load_path << File.expand_path("locale/en.rb", __dir__) diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/i18n_railtie.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/i18n_railtie.rb similarity index 56% rename from app/server/ruby/vendor/activesupport/lib/active_support/i18n_railtie.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/i18n_railtie.rb index 23cd6716e3..1e7185e950 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/i18n_railtie.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/i18n_railtie.rb @@ -1,15 +1,18 @@ +# frozen_string_literal: true + require "active_support" -require "active_support/file_update_checker" require "active_support/core_ext/array/wrap" +# :enddoc: + module I18n class Railtie < Rails::Railtie config.i18n = ActiveSupport::OrderedOptions.new config.i18n.railties_load_path = [] config.i18n.load_path = [] config.i18n.fallbacks = ActiveSupport::OrderedOptions.new - # Enforce I18n to check the available locales when setting a locale. - config.i18n.enforce_available_locales = true + + config.eager_load_namespaces << I18n # Set the i18n configuration after initialization since a lot of # configuration is still usually done in application initializers. @@ -23,8 +26,6 @@ class Railtie < Rails::Railtie I18n::Railtie.initialize_i18n(app) end - protected - @i18n_inited = false # Setup i18n configuration. @@ -36,17 +37,21 @@ def self.initialize_i18n(app) # Avoid issues with setting the default_locale by disabling available locales # check while configuring. enforce_available_locales = app.config.i18n.delete(:enforce_available_locales) - enforce_available_locales = I18n.enforce_available_locales unless I18n.enforce_available_locales.nil? + enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil? I18n.enforce_available_locales = false + reloadable_paths = [] app.config.i18n.each do |setting, value| case setting when :railties_load_path - app.config.i18n.load_path.unshift(*value) + reloadable_paths = value + app.config.i18n.load_path.unshift(*value.flat_map(&:existent)) when :load_path I18n.load_path += value + when :raise_on_missing_translations + forward_raise_on_missing_translations_config(app) else - I18n.send("#{setting}=", value) + I18n.public_send("#{setting}=", value) end end @@ -55,29 +60,47 @@ def self.initialize_i18n(app) # Restore available locales check so it will take place from now on. I18n.enforce_available_locales = enforce_available_locales - reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! } + directories = watched_dirs_with_extensions(reloadable_paths) + reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do + I18n.load_path.keep_if { |p| File.exist?(p) } + I18n.load_path |= reloadable_paths.flat_map(&:existent) + end + app.reloaders << reloader - ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated } + app.reloader.to_run do + reloader.execute_if_updated { require_unload_lock! } + end reloader.execute @i18n_inited = true end + def self.forward_raise_on_missing_translations_config(app) + ActiveSupport.on_load(:action_view) do + ActionView::Helpers::TranslationHelper.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations + end + + ActiveSupport.on_load(:action_controller) do + AbstractController::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations + end + end + def self.include_fallbacks_module - I18n.backend.class.send(:include, I18n::Backend::Fallbacks) + I18n.backend.class.include(I18n::Backend::Fallbacks) end def self.init_fallbacks(fallbacks) include_fallbacks_module - args = case fallbacks - when ActiveSupport::OrderedOptions - [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact - when Hash, Array - Array.wrap(fallbacks) - else # TrueClass - [] - end + args = \ + case fallbacks + when ActiveSupport::OrderedOptions + [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact + when Hash, Array + Array.wrap(fallbacks) + else # TrueClass + [I18n.default_locale] + end I18n.fallbacks = I18n::Locale::Fallbacks.new(*args) end @@ -92,5 +115,11 @@ def self.validate_fallbacks(fallbacks) raise "Unexpected fallback type #{fallbacks.inspect}" end end + + def self.watched_dirs_with_extensions(paths) + paths.each_with_object({}) do |path, result| + result[path.absolute_current] = path.extensions + end + end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/inflections.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflections.rb similarity index 84% rename from app/server/ruby/vendor/activesupport/lib/active_support/inflections.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflections.rb index 2ca1124e76..baf1cb3038 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/inflections.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflections.rb @@ -1,4 +1,6 @@ -require 'active_support/inflector/inflections' +# frozen_string_literal: true + +require "active_support/inflector/inflections" #-- # Defines the standard inflection rules. These are the starting point for @@ -8,8 +10,8 @@ #++ module ActiveSupport Inflector.inflections(:en) do |inflect| - inflect.plural(/$/, 's') - inflect.plural(/s$/i, 's') + inflect.plural(/$/, "s") + inflect.plural(/s$/i, "s") inflect.plural(/^(ax|test)is$/i, '\1es') inflect.plural(/(octop|vir)us$/i, '\1i') inflect.plural(/(octop|vir)i$/i, '\1i') @@ -18,7 +20,7 @@ module ActiveSupport inflect.plural(/(buffal|tomat)o$/i, '\1oes') inflect.plural(/([ti])um$/i, '\1a') inflect.plural(/([ti])a$/i, '\1a') - inflect.plural(/sis$/i, 'ses') + inflect.plural(/sis$/i, "ses") inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') inflect.plural(/(hive)$/i, '\1s') inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') @@ -30,7 +32,7 @@ module ActiveSupport inflect.plural(/^(oxen)$/i, '\1') inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/s$/i, '') + inflect.singular(/s$/i, "") inflect.singular(/(ss)$/i, '\1') inflect.singular(/(n)ews$/i, '\1ews') inflect.singular(/([ti])a$/i, '\1um') @@ -58,12 +60,12 @@ module ActiveSupport inflect.singular(/(quiz)zes$/i, '\1') inflect.singular(/(database)s$/i, '\1') - inflect.irregular('person', 'people') - inflect.irregular('man', 'men') - inflect.irregular('child', 'children') - inflect.irregular('sex', 'sexes') - inflect.irregular('move', 'moves') - inflect.irregular('zombie', 'zombies') + inflect.irregular("person", "people") + inflect.irregular("man", "men") + inflect.irregular("child", "children") + inflect.irregular("sex", "sexes") + inflect.irregular("move", "moves") + inflect.irregular("zombie", "zombies") inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police)) end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector.rb new file mode 100644 index 0000000000..d77f04c9c5 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# in case active_support/inflector is required without the rest of active_support +require "active_support/inflector/inflections" +require "active_support/inflector/transliterate" +require "active_support/inflector/methods" + +require "active_support/inflections" +require "active_support/core_ext/string/inflections" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/inflector/inflections.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/inflections.rb similarity index 74% rename from app/server/ruby/vendor/activesupport/lib/active_support/inflector/inflections.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/inflections.rb index eda0edff28..a9943a8838 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/inflector/inflections.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/inflections.rb @@ -1,6 +1,7 @@ -require 'thread_safe' -require 'active_support/core_ext/array/prepend_and_append' -require 'active_support/i18n' +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/i18n" module ActiveSupport module Inflector @@ -15,33 +16,76 @@ module Inflector # inflect.plural /^(ox)$/i, '\1\2en' # inflect.singular /^(ox)en/i, '\1' # - # inflect.irregular 'octopus', 'octopi' + # inflect.irregular 'cactus', 'cacti' # # inflect.uncountable 'equipment' # end # # New rules are added at the top. So in the example above, the irregular - # rule for octopus will now be the first of the pluralization and + # rule for cactus will now be the first of the pluralization and # singularization rules that is runs. This guarantees that your rules run # before any of the rules that may already have been loaded. class Inflections - @__instance__ = ThreadSafe::Cache.new + @__instance__ = Concurrent::Map.new + + class Uncountables < Array + def initialize + @regex_array = [] + super + end + + def delete(entry) + super entry + @regex_array.delete(to_regex(entry)) + end + + def <<(*word) + add(word) + end + + def add(words) + words = words.flatten.map(&:downcase) + concat(words) + @regex_array += words.map { |word| to_regex(word) } + self + end + + def uncountable?(str) + @regex_array.any? { |regex| regex.match? str } + end + + private + def to_regex(string) + /\b#{::Regexp.escape(string)}\Z/i + end + end def self.instance(locale = :en) @__instance__[locale] ||= new end - attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex + def self.instance_or_fallback(locale) + I18n.fallbacks[locale].each do |k| + return @__instance__[k] if @__instance__.key?(k) + end + instance(locale) + end + + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms + + attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc: def initialize - @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/ + @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {} + define_acronym_regex_patterns end # Private, for the test suite. def initialize_dup(orig) # :nodoc: - %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope| - instance_variable_set("@#{scope}", orig.send(scope).dup) + %w(plurals singulars uncountables humans acronyms).each do |scope| + instance_variable_set("@#{scope}", orig.public_send(scope).dup) end + define_acronym_regex_patterns end # Specifies a new acronym. An acronym must be specified as it will appear @@ -95,7 +139,7 @@ def initialize_dup(orig) # :nodoc: # camelize 'mcdonald' # => 'McDonald' def acronym(word) @acronyms[word.downcase] = word - @acronym_regex = /#{@acronyms.values.join("|")}/ + define_acronym_regex_patterns end # Specifies a new pluralization rule and its replacement. The rule can @@ -123,7 +167,7 @@ def singular(rule, replacement) # regular expressions. You simply pass the irregular in singular and # plural form. # - # irregular 'octopus', 'octopi' + # irregular 'cactus', 'cacti' # irregular 'person', 'people' def irregular(singular, plural) @uncountables.delete(singular) @@ -154,13 +198,13 @@ def irregular(singular, plural) end end - # Add uncountable words that shouldn't be attempted inflected. + # Specifies words that are uncountable and should not be inflected. # # uncountable 'money' # uncountable 'money', 'information' # uncountable %w( money information rice ) def uncountable(*words) - (@uncountables << words).flatten! + @uncountables.add(words) end # Specifies a humanized form of a string by a regular expression rule or @@ -178,18 +222,34 @@ def human(rule, replacement) # Clears the loaded inflections within a given scope (default is # :all). Give the scope as a symbol of the inflection type, the # options are: :plurals, :singulars, :uncountables, - # :humans. + # :humans, :acronyms. # # clear :all # clear :plurals def clear(scope = :all) case scope - when :all - @plurals, @singulars, @uncountables, @humans = [], [], [], [] - else - instance_variable_set "@#{scope}", [] + when :all + clear(:acronyms) + clear(:plurals) + clear(:singulars) + clear(:uncountables) + clear(:humans) + when :acronyms + @acronyms = {} + define_acronym_regex_patterns + when :uncountables + @uncountables = Uncountables.new + when :plurals, :singulars, :humans + instance_variable_set "@#{scope}", [] end end + + private + def define_acronym_regex_patterns + @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/ + @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/ + @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/ + end end # Yields a singleton instance of Inflector::Inflections so you can specify @@ -204,7 +264,7 @@ def inflections(locale = :en) if block_given? yield Inflections.instance(locale) else - Inflections.instance(locale) + Inflections.instance_or_fallback(locale) end end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/methods.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/methods.rb new file mode 100644 index 0000000000..ccc86a405a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/methods.rb @@ -0,0 +1,375 @@ +# frozen_string_literal: true + +require "active_support/inflections" +require "active_support/core_ext/object/blank" + +module ActiveSupport + # The Inflector transforms words from singular to plural, class names to table + # names, modularized class names to ones without, and class names to foreign + # keys. The default inflections for pluralization, singularization, and + # uncountable words are kept in inflections.rb. + # + # The Rails core team has stated patches for the inflections library will not + # be accepted in order to avoid breaking legacy applications which may be + # relying on errant inflections. If you discover an incorrect inflection and + # require it for your application or wish to define rules for languages other + # than English, please correct or add them yourself (explained below). + module Inflector + extend self + + # Returns the plural form of the word in the string. + # + # If passed an optional +locale+ parameter, the word will be + # pluralized using rules defined for that language. By default, + # this parameter is set to :en. + # + # pluralize('post') # => "posts" + # pluralize('octopus') # => "octopi" + # pluralize('sheep') # => "sheep" + # pluralize('words') # => "words" + # pluralize('CamelOctopus') # => "CamelOctopi" + # pluralize('ley', :es) # => "leyes" + def pluralize(word, locale = :en) + apply_inflections(word, inflections(locale).plurals, locale) + end + + # The reverse of #pluralize, returns the singular form of a word in a + # string. + # + # If passed an optional +locale+ parameter, the word will be + # singularized using rules defined for that language. By default, + # this parameter is set to :en. + # + # singularize('posts') # => "post" + # singularize('octopi') # => "octopus" + # singularize('sheep') # => "sheep" + # singularize('word') # => "word" + # singularize('CamelOctopi') # => "CamelOctopus" + # singularize('leyes', :es) # => "ley" + def singularize(word, locale = :en) + apply_inflections(word, inflections(locale).singulars, locale) + end + + # Converts strings to UpperCamelCase. + # If the +uppercase_first_letter+ parameter is set to false, then produces + # lowerCamelCase. + # + # Also converts '/' to '::' which is useful for converting + # paths to namespaces. + # + # camelize('active_model') # => "ActiveModel" + # camelize('active_model', false) # => "activeModel" + # camelize('active_model/errors') # => "ActiveModel::Errors" + # camelize('active_model/errors', false) # => "activeModel::Errors" + # + # As a rule of thumb you can think of +camelize+ as the inverse of + # #underscore, though there are cases where that does not hold: + # + # camelize(underscore('SSLError')) # => "SslError" + def camelize(term, uppercase_first_letter = true) + string = term.to_s + # String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent. + if !uppercase_first_letter || uppercase_first_letter == :lower + string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match } + else + string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match } + end + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do + word = $2 + substituted = inflections.acronyms[word] || word.capitalize! || word + $1 ? "::#{substituted}" : substituted + end + string + end + + # Makes an underscored, lowercase form from the expression in the string. + # + # Changes '::' to '/' to convert namespaces to paths. + # + # underscore('ActiveModel') # => "active_model" + # underscore('ActiveModel::Errors') # => "active_model/errors" + # + # As a rule of thumb you can think of +underscore+ as the inverse of + # #camelize, though there are cases where that does not hold: + # + # camelize(underscore('SSLError')) # => "SslError" + def underscore(camel_cased_word) + return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word) + word = camel_cased_word.to_s.gsub("::", "/") + word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" } + word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" } + word.tr!("-", "_") + word.downcase! + word + end + + # Tweaks an attribute name for display to end users. + # + # Specifically, performs these transformations: + # + # * Applies human inflection rules to the argument. + # * Deletes leading underscores, if any. + # * Removes an "_id" suffix if present. + # * Replaces underscores with spaces, if any. + # * Downcases all words except acronyms. + # * Capitalizes the first word. + # The capitalization of the first word can be turned off by setting the + # +:capitalize+ option to false (default is true). + # + # The trailing '_id' can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true (default is false). + # + # humanize('employee_salary') # => "Employee salary" + # humanize('author_id') # => "Author" + # humanize('author_id', capitalize: false) # => "author" + # humanize('_id') # => "Id" + # humanize('author_id', keep_id_suffix: true) # => "Author id" + # + # If "SSL" was defined to be an acronym: + # + # humanize('ssl_error') # => "SSL error" + # + def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false) + result = lower_case_and_underscored_word.to_s.dup + + inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + + result.tr!("_", " ") + result.lstrip! + if !keep_id_suffix && lower_case_and_underscored_word.end_with?("_id") + result.delete_suffix!(" id") + end + + result.gsub!(/([a-z\d]+)/i) do |match| + match.downcase! + inflections.acronyms[match] || match + end + + if capitalize + result.sub!(/\A\w/) do |match| + match.upcase! + match + end + end + + result + end + + # Converts just the first character to uppercase. + # + # upcase_first('what a Lovely Day') # => "What a Lovely Day" + # upcase_first('w') # => "W" + # upcase_first('') # => "" + def upcase_first(string) + string.length > 0 ? string[0].upcase.concat(string[1..-1]) : "" + end + + # Capitalizes all the words and replaces some characters in the string to + # create a nicer looking title. +titleize+ is meant for creating pretty + # output. It is not used in the Rails internals. + # + # The trailing '_id','Id'.. can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # titleize('man from the boondocks') # => "Man From The Boondocks" + # titleize('x-men: the last stand') # => "X Men: The Last Stand" + # titleize('TheManWithoutAPast') # => "The Man Without A Past" + # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" + # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id" + def titleize(word, keep_id_suffix: false) + humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(? "raw_scaled_scorers" + # tableize('ham_and_egg') # => "ham_and_eggs" + # tableize('fancyCategory') # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Creates a class name from a plural table name like Rails does for table + # names to models. Note that this returns a string and not a Class. (To + # convert to an actual class follow +classify+ with #constantize.) + # + # classify('ham_and_eggs') # => "HamAndEgg" + # classify('posts') # => "Post" + # + # Singular names are not handled correctly: + # + # classify('calculus') # => "Calculu" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ""))) + end + + # Replaces underscores with dashes in the string. + # + # dasherize('puni_puni') # => "puni-puni" + def dasherize(underscored_word) + underscored_word.tr("_", "-") + end + + # Removes the module part from the expression in the string. + # + # demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections" + # demodulize('Inflections') # => "Inflections" + # demodulize('::Inflections') # => "Inflections" + # demodulize('') # => "" + # + # See also #deconstantize. + def demodulize(path) + path = path.to_s + if i = path.rindex("::") + path[(i + 2)..-1] + else + path + end + end + + # Removes the rightmost segment from the constant expression in the string. + # + # deconstantize('Net::HTTP') # => "Net" + # deconstantize('::Net::HTTP') # => "::Net" + # deconstantize('String') # => "" + # deconstantize('::String') # => "" + # deconstantize('') # => "" + # + # See also #demodulize. + def deconstantize(path) + path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename + end + + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # foreign_key('Message') # => "message_id" + # foreign_key('Message', false) # => "messageid" + # foreign_key('Admin::Post') # => "post_id" + def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) + underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") + end + + # Tries to find a constant with the name specified in the argument string. + # + # constantize('Module') # => Module + # constantize('Foo::Bar') # => Foo::Bar + # + # The name is assumed to be the one of a top-level constant, no matter + # whether it starts with "::" or not. No lexical context is taken into + # account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # constantize('C') # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) + Object.const_get(camel_cased_word) + end + + # Tries to find a constant with the name specified in the argument string. + # + # safe_constantize('Module') # => Module + # safe_constantize('Foo::Bar') # => Foo::Bar + # + # The name is assumed to be the one of a top-level constant, no matter + # whether it starts with "::" or not. No lexical context is taken into + # account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # safe_constantize('C') # => 'outside', same as ::C + # end + # + # +nil+ is returned when the name is not in CamelCase or the constant (or + # part of it) is unknown. + # + # safe_constantize('blargle') # => nil + # safe_constantize('UnknownModule') # => nil + # safe_constantize('UnknownModule::Foo::Bar') # => nil + def safe_constantize(camel_cased_word) + constantize(camel_cased_word) + rescue NameError => e + raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) || + e.name.to_s == camel_cased_word.to_s) + rescue LoadError => e + message = e.respond_to?(:original_message) ? e.original_message : e.message + raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message) + end + + # Returns the suffix that should be added to a number to denote the position + # in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # ordinal(1) # => "st" + # ordinal(2) # => "nd" + # ordinal(1002) # => "nd" + # ordinal(1003) # => "rd" + # ordinal(-11) # => "th" + # ordinal(-1021) # => "st" + def ordinal(number) + I18n.translate("number.nth.ordinals", number: number) + end + + # Turns a number into an ordinal string used to denote the position in an + # ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # ordinalize(1) # => "1st" + # ordinalize(2) # => "2nd" + # ordinalize(1002) # => "1002nd" + # ordinalize(1003) # => "1003rd" + # ordinalize(-11) # => "-11th" + # ordinalize(-1021) # => "-1021st" + def ordinalize(number) + I18n.translate("number.nth.ordinalized", number: number) + end + + private + # Mounts a regular expression, returned as a string to ease interpolation, + # that will match part by part the given constant. + # + # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" + # const_regexp("::") # => "::" + def const_regexp(camel_cased_word) + parts = camel_cased_word.split("::") + + return Regexp.escape(camel_cased_word) if parts.blank? + + last = parts.pop + + parts.reverse!.inject(last) do |acc, part| + part.empty? ? acc : "#{part}(::#{acc})?" + end + end + + # Applies inflection rules for +singularize+ and +pluralize+. + # + # If passed an optional +locale+ parameter, the uncountables will be + # found for that locale. + # + # apply_inflections('post', inflections.plurals, :en) # => "posts" + # apply_inflections('posts', inflections.singulars, :en) # => "post" + def apply_inflections(word, rules, locale = :en) + result = word.to_s.dup + + if word.empty? || inflections(locale).uncountables.uncountable?(result) + result + else + rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + result + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/transliterate.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/transliterate.rb new file mode 100644 index 0000000000..e2a418926c --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/inflector/transliterate.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/multibyte" +require "active_support/i18n" + +module ActiveSupport + module Inflector + ALLOWED_ENCODINGS_FOR_TRANSLITERATE = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030].freeze + + # Replaces non-ASCII characters with an ASCII approximation, or if none + # exists, a replacement character which defaults to "?". + # + # transliterate('Ærøskøbing') + # # => "AEroskobing" + # + # Default approximations are provided for Western/Latin characters, + # e.g, "ø", "ñ", "é", "ß", etc. + # + # This method is I18n aware, so you can set up custom approximations for a + # locale. This can be useful, for example, to transliterate German's "ü" + # and "ö" to "ue" and "oe", or to add support for transliterating Russian + # to ASCII. + # + # In order to make your custom transliterations available, you must set + # them as the i18n.transliterate.rule i18n key: + # + # # Store the transliterations in locales/de.yml + # i18n: + # transliterate: + # rule: + # ü: "ue" + # ö: "oe" + # + # # Or set them using Ruby + # I18n.backend.store_translations(:de, i18n: { + # transliterate: { + # rule: { + # 'ü' => 'ue', + # 'ö' => 'oe' + # } + # } + # }) + # + # The value for i18n.transliterate.rule can be a simple Hash that + # maps characters to ASCII approximations as shown above, or, for more + # complex requirements, a Proc: + # + # I18n.backend.store_translations(:de, i18n: { + # transliterate: { + # rule: ->(string) { MyTransliterator.transliterate(string) } + # } + # }) + # + # Now you can have different transliterations for each locale: + # + # transliterate('Jürgen', locale: :en) + # # => "Jurgen" + # + # transliterate('Jürgen', locale: :de) + # # => "Juergen" + # + # Transliteration is restricted to UTF-8, US-ASCII, and GB18030 strings. + # Other encodings will raise an ArgumentError. + def transliterate(string, replacement = "?", locale: nil) + string = string.dup if string.frozen? + raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) + raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding) + + input_encoding = string.encoding + + # US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if + # US-ASCII is given. This way we can let tidy_bytes handle the string + # in the same way as we do for UTF-8 + string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII + + # GB18030 is Unicode compatible but is not a direct mapping so needs to be + # transcoded. Using invalid/undef :replace will result in loss of data in + # the event of invalid characters, but since tidy_bytes will replace + # invalid/undef with a "?" we're safe to do the same beforehand + string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030 + + transliterated = I18n.transliterate( + ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc), + replacement: replacement, + locale: locale + ) + + # Restore the string encoding of the input if it was not UTF-8. + # Apply invalid/undef :replace as tidy_bytes does + transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding + + transliterated + end + + # Replaces special characters in a string so that it may be used as part of + # a 'pretty' URL. + # + # parameterize("Donald E. Knuth") # => "donald-e-knuth" + # parameterize("^très|Jolie-- ") # => "tres-jolie" + # + # To use a custom separator, override the +separator+ argument. + # + # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" + # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie" + # + # To preserve the case of the characters in a string, use the +preserve_case+ argument. + # + # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" + # parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie" + # + # It preserves dashes and underscores unless they are used as separators: + # + # parameterize("^très|Jolie__ ") # => "tres-jolie__" + # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--" + # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--" + # + # If the optional parameter +locale+ is specified, + # the word will be parameterized as a word of that language. + # By default, this parameter is set to nil and it will use + # the configured I18n.locale. + def parameterize(string, separator: "-", preserve_case: false, locale: nil) + # Replace accented chars with their ASCII equivalents. + parameterized_string = transliterate(string, locale: locale) + + # Turn unwanted chars into the separator. + parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) + + unless separator.nil? || separator.empty? + if separator == "-" + re_duplicate_separator = /-{2,}/ + re_leading_trailing_separator = /^-|-$/i + else + re_sep = Regexp.escape(separator) + re_duplicate_separator = /#{re_sep}{2,}/ + re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i + end + # No more than one of the separator in a row. + parameterized_string.gsub!(re_duplicate_separator, separator) + # Remove leading/trailing separator. + parameterized_string.gsub!(re_leading_trailing_separator, "") + end + + parameterized_string.downcase! unless preserve_case + parameterized_string + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/isolated_execution_state.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/isolated_execution_state.rb new file mode 100644 index 0000000000..2b8b06f774 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/isolated_execution_state.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "fiber" + +module ActiveSupport + module IsolatedExecutionState # :nodoc: + @isolation_level = :thread + + Thread.attr_accessor :active_support_execution_state + Fiber.attr_accessor :active_support_execution_state + + class << self + attr_reader :isolation_level + + def isolation_level=(level) + unless %i(thread fiber).include?(level) + raise ArgumentError, "isolation_level must be `:thread` or `:fiber`, got: `#{level.inspect}`" + end + + if level != isolation_level + clear + singleton_class.alias_method(:current, "current_#{level}") + singleton_class.send(:private, :current) + @isolation_level = level + end + end + + def unique_id + self[:__id__] ||= Object.new + end + + def [](key) + current[key] + end + + def []=(key, value) + current[key] = value + end + + def key?(key) + current.key?(key) + end + + def delete(key) + current.delete(key) + end + + def clear + current.clear + end + + def share_with(other) + # Action Controller streaming spawns a new thread and copy thread locals. + # We do the same here for backward compatibility, but this is very much a hack + # and streaming should be rethought. + context = @isolation_level == :thread ? Thread.current : Fiber.current + context.active_support_execution_state = other.active_support_execution_state.dup + end + + private + def current_thread + Thread.current.active_support_execution_state ||= {} + end + + def current_fiber + Fiber.current.active_support_execution_state ||= {} + end + + alias_method :current, :current_thread + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json.rb new file mode 100644 index 0000000000..d7887175c0 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require "active_support/json/decoding" +require "active_support/json/encoding" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/json/decoding.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json/decoding.rb similarity index 54% rename from app/server/ruby/vendor/activesupport/lib/active_support/json/decoding.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json/decoding.rb index 8b5fc70dee..e40957ef3e 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/json/decoding.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json/decoding.rb @@ -1,6 +1,8 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/module/delegation' -require 'json' +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/delegation" +require "json" module ActiveSupport # Look for and parse json strings that look like ISO 8601 times. @@ -8,21 +10,16 @@ module ActiveSupport module JSON # matches YAML-formatted dates - DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/ - + DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\z/ + DATETIME_REGEX = /\A(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)\z/ + class << self # Parses a JSON string (JavaScript Object Notation) into a hash. - # See www.json.org for more info. + # See http://www.json.org for more info. # # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}") # => {"team" => "rails", "players" => "36"} - def decode(json, options = {}) - if options.present? - raise ArgumentError, "In Rails 4.1, ActiveSupport::JSON.decode no longer " \ - "accepts an options hash for MultiJSON. MultiJSON reached its end of life " \ - "and has been removed." - end - + def decode(json) data = ::JSON.parse(json, quirks_mode: true) if ActiveSupport.parse_json_times @@ -47,27 +44,32 @@ def parse_error end private - - def convert_dates_from(data) - case data - when nil - nil - when DATE_REGEX - begin - DateTime.parse(data) - rescue ArgumentError + def convert_dates_from(data) + case data + when nil + nil + when DATE_REGEX + begin + Date.parse(data) + rescue ArgumentError + data + end + when DATETIME_REGEX + begin + Time.zone.parse(data) + rescue ArgumentError + data + end + when Array + data.map! { |d| convert_dates_from(d) } + when Hash + data.transform_values! do |value| + convert_dates_from(value) + end + else data end - when Array - data.map! { |d| convert_dates_from(d) } - when Hash - data.each do |key, value| - data[key] = convert_dates_from(value) - end - else - data end - end end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/json/encoding.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json/encoding.rb similarity index 52% rename from app/server/ruby/vendor/activesupport/lib/active_support/json/encoding.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json/encoding.rb index f29d42276d..8e08b24959 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/json/encoding.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/json/encoding.rb @@ -1,19 +1,20 @@ -require 'active_support/core_ext/object/json' -require 'active_support/core_ext/module/delegation' +# frozen_string_literal: true + +require "active_support/core_ext/object/json" +require "active_support/core_ext/module/delegation" module ActiveSupport class << self delegate :use_standard_json_time_format, :use_standard_json_time_format=, :time_precision, :time_precision=, :escape_html_entities_in_json, :escape_html_entities_in_json=, - :encode_big_decimal_as_string, :encode_big_decimal_as_string=, :json_encoder, :json_encoder=, - :to => :'ActiveSupport::JSON::Encoding' + to: :'ActiveSupport::JSON::Encoding' end module JSON # Dumps objects in JSON (JavaScript Object Notation). - # See www.json.org for more info. + # See http://www.json.org for more info. # # ActiveSupport::JSON.encode({ team: 'rails', players: '36' }) # # => "{\"team\":\"rails\",\"players\":\"36\"}" @@ -21,8 +22,8 @@ def self.encode(value, options = nil) Encoding.json_encoder.new(options).encode(value) end - module Encoding #:nodoc: - class JSONGemEncoder #:nodoc: + module Encoding # :nodoc: + class JSONGemEncoder # :nodoc: attr_reader :options def initialize(options = nil) @@ -41,23 +42,31 @@ def encode(value) ESCAPED_CHARS = { "\u2028" => '\u2028', "\u2029" => '\u2029', - '>' => '\u003e', - '<' => '\u003c', - '&' => '\u0026', + ">" => '\u003e', + "<" => '\u003c', + "&" => '\u0026', } ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u # This class wraps all the strings we see and does the extra escaping - class EscapedString < String #:nodoc: + class EscapedString < String # :nodoc: def to_json(*) if Encoding.escape_html_entities_in_json - super.gsub ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS + s = super + s.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS + s else - super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS + s = super + s.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS + s end end + + def to_s + self + end end # Mark these as private so we don't leak encoding-specific constructs @@ -65,7 +74,8 @@ def to_json(*) :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString # Convert an object into a "JSON-ready" representation composed of - # primitives like Hash, Array, String, Numeric, and true/false/nil. + # primitives like Hash, Array, String, Numeric, + # and +true+/+false+/+nil+. # Recursively calls #as_json to the object to recursively build a # fully JSON-ready object. # @@ -81,9 +91,13 @@ def jsonify(value) when String EscapedString.new(value) when Numeric, NilClass, TrueClass, FalseClass - value + value.as_json when Hash - Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }] + result = {} + value.each do |k, v| + result[jsonify(k)] = jsonify(v) + end + result when Array value.map { |v| jsonify(v) } else @@ -113,54 +127,6 @@ class << self # Sets the encoder used by Rails to encode Ruby objects into JSON strings # in +Object#to_json+ and +ActiveSupport::JSON.encode+. attr_accessor :json_encoder - - def encode_big_decimal_as_string=(as_string) - message = \ - "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \ - "the new encoder will always encode them as strings.\n\n" \ - "You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \ - "your configuration file. If you have been setting this to true, you can safely remove it from " \ - "your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \ - "Gemfile in order to restore this functionality." - - raise NotImplementedError, message - end - - def encode_big_decimal_as_string - message = \ - "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \ - "the new encoder will always encode them as strings.\n\n" \ - "You are seeing this error because you are trying to check the value of the related configuration, " \ - "'active_support.encode_big_decimal_as_string'. If your application depends on this option, you should " \ - "add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \ - "In the future, it will be removed from Rails, so you should stop checking its value." - - ActiveSupport::Deprecation.warn message - - true - end - - # Deprecate CircularReferenceError - def const_missing(name) - if name == :CircularReferenceError - message = "The JSON encoder in Rails 4.1 no longer offers protection from circular references. " \ - "You are seeing this warning because you are rescuing from (or otherwise referencing) " \ - "ActiveSupport::Encoding::CircularReferenceError. In the future, this error will be " \ - "removed from Rails. You should remove these rescue blocks from your code and ensure " \ - "that your data structures are free of circular references so they can be properly " \ - "serialized into JSON.\n\n" \ - "For example, the following Hash contains a circular reference to itself:\n" \ - " h = {}\n" \ - " h['circular'] = h\n" \ - "In this case, calling h.to_json would not work properly." - - ActiveSupport::Deprecation.warn message - - SystemStackError - else - super - end - end end self.use_standard_json_time_format = true diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/key_generator.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/key_generator.rb new file mode 100644 index 0000000000..660bffddc0 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/key_generator.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "openssl" + +module ActiveSupport + # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2. + # It can be used to derive a number of keys for various purposes from a given secret. + # This lets Rails applications have a single secure secret, but avoid reusing that + # key in multiple incompatible contexts. + class KeyGenerator + class << self + def hash_digest_class=(klass) + if klass.kind_of?(Class) && klass < OpenSSL::Digest + @hash_digest_class = klass + else + raise ArgumentError, "#{klass} is expected to be an OpenSSL::Digest subclass" + end + end + + def hash_digest_class + @hash_digest_class ||= OpenSSL::Digest::SHA1 + end + end + + def initialize(secret, options = {}) + @secret = secret + # The default iterations are higher than required for our key derivation uses + # on the off chance someone uses this for password storage + @iterations = options[:iterations] || 2**16 + # Also allow configuration here so people can use this to build a rotation + # scheme when switching the digest class. + @hash_digest_class = options[:hash_digest_class] || self.class.hash_digest_class + end + + # Returns a derived key suitable for use. The default +key_size+ is chosen + # to be compatible with the default settings of ActiveSupport::MessageVerifier. + # i.e. OpenSSL::Digest::SHA1#block_length + def generate_key(salt, key_size = 64) + OpenSSL::PKCS5.pbkdf2_hmac(@secret, salt, @iterations, key_size, @hash_digest_class.new) + end + end + + # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid + # re-executing the key generation process when it's called using the same +salt+ and + # +key_size+. + class CachingKeyGenerator + def initialize(key_generator) + @key_generator = key_generator + @cache_keys = Concurrent::Map.new + end + + # Returns a derived key suitable for use. + def generate_key(*args) + @cache_keys[args.join("|")] ||= @key_generator.generate_key(*args) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/lazy_load_hooks.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/lazy_load_hooks.rb new file mode 100644 index 0000000000..a253c2bbda --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/lazy_load_hooks.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +module ActiveSupport + # LazyLoadHooks allows Rails to lazily load a lot of components and thus + # making the app boot faster. Because of this feature now there is no need to + # require ActiveRecord::Base at boot time purely to apply + # configuration. Instead a hook is registered that applies configuration once + # ActiveRecord::Base is loaded. Here ActiveRecord::Base is + # used as example but this feature can be applied elsewhere too. + # + # Here is an example where on_load method is called to register a hook. + # + # initializer 'active_record.initialize_timezone' do + # ActiveSupport.on_load(:active_record) do + # self.time_zone_aware_attributes = true + # self.default_timezone = :utc + # end + # end + # + # When the entirety of +ActiveRecord::Base+ has been + # evaluated then run_load_hooks is invoked. The very last line of + # +ActiveRecord::Base+ is: + # + # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) + # + # run_load_hooks will then execute all the hooks that were registered + # with the on_load method. In the case of the above example, it will + # execute the block of code that is in the +initializer+. + # + # Registering a hook that has already run results in that hook executing + # immediately. This allows hooks to be nested for code that relies on + # multiple lazily loaded components: + # + # initializer "action_text.renderer" do + # ActiveSupport.on_load(:action_controller_base) do + # ActiveSupport.on_load(:action_text_content) do + # self.default_renderer = Class.new(ActionController::Base).renderer + # end + # end + # end + module LazyLoadHooks + def self.extended(base) # :nodoc: + base.class_eval do + @load_hooks = Hash.new { |h, k| h[k] = [] } + @loaded = Hash.new { |h, k| h[k] = [] } + @run_once = Hash.new { |h, k| h[k] = [] } + end + end + + # Declares a block that will be executed when a Rails component is fully + # loaded. If the component has already loaded, the block is executed + # immediately. + # + # Options: + # + # * :yield - Yields the object that run_load_hooks to +block+. + # * :run_once - Given +block+ will run only once. + def on_load(name, options = {}, &block) + @loaded[name].each do |base| + execute_hook(name, base, options, block) + end + + @load_hooks[name] << [block, options] + end + + # Executes all blocks registered to +name+ via on_load, using +base+ as the + # evaluation context. + # + # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) + # + # In the case of the above example, it will execute all hooks registered + # for +:active_record+ within the class +ActiveRecord::Base+. + def run_load_hooks(name, base = Object) + @loaded[name] << base + @load_hooks[name].each do |hook, options| + execute_hook(name, base, options, hook) + end + end + + private + def with_execution_control(name, block, once) + unless @run_once[name].include?(block) + @run_once[name] << block if once + + yield + end + end + + def execute_hook(name, base, options, block) + with_execution_control(name, block, options[:run_once]) do + if options[:yield] + block.call(base) + else + if base.is_a?(Module) + base.class_eval(&block) + else + base.instance_eval(&block) + end + end + end + end + end + + extend LazyLoadHooks +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/locale/en.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/locale/en.rb new file mode 100644 index 0000000000..29eb9dec0c --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/locale/en.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +{ + en: { + number: { + nth: { + ordinals: lambda do |_key, options| + number = options[:number] + case number + when 1; "st" + when 2; "nd" + when 3; "rd" + when 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; "th" + else + num_modulo = number.to_i.abs % 100 + num_modulo %= 10 if num_modulo > 13 + case num_modulo + when 1; "st" + when 2; "nd" + when 3; "rd" + else "th" + end + end + end, + + ordinalized: lambda do |_key, options| + number = options[:number] + "#{number}#{ActiveSupport::Inflector.ordinal(number)}" + end + } + } + } +} diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/locale/en.yml b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/locale/en.yml similarity index 91% rename from app/server/ruby/vendor/activesupport/lib/active_support/locale/en.yml rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/locale/en.yml index a4563ace8f..0453883a79 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/locale/en.yml +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/locale/en.yml @@ -44,22 +44,25 @@ en: delimiter: "," # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) precision: 3 + # Determine how rounding is performed (see BigDecimal::mode) + round_mode: default # If set to true, precision will mean the number of significant digits instead # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) significant: false - # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2) + # If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2) strip_insignificant_zeros: false # Used in NumberHelper.number_to_currency() currency: format: - # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + # Where is the currency sign? %u is the currency unit, %n is the number (default: $5.00) format: "%u%n" unit: "$" - # These five are to override number.format and are optional + # These six are to override number.format and are optional separator: "." delimiter: "," precision: 2 + # round_mode: significant: false strip_insignificant_zeros: false @@ -87,10 +90,11 @@ en: # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human() human: format: - # These five are to override number.format and are optional + # These six are to override number.format and are optional # separator: delimiter: "" precision: 3 + # round_mode: significant: true strip_insignificant_zeros: true # Used in number_to_human_size() @@ -106,6 +110,8 @@ en: mb: "MB" gb: "GB" tb: "TB" + pb: "PB" + eb: "EB" # Used in NumberHelper.number_to_human() decimal_units: format: "%n %u" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/log_subscriber.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/log_subscriber.rb similarity index 50% rename from app/server/ruby/vendor/activesupport/lib/active_support/log_subscriber.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/log_subscriber.rb index e95dc5a866..ddeaff0867 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/log_subscriber.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/log_subscriber.rb @@ -1,9 +1,11 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/class/attribute' -require 'active_support/subscriber' +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/class/attribute" +require "active_support/subscriber" module ActiveSupport - # ActiveSupport::LogSubscriber is an object set to consume + # ActiveSupport::LogSubscriber is an object set to consume # ActiveSupport::Notifications with the sole purpose of logging them. # The log subscriber dispatches notifications to a registered object based # on its given namespace. @@ -14,7 +16,7 @@ module ActiveSupport # module ActiveRecord # class LogSubscriber < ActiveSupport::LogSubscriber # def sql(event) - # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" + # info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" # end # end # end @@ -27,13 +29,39 @@ module ActiveSupport # subscriber, the line above should be called after your # ActiveRecord::LogSubscriber definition. # - # After configured, whenever a "sql.active_record" notification is published, - # it will properly dispatch the event (ActiveSupport::Notifications::Event) to - # the sql method. + # A logger also needs to be set with ActiveRecord::LogSubscriber.logger=. + # This is assigned automatically in a Rails environment. + # + # After configured, whenever a "sql.active_record" notification is published, + # it will properly dispatch the event + # (ActiveSupport::Notifications::Event) to the sql method. + # + # Being an ActiveSupport::Notifications consumer, + # ActiveSupport::LogSubscriber exposes a simple interface to check if + # instrumented code raises an exception. It is common to log a different + # message in case of an error, and this can be achieved by extending + # the previous example: + # + # module ActiveRecord + # class LogSubscriber < ActiveSupport::LogSubscriber + # def sql(event) + # exception = event.payload[:exception] + # + # if exception + # exception_object = event.payload[:exception_object] + # + # error "[ERROR] #{event.payload[:name]}: #{exception.join(', ')} " \ + # "(#{exception_object.backtrace.first})" + # else + # # standard logger code + # end + # end + # end + # end # # Log subscriber also has some helpers to deal with logging and automatically - # flushes all logs when the request finishes (via action_dispatch.callback - # notification) in a Rails environment. + # flushes all logs when the request finishes + # (via action_dispatch.callback notification) in a Rails environment. class LogSubscriber < Subscriber # Embed in a String to clear all previous ANSI sequences. CLEAR = "\e[0m" @@ -49,8 +77,7 @@ class LogSubscriber < Subscriber CYAN = "\e[36m" WHITE = "\e[37m" - mattr_accessor :colorize_logging - self.colorize_logging = true + mattr_accessor :colorize_logging, default: true class << self def logger @@ -69,6 +96,11 @@ def log_subscribers def flush_all! logger.flush if logger.respond_to?(:flush) end + + private + def fetch_public_methods(subscriber, inherit_all) + subscriber.public_methods(inherit_all) - LogSubscriber.public_instance_methods(true) + end end def logger @@ -81,12 +113,17 @@ def start(name, id, payload) def finish(name, id, payload) super if logger - rescue Exception => e - logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" + rescue => e + log_exception(name, e) end - protected + def publish_event(event) + super if logger + rescue => e + log_exception(event.name, e) + end + private %w(info debug warn error fatal unknown).each do |level| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{level}(progname = nil, &block) @@ -95,15 +132,21 @@ def #{level}(progname = nil, &block) METHOD end - # Set color by using a string or one of the defined constants. If a third + # Set color by using a symbol or one of the defined constants. If a third # option is set to +true+, it also adds bold to the string. This is based # on the Highline implementation and will automatically append CLEAR to the # end of the returned String. - def color(text, color, bold=false) + def color(text, color, bold = false) # :doc: return text unless colorize_logging color = self.class.const_get(color.upcase) if color.is_a?(Symbol) bold = bold ? BOLD : "" "#{bold}#{color}#{text}#{CLEAR}" end + + def log_exception(name, e) + if logger + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" + end + end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/log_subscriber/test_helper.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/log_subscriber/test_helper.rb similarity index 83% rename from app/server/ruby/vendor/activesupport/lib/active_support/log_subscriber/test_helper.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/log_subscriber/test_helper.rb index 75f353f62c..b528a7fc10 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/log_subscriber/test_helper.rb @@ -1,6 +1,8 @@ -require 'active_support/log_subscriber' -require 'active_support/logger' -require 'active_support/notifications' +# frozen_string_literal: true + +require "active_support/log_subscriber" +require "active_support/logger" +require "active_support/notifications" module ActiveSupport class LogSubscriber @@ -10,7 +12,7 @@ class LogSubscriber # class SyncLogSubscriberTest < ActiveSupport::TestCase # include ActiveSupport::LogSubscriber::TestHelper # - # def setup + # setup do # ActiveRecord::LogSubscriber.attach_to(:active_record) # end # @@ -25,15 +27,15 @@ class LogSubscriber # # All you need to do is to ensure that your log subscriber is added to # Rails::Subscriber, as in the second line of the code above. The test - # helpers are responsible for setting up the queue, subscriptions and + # helpers are responsible for setting up the queue and subscriptions, and # turning colors in logs off. # # The messages are available in the @logger instance, which is a logger with # limited powers (it actually does not send anything to your output), and # you can collect them doing @logger.logged(level), where level is the level - # used in logging, like info, debug, warn and so on. + # used in logging, like info, debug, warn, and so on. module TestHelper - def setup + def setup # :nodoc: @logger = MockLogger.new @notifier = ActiveSupport::Notifications::Fanout.new @@ -44,7 +46,7 @@ def setup ActiveSupport::Notifications.notifier = @notifier end - def teardown + def teardown # :nodoc: set_logger(nil) ActiveSupport::Notifications.notifier = @old_notifier end @@ -58,15 +60,15 @@ class MockLogger def initialize(level = DEBUG) @flush_count = 0 @level = level - @logged = Hash.new { |h,k| h[k] = [] } + @logged = Hash.new { |h, k| h[k] = [] } end def method_missing(level, message = nil) - if block_given? - @logged[level] << yield - else - @logged[level] << message - end + if block_given? + @logged[level] << yield + else + @logged[level] << message + end end def logged(level) diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger.rb new file mode 100644 index 0000000000..1e241c13ac --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require "active_support/logger_silence" +require "active_support/logger_thread_safe_level" +require "logger" + +module ActiveSupport + class Logger < ::Logger + include LoggerSilence + + # Returns true if the logger destination matches one of the sources + # + # logger = Logger.new(STDOUT) + # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT) + # # => true + def self.logger_outputs_to?(logger, *sources) + logdev = logger.instance_variable_get(:@logdev) + logger_source = logdev.dev if logdev.respond_to?(:dev) + sources.any? { |source| source == logger_source } + end + + # Broadcasts logs to multiple loggers. + def self.broadcast(logger) # :nodoc: + Module.new do + define_method(:add) do |*args, &block| + logger.add(*args, &block) + super(*args, &block) + end + + define_method(:<<) do |x| + logger << x + super(x) + end + + define_method(:close) do + logger.close + super() + end + + define_method(:progname=) do |name| + logger.progname = name + super(name) + end + + define_method(:formatter=) do |formatter| + logger.formatter = formatter + super(formatter) + end + + define_method(:level=) do |level| + logger.level = level + super(level) + end + + define_method(:local_level=) do |level| + logger.local_level = level if logger.respond_to?(:local_level=) + super(level) if respond_to?(:local_level=) + end + + define_method(:silence) do |level = Logger::ERROR, &block| + if logger.respond_to?(:silence) + logger.silence(level) do + if defined?(super) + super(level, &block) + else + block.call(self) + end + end + else + if defined?(super) + super(level, &block) + else + block.call(self) + end + end + end + end + end + + def initialize(*args, **kwargs) + super + @formatter = SimpleFormatter.new + end + + # Simple formatter which only displays the message. + class SimpleFormatter < ::Logger::Formatter + # This method is invoked when a log event occurs + def call(severity, timestamp, progname, msg) + "#{String === msg ? msg : msg.inspect}\n" + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger_silence.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger_silence.rb new file mode 100644 index 0000000000..8567eff403 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger_silence.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/logger_thread_safe_level" + +module ActiveSupport + module LoggerSilence + extend ActiveSupport::Concern + + included do + cattr_accessor :silencer, default: true + include ActiveSupport::LoggerThreadSafeLevel + end + + # Silences the logger for the duration of the block. + def silence(severity = Logger::ERROR) + silencer ? log_at(severity) { yield self } : yield(self) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger_thread_safe_level.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger_thread_safe_level.rb new file mode 100644 index 0000000000..042f484f82 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/logger_thread_safe_level.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/module/attribute_accessors" +require "concurrent" +require "fiber" + +module ActiveSupport + module LoggerThreadSafeLevel # :nodoc: + extend ActiveSupport::Concern + + Logger::Severity.constants.each do |severity| + class_eval(<<-EOT, __FILE__, __LINE__ + 1) + def #{severity.downcase}? # def debug? + Logger::#{severity} >= level # DEBUG >= level + end # end + EOT + end + + def local_level + IsolatedExecutionState[:logger_thread_safe_level] + end + + def local_level=(level) + case level + when Integer + when Symbol + level = Logger::Severity.const_get(level.to_s.upcase) + when nil + else + raise ArgumentError, "Invalid log level: #{level.inspect}" + end + IsolatedExecutionState[:logger_thread_safe_level] = level + end + + def level + local_level || super + end + + # Change the thread-local level for the duration of the given block. + def log_at(level) + old_local_level, self.local_level = local_level, level + yield + ensure + self.local_level = old_local_level + end + + # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+. + # FIXME: Remove when the minimum Ruby version supports overriding Logger#level. + def add(severity, message = nil, progname = nil, &block) # :nodoc: + severity ||= UNKNOWN + progname ||= @progname + + return true if @logdev.nil? || severity < level + + if message.nil? + if block_given? + message = yield + else + message = progname + progname = @progname + end + end + + @logdev.write \ + format_message(format_severity(severity), Time.now, progname, message) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/message_encryptor.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/message_encryptor.rb new file mode 100644 index 0000000000..6528d1e93a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/message_encryptor.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true + +require "openssl" +require "base64" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/message_verifier" +require "active_support/messages/metadata" + +module ActiveSupport + # MessageEncryptor is a simple way to encrypt values which get stored + # somewhere you don't trust. + # + # The cipher text and initialization vector are base64 encoded and returned + # to you. + # + # This can be used in situations similar to the MessageVerifier, but + # where you don't want users to be able to determine the value of the payload. + # + # len = ActiveSupport::MessageEncryptor.key_len + # salt = SecureRandom.random_bytes(len) + # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..." + # crypt = ActiveSupport::MessageEncryptor.new(key) # => # + # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." + # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" + # + # The +decrypt_and_verify+ method will raise an + # ActiveSupport::MessageEncryptor::InvalidMessage exception if the data + # provided cannot be decrypted or verified. + # + # crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = crypt.encrypt_and_sign("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair" + # crypt.decrypt_and_verify(token, purpose: :shipping) # => nil + # crypt.decrypt_and_verify(token) # => nil + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = crypt.encrypt_and_sign("the conversation is lively") + # crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil + # crypt.decrypt_and_verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # crypt.encrypt_and_sign(parcel, expires_in: 1.month) + # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned up to the expire time. + # Thereafter, verifying returns +nil+. + # + # === Rotating keys + # + # MessageEncryptor also supports rotating out old configurations by falling + # back to a stack of encryptors. Call +rotate+ to build and add an encryptor + # so +decrypt_and_verify+ will also try the fallback. + # + # By default any rotated encryptors use the values of the primary + # encryptor unless specified otherwise. + # + # You'd give your encryptor the new defaults: + # + # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + # + # Then gradually rotate the old values out by adding them as fallbacks. Any message + # generated with the old values will then work until the rotation is removed. + # + # crypt.rotate old_secret # Fallback to an old secret instead of @secret. + # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm. + # + # Though if both the secret and the cipher was changed at the same time, + # the above should be combined into: + # + # crypt.rotate old_secret, cipher: "aes-256-cbc" + class MessageEncryptor + prepend Messages::Rotator::Encryptor + + cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false + + class << self + def default_cipher # :nodoc: + if use_authenticated_message_encryption + "aes-256-gcm" + else + "aes-256-cbc" + end + end + end + + module NullSerializer # :nodoc: + def self.load(value) + value + end + + def self.dump(value) + value + end + end + + module NullVerifier # :nodoc: + def self.verify(value) + value + end + + def self.generate(value) + value + end + end + + class InvalidMessage < StandardError; end + OpenSSLCipherError = OpenSSL::Cipher::CipherError + + # Initialize a new MessageEncryptor. +secret+ must be at least as long as + # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256 + # bits. If you are using a user-entered secret, you can generate a suitable + # key by using ActiveSupport::KeyGenerator or a similar key + # derivation function. + # + # First additional parameter is used as the signature key for MessageVerifier. + # This allows you to specify keys to encrypt and sign data. + # + # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret') + # + # Options: + # * :cipher - Cipher to use. Can be any cipher returned by + # OpenSSL::Cipher.ciphers. Default is 'aes-256-gcm'. + # * :digest - String of digest to use for signing. Default is + # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'. + # * :serializer - Object serializer to use. Default is +Marshal+. + def initialize(secret, sign_secret = nil, cipher: nil, digest: nil, serializer: nil) + @secret = secret + @sign_secret = sign_secret + @cipher = cipher || self.class.default_cipher + @digest = digest || "SHA1" unless aead_mode? + @verifier = resolve_verifier + @serializer = serializer || Marshal + end + + # Encrypt and sign a message. We need to sign the message in order to avoid + # padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/. + def encrypt_and_sign(value, expires_at: nil, expires_in: nil, purpose: nil) + verifier.generate(_encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose)) + end + + # Decrypt and verify a message. We need to verify the message in order to + # avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/. + def decrypt_and_verify(data, purpose: nil, **) + _decrypt(verifier.verify(data), purpose) + end + + # Given a cipher, returns the key length of the cipher to help generate the key of desired size + def self.key_len(cipher = default_cipher) + OpenSSL::Cipher.new(cipher).key_len + end + + private + def _encrypt(value, **metadata_options) + cipher = new_cipher + cipher.encrypt + cipher.key = @secret + + # Rely on OpenSSL for the initialization vector + iv = cipher.random_iv + cipher.auth_data = "" if aead_mode? + + encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), **metadata_options)) + encrypted_data << cipher.final + + blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" + blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode? + blob + end + + def _decrypt(encrypted_message, purpose) + cipher = new_cipher + encrypted_data, iv, auth_tag = encrypted_message.split("--").map { |v| ::Base64.strict_decode64(v) } + + # Currently the OpenSSL bindings do not raise an error if auth_tag is + # truncated, which would allow an attacker to easily forge it. See + # https://github.com/ruby/openssl/issues/63 + raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16) + + cipher.decrypt + cipher.key = @secret + cipher.iv = iv + if aead_mode? + cipher.auth_tag = auth_tag + cipher.auth_data = "" + end + + decrypted_data = cipher.update(encrypted_data) + decrypted_data << cipher.final + + message = Messages::Metadata.verify(decrypted_data, purpose) + @serializer.load(message) if message + rescue OpenSSLCipherError, TypeError, ArgumentError + raise InvalidMessage + end + + def new_cipher + OpenSSL::Cipher.new(@cipher) + end + + attr_reader :verifier + + def aead_mode? + @aead_mode ||= new_cipher.authenticated? + end + + def resolve_verifier + if aead_mode? + NullVerifier + else + MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/message_verifier.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/message_verifier.rb new file mode 100644 index 0000000000..c224bdc277 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/message_verifier.rb @@ -0,0 +1,237 @@ +# frozen_string_literal: true + +require "openssl" +require "base64" +require "active_support/core_ext/object/blank" +require "active_support/security_utils" +require "active_support/messages/metadata" +require "active_support/messages/rotator" + +module ActiveSupport + # +MessageVerifier+ makes it easy to generate and verify messages which are + # signed to prevent tampering. + # + # This is useful for cases like remember-me tokens and auto-unsubscribe links + # where the session store isn't suitable or available. + # + # Remember Me: + # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now]) + # + # In the authentication filter: + # + # id, time = @verifier.verify(cookies[:remember_me]) + # if Time.now < time + # self.current_user = User.find(id) + # end + # + # By default it uses Marshal to serialize the message. If you want to use + # another serialization method, you can set the serializer in the options + # hash upon initialization: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML) + # + # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default. + # If you want to use a different hash algorithm, you can change it by providing + # +:digest+ key as an option while initializing the verifier: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = @verifier.generate("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # @verifier.verified(token, purpose: :login) # => "this is the chair" + # @verifier.verified(token, purpose: :shipping) # => nil + # @verifier.verified(token) # => nil + # + # @verifier.verify(token, purpose: :login) # => "this is the chair" + # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = @verifier.generate("the conversation is lively") + # @verifier.verified(token, purpose: :scare_tactics) # => nil + # @verifier.verified(token) # => "the conversation is lively" + # + # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # @verifier.generate("parcel", expires_in: 1.month) + # @verifier.generate("doowad", expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned up to the expire time. + # Thereafter, the +verified+ method returns +nil+ while +verify+ raises + # ActiveSupport::MessageVerifier::InvalidSignature. + # + # === Rotating keys + # + # MessageVerifier also supports rotating out old configurations by falling + # back to a stack of verifiers. Call +rotate+ to build and add a verifier so + # either +verified+ or +verify+ will also try verifying with the fallback. + # + # By default any rotated verifiers use the values of the primary + # verifier unless specified otherwise. + # + # You'd give your verifier the new defaults: + # + # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON) + # + # Then gradually rotate the old values out by adding them as fallbacks. Any message + # generated with the old values will then work until the rotation is removed. + # + # verifier.rotate old_secret # Fallback to an old secret instead of @secret. + # verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512. + # verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON. + # + # Though the above would most likely be combined into one rotation: + # + # verifier.rotate old_secret, digest: "SHA256", serializer: Marshal + class MessageVerifier + prepend Messages::Rotator::Verifier + + class InvalidSignature < StandardError; end + + SEPARATOR = "--" # :nodoc: + SEPARATOR_LENGTH = SEPARATOR.length # :nodoc: + + def initialize(secret, digest: nil, serializer: nil) + raise ArgumentError, "Secret should not be nil." unless secret + @secret = secret + @digest = digest&.to_s || "SHA1" + @serializer = serializer || Marshal + end + + # Checks if a signed message could have been generated by signing an object + # with the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private message' + # verifier.valid_message?(signed_message) # => true + # + # tampered_message = signed_message.chop # editing the message invalidates the signature + # verifier.valid_message?(tampered_message) # => false + def valid_message?(signed_message) + data, digest = get_data_and_digest_from(signed_message) + digest_matches_data?(digest, data) + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # + # signed_message = verifier.generate 'a private message' + # verifier.verified(signed_message) # => 'a private message' + # + # Returns +nil+ if the message was not signed with the same secret. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # other_verifier.verified(signed_message) # => nil + # + # Returns +nil+ if the message is not Base64-encoded. + # + # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d" + # verifier.verified(invalid_message) # => nil + # + # Raises any error raised while decoding the signed message. + # + # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff" + # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format + def verified(signed_message, purpose: nil, **) + data, digest = get_data_and_digest_from(signed_message) + if digest_matches_data?(digest, data) + begin + message = Messages::Metadata.verify(decode(data), purpose) + @serializer.load(message) if message + rescue ArgumentError => argument_error + return if argument_error.message.include?("invalid base64") + raise + end + end + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private message' + # + # verifier.verify(signed_message) # => 'a private message' + # + # Raises +InvalidSignature+ if the message was not signed with the same + # secret or was not Base64-encoded. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature + def verify(*args, **options) + verified(*args, **options) || raise(InvalidSignature) + end + + # Generates a signed message for the provided value. + # + # The message is signed with the +MessageVerifier+'s secret. + # Returns Base64-encoded message joined with the generated signature. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772" + def generate(value, expires_at: nil, expires_in: nil, purpose: nil) + data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose)) + "#{data}#{SEPARATOR}#{generate_digest(data)}" + end + + private + def encode(data) + ::Base64.strict_encode64(data) + end + + def decode(data) + ::Base64.strict_decode64(data) + end + + def generate_digest(data) + OpenSSL::HMAC.hexdigest(@digest, @secret, data) + end + + def digest_length_in_hex + # In hexadecimal (AKA base16) it takes 4 bits to represent a character, + # hence we multiply the digest's length (in bytes) by 8 to get it in + # bits and divide by 4 to get its number of characters it hex. Well, 8 + # divided by 4 is 2. + @digest_length_in_hex ||= OpenSSL::Digest.new(@digest).digest_length * 2 + end + + def separator_index_for(signed_message) + index = signed_message.length - digest_length_in_hex - SEPARATOR_LENGTH + return if index.negative? || signed_message[index, SEPARATOR_LENGTH] != SEPARATOR + + index + end + + def get_data_and_digest_from(signed_message) + return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.empty? + + separator_index = separator_index_for(signed_message) + return if separator_index.nil? + + data = signed_message[0...separator_index] + digest = signed_message[separator_index + SEPARATOR_LENGTH..-1] + + [data, digest] + end + + def digest_matches_data?(digest, data) + data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/metadata.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/metadata.rb new file mode 100644 index 0000000000..4719d8e7a4 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/metadata.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "time" + +module ActiveSupport + module Messages # :nodoc: + class Metadata # :nodoc: + def initialize(message, expires_at = nil, purpose = nil) + @message, @purpose = message, purpose + @expires_at = expires_at.is_a?(String) ? parse_expires_at(expires_at) : expires_at + end + + def as_json(options = {}) + { _rails: { message: @message, exp: @expires_at, pur: @purpose } } + end + + class << self + def wrap(message, expires_at: nil, expires_in: nil, purpose: nil) + if expires_at || expires_in || purpose + JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose) + else + message + end + end + + def verify(message, purpose) + extract_metadata(message).verify(purpose) + end + + private + def pick_expiry(expires_at, expires_in) + if expires_at + expires_at.utc.iso8601(3) + elsif expires_in + Time.now.utc.advance(seconds: expires_in).iso8601(3) + end + end + + def extract_metadata(message) + data = JSON.decode(message) rescue nil + + if data.is_a?(Hash) && data.key?("_rails") + new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"]) + else + new(message) + end + end + + def encode(message) + ::Base64.strict_encode64(message) + end + + def decode(message) + ::Base64.strict_decode64(message) + end + end + + def verify(purpose) + @message if match?(purpose) && fresh? + end + + private + def match?(purpose) + @purpose.to_s == purpose.to_s + end + + def fresh? + @expires_at.nil? || Time.now.utc < @expires_at + end + + def parse_expires_at(expires_at) + if ActiveSupport.use_standard_json_time_format + Time.iso8601(expires_at) + else + Time.parse(expires_at) + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/rotation_configuration.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/rotation_configuration.rb new file mode 100644 index 0000000000..eef05fe317 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/rotation_configuration.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + class RotationConfiguration # :nodoc: + attr_reader :signed, :encrypted + + def initialize + @signed, @encrypted = [], [] + end + + def rotate(kind, *args, **options) + args << options unless options.empty? + case kind + when :signed + @signed << args + when :encrypted + @encrypted << args + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/rotator.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/rotator.rb new file mode 100644 index 0000000000..b19e1851e9 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/messages/rotator.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + module Rotator # :nodoc: + def initialize(*secrets, on_rotation: nil, **options) + super(*secrets, **options) + + @options = options + @rotations = [] + @on_rotation = on_rotation + end + + def rotate(*secrets, **options) + @rotations << build_rotation(*secrets, @options.merge(options)) + end + + module Encryptor + include Rotator + + def decrypt_and_verify(*args, on_rotation: @on_rotation, **options) + super + rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature + run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, **options) } || raise + end + + private + def build_rotation(secret = @secret, sign_secret = @sign_secret, options) + self.class.new(secret, sign_secret, **options) + end + end + + module Verifier + include Rotator + + def verified(*args, on_rotation: @on_rotation, **options) + super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, **options) } + end + + private + def build_rotation(secret = @secret, options) + self.class.new(secret, **options) + end + end + + private + def run_rotations(on_rotation) + @rotations.find do |rotation| + if message = yield(rotation) rescue next + on_rotation&.call + return message + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/multibyte.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte.rb similarity index 75% rename from app/server/ruby/vendor/activesupport/lib/active_support/multibyte.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte.rb index ffebd9a60b..03663506a0 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/multibyte.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte.rb @@ -1,7 +1,9 @@ -module ActiveSupport #:nodoc: +# frozen_string_literal: true + +module ActiveSupport # :nodoc: module Multibyte - autoload :Chars, 'active_support/multibyte/chars' - autoload :Unicode, 'active_support/multibyte/unicode' + autoload :Chars, "active_support/multibyte/chars" + autoload :Unicode, "active_support/multibyte/unicode" # The proxy class returned when calling mb_chars. You can use this accessor # to configure your own proxy class so you can support other encodings. See diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/multibyte/chars.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte/chars.rb similarity index 56% rename from app/server/ruby/vendor/activesupport/lib/active_support/multibyte/chars.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte/chars.rb index 3c0cf9f137..79dda27499 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/multibyte/chars.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte/chars.rb @@ -1,11 +1,12 @@ -# encoding: utf-8 -require 'active_support/json' -require 'active_support/core_ext/string/access' -require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/module/delegation' - -module ActiveSupport #:nodoc: - module Multibyte #:nodoc: +# frozen_string_literal: true + +require "active_support/json" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/module/delegation" + +module ActiveSupport # :nodoc: + module Multibyte # :nodoc: # Chars enables you to work transparently with UTF-8 encoding in the Ruby # String class without having extensive knowledge about the encoding. A # Chars object accepts a string upon initialization and proxies String @@ -16,7 +17,8 @@ module Multibyte #:nodoc: # through the +mb_chars+ method. Methods which would normally return a # String object now return a Chars object so methods can be chained. # - # 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string" + # 'The Perfect String '.mb_chars.downcase.strip + # # => # # # Chars objects are perfectly interchangeable with String objects as long as # no explicit class checks are made. If certain methods do explicitly check @@ -46,7 +48,7 @@ class Chars alias to_s wrapped_string alias to_str wrapped_string - delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string + delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string # Creates a new Chars instance by wrapping _string_. def initialize(string) @@ -57,7 +59,7 @@ def initialize(string) # Forward all undefined methods to the wrapped string. def method_missing(method, *args, &block) result = @wrapped_string.__send__(method, *args, &block) - if method.to_s =~ /!$/ + if method.end_with?("!") self if result else result.kind_of?(String) ? chars(result) : result @@ -71,12 +73,6 @@ def respond_to_missing?(method, include_private) @wrapped_string.respond_to?(method, include_private) end - # Returns +true+ when the proxy class can handle the string. Returns - # +false+ otherwise. - def self.consumes?(string) - string.encoding == Encoding::UTF_8 - end - # Works just like String#split, with the exception that the items # in the resulting list are Chars instances instead of String. This makes # chaining methods easier. @@ -86,17 +82,27 @@ def split(*args) @wrapped_string.split(*args).map { |i| self.class.new(i) } end - # Works like like String#slice!, but returns an instance of - # Chars, or nil if the string was not modified. + # Works like String#slice!, but returns an instance of + # Chars, or +nil+ if the string was not modified. The string will not be + # modified if the range given is out of bounds + # + # string = 'Welcome' + # string.mb_chars.slice!(3) # => # + # string # => 'Welome' + # string.mb_chars.slice!(0..3) # => # + # string # => 'me' def slice!(*args) - chars(@wrapped_string.slice!(*args)) + string_sliced = @wrapped_string.slice!(*args) + if string_sliced + chars(string_sliced) + end end # Reverses all characters in the string. # # 'Café'.mb_chars.reverse.to_s # => 'éfaC' def reverse - chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*')) + chars(@wrapped_string.grapheme_clusters.reverse.join) end # Limits the byte size of the string to a number of bytes without breaking @@ -105,79 +111,40 @@ def reverse # # 'こんにちは'.mb_chars.limit(7).to_s # => "こん" def limit(limit) - slice(0...translate_offset(limit)) - end - - # Converts characters in the string to uppercase. - # - # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?" - def upcase - chars Unicode.upcase(@wrapped_string) - end - - # Converts characters in the string to lowercase. - # - # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum" - def downcase - chars Unicode.downcase(@wrapped_string) - end - - # Converts characters in the string to the opposite case. - # - # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN" - def swapcase - chars Unicode.swapcase(@wrapped_string) - end - - # Converts the first character to uppercase and the remainder to lowercase. - # - # 'über'.mb_chars.capitalize.to_s # => "Über" - def capitalize - (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase + chars(@wrapped_string.truncate_bytes(limit, omission: nil)) end # Capitalizes the first letter of every word, when possible. # - # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró" - # "日本語".mb_chars.titleize # => "日本語" + # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró" + # "日本語".mb_chars.titleize.to_s # => "日本語" def titleize - chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)}) + chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase }) end alias_method :titlecase, :titleize - # Returns the KC normalization of the string by default. NFKC is - # considered the best normalization form for passing strings to databases - # and validations. - # - # * form - The form you want to normalize in. Should be one of the following: - # :c, :kc, :d, or :kd. Default is - # ActiveSupport::Multibyte::Unicode.default_normalization_form - def normalize(form = nil) - chars(Unicode.normalize(@wrapped_string, form)) - end - # Performs canonical decomposition on all the characters. # - # 'é'.length # => 2 - # 'é'.mb_chars.decompose.to_s.length # => 3 + # 'é'.length # => 1 + # 'é'.mb_chars.decompose.to_s.length # => 2 def decompose - chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*')) + chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*")) end # Performs composition on all the characters. # - # 'é'.length # => 3 - # 'é'.mb_chars.compose.to_s.length # => 2 + # 'é'.length # => 1 + # 'é'.mb_chars.compose.to_s.length # => 1 def compose - chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*')) + chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*")) end # Returns the number of grapheme clusters in the string. # # 'क्षि'.mb_chars.length # => 4 - # 'क्षि'.mb_chars.grapheme_length # => 3 + # 'क्षि'.mb_chars.grapheme_length # => 2 def grapheme_length - Unicode.unpack_graphemes(@wrapped_string).length + @wrapped_string.grapheme_clusters.length end # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent @@ -189,32 +156,19 @@ def tidy_bytes(force = false) chars(Unicode.tidy_bytes(@wrapped_string, force)) end - def as_json(options = nil) #:nodoc: + def as_json(options = nil) # :nodoc: to_s.as_json(options) end - %w(capitalize downcase reverse tidy_bytes upcase).each do |method| + %w(reverse tidy_bytes).each do |method| define_method("#{method}!") do |*args| - @wrapped_string = send(method, *args).to_s + @wrapped_string = public_send(method, *args).to_s self end end - protected - - def translate_offset(byte_offset) #:nodoc: - return nil if byte_offset.nil? - return 0 if @wrapped_string == '' - - begin - @wrapped_string.byteslice(0...byte_offset).unpack('U*').length - rescue ArgumentError - byte_offset -= 1 - retry - end - end - - def chars(string) #:nodoc: + private + def chars(string) self.class.new(string) end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte/unicode.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte/unicode.rb new file mode 100644 index 0000000000..1c3e98b9ea --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/multibyte/unicode.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveSupport + module Multibyte + module Unicode + extend self + + # The Unicode version that is supported by the implementation + UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"] + + # Decompose composed characters to the decomposed form. + def decompose(type, codepoints) + if type == :compatibility + codepoints.pack("U*").unicode_normalize(:nfkd).codepoints + else + codepoints.pack("U*").unicode_normalize(:nfd).codepoints + end + end + + # Compose decomposed characters to the composed form. + def compose(codepoints) + codepoints.pack("U*").unicode_normalize(:nfc).codepoints + end + + # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. + if !defined?(Rubinius) + # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent + # resulting in a valid UTF-8 string. + # + # Passing +true+ will forcibly tidy all bytes, assuming that the string's + # encoding is entirely CP1252 or ISO-8859-1. + def tidy_bytes(string, force = false) + return string if string.empty? || string.ascii_only? + return recode_windows1252_chars(string) if force + string.scrub { |bad| recode_windows1252_chars(bad) } + end + else + def tidy_bytes(string, force = false) + return string if string.empty? + return recode_windows1252_chars(string) if force + + # We can't transcode to the same format, so we choose a nearly-identical encoding. + # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to + # CP1252 when we get errors. The final string will be 'converted' back to UTF-8 + # before returning. + reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE) + + source = string.dup + out = "".force_encoding(Encoding::UTF_16LE) + + loop do + reader.primitive_convert(source, out) + _, _, _, error_bytes, _ = reader.primitive_errinfo + break if error_bytes.nil? + out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end + + reader.finish + + out.encode!(Encoding::UTF_8) + end + end + + private + def recode_windows1252_chars(string) + string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/notifications.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications.rb similarity index 50% rename from app/server/ruby/vendor/activesupport/lib/active_support/notifications.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications.rb index 7a96c66626..7190ab5b46 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/notifications.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications.rb @@ -1,9 +1,10 @@ -require 'active_support/notifications/instrumenter' -require 'active_support/notifications/fanout' -require 'active_support/per_thread_registry' +# frozen_string_literal: true + +require "active_support/notifications/instrumenter" +require "active_support/notifications/fanout" module ActiveSupport - # = Notifications + # = \Notifications # # ActiveSupport::Notifications provides an instrumentation API for # Ruby. @@ -13,10 +14,10 @@ module ActiveSupport # To instrument an event you just need to do: # # ActiveSupport::Notifications.instrument('render', extra: :information) do - # render text: 'Foo' + # render plain: 'Foo' # end # - # That executes the block first and notifies all subscribers once done. + # That first executes the block and then notifies all subscribers once done. # # In the example above +render+ is the name of the event, and the rest is called # the _payload_. The payload is a mechanism that allows instrumenters to pass @@ -32,10 +33,23 @@ module ActiveSupport # name # => String, name of the event (such as 'render' from above) # start # => Time, when the instrumented block started execution # finish # => Time, when the instrumented block ended execution - # id # => String, unique ID for this notification + # id # => String, unique ID for the instrumenter that fired the event # payload # => Hash, the payload # end # + # Here, the +start+ and +finish+ values represent wall-clock time. If you are + # concerned about accuracy, you can register a monotonic subscriber. + # + # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload| + # name # => String, name of the event (such as 'render' from above) + # start # => Monotonic time, when the instrumented block started execution + # finish # => Monotonic time, when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # The +start+ and +finish+ values above represent monotonic time. + # # For instance, let's store all "render" events in an array: # # events = [] @@ -48,7 +62,7 @@ module ActiveSupport # The block is saved and will be called whenever someone instruments "render": # # ActiveSupport::Notifications.instrument('render', extra: :information) do - # render text: 'Foo' + # render plain: 'Foo' # end # # event = events.first @@ -57,20 +71,25 @@ module ActiveSupport # event.payload # => { extra: :information } # # The block in the subscribe call gets the name of the event, start - # timestamp, end timestamp, a string with a unique identifier for that event + # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter # (something like "535801666f04d0298cd6"), and a hash with the payload, in # that order. # # If an exception happens during that particular instrumentation the payload will # have a key :exception with an array of two elements as value: a string with # the name of the exception class, and the exception message. + # The :exception_object key of the payload will have the exception + # itself as the value: # - # As the previous example depicts, the class ActiveSupport::Notifications::Event + # event.payload[:exception] # => ["ArgumentError", "Invalid value"] + # event.payload[:exception_object] # => # + # + # As the earlier example depicts, the class ActiveSupport::Notifications::Event # is able to take the arguments as they come and provide an object-oriented # interface to that data. # - # It is also possible to pass an object as the second parameter passed to the - # subscribe method instead of a block: + # It is also possible to pass an object which responds to call method + # as the second parameter to the subscribe method instead of a block: # # module ActionController # class PageRequest @@ -128,6 +147,16 @@ module ActiveSupport # during the execution of the block. The callback is unsubscribed automatically # after that. # + # To record +started+ and +finished+ values with monotonic time, + # specify the optional :monotonic option to the + # subscribed method. The :monotonic option is set + # to +false+ by default. + # + # callback = lambda {|name, started, finished, unique_id, payload| ... } + # ActiveSupport::Notifications.subscribed(callback, "sql.active_record", monotonic: true) do + # ... + # end + # # === Manual Unsubscription # # The +subscribe+ method returns a subscriber object: @@ -141,6 +170,20 @@ module ActiveSupport # # ActiveSupport::Notifications.unsubscribe(subscriber) # + # You can also unsubscribe by passing the name of the subscriber object. Note + # that this will unsubscribe all subscriptions with the given name: + # + # ActiveSupport::Notifications.unsubscribe("render") + # + # Subscribers using a regexp or other pattern-matching object will remain subscribed + # to all events that match their original pattern, unless those events match a string + # passed to +unsubscribe+: + # + # subscriber = ActiveSupport::Notifications.subscribe(/render/) { } + # ActiveSupport::Notifications.unsubscribe('render_template.action_view') + # subscriber.matches?('render_template.action_view') # => false + # subscriber.matches?('render_partial.action_view') # => true + # # == Default Queue # # Notifications ships with a queue implementation that consumes and publishes events @@ -154,6 +197,10 @@ def publish(name, *args) notifier.publish(name, *args) end + def publish_event(event) # :nodoc: + notifier.publish_event(event) + end + def instrument(name, payload = {}) if notifier.listening?(name) instrumenter.instrument(name, payload) { yield payload if block_given? } @@ -162,44 +209,70 @@ def instrument(name, payload = {}) end end - def subscribe(*args, &block) - notifier.subscribe(*args, &block) + # Subscribe to a given event name with the passed +block+. + # + # You can subscribe to events by passing a String to match exact event + # names, or by passing a Regexp to match all events that match a pattern. + # + # ActiveSupport::Notifications.subscribe(/render/) do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # The +block+ will receive five parameters with information about the event: + # + # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload| + # name # => String, name of the event (such as 'render' from above) + # start # => Time, when the instrumented block started execution + # finish # => Time, when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # If the block passed to the method only takes one parameter, + # it will yield an event object to the block: + # + # ActiveSupport::Notifications.subscribe(/render/) do |event| + # @event = event + # end + # + # Raises an error if invalid event name type is passed: + # + # ActiveSupport::Notifications.subscribe(:render) {|*args| ...} + # #=> ArgumentError (pattern must be specified as a String, Regexp or empty) + # + def subscribe(pattern = nil, callback = nil, &block) + notifier.subscribe(pattern, callback, monotonic: false, &block) + end + + # Performs the same functionality as #subscribe, but the +start+ and + # +finish+ block arguments are in monotonic time instead of wall-clock + # time. Monotonic time will not jump forward or backward (due to NTP or + # Daylights Savings). Use +monotonic_subscribe+ when accuracy of time + # duration is important. For example, computing elapsed time between + # two events. + def monotonic_subscribe(pattern = nil, callback = nil, &block) + notifier.subscribe(pattern, callback, monotonic: true, &block) end - def subscribed(callback, *args, &block) - subscriber = subscribe(*args, &callback) + def subscribed(callback, pattern = nil, monotonic: false, &block) + subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic) yield ensure unsubscribe(subscriber) end - def unsubscribe(args) - notifier.unsubscribe(args) + def unsubscribe(subscriber_or_name) + notifier.unsubscribe(subscriber_or_name) end def instrumenter - InstrumentationRegistry.instance.instrumenter_for(notifier) - end - end - - # This class is a registry which holds all of the +Instrumenter+ objects - # in a particular thread local. To access the +Instrumenter+ object for a - # particular +notifier+, you can call the following method: - # - # InstrumentationRegistry.instrumenter_for(notifier) - # - # The instrumenters for multiple notifiers are held in a single instance of - # this class. - class InstrumentationRegistry # :nodoc: - extend ActiveSupport::PerThreadRegistry - - def initialize - @registry = {} + registry[notifier] ||= Instrumenter.new(notifier) end - def instrumenter_for(notifier) - @registry[notifier] ||= Instrumenter.new(notifier) - end + private + def registry + ActiveSupport::IsolatedExecutionState[:active_support_notifications_registry] ||= {} + end end self.notifier = Fanout.new diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications/fanout.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications/fanout.rb new file mode 100644 index 0000000000..0759d3a086 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications/fanout.rb @@ -0,0 +1,285 @@ +# frozen_string_literal: true + +require "mutex_m" +require "concurrent/map" +require "set" +require "active_support/core_ext/object/try" + +module ActiveSupport + module Notifications + class InstrumentationSubscriberError < RuntimeError + attr_reader :exceptions + + def initialize(exceptions) + @exceptions = exceptions + exception_class_names = exceptions.map { |e| e.class.name } + super "Exception(s) occurred within instrumentation subscribers: #{exception_class_names.join(', ')}" + end + end + + # This is a default queue implementation that ships with Notifications. + # It just pushes events to all registered log subscribers. + # + # This class is thread safe. All methods are reentrant. + class Fanout + include Mutex_m + + def initialize + @string_subscribers = Hash.new { |h, k| h[k] = [] } + @other_subscribers = [] + @listeners_for = Concurrent::Map.new + super + end + + def subscribe(pattern = nil, callable = nil, monotonic: false, &block) + subscriber = Subscribers.new(pattern, callable || block, monotonic) + synchronize do + case pattern + when String + @string_subscribers[pattern] << subscriber + @listeners_for.delete(pattern) + when NilClass, Regexp + @other_subscribers << subscriber + @listeners_for.clear + else + raise ArgumentError, "pattern must be specified as a String, Regexp or empty" + end + end + subscriber + end + + def unsubscribe(subscriber_or_name) + synchronize do + case subscriber_or_name + when String + @string_subscribers[subscriber_or_name].clear + @listeners_for.delete(subscriber_or_name) + @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) } + else + pattern = subscriber_or_name.try(:pattern) + if String === pattern + @string_subscribers[pattern].delete(subscriber_or_name) + @listeners_for.delete(pattern) + else + @other_subscribers.delete(subscriber_or_name) + @listeners_for.clear + end + end + end + end + + def start(name, id, payload) + iterate_guarding_exceptions(listeners_for(name)) { |s| s.start(name, id, payload) } + end + + def finish(name, id, payload, listeners = listeners_for(name)) + iterate_guarding_exceptions(listeners) { |s| s.finish(name, id, payload) } + end + + def publish(name, *args) + iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) } + end + + def publish_event(event) + iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) } + end + + def iterate_guarding_exceptions(listeners) + exceptions = nil + + listeners.each do |s| + yield s + rescue Exception => e + exceptions ||= [] + exceptions << e + end + + if exceptions + if exceptions.size == 1 + raise exceptions.first + else + raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first + end + end + + listeners + end + + def listeners_for(name) + # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics) + @listeners_for[name] || synchronize do + # use synchronisation when accessing @subscribers + @listeners_for[name] ||= + @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) } + end + end + + def listening?(name) + listeners_for(name).any? + end + + # This is a sync queue, so there is no waiting. + def wait + end + + module Subscribers # :nodoc: + def self.new(pattern, listener, monotonic) + subscriber_class = monotonic ? MonotonicTimed : Timed + + if listener.respond_to?(:start) && listener.respond_to?(:finish) + subscriber_class = Evented + else + # Doing this to detect a single argument block or callable + # like `proc { |x| }` vs `proc { |*x| }`, `proc { |**x| }`, + # or `proc { |x, **y| }` + procish = listener.respond_to?(:parameters) ? listener : listener.method(:call) + + if procish.arity == 1 && procish.parameters.length == 1 + subscriber_class = EventObject + end + end + + subscriber_class.new(pattern, listener) + end + + class Matcher # :nodoc: + attr_reader :pattern, :exclusions + + def self.wrap(pattern) + if String === pattern + pattern + elsif pattern.nil? + AllMessages.new + else + new(pattern) + end + end + + def initialize(pattern) + @pattern = pattern + @exclusions = Set.new + end + + def unsubscribe!(name) + exclusions << -name if pattern === name + end + + def ===(name) + pattern === name && !exclusions.include?(name) + end + + class AllMessages + def ===(name) + true + end + + def unsubscribe!(*) + false + end + end + end + + class Evented # :nodoc: + attr_reader :pattern + + def initialize(pattern, delegate) + @pattern = Matcher.wrap(pattern) + @delegate = delegate + @can_publish = delegate.respond_to?(:publish) + @can_publish_event = delegate.respond_to?(:publish_event) + end + + def publish(name, *args) + if @can_publish + @delegate.publish name, *args + end + end + + def publish_event(event) + if @can_publish_event + @delegate.publish_event event + else + publish(event.name, event.time, event.end, event.transaction_id, event.payload) + end + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def subscribed_to?(name) + pattern === name + end + + def unsubscribe!(name) + pattern.unsubscribe!(name) + end + end + + class Timed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = IsolatedExecutionState[:_timestack] ||= [] + timestack.push Time.now + end + + def finish(name, id, payload) + timestack = IsolatedExecutionState[:_timestack] + started = timestack.pop + @delegate.call(name, started, Time.now, id, payload) + end + end + + class MonotonicTimed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = IsolatedExecutionState[:_timestack_monotonic] ||= [] + timestack.push Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + + def finish(name, id, payload) + timestack = IsolatedExecutionState[:_timestack_monotonic] + started = timestack.pop + @delegate.call(name, started, Process.clock_gettime(Process::CLOCK_MONOTONIC), id, payload) + end + end + + class EventObject < Evented + def start(name, id, payload) + stack = IsolatedExecutionState[:_event_stack] ||= [] + event = build_event name, id, payload + event.start! + stack.push event + end + + def finish(name, id, payload) + stack = IsolatedExecutionState[:_event_stack] + event = stack.pop + event.payload = payload + event.finish! + @delegate.call event + end + + def publish_event(event) + @delegate.call event + end + + private + def build_event(name, id, payload) + ActiveSupport::Notifications::Event.new name, nil, nil, id, payload + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications/instrumenter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications/instrumenter.rb new file mode 100644 index 0000000000..c69e8cd8d4 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/notifications/instrumenter.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +require "securerandom" + +module ActiveSupport + module Notifications + # Instrumenters are stored in a thread local. + class Instrumenter + attr_reader :id + + def initialize(notifier) + @id = unique_id + @notifier = notifier + end + + # Given a block, instrument it by measuring the time taken to execute + # and publish it. Without a block, simply send a message via the + # notifier. Notice that events get sent even if an error occurs in the + # passed-in block. + def instrument(name, payload = {}) + # some of the listeners might have state + listeners_state = start name, payload + begin + yield payload if block_given? + rescue Exception => e + payload[:exception] = [e.class.name, e.message] + payload[:exception_object] = e + raise e + ensure + finish_with_state listeners_state, name, payload + end + end + + def new_event(name, payload = {}) # :nodoc: + Event.new(name, nil, nil, @id, payload) + end + + # Send a start notification with +name+ and +payload+. + def start(name, payload) + @notifier.start name, @id, payload + end + + # Send a finish notification with +name+ and +payload+. + def finish(name, payload) + @notifier.finish name, @id, payload + end + + def finish_with_state(listeners_state, name, payload) + @notifier.finish name, @id, payload, listeners_state + end + + private + def unique_id + SecureRandom.hex(10) + end + end + + class Event + attr_reader :name, :time, :end, :transaction_id, :children + attr_accessor :payload + + def initialize(name, start, ending, transaction_id, payload) + @name = name + @payload = payload.dup + @time = start ? start.to_f * 1_000.0 : start + @transaction_id = transaction_id + @end = ending ? ending.to_f * 1_000.0 : ending + @children = [] + @cpu_time_start = 0.0 + @cpu_time_finish = 0.0 + @allocation_count_start = 0 + @allocation_count_finish = 0 + end + + def record + start! + begin + yield payload if block_given? + rescue Exception => e + payload[:exception] = [e.class.name, e.message] + payload[:exception_object] = e + raise e + ensure + finish! + end + end + + # Record information at the time this event starts + def start! + @time = now + @cpu_time_start = now_cpu + @allocation_count_start = now_allocations + end + + # Record information at the time this event finishes + def finish! + @cpu_time_finish = now_cpu + @end = now + @allocation_count_finish = now_allocations + end + + # Returns the CPU time (in milliseconds) passed since the call to + # +start!+ and the call to +finish!+ + def cpu_time + @cpu_time_finish - @cpu_time_start + end + + # Returns the idle time time (in milliseconds) passed since the call to + # +start!+ and the call to +finish!+ + def idle_time + duration - cpu_time + end + + # Returns the number of allocations made since the call to +start!+ and + # the call to +finish!+ + def allocations + @allocation_count_finish - @allocation_count_start + end + + # Returns the difference in milliseconds between when the execution of the + # event started and when it ended. + # + # ActiveSupport::Notifications.subscribe('wait') do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # ActiveSupport::Notifications.instrument('wait') do + # sleep 1 + # end + # + # @event.duration # => 1000.138 + def duration + self.end - time + end + + def <<(event) + @children << event + end + + def parent_of?(event) + @children.include? event + end + + private + def now + Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) + end + + begin + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond) + + def now_cpu + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond) + end + rescue + def now_cpu # rubocop:disable Lint/DuplicateMethods + 0.0 + end + end + + if GC.stat.key?(:total_allocated_objects) + def now_allocations + GC.stat(:total_allocated_objects) + end + else # Likely on JRuby, TruffleRuby + def now_allocations + 0 + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper.rb similarity index 65% rename from app/server/ruby/vendor/activesupport/lib/active_support/number_helper.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper.rb index b169e3af01..78c5285603 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper.rb @@ -1,9 +1,12 @@ +# frozen_string_literal: true + module ActiveSupport module NumberHelper extend ActiveSupport::Autoload eager_autoload do autoload :NumberConverter + autoload :RoundingHelper autoload :NumberToRoundedConverter autoload :NumberToDelimitedConverter autoload :NumberToHumanConverter @@ -15,7 +18,7 @@ module NumberHelper extend self - # Formats a +number+ into a US phone number (e.g., (555) + # Formats a +number+ into a phone number (US by default e.g., (555) # 123-9876). You can customize the format in the +options+ hash. # # ==== Options @@ -27,19 +30,26 @@ module NumberHelper # end of the generated number. # * :country_code - Sets the country code for the phone # number. + # * :pattern - Specifies how the number is divided into three + # groups with the custom regexp to override the default format. # ==== Examples # - # number_to_phone(5551234) # => 555-1234 - # number_to_phone('5551234') # => 555-1234 - # number_to_phone(1235551234) # => 123-555-1234 - # number_to_phone(1235551234, area_code: true) # => (123) 555-1234 - # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234 - # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555 - # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234 - # number_to_phone('123a456') # => 123a456 + # number_to_phone(5551234) # => "555-1234" + # number_to_phone('5551234') # => "555-1234" + # number_to_phone(1235551234) # => "123-555-1234" + # number_to_phone(1235551234, area_code: true) # => "(123) 555-1234" + # number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234" + # number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234" + # number_to_phone('123a456') # => "123a456" # # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.') - # # => +1.123.555.1234 x 1343 + # # => "+1.123.555.1234 x 1343" + # + # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true) + # # => "(755) 6123-4567" + # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/) + # # => "133-1234-5678" def number_to_phone(number, options = {}) NumberToPhoneConverter.convert(number, options) end @@ -47,12 +57,22 @@ def number_to_phone(number, options = {}) # Formats a +number+ into a currency string (e.g., $13.65). You # can customize the format in the +options+ hash. # + # The currency unit and number formatting of the current locale will be used + # unless otherwise specified in the provided options. No currency conversion + # is performed. If the user is given a way to change their locale, they will + # also be able to change the relative value of the currency displayed with + # this helper. If your application will ever support multiple locales, you + # may want to specify a constant :locale option or consider + # using a library capable of currency conversion. + # # ==== Options # # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :precision - Sets the level of precision (defaults # to 2). + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) # * :unit - Sets the denomination of the currency # (defaults to "$"). # * :separator - Sets the separator between the units @@ -63,25 +83,34 @@ def number_to_phone(number, options = {}) # (defaults to "%u%n"). Fields are %u for the # currency, and %n for the number. # * :negative_format - Sets the format for negative - # numbers (defaults to prepending an hyphen to the formatted + # numbers (defaults to prepending a hyphen to the formatted # number given by :format). Accepts the same fields # than :format, except %n is here the # absolute value of the number. + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). # # ==== Examples # - # number_to_currency(1234567890.50) # => $1,234,567,890.50 - # number_to_currency(1234567890.506) # => $1,234,567,890.51 - # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506 - # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 € - # number_to_currency('123a456') # => $123a456 + # number_to_currency(1234567890.50) # => "$1,234,567,890.50" + # number_to_currency(1234567890.506) # => "$1,234,567,890.51" + # number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506" + # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €" + # number_to_currency('123a456') # => "$123a456" # + # number_to_currency(-0.456789, precision: 0) + # # => "$0" # number_to_currency(-1234567890.50, negative_format: '(%u%n)') - # # => ($1,234,567,890.50) + # # => "($1,234,567,890.50)" # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '') - # # => £1234567890,50 + # # => "£1234567890,50" # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u') - # # => 1234567890,50 £ + # # => "1234567890,50 £" + # number_to_currency(1234567890.50, strip_insignificant_zeros: true) + # # => "$1,234,567,890.5" + # number_to_currency(1234567890.50, precision: 0, round_mode: :up) + # # => "$1,234,567,891" def number_to_currency(number, options = {}) NumberToCurrencyConverter.convert(number, options) end @@ -94,9 +123,11 @@ def number_to_currency(number, options = {}) # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :precision - Sets the precision of the number - # (defaults to 3). - # * :significant - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # (defaults to 3). Keeps the number's precision if +nil+. + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +false+). # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -110,14 +141,16 @@ def number_to_currency(number, options = {}) # # ==== Examples # - # number_to_percentage(100) # => 100.000% - # number_to_percentage('98') # => 98.000% - # number_to_percentage(100, precision: 0) # => 100% - # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% - # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% - # number_to_percentage(1000, locale: :fr) # => 1 000,000% - # number_to_percentage('98a') # => 98a% - # number_to_percentage(100, format: '%n %') # => 100 % + # number_to_percentage(100) # => "100.000%" + # number_to_percentage('98') # => "98.000%" + # number_to_percentage(100, precision: 0) # => "100%" + # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%" + # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%" + # number_to_percentage(1000, locale: :fr) # => "1000,000%" + # number_to_percentage(1000, precision: nil) # => "1000%" + # number_to_percentage('98a') # => "98a%" + # number_to_percentage(100, format: '%n %') # => "100.000 %" + # number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%" def number_to_percentage(number, options = {}) NumberToPercentageConverter.convert(number, options) end @@ -134,19 +167,25 @@ def number_to_percentage(number, options = {}) # to ","). # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). + # * :delimiter_pattern - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. # # ==== Examples # - # number_to_delimited(12345678) # => 12,345,678 - # number_to_delimited('123456') # => 123,456 - # number_to_delimited(12345678.05) # => 12,345,678.05 - # number_to_delimited(12345678, delimiter: '.') # => 12.345.678 - # number_to_delimited(12345678, delimiter: ',') # => 12,345,678 - # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05 - # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05 - # number_to_delimited('112a') # => 112a + # number_to_delimited(12345678) # => "12,345,678" + # number_to_delimited('123456') # => "123,456" + # number_to_delimited(12345678.05) # => "12,345,678.05" + # number_to_delimited(12345678, delimiter: '.') # => "12.345.678" + # number_to_delimited(12345678, delimiter: ',') # => "12,345,678" + # number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05" + # number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05" + # number_to_delimited('112a') # => "112a" # number_to_delimited(98765432.98, delimiter: ' ', separator: ',') - # # => 98 765 432,98 + # # => "98 765 432,98" + # number_to_delimited("123456.78", + # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) + # # => "1,23,456.78" def number_to_delimited(number, options = {}) NumberToDelimitedConverter.convert(number, options) end @@ -161,9 +200,11 @@ def number_to_delimited(number, options = {}) # * :locale - Sets the locale to be used for formatting # (defaults to current locale). # * :precision - Sets the precision of the number - # (defaults to 3). - # * :significant - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # (defaults to 3). Keeps the number's precision if +nil+. + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +false+). # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -175,27 +216,29 @@ def number_to_delimited(number, options = {}) # # ==== Examples # - # number_to_rounded(111.2345) # => 111.235 - # number_to_rounded(111.2345, precision: 2) # => 111.23 - # number_to_rounded(13, precision: 5) # => 13.00000 - # number_to_rounded(389.32314, precision: 0) # => 389 - # number_to_rounded(111.2345, significant: true) # => 111 - # number_to_rounded(111.2345, precision: 1, significant: true) # => 100 - # number_to_rounded(13, precision: 5, significant: true) # => 13.000 - # number_to_rounded(111.234, locale: :fr) # => 111,234 + # number_to_rounded(111.2345) # => "111.235" + # number_to_rounded(111.2345, precision: 2) # => "111.23" + # number_to_rounded(13, precision: 5) # => "13.00000" + # number_to_rounded(389.32314, precision: 0) # => "389" + # number_to_rounded(111.2345, significant: true) # => "111" + # number_to_rounded(111.2345, precision: 1, significant: true) # => "100" + # number_to_rounded(13, precision: 5, significant: true) # => "13.000" + # number_to_rounded(13, precision: nil) # => "13" + # number_to_rounded(389.32314, precision: 0, round_mode: :up) # => "390" + # number_to_rounded(111.234, locale: :fr) # => "111,234" # # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true) - # # => 13 + # # => "13" # - # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3 + # number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3" # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.') - # # => 1.111,23 + # # => "1.111,23" def number_to_rounded(number, options = {}) NumberToRoundedConverter.convert(number, options) end # Formats the bytes in +number+ into a more understandable - # representation (e.g., giving it 1500 yields 1.5 KB). This + # representation (e.g., giving it 1500 yields 1.46 KB). This # method is useful for reporting file sizes to users. You can # customize the format in the +options+ hash. # @@ -208,8 +251,10 @@ def number_to_rounded(number, options = {}) # (defaults to current locale). # * :precision - Sets the precision of the number # (defaults to 3). - # * :significant - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +true+) # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -218,32 +263,29 @@ def number_to_rounded(number, options = {}) # * :strip_insignificant_zeros - If +true+ removes # insignificant zeros after the decimal separator (defaults to # +true+) - # * :prefix - If +:si+ formats the number using the SI - # prefix (defaults to :binary) # # ==== Examples # - # number_to_human_size(123) # => 123 Bytes - # number_to_human_size(1234) # => 1.21 KB - # number_to_human_size(12345) # => 12.1 KB - # number_to_human_size(1234567) # => 1.18 MB - # number_to_human_size(1234567890) # => 1.15 GB - # number_to_human_size(1234567890123) # => 1.12 TB - # number_to_human_size(1234567, precision: 2) # => 1.2 MB - # number_to_human_size(483989, precision: 2) # => 470 KB - # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB - # - # Non-significant zeros after the fractional separator are stripped out by - # default (set :strip_insignificant_zeros to +false+ to change that): - # - # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB" - # number_to_human_size(524288000, precision: 5) # => "500 MB" + # number_to_human_size(123) # => "123 Bytes" + # number_to_human_size(1234) # => "1.21 KB" + # number_to_human_size(12345) # => "12.1 KB" + # number_to_human_size(1234567) # => "1.18 MB" + # number_to_human_size(1234567890) # => "1.15 GB" + # number_to_human_size(1234567890123) # => "1.12 TB" + # number_to_human_size(1234567890123456) # => "1.1 PB" + # number_to_human_size(1234567890123456789) # => "1.07 EB" + # number_to_human_size(1234567, precision: 2) # => "1.2 MB" + # number_to_human_size(483989, precision: 2) # => "470 KB" + # number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB" + # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB" + # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB" + # number_to_human_size(524288000, precision: 5) # => "500 MB" def number_to_human_size(number, options = {}) NumberToHumanSizeConverter.convert(number, options) end # Pretty prints (formats and approximates) a number in a way it - # is more readable by humans (eg.: 1200000000 becomes "1.2 + # is more readable by humans (e.g.: 1200000000 becomes "1.2 # Billion"). This is useful for numbers that can get very large # (and too hard to read). # @@ -251,7 +293,7 @@ def number_to_human_size(number, options = {}) # size. # # You can also define your own unit-quantifier names if you want - # to use other decimal units (eg.: 1500 becomes "1.5 + # to use other decimal units (e.g.: 1500 becomes "1.5 # kilometers", 0.150 becomes "150 milliliters", etc). You may # define a wide range of unit quantifiers, even fractional ones # (centi, deci, mili, etc). @@ -262,8 +304,10 @@ def number_to_human_size(number, options = {}) # (defaults to current locale). # * :precision - Sets the precision of the number # (defaults to 3). - # * :significant - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # * :round_mode - Determine how rounding is performed + # (defaults to :default. See BigDecimal::mode) + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +true+) # * :separator - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -276,12 +320,12 @@ def number_to_human_size(number, options = {}) # string containing an i18n scope where to find this hash. It # might have the following keys: # * *integers*: :unit, :ten, - # *:hundred, :thousand, :million, - # *:billion, :trillion, - # *:quadrillion + # :hundred, :thousand, :million, + # :billion, :trillion, + # :quadrillion # * *fractionals*: :deci, :centi, - # *:mili, :micro, :nano, - # *:pico, :femto + # :mili, :micro, :nano, + # :pico, :femto # * :format - Sets the format of the output string # (defaults to "%n %u"). The field types are: # * %u - The quantifier (ex.: 'thousand') @@ -299,23 +343,29 @@ def number_to_human_size(number, options = {}) # number_to_human(1234567890123456789) # => "1230 Quadrillion" # number_to_human(489939, precision: 2) # => "490 Thousand" # number_to_human(489939, precision: 4) # => "489.9 Thousand" + # number_to_human(489939, precision: 2 + # , round_mode: :down) # => "480 Thousand" # number_to_human(1234567, precision: 4, # significant: false) # => "1.2346 Million" # number_to_human(1234567, precision: 1, # separator: ',', # significant: false) # => "1,2 Million" # + # number_to_human(500000000, precision: 5) # => "500 Million" + # number_to_human(12345012345, significant: false) # => "12.345 Billion" + # # Non-significant zeros after the decimal separator are stripped # out by default (set :strip_insignificant_zeros to # +false+ to change that): # - # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion" - # number_to_human(500000000, precision: 5) # => "500 Million" + # number_to_human(12.00001) # => "12" + # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0" # # ==== Custom Unit Quantifiers # # You can also use your own custom unit quantifiers: - # number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt" + # + # number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt" # # If in your I18n locale you have: # diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_converter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_converter.rb similarity index 83% rename from app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_converter.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_converter.rb index 9d976f1831..168b1a5429 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_converter.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_converter.rb @@ -1,8 +1,10 @@ -require 'active_support/core_ext/big_decimal/conversions' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/hash/keys' -require 'active_support/i18n' -require 'active_support/core_ext/class/attribute' +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/hash/keys" +require "active_support/i18n" +require "active_support/core_ext/class/attribute" module ActiveSupport module NumberHelper @@ -28,7 +30,7 @@ class NumberConverter # :nodoc: # If set to true, precision will mean the number of significant digits instead # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) significant: false, - # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2) + # If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2) strip_insignificant_zeros: false }, @@ -134,22 +136,21 @@ def execute end private - def options @options ||= format_options.merge(opts) end - def format_options #:nodoc: + def format_options default_format_options.merge!(i18n_format_options) end - def default_format_options #:nodoc: + def default_format_options options = DEFAULTS[:format].dup options.merge!(DEFAULTS[namespace][:format]) if namespace options end - def i18n_format_options #:nodoc: + def i18n_format_options locale = opts[:locale] options = I18n.translate(:'number.format', locale: locale, default: {}).dup @@ -160,22 +161,20 @@ def i18n_format_options #:nodoc: options end - def translate_number_value_with_default(key, i18n_options = {}) #:nodoc: - I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options)) + def translate_number_value_with_default(key, **i18n_options) + I18n.translate(key, **{ default: default_value(key), scope: :number }.merge!(i18n_options)) end - def translate_in_locale(key, i18n_options = {}) - translate_number_value_with_default(key, { locale: options[:locale] }.merge(i18n_options)) + def translate_in_locale(key, **i18n_options) + translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options)) end def default_value(key) - key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } + key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } end - def valid_float? #:nodoc: - Float(number) - rescue ArgumentError, TypeError - false + def valid_float? + Float(number, exception: false) end end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_currency_converter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_currency_converter.rb new file mode 100644 index 0000000000..241ec903be --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_currency_converter.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToCurrencyConverter < NumberConverter # :nodoc: + self.namespace = :currency + + def convert + format = options[:format] + + number_f = valid_float? + if number_f + if number_f.negative? + number_f = number_f.abs + format = options[:negative_format] if (number_f * 10**options[:precision]) >= 0.5 + end + number_s = NumberToRoundedConverter.convert(number_f, options) + else + number_s = number.to_s.strip + format = options[:negative_format] if number_s.sub!(/^-/, "") + end + + format.gsub("%n", number_s).gsub("%u", options[:unit]) + end + + private + def options + @options ||= begin + defaults = default_format_options.merge(i18n_opts) + # Override negative format if format options are given + defaults[:negative_format] = "-#{opts[:format]}" if opts[:format] + defaults.merge!(opts) + end + end + + def i18n_opts + # Set International negative format if it does not exist + i18n = i18n_format_options + i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format] + i18n + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_delimited_converter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_delimited_converter.rb new file mode 100644 index 0000000000..4fb2fb7150 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToDelimitedConverter < NumberConverter # :nodoc: + self.validate_float = true + + DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ + + def convert + parts.join(options[:separator]) + end + + private + def parts + left, right = number.to_s.split(".") + left.gsub!(delimiter_pattern) do |digit_to_delimit| + "#{digit_to_delimit}#{options[:delimiter]}" + end + [left, right].compact + end + + def delimiter_pattern + options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_human_converter.rb similarity index 73% rename from app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_human_converter.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_human_converter.rb index 9a3dc526ae..3f92628501 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_human_converter.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_human_converter.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + module ActiveSupport module NumberHelper class NumberToHumanConverter < NumberConverter # :nodoc: @@ -9,36 +13,35 @@ class NumberToHumanConverter < NumberConverter # :nodoc: self.validate_float = true def convert # :nodoc: + @number = RoundingHelper.new(options).round(number) @number = Float(number) - # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files. unless options.key?(:strip_insignificant_zeros) options[:strip_insignificant_zeros] = true end units = opts[:units] exponent = calculate_exponent(units) - @number = number / (10 ** exponent) - - unit = determine_unit(units, exponent) + @number = number / (10**exponent) rounded_number = NumberToRoundedConverter.convert(number, options) - format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip + unit = determine_unit(units, exponent) + format.gsub("%n", rounded_number).gsub("%u", unit).strip end private - def format - options[:format] || translate_in_locale('human.decimal_units.format') + options[:format] || translate_in_locale("human.decimal_units.format") end def determine_unit(units, exponent) exp = DECIMAL_UNITS[exponent] case units when Hash - units[exp] || '' + units[exp] || "" when String, Symbol - I18n.translate("#{units}.#{exp}", :locale => options[:locale], :count => number.to_i) + I18n.translate("#{units}.#{exp}", locale: options[:locale], count: number.to_i) else translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i) end @@ -54,12 +57,12 @@ def unit_exponents(units) when Hash units when String, Symbol - I18n.translate(units.to_s, :locale => options[:locale], :raise => true) + I18n.translate(units.to_s, locale: options[:locale], raise: true) when nil translate_in_locale("human.decimal_units.units", raise: true) else raise ArgumentError, ":units must be a Hash or String translation scope." - end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by { |e| -e } + end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by(&:-@) end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_human_size_converter.rb similarity index 60% rename from app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_human_size_converter.rb index 78d2c9ae6e..57e1ffef37 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -1,7 +1,11 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + module ActiveSupport module NumberHelper - class NumberToHumanSizeConverter < NumberConverter #:nodoc: - STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] + class NumberToHumanSizeConverter < NumberConverter # :nodoc: + STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb] self.namespace = :human self.validate_float = true @@ -9,7 +13,7 @@ class NumberToHumanSizeConverter < NumberConverter #:nodoc: def convert @number = Float(number) - # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files. unless options.key?(:strip_insignificant_zeros) options[:strip_insignificant_zeros] = true end @@ -17,24 +21,23 @@ def convert if smaller_than_base? number_to_format = number.to_i.to_s else - human_size = number / (base ** exponent) + human_size = number / (base**exponent) number_to_format = NumberToRoundedConverter.convert(human_size, options) end - conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit) + conversion_format.gsub("%n", number_to_format).gsub("%u", unit) end private - def conversion_format - translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true) + translate_number_value_with_default("human.storage_units.format", locale: options[:locale], raise: true) end def unit - translate_number_value_with_default(storage_unit_key, :locale => options[:locale], :count => number.to_i, :raise => true) + translate_number_value_with_default(storage_unit_key, locale: options[:locale], count: number.to_i, raise: true) end def storage_unit_key - key_end = smaller_than_base? ? 'byte' : STORAGE_UNITS[exponent] + key_end = smaller_than_base? ? "byte" : STORAGE_UNITS[exponent] "human.storage_units.units.#{key_end}" end @@ -50,9 +53,8 @@ def smaller_than_base? end def base - opts[:prefix] == :si ? 1000 : 1024 + 1024 end end end end - diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_percentage_converter.rb similarity index 65% rename from app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_percentage_converter.rb index eafe2844f7..0c2e190f8a 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_percentage_converter.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + module ActiveSupport module NumberHelper class NumberToPercentageConverter < NumberConverter # :nodoc: @@ -5,7 +9,7 @@ class NumberToPercentageConverter < NumberConverter # :nodoc: def convert rounded_number = NumberToRoundedConverter.convert(number, options) - options[:format].gsub('%n', rounded_number) + options[:format].gsub("%n", rounded_number) end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_phone_converter.rb similarity index 60% rename from app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_phone_converter.rb index af2ee56d91..c9771d0a09 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_phone_converter.rb @@ -1,14 +1,17 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + module ActiveSupport module NumberHelper - class NumberToPhoneConverter < NumberConverter #:nodoc: + class NumberToPhoneConverter < NumberConverter # :nodoc: def convert - str = country_code(opts[:country_code]) + str = country_code(opts[:country_code]).dup str << convert_to_phone_number(number.to_s.strip) str << phone_ext(opts[:extension]) end private - def convert_to_phone_number(number) if opts[:area_code] convert_with_area_code(number) @@ -18,12 +21,16 @@ def convert_to_phone_number(number) end def convert_with_area_code(number) - number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3") + default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/ + number.gsub!(regexp_pattern(default_pattern), + "(\\1) \\2#{delimiter}\\3") number end def convert_without_area_code(number) - number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + default_pattern = /(\d{0,3})(\d{3})(\d{4})$/ + number.gsub!(regexp_pattern(default_pattern), + "\\1#{delimiter}\\2#{delimiter}\\3") number.slice!(0, 1) if start_with_delimiter?(number) number end @@ -43,7 +50,10 @@ def country_code(code) def phone_ext(ext) ext.blank? ? "" : " x #{ext}" end + + def regexp_pattern(default_pattern) + opts.fetch :pattern, default_pattern + end end end end - diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_rounded_converter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_rounded_converter.rb new file mode 100644 index 0000000000..f48a5158c3 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToRoundedConverter < NumberConverter # :nodoc: + self.namespace = :precision + self.validate_float = true + + def convert + helper = RoundingHelper.new(options) + rounded_number = helper.round(number) + + if precision = options[:precision] + if options[:significant] && precision > 0 + digits = helper.digit_count(rounded_number) + precision -= digits + precision = 0 if precision < 0 # don't let it be negative + end + + formatted_string = + if rounded_number.finite? + s = rounded_number.to_s("F") + a, b = s.split(".", 2) + if precision != 0 + b << "0" * precision + a << "." + a << b[0, precision] + end + a + else + # Infinity/NaN + "%f" % rounded_number + end + else + formatted_string = rounded_number + end + + delimited_number = NumberToDelimitedConverter.convert(formatted_string, options) + format_number(delimited_number) + end + + private + def strip_insignificant_zeros + options[:strip_insignificant_zeros] + end + + def format_number(number) + if strip_insignificant_zeros + escaped_separator = Regexp.escape(options[:separator]) + number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, "") + else + number + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/rounding_helper.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/rounding_helper.rb new file mode 100644 index 0000000000..14deca13ea --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/number_helper/rounding_helper.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class RoundingHelper # :nodoc: + attr_reader :options + + def initialize(options) + @options = options + end + + def round(number) + precision = absolute_precision(number) + return number unless precision + + rounded_number = convert_to_decimal(number).round(precision, options.fetch(:round_mode, :default).to_sym) + rounded_number.zero? ? rounded_number.abs : rounded_number # prevent showing negative zeros + end + + def digit_count(number) + return 1 if number.zero? + (Math.log10(number.abs) + 1).floor + end + + private + def convert_to_decimal(number) + case number + when Float, String + BigDecimal(number.to_s) + when Rational + BigDecimal(number, digit_count(number.to_i) + options[:precision]) + else + number.to_d + end + end + + def absolute_precision(number) + if options[:significant] && options[:precision] > 0 + options[:precision] - digit_count(convert_to_decimal(number)) + else + options[:precision] + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/option_merger.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/option_merger.rb new file mode 100644 index 0000000000..b6dd1263fa --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/option_merger.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/deep_merge" + +module ActiveSupport + class OptionMerger # :nodoc: + instance_methods.each do |method| + undef_method(method) unless method.start_with?("__", "instance_eval", "class", "object_id") + end + + def initialize(context, options) + @context, @options = context, options + end + + private + def method_missing(method, *arguments, &block) + options = nil + if arguments.size == 1 && arguments.first.is_a?(Proc) + proc = arguments.shift + arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) } + elsif arguments.last.respond_to?(:to_hash) + options = @options.deep_merge(arguments.pop) + else + options = @options + end + + if options + @context.__send__(method, *arguments, **options, &block) + else + @context.__send__(method, *arguments, &block) + end + end + + def respond_to_missing?(*arguments) + @context.respond_to?(*arguments) + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/ordered_hash.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ordered_hash.rb similarity index 71% rename from app/server/ruby/vendor/activesupport/lib/active_support/ordered_hash.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ordered_hash.rb index 4680d5acb7..39505bcb68 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/ordered_hash.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ordered_hash.rb @@ -1,11 +1,13 @@ -require 'yaml' +# frozen_string_literal: true + +require "yaml" YAML.add_builtin_type("omap") do |type, val| - ActiveSupport::OrderedHash[val.map{ |v| v.to_a.first }] + ActiveSupport::OrderedHash[val.map { |v| v.to_a.first }] end module ActiveSupport - # ActiveSupport::OrderedHash implements a hash that preserves + # DEPRECATED: ActiveSupport::OrderedHash implements a hash that preserves # insertion order. # # oh = ActiveSupport::OrderedHash.new @@ -14,18 +16,18 @@ module ActiveSupport # oh.keys # => [:a, :b], this order is guaranteed # # Also, maps the +omap+ feature for YAML files - # (See http://yaml.org/type/omap.html) to support ordered items + # (See https://yaml.org/type/omap.html) to support ordered items # when loading from yaml. # # ActiveSupport::OrderedHash is namespaced to prevent conflicts # with other implementations. - class OrderedHash < ::Hash + class OrderedHash < ::Hash # :nodoc: def to_yaml_type "!tag:yaml.org,2002:omap" end def encode_with(coder) - coder.represent_seq '!omap', map { |k,v| { k => v } } + coder.represent_seq "!omap", map { |k, v| { k => v } } end def select(*args, &block) diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/ordered_options.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ordered_options.rb similarity index 55% rename from app/server/ruby/vendor/activesupport/lib/active_support/ordered_options.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ordered_options.rb index a33e2c58a9..1a7f4d02e6 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/ordered_options.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ordered_options.rb @@ -1,19 +1,33 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" + module ActiveSupport - # Usually key value pairs are handled something like this: + # +OrderedOptions+ inherits from +Hash+ and provides dynamic accessor methods. + # + # With a +Hash+, key-value pairs are typically managed like this: # # h = {} # h[:boy] = 'John' # h[:girl] = 'Mary' # h[:boy] # => 'John' # h[:girl] # => 'Mary' + # h[:dog] # => nil # - # Using +OrderedOptions+, the above code could be reduced to: + # Using +OrderedOptions+, the above code can be written as: # # h = ActiveSupport::OrderedOptions.new # h.boy = 'John' # h.girl = 'Mary' # h.boy # => 'John' # h.girl # => 'Mary' + # h.dog # => nil + # + # To raise an exception when the value is blank, append a + # bang to the key name, like: + # + # h.dog! # => raises KeyError: :dog is blank + # class OrderedOptions < Hash alias_method :_get, :[] # preserve the original #[] method protected :_get # make it protected @@ -27,20 +41,34 @@ def [](key) end def method_missing(name, *args) - name_string = name.to_s - if name_string.chomp!('=') + name_string = +name.to_s + if name_string.chomp!("=") self[name_string] = args.first else - self[name] + bangs = name_string.chomp!("!") + + if bangs + self[name_string].presence || raise(KeyError.new(":#{name_string} is blank")) + else + self[name_string] + end end end def respond_to_missing?(name, include_private) true end + + def extractable_options? + true + end + + def inspect + "#<#{self.class.name} #{super}>" + end end - # +InheritableOptions+ provides a constructor to build an +OrderedOptions+ + # +InheritableOptions+ provides a constructor to build an OrderedOptions # hash inherited from another hash. # # Use this if you already have some hash and you want to create a new one based on it. @@ -52,9 +80,9 @@ class InheritableOptions < OrderedOptions def initialize(parent = nil) if parent.kind_of?(OrderedOptions) # use the faster _get when dealing with OrderedOptions - super() { |h,k| parent._get(k) } + super() { |h, k| parent._get(k) } elsif parent - super() { |h,k| parent[k] } + super() { |h, k| parent[k] } else super() end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/parameter_filter.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/parameter_filter.rb new file mode 100644 index 0000000000..b803dcdf7c --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/parameter_filter.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" + +module ActiveSupport + # +ParameterFilter+ replaces values in a Hash-like object if their + # keys match one of the specified filters. + # + # Matching based on nested keys is possible by using dot notation, e.g. + # "credit_card.number". + # + # If a proc is given as a filter, each key and value of the Hash-like + # and of any nested Hashes will be passed to it. The value or key can + # then be mutated as desired using methods such as String#replace. + # + # # Replaces values with "[FILTERED]" for keys that match /password/i. + # ActiveSupport::ParameterFilter.new([:password]) + # + # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i. + # ActiveSupport::ParameterFilter.new([:foo, "bar"]) + # + # # Replaces values for the exact key "pin" and for keys that begin with + # # "pin_". Does not match keys that otherwise include "pin" as a + # # substring, such as "shipping_id". + # ActiveSupport::ParameterFilter.new([/\Apin\z/, /\Apin_/]) + # + # # Replaces the value for :code in `{ credit_card: { code: "xxxx" } }`. + # # Does not change `{ file: { code: "xxxx" } }`. + # ActiveSupport::ParameterFilter.new(["credit_card.code"]) + # + # # Reverses values for keys that match /secret/i. + # ActiveSupport::ParameterFilter.new([-> (k, v) do + # v.reverse! if /secret/i.match?(k) + # end]) + # + class ParameterFilter + FILTERED = "[FILTERED]" # :nodoc: + + # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+. + # Other types of filters are treated as +String+ using +to_s+. + # For +Proc+ filters, key, value, and optional original hash is passed to block arguments. + # + # ==== Options + # + # * :mask - A replaced object when filtered. Defaults to "[FILTERED]". + def initialize(filters = [], mask: FILTERED) + @filters = filters + @mask = mask + end + + # Mask value of +params+ if key matches one of filters. + def filter(params) + compiled_filter.call(params) + end + + # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated. + def filter_param(key, value) + @filters.empty? ? value : compiled_filter.value_for_key(key, value) + end + + private + def compiled_filter + @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask) + end + + class CompiledFilter # :nodoc: + def self.compile(filters, mask:) + return lambda { |params| params.dup } if filters.empty? + + strings, regexps, blocks, deep_regexps, deep_strings = [], [], [], nil, nil + + filters.each do |item| + case item + when Proc + blocks << item + when Regexp + if item.to_s.include?("\\.") + (deep_regexps ||= []) << item + else + regexps << item + end + else + s = Regexp.escape(item.to_s) + if s.include?("\\.") + (deep_strings ||= []) << s + else + strings << s + end + end + end + + regexps << Regexp.new(strings.join("|"), true) unless strings.empty? + (deep_regexps ||= []) << Regexp.new(deep_strings.join("|"), true) if deep_strings&.any? + + new regexps, deep_regexps, blocks, mask: mask + end + + attr_reader :regexps, :deep_regexps, :blocks + + def initialize(regexps, deep_regexps, blocks, mask:) + @regexps = regexps + @deep_regexps = deep_regexps&.any? ? deep_regexps : nil + @blocks = blocks + @mask = mask + end + + def call(params, parents = [], original_params = params) + filtered_params = params.class.new + + params.each do |key, value| + filtered_params[key] = value_for_key(key, value, parents, original_params) + end + + filtered_params + end + + def value_for_key(key, value, parents = [], original_params = nil) + parents.push(key) if deep_regexps + if regexps.any? { |r| r.match?(key.to_s) } + value = @mask + elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) } + value = @mask + elsif value.is_a?(Hash) + value = call(value, parents, original_params) + elsif value.is_a?(Array) + # If we don't pop the current parent it will be duplicated as we + # process each array value. + parents.pop if deep_regexps + value = value.map { |v| value_for_key(key, v, parents, original_params) } + # Restore the parent stack after processing the array. + parents.push(key) if deep_regexps + elsif blocks.any? + key = key.dup if key.duplicable? + value = value.dup if value.duplicable? + blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) } + end + parents.pop if deep_regexps + value + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/per_thread_registry.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/per_thread_registry.rb similarity index 64% rename from app/server/ruby/vendor/activesupport/lib/active_support/per_thread_registry.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/per_thread_registry.rb index ca2e4d5625..cd8db9114a 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/per_thread_registry.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/per_thread_registry.rb @@ -1,4 +1,11 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + module ActiveSupport + # NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends. + # Please use that approach instead. + # # This module is used to encapsulate access to thread local variables. # # Instead of polluting the thread locals namespace: @@ -33,21 +40,26 @@ module ActiveSupport # If the class has an initializer, it must accept no arguments. module PerThreadRegistry def self.extended(object) - object.instance_variable_set '@per_thread_registry_key', object.name.freeze + ActiveSupport::Deprecation.warn(<<~MSG) + ActiveSupport::PerThreadRegistry is deprecated and will be removed in Rails 7.1. + Use `Module#thread_mattr_accessor` instead. + MSG + object.instance_variable_set :@per_thread_registry_key, object.name.freeze end def instance Thread.current[@per_thread_registry_key] ||= new end - protected - def method_missing(name, *args, &block) # :nodoc: + private + def method_missing(name, *args, &block) # Caches the method definition as a singleton method of the receiver. - define_singleton_method(name) do |*a, &b| - instance.public_send(name, *a, &b) - end + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance send(name, *args, &block) end + ruby2_keywords(:method_missing) end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/proxy_object.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/proxy_object.rb similarity index 92% rename from app/server/ruby/vendor/activesupport/lib/active_support/proxy_object.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/proxy_object.rb index 20a0fd8e62..0965fcd2d9 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/proxy_object.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/proxy_object.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveSupport # A class with no predefined methods that behaves similarly to Builder's # BlankSlate. Used for proxy classes. diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/rails.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/rails.rb new file mode 100644 index 0000000000..75676a2e47 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/rails.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# This is a private interface. +# +# Rails components cherry pick from Active Support as needed, but there are a +# few features that are used for sure in some way or another and it is not worth +# putting individual requires absolutely everywhere. Think blank? for example. +# +# This file is loaded by every Rails component except Active Support itself, +# but it does not belong to the Rails public interface. It is internal to +# Rails and can change anytime. + +# Defines Object#blank? and Object#present?. +require "active_support/core_ext/object/blank" + +# Support for ClassMethods and the included macro. +require "active_support/concern" + +# Defines Class#class_attribute. +require "active_support/core_ext/class/attribute" + +# Defines Module#delegate. +require "active_support/core_ext/module/delegation" + +# Defines ActiveSupport::Deprecation. +require "active_support/deprecation" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/railtie.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/railtie.rb new file mode 100644 index 0000000000..e86aa437fc --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/railtie.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/i18n_railtie" + +module ActiveSupport + class Railtie < Rails::Railtie # :nodoc: + config.active_support = ActiveSupport::OrderedOptions.new + config.active_support.disable_to_s_conversion = false + + config.eager_load_namespaces << ActiveSupport + + initializer "active_support.isolation_level" do |app| + config.after_initialize do + if level = app.config.active_support.delete(:isolation_level) + ActiveSupport::IsolatedExecutionState.isolation_level = level + end + end + end + + initializer "active_support.remove_deprecated_time_with_zone_name" do |app| + config.after_initialize do + if app.config.active_support.remove_deprecated_time_with_zone_name + require "active_support/time_with_zone" + TimeWithZone.singleton_class.remove_method(:name) + end + end + end + + initializer "active_support.set_authenticated_message_encryption" do |app| + config.after_initialize do + unless app.config.active_support.use_authenticated_message_encryption.nil? + ActiveSupport::MessageEncryptor.use_authenticated_message_encryption = + app.config.active_support.use_authenticated_message_encryption + end + end + end + + initializer "active_support.reset_execution_context" do |app| + app.reloader.before_class_unload { ActiveSupport::ExecutionContext.clear } + app.executor.to_run { ActiveSupport::ExecutionContext.clear } + app.executor.to_complete { ActiveSupport::ExecutionContext.clear } + end + + initializer "active_support.reset_all_current_attributes_instances" do |app| + app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all } + app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all } + app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all } + + ActiveSupport.on_load(:active_support_test_case) do + if app.config.active_support.executor_around_test_case + require "active_support/executor/test_helper" + include ActiveSupport::Executor::TestHelper + else + require "active_support/current_attributes/test_helper" + include ActiveSupport::CurrentAttributes::TestHelper + + require "active_support/execution_context/test_helper" + include ActiveSupport::ExecutionContext::TestHelper + end + end + end + + initializer "active_support.deprecation_behavior" do |app| + if app.config.active_support.report_deprecations == false + ActiveSupport::Deprecation.silenced = true + ActiveSupport::Deprecation.behavior = :silence + ActiveSupport::Deprecation.disallowed_behavior = :silence + else + if deprecation = app.config.active_support.deprecation + ActiveSupport::Deprecation.behavior = deprecation + end + + if disallowed_deprecation = app.config.active_support.disallowed_deprecation + ActiveSupport::Deprecation.disallowed_behavior = disallowed_deprecation + end + + if disallowed_warnings = app.config.active_support.disallowed_deprecation_warnings + ActiveSupport::Deprecation.disallowed_warnings = disallowed_warnings + end + end + end + + # Sets the default value for Time.zone + # If assigned value cannot be matched to a TimeZone, an exception will be raised. + initializer "active_support.initialize_time_zone" do |app| + begin + TZInfo::DataSource.get + rescue TZInfo::DataSourceNotFound => e + raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install" + end + require "active_support/core_ext/time/zones" + Time.zone_default = Time.find_zone!(app.config.time_zone) + end + + # Sets the default week start + # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. + initializer "active_support.initialize_beginning_of_week" do |app| + require "active_support/core_ext/date/calculations" + beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week) + + Date.beginning_of_week_default = beginning_of_week_default + end + + initializer "active_support.require_master_key" do |app| + if app.config.respond_to?(:require_master_key) && app.config.require_master_key + begin + app.credentials.key + rescue ActiveSupport::EncryptedFile::MissingKeyError => error + $stderr.puts error.message + exit 1 + end + end + end + + initializer "active_support.set_error_reporter" do |app| + ActiveSupport.error_reporter = app.executor.error_reporter + end + + initializer "active_support.set_configs" do |app| + app.config.active_support.each do |k, v| + k = "#{k}=" + ActiveSupport.public_send(k, v) if ActiveSupport.respond_to? k + end + end + + initializer "active_support.set_hash_digest_class" do |app| + config.after_initialize do + if klass = app.config.active_support.hash_digest_class + ActiveSupport::Digest.hash_digest_class = klass + end + end + end + + initializer "active_support.set_key_generator_hash_digest_class" do |app| + config.after_initialize do + if klass = app.config.active_support.key_generator_hash_digest_class + ActiveSupport::KeyGenerator.hash_digest_class = klass + end + end + end + + initializer "active_support.set_rfc4122_namespaced_uuids" do |app| + config.after_initialize do + if app.config.active_support.use_rfc4122_namespaced_uuids + require "active_support/core_ext/digest" + ::Digest::UUID.use_rfc4122_namespaced_uuids = app.config.active_support.use_rfc4122_namespaced_uuids + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/reloader.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/reloader.rb new file mode 100644 index 0000000000..e751866a27 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/reloader.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "active_support/execution_wrapper" +require "active_support/executor" + +module ActiveSupport + #-- + # This class defines several callbacks: + # + # to_prepare -- Run once at application startup, and also from + # +to_run+. + # + # to_run -- Run before a work run that is reloading. If + # +reload_classes_only_on_change+ is true (the default), the class + # unload will have already occurred. + # + # to_complete -- Run after a work run that has reloaded. If + # +reload_classes_only_on_change+ is false, the class unload will + # have occurred after the work run, but before this callback. + # + # before_class_unload -- Run immediately before the classes are + # unloaded. + # + # after_class_unload -- Run immediately after the classes are + # unloaded. + # + class Reloader < ExecutionWrapper + define_callbacks :prepare + + define_callbacks :class_unload + + # Registers a callback that will run once at application startup and every time the code is reloaded. + def self.to_prepare(*args, &block) + set_callback(:prepare, *args, &block) + end + + # Registers a callback that will run immediately before the classes are unloaded. + def self.before_class_unload(*args, &block) + set_callback(:class_unload, *args, &block) + end + + # Registers a callback that will run immediately after the classes are unloaded. + def self.after_class_unload(*args, &block) + set_callback(:class_unload, :after, *args, &block) + end + + to_run(:after) { self.class.prepare! } + + # Initiate a manual reload + def self.reload! + executor.wrap do + new.tap do |instance| + instance.run! + ensure + instance.complete! + end + end + prepare! + end + + def self.run!(reset: false) # :nodoc: + if check! + super + else + Null + end + end + + # Run the supplied block as a work unit, reloading code as needed + def self.wrap + executor.wrap do + super + end + end + + class_attribute :executor, default: Executor + class_attribute :check, default: lambda { false } + + def self.check! # :nodoc: + @should_reload ||= check.call + end + + def self.reloaded! # :nodoc: + @should_reload = false + end + + def self.prepare! # :nodoc: + new.run_callbacks(:prepare) + end + + def initialize + super + @locked = false + end + + # Acquire the ActiveSupport::Dependencies::Interlock unload lock, + # ensuring it will be released automatically + def require_unload_lock! + unless @locked + ActiveSupport::Dependencies.interlock.start_unloading + @locked = true + end + end + + # Release the unload lock if it has been previously obtained + def release_unload_lock! + if @locked + @locked = false + ActiveSupport::Dependencies.interlock.done_unloading + end + end + + def run! # :nodoc: + super + release_unload_lock! + end + + def class_unload!(&block) # :nodoc: + require_unload_lock! + run_callbacks(:class_unload, &block) + end + + def complete! # :nodoc: + super + self.class.reloaded! + ensure + release_unload_lock! + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/rescuable.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/rescuable.rb new file mode 100644 index 0000000000..3199d7775f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/rescuable.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + # Rescuable module adds support for easier exception handling. + module Rescuable + extend Concern + + included do + class_attribute :rescue_handlers, default: [] + end + + module ClassMethods + # Registers exception classes with a handler to be called by rescue_with_handler. + # + # rescue_from receives a series of exception classes or class + # names, and an exception handler specified by a trailing :with + # option containing the name of a method or a Proc object. Alternatively, a block + # can be given as the handler. + # + # Handlers that take one argument will be called with the exception, so + # that the exception can be inspected when dealing with it. + # + # Handlers are inherited. They are searched from right to left, from + # bottom to top, and up the hierarchy. The handler of the first class for + # which exception.is_a?(klass) holds true is the one invoked, if + # any. + # + # class ApplicationController < ActionController::Base + # rescue_from User::NotAuthorized, with: :deny_access + # rescue_from ActiveRecord::RecordInvalid, with: :show_record_errors + # + # rescue_from "MyApp::BaseError" do |exception| + # redirect_to root_url, alert: exception.message + # end + # + # private + # def deny_access + # head :forbidden + # end + # + # def show_record_errors(exception) + # redirect_back_or_to root_url, alert: exception.record.errors.full_messages.to_sentence + # end + # end + # + # Exceptions raised inside exception handlers are not propagated up. + def rescue_from(*klasses, with: nil, &block) + unless with + if block_given? + with = block + else + raise ArgumentError, "Need a handler. Pass the with: keyword argument or provide a block." + end + end + + klasses.each do |klass| + key = if klass.is_a?(Module) && klass.respond_to?(:===) + klass.name + elsif klass.is_a?(String) + klass + else + raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class" + end + + # Put the new handler at the end because the list is read in reverse. + self.rescue_handlers += [[key, with]] + end + end + + # Matches an exception to a handler based on the exception class. + # + # If no handler matches the exception, check for a handler matching the + # (optional) +exception.cause+. If no handler matches the exception or its + # cause, this returns +nil+, so you can deal with unhandled exceptions. + # Be sure to re-raise unhandled exceptions if this is what you expect. + # + # begin + # # ... + # rescue => exception + # rescue_with_handler(exception) || raise + # end + # + # Returns the exception if it was handled and +nil+ if it was not. + def rescue_with_handler(exception, object: self, visited_exceptions: []) + visited_exceptions << exception + + if handler = handler_for_rescue(exception, object: object) + handler.call exception + exception + elsif exception + if visited_exceptions.include?(exception.cause) + nil + else + rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions) + end + end + end + + def handler_for_rescue(exception, object: self) # :nodoc: + case rescuer = find_rescue_handler(exception) + when Symbol + method = object.method(rescuer) + if method.arity == 0 + -> e { method.call } + else + method + end + when Proc + if rescuer.arity == 0 + -> e { object.instance_exec(&rescuer) } + else + -> e { object.instance_exec(e, &rescuer) } + end + end + end + + private + def find_rescue_handler(exception) + if exception + # Handlers are in order of declaration but the most recently declared + # is the highest priority match, so we search for matching handlers + # in reverse. + _, handler = rescue_handlers.reverse_each.detect do |class_or_name, _| + if klass = constantize_rescue_handler_class(class_or_name) + klass === exception + end + end + + handler + end + end + + def constantize_rescue_handler_class(class_or_name) + case class_or_name + when String, Symbol + begin + # Try a lexical lookup first since we support + # + # class Super + # rescue_from 'Error', with: … + # end + # + # class Sub + # class Error < StandardError; end + # end + # + # so an Error raised in Sub will hit the 'Error' handler. + const_get class_or_name + rescue NameError + class_or_name.safe_constantize + end + else + class_or_name + end + end + end + + # Delegates to the class method, but uses the instance as the subject for + # rescue_from handlers (method calls, +instance_exec+ blocks). + def rescue_with_handler(exception) + self.class.rescue_with_handler exception, object: self + end + + # Internal handler lookup. Delegates to class method. Some libraries call + # this directly, so keeping it around for compatibility. + def handler_for_rescue(exception) # :nodoc: + self.class.handler_for_rescue exception, object: self + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ruby_features.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ruby_features.rb new file mode 100644 index 0000000000..8cdb89c20e --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/ruby_features.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ActiveSupport + module RubyFeatures # :nodoc: + CLASS_SUBCLASSES = Class.method_defined?(:subclasses) # RUBY_VERSION >= "3.1" + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/secure_compare_rotator.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/secure_compare_rotator.rb new file mode 100644 index 0000000000..982ebf1d99 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/secure_compare_rotator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/security_utils" +require "active_support/messages/rotator" + +module ActiveSupport + # The ActiveSupport::SecureCompareRotator is a wrapper around ActiveSupport::SecurityUtils.secure_compare + # and allows you to rotate a previously defined value to a new one. + # + # It can be used as follow: + # + # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value') + # rotator.rotate('previous_production_value') + # rotator.secure_compare!('previous_production_value') + # + # One real use case example would be to rotate a basic auth credentials: + # + # class MyController < ApplicationController + # def authenticate_request + # rotator = ActiveSupport::SecureCompareRotator.new('new_password') + # rotator.rotate('old_password') + # + # authenticate_or_request_with_http_basic do |username, password| + # rotator.secure_compare!(password) + # rescue ActiveSupport::SecureCompareRotator::InvalidMatch + # false + # end + # end + # end + class SecureCompareRotator + include SecurityUtils + prepend Messages::Rotator + + InvalidMatch = Class.new(StandardError) + + def initialize(value, **_options) + @value = value + end + + def secure_compare!(other_value, on_rotation: @on_rotation) + secure_compare(@value, other_value) || + run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } || + raise(InvalidMatch) + end + + private + def build_rotation(previous_value, _options) + self.class.new(previous_value) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/security_utils.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/security_utils.rb new file mode 100644 index 0000000000..aa00474448 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/security_utils.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ActiveSupport + module SecurityUtils + # Constant time string comparison, for fixed length strings. + # + # The values compared should be of fixed length, such as strings + # that have already been processed by HMAC. Raises in case of length mismatch. + + if defined?(OpenSSL.fixed_length_secure_compare) + def fixed_length_secure_compare(a, b) + OpenSSL.fixed_length_secure_compare(a, b) + end + else + def fixed_length_secure_compare(a, b) + raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize + + l = a.unpack "C#{a.bytesize}" + + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 + end + end + module_function :fixed_length_secure_compare + + # Secure string comparison for strings of variable length. + # + # While a timing attack would not be able to discern the content of + # a secret compared via secure_compare, it is possible to determine + # the secret length. This should be considered when using secure_compare + # to compare weak, short secrets to user input. + def secure_compare(a, b) + a.bytesize == b.bytesize && fixed_length_secure_compare(a, b) + end + module_function :secure_compare + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/string_inquirer.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/string_inquirer.rb similarity index 60% rename from app/server/ruby/vendor/activesupport/lib/active_support/string_inquirer.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/string_inquirer.rb index 45271c9163..8c4bf55044 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/string_inquirer.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/string_inquirer.rb @@ -1,22 +1,29 @@ +# frozen_string_literal: true + module ActiveSupport # Wrapping a string in this class gives you a prettier way to test # for equality. The value returned by Rails.env is wrapped - # in a StringInquirer object so instead of calling this: + # in a StringInquirer object, so instead of calling this: # # Rails.env == 'production' # # you can call this: # # Rails.env.production? + # + # == Instantiating a new StringInquirer + # + # vehicle = ActiveSupport::StringInquirer.new('car') + # vehicle.car? # => true + # vehicle.bike? # => false class StringInquirer < String private - def respond_to_missing?(method_name, include_private = false) - method_name[-1] == '?' + method_name.end_with?("?") || super end def method_missing(method_name, *arguments) - if method_name[-1] == '?' + if method_name.end_with?("?") self == method_name[0..-2] else super diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/subscriber.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/subscriber.rb new file mode 100644 index 0000000000..0f71443e42 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/subscriber.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require "active_support/notifications" + +module ActiveSupport + # ActiveSupport::Subscriber is an object set to consume + # ActiveSupport::Notifications. The subscriber dispatches notifications to + # a registered object based on its given namespace. + # + # An example would be an Active Record subscriber responsible for collecting + # statistics about queries: + # + # module ActiveRecord + # class StatsSubscriber < ActiveSupport::Subscriber + # attach_to :active_record + # + # def sql(event) + # Statsd.timing("sql.#{event.payload[:name]}", event.duration) + # end + # end + # end + # + # After configured, whenever a "sql.active_record" notification is published, + # it will properly dispatch the event (ActiveSupport::Notifications::Event) to + # the +sql+ method. + # + # We can detach a subscriber as well: + # + # ActiveRecord::StatsSubscriber.detach_from(:active_record) + class Subscriber + class << self + # Attach the subscriber to a namespace. + def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications, inherit_all: false) + @namespace = namespace + @subscriber = subscriber + @notifier = notifier + @inherit_all = inherit_all + + subscribers << subscriber + + # Add event subscribers for all existing methods on the class. + fetch_public_methods(subscriber, inherit_all).each do |event| + add_event_subscriber(event) + end + end + + # Detach the subscriber from a namespace. + def detach_from(namespace, notifier = ActiveSupport::Notifications) + @namespace = namespace + @subscriber = find_attached_subscriber + @notifier = notifier + + return unless subscriber + + subscribers.delete(subscriber) + + # Remove event subscribers of all existing methods on the class. + fetch_public_methods(subscriber, true).each do |event| + remove_event_subscriber(event) + end + + # Reset notifier so that event subscribers will not add for new methods added to the class. + @notifier = nil + end + + # Adds event subscribers for all new methods added to the class. + def method_added(event) + # Only public methods are added as subscribers, and only if a notifier + # has been set up. This means that subscribers will only be set up for + # classes that call #attach_to. + if public_method_defined?(event) && notifier + add_event_subscriber(event) + end + end + + def subscribers + @@subscribers ||= [] + end + + private + attr_reader :subscriber, :notifier, :namespace + + def add_event_subscriber(event) # :doc: + return if invalid_event?(event) + + pattern = prepare_pattern(event) + + # Don't add multiple subscribers (e.g. if methods are redefined). + return if pattern_subscribed?(pattern) + + subscriber.patterns[pattern] = notifier.subscribe(pattern, subscriber) + end + + def remove_event_subscriber(event) # :doc: + return if invalid_event?(event) + + pattern = prepare_pattern(event) + + return unless pattern_subscribed?(pattern) + + notifier.unsubscribe(subscriber.patterns[pattern]) + subscriber.patterns.delete(pattern) + end + + def find_attached_subscriber + subscribers.find { |attached_subscriber| attached_subscriber.instance_of?(self) } + end + + def invalid_event?(event) + %i{ start finish }.include?(event.to_sym) + end + + def prepare_pattern(event) + "#{event}.#{namespace}" + end + + def pattern_subscribed?(pattern) + subscriber.patterns.key?(pattern) + end + + def fetch_public_methods(subscriber, inherit_all) + subscriber.public_methods(inherit_all) - Subscriber.public_instance_methods(true) + end + end + + attr_reader :patterns # :nodoc: + + def initialize + @queue_key = [self.class.name, object_id].join "-" + @patterns = {} + super + end + + def start(name, id, payload) + event = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload) + event.start! + parent = event_stack.last + parent << event if parent + + event_stack.push event + end + + def finish(name, id, payload) + event = event_stack.pop + event.finish! + event.payload.merge!(payload) + + method = name.split(".").first + send(method, event) + end + + def publish_event(event) # :nodoc: + method = event.name.split(".").first + send(method, event) + end + + private + def event_stack + registry = ActiveSupport::IsolatedExecutionState[:active_support_subscriber_queue_registry] ||= {} + registry[@queue_key] ||= [] + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/tagged_logging.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/tagged_logging.rb new file mode 100644 index 0000000000..26852e590a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/tagged_logging.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/blank" +require "logger" +require "active_support/logger" + +module ActiveSupport + # Wraps any standard Logger object to provide tagging capabilities. + # + # May be called with a block: + # + # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff" + # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff" + # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff" + # + # If called without a block, a new logger will be returned with applied tags: + # + # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + # logger.tagged("BCX").info "Stuff" # Logs "[BCX] Stuff" + # logger.tagged("BCX", "Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff" + # logger.tagged("BCX").tagged("Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff" + # + # This is used by the default Rails.logger as configured by Railties to make + # it easy to stamp log lines with subdomains, request ids, and anything else + # to aid debugging of multi-user production applications. + module TaggedLogging + module Formatter # :nodoc: + # This method is invoked when a log event occurs. + def call(severity, timestamp, progname, msg) + super(severity, timestamp, progname, "#{tags_text}#{msg}") + end + + def tagged(*tags) + new_tags = push_tags(*tags) + yield self + ensure + pop_tags(new_tags.size) + end + + def push_tags(*tags) + tags.flatten! + tags.reject!(&:blank?) + current_tags.concat tags + tags + end + + def pop_tags(size = 1) + current_tags.pop size + end + + def clear_tags! + current_tags.clear + end + + def current_tags + # We use our object ID here to avoid conflicting with other instances + thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}" + IsolatedExecutionState[thread_key] ||= [] + end + + def tags_text + tags = current_tags + if tags.one? + "[#{tags[0]}] " + elsif tags.any? + tags.collect { |tag| "[#{tag}] " }.join + end + end + end + + module LocalTagStorage # :nodoc: + attr_accessor :current_tags + + def self.extended(base) + base.current_tags = [] + end + end + + def self.new(logger) + logger = logger.clone + + if logger.formatter + logger.formatter = logger.formatter.dup + else + # Ensure we set a default formatter so we aren't extending nil! + logger.formatter = ActiveSupport::Logger::SimpleFormatter.new + end + + logger.formatter.extend Formatter + logger.extend(self) + end + + delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter + + def tagged(*tags) + if block_given? + formatter.tagged(*tags) { yield self } + else + logger = ActiveSupport::TaggedLogging.new(self) + logger.formatter.extend LocalTagStorage + logger.push_tags(*formatter.current_tags, *tags) + logger + end + end + + def flush + clear_tags! + super if defined?(super) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/test_case.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/test_case.rb new file mode 100644 index 0000000000..2df7b8033d --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/test_case.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +gem "minitest" # make sure we get the gem, not stdlib +require "minitest" +require "active_support/testing/tagged_logging" +require "active_support/testing/setup_and_teardown" +require "active_support/testing/assertions" +require "active_support/testing/deprecation" +require "active_support/testing/declarative" +require "active_support/testing/isolation" +require "active_support/testing/constant_lookup" +require "active_support/testing/time_helpers" +require "active_support/testing/file_fixtures" +require "active_support/testing/parallelization" +require "active_support/testing/parallelize_executor" +require "concurrent/utility/processor_counter" + +module ActiveSupport + class TestCase < ::Minitest::Test + Assertion = Minitest::Assertion + + class << self + # Sets the order in which test cases are run. + # + # ActiveSupport::TestCase.test_order = :random # => :random + # + # Valid values are: + # * +:random+ (to run tests in random order) + # * +:parallel+ (to run tests in parallel) + # * +:sorted+ (to run tests alphabetically by method name) + # * +:alpha+ (equivalent to +:sorted+) + def test_order=(new_order) + ActiveSupport.test_order = new_order + end + + # Returns the order in which test cases are run. + # + # ActiveSupport::TestCase.test_order # => :random + # + # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+. + # Defaults to +:random+. + def test_order + ActiveSupport.test_order ||= :random + end + + # Parallelizes the test suite. + # + # Takes a +workers+ argument that controls how many times the process + # is forked. For each process a new database will be created suffixed + # with the worker number. + # + # test-database-0 + # test-database-1 + # + # If ENV["PARALLEL_WORKERS"] is set the workers argument will be ignored + # and the environment variable will be used instead. This is useful for CI + # environments, or other environments where you may need more workers than + # you do for local testing. + # + # If the number of workers is set to +1+ or fewer, the tests will not be + # parallelized. + # + # If +workers+ is set to +:number_of_processors+, the number of workers will be + # set to the actual core count on the machine you are on. + # + # The default parallelization method is to fork processes. If you'd like to + # use threads instead you can pass with: :threads to the +parallelize+ + # method. Note the threaded parallelization does not create multiple + # database and will not work with system tests at this time. + # + # parallelize(workers: :number_of_processors, with: :threads) + # + # The threaded parallelization uses minitest's parallel executor directly. + # The processes parallelization uses a Ruby DRb server. + # + # Because parallelization presents an overhead, it is only enabled when the + # number of tests to run is above the +threshold+ param. The default value is + # 50, and it's configurable via +config.active_support.test_parallelization_threshold+. + def parallelize(workers: :number_of_processors, with: :processes, threshold: ActiveSupport.test_parallelization_threshold) + workers = Concurrent.physical_processor_count if workers == :number_of_processors + workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] + + return if workers <= 1 + + Minitest.parallel_executor = ActiveSupport::Testing::ParallelizeExecutor.new(size: workers, with: with, threshold: threshold) + end + + # Set up hook for parallel testing. This can be used if you have multiple + # databases or any behavior that needs to be run after the process is forked + # but before the tests run. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_setup do + # # create databases + # end + # end + def parallelize_setup(&block) + ActiveSupport::Testing::Parallelization.after_fork_hook(&block) + end + + # Clean up hook for parallel testing. This can be used to drop databases + # if your app uses multiple write/read databases or other clean up before + # the tests finish. This runs before the forked process is closed. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_teardown do + # # drop databases + # end + # end + def parallelize_teardown(&block) + ActiveSupport::Testing::Parallelization.run_cleanup_hook(&block) + end + end + + alias_method :method_name, :name + + include ActiveSupport::Testing::TaggedLogging + prepend ActiveSupport::Testing::SetupAndTeardown + include ActiveSupport::Testing::Assertions + include ActiveSupport::Testing::Deprecation + include ActiveSupport::Testing::TimeHelpers + include ActiveSupport::Testing::FileFixtures + extend ActiveSupport::Testing::Declarative + + # test/unit backwards compatibility methods + alias :assert_raise :assert_raises + alias :assert_not_empty :refute_empty + alias :assert_not_equal :refute_equal + alias :assert_not_in_delta :refute_in_delta + alias :assert_not_in_epsilon :refute_in_epsilon + alias :assert_not_includes :refute_includes + alias :assert_not_instance_of :refute_instance_of + alias :assert_not_kind_of :refute_kind_of + alias :assert_no_match :refute_match + alias :assert_not_nil :refute_nil + alias :assert_not_operator :refute_operator + alias :assert_not_predicate :refute_predicate + alias :assert_not_respond_to :refute_respond_to + alias :assert_not_same :refute_same + + ActiveSupport.run_load_hooks(:active_support_test_case, self) + + def inspect # :nodoc: + Object.instance_method(:to_s).bind_call(self) + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/assertions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/assertions.rb new file mode 100644 index 0000000000..2e48ba1892 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/assertions.rb @@ -0,0 +1,265 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveSupport + module Testing + module Assertions + UNTRACKED = Object.new # :nodoc: + + # Asserts that an expression is not truthy. Passes if object is + # +nil+ or +false+. "Truthy" means "considered true in a conditional" + # like if foo. + # + # assert_not nil # => true + # assert_not false # => true + # assert_not 'foo' # => Expected "foo" to be nil or false + # + # An error message can be specified. + # + # assert_not foo, 'foo should be false' + def assert_not(object, message = nil) + message ||= "Expected #{mu_pp(object)} to be nil or false" + assert !object, message + end + + # Assertion that the block should not raise an exception. + # + # Passes if evaluated code in the yielded block raises no exception. + # + # assert_nothing_raised do + # perform_service(param: 'no_exception') + # end + def assert_nothing_raised + yield + rescue => error + raise Minitest::UnexpectedError.new(error) + end + + # Test numeric difference between the return value of an expression as a + # result of what is evaluated in the yielded block. + # + # assert_difference 'Article.count' do + # post :create, params: { article: {...} } + # end + # + # An arbitrary expression is passed in and evaluated. + # + # assert_difference 'Article.last.comments(:reload).size' do + # post :create, params: { comment: {...} } + # end + # + # An arbitrary positive or negative difference can be specified. + # The default is 1. + # + # assert_difference 'Article.count', -1 do + # post :delete, params: { id: ... } + # end + # + # An array of expressions can also be passed in and evaluated. + # + # assert_difference [ 'Article.count', 'Post.count' ], 2 do + # post :create, params: { article: {...} } + # end + # + # A hash of expressions/numeric differences can also be passed in and evaluated. + # + # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do + # post :create, params: { article: {...} } + # end + # + # A lambda or a list of lambdas can be passed in and evaluated: + # + # assert_difference ->{ Article.count }, 2 do + # post :create, params: { article: {...} } + # end + # + # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do + # post :create, params: { article: {...} } + # end + # + # An error message can be specified. + # + # assert_difference 'Article.count', -1, 'An Article should be destroyed' do + # post :delete, params: { id: ... } + # end + def assert_difference(expression, *args, &block) + expressions = + if expression.is_a?(Hash) + message = args[0] + expression + else + difference = args[0] || 1 + message = args[1] + Array(expression).index_with(difference) + end + + exps = expressions.keys.map { |e| + e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } + } + before = exps.map(&:call) + + retval = _assert_nothing_raised_or_warn("assert_difference", &block) + + expressions.zip(exps, before) do |(code, diff), exp, before_value| + error = "#{code.inspect} didn't change by #{diff}" + error = "#{message}.\n#{error}" if message + assert_equal(before_value + diff, exp.call, error) + end + + retval + end + + # Assertion that the numeric result of evaluating an expression is not + # changed before and after invoking the passed in block. + # + # assert_no_difference 'Article.count' do + # post :create, params: { article: invalid_attributes } + # end + # + # A lambda can be passed in and evaluated. + # + # assert_no_difference -> { Article.count } do + # post :create, params: { article: invalid_attributes } + # end + # + # An error message can be specified. + # + # assert_no_difference 'Article.count', 'An Article should not be created' do + # post :create, params: { article: invalid_attributes } + # end + # + # An array of expressions can also be passed in and evaluated. + # + # assert_no_difference [ 'Article.count', -> { Post.count } ] do + # post :create, params: { article: invalid_attributes } + # end + def assert_no_difference(expression, message = nil, &block) + assert_difference expression, 0, message, &block + end + + # Assertion that the result of evaluating an expression is changed before + # and after invoking the passed in block. + # + # assert_changes 'Status.all_good?' do + # post :create, params: { status: { ok: false } } + # end + # + # You can pass the block as a string to be evaluated in the context of + # the block. A lambda can be passed for the block as well. + # + # assert_changes -> { Status.all_good? } do + # post :create, params: { status: { ok: false } } + # end + # + # The assertion is useful to test side effects. The passed block can be + # anything that can be converted to string with #to_s. + # + # assert_changes :@object do + # @object = 42 + # end + # + # The keyword arguments +:from+ and +:to+ can be given to specify the + # expected initial value and the expected value after the block was + # executed. + # + # assert_changes :@object, from: nil, to: :foo do + # @object = :foo + # end + # + # An error message can be specified. + # + # assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do + # post :create, params: { status: { incident: true } } + # end + def assert_changes(expression, message = nil, from: UNTRACKED, to: UNTRACKED, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = _assert_nothing_raised_or_warn("assert_changes", &block) + + unless from == UNTRACKED + error = "Expected change from #{from.inspect}" + error = "#{message}.\n#{error}" if message + assert from === before, error + end + + after = exp.call + + error = "#{expression.inspect} didn't change" + error = "#{error}. It was already #{to}" if before == to + error = "#{message}.\n#{error}" if message + refute_equal before, after, error + + unless to == UNTRACKED + error = "Expected change to #{to}\n" + error = "#{message}.\n#{error}" if message + assert to === after, error + end + + retval + end + + # Assertion that the result of evaluating an expression is not changed before + # and after invoking the passed in block. + # + # assert_no_changes 'Status.all_good?' do + # post :create, params: { status: { ok: true } } + # end + # + # Provide the optional keyword argument :from to specify the expected + # initial value. + # + # assert_no_changes -> { Status.all_good? }, from: true do + # post :create, params: { status: { ok: true } } + # end + # + # An error message can be specified. + # + # assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do + # post :create, params: { status: { ok: false } } + # end + def assert_no_changes(expression, message = nil, from: UNTRACKED, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = _assert_nothing_raised_or_warn("assert_no_changes", &block) + + unless from == UNTRACKED + error = "Expected initial value of #{from.inspect}" + error = "#{message}.\n#{error}" if message + assert from === before, error + end + + after = exp.call + + error = "#{expression.inspect} changed" + error = "#{message}.\n#{error}" if message + + if before.nil? + assert_nil after, error + else + assert_equal before, after, error + end + + retval + end + + private + def _assert_nothing_raised_or_warn(assertion, &block) + assert_nothing_raised(&block) + rescue Minitest::UnexpectedError => e + if tagged_logger && tagged_logger.warn? + warning = <<~MSG + #{self.class} - #{name}: #{e.error.class} raised. + If you expected this exception, use `assert_raises` as near to the code that raises as possible. + Other block based assertions (e.g. `#{assertion}`) can be used, as long as `assert_raises` is inside their block. + MSG + tagged_logger.warn warning + end + + raise + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/autorun.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/autorun.rb new file mode 100644 index 0000000000..889b41659a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/autorun.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +gem "minitest" + +require "minitest" + +Minitest.autorun diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/constant_lookup.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/constant_lookup.rb similarity index 87% rename from app/server/ruby/vendor/activesupport/lib/active_support/testing/constant_lookup.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/constant_lookup.rb index 1b2a75c35d..51167e9237 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/constant_lookup.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/constant_lookup.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/concern" require "active_support/inflector" @@ -36,19 +38,14 @@ def determine_constant_from_test_name(test_name) while names.size > 0 do names.last.sub!(/Test$/, "") begin - constant = names.join("::").constantize + constant = names.join("::").safe_constantize break(constant) if yield(constant) - rescue NoMethodError # subclass of NameError - raise - rescue NameError - # Constant wasn't found, move on ensure names.pop end end end end - end end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/declarative.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/declarative.rb similarity index 50% rename from app/server/ruby/vendor/activesupport/lib/active_support/testing/declarative.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/declarative.rb index e709e6edf5..7c3403684d 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/declarative.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/declarative.rb @@ -1,30 +1,8 @@ +# frozen_string_literal: true + module ActiveSupport module Testing module Declarative - - def self.extended(klass) #:nodoc: - klass.class_eval do - - unless method_defined?(:describe) - def self.describe(text) - if block_given? - super - else - message = "`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n" - ActiveSupport::Deprecation.warn message - - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def self.name - "#{text}" - end - RUBY_EVAL - end - end - end - - end - end - unless defined?(Spec) # Helper to define a test method using a String. Under the hood, it replaces # spaces with underscores and defines the test method. @@ -33,7 +11,7 @@ def self.name # ... # end def test(name, &block) - test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym + test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym defined = method_defined? test_name raise "#{test_name} is already defined in #{self}" if defined if block_given? diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/deprecation.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/deprecation.rb new file mode 100644 index 0000000000..f762d24377 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/deprecation.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "active_support/deprecation" + +module ActiveSupport + module Testing + module Deprecation + # Asserts that a matching deprecation warning was emitted by the given deprecator during the execution of the yielded block. + # + # assert_deprecated(/foo/, CustomDeprecator) do + # CustomDeprecator.warn "foo should no longer be used" + # end + # + # The +match+ object may be a +Regexp+, or +String+ appearing in the message. + # + # assert_deprecated('foo', CustomDeprecator) do + # CustomDeprecator.warn "foo should no longer be used" + # end + # + # If the +match+ is omitted (or explicitly +nil+), any deprecation warning will match. + # + # assert_deprecated(nil, CustomDeprecator) do + # CustomDeprecator.warn "foo should no longer be used" + # end + # + # If no +deprecator+ is given, defaults to ActiveSupport::Deprecation. + # + # assert_deprecated do + # ActiveSupport::Deprecation.warn "foo should no longer be used" + # end + def assert_deprecated(match = nil, deprecator = nil, &block) + result, warnings = collect_deprecations(deprecator, &block) + assert !warnings.empty?, "Expected a deprecation warning within the block but received none" + if match + match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp) + assert warnings.any? { |w| match.match?(w) }, "No deprecation warning matched #{match}: #{warnings.join(', ')}" + end + result + end + + # Asserts that no deprecation warnings are emitted by the given deprecator during the execution of the yielded block. + # + # assert_not_deprecated(CustomDeprecator) do + # CustomDeprecator.warn "message" # fails assertion + # end + # + # If no +deprecator+ is given, defaults to ActiveSupport::Deprecation. + # + # assert_not_deprecated do + # ActiveSupport::Deprecation.warn "message" # fails assertion + # end + # + # assert_not_deprecated do + # CustomDeprecator.warn "message" # passes assertion + # end + def assert_not_deprecated(deprecator = nil, &block) + result, deprecations = collect_deprecations(deprecator, &block) + assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}" + result + end + + # Returns an array of all the deprecation warnings emitted by the given + # +deprecator+ during the execution of the yielded block. + # + # collect_deprecations(CustomDeprecator) do + # CustomDeprecator.warn "message" + # end # => ["message"] + # + # If no +deprecator+ is given, defaults to ActiveSupport::Deprecation. + # + # collect_deprecations do + # CustomDeprecator.warn "custom message" + # ActiveSupport::Deprecation.warn "message" + # end # => ["message"] + def collect_deprecations(deprecator = nil) + deprecator ||= ActiveSupport::Deprecation + old_behavior = deprecator.behavior + deprecations = [] + deprecator.behavior = Proc.new do |message, callstack| + deprecations << message + end + result = yield + [result, deprecations] + ensure + deprecator.behavior = old_behavior + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/file_fixtures.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/file_fixtures.rb new file mode 100644 index 0000000000..4eb7a88576 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/file_fixtures.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module ActiveSupport + module Testing + # Adds simple access to sample files called file fixtures. + # File fixtures are normal files stored in + # ActiveSupport::TestCase.file_fixture_path. + # + # File fixtures are represented as +Pathname+ objects. + # This makes it easy to extract specific information: + # + # file_fixture("example.txt").read # get the file's content + # file_fixture("example.mp3").size # get the file size + module FileFixtures + extend ActiveSupport::Concern + + included do + class_attribute :file_fixture_path, instance_writer: false + end + + # Returns a +Pathname+ to the fixture file named +fixture_name+. + # + # Raises +ArgumentError+ if +fixture_name+ can't be found. + def file_fixture(fixture_name) + path = Pathname.new(File.join(file_fixture_path, fixture_name)) + + if path.exist? + path + else + msg = "the directory '%s' does not contain a file named '%s'" + raise ArgumentError, msg % [file_fixture_path, fixture_name] + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/isolation.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/isolation.rb new file mode 100644 index 0000000000..378e7cdc63 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/isolation.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Isolation + require "thread" + + def self.included(klass) # :nodoc: + klass.class_eval do + parallelize_me! + end + end + + def self.forking_env? + !ENV["NO_FORK"] && Process.respond_to?(:fork) + end + + def run + serialized = run_in_isolation do + super + end + + Marshal.load(serialized) + end + + module Forking + def run_in_isolation(&blk) + IO.pipe do |read, write| + read.binmode + write.binmode + + pid = fork do + read.close + yield + begin + if error? + failures.map! { |e| + begin + Marshal.dump e + e + rescue TypeError + ex = Exception.new e.message + ex.set_backtrace e.backtrace + Minitest::UnexpectedError.new ex + end + } + end + test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup + result = Marshal.dump(test_result) + end + + write.puts [result].pack("m") + exit! + end + + write.close + result = read.read + Process.wait2(pid) + result.unpack1("m") + end + end + end + + module Subprocess + ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV) + + # Complicated H4X to get this working in windows / jruby with + # no forking. + def run_in_isolation(&blk) + require "tempfile" + + if ENV["ISOLATION_TEST"] + yield + test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup + File.open(ENV["ISOLATION_OUTPUT"], "w") do |file| + file.puts [Marshal.dump(test_result)].pack("m") + end + exit! + else + Tempfile.open("isolation") do |tmpfile| + env = { + "ISOLATION_TEST" => self.class.name, + "ISOLATION_OUTPUT" => tmpfile.path + } + + test_opts = "-n#{self.class.name}##{name}" + + load_path_args = [] + $-I.each do |p| + load_path_args << "-I" + load_path_args << File.expand_path(p) + end + + child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts]) + + begin + Process.wait(child.pid) + rescue Errno::ECHILD # The child process may exit before we wait + nil + end + + return tmpfile.read.unpack1("m") + end + end + end + end + + include forking_env? ? Forking : Subprocess + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/method_call_assertions.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/method_call_assertions.rb new file mode 100644 index 0000000000..1d016b096a --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/method_call_assertions.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "minitest/mock" + +module ActiveSupport + module Testing + module MethodCallAssertions # :nodoc: + private + def assert_called(object, method_name, message = nil, times: 1, returns: nil, &block) + times_called = 0 + + object.stub(method_name, proc { times_called += 1; returns }, &block) + + error = "Expected #{method_name} to be called #{times} times, " \ + "but was called #{times_called} times" + error = "#{message}.\n#{error}" if message + assert_equal times, times_called, error + end + + def assert_called_with(object, method_name, args, returns: nil, &block) + mock = Minitest::Mock.new + + if args.all?(Array) + args.each { |arg| mock.expect(:call, returns, arg) } + else + mock.expect(:call, returns, args) + end + + object.stub(method_name, mock, &block) + + mock.verify + end + + def assert_not_called(object, method_name, message = nil, &block) + assert_called(object, method_name, message, times: 0, &block) + end + + def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil) + times_called = 0 + klass.define_method("stubbed_#{method_name}") do |*| + times_called += 1 + + returns + end + + klass.alias_method "original_#{method_name}", method_name + klass.alias_method method_name, "stubbed_#{method_name}" + + yield + + error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times" + error = "#{message}.\n#{error}" if message + + assert_equal times, times_called, error + ensure + klass.alias_method method_name, "original_#{method_name}" + klass.undef_method "original_#{method_name}" + klass.undef_method "stubbed_#{method_name}" + end + + def assert_not_called_on_instance_of(klass, method_name, message = nil, &block) + assert_called_on_instance_of(klass, method_name, message, times: 0, &block) + end + + def stub_any_instance(klass, instance: klass.new) + klass.stub(:new, instance) { yield instance } + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization.rb new file mode 100644 index 0000000000..d1b2734ca1 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" unless Gem.win_platform? +require "active_support/core_ext/module/attribute_accessors" +require "active_support/testing/parallelization/server" +require "active_support/testing/parallelization/worker" + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + @@after_fork_hooks = [] + + def self.after_fork_hook(&blk) + @@after_fork_hooks << blk + end + + cattr_reader :after_fork_hooks + + @@run_cleanup_hooks = [] + + def self.run_cleanup_hook(&blk) + @@run_cleanup_hooks << blk + end + + cattr_reader :run_cleanup_hooks + + def initialize(worker_count) + @worker_count = worker_count + @queue_server = Server.new + @worker_pool = [] + @url = DRb.start_service("drbunix:", @queue_server).uri + end + + def start + @worker_pool = @worker_count.times.map do |worker| + Worker.new(worker, @url).start + end + end + + def <<(work) + @queue_server << work + end + + def size + @worker_count + end + + def shutdown + @queue_server.shutdown + @worker_pool.each { |pid| Process.waitpid pid } + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization/server.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization/server.rb new file mode 100644 index 0000000000..3961367c24 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization/server.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" unless Gem.win_platform? + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + class Server + include DRb::DRbUndumped + + def initialize + @queue = Queue.new + @active_workers = Concurrent::Map.new + @in_flight = Concurrent::Map.new + end + + def record(reporter, result) + raise DRb::DRbConnError if result.is_a?(DRb::DRbUnknown) + + @in_flight.delete([result.klass, result.name]) + + reporter.synchronize do + reporter.record(result) + end + end + + def <<(o) + o[2] = DRbObject.new(o[2]) if o + @queue << o + end + + def pop + if test = @queue.pop + @in_flight[[test[0].to_s, test[1]]] = test + test + end + end + + def start_worker(worker_id) + @active_workers[worker_id] = true + end + + def stop_worker(worker_id) + @active_workers.delete(worker_id) + end + + def active_workers? + @active_workers.size > 0 + end + + def interrupt + @queue.clear + end + + def shutdown + # Wait for initial queue to drain + while @queue.length != 0 + sleep 0.1 + end + + @queue.close + + # Wait until all workers have finished + while active_workers? + sleep 0.1 + end + + @in_flight.values.each do |(klass, name, reporter)| + result = Minitest::Result.from(klass.new(name)) + error = RuntimeError.new("result not reported") + error.set_backtrace([""]) + result.failures << Minitest::UnexpectedError.new(error) + reporter.synchronize do + reporter.record(result) + end + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization/worker.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization/worker.rb new file mode 100644 index 0000000000..393355a25f --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelization/worker.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + class Worker + def initialize(number, url) + @id = SecureRandom.uuid + @number = number + @url = url + @setup_exception = nil + end + + def start + fork do + set_process_title("(starting)") + + DRb.stop_service + + @queue = DRbObject.new_with_uri(@url) + @queue.start_worker(@id) + + begin + after_fork + rescue => @setup_exception; end + + work_from_queue + ensure + set_process_title("(stopping)") + + run_cleanup + @queue.stop_worker(@id) + end + end + + def work_from_queue + while job = @queue.pop + perform_job(job) + end + end + + def perform_job(job) + klass = job[0] + method = job[1] + reporter = job[2] + + set_process_title("#{klass}##{method}") + + result = klass.with_info_handler reporter do + Minitest.run_one_method(klass, method) + end + + safe_record(reporter, result) + end + + def safe_record(reporter, result) + add_setup_exception(result) if @setup_exception + + begin + @queue.record(reporter, result) + rescue DRb::DRbConnError + result.failures.map! do |failure| + if failure.respond_to?(:error) + # minitest >5.14.0 + error = DRb::DRbRemoteError.new(failure.error) + else + error = DRb::DRbRemoteError.new(failure.exception) + end + Minitest::UnexpectedError.new(error) + end + @queue.record(reporter, result) + rescue Interrupt + @queue.interrupt + raise + end + + set_process_title("(idle)") + end + + def after_fork + Parallelization.after_fork_hooks.each do |cb| + cb.call(@number) + end + end + + def run_cleanup + Parallelization.run_cleanup_hooks.each do |cb| + cb.call(@number) + end + end + + private + def add_setup_exception(result) + result.failures.prepend Minitest::UnexpectedError.new(@setup_exception) + end + + def set_process_title(status) + Process.setproctitle("Rails test worker #{@number} - #{status}") + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelize_executor.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelize_executor.rb new file mode 100644 index 0000000000..88299db3c0 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/parallelize_executor.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + class ParallelizeExecutor # :nodoc: + attr_reader :size, :parallelize_with, :threshold + + def initialize(size:, with:, threshold: ActiveSupport.test_parallelization_threshold) + @size = size + @parallelize_with = with + @threshold = threshold + end + + def start + parallelize if should_parallelize? + show_execution_info + + parallel_executor.start if parallelized? + end + + def <<(work) + parallel_executor << work if parallelized? + end + + def shutdown + parallel_executor.shutdown if parallelized? + end + + private + def parallel_executor + @parallel_executor ||= build_parallel_executor + end + + def build_parallel_executor + case parallelize_with + when :processes + Testing::Parallelization.new(size) + when :threads + ActiveSupport::TestCase.lock_threads = false if defined?(ActiveSupport::TestCase.lock_threads) + Minitest::Parallel::Executor.new(size) + else + raise ArgumentError, "#{parallelize_with} is not a supported parallelization executor." + end + end + + def parallelize + @parallelized = true + Minitest::Test.parallelize_me! + end + + def parallelized? + @parallelized if defined?(@parallelized) + end + + def should_parallelize? + ENV["PARALLEL_WORKERS"] || tests_count > threshold + end + + def tests_count + @tests_count ||= Minitest::Runnable.runnables.sum { |runnable| runnable.runnable_methods.size } + end + + def show_execution_info + puts execution_info + end + + def execution_info + if parallelized? + "Running #{tests_count} tests in parallel using #{parallel_executor.size} #{parallelize_with}" + else + "Running #{tests_count} tests in a single process (parallelization threshold is #{threshold})" + end + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/setup_and_teardown.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/setup_and_teardown.rb similarity index 73% rename from app/server/ruby/vendor/activesupport/lib/active_support/testing/setup_and_teardown.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/setup_and_teardown.rb index 33f2b8dc9b..35321cd157 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/setup_and_teardown.rb @@ -1,5 +1,6 @@ -require 'active_support/concern' -require 'active_support/callbacks' +# frozen_string_literal: true + +require "active_support/callbacks" module ActiveSupport module Testing @@ -17,11 +18,10 @@ module Testing # end # end module SetupAndTeardown - extend ActiveSupport::Concern - - included do - include ActiveSupport::Callbacks - define_callbacks :setup, :teardown + def self.prepended(klass) + klass.include ActiveSupport::Callbacks + klass.define_callbacks :setup, :teardown + klass.extend ClassMethods end module ClassMethods @@ -42,7 +42,12 @@ def before_setup # :nodoc: end def after_teardown # :nodoc: - run_callbacks :teardown + begin + run_callbacks :teardown + rescue => e + self.failures << Minitest::UnexpectedError.new(e) + end + super end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/stream.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/stream.rb new file mode 100644 index 0000000000..55017d3535 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/stream.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Stream # :nodoc: + private + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(IO::NULL) + stream.sync = true + yield + ensure + stream.reopen(old_stream) + old_stream.close + end + + def quietly(&block) + silence_stream(STDOUT) do + silence_stream(STDERR, &block) + end + end + + def capture(stream) + stream = stream.to_s + captured_stream = Tempfile.new(stream) + stream_io = eval("$#{stream}") + origin_stream = stream_io.dup + stream_io.reopen(captured_stream) + + yield + + stream_io.rewind + captured_stream.read + ensure + captured_stream.close + captured_stream.unlink + stream_io.reopen(origin_stream) + end + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/tagged_logging.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/tagged_logging.rb similarity index 78% rename from app/server/ruby/vendor/activesupport/lib/active_support/testing/tagged_logging.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/tagged_logging.rb index f4cee64091..7d38268b7f 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/tagged_logging.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/tagged_logging.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + module ActiveSupport module Testing # Logs a "PostsControllerTest: test name" heading before each test to # make test.log easier to search and follow along with. - module TaggedLogging #:nodoc: + module TaggedLogging # :nodoc: attr_writer :tagged_logger def before_setup - if tagged_logger + if tagged_logger && tagged_logger.info? heading = "#{self.class}: #{name}" - divider = '-' * heading.size + divider = "-" * heading.size tagged_logger.info divider tagged_logger.info heading tagged_logger.info divider diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/time_helpers.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/time_helpers.rb new file mode 100644 index 0000000000..a1155bf770 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/testing/time_helpers.rb @@ -0,0 +1,246 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/time/calculations" +require "concurrent/map" + +module ActiveSupport + module Testing + # Manages stubs for TimeHelpers + class SimpleStubs # :nodoc: + Stub = Struct.new(:object, :method_name, :original_method) + + def initialize + @stubs = Concurrent::Map.new { |h, k| h[k] = {} } + end + + # Stubs object.method_name with the given block + # If the method is already stubbed, remove that stub + # so that removing this stub will restore the original implementation. + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # target = Time.zone.local(2004, 11, 24, 1, 4, 44) + # simple_stubs.stub_object(Time, :now) { at(target.to_i) } + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + def stub_object(object, method_name, &block) + if stub = stubbing(object, method_name) + unstub_object(stub) + end + + new_name = "__simple_stub__#{method_name}" + + @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name) + + object.singleton_class.alias_method new_name, method_name + object.define_singleton_method(method_name, &block) + end + + # Remove all object-method stubs held by this instance + def unstub_all! + @stubs.each_value do |object_stubs| + object_stubs.each_value do |stub| + unstub_object(stub) + end + end + @stubs.clear + end + + # Returns the Stub for object#method_name + # (nil if it is not stubbed) + def stubbing(object, method_name) + @stubs[object.object_id][method_name] + end + + # Returns true if any stubs are set, false if there are none + def stubbed? + !@stubs.empty? + end + + private + # Restores the original object.method described by the Stub + def unstub_object(stub) + singleton_class = stub.object.singleton_class + singleton_class.silence_redefinition_of_method stub.method_name + singleton_class.alias_method stub.method_name, stub.original_method + singleton_class.undef_method stub.original_method + end + end + + # Contains helpers that help you test passage of time. + module TimeHelpers + def after_teardown + travel_back + super + end + + # Changes current time to the time in the future or in the past by a given time difference by + # stubbing +Time.now+, +Date.today+, and +DateTime.now+. The stubs are automatically removed + # at the end of the test. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel 1.day + # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # Date.current # => Sun, 10 Nov 2013 + # DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500 + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel 1.day do + # User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # end + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel(duration, &block) + travel_to Time.now + duration, &block + end + + # Changes current time to the given time by stubbing +Time.now+, + # +Date.today+, and +DateTime.now+ to return the time or date passed into this method. + # The stubs are automatically removed at the end of the test. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # Date.current # => Wed, 24 Nov 2004 + # DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500 + # + # Dates are taken as their timestamp at the beginning of the day in the + # application time zone. Time.current returns said timestamp, + # and Time.now its equivalent in the system time zone. Similarly, + # Date.current returns a date equal to the argument, and + # Date.today the date according to Time.now, which may + # be different. (Note that you rarely want to deal with Time.now, + # or Date.today, in order to honor the application time zone + # please always use Time.current and Date.current.) + # + # Note that the usec for the time passed will be set to 0 to prevent rounding + # errors with external services, like MySQL (which will round instead of floor, + # leading to off-by-one-second errors). + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) do + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # end + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel_to(date_or_time) + if block_given? && in_block + travel_to_nested_block_call = <<~MSG + + Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing. + + Instead of: + + travel_to 2.days.from_now do + # 2 days from today + travel_to 3.days.from_now do + # 5 days from today + end + end + + preferred way to achieve above is: + + travel 2.days do + # 2 days from today + end + + travel 5.days do + # 5 days from today + end + + MSG + raise travel_to_nested_block_call + end + + if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime) + now = date_or_time.midnight.to_time + elsif date_or_time.is_a?(String) + now = Time.zone.parse(date_or_time) + else + now = date_or_time.to_time.change(usec: 0) + end + + stubbed_time = Time.now if simple_stubs.stubbing(Time, :now) + simple_stubs.stub_object(Time, :now) { at(now.to_i) } + simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) } + simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) } + + if block_given? + begin + self.in_block = true + yield + ensure + if stubbed_time + travel_to stubbed_time + else + travel_back + end + self.in_block = false + end + end + end + + # Returns the current time back to its original state, by removing the stubs added by + # +travel+, +travel_to+, and +freeze_time+. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # + # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # + # travel_back + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # + # This method also accepts a block, which brings the stubs back at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # + # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # + # travel_back do + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # end + # + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + def travel_back + stubbed_time = Time.current if block_given? && simple_stubs.stubbed? + + simple_stubs.unstub_all! + yield if block_given? + ensure + travel_to stubbed_time if stubbed_time + end + alias_method :unfreeze_time, :travel_back + + # Calls +travel_to+ with +Time.now+. + # + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # freeze_time + # sleep(1) + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # freeze_time do + # sleep(1) + # User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # end + # Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00 + def freeze_time(&block) + travel_to Time.now, &block + end + + private + def simple_stubs + @simple_stubs ||= SimpleStubs.new + end + + attr_accessor :in_block + end + end +end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/time.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/time.rb new file mode 100644 index 0000000000..51854675bf --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/time.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveSupport + autoload :Duration, "active_support/duration" + autoload :TimeWithZone, "active_support/time_with_zone" + autoload :TimeZone, "active_support/values/time_zone" +end + +require "date" +require "time" + +require "active_support/core_ext/time" +require "active_support/core_ext/date" +require "active_support/core_ext/date_time" + +require "active_support/core_ext/integer/time" +require "active_support/core_ext/numeric/time" + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/zones" diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/time_with_zone.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/time_with_zone.rb new file mode 100644 index 0000000000..f2b23a9ab7 --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/time_with_zone.rb @@ -0,0 +1,625 @@ +# frozen_string_literal: true + +require "yaml" + +require "active_support/duration" +require "active_support/values/time_zone" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date_and_time/compatibility" + +module ActiveSupport + # A Time-like class that can represent a time in any time zone. Necessary + # because standard Ruby Time instances are limited to UTC and the + # system's ENV['TZ'] zone. + # + # You shouldn't ever need to create a TimeWithZone instance directly via +new+. + # Instead use methods +local+, +parse+, +at+, and +now+ on TimeZone instances, + # and +in_time_zone+ on Time and DateTime instances. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00 + # Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00 + # Time.zone.at(1171139445) # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00 + # Time.zone.now # => Sun, 18 May 2008 13:07:55.754107581 EDT -04:00 + # Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00 + # + # See Time and TimeZone for further documentation of these methods. + # + # TimeWithZone instances implement the same API as Ruby Time instances, so + # that Time and TimeWithZone instances are interchangeable. + # + # t = Time.zone.now # => Sun, 18 May 2008 13:27:25.031505668 EDT -04:00 + # t.hour # => 13 + # t.dst? # => true + # t.utc_offset # => -14400 + # t.zone # => "EDT" + # t.to_fs(:rfc822) # => "Sun, 18 May 2008 13:27:25 -0400" + # t + 1.day # => Mon, 19 May 2008 13:27:25.031505668 EDT -04:00 + # t.beginning_of_year # => Tue, 01 Jan 2008 00:00:00.000000000 EST -05:00 + # t > Time.utc(1999) # => true + # t.is_a?(Time) # => true + # t.is_a?(ActiveSupport::TimeWithZone) # => true + class TimeWithZone + # Report class name as 'Time' to thwart type checking. + def self.name + ActiveSupport::Deprecation.warn(<<~EOM) + ActiveSupport::TimeWithZone.name has been deprecated and + from Rails 7.1 will use the default Ruby implementation. + You can set `config.active_support.remove_deprecated_time_with_zone_name = true` + to enable the new behavior now. + EOM + + "Time" + end + + PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N" } + PRECISIONS[0] = "%FT%T" + + include Comparable, DateAndTime::Compatibility + attr_reader :time_zone + + def initialize(utc_time, time_zone, local_time = nil, period = nil) + @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil + @time_zone, @time = time_zone, local_time + @period = @utc ? period : get_period_and_ensure_valid_local_time(period) + end + + # Returns a Time instance that represents the time in +time_zone+. + def time + @time ||= incorporate_utc_offset(@utc, utc_offset) + end + + # Returns a Time instance of the simultaneous time in the UTC timezone. + def utc + @utc ||= incorporate_utc_offset(@time, -utc_offset) + end + alias_method :comparable_time, :utc + alias_method :getgm, :utc + alias_method :getutc, :utc + alias_method :gmtime, :utc + + # Returns the underlying TZInfo::TimezonePeriod. + def period + @period ||= time_zone.period_for_utc(@utc) + end + + # Returns the simultaneous time in Time.zone, or the specified zone. + def in_time_zone(new_zone = ::Time.zone) + return self if time_zone == new_zone + utc.in_time_zone(new_zone) + end + + # Returns a Time instance of the simultaneous time in the system timezone. + def localtime(utc_offset = nil) + utc.getlocal(utc_offset) + end + alias_method :getlocal, :localtime + + # Returns true if the current time is within Daylight Savings Time for the + # specified time zone. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.parse("2012-5-30").dst? # => true + # Time.zone.parse("2012-11-30").dst? # => false + def dst? + period.dst? + end + alias_method :isdst, :dst? + + # Returns true if the current time zone is set to UTC. + # + # Time.zone = 'UTC' # => 'UTC' + # Time.zone.now.utc? # => true + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.now.utc? # => false + def utc? + zone == "UTC" || zone == "UCT" + end + alias_method :gmt?, :utc? + + # Returns the offset from current time to UTC time in seconds. + def utc_offset + period.observed_utc_offset + end + alias_method :gmt_offset, :utc_offset + alias_method :gmtoff, :utc_offset + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" + # Time.zone.now.formatted_offset(true) # => "-05:00" + # Time.zone.now.formatted_offset(false) # => "-0500" + # Time.zone = 'UTC' # => "UTC" + # Time.zone.now.formatted_offset(true, "0") # => "0" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon) + end + + # Returns the time zone abbreviation. + # + # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" + # Time.zone.now.zone # => "EST" + def zone + period.abbreviation + end + + # Returns a string of the object's date, time, zone, and offset from UTC. + # + # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25.624541392 EST -05:00" + def inspect + "#{time.strftime('%a, %d %b %Y %H:%M:%S.%9N')} #{zone} #{formatted_offset}" + end + + # Returns a string of the object's date and time in the ISO 8601 standard + # format. + # + # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00" + def xmlschema(fraction_digits = 0) + "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z')}" + end + alias_method :iso8601, :xmlschema + alias_method :rfc3339, :xmlschema + + # Coerces time to a string for JSON encoding. The default format is ISO 8601. + # You can get %Y/%m/%d %H:%M:%S +offset style by setting + # ActiveSupport::JSON::Encoding.use_standard_json_time_format + # to +false+. + # + # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true + # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json + # # => "2005-02-01T05:15:10.000-10:00" + # + # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false + # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json + # # => "2005/02/01 05:15:10 -1000" + def as_json(options = nil) + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) + end + end + + def init_with(coder) # :nodoc: + initialize(coder["utc"], coder["zone"], coder["time"]) + end + + def encode_with(coder) # :nodoc: + coder.map = { "utc" => utc, "zone" => time_zone, "time" => time } + end + + # Returns a string of the object's date and time in the format used by + # HTTP requests. + # + # Time.zone.now.httpdate # => "Tue, 01 Jan 2013 04:39:43 GMT" + def httpdate + utc.httpdate + end + + # Returns a string of the object's date and time in the RFC 2822 standard + # format. + # + # Time.zone.now.rfc2822 # => "Tue, 01 Jan 2013 04:51:39 +0000" + def rfc2822 + to_fs(:rfc822) + end + alias_method :rfc822, :rfc2822 + + NOT_SET = Object.new # :nodoc: + + # Returns a string of the object's date and time. + def to_s(format = NOT_SET) + if format == :db + ActiveSupport::Deprecation.warn( + "TimeWithZone#to_s(:db) is deprecated. Please use TimeWithZone#to_fs(:db) instead." + ) + utc.to_fs(format) + elsif formatter = ::Time::DATE_FORMATS[format] + ActiveSupport::Deprecation.warn( + "TimeWithZone#to_s(#{format.inspect}) is deprecated. Please use TimeWithZone#to_fs(#{format.inspect}) instead." + ) + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + elsif format == NOT_SET + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format + else + ActiveSupport::Deprecation.warn( + "TimeWithZone#to_s(#{format.inspect}) is deprecated. Please use TimeWithZone#to_fs(#{format.inspect}) instead." + ) + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format + end + end + + # Returns a string of the object's date and time. + # + # This method is aliased to to_formatted_s. + # + # Accepts an optional format: + # * :default - default value, mimics Ruby Time#to_s format. + # * :db - format outputs time in UTC :db time. See Time#to_fs(:db). + # * Any key in Time::DATE_FORMATS can be used. See active_support/core_ext/time/conversions.rb. + def to_fs(format = :default) + if format == :db + utc.to_fs(format) + elsif formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + # Change to to_s when deprecation is gone. + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" + end + end + alias_method :to_formatted_s, :to_fs + + # Replaces %Z directive with +zone before passing to Time#strftime, + # so that zone information is correct. + def strftime(format) + format = format.gsub(/((?:\A|[^%])(?:%%)*)%Z/, "\\1#{zone}") + getlocal(utc_offset).strftime(format) + end + + # Use the time in UTC for comparisons. + def <=>(other) + utc <=> other + end + alias_method :before?, :< + alias_method :after?, :> + + # Returns true if the current object's time is within the specified + # +min+ and +max+ time. + def between?(min, max) + utc.between?(min, max) + end + + # Returns true if the current object's time is in the past. + def past? + utc.past? + end + + # Returns true if the current object's time falls within + # the current day. + def today? + time.today? + end + + # Returns true if the current object's time falls within + # the next day (tomorrow). + def tomorrow? + time.tomorrow? + end + alias :next_day? :tomorrow? + + # Returns true if the current object's time falls within + # the previous day (yesterday). + def yesterday? + time.yesterday? + end + alias :prev_day? :yesterday? + + # Returns true if the current object's time is in the future. + def future? + utc.future? + end + + # Returns +true+ if +other+ is equal to current object. + def eql?(other) + other.eql?(utc) + end + + def hash + utc.hash + end + + # Adds an interval of time to the current object's time and returns that + # value as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00 + # now + 1000 # => Sun, 02 Nov 2014 01:43:08.725182881 EDT -04:00 + # + # If we're adding a Duration of variable length (i.e., years, months, days), + # move forward from #time, otherwise move forward from #utc, for accuracy + # when moving across DST boundaries. + # + # For instance, a time + 24.hours will advance exactly 24 hours, while a + # time + 1.day will advance 23-25 hours, depending on the day. + # + # now + 24.hours # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00 + # now + 1.day # => Mon, 03 Nov 2014 01:26:28.725182881 EST -05:00 + def +(other) + if duration_of_variable_length?(other) + method_missing(:+, other) + else + result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other) + result.in_time_zone(time_zone) + end + end + alias_method :since, :+ + alias_method :in, :+ + + # Subtracts an interval of time and returns a new TimeWithZone object unless + # the other value +acts_like?+ time. In which case, it will subtract the + # other time and return the difference in seconds as a Float. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00 + # now - 1000 # => Mon, 03 Nov 2014 00:09:48.725182881 EST -05:00 + # + # If subtracting a Duration of variable length (i.e., years, months, days), + # move backward from #time, otherwise move backward from #utc, for accuracy + # when moving across DST boundaries. + # + # For instance, a time - 24.hours will go subtract exactly 24 hours, while a + # time - 1.day will subtract 23-25 hours, depending on the day. + # + # now - 24.hours # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00 + # now - 1.day # => Sun, 02 Nov 2014 00:26:28.725182881 EDT -04:00 + # + # If both the TimeWithZone object and the other value act like Time, a Float + # will be returned. + # + # Time.zone.now - 1.day.ago # => 86399.999967 + # + def -(other) + if other.acts_like?(:time) + to_time - other.to_time + elsif duration_of_variable_length?(other) + method_missing(:-, other) + else + result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other) + result.in_time_zone(time_zone) + end + end + + # Subtracts an interval of time from the current object's time and returns + # the result as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00 + # now.ago(1000) # => Mon, 03 Nov 2014 00:09:48.725182881 EST -05:00 + # + # If we're subtracting a Duration of variable length (i.e., years, months, + # days), move backward from #time, otherwise move backward from #utc, for + # accuracy when moving across DST boundaries. + # + # For instance, time.ago(24.hours) will move back exactly 24 hours, + # while time.ago(1.day) will move back 23-25 hours, depending on + # the day. + # + # now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00 + # now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28.725182881 EDT -04:00 + def ago(other) + since(-other) + end + + # Returns a new +ActiveSupport::TimeWithZone+ where one or more of the elements have + # been changed according to the +options+ parameter. The time options (:hour, + # :min, :sec, :usec, :nsec) reset cascadingly, + # so if only the hour is passed, then minute, sec, usec, and nsec is set to 0. If the + # hour and minute is passed, then sec, usec, and nsec is set to 0. The +options+ + # parameter takes a hash with any of these keys: :year, :month, + # :day, :hour, :min, :sec, :usec, + # :nsec, :offset, :zone. Pass either :usec + # or :nsec, not both. Similarly, pass either :zone or + # :offset, not both. + # + # t = Time.zone.now # => Fri, 14 Apr 2017 11:45:15.116992711 EST -05:00 + # t.change(year: 2020) # => Tue, 14 Apr 2020 11:45:15.116992711 EST -05:00 + # t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00.116992711 EST -05:00 + # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00.116992711 EST -05:00 + # t.change(offset: "-10:00") # => Fri, 14 Apr 2017 11:45:15.116992711 HST -10:00 + # t.change(zone: "Hawaii") # => Fri, 14 Apr 2017 11:45:15.116992711 HST -10:00 + def change(options) + if options[:zone] && options[:offset] + raise ArgumentError, "Can't change both :offset and :zone at the same time: #{options.inspect}" + end + + new_time = time.change(options) + + if options[:zone] + new_zone = ::Time.find_zone(options[:zone]) + elsif options[:offset] + new_zone = ::Time.find_zone(new_time.utc_offset) + end + + new_zone ||= time_zone + periods = new_zone.periods_for_local(new_time) + + self.class.new(nil, new_zone, new_time, periods.include?(period) ? period : nil) + end + + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The result is returned as a + # new TimeWithZone object. + # + # The +options+ parameter takes a hash with any of these keys: + # :years, :months, :weeks, :days, + # :hours, :minutes, :seconds. + # + # If advancing by a value of variable length (i.e., years, weeks, months, + # days), move forward from #time, otherwise move forward from #utc, for + # accuracy when moving across DST boundaries. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28.558049687 EDT -04:00 + # now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29.558049687 EDT -04:00 + # now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28.558049687 EDT -04:00 + # now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28.558049687 EST -05:00 + # now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28.558049687 EST -05:00 + # now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28.558049687 EST -05:00 + # now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28.558049687 EST -05:00 + # now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28.558049687 EST -05:00 + def advance(options) + # If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time, + # otherwise advance from #utc, for accuracy when moving across DST boundaries + if options.values_at(:years, :weeks, :months, :days).any? + method_missing(:advance, options) + else + utc.advance(options).in_time_zone(time_zone) + end + end + + %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| + class_eval <<-EOV, __FILE__, __LINE__ + 1 + def #{method_name} # def month + time.#{method_name} # time.month + end # end + EOV + end + + # Returns Array of parts of Time in sequence of + # [seconds, minutes, hours, day, month, year, weekday, yearday, dst?, zone]. + # + # now = Time.zone.now # => Tue, 18 Aug 2015 02:29:27.485278555 UTC +00:00 + # now.to_a # => [27, 29, 2, 18, 8, 2015, 2, 230, false, "UTC"] + def to_a + [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone] + end + + # Returns the object's date and time as a floating-point number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_f # => 1417709320.285418 + def to_f + utc.to_f + end + + # Returns the object's date and time as an integer number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_i # => 1417709320 + def to_i + utc.to_i + end + alias_method :tv_sec, :to_i + + # Returns the object's date and time as a rational number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_r # => (708854548642709/500000) + def to_r + utc.to_r + end + + # Returns an instance of DateTime with the timezone's UTC offset + # + # Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000 + # Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000 + def to_datetime + @to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) + end + + # Returns an instance of +Time+, either with the same UTC offset + # as +self+ or in the local system timezone depending on the setting + # of +ActiveSupport.to_time_preserves_timezone+. + def to_time + if preserve_timezone + @to_time_with_instance_offset ||= getlocal(utc_offset) + else + @to_time_with_system_offset ||= getlocal + end + end + + # So that +self+ acts_like?(:time). + def acts_like_time? + true + end + + # Say we're a Time to thwart type checking. + def is_a?(klass) + klass == ::Time || super + end + alias_method :kind_of?, :is_a? + + # An instance of ActiveSupport::TimeWithZone is never blank + def blank? + false + end + + def freeze + # preload instance variables before freezing + period; utc; time; to_datetime; to_time + super + end + + def marshal_dump + [utc, time_zone.name, time] + end + + def marshal_load(variables) + initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc) + end + + # respond_to_missing? is not called in some cases, such as when type conversion is + # performed with Kernel#String + def respond_to?(sym, include_priv = false) + # ensure that we're not going to throw and rescue from NoMethodError in method_missing which is slow + return false if sym.to_sym == :to_str + super + end + + # Ensure proxy class responds to all methods that underlying time instance + # responds to. + def respond_to_missing?(sym, include_priv) + return false if sym.to_sym == :acts_like_date? + time.respond_to?(sym, include_priv) + end + + # Send the missing method to +time+ instance, and wrap result in a new + # TimeWithZone with the existing +time_zone+. + def method_missing(...) + wrap_with_time_zone time.__send__(...) + rescue NoMethodError => e + raise e, e.message.sub(time.inspect, inspect).sub("Time", "ActiveSupport::TimeWithZone"), e.backtrace + end + + private + SECONDS_PER_DAY = 86400 + + def incorporate_utc_offset(time, offset) + if time.kind_of?(Date) + time + Rational(offset, SECONDS_PER_DAY) + else + time + offset + end + end + + def get_period_and_ensure_valid_local_time(period) + # we don't want a Time.local instance enforcing its own DST rules as well, + # so transfer time values to a utc constructor if necessary + @time = transfer_time_values_to_utc_constructor(@time) unless @time.utc? + begin + period || @time_zone.period_for_local(@time) + rescue ::TZInfo::PeriodNotFound + # time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again + @time += 1.hour + retry + end + end + + def transfer_time_values_to_utc_constructor(time) + # avoid creating another Time object if possible + return time if time.instance_of?(::Time) && time.utc? + ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec) + end + + def duration_of_variable_length?(obj) + ActiveSupport::Duration === obj && obj.variable? + end + + def wrap_with_time_zone(time) + if time.acts_like?(:time) + periods = time_zone.periods_for_local(time) + self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil) + elsif time.is_a?(Range) + wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end) + else + time + end + end + end +end + +# These prevent Psych from calling `ActiveSupport::TimeWithZone.name` +# and triggering the deprecation warning about the change in Rails 7.1. +YAML.load_tags["!ruby/object:ActiveSupport::TimeWithZone"] = "ActiveSupport::TimeWithZone" +YAML.dump_tags[ActiveSupport::TimeWithZone] = "!ruby/object:ActiveSupport::TimeWithZone" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/values/time_zone.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/values/time_zone.rb similarity index 58% rename from app/server/ruby/vendor/activesupport/lib/active_support/values/time_zone.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/values/time_zone.rb index 38f0d268f4..e059142ed6 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/values/time_zone.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/values/time_zone.rb @@ -1,19 +1,19 @@ -require 'tzinfo' -require 'thread_safe' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/try' +# frozen_string_literal: true + +require "tzinfo" +require "concurrent/map" module ActiveSupport - # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. + # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. # It allows us to do the following: # - # * Limit the set of zones provided by TZInfo to a meaningful subset of 146 + # * Limit the set of zones provided by TZInfo to a meaningful subset of 134 # zones. # * Retrieve and display zones with a friendlier name # (e.g., "Eastern Time (US & Canada)" instead of "America/New_York"). - # * Lazily load TZInfo::Timezone instances only when they're needed. + # * Lazily load TZInfo::Timezone instances only when they're needed. # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, - # +parse+, +at+ and +now+ methods. + # +parse+, +at+, and +now+ methods. # # If you set config.time_zone in the Rails Application, you can # access this TimeZone object via Time.zone: @@ -23,19 +23,13 @@ module ActiveSupport # config.time_zone = 'Eastern Time (US & Canada)' # end # - # Time.zone # => # + # Time.zone # => # # Time.zone.name # => "Eastern Time (US & Canada)" # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00 - # - # The version of TZInfo bundled with Active Support only includes the - # definitions necessary to support the zones defined by the TimeZone class. - # If you need to use zones that aren't defined by TimeZone, you'll need to - # install the TZInfo gem (if a recent version of the gem is installed locally, - # this will be used instead of the bundled version.) class TimeZone # Keys are Rails TimeZone names, values are TZInfo identifiers. MAPPING = { - "International Date Line West" => "Pacific/Midway", + "International Date Line West" => "Etc/GMT+12", "Midway Island" => "Pacific/Midway", "American Samoa" => "Pacific/Pago_Pago", "Hawaii" => "Pacific/Honolulu", @@ -66,6 +60,7 @@ class TimeZone "Buenos Aires" => "America/Argentina/Buenos_Aires", "Montevideo" => "America/Montevideo", "Georgetown" => "America/Guyana", + "Puerto Rico" => "America/Puerto_Rico", "Greenland" => "America/Godthab", "Mid-Atlantic" => "Atlantic/South_Georgia", "Azores" => "Atlantic/Azores", @@ -92,7 +87,8 @@ class TimeZone "Paris" => "Europe/Paris", "Amsterdam" => "Europe/Amsterdam", "Berlin" => "Europe/Berlin", - "Bern" => "Europe/Berlin", + "Bern" => "Europe/Zurich", + "Zurich" => "Europe/Zurich", "Rome" => "Europe/Rome", "Stockholm" => "Europe/Stockholm", "Vienna" => "Europe/Vienna", @@ -111,9 +107,11 @@ class TimeZone "Jerusalem" => "Asia/Jerusalem", "Harare" => "Africa/Harare", "Pretoria" => "Africa/Johannesburg", + "Kaliningrad" => "Europe/Kaliningrad", "Moscow" => "Europe/Moscow", "St. Petersburg" => "Europe/Moscow", - "Volgograd" => "Europe/Moscow", + "Volgograd" => "Europe/Volgograd", + "Samara" => "Europe/Samara", "Kuwait" => "Asia/Kuwait", "Riyadh" => "Asia/Riyadh", "Nairobi" => "Africa/Nairobi", @@ -170,6 +168,7 @@ class TimeZone "Guam" => "Pacific/Guam", "Port Moresby" => "Pacific/Port_Moresby", "Magadan" => "Asia/Magadan", + "Srednekolymsk" => "Asia/Srednekolymsk", "Solomon Is." => "Pacific/Guadalcanal", "New Caledonia" => "Pacific/Noumea", "Fiji" => "Pacific/Fiji", @@ -183,21 +182,112 @@ class TimeZone "Samoa" => "Pacific/Apia" } - UTC_OFFSET_WITH_COLON = '%s%02d:%02d' - UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '') + UTC_OFFSET_WITH_COLON = "%s%02d:%02d" # :nodoc: + UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "") # :nodoc: + private_constant :UTC_OFFSET_WITH_COLON, :UTC_OFFSET_WITHOUT_COLON - @lazy_zones_map = ThreadSafe::Cache.new + @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new - # Assumes self represents an offset from UTC in seconds (as returned from - # Time#utc_offset) and turns this into an +HH:MM formatted string. - # - # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" - def self.seconds_to_utc_offset(seconds, colon = true) - format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON - sign = (seconds < 0 ? '-' : '+') - hours = seconds.abs / 3600 - minutes = (seconds.abs % 3600) / 60 - format % [sign, hours, minutes] + class << self + # Assumes self represents an offset from UTC in seconds (as returned from + # Time#utc_offset) and turns this into an +HH:MM formatted string. + # + # ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" + def seconds_to_utc_offset(seconds, colon = true) + format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON + sign = (seconds < 0 ? "-" : "+") + hours = seconds.abs / 3600 + minutes = (seconds.abs % 3600) / 60 + format % [sign, hours, minutes] + end + + def find_tzinfo(name) + TZInfo::Timezone.get(MAPPING[name] || name) + end + + alias_method :create, :new + + # Returns a TimeZone instance with the given name, or +nil+ if no + # such TimeZone instance exists. (This exists to support the use of + # this class with the +composed_of+ macro.) + def new(name) + self[name] + end + + # Returns an array of all TimeZone objects. There are multiple + # TimeZone objects per time zone, in many cases, to make it easier + # for users to find their own time zone. + def all + @zones ||= zones_map.values.sort + end + + # Locate a specific time zone object. If the argument is a string, it + # is interpreted to mean the name of the timezone to locate. If it is a + # numeric value it is either the hour offset, or the second offset, of the + # timezone to find. (The first one with that offset will be returned.) + # Returns +nil+ if no such time zone is known to the system. + def [](arg) + case arg + when self + arg + when String + begin + @lazy_zones_map[arg] ||= create(arg) + rescue TZInfo::InvalidTimezoneIdentifier + nil + end + when TZInfo::Timezone + @lazy_zones_map[arg.name] ||= create(arg.name, nil, arg) + when Numeric, ActiveSupport::Duration + arg *= 3600 if arg.abs <= 13 + all.find { |z| z.utc_offset == arg.to_i } + else + raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" + end + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the USA. + def us_zones + country_zones(:us) + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the country specified by its ISO 3166-1 Alpha2 code. + def country_zones(country_code) + code = country_code.to_s.upcase + @country_zones[code] ||= load_country_zones(code) + end + + def clear # :nodoc: + @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new + @zones = nil + @zones_map = nil + end + + private + def load_country_zones(code) + country = TZInfo::Country.get(code) + country.zone_identifiers.flat_map do |tz_id| + if MAPPING.value?(tz_id) + MAPPING.inject([]) do |memo, (key, value)| + memo << self[key] if value == tz_id + memo + end + else + create(tz_id, nil, TZInfo::Timezone.get(tz_id)) + end + end.sort! + end + + def zones_map + @zones_map ||= MAPPING.each_with_object({}) do |(name, _), zones| + timezone = self[name] + zones[name] = timezone if timezone + end + end end include Comparable @@ -212,22 +302,20 @@ def initialize(name, utc_offset = nil, tzinfo = nil) @name = name @utc_offset = utc_offset @tzinfo = tzinfo || TimeZone.find_tzinfo(name) - @current_period = nil end # Returns the offset of this time zone from UTC in seconds. def utc_offset - if @utc_offset - @utc_offset - else - @current_period ||= tzinfo.try(:current_period) - @current_period.try(:utc_offset) - end + @utc_offset || tzinfo&.current_period&.base_utc_offset end - # Returns the offset of this time zone as a formatted string, of the - # format "+HH:MM". - def formatted_offset(colon=true, alternate_utc_string = nil) + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # zone = ActiveSupport::TimeZone['Central Time (US & Canada)'] + # zone.formatted_offset # => "-06:00" + # zone.formatted_offset(false) # => "-0600" + def formatted_offset(colon = true, alternate_utc_string = nil) utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon) end @@ -246,6 +334,13 @@ def =~(re) re === name || re === MAPPING[name] end + # Compare #name and TZInfo identifier to a supplied regexp, returning +true+ + # if a match is found. + def match?(re) + (re == name) || (re == MAPPING[name]) || + ((Regexp === re) && (re.match?(name) || re.match?(MAPPING[name]))) + end + # Returns a textual representation of this time zone. def to_s "(GMT#{formatted_offset}) #{name}" @@ -267,29 +362,51 @@ def local(*args) # Time.zone = 'Hawaii' # => "Hawaii" # Time.utc(2000).to_f # => 946684800.0 # Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - def at(secs) - Time.at(secs).utc.in_time_zone(self) + # + # A second argument can be supplied to specify sub-second precision. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.at(946684800, 123456.789).nsec # => 123456789 + def at(*args) + Time.at(*args).utc.in_time_zone(self) end # Method for creating new ActiveSupport::TimeWithZone instance in time zone - # of +self+ from parsed string. + # of +self+ from an ISO 8601 string. # - # Time.zone = 'Hawaii' # => "Hawaii" - # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 # - # If upper components are missing from the string, they are supplied from - # TimeZone#now: + # If the time components are missing then they will be set to zero. # - # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 - def parse(str, now=now()) - parts = Date._parse(str, false) - return if parts.empty? + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00 + # + # If the string is invalid then an +ArgumentError+ will be raised unlike +parse+ + # which usually returns +nil+ when given an invalid date string. + def iso8601(str) + # Historically `Date._iso8601(nil)` returns `{}`, but in the `date` gem versions `3.2.1`, `3.1.2`, `3.0.2`, + # and `2.0.1`, `Date._iso8601(nil)` raises `TypeError` https://github.com/ruby/date/issues/39 + # Future `date` releases are expected to revert back to the original behavior. + raise ArgumentError, "invalid date" if str.nil? + + parts = Date._iso8601(str) + + year = parts.fetch(:year) + + if parts.key?(:yday) + ordinal_date = Date.ordinal(year, parts.fetch(:yday)) + month = ordinal_date.month + day = ordinal_date.day + else + month = parts.fetch(:mon) + day = parts.fetch(:mday) + end time = Time.new( - parts.fetch(:year, now.year), - parts.fetch(:mon, now.month), - parts.fetch(:mday, now.day), + year, + month, + day, parts.fetch(:hour, 0), parts.fetch(:min, 0), parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), @@ -301,6 +418,85 @@ def parse(str, now=now()) else TimeWithZone.new(nil, self, time) end + + rescue Date::Error, KeyError + raise ArgumentError, "invalid date" + end + + # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from parsed string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If upper components are missing from the string, they are supplied from + # TimeZone#now: + # + # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 + # + # However, if the date component is not provided, but any other upper + # components are supplied, then the day of the month defaults to 1: + # + # Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00 + # + # If the string is invalid then an +ArgumentError+ could be raised. + def parse(str, now = now()) + parts_to_time(Date._parse(str, false), now) + end + + # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from an RFC 3339 string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.rfc3339('2000-01-01T00:00:00Z') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If the time or zone components are missing then an +ArgumentError+ will + # be raised. This is much stricter than either +parse+ or +iso8601+ which + # allow for missing components. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.rfc3339('1999-12-31') # => ArgumentError: invalid date + def rfc3339(str) + parts = Date._rfc3339(str) + + raise ArgumentError, "invalid date" if parts.empty? + + time = Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour), + parts.fetch(:min), + parts.fetch(:sec) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset) + ) + + TimeWithZone.new(time.utc, self) + end + + # Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone. + # + # Assumes that +str+ is a time in the time zone +self+, + # unless +format+ includes an explicit time zone. + # (This is the same behavior as +parse+.) + # In either case, the returned TimeWithZone has the timezone of +self+. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.strptime('1999-12-31 14:00:00', '%Y-%m-%d %H:%M:%S') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If upper components are missing from the string, they are supplied from + # TimeZone#now: + # + # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone.strptime('22:30:00', '%H:%M:%S') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 + # + # However, if the date component is not provided, but any other upper + # components are supplied, then the day of the month defaults to 1: + # + # Time.zone.strptime('Mar 2000', '%b %Y') # => Wed, 01 Mar 2000 00:00:00 HST -10:00 + def strptime(str, format, now = now()) + parts_to_time(DateTime._strptime(str, format), now) end # Returns an ActiveSupport::TimeWithZone instance representing the current @@ -312,7 +508,7 @@ def now time_now.utc.in_time_zone(self) end - # Return the current date in this time zone. + # Returns the current date in this time zone. def today tzinfo.now.to_date end @@ -328,94 +524,78 @@ def yesterday end # Adjust the given time to the simultaneous time in the time zone - # represented by +self+. Returns a Time.utc() instance -- if you want an - # ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead. + # represented by +self+. Returns a local time with the appropriate offset + # -- if you want an ActiveSupport::TimeWithZone instance, use + # Time#in_time_zone() instead. + # + # As of tzinfo 2, utc_to_local returns a Time with a non-zero utc_offset. + # See the +utc_to_local_returns_utc_offset_times+ config for more info. def utc_to_local(time) - tzinfo.utc_to_local(time) + tzinfo.utc_to_local(time).yield_self do |t| + ActiveSupport.utc_to_local_returns_utc_offset_times ? + t : Time.utc(t.year, t.month, t.day, t.hour, t.min, t.sec, t.sec_fraction * 1_000_000) + end end # Adjust the given time to the simultaneous time in UTC. Returns a # Time.utc() instance. - def local_to_utc(time, dst=true) + def local_to_utc(time, dst = true) tzinfo.local_to_utc(time, dst) end - # Available so that TimeZone instances respond like TZInfo::Timezone + # Available so that TimeZone instances respond like TZInfo::Timezone # instances. def period_for_utc(time) tzinfo.period_for_utc(time) end - # Available so that TimeZone instances respond like TZInfo::Timezone + # Available so that TimeZone instances respond like TZInfo::Timezone # instances. - def period_for_local(time, dst=true) - tzinfo.period_for_local(time, dst) + def period_for_local(time, dst = true) + tzinfo.period_for_local(time, dst) { |periods| periods.last } end - def periods_for_local(time) #:nodoc: + def periods_for_local(time) # :nodoc: tzinfo.periods_for_local(time) end - def self.find_tzinfo(name) - TZInfo::TimezoneProxy.new(MAPPING[name] || name) + def init_with(coder) # :nodoc: + initialize(coder["name"]) end - class << self - alias_method :create, :new - - # Returns a TimeZone instance with the given name, or +nil+ if no - # such TimeZone instance exists. (This exists to support the use of - # this class with the +composed_of+ macro.) - def new(name) - self[name] - end - - # Returns an array of all TimeZone objects. There are multiple - # TimeZone objects per time zone, in many cases, to make it easier - # for users to find their own time zone. - def all - @zones ||= zones_map.values.sort - end + def encode_with(coder) # :nodoc: + coder.tag = "!ruby/object:#{self.class}" + coder.map = { "name" => tzinfo.name } + end - def zones_map - @zones_map ||= begin - MAPPING.each_key {|place| self[place]} # load all the zones - @lazy_zones_map + private + def parts_to_time(parts, now) + raise ArgumentError, "invalid date" if parts.nil? + return if parts.empty? + + if parts[:seconds] + time = Time.at(parts[:seconds]) + else + time = Time.new( + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, 0) + ) end - end - # Locate a specific time zone object. If the argument is a string, it - # is interpreted to mean the name of the timezone to locate. If it is a - # numeric value it is either the hour offset, or the second offset, of the - # timezone to find. (The first one with that offset will be returned.) - # Returns +nil+ if no such time zone is known to the system. - def [](arg) - case arg - when String - begin - @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset } - rescue TZInfo::InvalidTimezoneIdentifier - nil - end - when Numeric, ActiveSupport::Duration - arg *= 3600 if arg.abs <= 13 - all.find { |z| z.utc_offset == arg.to_i } - else - raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" + if parts[:offset] || parts[:seconds] + TimeWithZone.new(time.utc, self) + else + TimeWithZone.new(nil, self, time) end end - # A convenience method for returning a collection of TimeZone objects - # for time zones in the USA. - def us_zones - @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } + def time_now + Time.now end - end - - private - - def time_now - Time.now - end end end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/version.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/version.rb new file mode 100644 index 0000000000..c3a07289de --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveSupport + # Returns the currently loaded version of Active Support as a Gem::Version. + def self.version + gem_version + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini.rb similarity index 63% rename from app/server/ruby/vendor/activesupport/lib/active_support/xml_mini.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini.rb index 009ee4db90..dd6bee0eaa 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini.rb @@ -1,9 +1,12 @@ -require 'time' -require 'base64' -require 'bigdecimal' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/date_time/calculations' +# frozen_string_literal: true + +require "time" +require "base64" +require "bigdecimal" +require "bigdecimal/util" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/date_time/calculations" module ActiveSupport # = XmlMini @@ -16,15 +19,15 @@ module XmlMini # This module decorates files deserialized using Hash.from_xml with # the original_filename and content_type methods. - module FileLike #:nodoc: + module FileLike # :nodoc: attr_writer :original_filename, :content_type def original_filename - @original_filename || 'untitled' + @original_filename || "untitled" end def content_type - @content_type || 'application/octet-stream' + @content_type || "application/octet-stream" end end @@ -32,24 +35,26 @@ def content_type "binary" => "base64" } unless defined?(DEFAULT_ENCODINGS) - TYPE_NAMES = { - "Symbol" => "symbol", - "Fixnum" => "integer", - "Bignum" => "integer", - "BigDecimal" => "decimal", - "Float" => "float", - "TrueClass" => "boolean", - "FalseClass" => "boolean", - "Date" => "date", - "DateTime" => "dateTime", - "Time" => "dateTime", - "Array" => "array", - "Hash" => "hash" - } unless defined?(TYPE_NAMES) + unless defined?(TYPE_NAMES) + TYPE_NAMES = { + "Symbol" => "symbol", + "Integer" => "integer", + "BigDecimal" => "decimal", + "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", + "Date" => "date", + "DateTime" => "dateTime", + "Time" => "dateTime", + "Array" => "array", + "Hash" => "hash" + } + end + TYPE_NAMES["ActiveSupport::TimeWithZone"] = TYPE_NAMES["Time"] FORMATTING = { "symbol" => Proc.new { |symbol| symbol.to_s }, - "date" => Proc.new { |date| date.to_s(:db) }, + "date" => Proc.new { |date| date.to_fs(:db) }, "dateTime" => Proc.new { |time| time.xmlschema }, "binary" => Proc.new { |binary| ::Base64.encode64(binary) }, "yaml" => Proc.new { |yaml| yaml.to_yaml } @@ -63,10 +68,16 @@ def content_type "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc }, "integer" => Proc.new { |integer| integer.to_i }, "float" => Proc.new { |float| float.to_f }, - "decimal" => Proc.new { |number| BigDecimal(number) }, + "decimal" => Proc.new do |number| + if String === number + number.to_d + else + BigDecimal(number) + end + end, "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, "string" => Proc.new { |string| string.to_s }, - "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, + "yaml" => Proc.new { |yaml| YAML.load(yaml) rescue yaml }, "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) }, "binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) }, "file" => Proc.new { |file, entity| _parse_file(file, entity) } @@ -78,7 +89,10 @@ def content_type ) end - delegate :parse, :to => :backend + attr_accessor :depth + self.depth = 100 + + delegate :parse, to: :backend def backend current_thread_backend || @backend @@ -100,7 +114,7 @@ def with_backend(name) def to_tag(key, value, options) type_name = options.delete(:type) - merged_options = options.merge(:root => key, :skip_instruct => true) + merged_options = options.merge(root: key, skip_instruct: true) if value.is_a?(::Method) || value.is_a?(::Proc) if value.arity == 1 @@ -118,7 +132,7 @@ def to_tag(key, value, options) key = rename_key(key.to_s, options) - attributes = options[:skip_types] || type_name.nil? ? { } : { :type => type_name } + attributes = options[:skip_types] || type_name.nil? ? {} : { type: type_name } attributes[:nil] = true if value.nil? encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name] @@ -141,40 +155,37 @@ def rename_key(key, options = {}) key end - protected - - def _dasherize(key) - # $2 must be a non-greedy regex for this to work - left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1,3] - "#{left}#{middle.tr('_ ', '--')}#{right}" - end - - # TODO: Add support for other encodings - def _parse_binary(bin, entity) #:nodoc: - case entity['encoding'] - when 'base64' - ::Base64.decode64(bin) - else - bin + private + def _dasherize(key) + # $2 must be a non-greedy regex for this to work + left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3] + "#{left}#{middle.tr('_ ', '--')}#{right}" end - end - def _parse_file(file, entity) - f = StringIO.new(::Base64.decode64(file)) - f.extend(FileLike) - f.original_filename = entity['name'] - f.content_type = entity['content_type'] - f - end + # TODO: Add support for other encodings + def _parse_binary(bin, entity) + case entity["encoding"] + when "base64" + ::Base64.decode64(bin) + else + bin + end + end - private + def _parse_file(file, entity) + f = StringIO.new(::Base64.decode64(file)) + f.extend(FileLike) + f.original_filename = entity["name"] + f.content_type = entity["content_type"] + f + end def current_thread_backend - Thread.current[:xml_mini_backend] + IsolatedExecutionState[:xml_mini_backend] end def current_thread_backend=(name) - Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name) + IsolatedExecutionState[:xml_mini_backend] = name && cast_backend_name_to_module(name) end def cast_backend_name_to_module(name) @@ -187,5 +198,5 @@ def cast_backend_name_to_module(name) end end - XmlMini.backend = 'REXML' + XmlMini.backend = "REXML" end diff --git a/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/jdom.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/jdom.rb new file mode 100644 index 0000000000..b5aa90907c --- /dev/null +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/jdom.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM.include?("java") + +require "jruby" +include Java + +require "active_support/core_ext/object/blank" + +java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder +java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory +java_import java.io.StringReader unless defined? StringReader +java_import org.xml.sax.InputSource unless defined? InputSource +java_import org.xml.sax.Attributes unless defined? Attributes +java_import org.w3c.dom.Node unless defined? Node + +module ActiveSupport + module XmlMini_JDOM # :nodoc: + extend self + + CONTENT_KEY = "__content__" + + NODE_TYPE_NAMES = %w{ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE + DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE + PROCESSING_INSTRUCTION_NODE TEXT_NODE} + + node_type_map = {} + NODE_TYPE_NAMES.each { |type| node_type_map[Node.send(type)] = type } + + # Parse an XML Document string or IO into a simple hash using Java's jdom. + # data:: + # XML Document string or IO to parse + def parse(data) + if data.respond_to?(:read) + data = data.read + end + + if data.blank? + {} + else + @dbf = DocumentBuilderFactory.new_instance + # secure processing of java xml + # https://archive.is/9xcQQ + @dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) + @dbf.setFeature("http://xml.org/sax/features/external-general-entities", false) + @dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false) + @dbf.setFeature(javax.xml.XMLConstants::FEATURE_SECURE_PROCESSING, true) + xml_string_reader = StringReader.new(data) + xml_input_source = InputSource.new(xml_string_reader) + doc = @dbf.new_document_builder.parse(xml_input_source) + merge_element!({ CONTENT_KEY => "" }, doc.document_element, XmlMini.depth) + end + end + + private + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash + def merge_element!(hash, element, depth) + raise "Document too deep!" if depth == 0 + delete_empty(hash) + merge!(hash, element.tag_name, collapse(element, depth)) + end + + def delete_empty(hash) + hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == "" + end + + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. + def collapse(element, depth) + hash = get_attributes(element) + + child_nodes = element.child_nodes + if child_nodes.length > 0 + (0...child_nodes.length).each do |i| + child = child_nodes.item(i) + merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE + end + merge_texts!(hash, element) unless empty_content?(element) + hash + else + merge_texts!(hash, element) + end + end + + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted element to. + # element:: + # XML element whose texts are to me merged into the hash + def merge_texts!(hash, element) + delete_empty(hash) + text_children = texts(element) + if text_children.join.empty? + hash + else + # must use value to prevent double-escaping + merge!(hash, CONTENT_KEY, text_children.join) + end + end + + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. + def merge!(hash, key, value) + if hash.has_key?(key) + if hash[key].instance_of?(Array) + hash[key] << value + else + hash[key] = [hash[key], value] + end + elsif value.instance_of?(Array) + hash[key] = [value] + else + hash[key] = value + end + hash + end + + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. + def get_attributes(element) + attribute_hash = {} + attributes = element.attributes + (0...attributes.length).each do |i| + attribute_hash[CONTENT_KEY] ||= "" + attribute_hash[attributes.item(i).name] = attributes.item(i).value + end + attribute_hash + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def texts(element) + texts = [] + child_nodes = element.child_nodes + (0...child_nodes.length).each do |i| + item = child_nodes.item(i) + if item.node_type == Node.TEXT_NODE + texts << item.get_data + end + end + texts + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def empty_content?(element) + text = +"" + child_nodes = element.child_nodes + (0...child_nodes.length).each do |i| + item = child_nodes.item(i) + if item.node_type == Node.TEXT_NODE + text << item.get_data.strip + end + end + text.strip.length == 0 + end + end +end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/libxml.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/libxml.rb similarity index 60% rename from app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/libxml.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/libxml.rb index 47a2824186..65976a2b59 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/libxml.rb @@ -1,9 +1,11 @@ -require 'libxml' -require 'active_support/core_ext/object/blank' -require 'stringio' +# frozen_string_literal: true + +require "libxml" +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport - module XmlMini_LibXML #:nodoc: + module XmlMini_LibXML # :nodoc: extend self # Parse an XML Document string or IO into a simple hash using libxml. @@ -11,44 +13,41 @@ module XmlMini_LibXML #:nodoc: # XML Document string or IO to parse def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end - char = data.getc - if char.nil? + if data.eof? {} else - data.ungetc(char) LibXML::XML::Parser.io(data).parse.to_hash end end - end end -module LibXML #:nodoc: - module Conversions #:nodoc: - module Document #:nodoc: +module LibXML # :nodoc: + module Conversions # :nodoc: + module Document # :nodoc: def to_hash root.to_hash end end - module Node #:nodoc: - CONTENT_ROOT = '__content__'.freeze + module Node # :nodoc: + CONTENT_ROOT = "__content__" # Convert XML document to hash. # # hash:: # Hash to merge the converted element into. - def to_hash(hash={}) + def to_hash(hash = {}) node_hash = {} # Insert node hash into parent hash correctly. case hash[name] - when Array then hash[name] << node_hash - when Hash then hash[name] = [hash[name], node_hash] - when nil then hash[name] = node_hash + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash end # Handle child elements @@ -56,7 +55,7 @@ def to_hash(hash={}) if c.element? c.to_hash(node_hash) elsif c.text? || c.cdata? - node_hash[CONTENT_ROOT] ||= '' + node_hash[CONTENT_ROOT] ||= +"" node_hash[CONTENT_ROOT] << c.content end end @@ -75,5 +74,7 @@ def to_hash(hash={}) end end -LibXML::XML::Document.send(:include, LibXML::Conversions::Document) -LibXML::XML::Node.send(:include, LibXML::Conversions::Node) +# :enddoc: + +LibXML::XML::Document.include(LibXML::Conversions::Document) +LibXML::XML::Node.include(LibXML::Conversions::Node) diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/libxmlsax.rb similarity index 68% rename from app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/libxmlsax.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/libxmlsax.rb index 70a95299ec..33d594c414 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/libxmlsax.rb @@ -1,19 +1,20 @@ -require 'libxml' -require 'active_support/core_ext/object/blank' -require 'stringio' +# frozen_string_literal: true + +require "libxml" +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport - module XmlMini_LibXMLSAX #:nodoc: + module XmlMini_LibXMLSAX # :nodoc: extend self # Class that will build the hash while the XML document # is being parsed using SAX events. class HashBuilder - include LibXML::XML::SaxParser::Callbacks - CONTENT_KEY = '__content__'.freeze - HASH_SIZE_KEY = '__hash_size__'.freeze + CONTENT_KEY = "__content__" + HASH_SIZE_KEY = "__hash_size__" attr_reader :hash @@ -22,7 +23,7 @@ def current_hash end def on_start_document - @hash = { CONTENT_KEY => '' } + @hash = { CONTENT_KEY => +"" } @hash_stack = [@hash] end @@ -32,20 +33,20 @@ def on_end_document end def on_start_element(name, attrs = {}) - new_hash = { CONTENT_KEY => '' }.merge!(attrs) + new_hash = { CONTENT_KEY => +"" }.merge!(attrs) new_hash[HASH_SIZE_KEY] = new_hash.size + 1 case current_hash[name] - when Array then current_hash[name] << new_hash - when Hash then current_hash[name] = [current_hash[name], new_hash] - when nil then current_hash[name] = new_hash + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash end @hash_stack.push(new_hash) end def on_end_element(name) - if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == '' + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == "" current_hash.delete(CONTENT_KEY) end @hash_stack.pop @@ -63,18 +64,15 @@ def on_characters(string) def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end - char = data.getc - if char.nil? + if data.eof? {} else - data.ungetc(char) - LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER) parser = LibXML::XML::SaxParser.io(data) - document = self.document_class.new + document = document_class.new parser.callbacks = document parser.parse diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/nokogiri.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/nokogiri.rb similarity index 66% rename from app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/nokogiri.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/nokogiri.rb index 7398d4fa82..3fb58bca82 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/nokogiri.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + begin - require 'nokogiri' + require "nokogiri" rescue LoadError => e $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" raise e end -require 'active_support/core_ext/object/blank' -require 'stringio' +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport - module XmlMini_Nokogiri #:nodoc: + module XmlMini_Nokogiri # :nodoc: extend self # Parse an XML Document string or IO into a simple hash using libxml / nokogiri. @@ -16,42 +18,40 @@ module XmlMini_Nokogiri #:nodoc: # XML Document string or IO to parse def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end - char = data.getc - if char.nil? + if data.eof? {} else - data.ungetc(char) doc = Nokogiri::XML(data) raise doc.errors.first if doc.errors.length > 0 doc.to_hash end end - module Conversions #:nodoc: - module Document #:nodoc: + module Conversions # :nodoc: + module Document # :nodoc: def to_hash root.to_hash end end - module Node #:nodoc: - CONTENT_ROOT = '__content__'.freeze + module Node # :nodoc: + CONTENT_ROOT = "__content__" # Convert XML document to hash. # # hash:: # Hash to merge the converted element into. - def to_hash(hash={}) + def to_hash(hash = {}) node_hash = {} # Insert node hash into parent hash correctly. case hash[name] - when Array then hash[name] << node_hash - when Hash then hash[name] = [hash[name], node_hash] - when nil then hash[name] = node_hash + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash end # Handle child elements @@ -59,7 +59,7 @@ def to_hash(hash={}) if c.element? c.to_hash(node_hash) elsif c.text? || c.cdata? - node_hash[CONTENT_ROOT] ||= '' + node_hash[CONTENT_ROOT] ||= +"" node_hash[CONTENT_ROOT] << c.content end end @@ -77,7 +77,7 @@ def to_hash(hash={}) end end - Nokogiri::XML::Document.send(:include, Conversions::Document) - Nokogiri::XML::Node.send(:include, Conversions::Node) + Nokogiri::XML::Document.include(Conversions::Document) + Nokogiri::XML::Node.include(Conversions::Node) end end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/nokogirisax.rb similarity index 71% rename from app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/nokogirisax.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/nokogirisax.rb index be2d6a4cb1..f3ba109c98 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/nokogirisax.rb @@ -1,22 +1,23 @@ +# frozen_string_literal: true + begin - require 'nokogiri' + require "nokogiri" rescue LoadError => e $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" raise e end -require 'active_support/core_ext/object/blank' -require 'stringio' +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport - module XmlMini_NokogiriSAX #:nodoc: + module XmlMini_NokogiriSAX # :nodoc: extend self # Class that will build the hash while the XML document # is being parsed using SAX events. class HashBuilder < Nokogiri::XML::SAX::Document - - CONTENT_KEY = '__content__'.freeze - HASH_SIZE_KEY = '__hash_size__'.freeze + CONTENT_KEY = "__content__" + HASH_SIZE_KEY = "__hash_size__" attr_reader :hash @@ -38,20 +39,20 @@ def error(error_message) end def start_element(name, attrs = []) - new_hash = { CONTENT_KEY => '' }.merge!(Hash[attrs]) + new_hash = { CONTENT_KEY => +"" }.merge!(Hash[attrs]) new_hash[HASH_SIZE_KEY] = new_hash.size + 1 case current_hash[name] - when Array then current_hash[name] << new_hash - when Hash then current_hash[name] = [current_hash[name], new_hash] - when nil then current_hash[name] = new_hash + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash end @hash_stack.push(new_hash) end def end_element(name) - if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == '' + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == "" current_hash.delete(CONTENT_KEY) end @hash_stack.pop @@ -69,15 +70,13 @@ def characters(string) def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end - char = data.getc - if char.nil? + if data.eof? {} else - data.ungetc(char) - document = self.document_class.new + document = document_class.new parser = Nokogiri::XML::SAX::Parser.new(document) parser.parse(data) document.hash diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/rexml.rb b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/rexml.rb similarity index 74% rename from app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/rexml.rb rename to app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/rexml.rb index 5c7c78bf70..e4e1a1f444 100644 --- a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/rexml.rb +++ b/app/server/ruby/vendor/activesupport-7.0.6/lib/active_support/xml_mini/rexml.rb @@ -1,12 +1,14 @@ -require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/object/blank' -require 'stringio' +# frozen_string_literal: true + +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/object/blank" +require "stringio" module ActiveSupport - module XmlMini_REXML #:nodoc: + module XmlMini_REXML # :nodoc: extend self - CONTENT_KEY = '__content__'.freeze + CONTENT_KEY = "__content__" # Parse an XML Document string or IO into a simple hash. # @@ -17,19 +19,17 @@ module XmlMini_REXML #:nodoc: # XML Document string or IO to parse def parse(data) if !data.respond_to?(:read) - data = StringIO.new(data || '') + data = StringIO.new(data || "") end - char = data.getc - if char.nil? + if data.eof? {} else - data.ungetc(char) - silence_warnings { require 'rexml/document' } unless defined?(REXML::Document) + require_rexml unless defined?(REXML::Document) doc = REXML::Document.new(data) if doc.root - merge_element!({}, doc.root) + merge_element!({}, doc.root, XmlMini.depth) else raise REXML::ParseException, "The document #{doc.to_s.inspect} does not have a valid root" @@ -38,25 +38,33 @@ def parse(data) end private + def require_rexml + silence_warnings { require "rexml/document" } + rescue LoadError => e + $stderr.puts "You don't have rexml installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + # Convert an XML element and merge into the hash # # hash:: # Hash to merge the converted element into. # element:: # XML element to merge into hash - def merge_element!(hash, element) - merge!(hash, element.name, collapse(element)) + def merge_element!(hash, element, depth) + raise REXML::ParseException, "The document is too deep" if depth == 0 + merge!(hash, element.name, collapse(element, depth)) end # Actually converts an XML document element into a data structure. # # element:: # The document element to be collapsed. - def collapse(element) + def collapse(element, depth) hash = get_attributes(element) if element.has_elements? - element.each_element {|child| merge_element!(hash, child) } + element.each_element { |child| merge_element!(hash, child, depth - 1) } merge_texts!(hash, element) unless empty_content?(element) hash else @@ -75,7 +83,7 @@ def merge_texts!(hash, element) hash else # must use value to prevent double-escaping - texts = '' + texts = +"" element.texts.each { |t| texts << t.value } merge!(hash, CONTENT_KEY, texts) end @@ -114,7 +122,7 @@ def merge!(hash, key, value) # XML element to extract attributes from. def get_attributes(element) attributes = {} - element.attributes.each { |n,v| attributes[n] = v } + element.attributes.each { |n, v| attributes[n] = v } attributes end diff --git a/app/server/ruby/vendor/activesupport/CHANGELOG.md b/app/server/ruby/vendor/activesupport/CHANGELOG.md deleted file mode 100644 index f65d9ea120..0000000000 --- a/app/server/ruby/vendor/activesupport/CHANGELOG.md +++ /dev/null @@ -1,56 +0,0 @@ -* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies - instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is - deprecated. - - *Pavel Pravosud* - -* `HashWithIndifferentAccess` better respects `#to_hash` on objects it's - given. In particular, `.new`, `#update`, `#merge`, `#replace` all accept - objects which respond to `#to_hash`, even if those objects are not Hashes - directly. - - *Peter Jaros* - -* Deprecate `Class#superclass_delegating_accessor`, use `Class#class_attribute` instead. - - *Akshay Vishnoi* - -* Ensure classes which `include Enumerable` get `#to_json` in addition to - `#as_json`. - - *Sammy Larbi* - -* Change the signature of `fetch_multi` to return a hash rather than an - array. This makes it consistent with the output of `read_multi`. - - *Parker Selbert* - -* Introduce `Concern#class_methods` as a sleek alternative to clunky - `module ClassMethods`. Add `Kernel#concern` to define at the toplevel - without chunky `module Foo; extend ActiveSupport::Concern` boilerplate. - - # app/models/concerns/authentication.rb - concern :Authentication do - included do - after_create :generate_private_key - end - - class_methods do - def authenticate(credentials) - # ... - end - end - - def generate_private_key - # ... - end - end - - # app/models/user.rb - class User < ActiveRecord::Base - include Authentication - end - - *Jeremy Kemper* - -Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/app/server/ruby/vendor/activesupport/README.rdoc b/app/server/ruby/vendor/activesupport/README.rdoc deleted file mode 100644 index f3582767c0..0000000000 --- a/app/server/ruby/vendor/activesupport/README.rdoc +++ /dev/null @@ -1,35 +0,0 @@ -= Active Support -- Utility classes and Ruby extensions from Rails - -Active Support is a collection of utility classes and standard library -extensions that were found useful for the Rails framework. These additions -reside in this package so they can be loaded as needed in Ruby projects -outside of Rails. - - -== Download and installation - -The latest version of Active Support can be installed with RubyGems: - - % [sudo] gem install activesupport - -Source code can be downloaded as part of the Rails project on GitHub: - -* https://github.com/rails/rails/tree/master/activesupport - - -== License - -Active Support is released under the MIT license: - -* http://www.opensource.org/licenses/MIT - - -== Support - -API documentation is at: - -* http://api.rubyonrails.org - -Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: - -* https://github.com/rails/rails/issues diff --git a/app/server/ruby/vendor/activesupport/Rakefile b/app/server/ruby/vendor/activesupport/Rakefile deleted file mode 100644 index 5ba153662a..0000000000 --- a/app/server/ruby/vendor/activesupport/Rakefile +++ /dev/null @@ -1,32 +0,0 @@ -require 'rake/testtask' -require 'rubygems/package_task' - -task :default => :test -Rake::TestTask.new do |t| - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' - t.warning = true - t.verbose = true -end - - -namespace :test do - task :isolated do - Dir.glob("test/**/*_test.rb").all? do |file| - sh(Gem.ruby, '-w', '-Ilib:test', file) - end or raise "Failures" - end -end - -spec = eval(File.read('activesupport.gemspec')) - -Gem::PackageTask.new(spec) do |p| - p.gem_spec = spec -end - -desc "Release to rubygems" -task :release => :package do - require 'rake/gemcutter' - Rake::Gemcutter::Tasks.new(spec).define - Rake::Task['gem:push'].invoke -end diff --git a/app/server/ruby/vendor/activesupport/activesupport.gemspec b/app/server/ruby/vendor/activesupport/activesupport.gemspec deleted file mode 100644 index f3625e8b79..0000000000 --- a/app/server/ruby/vendor/activesupport/activesupport.gemspec +++ /dev/null @@ -1,28 +0,0 @@ -version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip - -Gem::Specification.new do |s| - s.platform = Gem::Platform::RUBY - s.name = 'activesupport' - s.version = version - s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.' - s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.' - - s.required_ruby_version = '>= 1.9.3' - - s.license = 'MIT' - - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' - - s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*'] - s.require_path = 'lib' - - s.rdoc_options.concat ['--encoding', 'UTF-8'] - - s.add_dependency 'i18n', '~> 0.6', '>= 0.6.9' - s.add_dependency 'json', '~> 1.7', '>= 1.7.7' - s.add_dependency 'tzinfo', '~> 1.1' - s.add_dependency 'minitest', '~> 5.1' - s.add_dependency 'thread_safe','~> 0.1' -end diff --git a/app/server/ruby/vendor/activesupport/bin/generate_tables b/app/server/ruby/vendor/activesupport/bin/generate_tables deleted file mode 100755 index f39e89b7d0..0000000000 --- a/app/server/ruby/vendor/activesupport/bin/generate_tables +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env ruby - -begin - $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib')) - require 'active_support' -rescue IOError -end - -require 'open-uri' -require 'tmpdir' - -module ActiveSupport - module Multibyte - module Unicode - - class UnicodeDatabase - def load; end - end - - class DatabaseGenerator - BASE_URI = "http://www.unicode.org/Public/#{UNICODE_VERSION}/ucd/" - SOURCES = { - :codepoints => BASE_URI + 'UnicodeData.txt', - :composition_exclusion => BASE_URI + 'CompositionExclusions.txt', - :grapheme_break_property => BASE_URI + 'auxiliary/GraphemeBreakProperty.txt', - :cp1252 => 'http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT' - } - - def initialize - @ucd = Unicode::UnicodeDatabase.new - end - - def parse_codepoints(line) - codepoint = Codepoint.new - raise "Could not parse input." unless line =~ /^ - ([0-9A-F]+); # code - ([^;]+); # name - ([A-Z]+); # general category - ([0-9]+); # canonical combining class - ([A-Z]+); # bidi class - (<([A-Z]*)>)? # decomposition type - ((\ ?[0-9A-F]+)*); # decomposition mapping - ([0-9]*); # decimal digit - ([0-9]*); # digit - ([^;]*); # numeric - ([YN]*); # bidi mirrored - ([^;]*); # unicode 1.0 name - ([^;]*); # iso comment - ([0-9A-F]*); # simple uppercase mapping - ([0-9A-F]*); # simple lowercase mapping - ([0-9A-F]*)$/ix # simple titlecase mapping - codepoint.code = $1.hex - #codepoint.name = $2 - #codepoint.category = $3 - codepoint.combining_class = Integer($4) - #codepoint.bidi_class = $5 - codepoint.decomp_type = $7 - codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect { |element| element.hex } - #codepoint.bidi_mirrored = ($13=='Y') ? true : false - codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex - codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex - #codepoint.titlecase_mapping = ($18=='') ? nil : $18.hex - @ucd.codepoints[codepoint.code] = codepoint - end - - def parse_grapheme_break_property(line) - if line =~ /^([0-9A-F.]+)\s*;\s*([\w]+)\s*#/ - type = $2.downcase.intern - @ucd.boundary[type] ||= [] - if $1.include? '..' - parts = $1.split '..' - @ucd.boundary[type] << (parts[0].hex..parts[1].hex) - else - @ucd.boundary[type] << $1.hex - end - end - end - - def parse_composition_exclusion(line) - if line =~ /^([0-9A-F]+)/i - @ucd.composition_exclusion << $1.hex - end - end - - def parse_cp1252(line) - if line =~ /^([0-9A-Fx]+)\s([0-9A-Fx]+)/i - @ucd.cp1252[$1.hex] = $2.hex - end - end - - def create_composition_map - @ucd.codepoints.each do |_, cp| - if !cp.nil? and cp.combining_class == 0 and cp.decomp_type.nil? and !cp.decomp_mapping.nil? and cp.decomp_mapping.length == 2 and @ucd.codepoints[cp.decomp_mapping[0]].combining_class == 0 and !@ucd.composition_exclusion.include?(cp.code) - @ucd.composition_map[cp.decomp_mapping[0]] ||= {} - @ucd.composition_map[cp.decomp_mapping[0]][cp.decomp_mapping[1]] = cp.code - end - end - end - - def normalize_boundary_map - @ucd.boundary.each do |k,v| - if [:lf, :cr].include? k - @ucd.boundary[k] = v[0] - end - end - end - - def parse - SOURCES.each do |type, url| - filename = File.join(Dir.tmpdir, "#{url.split('/').last}") - unless File.exist?(filename) - $stderr.puts "Downloading #{url.split('/').last}" - File.open(filename, 'wb') do |target| - open(url) do |source| - source.each_line { |line| target.write line } - end - end - end - File.open(filename) do |file| - file.each_line { |line| send "parse_#{type}".intern, line } - end - end - create_composition_map - normalize_boundary_map - end - - def dump_to(filename) - File.open(filename, 'wb') do |f| - f.write Marshal.dump([@ucd.codepoints, @ucd.composition_exclusion, @ucd.composition_map, @ucd.boundary, @ucd.cp1252]) - end - end - end - end - end -end - -if __FILE__ == $0 - filename = ActiveSupport::Multibyte::Unicode::UnicodeDatabase.filename - generator = ActiveSupport::Multibyte::Unicode::DatabaseGenerator.new - generator.parse - print "Writing to: #{filename}" - generator.dump_to filename - puts " (#{File.size(filename)} bytes)" -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/all.rb b/app/server/ruby/vendor/activesupport/lib/active_support/all.rb deleted file mode 100644 index f537818300..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/all.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'active_support' -require 'active_support/time' -require 'active_support/core_ext' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/cache.rb b/app/server/ruby/vendor/activesupport/lib/active_support/cache.rb deleted file mode 100644 index a627fa8651..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/cache.rb +++ /dev/null @@ -1,718 +0,0 @@ -require 'benchmark' -require 'zlib' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/benchmark' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/numeric/bytes' -require 'active_support/core_ext/numeric/time' -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/string/inflections' - -module ActiveSupport - # See ActiveSupport::Cache::Store for documentation. - module Cache - autoload :FileStore, 'active_support/cache/file_store' - autoload :MemoryStore, 'active_support/cache/memory_store' - autoload :MemCacheStore, 'active_support/cache/mem_cache_store' - autoload :NullStore, 'active_support/cache/null_store' - - # These options mean something to all cache implementations. Individual cache - # implementations may support additional options. - UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl] - - module Strategy - autoload :LocalCache, 'active_support/cache/strategy/local_cache' - end - - class << self - # Creates a new CacheStore object according to the given options. - # - # If no arguments are passed to this method, then a new - # ActiveSupport::Cache::MemoryStore object will be returned. - # - # If you pass a Symbol as the first argument, then a corresponding cache - # store class under the ActiveSupport::Cache namespace will be created. - # For example: - # - # ActiveSupport::Cache.lookup_store(:memory_store) - # # => returns a new ActiveSupport::Cache::MemoryStore object - # - # ActiveSupport::Cache.lookup_store(:mem_cache_store) - # # => returns a new ActiveSupport::Cache::MemCacheStore object - # - # Any additional arguments will be passed to the corresponding cache store - # class's constructor: - # - # ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache') - # # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache') - # - # If the first argument is not a Symbol, then it will simply be returned: - # - # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) - # # => returns MyOwnCacheStore.new - def lookup_store(*store_option) - store, *parameters = *Array.wrap(store_option).flatten - - case store - when Symbol - retrieve_store_class(store).new(*parameters) - when nil - ActiveSupport::Cache::MemoryStore.new - else - store - end - end - - # Expands out the +key+ argument into a key that can be used for the - # cache store. Optionally accepts a namespace, and all keys will be - # scoped within that namespace. - # - # If the +key+ argument provided is an array, or responds to +to_a+, then - # each of elements in the array will be turned into parameters/keys and - # concatenated into a single key. For example: - # - # expand_cache_key([:foo, :bar]) # => "foo/bar" - # expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar" - # - # The +key+ argument can also respond to +cache_key+ or +to_param+. - def expand_cache_key(key, namespace = nil) - expanded_cache_key = namespace ? "#{namespace}/" : "" - - if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] - expanded_cache_key << "#{prefix}/" - end - - expanded_cache_key << retrieve_cache_key(key) - expanded_cache_key - end - - private - def retrieve_cache_key(key) - case - when key.respond_to?(:cache_key) then key.cache_key - when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param - when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) - else key.to_param - end.to_s - end - - # Obtains the specified cache store class, given the name of the +store+. - # Raises an error when the store class cannot be found. - def retrieve_store_class(store) - require "active_support/cache/#{store}" - rescue LoadError => e - raise "Could not find cache store adapter for #{store} (#{e})" - else - ActiveSupport::Cache.const_get(store.to_s.camelize) - end - end - - # An abstract cache store class. There are multiple cache store - # implementations, each having its own additional features. See the classes - # under the ActiveSupport::Cache module, e.g. - # ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most - # popular cache store for large production websites. - # - # Some implementations may not support all methods beyond the basic cache - # methods of +fetch+, +write+, +read+, +exist?+, and +delete+. - # - # ActiveSupport::Cache::Store can store any serializable Ruby object. - # - # cache = ActiveSupport::Cache::MemoryStore.new - # - # cache.read('city') # => nil - # cache.write('city', "Duckburgh") - # cache.read('city') # => "Duckburgh" - # - # Keys are always translated into Strings and are case sensitive. When an - # object is specified as a key and has a +cache_key+ method defined, this - # method will be called to define the key. Otherwise, the +to_param+ - # method will be called. Hashes and Arrays can also be used as keys. The - # elements will be delimited by slashes, and the elements within a Hash - # will be sorted by key so they are consistent. - # - # cache.read('city') == cache.read(:city) # => true - # - # Nil values can be cached. - # - # If your cache is on a shared infrastructure, you can define a namespace - # for your cache entries. If a namespace is defined, it will be prefixed on - # to every key. The namespace can be either a static value or a Proc. If it - # is a Proc, it will be invoked when each key is evaluated so that you can - # use application logic to invalidate keys. - # - # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable - # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace - # - # Caches can also store values in a compressed format to save space and - # reduce time spent sending data. Since there is overhead, values must be - # large enough to warrant compression. To turn on compression either pass - # compress: true in the initializer or as an option to +fetch+ - # or +write+. To specify the threshold at which to compress values, set the - # :compress_threshold option. The default threshold is 16K. - class Store - cattr_accessor :logger, :instance_writer => true - - attr_reader :silence, :options - alias :silence? :silence - - # Create a new cache. The options will be passed to any write method calls - # except for :namespace which can be used to set the global - # namespace for the cache. - def initialize(options = nil) - @options = options ? options.dup : {} - end - - # Silence the logger. - def silence! - @silence = true - self - end - - # Silence the logger within a block. - def mute - previous_silence, @silence = defined?(@silence) && @silence, true - yield - ensure - @silence = previous_silence - end - - # Set to +true+ if cache stores should be instrumented. - # Default is +false+. - def self.instrument=(boolean) - Thread.current[:instrument_cache_store] = boolean - end - - def self.instrument - Thread.current[:instrument_cache_store] || false - end - - # Fetches data from the cache, using the given key. If there is data in - # the cache with the given key, then that data is returned. - # - # If there is no such data in the cache (a cache miss), then +nil+ will be - # returned. However, if a block has been passed, that block will be passed - # the key and executed in the event of a cache miss. The return value of the - # block will be written to the cache under the given cache key, and that - # return value will be returned. - # - # cache.write('today', 'Monday') - # cache.fetch('today') # => "Monday" - # - # cache.fetch('city') # => nil - # cache.fetch('city') do - # 'Duckburgh' - # end - # cache.fetch('city') # => "Duckburgh" - # - # You may also specify additional options via the +options+ argument. - # Setting force: true will force a cache miss: - # - # cache.write('today', 'Monday') - # cache.fetch('today', force: true) # => nil - # - # Setting :compress will store a large cache entry set by the call - # in a compressed format. - # - # Setting :expires_in will set an expiration time on the cache. - # All caches support auto-expiring content after a specified number of - # seconds. This value can be specified as an option to the constructor - # (in which case all entries will be affected), or it can be supplied to - # the +fetch+ or +write+ method to effect just one entry. - # - # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes) - # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry - # - # Setting :race_condition_ttl is very useful in situations where - # a cache entry is used very frequently and is under heavy load. If a - # cache expires and due to heavy load several different processes will try - # to read data natively and then they all will try to write to cache. To - # avoid that case the first process to find an expired cache entry will - # bump the cache expiration time by the value set in :race_condition_ttl. - # Yes, this process is extending the time for a stale value by another few - # seconds. Because of extended life of the previous cache, other processes - # will continue to use slightly stale data for a just a bit longer. In the - # meantime that first process will go ahead and will write into cache the - # new value. After that all the processes will start getting new value. - # The key is to keep :race_condition_ttl small. - # - # If the process regenerating the entry errors out, the entry will be - # regenerated after the specified number of seconds. Also note that the - # life of stale cache is extended only if it expired recently. Otherwise - # a new value is generated and :race_condition_ttl does not play - # any role. - # - # # Set all values to expire after one minute. - # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute) - # - # cache.write('foo', 'original value') - # val_1 = nil - # val_2 = nil - # sleep 60 - # - # Thread.new do - # val_1 = cache.fetch('foo', race_condition_ttl: 10) do - # sleep 1 - # 'new value 1' - # end - # end - # - # Thread.new do - # val_2 = cache.fetch('foo', race_condition_ttl: 10) do - # 'new value 2' - # end - # end - # - # # val_1 => "new value 1" - # # val_2 => "original value" - # # sleep 10 # First thread extend the life of cache by another 10 seconds - # # cache.fetch('foo') => "new value 1" - # - # Other options will be handled by the specific cache store implementation. - # Internally, #fetch calls #read_entry, and calls #write_entry on a cache - # miss. +options+ will be passed to the #read and #write calls. - # - # For example, MemCacheStore's #write method supports the +:raw+ - # option, which tells the memcached server to store all values as strings. - # We can use this option with #fetch too: - # - # cache = ActiveSupport::Cache::MemCacheStore.new - # cache.fetch("foo", force: true, raw: true) do - # :bar - # end - # cache.fetch('foo') # => "bar" - def fetch(name, options = nil) - if block_given? - options = merged_options(options) - key = namespaced_key(name, options) - - cached_entry = find_cached_entry(key, name, options) unless options[:force] - entry = handle_expired_entry(cached_entry, key, options) - - if entry - get_entry_value(entry, name, options) - else - save_block_result_to_cache(name, options) { |_name| yield _name } - end - else - read(name, options) - end - end - - # Fetches data from the cache, using the given key. If there is data in - # the cache with the given key, then that data is returned. Otherwise, - # +nil+ is returned. - # - # Options are passed to the underlying cache implementation. - def read(name, options = nil) - options = merged_options(options) - key = namespaced_key(name, options) - instrument(:read, name, options) do |payload| - entry = read_entry(key, options) - if entry - if entry.expired? - delete_entry(key, options) - payload[:hit] = false if payload - nil - else - payload[:hit] = true if payload - entry.value - end - else - payload[:hit] = false if payload - nil - end - end - end - - # Read multiple values at once from the cache. Options can be passed - # in the last argument. - # - # Some cache implementation may optimize this method. - # - # Returns a hash mapping the names provided to the values found. - def read_multi(*names) - options = names.extract_options! - options = merged_options(options) - results = {} - names.each do |name| - key = namespaced_key(name, options) - entry = read_entry(key, options) - if entry - if entry.expired? - delete_entry(key, options) - else - results[name] = entry.value - end - end - end - results - end - - # Fetches data from the cache, using the given keys. If there is data in - # the cache with the given keys, then that data is returned. Otherwise, - # the supplied block is called for each key for which there was no data, - # and the result will be written to the cache and returned. - # - # Options are passed to the underlying cache implementation. - # - # Returns a hash with the data for each of the names. For example: - # - # cache.write("bim", "bam") - # cache.fetch_multi("bim", "boom") { |key| key * 2 } - # # => { "bam" => "bam", "boom" => "boomboom" } - # - def fetch_multi(*names) - options = names.extract_options! - options = merged_options(options) - results = read_multi(*names, options) - - names.each_with_object({}) do |name, memo| - memo[name] = results.fetch(name) do - value = yield name - write(name, value, options) - value - end - end - end - - # Writes the value to the cache, with the key. - # - # Options are passed to the underlying cache implementation. - def write(name, value, options = nil) - options = merged_options(options) - - instrument(:write, name, options) do - entry = Entry.new(value, options) - write_entry(namespaced_key(name, options), entry, options) - end - end - - # Deletes an entry in the cache. Returns +true+ if an entry is deleted. - # - # Options are passed to the underlying cache implementation. - def delete(name, options = nil) - options = merged_options(options) - - instrument(:delete, name) do - delete_entry(namespaced_key(name, options), options) - end - end - - # Returns +true+ if the cache contains an entry for the given key. - # - # Options are passed to the underlying cache implementation. - def exist?(name, options = nil) - options = merged_options(options) - - instrument(:exist?, name) do - entry = read_entry(namespaced_key(name, options), options) - (entry && !entry.expired?) || false - end - end - - # Delete all entries with keys matching the pattern. - # - # Options are passed to the underlying cache implementation. - # - # All implementations may not support this method. - def delete_matched(matcher, options = nil) - raise NotImplementedError.new("#{self.class.name} does not support delete_matched") - end - - # Increment an integer value in the cache. - # - # Options are passed to the underlying cache implementation. - # - # All implementations may not support this method. - def increment(name, amount = 1, options = nil) - raise NotImplementedError.new("#{self.class.name} does not support increment") - end - - # Decrement an integer value in the cache. - # - # Options are passed to the underlying cache implementation. - # - # All implementations may not support this method. - def decrement(name, amount = 1, options = nil) - raise NotImplementedError.new("#{self.class.name} does not support decrement") - end - - # Cleanup the cache by removing expired entries. - # - # Options are passed to the underlying cache implementation. - # - # All implementations may not support this method. - def cleanup(options = nil) - raise NotImplementedError.new("#{self.class.name} does not support cleanup") - end - - # Clear the entire cache. Be careful with this method since it could - # affect other processes if shared cache is being used. - # - # The options hash is passed to the underlying cache implementation. - # - # All implementations may not support this method. - def clear(options = nil) - raise NotImplementedError.new("#{self.class.name} does not support clear") - end - - protected - # Add the namespace defined in the options to a pattern designed to - # match keys. Implementations that support delete_matched should call - # this method to translate a pattern that matches names into one that - # matches namespaced keys. - def key_matcher(pattern, options) - prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] - if prefix - source = pattern.source - if source.start_with?('^') - source = source[1, source.length] - else - source = ".*#{source[0, source.length]}" - end - Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options) - else - pattern - end - end - - # Read an entry from the cache implementation. Subclasses must implement - # this method. - def read_entry(key, options) # :nodoc: - raise NotImplementedError.new - end - - # Write an entry to the cache implementation. Subclasses must implement - # this method. - def write_entry(key, entry, options) # :nodoc: - raise NotImplementedError.new - end - - # Delete an entry from the cache implementation. Subclasses must - # implement this method. - def delete_entry(key, options) # :nodoc: - raise NotImplementedError.new - end - - private - # Merge the default options with ones specific to a method call. - def merged_options(call_options) # :nodoc: - if call_options - options.merge(call_options) - else - options.dup - end - end - - # Expand key to be a consistent string value. Invoke +cache_key+ if - # object responds to +cache_key+. Otherwise, +to_param+ method will be - # called. If the key is a Hash, then keys will be sorted alphabetically. - def expanded_key(key) # :nodoc: - return key.cache_key.to_s if key.respond_to?(:cache_key) - - case key - when Array - if key.size > 1 - key = key.collect{|element| expanded_key(element)} - else - key = key.first - end - when Hash - key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"} - end - - key.to_param - end - - # Prefix a key with the namespace. Namespace and key will be delimited - # with a colon. - def namespaced_key(key, options) - key = expanded_key(key) - namespace = options[:namespace] if options - prefix = namespace.is_a?(Proc) ? namespace.call : namespace - key = "#{prefix}:#{key}" if prefix - key - end - - def instrument(operation, key, options = nil) - log(operation, key, options) - - if self.class.instrument - payload = { :key => key } - payload.merge!(options) if options.is_a?(Hash) - ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) } - else - yield(nil) - end - end - - def log(operation, key, options = nil) - return unless logger && logger.debug? && !silence? - logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}") - end - - def find_cached_entry(key, name, options) - instrument(:read, name, options) do |payload| - payload[:super_operation] = :fetch if payload - read_entry(key, options) - end - end - - def handle_expired_entry(entry, key, options) - if entry && entry.expired? - race_ttl = options[:race_condition_ttl].to_i - if race_ttl && (Time.now.to_f - entry.expires_at <= race_ttl) - # When an entry has :race_condition_ttl defined, put the stale entry back into the cache - # for a brief period while the entry is begin recalculated. - entry.expires_at = Time.now + race_ttl - write_entry(key, entry, :expires_in => race_ttl * 2) - else - delete_entry(key, options) - end - entry = nil - end - entry - end - - def get_entry_value(entry, name, options) - instrument(:fetch_hit, name, options) { |payload| } - entry.value - end - - def save_block_result_to_cache(name, options) - result = instrument(:generate, name, options) do |payload| - yield(name) - end - - write(name, result, options) - result - end - end - - # This class is used to represent cache entries. Cache entries have a value and an optional - # expiration time. The expiration time is used to support the :race_condition_ttl option - # on the cache. - # - # Since cache entries in most instances will be serialized, the internals of this class are highly optimized - # using short instance variable names that are lazily defined. - class Entry # :nodoc: - DEFAULT_COMPRESS_LIMIT = 16.kilobytes - - # Create a new cache entry for the specified value. Options supported are - # +:compress+, +:compress_threshold+, and +:expires_in+. - def initialize(value, options = {}) - if should_compress?(value, options) - @value = compress(value) - @compressed = true - else - @value = value - end - - @created_at = Time.now.to_f - @expires_in = options[:expires_in] - @expires_in = @expires_in.to_f if @expires_in - end - - def value - convert_version_4beta1_entry! if defined?(@v) - compressed? ? uncompress(@value) : @value - end - - # Check if the entry is expired. The +expires_in+ parameter can override - # the value set when the entry was created. - def expired? - convert_version_4beta1_entry! if defined?(@value) - @expires_in && @created_at + @expires_in <= Time.now.to_f - end - - def expires_at - @expires_in ? @created_at + @expires_in : nil - end - - def expires_at=(value) - if value - @expires_in = value.to_f - @created_at - else - @expires_in = nil - end - end - - # Returns the size of the cached value. This could be less than - # value.size if the data is compressed. - def size - if defined?(@s) - @s - else - case value - when NilClass - 0 - when String - @value.bytesize - else - @s = Marshal.dump(@value).bytesize - end - end - end - - # Duplicate the value in a class. This is used by cache implementations that don't natively - # serialize entries to protect against accidental cache modifications. - def dup_value! - convert_version_4beta1_entry! if defined?(@v) - - if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) - if @value.is_a?(String) - @value = @value.dup - else - @value = Marshal.load(Marshal.dump(@value)) - end - end - end - - private - def should_compress?(value, options) - if value && options[:compress] - compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT - serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize - - return true if serialized_value_size >= compress_threshold - end - - false - end - - def compressed? - defined?(@compressed) ? @compressed : false - end - - def compress(value) - Zlib::Deflate.deflate(Marshal.dump(value)) - end - - def uncompress(value) - Marshal.load(Zlib::Inflate.inflate(value)) - end - - # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue - # to ensure that cache entries created under the old version still work with the new class definition. - def convert_version_4beta1_entry! - if defined?(@v) - @value = @v - remove_instance_variable(:@v) - end - - if defined?(@c) - @compressed = @c - remove_instance_variable(:@c) - end - - if defined?(@x) && @x - @created_at ||= Time.now.to_f - @expires_in = @x - @created_at - remove_instance_variable(:@x) - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/cache/mem_cache_store.rb b/app/server/ruby/vendor/activesupport/lib/active_support/cache/mem_cache_store.rb deleted file mode 100644 index 61b4f0b8b0..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/cache/mem_cache_store.rb +++ /dev/null @@ -1,198 +0,0 @@ -begin - require 'dalli' -rescue LoadError => e - $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" - raise e -end - -require 'digest/md5' -require 'active_support/core_ext/marshal' -require 'active_support/core_ext/array/extract_options' - -module ActiveSupport - module Cache - # A cache store implementation which stores data in Memcached: - # http://memcached.org/ - # - # This is currently the most popular cache store for production websites. - # - # Special features: - # - Clustering and load balancing. One can specify multiple memcached servers, - # and MemCacheStore will load balance between all available servers. If a - # server goes down, then MemCacheStore will ignore it until it comes back up. - # - # MemCacheStore implements the Strategy::LocalCache strategy which implements - # an in-memory cache inside of a block. - class MemCacheStore < Store - ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n - - def self.build_mem_cache(*addresses) - addresses = addresses.flatten - options = addresses.extract_options! - addresses = ["localhost:11211"] if addresses.empty? - Dalli::Client.new(addresses, options) - end - - # Creates a new MemCacheStore object, with the given memcached server - # addresses. Each address is either a host name, or a host-with-port string - # in the form of "host_name:port". For example: - # - # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229") - # - # If no addresses are specified, then MemCacheStore will connect to - # localhost port 11211 (the default memcached port). - def initialize(*addresses) - addresses = addresses.flatten - options = addresses.extract_options! - super(options) - - unless [String, Dalli::Client, NilClass].include?(addresses.first.class) - raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance." - end - if addresses.first.is_a?(Dalli::Client) - @data = addresses.first - else - mem_cache_options = options.dup - UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)} - @data = self.class.build_mem_cache(*(addresses + [mem_cache_options])) - end - - extend Strategy::LocalCache - extend LocalCacheWithRaw - end - - # Reads multiple values from the cache using a single call to the - # servers for all keys. Options can be passed in the last argument. - def read_multi(*names) - options = names.extract_options! - options = merged_options(options) - keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}] - raw_values = @data.get_multi(keys_to_names.keys, :raw => true) - values = {} - raw_values.each do |key, value| - entry = deserialize_entry(value) - values[keys_to_names[key]] = entry.value unless entry.expired? - end - values - end - - # Increment a cached value. This method uses the memcached incr atomic - # operator and can only be used on values written with the :raw option. - # Calling it on a value not stored with :raw will initialize that value - # to zero. - def increment(name, amount = 1, options = nil) # :nodoc: - options = merged_options(options) - instrument(:increment, name, :amount => amount) do - @data.incr(escape_key(namespaced_key(name, options)), amount) - end - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil - end - - # Decrement a cached value. This method uses the memcached decr atomic - # operator and can only be used on values written with the :raw option. - # Calling it on a value not stored with :raw will initialize that value - # to zero. - def decrement(name, amount = 1, options = nil) # :nodoc: - options = merged_options(options) - instrument(:decrement, name, :amount => amount) do - @data.decr(escape_key(namespaced_key(name, options)), amount) - end - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil - end - - # Clear the entire cache on all memcached servers. This method should - # be used with care when shared cache is being used. - def clear(options = nil) - @data.flush_all - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil - end - - # Get the statistics from the memcached servers. - def stats - @data.stats - end - - protected - # Read an entry from the cache. - def read_entry(key, options) # :nodoc: - deserialize_entry(@data.get(escape_key(key), options)) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil - end - - # Write an entry to the cache. - def write_entry(key, entry, options) # :nodoc: - method = options && options[:unless_exist] ? :add : :set - value = options[:raw] ? entry.value.to_s : entry - expires_in = options[:expires_in].to_i - if expires_in > 0 && !options[:raw] - # Set the memcache expire a few minutes in the future to support race condition ttls on read - expires_in += 5.minutes - end - @data.send(method, escape_key(key), value, expires_in, options) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - false - end - - # Delete an entry from the cache. - def delete_entry(key, options) # :nodoc: - @data.delete(escape_key(key)) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - false - end - - private - - # Memcache keys are binaries. So we need to force their encoding to binary - # before applying the regular expression to ensure we are escaping all - # characters properly. - def escape_key(key) - key = key.to_s.dup - key = key.force_encoding(Encoding::ASCII_8BIT) - key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } - key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 - key - end - - def deserialize_entry(raw_value) - if raw_value - entry = Marshal.load(raw_value) rescue raw_value - entry.is_a?(Entry) ? entry : Entry.new(entry) - else - nil - end - end - - # Provide support for raw values in the local cache strategy. - module LocalCacheWithRaw # :nodoc: - protected - def read_entry(key, options) - entry = super - if options[:raw] && local_cache && entry - entry = deserialize_entry(entry.value) - end - entry - end - - def write_entry(key, entry, options) # :nodoc: - retval = super - if options[:raw] && local_cache && retval - raw_entry = Entry.new(entry.value.to_s) - raw_entry.expires_at = entry.expires_at - local_cache.write_entry(key, raw_entry, options) - end - retval - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/cache/strategy/local_cache.rb b/app/server/ruby/vendor/activesupport/lib/active_support/cache/strategy/local_cache.rb deleted file mode 100644 index e9ee98a128..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ /dev/null @@ -1,160 +0,0 @@ -require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/string/inflections' - -module ActiveSupport - module Cache - module Strategy - # Caches that implement LocalCache will be backed by an in-memory cache for the - # duration of a block. Repeated calls to the cache for the same key will hit the - # in-memory cache for faster access. - module LocalCache - autoload :Middleware, 'active_support/cache/strategy/local_cache_middleware' - - # Class for storing and registering the local caches. - class LocalCacheRegistry # :nodoc: - extend ActiveSupport::PerThreadRegistry - - def initialize - @registry = {} - end - - def cache_for(local_cache_key) - @registry[local_cache_key] - end - - def set_cache_for(local_cache_key, value) - @registry[local_cache_key] = value - end - - def self.set_cache_for(l, v); instance.set_cache_for l, v; end - def self.cache_for(l); instance.cache_for l; end - end - - # Simple memory backed cache. This cache is not thread safe and is intended only - # for serving as a temporary memory cache for a single thread. - class LocalStore < Store - def initialize - super - @data = {} - end - - # Don't allow synchronizing since it isn't thread safe, - def synchronize # :nodoc: - yield - end - - def clear(options = nil) - @data.clear - end - - def read_entry(key, options) - @data[key] - end - - def write_entry(key, value, options) - @data[key] = value - true - end - - def delete_entry(key, options) - !!@data.delete(key) - end - end - - # Use a local cache for the duration of block. - def with_local_cache - use_temporary_local_cache(LocalStore.new) { yield } - end - # Middleware class can be inserted as a Rack handler to be local cache for the - # duration of request. - def middleware - @middleware ||= Middleware.new( - "ActiveSupport::Cache::Strategy::LocalCache", - local_cache_key) - end - - def clear(options = nil) # :nodoc: - local_cache.clear(options) if local_cache - super - end - - def cleanup(options = nil) # :nodoc: - local_cache.clear(options) if local_cache - super - end - - def increment(name, amount = 1, options = nil) # :nodoc: - value = bypass_local_cache{super} - set_cache_value(value, name, amount, options) - value - end - - def decrement(name, amount = 1, options = nil) # :nodoc: - value = bypass_local_cache{super} - set_cache_value(value, name, amount, options) - value - end - - protected - def read_entry(key, options) # :nodoc: - if local_cache - entry = local_cache.read_entry(key, options) - unless entry - entry = super - local_cache.write_entry(key, entry, options) - end - entry - else - super - end - end - - def write_entry(key, entry, options) # :nodoc: - local_cache.write_entry(key, entry, options) if local_cache - super - end - - def delete_entry(key, options) # :nodoc: - local_cache.delete_entry(key, options) if local_cache - super - end - - def set_cache_value(value, name, amount, options) - if local_cache - local_cache.mute do - if value - local_cache.write(name, value, options) - else - local_cache.delete(name, options) - end - end - end - end - - private - - def local_cache_key - @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym - end - - def local_cache - LocalCacheRegistry.cache_for(local_cache_key) - end - - def bypass_local_cache - use_temporary_local_cache(nil) { yield } - end - - def use_temporary_local_cache(temporary_cache) - save_cache = LocalCacheRegistry.cache_for(local_cache_key) - begin - LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache) - yield - ensure - LocalCacheRegistry.set_cache_for(local_cache_key, save_cache) - end - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/callbacks.rb b/app/server/ruby/vendor/activesupport/lib/active_support/callbacks.rb deleted file mode 100644 index e14ece7f35..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/callbacks.rb +++ /dev/null @@ -1,745 +0,0 @@ -require 'active_support/concern' -require 'active_support/descendants_tracker' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/kernel/singleton_class' -require 'thread' - -module ActiveSupport - # Callbacks are code hooks that are run at key points in an object's life cycle. - # The typical use case is to have a base class define a set of callbacks - # relevant to the other functionality it supplies, so that subclasses can - # install callbacks that enhance or modify the base functionality without - # needing to override or redefine methods of the base class. - # - # Mixing in this module allows you to define the events in the object's - # life cycle that will support callbacks (via +ClassMethods.define_callbacks+), - # set the instance methods, procs, or callback objects to be called (via - # +ClassMethods.set_callback+), and run the installed callbacks at the - # appropriate times (via +run_callbacks+). - # - # Three kinds of callbacks are supported: before callbacks, run before a - # certain event; after callbacks, run after the event; and around callbacks, - # blocks that surround the event, triggering it when they yield. Callback code - # can be contained in instance methods, procs or lambdas, or callback objects - # that respond to certain predetermined methods. See +ClassMethods.set_callback+ - # for details. - # - # class Record - # include ActiveSupport::Callbacks - # define_callbacks :save - # - # def save - # run_callbacks :save do - # puts "- save" - # end - # end - # end - # - # class PersonRecord < Record - # set_callback :save, :before, :saving_message - # def saving_message - # puts "saving..." - # end - # - # set_callback :save, :after do |object| - # puts "saved" - # end - # end - # - # person = PersonRecord.new - # person.save - # - # Output: - # saving... - # - save - # saved - module Callbacks - extend Concern - - included do - extend ActiveSupport::DescendantsTracker - end - - CALLBACK_FILTER_TYPES = [:before, :after, :around] - - # Runs the callbacks for the given event. - # - # Calls the before and around callbacks in the order they were set, yields - # the block (if given one), and then runs the after callbacks in reverse - # order. - # - # If the callback chain was halted, returns +false+. Otherwise returns the - # result of the block, or +true+ if no block is given. - # - # run_callbacks :save do - # save - # end - def run_callbacks(kind, &block) - cbs = send("_#{kind}_callbacks") - if cbs.empty? - yield if block_given? - else - runner = cbs.compile - e = Filters::Environment.new(self, false, nil, block) - runner.call(e).value - end - end - - private - - # A hook invoked every time a before callback is halted. - # This can be overridden in AS::Callback implementors in order - # to provide better debugging/logging. - def halted_callback_hook(filter) - end - - module Conditionals # :nodoc: - class Value - def initialize(&block) - @block = block - end - def call(target, value); @block.call(value); end - end - end - - module Filters - Environment = Struct.new(:target, :halted, :value, :run_block) - - class End - def call(env) - block = env.run_block - env.value = !env.halted && (!block || block.call) - env - end - end - ENDING = End.new - - class Before - def self.build(next_callback, user_callback, user_conditions, chain_config, filter) - halted_lambda = chain_config[:terminator] - - if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) - elsif chain_config.key? :terminator - halting(next_callback, user_callback, halted_lambda, filter) - elsif user_conditions.any? - conditional(next_callback, user_callback, user_conditions) - else - simple next_callback, user_callback - end - end - - private - - def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) - lambda { |env| - target = env.target - value = env.value - halted = env.halted - - if !halted && user_conditions.all? { |c| c.call(target, value) } - result = user_callback.call target, value - env.halted = halted_lambda.call(target, result) - if env.halted - target.send :halted_callback_hook, filter - end - end - next_callback.call env - } - end - - def self.halting(next_callback, user_callback, halted_lambda, filter) - lambda { |env| - target = env.target - value = env.value - halted = env.halted - - unless halted - result = user_callback.call target, value - env.halted = halted_lambda.call(target, result) - if env.halted - target.send :halted_callback_hook, filter - end - end - next_callback.call env - } - end - - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| - target = env.target - value = env.value - - if user_conditions.all? { |c| c.call(target, value) } - user_callback.call target, value - end - next_callback.call env - } - end - - def self.simple(next_callback, user_callback) - lambda { |env| - user_callback.call env.target, env.value - next_callback.call env - } - end - end - - class After - def self.build(next_callback, user_callback, user_conditions, chain_config) - if chain_config[:skip_after_callbacks_if_terminated] - if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions) - elsif chain_config.key?(:terminator) - halting(next_callback, user_callback) - elsif user_conditions.any? - conditional next_callback, user_callback, user_conditions - else - simple next_callback, user_callback - end - else - if user_conditions.any? - conditional next_callback, user_callback, user_conditions - else - simple next_callback, user_callback - end - end - end - - private - - def self.halting_and_conditional(next_callback, user_callback, user_conditions) - lambda { |env| - env = next_callback.call env - target = env.target - value = env.value - halted = env.halted - - if !halted && user_conditions.all? { |c| c.call(target, value) } - user_callback.call target, value - end - env - } - end - - def self.halting(next_callback, user_callback) - lambda { |env| - env = next_callback.call env - unless env.halted - user_callback.call env.target, env.value - end - env - } - end - - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| - env = next_callback.call env - target = env.target - value = env.value - - if user_conditions.all? { |c| c.call(target, value) } - user_callback.call target, value - end - env - } - end - - def self.simple(next_callback, user_callback) - lambda { |env| - env = next_callback.call env - user_callback.call env.target, env.value - env - } - end - end - - class Around - def self.build(next_callback, user_callback, user_conditions, chain_config) - if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions) - elsif chain_config.key? :terminator - halting(next_callback, user_callback) - elsif user_conditions.any? - conditional(next_callback, user_callback, user_conditions) - else - simple(next_callback, user_callback) - end - end - - private - - def self.halting_and_conditional(next_callback, user_callback, user_conditions) - lambda { |env| - target = env.target - value = env.value - halted = env.halted - - if !halted && user_conditions.all? { |c| c.call(target, value) } - user_callback.call(target, value) { - env = next_callback.call env - env.value - } - env - else - next_callback.call env - end - } - end - - def self.halting(next_callback, user_callback) - lambda { |env| - target = env.target - value = env.value - - unless env.halted - user_callback.call(target, value) { - env = next_callback.call env - env.value - } - env - else - next_callback.call env - end - } - end - - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| - target = env.target - value = env.value - - if user_conditions.all? { |c| c.call(target, value) } - user_callback.call(target, value) { - env = next_callback.call env - env.value - } - env - else - next_callback.call env - end - } - end - - def self.simple(next_callback, user_callback) - lambda { |env| - user_callback.call(env.target, env.value) { - env = next_callback.call env - env.value - } - env - } - end - end - end - - class Callback #:nodoc:# - def self.build(chain, filter, kind, options) - new chain.name, filter, kind, options, chain.config - end - - attr_accessor :kind, :name - attr_reader :chain_config - - def initialize(name, filter, kind, options, chain_config) - @chain_config = chain_config - @name = name - @kind = kind - @filter = filter - @key = compute_identifier filter - @if = Array(options[:if]) - @unless = Array(options[:unless]) - end - - def filter; @key; end - def raw_filter; @filter; end - - def merge(chain, new_options) - options = { - :if => @if.dup, - :unless => @unless.dup - } - - options[:if].concat Array(new_options.fetch(:unless, [])) - options[:unless].concat Array(new_options.fetch(:if, [])) - - self.class.build chain, @filter, @kind, options - end - - def matches?(_kind, _filter) - @kind == _kind && filter == _filter - end - - def duplicates?(other) - case @filter - when Symbol, String - matches?(other.kind, other.filter) - else - false - end - end - - # Wraps code with filter - def apply(next_callback) - user_conditions = conditions_lambdas - user_callback = make_lambda @filter - - case kind - when :before - Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter) - when :after - Filters::After.build(next_callback, user_callback, user_conditions, chain_config) - when :around - Filters::Around.build(next_callback, user_callback, user_conditions, chain_config) - end - end - - private - - def invert_lambda(l) - lambda { |*args, &blk| !l.call(*args, &blk) } - end - - # Filters support: - # - # Symbols:: A method to call. - # Strings:: Some content to evaluate. - # Procs:: A proc to call with the object. - # Objects:: An object with a before_foo method on it to call. - # - # All of these objects are compiled into methods and handled - # the same after this point: - # - # Symbols:: Already methods. - # Strings:: class_eval'd into methods. - # Procs:: using define_method compiled into methods. - # Objects:: - # a method is created that calls the before_foo method - # on the object. - def make_lambda(filter) - case filter - when Symbol - lambda { |target, _, &blk| target.send filter, &blk } - when String - l = eval "lambda { |value| #{filter} }" - lambda { |target, value| target.instance_exec(value, &l) } - when Conditionals::Value then filter - when ::Proc - if filter.arity > 1 - return lambda { |target, _, &block| - raise ArgumentError unless block - target.instance_exec(target, block, &filter) - } - end - - if filter.arity <= 0 - lambda { |target, _| target.instance_exec(&filter) } - else - lambda { |target, _| target.instance_exec(target, &filter) } - end - else - scopes = Array(chain_config[:scope]) - method_to_call = scopes.map{ |s| public_send(s) }.join("_") - - lambda { |target, _, &blk| - filter.public_send method_to_call, target, &blk - } - end - end - - def compute_identifier(filter) - case filter - when String, ::Proc - filter.object_id - else - filter - end - end - - def conditions_lambdas - @if.map { |c| make_lambda c } + - @unless.map { |c| invert_lambda make_lambda c } - end - end - - # An Array with a compile method. - class CallbackChain #:nodoc:# - include Enumerable - - attr_reader :name, :config - - def initialize(name, config) - @name = name - @config = { - :scope => [ :kind ] - }.merge!(config) - @chain = [] - @callbacks = nil - @mutex = Mutex.new - end - - def each(&block); @chain.each(&block); end - def index(o); @chain.index(o); end - def empty?; @chain.empty?; end - - def insert(index, o) - @callbacks = nil - @chain.insert(index, o) - end - - def delete(o) - @callbacks = nil - @chain.delete(o) - end - - def clear - @callbacks = nil - @chain.clear - self - end - - def initialize_copy(other) - @callbacks = nil - @chain = other.chain.dup - @mutex = Mutex.new - end - - def compile - @callbacks || @mutex.synchronize do - @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback| - callback.apply chain - end - end - end - - def append(*callbacks) - callbacks.each { |c| append_one(c) } - end - - def prepend(*callbacks) - callbacks.each { |c| prepend_one(c) } - end - - protected - def chain; @chain; end - - private - - def append_one(callback) - @callbacks = nil - remove_duplicates(callback) - @chain.push(callback) - end - - def prepend_one(callback) - @callbacks = nil - remove_duplicates(callback) - @chain.unshift(callback) - end - - def remove_duplicates(callback) - @callbacks = nil - @chain.delete_if { |c| callback.duplicates?(c) } - end - end - - module ClassMethods - def normalize_callback_params(filters, block) # :nodoc: - type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before - options = filters.extract_options! - filters.unshift(block) if block - [type, filters, options.dup] - end - - # This is used internally to append, prepend and skip callbacks to the - # CallbackChain. - def __update_callbacks(name) #:nodoc: - ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target| - chain = target.get_callbacks name - yield target, chain.dup - end - end - - # Install a callback for the given event. - # - # set_callback :save, :before, :before_meth - # set_callback :save, :after, :after_meth, if: :condition - # set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff } - # - # The second arguments indicates whether the callback is to be run +:before+, - # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This - # means the first example above can also be written as: - # - # set_callback :save, :before_meth - # - # The callback can be specified as a symbol naming an instance method; as a - # proc, lambda, or block; as a string to be instance evaluated; or as an - # object that responds to a certain method determined by the :scope - # argument to +define_callbacks+. - # - # If a proc, lambda, or block is given, its body is evaluated in the context - # of the current object. It can also optionally accept the current object as - # an argument. - # - # Before and around callbacks are called in the order that they are set; - # after callbacks are called in the reverse order. - # - # Around callbacks can access the return value from the event, if it - # wasn't halted, from the +yield+ call. - # - # ===== Options - # - # * :if - A symbol naming an instance method or a proc; the - # callback will be called only when it returns a +true+ value. - # * :unless - A symbol naming an instance method or a proc; the - # callback will be called only when it returns a +false+ value. - # * :prepend - If +true+, the callback will be prepended to the - # existing chain rather than appended. - def set_callback(name, *filter_list, &block) - type, filters, options = normalize_callback_params(filter_list, block) - self_chain = get_callbacks name - mapped = filters.map do |filter| - Callback.build(self_chain, filter, type, options) - end - - __update_callbacks(name) do |target, chain| - options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) - target.set_callbacks name, chain - end - end - - # Skip a previously set callback. Like +set_callback+, :if or - # :unless options may be passed in order to control when the - # callback is skipped. - # - # class Writer < Person - # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 } - # end - def skip_callback(name, *filter_list, &block) - type, filters, options = normalize_callback_params(filter_list, block) - - __update_callbacks(name) do |target, chain| - filters.each do |filter| - filter = chain.find {|c| c.matches?(type, filter) } - - if filter && options.any? - new_filter = filter.merge(chain, options) - chain.insert(chain.index(filter), new_filter) - end - - chain.delete(filter) - end - target.set_callbacks name, chain - end - end - - # Remove all set callbacks for the given event. - def reset_callbacks(name) - callbacks = get_callbacks name - - ActiveSupport::DescendantsTracker.descendants(self).each do |target| - chain = target.get_callbacks(name).dup - callbacks.each { |c| chain.delete(c) } - target.set_callbacks name, chain - end - - self.set_callbacks name, callbacks.dup.clear - end - - # Define sets of events in the object life cycle that support callbacks. - # - # define_callbacks :validate - # define_callbacks :initialize, :save, :destroy - # - # ===== Options - # - # * :terminator - Determines when a before filter will halt the - # callback chain, preventing following callbacks from being called and - # the event from being triggered. This should be a lambda to be executed. - # The current object and the return result of the callback will be called - # with the lambda. - # - # define_callbacks :validate, terminator: ->(target, result) { result == false } - # - # In this example, if any before validate callbacks returns +false+, - # other callbacks are not executed. Defaults to +false+, meaning no value - # halts the chain. - # - # * :skip_after_callbacks_if_terminated - Determines if after - # callbacks should be terminated by the :terminator option. By - # default after callbacks executed no matter if callback chain was - # terminated or not. Option makes sense only when :terminator - # option is specified. - # - # * :scope - Indicates which methods should be executed when an - # object is used as a callback. - # - # class Audit - # def before(caller) - # puts 'Audit: before is called' - # end - # - # def before_save(caller) - # puts 'Audit: before_save is called' - # end - # end - # - # class Account - # include ActiveSupport::Callbacks - # - # define_callbacks :save - # set_callback :save, :before, Audit.new - # - # def save - # run_callbacks :save do - # puts 'save in main' - # end - # end - # end - # - # In the above case whenever you save an account the method - # Audit#before will be called. On the other hand - # - # define_callbacks :save, scope: [:kind, :name] - # - # would trigger Audit#before_save instead. That's constructed - # by calling #{kind}_#{name} on the given instance. In this - # case "kind" is "before" and "name" is "save". In this context +:kind+ - # and +:name+ have special meanings: +:kind+ refers to the kind of - # callback (before/after/around) and +:name+ refers to the method on - # which callbacks are being defined. - # - # A declaration like - # - # define_callbacks :save, scope: [:name] - # - # would call Audit#save. - def define_callbacks(*names) - options = names.extract_options! - if options.key?(:terminator) && String === options[:terminator] - ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda" - value = options[:terminator] - line = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__ - options[:terminator] = lambda { |target, result| target.instance_exec(result, &line) } - end - - names.each do |name| - class_attribute "_#{name}_callbacks" - set_callbacks name, CallbackChain.new(name, options) - end - end - - protected - - def get_callbacks(name) - send "_#{name}_callbacks" - end - - def set_callbacks(name, callbacks) - send "_#{name}_callbacks=", callbacks - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/concurrency/latch.rb b/app/server/ruby/vendor/activesupport/lib/active_support/concurrency/latch.rb deleted file mode 100644 index 1507de433e..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/concurrency/latch.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'thread' -require 'monitor' - -module ActiveSupport - module Concurrency - class Latch - def initialize(count = 1) - @count = count - @lock = Monitor.new - @cv = @lock.new_cond - end - - def release - @lock.synchronize do - @count -= 1 if @count > 0 - @cv.broadcast if @count.zero? - end - end - - def await - @lock.synchronize do - @cv.wait_while { @count > 0 } - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext.rb deleted file mode 100644 index 199aa91020..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext.rb +++ /dev/null @@ -1,3 +0,0 @@ -Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path| - require path -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array.rb deleted file mode 100644 index 7d0c1e4c8d..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/array/access' -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/array/grouping' -require 'active_support/core_ext/array/prepend_and_append' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/access.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/access.rb deleted file mode 100644 index 67f58bc0fe..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/access.rb +++ /dev/null @@ -1,56 +0,0 @@ -class Array - # Returns the tail of the array from +position+. - # - # %w( a b c d ).from(0) # => ["a", "b", "c", "d"] - # %w( a b c d ).from(2) # => ["c", "d"] - # %w( a b c d ).from(10) # => [] - # %w().from(0) # => [] - def from(position) - self[position, length] || [] - end - - # Returns the beginning of the array up to +position+. - # - # %w( a b c d ).to(0) # => ["a"] - # %w( a b c d ).to(2) # => ["a", "b", "c"] - # %w( a b c d ).to(10) # => ["a", "b", "c", "d"] - # %w().to(0) # => [] - def to(position) - first position + 1 - end - - # Equal to self[1]. - # - # %w( a b c d e ).second # => "b" - def second - self[1] - end - - # Equal to self[2]. - # - # %w( a b c d e ).third # => "c" - def third - self[2] - end - - # Equal to self[3]. - # - # %w( a b c d e ).fourth # => "d" - def fourth - self[3] - end - - # Equal to self[4]. - # - # %w( a b c d e ).fifth # => "e" - def fifth - self[4] - end - - # Equal to self[41]. Also known as accessing "the reddit". - # - # (1..42).to_a.forty_two # => 42 - def forty_two - self[41] - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb deleted file mode 100644 index f8d48b69df..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Array - # The human way of thinking about adding stuff to the end of a list is with append. - alias_method :append, :<< - - # The human way of thinking about adding stuff to the beginning of a list is with prepend. - alias_method :prepend, :unshift -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal.rb deleted file mode 100644 index 8143113cfa..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/big_decimal/conversions' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb deleted file mode 100644 index 843c592669..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'bigdecimal' -require 'bigdecimal/util' - -class BigDecimal - DEFAULT_STRING_FORMAT = 'F' - def to_formatted_s(*args) - if args[0].is_a?(Symbol) - super - else - format = args[0] || DEFAULT_STRING_FORMAT - _original_to_s(format) - end - end - alias_method :_original_to_s, :to_s - alias_method :to_s, :to_formatted_s -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb deleted file mode 100644 index 46ba93ead4..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +++ /dev/null @@ -1,14 +0,0 @@ -ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprecated and will be removed in the future.' - -require 'bigdecimal' -require 'yaml' -require 'active_support/core_ext/big_decimal/conversions' - -class BigDecimal - YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } - - def encode_with(coder) - string = to_s - coder.represent_scalar(nil, YAML_MAPPING[string] || string) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class.rb deleted file mode 100644 index c750a10bb2..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/class/delegating_attributes' -require 'active_support/core_ext/class/subclasses' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb deleted file mode 100644 index 1c305c5970..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/deprecation' - - -class Class - def superclass_delegating_accessor(name, options = {}) - # Create private _name and _name= methods that can still be used if the public - # methods are overridden. - _superclass_delegating_accessor("_#{name}", options) - - # Generate the public methods name, name=, and name?. - # These methods dispatch to the private _name, and _name= methods, making them - # overridable. - singleton_class.send(:define_method, name) { send("_#{name}") } - singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") } - singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) } - - # If an instance_reader is needed, generate public instance methods name and name?. - if options[:instance_reader] != false - define_method(name) { send("_#{name}") } - define_method("#{name}?") { !!send("#{name}") } - end - end - - deprecate superclass_delegating_accessor: :class_attribute - - private - # Take the object being set and store it in a method. This gives us automatic - # inheritance behavior, without having to store the object in an instance - # variable and look up the superclass chain manually. - def _stash_object_in_method(object, method, instance_reader = true) - singleton_class.remove_possible_method(method) - singleton_class.send(:define_method, method) { object } - remove_possible_method(method) - define_method(method) { object } if instance_reader - end - - def _superclass_delegating_accessor(name, options = {}) - singleton_class.send(:define_method, "#{name}=") do |value| - _stash_object_in_method(value, name, options[:instance_reader] != false) - end - send("#{name}=", nil) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/subclasses.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/subclasses.rb deleted file mode 100644 index 3c4bfc5f1e..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/module/reachable' - -class Class - begin - ObjectSpace.each_object(Class.new) {} - - def descendants # :nodoc: - descendants = [] - ObjectSpace.each_object(singleton_class) do |k| - descendants.unshift k unless k == self - end - descendants - end - rescue StandardError # JRuby - def descendants # :nodoc: - descendants = [] - ObjectSpace.each_object(Class) do |k| - descendants.unshift k if k < self - end - descendants.uniq! - descendants - end - end - - # Returns an array with the direct children of +self+. - # - # Integer.subclasses # => [Fixnum, Bignum] - # - # class Foo; end - # class Bar < Foo; end - # class Baz < Bar; end - # - # Foo.subclasses # => [Bar] - def subclasses - subclasses, chain = [], descendants - chain.each do |k| - subclasses << k unless chain.any? { |c| c > k } - end - subclasses - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date.rb deleted file mode 100644 index 465fedda80..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/date/acts_like' -require 'active_support/core_ext/date/calculations' -require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/date/zones' - diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/conversions.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/conversions.rb deleted file mode 100644 index df419a6e63..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/conversions.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'date' -require 'active_support/inflector/methods' -require 'active_support/core_ext/date/zones' -require 'active_support/core_ext/module/remove_method' - -class Date - DATE_FORMATS = { - :short => '%e %b', - :long => '%B %e, %Y', - :db => '%Y-%m-%d', - :number => '%Y%m%d', - :long_ordinal => lambda { |date| - day_format = ActiveSupport::Inflector.ordinalize(date.day) - date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" - }, - :rfc822 => '%e %b %Y', - :iso8601 => lambda { |date| date.iso8601 } - } - - # Ruby 1.9 has Date#to_time which converts to localtime only. - remove_method :to_time - - # Ruby 1.9 has Date#xmlschema which converts to a string without the time - # component. This removal may generate an issue on FreeBSD, that's why we - # need to use remove_possible_method here - remove_possible_method :xmlschema - - # Convert to a formatted string. See DATE_FORMATS for predefined formats. - # - # This method is aliased to to_s. - # - # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 - # - # date.to_formatted_s(:db) # => "2007-11-10" - # date.to_s(:db) # => "2007-11-10" - # - # date.to_formatted_s(:short) # => "10 Nov" - # date.to_formatted_s(:long) # => "November 10, 2007" - # date.to_formatted_s(:long_ordinal) # => "November 10th, 2007" - # date.to_formatted_s(:rfc822) # => "10 Nov 2007" - # date.to_formatted_s(:iso8601) # => "2007-11-10" - # - # == Adding your own date formats to to_formatted_s - # You can add your own formats to the Date::DATE_FORMATS hash. - # Use the format name as the hash key and either a strftime string - # or Proc instance that takes a date argument as the value. - # - # # config/initializers/date_formats.rb - # Date::DATE_FORMATS[:month_and_year] = '%B %Y' - # Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") } - def to_formatted_s(format = :default) - if formatter = DATE_FORMATS[format] - if formatter.respond_to?(:call) - formatter.call(self).to_s - else - strftime(formatter) - end - else - to_default_s - end - end - alias_method :to_default_s, :to_s - alias_method :to_s, :to_formatted_s - - # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005" - def readable_inspect - strftime('%a, %d %b %Y') - end - alias_method :default_inspect, :inspect - alias_method :inspect, :readable_inspect - - # Converts a Date instance to a Time, where the time is set to the beginning of the day. - # The timezone can be either :local or :utc (default :local). - # - # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 - # - # date.to_time # => Sat Nov 10 00:00:00 0800 2007 - # date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007 - # - # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007 - def to_time(form = :local) - ::Time.send(form, year, month, day) - end - - def xmlschema - in_time_zone.xmlschema - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/zones.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/zones.rb deleted file mode 100644 index d109b430db..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date/zones.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'date' -require 'active_support/core_ext/date_and_time/zones' - -class Date - include DateAndTime::Zones -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb deleted file mode 100644 index b85e49aca5..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ /dev/null @@ -1,251 +0,0 @@ -module DateAndTime - module Calculations - DAYS_INTO_WEEK = { - :monday => 0, - :tuesday => 1, - :wednesday => 2, - :thursday => 3, - :friday => 4, - :saturday => 5, - :sunday => 6 - } - - # Returns a new date/time representing yesterday. - def yesterday - advance(:days => -1) - end - - # Returns a new date/time representing tomorrow. - def tomorrow - advance(:days => 1) - end - - # Returns true if the date/time is today. - def today? - to_date == ::Date.current - end - - # Returns true if the date/time is in the past. - def past? - self < self.class.current - end - - # Returns true if the date/time is in the future. - def future? - self > self.class.current - end - - # Returns a new date/time the specified number of days ago. - def days_ago(days) - advance(:days => -days) - end - - # Returns a new date/time the specified number of days in the future. - def days_since(days) - advance(:days => days) - end - - # Returns a new date/time the specified number of weeks ago. - def weeks_ago(weeks) - advance(:weeks => -weeks) - end - - # Returns a new date/time the specified number of weeks in the future. - def weeks_since(weeks) - advance(:weeks => weeks) - end - - # Returns a new date/time the specified number of months ago. - def months_ago(months) - advance(:months => -months) - end - - # Returns a new date/time the specified number of months in the future. - def months_since(months) - advance(:months => months) - end - - # Returns a new date/time the specified number of years ago. - def years_ago(years) - advance(:years => -years) - end - - # Returns a new date/time the specified number of years in the future. - def years_since(years) - advance(:years => years) - end - - # Returns a new date/time at the start of the month. - # DateTime objects will have a time set to 0:00. - def beginning_of_month - first_hour(change(:day => 1)) - end - alias :at_beginning_of_month :beginning_of_month - - # Returns a new date/time at the start of the quarter. - # Example: 1st January, 1st July, 1st October. - # DateTime objects will have a time set to 0:00. - def beginning_of_quarter - first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month } - beginning_of_month.change(:month => first_quarter_month) - end - alias :at_beginning_of_quarter :beginning_of_quarter - - # Returns a new date/time at the end of the quarter. - # Example: 31st March, 30th June, 30th September. - # DateTime objects will have a time set to 23:59:59. - def end_of_quarter - last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month } - beginning_of_month.change(:month => last_quarter_month).end_of_month - end - alias :at_end_of_quarter :end_of_quarter - - # Return a new date/time at the beginning of the year. - # Example: 1st January. - # DateTime objects will have a time set to 0:00. - def beginning_of_year - change(:month => 1).beginning_of_month - end - alias :at_beginning_of_year :beginning_of_year - - # Returns a new date/time representing the given day in the next week. - # The +given_day_in_next_week+ defaults to the beginning of the week - # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ - # when set. +DateTime+ objects have their time set to 0:00. - def next_week(given_day_in_next_week = Date.beginning_of_week) - first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) - end - - # Short-hand for months_since(1). - def next_month - months_since(1) - end - - # Short-hand for months_since(3) - def next_quarter - months_since(3) - end - - # Short-hand for years_since(1). - def next_year - years_since(1) - end - - # Returns a new date/time representing the given day in the previous week. - # Week is assumed to start on +start_day+, default is - # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. - # DateTime objects have their time set to 0:00. - def prev_week(start_day = Date.beginning_of_week) - first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) - end - alias_method :last_week, :prev_week - - # Short-hand for months_ago(1). - def prev_month - months_ago(1) - end - alias_method :last_month, :prev_month - - # Short-hand for months_ago(3). - def prev_quarter - months_ago(3) - end - alias_method :last_quarter, :prev_quarter - - # Short-hand for years_ago(1). - def prev_year - years_ago(1) - end - alias_method :last_year, :prev_year - - # Returns the number of days to the start of the week on the given day. - # Week is assumed to start on +start_day+, default is - # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. - def days_to_week_start(start_day = Date.beginning_of_week) - start_day_number = DAYS_INTO_WEEK[start_day] - current_day_number = wday != 0 ? wday - 1 : 6 - (current_day_number - start_day_number) % 7 - end - - # Returns a new date/time representing the start of this week on the given day. - # Week is assumed to start on +start_day+, default is - # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. - # +DateTime+ objects have their time set to 0:00. - def beginning_of_week(start_day = Date.beginning_of_week) - result = days_ago(days_to_week_start(start_day)) - acts_like?(:time) ? result.midnight : result - end - alias :at_beginning_of_week :beginning_of_week - - # Returns Monday of this week assuming that week starts on Monday. - # +DateTime+ objects have their time set to 0:00. - def monday - beginning_of_week(:monday) - end - - # Returns a new date/time representing the end of this week on the given day. - # Week is assumed to start on +start_day+, default is - # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. - # DateTime objects have their time set to 23:59:59. - def end_of_week(start_day = Date.beginning_of_week) - last_hour(days_since(6 - days_to_week_start(start_day))) - end - alias :at_end_of_week :end_of_week - - # Returns Sunday of this week assuming that week starts on Monday. - # +DateTime+ objects have their time set to 23:59:59. - def sunday - end_of_week(:monday) - end - - # Returns a new date/time representing the end of the month. - # DateTime objects will have a time set to 23:59:59. - def end_of_month - last_day = ::Time.days_in_month(month, year) - last_hour(days_since(last_day - day)) - end - alias :at_end_of_month :end_of_month - - # Returns a new date/time representing the end of the year. - # DateTime objects will have a time set to 23:59:59. - def end_of_year - change(:month => 12).end_of_month - end - alias :at_end_of_year :end_of_year - - # Returns a Range representing the whole week of the current date/time. - # Week starts on start_day, default is Date.week_start or config.week_start when set. - def all_week(start_day = Date.beginning_of_week) - beginning_of_week(start_day)..end_of_week(start_day) - end - - # Returns a Range representing the whole month of the current date/time. - def all_month - beginning_of_month..end_of_month - end - - # Returns a Range representing the whole quarter of the current date/time. - def all_quarter - beginning_of_quarter..end_of_quarter - end - - # Returns a Range representing the whole year of the current date/time. - def all_year - beginning_of_year..end_of_year - end - - private - - def first_hour(date_or_time) - date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time - end - - def last_hour(date_or_time) - date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time - end - - def days_span(day) - (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time.rb deleted file mode 100644 index e8a27b9f38..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'active_support/core_ext/date_time/acts_like' -require 'active_support/core_ext/date_time/calculations' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/date_time/zones' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/zones.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/zones.rb deleted file mode 100644 index c39f358395..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/date_time/zones.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'date' -require 'active_support/core_ext/date_and_time/zones' - -class DateTime - include DateAndTime::Zones -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/enumerable.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/enumerable.rb deleted file mode 100644 index 1343beb87a..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/enumerable.rb +++ /dev/null @@ -1,80 +0,0 @@ -module Enumerable - # Calculates a sum from the elements. - # - # payments.sum { |p| p.price * p.tax_rate } - # payments.sum(&:price) - # - # The latter is a shortcut for: - # - # payments.inject(0) { |sum, p| sum + p.price } - # - # It can also calculate the sum without the use of a block. - # - # [5, 15, 10].sum # => 30 - # ['foo', 'bar'].sum # => "foobar" - # [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5] - # - # The default sum of an empty list is zero. You can override this default: - # - # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) - def sum(identity = 0, &block) - if block_given? - map(&block).sum(identity) - else - inject { |sum, element| sum + element } || identity - end - end - - # Convert an enumerable to a hash. - # - # people.index_by(&:login) - # => { "nextangle" => , "chade-" => , ...} - # people.index_by { |person| "#{person.first_name} #{person.last_name}" } - # => { "Chade- Fowlersburg-e" => , "David Heinemeier Hansson" => , ...} - def index_by - if block_given? - Hash[map { |elem| [yield(elem), elem] }] - else - to_enum(:index_by) { size if respond_to?(:size) } - end - end - - # Returns +true+ if the enumerable has more than 1 element. Functionally - # equivalent to enum.to_a.size > 1. Can be called with a block too, - # much like any?, so people.many? { |p| p.age > 26 } returns +true+ - # if more than one person is over 26. - def many? - cnt = 0 - if block_given? - any? do |element| - cnt += 1 if yield element - cnt > 1 - end - else - any? { (cnt += 1) > 1 } - end - end - - # The negative of the Enumerable#include?. Returns +true+ if the - # collection does not include the object. - def exclude?(object) - !include?(object) - end -end - -class Range #:nodoc: - # Optimize range sum to use arithmetic progression if a block is not given and - # we have a range of numeric values. - def sum(identity = 0) - if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer)) - super - else - actual_last = exclude_end? ? (last - 1) : last - if actual_last >= first - (actual_last - first + 1) * (actual_last + first) / 2 - else - identity - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/file.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/file.rb deleted file mode 100644 index dc24afbe7f..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/file.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/file/atomic' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/file/atomic.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/file/atomic.rb deleted file mode 100644 index 0e7e3ba378..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/file/atomic.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'fileutils' - -class File - # Write to a file atomically. Useful for situations where you don't - # want other processes or threads to see half-written files. - # - # File.atomic_write('important.file') do |file| - # file.write('hello') - # end - # - # If your temp directory is not on the same filesystem as the file you're - # trying to write, you can provide a different temporary directory. - # - # File.atomic_write('/data/something.important', '/data/tmp') do |file| - # file.write('hello') - # end - def self.atomic_write(file_name, temp_dir = Dir.tmpdir) - require 'tempfile' unless defined?(Tempfile) - require 'fileutils' unless defined?(FileUtils) - - temp_file = Tempfile.new(basename(file_name), temp_dir) - temp_file.binmode - yield temp_file - temp_file.close - - if File.exist?(file_name) - # Get original file permissions - old_stat = stat(file_name) - else - # If not possible, probe which are the default permissions in the - # destination directory. - old_stat = probe_stat_in(dirname(file_name)) - end - - # Overwrite original file with temp file - FileUtils.mv(temp_file.path, file_name) - - # Set correct permissions on new file - begin - chown(old_stat.uid, old_stat.gid, file_name) - # This operation will affect filesystem ACL's - chmod(old_stat.mode, file_name) - rescue Errno::EPERM - # Changing file ownership failed, moving on. - end - end - - # Private utility method. - def self.probe_stat_in(dir) #:nodoc: - basename = [ - '.permissions_check', - Thread.current.object_id, - Process.pid, - rand(1000000) - ].join('.') - - file_name = join(dir, basename) - FileUtils.touch(file_name) - stat(file_name) - ensure - FileUtils.rm_f(file_name) if file_name - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash.rb deleted file mode 100644 index f68e1662f9..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'active_support/core_ext/hash/compact' -require 'active_support/core_ext/hash/conversions' -require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/core_ext/hash/slice' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/compact.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/compact.rb deleted file mode 100644 index 6566215a4d..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/compact.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Hash - # Returns a hash with non +nil+ values. - # - # hash = { a: true, b: false, c: nil} - # hash.compact # => { a: true, b: false} - # hash # => { a: true, b: false, c: nil} - # { c: nil }.compact # => {} - def compact - self.select { |_, value| !value.nil? } - end - - # Replaces current hash with non +nil+ values. - # - # hash = { a: true, b: false, c: nil} - # hash.compact! # => { a: true, b: false} - # hash # => { a: true, b: false} - def compact! - self.reject! { |_, value| value.nil? } - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/deep_merge.rb deleted file mode 100644 index dc86c92003..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ /dev/null @@ -1,27 +0,0 @@ -class Hash - # Returns a new hash with +self+ and +other_hash+ merged recursively. - # - # h1 = { x: { y: [4, 5, 6] }, z: [7, 8, 9] } - # h2 = { x: { y: [7, 8, 9] }, z: 'xyz' } - # - # h1.deep_merge(h2) # => {x: {y: [7, 8, 9]}, z: "xyz"} - # h2.deep_merge(h1) # => {x: {y: [4, 5, 6]}, z: [7, 8, 9]} - # h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) } - # # => {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]} - def deep_merge(other_hash, &block) - dup.deep_merge!(other_hash, &block) - end - - # Same as +deep_merge+, but modifies +self+. - def deep_merge!(other_hash, &block) - other_hash.each_pair do |k,v| - tv = self[k] - if tv.is_a?(Hash) && v.is_a?(Hash) - self[k] = tv.deep_merge(v, &block) - else - self[k] = block && tv ? block.call(k, tv, v) : v - end - end - self - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/except.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/except.rb deleted file mode 100644 index 682d089881..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/except.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Hash - # Returns a hash that includes everything but the given keys. This is useful for - # limiting a set of parameters to everything but a few known toggles: - # - # @person.update(params[:person].except(:admin)) - def except(*keys) - dup.except!(*keys) - end - - # Replaces the hash without the given keys. - def except!(*keys) - keys.each { |key| delete(key) } - self - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/slice.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/slice.rb deleted file mode 100644 index 8ad600b171..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/hash/slice.rb +++ /dev/null @@ -1,42 +0,0 @@ -class Hash - # Slice a hash to include only the given keys. This is useful for - # limiting an options hash to valid keys before passing to a method: - # - # def search(criteria = {}) - # criteria.assert_valid_keys(:mass, :velocity, :time) - # end - # - # search(options.slice(:mass, :velocity, :time)) - # - # If you have an array of keys you want to limit to, you should splat them: - # - # valid_keys = [:mass, :velocity, :time] - # search(options.slice(*valid_keys)) - def slice(*keys) - keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true) - keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) } - end - - # Replaces the hash with only the given keys. - # Returns a hash containing the removed key/value pairs. - # - # { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b) - # # => {:c=>3, :d=>4} - def slice!(*keys) - keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true) - omit = slice(*self.keys - keys) - hash = slice(*keys) - hash.default = default - hash.default_proc = default_proc if default_proc - replace(hash) - omit - end - - # Removes and returns the key/value pairs matching the given keys. - # - # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2} - # { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1} - def extract!(*keys) - keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) } - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer.rb deleted file mode 100644 index a44a1b4c74..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'active_support/core_ext/integer/multiple' -require 'active_support/core_ext/integer/inflections' -require 'active_support/core_ext/integer/time' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/inflections.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/inflections.rb deleted file mode 100644 index 56f2ed5985..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/inflections.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'active_support/inflector' - -class Integer - # Ordinalize turns a number into an ordinal string used to denote the - # position in an ordered sequence such as 1st, 2nd, 3rd, 4th. - # - # 1.ordinalize # => "1st" - # 2.ordinalize # => "2nd" - # 1002.ordinalize # => "1002nd" - # 1003.ordinalize # => "1003rd" - # -11.ordinalize # => "-11th" - # -1001.ordinalize # => "-1001st" - def ordinalize - ActiveSupport::Inflector.ordinalize(self) - end - - # Ordinal returns the suffix used to denote the position - # in an ordered sequence such as 1st, 2nd, 3rd, 4th. - # - # 1.ordinal # => "st" - # 2.ordinal # => "nd" - # 1002.ordinal # => "nd" - # 1003.ordinal # => "rd" - # -11.ordinal # => "th" - # -1001.ordinal # => "st" - def ordinal - ActiveSupport::Inflector.ordinal(self) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/time.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/time.rb deleted file mode 100644 index 82080ffe51..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/integer/time.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'active_support/duration' -require 'active_support/core_ext/numeric/time' - -class Integer - # Enables the use of time calculations and declarations, like 45.minutes + - # 2.hours + 4.years. - # - # These methods use Time#advance for precise date calculations when using - # from_now, +ago+, etc. as well as adding or subtracting their - # results from a Time object. - # - # # equivalent to Time.now.advance(months: 1) - # 1.month.from_now - # - # # equivalent to Time.now.advance(years: 2) - # 2.years.from_now - # - # # equivalent to Time.now.advance(months: 4, years: 5) - # (4.months + 5.years).from_now - # - # While these methods provide precise calculation when used as in the examples - # above, care should be taken to note that this is not true if the result of - # +months+, +years+, etc is converted before use: - # - # # equivalent to 30.days.to_i.from_now - # 1.month.to_i.from_now - # - # # equivalent to 365.25.days.to_f.from_now - # 1.year.to_f.from_now - # - # In such cases, Ruby's core - # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and - # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision - # date and time arithmetic. - def months - ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) - end - alias :month :months - - def years - ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]]) - end - alias :year :years -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel.rb deleted file mode 100644 index aa19aed43b..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/kernel/agnostics' -require 'active_support/core_ext/kernel/concern' -require 'active_support/core_ext/kernel/debugger' -require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/kernel/singleton_class' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/agnostics.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/agnostics.rb deleted file mode 100644 index 64837d87aa..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/agnostics.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Object - # Makes backticks behave (somewhat more) similarly on all platforms. - # On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the - # spawned shell prints a message to stderr and sets $?. We emulate - # Unix on the former but not the latter. - def `(command) #:nodoc: - super - rescue Errno::ENOENT => e - STDERR.puts "#$0: #{e}" - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/debugger.rb deleted file mode 100644 index 2073cac98d..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Kernel - unless respond_to?(:debugger) - # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it). - def debugger - message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n" - defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message) - end - alias breakpoint debugger unless respond_to?(:breakpoint) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/reporting.rb deleted file mode 100644 index f3f8416905..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'rbconfig' -require 'tempfile' - -module Kernel - # Sets $VERBOSE to nil for the duration of the block and back to its original - # value afterwards. - # - # silence_warnings do - # value = noisy_call # no warning voiced - # end - # - # noisy_call # warning voiced - def silence_warnings - with_warnings(nil) { yield } - end - - # Sets $VERBOSE to +true+ for the duration of the block and back to its - # original value afterwards. - def enable_warnings - with_warnings(true) { yield } - end - - # Sets $VERBOSE for the duration of the block and back to its original - # value afterwards. - def with_warnings(flag) - old_verbose, $VERBOSE = $VERBOSE, flag - yield - ensure - $VERBOSE = old_verbose - end - - # For compatibility - def silence_stderr #:nodoc: - silence_stream(STDERR) { yield } - end - - # Silences any stream for the duration of the block. - # - # silence_stream(STDOUT) do - # puts 'This will never be seen' - # end - # - # puts 'But this will' - # - # This method is not thread-safe. - def silence_stream(stream) - old_stream = stream.dup - stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') - stream.sync = true - yield - ensure - stream.reopen(old_stream) - old_stream.close - end - - # Blocks and ignores any exception passed as argument if raised within the block. - # - # suppress(ZeroDivisionError) do - # 1/0 - # puts 'This code is NOT reached' - # end - # - # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' - def suppress(*exception_classes) - yield - rescue *exception_classes - end - - # Captures the given stream and returns it: - # - # stream = capture(:stdout) { puts 'notice' } - # stream # => "notice\n" - # - # stream = capture(:stderr) { warn 'error' } - # stream # => "error\n" - # - # even for subprocesses: - # - # stream = capture(:stdout) { system('echo notice') } - # stream # => "notice\n" - # - # stream = capture(:stderr) { system('echo error 1>&2') } - # stream # => "error\n" - def capture(stream) - stream = stream.to_s - captured_stream = Tempfile.new(stream) - stream_io = eval("$#{stream}") - origin_stream = stream_io.dup - stream_io.reopen(captured_stream) - - yield - - stream_io.rewind - return captured_stream.read - ensure - captured_stream.close - captured_stream.unlink - stream_io.reopen(origin_stream) - end - alias :silence :capture - - # Silences both STDOUT and STDERR, even for subprocesses. - # - # quietly { system 'bundle install' } - # - # This method is not thread-safe. - def quietly - silence_stream(STDOUT) do - silence_stream(STDERR) do - yield - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/load_error.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/load_error.rb deleted file mode 100644 index fe24f3716d..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/load_error.rb +++ /dev/null @@ -1,25 +0,0 @@ -class LoadError - REGEXPS = [ - /^no such file to load -- (.+)$/i, - /^Missing \w+ (?:file\s*)?([^\s]+.rb)$/i, - /^Missing API definition file in (.+)$/i, - /^cannot load such file -- (.+)$/i, - ] - - unless method_defined?(:path) - def path - @path ||= begin - REGEXPS.find do |regex| - message =~ regex - end - $1 - end - end - end - - def is_missing?(location) - location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '') - end -end - -MissingSourceFile = LoadError \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/marshal.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/marshal.rb deleted file mode 100644 index 56c79c04bd..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/marshal.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'active_support/core_ext/module/aliasing' - -module Marshal - class << self - def load_with_autoloading(source) - load_without_autoloading(source) - rescue ArgumentError, NameError => exc - if exc.message.match(%r|undefined class/module (.+)|) - # try loading the class/module - $1.constantize - # if it is a IO we need to go back to read the object - source.rewind if source.respond_to?(:rewind) - retry - else - raise exc - end - end - - alias_method_chain :load, :autoloading - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module.rb deleted file mode 100644 index b4efff8b24..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/module/introspection' -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/module/reachable' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/module/concerning' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/module/deprecation' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/qualified_const' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/aliasing.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/aliasing.rb deleted file mode 100644 index 580cb80413..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ /dev/null @@ -1,69 +0,0 @@ -class Module - # Encapsulates the common pattern of: - # - # alias_method :foo_without_feature, :foo - # alias_method :foo, :foo_with_feature - # - # With this, you simply do: - # - # alias_method_chain :foo, :feature - # - # And both aliases are set up for you. - # - # Query and bang methods (foo?, foo!) keep the same punctuation: - # - # alias_method_chain :foo?, :feature - # - # is equivalent to - # - # alias_method :foo_without_feature?, :foo? - # alias_method :foo?, :foo_with_feature? - # - # so you can safely chain foo, foo?, and foo! with the same feature. - def alias_method_chain(target, feature) - # Strip out punctuation on predicates or bang methods since - # e.g. target?_without_feature is not a valid method name. - aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 - yield(aliased_target, punctuation) if block_given? - - with_method = "#{aliased_target}_with_#{feature}#{punctuation}" - without_method = "#{aliased_target}_without_#{feature}#{punctuation}" - - alias_method without_method, target - alias_method target, with_method - - case - when public_method_defined?(without_method) - public target - when protected_method_defined?(without_method) - protected target - when private_method_defined?(without_method) - private target - end - end - - # Allows you to make aliases for attributes, which includes - # getter, setter, and query methods. - # - # class Content < ActiveRecord::Base - # # has a title attribute - # end - # - # class Email < Content - # alias_attribute :subject, :title - # end - # - # e = Email.find(1) - # e.title # => "Superstars" - # e.subject # => "Superstars" - # e.subject? # => true - # e.subject = "Megastars" - # e.title # => "Megastars" - def alias_attribute(new_name, old_name) - module_eval <<-STR, __FILE__, __LINE__ + 1 - def #{new_name}; self.#{old_name}; end # def subject; self.title; end - def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end - def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end - STR - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb deleted file mode 100644 index d317df5079..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ /dev/null @@ -1,212 +0,0 @@ -require 'active_support/core_ext/array/extract_options' - -# Extends the module object with class/module and instance accessors for -# class/module attributes, just like the native attr* accessors for instance -# attributes. -class Module - # Defines a class attribute and creates a class and instance reader methods. - # The underlying the class variable is set to +nil+, if it is not previously - # defined. - # - # module HairColors - # mattr_reader :hair_colors - # end - # - # HairColors.hair_colors # => nil - # HairColors.class_variable_set("@@hair_colors", [:brown, :black]) - # HairColors.hair_colors # => [:brown, :black] - # - # The attribute name must be a valid method name in Ruby. - # - # module Foo - # mattr_reader :"1_Badname " - # end - # # => NameError: invalid attribute name - # - # If you want to opt out the creation on the instance reader method, pass - # instance_reader: false or instance_accessor: false. - # - # module HairColors - # mattr_writer :hair_colors, instance_reader: false - # end - # - # class Person - # include HairColors - # end - # - # Person.new.hair_colors # => NoMethodError - # - # - # Also, you can pass a block to set up the attribute with a default value. - # - # module HairColors - # cattr_reader :hair_colors do - # [:brown, :black, :blonde, :red] - # end - # end - # - # class Person - # include HairColors - # end - # - # Person.hair_colors # => [:brown, :black, :blonde, :red] - def mattr_reader(*syms) - options = syms.extract_options! - syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - @@#{sym} = nil unless defined? @@#{sym} - - def self.#{sym} - @@#{sym} - end - EOS - - unless options[:instance_reader] == false || options[:instance_accessor] == false - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{sym} - @@#{sym} - end - EOS - end - class_variable_set("@@#{sym}", yield) if block_given? - end - end - alias :cattr_reader :mattr_reader - - # Defines a class attribute and creates a class and instance writer methods to - # allow assignment to the attribute. - # - # module HairColors - # mattr_writer :hair_colors - # end - # - # class Person - # include HairColors - # end - # - # HairColors.hair_colors = [:brown, :black] - # Person.class_variable_get("@@hair_colors") # => [:brown, :black] - # Person.new.hair_colors = [:blonde, :red] - # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red] - # - # If you want to opt out the instance writer method, pass - # instance_writer: false or instance_accessor: false. - # - # module HairColors - # mattr_writer :hair_colors, instance_writer: false - # end - # - # class Person - # include HairColors - # end - # - # Person.new.hair_colors = [:blonde, :red] # => NoMethodError - # - # Also, you can pass a block to set up the attribute with a default value. - # - # class HairColors - # mattr_writer :hair_colors do - # [:brown, :black, :blonde, :red] - # end - # end - # - # class Person - # include HairColors - # end - # - # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] - def mattr_writer(*syms) - options = syms.extract_options! - syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - @@#{sym} = nil unless defined? @@#{sym} - - def self.#{sym}=(obj) - @@#{sym} = obj - end - EOS - - unless options[:instance_writer] == false || options[:instance_accessor] == false - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{sym}=(obj) - @@#{sym} = obj - end - EOS - end - send("#{sym}=", yield) if block_given? - end - end - alias :cattr_writer :mattr_writer - - # Defines both class and instance accessors for class attributes. - # - # module HairColors - # mattr_accessor :hair_colors - # end - # - # class Person - # include HairColors - # end - # - # Person.hair_colors = [:brown, :black, :blonde, :red] - # Person.hair_colors # => [:brown, :black, :blonde, :red] - # Person.new.hair_colors # => [:brown, :black, :blonde, :red] - # - # If a subclass changes the value then that would also change the value for - # parent class. Similarly if parent class changes the value then that would - # change the value of subclasses too. - # - # class Male < Person - # end - # - # Male.hair_colors << :blue - # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue] - # - # To opt out of the instance writer method, pass instance_writer: false. - # To opt out of the instance reader method, pass instance_reader: false. - # - # module HairColors - # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false - # end - # - # class Person - # include HairColors - # end - # - # Person.new.hair_colors = [:brown] # => NoMethodError - # Person.new.hair_colors # => NoMethodError - # - # Or pass instance_accessor: false, to opt out both instance methods. - # - # module HairColors - # mattr_accessor :hair_colors, instance_accessor: false - # end - # - # class Person - # include HairColors - # end - # - # Person.new.hair_colors = [:brown] # => NoMethodError - # Person.new.hair_colors # => NoMethodError - # - # Also you can pass a block to set up the attribute with a default value. - # - # module HairColors - # mattr_accessor :hair_colors do - # [:brown, :black, :blonde, :red] - # end - # end - # - # class Person - # include HairColors - # end - # - # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red] - def mattr_accessor(*syms, &blk) - mattr_reader(*syms, &blk) - mattr_writer(*syms, &blk) - end - alias :cattr_accessor :mattr_accessor -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/delegation.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/delegation.rb deleted file mode 100644 index f855833a24..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/delegation.rb +++ /dev/null @@ -1,209 +0,0 @@ -class Module - # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ - # option is not used. - class DelegationError < NoMethodError; end - - # Provides a +delegate+ class method to easily expose contained objects' - # public methods as your own. - # - # ==== Options - # * :to - Specifies the target object - # * :prefix - Prefixes the new method with the target name or a custom prefix - # * :allow_nil - if set to true, prevents a +NoMethodError+ to be raised - # - # The macro receives one or more method names (specified as symbols or - # strings) and the name of the target object via the :to option - # (also a symbol or string). - # - # Delegation is particularly useful with Active Record associations: - # - # class Greeter < ActiveRecord::Base - # def hello - # 'hello' - # end - # - # def goodbye - # 'goodbye' - # end - # end - # - # class Foo < ActiveRecord::Base - # belongs_to :greeter - # delegate :hello, to: :greeter - # end - # - # Foo.new.hello # => "hello" - # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for # - # - # Multiple delegates to the same target are allowed: - # - # class Foo < ActiveRecord::Base - # belongs_to :greeter - # delegate :hello, :goodbye, to: :greeter - # end - # - # Foo.new.goodbye # => "goodbye" - # - # Methods can be delegated to instance variables, class variables, or constants - # by providing them as a symbols: - # - # class Foo - # CONSTANT_ARRAY = [0,1,2,3] - # @@class_array = [4,5,6,7] - # - # def initialize - # @instance_array = [8,9,10,11] - # end - # delegate :sum, to: :CONSTANT_ARRAY - # delegate :min, to: :@@class_array - # delegate :max, to: :@instance_array - # end - # - # Foo.new.sum # => 6 - # Foo.new.min # => 4 - # Foo.new.max # => 11 - # - # It's also possible to delegate a method to the class by using +:class+: - # - # class Foo - # def self.hello - # "world" - # end - # - # delegate :hello, to: :class - # end - # - # Foo.new.hello # => "world" - # - # Delegates can optionally be prefixed using the :prefix option. If the value - # is true, the delegate methods are prefixed with the name of the object being - # delegated to. - # - # Person = Struct.new(:name, :address) - # - # class Invoice < Struct.new(:client) - # delegate :name, :address, to: :client, prefix: true - # end - # - # john_doe = Person.new('John Doe', 'Vimmersvej 13') - # invoice = Invoice.new(john_doe) - # invoice.client_name # => "John Doe" - # invoice.client_address # => "Vimmersvej 13" - # - # It is also possible to supply a custom prefix. - # - # class Invoice < Struct.new(:client) - # delegate :name, :address, to: :client, prefix: :customer - # end - # - # invoice = Invoice.new(john_doe) - # invoice.customer_name # => 'John Doe' - # invoice.customer_address # => 'Vimmersvej 13' - # - # If the target is +nil+ and does not respond to the delegated method a - # +NoMethodError+ is raised, as with any other value. Sometimes, however, it - # makes sense to be robust to that situation and that is the purpose of the - # :allow_nil option: If the target is not +nil+, or it is and - # responds to the method, everything works as usual. But if it is +nil+ and - # does not respond to the delegated method, +nil+ is returned. - # - # class User < ActiveRecord::Base - # has_one :profile - # delegate :age, to: :profile - # end - # - # User.new.age # raises NoMethodError: undefined method `age' - # - # But if not having a profile yet is fine and should not be an error - # condition: - # - # class User < ActiveRecord::Base - # has_one :profile - # delegate :age, to: :profile, allow_nil: true - # end - # - # User.new.age # nil - # - # Note that if the target is not +nil+ then the call is attempted regardless of the - # :allow_nil option, and thus an exception is still raised if said object - # does not respond to the method: - # - # class Foo - # def initialize(bar) - # @bar = bar - # end - # - # delegate :name, to: :@bar, allow_nil: true - # end - # - # Foo.new("Bar").name # raises NoMethodError: undefined method `name' - # - # The target method must be public, otherwise it will raise +NoMethodError+. - # - def delegate(*methods) - options = methods.pop - unless options.is_a?(Hash) && to = options[:to] - raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).' - end - - prefix, allow_nil = options.values_at(:prefix, :allow_nil) - - if prefix == true && to =~ /^[^a-z_]/ - raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.' - end - - method_prefix = \ - if prefix - "#{prefix == true ? to : prefix}_" - else - '' - end - - file, line = caller.first.split(':', 2) - line = line.to_i - - to = to.to_s - to = 'self.class' if to == 'class' - - methods.each do |method| - # Attribute writer methods only accept one argument. Makes sure []= - # methods still accept two arguments. - definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' - - # The following generated methods call the target exactly once, storing - # the returned value in a dummy variable. - # - # Reason is twofold: On one hand doing less calls is in general better. - # On the other hand it could be that the target has side-effects, - # whereas conceptually, from the user point of view, the delegator should - # be doing one call. - if allow_nil - method_def = [ - "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block) - "_ = #{to}", # _ = client - "if !_.nil? || nil.respond_to?(:#{method})", # if !_.nil? || nil.respond_to?(:name) - " _.#{method}(#{definition})", # _.name(*args, &block) - "end", # end - "end" # end - ].join ';' - else - exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") - - method_def = [ - "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block) - " _ = #{to}", # _ = client - " _.#{method}(#{definition})", # _.name(*args, &block) - "rescue NoMethodError => e", # rescue NoMethodError => e - " if _.nil? && e.name == :#{method}", # if _.nil? && e.name == :name - " #{exception}", # # add helpful message to the exception - " else", # else - " raise", # raise - " end", # end - "end" # end - ].join ';' - end - - module_eval(method_def, file, line) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/introspection.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/introspection.rb deleted file mode 100644 index f1d26ef28f..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/introspection.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'active_support/inflector' - -class Module - # Returns the name of the module containing this one. - # - # M::N.parent_name # => "M" - def parent_name - if defined? @parent_name - @parent_name - else - @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil - end - end - - # Returns the module which contains this one according to its name. - # - # module M - # module N - # end - # end - # X = M::N - # - # M::N.parent # => M - # X.parent # => M - # - # The parent of top-level and anonymous modules is Object. - # - # M.parent # => Object - # Module.new.parent # => Object - def parent - parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object - end - - # Returns all the parents of this module according to its name, ordered from - # nested outwards. The receiver is not contained within the result. - # - # module M - # module N - # end - # end - # X = M::N - # - # M.parents # => [Object] - # M::N.parents # => [M, Object] - # X.parents # => [M, Object] - def parents - parents = [] - if parent_name - parts = parent_name.split('::') - until parts.empty? - parents << ActiveSupport::Inflector.constantize(parts * '::') - parts.pop - end - end - parents << Object unless parents.include? Object - parents - end - - def local_constants #:nodoc: - constants(false) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/method_transplanting.rb deleted file mode 100644 index b1097cc83b..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/method_transplanting.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Module - ### - # TODO: remove this after 1.9 support is dropped - def methods_transplantable? # :nodoc: - x = Module.new { def foo; end } - Module.new { define_method :bar, x.instance_method(:foo) } - true - rescue TypeError - false - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/qualified_const.rb deleted file mode 100644 index 65525013db..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/qualified_const.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'active_support/core_ext/string/inflections' - -#-- -# Allows code reuse in the methods below without polluting Module. -#++ -module QualifiedConstUtils - def self.raise_if_absolute(path) - raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/ - end - - def self.names(path) - path.split('::') - end -end - -## -# Extends the API for constants to be able to deal with qualified names. Arguments -# are assumed to be relative to the receiver. -# -#-- -# Qualified names are required to be relative because we are extending existing -# methods that expect constant names, ie, relative paths of length 1. For example, -# Object.const_get('::String') raises NameError and so does qualified_const_get. -#++ -class Module - def qualified_const_defined?(path, search_parents=true) - QualifiedConstUtils.raise_if_absolute(path) - - QualifiedConstUtils.names(path).inject(self) do |mod, name| - return unless mod.const_defined?(name, search_parents) - mod.const_get(name) - end - return true - end - - def qualified_const_get(path) - QualifiedConstUtils.raise_if_absolute(path) - - QualifiedConstUtils.names(path).inject(self) do |mod, name| - mod.const_get(name) - end - end - - def qualified_const_set(path, value) - QualifiedConstUtils.raise_if_absolute(path) - - const_name = path.demodulize - mod_name = path.deconstantize - mod = mod_name.empty? ? self : qualified_const_get(mod_name) - mod.const_set(const_name, value) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/reachable.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/reachable.rb deleted file mode 100644 index 5d3d0e9851..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/reachable.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/string/inflections' - -class Module - def reachable? #:nodoc: - !anonymous? && name.safe_constantize.equal?(self) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/remove_method.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/remove_method.rb deleted file mode 100644 index 719071d1c2..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ /dev/null @@ -1,12 +0,0 @@ -class Module - def remove_possible_method(method) - if method_defined?(method) || private_method_defined?(method) - undef_method(method) - end - end - - def redefine_method(method, &block) - remove_possible_method(method) - define_method(method, &block) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/name_error.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/name_error.rb deleted file mode 100644 index e1ebd4f91c..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/name_error.rb +++ /dev/null @@ -1,18 +0,0 @@ -class NameError - # Extract the name of the missing constant from the exception message. - def missing_name - if /undefined local variable or method/ !~ message - $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message - end - end - - # Was this exception raised because the given name was missing? - def missing_name?(name) - if name.is_a? Symbol - last_name = (missing_name || '').split('::').last - last_name == name.to_s - else - missing_name == name.to_s - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric.rb deleted file mode 100644 index a6bc0624be..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'active_support/core_ext/numeric/bytes' -require 'active_support/core_ext/numeric/time' -require 'active_support/core_ext/numeric/conversions' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/conversions.rb deleted file mode 100644 index 6d3635c69a..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ /dev/null @@ -1,135 +0,0 @@ -require 'active_support/core_ext/big_decimal/conversions' -require 'active_support/number_helper' - -class Numeric - - # Provides options for converting numbers into formatted strings. - # Options are provided for phone numbers, currency, percentage, - # precision, positional notation, file size and pretty printing. - # - # ==== Options - # - # For details on which formats use which options, see ActiveSupport::NumberHelper - # - # ==== Examples - # - # Phone Numbers: - # 5551234.to_s(:phone) # => 555-1234 - # 1235551234.to_s(:phone) # => 123-555-1234 - # 1235551234.to_s(:phone, area_code: true) # => (123) 555-1234 - # 1235551234.to_s(:phone, delimiter: ' ') # => 123 555 1234 - # 1235551234.to_s(:phone, area_code: true, extension: 555) # => (123) 555-1234 x 555 - # 1235551234.to_s(:phone, country_code: 1) # => +1-123-555-1234 - # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.') - # # => +1.123.555.1234 x 1343 - # - # Currency: - # 1234567890.50.to_s(:currency) # => $1,234,567,890.50 - # 1234567890.506.to_s(:currency) # => $1,234,567,890.51 - # 1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506 - # 1234567890.506.to_s(:currency, locale: :fr) # => 1 234 567 890,51 € - # -1234567890.50.to_s(:currency, negative_format: '(%u%n)') - # # => ($1,234,567,890.50) - # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '') - # # => £1234567890,50 - # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u') - # # => 1234567890,50 £ - # - # Percentage: - # 100.to_s(:percentage) # => 100.000% - # 100.to_s(:percentage, precision: 0) # => 100% - # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000% - # 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399% - # 1000.to_s(:percentage, locale: :fr) # => 1 000,000% - # 100.to_s(:percentage, format: '%n %') # => 100 % - # - # Delimited: - # 12345678.to_s(:delimited) # => 12,345,678 - # 12345678.05.to_s(:delimited) # => 12,345,678.05 - # 12345678.to_s(:delimited, delimiter: '.') # => 12.345.678 - # 12345678.to_s(:delimited, delimiter: ',') # => 12,345,678 - # 12345678.05.to_s(:delimited, separator: ' ') # => 12,345,678 05 - # 12345678.05.to_s(:delimited, locale: :fr) # => 12 345 678,05 - # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',') - # # => 98 765 432,98 - # - # Rounded: - # 111.2345.to_s(:rounded) # => 111.235 - # 111.2345.to_s(:rounded, precision: 2) # => 111.23 - # 13.to_s(:rounded, precision: 5) # => 13.00000 - # 389.32314.to_s(:rounded, precision: 0) # => 389 - # 111.2345.to_s(:rounded, significant: true) # => 111 - # 111.2345.to_s(:rounded, precision: 1, significant: true) # => 100 - # 13.to_s(:rounded, precision: 5, significant: true) # => 13.000 - # 111.234.to_s(:rounded, locale: :fr) # => 111,234 - # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true) - # # => 13 - # 389.32314.to_s(:rounded, precision: 4, significant: true) # => 389.3 - # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.') - # # => 1.111,23 - # - # Human-friendly size in Bytes: - # 123.to_s(:human_size) # => 123 Bytes - # 1234.to_s(:human_size) # => 1.21 KB - # 12345.to_s(:human_size) # => 12.1 KB - # 1234567.to_s(:human_size) # => 1.18 MB - # 1234567890.to_s(:human_size) # => 1.15 GB - # 1234567890123.to_s(:human_size) # => 1.12 TB - # 1234567.to_s(:human_size, precision: 2) # => 1.2 MB - # 483989.to_s(:human_size, precision: 2) # => 470 KB - # 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB - # 1234567890123.to_s(:human_size, precision: 5) # => "1.1229 TB" - # 524288000.to_s(:human_size, precision: 5) # => "500 MB" - # - # Human-friendly format: - # 123.to_s(:human) # => "123" - # 1234.to_s(:human) # => "1.23 Thousand" - # 12345.to_s(:human) # => "12.3 Thousand" - # 1234567.to_s(:human) # => "1.23 Million" - # 1234567890.to_s(:human) # => "1.23 Billion" - # 1234567890123.to_s(:human) # => "1.23 Trillion" - # 1234567890123456.to_s(:human) # => "1.23 Quadrillion" - # 1234567890123456789.to_s(:human) # => "1230 Quadrillion" - # 489939.to_s(:human, precision: 2) # => "490 Thousand" - # 489939.to_s(:human, precision: 4) # => "489.9 Thousand" - # 1234567.to_s(:human, precision: 4, - # significant: false) # => "1.2346 Million" - # 1234567.to_s(:human, precision: 1, - # separator: ',', - # significant: false) # => "1,2 Million" - def to_formatted_s(format = :default, options = {}) - case format - when :phone - return ActiveSupport::NumberHelper.number_to_phone(self, options) - when :currency - return ActiveSupport::NumberHelper.number_to_currency(self, options) - when :percentage - return ActiveSupport::NumberHelper.number_to_percentage(self, options) - when :delimited - return ActiveSupport::NumberHelper.number_to_delimited(self, options) - when :rounded - return ActiveSupport::NumberHelper.number_to_rounded(self, options) - when :human - return ActiveSupport::NumberHelper.number_to_human(self, options) - when :human_size - return ActiveSupport::NumberHelper.number_to_human_size(self, options) - else - self.to_default_s - end - end - - [Float, Fixnum, Bignum, BigDecimal].each do |klass| - klass.send(:alias_method, :to_default_s, :to_s) - - klass.send(:define_method, :to_s) do |*args| - if args[0].is_a?(Symbol) - format = args[0] - options = args[1] || {} - - self.to_formatted_s(format, options) - else - to_default_s(*args) - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/time.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/time.rb deleted file mode 100644 index 704c4248d9..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/numeric/time.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'active_support/duration' -require 'active_support/core_ext/time/calculations' -require 'active_support/core_ext/time/acts_like' - -class Numeric - # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. - # - # These methods use Time#advance for precise date calculations when using from_now, ago, etc. - # as well as adding or subtracting their results from a Time object. For example: - # - # # equivalent to Time.current.advance(months: 1) - # 1.month.from_now - # - # # equivalent to Time.current.advance(years: 2) - # 2.years.from_now - # - # # equivalent to Time.current.advance(months: 4, years: 5) - # (4.months + 5.years).from_now - # - # While these methods provide precise calculation when used as in the examples above, care - # should be taken to note that this is not true if the result of `months', `years', etc is - # converted before use: - # - # # equivalent to 30.days.to_i.from_now - # 1.month.to_i.from_now - # - # # equivalent to 365.25.days.to_f.from_now - # 1.year.to_f.from_now - # - # In such cases, Ruby's core - # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and - # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision - # date and time arithmetic. - def seconds - ActiveSupport::Duration.new(self, [[:seconds, self]]) - end - alias :second :seconds - - def minutes - ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]]) - end - alias :minute :minutes - - def hours - ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]]) - end - alias :hour :hours - - def days - ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) - end - alias :day :days - - def weeks - ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) - end - alias :week :weeks - - def fortnights - ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]]) - end - alias :fortnight :fortnights - - # Reads best without arguments: 10.minutes.ago - def ago(time = ::Time.current) - ActiveSupport::Deprecation.warn "Calling #ago or #until on a number (e.g. 5.ago) is deprecated and will be removed in the future, use 5.seconds.ago instead" - time - self - end - - # Reads best with argument: 10.minutes.until(time) - alias :until :ago - - # Reads best with argument: 10.minutes.since(time) - def since(time = ::Time.current) - ActiveSupport::Deprecation.warn "Calling #since or #from_now on a number (e.g. 5.since) is deprecated and will be removed in the future, use 5.seconds.since instead" - time + self - end - - # Reads best without arguments: 10.minutes.from_now - alias :from_now :since - - # Used with the standard time durations, like 1.hour.in_milliseconds -- - # so we can feed them to JavaScript functions like getTime(). - def in_milliseconds - self * 1000 - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object.rb deleted file mode 100644 index f4f9152d6a..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'active_support/core_ext/object/acts_like' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/object/deep_dup' -require 'active_support/core_ext/object/try' -require 'active_support/core_ext/object/inclusion' - -require 'active_support/core_ext/object/conversions' -require 'active_support/core_ext/object/instance_variables' - -require 'active_support/core_ext/object/json' -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/object/with_options' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/acts_like.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/acts_like.rb deleted file mode 100644 index 3912cc5ace..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/acts_like.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Object - # A duck-type assistant method. For example, Active Support extends Date - # to define an acts_like_date? method, and extends Time to define - # acts_like_time?. As a result, we can do x.acts_like?(:time) and - # x.acts_like?(:date) to do duck-type-safe comparisons, since classes that - # we want to act like Time simply need to define an acts_like_time? method. - def acts_like?(duck) - respond_to? :"acts_like_#{duck}?" - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/conversions.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/conversions.rb deleted file mode 100644 index 540f7aadb0..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/conversions.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/hash/conversions' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/duplicable.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/duplicable.rb deleted file mode 100644 index 9cd7485e2e..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ /dev/null @@ -1,90 +0,0 @@ -#-- -# Most objects are cloneable, but not all. For example you can't dup +nil+: -# -# nil.dup # => TypeError: can't dup NilClass -# -# Classes may signal their instances are not duplicable removing +dup+/+clone+ -# or raising exceptions from them. So, to dup an arbitrary object you normally -# use an optimistic approach and are ready to catch an exception, say: -# -# arbitrary_object.dup rescue object -# -# Rails dups objects in a few critical spots where they are not that arbitrary. -# That rescue is very expensive (like 40 times slower than a predicate), and it -# is often triggered. -# -# That's why we hardcode the following cases and check duplicable? instead of -# using that rescue idiom. -#++ -class Object - # Can you safely dup this object? - # - # False for +nil+, +false+, +true+, symbol, and number objects; - # true otherwise. - def duplicable? - true - end -end - -class NilClass - # +nil+ is not duplicable: - # - # nil.duplicable? # => false - # nil.dup # => TypeError: can't dup NilClass - def duplicable? - false - end -end - -class FalseClass - # +false+ is not duplicable: - # - # false.duplicable? # => false - # false.dup # => TypeError: can't dup FalseClass - def duplicable? - false - end -end - -class TrueClass - # +true+ is not duplicable: - # - # true.duplicable? # => false - # true.dup # => TypeError: can't dup TrueClass - def duplicable? - false - end -end - -class Symbol - # Symbols are not duplicable: - # - # :my_symbol.duplicable? # => false - # :my_symbol.dup # => TypeError: can't dup Symbol - def duplicable? - false - end -end - -class Numeric - # Numbers are not duplicable: - # - # 3.duplicable? # => false - # 3.dup # => TypeError: can't dup Fixnum - def duplicable? - false - end -end - -require 'bigdecimal' -class BigDecimal - begin - BigDecimal.new('4.56').dup - - def duplicable? - true - end - rescue TypeError - # can't dup, so use superclass implementation - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_json.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_json.rb deleted file mode 100644 index f58364f9c6..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_json.rb +++ /dev/null @@ -1,5 +0,0 @@ -ActiveSupport::Deprecation.warn 'You have required `active_support/core_ext/object/to_json`. ' \ - 'This file will be removed in Rails 4.2. You should require `active_support/core_ext/object/json` ' \ - 'instead.' - -require 'active_support/core_ext/object/json' \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_param.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_param.rb deleted file mode 100644 index 13be0038c2..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_param.rb +++ /dev/null @@ -1,62 +0,0 @@ -class Object - # Alias of to_s. - def to_param - to_s - end -end - -class NilClass - # Returns +self+. - def to_param - self - end -end - -class TrueClass - # Returns +self+. - def to_param - self - end -end - -class FalseClass - # Returns +self+. - def to_param - self - end -end - -class Array - # Calls to_param on all its elements and joins the result with - # slashes. This is used by url_for in Action Pack. - def to_param - collect { |e| e.to_param }.join '/' - end -end - -class Hash - # Returns a string representation of the receiver suitable for use as a URL - # query string: - # - # {name: 'David', nationality: 'Danish'}.to_param - # # => "name=David&nationality=Danish" - # - # An optional namespace can be passed to enclose the param names: - # - # {name: 'David', nationality: 'Danish'}.to_param('user') - # # => "user[name]=David&user[nationality]=Danish" - # - # The string pairs "key=value" that conform the query string - # are sorted lexicographically in ascending order. - # - # This method is also aliased as +to_query+. - def to_param(namespace = nil) - if empty? - namespace ? nil.to_query(namespace) : '' - else - collect do |key, value| - value.to_query(namespace ? "#{namespace}[#{key}]" : key) - end.sort! * '&' - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_query.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_query.rb deleted file mode 100644 index 37352fa608..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/to_query.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'active_support/core_ext/object/to_param' - -class Object - # Converts an object into a string suitable for use as a URL query string, using the given key as the - # param name. - # - # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work. - def to_query(key) - require 'cgi' unless defined?(CGI) && defined?(CGI::escape) - "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}" - end -end - -class Array - # Converts an array into a string suitable for use as a URL query string, - # using the given +key+ as the param name. - # - # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" - def to_query(key) - prefix = "#{key}[]" - - if empty? - nil.to_query(prefix) - else - collect { |value| value.to_query(prefix) }.join '&' - end - end -end - -class Hash - alias_method :to_query, :to_param -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/try.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/try.rb deleted file mode 100644 index 48190e1e66..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/try.rb +++ /dev/null @@ -1,78 +0,0 @@ -class Object - # Invokes the public method whose name goes as first argument just like - # +public_send+ does, except that if the receiver does not respond to it the - # call returns +nil+ rather than raising an exception. - # - # This method is defined to be able to write - # - # @person.try(:name) - # - # instead of - # - # @person ? @person.name : nil - # - # +try+ returns +nil+ when called on +nil+ regardless of whether it responds - # to the method: - # - # nil.try(:to_i) # => nil, rather than 0 - # - # Arguments and blocks are forwarded to the method if invoked: - # - # @posts.try(:each_slice, 2) do |a, b| - # ... - # end - # - # The number of arguments in the signature must match. If the object responds - # to the method the call is attempted and +ArgumentError+ is still raised - # otherwise. - # - # If +try+ is called without arguments it yields the receiver to a given - # block unless it is +nil+: - # - # @person.try do |p| - # ... - # end - # - # Please also note that +try+ is defined on +Object+, therefore it won't work - # with instances of classes that do not have +Object+ among their ancestors, - # like direct subclasses of +BasicObject+. For example, using +try+ with - # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on - # delegator itself. - def try(*a, &b) - if a.empty? && block_given? - yield self - else - public_send(*a, &b) if respond_to?(a.first) - end - end - - # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implement the tried method. - def try!(*a, &b) - if a.empty? && block_given? - yield self - else - public_send(*a, &b) - end - end -end - -class NilClass - # Calling +try+ on +nil+ always returns +nil+. - # It becomes specially helpful when navigating through associations that may return +nil+. - # - # nil.try(:name) # => nil - # - # Without +try+ - # @person && !@person.children.blank? && @person.children.first.name - # - # With +try+ - # @person.try(:children).try(:first).try(:name) - def try(*args) - nil - end - - def try!(*args) - nil - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/with_options.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/with_options.rb deleted file mode 100644 index 42e388b065..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/object/with_options.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'active_support/option_merger' - -class Object - # An elegant way to factor duplication out of options passed to a series of - # method calls. Each method called in the block, with the block variable as - # the receiver, will have its options merged with the default +options+ hash - # provided. Each method called on the block variable must take an options - # hash as its final argument. - # - # Without with_options>, this code contains duplication: - # - # class Account < ActiveRecord::Base - # has_many :customers, dependent: :destroy - # has_many :products, dependent: :destroy - # has_many :invoices, dependent: :destroy - # has_many :expenses, dependent: :destroy - # end - # - # Using with_options, we can remove the duplication: - # - # class Account < ActiveRecord::Base - # with_options dependent: :destroy do |assoc| - # assoc.has_many :customers - # assoc.has_many :products - # assoc.has_many :invoices - # assoc.has_many :expenses - # end - # end - # - # It can also be used with an explicit receiver: - # - # I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n| - # subject i18n.t :subject - # body i18n.t :body, user_name: user.name - # end - # - # with_options can also be nested since the call is forwarded to its receiver. - # Each nesting level will merge inherited defaults in addition to their own. - def with_options(options) - yield ActiveSupport::OptionMerger.new(self, options) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range.rb deleted file mode 100644 index 9368e81235..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'active_support/core_ext/range/conversions' -require 'active_support/core_ext/range/include_range' -require 'active_support/core_ext/range/overlaps' -require 'active_support/core_ext/range/each' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/conversions.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/conversions.rb deleted file mode 100644 index b1a12781f3..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/conversions.rb +++ /dev/null @@ -1,19 +0,0 @@ -class Range - RANGE_FORMATS = { - :db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" } - } - - # Gives a human readable format of the range. - # - # (1..100).to_formatted_s # => "1..100" - def to_formatted_s(format = :default) - if formatter = RANGE_FORMATS[format] - formatter.call(first, last) - else - to_default_s - end - end - - alias_method :to_default_s, :to_s - alias_method :to_s, :to_formatted_s -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/each.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/each.rb deleted file mode 100644 index ecef78f55f..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/each.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'active_support/core_ext/module/aliasing' - -class Range #:nodoc: - - def each_with_time_with_zone(&block) - ensure_iteration_allowed - each_without_time_with_zone(&block) - end - alias_method_chain :each, :time_with_zone - - def step_with_time_with_zone(n = 1, &block) - ensure_iteration_allowed - step_without_time_with_zone(n, &block) - end - alias_method_chain :step, :time_with_zone - - private - def ensure_iteration_allowed - if first.is_a?(Time) - raise TypeError, "can't iterate from #{first.class}" - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/include_range.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/include_range.rb deleted file mode 100644 index 3a07401c8a..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/range/include_range.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'active_support/core_ext/module/aliasing' - -class Range - # Extends the default Range#include? to support range comparisons. - # (1..5).include?(1..5) # => true - # (1..5).include?(2..3) # => true - # (1..5).include?(2..6) # => false - # - # The native Range#include? behavior is untouched. - # ('a'..'f').include?('c') # => true - # (5..9).include?(11) # => false - def include_with_range?(value) - if value.is_a?(::Range) - # 1...10 includes 1..9 but it does not include 1..10. - operator = exclude_end? && !value.exclude_end? ? :< : :<= - include_without_range?(value.first) && value.last.send(operator, last) - else - include_without_range?(value) - end - end - - alias_method_chain :include?, :range -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/regexp.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/regexp.rb deleted file mode 100644 index 784145f5fb..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/regexp.rb +++ /dev/null @@ -1,5 +0,0 @@ -class Regexp #:nodoc: - def multiline? - options & MULTILINE == MULTILINE - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string.rb deleted file mode 100644 index c656db2c6c..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_support/core_ext/string/conversions' -require 'active_support/core_ext/string/filters' -require 'active_support/core_ext/string/multibyte' -require 'active_support/core_ext/string/starts_ends_with' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/string/access' -require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/string/exclude' -require 'active_support/core_ext/string/strip' -require 'active_support/core_ext/string/inquiry' -require 'active_support/core_ext/string/indent' -require 'active_support/core_ext/string/zones' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/behavior.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/behavior.rb deleted file mode 100644 index 4aa960039b..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/behavior.rb +++ /dev/null @@ -1,6 +0,0 @@ -class String - # Enable more predictable duck-typing on String-like classes. See Object#acts_like?. - def acts_like_string? - true - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/filters.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/filters.rb deleted file mode 100644 index 49c0df6026..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/filters.rb +++ /dev/null @@ -1,65 +0,0 @@ -class String - # Returns the string, first removing all whitespace on both ends of - # the string, and then changing remaining consecutive whitespace - # groups into one space each. - # - # Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). - # - # %{ Multi-line - # string }.squish # => "Multi-line string" - # " foo bar \n \t boo".squish # => "foo bar boo" - def squish - dup.squish! - end - - # Performs a destructive squish. See String#squish. - def squish! - gsub!(/\A[[:space:]]+/, '') - gsub!(/[[:space:]]+\z/, '') - gsub!(/[[:space:]]+/, ' ') - self - end - - # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, ''). - def remove(pattern) - gsub pattern, '' - end - - # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, ''). - def remove!(pattern) - gsub! pattern, '' - end - - # Truncates a given +text+ after a given length if +text+ is longer than length: - # - # 'Once upon a time in a world far far away'.truncate(27) - # # => "Once upon a time in a wo..." - # - # Pass a string or regexp :separator to truncate +text+ at a natural break: - # - # 'Once upon a time in a world far far away'.truncate(27, separator: ' ') - # # => "Once upon a time in a..." - # - # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/) - # # => "Once upon a time in a..." - # - # The last characters will be replaced with the :omission string (defaults to "...") - # for a total length not exceeding length: - # - # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)') - # # => "And they f... (continued)" - def truncate(truncate_at, options = {}) - return dup unless length > truncate_at - - omission = options[:omission] || '...' - length_with_room_for_omission = truncate_at - omission.length - stop = \ - if options[:separator] - rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission - else - length_with_room_for_omission - end - - "#{self[0, stop]}#{omission}" - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/output_safety.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/output_safety.rb deleted file mode 100644 index 2c8995be9a..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ /dev/null @@ -1,243 +0,0 @@ -require 'erb' -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/deprecation' - -class ERB - module Util - HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } - JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } - HTML_ESCAPE_REGEXP = /[&"'><]/ - HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ - JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u - - # A utility method for escaping HTML tag characters. - # This method is also aliased as h. - # - # In your ERB templates, use this method to escape any unsafe content. For example: - # <%=h @person.name %> - # - # puts html_escape('is a > 0 & a < 10?') - # # => is a > 0 & a < 10? - def html_escape(s) - s = s.to_s - if s.html_safe? - s - else - s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE).html_safe - end - end - - # Aliasing twice issues a warning "discarding old...". Remove first to avoid it. - remove_method(:h) - alias h html_escape - - module_function :h - - singleton_class.send(:remove_method, :html_escape) - module_function :html_escape - - # A utility method for escaping HTML without affecting existing escaped entities. - # - # html_escape_once('1 < 2 & 3') - # # => "1 < 2 & 3" - # - # html_escape_once('<< Accept & Checkout') - # # => "<< Accept & Checkout" - def html_escape_once(s) - result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) - s.html_safe? ? result.html_safe : result - end - - module_function :html_escape_once - - # A utility method for escaping HTML entities in JSON strings. Specifically, the - # &, > and < characters are replaced with their equivalent unicode escaped form - - # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also - # escaped as they are treated as newline characters in some JavaScript engines. - # These sequences have identical meaning as the original characters inside the - # context of a JSON string, so assuming the input is a valid and well-formed - # JSON value, the output will have equivalent meaning when parsed: - # - # json = JSON.generate({ name: ""}) - # # => "{\"name\":\"\"}" - # - # json_escape(json) - # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}" - # - # JSON.parse(json) == JSON.parse(json_escape(json)) - # # => true - # - # The intended use case for this method is to escape JSON strings before including - # them inside a script tag to avoid XSS vulnerability: - # - # - # - # It is necessary to +raw+ the result of +json_escape+, so that quotation marks - # don't get converted to " entities. +json_escape+ doesn't - # automatically flag the result as HTML safe, since the raw value is unsafe to - # use inside HTML attributes. - # - # If you need to output JSON elsewhere in your HTML, you can just do something - # like this, as any unsafe characters (including quotation marks) will be - # automatically escaped for you: - # - #
...
- # - # WARNING: this helper only works with valid JSON. Using this on non-JSON values - # will open up serious XSS vulnerabilities. For example, if you replace the - # +current_user.to_json+ in the example above with user input instead, the browser - # will happily eval() that string as JavaScript. - # - # The escaping performed in this method is identical to those performed in the - # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is - # set to true. Because this transformation is idempotent, this helper can be - # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true. - # - # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+ - # is enabled, or if you are unsure where your JSON string originated from, it - # is recommended that you always apply this helper (other libraries, such as the - # JSON gem, do not provide this kind of protection by default; also some gems - # might override +to_json+ to bypass Active Support's encoder). - def json_escape(s) - result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) - s.html_safe? ? result.html_safe : result - end - - module_function :json_escape - end -end - -class Object - def html_safe? - false - end -end - -class Numeric - def html_safe? - true - end -end - -module ActiveSupport #:nodoc: - class SafeBuffer < String - UNSAFE_STRING_METHODS = %w( - capitalize chomp chop delete downcase gsub lstrip next reverse rstrip - slice squeeze strip sub succ swapcase tr tr_s upcase - ) - - alias_method :original_concat, :concat - private :original_concat - - class SafeConcatError < StandardError - def initialize - super 'Could not concatenate to the buffer because it is not html safe.' - end - end - - def [](*args) - if args.size < 2 - super - else - if html_safe? - new_safe_buffer = super - new_safe_buffer.instance_eval { @html_safe = true } - new_safe_buffer - else - to_str[*args] - end - end - end - - def safe_concat(value) - raise SafeConcatError unless html_safe? - original_concat(value) - end - - def initialize(*) - @html_safe = true - super - end - - def initialize_copy(other) - super - @html_safe = other.html_safe? - end - - def clone_empty - self[0, 0] - end - - %w[concat prepend].each do |method_name| - define_method method_name do |value| - super(html_escape_interpolated_argument(value)) - end - end - alias << concat - - def prepend!(value) - ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend - prepend value - end - - def +(other) - dup.concat(other) - end - - def %(args) - case args - when Hash - escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }] - else - escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) } - end - - self.class.new(super(escaped_args)) - end - - def html_safe? - defined?(@html_safe) && @html_safe - end - - def to_s - self - end - - def to_param - to_str - end - - def encode_with(coder) - coder.represent_scalar nil, to_str - end - - UNSAFE_STRING_METHODS.each do |unsafe_method| - if unsafe_method.respond_to?(unsafe_method) - class_eval <<-EOT, __FILE__, __LINE__ + 1 - def #{unsafe_method}(*args, &block) # def capitalize(*args, &block) - to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block) - end # end - - def #{unsafe_method}!(*args) # def capitalize!(*args) - @html_safe = false # @html_safe = false - super # super - end # end - EOT - end - end - - private - - def html_escape_interpolated_argument(arg) - (!html_safe? || arg.html_safe?) ? arg : ERB::Util.h(arg) - end - end -end - -class String - def html_safe - ActiveSupport::SafeBuffer.new(self) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb deleted file mode 100644 index 641acf62d0..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb +++ /dev/null @@ -1,4 +0,0 @@ -class String - alias_method :starts_with?, :start_with? - alias_method :ends_with?, :end_with? -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/struct.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/struct.rb deleted file mode 100644 index c2c30044f2..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/struct.rb +++ /dev/null @@ -1,6 +0,0 @@ -# Backport of Struct#to_h from Ruby 2.0 -class Struct # :nodoc: - def to_h - Hash[members.zip(values)] - end -end unless Struct.instance_methods.include?(:to_h) diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/thread.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/thread.rb deleted file mode 100644 index ac1ffa4128..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/thread.rb +++ /dev/null @@ -1,79 +0,0 @@ -class Thread - LOCK = Mutex.new # :nodoc: - - # Returns the value of a thread local variable that has been set. Note that - # these are different than fiber local values. - # - # Thread local values are carried along with threads, and do not respect - # fibers. For example: - # - # Thread.new { - # Thread.current.thread_variable_set("foo", "bar") # set a thread local - # Thread.current["foo"] = "bar" # set a fiber local - # - # Fiber.new { - # Fiber.yield [ - # Thread.current.thread_variable_get("foo"), # get the thread local - # Thread.current["foo"], # get the fiber local - # ] - # }.resume - # }.join.value # => ['bar', nil] - # - # The value "bar" is returned for the thread local, where +nil+ is returned - # for the fiber local. The fiber is executed in the same thread, so the - # thread local values are available. - def thread_variable_get(key) - _locals[key.to_sym] - end - - # Sets a thread local with +key+ to +value+. Note that these are local to - # threads, and not to fibers. Please see Thread#thread_variable_get for - # more information. - def thread_variable_set(key, value) - _locals[key.to_sym] = value - end - - # Returns an array of the names of the thread-local variables (as Symbols). - # - # thr = Thread.new do - # Thread.current.thread_variable_set(:cat, 'meow') - # Thread.current.thread_variable_set("dog", 'woof') - # end - # thr.join # => # - # thr.thread_variables # => [:dog, :cat] - # - # Note that these are not fiber local variables. Please see Thread#thread_variable_get - # for more details. - def thread_variables - _locals.keys - end - - # Returns true if the given string (or symbol) exists as a - # thread-local variable. - # - # me = Thread.current - # me.thread_variable_set(:oliver, "a") - # me.thread_variable?(:oliver) # => true - # me.thread_variable?(:stanley) # => false - # - # Note that these are not fiber local variables. Please see Thread#thread_variable_get - # for more details. - def thread_variable?(key) - _locals.has_key?(key.to_sym) - end - - def freeze - _locals.freeze - super - end - - private - - def _locals - if defined?(@_locals) - @_locals - else - LOCK.synchronize { @_locals ||= {} } - end - end -end unless Thread.instance_methods.include?(:thread_variable_set) diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time.rb deleted file mode 100644 index 32cffe237d..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/time/acts_like' -require 'active_support/core_ext/time/calculations' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/time/marshal' -require 'active_support/core_ext/time/zones' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/conversions.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/conversions.rb deleted file mode 100644 index 9fd26156c7..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/conversions.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'active_support/inflector/methods' -require 'active_support/values/time_zone' - -class Time - DATE_FORMATS = { - :db => '%Y-%m-%d %H:%M:%S', - :number => '%Y%m%d%H%M%S', - :nsec => '%Y%m%d%H%M%S%9N', - :time => '%H:%M', - :short => '%d %b %H:%M', - :long => '%B %d, %Y %H:%M', - :long_ordinal => lambda { |time| - day_format = ActiveSupport::Inflector.ordinalize(time.day) - time.strftime("%B #{day_format}, %Y %H:%M") - }, - :rfc822 => lambda { |time| - offset_format = time.formatted_offset(false) - time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") - }, - :iso8601 => lambda { |time| time.iso8601 } - } - - # Converts to a formatted string. See DATE_FORMATS for builtin formats. - # - # This method is aliased to to_s. - # - # time = Time.now # => Thu Jan 18 06:10:17 CST 2007 - # - # time.to_formatted_s(:time) # => "06:10" - # time.to_s(:time) # => "06:10" - # - # time.to_formatted_s(:db) # => "2007-01-18 06:10:17" - # time.to_formatted_s(:number) # => "20070118061017" - # time.to_formatted_s(:short) # => "18 Jan 06:10" - # time.to_formatted_s(:long) # => "January 18, 2007 06:10" - # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10" - # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" - # time.to_formatted_s(:iso8601) # => "2007-01-18T06:10:17-06:00" - # - # == Adding your own time formats to +to_formatted_s+ - # You can add your own formats to the Time::DATE_FORMATS hash. - # Use the format name as the hash key and either a strftime string - # or Proc instance that takes a time argument as the value. - # - # # config/initializers/time_formats.rb - # Time::DATE_FORMATS[:month_and_year] = '%B %Y' - # Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") } - def to_formatted_s(format = :default) - if formatter = DATE_FORMATS[format] - formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) - else - to_default_s - end - end - alias_method :to_default_s, :to_s - alias_method :to_s, :to_formatted_s - - # Returns the UTC offset as an +HH:MM formatted string. - # - # Time.local(2000).formatted_offset # => "-06:00" - # Time.local(2000).formatted_offset(false) # => "-0600" - def formatted_offset(colon = true, alternate_utc_string = nil) - utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon) - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/marshal.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/marshal.rb deleted file mode 100644 index 497c4c3fb8..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/marshal.rb +++ /dev/null @@ -1,30 +0,0 @@ -# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only -# preserves utc_offset. Preserve zone also, even though it may not -# work in some edge cases. -if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone - class Time - class << self - alias_method :_load_without_zone, :_load - def _load(marshaled_time) - time = _load_without_zone(marshaled_time) - time.instance_eval do - if zone = defined?(@_zone) && remove_instance_variable('@_zone') - ary = to_a - ary[0] += subsec if ary[0] == sec - ary[-1] = zone - utc? ? Time.utc(*ary) : Time.local(*ary) - else - self - end - end - end - end - - alias_method :_dump_without_zone, :_dump - def _dump(*args) - obj = dup - obj.instance_variable_set('@_zone', zone) - obj.send :_dump_without_zone, *args - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/zones.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/zones.rb deleted file mode 100644 index bbda04d60c..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/time/zones.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'active_support/time_with_zone' -require 'active_support/core_ext/date_and_time/zones' - -class Time - include DateAndTime::Zones - class << self - attr_accessor :zone_default - - # Returns the TimeZone for the current request, if this has been set (via Time.zone=). - # If Time.zone has not been set for the current request, returns the TimeZone specified in config.time_zone. - def zone - Thread.current[:time_zone] || zone_default - end - - # Sets Time.zone to a TimeZone object for the current request/thread. - # - # This method accepts any of the following: - # - # * A Rails TimeZone object. - # * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", -5.hours). - # * A TZInfo::Timezone object. - # * An identifier for a TZInfo::Timezone object (e.g., "America/New_York"). - # - # Here's an example of how you might set Time.zone on a per request basis and reset it when the request is done. - # current_user.time_zone just needs to return a string identifying the user's preferred time zone: - # - # class ApplicationController < ActionController::Base - # around_filter :set_time_zone - # - # def set_time_zone - # if logged_in? - # Time.use_zone(current_user.time_zone) { yield } - # else - # yield - # end - # end - # end - def zone=(time_zone) - Thread.current[:time_zone] = find_zone!(time_zone) - end - - # Allows override of Time.zone locally inside supplied block; resets Time.zone to existing value when done. - def use_zone(time_zone) - new_zone = find_zone!(time_zone) - begin - old_zone, ::Time.zone = ::Time.zone, new_zone - yield - ensure - ::Time.zone = old_zone - end - end - - # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones. - def find_zone!(time_zone) - if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) - time_zone - else - # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone) - unless time_zone.respond_to?(:period_for_local) - time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) - end - - # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone - if time_zone.is_a?(ActiveSupport::TimeZone) - time_zone - else - ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone) - end - end - rescue TZInfo::InvalidTimezoneIdentifier - raise ArgumentError, "Invalid Timezone: #{time_zone}" - end - - def find_zone(time_zone) - find_zone!(time_zone) rescue nil - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/uri.rb b/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/uri.rb deleted file mode 100644 index bfe0832b37..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/core_ext/uri.rb +++ /dev/null @@ -1,26 +0,0 @@ -# encoding: utf-8 - -require 'uri' -str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. -parser = URI::Parser.new - -unless str == parser.unescape(parser.escape(str)) - URI::Parser.class_eval do - remove_method :unescape - def unescape(str, escaped = /%[a-fA-F\d]{2}/) - # TODO: Are we actually sure that ASCII == UTF-8? - # YK: My initial experiments say yes, but let's be sure please - enc = str.encoding - enc = Encoding::UTF_8 if enc == Encoding::US_ASCII - str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc) - end - end -end - -module URI - class << self - def parser - @parser ||= URI::Parser.new - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/dependencies.rb b/app/server/ruby/vendor/activesupport/lib/active_support/dependencies.rb deleted file mode 100644 index 59675d744e..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/dependencies.rb +++ /dev/null @@ -1,748 +0,0 @@ -require 'set' -require 'thread' -require 'thread_safe' -require 'pathname' -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/module/introspection' -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/module/qualified_const' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/load_error' -require 'active_support/core_ext/name_error' -require 'active_support/core_ext/string/starts_ends_with' -require 'active_support/inflector' - -module ActiveSupport #:nodoc: - module Dependencies #:nodoc: - extend self - - # Should we turn on Ruby warnings on the first load of dependent files? - mattr_accessor :warnings_on_first_load - self.warnings_on_first_load = false - - # All files ever loaded. - mattr_accessor :history - self.history = Set.new - - # All files currently loaded. - mattr_accessor :loaded - self.loaded = Set.new - - # Should we load files or require them? - mattr_accessor :mechanism - self.mechanism = ENV['NO_RELOAD'] ? :require : :load - - # The set of directories from which we may automatically load files. Files - # under these directories will be reloaded on each request in development mode, - # unless the directory also appears in autoload_once_paths. - mattr_accessor :autoload_paths - self.autoload_paths = [] - - # The set of directories from which automatically loaded constants are loaded - # only once. All directories in this set must also be present in +autoload_paths+. - mattr_accessor :autoload_once_paths - self.autoload_once_paths = [] - - # An array of qualified constant names that have been loaded. Adding a name - # to this array will cause it to be unloaded the next time Dependencies are - # cleared. - mattr_accessor :autoloaded_constants - self.autoloaded_constants = [] - - # An array of constant names that need to be unloaded on every request. Used - # to allow arbitrary constants to be marked for unloading. - mattr_accessor :explicitly_unloadable_constants - self.explicitly_unloadable_constants = [] - - # The logger is used for generating information on the action run-time - # (including benchmarking) if available. Can be set to nil for no logging. - # Compatible with both Ruby's own Logger and Log4r loggers. - mattr_accessor :logger - - # Set to +true+ to enable logging of const_missing and file loads. - mattr_accessor :log_activity - self.log_activity = false - - # The WatchStack keeps a stack of the modules being watched as files are - # loaded. If a file in the process of being loaded (parent.rb) triggers the - # load of another file (child.rb) the stack will ensure that child.rb - # handles the new constants. - # - # If child.rb is being autoloaded, its constants will be added to - # autoloaded_constants. If it was being `require`d, they will be discarded. - # - # This is handled by walking back up the watch stack and adding the constants - # found by child.rb to the list of original constants in parent.rb. - class WatchStack - include Enumerable - - # @watching is a stack of lists of constants being watched. For instance, - # if parent.rb is autoloaded, the stack will look like [[Object]]. If - # parent.rb then requires namespace/child.rb, the stack will look like - # [[Object], [Namespace]]. - - def initialize - @watching = [] - @stack = Hash.new { |h,k| h[k] = [] } - end - - def each(&block) - @stack.each(&block) - end - - def watching? - !@watching.empty? - end - - # Returns a list of new constants found since the last call to - # watch_namespaces. - def new_constants - constants = [] - - # Grab the list of namespaces that we're looking for new constants under - @watching.last.each do |namespace| - # Retrieve the constants that were present under the namespace when watch_namespaces - # was originally called - original_constants = @stack[namespace].last - - mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace) - next unless mod.is_a?(Module) - - # Get a list of the constants that were added - new_constants = mod.local_constants - original_constants - - # self[namespace] returns an Array of the constants that are being evaluated - # for that namespace. For instance, if parent.rb requires child.rb, the first - # element of self[Object] will be an Array of the constants that were present - # before parent.rb was required. The second element will be an Array of the - # constants that were present before child.rb was required. - @stack[namespace].each do |namespace_constants| - namespace_constants.concat(new_constants) - end - - # Normalize the list of new constants, and add them to the list we will return - new_constants.each do |suffix| - constants << ([namespace, suffix] - ["Object"]).join("::") - end - end - constants - ensure - # A call to new_constants is always called after a call to watch_namespaces - pop_modules(@watching.pop) - end - - # Add a set of modules to the watch stack, remembering the initial - # constants. - def watch_namespaces(namespaces) - @watching << namespaces.map do |namespace| - module_name = Dependencies.to_constant_name(namespace) - original_constants = Dependencies.qualified_const_defined?(module_name) ? - Inflector.constantize(module_name).local_constants : [] - - @stack[module_name] << original_constants - module_name - end - end - - private - def pop_modules(modules) - modules.each { |mod| @stack[mod].pop } - end - end - - # An internal stack used to record which constants are loaded by any block. - mattr_accessor :constant_watch_stack - self.constant_watch_stack = WatchStack.new - - # Module includes this module. - module ModuleConstMissing #:nodoc: - def self.append_features(base) - base.class_eval do - # Emulate #exclude via an ivar - return if defined?(@_const_missing) && @_const_missing - @_const_missing = instance_method(:const_missing) - remove_method(:const_missing) - end - super - end - - def self.exclude_from(base) - base.class_eval do - define_method :const_missing, @_const_missing - @_const_missing = nil - end - end - - def const_missing(const_name) - from_mod = anonymous? ? guess_for_anonymous(const_name) : self - Dependencies.load_missing_constant(from_mod, const_name) - end - - # Dependencies assumes the name of the module reflects the nesting (unless - # it can be proven that is not the case), and the path to the file that - # defines the constant. Anonymous modules cannot follow these conventions - # and we assume therefore the user wants to refer to a top-level constant. - def guess_for_anonymous(const_name) - if Object.const_defined?(const_name) - raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module" - else - Object - end - end - - def unloadable(const_desc = self) - super(const_desc) - end - end - - # Object includes this module. - module Loadable #:nodoc: - def self.exclude_from(base) - base.class_eval { define_method(:load, Kernel.instance_method(:load)) } - end - - def require_or_load(file_name) - Dependencies.require_or_load(file_name) - end - - # Interprets a file using mechanism and marks its defined - # constants as autoloaded. file_name can be either a string or - # respond to to_path. - # - # Use this method in code that absolutely needs a certain constant to be - # defined at that point. A typical use case is to make constant name - # resolution deterministic for constants with the same relative name in - # different namespaces whose evaluation would depend on load order - # otherwise. - def require_dependency(file_name, message = "No such file to load -- %s") - file_name = file_name.to_path if file_name.respond_to?(:to_path) - unless file_name.is_a?(String) - raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" - end - - Dependencies.depend_on(file_name, message) - end - - def load_dependency(file) - if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching? - Dependencies.new_constants_in(Object) { yield } - else - yield - end - rescue Exception => exception # errors from loading file - exception.blame_file! file if exception.respond_to? :blame_file! - raise - end - - def load(file, wrap = false) - result = false - load_dependency(file) { result = super } - result - end - - def require(file) - result = false - load_dependency(file) { result = super } - result - end - - # Mark the given constant as unloadable. Unloadable constants are removed - # each time dependencies are cleared. - # - # Note that marking a constant for unloading need only be done once. Setup - # or init scripts may list each unloadable constant that may need unloading; - # each constant will be removed for every subsequent clear, as opposed to - # for the first clear. - # - # The provided constant descriptor may be a (non-anonymous) module or class, - # or a qualified constant name as a string or symbol. - # - # Returns +true+ if the constant was not previously marked for unloading, - # +false+ otherwise. - def unloadable(const_desc) - Dependencies.mark_for_unload const_desc - end - end - - # Exception file-blaming. - module Blamable #:nodoc: - def blame_file!(file) - (@blamed_files ||= []).unshift file - end - - def blamed_files - @blamed_files ||= [] - end - - def describe_blame - return nil if blamed_files.empty? - "This error occurred while loading the following files:\n #{blamed_files.join "\n "}" - end - - def copy_blame!(exc) - @blamed_files = exc.blamed_files.clone - self - end - end - - def hook! - Object.class_eval { include Loadable } - Module.class_eval { include ModuleConstMissing } - Exception.class_eval { include Blamable } - end - - def unhook! - ModuleConstMissing.exclude_from(Module) - Loadable.exclude_from(Object) - end - - def load? - mechanism == :load - end - - def depend_on(file_name, message = "No such file to load -- %s.rb") - path = search_for_file(file_name) - require_or_load(path || file_name) - rescue LoadError => load_error - if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] - load_error.message.replace(message % file_name) - load_error.copy_blame!(load_error) - end - raise - end - - def clear - log_call - loaded.clear - remove_unloadable_constants! - end - - def require_or_load(file_name, const_path = nil) - log_call file_name, const_path - file_name = $` if file_name =~ /\.rb\z/ - expanded = File.expand_path(file_name) - return if loaded.include?(expanded) - - # Record that we've seen this file *before* loading it to avoid an - # infinite loop with mutual dependencies. - loaded << expanded - - begin - if load? - log "loading #{file_name}" - - # Enable warnings if this file has not been loaded before and - # warnings_on_first_load is set. - load_args = ["#{file_name}.rb"] - load_args << const_path unless const_path.nil? - - if !warnings_on_first_load or history.include?(expanded) - result = load_file(*load_args) - else - enable_warnings { result = load_file(*load_args) } - end - else - log "requiring #{file_name}" - result = require file_name - end - rescue Exception - loaded.delete expanded - raise - end - - # Record history *after* loading so first load gets warnings. - history << expanded - result - end - - # Is the provided constant path defined? - def qualified_const_defined?(path) - Object.qualified_const_defined?(path.sub(/^::/, ''), false) - end - - # Given +path+, a filesystem path to a ruby file, return an array of - # constant paths which would cause Dependencies to attempt to load this - # file. - def loadable_constants_for_path(path, bases = autoload_paths) - path = $` if path =~ /\.rb\z/ - expanded_path = File.expand_path(path) - paths = [] - - bases.each do |root| - expanded_root = File.expand_path(root) - next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path - - nesting = expanded_path[(expanded_root.size)..-1] - nesting = nesting[1..-1] if nesting && nesting[0] == ?/ - next if nesting.blank? - - paths << nesting.camelize - end - - paths.uniq! - paths - end - - # Search for a file in autoload_paths matching the provided suffix. - def search_for_file(path_suffix) - path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb") - - autoload_paths.each do |root| - path = File.join(root, path_suffix) - return path if File.file? path - end - nil # Gee, I sure wish we had first_match ;-) - end - - # Does the provided path_suffix correspond to an autoloadable module? - # Instead of returning a boolean, the autoload base for this module is - # returned. - def autoloadable_module?(path_suffix) - autoload_paths.each do |load_path| - return load_path if File.directory? File.join(load_path, path_suffix) - end - nil - end - - def load_once_path?(path) - # to_s works around a ruby1.9 issue where String#starts_with?(Pathname) - # will raise a TypeError: no implicit conversion of Pathname into String - autoload_once_paths.any? { |base| path.starts_with? base.to_s } - end - - # Attempt to autoload the provided module name by searching for a directory - # matching the expected path suffix. If found, the module is created and - # assigned to +into+'s constants with the name +const_name+. Provided that - # the directory was loaded from a reloadable base path, it is added to the - # set of constants that are to be unloaded. - def autoload_module!(into, const_name, qualified_name, path_suffix) - return nil unless base_path = autoloadable_module?(path_suffix) - mod = Module.new - into.const_set const_name, mod - autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) - mod - end - - # Load the file at the provided path. +const_paths+ is a set of qualified - # constant names. When loading the file, Dependencies will watch for the - # addition of these constants. Each that is defined will be marked as - # autoloaded, and will be removed when Dependencies.clear is next called. - # - # If the second parameter is left off, then Dependencies will construct a - # set of names that the file at +path+ may define. See - # +loadable_constants_for_path+ for more details. - def load_file(path, const_paths = loadable_constants_for_path(path)) - log_call path, const_paths - const_paths = [const_paths].compact unless const_paths.is_a? Array - parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object } - - result = nil - newly_defined_paths = new_constants_in(*parent_paths) do - result = Kernel.load path - end - - autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) - autoloaded_constants.uniq! - log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty? - result - end - - # Returns the constant path for the provided parent and constant name. - def qualified_name_for(mod, name) - mod_name = to_constant_name mod - mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}" - end - - # Load the constant named +const_name+ which is missing from +from_mod+. If - # it is not possible to load the constant into from_mod, try its parent - # module using +const_missing+. - def load_missing_constant(from_mod, const_name) - log_call from_mod, const_name - - unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod) - raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" - end - - qualified_name = qualified_name_for from_mod, const_name - path_suffix = qualified_name.underscore - - file_path = search_for_file(path_suffix) - - if file_path - expanded = File.expand_path(file_path) - expanded.sub!(/\.rb\z/, '') - - if loaded.include?(expanded) - raise "Circular dependency detected while autoloading constant #{qualified_name}" - else - require_or_load(expanded, qualified_name) - raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false) - return from_mod.const_get(const_name) - end - elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) - return mod - elsif (parent = from_mod.parent) && parent != from_mod && - ! from_mod.parents.any? { |p| p.const_defined?(const_name, false) } - # If our parents do not have a constant named +const_name+ then we are free - # to attempt to load upwards. If they do have such a constant, then this - # const_missing must be due to from_mod::const_name, which should not - # return constants from from_mod's parents. - begin - # Since Ruby does not pass the nesting at the point the unknown - # constant triggered the callback we cannot fully emulate constant - # name lookup and need to make a trade-off: we are going to assume - # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even - # though it might not be. Counterexamples are - # - # class Foo::Bar - # Module.nesting # => [Foo::Bar] - # end - # - # or - # - # module M::N - # module S::T - # Module.nesting # => [S::T, M::N] - # end - # end - # - # for example. - return parent.const_missing(const_name) - rescue NameError => e - raise unless e.missing_name? qualified_name_for(parent, const_name) - end - end - - raise NameError, - "uninitialized constant #{qualified_name}", - caller.reject { |l| l.starts_with? __FILE__ } - end - - # Remove the constants that have been autoloaded, and those that have been - # marked for unloading. Before each constant is removed a callback is sent - # to its class/module if it implements +before_remove_const+. - # - # The callback implementation should be restricted to cleaning up caches, etc. - # as the environment will be in an inconsistent state, e.g. other constants - # may have already been unloaded and not accessible. - def remove_unloadable_constants! - autoloaded_constants.each { |const| remove_constant const } - autoloaded_constants.clear - Reference.clear! - explicitly_unloadable_constants.each { |const| remove_constant const } - end - - class ClassCache - def initialize - @store = ThreadSafe::Cache.new - end - - def empty? - @store.empty? - end - - def key?(key) - @store.key?(key) - end - - def get(key) - key = key.name if key.respond_to?(:name) - @store[key] ||= Inflector.constantize(key) - end - alias :[] :get - - def safe_get(key) - key = key.name if key.respond_to?(:name) - @store[key] ||= Inflector.safe_constantize(key) - end - - def store(klass) - return self unless klass.respond_to?(:name) - raise(ArgumentError, 'anonymous classes cannot be cached') if klass.name.empty? - @store[klass.name] = klass - self - end - - def clear! - @store.clear - end - end - - Reference = ClassCache.new - - # Store a reference to a class +klass+. - def reference(klass) - Reference.store klass - end - - # Get the reference for class named +name+. - # Raises an exception if referenced class does not exist. - def constantize(name) - Reference.get(name) - end - - # Get the reference for class named +name+ if one exists. - # Otherwise returns +nil+. - def safe_constantize(name) - Reference.safe_get(name) - end - - # Determine if the given constant has been automatically loaded. - def autoloaded?(desc) - return false if desc.is_a?(Module) && desc.anonymous? - name = to_constant_name desc - return false unless qualified_const_defined? name - return autoloaded_constants.include?(name) - end - - # Will the provided constant descriptor be unloaded? - def will_unload?(const_desc) - autoloaded?(const_desc) || - explicitly_unloadable_constants.include?(to_constant_name(const_desc)) - end - - # Mark the provided constant name for unloading. This constant will be - # unloaded on each request, not just the next one. - def mark_for_unload(const_desc) - name = to_constant_name const_desc - if explicitly_unloadable_constants.include? name - false - else - explicitly_unloadable_constants << name - true - end - end - - # Run the provided block and detect the new constants that were loaded during - # its execution. Constants may only be regarded as 'new' once -- so if the - # block calls +new_constants_in+ again, then the constants defined within the - # inner call will not be reported in this one. - # - # If the provided block does not run to completion, and instead raises an - # exception, any new constants are regarded as being only partially defined - # and will be removed immediately. - def new_constants_in(*descs) - log_call(*descs) - - constant_watch_stack.watch_namespaces(descs) - aborting = true - - begin - yield # Now yield to the code that is to define new constants. - aborting = false - ensure - new_constants = constant_watch_stack.new_constants - - log "New constants: #{new_constants * ', '}" - return new_constants unless aborting - - log "Error during loading, removing partially loaded constants " - new_constants.each { |c| remove_constant(c) }.clear - end - - [] - end - - # Convert the provided const desc to a qualified constant name (as a string). - # A module, class, symbol, or string may be provided. - def to_constant_name(desc) #:nodoc: - case desc - when String then desc.sub(/^::/, '') - when Symbol then desc.to_s - when Module - desc.name || - raise(ArgumentError, "Anonymous modules have no name to be referenced by") - else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" - end - end - - def remove_constant(const) #:nodoc: - # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo. - normalized = const.to_s.sub(/\A::/, '') - normalized.sub!(/\A(Object::)+/, '') - - constants = normalized.split('::') - to_remove = constants.pop - - # Remove the file path from the loaded list. - file_path = search_for_file(const.underscore) - if file_path - expanded = File.expand_path(file_path) - expanded.sub!(/\.rb\z/, '') - self.loaded.delete(expanded) - end - - if constants.empty? - parent = Object - else - # This method is robust to non-reachable constants. - # - # Non-reachable constants may be passed if some of the parents were - # autoloaded and already removed. It is easier to do a sanity check - # here than require the caller to be clever. We check the parent - # rather than the very const argument because we do not want to - # trigger Kernel#autoloads, see the comment below. - parent_name = constants.join('::') - return unless qualified_const_defined?(parent_name) - parent = constantize(parent_name) - end - - log "removing constant #{const}" - - # In an autoloaded user.rb like this - # - # autoload :Foo, 'foo' - # - # class User < ActiveRecord::Base - # end - # - # we correctly register "Foo" as being autoloaded. But if the app does - # not use the "Foo" constant we need to be careful not to trigger - # loading "foo.rb" ourselves. While #const_defined? and #const_get? do - # require the file, #autoload? and #remove_const don't. - # - # We are going to remove the constant nonetheless ---which exists as - # far as Ruby is concerned--- because if the user removes the macro - # call from a class or module that were not autoloaded, as in the - # example above with Object, accessing to that constant must err. - unless parent.autoload?(to_remove) - begin - constantized = parent.const_get(to_remove, false) - rescue NameError - log "the constant #{const} is not reachable anymore, skipping" - return - else - constantized.before_remove_const if constantized.respond_to?(:before_remove_const) - end - end - - begin - parent.instance_eval { remove_const to_remove } - rescue NameError - log "the constant #{const} is not reachable anymore, skipping" - end - end - - protected - def log_call(*args) - if log_activity? - arg_str = args.collect { |arg| arg.inspect } * ', ' - /in `([a-z_\?\!]+)'/ =~ caller(1).first - selector = $1 || '' - log "called #{selector}(#{arg_str})" - end - end - - def log(msg) - logger.debug "Dependencies: #{msg}" if log_activity? - end - - def log_activity? - logger && log_activity - end - end -end - -ActiveSupport::Dependencies.hook! diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/behaviors.rb b/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/behaviors.rb deleted file mode 100644 index 328b8c320a..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/behaviors.rb +++ /dev/null @@ -1,76 +0,0 @@ -require "active_support/notifications" - -module ActiveSupport - class DeprecationException < StandardError - end - - class Deprecation - # Default warning behaviors per Rails.env. - DEFAULT_BEHAVIORS = { - raise: ->(message, callstack) { - e = DeprecationException.new(message) - e.set_backtrace(callstack) - raise e - }, - - stderr: ->(message, callstack) { - $stderr.puts(message) - $stderr.puts callstack.join("\n ") if debug - }, - - log: ->(message, callstack) { - logger = - if defined?(Rails) && Rails.logger - Rails.logger - else - require 'active_support/logger' - ActiveSupport::Logger.new($stderr) - end - logger.warn message - logger.debug callstack.join("\n ") if debug - }, - - notify: ->(message, callstack) { - ActiveSupport::Notifications.instrument("deprecation.rails", - :message => message, :callstack => callstack) - }, - - silence: ->(message, callstack) {}, - } - - module Behavior - # Whether to print a backtrace along with the warning. - attr_accessor :debug - - # Returns the current behavior or if one isn't set, defaults to +:stderr+. - def behavior - @behavior ||= [DEFAULT_BEHAVIORS[:stderr]] - end - - # Sets the behavior to the specified value. Can be a single value, array, - # or an object that responds to +call+. - # - # Available behaviors: - # - # [+raise+] Raise ActiveSupport::DeprecationException. - # [+stderr+] Log all deprecation warnings to +$stderr+. - # [+log+] Log all deprecation warnings to +Rails.logger+. - # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. - # [+silence+] Do nothing. - # - # Setting behaviors only affects deprecations that happen after boot time. - # Deprecation warnings raised by gems are not affected by this setting - # because they happen before Rails boots up. - # - # ActiveSupport::Deprecation.behavior = :stderr - # ActiveSupport::Deprecation.behavior = [:stderr, :log] - # ActiveSupport::Deprecation.behavior = MyCustomHandler - # ActiveSupport::Deprecation.behavior = ->(message, callstack) { - # # custom stuff - # } - def behavior=(behavior) - @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b } - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/method_wrappers.rb b/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/method_wrappers.rb deleted file mode 100644 index cab8a1b14d..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/array/extract_options' - -module ActiveSupport - class Deprecation - module MethodWrapper - # Declare that a method has been deprecated. - # - # module Fred - # extend self - # - # def foo; end - # def bar; end - # def baz; end - # end - # - # ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead') - # # => [:foo, :bar, :baz] - # - # Fred.foo - # # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1." - # - # Fred.bar - # # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)." - # - # Fred.baz - # # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)." - def deprecate_methods(target_module, *method_names) - options = method_names.extract_options! - deprecator = options.delete(:deprecator) || ActiveSupport::Deprecation.instance - method_names += options.keys - - method_names.each do |method_name| - target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation| - target_module.send(:define_method, "#{target}_with_deprecation#{punctuation}") do |*args, &block| - deprecator.deprecation_warning(method_name, options[method_name]) - send(:"#{target}_without_deprecation#{punctuation}", *args, &block) - end - end - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/proxy_wrappers.rb deleted file mode 100644 index a03a66b96b..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ /dev/null @@ -1,126 +0,0 @@ -require 'active_support/inflector/methods' - -module ActiveSupport - class Deprecation - class DeprecationProxy #:nodoc: - def self.new(*args, &block) - object = args.first - - return object unless object - super - end - - instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$/ } - - # Don't give a deprecation warning on inspect since test/unit and error - # logs rely on it for diagnostics. - def inspect - target.inspect - end - - private - def method_missing(called, *args, &block) - warn caller, called, args - target.__send__(called, *args, &block) - end - end - - # This DeprecatedObjectProxy transforms object to deprecated object. - # - # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!") - # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance) - # - # When someone executes any method except +inspect+ on proxy object this will - # trigger +warn+ method on +deprecator_instance+. - # - # Default deprecator is ActiveSupport::Deprecation - class DeprecatedObjectProxy < DeprecationProxy - def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance) - @object = object - @message = message - @deprecator = deprecator - end - - private - def target - @object - end - - def warn(callstack, called, args) - @deprecator.warn(@message, callstack) - end - end - - # This DeprecatedInstanceVariableProxy transforms instance variable to - # deprecated instance variable. - # - # class Example - # def initialize(deprecator) - # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator) - # @_request = :a_request - # end - # - # def request - # @_request - # end - # - # def old_request - # @request - # end - # end - # - # When someone execute any method on @request variable this will trigger - # +warn+ method on +deprecator_instance+ and will fetch @_request - # variable via +request+ method and execute the same method on non-proxy - # instance variable. - # - # Default deprecator is ActiveSupport::Deprecation. - class DeprecatedInstanceVariableProxy < DeprecationProxy - def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance) - @instance = instance - @method = method - @var = var - @deprecator = deprecator - end - - private - def target - @instance.__send__(@method) - end - - def warn(callstack, called, args) - @deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack) - end - end - - # This DeprecatedConstantProxy transforms constant to deprecated constant. - # - # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST') - # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance) - # - # When someone use old constant this will trigger +warn+ method on - # +deprecator_instance+. - # - # Default deprecator is ActiveSupport::Deprecation. - class DeprecatedConstantProxy < DeprecationProxy - def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance) - @old_const = old_const - @new_const = new_const - @deprecator = deprecator - end - - def class - target.class - end - - private - def target - ActiveSupport::Inflector.constantize(@new_const.to_s) - end - - def warn(callstack, called, args) - @deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack) - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/reporting.rb b/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/reporting.rb deleted file mode 100644 index a7d265d732..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/deprecation/reporting.rb +++ /dev/null @@ -1,94 +0,0 @@ -module ActiveSupport - class Deprecation - module Reporting - # Whether to print a message (silent mode) - attr_accessor :silenced - # Name of gem where method is deprecated - attr_accessor :gem_name - - # Outputs a deprecation warning to the output configured by - # ActiveSupport::Deprecation.behavior. - # - # ActiveSupport::Deprecation.warn('something broke!') - # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" - def warn(message = nil, callstack = nil) - return if silenced - - callstack ||= caller(2) - deprecation_message(callstack, message).tap do |m| - behavior.each { |b| b.call(m, callstack) } - end - end - - # Silence deprecation warnings within the block. - # - # ActiveSupport::Deprecation.warn('something broke!') - # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" - # - # ActiveSupport::Deprecation.silence do - # ActiveSupport::Deprecation.warn('something broke!') - # end - # # => nil - def silence - old_silenced, @silenced = @silenced, true - yield - ensure - @silenced = old_silenced - end - - def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) - caller_backtrace ||= caller(2) - deprecated_method_warning(deprecated_method_name, message).tap do |msg| - warn(msg, caller_backtrace) - end - end - - private - # Outputs a deprecation warning message - # - # ActiveSupport::Deprecation.deprecated_method_warning(:method_name) - # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}" - # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method) - # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)" - # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message") - # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)" - def deprecated_method_warning(method_name, message = nil) - warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}" - case message - when Symbol then "#{warning} (use #{message} instead)" - when String then "#{warning} (#{message})" - else warning - end - end - - def deprecation_message(callstack, message = nil) - message ||= "You are using deprecated behavior which will be removed from the next major or minor release." - message += '.' unless message =~ /\.$/ - "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}" - end - - def deprecation_caller_message(callstack) - file, line, method = extract_callstack(callstack) - if file - if line && method - "(called from #{method} at #{file}:#{line})" - else - "(called from #{file}:#{line})" - end - end - end - - def extract_callstack(callstack) - rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" - offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first - if offending_line - if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) - md.captures - else - offending_line - end - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/descendants_tracker.rb b/app/server/ruby/vendor/activesupport/lib/active_support/descendants_tracker.rb deleted file mode 100644 index 27861e01d0..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/descendants_tracker.rb +++ /dev/null @@ -1,60 +0,0 @@ -module ActiveSupport - # This module provides an internal implementation to track descendants - # which is faster than iterating through ObjectSpace. - module DescendantsTracker - @@direct_descendants = {} - - class << self - def direct_descendants(klass) - @@direct_descendants[klass] || [] - end - - def descendants(klass) - arr = [] - accumulate_descendants(klass, arr) - arr - end - - def clear - if defined? ActiveSupport::Dependencies - @@direct_descendants.each do |klass, descendants| - if ActiveSupport::Dependencies.autoloaded?(klass) - @@direct_descendants.delete(klass) - else - descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } - end - end - else - @@direct_descendants.clear - end - end - - # This is the only method that is not thread safe, but is only ever called - # during the eager loading phase. - def store_inherited(klass, descendant) - (@@direct_descendants[klass] ||= []) << descendant - end - - private - def accumulate_descendants(klass, acc) - if direct_descendants = @@direct_descendants[klass] - acc.concat(direct_descendants) - direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) } - end - end - end - - def inherited(base) - DescendantsTracker.store_inherited(self, base) - super - end - - def direct_descendants - DescendantsTracker.direct_descendants(self) - end - - def descendants - DescendantsTracker.descendants(self) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/duration.rb b/app/server/ruby/vendor/activesupport/lib/active_support/duration.rb deleted file mode 100644 index 7df4857c25..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/duration.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'active_support/proxy_object' -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/object/acts_like' - -module ActiveSupport - # Provides accurate date and time measurements using Date#advance and - # Time#advance, respectively. It mainly supports the methods on Numeric. - # - # 1.month.ago # equivalent to Time.now.advance(months: -1) - class Duration < ProxyObject - attr_accessor :value, :parts - - def initialize(value, parts) #:nodoc: - @value, @parts = value, parts - end - - # Adds another Duration or a Numeric to this Duration. Numeric values - # are treated as seconds. - def +(other) - if Duration === other - Duration.new(value + other.value, @parts + other.parts) - else - Duration.new(value + other, @parts + [[:seconds, other]]) - end - end - - # Subtracts another Duration or a Numeric from this Duration. Numeric - # values are treated as seconds. - def -(other) - self + (-other) - end - - def -@ #:nodoc: - Duration.new(-value, parts.map { |type,number| [type, -number] }) - end - - def is_a?(klass) #:nodoc: - Duration == klass || value.is_a?(klass) - end - alias :kind_of? :is_a? - - # Returns +true+ if +other+ is also a Duration instance with the - # same +value+, or if other == value. - def ==(other) - if Duration === other - other.value == value - else - other == value - end - end - - def self.===(other) #:nodoc: - other.is_a?(Duration) - rescue ::NoMethodError - false - end - - # Calculates a new Time or Date that is as far in the future - # as this Duration represents. - def since(time = ::Time.current) - sum(1, time) - end - alias :from_now :since - - # Calculates a new Time or Date that is as far in the past - # as this Duration represents. - def ago(time = ::Time.current) - sum(-1, time) - end - alias :until :ago - - def inspect #:nodoc: - parts. - reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }. - sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}. - map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}. - to_sentence(:locale => :en) - end - - def as_json(options = nil) #:nodoc: - to_i - end - - protected - - def sum(sign, time = ::Time.current) #:nodoc: - parts.inject(time) do |t,(type,number)| - if t.acts_like?(:time) || t.acts_like?(:date) - if type == :seconds - t.since(sign * number) - else - t.advance(type => sign * number) - end - else - raise ::ArgumentError, "expected a time or date, got #{time.inspect}" - end - end - end - - private - - # We define it as a workaround to Ruby 2.0.0-p353 bug. - # For more information, check rails/rails#13055. - # It should be dropped once a new Ruby patch-level - # release after 2.0.0-p353 happens. - def ===(other) #:nodoc: - value === other - end - - def method_missing(method, *args, &block) #:nodoc: - value.send(method, *args, &block) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/file_watcher.rb b/app/server/ruby/vendor/activesupport/lib/active_support/file_watcher.rb deleted file mode 100644 index 81e63e76a7..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/file_watcher.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActiveSupport - class FileWatcher - class Backend - def initialize(path, watcher) - @watcher = watcher - @path = path - end - - def trigger(files) - @watcher.trigger(files) - end - end - - def initialize - @regex_matchers = {} - end - - def watch(pattern, &block) - @regex_matchers[pattern] = block - end - - def trigger(files) - trigger_files = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } } - - files.each do |file, state| - @regex_matchers.each do |pattern, block| - trigger_files[block][state] << file if pattern === file - end - end - - trigger_files.each do |block, payload| - block.call payload - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/gem_version.rb b/app/server/ruby/vendor/activesupport/lib/active_support/gem_version.rb deleted file mode 100644 index 83a3bf7a5d..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/gem_version.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveSupport - # Returns the version of the currently loaded ActiveSupport as a Gem::Version - def self.gem_version - Gem::Version.new VERSION::STRING - end - - module VERSION - MAJOR = 4 - MINOR = 2 - TINY = 0 - PRE = "alpha" - - STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/hash_with_indifferent_access.rb b/app/server/ruby/vendor/activesupport/lib/active_support/hash_with_indifferent_access.rb deleted file mode 100644 index a4ebdea598..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ /dev/null @@ -1,272 +0,0 @@ -require 'active_support/core_ext/hash/keys' - -module ActiveSupport - # Implements a hash where keys :foo and "foo" are considered - # to be the same. - # - # rgb = ActiveSupport::HashWithIndifferentAccess.new - # - # rgb[:black] = '#000000' - # rgb[:black] # => '#000000' - # rgb['black'] # => '#000000' - # - # rgb['white'] = '#FFFFFF' - # rgb[:white] # => '#FFFFFF' - # rgb['white'] # => '#FFFFFF' - # - # Internally symbols are mapped to strings when used as keys in the entire - # writing interface (calling []=, merge, etc). This - # mapping belongs to the public interface. For example, given: - # - # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1) - # - # You are guaranteed that the key is returned as a string: - # - # hash.keys # => ["a"] - # - # Technically other types of keys are accepted: - # - # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1) - # hash[0] = 0 - # hash # => {"a"=>1, 0=>0} - # - # but this class is intended for use cases where strings or symbols are the - # expected keys and it is convenient to understand both as the same. For - # example the +params+ hash in Ruby on Rails. - # - # Note that core extensions define Hash#with_indifferent_access: - # - # rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access - # - # which may be handy. - class HashWithIndifferentAccess < Hash - # Returns +true+ so that Array#extract_options! finds members of - # this class. - def extractable_options? - true - end - - def with_indifferent_access - dup - end - - def nested_under_indifferent_access - self - end - - def initialize(constructor = {}) - if constructor.respond_to?(:to_hash) - super() - update(constructor.to_hash) - else - super(constructor) - end - end - - def default(key = nil) - if key.is_a?(Symbol) && include?(key = key.to_s) - self[key] - else - super - end - end - - def self.new_from_hash_copying_default(hash) - hash = hash.to_hash - new(hash).tap do |new_hash| - new_hash.default = hash.default - end - end - - def self.[](*args) - new.merge!(Hash[*args]) - end - - alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) - alias_method :regular_update, :update unless method_defined?(:regular_update) - - # Assigns a new value to the hash: - # - # hash = ActiveSupport::HashWithIndifferentAccess.new - # hash[:key] = 'value' - # - # This value can be later fetched using either +:key+ or +'key'+. - def []=(key, value) - regular_writer(convert_key(key), convert_value(value, for: :assignment)) - end - - alias_method :store, :[]= - - # Updates the receiver in-place, merging in the hash passed as argument: - # - # hash_1 = ActiveSupport::HashWithIndifferentAccess.new - # hash_1[:key] = 'value' - # - # hash_2 = ActiveSupport::HashWithIndifferentAccess.new - # hash_2[:key] = 'New Value!' - # - # hash_1.update(hash_2) # => {"key"=>"New Value!"} - # - # The argument can be either an - # ActiveSupport::HashWithIndifferentAccess or a regular +Hash+. - # In either case the merge respects the semantics of indifferent access. - # - # If the argument is a regular hash with keys +:key+ and +"key"+ only one - # of the values end up in the receiver, but which one is unspecified. - # - # When given a block, the value for duplicated keys will be determined - # by the result of invoking the block with the duplicated key, the value - # in the receiver, and the value in +other_hash+. The rules for duplicated - # keys follow the semantics of indifferent access: - # - # hash_1[:key] = 10 - # hash_2['key'] = 12 - # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22} - def update(other_hash) - if other_hash.is_a? HashWithIndifferentAccess - super(other_hash) - else - other_hash.to_hash.each_pair do |key, value| - if block_given? && key?(key) - value = yield(convert_key(key), self[key], value) - end - regular_writer(convert_key(key), convert_value(value)) - end - self - end - end - - alias_method :merge!, :update - - # Checks the hash for a key matching the argument passed in: - # - # hash = ActiveSupport::HashWithIndifferentAccess.new - # hash['key'] = 'value' - # hash.key?(:key) # => true - # hash.key?('key') # => true - def key?(key) - super(convert_key(key)) - end - - alias_method :include?, :key? - alias_method :has_key?, :key? - alias_method :member?, :key? - - # Same as Hash#fetch where the key passed as argument can be - # either a string or a symbol: - # - # counters = ActiveSupport::HashWithIndifferentAccess.new - # counters[:foo] = 1 - # - # counters.fetch('foo') # => 1 - # counters.fetch(:bar, 0) # => 0 - # counters.fetch(:bar) { |key| 0 } # => 0 - # counters.fetch(:zoo) # => KeyError: key not found: "zoo" - def fetch(key, *extras) - super(convert_key(key), *extras) - end - - # Returns an array of the values at the specified indices: - # - # hash = ActiveSupport::HashWithIndifferentAccess.new - # hash[:a] = 'x' - # hash[:b] = 'y' - # hash.values_at('a', 'b') # => ["x", "y"] - def values_at(*indices) - indices.collect { |key| self[convert_key(key)] } - end - - # Returns an exact copy of the hash. - def dup - self.class.new(self).tap do |new_hash| - new_hash.default = default - end - end - - # This method has the same semantics of +update+, except it does not - # modify the receiver but rather returns a new hash with indifferent - # access with the result of the merge. - def merge(hash, &block) - self.dup.update(hash, &block) - end - - # Like +merge+ but the other way around: Merges the receiver into the - # argument and returns a new hash with indifferent access as result: - # - # hash = ActiveSupport::HashWithIndifferentAccess.new - # hash['a'] = nil - # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1} - def reverse_merge(other_hash) - super(self.class.new_from_hash_copying_default(other_hash)) - end - - # Same semantics as +reverse_merge+ but modifies the receiver in-place. - def reverse_merge!(other_hash) - replace(reverse_merge( other_hash )) - end - - # Replaces the contents of this hash with other_hash. - # - # h = { "a" => 100, "b" => 200 } - # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400} - def replace(other_hash) - super(self.class.new_from_hash_copying_default(other_hash)) - end - - # Removes the specified key from the hash. - def delete(key) - super(convert_key(key)) - end - - def stringify_keys!; self end - def deep_stringify_keys!; self end - def stringify_keys; dup end - def deep_stringify_keys; dup end - undef :symbolize_keys! - undef :deep_symbolize_keys! - def symbolize_keys; to_hash.symbolize_keys! end - def deep_symbolize_keys; to_hash.deep_symbolize_keys! end - def to_options!; self end - - def select(*args, &block) - dup.tap { |hash| hash.select!(*args, &block) } - end - - def reject(*args, &block) - dup.tap { |hash| hash.reject!(*args, &block) } - end - - # Convert to a regular hash with string keys. - def to_hash - _new_hash= {} - each do |key, value| - _new_hash[convert_key(key)] = convert_value(value, for: :to_hash) - end - Hash.new(default).merge!(_new_hash) - end - - protected - def convert_key(key) - key.kind_of?(Symbol) ? key.to_s : key - end - - def convert_value(value, options = {}) - if value.is_a? Hash - if options[:for] == :to_hash - value.to_hash - else - value.nested_under_indifferent_access - end - elsif value.is_a?(Array) - unless options[:for] == :assignment - value = value.dup - end - value.map! { |e| convert_value(e, options) } - else - value - end - end - end -end - -HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/i18n.rb b/app/server/ruby/vendor/activesupport/lib/active_support/i18n.rb deleted file mode 100644 index 6cc98191d4..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/i18n.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' -begin - require 'i18n' -rescue LoadError => e - $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install" - raise e -end -require 'active_support/lazy_load_hooks' - -ActiveSupport.run_load_hooks(:i18n) -I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/inflector.rb b/app/server/ruby/vendor/activesupport/lib/active_support/inflector.rb deleted file mode 100644 index 215a60eba7..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/inflector.rb +++ /dev/null @@ -1,7 +0,0 @@ -# in case active_support/inflector is required without the rest of active_support -require 'active_support/inflector/inflections' -require 'active_support/inflector/transliterate' -require 'active_support/inflector/methods' - -require 'active_support/inflections' -require 'active_support/core_ext/string/inflections' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/inflector/methods.rb b/app/server/ruby/vendor/activesupport/lib/active_support/inflector/methods.rb deleted file mode 100644 index a270c4452f..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/inflector/methods.rb +++ /dev/null @@ -1,360 +0,0 @@ -# encoding: utf-8 - -require 'active_support/inflections' - -module ActiveSupport - # The Inflector transforms words from singular to plural, class names to table - # names, modularized class names to ones without, and class names to foreign - # keys. The default inflections for pluralization, singularization, and - # uncountable words are kept in inflections.rb. - # - # The Rails core team has stated patches for the inflections library will not - # be accepted in order to avoid breaking legacy applications which may be - # relying on errant inflections. If you discover an incorrect inflection and - # require it for your application or wish to define rules for languages other - # than English, please correct or add them yourself (explained below). - module Inflector - extend self - - # Returns the plural form of the word in the string. - # - # If passed an optional +locale+ parameter, the word will be - # pluralized using rules defined for that language. By default, - # this parameter is set to :en. - # - # 'post'.pluralize # => "posts" - # 'octopus'.pluralize # => "octopi" - # 'sheep'.pluralize # => "sheep" - # 'words'.pluralize # => "words" - # 'CamelOctopus'.pluralize # => "CamelOctopi" - # 'ley'.pluralize(:es) # => "leyes" - def pluralize(word, locale = :en) - apply_inflections(word, inflections(locale).plurals) - end - - # The reverse of +pluralize+, returns the singular form of a word in a - # string. - # - # If passed an optional +locale+ parameter, the word will be - # singularized using rules defined for that language. By default, - # this parameter is set to :en. - # - # 'posts'.singularize # => "post" - # 'octopi'.singularize # => "octopus" - # 'sheep'.singularize # => "sheep" - # 'word'.singularize # => "word" - # 'CamelOctopi'.singularize # => "CamelOctopus" - # 'leyes'.singularize(:es) # => "ley" - def singularize(word, locale = :en) - apply_inflections(word, inflections(locale).singulars) - end - - # By default, +camelize+ converts strings to UpperCamelCase. If the argument - # to +camelize+ is set to :lower then +camelize+ produces - # lowerCamelCase. - # - # +camelize+ will also convert '/' to '::' which is useful for converting - # paths to namespaces. - # - # 'active_model'.camelize # => "ActiveModel" - # 'active_model'.camelize(:lower) # => "activeModel" - # 'active_model/errors'.camelize # => "ActiveModel::Errors" - # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors" - # - # As a rule of thumb you can think of +camelize+ as the inverse of - # +underscore+, though there are cases where that does not hold: - # - # 'SSLError'.underscore.camelize # => "SslError" - def camelize(term, uppercase_first_letter = true) - string = term.to_s - if uppercase_first_letter - string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } - else - string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } - end - string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } - string.gsub!('/', '::') - string - end - - # Makes an underscored, lowercase form from the expression in the string. - # - # Changes '::' to '/' to convert namespaces to paths. - # - # 'ActiveModel'.underscore # => "active_model" - # 'ActiveModel::Errors'.underscore # => "active_model/errors" - # - # As a rule of thumb you can think of +underscore+ as the inverse of - # +camelize+, though there are cases where that does not hold: - # - # 'SSLError'.underscore.camelize # => "SslError" - def underscore(camel_cased_word) - return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/ - word = camel_cased_word.to_s.gsub('::', '/') - word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } - word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') - word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') - word.tr!("-", "_") - word.downcase! - word - end - - # Capitalizes the first word, turns underscores into spaces, and strips a - # trailing '_id' if present. - # Like +titleize+, this is meant for creating pretty output. - # - # The capitalization of the first word can be turned off by setting the - # optional parameter +capitalize+ to false. - # By default, this parameter is true. - # - # humanize('employee_salary') # => "Employee salary" - # humanize('author_id') # => "Author" - # humanize('author_id', capitalize: false) # => "author" - def humanize(lower_case_and_underscored_word, options = {}) - result = lower_case_and_underscored_word.to_s.dup - inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } - result.gsub!(/_id$/, "") - result.tr!('_', ' ') - result.gsub!(/([a-z\d]*)/i) { |match| - "#{inflections.acronyms[match] || match.downcase}" - } - result.gsub!(/^\w/) { |match| match.upcase } if options.fetch(:capitalize, true) - result - end - - # Capitalizes all the words and replaces some characters in the string to - # create a nicer looking title. +titleize+ is meant for creating pretty - # output. It is not used in the Rails internals. - # - # +titleize+ is also aliased as +titlecase+. - # - # 'man from the boondocks'.titleize # => "Man From The Boondocks" - # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" - # 'TheManWithoutAPast'.titleize # => "The Man Without A Past" - # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark" - def titleize(word) - humanize(underscore(word)).gsub(/\b(? "raw_scaled_scorers" - # 'egg_and_ham'.tableize # => "egg_and_hams" - # 'fancyCategory'.tableize # => "fancy_categories" - def tableize(class_name) - pluralize(underscore(class_name)) - end - - # Create a class name from a plural table name like Rails does for table - # names to models. Note that this returns a string and not a Class (To - # convert to an actual class follow +classify+ with +constantize+). - # - # 'egg_and_hams'.classify # => "EggAndHam" - # 'posts'.classify # => "Post" - # - # Singular names are not handled correctly: - # - # 'business'.classify # => "Busines" - def classify(table_name) - # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) - end - - # Replaces underscores with dashes in the string. - # - # 'puni_puni'.dasherize # => "puni-puni" - def dasherize(underscored_word) - underscored_word.tr('_', '-') - end - - # Removes the module part from the expression in the string. - # - # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" - # 'Inflections'.demodulize # => "Inflections" - # - # See also +deconstantize+. - def demodulize(path) - path = path.to_s - if i = path.rindex('::') - path[(i+2)..-1] - else - path - end - end - - # Removes the rightmost segment from the constant expression in the string. - # - # 'Net::HTTP'.deconstantize # => "Net" - # '::Net::HTTP'.deconstantize # => "::Net" - # 'String'.deconstantize # => "" - # '::String'.deconstantize # => "" - # ''.deconstantize # => "" - # - # See also +demodulize+. - def deconstantize(path) - path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename - end - - # Creates a foreign key name from a class name. - # +separate_class_name_and_id_with_underscore+ sets whether - # the method should put '_' between the name and 'id'. - # - # 'Message'.foreign_key # => "message_id" - # 'Message'.foreign_key(false) # => "messageid" - # 'Admin::Post'.foreign_key # => "post_id" - def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) - underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") - end - - # Tries to find a constant with the name specified in the argument string. - # - # 'Module'.constantize # => Module - # 'Test::Unit'.constantize # => Test::Unit - # - # The name is assumed to be the one of a top-level constant, no matter - # whether it starts with "::" or not. No lexical context is taken into - # account: - # - # C = 'outside' - # module M - # C = 'inside' - # C # => 'inside' - # 'C'.constantize # => 'outside', same as ::C - # end - # - # NameError is raised when the name is not in CamelCase or the constant is - # unknown. - def constantize(camel_cased_word) - names = camel_cased_word.split('::') - - # Trigger a builtin NameError exception including the ill-formed constant in the message. - Object.const_get(camel_cased_word) if names.empty? - - # Remove the first blank element in case of '::ClassName' notation. - names.shift if names.size > 1 && names.first.empty? - - names.inject(Object) do |constant, name| - if constant == Object - constant.const_get(name) - else - candidate = constant.const_get(name) - next candidate if constant.const_defined?(name, false) - next candidate unless Object.const_defined?(name) - - # Go down the ancestors to check it it's owned - # directly before we reach Object or the end of ancestors. - constant = constant.ancestors.inject do |const, ancestor| - break const if ancestor == Object - break ancestor if ancestor.const_defined?(name, false) - const - end - - # owner is in Object, so raise - constant.const_get(name, false) - end - end - end - - # Tries to find a constant with the name specified in the argument string. - # - # 'Module'.safe_constantize # => Module - # 'Test::Unit'.safe_constantize # => Test::Unit - # - # The name is assumed to be the one of a top-level constant, no matter - # whether it starts with "::" or not. No lexical context is taken into - # account: - # - # C = 'outside' - # module M - # C = 'inside' - # C # => 'inside' - # 'C'.safe_constantize # => 'outside', same as ::C - # end - # - # +nil+ is returned when the name is not in CamelCase or the constant (or - # part of it) is unknown. - # - # 'blargle'.safe_constantize # => nil - # 'UnknownModule'.safe_constantize # => nil - # 'UnknownModule::Foo::Bar'.safe_constantize # => nil - def safe_constantize(camel_cased_word) - constantize(camel_cased_word) - rescue NameError => e - raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ || - e.name.to_s == camel_cased_word.to_s - rescue ArgumentError => e - raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ - end - - # Returns the suffix that should be added to a number to denote the position - # in an ordered sequence such as 1st, 2nd, 3rd, 4th. - # - # ordinal(1) # => "st" - # ordinal(2) # => "nd" - # ordinal(1002) # => "nd" - # ordinal(1003) # => "rd" - # ordinal(-11) # => "th" - # ordinal(-1021) # => "st" - def ordinal(number) - abs_number = number.to_i.abs - - if (11..13).include?(abs_number % 100) - "th" - else - case abs_number % 10 - when 1; "st" - when 2; "nd" - when 3; "rd" - else "th" - end - end - end - - # Turns a number into an ordinal string used to denote the position in an - # ordered sequence such as 1st, 2nd, 3rd, 4th. - # - # ordinalize(1) # => "1st" - # ordinalize(2) # => "2nd" - # ordinalize(1002) # => "1002nd" - # ordinalize(1003) # => "1003rd" - # ordinalize(-11) # => "-11th" - # ordinalize(-1021) # => "-1021st" - def ordinalize(number) - "#{number}#{ordinal(number)}" - end - - private - - # Mount a regular expression that will match part by part of the constant. - # - # const_regexp("Foo::Bar::Baz") # => /Foo(::Bar(::Baz)?)?/ - # const_regexp("::") # => /::/ - def const_regexp(camel_cased_word) #:nodoc: - parts = camel_cased_word.split("::") - - return Regexp.escape(camel_cased_word) if parts.blank? - - last = parts.pop - - parts.reverse.inject(last) do |acc, part| - part.empty? ? acc : "#{part}(::#{acc})?" - end - end - - # Applies inflection rules for +singularize+ and +pluralize+. - # - # apply_inflections('post', inflections.plurals) # => "posts" - # apply_inflections('posts', inflections.singulars) # => "post" - def apply_inflections(word, rules) - result = word.to_s.dup - - if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/]) - result - else - rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } - result - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/inflector/transliterate.rb b/app/server/ruby/vendor/activesupport/lib/active_support/inflector/transliterate.rb deleted file mode 100644 index 1cde417fc5..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/inflector/transliterate.rb +++ /dev/null @@ -1,97 +0,0 @@ -# encoding: utf-8 -require 'active_support/core_ext/string/multibyte' -require 'active_support/i18n' - -module ActiveSupport - module Inflector - - # Replaces non-ASCII characters with an ASCII approximation, or if none - # exists, a replacement character which defaults to "?". - # - # transliterate('Ærøskøbing') - # # => "AEroskobing" - # - # Default approximations are provided for Western/Latin characters, - # e.g, "ø", "ñ", "é", "ß", etc. - # - # This method is I18n aware, so you can set up custom approximations for a - # locale. This can be useful, for example, to transliterate German's "ü" - # and "ö" to "ue" and "oe", or to add support for transliterating Russian - # to ASCII. - # - # In order to make your custom transliterations available, you must set - # them as the i18n.transliterate.rule i18n key: - # - # # Store the transliterations in locales/de.yml - # i18n: - # transliterate: - # rule: - # ü: "ue" - # ö: "oe" - # - # # Or set them using Ruby - # I18n.backend.store_translations(:de, i18n: { - # transliterate: { - # rule: { - # 'ü' => 'ue', - # 'ö' => 'oe' - # } - # } - # }) - # - # The value for i18n.transliterate.rule can be a simple Hash that - # maps characters to ASCII approximations as shown above, or, for more - # complex requirements, a Proc: - # - # I18n.backend.store_translations(:de, i18n: { - # transliterate: { - # rule: ->(string) { MyTransliterator.transliterate(string) } - # } - # }) - # - # Now you can have different transliterations for each locale: - # - # I18n.locale = :en - # transliterate('Jürgen') - # # => "Jurgen" - # - # I18n.locale = :de - # transliterate('Jürgen') - # # => "Juergen" - def transliterate(string, replacement = "?") - I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize( - ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c), - :replacement => replacement) - end - - # Replaces special characters in a string so that it may be used as part of - # a 'pretty' URL. - # - # class Person - # def to_param - # "#{id}-#{name.parameterize}" - # end - # end - # - # @person = Person.find(1) - # # => # - # - # <%= link_to(@person.name, person_path(@person)) %> - # # => Donald E. Knuth - def parameterize(string, sep = '-') - # replace accented chars with their ascii equivalents - parameterized_string = transliterate(string) - # Turn unwanted chars into the separator - parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep) - unless sep.nil? || sep.empty? - re_sep = Regexp.escape(sep) - # No more than one of the separator in a row. - parameterized_string.gsub!(/#{re_sep}{2,}/, sep) - # Remove leading/trailing separator. - parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '') - end - parameterized_string.downcase - end - - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/json.rb b/app/server/ruby/vendor/activesupport/lib/active_support/json.rb deleted file mode 100644 index 3e1d9b1d33..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/json.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'active_support/json/decoding' -require 'active_support/json/encoding' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/key_generator.rb b/app/server/ruby/vendor/activesupport/lib/active_support/key_generator.rb deleted file mode 100644 index 51d2da3a79..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/key_generator.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'thread_safe' -require 'openssl' - -module ActiveSupport - # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2 - # It can be used to derive a number of keys for various purposes from a given secret. - # This lets Rails applications have a single secure secret, but avoid reusing that - # key in multiple incompatible contexts. - class KeyGenerator - def initialize(secret, options = {}) - @secret = secret - # The default iterations are higher than required for our key derivation uses - # on the off chance someone uses this for password storage - @iterations = options[:iterations] || 2**16 - end - - # Returns a derived key suitable for use. The default key_size is chosen - # to be compatible with the default settings of ActiveSupport::MessageVerifier. - # i.e. OpenSSL::Digest::SHA1#block_length - def generate_key(salt, key_size=64) - OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) - end - end - - # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid - # re-executing the key generation process when it's called using the same salt and - # key_size - class CachingKeyGenerator - def initialize(key_generator) - @key_generator = key_generator - @cache_keys = ThreadSafe::Cache.new - end - - # Returns a derived key suitable for use. The default key_size is chosen - # to be compatible with the default settings of ActiveSupport::MessageVerifier. - # i.e. OpenSSL::Digest::SHA1#block_length - def generate_key(salt, key_size=64) - @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size) - end - end - - class LegacyKeyGenerator # :nodoc: - SECRET_MIN_LENGTH = 30 # Characters - - def initialize(secret) - ensure_secret_secure(secret) - @secret = secret - end - - def generate_key(salt) - @secret - end - - private - - # To prevent users from using something insecure like "Password" we make sure that the - # secret they've provided is at least 30 characters in length. - def ensure_secret_secure(secret) - if secret.blank? - raise ArgumentError, "A secret is required to generate an integrity hash " \ - "for cookie session data. Set a secret_key_base of at least " \ - "#{SECRET_MIN_LENGTH} characters in config/secrets.yml." - end - - if secret.length < SECRET_MIN_LENGTH - raise ArgumentError, "Secret should be something secure, " \ - "like \"#{SecureRandom.hex(16)}\". The value you " \ - "provided, \"#{secret}\", is shorter than the minimum length " \ - "of #{SECRET_MIN_LENGTH} characters." - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/lazy_load_hooks.rb b/app/server/ruby/vendor/activesupport/lib/active_support/lazy_load_hooks.rb deleted file mode 100644 index e2b8f0f648..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/lazy_load_hooks.rb +++ /dev/null @@ -1,48 +0,0 @@ -module ActiveSupport - # lazy_load_hooks allows Rails to lazily load a lot of components and thus - # making the app boot faster. Because of this feature now there is no need to - # require ActiveRecord::Base at boot time purely to apply - # configuration. Instead a hook is registered that applies configuration once - # ActiveRecord::Base is loaded. Here ActiveRecord::Base is - # used as example but this feature can be applied elsewhere too. - # - # Here is an example where +on_load+ method is called to register a hook. - # - # initializer 'active_record.initialize_timezone' do - # ActiveSupport.on_load(:active_record) do - # self.time_zone_aware_attributes = true - # self.default_timezone = :utc - # end - # end - # - # When the entirety of +activerecord/lib/active_record/base.rb+ has been - # evaluated then +run_load_hooks+ is invoked. The very last line of - # +activerecord/lib/active_record/base.rb+ is: - # - # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) - @load_hooks = Hash.new { |h,k| h[k] = [] } - @loaded = Hash.new { |h,k| h[k] = [] } - - def self.on_load(name, options = {}, &block) - @loaded[name].each do |base| - execute_hook(base, options, block) - end - - @load_hooks[name] << [block, options] - end - - def self.execute_hook(base, options, block) - if options[:yield] - block.call(base) - else - base.instance_eval(&block) - end - end - - def self.run_load_hooks(name, base = Object) - @loaded[name] << base - @load_hooks[name].each do |hook, options| - execute_hook(base, options, hook) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/logger.rb b/app/server/ruby/vendor/activesupport/lib/active_support/logger.rb deleted file mode 100644 index 33fccdcf95..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/logger.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/logger_silence' -require 'logger' - -module ActiveSupport - class Logger < ::Logger - include LoggerSilence - - # Broadcasts logs to multiple loggers. - def self.broadcast(logger) # :nodoc: - Module.new do - define_method(:add) do |*args, &block| - logger.add(*args, &block) - super(*args, &block) - end - - define_method(:<<) do |x| - logger << x - super(x) - end - - define_method(:close) do - logger.close - super() - end - - define_method(:progname=) do |name| - logger.progname = name - super(name) - end - - define_method(:formatter=) do |formatter| - logger.formatter = formatter - super(formatter) - end - - define_method(:level=) do |level| - logger.level = level - super(level) - end - end - end - - def initialize(*args) - super - @formatter = SimpleFormatter.new - end - - # Simple formatter which only displays the message. - class SimpleFormatter < ::Logger::Formatter - # This method is invoked when a log event occurs - def call(severity, timestamp, progname, msg) - "#{String === msg ? msg : msg.inspect}\n" - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/logger_silence.rb b/app/server/ruby/vendor/activesupport/lib/active_support/logger_silence.rb deleted file mode 100644 index a8efdef944..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/logger_silence.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'active_support/concern' - -module LoggerSilence - extend ActiveSupport::Concern - - included do - cattr_accessor :silencer - self.silencer = true - end - - # Silences the logger for the duration of the block. - def silence(temporary_level = Logger::ERROR) - if silencer - begin - old_logger_level, self.level = level, temporary_level - yield self - ensure - self.level = old_logger_level - end - else - yield self - end - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/message_encryptor.rb b/app/server/ruby/vendor/activesupport/lib/active_support/message_encryptor.rb deleted file mode 100644 index b019ad0dec..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/message_encryptor.rb +++ /dev/null @@ -1,106 +0,0 @@ -require 'openssl' -require 'base64' -require 'active_support/core_ext/array/extract_options' - -module ActiveSupport - # MessageEncryptor is a simple way to encrypt values which get stored - # somewhere you don't trust. - # - # The cipher text and initialization vector are base64 encoded and returned - # to you. - # - # This can be used in situations similar to the MessageVerifier, but - # where you don't want users to be able to determine the value of the payload. - # - # salt = SecureRandom.random_bytes(64) - # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..." - # crypt = ActiveSupport::MessageEncryptor.new(key) # => # - # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." - # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" - class MessageEncryptor - module NullSerializer #:nodoc: - def self.load(value) - value - end - - def self.dump(value) - value - end - end - - class InvalidMessage < StandardError; end - OpenSSLCipherError = OpenSSL::Cipher::CipherError - - # Initialize a new MessageEncryptor. +secret+ must be at least as long as - # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256 - # bits. If you are using a user-entered secret, you can generate a suitable - # key with OpenSSL::Digest::SHA256.new(user_secret).digest or - # similar. - # - # Options: - # * :cipher - Cipher to use. Can be any cipher returned by - # OpenSSL::Cipher.ciphers. Default is 'aes-256-cbc'. - # * :serializer - Object serializer to use. Default is +Marshal+. - def initialize(secret, *signature_key_or_options) - options = signature_key_or_options.extract_options! - sign_secret = signature_key_or_options.first - @secret = secret - @sign_secret = sign_secret - @cipher = options[:cipher] || 'aes-256-cbc' - @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer) - @serializer = options[:serializer] || Marshal - end - - # Encrypt and sign a message. We need to sign the message in order to avoid - # padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks. - def encrypt_and_sign(value) - verifier.generate(_encrypt(value)) - end - - # Decrypt and verify a message. We need to verify the message in order to - # avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks. - def decrypt_and_verify(value) - _decrypt(verifier.verify(value)) - end - - private - - def _encrypt(value) - cipher = new_cipher - cipher.encrypt - cipher.key = @secret - - # Rely on OpenSSL for the initialization vector - iv = cipher.random_iv - - encrypted_data = cipher.update(@serializer.dump(value)) - encrypted_data << cipher.final - - "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" - end - - def _decrypt(encrypted_message) - cipher = new_cipher - encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)} - - cipher.decrypt - cipher.key = @secret - cipher.iv = iv - - decrypted_data = cipher.update(encrypted_data) - decrypted_data << cipher.final - - @serializer.load(decrypted_data) - rescue OpenSSLCipherError, TypeError, ArgumentError - raise InvalidMessage - end - - def new_cipher - OpenSSL::Cipher::Cipher.new(@cipher) - end - - def verifier - @verifier - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/message_verifier.rb b/app/server/ruby/vendor/activesupport/lib/active_support/message_verifier.rb deleted file mode 100644 index 8e6e1dcfeb..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/message_verifier.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'base64' -require 'active_support/core_ext/object/blank' - -module ActiveSupport - # +MessageVerifier+ makes it easy to generate and verify messages which are - # signed to prevent tampering. - # - # This is useful for cases like remember-me tokens and auto-unsubscribe links - # where the session store isn't suitable or available. - # - # Remember Me: - # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now]) - # - # In the authentication filter: - # - # id, time = @verifier.verify(cookies[:remember_me]) - # if time < Time.now - # self.current_user = User.find(id) - # end - # - # By default it uses Marshal to serialize the message. If you want to use - # another serialization method, you can set the serializer in the options - # hash upon initialization: - # - # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML) - class MessageVerifier - class InvalidSignature < StandardError; end - - def initialize(secret, options = {}) - @secret = secret - @digest = options[:digest] || 'SHA1' - @serializer = options[:serializer] || Marshal - end - - def verify(signed_message) - raise InvalidSignature if signed_message.blank? - - data, digest = signed_message.split("--") - if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) - begin - @serializer.load(::Base64.strict_decode64(data)) - rescue ArgumentError => argument_error - raise InvalidSignature if argument_error.message =~ %r{invalid base64} - raise - end - else - raise InvalidSignature - end - end - - def generate(value) - data = ::Base64.strict_encode64(@serializer.dump(value)) - "#{data}--#{generate_digest(data)}" - end - - private - # constant-time comparison algorithm to prevent timing attacks - def secure_compare(a, b) - return false unless a.bytesize == b.bytesize - - l = a.unpack "C#{a.bytesize}" - - res = 0 - b.each_byte { |byte| res |= byte ^ l.shift } - res == 0 - end - - def generate_digest(data) - require 'openssl' unless defined?(OpenSSL) - OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/multibyte/unicode.rb b/app/server/ruby/vendor/activesupport/lib/active_support/multibyte/unicode.rb deleted file mode 100644 index ea3cdcd024..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/multibyte/unicode.rb +++ /dev/null @@ -1,390 +0,0 @@ -# encoding: utf-8 -module ActiveSupport - module Multibyte - module Unicode - - extend self - - # A list of all available normalization forms. - # See http://www.unicode.org/reports/tr15/tr15-29.html for more - # information about normalization. - NORMALIZATION_FORMS = [:c, :kc, :d, :kd] - - # The Unicode version that is supported by the implementation - UNICODE_VERSION = '6.3.0' - - # The default normalization used for operations that require - # normalization. It can be set to any of the normalizations - # in NORMALIZATION_FORMS. - # - # ActiveSupport::Multibyte::Unicode.default_normalization_form = :c - attr_accessor :default_normalization_form - @default_normalization_form = :kc - - # Hangul character boundaries and properties - HANGUL_SBASE = 0xAC00 - HANGUL_LBASE = 0x1100 - HANGUL_VBASE = 0x1161 - HANGUL_TBASE = 0x11A7 - HANGUL_LCOUNT = 19 - HANGUL_VCOUNT = 21 - HANGUL_TCOUNT = 28 - HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT - HANGUL_SCOUNT = 11172 - HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT - HANGUL_JAMO_FIRST = 0x1100 - HANGUL_JAMO_LAST = 0x11FF - - # All the unicode whitespace - WHITESPACE = [ - (0x0009..0x000D).to_a, # White_Space # Cc [5] .. - 0x0020, # White_Space # Zs SPACE - 0x0085, # White_Space # Cc - 0x00A0, # White_Space # Zs NO-BREAK SPACE - 0x1680, # White_Space # Zs OGHAM SPACE MARK - 0x180E, # White_Space # Zs MONGOLIAN VOWEL SEPARATOR - (0x2000..0x200A).to_a, # White_Space # Zs [11] EN QUAD..HAIR SPACE - 0x2028, # White_Space # Zl LINE SEPARATOR - 0x2029, # White_Space # Zp PARAGRAPH SEPARATOR - 0x202F, # White_Space # Zs NARROW NO-BREAK SPACE - 0x205F, # White_Space # Zs MEDIUM MATHEMATICAL SPACE - 0x3000, # White_Space # Zs IDEOGRAPHIC SPACE - ].flatten.freeze - - # BOM (byte order mark) can also be seen as whitespace, it's a - # non-rendering character used to distinguish between little and big - # endian. This is not an issue in utf-8, so it must be ignored. - LEADERS_AND_TRAILERS = WHITESPACE + [65279] # ZERO-WIDTH NO-BREAK SPACE aka BOM - - # Returns a regular expression pattern that matches the passed Unicode - # codepoints. - def self.codepoints_to_pattern(array_of_codepoints) #:nodoc: - array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|') - end - TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u - LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u - - # Detect whether the codepoint is in a certain character class. Returns - # +true+ when it's in the specified character class and +false+ otherwise. - # Valid character classes are: :cr, :lf, :l, - # :v, :lv, :lvt and :t. - # - # Primarily used by the grapheme cluster support. - def in_char_class?(codepoint, classes) - classes.detect { |c| database.boundary[c] === codepoint } ? true : false - end - - # Unpack the string at grapheme boundaries. Returns a list of character - # lists. - # - # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]] - # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]] - def unpack_graphemes(string) - codepoints = string.codepoints.to_a - unpacked = [] - pos = 0 - marker = 0 - eoc = codepoints.length - while(pos < eoc) - pos += 1 - previous = codepoints[pos-1] - current = codepoints[pos] - if ( - # CR X LF - ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or - # L X (L|V|LV|LVT) - ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or - # (LV|V) X (V|T) - ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or - # (LVT|T) X (T) - ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or - # X Extend - (database.boundary[:extend] === current) - ) - else - unpacked << codepoints[marker..pos-1] - marker = pos - end - end - unpacked - end - - # Reverse operation of unpack_graphemes. - # - # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि' - def pack_graphemes(unpacked) - unpacked.flatten.pack('U*') - end - - # Re-order codepoints so the string becomes canonical. - def reorder_characters(codepoints) - length = codepoints.length- 1 - pos = 0 - while pos < length do - cp1, cp2 = database.codepoints[codepoints[pos]], database.codepoints[codepoints[pos+1]] - if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0) - codepoints[pos..pos+1] = cp2.code, cp1.code - pos += (pos > 0 ? -1 : 1) - else - pos += 1 - end - end - codepoints - end - - # Decompose composed characters to the decomposed form. - def decompose(type, codepoints) - codepoints.inject([]) do |decomposed, cp| - # if it's a hangul syllable starter character - if HANGUL_SBASE <= cp and cp < HANGUL_SLAST - sindex = cp - HANGUL_SBASE - ncp = [] # new codepoints - ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT - ncp << HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT - tindex = sindex % HANGUL_TCOUNT - ncp << (HANGUL_TBASE + tindex) unless tindex == 0 - decomposed.concat ncp - # if the codepoint is decomposable in with the current decomposition type - elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatibility) - decomposed.concat decompose(type, ncp.dup) - else - decomposed << cp - end - end - end - - # Compose decomposed characters to the composed form. - def compose(codepoints) - pos = 0 - eoa = codepoints.length - 1 - starter_pos = 0 - starter_char = codepoints[0] - previous_combining_class = -1 - while pos < eoa - pos += 1 - lindex = starter_char - HANGUL_LBASE - # -- Hangul - if 0 <= lindex and lindex < HANGUL_LCOUNT - vindex = codepoints[starter_pos+1] - HANGUL_VBASE rescue vindex = -1 - if 0 <= vindex and vindex < HANGUL_VCOUNT - tindex = codepoints[starter_pos+2] - HANGUL_TBASE rescue tindex = -1 - if 0 <= tindex and tindex < HANGUL_TCOUNT - j = starter_pos + 2 - eoa -= 2 - else - tindex = 0 - j = starter_pos + 1 - eoa -= 1 - end - codepoints[starter_pos..j] = (lindex * HANGUL_VCOUNT + vindex) * HANGUL_TCOUNT + tindex + HANGUL_SBASE - end - starter_pos += 1 - starter_char = codepoints[starter_pos] - # -- Other characters - else - current_char = codepoints[pos] - current = database.codepoints[current_char] - if current.combining_class > previous_combining_class - if ref = database.composition_map[starter_char] - composition = ref[current_char] - else - composition = nil - end - unless composition.nil? - codepoints[starter_pos] = composition - starter_char = composition - codepoints.delete_at pos - eoa -= 1 - pos -= 1 - previous_combining_class = -1 - else - previous_combining_class = current.combining_class - end - else - previous_combining_class = current.combining_class - end - if current.combining_class == 0 - starter_pos = pos - starter_char = codepoints[pos] - end - end - end - codepoints - end - - # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1. - if '<3'.respond_to?(:scrub) - # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent - # resulting in a valid UTF-8 string. - # - # Passing +true+ will forcibly tidy all bytes, assuming that the string's - # encoding is entirely CP1252 or ISO-8859-1. - def tidy_bytes(string, force = false) - return string if string.empty? - return recode_windows1252_chars(string) if force - string.scrub { |bad| recode_windows1252_chars(bad) } - end - else - def tidy_bytes(string, force = false) - return string if string.empty? - return recode_windows1252_chars(string) if force - - # We can't transcode to the same format, so we choose a nearly-identical encoding. - # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to - # CP1252 when we get errors. The final string will be 'converted' back to UTF-8 - # before returning. - reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE) - - source = string.dup - out = ''.force_encoding(Encoding::UTF_16LE) - - loop do - reader.primitive_convert(source, out) - _, _, _, error_bytes, _ = reader.primitive_errinfo - break if error_bytes.nil? - out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace) - end - - reader.finish - - out.encode!(Encoding::UTF_8) - end - end - - # Returns the KC normalization of the string by default. NFKC is - # considered the best normalization form for passing strings to databases - # and validations. - # - # * string - The string to perform normalization on. - # * form - The form you want to normalize in. Should be one of - # the following: :c, :kc, :d, or :kd. - # Default is ActiveSupport::Multibyte.default_normalization_form. - def normalize(string, form=nil) - form ||= @default_normalization_form - # See http://www.unicode.org/reports/tr15, Table 1 - codepoints = string.codepoints.to_a - case form - when :d - reorder_characters(decompose(:canonical, codepoints)) - when :c - compose(reorder_characters(decompose(:canonical, codepoints))) - when :kd - reorder_characters(decompose(:compatibility, codepoints)) - when :kc - compose(reorder_characters(decompose(:compatibility, codepoints))) - else - raise ArgumentError, "#{form} is not a valid normalization variant", caller - end.pack('U*') - end - - def downcase(string) - apply_mapping string, :lowercase_mapping - end - - def upcase(string) - apply_mapping string, :uppercase_mapping - end - - def swapcase(string) - apply_mapping string, :swapcase_mapping - end - - # Holds data about a codepoint in the Unicode database. - class Codepoint - attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping - - # Initializing Codepoint object with default values - def initialize - @combining_class = 0 - @uppercase_mapping = 0 - @lowercase_mapping = 0 - end - - def swapcase_mapping - uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping - end - end - - # Holds static data from the Unicode database. - class UnicodeDatabase - ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252 - - attr_writer(*ATTRIBUTES) - - def initialize - @codepoints = Hash.new(Codepoint.new) - @composition_exclusion = [] - @composition_map = {} - @boundary = {} - @cp1252 = {} - end - - # Lazy load the Unicode database so it's only loaded when it's actually used - ATTRIBUTES.each do |attr_name| - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{attr_name} # def codepoints - load # load - @#{attr_name} # @codepoints - end # end - EOS - end - - # Loads the Unicode database and returns all the internal objects of - # UnicodeDatabase. - def load - begin - @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read } - rescue => e - raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable") - end - - # Redefine the === method so we can write shorter rules for grapheme cluster breaks - @boundary.each do |k,_| - @boundary[k].instance_eval do - def ===(other) - detect { |i| i === other } ? true : false - end - end if @boundary[k].kind_of?(Array) - end - - # define attr_reader methods for the instance variables - class << self - attr_reader(*ATTRIBUTES) - end - end - - # Returns the directory in which the data files are stored. - def self.dirname - File.dirname(__FILE__) + '/../values/' - end - - # Returns the filename for the data file for this version. - def self.filename - File.expand_path File.join(dirname, "unicode_tables.dat") - end - end - - private - - def apply_mapping(string, mapping) #:nodoc: - string.each_codepoint.map do |codepoint| - cp = database.codepoints[codepoint] - if cp and (ncp = cp.send(mapping)) and ncp > 0 - ncp - else - codepoint - end - end.pack('U*') - end - - def recode_windows1252_chars(string) - string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) - end - - def database - @database ||= UnicodeDatabase.new - end - - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/notifications/fanout.rb b/app/server/ruby/vendor/activesupport/lib/active_support/notifications/fanout.rb deleted file mode 100644 index 8f5fa646e8..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/notifications/fanout.rb +++ /dev/null @@ -1,152 +0,0 @@ -require 'mutex_m' -require 'thread_safe' - -module ActiveSupport - module Notifications - # This is a default queue implementation that ships with Notifications. - # It just pushes events to all registered log subscribers. - # - # This class is thread safe. All methods are reentrant. - class Fanout - include Mutex_m - - def initialize - @subscribers = [] - @listeners_for = ThreadSafe::Cache.new - super - end - - def subscribe(pattern = nil, block = Proc.new) - subscriber = Subscribers.new pattern, block - synchronize do - @subscribers << subscriber - @listeners_for.clear - end - subscriber - end - - def unsubscribe(subscriber) - synchronize do - @subscribers.reject! { |s| s.matches?(subscriber) } - @listeners_for.clear - end - end - - def start(name, id, payload) - listeners_for(name).each { |s| s.start(name, id, payload) } - end - - def finish(name, id, payload) - listeners_for(name).each { |s| s.finish(name, id, payload) } - end - - def publish(name, *args) - listeners_for(name).each { |s| s.publish(name, *args) } - end - - def listeners_for(name) - # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics) - @listeners_for[name] || synchronize do - # use synchronisation when accessing @subscribers - @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } - end - end - - def listening?(name) - listeners_for(name).any? - end - - # This is a sync queue, so there is no waiting. - def wait - end - - module Subscribers # :nodoc: - def self.new(pattern, listener) - if listener.respond_to?(:start) and listener.respond_to?(:finish) - subscriber = Evented.new pattern, listener - else - subscriber = Timed.new pattern, listener - end - - unless pattern - AllMessages.new(subscriber) - else - subscriber - end - end - - class Evented #:nodoc: - def initialize(pattern, delegate) - @pattern = pattern - @delegate = delegate - @can_publish = delegate.respond_to?(:publish) - end - - def publish(name, *args) - if @can_publish - @delegate.publish name, *args - end - end - - def start(name, id, payload) - @delegate.start name, id, payload - end - - def finish(name, id, payload) - @delegate.finish name, id, payload - end - - def subscribed_to?(name) - @pattern === name.to_s - end - - def matches?(subscriber_or_name) - self === subscriber_or_name || - @pattern && @pattern === subscriber_or_name - end - end - - class Timed < Evented - def publish(name, *args) - @delegate.call name, *args - end - - def start(name, id, payload) - timestack = Thread.current[:_timestack] ||= [] - timestack.push Time.now - end - - def finish(name, id, payload) - timestack = Thread.current[:_timestack] - started = timestack.pop - @delegate.call(name, started, Time.now, id, payload) - end - end - - class AllMessages # :nodoc: - def initialize(delegate) - @delegate = delegate - end - - def start(name, id, payload) - @delegate.start name, id, payload - end - - def finish(name, id, payload) - @delegate.finish name, id, payload - end - - def publish(name, *args) - @delegate.publish name, *args - end - - def subscribed_to?(name) - true - end - - alias :matches? :=== - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/notifications/instrumenter.rb b/app/server/ruby/vendor/activesupport/lib/active_support/notifications/instrumenter.rb deleted file mode 100644 index 3a244b34b5..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/notifications/instrumenter.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'securerandom' - -module ActiveSupport - module Notifications - # Instrumenters are stored in a thread local. - class Instrumenter - attr_reader :id - - def initialize(notifier) - @id = unique_id - @notifier = notifier - end - - # Instrument the given block by measuring the time taken to execute it - # and publish it. Notice that events get sent even if an error occurs - # in the passed-in block. - def instrument(name, payload={}) - start name, payload - begin - yield payload - rescue Exception => e - payload[:exception] = [e.class.name, e.message] - raise e - ensure - finish name, payload - end - end - - # Send a start notification with +name+ and +payload+. - def start(name, payload) - @notifier.start name, @id, payload - end - - # Send a finish notification with +name+ and +payload+. - def finish(name, payload) - @notifier.finish name, @id, payload - end - - private - - def unique_id - SecureRandom.hex(10) - end - end - - class Event - attr_reader :name, :time, :transaction_id, :payload, :children - attr_accessor :end - - def initialize(name, start, ending, transaction_id, payload) - @name = name - @payload = payload.dup - @time = start - @transaction_id = transaction_id - @end = ending - @children = [] - @duration = nil - end - - def duration - @duration ||= 1000.0 * (self.end - time) - end - - def <<(event) - @children << event - end - - def parent_of?(event) - @children.include? event - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb deleted file mode 100644 index 9ae27a896a..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb +++ /dev/null @@ -1,46 +0,0 @@ -module ActiveSupport - module NumberHelper - class NumberToCurrencyConverter < NumberConverter # :nodoc: - self.namespace = :currency - - def convert - number = self.number.to_s.strip - format = options[:format] - - if is_negative?(number) - format = options[:negative_format] - number = absolute_value(number) - end - - rounded_number = NumberToRoundedConverter.convert(number, options) - format.gsub('%n', rounded_number).gsub('%u', options[:unit]) - end - - private - - def is_negative?(number) - number.to_f.phase != 0 - end - - def absolute_value(number) - number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '') - end - - def options - @options ||= begin - defaults = default_format_options.merge(i18n_opts) - # Override negative format if format options is given - defaults[:negative_format] = "-#{opts[:format]}" if opts[:format] - defaults.merge!(opts) - end - end - - def i18n_opts - # Set International negative format if not exists - i18n = i18n_format_options - i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format] - i18n - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb deleted file mode 100644 index 6405afc9a6..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveSupport - module NumberHelper - class NumberToDelimitedConverter < NumberConverter #:nodoc: - self.validate_float = true - - DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ - - def convert - parts.join(options[:separator]) - end - - private - - def parts - left, right = number.to_s.split('.') - left.gsub!(DELIMITED_REGEX) { "#{$1}#{options[:delimiter]}" } - [left, right].compact - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb deleted file mode 100644 index c45f6cdcfa..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ /dev/null @@ -1,91 +0,0 @@ -module ActiveSupport - module NumberHelper - class NumberToRoundedConverter < NumberConverter # :nodoc: - self.namespace = :precision - self.validate_float = true - - def convert - precision = options.delete :precision - significant = options.delete :significant - - case number - when Float, String - @number = BigDecimal(number.to_s) - when Rational - if significant - @number = BigDecimal(number, digit_count(number.to_i) + precision) - else - @number = BigDecimal(number, precision) - end - else - @number = number.to_d - end - - if significant && precision > 0 - digits, rounded_number = digits_and_rounded_number(precision) - precision -= digits - precision = 0 if precision < 0 # don't let it be negative - else - rounded_number = number.round(precision) - rounded_number = rounded_number.to_i if precision == 0 - rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros - end - - formatted_string = - if BigDecimal === rounded_number && rounded_number.finite? - s = rounded_number.to_s('F') + '0'*precision - a, b = s.split('.', 2) - a + '.' + b[0, precision] - else - "%01.#{precision}f" % rounded_number - end - - delimited_number = NumberToDelimitedConverter.convert(formatted_string, options) - format_number(delimited_number) - end - - private - - def digits_and_rounded_number(precision) - if zero? - [1, 0] - else - digits = digit_count(number) - multiplier = 10 ** (digits - precision) - rounded_number = calculate_rounded_number(multiplier) - digits = digit_count(rounded_number) # After rounding, the number of digits may have changed - [digits, rounded_number] - end - end - - def calculate_rounded_number(multiplier) - (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier - end - - def digit_count(number) - (Math.log10(absolute_number(number)) + 1).floor - end - - def strip_insignificant_zeros - options[:strip_insignificant_zeros] - end - - def format_number(number) - if strip_insignificant_zeros - escaped_separator = Regexp.escape(options[:separator]) - number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') - else - number - end - end - - def absolute_number(number) - number.respond_to?(:abs) ? number.abs : number.to_d.abs - end - - def zero? - number.respond_to?(:zero?) ? number.zero? : number.to_d.zero? - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/option_merger.rb b/app/server/ruby/vendor/activesupport/lib/active_support/option_merger.rb deleted file mode 100644 index dea84e437f..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/option_merger.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'active_support/core_ext/hash/deep_merge' - -module ActiveSupport - class OptionMerger #:nodoc: - instance_methods.each do |method| - undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/ - end - - def initialize(context, options) - @context, @options = context, options - end - - private - def method_missing(method, *arguments, &block) - if arguments.first.is_a?(Proc) - proc = arguments.pop - arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) } - else - arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup) - end - - @context.__send__(method, *arguments, &block) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/rails.rb b/app/server/ruby/vendor/activesupport/lib/active_support/rails.rb deleted file mode 100644 index b05c3ff126..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/rails.rb +++ /dev/null @@ -1,27 +0,0 @@ -# This is private interface. -# -# Rails components cherry pick from Active Support as needed, but there are a -# few features that are used for sure some way or another and it is not worth -# to put individual requires absolutely everywhere. Think blank? for example. -# -# This file is loaded by every Rails component except Active Support itself, -# but it does not belong to the Rails public interface. It is internal to -# Rails and can change anytime. - -# Defines Object#blank? and Object#present?. -require 'active_support/core_ext/object/blank' - -# Rails own autoload, eager_load, etc. -require 'active_support/dependencies/autoload' - -# Support for ClassMethods and the included macro. -require 'active_support/concern' - -# Defines Class#class_attribute. -require 'active_support/core_ext/class/attribute' - -# Defines Module#delegate. -require 'active_support/core_ext/module/delegation' - -# Defines ActiveSupport::Deprecation. -require 'active_support/deprecation' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/railtie.rb b/app/server/ruby/vendor/activesupport/lib/active_support/railtie.rb deleted file mode 100644 index 133aa6a054..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/railtie.rb +++ /dev/null @@ -1,46 +0,0 @@ -require "active_support" -require "active_support/i18n_railtie" - -module ActiveSupport - class Railtie < Rails::Railtie # :nodoc: - config.active_support = ActiveSupport::OrderedOptions.new - - config.eager_load_namespaces << ActiveSupport - - initializer "active_support.deprecation_behavior" do |app| - if deprecation = app.config.active_support.deprecation - ActiveSupport::Deprecation.behavior = deprecation - end - end - - # Sets the default value for Time.zone - # If assigned value cannot be matched to a TimeZone, an exception will be raised. - initializer "active_support.initialize_time_zone" do |app| - require 'active_support/core_ext/time/zones' - zone_default = Time.find_zone!(app.config.time_zone) - - unless zone_default - raise 'Value assigned to config.time_zone not recognized. ' \ - 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' - end - - Time.zone_default = zone_default - end - - # Sets the default week start - # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. - initializer "active_support.initialize_beginning_of_week" do |app| - require 'active_support/core_ext/date/calculations' - beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week) - - Date.beginning_of_week_default = beginning_of_week_default - end - - initializer "active_support.set_configs" do |app| - app.config.active_support.each do |k, v| - k = "#{k}=" - ActiveSupport.send(k, v) if ActiveSupport.respond_to? k - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/rescuable.rb b/app/server/ruby/vendor/activesupport/lib/active_support/rescuable.rb deleted file mode 100644 index a7eba91ac5..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/rescuable.rb +++ /dev/null @@ -1,119 +0,0 @@ -require 'active_support/concern' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/array/extract_options' - -module ActiveSupport - # Rescuable module adds support for easier exception handling. - module Rescuable - extend Concern - - included do - class_attribute :rescue_handlers - self.rescue_handlers = [] - end - - module ClassMethods - # Rescue exceptions raised in controller actions. - # - # rescue_from receives a series of exception classes or class - # names, and a trailing :with option with the name of a method - # or a Proc object to be called to handle them. Alternatively a block can - # be given. - # - # Handlers that take one argument will be called with the exception, so - # that the exception can be inspected when dealing with it. - # - # Handlers are inherited. They are searched from right to left, from - # bottom to top, and up the hierarchy. The handler of the first class for - # which exception.is_a?(klass) holds true is the one invoked, if - # any. - # - # class ApplicationController < ActionController::Base - # rescue_from User::NotAuthorized, with: :deny_access # self defined exception - # rescue_from ActiveRecord::RecordInvalid, with: :show_errors - # - # rescue_from 'MyAppError::Base' do |exception| - # render xml: exception, status: 500 - # end - # - # protected - # def deny_access - # ... - # end - # - # def show_errors(exception) - # exception.record.new_record? ? ... - # end - # end - # - # Exceptions raised inside exception handlers are not propagated up. - def rescue_from(*klasses, &block) - options = klasses.extract_options! - - unless options.has_key?(:with) - if block_given? - options[:with] = block - else - raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument." - end - end - - klasses.each do |klass| - key = if klass.is_a?(Class) && klass <= Exception - klass.name - elsif klass.is_a?(String) - klass - else - raise ArgumentError, "#{klass} is neither an Exception nor a String" - end - - # put the new handler at the end because the list is read in reverse - self.rescue_handlers += [[key, options[:with]]] - end - end - end - - # Tries to rescue the exception by looking up and calling a registered handler. - def rescue_with_handler(exception) - if handler = handler_for_rescue(exception) - handler.arity != 0 ? handler.call(exception) : handler.call - true # don't rely on the return value of the handler - end - end - - def handler_for_rescue(exception) - # We go from right to left because pairs are pushed onto rescue_handlers - # as rescue_from declarations are found. - _, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler| - # The purpose of allowing strings in rescue_from is to support the - # declaration of handler associations for exception classes whose - # definition is yet unknown. - # - # Since this loop needs the constants it would be inconsistent to - # assume they should exist at this point. An early raised exception - # could trigger some other handler and the array could include - # precisely a string whose corresponding constant has not yet been - # seen. This is why we are tolerant to unknown constants. - # - # Note that this tolerance only matters if the exception was given as - # a string, otherwise a NameError will be raised by the interpreter - # itself when rescue_from CONSTANT is executed. - klass = self.class.const_get(klass_name) rescue nil - klass ||= klass_name.constantize rescue nil - exception.is_a?(klass) if klass - end - - case rescuer - when Symbol - method(rescuer) - when Proc - if rescuer.arity == 0 - Proc.new { instance_exec(&rescuer) } - else - Proc.new { |_exception| instance_exec(_exception, &rescuer) } - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/subscriber.rb b/app/server/ruby/vendor/activesupport/lib/active_support/subscriber.rb deleted file mode 100644 index 4b9b48539f..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/subscriber.rb +++ /dev/null @@ -1,116 +0,0 @@ -require 'active_support/per_thread_registry' - -module ActiveSupport - # ActiveSupport::Subscriber is an object set to consume - # ActiveSupport::Notifications. The subscriber dispatches notifications to - # a registered object based on its given namespace. - # - # An example would be Active Record subscriber responsible for collecting - # statistics about queries: - # - # module ActiveRecord - # class StatsSubscriber < ActiveSupport::Subscriber - # def sql(event) - # Statsd.timing("sql.#{event.payload[:name]}", event.duration) - # end - # end - # end - # - # And it's finally registered as: - # - # ActiveRecord::StatsSubscriber.attach_to :active_record - # - # Since we need to know all instance methods before attaching the log - # subscriber, the line above should be called after your subscriber definition. - # - # After configured, whenever a "sql.active_record" notification is published, - # it will properly dispatch the event (ActiveSupport::Notifications::Event) to - # the +sql+ method. - class Subscriber - class << self - - # Attach the subscriber to a namespace. - def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications) - @namespace = namespace - @subscriber = subscriber - @notifier = notifier - - subscribers << subscriber - - # Add event subscribers for all existing methods on the class. - subscriber.public_methods(false).each do |event| - add_event_subscriber(event) - end - end - - # Adds event subscribers for all new methods added to the class. - def method_added(event) - # Only public methods are added as subscribers, and only if a notifier - # has been set up. This means that subscribers will only be set up for - # classes that call #attach_to. - if public_method_defined?(event) && notifier - add_event_subscriber(event) - end - end - - def subscribers - @@subscribers ||= [] - end - - protected - - attr_reader :subscriber, :notifier, :namespace - - def add_event_subscriber(event) - return if %w{ start finish }.include?(event.to_s) - - notifier.subscribe("#{event}.#{namespace}", subscriber) - end - end - - def initialize - @queue_key = [self.class.name, object_id].join "-" - super - end - - def start(name, id, payload) - e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload) - parent = event_stack.last - parent << e if parent - - event_stack.push e - end - - def finish(name, id, payload) - finished = Time.now - event = event_stack.pop - event.end = finished - event.payload.merge!(payload) - - method = name.split('.').first - send(method, event) - end - - private - - def event_stack - SubscriberQueueRegistry.instance.get_queue(@queue_key) - end - end - - # This is a registry for all the event stacks kept for subscribers. - # - # See the documentation of ActiveSupport::PerThreadRegistry - # for further details. - class SubscriberQueueRegistry # :nodoc: - extend PerThreadRegistry - - def initialize - @registry = {} - end - - def get_queue(queue_key) - @registry[queue_key] ||= [] - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/tagged_logging.rb b/app/server/ruby/vendor/activesupport/lib/active_support/tagged_logging.rb deleted file mode 100644 index d5c2222d2e..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/tagged_logging.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/object/blank' -require 'logger' -require 'active_support/logger' - -module ActiveSupport - # Wraps any standard Logger object to provide tagging capabilities. - # - # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) - # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff" - # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff" - # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff" - # - # This is used by the default Rails.logger as configured by Railties to make - # it easy to stamp log lines with subdomains, request ids, and anything else - # to aid debugging of multi-user production applications. - module TaggedLogging - module Formatter # :nodoc: - # This method is invoked when a log event occurs. - def call(severity, timestamp, progname, msg) - super(severity, timestamp, progname, "#{tags_text}#{msg}") - end - - def tagged(*tags) - new_tags = push_tags(*tags) - yield self - ensure - pop_tags(new_tags.size) - end - - def push_tags(*tags) - tags.flatten.reject(&:blank?).tap do |new_tags| - current_tags.concat new_tags - end - end - - def pop_tags(size = 1) - current_tags.pop size - end - - def clear_tags! - current_tags.clear - end - - def current_tags - Thread.current[:activesupport_tagged_logging_tags] ||= [] - end - - private - def tags_text - tags = current_tags - if tags.any? - tags.collect { |tag| "[#{tag}] " }.join - end - end - end - - def self.new(logger) - # Ensure we set a default formatter so we aren't extending nil! - logger.formatter ||= ActiveSupport::Logger::SimpleFormatter.new - logger.formatter.extend Formatter - logger.extend(self) - end - - delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter - - def tagged(*tags) - formatter.tagged(*tags) { yield self } - end - - def flush - clear_tags! - super if defined?(super) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/test_case.rb b/app/server/ruby/vendor/activesupport/lib/active_support/test_case.rb deleted file mode 100644 index 2fb5c04316..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/test_case.rb +++ /dev/null @@ -1,66 +0,0 @@ -gem 'minitest' # make sure we get the gem, not stdlib -require 'minitest' -require 'active_support/testing/tagged_logging' -require 'active_support/testing/setup_and_teardown' -require 'active_support/testing/assertions' -require 'active_support/testing/deprecation' -require 'active_support/testing/declarative' -require 'active_support/testing/isolation' -require 'active_support/testing/constant_lookup' -require 'active_support/testing/time_helpers' -require 'active_support/core_ext/kernel/reporting' -require 'active_support/deprecation' - -begin - silence_warnings { require 'mocha/setup' } -rescue LoadError -end - -module ActiveSupport - class TestCase < ::Minitest::Test - Assertion = Minitest::Assertion - - alias_method :method_name, :name - - $tags = {} - def self.for_tag(tag) - yield if $tags[tag] - end - - # FIXME: we have tests that depend on run order, we should fix that and - # remove this method call. - self.i_suck_and_my_tests_are_order_dependent! - - include ActiveSupport::Testing::TaggedLogging - include ActiveSupport::Testing::SetupAndTeardown - include ActiveSupport::Testing::Assertions - include ActiveSupport::Testing::Deprecation - include ActiveSupport::Testing::TimeHelpers - extend ActiveSupport::Testing::Declarative - - # test/unit backwards compatibility methods - alias :assert_raise :assert_raises - alias :assert_not_empty :refute_empty - alias :assert_not_equal :refute_equal - alias :assert_not_in_delta :refute_in_delta - alias :assert_not_in_epsilon :refute_in_epsilon - alias :assert_not_includes :refute_includes - alias :assert_not_instance_of :refute_instance_of - alias :assert_not_kind_of :refute_kind_of - alias :assert_no_match :refute_match - alias :assert_not_nil :refute_nil - alias :assert_not_operator :refute_operator - alias :assert_not_predicate :refute_predicate - alias :assert_not_respond_to :refute_respond_to - alias :assert_not_same :refute_same - - # Fails if the block raises an exception. - # - # assert_nothing_raised do - # ... - # end - def assert_nothing_raised(*args) - yield - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/assertions.rb b/app/server/ruby/vendor/activesupport/lib/active_support/testing/assertions.rb deleted file mode 100644 index 76a591bc3b..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/assertions.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'active_support/core_ext/object/blank' - -module ActiveSupport - module Testing - module Assertions - # Assert that an expression is not truthy. Passes if object is - # +nil+ or +false+. "Truthy" means "considered true in a conditional" - # like if foo. - # - # assert_not nil # => true - # assert_not false # => true - # assert_not 'foo' # => 'foo' is not nil or false - # - # An error message can be specified. - # - # assert_not foo, 'foo should be false' - def assert_not(object, message = nil) - message ||= "Expected #{mu_pp(object)} to be nil or false" - assert !object, message - end - - # Test numeric difference between the return value of an expression as a - # result of what is evaluated in the yielded block. - # - # assert_difference 'Article.count' do - # post :create, article: {...} - # end - # - # An arbitrary expression is passed in and evaluated. - # - # assert_difference 'assigns(:article).comments(:reload).size' do - # post :create, comment: {...} - # end - # - # An arbitrary positive or negative difference can be specified. - # The default is 1. - # - # assert_difference 'Article.count', -1 do - # post :delete, id: ... - # end - # - # An array of expressions can also be passed in and evaluated. - # - # assert_difference [ 'Article.count', 'Post.count' ], 2 do - # post :create, article: {...} - # end - # - # A lambda or a list of lambdas can be passed in and evaluated: - # - # assert_difference ->{ Article.count }, 2 do - # post :create, article: {...} - # end - # - # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do - # post :create, article: {...} - # end - # - # An error message can be specified. - # - # assert_difference 'Article.count', -1, 'An Article should be destroyed' do - # post :delete, id: ... - # end - def assert_difference(expression, difference = 1, message = nil, &block) - expressions = Array(expression) - - exps = expressions.map { |e| - e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } - } - before = exps.map { |e| e.call } - - yield - - expressions.zip(exps).each_with_index do |(code, e), i| - error = "#{code.inspect} didn't change by #{difference}" - error = "#{message}.\n#{error}" if message - assert_equal(before[i] + difference, e.call, error) - end - end - - # Assertion that the numeric result of evaluating an expression is not - # changed before and after invoking the passed in block. - # - # assert_no_difference 'Article.count' do - # post :create, article: invalid_attributes - # end - # - # An error message can be specified. - # - # assert_no_difference 'Article.count', 'An Article should not be created' do - # post :create, article: invalid_attributes - # end - def assert_no_difference(expression, message = nil, &block) - assert_difference expression, 0, message, &block - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/autorun.rb b/app/server/ruby/vendor/activesupport/lib/active_support/testing/autorun.rb deleted file mode 100644 index 5aa5f46310..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/autorun.rb +++ /dev/null @@ -1,5 +0,0 @@ -gem 'minitest' - -require 'minitest' - -Minitest.autorun diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/deprecation.rb b/app/server/ruby/vendor/activesupport/lib/active_support/testing/deprecation.rb deleted file mode 100644 index 6c94c611b6..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/deprecation.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'active_support/deprecation' - -module ActiveSupport - module Testing - module Deprecation #:nodoc: - def assert_deprecated(match = nil, &block) - result, warnings = collect_deprecations(&block) - assert !warnings.empty?, "Expected a deprecation warning within the block but received none" - if match - match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp) - assert warnings.any? { |w| w =~ match }, "No deprecation warning matched #{match}: #{warnings.join(', ')}" - end - result - end - - def assert_not_deprecated(&block) - result, deprecations = collect_deprecations(&block) - assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}" - result - end - - def collect_deprecations - old_behavior = ActiveSupport::Deprecation.behavior - deprecations = [] - ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack| - deprecations << message - end - result = yield - [result, deprecations] - ensure - ActiveSupport::Deprecation.behavior = old_behavior - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/isolation.rb b/app/server/ruby/vendor/activesupport/lib/active_support/testing/isolation.rb deleted file mode 100644 index 908af176be..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/isolation.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'rbconfig' - -module ActiveSupport - module Testing - module Isolation - require 'thread' - - def self.included(klass) #:nodoc: - klass.class_eval do - parallelize_me! - end - end - - def self.forking_env? - !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) - end - - @@class_setup_mutex = Mutex.new - - def _run_class_setup # class setup method should only happen in parent - @@class_setup_mutex.synchronize do - unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST'] - self.class.setup if self.class.respond_to?(:setup) - @@ran_class_setup = true - end - end - end - - def run - serialized = run_in_isolation do - super - end - - Marshal.load(serialized) - end - - module Forking - def run_in_isolation(&blk) - read, write = IO.pipe - read.binmode - write.binmode - - pid = fork do - read.close - yield - write.puts [Marshal.dump(self.dup)].pack("m") - exit! - end - - write.close - result = read.read - Process.wait2(pid) - return result.unpack("m")[0] - end - end - - module Subprocess - ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV) - - # Crazy H4X to get this working in windows / jruby with - # no forking. - def run_in_isolation(&blk) - require "tempfile" - - if ENV["ISOLATION_TEST"] - yield - File.open(ENV["ISOLATION_OUTPUT"], "w") do |file| - file.puts [Marshal.dump(self.dup)].pack("m") - end - exit! - else - Tempfile.open("isolation") do |tmpfile| - ENV["ISOLATION_TEST"] = self.class.name - ENV["ISOLATION_OUTPUT"] = tmpfile.path - - load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ") - `#{Gem.ruby} #{load_paths} #{$0} #{ORIG_ARGV.join(" ")}` - - ENV.delete("ISOLATION_TEST") - ENV.delete("ISOLATION_OUTPUT") - - return tmpfile.read.unpack("m")[0] - end - end - end - end - - include forking_env? ? Forking : Subprocess - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/testing/time_helpers.rb b/app/server/ruby/vendor/activesupport/lib/active_support/testing/time_helpers.rb deleted file mode 100644 index eefa84262e..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/testing/time_helpers.rb +++ /dev/null @@ -1,127 +0,0 @@ -module ActiveSupport - module Testing - class SimpleStubs # :nodoc: - Stub = Struct.new(:object, :method_name, :original_method) - - def initialize - @stubs = {} - end - - def stub_object(object, method_name, return_value) - key = [object.object_id, method_name] - - if stub = @stubs[key] - unstub_object(stub) - end - - new_name = "__simple_stub__#{method_name}" - - @stubs[key] = Stub.new(object, method_name, new_name) - - object.singleton_class.send :alias_method, new_name, method_name - object.define_singleton_method(method_name) { return_value } - end - - def unstub_all! - @stubs.each_value do |stub| - unstub_object(stub) - end - @stubs = {} - end - - private - - def unstub_object(stub) - singleton_class = stub.object.singleton_class - singleton_class.send :undef_method, stub.method_name - singleton_class.send :alias_method, stub.method_name, stub.original_method - singleton_class.send :undef_method, stub.original_method - end - end - - # Containing helpers that helps you test passage of time. - module TimeHelpers - # Changes current time to the time in the future or in the past by a given time difference by - # stubbing +Time.now+ and +Date.today+. - # - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - # travel 1.day - # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00 - # Date.current # => Sun, 10 Nov 2013 - # - # This method also accepts a block, which will return the current time back to its original - # state at the end of the block: - # - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - # travel 1.day do - # User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00 - # end - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - def travel(duration, &block) - travel_to Time.now + duration, &block - end - - # Changes current time to the given time by stubbing +Time.now+ and - # +Date.today+ to return the time or date passed into this method. - # - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - # travel_to Time.new(2004, 11, 24, 01, 04, 44) - # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 - # Date.current # => Wed, 24 Nov 2004 - # - # Dates are taken as their timestamp at the beginning of the day in the - # application time zone. Time.current returns said timestamp, - # and Time.now its equivalent in the system time zone. Similarly, - # Date.current returns a date equal to the argument, and - # Date.today the date according to Time.now, which may - # be different. (Note that you rarely want to deal with Time.now, - # or Date.today, in order to honor the application time zone - # please always use Time.current and Date.current.) - # - # This method also accepts a block, which will return the current time back to its original - # state at the end of the block: - # - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - # travel_to Time.new(2004, 11, 24, 01, 04, 44) do - # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 - # end - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - def travel_to(date_or_time, &block) - if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime) - now = date_or_time.midnight.to_time - else - now = date_or_time.to_time - end - - simple_stubs.stub_object(Time, :now, now) - simple_stubs.stub_object(Date, :today, now.to_date) - - if block_given? - begin - block.call - ensure - travel_back - end - end - end - - # Returns the current time back to its original state, by removing the stubs added by - # `travel` and `travel_to`. - # - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - # travel_to Time.new(2004, 11, 24, 01, 04, 44) - # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 - # travel_back - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 - def travel_back - simple_stubs.unstub_all! - end - - private - - def simple_stubs - @simple_stubs ||= SimpleStubs.new - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/time.rb b/app/server/ruby/vendor/activesupport/lib/active_support/time.rb deleted file mode 100644 index 92a593965e..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/time.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'active_support' - -module ActiveSupport - autoload :Duration, 'active_support/duration' - autoload :TimeWithZone, 'active_support/time_with_zone' - autoload :TimeZone, 'active_support/values/time_zone' -end - -require 'date' -require 'time' - -require 'active_support/core_ext/time' -require 'active_support/core_ext/date' -require 'active_support/core_ext/date_time' - -require 'active_support/core_ext/integer/time' -require 'active_support/core_ext/numeric/time' - -require 'active_support/core_ext/string/conversions' -require 'active_support/core_ext/string/zones' diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/time_with_zone.rb b/app/server/ruby/vendor/activesupport/lib/active_support/time_with_zone.rb deleted file mode 100644 index c25c97cfa8..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/time_with_zone.rb +++ /dev/null @@ -1,402 +0,0 @@ -require 'active_support/values/time_zone' -require 'active_support/core_ext/object/acts_like' - -module ActiveSupport - # A Time-like class that can represent a time in any time zone. Necessary - # because standard Ruby Time instances are limited to UTC and the - # system's ENV['TZ'] zone. - # - # You shouldn't ever need to create a TimeWithZone instance directly via +new+. - # Instead use methods +local+, +parse+, +at+ and +now+ on TimeZone instances, - # and +in_time_zone+ on Time and DateTime instances. - # - # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' - # Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00 - # Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00 - # Time.zone.at(1170361845) # => Sat, 10 Feb 2007 15:30:45 EST -05:00 - # Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00 - # Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00 - # - # See Time and TimeZone for further documentation of these methods. - # - # TimeWithZone instances implement the same API as Ruby Time instances, so - # that Time and TimeWithZone instances are interchangeable. - # - # t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00 - # t.hour # => 13 - # t.dst? # => true - # t.utc_offset # => -14400 - # t.zone # => "EDT" - # t.to_s(:rfc822) # => "Sun, 18 May 2008 13:27:25 -0400" - # t + 1.day # => Mon, 19 May 2008 13:27:25 EDT -04:00 - # t.beginning_of_year # => Tue, 01 Jan 2008 00:00:00 EST -05:00 - # t > Time.utc(1999) # => true - # t.is_a?(Time) # => true - # t.is_a?(ActiveSupport::TimeWithZone) # => true - class TimeWithZone - - # Report class name as 'Time' to thwart type checking. - def self.name - 'Time' - end - - include Comparable - attr_reader :time_zone - - def initialize(utc_time, time_zone, local_time = nil, period = nil) - @utc, @time_zone, @time = utc_time, time_zone, local_time - @period = @utc ? period : get_period_and_ensure_valid_local_time(period) - end - - # Returns a Time or DateTime instance that represents the time in +time_zone+. - def time - @time ||= period.to_local(@utc) - end - - # Returns a Time or DateTime instance that represents the time in UTC. - def utc - @utc ||= period.to_utc(@time) - end - alias_method :comparable_time, :utc - alias_method :getgm, :utc - alias_method :getutc, :utc - alias_method :gmtime, :utc - - # Returns the underlying TZInfo::TimezonePeriod. - def period - @period ||= time_zone.period_for_utc(@utc) - end - - # Returns the simultaneous time in Time.zone, or the specified zone. - def in_time_zone(new_zone = ::Time.zone) - return self if time_zone == new_zone - utc.in_time_zone(new_zone) - end - - # Returns a Time.local() instance of the simultaneous time in your - # system's ENV['TZ'] zone. - def localtime - utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal - end - alias_method :getlocal, :localtime - - # Returns true if the current time is within Daylight Savings Time for the - # specified time zone. - # - # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' - # Time.zone.parse("2012-5-30").dst? # => true - # Time.zone.parse("2012-11-30").dst? # => false - def dst? - period.dst? - end - alias_method :isdst, :dst? - - # Returns true if the current time zone is set to UTC. - # - # Time.zone = 'UTC' # => 'UTC' - # Time.zone.now.utc? # => true - # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' - # Time.zone.now.utc? # => false - def utc? - time_zone.name == 'UTC' - end - alias_method :gmt?, :utc? - - # Returns the offset from current time to UTC time in seconds. - def utc_offset - period.utc_total_offset - end - alias_method :gmt_offset, :utc_offset - alias_method :gmtoff, :utc_offset - - # Returns a formatted string of the offset from UTC, or an alternative - # string if the time zone is already UTC. - # - # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" - # Time.zone.now.formatted_offset(true) # => "-05:00" - # Time.zone.now.formatted_offset(false) # => "-0500" - # Time.zone = 'UTC' # => "UTC" - # Time.zone.now.formatted_offset(true, "0") # => "0" - def formatted_offset(colon = true, alternate_utc_string = nil) - utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon) - end - - # Time uses +zone+ to display the time zone abbreviation, so we're - # duck-typing it. - def zone - period.zone_identifier.to_s - end - - def inspect - "#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}" - end - - def xmlschema(fraction_digits = 0) - fraction = if fraction_digits.to_i > 0 - (".%06i" % time.usec)[0, fraction_digits.to_i + 1] - end - - "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" - end - alias_method :iso8601, :xmlschema - - # Coerces time to a string for JSON encoding. The default format is ISO 8601. - # You can get %Y/%m/%d %H:%M:%S +offset style by setting - # ActiveSupport::JSON::Encoding.use_standard_json_time_format - # to +false+. - # - # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true - # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json - # # => "2005-02-01T05:15:10.000-10:00" - # - # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false - # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json - # # => "2005/02/01 05:15:10 -1000" - def as_json(options = nil) - if ActiveSupport::JSON::Encoding.use_standard_json_time_format - xmlschema(ActiveSupport::JSON::Encoding.time_precision) - else - %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) - end - end - - def encode_with(coder) - if coder.respond_to?(:represent_object) - coder.represent_object(nil, utc) - else - coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ")) - end - end - - # Returns a string of the object's date and time in the format used by - # HTTP requests. - # - # Time.zone.now.httpdate # => "Tue, 01 Jan 2013 04:39:43 GMT" - def httpdate - utc.httpdate - end - - # Returns a string of the object's date and time in the RFC 2822 standard - # format. - # - # Time.zone.now.rfc2822 # => "Tue, 01 Jan 2013 04:51:39 +0000" - def rfc2822 - to_s(:rfc822) - end - alias_method :rfc822, :rfc2822 - - # :db format outputs time in UTC; all others output time in local. - # Uses TimeWithZone's +strftime+, so %Z and %z work correctly. - def to_s(format = :default) - if format == :db - utc.to_s(format) - elsif formatter = ::Time::DATE_FORMATS[format] - formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) - else - "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format - end - end - alias_method :to_formatted_s, :to_s - - # Replaces %Z and %z directives with +zone+ and - # +formatted_offset+, respectively, before passing to Time#strftime, so - # that zone information is correct - def strftime(format) - format = format.gsub('%Z', zone) - .gsub('%z', formatted_offset(false)) - .gsub('%:z', formatted_offset(true)) - .gsub('%::z', formatted_offset(true) + ":00") - time.strftime(format) - end - - # Use the time in UTC for comparisons. - def <=>(other) - utc <=> other - end - - # Returns true if the current object's time is within the specified - # +min+ and +max+ time. - def between?(min, max) - utc.between?(min, max) - end - - # Returns true if the current object's time is in the past. - def past? - utc.past? - end - - # Returns true if the current object's time falls within - # the current day. - def today? - time.today? - end - - # Returns true if the current object's time is in the future. - def future? - utc.future? - end - - def eql?(other) - utc.eql?(other) - end - - def hash - utc.hash - end - - def +(other) - # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time, - # otherwise move forward from #utc, for accuracy when moving across DST boundaries - if duration_of_variable_length?(other) - method_missing(:+, other) - else - result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other) - result.in_time_zone(time_zone) - end - end - - def -(other) - # If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time, - # otherwise move backwards #utc, for accuracy when moving across DST boundaries - if other.acts_like?(:time) - utc.to_f - other.to_f - elsif duration_of_variable_length?(other) - method_missing(:-, other) - else - result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other) - result.in_time_zone(time_zone) - end - end - - def since(other) - # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time, - # otherwise move forward from #utc, for accuracy when moving across DST boundaries - if duration_of_variable_length?(other) - method_missing(:since, other) - else - utc.since(other).in_time_zone(time_zone) - end - end - - def ago(other) - since(-other) - end - - def advance(options) - # If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time, - # otherwise advance from #utc, for accuracy when moving across DST boundaries - if options.values_at(:years, :weeks, :months, :days).any? - method_missing(:advance, options) - else - utc.advance(options).in_time_zone(time_zone) - end - end - - %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| - class_eval <<-EOV, __FILE__, __LINE__ + 1 - def #{method_name} # def month - time.#{method_name} # time.month - end # end - EOV - end - - def to_a - [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone] - end - - def to_f - utc.to_f - end - - def to_i - utc.to_i - end - alias_method :tv_sec, :to_i - - def to_r - utc.to_r - end - - # Return an instance of Time in the system timezone. - def to_time - utc.to_time - end - - def to_datetime - utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) - end - - # So that +self+ acts_like?(:time). - def acts_like_time? - true - end - - # Say we're a Time to thwart type checking. - def is_a?(klass) - klass == ::Time || super - end - alias_method :kind_of?, :is_a? - - def freeze - period; utc; time # preload instance variables before freezing - super - end - - def marshal_dump - [utc, time_zone.name, time] - end - - def marshal_load(variables) - initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc) - end - - # Ensure proxy class responds to all methods that underlying time instance - # responds to. - def respond_to_missing?(sym, include_priv) - # consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime - return false if sym.to_sym == :acts_like_date? - time.respond_to?(sym, include_priv) - end - - # Send the missing method to +time+ instance, and wrap result in a new - # TimeWithZone with the existing +time_zone+. - def method_missing(sym, *args, &block) - wrap_with_time_zone time.__send__(sym, *args, &block) - rescue NoMethodError => e - raise e, e.message.sub(time.inspect, self.inspect), e.backtrace - end - - private - def get_period_and_ensure_valid_local_time(period) - # we don't want a Time.local instance enforcing its own DST rules as well, - # so transfer time values to a utc constructor if necessary - @time = transfer_time_values_to_utc_constructor(@time) unless @time.utc? - begin - period || @time_zone.period_for_local(@time) - rescue ::TZInfo::PeriodNotFound - # time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again - @time += 1.hour - retry - end - end - - def transfer_time_values_to_utc_constructor(time) - ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000)) - end - - def duration_of_variable_length?(obj) - ActiveSupport::Duration === obj && obj.parts.any? {|p| [:years, :months, :days].include?(p[0]) } - end - - def wrap_with_time_zone(time) - if time.acts_like?(:time) - periods = time_zone.periods_for_local(time) - self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil) - elsif time.is_a?(Range) - wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end) - else - time - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/values/unicode_tables.dat b/app/server/ruby/vendor/activesupport/lib/active_support/values/unicode_tables.dat deleted file mode 100644 index 394ee95f4b2efad33d8b6e5d4fdbcf3c26338339..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 904640 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

J-l#vnHb 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 diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/version.rb b/app/server/ruby/vendor/activesupport/lib/active_support/version.rb deleted file mode 100644 index fe03984546..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/version.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative 'gem_version' - -module ActiveSupport - # Returns the version of the currently loaded ActiveSupport as a Gem::Version - def self.version - gem_version - end -end diff --git a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/jdom.rb b/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/jdom.rb deleted file mode 100644 index 27c64c4dca..0000000000 --- a/app/server/ruby/vendor/activesupport/lib/active_support/xml_mini/jdom.rb +++ /dev/null @@ -1,180 +0,0 @@ -raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM =~ /java/ - -require 'jruby' -include Java - -require 'active_support/core_ext/object/blank' - -java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder -java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory -java_import java.io.StringReader unless defined? StringReader -java_import org.xml.sax.InputSource unless defined? InputSource -java_import org.xml.sax.Attributes unless defined? Attributes -java_import org.w3c.dom.Node unless defined? Node - -module ActiveSupport - module XmlMini_JDOM #:nodoc: - extend self - - CONTENT_KEY = '__content__'.freeze - - NODE_TYPE_NAMES = %w{ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE - DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE - PROCESSING_INSTRUCTION_NODE TEXT_NODE} - - node_type_map = {} - NODE_TYPE_NAMES.each { |type| node_type_map[Node.send(type)] = type } - - # Parse an XML Document string or IO into a simple hash using Java's jdom. - # data:: - # XML Document string or IO to parse - def parse(data) - if data.respond_to?(:read) - data = data.read - end - - if data.blank? - {} - else - @dbf = DocumentBuilderFactory.new_instance - # secure processing of java xml - # http://www.ibm.com/developerworks/xml/library/x-tipcfsx/index.html - @dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) - @dbf.setFeature("http://xml.org/sax/features/external-general-entities", false) - @dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false) - @dbf.setFeature(javax.xml.XMLConstants::FEATURE_SECURE_PROCESSING, true) - xml_string_reader = StringReader.new(data) - xml_input_source = InputSource.new(xml_string_reader) - doc = @dbf.new_document_builder.parse(xml_input_source) - merge_element!({CONTENT_KEY => ''}, doc.document_element) - end - end - - private - - # Convert an XML element and merge into the hash - # - # hash:: - # Hash to merge the converted element into. - # element:: - # XML element to merge into hash - def merge_element!(hash, element) - delete_empty(hash) - merge!(hash, element.tag_name, collapse(element)) - end - - def delete_empty(hash) - hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == '' - end - - # Actually converts an XML document element into a data structure. - # - # element:: - # The document element to be collapsed. - def collapse(element) - hash = get_attributes(element) - - child_nodes = element.child_nodes - if child_nodes.length > 0 - (0...child_nodes.length).each do |i| - child = child_nodes.item(i) - merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE - end - merge_texts!(hash, element) unless empty_content?(element) - hash - else - merge_texts!(hash, element) - end - end - - # Merge all the texts of an element into the hash - # - # hash:: - # Hash to add the converted element to. - # element:: - # XML element whose texts are to me merged into the hash - def merge_texts!(hash, element) - delete_empty(hash) - text_children = texts(element) - if text_children.join.empty? - hash - else - # must use value to prevent double-escaping - merge!(hash, CONTENT_KEY, text_children.join) - end - end - - # Adds a new key/value pair to an existing Hash. If the key to be added - # already exists and the existing value associated with key is not - # an Array, it will be wrapped in an Array. Then the new value is - # appended to that Array. - # - # hash:: - # Hash to add key/value pair to. - # key:: - # Key to be added. - # value:: - # Value to be associated with key. - def merge!(hash, key, value) - if hash.has_key?(key) - if hash[key].instance_of?(Array) - hash[key] << value - else - hash[key] = [hash[key], value] - end - elsif value.instance_of?(Array) - hash[key] = [value] - else - hash[key] = value - end - hash - end - - # Converts the attributes array of an XML element into a hash. - # Returns an empty Hash if node has no attributes. - # - # element:: - # XML element to extract attributes from. - def get_attributes(element) - attribute_hash = {} - attributes = element.attributes - (0...attributes.length).each do |i| - attribute_hash[CONTENT_KEY] ||= '' - attribute_hash[attributes.item(i).name] = attributes.item(i).value - end - attribute_hash - end - - # Determines if a document element has text content - # - # element:: - # XML element to be checked. - def texts(element) - texts = [] - child_nodes = element.child_nodes - (0...child_nodes.length).each do |i| - item = child_nodes.item(i) - if item.node_type == Node.TEXT_NODE - texts << item.get_data - end - end - texts - end - - # Determines if a document element has text content - # - # element:: - # XML element to be checked. - def empty_content?(element) - text = '' - child_nodes = element.child_nodes - (0...child_nodes.length).each do |i| - item = child_nodes.item(i) - if item.node_type == Node.TEXT_NODE - text << item.get_data.strip - end - end - text.strip.length == 0 - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/abstract_unit.rb b/app/server/ruby/vendor/activesupport/test/abstract_unit.rb deleted file mode 100644 index 0b393e0c7a..0000000000 --- a/app/server/ruby/vendor/activesupport/test/abstract_unit.rb +++ /dev/null @@ -1,38 +0,0 @@ -ORIG_ARGV = ARGV.dup - -begin - old, $VERBOSE = $VERBOSE, nil - require File.expand_path('../../../load_paths', __FILE__) -ensure - $VERBOSE = old -end - -require 'active_support/core_ext/kernel/reporting' - -silence_warnings do - Encoding.default_internal = "UTF-8" - Encoding.default_external = "UTF-8" -end - -require 'active_support/testing/autorun' - -ENV['NO_RELOAD'] = '1' -require 'active_support' - -Thread.abort_on_exception = true - -# Show backtraces for deprecated behavior for quicker cleanup. -ActiveSupport::Deprecation.debug = true - -# Disable available locale checks to avoid warnings running the test suite. -I18n.enforce_available_locales = false - -# Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = '') - skip message if RUBY_ENGINE == 'rbx' -end - -# Skips the current run on JRuby using Minitest::Assertions#skip -def jruby_skip(message = '') - skip message if defined?(JRUBY_VERSION) -end diff --git a/app/server/ruby/vendor/activesupport/test/autoload_test.rb b/app/server/ruby/vendor/activesupport/test/autoload_test.rb deleted file mode 100644 index 7d02d835a8..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoload_test.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'abstract_unit' - -class TestAutoloadModule < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - module ::Fixtures - extend ActiveSupport::Autoload - - module Autoload - extend ActiveSupport::Autoload - end - end - - test "the autoload module works like normal autoload" do - module ::Fixtures::Autoload - autoload :SomeClass, "fixtures/autoload/some_class" - end - - assert_nothing_raised { ::Fixtures::Autoload::SomeClass } - end - - test "when specifying an :eager constant it still works like normal autoload by default" do - module ::Fixtures::Autoload - autoload :SomeClass, "fixtures/autoload/some_class" - end - - assert !$LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") - assert_nothing_raised { ::Fixtures::Autoload::SomeClass } - end - - test "the location of autoloaded constants defaults to :name.underscore" do - module ::Fixtures::Autoload - autoload :SomeClass - end - - assert !$LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") - assert_nothing_raised { ::Fixtures::Autoload::SomeClass } - end - - test "the location of :eager autoloaded constants defaults to :name.underscore" do - module ::Fixtures::Autoload - autoload :SomeClass - end - - ::Fixtures::Autoload.eager_load! - assert_nothing_raised { ::Fixtures::Autoload::SomeClass } - end - - test "a directory for a block of autoloads can be specified" do - module ::Fixtures - autoload_under "autoload" do - autoload :AnotherClass - end - end - - assert !$LOADED_FEATURES.include?("fixtures/autoload/another_class.rb") - assert_nothing_raised { ::Fixtures::AnotherClass } - end - - test "a path for a block of autoloads can be specified" do - module ::Fixtures - autoload_at "fixtures/autoload/another_class" do - autoload :AnotherClass - end - end - - assert !$LOADED_FEATURES.include?("fixtures/autoload/another_class.rb") - assert_nothing_raised { ::Fixtures::AnotherClass } - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/b.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/b.rb deleted file mode 100644 index 9c9e6454cf..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/b.rb +++ /dev/null @@ -1,2 +0,0 @@ -class A::B -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/c/d.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/c/d.rb deleted file mode 100644 index 0f40d6fbc4..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/c/d.rb +++ /dev/null @@ -1,2 +0,0 @@ -class A::C::D -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/c/e/f.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/c/e/f.rb deleted file mode 100644 index 57dba5a307..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/a/c/e/f.rb +++ /dev/null @@ -1,2 +0,0 @@ -class A::C::E::F -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/application.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/application.rb deleted file mode 100644 index d7d3096dcb..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/application.rb +++ /dev/null @@ -1 +0,0 @@ -ApplicationController = 10 diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/circular1.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/circular1.rb deleted file mode 100644 index a45761f066..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/circular1.rb +++ /dev/null @@ -1,6 +0,0 @@ -silence_warnings do - Circular2 -end - -class Circular1 -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/circular2.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/circular2.rb deleted file mode 100644 index c847fa5001..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/circular2.rb +++ /dev/null @@ -1,4 +0,0 @@ -Circular1 - -class Circular2 -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder.rb deleted file mode 100644 index ad2b27be61..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder.rb +++ /dev/null @@ -1,3 +0,0 @@ -class ClassFolder - ConstantInClassFolder = 'indeed' -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb deleted file mode 100644 index 402609c583..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/class_folder_subclass.rb +++ /dev/null @@ -1,3 +0,0 @@ -class ClassFolder::ClassFolderSubclass < ClassFolder - ConstantInClassFolder = 'indeed' -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/inline_class.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/inline_class.rb deleted file mode 100644 index 8235e90724..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/inline_class.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ClassFolder::InlineClass -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb deleted file mode 100644 index 57a13d89ea..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb +++ /dev/null @@ -1,7 +0,0 @@ -class ClassFolder - class NestedClass - end - - class SiblingClass - end -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/conflict.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/conflict.rb deleted file mode 100644 index 4ac6201902..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/conflict.rb +++ /dev/null @@ -1 +0,0 @@ -Conflict = 2 \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/counting_loader.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/counting_loader.rb deleted file mode 100644 index 4225c4412c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/counting_loader.rb +++ /dev/null @@ -1,5 +0,0 @@ -$counting_loaded_times ||= 0 -$counting_loaded_times += 1 - -module CountingLoader -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/cross_site_dependency.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/cross_site_dependency.rb deleted file mode 100644 index 21ee554e92..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/cross_site_dependency.rb +++ /dev/null @@ -1,2 +0,0 @@ -class CrossSiteDependency -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/e.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/e.rb deleted file mode 100644 index 2f59e4fb75..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/e.rb +++ /dev/null @@ -1,2 +0,0 @@ -class E -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/html/some_class.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/html/some_class.rb deleted file mode 100644 index b43d15d891..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/html/some_class.rb +++ /dev/null @@ -1,4 +0,0 @@ -module HTML - class SomeClass - end -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb deleted file mode 100644 index e3d1218c96..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb +++ /dev/null @@ -1,3 +0,0 @@ -module LoadedConstant -end - diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/loads_constant.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/loads_constant.rb deleted file mode 100644 index 0b30dc8bca..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/loads_constant.rb +++ /dev/null @@ -1,5 +0,0 @@ -module LoadsConstant -end - -# The _ = assignment is to prevent warnings -_ = RequiresConstant diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/inline_class.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/inline_class.rb deleted file mode 100644 index ca83437046..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/inline_class.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ModuleFolder::InlineClass -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb deleted file mode 100644 index fc4076bd0a..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb +++ /dev/null @@ -1,4 +0,0 @@ -module ModuleFolder - class NestedClass - end -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb deleted file mode 100644 index 80244b8bab..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ModuleFolder::NestedSibling -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb deleted file mode 100644 index d12d02f3aa..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/module_with_custom_const_missing/a/b.rb +++ /dev/null @@ -1 +0,0 @@ -ModuleWithCustomConstMissing::A::B = "10" \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/multiple_constant_file.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/multiple_constant_file.rb deleted file mode 100644 index a9ff4eb89c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/multiple_constant_file.rb +++ /dev/null @@ -1,2 +0,0 @@ -MultipleConstantFile = 10 -SiblingConstant = MultipleConstantFile * 2 diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/raises_name_error.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/raises_name_error.rb deleted file mode 100644 index a49960abf0..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/raises_name_error.rb +++ /dev/null @@ -1,3 +0,0 @@ -class RaisesNameError - FooBarBaz -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/raises_no_method_error.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/raises_no_method_error.rb deleted file mode 100644 index e1b8fce24a..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/raises_no_method_error.rb +++ /dev/null @@ -1,3 +0,0 @@ -class RaisesNoMethodError - self.foobar_method_doesnt_exist -end diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/requires_constant.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/requires_constant.rb deleted file mode 100644 index 14804a0de0..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/requires_constant.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "loaded_constant" - -module RequiresConstant -end - diff --git a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/should_not_be_required.rb b/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/should_not_be_required.rb deleted file mode 100644 index 1fcf170cc5..0000000000 --- a/app/server/ruby/vendor/activesupport/test/autoloading_fixtures/should_not_be_required.rb +++ /dev/null @@ -1 +0,0 @@ -ShouldNotBeAutoloaded = 0 diff --git a/app/server/ruby/vendor/activesupport/test/benchmarkable_test.rb b/app/server/ruby/vendor/activesupport/test/benchmarkable_test.rb deleted file mode 100644 index 04d4f5e503..0000000000 --- a/app/server/ruby/vendor/activesupport/test/benchmarkable_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'abstract_unit' - -class BenchmarkableTest < ActiveSupport::TestCase - include ActiveSupport::Benchmarkable - - attr_reader :buffer, :logger - - class Buffer - include Enumerable - - def initialize; @lines = []; end - def each(&block); @lines.each(&block); end - def write(x); @lines << x; end - def close; end - def last; @lines.last; end - def size; @lines.size; end - def empty?; @lines.empty?; end - end - - def setup - @buffer = Buffer.new - @logger = ActiveSupport::Logger.new(@buffer) - end - - def test_without_block - assert_raise(LocalJumpError) { benchmark } - assert buffer.empty? - end - - def test_defaults - i_was_run = false - benchmark { i_was_run = true } - assert i_was_run - assert_last_logged - end - - def test_with_message - i_was_run = false - benchmark('test_run') { i_was_run = true } - assert i_was_run - assert_last_logged 'test_run' - end - - def test_within_level - logger.level = ActiveSupport::Logger::DEBUG - benchmark('included_debug_run', :level => :debug) { } - assert_last_logged 'included_debug_run' - end - - def test_outside_level - logger.level = ActiveSupport::Logger::ERROR - benchmark('skipped_debug_run', :level => :debug) { } - assert_no_match(/skipped_debug_run/, buffer.last) - ensure - logger.level = ActiveSupport::Logger::DEBUG - end - - private - def assert_last_logged(message = 'Benchmarking') - assert_match(/^#{message} \(.*\)$/, buffer.last) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/broadcast_logger_test.rb b/app/server/ruby/vendor/activesupport/test/broadcast_logger_test.rb deleted file mode 100644 index 6d4e3b74f7..0000000000 --- a/app/server/ruby/vendor/activesupport/test/broadcast_logger_test.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'abstract_unit' - -module ActiveSupport - class BroadcastLoggerTest < TestCase - attr_reader :logger, :log1, :log2 - def setup - @log1 = FakeLogger.new - @log2 = FakeLogger.new - @log1.extend Logger.broadcast @log2 - @logger = @log1 - end - - def test_debug - logger.debug "foo" - assert_equal 'foo', log1.adds.first[2] - assert_equal 'foo', log2.adds.first[2] - end - - def test_close - logger.close - assert log1.closed, 'should be closed' - assert log2.closed, 'should be closed' - end - - def test_chevrons - logger << "foo" - assert_equal %w{ foo }, log1.chevrons - assert_equal %w{ foo }, log2.chevrons - end - - def test_level - assert_nil logger.level - logger.level = 10 - assert_equal 10, log1.level - assert_equal 10, log2.level - end - - def test_progname - assert_nil logger.progname - logger.progname = 10 - assert_equal 10, log1.progname - assert_equal 10, log2.progname - end - - def test_formatter - assert_nil logger.formatter - logger.formatter = 10 - assert_equal 10, log1.formatter - assert_equal 10, log2.formatter - end - - class FakeLogger - attr_reader :adds, :closed, :chevrons - attr_accessor :level, :progname, :formatter - - def initialize - @adds = [] - @closed = false - @chevrons = [] - @level = nil - @progname = nil - @formatter = nil - end - - def debug msg, &block - add(:omg, nil, msg, &block) - end - - def << x - @chevrons << x - end - - def add(*args) - @adds << args - end - - def close - @closed = true - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/caching_test.rb b/app/server/ruby/vendor/activesupport/test/caching_test.rb deleted file mode 100644 index 18923f61d1..0000000000 --- a/app/server/ruby/vendor/activesupport/test/caching_test.rb +++ /dev/null @@ -1,1072 +0,0 @@ -require 'logger' -require 'abstract_unit' -require 'active_support/cache' -require 'dependencies_test_helpers' - -module ActiveSupport - module Cache - module Strategy - module LocalCache - class MiddlewareTest < ActiveSupport::TestCase - def test_local_cache_cleared_on_close - key = "super awesome key" - assert_nil LocalCacheRegistry.cache_for key - middleware = Middleware.new('<3', key).new(->(env) { - assert LocalCacheRegistry.cache_for(key), 'should have a cache' - [200, {}, []] - }) - _, _, body = middleware.call({}) - assert LocalCacheRegistry.cache_for(key), 'should still have a cache' - body.each { } - assert LocalCacheRegistry.cache_for(key), 'should still have a cache' - body.close - assert_nil LocalCacheRegistry.cache_for(key) - end - - def test_local_cache_cleared_on_exception - key = "super awesome key" - assert_nil LocalCacheRegistry.cache_for key - middleware = Middleware.new('<3', key).new(->(env) { - assert LocalCacheRegistry.cache_for(key), 'should have a cache' - raise - }) - assert_raises(RuntimeError) { middleware.call({}) } - assert_nil LocalCacheRegistry.cache_for(key) - end - end - end - end - end -end - -class CacheKeyTest < ActiveSupport::TestCase - def test_entry_legacy_optional_ivars - legacy = Class.new(ActiveSupport::Cache::Entry) do - def initialize(value, options = {}) - @value = value - @expires_in = nil - @created_at = nil - super - end - end - - entry = legacy.new 'foo' - assert_equal 'foo', entry.value - end - - def test_expand_cache_key - assert_equal '1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true]) - assert_equal 'name/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name) - end - - def test_expand_cache_key_with_rails_cache_id - begin - ENV['RAILS_CACHE_ID'] = 'c99' - assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo) - assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key([:foo]) - assert_equal 'c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar]) - assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key(:foo, :nm) - assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm) - assert_equal 'nm/c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm) - ensure - ENV['RAILS_CACHE_ID'] = nil - end - end - - def test_expand_cache_key_with_rails_app_version - begin - ENV['RAILS_APP_VERSION'] = 'rails3' - assert_equal 'rails3/foo', ActiveSupport::Cache.expand_cache_key(:foo) - ensure - ENV['RAILS_APP_VERSION'] = nil - end - end - - def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version - begin - ENV['RAILS_CACHE_ID'] = 'c99' - ENV['RAILS_APP_VERSION'] = 'rails3' - assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo) - ensure - ENV['RAILS_CACHE_ID'] = nil - ENV['RAILS_APP_VERSION'] = nil - end - end - - def test_expand_cache_key_respond_to_cache_key - key = 'foo' - def key.cache_key - :foo_key - end - assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key(key) - end - - def test_expand_cache_key_array_with_something_that_responds_to_cache_key - key = 'foo' - def key.cache_key - :foo_key - end - assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key([key]) - end - - def test_expand_cache_key_of_nil - assert_equal '', ActiveSupport::Cache.expand_cache_key(nil) - end - - def test_expand_cache_key_of_false - assert_equal 'false', ActiveSupport::Cache.expand_cache_key(false) - end - - def test_expand_cache_key_of_true - assert_equal 'true', ActiveSupport::Cache.expand_cache_key(true) - end - - def test_expand_cache_key_of_array_like_object - assert_equal 'foo/bar/baz', ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum) - end -end - -class CacheStoreSettingTest < ActiveSupport::TestCase - def test_file_fragment_cache_store - store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory" - assert_kind_of(ActiveSupport::Cache::FileStore, store) - assert_equal "/path/to/cache/directory", store.cache_path - end - - def test_mem_cache_fragment_cache_store - Dalli::Client.expects(:new).with(%w[localhost], {}) - store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost" - assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) - end - - def test_mem_cache_fragment_cache_store_with_given_mem_cache - mem_cache = Dalli::Client.new - Dalli::Client.expects(:new).never - store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache - assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) - end - - def test_mem_cache_fragment_cache_store_with_not_dalli_client - Dalli::Client.expects(:new).never - memcache = Object.new - assert_raises(ArgumentError) do - ActiveSupport::Cache.lookup_store :mem_cache_store, memcache - end - end - - def test_mem_cache_fragment_cache_store_with_multiple_servers - Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], {}) - store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1' - assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) - end - - def test_mem_cache_fragment_cache_store_with_options - Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], { :timeout => 10 }) - store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10 - assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) - assert_equal 'foo', store.options[:namespace] - end - - def test_object_assigned_fragment_cache_store - store = ActiveSupport::Cache.lookup_store ActiveSupport::Cache::FileStore.new("/path/to/cache/directory") - assert_kind_of(ActiveSupport::Cache::FileStore, store) - assert_equal "/path/to/cache/directory", store.cache_path - end -end - -class CacheStoreNamespaceTest < ActiveSupport::TestCase - def test_static_namespace - cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "tester") - cache.write("foo", "bar") - assert_equal "bar", cache.read("foo") - assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value - end - - def test_proc_namespace - test_val = "tester" - proc = lambda{test_val} - cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => proc) - cache.write("foo", "bar") - assert_equal "bar", cache.read("foo") - assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value - end - - def test_delete_matched_key_start - cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "tester") - cache.write("foo", "bar") - cache.write("fu", "baz") - cache.delete_matched(/^fo/) - assert !cache.exist?("foo") - assert cache.exist?("fu") - end - - def test_delete_matched_key - cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "foo") - cache.write("foo", "bar") - cache.write("fu", "baz") - cache.delete_matched(/OO/i) - assert !cache.exist?("foo") - assert cache.exist?("fu") - end -end - -# Tests the base functionality that should be identical across all cache stores. -module CacheStoreBehavior - def test_should_read_and_write_strings - assert @cache.write('foo', 'bar') - assert_equal 'bar', @cache.read('foo') - end - - def test_should_overwrite - @cache.write('foo', 'bar') - @cache.write('foo', 'baz') - assert_equal 'baz', @cache.read('foo') - end - - def test_fetch_without_cache_miss - @cache.write('foo', 'bar') - @cache.expects(:write).never - assert_equal 'bar', @cache.fetch('foo') { 'baz' } - end - - def test_fetch_with_cache_miss - @cache.expects(:write).with('foo', 'baz', @cache.options) - assert_equal 'baz', @cache.fetch('foo') { 'baz' } - end - - def test_fetch_with_cache_miss_passes_key_to_block - cache_miss = false - assert_equal 3, @cache.fetch('foo') { |key| cache_miss = true; key.length } - assert cache_miss - - cache_miss = false - assert_equal 3, @cache.fetch('foo') { |key| cache_miss = true; key.length } - assert !cache_miss - end - - def test_fetch_with_forced_cache_miss - @cache.write('foo', 'bar') - @cache.expects(:read).never - @cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true)) - @cache.fetch('foo', :force => true) { 'bar' } - end - - def test_fetch_with_cached_nil - @cache.write('foo', nil) - @cache.expects(:write).never - assert_nil @cache.fetch('foo') { 'baz' } - end - - def test_should_read_and_write_hash - assert @cache.write('foo', {:a => "b"}) - assert_equal({:a => "b"}, @cache.read('foo')) - end - - def test_should_read_and_write_integer - assert @cache.write('foo', 1) - assert_equal 1, @cache.read('foo') - end - - def test_should_read_and_write_nil - assert @cache.write('foo', nil) - assert_equal nil, @cache.read('foo') - end - - def test_should_read_and_write_false - assert @cache.write('foo', false) - assert_equal false, @cache.read('foo') - end - - def test_read_multi - @cache.write('foo', 'bar') - @cache.write('fu', 'baz') - @cache.write('fud', 'biz') - assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu')) - end - - def test_read_multi_with_expires - time = Time.now - @cache.write('foo', 'bar', :expires_in => 10) - @cache.write('fu', 'baz') - @cache.write('fud', 'biz') - Time.stubs(:now).returns(time + 11) - assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu')) - end - - def test_fetch_multi - @cache.write('foo', 'bar') - @cache.write('fud', 'biz') - - values = @cache.fetch_multi('foo', 'fu', 'fud') { |value| value * 2 } - - assert_equal({ 'foo' => 'bar', 'fu' => 'fufu', 'fud' => 'biz' }, values) - assert_equal('fufu', @cache.read('fu')) - end - - def test_multi_with_objects - foo = stub(:title => 'FOO!', :cache_key => 'foo') - bar = stub(:cache_key => 'bar') - - @cache.write('bar', 'BAM!') - - values = @cache.fetch_multi(foo, bar) { |object| object.title } - - assert_equal({ foo => 'FOO!', bar => 'BAM!' }, values) - end - - def test_read_and_write_compressed_small_data - @cache.write('foo', 'bar', :compress => true) - assert_equal 'bar', @cache.read('foo') - end - - def test_read_and_write_compressed_large_data - @cache.write('foo', 'bar', :compress => true, :compress_threshold => 2) - assert_equal 'bar', @cache.read('foo') - end - - def test_read_and_write_compressed_nil - @cache.write('foo', nil, :compress => true) - assert_nil @cache.read('foo') - end - - def test_cache_key - obj = Object.new - def obj.cache_key - :foo - end - @cache.write(obj, "bar") - assert_equal "bar", @cache.read("foo") - end - - def test_param_as_cache_key - obj = Object.new - def obj.to_param - "foo" - end - @cache.write(obj, "bar") - assert_equal "bar", @cache.read("foo") - end - - def test_array_as_cache_key - @cache.write([:fu, "foo"], "bar") - assert_equal "bar", @cache.read("fu/foo") - end - - def test_hash_as_cache_key - @cache.write({:foo => 1, :fu => 2}, "bar") - assert_equal "bar", @cache.read("foo=1/fu=2") - end - - def test_keys_are_case_sensitive - @cache.write("foo", "bar") - assert_nil @cache.read("FOO") - end - - def test_exist - @cache.write('foo', 'bar') - assert_equal true, @cache.exist?('foo') - assert_equal false, @cache.exist?('bar') - end - - def test_nil_exist - @cache.write('foo', nil) - assert @cache.exist?('foo') - end - - def test_delete - @cache.write('foo', 'bar') - assert @cache.exist?('foo') - assert @cache.delete('foo') - assert !@cache.exist?('foo') - end - - def test_original_store_objects_should_not_be_immutable - bar = 'bar' - @cache.write('foo', bar) - assert_nothing_raised { bar.gsub!(/.*/, 'baz') } - end - - def test_expires_in - time = Time.local(2008, 4, 24) - Time.stubs(:now).returns(time) - - @cache.write('foo', 'bar') - assert_equal 'bar', @cache.read('foo') - - Time.stubs(:now).returns(time + 30) - assert_equal 'bar', @cache.read('foo') - - Time.stubs(:now).returns(time + 61) - assert_nil @cache.read('foo') - end - - def test_race_condition_protection - time = Time.now - @cache.write('foo', 'bar', :expires_in => 60) - Time.stubs(:now).returns(time + 61) - result = @cache.fetch('foo', :race_condition_ttl => 10) do - assert_equal 'bar', @cache.read('foo') - "baz" - end - assert_equal "baz", result - end - - def test_race_condition_protection_is_limited - time = Time.now - @cache.write('foo', 'bar', :expires_in => 60) - Time.stubs(:now).returns(time + 71) - result = @cache.fetch('foo', :race_condition_ttl => 10) do - assert_equal nil, @cache.read('foo') - "baz" - end - assert_equal "baz", result - end - - def test_race_condition_protection_is_safe - time = Time.now - @cache.write('foo', 'bar', :expires_in => 60) - Time.stubs(:now).returns(time + 61) - begin - @cache.fetch('foo', :race_condition_ttl => 10) do - assert_equal 'bar', @cache.read('foo') - raise ArgumentError.new - end - rescue ArgumentError - end - assert_equal "bar", @cache.read('foo') - Time.stubs(:now).returns(time + 91) - assert_nil @cache.read('foo') - end - - def test_crazy_key_characters - crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-" - assert @cache.write(crazy_key, "1", :raw => true) - assert_equal "1", @cache.read(crazy_key) - assert_equal "1", @cache.fetch(crazy_key) - assert @cache.delete(crazy_key) - assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" } - assert_equal 3, @cache.increment(crazy_key) - assert_equal 2, @cache.decrement(crazy_key) - end - - def test_really_long_keys - key = "" - 900.times{key << "x"} - assert @cache.write(key, "bar") - assert_equal "bar", @cache.read(key) - assert_equal "bar", @cache.fetch(key) - assert_nil @cache.read("#{key}x") - assert_equal({key => "bar"}, @cache.read_multi(key)) - assert @cache.delete(key) - end -end - -# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters -# The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special -# characters like the umlaut in UTF-8. -module EncodedKeyCacheBehavior - Encoding.list.each do |encoding| - define_method "test_#{encoding.name.underscore}_encoded_values" do - key = "foo".force_encoding(encoding) - assert @cache.write(key, "1", :raw => true) - assert_equal "1", @cache.read(key) - assert_equal "1", @cache.fetch(key) - assert @cache.delete(key) - assert_equal "2", @cache.fetch(key, :raw => true) { "2" } - assert_equal 3, @cache.increment(key) - assert_equal 2, @cache.decrement(key) - end - end - - def test_common_utf8_values - key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) - assert @cache.write(key, "1", :raw => true) - assert_equal "1", @cache.read(key) - assert_equal "1", @cache.fetch(key) - assert @cache.delete(key) - assert_equal "2", @cache.fetch(key, :raw => true) { "2" } - assert_equal 3, @cache.increment(key) - assert_equal 2, @cache.decrement(key) - end - - def test_retains_encoding - key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) - assert @cache.write(key, "1", :raw => true) - assert_equal Encoding::UTF_8, key.encoding - end -end - -module CacheDeleteMatchedBehavior - def test_delete_matched - @cache.write("foo", "bar") - @cache.write("fu", "baz") - @cache.write("foo/bar", "baz") - @cache.write("fu/baz", "bar") - @cache.delete_matched(/oo/) - assert !@cache.exist?("foo") - assert @cache.exist?("fu") - assert !@cache.exist?("foo/bar") - assert @cache.exist?("fu/baz") - end -end - -module CacheIncrementDecrementBehavior - def test_increment - @cache.write('foo', 1, :raw => true) - assert_equal 1, @cache.read('foo').to_i - assert_equal 2, @cache.increment('foo') - assert_equal 2, @cache.read('foo').to_i - assert_equal 3, @cache.increment('foo') - assert_equal 3, @cache.read('foo').to_i - assert_nil @cache.increment('bar') - end - - def test_decrement - @cache.write('foo', 3, :raw => true) - assert_equal 3, @cache.read('foo').to_i - assert_equal 2, @cache.decrement('foo') - assert_equal 2, @cache.read('foo').to_i - assert_equal 1, @cache.decrement('foo') - assert_equal 1, @cache.read('foo').to_i - assert_nil @cache.decrement('bar') - end -end - -module LocalCacheBehavior - def test_local_writes_are_persistent_on_the_remote_cache - retval = @cache.with_local_cache do - @cache.write('foo', 'bar') - end - assert retval - assert_equal 'bar', @cache.read('foo') - end - - def test_clear_also_clears_local_cache - @cache.with_local_cache do - @cache.write('foo', 'bar') - @cache.clear - assert_nil @cache.read('foo') - end - - assert_nil @cache.read('foo') - end - - def test_local_cache_of_write - @cache.with_local_cache do - @cache.write('foo', 'bar') - @peek.delete('foo') - assert_equal 'bar', @cache.read('foo') - end - end - - def test_local_cache_of_read - @cache.write('foo', 'bar') - @cache.with_local_cache do - assert_equal 'bar', @cache.read('foo') - end - end - - def test_local_cache_of_write_nil - @cache.with_local_cache do - assert @cache.write('foo', nil) - assert_nil @cache.read('foo') - @peek.write('foo', 'bar') - assert_nil @cache.read('foo') - end - end - - def test_local_cache_of_delete - @cache.with_local_cache do - @cache.write('foo', 'bar') - @cache.delete('foo') - assert_nil @cache.read('foo') - end - end - - def test_local_cache_of_exist - @cache.with_local_cache do - @cache.write('foo', 'bar') - @peek.delete('foo') - assert @cache.exist?('foo') - end - end - - def test_local_cache_of_increment - @cache.with_local_cache do - @cache.write('foo', 1, :raw => true) - @peek.write('foo', 2, :raw => true) - @cache.increment('foo') - assert_equal 3, @cache.read('foo') - end - end - - def test_local_cache_of_decrement - @cache.with_local_cache do - @cache.write('foo', 1, :raw => true) - @peek.write('foo', 3, :raw => true) - @cache.decrement('foo') - assert_equal 2, @cache.read('foo') - end - end - - def test_middleware - app = lambda { |env| - result = @cache.write('foo', 'bar') - assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written - assert result - [200, {}, []] - } - app = @cache.middleware.new(app) - app.call({}) - end -end - -module AutoloadingCacheBehavior - include DependenciesTestHelpers - def test_simple_autoloading - with_autoloading_fixtures do - @cache.write('foo', E.new) - end - - remove_constants(:E) - ActiveSupport::Dependencies.clear - - with_autoloading_fixtures do - assert_kind_of E, @cache.read('foo') - end - - remove_constants(:E) - ActiveSupport::Dependencies.clear - end - - def test_two_classes_autoloading - with_autoloading_fixtures do - @cache.write('foo', [E.new, ClassFolder.new]) - end - - remove_constants(:E, :ClassFolder) - ActiveSupport::Dependencies.clear - - with_autoloading_fixtures do - loaded = @cache.read('foo') - assert_kind_of Array, loaded - assert_equal 2, loaded.size - assert_kind_of E, loaded[0] - assert_kind_of ClassFolder, loaded[1] - end - - remove_constants(:E, :ClassFolder) - ActiveSupport::Dependencies.clear - end -end - -class FileStoreTest < ActiveSupport::TestCase - def setup - Dir.mkdir(cache_dir) unless File.exist?(cache_dir) - @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) - @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) - @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), :expires_in => 60) - - @buffer = StringIO.new - @cache.logger = ActiveSupport::Logger.new(@buffer) - end - - def teardown - FileUtils.rm_r(cache_dir) - end - - def cache_dir - File.join(Dir.pwd, 'tmp_cache') - end - - include CacheStoreBehavior - include LocalCacheBehavior - include CacheDeleteMatchedBehavior - include CacheIncrementDecrementBehavior - include AutoloadingCacheBehavior - - def test_clear - filepath = File.join(cache_dir, ".gitkeep") - FileUtils.touch(filepath) - @cache.clear - assert File.exist?(filepath) - end - - def test_key_transformation - key = @cache.send(:key_file_path, "views/index?id=1") - assert_equal "views/index?id=1", @cache.send(:file_path_key, key) - end - - def test_key_transformation_with_pathname - FileUtils.touch(File.join(cache_dir, "foo")) - key = @cache_with_pathname.send(:key_file_path, "views/index?id=1") - assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) - end - - # Test that generated cache keys are short enough to have Tempfile stuff added to them and - # remain valid - def test_filename_max_size - key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}" - path = @cache.send(:key_file_path, key) - Dir::Tmpname.create(path) do |tmpname, n, opts| - assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}" - end - end - - # Because file systems have a maximum filename size, filenames > max size should be split in to directories - # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B - def test_key_transformation_max_filename_size - key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B" - path = @cache.send(:key_file_path, key) - assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE} - assert_equal 'B', File.basename(path) - end - - # If nothing has been stored in the cache, there is a chance the cache directory does not yet exist - # Ensure delete_matched gracefully handles this case - def test_delete_matched_when_cache_directory_does_not_exist - assert_nothing_raised(Exception) do - ActiveSupport::Cache::FileStore.new('/test/cache/directory').delete_matched(/does_not_exist/) - end - end - - def test_delete_does_not_delete_empty_parent_dir - sub_cache_dir = File.join(cache_dir, 'subdir/') - sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir) - assert_nothing_raised(Exception) do - assert sub_cache_store.write('foo', 'bar') - assert sub_cache_store.delete('foo') - end - assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!" - assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!" - assert Dir.entries(sub_cache_dir).reject {|f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f)}.empty? - end - - def test_log_exception_when_cache_read_fails - File.expects(:exist?).raises(StandardError, "failed") - @cache.send(:read_entry, "winston", {}) - assert @buffer.string.present? - end - - def test_cleanup_removes_all_expired_entries - time = Time.now - @cache.write('foo', 'bar', expires_in: 10) - @cache.write('baz', 'qux') - @cache.write('quux', 'corge', expires_in: 20) - Time.stubs(:now).returns(time + 15) - @cache.cleanup - assert_not @cache.exist?('foo') - assert @cache.exist?('baz') - assert @cache.exist?('quux') - end - - def test_write_with_unless_exist - assert_equal true, @cache.write(1, "aaaaaaaaaa") - assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) - @cache.write(1, nil) - assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) - end -end - -class MemoryStoreTest < ActiveSupport::TestCase - def setup - @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa")) - @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10 + 1) - end - - include CacheStoreBehavior - include CacheDeleteMatchedBehavior - include CacheIncrementDecrementBehavior - - def test_prune_size - @cache.write(1, "aaaaaaaaaa") && sleep(0.001) - @cache.write(2, "bbbbbbbbbb") && sleep(0.001) - @cache.write(3, "cccccccccc") && sleep(0.001) - @cache.write(4, "dddddddddd") && sleep(0.001) - @cache.write(5, "eeeeeeeeee") && sleep(0.001) - @cache.read(2) && sleep(0.001) - @cache.read(4) - @cache.prune(@record_size * 3) - assert @cache.exist?(5) - assert @cache.exist?(4) - assert !@cache.exist?(3), "no entry" - assert @cache.exist?(2) - assert !@cache.exist?(1), "no entry" - end - - def test_prune_size_on_write - @cache.write(1, "aaaaaaaaaa") && sleep(0.001) - @cache.write(2, "bbbbbbbbbb") && sleep(0.001) - @cache.write(3, "cccccccccc") && sleep(0.001) - @cache.write(4, "dddddddddd") && sleep(0.001) - @cache.write(5, "eeeeeeeeee") && sleep(0.001) - @cache.write(6, "ffffffffff") && sleep(0.001) - @cache.write(7, "gggggggggg") && sleep(0.001) - @cache.write(8, "hhhhhhhhhh") && sleep(0.001) - @cache.write(9, "iiiiiiiiii") && sleep(0.001) - @cache.write(10, "kkkkkkkkkk") && sleep(0.001) - @cache.read(2) && sleep(0.001) - @cache.read(4) && sleep(0.001) - @cache.write(11, "llllllllll") - assert @cache.exist?(11) - assert @cache.exist?(10) - assert @cache.exist?(9) - assert @cache.exist?(8) - assert @cache.exist?(7) - assert !@cache.exist?(6), "no entry" - assert !@cache.exist?(5), "no entry" - assert @cache.exist?(4) - assert !@cache.exist?(3), "no entry" - assert @cache.exist?(2) - assert !@cache.exist?(1), "no entry" - end - - def test_prune_size_on_write_based_on_key_length - @cache.write(1, "aaaaaaaaaa") && sleep(0.001) - @cache.write(2, "bbbbbbbbbb") && sleep(0.001) - @cache.write(3, "cccccccccc") && sleep(0.001) - @cache.write(4, "dddddddddd") && sleep(0.001) - @cache.write(5, "eeeeeeeeee") && sleep(0.001) - @cache.write(6, "ffffffffff") && sleep(0.001) - @cache.write(7, "gggggggggg") && sleep(0.001) - @cache.write(8, "hhhhhhhhhh") && sleep(0.001) - @cache.write(9, "iiiiiiiiii") && sleep(0.001) - long_key = '*' * 2 * @record_size - @cache.write(long_key, "llllllllll") - assert @cache.exist?(long_key) - assert @cache.exist?(9) - assert @cache.exist?(8) - assert @cache.exist?(7) - assert @cache.exist?(6) - assert !@cache.exist?(5), "no entry" - assert !@cache.exist?(4), "no entry" - assert !@cache.exist?(3), "no entry" - assert !@cache.exist?(2), "no entry" - assert !@cache.exist?(1), "no entry" - end - - def test_pruning_is_capped_at_a_max_time - def @cache.delete_entry (*args) - sleep(0.01) - super - end - @cache.write(1, "aaaaaaaaaa") && sleep(0.001) - @cache.write(2, "bbbbbbbbbb") && sleep(0.001) - @cache.write(3, "cccccccccc") && sleep(0.001) - @cache.write(4, "dddddddddd") && sleep(0.001) - @cache.write(5, "eeeeeeeeee") && sleep(0.001) - @cache.prune(30, 0.001) - assert @cache.exist?(5) - assert @cache.exist?(4) - assert @cache.exist?(3) - assert @cache.exist?(2) - assert !@cache.exist?(1) - end - - def test_write_with_unless_exist - assert_equal true, @cache.write(1, "aaaaaaaaaa") - assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true) - @cache.write(1, nil) - assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true) - end -end - -class MemCacheStoreTest < ActiveSupport::TestCase - require 'dalli' - - begin - ss = Dalli::Client.new('localhost:11211').stats - raise Dalli::DalliError unless ss['localhost:11211'] - - MEMCACHE_UP = true - rescue Dalli::DalliError - $stderr.puts "Skipping memcached tests. Start memcached and try again." - MEMCACHE_UP = false - end - - def setup - skip "memcache server is not up" unless MEMCACHE_UP - - @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :expires_in => 60) - @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store) - @data = @cache.instance_variable_get(:@data) - @cache.clear - @cache.silence! - @cache.logger = ActiveSupport::Logger.new("/dev/null") - end - - include CacheStoreBehavior - include LocalCacheBehavior - include CacheIncrementDecrementBehavior - include EncodedKeyCacheBehavior - include AutoloadingCacheBehavior - - def test_raw_values - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) - cache.clear - cache.write("foo", 2) - assert_equal "2", cache.read("foo") - end - - def test_raw_values_with_marshal - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) - cache.clear - cache.write("foo", Marshal.dump([])) - assert_equal [], cache.read("foo") - end - - def test_local_cache_raw_values - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) - cache.clear - cache.with_local_cache do - cache.write("foo", 2) - assert_equal "2", cache.read("foo") - end - end - - def test_local_cache_raw_values_with_marshal - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) - cache.clear - cache.with_local_cache do - cache.write("foo", Marshal.dump([])) - assert_equal [], cache.read("foo") - end - end - - def test_read_should_return_a_different_object_id_each_time_it_is_called - @cache.write('foo', 'bar') - assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id - value = @cache.read('foo') - value << 'bingo' - assert_not_equal value, @cache.read('foo') - end -end - -class NullStoreTest < ActiveSupport::TestCase - def setup - @cache = ActiveSupport::Cache.lookup_store(:null_store) - end - - def test_clear - @cache.clear - end - - def test_cleanup - @cache.cleanup - end - - def test_write - assert_equal true, @cache.write("name", "value") - end - - def test_read - @cache.write("name", "value") - assert_nil @cache.read("name") - end - - def test_delete - @cache.write("name", "value") - assert_equal false, @cache.delete("name") - end - - def test_increment - @cache.write("name", 1, :raw => true) - assert_nil @cache.increment("name") - end - - def test_decrement - @cache.write("name", 1, :raw => true) - assert_nil @cache.increment("name") - end - - def test_delete_matched - @cache.write("name", "value") - @cache.delete_matched(/name/) - end - - def test_local_store_strategy - @cache.with_local_cache do - @cache.write("name", "value") - assert_equal "value", @cache.read("name") - @cache.delete("name") - assert_nil @cache.read("name") - @cache.write("name", "value") - end - assert_nil @cache.read("name") - end - - def test_setting_nil_cache_store - assert ActiveSupport::Cache.lookup_store.class.name, ActiveSupport::Cache::NullStore.name - end -end - -class CacheStoreLoggerTest < ActiveSupport::TestCase - def setup - @cache = ActiveSupport::Cache.lookup_store(:memory_store) - - @buffer = StringIO.new - @cache.logger = ActiveSupport::Logger.new(@buffer) - end - - def test_logging - @cache.fetch('foo') { 'bar' } - assert @buffer.string.present? - end - - def test_mute_logging - @cache.mute { @cache.fetch('foo') { 'bar' } } - assert @buffer.string.blank? - end -end - -class CacheEntryTest < ActiveSupport::TestCase - def test_expired - entry = ActiveSupport::Cache::Entry.new("value") - assert !entry.expired?, 'entry not expired' - entry = ActiveSupport::Cache::Entry.new("value", :expires_in => 60) - assert !entry.expired?, 'entry not expired' - time = Time.now + 61 - Time.stubs(:now).returns(time) - assert entry.expired?, 'entry is expired' - end - - def test_compress_values - value = "value" * 100 - entry = ActiveSupport::Cache::Entry.new(value, :compress => true, :compress_threshold => 1) - assert_equal value, entry.value - assert(value.bytesize > entry.size, "value is compressed") - end - - def test_non_compress_values - value = "value" * 100 - entry = ActiveSupport::Cache::Entry.new(value) - assert_equal value, entry.value - assert_equal value.bytesize, entry.size - end - - def test_restoring_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, "hello") - version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - assert_equal false, entry.expired? - end - - def test_restoring_compressed_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello"))) - version_4beta1_entry.instance_variable_set(:@c, true) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - end - - def test_restoring_expired_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, "hello") - version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - assert_equal true, entry.expired? - end -end diff --git a/app/server/ruby/vendor/activesupport/test/callback_inheritance_test.rb b/app/server/ruby/vendor/activesupport/test/callback_inheritance_test.rb deleted file mode 100644 index 1adfe4edf4..0000000000 --- a/app/server/ruby/vendor/activesupport/test/callback_inheritance_test.rb +++ /dev/null @@ -1,176 +0,0 @@ -require 'abstract_unit' - -class GrandParent - include ActiveSupport::Callbacks - - attr_reader :log, :action_name - def initialize(action_name) - @action_name, @log = action_name, [] - end - - define_callbacks :dispatch - set_callback :dispatch, :before, :before1, :before2, :if => proc {|c| c.action_name == "index" || c.action_name == "update" } - set_callback :dispatch, :after, :after1, :after2, :if => proc {|c| c.action_name == "update" || c.action_name == "delete" } - - def before1 - @log << "before1" - end - - def before2 - @log << "before2" - end - - def after1 - @log << "after1" - end - - def after2 - @log << "after2" - end - - def dispatch - run_callbacks :dispatch do - @log << action_name - end - self - end -end - -class Parent < GrandParent - skip_callback :dispatch, :before, :before2, :unless => proc {|c| c.action_name == "update" } - skip_callback :dispatch, :after, :after2, :unless => proc {|c| c.action_name == "delete" } -end - -class Child < GrandParent - skip_callback :dispatch, :before, :before2, :unless => proc {|c| c.action_name == "update" }, :if => :state_open? - - def state_open? - @state == :open - end - - def initialize(action_name, state) - super(action_name) - @state = state - end -end - -class EmptyParent - include ActiveSupport::Callbacks - - def performed? - @performed ||= false - end - - define_callbacks :dispatch - - def perform! - @performed = true - end - - def dispatch - run_callbacks :dispatch - self - end -end - -class EmptyChild < EmptyParent - set_callback :dispatch, :before, :do_nothing - - def do_nothing - end -end - -class CountingParent - include ActiveSupport::Callbacks - - attr_reader :count - - define_callbacks :dispatch - - def initialize - @count = 0 - end - - def count! - @count += 1 - end - - def dispatch - run_callbacks(:dispatch) - self - end -end - -class CountingChild < CountingParent -end - -class BasicCallbacksTest < ActiveSupport::TestCase - def setup - @index = GrandParent.new("index").dispatch - @update = GrandParent.new("update").dispatch - @delete = GrandParent.new("delete").dispatch - end - - def test_basic_conditional_callback1 - assert_equal %w(before1 before2 index), @index.log - end - - def test_basic_conditional_callback2 - assert_equal %w(before1 before2 update after2 after1), @update.log - end - - def test_basic_conditional_callback3 - assert_equal %w(delete after2 after1), @delete.log - end -end - -class InheritedCallbacksTest < ActiveSupport::TestCase - def setup - @index = Parent.new("index").dispatch - @update = Parent.new("update").dispatch - @delete = Parent.new("delete").dispatch - end - - def test_inherited_excluded - assert_equal %w(before1 index), @index.log - end - - def test_inherited_not_excluded - assert_equal %w(before1 before2 update after1), @update.log - end - - def test_partially_excluded - assert_equal %w(delete after2 after1), @delete.log - end -end - -class InheritedCallbacksTest2 < ActiveSupport::TestCase - def setup - @update1 = Child.new("update", :open).dispatch - @update2 = Child.new("update", :closed).dispatch - end - - def test_crazy_mix_on - assert_equal %w(before1 update after2 after1), @update1.log - end - - def test_crazy_mix_off - assert_equal %w(before1 before2 update after2 after1), @update2.log - end -end - -class DynamicInheritedCallbacks < ActiveSupport::TestCase - def test_callbacks_looks_to_the_superclass_before_running - child = EmptyChild.new.dispatch - assert !child.performed? - EmptyParent.set_callback :dispatch, :before, :perform! - child = EmptyChild.new.dispatch - assert child.performed? - end - - def test_callbacks_should_be_performed_once_in_child_class - CountingParent.set_callback(:dispatch, :before) { count! } - child = CountingChild.new.dispatch - assert_equal 1, child.count - end -end diff --git a/app/server/ruby/vendor/activesupport/test/callbacks_test.rb b/app/server/ruby/vendor/activesupport/test/callbacks_test.rb deleted file mode 100644 index 32c2dfdfc0..0000000000 --- a/app/server/ruby/vendor/activesupport/test/callbacks_test.rb +++ /dev/null @@ -1,1003 +0,0 @@ -require 'abstract_unit' - -module CallbacksTest - class Record - include ActiveSupport::Callbacks - - define_callbacks :save - - def self.before_save(*filters, &blk) - set_callback(:save, :before, *filters, &blk) - end - - def self.after_save(*filters, &blk) - set_callback(:save, :after, *filters, &blk) - end - - class << self - def callback_symbol(callback_method) - method_name = :"#{callback_method}_method" - define_method(method_name) do - history << [callback_method, :symbol] - end - method_name - end - - def callback_string(callback_method) - "history << [#{callback_method.to_sym.inspect}, :string]" - end - - def callback_proc(callback_method) - Proc.new { |model| model.history << [callback_method, :proc] } - end - - def callback_object(callback_method) - klass = Class.new - klass.send(:define_method, callback_method) do |model| - model.history << [:"#{callback_method}_save", :object] - end - klass.new - end - end - - def history - @history ||= [] - end - end - - class CallbackClass - def self.before(model) - model.history << [:before_save, :class] - end - - def self.after(model) - model.history << [:after_save, :class] - end - end - - class Person < Record - [:before_save, :after_save].each do |callback_method| - callback_method_sym = callback_method.to_sym - send(callback_method, callback_symbol(callback_method_sym)) - send(callback_method, callback_string(callback_method_sym)) - send(callback_method, callback_proc(callback_method_sym)) - send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, ''))) - send(callback_method, CallbackClass) - send(callback_method) { |model| model.history << [callback_method_sym, :block] } - end - - def save - run_callbacks :save - end - end - - class PersonSkipper < Person - skip_callback :save, :before, :before_save_method, :if => :yes - skip_callback :save, :after, :before_save_method, :unless => :yes - skip_callback :save, :after, :before_save_method, :if => :no - skip_callback :save, :before, :before_save_method, :unless => :no - skip_callback :save, :before, CallbackClass , :if => :yes - def yes; true; end - def no; false; end - end - - class PersonForProgrammaticSkipping < Person - end - - class ParentController - include ActiveSupport::Callbacks - - define_callbacks :dispatch - - set_callback :dispatch, :before, :log, :unless => proc {|c| c.action_name == :index || c.action_name == :show } - set_callback :dispatch, :after, :log2 - - attr_reader :action_name, :logger - def initialize(action_name) - @action_name, @logger = action_name, [] - end - - def log - @logger << action_name - end - - def log2 - @logger << action_name - end - - def dispatch - run_callbacks :dispatch do - @logger << "Done" - end - self - end - end - - class Child < ParentController - skip_callback :dispatch, :before, :log, :if => proc {|c| c.action_name == :update} - skip_callback :dispatch, :after, :log2 - end - - class OneTimeCompile < Record - @@starts_true, @@starts_false = true, false - - def initialize - super - end - - before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :if => :starts_true - before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :if => :starts_false - before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :unless => :starts_true - before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :unless => :starts_false - - def starts_true - if @@starts_true - @@starts_true = false - return true - end - @@starts_true - end - - def starts_false - unless @@starts_false - @@starts_false = true - return false - end - @@starts_false - end - - def save - run_callbacks :save - end - end - - class OneTimeCompileTest < ActiveSupport::TestCase - def test_optimized_first_compile - around = OneTimeCompile.new - around.save - assert_equal [ - [:before_save, :starts_true, :if], - [:before_save, :starts_true, :unless] - ], around.history - end - end - - class AfterSaveConditionalPerson < Record - after_save Proc.new { |r| r.history << [:after_save, :string1] } - after_save Proc.new { |r| r.history << [:after_save, :string2] } - def save - run_callbacks :save - end - end - - class AfterSaveConditionalPersonCallbackTest < ActiveSupport::TestCase - def test_after_save_runs_in_the_reverse_order - person = AfterSaveConditionalPerson.new - person.save - assert_equal [ - [:after_save, :string2], - [:after_save, :string1] - ], person.history - end - end - - - - class ConditionalPerson < Record - # proc - before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true } - before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false } - before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false } - before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true } - # symbol - before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes - before_save Proc.new { |r| r.history << "b00m" }, :if => :no - before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no - before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes - # string - before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes' - before_save Proc.new { |r| r.history << "b00m" }, :if => 'no' - before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no' - before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes' - # Combined if and unless - before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no - before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes - - def yes; true; end - def other_yes; true; end - def no; false; end - def other_no; false; end - - def save - run_callbacks :save - end - end - - class CleanPerson < ConditionalPerson - reset_callbacks :save - end - - class MySuper - include ActiveSupport::Callbacks - define_callbacks :save - end - - class AroundPerson < MySuper - attr_reader :history - - set_callback :save, :before, :nope, :if => :no - set_callback :save, :before, :nope, :unless => :yes - set_callback :save, :after, :tweedle - set_callback :save, :before, "tweedle_dee" - set_callback :save, :before, proc {|m| m.history << "yup" } - set_callback :save, :before, :nope, :if => proc { false } - set_callback :save, :before, :nope, :unless => proc { true } - set_callback :save, :before, :yup, :if => proc { true } - set_callback :save, :before, :yup, :unless => proc { false } - set_callback :save, :around, :tweedle_dum - set_callback :save, :around, :w0tyes, :if => :yes - set_callback :save, :around, :w0tno, :if => :no - set_callback :save, :around, :tweedle_deedle - - def no; false; end - def yes; true; end - - def nope - @history << "boom" - end - - def yup - @history << "yup" - end - - def w0tyes - @history << "w0tyes before" - yield - @history << "w0tyes after" - end - - def w0tno - @history << "boom" - yield - end - - def tweedle_dee - @history << "tweedle dee" - end - - def tweedle_dum - @history << "tweedle dum pre" - yield - @history << "tweedle dum post" - end - - def tweedle - @history << "tweedle" - end - - def tweedle_deedle - @history << "tweedle deedle pre" - yield - @history << "tweedle deedle post" - end - - def initialize - @history = [] - end - - def save - run_callbacks :save do - @history << "running" - end - end - end - - class AroundPersonResult < MySuper - attr_reader :result - - set_callback :save, :after, :tweedle_1 - set_callback :save, :around, :tweedle_dum - set_callback :save, :after, :tweedle_2 - - def tweedle_dum - @result = yield - end - - def tweedle_1 - :tweedle_1 - end - - def tweedle_2 - :tweedle_2 - end - - def save - run_callbacks :save do - :running - end - end - end - - class HyphenatedCallbacks - include ActiveSupport::Callbacks - define_callbacks :save - attr_reader :stuff - - set_callback :save, :before, :action, :if => :yes - - def yes() true end - - def action - @stuff = "ACTION" - end - - def save - run_callbacks :save do - @stuff - end - end - end - - module ExtendModule - def self.extended(base) - base.class_eval do - set_callback :save, :before, :record3 - end - end - def record3 - @recorder << 3 - end - end - - module IncludeModule - def self.included(base) - base.class_eval do - set_callback :save, :before, :record2 - end - end - def record2 - @recorder << 2 - end - end - - class ExtendCallbacks - - include ActiveSupport::Callbacks - - define_callbacks :save - set_callback :save, :before, :record1 - - include IncludeModule - - def save - run_callbacks :save - end - - attr_reader :recorder - - def initialize - @recorder = [] - end - - private - - def record1 - @recorder << 1 - end - end - - class AroundCallbacksTest < ActiveSupport::TestCase - def test_save_around - around = AroundPerson.new - around.save - assert_equal [ - "tweedle dee", - "yup", "yup", - "tweedle dum pre", - "w0tyes before", - "tweedle deedle pre", - "running", - "tweedle deedle post", - "w0tyes after", - "tweedle dum post", - "tweedle" - ], around.history - end - end - - class AroundCallbackResultTest < ActiveSupport::TestCase - def test_save_around - around = AroundPersonResult.new - around.save - assert_equal :running, around.result - end - end - - class SkipCallbacksTest < ActiveSupport::TestCase - def test_skip_person - person = PersonSkipper.new - assert_equal [], person.history - person.save - assert_equal [ - [:before_save, :string], - [:before_save, :proc], - [:before_save, :object], - [:before_save, :block], - [:after_save, :block], - [:after_save, :class], - [:after_save, :object], - [:after_save, :proc], - [:after_save, :string], - [:after_save, :symbol] - ], person.history - end - - def test_skip_person_programmatically - PersonForProgrammaticSkipping._save_callbacks.each do |save_callback| - if "before" == save_callback.kind.to_s - PersonForProgrammaticSkipping.skip_callback("save", save_callback.kind, save_callback.filter) - end - end - person = PersonForProgrammaticSkipping.new - assert_equal [], person.history - person.save - assert_equal [ - [:after_save, :block], - [:after_save, :class], - [:after_save, :object], - [:after_save, :proc], - [:after_save, :string], - [:after_save, :symbol] - ], person.history - end - end - - class CallbacksTest < ActiveSupport::TestCase - - def test_save_person - person = Person.new - assert_equal [], person.history - person.save - assert_equal [ - [:before_save, :symbol], - [:before_save, :string], - [:before_save, :proc], - [:before_save, :object], - [:before_save, :class], - [:before_save, :block], - [:after_save, :block], - [:after_save, :class], - [:after_save, :object], - [:after_save, :proc], - [:after_save, :string], - [:after_save, :symbol] - ], person.history - end - end - - class ConditionalCallbackTest < ActiveSupport::TestCase - def test_save_conditional_person - person = ConditionalPerson.new - person.save - assert_equal [ - [:before_save, :proc], - [:before_save, :proc], - [:before_save, :symbol], - [:before_save, :symbol], - [:before_save, :string], - [:before_save, :string], - [:before_save, :combined_symbol], - ], person.history - end - end - - - - class ResetCallbackTest < ActiveSupport::TestCase - def test_save_conditional_person - person = CleanPerson.new - person.save - assert_equal [], person.history - end - end - - class CallbackTerminator - include ActiveSupport::Callbacks - - define_callbacks :save, :terminator => ->(_,result) { result == :halt } - - set_callback :save, :before, :first - set_callback :save, :before, :second - set_callback :save, :around, :around_it - set_callback :save, :before, :third - set_callback :save, :after, :first - set_callback :save, :around, :around_it - set_callback :save, :after, :second - set_callback :save, :around, :around_it - set_callback :save, :after, :third - - - attr_reader :history, :saved, :halted - def initialize - @history = [] - end - - def around_it - @history << "around1" - yield - @history << "around2" - end - - def first - @history << "first" - end - - def second - @history << "second" - :halt - end - - def third - @history << "third" - end - - def save - run_callbacks :save do - @saved = true - end - end - - def halted_callback_hook(filter) - @halted = filter - end - end - - class CallbackObject - def before(caller) - caller.record << "before" - end - - def before_save(caller) - caller.record << "before save" - end - - def around(caller) - caller.record << "around before" - yield - caller.record << "around after" - end - end - - class UsingObjectBefore - include ActiveSupport::Callbacks - - define_callbacks :save - set_callback :save, :before, CallbackObject.new - - attr_accessor :record - def initialize - @record = [] - end - - def save - run_callbacks :save do - @record << "yielded" - end - end - end - - class UsingObjectAround - include ActiveSupport::Callbacks - - define_callbacks :save - set_callback :save, :around, CallbackObject.new - - attr_accessor :record - def initialize - @record = [] - end - - def save - run_callbacks :save do - @record << "yielded" - end - end - end - - class CustomScopeObject - include ActiveSupport::Callbacks - - define_callbacks :save, :scope => [:kind, :name] - set_callback :save, :before, CallbackObject.new - - attr_accessor :record - def initialize - @record = [] - end - - def save - run_callbacks :save do - @record << "yielded" - "CallbackResult" - end - end - end - - class OneTwoThreeSave - include ActiveSupport::Callbacks - - define_callbacks :save - - attr_accessor :record - - def initialize - @record = [] - end - - def save - run_callbacks :save do - @record << "yielded" - end - end - - def first - @record << "one" - end - - def second - @record << "two" - end - - def third - @record << "three" - end - end - - class DuplicatingCallbacks < OneTwoThreeSave - set_callback :save, :before, :first, :second - set_callback :save, :before, :first, :third - end - - class DuplicatingCallbacksInSameCall < OneTwoThreeSave - set_callback :save, :before, :first, :second, :first, :third - end - - class UsingObjectTest < ActiveSupport::TestCase - def test_before_object - u = UsingObjectBefore.new - u.save - assert_equal ["before", "yielded"], u.record - end - - def test_around_object - u = UsingObjectAround.new - u.save - assert_equal ["around before", "yielded", "around after"], u.record - end - - def test_customized_object - u = CustomScopeObject.new - u.save - assert_equal ["before save", "yielded"], u.record - end - - def test_block_result_is_returned - u = CustomScopeObject.new - assert_equal "CallbackResult", u.save - end - end - - class CallbackTerminatorTest < ActiveSupport::TestCase - def test_termination - terminator = CallbackTerminator.new - terminator.save - assert_equal ["first", "second", "third", "second", "first"], terminator.history - end - - def test_termination_invokes_hook - terminator = CallbackTerminator.new - terminator.save - assert_equal :second, terminator.halted - end - - def test_block_never_called_if_terminated - obj = CallbackTerminator.new - obj.save - assert !obj.saved - end - end - - class HyphenatedKeyTest < ActiveSupport::TestCase - def test_save - obj = HyphenatedCallbacks.new - obj.save - assert_equal "ACTION", obj.stuff - end - end - - class WriterSkipper < Person - attr_accessor :age - skip_callback :save, :before, :before_save_method, :if => lambda {self.age > 21} - end - - class WriterCallbacksTest < ActiveSupport::TestCase - def test_skip_writer - writer = WriterSkipper.new - writer.age = 18 - assert_equal [], writer.history - writer.save - assert_equal [ - [:before_save, :symbol], - [:before_save, :string], - [:before_save, :proc], - [:before_save, :object], - [:before_save, :class], - [:before_save, :block], - [:after_save, :block], - [:after_save, :class], - [:after_save, :object], - [:after_save, :proc], - [:after_save, :string], - [:after_save, :symbol] - ], writer.history - end - end - - class ExtendCallbacksTest < ActiveSupport::TestCase - def test_save - model = ExtendCallbacks.new.extend ExtendModule - model.save - assert_equal [1, 2, 3], model.recorder - end - end - - class ExcludingDuplicatesCallbackTest < ActiveSupport::TestCase - def test_excludes_duplicates_in_separate_calls - model = DuplicatingCallbacks.new - model.save - assert_equal ["two", "one", "three", "yielded"], model.record - end - - def test_excludes_duplicates_in_one_call - model = DuplicatingCallbacksInSameCall.new - model.save - assert_equal ["two", "one", "three", "yielded"], model.record - end - end - - class CallbackProcTest < ActiveSupport::TestCase - def build_class(callback) - Class.new { - include ActiveSupport::Callbacks - define_callbacks :foo - set_callback :foo, :before, callback - def run; run_callbacks :foo; end - } - end - - def test_proc_arity_0 - calls = [] - klass = build_class(->() { calls << :foo }) - klass.new.run - assert_equal [:foo], calls - end - - def test_proc_arity_1 - calls = [] - klass = build_class(->(o) { calls << o }) - instance = klass.new - instance.run - assert_equal [instance], calls - end - - def test_proc_arity_2 - assert_raises(ArgumentError) do - klass = build_class(->(x,y) { }) - klass.new.run - end - end - - def test_proc_negative_called_with_empty_list - calls = [] - klass = build_class(->(*args) { calls << args }) - klass.new.run - assert_equal [[]], calls - end - end - - class ConditionalTests < ActiveSupport::TestCase - def build_class(callback) - Class.new { - include ActiveSupport::Callbacks - define_callbacks :foo - set_callback :foo, :before, :foo, :if => callback - def foo; end - def run; run_callbacks :foo; end - } - end - - # FIXME: do we really want to support classes as conditionals? There were - # no tests for it previous to this. - def test_class_conditional_with_scope - z = [] - callback = Class.new { - define_singleton_method(:foo) { |o| z << o } - } - klass = Class.new { - include ActiveSupport::Callbacks - define_callbacks :foo, :scope => [:name] - set_callback :foo, :before, :foo, :if => callback - def run; run_callbacks :foo; end - private - def foo; end - } - object = klass.new - object.run - assert_equal [object], z - end - - # FIXME: do we really want to support classes as conditionals? There were - # no tests for it previous to this. - def test_class - z = [] - klass = build_class Class.new { - define_singleton_method(:before) { |o| z << o } - } - object = klass.new - object.run - assert_equal [object], z - end - - def test_proc_negative_arity # passes an empty list if *args - z = [] - object = build_class(->(*args) { z << args }).new - object.run - assert_equal [], z.flatten - end - - def test_proc_arity0 - z = [] - object = build_class(->() { z << 0 }).new - object.run - assert_equal [0], z - end - - def test_proc_arity1 - z = [] - object = build_class(->(x) { z << x }).new - object.run - assert_equal [object], z - end - - def test_proc_arity2 - assert_raises(ArgumentError) do - object = build_class(->(a,b) { }).new - object.run - end - end - end - - class ResetCallbackTest < ActiveSupport::TestCase - def build_class(memo) - klass = Class.new { - include ActiveSupport::Callbacks - define_callbacks :foo - set_callback :foo, :before, :hello - def run; run_callbacks :foo; end - } - klass.class_eval { - define_method(:hello) { memo << :hi } - } - klass - end - - def test_reset_callbacks - events = [] - klass = build_class events - klass.new.run - assert_equal 1, events.length - - klass.reset_callbacks :foo - klass.new.run - assert_equal 1, events.length - end - - def test_reset_impacts_subclasses - events = [] - klass = build_class events - subclass = Class.new(klass) { set_callback :foo, :before, :world } - subclass.class_eval { define_method(:world) { events << :world } } - - subclass.new.run - assert_equal 2, events.length - - klass.reset_callbacks :foo - subclass.new.run - assert_equal 3, events.length - end - end - - class CallbackTypeTest < ActiveSupport::TestCase - def build_class(callback, n = 10) - Class.new { - include ActiveSupport::Callbacks - define_callbacks :foo - n.times { set_callback :foo, :before, callback } - def run; run_callbacks :foo; end - def self.skip(thing); skip_callback :foo, :before, thing; end - } - end - - def test_add_class - calls = [] - callback = Class.new { - define_singleton_method(:before) { |o| calls << o } - } - build_class(callback).new.run - assert_equal 10, calls.length - end - - def test_add_lambda - calls = [] - build_class(->(o) { calls << o }).new.run - assert_equal 10, calls.length - end - - def test_add_symbol - calls = [] - klass = build_class(:bar) - klass.class_eval { define_method(:bar) { calls << klass } } - klass.new.run - assert_equal 1, calls.length - end - - def test_add_eval - calls = [] - klass = build_class("bar") - klass.class_eval { define_method(:bar) { calls << klass } } - klass.new.run - assert_equal 1, calls.length - end - - def test_skip_class # removes one at a time - calls = [] - callback = Class.new { - define_singleton_method(:before) { |o| calls << o } - } - klass = build_class(callback) - 9.downto(0) { |i| - klass.skip callback - klass.new.run - assert_equal i, calls.length - calls.clear - } - end - - def test_skip_lambda # removes nothing - calls = [] - callback = ->(o) { calls << o } - klass = build_class(callback) - 10.times { klass.skip callback } - klass.new.run - assert_equal 10, calls.length - end - - def test_skip_symbol # removes all - calls = [] - klass = build_class(:bar) - klass.class_eval { define_method(:bar) { calls << klass } } - klass.skip :bar - klass.new.run - assert_equal 0, calls.length - end - - def test_skip_eval # removes nothing - calls = [] - klass = build_class("bar") - klass.class_eval { define_method(:bar) { calls << klass } } - klass.skip "bar" - klass.new.run - assert_equal 1, calls.length - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/class_cache_test.rb b/app/server/ruby/vendor/activesupport/test/class_cache_test.rb deleted file mode 100644 index b96f476ce6..0000000000 --- a/app/server/ruby/vendor/activesupport/test/class_cache_test.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'abstract_unit' -require 'active_support/dependencies' - -module ActiveSupport - module Dependencies - class ClassCacheTest < ActiveSupport::TestCase - def setup - @cache = ClassCache.new - end - - def test_empty? - assert @cache.empty? - @cache.store(ClassCacheTest) - assert !@cache.empty? - end - - def test_clear! - assert @cache.empty? - @cache.store(ClassCacheTest) - assert !@cache.empty? - @cache.clear! - assert @cache.empty? - end - - def test_set_key - @cache.store(ClassCacheTest) - assert @cache.key?(ClassCacheTest.name) - end - - def test_get_with_class - @cache.store(ClassCacheTest) - assert_equal ClassCacheTest, @cache.get(ClassCacheTest) - end - - def test_get_with_name - @cache.store(ClassCacheTest) - assert_equal ClassCacheTest, @cache.get(ClassCacheTest.name) - end - - def test_get_constantizes - assert @cache.empty? - assert_equal ClassCacheTest, @cache.get(ClassCacheTest.name) - end - - def test_get_constantizes_fails_on_invalid_names - assert @cache.empty? - assert_raise NameError do - @cache.get("OmgTotallyInvalidConstantName") - end - end - - def test_get_alias - assert @cache.empty? - assert_equal @cache[ClassCacheTest.name], @cache.get(ClassCacheTest.name) - end - - def test_safe_get_constantizes - assert @cache.empty? - assert_equal ClassCacheTest, @cache.safe_get(ClassCacheTest.name) - end - - def test_safe_get_constantizes_doesnt_fail_on_invalid_names - assert @cache.empty? - assert_equal nil, @cache.safe_get("OmgTotallyInvalidConstantName") - end - - def test_new_rejects_strings - @cache.store ClassCacheTest.name - assert !@cache.key?(ClassCacheTest.name) - end - - def test_store_returns_self - x = @cache.store ClassCacheTest - assert_equal @cache, x - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/clean_backtrace_test.rb b/app/server/ruby/vendor/activesupport/test/clean_backtrace_test.rb deleted file mode 100644 index dd67a45cf6..0000000000 --- a/app/server/ruby/vendor/activesupport/test/clean_backtrace_test.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'abstract_unit' - -class BacktraceCleanerFilterTest < ActiveSupport::TestCase - def setup - @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_filter { |line| line.gsub("/my/prefix", '') } - end - - test "backtrace should filter all lines in a backtrace, removing prefixes" do - assert_equal \ - ["/my/class.rb", "/my/module.rb"], - @bc.clean(["/my/prefix/my/class.rb", "/my/prefix/my/module.rb"]) - end - - test "backtrace cleaner should allow removing filters" do - @bc.remove_filters! - assert_equal "/my/prefix/my/class.rb", @bc.clean(["/my/prefix/my/class.rb"]).first - end - - test "backtrace should contain unaltered lines if they dont match a filter" do - assert_equal "/my/other_prefix/my/class.rb", @bc.clean([ "/my/other_prefix/my/class.rb" ]).first - end - -end - -class BacktraceCleanerSilencerTest < ActiveSupport::TestCase - def setup - @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_silencer { |line| line =~ /mongrel/ } - end - - test "backtrace should not contain lines that match the silencer" do - assert_equal \ - [ "/other/class.rb" ], - @bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb" ]) - end -end - -class BacktraceCleanerMultipleSilencersTest < ActiveSupport::TestCase - def setup - @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_silencer { |line| line =~ /mongrel/ } - @bc.add_silencer { |line| line =~ /yolo/ } - end - - test "backtrace should not contain lines that match the silencers" do - assert_equal \ - [ "/other/class.rb" ], - @bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb", "/other/yolo.rb" ]) - end - - test "backtrace should only contain lines that match the silencers" do - assert_equal \ - [ "/mongrel/class.rb", "/mongrel/stuff.rb", "/other/yolo.rb" ], - @bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb", "/other/yolo.rb" ], - :noise) - end -end - -class BacktraceCleanerFilterAndSilencerTest < ActiveSupport::TestCase - def setup - @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_filter { |line| line.gsub("/mongrel", "") } - @bc.add_silencer { |line| line =~ /mongrel/ } - end - - test "backtrace should not silence lines that has first had their silence hook filtered out" do - assert_equal [ "/class.rb" ], @bc.clean([ "/mongrel/class.rb" ]) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/clean_logger_test.rb b/app/server/ruby/vendor/activesupport/test/clean_logger_test.rb deleted file mode 100644 index 02693a97dc..0000000000 --- a/app/server/ruby/vendor/activesupport/test/clean_logger_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'abstract_unit' -require 'stringio' -require 'active_support/logger' - -class CleanLoggerTest < ActiveSupport::TestCase - def setup - @out = StringIO.new - @logger = ActiveSupport::Logger.new(@out) - end - - def test_format_message - @logger.error 'error' - assert_equal "error\n", @out.string - end - - def test_datetime_format - @logger.formatter = Logger::Formatter.new - @logger.formatter.datetime_format = "%Y-%m-%d" - @logger.debug 'debug' - assert_equal "%Y-%m-%d", @logger.formatter.datetime_format - assert_match(/D, \[\d\d\d\d-\d\d-\d\d#\d+\] DEBUG -- : debug/, @out.string) - end - - def test_nonstring_formatting - an_object = [1, 2, 3, 4, 5] - @logger.debug an_object - assert_equal("#{an_object.inspect}\n", @out.string) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/concern_test.rb b/app/server/ruby/vendor/activesupport/test/concern_test.rb deleted file mode 100644 index 60bd8a06aa..0000000000 --- a/app/server/ruby/vendor/activesupport/test/concern_test.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'abstract_unit' -require 'active_support/concern' - -class ConcernTest < ActiveSupport::TestCase - module Baz - extend ActiveSupport::Concern - - class_methods do - def baz - "baz" - end - - def included_ran=(value) - @@included_ran = value - end - - def included_ran - @@included_ran - end - end - - included do - self.included_ran = true - end - - def baz - "baz" - end - end - - module Bar - extend ActiveSupport::Concern - - include Baz - - module ClassMethods - def baz - "bar's baz + " + super - end - end - - def bar - "bar" - end - - def baz - "bar+" + super - end - end - - module Foo - extend ActiveSupport::Concern - - include Bar, Baz - end - - def setup - @klass = Class.new - end - - def test_module_is_included_normally - @klass.send(:include, Baz) - assert_equal "baz", @klass.new.baz - assert @klass.included_modules.include?(ConcernTest::Baz) - end - - def test_class_methods_are_extended - @klass.send(:include, Baz) - assert_equal "baz", @klass.baz - assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0] - end - - def test_included_block_is_ran - @klass.send(:include, Baz) - assert_equal true, @klass.included_ran - end - - def test_modules_dependencies_are_met - @klass.send(:include, Bar) - assert_equal "bar", @klass.new.bar - assert_equal "bar+baz", @klass.new.baz - assert_equal "bar's baz + baz", @klass.baz - assert @klass.included_modules.include?(ConcernTest::Bar) - end - - def test_dependencies_with_multiple_modules - @klass.send(:include, Foo) - assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2] - end - - def test_raise_on_multiple_included_calls - assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do - Module.new do - extend ActiveSupport::Concern - - included do - end - - included do - end - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/configurable_test.rb b/app/server/ruby/vendor/activesupport/test/configurable_test.rb deleted file mode 100644 index ef847fc557..0000000000 --- a/app/server/ruby/vendor/activesupport/test/configurable_test.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'abstract_unit' -require 'active_support/configurable' - -class ConfigurableActiveSupport < ActiveSupport::TestCase - class Parent - include ActiveSupport::Configurable - config_accessor :foo - config_accessor :bar, instance_reader: false, instance_writer: false - config_accessor :baz, instance_accessor: false - end - - class Child < Parent - end - - setup do - Parent.config.clear - Parent.config.foo = :bar - - Child.config.clear - end - - test "adds a configuration hash" do - assert_equal({ foo: :bar }, Parent.config) - end - - test "adds a configuration hash to a module as well" do - mixin = Module.new { include ActiveSupport::Configurable } - mixin.config.foo = :bar - assert_equal({ foo: :bar }, mixin.config) - end - - test "configuration hash is inheritable" do - assert_equal :bar, Child.config.foo - assert_equal :bar, Parent.config.foo - - Child.config.foo = :baz - assert_equal :baz, Child.config.foo - assert_equal :bar, Parent.config.foo - end - - test "configuration accessors are not available on instance" do - instance = Parent.new - - assert !instance.respond_to?(:bar) - assert !instance.respond_to?(:bar=) - - assert !instance.respond_to?(:baz) - assert !instance.respond_to?(:baz=) - end - - test "configuration accessors can take a default value" do - parent = Class.new do - include ActiveSupport::Configurable - config_accessor :hair_colors, :tshirt_colors do - [:black, :blue, :white] - end - end - - assert_equal [:black, :blue, :white], parent.hair_colors - assert_equal [:black, :blue, :white], parent.tshirt_colors - end - - test "configuration hash is available on instance" do - instance = Parent.new - assert_equal :bar, instance.config.foo - assert_equal :bar, Parent.config.foo - - instance.config.foo = :baz - assert_equal :baz, instance.config.foo - assert_equal :bar, Parent.config.foo - end - - test "configuration is crystalizeable" do - parent = Class.new { include ActiveSupport::Configurable } - child = Class.new(parent) - - parent.config.bar = :foo - assert_method_not_defined parent.config, :bar - assert_method_not_defined child.config, :bar - assert_method_not_defined child.new.config, :bar - - parent.config.compile_methods! - assert_equal :foo, parent.config.bar - assert_equal :foo, child.new.config.bar - - assert_method_defined parent.config, :bar - assert_method_defined child.config, :bar - assert_method_defined child.new.config, :bar - end - - test "should raise name error if attribute name is invalid" do - assert_raises NameError do - Class.new do - include ActiveSupport::Configurable - config_accessor "invalid attribute name" - end - end - - assert_raises NameError do - Class.new do - include ActiveSupport::Configurable - config_accessor "invalid\nattribute" - end - end - - assert_raises NameError do - Class.new do - include ActiveSupport::Configurable - config_accessor "invalid\n" - end - end - end - - def assert_method_defined(object, method) - methods = object.public_methods.map(&:to_s) - assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}" - end - - def assert_method_not_defined(object, method) - methods = object.public_methods.map(&:to_s) - assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}" - end -end diff --git a/app/server/ruby/vendor/activesupport/test/constantize_test_cases.rb b/app/server/ruby/vendor/activesupport/test/constantize_test_cases.rb deleted file mode 100644 index bbeb710a0c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/constantize_test_cases.rb +++ /dev/null @@ -1,75 +0,0 @@ -module Ace - module Base - class Case - class Dice - end - end - class Fase < Case - end - end - class Gas - include Base - end -end - -class Object - module AddtlGlobalConstants - class Case - class Dice - end - end - end - include AddtlGlobalConstants -end - -module ConstantizeTestCases - def run_constantize_tests_on - assert_equal Ace::Base::Case, yield("Ace::Base::Case") - assert_equal Ace::Base::Case, yield("::Ace::Base::Case") - assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") - assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") - assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") - assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") - assert_equal Case::Dice, yield("Case::Dice") - assert_equal Case::Dice, yield("Object::Case::Dice") - assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") - assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") - assert_raises(NameError) { yield("UnknownClass") } - assert_raises(NameError) { yield("UnknownClass::Ace") } - assert_raises(NameError) { yield("UnknownClass::Ace::Base") } - assert_raises(NameError) { yield("An invalid string") } - assert_raises(NameError) { yield("InvalidClass\n") } - assert_raises(NameError) { yield("Ace::ConstantizeTestCases") } - assert_raises(NameError) { yield("Ace::Base::ConstantizeTestCases") } - assert_raises(NameError) { yield("Ace::Gas::Base") } - assert_raises(NameError) { yield("Ace::Gas::ConstantizeTestCases") } - assert_raises(NameError) { yield("") } - assert_raises(NameError) { yield("::") } - end - - def run_safe_constantize_tests_on - assert_equal Ace::Base::Case, yield("Ace::Base::Case") - assert_equal Ace::Base::Case, yield("::Ace::Base::Case") - assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") - assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") - assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") - assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") - assert_equal Case::Dice, yield("Case::Dice") - assert_equal Case::Dice, yield("Object::Case::Dice") - assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") - assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") - assert_nil yield("") - assert_nil yield("::") - assert_nil yield("UnknownClass") - assert_nil yield("UnknownClass::Ace") - assert_nil yield("UnknownClass::Ace::Base") - assert_nil yield("An invalid string") - assert_nil yield("InvalidClass\n") - assert_nil yield("blargle") - assert_nil yield("Ace::ConstantizeTestCases") - assert_nil yield("Ace::Base::ConstantizeTestCases") - assert_nil yield("Ace::Gas::Base") - assert_nil yield("Ace::Gas::ConstantizeTestCases") - assert_nil yield("#::Nested_1") - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/array_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/array_ext_test.rb deleted file mode 100644 index 57722fd52a..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/array_ext_test.rb +++ /dev/null @@ -1,451 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' -require 'active_support/core_ext/big_decimal' -require 'active_support/core_ext/object/conversions' - -require 'active_support/core_ext' # FIXME: pulling in all to_xml extensions -require 'active_support/hash_with_indifferent_access' - -class ArrayExtAccessTests < ActiveSupport::TestCase - def test_from - assert_equal %w( a b c d ), %w( a b c d ).from(0) - assert_equal %w( c d ), %w( a b c d ).from(2) - assert_equal %w(), %w( a b c d ).from(10) - end - - def test_to - assert_equal %w( a ), %w( a b c d ).to(0) - assert_equal %w( a b c ), %w( a b c d ).to(2) - assert_equal %w( a b c d ), %w( a b c d ).to(10) - end - - def test_second_through_tenth - array = (1..42).to_a - - assert_equal array[1], array.second - assert_equal array[2], array.third - assert_equal array[3], array.fourth - assert_equal array[4], array.fifth - assert_equal array[41], array.forty_two - end -end - -class ArrayExtToParamTests < ActiveSupport::TestCase - class ToParam < String - def to_param - "#{self}1" - end - end - - def test_string_array - assert_equal '', %w().to_param - assert_equal 'hello/world', %w(hello world).to_param - assert_equal 'hello/10', %w(hello 10).to_param - end - - def test_number_array - assert_equal '10/20', [10, 20].to_param - end - - def test_to_param_array - assert_equal 'custom1/param1', [ToParam.new('custom'), ToParam.new('param')].to_param - end -end - -class ArrayExtToSentenceTests < ActiveSupport::TestCase - def test_plain_array_to_sentence - assert_equal "", [].to_sentence - assert_equal "one", ['one'].to_sentence - assert_equal "one and two", ['one', 'two'].to_sentence - assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence - end - - def test_to_sentence_with_words_connector - assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(:words_connector => ' ') - assert_equal "one & two, and three", ['one', 'two', 'three'].to_sentence(:words_connector => ' & ') - assert_equal "onetwo, and three", ['one', 'two', 'three'].to_sentence(:words_connector => nil) - end - - def test_to_sentence_with_last_word_connector - assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ', and also ') - assert_equal "one, twothree", ['one', 'two', 'three'].to_sentence(:last_word_connector => nil) - assert_equal "one, two three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ' ') - assert_equal "one, two and three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ' and ') - end - - def test_two_elements - assert_equal "one and two", ['one', 'two'].to_sentence - assert_equal "one two", ['one', 'two'].to_sentence(:two_words_connector => ' ') - end - - def test_one_element - assert_equal "one", ['one'].to_sentence - end - - def test_one_element_not_same_object - elements = ["one"] - assert_not_equal elements[0].object_id, elements.to_sentence.object_id - end - - def test_one_non_string_element - assert_equal '1', [1].to_sentence - end - - def test_does_not_modify_given_hash - options = { words_connector: ' ' } - assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options) - assert_equal({ words_connector: ' ' }, options) - end - - def test_with_blank_elements - assert_equal ", one, , two, and three", [nil, 'one', '', 'two', 'three'].to_sentence - end -end - -class ArrayExtToSTests < ActiveSupport::TestCase - def test_to_s_db - collection = [ - Class.new { def id() 1 end }.new, - Class.new { def id() 2 end }.new, - Class.new { def id() 3 end }.new - ] - - assert_equal "null", [].to_s(:db) - assert_equal "1,2,3", collection.to_s(:db) - end -end - -class ArrayExtGroupingTests < ActiveSupport::TestCase - def setup - Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn) - end - - def teardown - Fixnum.send :public, :/ - end - - def test_in_groups_of_with_perfect_fit - groups = [] - ('a'..'i').to_a.in_groups_of(3) do |group| - groups << group - end - - assert_equal [%w(a b c), %w(d e f), %w(g h i)], groups - assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3) - end - - def test_in_groups_of_with_padding - groups = [] - ('a'..'g').to_a.in_groups_of(3) do |group| - groups << group - end - - assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups - end - - def test_in_groups_of_pads_with_specified_values - groups = [] - - ('a'..'g').to_a.in_groups_of(3, 'foo') do |group| - groups << group - end - - assert_equal [%w(a b c), %w(d e f), ['g', 'foo', 'foo']], groups - end - - def test_in_groups_of_without_padding - groups = [] - - ('a'..'g').to_a.in_groups_of(3, false) do |group| - groups << group - end - - assert_equal [%w(a b c), %w(d e f), ['g']], groups - end - - def test_in_groups_returned_array_size - array = (1..7).to_a - - 1.upto(array.size + 1) do |number| - assert_equal number, array.in_groups(number).size - end - end - - def test_in_groups_with_empty_array - assert_equal [[], [], []], [].in_groups(3) - end - - def test_in_groups_with_block - array = (1..9).to_a - groups = [] - - array.in_groups(3) do |group| - groups << group - end - - assert_equal array.in_groups(3), groups - end - - def test_in_groups_with_perfect_fit - assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - (1..9).to_a.in_groups(3) - end - - def test_in_groups_with_padding - array = (1..7).to_a - - assert_equal [[1, 2, 3], [4, 5, nil], [6, 7, nil]], - array.in_groups(3) - assert_equal [[1, 2, 3], [4, 5, 'foo'], [6, 7, 'foo']], - array.in_groups(3, 'foo') - end - - def test_in_groups_without_padding - assert_equal [[1, 2, 3], [4, 5], [6, 7]], - (1..7).to_a.in_groups(3, false) - end -end - -class ArraySplitTests < ActiveSupport::TestCase - def test_split_with_empty_array - assert_equal [[]], [].split(0) - end - - def test_split_with_argument - a = [1, 2, 3, 4, 5] - assert_equal [[1, 2], [4, 5]], a.split(3) - assert_equal [[1, 2, 3, 4, 5]], a.split(0) - assert_equal [1, 2, 3, 4, 5], a - end - - def test_split_with_block - a = (1..10).to_a - assert_equal [[1, 2], [4, 5], [7, 8], [10]], a.split { |i| i % 3 == 0 } - assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 ,10], a - end - - def test_split_with_edge_values - a = [1, 2, 3, 4, 5] - assert_equal [[], [2, 3, 4, 5]], a.split(1) - assert_equal [[1, 2, 3, 4], []], a.split(5) - assert_equal [[], [2, 3, 4], []], a.split { |i| i == 1 || i == 5 } - assert_equal [1, 2, 3, 4, 5], a - end -end - -class ArrayToXmlTests < ActiveSupport::TestCase - def test_to_xml - xml = [ - { :name => "David", :age => 26, :age_in_millis => 820497600000 }, - { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') } - ].to_xml(:skip_instruct => true, :indent => 0) - - assert_equal '', xml.first(30) - assert xml.include?(%(26)), xml - assert xml.include?(%(820497600000)), xml - assert xml.include?(%(David)), xml - assert xml.include?(%(31)), xml - assert xml.include?(%(1.0)), xml - assert xml.include?(%(Jason)), xml - end - - def test_to_xml_with_dedicated_name - xml = [ - { :name => "David", :age => 26, :age_in_millis => 820497600000 }, { :name => "Jason", :age => 31 } - ].to_xml(:skip_instruct => true, :indent => 0, :root => "people") - - assert_equal '', xml.first(29) - end - - def test_to_xml_with_options - xml = [ - { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } - ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0) - - assert_equal "", xml.first(17) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - assert xml.include?(%(Evergreen)) - assert xml.include?(%(Jason)) - end - - def test_to_xml_with_dasherize_false - xml = [ - { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } - ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => false) - - assert_equal "", xml.first(17) - assert xml.include?(%(Paulina)) - assert xml.include?(%(Evergreen)) - end - - def test_to_xml_with_dasherize_true - xml = [ - { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } - ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => true) - - assert_equal "", xml.first(17) - assert xml.include?(%(Paulina)) - assert xml.include?(%(Evergreen)) - end - - def test_to_with_instruct - xml = [ - { :name => "David", :age => 26, :age_in_millis => 820497600000 }, - { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') } - ].to_xml(:skip_instruct => false, :indent => 0) - - assert_match(/^<\?xml [^>]*/, xml) - assert_equal 0, xml.rindex(/<\?xml /) - end - - def test_to_xml_with_block - xml = [ - { :name => "David", :age => 26, :age_in_millis => 820497600000 }, - { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') } - ].to_xml(:skip_instruct => true, :indent => 0) do |builder| - builder.count 2 - end - - assert xml.include?(%(2)), xml - end - - def test_to_xml_with_empty - xml = [].to_xml - assert_match(/type="array"\/>/, xml) - end - - def test_to_xml_dups_options - options = {:skip_instruct => true} - [].to_xml(options) - # :builder, etc, shouldn't be added to options - assert_equal({:skip_instruct => true}, options) - end -end - -class ArrayExtractOptionsTests < ActiveSupport::TestCase - class HashSubclass < Hash - end - - class ExtractableHashSubclass < Hash - def extractable_options? - true - end - end - - def test_extract_options - assert_equal({}, [].extract_options!) - assert_equal({}, [1].extract_options!) - assert_equal({:a=>:b}, [{:a=>:b}].extract_options!) - assert_equal({:a=>:b}, [1, {:a=>:b}].extract_options!) - end - - def test_extract_options_doesnt_extract_hash_subclasses - hash = HashSubclass.new - hash[:foo] = 1 - array = [hash] - options = array.extract_options! - assert_equal({}, options) - assert_equal [hash], array - end - - def test_extract_options_extracts_extractable_subclass - hash = ExtractableHashSubclass.new - hash[:foo] = 1 - array = [hash] - options = array.extract_options! - assert_equal({:foo => 1}, options) - assert_equal [], array - end - - def test_extract_options_extracts_hwia - hash = [{:foo => 1}.with_indifferent_access] - options = hash.extract_options! - assert_equal 1, options[:foo] - end -end - -class ArrayWrapperTests < ActiveSupport::TestCase - class FakeCollection - def to_ary - ["foo", "bar"] - end - end - - class Proxy - def initialize(target) @target = target end - def method_missing(*a) @target.send(*a) end - end - - class DoubtfulToAry - def to_ary - :not_an_array - end - end - - class NilToAry - def to_ary - nil - end - end - - def test_array - ary = %w(foo bar) - assert_same ary, Array.wrap(ary) - end - - def test_nil - assert_equal [], Array.wrap(nil) - end - - def test_object - o = Object.new - assert_equal [o], Array.wrap(o) - end - - def test_string - assert_equal ["foo"], Array.wrap("foo") - end - - def test_string_with_newline - assert_equal ["foo\nbar"], Array.wrap("foo\nbar") - end - - def test_object_with_to_ary - assert_equal ["foo", "bar"], Array.wrap(FakeCollection.new) - end - - def test_proxy_object - p = Proxy.new(Object.new) - assert_equal [p], Array.wrap(p) - end - - def test_proxy_to_object_with_to_ary - p = Proxy.new(FakeCollection.new) - assert_equal [p], Array.wrap(p) - end - - def test_struct - o = Struct.new(:foo).new(123) - assert_equal [o], Array.wrap(o) - end - - def test_wrap_returns_wrapped_if_to_ary_returns_nil - o = NilToAry.new - assert_equal [o], Array.wrap(o) - end - - def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array - assert_equal DoubtfulToAry.new.to_ary, Array.wrap(DoubtfulToAry.new) - end -end - -class ArrayPrependAppendTest < ActiveSupport::TestCase - def test_append - assert_equal [1, 2], [1].append(2) - end - - def test_prepend - assert_equal [2, 1], [1].prepend(2) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb deleted file mode 100644 index e634679d20..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'abstract_unit' - -class BigDecimalYamlConversionsTest < ActiveSupport::TestCase - def test_to_yaml - assert_deprecated { require 'active_support/core_ext/big_decimal/yaml_conversions' } - assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) - assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) - assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) - assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/bigdecimal_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/bigdecimal_test.rb deleted file mode 100644 index 423a3f2e9d..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/bigdecimal_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/big_decimal' - -class BigDecimalTest < ActiveSupport::TestCase - def test_to_s - bd = BigDecimal.new '0.01' - assert_equal '0.01', bd.to_s - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/blank_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/blank_test.rb deleted file mode 100644 index 246bc7fa61..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/blank_test.rb +++ /dev/null @@ -1,36 +0,0 @@ -# encoding: utf-8 - -require 'abstract_unit' -require 'active_support/core_ext/object/blank' - -class BlankTest < ActiveSupport::TestCase - class EmptyTrue - def empty? - 0 - end - end - - class EmptyFalse - def empty? - nil - end - end - - BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', "\u00a0", [], {} ] - NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ] - - def test_blank - BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" } - NOT.each { |v| assert_equal false, v.blank?, "#{v.inspect} should not be blank" } - end - - def test_present - BLANK.each { |v| assert_equal false, v.present?, "#{v.inspect} should not be present" } - NOT.each { |v| assert_equal true, v.present?, "#{v.inspect} should be present" } - end - - def test_presence - BLANK.each { |v| assert_equal nil, v.presence, "#{v.inspect}.presence should return nil" } - NOT.each { |v| assert_equal v, v.presence, "#{v.inspect}.presence should return self" } - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/class/attribute_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/class/attribute_test.rb deleted file mode 100644 index e7a1334db3..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/class/attribute_test.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/class/attribute' - -class ClassAttributeTest < ActiveSupport::TestCase - def setup - @klass = Class.new { class_attribute :setting } - @sub = Class.new(@klass) - end - - test 'defaults to nil' do - assert_nil @klass.setting - assert_nil @sub.setting - end - - test 'inheritable' do - @klass.setting = 1 - assert_equal 1, @sub.setting - end - - test 'overridable' do - @sub.setting = 1 - assert_nil @klass.setting - - @klass.setting = 2 - assert_equal 1, @sub.setting - - assert_equal 1, Class.new(@sub).setting - end - - test 'predicate method' do - assert_equal false, @klass.setting? - @klass.setting = 1 - assert_equal true, @klass.setting? - end - - test 'instance reader delegates to class' do - assert_nil @klass.new.setting - - @klass.setting = 1 - assert_equal 1, @klass.new.setting - end - - test 'instance override' do - object = @klass.new - object.setting = 1 - assert_nil @klass.setting - @klass.setting = 2 - assert_equal 1, object.setting - end - - test 'instance predicate' do - object = @klass.new - assert_equal false, object.setting? - object.setting = 1 - assert_equal true, object.setting? - end - - test 'disabling instance writer' do - object = Class.new { class_attribute :setting, :instance_writer => false }.new - assert_raise(NoMethodError) { object.setting = 'boom' } - end - - test 'disabling instance reader' do - object = Class.new { class_attribute :setting, :instance_reader => false }.new - assert_raise(NoMethodError) { object.setting } - assert_raise(NoMethodError) { object.setting? } - end - - test 'disabling both instance writer and reader' do - object = Class.new { class_attribute :setting, :instance_accessor => false }.new - assert_raise(NoMethodError) { object.setting } - assert_raise(NoMethodError) { object.setting? } - assert_raise(NoMethodError) { object.setting = 'boom' } - end - - test 'disabling instance predicate' do - object = Class.new { class_attribute :setting, instance_predicate: false }.new - assert_raise(NoMethodError) { object.setting? } - end - - test 'works well with singleton classes' do - object = @klass.new - object.singleton_class.setting = 'foo' - assert_equal 'foo', object.setting - end - - test 'setter returns set value' do - val = @klass.send(:setting=, 1) - assert_equal 1, val - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/class/delegating_attributes_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/class/delegating_attributes_test.rb deleted file mode 100644 index 447b1d10ad..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ /dev/null @@ -1,122 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/class/delegating_attributes' - -module DelegatingFixtures - class Parent - end - - class Child < Parent - ActiveSupport::Deprecation.silence do - superclass_delegating_accessor :some_attribute - end - end - - class Mokopuna < Child - end - - class PercysMom - ActiveSupport::Deprecation.silence do - superclass_delegating_accessor :superpower - end - end - - class Percy < PercysMom - end -end - -class DelegatingAttributesTest < ActiveSupport::TestCase - include DelegatingFixtures - attr_reader :single_class - - def setup - @single_class = Class.new(Object) - end - - def test_simple_accessor_declaration - assert_deprecated do - single_class.superclass_delegating_accessor :both - end - - # Class should have accessor and mutator - # the instance should have an accessor only - assert_respond_to single_class, :both - assert_respond_to single_class, :both= - assert single_class.public_instance_methods.map(&:to_s).include?("both") - assert !single_class.public_instance_methods.map(&:to_s).include?("both=") - end - - def test_simple_accessor_declaration_with_instance_reader_false - _instance_methods = single_class.public_instance_methods - - assert_deprecated do - single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false - end - - assert_respond_to single_class, :no_instance_reader - assert_respond_to single_class, :no_instance_reader= - assert !_instance_methods.include?(:no_instance_reader) - assert !_instance_methods.include?(:no_instance_reader?) - assert !_instance_methods.include?(:_no_instance_reader) - end - - def test_working_with_simple_attributes - assert_deprecated do - single_class.superclass_delegating_accessor :both - end - - single_class.both = "HMMM" - - assert_equal "HMMM", single_class.both - assert_equal true, single_class.both? - - assert_equal "HMMM", single_class.new.both - assert_equal true, single_class.new.both? - - single_class.both = false - assert_equal false, single_class.both? - end - - def test_child_class_delegates_to_parent_but_can_be_overridden - parent = Class.new - - assert_deprecated do - parent.superclass_delegating_accessor :both - end - - child = Class.new(parent) - parent.both = "1" - assert_equal "1", child.both - - child.both = "2" - assert_equal "1", parent.both - assert_equal "2", child.both - - parent.both = "3" - assert_equal "3", parent.both - assert_equal "2", child.both - end - - def test_delegation_stops_at_the_right_level - assert_nil Percy.superpower - assert_nil PercysMom.superpower - - PercysMom.superpower = :heatvision - assert_equal :heatvision, Percy.superpower - end - - def test_delegation_stops_for_nil - Mokopuna.some_attribute = nil - Child.some_attribute="1" - - assert_equal "1", Child.some_attribute - assert_nil Mokopuna.some_attribute - ensure - Child.some_attribute=nil - end - - def test_deprecation_warning - assert_deprecated(/superclass_delegating_accessor is deprecated/) do - single_class.superclass_delegating_accessor :test_attribute - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/class_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/class_test.rb deleted file mode 100644 index 9c6c579ef7..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/class_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/class' -require 'set' - -class ClassTest < ActiveSupport::TestCase - class Parent; end - class Foo < Parent; end - class Bar < Foo; end - class Baz < Bar; end - - class A < Parent; end - class B < A; end - class C < B; end - - def test_descendants - assert_equal [Foo, Bar, Baz, A, B, C].to_set, Parent.descendants.to_set - assert_equal [Bar, Baz].to_set, Foo.descendants.to_set - assert_equal [Baz], Bar.descendants - assert_equal [], Baz.descendants - end - - def test_subclasses - assert_equal [Foo, A].to_set, Parent.subclasses.to_set - assert_equal [Bar], Foo.subclasses - assert_equal [Baz], Bar.subclasses - assert_equal [], Baz.subclasses - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/date_and_time_behavior.rb b/app/server/ruby/vendor/activesupport/test/core_ext/date_and_time_behavior.rb deleted file mode 100644 index b4ef5a0597..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/date_and_time_behavior.rb +++ /dev/null @@ -1,241 +0,0 @@ -require 'abstract_unit' - -module DateAndTimeBehavior - def test_yesterday - assert_equal date_time_init(2005,2,21,10,10,10), date_time_init(2005,2,22,10,10,10).yesterday - assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).yesterday.yesterday - end - - def test_tomorrow - assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).tomorrow - assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).tomorrow.tomorrow - end - - def test_days_ago - assert_equal date_time_init(2005,6,4,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(1) - assert_equal date_time_init(2005,5,31,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(5) - end - - def test_days_since - assert_equal date_time_init(2005,6,6,10,10,10), date_time_init(2005,6,5,10,10,10).days_since(1) - assert_equal date_time_init(2005,1,1,10,10,10), date_time_init(2004,12,31,10,10,10).days_since(1) - end - - def test_weeks_ago - assert_equal date_time_init(2005,5,29,10,10,10), date_time_init(2005,6,5,10,10,10).weeks_ago(1) - assert_equal date_time_init(2005,5,1,10,10,10), date_time_init(2005,6,5,10,10,10).weeks_ago(5) - assert_equal date_time_init(2005,4,24,10,10,10), date_time_init(2005,6,5,10,10,10).weeks_ago(6) - assert_equal date_time_init(2005,2,27,10,10,10), date_time_init(2005,6,5,10,10,10).weeks_ago(14) - assert_equal date_time_init(2004,12,25,10,10,10), date_time_init(2005,1,1,10,10,10).weeks_ago(1) - end - - def test_weeks_since - assert_equal date_time_init(2005,7,14,10,10,10), date_time_init(2005,7,7,10,10,10).weeks_since(1) - assert_equal date_time_init(2005,7,14,10,10,10), date_time_init(2005,7,7,10,10,10).weeks_since(1) - assert_equal date_time_init(2005,7,4,10,10,10), date_time_init(2005,6,27,10,10,10).weeks_since(1) - assert_equal date_time_init(2005,1,4,10,10,10), date_time_init(2004,12,28,10,10,10).weeks_since(1) - end - - def test_months_ago - assert_equal date_time_init(2005,5,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(1) - assert_equal date_time_init(2004,11,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(7) - assert_equal date_time_init(2004,12,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(6) - assert_equal date_time_init(2004,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(12) - assert_equal date_time_init(2003,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(24) - end - - def test_months_since - assert_equal date_time_init(2005,7,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(1) - assert_equal date_time_init(2006,1,5,10,10,10), date_time_init(2005,12,5,10,10,10).months_since(1) - assert_equal date_time_init(2005,12,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(6) - assert_equal date_time_init(2006,6,5,10,10,10), date_time_init(2005,12,5,10,10,10).months_since(6) - assert_equal date_time_init(2006,1,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(7) - assert_equal date_time_init(2006,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(12) - assert_equal date_time_init(2007,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(24) - assert_equal date_time_init(2005,4,30,10,10,10), date_time_init(2005,3,31,10,10,10).months_since(1) - assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,1,29,10,10,10).months_since(1) - assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,1,30,10,10,10).months_since(1) - assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,1,31,10,10,10).months_since(1) - end - - def test_years_ago - assert_equal date_time_init(2004,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_ago(1) - assert_equal date_time_init(1998,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_ago(7) - assert_equal date_time_init(2003,2,28,10,10,10), date_time_init(2004,2,29,10,10,10).years_ago(1) # 1 year ago from leap day - end - - def test_years_since - assert_equal date_time_init(2006,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_since(1) - assert_equal date_time_init(2012,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_since(7) - assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2004,2,29,10,10,10).years_since(1) # 1 year since leap day - assert_equal date_time_init(2182,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_since(177) - end - - def test_beginning_of_month - assert_equal date_time_init(2005,2,1,0,0,0), date_time_init(2005,2,22,10,10,10).beginning_of_month - end - - def test_beginning_of_quarter - assert_equal date_time_init(2005,1,1,0,0,0), date_time_init(2005,2,15,10,10,10).beginning_of_quarter - assert_equal date_time_init(2005,1,1,0,0,0), date_time_init(2005,1,1,0,0,0).beginning_of_quarter - assert_equal date_time_init(2005,10,1,0,0,0), date_time_init(2005,12,31,10,10,10).beginning_of_quarter - assert_equal date_time_init(2005,4,1,0,0,0), date_time_init(2005,6,30,23,59,59).beginning_of_quarter - end - - def test_end_of_quarter - assert_equal date_time_init(2007,3,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,2,15,10,10,10).end_of_quarter - assert_equal date_time_init(2007,3,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,3,31,0,0,0).end_of_quarter - assert_equal date_time_init(2007,12,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,12,21,10,10,10).end_of_quarter - assert_equal date_time_init(2007,6,30,23,59,59,Rational(999999999, 1000)), date_time_init(2007,4,1,0,0,0).end_of_quarter - assert_equal date_time_init(2008,6,30,23,59,59,Rational(999999999, 1000)), date_time_init(2008,5,31,0,0,0).end_of_quarter - end - - def test_beginning_of_year - assert_equal date_time_init(2005,1,1,0,0,0), date_time_init(2005,2,22,10,10,10).beginning_of_year - end - - def test_next_week - # M | T | W | T | F | S | S # M | T | W | T | F | S | S # - # | 22/2 | | | | | # 28/2 | | | | | | # monday in next week `next_week` - # | 22/2 | | | | | # | | | | 4/3 | | # friday in next week `next_week(:friday)` - # 23/10 | | | | | | # 30/10 | | | | | | # monday in next week `next_week` - # 23/10 | | | | | | # | | 1/11 | | | | # wednesday in next week `next_week(:wednesday)` - assert_equal date_time_init(2005,2,28,0,0,0), date_time_init(2005,2,22,15,15,10).next_week - assert_equal date_time_init(2005,3,4,0,0,0), date_time_init(2005,2,22,15,15,10).next_week(:friday) - assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week - assert_equal date_time_init(2006,11,1,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:wednesday) - end - - def test_next_week_with_default_beginning_of_week_set - with_bw_default(:tuesday) do - assert_equal Time.local(2012, 3, 28), Time.local(2012, 3, 21).next_week(:wednesday) - assert_equal Time.local(2012, 3, 31), Time.local(2012, 3, 21).next_week(:saturday) - assert_equal Time.local(2012, 3, 27), Time.local(2012, 3, 21).next_week(:tuesday) - assert_equal Time.local(2012, 4, 02), Time.local(2012, 3, 21).next_week(:monday) - end - end - - def test_next_month_on_31st - assert_equal date_time_init(2005,9,30,15,15,10), date_time_init(2005,8,31,15,15,10).next_month - end - - def test_next_quarter_on_31st - assert_equal date_time_init(2005,11,30,15,15,10), date_time_init(2005,8,31,15,15,10).next_quarter - end - - def test_next_year - assert_equal date_time_init(2006,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).next_year - end - - def test_prev_week - assert_equal date_time_init(2005,2,21,0,0,0), date_time_init(2005,3,1,15,15,10).prev_week - assert_equal date_time_init(2005,2,22,0,0,0), date_time_init(2005,3,1,15,15,10).prev_week(:tuesday) - assert_equal date_time_init(2005,2,25,0,0,0), date_time_init(2005,3,1,15,15,10).prev_week(:friday) - assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,11,6,0,0,0).prev_week - assert_equal date_time_init(2006,11,15,0,0,0), date_time_init(2006,11,23,0,0,0).prev_week(:wednesday) - end - - def test_prev_week_with_default_beginning_of_week - with_bw_default(:tuesday) do - assert_equal Time.local(2012, 3, 14), Time.local(2012, 3, 21).prev_week(:wednesday) - assert_equal Time.local(2012, 3, 17), Time.local(2012, 3, 21).prev_week(:saturday) - assert_equal Time.local(2012, 3, 13), Time.local(2012, 3, 21).prev_week(:tuesday) - assert_equal Time.local(2012, 3, 19), Time.local(2012, 3, 21).prev_week(:monday) - end - end - - def test_prev_month_on_31st - assert_equal date_time_init(2004,2,29,10,10,10), date_time_init(2004,3,31,10,10,10).prev_month - end - - def test_prev_quarter_on_31st - assert_equal date_time_init(2004,2,29,10,10,10), date_time_init(2004,5,31,10,10,10).prev_quarter - end - - def test_prev_year - assert_equal date_time_init(2004,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).prev_year - end - - def test_days_to_week_start - assert_equal 0, date_time_init(2011,11,01,0,0,0).days_to_week_start(:tuesday) - assert_equal 1, date_time_init(2011,11,02,0,0,0).days_to_week_start(:tuesday) - assert_equal 2, date_time_init(2011,11,03,0,0,0).days_to_week_start(:tuesday) - assert_equal 3, date_time_init(2011,11,04,0,0,0).days_to_week_start(:tuesday) - assert_equal 4, date_time_init(2011,11,05,0,0,0).days_to_week_start(:tuesday) - assert_equal 5, date_time_init(2011,11,06,0,0,0).days_to_week_start(:tuesday) - assert_equal 6, date_time_init(2011,11,07,0,0,0).days_to_week_start(:tuesday) - - assert_equal 3, date_time_init(2011,11,03,0,0,0).days_to_week_start(:monday) - assert_equal 3, date_time_init(2011,11,04,0,0,0).days_to_week_start(:tuesday) - assert_equal 3, date_time_init(2011,11,05,0,0,0).days_to_week_start(:wednesday) - assert_equal 3, date_time_init(2011,11,06,0,0,0).days_to_week_start(:thursday) - assert_equal 3, date_time_init(2011,11,07,0,0,0).days_to_week_start(:friday) - assert_equal 3, date_time_init(2011,11,8,0,0,0).days_to_week_start(:saturday) - assert_equal 3, date_time_init(2011,11,9,0,0,0).days_to_week_start(:sunday) - end - - def test_days_to_week_start_with_default_set - with_bw_default(:friday) do - assert_equal 6, Time.local(2012,03,8,0,0,0).days_to_week_start - assert_equal 5, Time.local(2012,03,7,0,0,0).days_to_week_start - assert_equal 4, Time.local(2012,03,6,0,0,0).days_to_week_start - assert_equal 3, Time.local(2012,03,5,0,0,0).days_to_week_start - assert_equal 2, Time.local(2012,03,4,0,0,0).days_to_week_start - assert_equal 1, Time.local(2012,03,3,0,0,0).days_to_week_start - assert_equal 0, Time.local(2012,03,2,0,0,0).days_to_week_start - end - end - - def test_beginning_of_week - assert_equal date_time_init(2005,1,31,0,0,0), date_time_init(2005,2,4,10,10,10).beginning_of_week - assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,11,28,0,0,0).beginning_of_week #monday - assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,11,29,0,0,0).beginning_of_week #tuesday - assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,11,30,0,0,0).beginning_of_week #wednesday - assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,12,01,0,0,0).beginning_of_week #thursday - assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,12,02,0,0,0).beginning_of_week #friday - assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,12,03,0,0,0).beginning_of_week #saturday - assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,12,04,0,0,0).beginning_of_week #sunday - end - - def test_end_of_week - assert_equal date_time_init(2008,1,6,23,59,59,Rational(999999999, 1000)), date_time_init(2007,12,31,10,10,10).end_of_week - assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,27,0,0,0).end_of_week #monday - assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,28,0,0,0).end_of_week #tuesday - assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,29,0,0,0).end_of_week #wednesday - assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,30,0,0,0).end_of_week #thursday - assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,31,0,0,0).end_of_week #friday - assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,9,01,0,0,0).end_of_week #saturday - assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,9,02,0,0,0).end_of_week #sunday - end - - def test_end_of_month - assert_equal date_time_init(2005,3,31,23,59,59,Rational(999999999, 1000)), date_time_init(2005,3,20,10,10,10).end_of_month - assert_equal date_time_init(2005,2,28,23,59,59,Rational(999999999, 1000)), date_time_init(2005,2,20,10,10,10).end_of_month - assert_equal date_time_init(2005,4,30,23,59,59,Rational(999999999, 1000)), date_time_init(2005,4,20,10,10,10).end_of_month - end - - def test_end_of_year - assert_equal date_time_init(2007,12,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,2,22,10,10,10).end_of_year - assert_equal date_time_init(2007,12,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,12,31,10,10,10).end_of_year - end - - def test_monday_with_default_beginning_of_week_set - with_bw_default(:saturday) do - assert_equal date_time_init(2012,9,17,0,0,0), date_time_init(2012,9,18,0,0,0).monday - end - end - - def test_sunday_with_default_beginning_of_week_set - with_bw_default(:wednesday) do - assert_equal date_time_init(2012,9,23,23,59,59, Rational(999999999, 1000)), date_time_init(2012,9,19,0,0,0).sunday - end - end - - def with_bw_default(bw = :monday) - old_bw = Date.beginning_of_week - Date.beginning_of_week = bw - yield - ensure - Date.beginning_of_week = old_bw - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/date_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/date_ext_test.rb deleted file mode 100644 index 5d0af035cc..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/date_ext_test.rb +++ /dev/null @@ -1,387 +0,0 @@ -require 'abstract_unit' -require 'active_support/time' -require 'core_ext/date_and_time_behavior' - -class DateExtCalculationsTest < ActiveSupport::TestCase - def date_time_init(year,month,day,*args) - Date.new(year,month,day) - end - - include DateAndTimeBehavior - - def test_yesterday_in_calendar_reform - assert_equal Date.new(1582,10,4), Date.new(1582,10,15).yesterday - end - - def test_tomorrow_in_calendar_reform - assert_equal Date.new(1582,10,15), Date.new(1582,10,4).tomorrow - end - - def test_to_s - date = Date.new(2005, 2, 21) - assert_equal "2005-02-21", date.to_s - assert_equal "21 Feb", date.to_s(:short) - assert_equal "February 21, 2005", date.to_s(:long) - assert_equal "February 21st, 2005", date.to_s(:long_ordinal) - assert_equal "2005-02-21", date.to_s(:db) - assert_equal "21 Feb 2005", date.to_s(:rfc822) - assert_equal "2005-02-21", date.to_s(:iso8601) - end - - def test_readable_inspect - assert_equal "Mon, 21 Feb 2005", Date.new(2005, 2, 21).readable_inspect - assert_equal Date.new(2005, 2, 21).readable_inspect, Date.new(2005, 2, 21).inspect - end - - def test_to_time - with_env_tz 'US/Eastern' do - assert_equal Time, Date.new(2005, 2, 21).to_time.class - assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time - assert_equal Time.local(2005, 2, 21).utc_offset, Date.new(2005, 2, 21).to_time.utc_offset - end - - silence_warnings do - 0.upto(138) do |year| - [:utc, :local].each do |format| - assert_equal year, Date.new(year).to_time(format).year - end - end - end - end - - def test_compare_to_time - assert Date.yesterday < Time.now - end - - def test_to_datetime - assert_equal DateTime.civil(2005, 2, 21), Date.new(2005, 2, 21).to_datetime - assert_equal 0, Date.new(2005, 2, 21).to_datetime.offset # use UTC offset - assert_equal ::Date::ITALY, Date.new(2005, 2, 21).to_datetime.start # use Ruby's default start value - end - - def test_to_date - assert_equal Date.new(2005, 2, 21), Date.new(2005, 2, 21).to_date - end - - def test_change - assert_equal Date.new(2005, 2, 21), Date.new(2005, 2, 11).change(:day => 21) - assert_equal Date.new(2007, 5, 11), Date.new(2005, 2, 11).change(:year => 2007, :month => 5) - assert_equal Date.new(2006,2,22), Date.new(2005,2,22).change(:year => 2006) - assert_equal Date.new(2005,6,22), Date.new(2005,2,22).change(:month => 6) - end - - def test_sunday - assert_equal Date.new(2008,3,2), Date.new(2008,3,02).sunday - assert_equal Date.new(2008,3,2), Date.new(2008,2,29).sunday - end - - def test_beginning_of_week_in_calendar_reform - assert_equal Date.new(1582,10,1), Date.new(1582,10,15).beginning_of_week #friday - end - - def test_end_of_week_in_calendar_reform - assert_equal Date.new(1582,10,17), Date.new(1582,10,4).end_of_week #thursday - end - - def test_end_of_year - assert_equal Date.new(2008,12,31).to_s, Date.new(2008,2,22).end_of_year.to_s - end - - def test_end_of_month - assert_equal Date.new(2005,3,31), Date.new(2005,3,20).end_of_month - assert_equal Date.new(2005,2,28), Date.new(2005,2,20).end_of_month - assert_equal Date.new(2005,4,30), Date.new(2005,4,20).end_of_month - end - - def test_prev_year_in_leap_years - assert_equal Date.new(1999,2,28), Date.new(2000,2,29).prev_year - end - - def test_prev_year_in_calendar_reform - assert_equal Date.new(1582,10,4), Date.new(1583,10,14).prev_year - end - - def test_last_year - assert_equal Date.new(2004,6,5), Date.new(2005,6,5).last_year - end - - def test_last_year_in_leap_years - assert_equal Date.new(1999,2,28), Date.new(2000,2,29).last_year - end - - def test_last_year_in_calendar_reform - assert_equal Date.new(1582,10,4), Date.new(1583,10,14).last_year - end - - def test_next_year_in_leap_years - assert_equal Date.new(2001,2,28), Date.new(2000,2,29).next_year - end - - def test_next_year_in_calendar_reform - assert_equal Date.new(1582,10,4), Date.new(1581,10,10).next_year - end - - def test_advance - assert_equal Date.new(2006,2,28), Date.new(2005,2,28).advance(:years => 1) - assert_equal Date.new(2005,6,28), Date.new(2005,2,28).advance(:months => 4) - assert_equal Date.new(2005,3,21), Date.new(2005,2,28).advance(:weeks => 3) - assert_equal Date.new(2005,3,5), Date.new(2005,2,28).advance(:days => 5) - assert_equal Date.new(2012,9,28), Date.new(2005,2,28).advance(:years => 7, :months => 7) - assert_equal Date.new(2013,10,3), Date.new(2005,2,28).advance(:years => 7, :months => 19, :days => 5) - assert_equal Date.new(2013,10,17), Date.new(2005,2,28).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal Date.new(2005,2,28), Date.new(2004,2,29).advance(:years => 1) #leap day plus one year - end - - def test_advance_does_first_years_and_then_days - assert_equal Date.new(2012, 2, 29), Date.new(2011, 2, 28).advance(:years => 1, :days => 1) - # If day was done first we would jump to 2012-03-01 instead. - end - - def test_advance_does_first_months_and_then_days - assert_equal Date.new(2010, 3, 29), Date.new(2010, 2, 28).advance(:months => 1, :days => 1) - # If day was done first we would jump to 2010-04-01 instead. - end - - def test_advance_in_calendar_reform - assert_equal Date.new(1582,10,15), Date.new(1582,10,4).advance(:days => 1) - assert_equal Date.new(1582,10,4), Date.new(1582,10,15).advance(:days => -1) - 5.upto(14) do |day| - assert_equal Date.new(1582,10,4), Date.new(1582,9,day).advance(:months => 1) - assert_equal Date.new(1582,10,4), Date.new(1582,11,day).advance(:months => -1) - assert_equal Date.new(1582,10,4), Date.new(1581,10,day).advance(:years => 1) - assert_equal Date.new(1582,10,4), Date.new(1583,10,day).advance(:years => -1) - end - end - - def test_last_week - assert_equal Date.new(2005,5,9), Date.new(2005,5,17).last_week - assert_equal Date.new(2006,12,25), Date.new(2007,1,7).last_week - assert_equal Date.new(2010,2,12), Date.new(2010,2,19).last_week(:friday) - assert_equal Date.new(2010,2,13), Date.new(2010,2,19).last_week(:saturday) - assert_equal Date.new(2010,2,27), Date.new(2010,3,4).last_week(:saturday) - end - - def test_next_week_in_calendar_reform - assert_equal Date.new(1582,10,15), Date.new(1582,9,30).next_week(:friday) - assert_equal Date.new(1582,10,18), Date.new(1582,10,4).next_week - end - - def test_last_month_on_31st - assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month - end - - def test_last_quarter_on_31st - assert_equal Date.new(2004, 2, 29), Date.new(2004, 5, 31).last_quarter - end - - def test_yesterday_constructor - assert_equal Date.current - 1, Date.yesterday - end - - def test_yesterday_constructor_when_zone_is_not_set - with_env_tz 'UTC' do - with_tz_default do - assert_equal(Date.today - 1, Date.yesterday) - end - end - end - - def test_yesterday_constructor_when_zone_is_set - with_env_tz 'UTC' do - with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5 - Time.stubs(:now).returns Time.local(2000, 1, 1) - assert_equal Date.new(1999, 12, 30), Date.yesterday - end - end - end - - def test_tomorrow_constructor - assert_equal Date.current + 1, Date.tomorrow - end - - def test_tomorrow_constructor_when_zone_is_not_set - with_env_tz 'UTC' do - with_tz_default do - assert_equal(Date.today + 1, Date.tomorrow) - end - end - end - - def test_tomorrow_constructor_when_zone_is_set - with_env_tz 'UTC' do - with_tz_default ActiveSupport::TimeZone['Europe/Paris'] do # UTC +1 - Time.stubs(:now).returns Time.local(1999, 12, 31, 23) - assert_equal Date.new(2000, 1, 2), Date.tomorrow - end - end - end - - def test_since - assert_equal Time.local(2005,2,21,0,0,45), Date.new(2005,2,21).since(45) - end - - def test_since_when_zone_is_set - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'UTC' do - with_tz_default zone do - assert_equal zone.local(2005,2,21,0,0,45), Date.new(2005,2,21).since(45) - assert_equal zone, Date.new(2005,2,21).since(45).time_zone - end - end - end - - def test_ago - assert_equal Time.local(2005,2,20,23,59,15), Date.new(2005,2,21).ago(45) - end - - def test_ago_when_zone_is_set - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'UTC' do - with_tz_default zone do - assert_equal zone.local(2005,2,20,23,59,15), Date.new(2005,2,21).ago(45) - assert_equal zone, Date.new(2005,2,21).ago(45).time_zone - end - end - end - - def test_beginning_of_day - assert_equal Time.local(2005,2,21,0,0,0), Date.new(2005,2,21).beginning_of_day - end - - def test_middle_of_day - assert_equal Time.local(2005,2,21,12,0,0), Date.new(2005,2,21).middle_of_day - end - - def test_beginning_of_day_when_zone_is_set - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'UTC' do - with_tz_default zone do - assert_equal zone.local(2005,2,21,0,0,0), Date.new(2005,2,21).beginning_of_day - assert_equal zone, Date.new(2005,2,21).beginning_of_day.time_zone - end - end - end - - def test_end_of_day - assert_equal Time.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day - end - - def test_end_of_day_when_zone_is_set - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'UTC' do - with_tz_default zone do - assert_equal zone.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day - assert_equal zone, Date.new(2005,2,21).end_of_day.time_zone - end - end - end - - def test_all_week - assert_equal Date.new(2011,6,6)..Date.new(2011,6,12), Date.new(2011,6,7).all_week - assert_equal Date.new(2011,6,5)..Date.new(2011,6,11), Date.new(2011,6,7).all_week(:sunday) - end - - def test_all_month - assert_equal Date.new(2011,6,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_month - end - - def test_all_quarter - assert_equal Date.new(2011,4,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_quarter - end - - def test_all_year - assert_equal Date.new(2011,1,1)..Date.new(2011,12,31), Date.new(2011,6,7).all_year - end - - def test_xmlschema - with_env_tz 'US/Eastern' do - assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema) - assert_match(/^1980-06-28T00:00:00-04:?00$/, Date.new(1980, 6, 28).xmlschema) - # these tests are only of interest on platforms where older dates #to_time fail over to DateTime - if ::DateTime === Date.new(1880, 6, 28).to_time - assert_match(/^1880-02-28T00:00:00-05:?00$/, Date.new(1880, 2, 28).xmlschema) - assert_match(/^1880-06-28T00:00:00-05:?00$/, Date.new(1880, 6, 28).xmlschema) # DateTimes aren't aware of DST rules - end - end - end - - def test_xmlschema_when_zone_is_set - with_env_tz 'UTC' do - with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5 - assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema) - assert_match(/^1980-06-28T00:00:00-04:?00$/, Date.new(1980, 6, 28).xmlschema) - end - end - end - - def test_past - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal true, Date.new(1999, 12, 31).past? - assert_equal false, Date.new(2000,1,1).past? - assert_equal false, Date.new(2000,1,2).past? - end - - def test_future - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, Date.new(1999, 12, 31).future? - assert_equal false, Date.new(2000,1,1).future? - assert_equal true, Date.new(2000,1,2).future? - end - - def test_current_returns_date_today_when_zone_not_set - with_env_tz 'US/Central' do - Time.stubs(:now).returns Time.local(1999, 12, 31, 23) - assert_equal Date.today, Date.current - end - end - - def test_current_returns_time_zone_today_when_zone_is_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Central' do - assert_equal ::Time.zone.today, Date.current - end - ensure - Time.zone = nil - end - - def test_date_advance_should_not_change_passed_options_hash - options = { :years => 3, :months => 11, :days => 2 } - Date.new(2005,2,28).advance(options) - assert_equal({ :years => 3, :months => 11, :days => 2 }, options) - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end - - def with_tz_default(tz = nil) - old_tz = Time.zone - Time.zone = tz - yield - ensure - Time.zone = old_tz - end -end - -class DateExtBehaviorTest < ActiveSupport::TestCase - def test_date_acts_like_date - assert Date.new.acts_like_date? - end - - def test_freeze_doesnt_clobber_memoized_instance_methods - assert_nothing_raised do - Date.today.freeze.inspect - end - end - - def test_can_freeze_twice - assert_nothing_raised do - Date.today.freeze.freeze - end - end -end - diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/date_time_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/date_time_ext_test.rb deleted file mode 100644 index 0a40aeb96c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/date_time_ext_test.rb +++ /dev/null @@ -1,357 +0,0 @@ -require 'abstract_unit' -require 'active_support/time' -require 'core_ext/date_and_time_behavior' - -class DateTimeExtCalculationsTest < ActiveSupport::TestCase - def date_time_init(year,month,day,hour,minute,second,*args) - DateTime.civil(year,month,day,hour,minute,second) - end - - include DateAndTimeBehavior - - def test_to_s - datetime = DateTime.new(2005, 2, 21, 14, 30, 0, 0) - assert_equal "2005-02-21 14:30:00", datetime.to_s(:db) - assert_equal "14:30", datetime.to_s(:time) - assert_equal "21 Feb 14:30", datetime.to_s(:short) - assert_equal "February 21, 2005 14:30", datetime.to_s(:long) - assert_equal "Mon, 21 Feb 2005 14:30:00 +0000", datetime.to_s(:rfc822) - assert_equal "February 21st, 2005 14:30", datetime.to_s(:long_ordinal) - assert_match(/^2005-02-21T14:30:00(Z|\+00:00)$/, datetime.to_s) - - with_env_tz "US/Central" do - assert_equal "2009-02-05T14:30:05-06:00", DateTime.civil(2009, 2, 5, 14, 30, 5, Rational(-21600, 86400)).to_s(:iso8601) - assert_equal "2008-06-09T04:05:01-05:00", DateTime.civil(2008, 6, 9, 4, 5, 1, Rational(-18000, 86400)).to_s(:iso8601) - assert_equal "2009-02-05T14:30:05+00:00", DateTime.civil(2009, 2, 5, 14, 30, 5).to_s(:iso8601) - end - end - - def test_readable_inspect - datetime = DateTime.new(2005, 2, 21, 14, 30, 0) - assert_equal "Mon, 21 Feb 2005 14:30:00 +0000", datetime.readable_inspect - assert_equal datetime.readable_inspect, datetime.inspect - end - - def test_custom_date_format - Time::DATE_FORMATS[:custom] = '%Y%m%d%H%M%S' - assert_equal '20050221143000', DateTime.new(2005, 2, 21, 14, 30, 0).to_s(:custom) - Time::DATE_FORMATS.delete(:custom) - end - - def test_to_date - assert_equal Date.new(2005, 2, 21), DateTime.new(2005, 2, 21, 14, 30, 0).to_date - end - - def test_to_datetime - assert_equal DateTime.new(2005, 2, 21, 14, 30, 0), DateTime.new(2005, 2, 21, 14, 30, 0).to_datetime - end - - def test_to_time - with_env_tz 'US/Eastern' do - assert_equal Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.class - assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time - assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset - end - end - - def test_to_time_preserves_fractional_seconds - assert_equal Time.utc(2005, 2, 21, 10, 11, 12, 256), DateTime.new(2005, 2, 21, 10, 11, 12 + Rational(256, 1000000), 0).to_time - end - - def test_civil_from_format - assert_equal Time.local(2010, 5, 4, 0, 0, 0), DateTime.civil_from_format(:local, 2010, 5, 4) - assert_equal Time.utc(2010, 5, 4, 0, 0, 0), DateTime.civil_from_format(:utc, 2010, 5, 4) - end - - def test_seconds_since_midnight - assert_equal 1,DateTime.civil(2005,1,1,0,0,1).seconds_since_midnight - assert_equal 60,DateTime.civil(2005,1,1,0,1,0).seconds_since_midnight - assert_equal 3660,DateTime.civil(2005,1,1,1,1,0).seconds_since_midnight - assert_equal 86399,DateTime.civil(2005,1,1,23,59,59).seconds_since_midnight - end - - def test_seconds_until_end_of_day - assert_equal 0, DateTime.civil(2005,1,1,23,59,59).seconds_until_end_of_day - assert_equal 1, DateTime.civil(2005,1,1,23,59,58).seconds_until_end_of_day - assert_equal 60, DateTime.civil(2005,1,1,23,58,59).seconds_until_end_of_day - assert_equal 3660, DateTime.civil(2005,1,1,22,58,59).seconds_until_end_of_day - assert_equal 86399, DateTime.civil(2005,1,1,0,0,0).seconds_until_end_of_day - end - - def test_beginning_of_day - assert_equal DateTime.civil(2005,2,4,0,0,0), DateTime.civil(2005,2,4,10,10,10).beginning_of_day - end - - def test_middle_of_day - assert_equal DateTime.civil(2005,2,4,12,0,0), DateTime.civil(2005,2,4,10,10,10).middle_of_day - end - - def test_end_of_day - assert_equal DateTime.civil(2005,2,4,23,59,59), DateTime.civil(2005,2,4,10,10,10).end_of_day - end - - def test_beginning_of_hour - assert_equal DateTime.civil(2005,2,4,19,0,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_hour - end - - def test_end_of_hour - assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour - end - - def test_beginning_of_minute - assert_equal DateTime.civil(2005,2,4,19,30,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_minute - end - - def test_end_of_minute - assert_equal DateTime.civil(2005,2,4,19,30,59), DateTime.civil(2005,2,4,19,30,10).end_of_minute - end - - def test_end_of_month - assert_equal DateTime.civil(2005,3,31,23,59,59), DateTime.civil(2005,3,20,10,10,10).end_of_month - assert_equal DateTime.civil(2005,2,28,23,59,59), DateTime.civil(2005,2,20,10,10,10).end_of_month - assert_equal DateTime.civil(2005,4,30,23,59,59), DateTime.civil(2005,4,20,10,10,10).end_of_month - end - - def test_last_year - assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).last_year - end - - def test_ago - assert_equal DateTime.civil(2005,2,22,10,10,9), DateTime.civil(2005,2,22,10,10,10).ago(1) - assert_equal DateTime.civil(2005,2,22,9,10,10), DateTime.civil(2005,2,22,10,10,10).ago(3600) - assert_equal DateTime.civil(2005,2,20,10,10,10), DateTime.civil(2005,2,22,10,10,10).ago(86400*2) - assert_equal DateTime.civil(2005,2,20,9,9,45), DateTime.civil(2005,2,22,10,10,10).ago(86400*2 + 3600 + 25) - end - - def test_since - assert_equal DateTime.civil(2005,2,22,10,10,11), DateTime.civil(2005,2,22,10,10,10).since(1) - assert_equal DateTime.civil(2005,2,22,11,10,10), DateTime.civil(2005,2,22,10,10,10).since(3600) - assert_equal DateTime.civil(2005,2,24,10,10,10), DateTime.civil(2005,2,22,10,10,10).since(86400*2) - assert_equal DateTime.civil(2005,2,24,11,10,35), DateTime.civil(2005,2,22,10,10,10).since(86400*2 + 3600 + 25) - assert_equal DateTime.civil(2005,2,22,10,10,11), DateTime.civil(2005,2,22,10,10,10).since(1.333) - assert_equal DateTime.civil(2005,2,22,10,10,12), DateTime.civil(2005,2,22,10,10,10).since(1.667) - end - - def test_change - assert_equal DateTime.civil(2006,2,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(:year => 2006) - assert_equal DateTime.civil(2005,6,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(:month => 6) - assert_equal DateTime.civil(2012,9,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(:year => 2012, :month => 9) - assert_equal DateTime.civil(2005,2,22,16), DateTime.civil(2005,2,22,15,15,10).change(:hour => 16) - assert_equal DateTime.civil(2005,2,22,16,45), DateTime.civil(2005,2,22,15,15,10).change(:hour => 16, :min => 45) - assert_equal DateTime.civil(2005,2,22,15,45), DateTime.civil(2005,2,22,15,15,10).change(:min => 45) - - # datetime with fractions of a second - assert_equal DateTime.civil(2005,2,1,15,15,10.7), DateTime.civil(2005,2,22,15,15,10.7).change(:day => 1) - end - - def test_advance - assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 1) - assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:months => 4) - assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:weeks => 3) - assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:days => 5) - assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 7) - assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) - assert_equal DateTime.civil(2013,10,17,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal DateTime.civil(2001,12,27,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1) - assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year - assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5) - assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(:minutes => 7) - assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(:seconds => 9) - assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) - assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) - end - - def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas - # If the time deltas were processed first, the following datetimes would be advanced to 2010/04/01 instead. - assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(:months => 1, :seconds => 1) - assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59).advance(:months => 1, :minutes => 1) - assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23).advance(:months => 1, :hours => 1) - assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 22, 58, 59).advance(:months => 1, :hours => 1, :minutes => 1, :seconds => 1) - end - - def test_last_week - assert_equal DateTime.civil(2005,2,21), DateTime.civil(2005,3,1,15,15,10).last_week - assert_equal DateTime.civil(2005,2,22), DateTime.civil(2005,3,1,15,15,10).last_week(:tuesday) - assert_equal DateTime.civil(2005,2,25), DateTime.civil(2005,3,1,15,15,10).last_week(:friday) - assert_equal DateTime.civil(2006,10,30), DateTime.civil(2006,11,6,0,0,0).last_week - assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).last_week(:wednesday) - end - - def test_last_month_on_31st - assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month - end - - def test_last_quarter_on_31st - assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 5, 31).last_quarter - end - - def test_xmlschema - assert_match(/^1880-02-28T15:15:10\+00:?00$/, DateTime.civil(1880, 2, 28, 15, 15, 10).xmlschema) - assert_match(/^1980-02-28T15:15:10\+00:?00$/, DateTime.civil(1980, 2, 28, 15, 15, 10).xmlschema) - assert_match(/^2080-02-28T15:15:10\+00:?00$/, DateTime.civil(2080, 2, 28, 15, 15, 10).xmlschema) - assert_match(/^1880-02-28T15:15:10-06:?00$/, DateTime.civil(1880, 2, 28, 15, 15, 10, -0.25).xmlschema) - assert_match(/^1980-02-28T15:15:10-06:?00$/, DateTime.civil(1980, 2, 28, 15, 15, 10, -0.25).xmlschema) - assert_match(/^2080-02-28T15:15:10-06:?00$/, DateTime.civil(2080, 2, 28, 15, 15, 10, -0.25).xmlschema) - end - - def test_today_with_offset - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, DateTime.civil(1999,12,31,23,59,59, Rational(-18000, 86400)).today? - assert_equal true, DateTime.civil(2000,1,1,0,0,0, Rational(-18000, 86400)).today? - assert_equal true, DateTime.civil(2000,1,1,23,59,59, Rational(-18000, 86400)).today? - assert_equal false, DateTime.civil(2000,1,2,0,0,0, Rational(-18000, 86400)).today? - end - - def test_today_without_offset - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, DateTime.civil(1999,12,31,23,59,59).today? - assert_equal true, DateTime.civil(2000,1,1,0).today? - assert_equal true, DateTime.civil(2000,1,1,23,59,59).today? - assert_equal false, DateTime.civil(2000,1,2,0).today? - end - - def test_past_with_offset - DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) - assert_equal true, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).past? - assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).past? - assert_equal false, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).past? - end - - def test_past_without_offset - DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) - assert_equal true, DateTime.civil(2005,2,10,20,30,44).past? - assert_equal false, DateTime.civil(2005,2,10,20,30,45).past? - assert_equal false, DateTime.civil(2005,2,10,20,30,46).past? - end - - def test_future_with_offset - DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) - assert_equal false, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).future? - assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).future? - assert_equal true, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).future? - end - - def test_future_without_offset - DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) - assert_equal false, DateTime.civil(2005,2,10,20,30,44).future? - assert_equal false, DateTime.civil(2005,2,10,20,30,45).future? - assert_equal true, DateTime.civil(2005,2,10,20,30,46).future? - end - - def test_current_returns_date_today_when_zone_is_not_set - with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59) - assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current - end - end - - def test_current_returns_time_zone_today_when_zone_is_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59) - assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current - end - ensure - Time.zone = nil - end - - def test_current_without_time_zone - assert_kind_of DateTime, DateTime.current - end - - def test_current_with_time_zone - with_env_tz 'US/Eastern' do - assert_kind_of DateTime, DateTime.current - end - end - - def test_acts_like_date - assert DateTime.new.acts_like_date? - end - - def test_acts_like_time - assert DateTime.new.acts_like_time? - end - - def test_utc? - assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12).utc? - assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc? - assert_equal false, DateTime.civil(2005, 2, 21, 10, 11, 12, 0.25).utc? - assert_equal false, DateTime.civil(2005, 2, 21, 10, 11, 12, -0.25).utc? - end - - def test_utc_offset - assert_equal 0, DateTime.civil(2005, 2, 21, 10, 11, 12).utc_offset - assert_equal 0, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc_offset - assert_equal 21600, DateTime.civil(2005, 2, 21, 10, 11, 12, 0.25).utc_offset - assert_equal( -21600, DateTime.civil(2005, 2, 21, 10, 11, 12, -0.25).utc_offset ) - assert_equal( -18000, DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc_offset ) - end - - def test_utc - assert_equal DateTime.civil(2005, 2, 21, 16, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc - assert_equal DateTime.civil(2005, 2, 21, 15, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc - assert_equal DateTime.civil(2005, 2, 21, 10, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc - assert_equal DateTime.civil(2005, 2, 21, 9, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(1, 24)).utc - assert_equal DateTime.civil(2005, 2, 21, 9, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(1, 24)).getutc - end - - def test_formatted_offset_with_utc - assert_equal '+00:00', DateTime.civil(2000).formatted_offset - assert_equal '+0000', DateTime.civil(2000).formatted_offset(false) - assert_equal 'UTC', DateTime.civil(2000).formatted_offset(true, 'UTC') - end - - def test_formatted_offset_with_local - dt = DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)) - assert_equal '-05:00', dt.formatted_offset - assert_equal '-0500', dt.formatted_offset(false) - end - - def test_compare_with_time - assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59) - assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0) - assert_equal(-1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1)) - end - - def test_compare_with_datetime - assert_equal 1, DateTime.civil(2000) <=> DateTime.civil(1999, 12, 31, 23, 59, 59) - assert_equal 0, DateTime.civil(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 0) - assert_equal(-1, DateTime.civil(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 1)) - end - - def test_compare_with_time_with_zone - assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) - assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) - assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) - end - - def test_to_f - assert_equal 946684800.0, DateTime.civil(2000).to_f - assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f - end - - def test_to_i - assert_equal 946684800, DateTime.civil(2000).to_i - assert_equal 946684800, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_i - end - - def test_usec - assert_equal 0, DateTime.civil(2000).usec - assert_equal 500000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).usec - end - - def test_nsec - assert_equal 0, DateTime.civil(2000).nsec - assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/deep_dup_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/deep_dup_test.rb deleted file mode 100644 index 91d558dbb5..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/deep_dup_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/object' - -class DeepDupTest < ActiveSupport::TestCase - - def test_array_deep_dup - array = [1, [2, 3]] - dup = array.deep_dup - dup[1][2] = 4 - assert_equal nil, array[1][2] - assert_equal 4, dup[1][2] - end - - def test_hash_deep_dup - hash = { :a => { :b => 'b' } } - dup = hash.deep_dup - dup[:a][:c] = 'c' - assert_equal nil, hash[:a][:c] - assert_equal 'c', dup[:a][:c] - end - - def test_array_deep_dup_with_hash_inside - array = [1, { :a => 2, :b => 3 } ] - dup = array.deep_dup - dup[1][:c] = 4 - assert_equal nil, array[1][:c] - assert_equal 4, dup[1][:c] - end - - def test_hash_deep_dup_with_array_inside - hash = { :a => [1, 2] } - dup = hash.deep_dup - dup[:a][2] = 'c' - assert_equal nil, hash[:a][2] - assert_equal 'c', dup[:a][2] - end - - def test_deep_dup_initialize - zero_hash = Hash.new 0 - hash = { :a => zero_hash } - dup = hash.deep_dup - assert_equal 0, dup[:a][44] - end - - def test_object_deep_dup - object = Object.new - dup = object.deep_dup - dup.instance_variable_set(:@a, 1) - assert !object.instance_variable_defined?(:@a) - assert dup.instance_variable_defined?(:@a) - end - -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/duplicable_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/duplicable_test.rb deleted file mode 100644 index e0566e012c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/duplicable_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'abstract_unit' -require 'bigdecimal' -require 'active_support/core_ext/object/duplicable' -require 'active_support/core_ext/numeric/time' - -class DuplicableTest < ActiveSupport::TestCase - RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds] - YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] - NO = [] - - begin - bd = BigDecimal.new('4.56') - YES << bd.dup - rescue TypeError - RAISE_DUP << bd - end - - - def test_duplicable - (RAISE_DUP + NO).each do |v| - assert !v.duplicable? - end - - YES.each do |v| - assert v.duplicable?, "#{v.class} should be duplicable" - end - - (YES + NO).each do |v| - assert_nothing_raised { v.dup } - end - - RAISE_DUP.each do |v| - assert_raises(TypeError, v.class.name) do - v.dup - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/duration_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/duration_test.rb deleted file mode 100644 index 28ba33331e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/duration_test.rb +++ /dev/null @@ -1,171 +0,0 @@ -require 'abstract_unit' -require 'active_support/inflector' -require 'active_support/time' -require 'active_support/json' - -class DurationTest < ActiveSupport::TestCase - def test_is_a - d = 1.day - assert d.is_a?(ActiveSupport::Duration) - assert_kind_of ActiveSupport::Duration, d - assert_kind_of Numeric, d - assert_kind_of Fixnum, d - assert !d.is_a?(Hash) - - k = Class.new - class << k; undef_method :== end - assert !d.is_a?(k) - end - - def test_threequals - assert ActiveSupport::Duration === 1.day - assert !(ActiveSupport::Duration === 1.day.to_i) - assert !(ActiveSupport::Duration === 'foo') - assert !(ActiveSupport::Duration === ActiveSupport::ProxyObject.new) - end - - def test_equals - assert 1.day == 1.day - assert 1.day == 1.day.to_i - assert 1.day.to_i == 1.day - assert !(1.day == 'foo') - end - - def test_inspect - assert_equal '0 seconds', 0.seconds.inspect - assert_equal '1 month', 1.month.inspect - assert_equal '1 month and 1 day', (1.month + 1.day).inspect - assert_equal '6 months and -2 days', (6.months - 2.days).inspect - assert_equal '10 seconds', 10.seconds.inspect - assert_equal '10 years, 2 months, and 1 day', (10.years + 2.months + 1.day).inspect - assert_equal '10 years, 2 months, and 1 day', (10.years + 1.month + 1.day + 1.month).inspect - assert_equal '10 years, 2 months, and 1 day', (1.day + 10.years + 2.months).inspect - assert_equal '7 days', 1.week.inspect - assert_equal '14 days', 1.fortnight.inspect - end - - def test_minus_with_duration_does_not_break_subtraction_of_date_from_date - assert_nothing_raised { Date.today - Date.today } - end - - def test_plus_with_time - assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration" - end - - def test_argument_error - e = assert_raise ArgumentError do - 1.second.ago('') - end - assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb" - end - - def test_fractional_weeks - assert_equal((86400 * 7) * 1.5, 1.5.weeks) - assert_equal((86400 * 7) * 1.7, 1.7.weeks) - end - - def test_fractional_days - assert_equal 86400 * 1.5, 1.5.days - assert_equal 86400 * 1.7, 1.7.days - end - - def test_since_and_ago - t = Time.local(2000) - assert t + 1, 1.second.since(t) - assert t - 1, 1.second.ago(t) - end - - def test_since_and_ago_without_argument - now = Time.now - assert 1.second.since >= now + 1 - now = Time.now - assert 1.second.ago >= now - 1 - end - - def test_since_and_ago_with_fractional_days - t = Time.local(2000) - # since - assert_equal 36.hours.since(t), 1.5.days.since(t) - assert_in_delta((24 * 1.7).hours.since(t), 1.7.days.since(t), 1) - # ago - assert_equal 36.hours.ago(t), 1.5.days.ago(t) - assert_in_delta((24 * 1.7).hours.ago(t), 1.7.days.ago(t), 1) - end - - def test_since_and_ago_with_fractional_weeks - t = Time.local(2000) - # since - assert_equal((7 * 36).hours.since(t), 1.5.weeks.since(t)) - assert_in_delta((7 * 24 * 1.7).hours.since(t), 1.7.weeks.since(t), 1) - # ago - assert_equal((7 * 36).hours.ago(t), 1.5.weeks.ago(t)) - assert_in_delta((7 * 24 * 1.7).hours.ago(t), 1.7.weeks.ago(t), 1) - end - - def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set - Time.zone = nil - with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - # since - assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since - assert_equal Time.local(2000,1,1,0,0,5), 5.seconds.since - # ago - assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago - assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago - end - end - - def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - # since - assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since - assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time - assert_equal 'Eastern Time (US & Canada)', 5.seconds.since.time_zone.name - # ago - assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago - assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time - assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name - end - ensure - Time.zone = nil - end - - def test_adding_hours_across_dst_boundary - with_env_tz 'CET' do - assert_equal Time.local(2009,3,29,0,0,0) + 24.hours, Time.local(2009,3,30,1,0,0) - end - end - - def test_adding_day_across_dst_boundary - with_env_tz 'CET' do - assert_equal Time.local(2009,3,29,0,0,0) + 1.day, Time.local(2009,3,30,0,0,0) - end - end - - def test_delegation_with_block_works - counter = 0 - assert_nothing_raised do - 1.minute.times {counter += 1} - end - assert_equal counter, 60 - end - - def test_to_json - assert_equal '172800', 2.days.to_json - end - - def test_case_when - cased = case 1.day when 1.day then "ok" end - assert_equal cased, "ok" - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/enumerable_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/enumerable_test.rb deleted file mode 100644 index 6fcf6e8743..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/enumerable_test.rb +++ /dev/null @@ -1,106 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/array' -require 'active_support/core_ext/enumerable' - -Payment = Struct.new(:price) -class SummablePayment < Payment - def +(p) self.class.new(price + p.price) end -end - -class EnumerableTests < ActiveSupport::TestCase - - class GenericEnumerable - include Enumerable - def initialize(values = [1, 2, 3]) - @values = values - end - - def each - @values.each{|v| yield v} - end - end - - def test_sums - enum = GenericEnumerable.new([5, 15, 10]) - assert_equal 30, enum.sum - assert_equal 60, enum.sum { |i| i * 2} - - enum = GenericEnumerable.new(%w(a b c)) - assert_equal 'abc', enum.sum - assert_equal 'aabbcc', enum.sum { |i| i * 2 } - - payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ]) - assert_equal 30, payments.sum(&:price) - assert_equal 60, payments.sum { |p| p.price * 2 } - - payments = GenericEnumerable.new([ SummablePayment.new(5), SummablePayment.new(15) ]) - assert_equal SummablePayment.new(20), payments.sum - assert_equal SummablePayment.new(20), payments.sum { |p| p } - end - - def test_nil_sums - expected_raise = TypeError - - assert_raise(expected_raise) { GenericEnumerable.new([5, 15, nil]).sum } - - payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10), Payment.new(nil) ]) - assert_raise(expected_raise) { payments.sum(&:price) } - - assert_equal 60, payments.sum { |p| p.price.to_i * 2 } - end - - def test_empty_sums - assert_equal 0, GenericEnumerable.new([]).sum - assert_equal 0, GenericEnumerable.new([]).sum { |i| i + 10 } - assert_equal Payment.new(0), GenericEnumerable.new([]).sum(Payment.new(0)) - end - - def test_range_sums - assert_equal 20, (1..4).sum { |i| i * 2 } - assert_equal 10, (1..4).sum - assert_equal 10, (1..4.5).sum - assert_equal 6, (1...4).sum - assert_equal 'abc', ('a'..'c').sum - assert_equal 50_000_005_000_000, (0..10_000_000).sum - assert_equal 0, (10..0).sum - assert_equal 5, (10..0).sum(5) - assert_equal 10, (10..10).sum - assert_equal 42, (10...10).sum(42) - end - - def test_index_by - payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ]) - assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, - payments.index_by { |p| p.price }) - assert_equal Enumerator, payments.index_by.class - if Enumerator.method_defined? :size - assert_equal nil, payments.index_by.size - assert_equal 42, (1..42).index_by.size - end - assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, - payments.index_by.each { |p| p.price }) - end - - def test_many - assert_equal false, GenericEnumerable.new([] ).many? - assert_equal false, GenericEnumerable.new([ 1 ] ).many? - assert_equal true, GenericEnumerable.new([ 1, 2 ] ).many? - - assert_equal false, GenericEnumerable.new([] ).many? {|x| x > 1 } - assert_equal false, GenericEnumerable.new([ 2 ] ).many? {|x| x > 1 } - assert_equal false, GenericEnumerable.new([ 1, 2 ] ).many? {|x| x > 1 } - assert_equal true, GenericEnumerable.new([ 1, 2, 2 ]).many? {|x| x > 1 } - end - - def test_many_iterates_only_on_what_is_needed - infinity = 1.0/0.0 - very_long_enum = 0..infinity - assert_equal true, very_long_enum.many? - assert_equal true, very_long_enum.many?{|x| x > 100} - end - - def test_exclude? - assert_equal true, GenericEnumerable.new([ 1 ]).exclude?(2) - assert_equal false, GenericEnumerable.new([ 1 ]).exclude?(1) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/file_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/file_test.rb deleted file mode 100644 index 2c04e9687c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/file_test.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/file' - -class AtomicWriteTest < ActiveSupport::TestCase - def test_atomic_write_without_errors - contents = "Atomic Text" - File.atomic_write(file_name, Dir.pwd) do |file| - file.write(contents) - assert !File.exist?(file_name) - end - assert File.exist?(file_name) - assert_equal contents, File.read(file_name) - ensure - File.unlink(file_name) rescue nil - end - - def test_atomic_write_doesnt_write_when_block_raises - File.atomic_write(file_name) do |file| - file.write("testing") - raise "something bad" - end - rescue - assert !File.exist?(file_name) - end - - def test_atomic_write_preserves_file_permissions - contents = "Atomic Text" - File.open(file_name, "w", 0755) do |file| - file.write(contents) - assert File.exist?(file_name) - end - assert File.exist?(file_name) - assert_equal 0100755 & ~File.umask, file_mode - assert_equal contents, File.read(file_name) - - File.atomic_write(file_name, Dir.pwd) do |file| - file.write(contents) - assert File.exist?(file_name) - end - assert File.exist?(file_name) - assert_equal 0100755 & ~File.umask, file_mode - assert_equal contents, File.read(file_name) - ensure - File.unlink(file_name) rescue nil - end - - def test_atomic_write_preserves_default_file_permissions - contents = "Atomic Text" - File.atomic_write(file_name, Dir.pwd) do |file| - file.write(contents) - assert !File.exist?(file_name) - end - assert File.exist?(file_name) - assert_equal File.probe_stat_in(Dir.pwd).mode, file_mode - assert_equal contents, File.read(file_name) - ensure - File.unlink(file_name) rescue nil - end - - private - def file_name - "atomic.file" - end - - def file_mode - File.stat(file_name).mode - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/hash_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/hash_ext_test.rb deleted file mode 100644 index 69a380c7cb..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/hash_ext_test.rb +++ /dev/null @@ -1,1592 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/hash' -require 'bigdecimal' -require 'active_support/core_ext/string/access' -require 'active_support/ordered_hash' -require 'active_support/core_ext/object/conversions' -require 'active_support/core_ext/object/deep_dup' -require 'active_support/inflections' - -class HashExtTest < ActiveSupport::TestCase - class IndifferentHash < ActiveSupport::HashWithIndifferentAccess - end - - class SubclassingArray < Array - end - - class SubclassingHash < Hash - end - - class NonIndifferentHash < Hash - def nested_under_indifferent_access - self - end - end - - class HashByConversion - def initialize(hash) - @hash = hash - end - - def to_hash - @hash - end - end - - def setup - @strings = { 'a' => 1, 'b' => 2 } - @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } } - @symbols = { :a => 1, :b => 2 } - @nested_symbols = { :a => { :b => { :c => 3 } } } - @mixed = { :a => 1, 'b' => 2 } - @nested_mixed = { 'a' => { :b => { 'c' => 3 } } } - @fixnums = { 0 => 1, 1 => 2 } - @nested_fixnums = { 0 => { 1 => { 2 => 3} } } - @illegal_symbols = { [] => 3 } - @nested_illegal_symbols = { [] => { [] => 3} } - @upcase_strings = { 'A' => 1, 'B' => 2 } - @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } } - end - - def test_methods - h = {} - assert_respond_to h, :transform_keys - assert_respond_to h, :transform_keys! - assert_respond_to h, :deep_transform_keys - assert_respond_to h, :deep_transform_keys! - assert_respond_to h, :symbolize_keys - assert_respond_to h, :symbolize_keys! - assert_respond_to h, :deep_symbolize_keys - assert_respond_to h, :deep_symbolize_keys! - assert_respond_to h, :stringify_keys - assert_respond_to h, :stringify_keys! - assert_respond_to h, :deep_stringify_keys - assert_respond_to h, :deep_stringify_keys! - assert_respond_to h, :to_options - assert_respond_to h, :to_options! - assert_respond_to h, :compact - assert_respond_to h, :compact! - end - - def test_transform_keys - assert_equal @upcase_strings, @strings.transform_keys{ |key| key.to_s.upcase } - assert_equal @upcase_strings, @symbols.transform_keys{ |key| key.to_s.upcase } - assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase } - end - - def test_transform_keys_not_mutates - transformed_hash = @mixed.dup - transformed_hash.transform_keys{ |key| key.to_s.upcase } - assert_equal @mixed, transformed_hash - end - - def test_deep_transform_keys - assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase } - end - - def test_deep_transform_keys_not_mutates - transformed_hash = @nested_mixed.deep_dup - transformed_hash.deep_transform_keys{ |key| key.to_s.upcase } - assert_equal @nested_mixed, transformed_hash - end - - def test_transform_keys! - assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase } - assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase } - assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase } - end - - def test_transform_keys_with_bang_mutates - transformed_hash = @mixed.dup - transformed_hash.transform_keys!{ |key| key.to_s.upcase } - assert_equal @upcase_strings, transformed_hash - assert_equal @mixed, { :a => 1, "b" => 2 } - end - - def test_deep_transform_keys! - assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } - end - - def test_deep_transform_keys_with_bang_mutates - transformed_hash = @nested_mixed.deep_dup - transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase } - assert_equal @nested_upcase_strings, transformed_hash - assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } - end - - def test_symbolize_keys - assert_equal @symbols, @symbols.symbolize_keys - assert_equal @symbols, @strings.symbolize_keys - assert_equal @symbols, @mixed.symbolize_keys - end - - def test_symbolize_keys_not_mutates - transformed_hash = @mixed.dup - transformed_hash.symbolize_keys - assert_equal @mixed, transformed_hash - end - - def test_deep_symbolize_keys - assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys - assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys - assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys - end - - def test_deep_symbolize_keys_not_mutates - transformed_hash = @nested_mixed.deep_dup - transformed_hash.deep_symbolize_keys - assert_equal @nested_mixed, transformed_hash - end - - def test_symbolize_keys! - assert_equal @symbols, @symbols.dup.symbolize_keys! - assert_equal @symbols, @strings.dup.symbolize_keys! - assert_equal @symbols, @mixed.dup.symbolize_keys! - end - - def test_symbolize_keys_with_bang_mutates - transformed_hash = @mixed.dup - transformed_hash.deep_symbolize_keys! - assert_equal @symbols, transformed_hash - assert_equal @mixed, { :a => 1, "b" => 2 } - end - - def test_deep_symbolize_keys! - assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys! - assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys! - assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys! - end - - def test_deep_symbolize_keys_with_bang_mutates - transformed_hash = @nested_mixed.deep_dup - transformed_hash.deep_symbolize_keys! - assert_equal @nested_symbols, transformed_hash - assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } - end - - def test_symbolize_keys_preserves_keys_that_cant_be_symbolized - assert_equal @illegal_symbols, @illegal_symbols.symbolize_keys - assert_equal @illegal_symbols, @illegal_symbols.dup.symbolize_keys! - end - - def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized - assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_symbolize_keys - assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys! - end - - def test_symbolize_keys_preserves_fixnum_keys - assert_equal @fixnums, @fixnums.symbolize_keys - assert_equal @fixnums, @fixnums.dup.symbolize_keys! - end - - def test_deep_symbolize_keys_preserves_fixnum_keys - assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys - assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys! - end - - def test_stringify_keys - assert_equal @strings, @symbols.stringify_keys - assert_equal @strings, @strings.stringify_keys - assert_equal @strings, @mixed.stringify_keys - end - - def test_stringify_keys_not_mutates - transformed_hash = @mixed.dup - transformed_hash.stringify_keys - assert_equal @mixed, transformed_hash - end - - def test_deep_stringify_keys - assert_equal @nested_strings, @nested_symbols.deep_stringify_keys - assert_equal @nested_strings, @nested_strings.deep_stringify_keys - assert_equal @nested_strings, @nested_mixed.deep_stringify_keys - end - - def test_deep_stringify_keys_not_mutates - transformed_hash = @nested_mixed.deep_dup - transformed_hash.deep_stringify_keys - assert_equal @nested_mixed, transformed_hash - end - - def test_stringify_keys! - assert_equal @strings, @symbols.dup.stringify_keys! - assert_equal @strings, @strings.dup.stringify_keys! - assert_equal @strings, @mixed.dup.stringify_keys! - end - - def test_stringify_keys_with_bang_mutates - transformed_hash = @mixed.dup - transformed_hash.stringify_keys! - assert_equal @strings, transformed_hash - assert_equal @mixed, { :a => 1, "b" => 2 } - end - - def test_deep_stringify_keys! - assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys! - assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys! - assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys! - end - - def test_deep_stringify_keys_with_bang_mutates - transformed_hash = @nested_mixed.deep_dup - transformed_hash.deep_stringify_keys! - assert_equal @nested_strings, transformed_hash - assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } - end - - def test_symbolize_keys_for_hash_with_indifferent_access - assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys - assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys - assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys - assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys - end - - def test_deep_symbolize_keys_for_hash_with_indifferent_access - assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys - assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys - assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys - assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys - end - - - def test_symbolize_keys_bang_for_hash_with_indifferent_access - assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! } - assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! } - assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! } - end - - def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access - assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } - assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! } - assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! } - end - - def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access - assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys - assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! } - end - - def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access - assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys - assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } - end - - def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access - assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys - assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! } - end - - def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access - assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys - assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! } - end - - def test_stringify_keys_for_hash_with_indifferent_access - assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys - assert_equal @strings, @symbols.with_indifferent_access.stringify_keys - assert_equal @strings, @strings.with_indifferent_access.stringify_keys - assert_equal @strings, @mixed.with_indifferent_access.stringify_keys - end - - def test_deep_stringify_keys_for_hash_with_indifferent_access - assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys - assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys - assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys - assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys - end - - def test_stringify_keys_bang_for_hash_with_indifferent_access - assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys! - assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys! - assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys! - assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys! - end - - def test_deep_stringify_keys_bang_for_hash_with_indifferent_access - assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys! - assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys! - assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys! - assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys! - end - - def test_nested_under_indifferent_access - foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access - assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"] - - foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access - assert_kind_of NonIndifferentHash, foo["foo"] - - foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access - assert_kind_of IndifferentHash, foo["foo"] - end - - def test_indifferent_assorted - @strings = @strings.with_indifferent_access - @symbols = @symbols.with_indifferent_access - @mixed = @mixed.with_indifferent_access - - assert_equal 'a', @strings.__send__(:convert_key, :a) - - assert_equal 1, @strings.fetch('a') - assert_equal 1, @strings.fetch(:a.to_s) - assert_equal 1, @strings.fetch(:a) - - hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed } - method_map = { :'[]' => 1, :fetch => 1, :values_at => [1], - :has_key? => true, :include? => true, :key? => true, - :member? => true } - - hashes.each do |name, hash| - method_map.sort_by { |m| m.to_s }.each do |meth, expected| - assert_equal(expected, hash.__send__(meth, 'a'), - "Calling #{name}.#{meth} 'a'") - assert_equal(expected, hash.__send__(meth, :a), - "Calling #{name}.#{meth} :a") - end - end - - assert_equal [1, 2], @strings.values_at('a', 'b') - assert_equal [1, 2], @strings.values_at(:a, :b) - assert_equal [1, 2], @symbols.values_at('a', 'b') - assert_equal [1, 2], @symbols.values_at(:a, :b) - assert_equal [1, 2], @mixed.values_at('a', 'b') - assert_equal [1, 2], @mixed.values_at(:a, :b) - end - - def test_indifferent_reading - hash = HashWithIndifferentAccess.new - hash["a"] = 1 - hash["b"] = true - hash["c"] = false - hash["d"] = nil - - assert_equal 1, hash[:a] - assert_equal true, hash[:b] - assert_equal false, hash[:c] - assert_equal nil, hash[:d] - assert_equal nil, hash[:e] - end - - def test_indifferent_reading_with_nonnil_default - hash = HashWithIndifferentAccess.new(1) - hash["a"] = 1 - hash["b"] = true - hash["c"] = false - hash["d"] = nil - - assert_equal 1, hash[:a] - assert_equal true, hash[:b] - assert_equal false, hash[:c] - assert_equal nil, hash[:d] - assert_equal 1, hash[:e] - end - - def test_indifferent_writing - hash = HashWithIndifferentAccess.new - hash[:a] = 1 - hash['b'] = 2 - hash[3] = 3 - - assert_equal hash['a'], 1 - assert_equal hash['b'], 2 - assert_equal hash[:a], 1 - assert_equal hash[:b], 2 - assert_equal hash[3], 3 - end - - def test_indifferent_update - hash = HashWithIndifferentAccess.new - hash[:a] = 'a' - hash['b'] = 'b' - - updated_with_strings = hash.update(@strings) - updated_with_symbols = hash.update(@symbols) - updated_with_mixed = hash.update(@mixed) - - assert_equal updated_with_strings[:a], 1 - assert_equal updated_with_strings['a'], 1 - assert_equal updated_with_strings['b'], 2 - - assert_equal updated_with_symbols[:a], 1 - assert_equal updated_with_symbols['b'], 2 - assert_equal updated_with_symbols[:b], 2 - - assert_equal updated_with_mixed[:a], 1 - assert_equal updated_with_mixed['b'], 2 - - assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 } - end - - def test_update_with_to_hash_conversion - hash = HashWithIndifferentAccess.new - hash.update HashByConversion.new({ :a => 1 }) - assert_equal hash['a'], 1 - end - - def test_indifferent_merging - hash = HashWithIndifferentAccess.new - hash[:a] = 'failure' - hash['b'] = 'failure' - - other = { 'a' => 1, :b => 2 } - - merged = hash.merge(other) - - assert_equal HashWithIndifferentAccess, merged.class - assert_equal 1, merged[:a] - assert_equal 2, merged['b'] - - hash.update(other) - - assert_equal 1, hash[:a] - assert_equal 2, hash['b'] - end - - def test_merge_with_to_hash_conversion - hash = HashWithIndifferentAccess.new - merged = hash.merge HashByConversion.new({ :a => 1 }) - assert_equal merged['a'], 1 - end - - def test_indifferent_replace - hash = HashWithIndifferentAccess.new - hash[:a] = 42 - - replaced = hash.replace(b: 12) - - assert hash.key?('b') - assert !hash.key?(:a) - assert_equal 12, hash[:b] - assert_same hash, replaced - end - - def test_replace_with_to_hash_conversion - hash = HashWithIndifferentAccess.new - hash[:a] = 42 - - replaced = hash.replace(HashByConversion.new(b: 12)) - - assert hash.key?('b') - assert !hash.key?(:a) - assert_equal 12, hash[:b] - assert_same hash, replaced - end - - def test_indifferent_merging_with_block - hash = HashWithIndifferentAccess.new - hash[:a] = 1 - hash['b'] = 3 - - other = { 'a' => 4, :b => 2, 'c' => 10 } - - merged = hash.merge(other) { |key, old, new| old > new ? old : new } - - assert_equal HashWithIndifferentAccess, merged.class - assert_equal 4, merged[:a] - assert_equal 3, merged['b'] - assert_equal 10, merged[:c] - - other_indifferent = HashWithIndifferentAccess.new('a' => 9, :b => 2) - - merged = hash.merge(other_indifferent) { |key, old, new| old + new } - - assert_equal HashWithIndifferentAccess, merged.class - assert_equal 10, merged[:a] - assert_equal 5, merged[:b] - end - - def test_indifferent_reverse_merging - hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value') - hash.reverse_merge!(:some => 'noclobber', :another => 'clobber') - assert_equal 'value', hash[:some] - assert_equal 'clobber', hash[:another] - end - - def test_indifferent_deleting - get_hash = proc{ { :a => 'foo' }.with_indifferent_access } - hash = get_hash.call - assert_equal hash.delete(:a), 'foo' - assert_equal hash.delete(:a), nil - hash = get_hash.call - assert_equal hash.delete('a'), 'foo' - assert_equal hash.delete('a'), nil - end - - def test_indifferent_select - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| v == 1} - - assert_equal({ 'a' => 1 }, hash) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash - end - - def test_indifferent_select_returns_a_hash_when_unchanged - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true} - - assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash - end - - def test_indifferent_select_bang - indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) - indifferent_strings.select! {|k,v| v == 1} - - assert_equal({ 'a' => 1 }, indifferent_strings) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings - end - - def test_indifferent_reject - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject {|k,v| v != 1} - - assert_equal({ 'a' => 1 }, hash) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash - end - - def test_indifferent_reject_bang - indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) - indifferent_strings.reject! {|k,v| v != 1} - - assert_equal({ 'a' => 1 }, indifferent_strings) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings - end - - def test_indifferent_to_hash - # Should convert to a Hash with String keys. - assert_equal @strings, @mixed.with_indifferent_access.to_hash - - # Should preserve the default value. - mixed_with_default = @mixed.dup - mixed_with_default.default = '1234' - roundtrip = mixed_with_default.with_indifferent_access.to_hash - assert_equal @strings, roundtrip - assert_equal '1234', roundtrip.default - new_to_hash = @nested_mixed.with_indifferent_access.to_hash - assert_not new_to_hash.instance_of?(HashWithIndifferentAccess) - assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess) - assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess) - end - - def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access - hash = HashWithIndifferentAccess.new {|h, k| h[k] = []} - hash[:a] << 1 - - assert_equal [1], hash[:a] - end - - def test_with_indifferent_access_has_no_side_effects_on_existing_hash - hash = {content: [{:foo => :bar, 'bar' => 'baz'}]} - hash.with_indifferent_access - - assert_equal [:foo, "bar"], hash[:content].first.keys - end - - def test_indifferent_hash_with_array_of_hashes - hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access - assert_equal "1", hash[:urls][:url].first[:address] - - hash = hash.to_hash - assert_not hash.instance_of?(HashWithIndifferentAccess) - assert_not hash["urls"].instance_of?(HashWithIndifferentAccess) - assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess) - end - - def test_should_preserve_array_subclass_when_value_is_array - array = SubclassingArray.new - array << { "address" => "1" } - hash = { "urls" => { "url" => array }}.with_indifferent_access - assert_equal SubclassingArray, hash[:urls][:url].class - end - - def test_should_preserve_array_class_when_hash_value_is_frozen_array - array = SubclassingArray.new - array << { "address" => "1" } - hash = { "urls" => { "url" => array.freeze }}.with_indifferent_access - assert_equal SubclassingArray, hash[:urls][:url].class - end - - def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash - h = HashWithIndifferentAccess.new - h[:first] = 1 - h = h.stringify_keys - assert_equal 1, h['first'] - h = HashWithIndifferentAccess.new - h['first'] = 1 - h = h.symbolize_keys - assert_equal 1, h[:first] - end - - def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash - h = HashWithIndifferentAccess.new - h[:first] = 1 - h = h.deep_stringify_keys - assert_equal 1, h['first'] - h = HashWithIndifferentAccess.new - h['first'] = 1 - h = h.deep_symbolize_keys - assert_equal 1, h[:first] - end - - def test_to_options_on_indifferent_preserves_hash - h = HashWithIndifferentAccess.new - h['first'] = 1 - h.to_options! - assert_equal 1, h['first'] - end - - def test_indifferent_subhashes - h = {'user' => {'id' => 5}}.with_indifferent_access - ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} - - h = {:user => {:id => 5}}.with_indifferent_access - ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} - end - - def test_indifferent_duplication - # Should preserve default value - h = HashWithIndifferentAccess.new - h.default = '1234' - assert_equal h.default, h.dup.default - - # Should preserve class for subclasses - h = IndifferentHash.new - assert_equal h.class, h.dup.class - end - - def test_assert_valid_keys - assert_nothing_raised do - { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) - { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) - end - - exception = assert_raise ArgumentError do - { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) - end - assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message - - exception = assert_raise ArgumentError do - { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) - end - assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message - end - - def test_assorted_keys_not_stringified - original = {Object.new => 2, 1 => 2, [] => true} - indiff = original.with_indifferent_access - assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!") - end - - def test_deep_merge - hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } } - hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } - expected = { :a => 1, :b => "b", :c => { :c1 => 2, :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } } - assert_equal expected, hash_1.deep_merge(hash_2) - - hash_1.deep_merge!(hash_2) - assert_equal expected, hash_1 - end - - def test_deep_merge_with_block - hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } } - hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } - expected = { :a => [:a, "a", 1], :b => "b", :c => { :c1 => [:c1, "c1", 2], :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } } - assert_equal(expected, hash_1.deep_merge(hash_2) { |k,o,n| [k, o, n] }) - - hash_1.deep_merge!(hash_2) { |k,o,n| [k, o, n] } - assert_equal expected, hash_1 - end - - def test_deep_merge_on_indifferent_access - hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }) - hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }) - hash_3 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } - expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } } - assert_equal expected, hash_1.deep_merge(hash_2) - assert_equal expected, hash_1.deep_merge(hash_3) - - hash_1.deep_merge!(hash_2) - assert_equal expected, hash_1 - end - - def test_store_on_indifferent_access - hash = HashWithIndifferentAccess.new - hash.store(:test1, 1) - hash.store('test1', 11) - hash[:test2] = 2 - hash['test2'] = 22 - expected = { "test1" => 11, "test2" => 22 } - assert_equal expected, hash - end - - def test_constructor_on_indifferent_access - hash = HashWithIndifferentAccess[:foo, 1] - assert_equal 1, hash[:foo] - assert_equal 1, hash['foo'] - hash[:foo] = 3 - assert_equal 3, hash[:foo] - assert_equal 3, hash['foo'] - end - - def test_reverse_merge - defaults = { :a => "x", :b => "y", :c => 10 }.freeze - options = { :a => 1, :b => 2 } - expected = { :a => 1, :b => 2, :c => 10 } - - # Should merge defaults into options, creating a new hash. - assert_equal expected, options.reverse_merge(defaults) - assert_not_equal expected, options - - # Should merge! defaults into options, replacing options. - merged = options.dup - assert_equal expected, merged.reverse_merge!(defaults) - assert_equal expected, merged - - # Should be an alias for reverse_merge! - merged = options.dup - assert_equal expected, merged.reverse_update(defaults) - assert_equal expected, merged - end - - def test_slice - original = { :a => 'x', :b => 'y', :c => 10 } - expected = { :a => 'x', :b => 'y' } - - # Should return a new hash with only the given keys. - assert_equal expected, original.slice(:a, :b) - assert_not_equal expected, original - end - - def test_slice_inplace - original = { :a => 'x', :b => 'y', :c => 10 } - expected = { :c => 10 } - - # Should replace the hash with only the given keys. - assert_equal expected, original.slice!(:a, :b) - end - - def test_slice_with_an_array_key - original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" } - expected = { [:a, :b] => "an array key", :c => 10 } - - # Should return a new hash with only the given keys when given an array key. - assert_equal expected, original.slice([:a, :b], :c) - assert_not_equal expected, original - end - - def test_slice_inplace_with_an_array_key - original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" } - expected = { :a => 'x', :b => 'y' } - - # Should replace the hash with only the given keys when given an array key. - assert_equal expected, original.slice!([:a, :b], :c) - end - - def test_slice_with_splatted_keys - original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" } - expected = { :a => 'x', :b => "y" } - - # Should grab each of the splatted keys. - assert_equal expected, original.slice(*[:a, :b]) - end - - def test_indifferent_slice - original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access - expected = { :a => 'x', :b => 'y' }.with_indifferent_access - - [['a', 'b'], [:a, :b]].each do |keys| - # Should return a new hash with only the given keys. - assert_equal expected, original.slice(*keys), keys.inspect - assert_not_equal expected, original - end - end - - def test_indifferent_slice_inplace - original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access - expected = { :c => 10 }.with_indifferent_access - - [['a', 'b'], [:a, :b]].each do |keys| - # Should replace the hash with only the given keys. - copy = original.dup - assert_equal expected, copy.slice!(*keys) - end - end - - def test_indifferent_slice_access_with_symbols - original = {'login' => 'bender', 'password' => 'shiny', 'stuff' => 'foo'} - original = original.with_indifferent_access - - slice = original.slice(:login, :password) - - assert_equal 'bender', slice[:login] - assert_equal 'bender', slice['login'] - end - - def test_slice_bang_does_not_override_default - hash = Hash.new(0) - hash.update(a: 1, b: 2) - - hash.slice!(:a) - - assert_equal 0, hash[:c] - end - - def test_slice_bang_does_not_override_default_proc - hash = Hash.new { |h, k| h[k] = [] } - hash.update(a: 1, b: 2) - - hash.slice!(:a) - - assert_equal [], hash[:c] - end - - def test_extract - original = {:a => 1, :b => 2, :c => 3, :d => 4} - expected = {:a => 1, :b => 2} - remaining = {:c => 3, :d => 4} - - assert_equal expected, original.extract!(:a, :b, :x) - assert_equal remaining, original - end - - def test_extract_nils - original = {:a => nil, :b => nil} - expected = {:a => nil} - extracted = original.extract!(:a, :x) - - assert_equal expected, extracted - assert_equal nil, extracted[:a] - assert_equal nil, extracted[:x] - end - - def test_indifferent_extract - original = {:a => 1, 'b' => 2, :c => 3, 'd' => 4}.with_indifferent_access - expected = {:a => 1, :b => 2}.with_indifferent_access - remaining = {:c => 3, :d => 4}.with_indifferent_access - - [['a', 'b'], [:a, :b]].each do |keys| - copy = original.dup - assert_equal expected, copy.extract!(*keys) - assert_equal remaining, copy - end - end - - def test_except - original = { :a => 'x', :b => 'y', :c => 10 } - expected = { :a => 'x', :b => 'y' } - - # Should return a new hash without the given keys. - assert_equal expected, original.except(:c) - assert_not_equal expected, original - - # Should replace the hash without the given keys. - assert_equal expected, original.except!(:c) - assert_equal expected, original - end - - def test_except_with_more_than_one_argument - original = { :a => 'x', :b => 'y', :c => 10 } - expected = { :a => 'x' } - assert_equal expected, original.except(:b, :c) - end - - def test_except_with_original_frozen - original = { :a => 'x', :b => 'y' } - original.freeze - assert_nothing_raised { original.except(:a) } - end - - def test_except_with_mocha_expectation_on_original - original = { :a => 'x', :b => 'y' } - original.expects(:delete).never - original.except(:a) - end - - def test_compact - hash_contain_nil_value = @symbols.merge(z: nil) - hash_with_only_nil_values = { a: nil, b: nil } - - h = hash_contain_nil_value.dup - assert_equal(@symbols, h.compact) - assert_equal(hash_contain_nil_value, h) - - h = hash_with_only_nil_values.dup - assert_equal({}, h.compact) - assert_equal(hash_with_only_nil_values, h) - end - - def test_compact! - hash_contain_nil_value = @symbols.merge(z: nil) - hash_with_only_nil_values = { a: nil, b: nil } - - h = hash_contain_nil_value.dup - assert_equal(@symbols, h.compact!) - assert_equal(@symbols, h) - - h = hash_with_only_nil_values.dup - assert_equal({}, h.compact!) - assert_equal({}, h) - end - - def test_new_with_to_hash_conversion - hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1)) - assert hash.key?('a') - assert_equal 1, hash[:a] - end -end - -class IWriteMyOwnXML - def to_xml(options = {}) - options[:indent] ||= 2 - xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) - xml.instruct! unless options[:skip_instruct] - xml.level_one do - xml.tag!(:second_level, 'content') - end - end -end - -class HashExtToParamTests < ActiveSupport::TestCase - class ToParam < String - def to_param - "#{self}-1" - end - end - - def test_string_hash - assert_equal '', {}.to_param - assert_equal 'hello=world', { :hello => "world" }.to_param - assert_equal 'hello=10', { "hello" => 10 }.to_param - assert_equal 'hello=world&say_bye=true', {:hello => "world", "say_bye" => true}.to_param - end - - def test_number_hash - assert_equal '10=20&30=40&50=60', {10 => 20, 30 => 40, 50 => 60}.to_param - end - - def test_to_param_hash - assert_equal 'custom-1=param-1&custom2-1=param2-1', {ToParam.new('custom') => ToParam.new('param'), ToParam.new('custom2') => ToParam.new('param2')}.to_param - end - - def test_to_param_hash_escapes_its_keys_and_values - assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param - end - - def test_to_param_orders_by_key_in_ascending_order - assert_equal 'a=2&b=1&c=0', Hash[*%w(b 1 c 0 a 2)].to_param - end -end - -class HashToXmlTest < ActiveSupport::TestCase - def setup - @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 } - end - - def test_one_level - xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - end - - def test_one_level_dasherize_false - xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => false)) - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - end - - def test_one_level_dasherize_true - xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => true)) - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - end - - def test_one_level_camelize_true - xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => true)) - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - end - - def test_one_level_camelize_lower - xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => :lower)) - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - end - - def test_one_level_with_types - xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options) - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - assert xml.include?(%(26)) - assert xml.include?(%(820497600000)) - assert xml.include?(%(2005-11-15)) - assert xml.include?(%(yes)) - end - - def test_one_level_with_nils - xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options) - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - assert xml.include?(%()) - end - - def test_one_level_with_skipping_types - xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options.merge(:skip_types => true)) - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - assert xml.include?(%()) - end - - def test_one_level_with_yielding - xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) do |x| - x.creator("Rails") - end - - assert_equal "", xml.first(8) - assert xml.include?(%(Paulina)) - assert xml.include?(%(David)) - assert xml.include?(%(Rails)) - end - - def test_two_levels - xml = { :name => "David", :address => { :street => "Paulina" } }.to_xml(@xml_options) - assert_equal "", xml.first(8) - assert xml.include?(%(
Paulina
)) - assert xml.include?(%(David)) - end - - def test_two_levels_with_second_level_overriding_to_xml - xml = { :name => "David", :address => { :street => "Paulina" }, :child => IWriteMyOwnXML.new }.to_xml(@xml_options) - assert_equal "", xml.first(8) - assert xml.include?(%(
Paulina
)) - assert xml.include?(%(content)) - end - - def test_two_levels_with_array - xml = { :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] }.to_xml(@xml_options) - assert_equal "", xml.first(8) - assert xml.include?(%(
)) - assert xml.include?(%(
Paulina
)) - assert xml.include?(%(
Evergreen
)) - assert xml.include?(%(David)) - end - - def test_three_levels_with_array - xml = { :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] }.to_xml(@xml_options) - assert xml.include?(%(
)) - end - - def test_timezoned_attributes - xml = { - :created_at => Time.utc(1999,2,2), - :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)') - }.to_xml(@xml_options) - assert_match %r{1999-02-02T00:00:00Z}, xml - assert_match %r{1999-02-01T19:00:00-05:00}, xml - end - - def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding - topics_xml = <<-EOT - - - The First Topic - David - 1 - false - 0 - 2592000000 - 2003-07-16 - 2003-07-16T09:28:00+0000 - Have a nice day - david@loudthinking.com - - - - The Second Topic - Jason - 1 - false - 0 - 2592000000 - 2003-07-16 - 2003-07-16T09:28:00+0000 - Have a nice day - david@loudthinking.com - - - - EOT - - expected_topic_hash = { - :title => "The First Topic", - :author_name => "David", - :id => 1, - :approved => false, - :replies_count => 0, - :replies_close_in => 2592000000, - :written_on => Date.new(2003, 7, 16), - :viewed_at => Time.utc(2003, 7, 16, 9, 28), - :content => "Have a nice day", - :author_email_address => "david@loudthinking.com", - :parent_id => nil - }.stringify_keys - - assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first - end - - def test_single_record_from_xml - topic_xml = <<-EOT - - The First Topic - David - 1 - true - 0 - 2592000000 - 2003-07-16 - 2003-07-16T09:28:00+0000 - david@loudthinking.com - - 1.5 - 135 - - EOT - - expected_topic_hash = { - :title => "The First Topic", - :author_name => "David", - :id => 1, - :approved => true, - :replies_count => 0, - :replies_close_in => 2592000000, - :written_on => Date.new(2003, 7, 16), - :viewed_at => Time.utc(2003, 7, 16, 9, 28), - :author_email_address => "david@loudthinking.com", - :parent_id => nil, - :ad_revenue => BigDecimal("1.50"), - :optimum_viewing_angle => 135.0, - }.stringify_keys - - assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"] - end - - def test_single_record_from_xml_with_nil_values - topic_xml = <<-EOT - - - - - - - - - EOT - - expected_topic_hash = { - :title => nil, - :id => nil, - :approved => nil, - :written_on => nil, - :viewed_at => nil, - :parent_id => nil - }.stringify_keys - - assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"] - end - - def test_multiple_records_from_xml - topics_xml = <<-EOT - - - The First Topic - David - 1 - false - 0 - 2592000000 - 2003-07-16 - 2003-07-16T09:28:00+0000 - Have a nice day - david@loudthinking.com - - - - The Second Topic - Jason - 1 - false - 0 - 2592000000 - 2003-07-16 - 2003-07-16T09:28:00+0000 - Have a nice day - david@loudthinking.com - - - - EOT - - expected_topic_hash = { - :title => "The First Topic", - :author_name => "David", - :id => 1, - :approved => false, - :replies_count => 0, - :replies_close_in => 2592000000, - :written_on => Date.new(2003, 7, 16), - :viewed_at => Time.utc(2003, 7, 16, 9, 28), - :content => "Have a nice day", - :author_email_address => "david@loudthinking.com", - :parent_id => nil - }.stringify_keys - - assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first - end - - def test_single_record_from_xml_with_attributes_other_than_type - topic_xml = <<-EOT - - - - - - EOT - - expected_topic_hash = { - :id => "175756086", - :owner => "55569174@N00", - :secret => "0279bf37a1", - :server => "76", - :title => "Colored Pencil PhotoBooth Fun", - :ispublic => "1", - :isfriend => "0", - :isfamily => "0", - }.stringify_keys - - assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"] - end - - def test_all_caps_key_from_xml - test_xml = <<-EOT - - Lorem Ipsum - - EOT - - expected_hash = { - "ABC3XYZ" => { - "TEST" => "Lorem Ipsum" - } - } - - assert_equal expected_hash, Hash.from_xml(test_xml) - end - - def test_empty_array_from_xml - blog_xml = <<-XML - - - - XML - expected_blog_hash = {"blog" => {"posts" => []}} - assert_equal expected_blog_hash, Hash.from_xml(blog_xml) - end - - def test_empty_array_with_whitespace_from_xml - blog_xml = <<-XML - - - - - XML - expected_blog_hash = {"blog" => {"posts" => []}} - assert_equal expected_blog_hash, Hash.from_xml(blog_xml) - end - - def test_array_with_one_entry_from_xml - blog_xml = <<-XML - - - a post - - - XML - expected_blog_hash = {"blog" => {"posts" => ["a post"]}} - assert_equal expected_blog_hash, Hash.from_xml(blog_xml) - end - - def test_array_with_multiple_entries_from_xml - blog_xml = <<-XML - - - a post - another post - - - XML - expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}} - assert_equal expected_blog_hash, Hash.from_xml(blog_xml) - end - - def test_file_from_xml - blog_xml = <<-XML - - - - - XML - hash = Hash.from_xml(blog_xml) - assert hash.has_key?('blog') - assert hash['blog'].has_key?('logo') - - file = hash['blog']['logo'] - assert_equal 'logo.png', file.original_filename - assert_equal 'image/png', file.content_type - end - - def test_file_from_xml_with_defaults - blog_xml = <<-XML - - - - - XML - file = Hash.from_xml(blog_xml)['blog']['logo'] - assert_equal 'untitled', file.original_filename - assert_equal 'application/octet-stream', file.content_type - end - - def test_tag_with_attrs_and_whitespace - xml = <<-XML - - - XML - hash = Hash.from_xml(xml) - assert_equal "bacon is the best", hash['blog']['name'] - end - - def test_empty_cdata_from_xml - xml = "" - - assert_equal "", Hash.from_xml(xml)["data"] - end - - def test_xsd_like_types_from_xml - bacon_xml = <<-EOT - - 0.5 - 12.50 - 1 - 2007-12-25T12:34:56+0000 - - YmFiZS5wbmc= - VGhhdCdsbCBkbywgcGlnLg== - - EOT - - expected_bacon_hash = { - :weight => 0.5, - :chunky => true, - :price => BigDecimal("12.50"), - :expires_at => Time.utc(2007,12,25,12,34,56), - :notes => "", - :illustration => "babe.png", - :caption => "That'll do, pig." - }.stringify_keys - - assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"] - end - - def test_type_trickles_through_when_unknown - product_xml = <<-EOT - - 0.5 - image.gif - - - EOT - - expected_product_hash = { - :weight => 0.5, - :image => {'type' => 'ProductImage', 'filename' => 'image.gif' }, - }.stringify_keys - - assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"] - end - - def test_from_xml_raises_on_disallowed_type_attributes - assert_raise ActiveSupport::XMLConverter::DisallowedType do - Hash.from_xml 'value', %w(foo) - end - end - - def test_from_xml_disallows_symbol_and_yaml_types_by_default - assert_raise ActiveSupport::XMLConverter::DisallowedType do - Hash.from_xml 'value' - end - - assert_raise ActiveSupport::XMLConverter::DisallowedType do - Hash.from_xml 'value' - end - end - - def test_from_trusted_xml_allows_symbol_and_yaml_types - expected = { 'product' => { 'name' => :value }} - assert_equal expected, Hash.from_trusted_xml('value') - assert_equal expected, Hash.from_trusted_xml(':value') - end - - def test_should_use_default_value_for_unknown_key - hash_wia = HashWithIndifferentAccess.new(3) - assert_equal 3, hash_wia[:new_key] - end - - def test_should_use_default_value_if_no_key_is_supplied - hash_wia = HashWithIndifferentAccess.new(3) - assert_equal 3, hash_wia.default - end - - def test_should_nil_if_no_default_value_is_supplied - hash_wia = HashWithIndifferentAccess.new - assert_nil hash_wia.default - end - - def test_should_return_dup_for_with_indifferent_access - hash_wia = HashWithIndifferentAccess.new - assert_equal hash_wia, hash_wia.with_indifferent_access - assert_not_same hash_wia, hash_wia.with_indifferent_access - end - - def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access - hash = Hash.new(3) - hash_wia = hash.with_indifferent_access - assert_equal 3, hash_wia.default - end - - # The XML builder seems to fail miserably when trying to tag something - # with the same name as a Kernel method (throw, test, loop, select ...) - def test_kernel_method_names_to_xml - hash = { :throw => { :ball => 'red' } } - expected = 'red' - - assert_nothing_raised do - assert_equal expected, hash.to_xml(@xml_options) - end - end - - def test_empty_string_works_for_typecast_xml_value - assert_nothing_raised do - ActiveSupport::XMLConverter.new("").to_h - end - end - - def test_escaping_to_xml - hash = { - :bare_string => 'First & Last Name', - :pre_escaped_string => 'First & Last Name' - }.stringify_keys - - expected_xml = 'First & Last NameFirst &amp; Last Name' - assert_equal expected_xml, hash.to_xml(@xml_options) - end - - def test_unescaping_from_xml - xml_string = 'First & Last NameFirst &amp; Last Name' - expected_hash = { - :bare_string => 'First & Last Name', - :pre_escaped_string => 'First & Last Name' - }.stringify_keys - assert_equal expected_hash, Hash.from_xml(xml_string)['person'] - end - - def test_roundtrip_to_xml_from_xml - hash = { - :bare_string => 'First & Last Name', - :pre_escaped_string => 'First & Last Name' - }.stringify_keys - - assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person'] - end - - def test_datetime_xml_type_with_utc_time - alert_xml = <<-XML - - 2008-02-10T15:30:45Z - - XML - alert_at = Hash.from_xml(alert_xml)['alert']['alert_at'] - assert alert_at.utc? - assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at - end - - def test_datetime_xml_type_with_non_utc_time - alert_xml = <<-XML - - 2008-02-10T10:30:45-05:00 - - XML - alert_at = Hash.from_xml(alert_xml)['alert']['alert_at'] - assert alert_at.utc? - assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at - end - - def test_datetime_xml_type_with_far_future_date - alert_xml = <<-XML - - 2050-02-10T15:30:45Z - - XML - alert_at = Hash.from_xml(alert_xml)['alert']['alert_at'] - assert alert_at.utc? - assert_equal 2050, alert_at.year - assert_equal 2, alert_at.month - assert_equal 10, alert_at.day - assert_equal 15, alert_at.hour - assert_equal 30, alert_at.min - assert_equal 45, alert_at.sec - end - - def test_to_xml_dups_options - options = {:skip_instruct => true} - {}.to_xml(options) - # :builder, etc, shouldn't be added to options - assert_equal({:skip_instruct => true}, options) - end - - def test_expansion_count_is_limited - expected = - case ActiveSupport::XmlMini.backend.name - when 'ActiveSupport::XmlMini_REXML'; RuntimeError - when 'ActiveSupport::XmlMini_Nokogiri'; Nokogiri::XML::SyntaxError - when 'ActiveSupport::XmlMini_NokogiriSAX'; RuntimeError - when 'ActiveSupport::XmlMini_LibXML'; LibXML::XML::Error - when 'ActiveSupport::XmlMini_LibXMLSAX'; LibXML::XML::Error - end - - assert_raise expected do - attack_xml = <<-EOT - - - - - - - - - ]> - - &a; - - EOT - Hash.from_xml(attack_xml) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/integer_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/integer_ext_test.rb deleted file mode 100644 index 41736fb672..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/integer_ext_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/integer' - -class IntegerExtTest < ActiveSupport::TestCase - PRIME = 22953686867719691230002707821868552601124472329079 - - def test_multiple_of - [ -7, 0, 7, 14 ].each { |i| assert i.multiple_of?(7) } - [ -7, 7, 14 ].each { |i| assert ! i.multiple_of?(6) } - - # test the 0 edge case - assert 0.multiple_of?(0) - assert !5.multiple_of?(0) - - # test with a prime - [2, 3, 5, 7].each { |i| assert !PRIME.multiple_of?(i) } - end - - def test_ordinalize - # These tests are mostly just to ensure that the ordinalize method exists. - # Its results are tested comprehensively in the inflector test cases. - assert_equal '1st', 1.ordinalize - assert_equal '8th', 8.ordinalize - end - - def test_ordinal - assert_equal 'st', 1.ordinal - assert_equal 'th', 8.ordinal - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/kernel/concern_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/kernel/concern_test.rb deleted file mode 100644 index 9b1fdda3b0..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/kernel/concern_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/kernel/concern' - -class KernelConcernTest < ActiveSupport::TestCase - def test_may_be_defined_at_toplevel - mod = ::TOPLEVEL_BINDING.eval 'concern(:ToplevelConcern) { }' - assert_equal mod, ::ToplevelConcern - assert_kind_of ActiveSupport::Concern, ::ToplevelConcern - assert !Object.ancestors.include?(::ToplevelConcern), mod.ancestors.inspect - Object.send :remove_const, :ToplevelConcern - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/kernel_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/kernel_test.rb deleted file mode 100644 index 18b251173f..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/kernel_test.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/kernel' - -class KernelTest < ActiveSupport::TestCase - def test_silence_warnings - silence_warnings { assert_nil $VERBOSE } - assert_equal 1234, silence_warnings { 1234 } - end - - def test_silence_warnings_verbose_invariant - old_verbose = $VERBOSE - silence_warnings { raise } - flunk - rescue - assert_equal old_verbose, $VERBOSE - end - - - def test_enable_warnings - enable_warnings { assert_equal true, $VERBOSE } - assert_equal 1234, enable_warnings { 1234 } - end - - def test_enable_warnings_verbose_invariant - old_verbose = $VERBOSE - enable_warnings { raise } - flunk - rescue - assert_equal old_verbose, $VERBOSE - end - - - def test_silence_stderr - old_stderr_position = STDERR.tell - silence_stderr { STDERR.puts 'hello world' } - assert_equal old_stderr_position, STDERR.tell - rescue Errno::ESPIPE - # Skip if we can't STDERR.tell - end - - def test_silence_stream - old_stream_position = STDOUT.tell - silence_stream(STDOUT) { STDOUT.puts 'hello world' } - assert_equal old_stream_position, STDOUT.tell - rescue Errno::ESPIPE - # Skip if we can't stream.tell - end - - def test_silence_stream_closes_file_descriptors - stream = StringIO.new - dup_stream = StringIO.new - stream.stubs(:dup).returns(dup_stream) - dup_stream.expects(:close) - silence_stream(stream) { stream.puts 'hello world' } - end - - def test_quietly - old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell - quietly do - puts 'see me, feel me' - STDERR.puts 'touch me, heal me' - end - assert_equal old_stdout_position, STDOUT.tell - assert_equal old_stderr_position, STDERR.tell - rescue Errno::ESPIPE - # Skip if we can't STDERR.tell - end - - def test_silence_stderr_with_return_value - assert_equal 1, silence_stderr { 1 } - end - - def test_class_eval - o = Object.new - class << o; @x = 1; end - assert_equal 1, o.class_eval { @x } - end - - def test_capture - assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' } - assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' } - assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') } - assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') } - end -end - -class KernelSuppressTest < ActiveSupport::TestCase - def test_reraise - assert_raise(LoadError) do - suppress(ArgumentError) { raise LoadError } - end - end - - def test_suppression - suppress(ArgumentError) { raise ArgumentError } - suppress(LoadError) { raise LoadError } - suppress(LoadError, ArgumentError) { raise LoadError } - suppress(LoadError, ArgumentError) { raise ArgumentError } - end -end - -class MockStdErr - attr_reader :output - def puts(message) - @output ||= [] - @output << message - end - - def info(message) - puts(message) - end - - def write(message) - puts(message) - end -end - -class KernelDebuggerTest < ActiveSupport::TestCase - def test_debugger_not_available_message_to_stderr - old_stderr = $stderr - $stderr = MockStdErr.new - debugger - assert_match(/Debugger requested/, $stderr.output.first) - ensure - $stderr = old_stderr - end - - def test_debugger_not_available_message_to_rails_logger - rails = Class.new do - def self.logger - @logger ||= MockStdErr.new - end - end - Object.const_set(:Rails, rails) - debugger - assert_match(/Debugger requested/, rails.logger.output.first) - ensure - Object.send(:remove_const, :Rails) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/load_error_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/load_error_test.rb deleted file mode 100644 index 31863d0aca..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/load_error_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/load_error' - -class TestMissingSourceFile < ActiveSupport::TestCase - def test_with_require - assert_raise(MissingSourceFile) { require 'no_this_file_don\'t_exist' } - end - def test_with_load - assert_raise(MissingSourceFile) { load 'nor_does_this_one' } - end - def test_path - begin load 'nor/this/one.rb' - rescue MissingSourceFile => e - assert_equal 'nor/this/one.rb', e.path - end - end -end - -class TestLoadError < ActiveSupport::TestCase - def test_with_require - assert_raise(LoadError) { require 'no_this_file_don\'t_exist' } - end - def test_with_load - assert_raise(LoadError) { load 'nor_does_this_one' } - end - def test_path - begin load 'nor/this/one.rb' - rescue LoadError => e - assert_equal 'nor/this/one.rb', e.path - end - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/marshal_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/marshal_test.rb deleted file mode 100644 index 8f3f710dfd..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/marshal_test.rb +++ /dev/null @@ -1,124 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/marshal' -require 'dependencies_test_helpers' - -class MarshalTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - include DependenciesTestHelpers - - def teardown - ActiveSupport::Dependencies.clear - remove_constants(:E, :ClassFolder) - end - - test "that Marshal#load still works" do - sanity_data = ["test", [1, 2, 3], {a: [1, 2, 3]}, ActiveSupport::TestCase] - sanity_data.each do |obj| - dumped = Marshal.dump(obj) - assert_equal Marshal.load_without_autoloading(dumped), Marshal.load(dumped) - end - end - - test "that a missing class is autoloaded from string" do - dumped = nil - with_autoloading_fixtures do - dumped = Marshal.dump(E.new) - end - - remove_constants(:E) - ActiveSupport::Dependencies.clear - - with_autoloading_fixtures do - assert_kind_of E, Marshal.load(dumped) - end - end - - test "that classes in sub modules work" do - dumped = nil - with_autoloading_fixtures do - dumped = Marshal.dump(ClassFolder::ClassFolderSubclass.new) - end - - remove_constants(:ClassFolder) - ActiveSupport::Dependencies.clear - - with_autoloading_fixtures do - assert_kind_of ClassFolder::ClassFolderSubclass, Marshal.load(dumped) - end - end - - test "that more than one missing class is autoloaded" do - dumped = nil - with_autoloading_fixtures do - dumped = Marshal.dump([E.new, ClassFolder.new]) - end - - remove_constants(:E, :ClassFolder) - ActiveSupport::Dependencies.clear - - with_autoloading_fixtures do - loaded = Marshal.load(dumped) - assert_equal 2, loaded.size - assert_kind_of E, loaded[0] - assert_kind_of ClassFolder, loaded[1] - end - end - - test "that a real missing class is causing an exception" do - dumped = nil - with_autoloading_fixtures do - dumped = Marshal.dump(E.new) - end - - remove_constants(:E) - ActiveSupport::Dependencies.clear - - assert_raise(NameError) do - Marshal.load(dumped) - end - end - - test "when first class is autoloaded and second not" do - dumped = nil - class SomeClass - end - - with_autoloading_fixtures do - dumped = Marshal.dump([E.new, SomeClass.new]) - end - - remove_constants(:E) - self.class.send(:remove_const, :SomeClass) - ActiveSupport::Dependencies.clear - - with_autoloading_fixtures do - assert_raise(NameError) do - Marshal.load(dumped) - end - - assert_nothing_raised("E failed to load while we expect only SomeClass to fail loading") do - E.new - end - - assert_raise(NameError, "We expected SomeClass to not be loaded but it is!") do - SomeClass.new - end - end - end - - test "loading classes from files trigger autoloading" do - Tempfile.open("object_serializer_test") do |f| - with_autoloading_fixtures do - Marshal.dump(E.new, f) - end - - f.rewind - remove_constants(:E) - ActiveSupport::Dependencies.clear - - with_autoloading_fixtures do - assert_kind_of E, Marshal.load(f) - end - end - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module/anonymous_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module/anonymous_test.rb deleted file mode 100644 index cb556af772..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module/anonymous_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/anonymous' - -class AnonymousTest < ActiveSupport::TestCase - test "an anonymous class or module are anonymous" do - assert Module.new.anonymous? - assert Class.new.anonymous? - end - - test "a named class or module are not anonymous" do - assert !Kernel.anonymous? - assert !Object.anonymous? - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module/attr_internal_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module/attr_internal_test.rb deleted file mode 100644 index 2aea14cf2b..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module/attr_internal_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/attr_internal' - -class AttrInternalTest < ActiveSupport::TestCase - def setup - @target = Class.new - @instance = @target.new - end - - def test_reader - assert_nothing_raised { @target.attr_internal_reader :foo } - - assert !@instance.instance_variable_defined?('@_foo') - assert_raise(NoMethodError) { @instance.foo = 1 } - - @instance.instance_variable_set('@_foo', 1) - assert_nothing_raised { assert_equal 1, @instance.foo } - end - - def test_writer - assert_nothing_raised { @target.attr_internal_writer :foo } - - assert !@instance.instance_variable_defined?('@_foo') - assert_nothing_raised { assert_equal 1, @instance.foo = 1 } - - assert_equal 1, @instance.instance_variable_get('@_foo') - assert_raise(NoMethodError) { @instance.foo } - end - - def test_accessor - assert_nothing_raised { @target.attr_internal :foo } - - assert !@instance.instance_variable_defined?('@_foo') - assert_nothing_raised { assert_equal 1, @instance.foo = 1 } - - assert_equal 1, @instance.instance_variable_get('@_foo') - assert_nothing_raised { assert_equal 1, @instance.foo } - end - - def test_naming_format - assert_equal '@_%s', Module.attr_internal_naming_format - assert_nothing_raised { Module.attr_internal_naming_format = '@abc%sdef' } - @target.attr_internal :foo - - assert !@instance.instance_variable_defined?('@_foo') - assert !@instance.instance_variable_defined?('@abcfoodef') - assert_nothing_raised { @instance.foo = 1 } - assert !@instance.instance_variable_defined?('@_foo') - assert @instance.instance_variable_defined?('@abcfoodef') - ensure - Module.attr_internal_naming_format = '@_%s' - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module/attribute_accessor_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module/attribute_accessor_test.rb deleted file mode 100644 index 48f3cc579f..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/attribute_accessors' - -class ModuleAttributeAccessorTest < ActiveSupport::TestCase - def setup - m = @module = Module.new do - mattr_accessor :foo - mattr_accessor :bar, :instance_writer => false - mattr_reader :shaq, :instance_reader => false - mattr_accessor :camp, :instance_accessor => false - - cattr_accessor(:defa) { 'default_accessor_value' } - cattr_reader(:defr) { 'default_reader_value' } - cattr_writer(:defw) { 'default_writer_value' } - cattr_accessor(:quux) { :quux } - end - @class = Class.new - @class.instance_eval { include m } - @object = @class.new - end - - def test_should_use_mattr_default - assert_nil @module.foo - assert_nil @object.foo - end - - def test_should_set_mattr_value - @module.foo = :test - assert_equal :test, @object.foo - - @object.foo = :test2 - assert_equal :test2, @module.foo - end - - def test_cattr_accessor_default_value - assert_equal :quux, @module.quux - assert_equal :quux, @object.quux - end - - def test_should_not_create_instance_writer - assert_respond_to @module, :foo - assert_respond_to @module, :foo= - assert_respond_to @object, :bar - assert !@object.respond_to?(:bar=) - end - - def test_should_not_create_instance_reader - assert_respond_to @module, :shaq - assert !@object.respond_to?(:shaq) - end - - def test_should_not_create_instance_accessors - assert_respond_to @module, :camp - assert !@object.respond_to?(:camp) - assert !@object.respond_to?(:camp=) - end - - def test_should_raise_name_error_if_attribute_name_is_invalid - exception = assert_raises NameError do - Class.new do - cattr_reader "1nvalid" - end - end - assert_equal "invalid attribute name: 1nvalid", exception.message - - exception = assert_raises NameError do - Class.new do - cattr_writer "1nvalid" - end - end - assert_equal "invalid attribute name: 1nvalid", exception.message - end - - def test_should_use_default_value_if_block_passed - assert_equal 'default_accessor_value', @module.defa - assert_equal 'default_reader_value', @module.defr - assert_equal 'default_writer_value', @module.class_variable_get('@@defw') - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module/attribute_aliasing_test.rb deleted file mode 100644 index 29c3053b47..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module/attribute_aliasing_test.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/aliasing' - -module AttributeAliasing - class Content - attr_accessor :title, :Data - - def initialize - @title, @Data = nil, nil - end - - def title? - !title.nil? - end - - def Data? - !self.Data.nil? - end - end - - class Email < Content - alias_attribute :subject, :title - alias_attribute :body, :Data - end -end - -class AttributeAliasingTest < ActiveSupport::TestCase - def test_attribute_alias - e = AttributeAliasing::Email.new - - assert !e.subject? - - e.title = "Upgrade computer" - assert_equal "Upgrade computer", e.subject - assert e.subject? - - e.subject = "We got a long way to go" - assert_equal "We got a long way to go", e.title - assert e.title? - end - - def test_aliasing_to_uppercase_attributes - # Although it's very un-Ruby, some people's AR-mapped tables have - # upper-case attributes, and when people want to alias those names - # to more sensible ones, everything goes *foof*. - e = AttributeAliasing::Email.new - - assert !e.body? - assert !e.Data? - - e.body = "No, really, this is not a joke." - assert_equal "No, really, this is not a joke.", e.Data - assert e.Data? - - e.Data = "Uppercased methods are teh suck" - assert_equal "Uppercased methods are teh suck", e.body - assert e.body? - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module/concerning_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module/concerning_test.rb deleted file mode 100644 index 07d860b71c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module/concerning_test.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/concerning' - -class ModuleConcerningTest < ActiveSupport::TestCase - def test_concerning_declares_a_concern_and_includes_it_immediately - klass = Class.new { concerning(:Foo) { } } - assert klass.ancestors.include?(klass::Foo), klass.ancestors.inspect - end -end - -class ModuleConcernTest < ActiveSupport::TestCase - def test_concern_creates_a_module_extended_with_active_support_concern - klass = Class.new do - concern :Baz do - included { @foo = 1 } - def should_be_public; end - end - end - - # Declares a concern but doesn't include it - assert klass.const_defined?(:Baz, false) - assert !ModuleConcernTest.const_defined?(:Baz) - assert_kind_of ActiveSupport::Concern, klass::Baz - assert !klass.ancestors.include?(klass::Baz), klass.ancestors.inspect - - # Public method visibility by default - assert klass::Baz.public_instance_methods.map(&:to_s).include?('should_be_public') - - # Calls included hook - assert_equal 1, Class.new { include klass::Baz }.instance_variable_get('@foo') - end - - class Foo - concerning :Bar do - module ClassMethods - def will_be_orphaned; end - end - - const_set :ClassMethods, Module.new { - def hacked_on; end - } - - # Doesn't overwrite existing ClassMethods module. - class_methods do - def nicer_dsl; end - end - - # Doesn't overwrite previous class_methods definitions. - class_methods do - def doesnt_clobber; end - end - end - end - - def test_using_class_methods_blocks_instead_of_ClassMethods_module - assert !Foo.respond_to?(:will_be_orphaned) - assert Foo.respond_to?(:hacked_on) - assert Foo.respond_to?(:nicer_dsl) - assert Foo.respond_to?(:doesnt_clobber) - - # Orphan in Foo::ClassMethods, not Bar::ClassMethods. - assert Foo.const_defined?(:ClassMethods) - assert Foo::ClassMethods.method_defined?(:will_be_orphaned) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module/qualified_const_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module/qualified_const_test.rb deleted file mode 100644 index 37c9228a64..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module/qualified_const_test.rb +++ /dev/null @@ -1,108 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/qualified_const' - -module QualifiedConstTestMod - X = false - - module M - X = 1 - - class C - X = 2 - end - end - - module N - include M - end -end - -class QualifiedConstTest < ActiveSupport::TestCase - test "Object.qualified_const_defined?" do - assert Object.qualified_const_defined?("QualifiedConstTestMod") - assert !Object.qualified_const_defined?("NonExistingQualifiedConstTestMod") - - assert Object.qualified_const_defined?("QualifiedConstTestMod::X") - assert !Object.qualified_const_defined?("QualifiedConstTestMod::Y") - - assert Object.qualified_const_defined?("QualifiedConstTestMod::M::X") - assert !Object.qualified_const_defined?("QualifiedConstTestMod::M::Y") - - if Module.method(:const_defined?).arity == 1 - assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X") - else - assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X") - assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X", false) - assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X", true) - end - end - - test "mod.qualified_const_defined?" do - assert QualifiedConstTestMod.qualified_const_defined?("M") - assert !QualifiedConstTestMod.qualified_const_defined?("NonExistingM") - - assert QualifiedConstTestMod.qualified_const_defined?("M::X") - assert !QualifiedConstTestMod.qualified_const_defined?("M::Y") - - assert QualifiedConstTestMod.qualified_const_defined?("M::C::X") - assert !QualifiedConstTestMod.qualified_const_defined?("M::C::Y") - - if Module.method(:const_defined?).arity == 1 - assert !QualifiedConstTestMod.qualified_const_defined?("QualifiedConstTestMod::N::X") - else - assert QualifiedConstTestMod.qualified_const_defined?("N::X") - assert !QualifiedConstTestMod.qualified_const_defined?("N::X", false) - assert QualifiedConstTestMod.qualified_const_defined?("N::X", true) - end - end - - test "qualified_const_get" do - assert_equal false, Object.qualified_const_get("QualifiedConstTestMod::X") - assert_equal false, QualifiedConstTestMod.qualified_const_get("X") - assert_equal 1, QualifiedConstTestMod.qualified_const_get("M::X") - assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X") - assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X") - - assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y")} - end - - test "qualified_const_set" do - begin - m = Module.new - assert_equal m, Object.qualified_const_set("QualifiedConstTestMod2", m) - assert_equal m, ::QualifiedConstTestMod2 - - # We are going to assign to existing constants on purpose, so silence warnings. - silence_warnings do - assert_equal true, QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", true) - assert_equal true, QualifiedConstTestMod::X - - assert_equal 10, QualifiedConstTestMod::M.qualified_const_set("X", 10) - assert_equal 10, QualifiedConstTestMod::M::X - end - ensure - silence_warnings do - QualifiedConstTestMod.qualified_const_set('QualifiedConstTestMod::X', false) - QualifiedConstTestMod::M.qualified_const_set('X', 1) - end - end - end - - test "reject absolute paths" do - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X")} - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y")} - - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X")} - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y")} - - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil)} - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil)} - end - - private - - def assert_raise_with_message(expected_exception, expected_message, &block) - exception = assert_raise(expected_exception, &block) - assert_equal expected_message, exception.message - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module/reachable_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module/reachable_test.rb deleted file mode 100644 index 80eb31a5c4..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module/reachable_test.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/reachable' - -class AnonymousTest < ActiveSupport::TestCase - test "an anonymous class or module is not reachable" do - assert !Module.new.reachable? - assert !Class.new.reachable? - end - - test "ordinary named classes or modules are reachable" do - assert Kernel.reachable? - assert Object.reachable? - end - - test "a named class or module whose constant has gone is not reachable" do - c = eval "class C; end; C" - m = eval "module M; end; M" - - self.class.send(:remove_const, :C) - self.class.send(:remove_const, :M) - - assert !c.reachable? - assert !m.reachable? - end - - test "a named class or module whose constants store different objects are not reachable" do - c = eval "class C; end; C" - m = eval "module M; end; M" - - self.class.send(:remove_const, :C) - self.class.send(:remove_const, :M) - - eval "class C; end" - eval "module M; end" - - assert C.reachable? - assert M.reachable? - assert !c.reachable? - assert !m.reachable? - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module/remove_method_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module/remove_method_test.rb deleted file mode 100644 index 4657f0c175..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module/remove_method_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/remove_method' - -module RemoveMethodTests - class A - def do_something - return 1 - end - - end -end - -class RemoveMethodTest < ActiveSupport::TestCase - - def test_remove_method_from_an_object - RemoveMethodTests::A.class_eval{ - self.remove_possible_method(:do_something) - } - assert !RemoveMethodTests::A.new.respond_to?(:do_something) - end - - def test_redefine_method_in_an_object - RemoveMethodTests::A.class_eval{ - self.redefine_method(:do_something) { return 100 } - } - assert_equal 100, RemoveMethodTests::A.new.do_something - end - -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/module_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/module_test.rb deleted file mode 100644 index ff6e21854e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/module_test.rb +++ /dev/null @@ -1,498 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module' - -module One - Constant1 = "Hello World" - Constant2 = "What's up?" -end - -class Ab - include One - Constant1 = "Hello World" # Will have different object id than One::Constant1 - Constant3 = "Goodbye World" -end - -module Yz - module Zy - class Cd - include One - end - end -end - -Somewhere = Struct.new(:street, :city) do - attr_accessor :name -end - -class Someone < Struct.new(:name, :place) - delegate :street, :city, :to_f, :to => :place - delegate :name=, :to => :place, :prefix => true - delegate :upcase, :to => "place.city" - delegate :table_name, :to => :class - delegate :table_name, :to => :class, :prefix => true - - def self.table_name - 'some_table' - end - - FAILED_DELEGATE_LINE = __LINE__ + 1 - delegate :foo, :to => :place - - FAILED_DELEGATE_LINE_2 = __LINE__ + 1 - delegate :bar, :to => :place, :allow_nil => true -end - -Invoice = Struct.new(:client) do - delegate :street, :city, :name, :to => :client, :prefix => true - delegate :street, :city, :name, :to => :client, :prefix => :customer -end - -Project = Struct.new(:description, :person) do - delegate :name, :to => :person, :allow_nil => true - delegate :to_f, :to => :description, :allow_nil => true -end - -Developer = Struct.new(:client) do - delegate :name, :to => :client, :prefix => nil -end - -Tester = Struct.new(:client) do - delegate :name, :to => :client, :prefix => false -end - -Product = Struct.new(:name) do - delegate :name, :to => :manufacturer, :prefix => true - delegate :name, :to => :type, :prefix => true - - def manufacturer - @manufacturer ||= begin - nil.unknown_method - end - end - - def type - @type ||= begin - nil.type_name - end - end -end - -class ParameterSet - delegate :[], :[]=, :to => :@params - - def initialize - @params = {:foo => "bar"} - end -end - -class Name - delegate :upcase, :to => :@full_name - - def initialize(first, last) - @full_name = "#{first} #{last}" - end -end - -class SideEffect - attr_reader :ints - - delegate :to_i, :to => :shift, :allow_nil => true - delegate :to_s, :to => :shift - - def initialize - @ints = [1, 2, 3] - end - - def shift - @ints.shift - end -end - -class ModuleTest < ActiveSupport::TestCase - def setup - @david = Someone.new("David", Somewhere.new("Paulina", "Chicago")) - end - - def test_delegation_to_methods - assert_equal "Paulina", @david.street - assert_equal "Chicago", @david.city - end - - def test_delegation_to_assignment_method - @david.place_name = "Fred" - assert_equal "Fred", @david.place.name - end - - def test_delegation_to_index_get_method - @params = ParameterSet.new - assert_equal "bar", @params[:foo] - end - - def test_delegation_to_index_set_method - @params = ParameterSet.new - @params[:foo] = "baz" - assert_equal "baz", @params[:foo] - end - - def test_delegation_down_hierarchy - assert_equal "CHICAGO", @david.upcase - end - - def test_delegation_to_instance_variable - david = Name.new("David", "Hansson") - assert_equal "DAVID HANSSON", david.upcase - end - - def test_delegation_to_class_method - assert_equal 'some_table', @david.table_name - assert_equal 'some_table', @david.class_table_name - end - - def test_missing_delegation_target - assert_raise(ArgumentError) do - Name.send :delegate, :nowhere - end - assert_raise(ArgumentError) do - Name.send :delegate, :noplace, :tos => :hollywood - end - end - - def test_delegation_prefix - invoice = Invoice.new(@david) - assert_equal invoice.client_name, "David" - assert_equal invoice.client_street, "Paulina" - assert_equal invoice.client_city, "Chicago" - end - - def test_delegation_custom_prefix - invoice = Invoice.new(@david) - assert_equal invoice.customer_name, "David" - assert_equal invoice.customer_street, "Paulina" - assert_equal invoice.customer_city, "Chicago" - end - - def test_delegation_prefix_with_nil_or_false - assert_equal Developer.new(@david).name, "David" - assert_equal Tester.new(@david).name, "David" - end - - def test_delegation_prefix_with_instance_variable - assert_raise ArgumentError do - Class.new do - def initialize(client) - @client = client - end - delegate :name, :address, :to => :@client, :prefix => true - end - end - end - - def test_delegation_with_allow_nil - rails = Project.new("Rails", Someone.new("David")) - assert_equal rails.name, "David" - end - - def test_delegation_with_allow_nil_and_nil_value - rails = Project.new("Rails") - assert_nil rails.name - end - - # Ensures with check for nil, not for a falseish target. - def test_delegation_with_allow_nil_and_false_value - project = Project.new(false, false) - assert_raise(NoMethodError) { project.name } - end - - def test_delegation_with_allow_nil_and_invalid_value - rails = Project.new("Rails", "David") - assert_raise(NoMethodError) { rails.name } - end - - def test_delegation_with_allow_nil_and_nil_value_and_prefix - Project.class_eval do - delegate :name, :to => :person, :allow_nil => true, :prefix => true - end - rails = Project.new("Rails") - assert_nil rails.person_name - end - - def test_delegation_without_allow_nil_and_nil_value - david = Someone.new("David") - assert_raise(Module::DelegationError) { david.street } - end - - def test_delegation_to_method_that_exists_on_nil - nil_person = Someone.new(nil) - assert_equal 0.0, nil_person.to_f - end - - def test_delegation_to_method_that_exists_on_nil_when_allowing_nil - nil_project = Project.new(nil) - assert_equal 0.0, nil_project.to_f - end - - def test_delegation_does_not_raise_error_when_removing_singleton_instance_methods - parent = Class.new do - def self.parent_method; end - end - - assert_nothing_raised do - Class.new(parent) do - class << self - delegate :parent_method, :to => :superclass - end - end - end - end - - def test_delegation_line_number - _, line = Someone.instance_method(:foo).source_location - assert_equal Someone::FAILED_DELEGATE_LINE, line - end - - def test_delegate_line_with_nil - _, line = Someone.instance_method(:bar).source_location - assert_equal Someone::FAILED_DELEGATE_LINE_2, line - end - - def test_delegation_exception_backtrace - someone = Someone.new("foo", "bar") - someone.foo - rescue NoMethodError => e - file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE}" - # We can't simply check the first line of the backtrace, because JRuby reports the call to __send__ in the backtrace. - assert e.backtrace.any?{|a| a.include?(file_and_line)}, - "[#{e.backtrace.inspect}] did not include [#{file_and_line}]" - end - - def test_delegation_exception_backtrace_with_allow_nil - someone = Someone.new("foo", "bar") - someone.bar - rescue NoMethodError => e - file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE_2}" - # We can't simply check the first line of the backtrace, because JRuby reports the call to __send__ in the backtrace. - assert e.backtrace.any?{|a| a.include?(file_and_line)}, - "[#{e.backtrace.inspect}] did not include [#{file_and_line}]" - end - - def test_delegation_invokes_the_target_exactly_once - se = SideEffect.new - - assert_equal 1, se.to_i - assert_equal [2, 3], se.ints - - assert_equal '2', se.to_s - assert_equal [3], se.ints - end - - def test_delegation_doesnt_mask_nested_no_method_error_on_nil_receiver - product = Product.new('Widget') - - # Nested NoMethodError is a different name from the delegation - assert_raise(NoMethodError) { product.manufacturer_name } - - # Nested NoMethodError is the same name as the delegation - assert_raise(NoMethodError) { product.type_name } - end - - def test_parent - assert_equal Yz::Zy, Yz::Zy::Cd.parent - assert_equal Yz, Yz::Zy.parent - assert_equal Object, Yz.parent - end - - def test_parents - assert_equal [Yz::Zy, Yz, Object], Yz::Zy::Cd.parents - assert_equal [Yz, Object], Yz::Zy.parents - end - - def test_local_constants - assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s) - end -end - -module BarMethodAliaser - def self.included(foo_class) - foo_class.class_eval do - include BarMethods - alias_method_chain :bar, :baz - end - end -end - -module BarMethods - def bar_with_baz - bar_without_baz << '_with_baz' - end - - def quux_with_baz! - quux_without_baz! << '_with_baz' - end - - def quux_with_baz? - false - end - - def quux_with_baz=(v) - send(:quux_without_baz=, v) << '_with_baz' - end - - def duck_with_orange - duck_without_orange << '_with_orange' - end -end - -class MethodAliasingTest < ActiveSupport::TestCase - def setup - Object.const_set :FooClassWithBarMethod, Class.new { def bar() 'bar' end } - @instance = FooClassWithBarMethod.new - end - - def teardown - Object.instance_eval { remove_const :FooClassWithBarMethod } - end - - def test_alias_method_chain - assert @instance.respond_to?(:bar) - feature_aliases = [:bar_with_baz, :bar_without_baz] - - feature_aliases.each do |method| - assert !@instance.respond_to?(method) - end - - assert_equal 'bar', @instance.bar - - FooClassWithBarMethod.class_eval { include BarMethodAliaser } - - feature_aliases.each do |method| - assert_respond_to @instance, method - end - - assert_equal 'bar_with_baz', @instance.bar - assert_equal 'bar', @instance.bar_without_baz - end - - def test_alias_method_chain_with_punctuation_method - FooClassWithBarMethod.class_eval do - def quux!; 'quux' end - end - - assert !@instance.respond_to?(:quux_with_baz!) - FooClassWithBarMethod.class_eval do - include BarMethodAliaser - alias_method_chain :quux!, :baz - end - assert_respond_to @instance, :quux_with_baz! - - assert_equal 'quux_with_baz', @instance.quux! - assert_equal 'quux', @instance.quux_without_baz! - end - - def test_alias_method_chain_with_same_names_between_predicates_and_bang_methods - FooClassWithBarMethod.class_eval do - def quux!; 'quux!' end - def quux?; true end - def quux=(v); 'quux=' end - end - - assert !@instance.respond_to?(:quux_with_baz!) - assert !@instance.respond_to?(:quux_with_baz?) - assert !@instance.respond_to?(:quux_with_baz=) - - FooClassWithBarMethod.class_eval { include BarMethodAliaser } - assert_respond_to @instance, :quux_with_baz! - assert_respond_to @instance, :quux_with_baz? - assert_respond_to @instance, :quux_with_baz= - - - FooClassWithBarMethod.alias_method_chain :quux!, :baz - assert_equal 'quux!_with_baz', @instance.quux! - assert_equal 'quux!', @instance.quux_without_baz! - - FooClassWithBarMethod.alias_method_chain :quux?, :baz - assert_equal false, @instance.quux? - assert_equal true, @instance.quux_without_baz? - - FooClassWithBarMethod.alias_method_chain :quux=, :baz - assert_equal 'quux=_with_baz', @instance.send(:quux=, 1234) - assert_equal 'quux=', @instance.send(:quux_without_baz=, 1234) - end - - def test_alias_method_chain_with_feature_punctuation - FooClassWithBarMethod.class_eval do - def quux; 'quux' end - def quux?; 'quux?' end - include BarMethodAliaser - alias_method_chain :quux, :baz! - end - - assert_nothing_raised do - assert_equal 'quux_with_baz', @instance.quux_with_baz! - end - - assert_raise(NameError) do - FooClassWithBarMethod.alias_method_chain :quux?, :baz! - end - end - - def test_alias_method_chain_yields_target_and_punctuation - args = nil - - FooClassWithBarMethod.class_eval do - def quux?; end - include BarMethods - - FooClassWithBarMethod.alias_method_chain :quux?, :baz do |target, punctuation| - args = [target, punctuation] - end - end - - assert_not_nil args - assert_equal 'quux', args[0] - assert_equal '?', args[1] - end - - def test_alias_method_chain_preserves_private_method_status - FooClassWithBarMethod.class_eval do - def duck; 'duck' end - include BarMethodAliaser - private :duck - alias_method_chain :duck, :orange - end - - assert_raise NoMethodError do - @instance.duck - end - - assert_equal 'duck_with_orange', @instance.instance_eval { duck } - assert FooClassWithBarMethod.private_method_defined?(:duck) - end - - def test_alias_method_chain_preserves_protected_method_status - FooClassWithBarMethod.class_eval do - def duck; 'duck' end - include BarMethodAliaser - protected :duck - alias_method_chain :duck, :orange - end - - assert_raise NoMethodError do - @instance.duck - end - - assert_equal 'duck_with_orange', @instance.instance_eval { duck } - assert FooClassWithBarMethod.protected_method_defined?(:duck) - end - - def test_alias_method_chain_preserves_public_method_status - FooClassWithBarMethod.class_eval do - def duck; 'duck' end - include BarMethodAliaser - public :duck - alias_method_chain :duck, :orange - end - - assert_equal 'duck_with_orange', @instance.duck - assert FooClassWithBarMethod.public_method_defined?(:duck) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/name_error_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/name_error_test.rb deleted file mode 100644 index 7525f80cf0..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/name_error_test.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/name_error' - -class NameErrorTest < ActiveSupport::TestCase - def test_name_error_should_set_missing_name - exc = assert_raise NameError do - SomeNameThatNobodyWillUse____Really ? 1 : 0 - end - assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name - assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really) - assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really") - end - - def test_missing_method_should_ignore_missing_name - exc = assert_raise NameError do - some_method_that_does_not_exist - end - assert !exc.missing_name?(:Foo) - assert_nil exc.missing_name - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/numeric_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/numeric_ext_test.rb deleted file mode 100644 index 3b1dabea8d..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/numeric_ext_test.rb +++ /dev/null @@ -1,442 +0,0 @@ -require 'abstract_unit' -require 'active_support/time' -require 'active_support/core_ext/numeric' -require 'active_support/core_ext/integer' - -class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase - def setup - @now = Time.local(2005,2,10,15,30,45) - @dtnow = DateTime.civil(2005,2,10,15,30,45) - @seconds = { - 1.minute => 60, - 10.minutes => 600, - 1.hour + 15.minutes => 4500, - 2.days + 4.hours + 30.minutes => 189000, - 5.years + 1.month + 1.fortnight => 161589600 - } - end - - def test_units - @seconds.each do |actual, expected| - assert_equal expected, actual - end - end - - def test_deprecated_since_and_ago - assert_equal @now + 1, assert_deprecated { 1.since(@now) } - assert_equal @now - 1, assert_deprecated { 1.ago(@now) } - end - - def test_deprecated_since_and_ago_without_argument - now = Time.now - assert assert_deprecated { 1.since } >= now + 1 - now = Time.now - assert assert_deprecated { 1.ago } >= now - 1 - end - - def test_irregular_durations - assert_equal @now.advance(:days => 3000), 3000.days.since(@now) - assert_equal @now.advance(:months => 1), 1.month.since(@now) - assert_equal @now.advance(:months => -1), 1.month.until(@now) - assert_equal @now.advance(:years => 20), 20.years.since(@now) - assert_equal @dtnow.advance(:days => 3000), 3000.days.since(@dtnow) - assert_equal @dtnow.advance(:months => 1), 1.month.since(@dtnow) - assert_equal @dtnow.advance(:months => -1), 1.month.until(@dtnow) - assert_equal @dtnow.advance(:years => 20), 20.years.since(@dtnow) - end - - def test_duration_addition - assert_equal @now.advance(:days => 1).advance(:months => 1), (1.day + 1.month).since(@now) - assert_equal @now.advance(:days => 7), (1.week + 5.seconds - 5.seconds).since(@now) - assert_equal @now.advance(:years => 2), (4.years - 2.years).since(@now) - assert_equal @dtnow.advance(:days => 1).advance(:months => 1), (1.day + 1.month).since(@dtnow) - assert_equal @dtnow.advance(:days => 7), (1.week + 5.seconds - 5.seconds).since(@dtnow) - assert_equal @dtnow.advance(:years => 2), (4.years - 2.years).since(@dtnow) - end - - def test_time_plus_duration - assert_equal @now + 8, @now + 8.seconds - assert_equal @now + 22.9, @now + 22.9.seconds - assert_equal @now.advance(:days => 15), @now + 15.days - assert_equal @now.advance(:months => 1), @now + 1.month - assert_equal @dtnow.since(8), @dtnow + 8.seconds - assert_equal @dtnow.since(22.9), @dtnow + 22.9.seconds - assert_equal @dtnow.advance(:days => 15), @dtnow + 15.days - assert_equal @dtnow.advance(:months => 1), @dtnow + 1.month - end - - def test_chaining_duration_operations - assert_equal @now.advance(:days => 2).advance(:months => -3), @now + 2.days - 3.months - assert_equal @now.advance(:days => 1).advance(:months => 2), @now + 1.day + 2.months - assert_equal @dtnow.advance(:days => 2).advance(:months => -3), @dtnow + 2.days - 3.months - assert_equal @dtnow.advance(:days => 1).advance(:months => 2), @dtnow + 1.day + 2.months - end - - def test_duration_after_conversion_is_no_longer_accurate - assert_equal 30.days.to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now) - assert_equal 365.25.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now) - assert_equal 30.days.to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow) - assert_equal 365.25.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow) - end - - def test_add_one_year_to_leap_day - assert_equal Time.utc(2005,2,28,15,15,10), Time.utc(2004,2,29,15,15,10) + 1.year - assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10) + 1.year - end - - def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set - Time.zone = nil - with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - # since - assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since } - assert_equal Time.local(2000,1,1,0,0,5), assert_deprecated { 5.since } - # ago - assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago } - assert_equal Time.local(1999,12,31,23,59,55), assert_deprecated { 5.ago } - end - end - - def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - # since - assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since } - assert_equal Time.utc(2000,1,1,0,0,5), assert_deprecated { 5.since.time } - assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.since.time_zone.name } - # ago - assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago } - assert_equal Time.utc(1999,12,31,23,59,55), assert_deprecated { 5.ago.time } - assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.ago.time_zone.name } - end - ensure - Time.zone = nil - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end -end - -class NumericExtDateTest < ActiveSupport::TestCase - def setup - @today = Date.today - end - - def test_date_plus_duration - assert_equal @today + 1, @today + 1.day - assert_equal @today >> 1, @today + 1.month - assert_equal @today.to_time.since(1), @today + 1.second - assert_equal @today.to_time.since(60), @today + 1.minute - assert_equal @today.to_time.since(60*60), @today + 1.hour - end - - def test_chaining_duration_operations - assert_equal @today.advance(:days => 2).advance(:months => -3), @today + 2.days - 3.months - assert_equal @today.advance(:days => 1).advance(:months => 2), @today + 1.day + 2.months - end - - def test_add_one_year_to_leap_day - assert_equal Date.new(2005,2,28), Date.new(2004,2,29) + 1.year - end -end - -class NumericExtSizeTest < ActiveSupport::TestCase - def test_unit_in_terms_of_another - assert_equal 1024.bytes, 1.kilobyte - assert_equal 1024.kilobytes, 1.megabyte - assert_equal 3584.0.kilobytes, 3.5.megabytes - assert_equal 3584.0.megabytes, 3.5.gigabytes - assert_equal 1.kilobyte ** 4, 1.terabyte - assert_equal 1024.kilobytes + 2.megabytes, 3.megabytes - assert_equal 2.gigabytes / 4, 512.megabytes - assert_equal 256.megabytes * 20 + 5.gigabytes, 10.gigabytes - assert_equal 1.kilobyte ** 5, 1.petabyte - assert_equal 1.kilobyte ** 6, 1.exabyte - end - - def test_units_as_bytes_independently - assert_equal 3145728, 3.megabytes - assert_equal 3145728, 3.megabyte - assert_equal 3072, 3.kilobytes - assert_equal 3072, 3.kilobyte - assert_equal 3221225472, 3.gigabytes - assert_equal 3221225472, 3.gigabyte - assert_equal 3298534883328, 3.terabytes - assert_equal 3298534883328, 3.terabyte - assert_equal 3377699720527872, 3.petabytes - assert_equal 3377699720527872, 3.petabyte - assert_equal 3458764513820540928, 3.exabytes - assert_equal 3458764513820540928, 3.exabyte - end -end - -class NumericExtFormattingTest < ActiveSupport::TestCase - def kilobytes(number) - number * 1024 - end - - def megabytes(number) - kilobytes(number) * 1024 - end - - def gigabytes(number) - megabytes(number) * 1024 - end - - def terabytes(number) - gigabytes(number) * 1024 - end - - def test_to_s__phone - assert_equal("555-1234", 5551234.to_s(:phone)) - assert_equal("800-555-1212", 8005551212.to_s(:phone)) - assert_equal("(800) 555-1212", 8005551212.to_s(:phone, :area_code => true)) - assert_equal("800 555 1212", 8005551212.to_s(:phone, :delimiter => " ")) - assert_equal("(800) 555-1212 x 123", 8005551212.to_s(:phone, :area_code => true, :extension => 123)) - assert_equal("800-555-1212", 8005551212.to_s(:phone, :extension => " ")) - assert_equal("555.1212", 5551212.to_s(:phone, :delimiter => '.')) - assert_equal("+1-800-555-1212", 8005551212.to_s(:phone, :country_code => 1)) - assert_equal("+18005551212", 8005551212.to_s(:phone, :country_code => 1, :delimiter => '')) - assert_equal("22-555-1212", 225551212.to_s(:phone)) - assert_equal("+45-22-555-1212", 225551212.to_s(:phone, :country_code => 45)) - end - - def test_to_s__currency - assert_equal("$1,234,567,890.50", 1234567890.50.to_s(:currency)) - assert_equal("$1,234,567,890.51", 1234567890.506.to_s(:currency)) - assert_equal("-$1,234,567,890.50", -1234567890.50.to_s(:currency)) - assert_equal("-$ 1,234,567,890.50", -1234567890.50.to_s(:currency, :format => "%u %n")) - assert_equal("($1,234,567,890.50)", -1234567890.50.to_s(:currency, :negative_format => "(%u%n)")) - assert_equal("$1,234,567,892", 1234567891.50.to_s(:currency, :precision => 0)) - assert_equal("$1,234,567,890.5", 1234567890.50.to_s(:currency, :precision => 1)) - assert_equal("£1234567890,50", 1234567890.50.to_s(:currency, :unit => "£", :separator => ",", :delimiter => "")) - end - - - def test_to_s__rounded - assert_equal("-111.235", -111.2346.to_s(:rounded)) - assert_equal("111.235", 111.2346.to_s(:rounded)) - assert_equal("31.83", 31.825.to_s(:rounded, :precision => 2)) - assert_equal("111.23", 111.2346.to_s(:rounded, :precision => 2)) - assert_equal("111.00", 111.to_s(:rounded, :precision => 2)) - assert_equal("3268", (32.6751 * 100.00).to_s(:rounded, :precision => 0)) - assert_equal("112", 111.50.to_s(:rounded, :precision => 0)) - assert_equal("1234567892", 1234567891.50.to_s(:rounded, :precision => 0)) - assert_equal("0", 0.to_s(:rounded, :precision => 0)) - assert_equal("0.00100", 0.001.to_s(:rounded, :precision => 5)) - assert_equal("0.001", 0.00111.to_s(:rounded, :precision => 3)) - assert_equal("10.00", 9.995.to_s(:rounded, :precision => 2)) - assert_equal("11.00", 10.995.to_s(:rounded, :precision => 2)) - assert_equal("0.00", -0.001.to_s(:rounded, :precision => 2)) - end - - def test_to_s__percentage - assert_equal("100.000%", 100.to_s(:percentage)) - assert_equal("100%", 100.to_s(:percentage, :precision => 0)) - assert_equal("302.06%", 302.0574.to_s(:percentage, :precision => 2)) - assert_equal("123.4%", 123.400.to_s(:percentage, :precision => 3, :strip_insignificant_zeros => true)) - assert_equal("1.000,000%", 1000.to_s(:percentage, :delimiter => '.', :separator => ',')) - assert_equal("1000.000 %", 1000.to_s(:percentage, :format => "%n %")) - end - - def test_to_s__delimited - assert_equal("12,345,678", 12345678.to_s(:delimited)) - assert_equal("0", 0.to_s(:delimited)) - assert_equal("123", 123.to_s(:delimited)) - assert_equal("123,456", 123456.to_s(:delimited)) - assert_equal("123,456.78", 123456.78.to_s(:delimited)) - assert_equal("123,456.789", 123456.789.to_s(:delimited)) - assert_equal("123,456.78901", 123456.78901.to_s(:delimited)) - assert_equal("123,456,789.78901", 123456789.78901.to_s(:delimited)) - assert_equal("0.78901", 0.78901.to_s(:delimited)) - end - - def test_to_s__delimited__with_options_hash - assert_equal '12 345 678', 12345678.to_s(:delimited, :delimiter => ' ') - assert_equal '12,345,678-05', 12345678.05.to_s(:delimited, :separator => '-') - assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :separator => ',', :delimiter => '.') - assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :delimiter => '.', :separator => ',') - end - - - def test_to_s__rounded_with_custom_delimiter_and_separator - assert_equal '31,83', 31.825.to_s(:rounded, :precision => 2, :separator => ',') - assert_equal '1.231,83', 1231.825.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.') - end - - def test_to_s__rounded__with_significant_digits - assert_equal "124000", 123987.to_s(:rounded, :precision => 3, :significant => true) - assert_equal "120000000", 123987876.to_s(:rounded, :precision => 2, :significant => true ) - assert_equal "9775", 9775.to_s(:rounded, :precision => 4, :significant => true ) - assert_equal "5.4", 5.3923.to_s(:rounded, :precision => 2, :significant => true ) - assert_equal "5", 5.3923.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "1", 1.232.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "7", 7.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "1", 1.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "53", 52.7923.to_s(:rounded, :precision => 2, :significant => true ) - assert_equal "9775.00", 9775.to_s(:rounded, :precision => 6, :significant => true ) - assert_equal "5.392900", 5.3929.to_s(:rounded, :precision => 7, :significant => true ) - assert_equal "0.0", 0.to_s(:rounded, :precision => 2, :significant => true ) - assert_equal "0", 0.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "0.0001", 0.0001.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "0.000100", 0.0001.to_s(:rounded, :precision => 3, :significant => true ) - assert_equal "0.0001", 0.0001111.to_s(:rounded, :precision => 1, :significant => true ) - assert_equal "10.0", 9.995.to_s(:rounded, :precision => 3, :significant => true) - assert_equal "9.99", 9.994.to_s(:rounded, :precision => 3, :significant => true) - assert_equal "11.0", 10.995.to_s(:rounded, :precision => 3, :significant => true) - end - - def test_to_s__rounded__with_strip_insignificant_zeros - assert_equal "9775.43", 9775.43.to_s(:rounded, :precision => 4, :strip_insignificant_zeros => true ) - assert_equal "9775.2", 9775.2.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - assert_equal "0", 0.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - end - - def test_to_s__rounded__with_significant_true_and_zero_precision - # Zero precision with significant is a mistake (would always return zero), - # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) - assert_equal "124", 123.987.to_s(:rounded, :precision => 0, :significant => true) - assert_equal "12", 12.to_s(:rounded, :precision => 0, :significant => true ) - end - - def test_to_s__human_size - assert_equal '0 Bytes', 0.to_s(:human_size) - assert_equal '1 Byte', 1.to_s(:human_size) - assert_equal '3 Bytes', 3.14159265.to_s(:human_size) - assert_equal '123 Bytes', 123.0.to_s(:human_size) - assert_equal '123 Bytes', 123.to_s(:human_size) - assert_equal '1.21 KB', 1234.to_s(:human_size) - assert_equal '12.1 KB', 12345.to_s(:human_size) - assert_equal '1.18 MB', 1234567.to_s(:human_size) - assert_equal '1.15 GB', 1234567890.to_s(:human_size) - assert_equal '1.12 TB', 1234567890123.to_s(:human_size) - assert_equal '1030 TB', terabytes(1026).to_s(:human_size) - assert_equal '444 KB', kilobytes(444).to_s(:human_size) - assert_equal '1020 MB', megabytes(1023).to_s(:human_size) - assert_equal '3 TB', terabytes(3).to_s(:human_size) - assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2) - assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4) - assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2) - assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4) - assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4) - assert_equal '1 Byte', 1.1.to_s(:human_size) - assert_equal '10 Bytes', 10.to_s(:human_size) - end - - def test_to_s__human_size_with_si_prefix - assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si) - assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si) - assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si) - assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si) - assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si) - assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si) - assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si) - assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si) - end - - def test_to_s__human_size_with_options_hash - assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2) - assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4) - assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2) - assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4) - assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4) - assert_equal '1 TB', 1234567890123.to_s(:human_size, :precision => 1) - assert_equal '500 MB', 524288000.to_s(:human_size, :precision=>3) - assert_equal '10 MB', 9961472.to_s(:human_size, :precision=>0) - assert_equal '40 KB', 41010.to_s(:human_size, :precision => 1) - assert_equal '40 KB', 41100.to_s(:human_size, :precision => 2) - assert_equal '1.0 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2, :strip_insignificant_zeros => false) - assert_equal '1.012 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :significant => false) - assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 0, :significant => true) #ignores significant it precision is 0 - end - - def test_to_s__human_size_with_custom_delimiter_and_separator - assert_equal '1,01 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :separator => ',') - assert_equal '1,01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4, :separator => ',') - assert_equal '1.000,1 TB', terabytes(1000.1).to_s(:human_size, :precision => 5, :delimiter => '.', :separator => ',') - end - - def test_number_to_human - assert_equal '-123', -123.to_s(:human) - assert_equal '-0.5', -0.5.to_s(:human) - assert_equal '0', 0.to_s(:human) - assert_equal '0.5', 0.5.to_s(:human) - assert_equal '123', 123.to_s(:human) - assert_equal '1.23 Thousand', 1234.to_s(:human) - assert_equal '12.3 Thousand', 12345.to_s(:human) - assert_equal '1.23 Million', 1234567.to_s(:human) - assert_equal '1.23 Billion', 1234567890.to_s(:human) - assert_equal '1.23 Trillion', 1234567890123.to_s(:human) - assert_equal '1.23 Quadrillion', 1234567890123456.to_s(:human) - assert_equal '1230 Quadrillion', 1234567890123456789.to_s(:human) - assert_equal '490 Thousand', 489939.to_s(:human, :precision => 2) - assert_equal '489.9 Thousand', 489939.to_s(:human, :precision => 4) - assert_equal '489 Thousand', 489000.to_s(:human, :precision => 4) - assert_equal '489.0 Thousand', 489000.to_s(:human, :precision => 4, :strip_insignificant_zeros => false) - assert_equal '1.2346 Million', 1234567.to_s(:human, :precision => 4, :significant => false) - assert_equal '1,2 Million', 1234567.to_s(:human, :precision => 1, :significant => false, :separator => ',') - assert_equal '1 Million', 1234567.to_s(:human, :precision => 0, :significant => true, :separator => ',') #significant forced to false - end - - def test_number_to_human_with_custom_units - #Only integers - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123 lt', 123456.to_s(:human, :units => volume) - assert_equal '12 ml', 12.to_s(:human, :units => volume) - assert_equal '1.23 m3', 1234567.to_s(:human, :units => volume) - - #Including fractionals - distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - assert_equal '1.23 mm', 0.00123.to_s(:human, :units => distance) - assert_equal '1.23 cm', 0.0123.to_s(:human, :units => distance) - assert_equal '1.23 dm', 0.123.to_s(:human, :units => distance) - assert_equal '1.23 m', 1.23.to_s(:human, :units => distance) - assert_equal '1.23 dam', 12.3.to_s(:human, :units => distance) - assert_equal '1.23 hm', 123.to_s(:human, :units => distance) - assert_equal '1.23 km', 1230.to_s(:human, :units => distance) - assert_equal '1.23 km', 1230.to_s(:human, :units => distance) - assert_equal '1.23 km', 1230.to_s(:human, :units => distance) - assert_equal '12.3 km', 12300.to_s(:human, :units => distance) - - #The quantifiers don't need to be a continuous sequence - gangster = {:hundred => "hundred bucks", :million => "thousand quids"} - assert_equal '1 hundred bucks', 100.to_s(:human, :units => gangster) - assert_equal '25 hundred bucks', 2500.to_s(:human, :units => gangster) - assert_equal '25 thousand quids', 25000000.to_s(:human, :units => gangster) - assert_equal '12300 thousand quids', 12345000000.to_s(:human, :units => gangster) - - #Spaces are stripped from the resulting string - assert_equal '4', 4.to_s(:human, :units => {:unit => "", :ten => 'tens '}) - assert_equal '4.5 tens', 45.to_s(:human, :units => {:unit => "", :ten => ' tens '}) - end - - def test_number_to_human_with_custom_format - assert_equal '123 times Thousand', 123456.to_s(:human, :format => "%n times %u") - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123.lt', 123456.to_s(:human, :units => volume, :format => "%n.%u") - end - - def test_to_s__injected_on_proper_types - assert_equal Fixnum, 1230.class - assert_equal '1.23 Thousand', 1230.to_s(:human) - - assert_equal Float, Float(1230).class - assert_equal '1.23 Thousand', Float(1230).to_s(:human) - - assert_equal Bignum, (100**10).class - assert_equal '100000 Quadrillion', (100**10).to_s(:human) - - assert_equal BigDecimal, BigDecimal("1000010").class - assert_equal '1 Million', BigDecimal("1000010").to_s(:human) - end - - def test_in_milliseconds - assert_equal 10_000, 10.seconds.in_milliseconds - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/object/inclusion_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/object/inclusion_test.rb deleted file mode 100644 index b054a8dd31..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/object/inclusion_test.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/inclusion' - -class InTest < ActiveSupport::TestCase - def test_in_array - assert 1.in?([1,2]) - assert !3.in?([1,2]) - end - - def test_in_hash - h = { "a" => 100, "b" => 200 } - assert "a".in?(h) - assert !"z".in?(h) - end - - def test_in_string - assert "lo".in?("hello") - assert !"ol".in?("hello") - assert ?h.in?("hello") - end - - def test_in_range - assert 25.in?(1..50) - assert !75.in?(1..50) - end - - def test_in_set - s = Set.new([1,2]) - assert 1.in?(s) - assert !3.in?(s) - end - - module A - end - class B - include A - end - class C < B - end - - def test_in_module - assert A.in?(B) - assert A.in?(C) - assert !A.in?(A) - end - - def test_no_method_catching - assert_raise(ArgumentError) { 1.in?(1) } - end - - def test_presence_in - assert_equal "stuff", "stuff".presence_in(%w( lots of stuff )) - assert_nil "stuff".presence_in(%w( lots of crap )) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/object/json_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/object/json_test.rb deleted file mode 100644 index d3d31530df..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/object/json_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'abstract_unit' - -class JsonTest < ActiveSupport::TestCase - # See activesupport/test/json/encoding_test.rb for JSON encoding tests - - def test_deprecated_require_to_json_rb - assert_deprecated { require 'active_support/core_ext/object/to_json' } - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/object/to_param_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/object/to_param_test.rb deleted file mode 100644 index bd7c6c422a..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/object/to_param_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/to_param' - -class ToParamTest < ActiveSupport::TestCase - def test_object - foo = Object.new - def foo.to_s; 'foo' end - assert_equal 'foo', foo.to_param - end - - def test_nil - assert_nil nil.to_param - end - - def test_boolean - assert_equal true, true.to_param - assert_equal false, false.to_param - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/object/to_query_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/object/to_query_test.rb deleted file mode 100644 index f887a9e613..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/object/to_query_test.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'abstract_unit' -require 'active_support/ordered_hash' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/string/output_safety' - -class ToQueryTest < ActiveSupport::TestCase - def test_simple_conversion - assert_query_equal 'a=10', :a => 10 - end - - def test_cgi_escaping - assert_query_equal 'a%3Ab=c+d', 'a:b' => 'c d' - end - - def test_html_safe_parameter_key - assert_query_equal 'a%3Ab=c+d', 'a:b'.html_safe => 'c d' - end - - def test_html_safe_parameter_value - assert_query_equal 'a=%5B10%5D', 'a' => '[10]'.html_safe - end - - def test_nil_parameter_value - empty = Object.new - def empty.to_param; nil end - assert_query_equal 'a=', 'a' => empty - end - - def test_nested_conversion - assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas', - :person => Hash[:login, 'seckar', :name, 'Nicholas'] - end - - def test_multiple_nested - assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10', - Hash[:account, {:person => {:id => 20}}, :person, {:id => 10}] - end - - def test_array_values - assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20', - :person => {:id => [10, 20]} - end - - def test_array_values_are_not_sorted - assert_query_equal 'person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10', - :person => {:id => [20, 10]} - end - - def test_nested_empty_hash - assert_equal '', - {}.to_query - assert_query_equal 'a=1&b%5Bc%5D=3&b%5Bd%5D=', - { a: 1, b: { c: 3, d: {} } } - assert_query_equal 'b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12', - { p: 12, b: { c: false, e: nil, f: '' } } - assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=&b%5Bk%5D=', - { b: { c: 3, k: {}, f: '' } } - assert_query_equal 'a%5B%5D=&b=3', - {a: [], b: 3} - end - - private - def assert_query_equal(expected, actual) - assert_equal expected.split('&'), actual.to_query.split('&') - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/object_and_class_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/object_and_class_ext_test.rb deleted file mode 100644 index 0f454fdd95..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/object_and_class_ext_test.rb +++ /dev/null @@ -1,156 +0,0 @@ -require 'abstract_unit' -require 'active_support/time' -require 'active_support/core_ext/object' -require 'active_support/core_ext/class/subclasses' - -class ObjectTests < ActiveSupport::TestCase - class DuckTime - def acts_like_time? - true - end - end - - def test_duck_typing - object = Object.new - time = Time.now - date = Date.today - dt = DateTime.new - duck = DuckTime.new - - assert !object.acts_like?(:time) - assert !object.acts_like?(:date) - - assert time.acts_like?(:time) - assert !time.acts_like?(:date) - - assert !date.acts_like?(:time) - assert date.acts_like?(:date) - - assert dt.acts_like?(:time) - assert dt.acts_like?(:date) - - assert duck.acts_like?(:time) - assert !duck.acts_like?(:date) - end -end - -class ObjectInstanceVariableTest < ActiveSupport::TestCase - def setup - @source, @dest = Object.new, Object.new - @source.instance_variable_set(:@bar, 'bar') - @source.instance_variable_set(:@baz, 'baz') - end - - def test_instance_variable_names - assert_equal %w(@bar @baz), @source.instance_variable_names.sort - end - - def test_instance_values - object = Object.new - object.instance_variable_set :@a, 1 - object.instance_variable_set :@b, 2 - assert_equal({'a' => 1, 'b' => 2}, object.instance_values) - end - - def test_instance_exec_passes_arguments_to_block - assert_equal %w(hello goodbye), 'hello'.instance_exec('goodbye') { |v| [self, v] } - end - - def test_instance_exec_with_frozen_obj - assert_equal %w(olleh goodbye), 'hello'.freeze.instance_exec('goodbye') { |v| [reverse, v] } - end - - def test_instance_exec_nested - assert_equal %w(goodbye olleh bar), 'hello'.instance_exec('goodbye') { |arg| - [arg] + instance_exec('bar') { |v| [reverse, v] } } - end -end - -class ObjectTryTest < ActiveSupport::TestCase - def setup - @string = "Hello" - end - - def test_nonexisting_method - method = :undefined_method - assert !@string.respond_to?(method) - assert_nil @string.try(method) - end - - def test_nonexisting_method_with_arguments - method = :undefined_method - assert !@string.respond_to?(method) - assert_nil @string.try(method, 'llo', 'y') - end - - def test_nonexisting_method_bang - method = :undefined_method - assert !@string.respond_to?(method) - assert_raise(NoMethodError) { @string.try!(method) } - end - - def test_nonexisting_method_with_arguments_bang - method = :undefined_method - assert !@string.respond_to?(method) - assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') } - end - - def test_try_only_block_bang - assert_equal @string.reverse, @string.try! { |s| s.reverse } - end - - def test_valid_method - assert_equal 5, @string.try(:size) - end - - def test_argument_forwarding - assert_equal 'Hey', @string.try(:sub, 'llo', 'y') - end - - def test_block_forwarding - assert_equal 'Hey', @string.try(:sub, 'llo') { |match| 'y' } - end - - def test_nil_to_type - assert_nil nil.try(:to_s) - assert_nil nil.try(:to_i) - end - - def test_false_try - assert_equal 'false', false.try(:to_s) - end - - def test_try_only_block - assert_equal @string.reverse, @string.try { |s| s.reverse } - end - - def test_try_only_block_nil - ran = false - nil.try { ran = true } - assert_equal false, ran - end - - def test_try_with_private_method_bang - klass = Class.new do - private - - def private_method - 'private method' - end - end - - assert_raise(NoMethodError) { klass.new.try!(:private_method) } - end - - def test_try_with_private_method - klass = Class.new do - private - - def private_method - 'private method' - end - end - - assert_nil klass.new.try(:private_method) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/range_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/range_ext_test.rb deleted file mode 100644 index 150e6b65fb..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/range_ext_test.rb +++ /dev/null @@ -1,119 +0,0 @@ -require 'abstract_unit' -require 'active_support/time' -require 'active_support/core_ext/range' - -class RangeTest < ActiveSupport::TestCase - def test_to_s_from_dates - date_range = Date.new(2005, 12, 10)..Date.new(2005, 12, 12) - assert_equal "BETWEEN '2005-12-10' AND '2005-12-12'", date_range.to_s(:db) - end - - def test_to_s_from_times - date_range = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30) - assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db) - end - - def test_date_range - assert_instance_of Range, DateTime.new..DateTime.new - assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new - end - - def test_overlaps_last_inclusive - assert((1..5).overlaps?(5..10)) - end - - def test_overlaps_last_exclusive - assert !(1...5).overlaps?(5..10) - end - - def test_overlaps_first_inclusive - assert((5..10).overlaps?(1..5)) - end - - def test_overlaps_first_exclusive - assert !(5..10).overlaps?(1...5) - end - - def test_should_include_identical_inclusive - assert((1..10).include?(1..10)) - end - - def test_should_include_identical_exclusive - assert((1...10).include?(1...10)) - end - - def test_should_include_other_with_exclusive_end - assert((1..10).include?(1...10)) - end - - def test_should_compare_identical_inclusive - assert((1..10) === (1..10)) - end - - def test_should_compare_identical_exclusive - assert((1...10) === (1...10)) - end - - def test_should_compare_other_with_exclusive_end - assert((1..10) === (1...10)) - end - - def test_exclusive_end_should_not_include_identical_with_inclusive_end - assert !(1...10).include?(1..10) - end - - def test_should_not_include_overlapping_first - assert !(2..8).include?(1..3) - end - - def test_should_not_include_overlapping_last - assert !(2..8).include?(5..9) - end - - def test_should_include_identical_exclusive_with_floats - assert((1.0...10.0).include?(1.0...10.0)) - end - - def test_cover_is_not_override - range = (1..3) - assert range.method(:include?) != range.method(:cover?) - end - - def test_overlaps_on_time - time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30) - time_range_2 = Time.utc(2005, 12, 10, 17, 00)..Time.utc(2005, 12, 10, 18, 00) - assert time_range_1.overlaps?(time_range_2) - end - - def test_no_overlaps_on_time - time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30) - time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00) - assert !time_range_1.overlaps?(time_range_2) - end - - def test_each_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) - assert_raises TypeError do - ((twz - 1.hour)..twz).each {} - end - end - - def test_step_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) - assert_raises TypeError do - ((twz - 1.hour)..twz).step(1) {} - end - end - - def test_include_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) - assert_raises TypeError do - ((twz - 1.hour)..twz).include?(twz) - end - end - - def test_date_time_with_each - datetime = DateTime.now - assert ((datetime - 1.hour)..datetime).each {} - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/regexp_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/regexp_ext_test.rb deleted file mode 100644 index c2398d31bd..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/regexp_ext_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/regexp' - -class RegexpExtAccessTests < ActiveSupport::TestCase - def test_multiline - assert_equal true, //m.multiline? - assert_equal false, //.multiline? - assert_equal false, /(?m:)/.multiline? - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/string_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/string_ext_test.rb deleted file mode 100644 index ea12f1ced5..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/string_ext_test.rb +++ /dev/null @@ -1,806 +0,0 @@ -# encoding: utf-8 -require 'date' -require 'abstract_unit' -require 'inflector_test_cases' -require 'constantize_test_cases' - -require 'active_support/inflector' -require 'active_support/core_ext/string' -require 'active_support/time' -require 'active_support/core_ext/string/strip' -require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/string/indent' - -class StringInflectionsTest < ActiveSupport::TestCase - include InflectorTestCases - include ConstantizeTestCases - - def test_strip_heredoc_on_an_empty_string - assert_equal '', ''.strip_heredoc - end - - def test_strip_heredoc_on_a_string_with_no_lines - assert_equal 'x', 'x'.strip_heredoc - assert_equal 'x', ' x'.strip_heredoc - end - - def test_strip_heredoc_on_a_heredoc_with_no_margin - assert_equal "foo\nbar", "foo\nbar".strip_heredoc - assert_equal "foo\n bar", "foo\n bar".strip_heredoc - end - - def test_strip_heredoc_on_a_regular_indented_heredoc - assert_equal "foo\n bar\nbaz\n", <<-EOS.strip_heredoc - foo - bar - baz - EOS - end - - def test_strip_heredoc_on_a_regular_indented_heredoc_with_blank_lines - assert_equal "foo\n bar\n\nbaz\n", <<-EOS.strip_heredoc - foo - bar - - baz - EOS - end - - def test_pluralize - SingularToPlural.each do |singular, plural| - assert_equal(plural, singular.pluralize) - end - - assert_equal("plurals", "plurals".pluralize) - - assert_equal("blargles", "blargle".pluralize(0)) - assert_equal("blargle", "blargle".pluralize(1)) - assert_equal("blargles", "blargle".pluralize(2)) - end - - def test_singularize - SingularToPlural.each do |singular, plural| - assert_equal(singular, plural.singularize) - end - end - - def test_titleize - MixtureToTitleCase.each do |before, titleized| - assert_equal(titleized, before.titleize) - end - end - - def test_camelize - CamelToUnderscore.each do |camel, underscore| - assert_equal(camel, underscore.camelize) - end - end - - def test_camelize_lower - assert_equal('capital', 'Capital'.camelize(:lower)) - end - - def test_dasherize - UnderscoresToDashes.each do |underscored, dasherized| - assert_equal(dasherized, underscored.dasherize) - end - end - - def test_underscore - CamelToUnderscore.each do |camel, underscore| - assert_equal(underscore, camel.underscore) - end - - assert_equal "html_tidy", "HTMLTidy".underscore - assert_equal "html_tidy_generator", "HTMLTidyGenerator".underscore - end - - def test_underscore_to_lower_camel - UnderscoreToLowerCamel.each do |underscored, lower_camel| - assert_equal(lower_camel, underscored.camelize(:lower)) - end - end - - def test_demodulize - assert_equal "Account", "MyApplication::Billing::Account".demodulize - end - - def test_deconstantize - assert_equal "MyApplication::Billing", "MyApplication::Billing::Account".deconstantize - end - - def test_foreign_key - ClassNameToForeignKeyWithUnderscore.each do |klass, foreign_key| - assert_equal(foreign_key, klass.foreign_key) - end - - ClassNameToForeignKeyWithoutUnderscore.each do |klass, foreign_key| - assert_equal(foreign_key, klass.foreign_key(false)) - end - end - - def test_tableize - ClassNameToTableName.each do |class_name, table_name| - assert_equal(table_name, class_name.tableize) - end - end - - def test_classify - ClassNameToTableName.each do |class_name, table_name| - assert_equal(class_name, table_name.classify) - end - end - - def test_string_parameterized_normal - StringToParameterized.each do |normal, slugged| - assert_equal(normal.parameterize, slugged) - end - end - - def test_string_parameterized_no_separator - StringToParameterizeWithNoSeparator.each do |normal, slugged| - assert_equal(normal.parameterize(''), slugged) - end - end - - def test_string_parameterized_underscore - StringToParameterizeWithUnderscore.each do |normal, slugged| - assert_equal(normal.parameterize('_'), slugged) - end - end - - def test_humanize - UnderscoreToHuman.each do |underscore, human| - assert_equal(human, underscore.humanize) - end - end - - def test_humanize_without_capitalize - UnderscoreToHumanWithoutCapitalize.each do |underscore, human| - assert_equal(human, underscore.humanize(capitalize: false)) - end - end - - def test_humanize_with_html_escape - assert_equal 'Hello', ERB::Util.html_escape("hello").humanize - end - - def test_ord - assert_equal 97, 'a'.ord - assert_equal 97, 'abc'.ord - end - - def test_starts_ends_with_alias - s = "hello" - assert s.starts_with?('h') - assert s.starts_with?('hel') - assert !s.starts_with?('el') - - assert s.ends_with?('o') - assert s.ends_with?('lo') - assert !s.ends_with?('el') - end - - def test_string_squish - original = %{\u180E\u180E A string surrounded by unicode mongolian vowel separators, - with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u180E\u180E} - - expected = "A string surrounded by unicode mongolian vowel separators, " + - "with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )." - - # Make sure squish returns what we expect: - assert_equal original.squish, expected - # But doesn't modify the original string: - assert_not_equal original, expected - - # Make sure squish! returns what we expect: - assert_equal original.squish!, expected - # And changes the original string: - assert_equal original, expected - end - - def test_string_inquiry - assert "production".inquiry.production? - assert !"production".inquiry.development? - end - - def test_truncate - assert_equal "Hello World!", "Hello World!".truncate(12) - assert_equal "Hello Wor...", "Hello World!!".truncate(12) - end - - def test_truncate_with_omission_and_seperator - assert_equal "Hello[...]", "Hello World!".truncate(10, :omission => "[...]") - assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => ' ') - assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => ' ') - assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => ' ') - end - - def test_truncate_with_omission_and_regexp_seperator - assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => /\s/) - assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => /\s/) - assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => /\s/) - end - - def test_truncate_multibyte - assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8), - "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8).truncate(10) - end - - def test_truncate_should_not_be_html_safe - assert !"Hello World!".truncate(12).html_safe? - end - - def test_remove - assert_equal "Summer", "Fast Summer".remove(/Fast /) - assert_equal "Summer", "Fast Summer".remove!(/Fast /) - end - - def test_constantize - run_constantize_tests_on do |string| - string.constantize - end - end - - def test_safe_constantize - run_safe_constantize_tests_on do |string| - string.safe_constantize - end - end -end - -class StringAccessTest < ActiveSupport::TestCase - test "#at with Fixnum, returns a substring of one character at that position" do - assert_equal "h", "hello".at(0) - end - - test "#at with Range, returns a substring containing characters at offsets" do - assert_equal "lo", "hello".at(-2..-1) - end - - test "#at with Regex, returns the matching portion of the string" do - assert_equal "lo", "hello".at(/lo/) - assert_equal nil, "hello".at(/nonexisting/) - end - - test "#from with positive Fixnum, returns substring from the given position to the end" do - assert_equal "llo", "hello".from(2) - end - - test "#from with negative Fixnum, position is counted from the end" do - assert_equal "lo", "hello".from(-2) - end - - test "#to with positive Fixnum, substring from the beginning to the given position" do - assert_equal "hel", "hello".to(2) - end - - test "#to with negative Fixnum, position is counted from the end" do - assert_equal "hell", "hello".to(-2) - end - - test "#from and #to can be combined" do - assert_equal "hello", "hello".from(0).to(-1) - assert_equal "ell", "hello".from(1).to(-2) - end - - test "#first returns the first character" do - assert_equal "h", "hello".first - assert_equal 'x', 'x'.first - end - - test "#first with Fixnum, returns a substring from the beginning to position" do - assert_equal "he", "hello".first(2) - assert_equal "", "hello".first(0) - assert_equal "hello", "hello".first(10) - assert_equal 'x', 'x'.first(4) - end - - test "#last returns the last character" do - assert_equal "o", "hello".last - assert_equal 'x', 'x'.last - end - - test "#last with Fixnum, returns a substring from the end to position" do - assert_equal "llo", "hello".last(3) - assert_equal "hello", "hello".last(10) - assert_equal "", "hello".last(0) - assert_equal 'x', 'x'.last(4) - end - - test "access returns a real string" do - hash = {} - hash["h"] = true - hash["hello123".at(0)] = true - assert_equal %w(h), hash.keys - - hash = {} - hash["llo"] = true - hash["hello".from(2)] = true - assert_equal %w(llo), hash.keys - - hash = {} - hash["hel"] = true - hash["hello".to(2)] = true - assert_equal %w(hel), hash.keys - - hash = {} - hash["hello"] = true - hash["123hello".last(5)] = true - assert_equal %w(hello), hash.keys - - hash = {} - hash["hello"] = true - hash["hello123".first(5)] = true - assert_equal %w(hello), hash.keys - end -end - -class StringConversionsTest < ActiveSupport::TestCase - def test_string_to_time - with_env_tz "Europe/Moscow" do - assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:utc) - assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time - assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:utc) - assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time - assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:utc) - assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time - assert_equal Time.local(2011, 2, 27, 17, 50), "2011-02-27 13:50 -0100".to_time - assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc) - assert_equal Time.local(2005, 2, 27, 22, 50), "2005-02-27 14:50 -0500".to_time - assert_nil "".to_time - end - end - - def test_string_to_time_utc_offset - with_env_tz "US/Eastern" do - assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset - assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset) - assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset - assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset) - end - end - - def test_partial_string_to_time - with_env_tz "Europe/Moscow" do - now = Time.now - assert_equal Time.local(now.year, now.month, now.day, 23, 50), "23:50".to_time - assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "23:50".to_time(:utc) - assert_equal Time.local(now.year, now.month, now.day, 18, 50), "13:50 -0100".to_time - assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "22:50 -0100".to_time(:utc) - end - end - - def test_standard_time_string_to_time_when_current_time_is_standard_time - with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 1, 1)) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc) - end - end - - def test_standard_time_string_to_time_when_current_time_is_daylight_savings - with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 7, 1)) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc) - end - end - - def test_daylight_savings_string_to_time_when_current_time_is_standard_time - with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 1, 1)) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc) - end - end - - def test_daylight_savings_string_to_time_when_current_time_is_daylight_savings - with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 7, 1)) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc) - end - end - - def test_partial_string_to_time_when_current_time_is_standard_time - with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 1, 1)) - assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time - assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time - assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time - assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc) - end - end - - def test_partial_string_to_time_when_current_time_is_daylight_savings - with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 7, 1)) - assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time - assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time - assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time - assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time - assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc) - end - end - - def test_string_to_datetime - assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_datetime - assert_equal 0, "2039-02-27 23:50".to_datetime.offset # use UTC offset - assert_equal ::Date::ITALY, "2039-02-27 23:50".to_datetime.start # use Ruby's default start value - assert_equal DateTime.civil(2039, 2, 27, 23, 50, 19 + Rational(275038, 1000000), "-04:00"), "2039-02-27T23:50:19.275038-04:00".to_datetime - assert_nil "".to_datetime - end - - def test_partial_string_to_datetime - now = DateTime.now - assert_equal DateTime.civil(now.year, now.month, now.day, 23, 50), "23:50".to_datetime - assert_equal DateTime.civil(now.year, now.month, now.day, 23, 50, 0, "-04:00"), "23:50 -0400".to_datetime - end - - def test_string_to_date - assert_equal Date.new(2005, 2, 27), "2005-02-27".to_date - assert_nil "".to_date - assert_equal Date.new(Date.today.year, 2, 3), "Feb 3rd".to_date - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end -end - -class StringBehaviourTest < ActiveSupport::TestCase - def test_acts_like_string - assert 'Bambi'.acts_like_string? - end -end - -class CoreExtStringMultibyteTest < ActiveSupport::TestCase - UTF8_STRING = 'こにちわ' - ASCII_STRING = 'ohayo'.encode('US-ASCII') - EUC_JP_STRING = 'さよなら'.encode('EUC-JP') - INVALID_UTF8_STRING = "\270\236\010\210\245" - - def test_core_ext_adds_mb_chars - assert_respond_to UTF8_STRING, :mb_chars - end - - def test_string_should_recognize_utf8_strings - assert UTF8_STRING.is_utf8? - assert ASCII_STRING.is_utf8? - assert !EUC_JP_STRING.is_utf8? - assert !INVALID_UTF8_STRING.is_utf8? - end - - def test_mb_chars_returns_instance_of_proxy_class - assert_kind_of ActiveSupport::Multibyte.proxy_class, UTF8_STRING.mb_chars - end -end - -class OutputSafetyTest < ActiveSupport::TestCase - def setup - @string = "hello" - @object = Class.new(Object) do - def to_s - "other" - end - end.new - end - - test "A string is unsafe by default" do - assert !@string.html_safe? - end - - test "A string can be marked safe" do - string = @string.html_safe - assert string.html_safe? - end - - test "Marking a string safe returns the string" do - assert_equal @string, @string.html_safe - end - - test "A fixnum is safe by default" do - assert 5.html_safe? - end - - test "a float is safe by default" do - assert 5.7.html_safe? - end - - test "An object is unsafe by default" do - assert !@object.html_safe? - end - - test "Adding an object to a safe string returns a safe string" do - string = @string.html_safe - string << @object - - assert_equal "helloother", string - assert string.html_safe? - end - - test "Adding a safe string to another safe string returns a safe string" do - @other_string = "other".html_safe - string = @string.html_safe - @combination = @other_string + string - - assert_equal "otherhello", @combination - assert @combination.html_safe? - end - - test "Adding an unsafe string to a safe string escapes it and returns a safe string" do - @other_string = "other".html_safe - @combination = @other_string + "" - @other_combination = @string + "" - - assert_equal "other<foo>", @combination - assert_equal "hello", @other_combination - - assert @combination.html_safe? - assert !@other_combination.html_safe? - end - - test "Prepending safe onto unsafe yields unsafe" do - @string.prepend "other".html_safe - assert !@string.html_safe? - assert_equal @string, "otherhello" - end - - test "Prepending unsafe onto safe yields escaped safe" do - other = "other".html_safe - other.prepend "" - assert other.html_safe? - assert_equal other, "<foo>other" - end - - test "Deprecated #prepend! method is still present" do - other = "other".html_safe - - assert_deprecated do - other.prepend! "" - end - - assert_equal other, "<foo>other" - end - - test "Concatting safe onto unsafe yields unsafe" do - @other_string = "other" - - string = @string.html_safe - @other_string.concat(string) - assert !@other_string.html_safe? - end - - test "Concatting unsafe onto safe yields escaped safe" do - @other_string = "other".html_safe - string = @other_string.concat("") - assert_equal "other<foo>", string - assert string.html_safe? - end - - test "Concatting safe onto safe yields safe" do - @other_string = "other".html_safe - string = @string.html_safe - - @other_string.concat(string) - assert @other_string.html_safe? - end - - test "Concatting safe onto unsafe with << yields unsafe" do - @other_string = "other" - string = @string.html_safe - - @other_string << string - assert !@other_string.html_safe? - end - - test "Concatting unsafe onto safe with << yields escaped safe" do - @other_string = "other".html_safe - string = @other_string << "" - assert_equal "other<foo>", string - assert string.html_safe? - end - - test "Concatting safe onto safe with << yields safe" do - @other_string = "other".html_safe - string = @string.html_safe - - @other_string << string - assert @other_string.html_safe? - end - - test "Concatting safe onto unsafe with % yields unsafe" do - @other_string = "other%s" - string = @string.html_safe - - @other_string = @other_string % string - assert !@other_string.html_safe? - end - - test "Concatting unsafe onto safe with % yields escaped safe" do - @other_string = "other%s".html_safe - string = @other_string % "" - - assert_equal "other<foo>", string - assert string.html_safe? - end - - test "Concatting safe onto safe with % yields safe" do - @other_string = "other%s".html_safe - string = @string.html_safe - - @other_string = @other_string % string - assert @other_string.html_safe? - end - - test "Concatting with % doesn't modify a string" do - @other_string = ["

", "", "

"] - _ = "%s %s %s".html_safe % @other_string - - assert_equal ["

", "", "

"], @other_string - end - - test "Concatting a fixnum to safe always yields safe" do - string = @string.html_safe - string = string.concat(13) - assert_equal "hello".concat(13), string - assert string.html_safe? - end - - test 'emits normal string yaml' do - assert_equal 'foo'.to_yaml, 'foo'.html_safe.to_yaml(:foo => 1) - end - - test "call to_param returns a normal string" do - string = @string.html_safe - assert string.html_safe? - assert !string.to_param.html_safe? - end - - test "ERB::Util.html_escape should escape unsafe characters" do - string = '<>&"\'' - expected = '<>&"'' - assert_equal expected, ERB::Util.html_escape(string) - end - - test "ERB::Util.html_escape should correctly handle invalid UTF-8 strings" do - string = [192, 60].pack('CC') - expected = 192.chr + "<" - assert_equal expected, ERB::Util.html_escape(string) - end - - test "ERB::Util.html_escape should not escape safe strings" do - string = "hello".html_safe - assert_equal string, ERB::Util.html_escape(string) - end -end - -class StringExcludeTest < ActiveSupport::TestCase - test 'inverse of #include' do - assert_equal false, 'foo'.exclude?('o') - assert_equal true, 'foo'.exclude?('p') - end -end - -class StringIndentTest < ActiveSupport::TestCase - test 'does not indent strings that only contain newlines (edge cases)' do - ['', "\n", "\n" * 7].each do |str| - assert_nil str.indent!(8) - assert_equal str, str.indent(8) - assert_equal str, str.indent(1, "\t") - end - end - - test "by default, indents with spaces if the existing indentation uses them" do - assert_equal " foo\n bar", "foo\n bar".indent(4) - end - - test "by default, indents with tabs if the existing indentation uses them" do - assert_equal "\tfoo\n\t\t\bar", "foo\n\t\bar".indent(1) - end - - test "by default, indents with spaces as a fallback if there is no indentation" do - assert_equal " foo\n bar\n baz", "foo\nbar\nbaz".indent(3) - end - - # Nothing is said about existing indentation that mixes spaces and tabs, so - # there is nothing to test. - - test 'uses the indent char if passed' do - assert_equal < April 3rd 3:00am DT - assert_equal 2*3600-1, Time.local(2005,4,3,1,59,59).seconds_since_midnight, 'just before DST start' - assert_equal 2*3600+1, Time.local(2005,4,3,3, 0, 1).seconds_since_midnight, 'just after DST start' - end - - with_env_tz 'NZ' do - # dt: New Zealand: 2006 October 1st 2:00am ST => October 1st 3:00am DT - assert_equal 2*3600-1, Time.local(2006,10,1,1,59,59).seconds_since_midnight, 'just before DST start' - assert_equal 2*3600+1, Time.local(2006,10,1,3, 0, 1).seconds_since_midnight, 'just after DST start' - end - end - - def test_seconds_since_midnight_at_daylight_savings_time_end - with_env_tz 'US/Eastern' do - # st: US: 2005 October 30th 2:00am DT => October 30th 1:00am ST - # avoid setting a time between 1:00 and 2:00 since that requires specifying whether DST is active - assert_equal 1*3600-1, Time.local(2005,10,30,0,59,59).seconds_since_midnight, 'just before DST end' - assert_equal 3*3600+1, Time.local(2005,10,30,2, 0, 1).seconds_since_midnight, 'just after DST end' - - # now set a time between 1:00 and 2:00 by specifying whether DST is active - # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz ) - assert_equal 1*3600+30*60, Time.local(0,30,1,30,10,2005,0,0,true,ENV['TZ']).seconds_since_midnight, 'before DST end' - assert_equal 2*3600+30*60, Time.local(0,30,1,30,10,2005,0,0,false,ENV['TZ']).seconds_since_midnight, 'after DST end' - end - - with_env_tz 'NZ' do - # st: New Zealand: 2006 March 19th 3:00am DT => March 19th 2:00am ST - # avoid setting a time between 2:00 and 3:00 since that requires specifying whether DST is active - assert_equal 2*3600-1, Time.local(2006,3,19,1,59,59).seconds_since_midnight, 'just before DST end' - assert_equal 4*3600+1, Time.local(2006,3,19,3, 0, 1).seconds_since_midnight, 'just after DST end' - - # now set a time between 2:00 and 3:00 by specifying whether DST is active - # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz ) - assert_equal 2*3600+30*60, Time.local(0,30,2,19,3,2006,0,0,true, ENV['TZ']).seconds_since_midnight, 'before DST end' - assert_equal 3*3600+30*60, Time.local(0,30,2,19,3,2006,0,0,false,ENV['TZ']).seconds_since_midnight, 'after DST end' - end - end - - def test_seconds_until_end_of_day - assert_equal 0, Time.local(2005,1,1,23,59,59).seconds_until_end_of_day - assert_equal 1, Time.local(2005,1,1,23,59,58).seconds_until_end_of_day - assert_equal 60, Time.local(2005,1,1,23,58,59).seconds_until_end_of_day - assert_equal 3660, Time.local(2005,1,1,22,58,59).seconds_until_end_of_day - assert_equal 86399, Time.local(2005,1,1,0,0,0).seconds_until_end_of_day - end - - def test_seconds_until_end_of_day_at_daylight_savings_time_start - with_env_tz 'US/Eastern' do - # dt: US: 2005 April 3rd 2:00am ST => April 3rd 3:00am DT - assert_equal 21*3600, Time.local(2005,4,3,1,59,59).seconds_until_end_of_day, 'just before DST start' - assert_equal 21*3600-2, Time.local(2005,4,3,3,0,1).seconds_until_end_of_day, 'just after DST start' - end - - with_env_tz 'NZ' do - # dt: New Zealand: 2006 October 1st 2:00am ST => October 1st 3:00am DT - assert_equal 21*3600, Time.local(2006,10,1,1,59,59).seconds_until_end_of_day, 'just before DST start' - assert_equal 21*3600-2, Time.local(2006,10,1,3,0,1).seconds_until_end_of_day, 'just after DST start' - end - end - - def test_seconds_until_end_of_day_at_daylight_savings_time_end - with_env_tz 'US/Eastern' do - # st: US: 2005 October 30th 2:00am DT => October 30th 1:00am ST - # avoid setting a time between 1:00 and 2:00 since that requires specifying whether DST is active - assert_equal 24*3600, Time.local(2005,10,30,0,59,59).seconds_until_end_of_day, 'just before DST end' - assert_equal 22*3600-2, Time.local(2005,10,30,2,0,1).seconds_until_end_of_day, 'just after DST end' - - # now set a time between 1:00 and 2:00 by specifying whether DST is active - # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz ) - assert_equal 24*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,true,ENV['TZ']).seconds_until_end_of_day, 'before DST end' - assert_equal 23*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,false,ENV['TZ']).seconds_until_end_of_day, 'after DST end' - end - - with_env_tz 'NZ' do - # st: New Zealand: 2006 March 19th 3:00am DT => March 19th 2:00am ST - # avoid setting a time between 2:00 and 3:00 since that requires specifying whether DST is active - assert_equal 23*3600, Time.local(2006,3,19,1,59,59).seconds_until_end_of_day, 'just before DST end' - assert_equal 21*3600-2, Time.local(2006,3,19,3,0,1).seconds_until_end_of_day, 'just after DST end' - - # now set a time between 2:00 and 3:00 by specifying whether DST is active - # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz ) - assert_equal 23*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,true, ENV['TZ']).seconds_until_end_of_day, 'before DST end' - assert_equal 22*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,false,ENV['TZ']).seconds_until_end_of_day, 'after DST end' - end - end - - def test_beginning_of_day - assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day - with_env_tz 'US/Eastern' do - assert_equal Time.local(2006,4,2,0,0,0), Time.local(2006,4,2,10,10,10).beginning_of_day, 'start DST' - assert_equal Time.local(2006,10,29,0,0,0), Time.local(2006,10,29,10,10,10).beginning_of_day, 'ends DST' - end - with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,0,0,0), Time.local(2006,3,19,10,10,10).beginning_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,0,0,0), Time.local(2006,10,1,10,10,10).beginning_of_day, 'start DST' - end - end - - def test_middle_of_day - assert_equal Time.local(2005,2,4,12,0,0), Time.local(2005,2,4,10,10,10).middle_of_day - with_env_tz 'US/Eastern' do - assert_equal Time.local(2006,4,2,12,0,0), Time.local(2006,4,2,10,10,10).middle_of_day, 'start DST' - assert_equal Time.local(2006,10,29,12,0,0), Time.local(2006,10,29,10,10,10).middle_of_day, 'ends DST' - end - with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,12,0,0), Time.local(2006,3,19,10,10,10).middle_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,12,0,0), Time.local(2006,10,1,10,10,10).middle_of_day, 'start DST' - end - end - - def test_beginning_of_hour - assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour - end - - def test_beginning_of_minute - assert_equal Time.local(2005,2,4,19,30,0), Time.local(2005,2,4,19,30,10).beginning_of_minute - end - - def test_end_of_day - assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day - with_env_tz 'US/Eastern' do - assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' - assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' - end - with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' - end - end - - def test_end_of_hour - assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour - end - - def test_end_of_minute - assert_equal Time.local(2005,2,4,19,30,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_minute - end - - def test_last_year - assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year - end - - def test_ago - assert_equal Time.local(2005,2,22,10,10,9), Time.local(2005,2,22,10,10,10).ago(1) - assert_equal Time.local(2005,2,22,9,10,10), Time.local(2005,2,22,10,10,10).ago(3600) - assert_equal Time.local(2005,2,20,10,10,10), Time.local(2005,2,22,10,10,10).ago(86400*2) - assert_equal Time.local(2005,2,20,9,9,45), Time.local(2005,2,22,10,10,10).ago(86400*2 + 3600 + 25) - end - - def test_daylight_savings_time_crossings_backward_start - with_env_tz 'US/Eastern' do - # dt: US: 2005 April 3rd 4:18am - assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(24.hours), 'dt-24.hours=>st' - assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400), 'dt-86400=>st' - assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400.seconds), 'dt-86400.seconds=>st' - - assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(24.hours), 'st-24.hours=>st' - assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400), 'st-86400=>st' - assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400.seconds), 'st-86400.seconds=>st' - end - with_env_tz 'NZ' do - # dt: New Zealand: 2006 October 1st 4:18am - assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(24.hours), 'dt-24.hours=>st' - assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400), 'dt-86400=>st' - assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400.seconds), 'dt-86400.seconds=>st' - - assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(24.hours), 'st-24.hours=>st' - assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400), 'st-86400=>st' - assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400.seconds), 'st-86400.seconds=>st' - end - end - - def test_daylight_savings_time_crossings_backward_end - with_env_tz 'US/Eastern' do - # st: US: 2005 October 30th 4:03am - assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(24.hours), 'st-24.hours=>dt' - assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400), 'st-86400=>dt' - assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400.seconds), 'st-86400.seconds=>dt' - - assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(24.hours), 'dt-24.hours=>dt' - assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400), 'dt-86400=>dt' - assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400.seconds), 'dt-86400.seconds=>dt' - end - with_env_tz 'NZ' do - # st: New Zealand: 2006 March 19th 4:03am - assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(24.hours), 'st-24.hours=>dt' - assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400), 'st-86400=>dt' - assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400.seconds), 'st-86400.seconds=>dt' - - assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(24.hours), 'dt-24.hours=>dt' - assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400), 'dt-86400=>dt' - assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400.seconds), 'dt-86400.seconds=>dt' - end - end - - def test_daylight_savings_time_crossings_backward_start_1day - with_env_tz 'US/Eastern' do - # dt: US: 2005 April 3rd 4:18am - assert_equal Time.local(2005,4,2,4,18,0), Time.local(2005,4,3,4,18,0).ago(1.day), 'dt-1.day=>st' - assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(1.day), 'st-1.day=>st' - end - with_env_tz 'NZ' do - # dt: New Zealand: 2006 October 1st 4:18am - assert_equal Time.local(2006,9,30,4,18,0), Time.local(2006,10,1,4,18,0).ago(1.day), 'dt-1.day=>st' - assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(1.day), 'st-1.day=>st' - end - end - - def test_daylight_savings_time_crossings_backward_end_1day - with_env_tz 'US/Eastern' do - # st: US: 2005 October 30th 4:03am - assert_equal Time.local(2005,10,29,4,3), Time.local(2005,10,30,4,3,0).ago(1.day), 'st-1.day=>dt' - assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(1.day), 'dt-1.day=>dt' - end - with_env_tz 'NZ' do - # st: New Zealand: 2006 March 19th 4:03am - assert_equal Time.local(2006,3,18,4,3), Time.local(2006,3,19,4,3,0).ago(1.day), 'st-1.day=>dt' - assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(1.day), 'dt-1.day=>dt' - end - end - - def test_since - assert_equal Time.local(2005,2,22,10,10,11), Time.local(2005,2,22,10,10,10).since(1) - assert_equal Time.local(2005,2,22,11,10,10), Time.local(2005,2,22,10,10,10).since(3600) - assert_equal Time.local(2005,2,24,10,10,10), Time.local(2005,2,22,10,10,10).since(86400*2) - assert_equal Time.local(2005,2,24,11,10,35), Time.local(2005,2,22,10,10,10).since(86400*2 + 3600 + 25) - # when out of range of Time, returns a DateTime - assert_equal DateTime.civil(2038,1,20,11,59,59), Time.utc(2038,1,18,11,59,59).since(86400*2) - end - - def test_daylight_savings_time_crossings_forward_start - with_env_tz 'US/Eastern' do - # st: US: 2005 April 2nd 7:27pm - assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(24.hours), 'st+24.hours=>dt' - assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400), 'st+86400=>dt' - assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400.seconds), 'st+86400.seconds=>dt' - - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(24.hours), 'dt+24.hours=>dt' - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400), 'dt+86400=>dt' - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400.seconds), 'dt+86400.seconds=>dt' - end - with_env_tz 'NZ' do - # st: New Zealand: 2006 September 30th 7:27pm - assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(24.hours), 'st+24.hours=>dt' - assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400), 'st+86400=>dt' - assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400.seconds), 'st+86400.seconds=>dt' - - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(24.hours), 'dt+24.hours=>dt' - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400), 'dt+86400=>dt' - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400.seconds), 'dt+86400.seconds=>dt' - end - end - - def test_daylight_savings_time_crossings_forward_start_1day - with_env_tz 'US/Eastern' do - # st: US: 2005 April 2nd 7:27pm - assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,2,19,27,0).since(1.day), 'st+1.day=>dt' - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(1.day), 'dt+1.day=>dt' - end - with_env_tz 'NZ' do - # st: New Zealand: 2006 September 30th 7:27pm - assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,9,30,19,27,0).since(1.day), 'st+1.day=>dt' - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(1.day), 'dt+1.day=>dt' - end - end - - def test_daylight_savings_time_crossings_forward_start_tomorrow - with_env_tz 'US/Eastern' do - # st: US: 2005 April 2nd 7:27pm - assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,2,19,27,0).tomorrow, 'st+1.day=>dt' - assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).tomorrow, 'dt+1.day=>dt' - end - with_env_tz 'NZ' do - # st: New Zealand: 2006 September 30th 7:27pm - assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,9,30,19,27,0).tomorrow, 'st+1.day=>dt' - assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).tomorrow, 'dt+1.day=>dt' - end - end - - def test_daylight_savings_time_crossings_backward_start_yesterday - with_env_tz 'US/Eastern' do - # st: US: 2005 April 2nd 7:27pm - assert_equal Time.local(2005,4,2,19,27,0), Time.local(2005,4,3,19,27,0).yesterday, 'dt-1.day=>st' - assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,4,19,27,0).yesterday, 'dt-1.day=>dt' - end - with_env_tz 'NZ' do - # st: New Zealand: 2006 September 30th 7:27pm - assert_equal Time.local(2006,9,30,19,27,0), Time.local(2006,10,1,19,27,0).yesterday, 'dt-1.day=>st' - assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,10,2,19,27,0).yesterday, 'dt-1.day=>dt' - end - end - - def test_daylight_savings_time_crossings_forward_end - with_env_tz 'US/Eastern' do - # dt: US: 2005 October 30th 12:45am - assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(24.hours), 'dt+24.hours=>st' - assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400), 'dt+86400=>st' - assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400.seconds), 'dt+86400.seconds=>st' - - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(24.hours), 'st+24.hours=>st' - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400), 'st+86400=>st' - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400.seconds), 'st+86400.seconds=>st' - end - with_env_tz 'NZ' do - # dt: New Zealand: 2006 March 19th 1:45am - assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(24.hours), 'dt+24.hours=>st' - assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400), 'dt+86400=>st' - assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400.seconds), 'dt+86400.seconds=>st' - - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(24.hours), 'st+24.hours=>st' - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400), 'st+86400=>st' - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400.seconds), 'st+86400.seconds=>st' - end - end - - def test_daylight_savings_time_crossings_forward_end_1day - with_env_tz 'US/Eastern' do - # dt: US: 2005 October 30th 12:45am - assert_equal Time.local(2005,10,31,0,45,0), Time.local(2005,10,30,0,45,0).since(1.day), 'dt+1.day=>st' - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(1.day), 'st+1.day=>st' - end - with_env_tz 'NZ' do - # dt: New Zealand: 2006 March 19th 1:45am - assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,19,1,45,0).since(1.day), 'dt+1.day=>st' - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(1.day), 'st+1.day=>st' - end - end - - def test_daylight_savings_time_crossings_forward_end_tomorrow - with_env_tz 'US/Eastern' do - # dt: US: 2005 October 30th 12:45am - assert_equal Time.local(2005,10,31,0,45,0), Time.local(2005,10,30,0,45,0).tomorrow, 'dt+1.day=>st' - assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).tomorrow, 'st+1.day=>st' - end - with_env_tz 'NZ' do - # dt: New Zealand: 2006 March 19th 1:45am - assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,19,1,45,0).tomorrow, 'dt+1.day=>st' - assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).tomorrow, 'st+1.day=>st' - end - end - - def test_daylight_savings_time_crossings_backward_end_yesterday - with_env_tz 'US/Eastern' do - # dt: US: 2005 October 30th 12:45am - assert_equal Time.local(2005,10,30,0,45,0), Time.local(2005,10,31,0,45,0).yesterday, 'st-1.day=>dt' - assert_equal Time.local(2005,10, 31,0,45,0), Time.local(2005,11,1,0,45,0).yesterday, 'st-1.day=>st' - end - with_env_tz 'NZ' do - # dt: New Zealand: 2006 March 19th 1:45am - assert_equal Time.local(2006,3,19,1,45,0), Time.local(2006,3,20,1,45,0).yesterday, 'st-1.day=>dt' - assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,21,1,45,0).yesterday, 'st-1.day=>st' - end - end - - def test_change - assert_equal Time.local(2006,2,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:year => 2006) - assert_equal Time.local(2005,6,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:month => 6) - assert_equal Time.local(2012,9,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:year => 2012, :month => 9) - assert_equal Time.local(2005,2,22,16), Time.local(2005,2,22,15,15,10).change(:hour => 16) - assert_equal Time.local(2005,2,22,16,45), Time.local(2005,2,22,15,15,10).change(:hour => 16, :min => 45) - assert_equal Time.local(2005,2,22,15,45), Time.local(2005,2,22,15,15,10).change(:min => 45) - - assert_equal Time.local(2005,1,2, 5, 0, 0, 0), Time.local(2005,1,2,11,22,33,44).change(:hour => 5) - assert_equal Time.local(2005,1,2,11, 6, 0, 0), Time.local(2005,1,2,11,22,33,44).change(:min => 6) - assert_equal Time.local(2005,1,2,11,22, 7, 0), Time.local(2005,1,2,11,22,33,44).change(:sec => 7) - assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8) - end - - def test_utc_change - assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:year => 2006) - assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:month => 6) - assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:year => 2012, :month => 9) - assert_equal Time.utc(2005,2,22,16), Time.utc(2005,2,22,15,15,10).change(:hour => 16) - assert_equal Time.utc(2005,2,22,16,45), Time.utc(2005,2,22,15,15,10).change(:hour => 16, :min => 45) - assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(:min => 45) - end - - def test_offset_change - assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2006) - assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:month => 6) - assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2012, :month => 9) - assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16) - assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16, :min => 45) - assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:min => 45) - end - - def test_advance - assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 1) - assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:months => 4) - assert_equal Time.local(2005,3,21,15,15,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3) - assert_equal Time.local(2005,3,25,3,15,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3.5) - assert_in_delta Time.local(2005,3,26,12,51,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3.7), 1 - assert_equal Time.local(2005,3,5,15,15,10), Time.local(2005,2,28,15,15,10).advance(:days => 5) - assert_equal Time.local(2005,3,6,3,15,10), Time.local(2005,2,28,15,15,10).advance(:days => 5.5) - assert_in_delta Time.local(2005,3,6,8,3,10), Time.local(2005,2,28,15,15,10).advance(:days => 5.7), 1 - assert_equal Time.local(2012,9,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 7) - assert_equal Time.local(2013,10,3,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) - assert_equal Time.local(2013,10,17,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal Time.local(2001,12,27,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1) - assert_equal Time.local(2005,2,28,15,15,10), Time.local(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year - assert_equal Time.local(2005,2,28,20,15,10), Time.local(2005,2,28,15,15,10).advance(:hours => 5) - assert_equal Time.local(2005,2,28,15,22,10), Time.local(2005,2,28,15,15,10).advance(:minutes => 7) - assert_equal Time.local(2005,2,28,15,15,19), Time.local(2005,2,28,15,15,10).advance(:seconds => 9) - assert_equal Time.local(2005,2,28,20,22,19), Time.local(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal Time.local(2005,2,28,10,8,1), Time.local(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) - assert_equal Time.local(2013,10,17,20,22,19), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) - end - - def test_utc_advance - assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 1) - assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:months => 4) - assert_equal Time.utc(2005,3,21,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3) - assert_equal Time.utc(2005,3,25,3,15,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3.5) - assert_in_delta Time.utc(2005,3,26,12,51,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3.7), 1 - assert_equal Time.utc(2005,3,5,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5) - assert_equal Time.utc(2005,3,6,3,15,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5.5) - assert_in_delta Time.utc(2005,3,6,8,3,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5.7), 1 - assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 7) - assert_equal Time.utc(2013,10,3,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 19, :days => 11) - assert_equal Time.utc(2013,10,17,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal Time.utc(2001,12,27,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1) - assert_equal Time.utc(2005,2,28,15,15,10), Time.utc(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year - assert_equal Time.utc(2005,2,28,20,15,10), Time.utc(2005,2,28,15,15,10).advance(:hours => 5) - assert_equal Time.utc(2005,2,28,15,22,10), Time.utc(2005,2,28,15,15,10).advance(:minutes => 7) - assert_equal Time.utc(2005,2,28,15,15,19), Time.utc(2005,2,28,15,15,10).advance(:seconds => 9) - assert_equal Time.utc(2005,2,28,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal Time.utc(2005,2,28,10,8,1), Time.utc(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) - assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) - end - - def test_offset_advance - assert_equal Time.new(2006,2,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 1) - assert_equal Time.new(2005,6,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:months => 4) - assert_equal Time.new(2005,3,21,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3) - assert_equal Time.new(2005,3,25,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.5) - assert_in_delta Time.new(2005,3,26,12,51,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.7), 1 - assert_equal Time.new(2005,3,5,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5) - assert_equal Time.new(2005,3,6,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.5) - assert_in_delta Time.new(2005,3,6,8,3,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.7), 1 - assert_equal Time.new(2012,9,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 7) - assert_equal Time.new(2013,10,3,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 19, :days => 11) - assert_equal Time.new(2013,10,17,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5) - assert_equal Time.new(2001,12,27,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => -3, :months => -2, :days => -1) - assert_equal Time.new(2005,2,28,15,15,10,'-08:00'), Time.new(2004,2,29,15,15,10,'-08:00').advance(:years => 1) #leap day plus one year - assert_equal Time.new(2005,2,28,20,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5) - assert_equal Time.new(2005,2,28,15,22,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:minutes => 7) - assert_equal Time.new(2005,2,28,15,15,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:seconds => 9) - assert_equal Time.new(2005,2,28,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal Time.new(2005,2,28,10,8,1,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => -5, :minutes => -7, :seconds => -9) - assert_equal Time.new(2013,10,17,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) - end - - def test_advance_with_nsec - t = Time.at(0, Rational(108635108, 1000)) - assert_equal t, t.advance(:months => 0) - end - - def test_advance_gregorian_proleptic - assert_equal Time.local(1582,10,14,15,15,10), Time.local(1582,10,15,15,15,10).advance(:days => -1) - assert_equal Time.local(1582,10,15,15,15,10), Time.local(1582,10,14,15,15,10).advance(:days => 1) - assert_equal Time.local(1582,10,5,15,15,10), Time.local(1582,10,4,15,15,10).advance(:days => 1) - assert_equal Time.local(1582,10,4,15,15,10), Time.local(1582,10,5,15,15,10).advance(:days => -1) - end - - def test_last_week - with_env_tz 'US/Eastern' do - assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week - assert_equal Time.local(2005,2,22), Time.local(2005,3,1,15,15,10).last_week(:tuesday) - assert_equal Time.local(2005,2,25), Time.local(2005,3,1,15,15,10).last_week(:friday) - assert_equal Time.local(2006,10,30), Time.local(2006,11,6,0,0,0).last_week - assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).last_week(:wednesday) - end - end - - def test_next_week_near_daylight_start - with_env_tz 'US/Eastern' do - assert_equal Time.local(2006,4,3), Time.local(2006,4,2,23,1,0).next_week, 'just crossed standard => daylight' - end - with_env_tz 'NZ' do - assert_equal Time.local(2006,10,2), Time.local(2006,10,1,23,1,0).next_week, 'just crossed standard => daylight' - end - end - - def test_next_week_near_daylight_end - with_env_tz 'US/Eastern' do - assert_equal Time.local(2006,10,30), Time.local(2006,10,29,23,1,0).next_week, 'just crossed daylight => standard' - end - with_env_tz 'NZ' do - assert_equal Time.local(2006,3,20), Time.local(2006,3,19,23,1,0).next_week, 'just crossed daylight => standard' - end - end - - def test_to_s - time = Time.utc(2005, 2, 21, 17, 44, 30.12345678901) - assert_equal time.to_default_s, time.to_s - assert_equal time.to_default_s, time.to_s(:doesnt_exist) - assert_equal "2005-02-21 17:44:30", time.to_s(:db) - assert_equal "21 Feb 17:44", time.to_s(:short) - assert_equal "17:44", time.to_s(:time) - assert_equal "20050221174430", time.to_s(:number) - assert_equal "20050221174430123456789", time.to_s(:nsec) - assert_equal "February 21, 2005 17:44", time.to_s(:long) - assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal) - with_env_tz "UTC" do - assert_equal "Mon, 21 Feb 2005 17:44:30 +0000", time.to_s(:rfc822) - end - with_env_tz "US/Central" do - assert_equal "Thu, 05 Feb 2009 14:30:05 -0600", Time.local(2009, 2, 5, 14, 30, 5).to_s(:rfc822) - assert_equal "Mon, 09 Jun 2008 04:05:01 -0500", Time.local(2008, 6, 9, 4, 5, 1).to_s(:rfc822) - assert_equal "2009-02-05T14:30:05-06:00", Time.local(2009, 2, 5, 14, 30, 5).to_s(:iso8601) - assert_equal "2008-06-09T04:05:01-05:00", Time.local(2008, 6, 9, 4, 5, 1).to_s(:iso8601) - assert_equal "2009-02-05T14:30:05Z", Time.utc(2009, 2, 5, 14, 30, 5).to_s(:iso8601) - end - end - - def test_custom_date_format - Time::DATE_FORMATS[:custom] = '%Y%m%d%H%M%S' - assert_equal '20050221143000', Time.local(2005, 2, 21, 14, 30, 0).to_s(:custom) - Time::DATE_FORMATS.delete(:custom) - end - - def test_to_date - assert_equal Date.new(2005, 2, 21), Time.local(2005, 2, 21, 17, 44, 30).to_date - end - - def test_to_datetime - assert_equal Time.utc(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, 0) - with_env_tz 'US/Eastern' do - assert_equal Time.local(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, Rational(Time.local(2005, 2, 21, 17, 44, 30).utc_offset, 86400)) - end - with_env_tz 'NZ' do - assert_equal Time.local(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, Rational(Time.local(2005, 2, 21, 17, 44, 30).utc_offset, 86400)) - end - assert_equal ::Date::ITALY, Time.utc(2005, 2, 21, 17, 44, 30).to_datetime.start # use Ruby's default start value - end - - def test_to_time - with_env_tz 'US/Eastern' do - assert_equal Time, Time.local(2005, 2, 21, 17, 44, 30).to_time.class - assert_equal Time.local(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30).to_time - assert_equal Time.local(2005, 2, 21, 17, 44, 30).utc_offset, Time.local(2005, 2, 21, 17, 44, 30).to_time.utc_offset - end - end - - # NOTE: this test seems to fail (changeset 1958) only on certain platforms, - # like OSX, and FreeBSD 5.4. - def test_fp_inaccuracy_ticket_1836 - midnight = Time.local(2005, 2, 21, 0, 0, 0) - assert_equal midnight.midnight, (midnight + 1.hour + 0.000001).midnight - end - - def test_days_in_month_with_year - assert_equal 31, Time.days_in_month(1, 2005) - - assert_equal 28, Time.days_in_month(2, 2005) - assert_equal 29, Time.days_in_month(2, 2004) - assert_equal 29, Time.days_in_month(2, 2000) - assert_equal 28, Time.days_in_month(2, 1900) - - assert_equal 31, Time.days_in_month(3, 2005) - assert_equal 30, Time.days_in_month(4, 2005) - assert_equal 31, Time.days_in_month(5, 2005) - assert_equal 30, Time.days_in_month(6, 2005) - assert_equal 31, Time.days_in_month(7, 2005) - assert_equal 31, Time.days_in_month(8, 2005) - assert_equal 30, Time.days_in_month(9, 2005) - assert_equal 31, Time.days_in_month(10, 2005) - assert_equal 30, Time.days_in_month(11, 2005) - assert_equal 31, Time.days_in_month(12, 2005) - end - - def test_days_in_month_feb_in_common_year_without_year_arg - Time.stubs(:now).returns(Time.utc(2007)) - assert_equal 28, Time.days_in_month(2) - end - - def test_days_in_month_feb_in_leap_year_without_year_arg - Time.stubs(:now).returns(Time.utc(2008)) - assert_equal 29, Time.days_in_month(2) - end - - def test_last_month_on_31st - assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month - end - - def test_xmlschema_is_available - assert_nothing_raised { Time.now.xmlschema } - end - - def test_today_with_time_local - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, Time.local(1999,12,31,23,59,59).today? - assert_equal true, Time.local(2000,1,1,0).today? - assert_equal true, Time.local(2000,1,1,23,59,59).today? - assert_equal false, Time.local(2000,1,2,0).today? - end - - def test_today_with_time_utc - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, Time.utc(1999,12,31,23,59,59).today? - assert_equal true, Time.utc(2000,1,1,0).today? - assert_equal true, Time.utc(2000,1,1,23,59,59).today? - assert_equal false, Time.utc(2000,1,2,0).today? - end - - def test_past_with_time_current_as_time_local - with_env_tz 'US/Eastern' do - Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45)) - assert_equal true, Time.local(2005,2,10,15,30,44).past? - assert_equal false, Time.local(2005,2,10,15,30,45).past? - assert_equal false, Time.local(2005,2,10,15,30,46).past? - assert_equal true, Time.utc(2005,2,10,20,30,44).past? - assert_equal false, Time.utc(2005,2,10,20,30,45).past? - assert_equal false, Time.utc(2005,2,10,20,30,46).past? - end - end - - def test_past_with_time_current_as_time_with_zone - with_env_tz 'US/Eastern' do - twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)') - Time.stubs(:current).returns(twz) - assert_equal true, Time.local(2005,2,10,10,30,44).past? - assert_equal false, Time.local(2005,2,10,10,30,45).past? - assert_equal false, Time.local(2005,2,10,10,30,46).past? - assert_equal true, Time.utc(2005,2,10,15,30,44).past? - assert_equal false, Time.utc(2005,2,10,15,30,45).past? - assert_equal false, Time.utc(2005,2,10,15,30,46).past? - end - end - - def test_future_with_time_current_as_time_local - with_env_tz 'US/Eastern' do - Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45)) - assert_equal false, Time.local(2005,2,10,15,30,44).future? - assert_equal false, Time.local(2005,2,10,15,30,45).future? - assert_equal true, Time.local(2005,2,10,15,30,46).future? - assert_equal false, Time.utc(2005,2,10,20,30,44).future? - assert_equal false, Time.utc(2005,2,10,20,30,45).future? - assert_equal true, Time.utc(2005,2,10,20,30,46).future? - end - end - - def test_future_with_time_current_as_time_with_zone - with_env_tz 'US/Eastern' do - twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)') - Time.stubs(:current).returns(twz) - assert_equal false, Time.local(2005,2,10,10,30,44).future? - assert_equal false, Time.local(2005,2,10,10,30,45).future? - assert_equal true, Time.local(2005,2,10,10,30,46).future? - assert_equal false, Time.utc(2005,2,10,15,30,44).future? - assert_equal false, Time.utc(2005,2,10,15,30,45).future? - assert_equal true, Time.utc(2005,2,10,15,30,46).future? - end - end - - def test_acts_like_time - assert Time.new.acts_like_time? - end - - def test_formatted_offset_with_utc - assert_equal '+00:00', Time.utc(2000).formatted_offset - assert_equal '+0000', Time.utc(2000).formatted_offset(false) - assert_equal 'UTC', Time.utc(2000).formatted_offset(true, 'UTC') - end - - def test_formatted_offset_with_local - with_env_tz 'US/Eastern' do - assert_equal '-05:00', Time.local(2000).formatted_offset - assert_equal '-0500', Time.local(2000).formatted_offset(false) - assert_equal '-04:00', Time.local(2000, 7).formatted_offset - assert_equal '-0400', Time.local(2000, 7).formatted_offset(false) - end - end - - def test_compare_with_time - assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999) - assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0) - assert_equal(-1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0, 001)) - end - - def test_compare_with_datetime - assert_equal 1, Time.utc(2000) <=> DateTime.civil(1999, 12, 31, 23, 59, 59) - assert_equal 0, Time.utc(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 0) - assert_equal(-1, Time.utc(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 1)) - end - - def test_compare_with_time_with_zone - assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) - assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) - assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) - end - - def test_at_with_datetime - assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0)) - - # Only test this if the underlying Time.at raises a TypeError - begin - Time.at_without_coercion(Time.now, 0) - rescue TypeError - assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0), 0)) } - end - end - - def test_at_with_datetime_returns_local_time - with_env_tz 'US/Eastern' do - dt = DateTime.civil(2000, 1, 1, 0, 0, 0, '+0') - assert_equal Time.local(1999, 12, 31, 19, 0, 0), Time.at(dt) - assert_equal 'EST', Time.at(dt).zone - assert_equal(-18000, Time.at(dt).utc_offset) - - # Daylight savings - dt = DateTime.civil(2000, 7, 1, 1, 0, 0, '+1') - assert_equal Time.local(2000, 6, 30, 20, 0, 0), Time.at(dt) - assert_equal 'EDT', Time.at(dt).zone - assert_equal(-14400, Time.at(dt).utc_offset) - end - end - - def test_at_with_time_with_zone - assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'])) - - # Only test this if the underlying Time.at raises a TypeError - begin - Time.at_without_coercion(Time.now, 0) - rescue TypeError - assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC']), 0)) } - end - end - - def test_at_with_time_with_zone_returns_local_time - with_env_tz 'US/Eastern' do - twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['London']) - assert_equal Time.local(1999, 12, 31, 19, 0, 0), Time.at(twz) - assert_equal 'EST', Time.at(twz).zone - assert_equal(-18000, Time.at(twz).utc_offset) - - # Daylight savings - twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 7, 1, 0, 0, 0), ActiveSupport::TimeZone['London']) - assert_equal Time.local(2000, 6, 30, 20, 0, 0), Time.at(twz) - assert_equal 'EDT', Time.at(twz).zone - assert_equal(-14400, Time.at(twz).utc_offset) - end - end - - def test_at_with_time_microsecond_precision - assert_equal Time.at(Time.utc(2000, 1, 1, 0, 0, 0, 111)).to_f, Time.utc(2000, 1, 1, 0, 0, 0, 111).to_f - end - - def test_at_with_utc_time - with_env_tz 'US/Eastern' do - assert_equal Time.utc(2000), Time.at(Time.utc(2000)) - assert_equal 'UTC', Time.at(Time.utc(2000)).zone - assert_equal(0, Time.at(Time.utc(2000)).utc_offset) - end - end - - def test_at_with_local_time - with_env_tz 'US/Eastern' do - assert_equal Time.local(2000), Time.at(Time.local(2000)) - assert_equal 'EST', Time.at(Time.local(2000)).zone - assert_equal(-18000, Time.at(Time.local(2000)).utc_offset) - - assert_equal Time.local(2000, 7, 1), Time.at(Time.local(2000, 7, 1)) - assert_equal 'EDT', Time.at(Time.local(2000, 7, 1)).zone - assert_equal(-14400, Time.at(Time.local(2000, 7, 1)).utc_offset) - end - end - - def test_eql? - assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) - assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) - assert_equal false,Time.utc(2000, 1, 1, 0, 0, 1).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) - end - - def test_minus_with_time_with_zone - assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] ) - end - - def test_minus_with_datetime - assert_equal 86_400.0, Time.utc(2000, 1, 2) - DateTime.civil(2000, 1, 1) - end - - def test_time_created_with_local_constructor_cannot_represent_times_during_hour_skipped_by_dst - with_env_tz 'US/Eastern' do - # On Apr 2 2006 at 2:00AM in US, clocks were moved forward to 3:00AM. - # Therefore, 2AM EST doesn't exist for this date; Time.local fails over to 3:00AM EDT - assert_equal Time.local(2006, 4, 2, 3), Time.local(2006, 4, 2, 2) - assert Time.local(2006, 4, 2, 2).dst? - end - end - - def test_case_equality - assert Time === Time.utc(2000) - assert Time === ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) - assert Time === Class.new(Time).utc(2000) - assert_equal false, Time === DateTime.civil(2000) - assert_equal false, Class.new(Time) === Time.utc(2000) - assert_equal false, Class.new(Time) === ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) - end - - def test_all_day - assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_day - end - - def test_all_day_with_timezone - beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,0,0,0)) - end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))) - - assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin - assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.end - end - - def test_all_week - assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week - assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week(:sunday) - end - - def test_all_month - assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_month - end - - def test_all_quarter - assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_quarter - end - - def test_all_year - assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_year - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end - -end - -class TimeExtMarshalingTest < ActiveSupport::TestCase - def test_marshaling_with_utc_instance - t = Time.utc(2000) - unmarshaled = Marshal.load(Marshal.dump(t)) - assert_equal "UTC", unmarshaled.zone - assert_equal t, unmarshaled - end - - def test_marshaling_with_local_instance - t = Time.local(2000) - unmarshaled = Marshal.load(Marshal.dump(t)) - assert_equal t.zone, unmarshaled.zone - assert_equal t, unmarshaled - end - - def test_marshaling_with_frozen_utc_instance - t = Time.utc(2000).freeze - unmarshaled = Marshal.load(Marshal.dump(t)) - assert_equal "UTC", unmarshaled.zone - assert_equal t, unmarshaled - end - - def test_marshaling_with_frozen_local_instance - t = Time.local(2000).freeze - unmarshaled = Marshal.load(Marshal.dump(t)) - assert_equal t.zone, unmarshaled.zone - assert_equal t, unmarshaled - end - - def test_marshalling_preserves_fractional_seconds - t = Time.parse('00:00:00.500') - unmarshaled = Marshal.load(Marshal.dump(t)) - assert_equal t.to_f, unmarshaled.to_f - assert_equal t, unmarshaled - end - - def test_last_quarter_on_31st - assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).last_quarter - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/time_with_zone_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/time_with_zone_test.rb deleted file mode 100644 index 7fe4d4a6b2..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/time_with_zone_test.rb +++ /dev/null @@ -1,1138 +0,0 @@ -require 'abstract_unit' -require 'active_support/time' - -class TimeWithZoneTest < ActiveSupport::TestCase - - def setup - @utc = Time.utc(2000, 1, 1, 0) - @time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - @twz = ActiveSupport::TimeWithZone.new(@utc, @time_zone) - end - - def test_utc - assert_equal @utc, @twz.utc - end - - def test_time - assert_equal Time.utc(1999, 12, 31, 19), @twz.time - end - - def test_time_zone - assert_equal @time_zone, @twz.time_zone - end - - def test_in_time_zone - Time.use_zone 'Alaska' do - assert_equal ActiveSupport::TimeWithZone.new(@utc, ActiveSupport::TimeZone['Alaska']), @twz.in_time_zone - end - end - - def test_in_time_zone_with_argument - assert_equal ActiveSupport::TimeWithZone.new(@utc, ActiveSupport::TimeZone['Alaska']), @twz.in_time_zone('Alaska') - end - - def test_in_time_zone_with_new_zone_equal_to_old_zone_does_not_create_new_object - assert_equal @twz.object_id, @twz.in_time_zone(ActiveSupport::TimeZone['Eastern Time (US & Canada)']).object_id - end - - def test_in_time_zone_with_bad_argument - assert_raise(ArgumentError) { @twz.in_time_zone('No such timezone exists') } - assert_raise(ArgumentError) { @twz.in_time_zone(-15.hours) } - assert_raise(ArgumentError) { @twz.in_time_zone(Object.new) } - end - - def test_localtime - assert_equal @twz.localtime, @twz.utc.getlocal - end - - def test_utc? - assert_equal false, @twz.utc? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']).utc? - end - - def test_formatted_offset - assert_equal '-05:00', @twz.formatted_offset - assert_equal '-04:00', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset #dst - end - - def test_dst? - assert_equal false, @twz.dst? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).dst? - end - - def test_zone - assert_equal 'EST', @twz.zone - assert_equal 'EDT', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst - end - - def test_nsec - local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)) - with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local) - - assert_equal local.nsec, with_zone.nsec - assert_equal with_zone.nsec, 999999999 - end - - def test_strftime - assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z') - end - - def test_inspect - assert_equal 'Fri, 31 Dec 1999 19:00:00 EST -05:00', @twz.inspect - end - - def test_to_s - assert_equal '1999-12-31 19:00:00 -0500', @twz.to_s - end - - def test_to_formatted_s - assert_equal '1999-12-31 19:00:00 -0500', @twz.to_formatted_s - end - - def test_to_s_db - assert_equal '2000-01-01 00:00:00', @twz.to_s(:db) - end - - def test_xmlschema - assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema - end - - def test_xmlschema_with_fractional_seconds - @twz += 0.1234560001 # advance the time by a fraction of a second - assert_equal "1999-12-31T19:00:00.123-05:00", @twz.xmlschema(3) - assert_equal "1999-12-31T19:00:00.123456-05:00", @twz.xmlschema(6) - assert_equal "1999-12-31T19:00:00.123456-05:00", @twz.xmlschema(12) - end - - def test_xmlschema_with_fractional_seconds_lower_than_hundred_thousand - @twz += 0.001234 # advance the time by a fraction - assert_equal "1999-12-31T19:00:00.001-05:00", @twz.xmlschema(3) - assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(6) - assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(12) - end - - def test_xmlschema_with_nil_fractional_seconds - assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema(nil) - end - - def test_to_yaml - assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml) - end - - def test_ruby_to_yaml - assert_match(/---\s*\n:twz: 2000-01-01 00:00:00(\.0+)?\s*Z\n/, {:twz => @twz}.to_yaml) - end - - def test_httpdate - assert_equal 'Sat, 01 Jan 2000 00:00:00 GMT', @twz.httpdate - end - - def test_rfc2822 - assert_equal "Fri, 31 Dec 1999 19:00:00 -0500", @twz.rfc2822 - end - - def test_compare_with_time - assert_equal 1, @twz <=> Time.utc(1999, 12, 31, 23, 59, 59) - assert_equal 0, @twz <=> Time.utc(2000, 1, 1, 0, 0, 0) - assert_equal(-1, @twz <=> Time.utc(2000, 1, 1, 0, 0, 1)) - end - - def test_compare_with_datetime - assert_equal 1, @twz <=> DateTime.civil(1999, 12, 31, 23, 59, 59) - assert_equal 0, @twz <=> DateTime.civil(2000, 1, 1, 0, 0, 0) - assert_equal(-1, @twz <=> DateTime.civil(2000, 1, 1, 0, 0, 1)) - end - - def test_compare_with_time_with_zone - assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) - assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) - assert_equal(-1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) - end - - def test_between? - assert @twz.between?(Time.utc(1999,12,31,23,59,59), Time.utc(2000,1,1,0,0,1)) - assert_equal false, @twz.between?(Time.utc(2000,1,1,0,0,1), Time.utc(2000,1,1,0,0,2)) - end - - def test_today - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(1999,12,31,23,59,59) ).today? - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,0) ).today? - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,23,59,59) ).today? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,2,0) ).today? - end - - def test_past_with_time_current_as_time_local - with_env_tz 'US/Eastern' do - Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45)) - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past? - end - end - - def test_past_with_time_current_as_time_with_zone - twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) ) - Time.stubs(:current).returns(twz) - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past? - end - - def test_future_with_time_current_as_time_local - with_env_tz 'US/Eastern' do - Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45)) - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future? - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future? - end - end - - def test_future_with_time_current_as_time_with_zone - twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) ) - Time.stubs(:current).returns(twz) - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future? - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future? - end - - def test_eql? - assert_equal true, @twz.eql?(Time.utc(2000)) - assert_equal true, @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) - assert_equal false, @twz.eql?( Time.utc(2000, 1, 1, 0, 0, 1) ) - assert_equal false, @twz.eql?( DateTime.civil(1999, 12, 31, 23, 59, 59) ) - end - - def test_hash - assert_equal Time.utc(2000).hash, @twz.hash - assert_equal Time.utc(2000).hash, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]).hash - end - - def test_plus_with_integer - assert_equal Time.utc(1999, 12, 31, 19, 0 ,5), (@twz + 5).time - end - - def test_plus_with_integer_when_self_wraps_datetime - datetime = DateTime.civil(2000, 1, 1, 0) - twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone) - assert_equal DateTime.civil(1999, 12, 31, 19, 0 ,5), (twz + 5).time - end - - def test_plus_when_crossing_time_class_limit - twz = ActiveSupport::TimeWithZone.new(Time.utc(2038, 1, 19), @time_zone) - assert_equal [0, 0, 19, 19, 1, 2038], (twz + 86_400).to_a[0,6] - end - - def test_plus_with_duration - assert_equal Time.utc(2000, 1, 5, 19, 0 ,0), (@twz + 5.days).time - end - - def test_minus_with_integer - assert_equal Time.utc(1999, 12, 31, 18, 59 ,55), (@twz - 5).time - end - - def test_minus_with_integer_when_self_wraps_datetime - datetime = DateTime.civil(2000, 1, 1, 0) - twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone) - assert_equal DateTime.civil(1999, 12, 31, 18, 59 ,55), (twz - 5).time - end - - def test_minus_with_duration - assert_equal Time.utc(1999, 12, 26, 19, 0 ,0), (@twz - 5.days).time - end - - def test_minus_with_time - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1) - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 1) - end - - def test_minus_with_time_with_zone - twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] ) - twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - assert_equal 86_400.0, twz2 - twz1 - end - - def test_minus_with_datetime - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1) - end - - def test_minus_with_wrapped_datetime - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1) - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1) - end - - def test_plus_and_minus_enforce_spring_dst_rules - utc = Time.utc(2006,4,2,6,59,59) # == Apr 2 2006 01:59:59 EST; i.e., 1 second before daylight savings start - twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal Time.utc(2006,4,2,1,59,59), twz.time - assert_equal false, twz.dst? - assert_equal 'EST', twz.zone - twz = twz + 1 - assert_equal Time.utc(2006,4,2,3), twz.time # adding 1 sec springs forward to 3:00AM EDT - assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone - twz = twz - 1 # subtracting 1 second takes goes back to 1:59:59AM EST - assert_equal Time.utc(2006,4,2,1,59,59), twz.time - assert_equal false, twz.dst? - assert_equal 'EST', twz.zone - end - - def test_plus_and_minus_enforce_fall_dst_rules - utc = Time.utc(2006,10,29,5,59,59) # == Oct 29 2006 01:59:59 EST; i.e., 1 second before daylight savings end - twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal Time.utc(2006,10,29,1,59,59), twz.time - assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone - twz = twz + 1 - assert_equal Time.utc(2006,10,29,1), twz.time # adding 1 sec falls back from 1:59:59 EDT to 1:00AM EST - assert_equal false, twz.dst? - assert_equal 'EST', twz.zone - twz = twz - 1 - assert_equal Time.utc(2006,10,29,1,59,59), twz.time # subtracting 1 sec goes back to 1:59:59AM EDT - assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone - end - - def test_to_a - assert_equal [45, 30, 5, 1, 2, 2000, 2, 32, false, "HST"], ActiveSupport::TimeWithZone.new( Time.utc(2000, 2, 1, 15, 30, 45), ActiveSupport::TimeZone['Hawaii'] ).to_a - end - - def test_to_f - result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii'] ).to_f - assert_equal 946684800.0, result - assert_kind_of Float, result - end - - def test_to_i - result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii'] ).to_i - assert_equal 946684800, result - assert_kind_of Integer, result - end - - def test_to_i_with_wrapped_datetime - datetime = DateTime.civil(2000, 1, 1, 0) - twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone) - assert_equal 946684800, twz.to_i - end - - def test_to_r - result = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']).to_r - assert_equal Rational(946684800, 1), result - assert_kind_of Rational, result - end - - def test_time_at - time = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']) - assert_equal time, Time.at(time) - end - - def test_to_time - with_env_tz 'US/Eastern' do - assert_equal Time, @twz.to_time.class - assert_equal Time.local(1999, 12, 31, 19), @twz.to_time - assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset - end - end - - def test_to_date - # 1 sec before midnight Jan 1 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 4, 59, 59), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date - # midnight Jan 1 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 5, 0, 0), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date - # 1 sec before midnight Jan 2 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 4, 59, 59), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date - # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 5, 0, 0), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date - end - - def test_to_datetime - assert_equal DateTime.civil(1999, 12, 31, 19, 0, 0, Rational(-18_000, 86_400)), @twz.to_datetime - end - - def test_acts_like_time - assert @twz.acts_like?(:time) - assert ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:time) - end - - def test_acts_like_date - assert_equal false, @twz.acts_like?(:date) - assert_equal false, ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:date) - end - - def test_is_a - assert_kind_of Time, @twz - assert_kind_of Time, @twz - assert_kind_of ActiveSupport::TimeWithZone, @twz - end - - def test_class_name - assert_equal 'Time', ActiveSupport::TimeWithZone.name - end - - def test_method_missing_with_time_return_value - assert_instance_of ActiveSupport::TimeWithZone, @twz.months_since(1) - assert_equal Time.utc(2000, 1, 31, 19, 0 ,0), @twz.months_since(1).time - end - - def test_marshal_dump_and_load - marshal_str = Marshal.dump(@twz) - mtime = Marshal.load(marshal_str) - assert_equal Time.utc(2000, 1, 1, 0), mtime.utc - assert mtime.utc.utc? - assert_equal ActiveSupport::TimeZone['Eastern Time (US & Canada)'], mtime.time_zone - assert_equal Time.utc(1999, 12, 31, 19), mtime.time - assert mtime.time.utc? - assert_equal @twz.inspect, mtime.inspect - end - - def test_marshal_dump_and_load_with_tzinfo_identifier - twz = ActiveSupport::TimeWithZone.new(@utc, TZInfo::Timezone.get('America/New_York')) - marshal_str = Marshal.dump(twz) - mtime = Marshal.load(marshal_str) - assert_equal Time.utc(2000, 1, 1, 0), mtime.utc - assert mtime.utc.utc? - assert_equal 'America/New_York', mtime.time_zone.name - assert_equal Time.utc(1999, 12, 31, 19), mtime.time - assert mtime.time.utc? - assert_equal @twz.inspect, mtime.inspect - end - - def test_freeze - @twz.freeze - assert @twz.frozen? - end - - def test_freeze_preloads_instance_variables - @twz.freeze - assert_nothing_raised do - @twz.period - @twz.time - end - end - - def test_method_missing_with_non_time_return_value - @twz.time.expects(:foo).returns('bar') - assert_equal 'bar', @twz.foo - end - - def test_date_part_value_methods - twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone) - twz.expects(:method_missing).never - assert_equal 1999, twz.year - assert_equal 12, twz.month - assert_equal 31, twz.day - assert_equal 14, twz.hour - assert_equal 18, twz.min - assert_equal 17, twz.sec - assert_equal 500, twz.usec - assert_equal 5, twz.wday - assert_equal 365, twz.yday - end - - def test_usec_returns_0_when_datetime_is_wrapped - twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone) - assert_equal 0, twz.usec - end - - def test_usec_returns_sec_fraction_when_datetime_is_wrapped - twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone) - assert_equal 500000, twz.usec - end - - def test_nsec_returns_sec_fraction_when_datetime_is_wrapped - twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone) - assert_equal 500000000, twz.nsec - end - - def test_utc_to_local_conversion_saves_period_in_instance_variable - assert_nil @twz.instance_variable_get('@period') - @twz.time - assert_kind_of TZInfo::TimezonePeriod, @twz.instance_variable_get('@period') - end - - def test_instance_created_with_local_time_returns_correct_utc_time - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(1999, 12, 31, 19)) - assert_equal Time.utc(2000), twz.utc - end - - def test_instance_created_with_local_time_enforces_spring_dst_rules - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,2)) # first second of DST - assert_equal Time.utc(2006,4,2,3), twz.time # springs forward to 3AM - assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone - end - - def test_instance_created_with_local_time_enforces_fall_dst_rules - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,1)) # 1AM can be either DST or non-DST; we'll pick DST - assert_equal Time.utc(2006,10,29,1), twz.time - assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone - end - - def test_ruby_19_weekday_name_query_methods - %w(sunday? monday? tuesday? wednesday? thursday? friday? saturday?).each do |name| - assert_respond_to @twz, name - assert_equal @twz.send(name), @twz.method(name).call - end - end - - def test_utc_to_local_conversion_with_far_future_datetime - assert_equal [0,0,19,31,12,2049], ActiveSupport::TimeWithZone.new(DateTime.civil(2050), @time_zone).to_a[0,6] - end - - def test_local_to_utc_conversion_with_far_future_datetime - assert_equal DateTime.civil(2050).to_f, ActiveSupport::TimeWithZone.new(nil, @time_zone, DateTime.civil(2049,12,31,19)).to_f - end - - def test_change - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.change(:year => 2001).inspect - assert_equal "Wed, 31 Mar 1999 19:00:00 EST -05:00", @twz.change(:month => 3).inspect - assert_equal "Wed, 03 Mar 1999 19:00:00 EST -05:00", @twz.change(:month => 2).inspect - assert_equal "Wed, 15 Dec 1999 19:00:00 EST -05:00", @twz.change(:day => 15).inspect - assert_equal "Fri, 31 Dec 1999 06:00:00 EST -05:00", @twz.change(:hour => 6).inspect - assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.change(:min => 15).inspect - assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(:sec => 30).inspect - end - - def test_change_at_dst_boundary - twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid']) - assert_equal twz, twz.change(:min => 0) - end - - def test_round_at_dst_boundary - twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid']) - assert_equal twz, twz.round - end - - def test_advance - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(:years => 2).inspect - assert_equal "Fri, 31 Mar 2000 19:00:00 EST -05:00", @twz.advance(:months => 3).inspect - assert_equal "Tue, 04 Jan 2000 19:00:00 EST -05:00", @twz.advance(:days => 4).inspect - assert_equal "Sat, 01 Jan 2000 01:00:00 EST -05:00", @twz.advance(:hours => 6).inspect - assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.advance(:minutes => 15).inspect - assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(:seconds => 30).inspect - end - - def test_beginning_of_year - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Fri, 01 Jan 1999 00:00:00 EST -05:00", @twz.beginning_of_year.inspect - end - - def test_end_of_year - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_year.inspect - end - - def test_beginning_of_month - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Wed, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect - end - - def test_end_of_month - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_month.inspect - end - - def test_beginning_of_day - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Fri, 31 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_day.inspect - end - - def test_end_of_day - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_day.inspect - end - - def test_beginning_of_hour - utc = Time.utc(2000, 1, 1, 0, 30) - twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect - end - - def test_end_of_hour - utc = Time.utc(2000, 1, 1, 0, 30) - twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect - assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect - end - - def test_beginning_of_minute - utc = Time.utc(2000, 1, 1, 0, 30, 10) - twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal "Fri, 31 Dec 1999 19:30:10 EST -05:00", twz.inspect - assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect - end - - def test_end_of_minute - utc = Time.utc(2000, 1, 1, 0, 30, 10) - twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal "Fri, 31 Dec 1999 19:30:10 EST -05:00", twz.inspect - assert_equal "Fri, 31 Dec 1999 19:30:59 EST -05:00", twz.end_of_minute.inspect - end - - def test_since - assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect - end - - def test_ago - assert_equal "Fri, 31 Dec 1999 18:59:59 EST -05:00", @twz.ago(1).inspect - end - - def test_seconds_since_midnight - assert_equal 19 * 60 * 60, @twz.seconds_since_midnight - end - - def test_advance_1_year_from_leap_day - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2004,2,29)) - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(:years => 1).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.years_since(1).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.since(1.year).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.year).inspect - end - - def test_advance_1_month_from_last_day_of_january - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2005,1,31)) - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(:months => 1).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.months_since(1).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.since(1.month).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.month).inspect - end - - def test_advance_1_month_from_last_day_of_january_during_leap_year - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000,1,31)) - assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.advance(:months => 1).inspect - assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.months_since(1).inspect - assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.since(1.month).inspect - assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", (twz + 1.month).inspect - end - - def test_advance_1_month_into_spring_dst_gap - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,3,2,2)) - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(:months => 1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.months_since(1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1.month).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.month).inspect - end - - def test_advance_1_second_into_spring_dst_gap - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,1,59,59)) - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(:seconds => 1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1.second).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.second).inspect - end - - def test_advance_1_day_across_spring_dst_transition - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30)) - # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long - # When we advance 1 day, we want to end up at the same time on the next day - assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.advance(:days => 1).inspect - assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.since(1.days).inspect - assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", (twz + 1.days).inspect - assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", twz.since(1.days + 1.second).inspect - assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", (twz + 1.days + 1.second).inspect - end - - def test_advance_1_day_across_spring_dst_transition_backwards - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,10,30)) - # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long - # When we advance back 1 day, we want to end up at the same time on the previous day - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:days => -1).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.days).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1.days).inspect - assert_equal "Sat, 01 Apr 2006 10:30:01 EST -05:00", twz.ago(1.days - 1.second).inspect - end - - def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30)) - # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long - # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 86400).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 86400.seconds).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(86400).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(86400.seconds).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:seconds => 86400).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 1440.minutes).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(1440.minutes).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:minutes => 1440).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 24.hours).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(24.hours).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:hours => 24).inspect - end - - def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition_backwards - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,11,30)) - # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long - # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 86400).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 86400.seconds).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(86400).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(86400.seconds).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:seconds => -86400).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1440.minutes).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1440.minutes).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:minutes => -1440).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 24.hours).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(24.hours).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:hours => -24).inspect - end - - def test_advance_1_day_across_fall_dst_transition - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30)) - # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long - # When we advance 1 day, we want to end up at the same time on the next day - assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.advance(:days => 1).inspect - assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.since(1.days).inspect - assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", (twz + 1.days).inspect - assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", twz.since(1.days + 1.second).inspect - assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", (twz + 1.days + 1.second).inspect - end - - def test_advance_1_day_across_fall_dst_transition_backwards - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,10,30)) - # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long - # When we advance backwards 1 day, we want to end up at the same time on the previous day - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:days => -1).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.days).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.days).inspect - assert_equal "Sat, 28 Oct 2006 10:30:01 EDT -04:00", twz.ago(1.days - 1.second).inspect - end - - def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30)) - # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long - # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 86400).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 86400.seconds).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(86400).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(86400.seconds).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:seconds => 86400).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 1440.minutes).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(1440.minutes).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:minutes => 1440).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 24.hours).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(24.hours).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:hours => 24).inspect - end - - def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition_backwards - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,9,30)) - # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long - # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 86400).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 86400.seconds).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(86400).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(86400.seconds).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:seconds => -86400).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1440.minutes).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1440.minutes).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:minutes => -1440).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 24.hours).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(24.hours).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:hours => -24).inspect - end - - def test_advance_1_month_across_spring_dst_transition - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30)) - assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.advance(:months => 1).inspect - assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.months_since(1).inspect - assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.since(1.month).inspect - assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", (twz + 1.month).inspect - end - - def test_advance_1_month_across_spring_dst_transition_backwards - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,5,1,10,30)) - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:months => -1).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.months_ago(1).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.month).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1.month).inspect - end - - def test_advance_1_month_across_fall_dst_transition - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30)) - assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.advance(:months => 1).inspect - assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.months_since(1).inspect - assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.since(1.month).inspect - assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", (twz + 1.month).inspect - end - - def test_advance_1_month_across_fall_dst_transition_backwards - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,11,28,10,30)) - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:months => -1).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.months_ago(1).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.month).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.month).inspect - end - - def test_advance_1_year - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,2,15,10,30)) - assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.advance(:years => 1).inspect - assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.years_since(1).inspect - assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", (twz + 1.year).inspect - assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.advance(:years => -1).inspect - assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.years_ago(1).inspect - assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", (twz - 1.year).inspect - end - - def test_advance_1_year_during_dst - twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,7,15,10,30)) - assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.advance(:years => 1).inspect - assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.years_since(1).inspect - assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", (twz + 1.year).inspect - assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.advance(:years => -1).inspect - assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.years_ago(1).inspect - assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", (twz - 1.year).inspect - end - - def test_no_method_error_has_proper_context - e = assert_raises(NoMethodError) { - @twz.this_method_does_not_exist - } - assert_equal "undefined method `this_method_does_not_exist' for Fri, 31 Dec 1999 19:00:00 EST -05:00:Time", e.message - assert_no_match "rescue", e.backtrace.first - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end -end - -class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase - def setup - @t, @dt = Time.utc(2000), DateTime.civil(2000) - end - - def teardown - Time.zone = nil - end - - def test_in_time_zone - Time.use_zone 'Alaska' do - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @t.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @dt.in_time_zone.inspect - end - Time.use_zone 'Hawaii' do - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @t.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @dt.in_time_zone.inspect - end - Time.use_zone nil do - assert_equal @t, @t.in_time_zone - assert_equal @dt, @dt.in_time_zone - end - end - - def test_nil_time_zone - Time.use_zone nil do - assert !@t.in_time_zone.respond_to?(:period), 'no period method' - assert !@dt.in_time_zone.respond_to?(:period), 'no period method' - end - end - - def test_in_time_zone_with_argument - Time.use_zone 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone) - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @t.in_time_zone('Alaska').inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @dt.in_time_zone('Alaska').inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @t.in_time_zone('Hawaii').inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @dt.in_time_zone('Hawaii').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @t.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @dt.in_time_zone('UTC').inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @t.in_time_zone(-9.hours).inspect - end - end - - def test_in_time_zone_with_invalid_argument - assert_raise(ArgumentError) { @t.in_time_zone("No such timezone exists") } - assert_raise(ArgumentError) { @dt.in_time_zone("No such timezone exists") } - assert_raise(ArgumentError) { @t.in_time_zone(-15.hours) } - assert_raise(ArgumentError) { @dt.in_time_zone(-15.hours) } - assert_raise(ArgumentError) { @t.in_time_zone(Object.new) } - assert_raise(ArgumentError) { @dt.in_time_zone(Object.new) } - end - - def test_in_time_zone_with_time_local_instance - with_env_tz 'US/Eastern' do - time = Time.local(1999, 12, 31, 19) # == Time.utc(2000) - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', time.in_time_zone('Alaska').inspect - end - end - - def test_localtime - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_equal @dt.in_time_zone.localtime, @dt.in_time_zone.utc.to_time.getlocal - ensure - Time.zone = nil - end - - def test_use_zone - Time.zone = 'Alaska' - Time.use_zone 'Hawaii' do - assert_equal ActiveSupport::TimeZone['Hawaii'], Time.zone - end - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - end - - def test_use_zone_with_exception_raised - Time.zone = 'Alaska' - assert_raise RuntimeError do - Time.use_zone('Hawaii') { raise RuntimeError } - end - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - end - - def test_use_zone_raises_on_invalid_timezone - Time.zone = 'Alaska' - assert_raise ArgumentError do - Time.use_zone("No such timezone exists") { } - end - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - end - - def test_time_zone_getter_and_setter - Time.zone = ActiveSupport::TimeZone['Alaska'] - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - Time.zone = 'Alaska' - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - Time.zone = -9.hours - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - Time.zone = nil - assert_equal nil, Time.zone - end - - def test_time_zone_getter_and_setter_with_zone_default_set - Time.zone_default = ActiveSupport::TimeZone['Alaska'] - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - Time.zone = ActiveSupport::TimeZone['Hawaii'] - assert_equal ActiveSupport::TimeZone['Hawaii'], Time.zone - Time.zone = nil - assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone - ensure - Time.zone = nil - Time.zone_default = nil - end - - def test_time_zone_setter_is_thread_safe - Time.use_zone 'Paris' do - t1 = Thread.new { Time.zone = 'Alaska' }.join - t2 = Thread.new { Time.zone = 'Hawaii' }.join - assert t1.stop?, "Thread 1 did not finish running" - assert t2.stop?, "Thread 2 did not finish running" - assert_equal ActiveSupport::TimeZone['Paris'], Time.zone - assert_equal ActiveSupport::TimeZone['Alaska'], t1[:time_zone] - assert_equal ActiveSupport::TimeZone['Hawaii'], t2[:time_zone] - end - end - - def test_time_zone_setter_with_tzinfo_timezone_object_wraps_in_rails_time_zone - tzinfo = TZInfo::Timezone.get('America/New_York') - Time.zone = tzinfo - assert_kind_of ActiveSupport::TimeZone, Time.zone - assert_equal tzinfo, Time.zone.tzinfo - assert_equal 'America/New_York', Time.zone.name - assert_equal(-18_000, Time.zone.utc_offset) - end - - def test_time_zone_setter_with_tzinfo_timezone_identifier_does_lookup_and_wraps_in_rails_time_zone - Time.zone = 'America/New_York' - assert_kind_of ActiveSupport::TimeZone, Time.zone - assert_equal 'America/New_York', Time.zone.tzinfo.name - assert_equal 'America/New_York', Time.zone.name - assert_equal(-18_000, Time.zone.utc_offset) - end - - def test_time_zone_setter_with_invalid_zone - assert_raise(ArgumentError){ Time.zone = "No such timezone exists" } - assert_raise(ArgumentError){ Time.zone = -15.hours } - assert_raise(ArgumentError){ Time.zone = Object.new } - end - - def test_find_zone_without_bang_returns_nil_if_time_zone_can_not_be_found - assert_nil Time.find_zone('No such timezone exists') - assert_nil Time.find_zone(-15.hours) - assert_nil Time.find_zone(Object.new) - end - - def test_find_zone_with_bang_raises_if_time_zone_can_not_be_found - assert_raise(ArgumentError) { Time.find_zone!('No such timezone exists') } - assert_raise(ArgumentError) { Time.find_zone!(-15.hours) } - assert_raise(ArgumentError) { Time.find_zone!(Object.new) } - end - - def test_time_zone_setter_with_find_zone_without_bang - assert_nil Time.zone = Time.find_zone('No such timezone exists') - assert_nil Time.zone = Time.find_zone(-15.hours) - assert_nil Time.zone = Time.find_zone(Object.new) - end - - def test_current_returns_time_now_when_zone_not_set - with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone) - assert_equal Time.local(2000), Time.current - end - end - - def test_current_returns_time_zone_now_when_zone_set - Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone) - assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name - assert_equal Time.utc(2000), Time.current.time - end - ensure - Time.zone = nil - end - - def test_time_in_time_zone_doesnt_affect_receiver - with_env_tz 'Europe/London' do - time = Time.local(2000, 7, 1) - time_with_zone = time.in_time_zone('Eastern Time (US & Canada)') - assert_equal Time.utc(2000, 6, 30, 23, 0, 0), time_with_zone - assert_not time.utc?, 'time expected to be local, but is UTC' - end - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end -end - -class TimeWithZoneMethodsForDate < ActiveSupport::TestCase - def setup - @d = Date.civil(2000) - end - - def teardown - Time.zone = nil - end - - def test_in_time_zone - with_tz_default 'Alaska' do - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone.inspect - end - with_tz_default 'Hawaii' do - assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @d.in_time_zone.inspect - end - with_tz_default nil do - assert_equal @d.to_time, @d.in_time_zone - end - end - - def test_nil_time_zone - with_tz_default nil do - assert !@d.in_time_zone.respond_to?(:period), 'no period method' - end - end - - def test_in_time_zone_with_argument - with_tz_default 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone) - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone('Alaska').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @d.in_time_zone('Hawaii').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @d.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone(-9.hours).inspect - end - end - - def test_in_time_zone_with_invalid_argument - assert_raise(ArgumentError) { @d.in_time_zone("No such timezone exists") } - assert_raise(ArgumentError) { @d.in_time_zone(-15.hours) } - assert_raise(ArgumentError) { @d.in_time_zone(Object.new) } - end - - protected - def with_tz_default(tz = nil) - old_tz = Time.zone - Time.zone = tz - yield - ensure - Time.zone = old_tz - end -end - -class TimeWithZoneMethodsForString < ActiveSupport::TestCase - def setup - @s = "Sat, 01 Jan 2000 00:00:00" - @u = "Sat, 01 Jan 2000 00:00:00 UTC +00:00" - @z = "Fri, 31 Dec 1999 19:00:00 EST -05:00" - end - - def teardown - Time.zone = nil - end - - def test_in_time_zone - with_tz_default 'Alaska' do - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone.inspect - end - with_tz_default 'Hawaii' do - assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @s.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @u.in_time_zone.inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @z.in_time_zone.inspect - end - with_tz_default nil do - assert_equal @s.to_time, @s.in_time_zone - assert_equal @u.to_time, @u.in_time_zone - assert_equal @z.to_time, @z.in_time_zone - end - end - - def test_nil_time_zone - with_tz_default nil do - assert !@s.in_time_zone.respond_to?(:period), 'no period method' - assert !@u.in_time_zone.respond_to?(:period), 'no period method' - assert !@z.in_time_zone.respond_to?(:period), 'no period method' - end - end - - def test_in_time_zone_with_argument - with_tz_default 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone) - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone('Alaska').inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone('Alaska').inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone('Alaska').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @s.in_time_zone('Hawaii').inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @u.in_time_zone('Hawaii').inspect - assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @z.in_time_zone('Hawaii').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @s.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @u.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @z.in_time_zone('UTC').inspect - assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone(-9.hours).inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone(-9.hours).inspect - assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone(-9.hours).inspect - end - end - - def test_in_time_zone_with_invalid_argument - assert_raise(ArgumentError) { @s.in_time_zone("No such timezone exists") } - assert_raise(ArgumentError) { @u.in_time_zone("No such timezone exists") } - assert_raise(ArgumentError) { @z.in_time_zone("No such timezone exists") } - assert_raise(ArgumentError) { @s.in_time_zone(-15.hours) } - assert_raise(ArgumentError) { @u.in_time_zone(-15.hours) } - assert_raise(ArgumentError) { @z.in_time_zone(-15.hours) } - assert_raise(ArgumentError) { @s.in_time_zone(Object.new) } - assert_raise(ArgumentError) { @u.in_time_zone(Object.new) } - assert_raise(ArgumentError) { @z.in_time_zone(Object.new) } - end - - protected - def with_tz_default(tz = nil) - old_tz = Time.zone - Time.zone = tz - yield - ensure - Time.zone = old_tz - end -end diff --git a/app/server/ruby/vendor/activesupport/test/core_ext/uri_ext_test.rb b/app/server/ruby/vendor/activesupport/test/core_ext/uri_ext_test.rb deleted file mode 100644 index 03e388dd7a..0000000000 --- a/app/server/ruby/vendor/activesupport/test/core_ext/uri_ext_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -# encoding: utf-8 -require 'abstract_unit' -require 'uri' -require 'active_support/core_ext/uri' - -class URIExtTest < ActiveSupport::TestCase - def test_uri_decode_handle_multibyte - str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. - - parser = URI::Parser.new - assert_equal str, parser.unescape(parser.escape(str)) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/check_warnings.rb b/app/server/ruby/vendor/activesupport/test/dependencies/check_warnings.rb deleted file mode 100644 index 03c3dca1d6..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/check_warnings.rb +++ /dev/null @@ -1,2 +0,0 @@ -$check_warnings_load_count += 1 -$checked_verbose = $VERBOSE diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/conflict.rb b/app/server/ruby/vendor/activesupport/test/dependencies/conflict.rb deleted file mode 100644 index e888b7b54c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/conflict.rb +++ /dev/null @@ -1 +0,0 @@ -Conflict = 1 \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/cross_site_depender.rb b/app/server/ruby/vendor/activesupport/test/dependencies/cross_site_depender.rb deleted file mode 100644 index a31015fc5e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/cross_site_depender.rb +++ /dev/null @@ -1,3 +0,0 @@ -class CrossSiteDepender - CrossSiteDependency -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/mutual_one.rb b/app/server/ruby/vendor/activesupport/test/dependencies/mutual_one.rb deleted file mode 100644 index 576eb31711..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/mutual_one.rb +++ /dev/null @@ -1,4 +0,0 @@ -$mutual_dependencies_count += 1 -require_dependency 'mutual_two' -require_dependency 'mutual_two.rb' -require_dependency 'mutual_two' diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/mutual_two.rb b/app/server/ruby/vendor/activesupport/test/dependencies/mutual_two.rb deleted file mode 100644 index fdbc2dcd84..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/mutual_two.rb +++ /dev/null @@ -1,4 +0,0 @@ -$mutual_dependencies_count += 1 -require_dependency 'mutual_one.rb' -require_dependency 'mutual_one' -require_dependency 'mutual_one.rb' diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/raises_exception.rb b/app/server/ruby/vendor/activesupport/test/dependencies/raises_exception.rb deleted file mode 100644 index dd745ac20e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/raises_exception.rb +++ /dev/null @@ -1,3 +0,0 @@ -$raises_exception_load_count += 1 -raise Exception, 'Loading me failed, so do not add to loaded or history.' -$raises_exception_load_count += 1 diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/raises_exception_without_blame_file.rb b/app/server/ruby/vendor/activesupport/test/dependencies/raises_exception_without_blame_file.rb deleted file mode 100644 index 4b2da6ff30..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/raises_exception_without_blame_file.rb +++ /dev/null @@ -1,5 +0,0 @@ -exception = Exception.new('I am not blamable!') -class << exception - undef_method(:blame_file!) -end -raise exception diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/requires_nonexistent0.rb b/app/server/ruby/vendor/activesupport/test/dependencies/requires_nonexistent0.rb deleted file mode 100644 index 7e24b3916c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/requires_nonexistent0.rb +++ /dev/null @@ -1 +0,0 @@ -require 'RMagickDontExistDude' diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/requires_nonexistent1.rb b/app/server/ruby/vendor/activesupport/test/dependencies/requires_nonexistent1.rb deleted file mode 100644 index 41e6668164..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/requires_nonexistent1.rb +++ /dev/null @@ -1 +0,0 @@ -require_dependency 'requires_nonexistent0' diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/service_one.rb b/app/server/ruby/vendor/activesupport/test/dependencies/service_one.rb deleted file mode 100644 index f43bfea235..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/service_one.rb +++ /dev/null @@ -1,5 +0,0 @@ -$loaded_service_one ||= 0 -$loaded_service_one += 1 - -class ServiceOne -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/dependencies/service_two.rb b/app/server/ruby/vendor/activesupport/test/dependencies/service_two.rb deleted file mode 100644 index 5205a78bb8..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies/service_two.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ServiceTwo -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/dependencies_test.rb b/app/server/ruby/vendor/activesupport/test/dependencies_test.rb deleted file mode 100644 index 4ca63b3417..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies_test.rb +++ /dev/null @@ -1,997 +0,0 @@ -require 'abstract_unit' -require 'pp' -require 'active_support/dependencies' -require 'dependencies_test_helpers' - -module ModuleWithMissing - mattr_accessor :missing_count - def self.const_missing(name) - self.missing_count += 1 - name - end -end - -module ModuleWithConstant - InheritedConstant = "Hello" -end - -class DependenciesTest < ActiveSupport::TestCase - def teardown - ActiveSupport::Dependencies.clear - end - - include DependenciesTestHelpers - - def test_depend_on_path - skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0' - - expected = assert_raises(LoadError) do - Kernel.require 'omgwtfbbq' - end - - e = assert_raises(LoadError) do - ActiveSupport::Dependencies.depend_on 'omgwtfbbq' - end - assert_equal expected.path, e.path - end - - def test_require_dependency_accepts_an_object_which_implements_to_path - o = Object.new - def o.to_path; 'dependencies/service_one'; end - assert_nothing_raised { - require_dependency o - } - assert defined?(ServiceOne) - ensure - remove_constants(:ServiceOne) - end - - def test_tracking_loaded_files - require_dependency 'dependencies/service_one' - require_dependency 'dependencies/service_two' - assert_equal 2, ActiveSupport::Dependencies.loaded.size - ensure - Object.send(:remove_const, :ServiceOne) if Object.const_defined?(:ServiceOne) - Object.send(:remove_const, :ServiceTwo) if Object.const_defined?(:ServiceTwo) - end - - def test_tracking_identical_loaded_files - require_dependency 'dependencies/service_one' - require_dependency 'dependencies/service_one' - assert_equal 1, ActiveSupport::Dependencies.loaded.size - ensure - Object.send(:remove_const, :ServiceOne) if Object.const_defined?(:ServiceOne) - end - - def test_missing_dependency_raises_missing_source_file - assert_raise(MissingSourceFile) { require_dependency("missing_service") } - end - - def test_dependency_which_raises_exception_isnt_added_to_loaded_set - with_loading do - filename = 'dependencies/raises_exception' - $raises_exception_load_count = 0 - - 5.times do |count| - e = assert_raise Exception, 'should have loaded dependencies/raises_exception which raises an exception' do - require_dependency filename - end - - assert_equal 'Loading me failed, so do not add to loaded or history.', e.message - assert_equal count + 1, $raises_exception_load_count - - assert !ActiveSupport::Dependencies.loaded.include?(filename) - assert !ActiveSupport::Dependencies.history.include?(filename) - end - end - end - - def test_dependency_which_raises_doesnt_blindly_call_blame_file! - with_loading do - filename = 'dependencies/raises_exception_without_blame_file' - - assert_raises(Exception) { require_dependency filename } - end - end - - def test_warnings_should_be_enabled_on_first_load - with_loading 'dependencies' do - old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true - - filename = "check_warnings" - expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}") - $check_warnings_load_count = 0 - - assert !ActiveSupport::Dependencies.loaded.include?(expanded) - assert !ActiveSupport::Dependencies.history.include?(expanded) - - silence_warnings { require_dependency filename } - assert_equal 1, $check_warnings_load_count - assert_equal true, $checked_verbose, 'On first load warnings should be enabled.' - - assert ActiveSupport::Dependencies.loaded.include?(expanded) - ActiveSupport::Dependencies.clear - assert !ActiveSupport::Dependencies.loaded.include?(expanded) - assert ActiveSupport::Dependencies.history.include?(expanded) - - silence_warnings { require_dependency filename } - assert_equal 2, $check_warnings_load_count - assert_equal nil, $checked_verbose, 'After first load warnings should be left alone.' - - assert ActiveSupport::Dependencies.loaded.include?(expanded) - ActiveSupport::Dependencies.clear - assert !ActiveSupport::Dependencies.loaded.include?(expanded) - assert ActiveSupport::Dependencies.history.include?(expanded) - - enable_warnings { require_dependency filename } - assert_equal 3, $check_warnings_load_count - assert_equal true, $checked_verbose, 'After first load warnings should be left alone.' - - assert ActiveSupport::Dependencies.loaded.include?(expanded) - ActiveSupport::Dependencies.warnings_on_first_load = old_warnings - end - end - - def test_mutual_dependencies_dont_infinite_loop - with_loading 'dependencies' do - $mutual_dependencies_count = 0 - assert_nothing_raised { require_dependency 'mutual_one' } - assert_equal 2, $mutual_dependencies_count - - ActiveSupport::Dependencies.clear - - $mutual_dependencies_count = 0 - assert_nothing_raised { require_dependency 'mutual_two' } - assert_equal 2, $mutual_dependencies_count - end - end - - def test_circular_autoloading_detection - with_autoloading_fixtures do - e = assert_raise(RuntimeError) { Circular1 } - assert_equal "Circular dependency detected while autoloading constant Circular1", e.message - end - end - - def test_module_loading - with_autoloading_fixtures do - assert_kind_of Module, A - assert_kind_of Class, A::B - assert_kind_of Class, A::C::D - assert_kind_of Class, A::C::E::F - end - end - - def test_non_existing_const_raises_name_error - with_autoloading_fixtures do - assert_raise(NameError) { DoesNotExist } - assert_raise(NameError) { NoModule::DoesNotExist } - assert_raise(NameError) { A::DoesNotExist } - assert_raise(NameError) { A::B::DoesNotExist } - end - end - - def test_directories_manifest_as_modules_unless_const_defined - with_autoloading_fixtures do - assert_kind_of Module, ModuleFolder - Object.__send__ :remove_const, :ModuleFolder - end - end - - def test_module_with_nested_class - with_autoloading_fixtures do - assert_kind_of Class, ModuleFolder::NestedClass - Object.__send__ :remove_const, :ModuleFolder - end - end - - def test_module_with_nested_inline_class - with_autoloading_fixtures do - assert_kind_of Class, ModuleFolder::InlineClass - Object.__send__ :remove_const, :ModuleFolder - end - end - - def test_directories_may_manifest_as_nested_classes - with_autoloading_fixtures do - assert_kind_of Class, ClassFolder - Object.__send__ :remove_const, :ClassFolder - end - end - - def test_class_with_nested_class - with_autoloading_fixtures do - assert_kind_of Class, ClassFolder::NestedClass - Object.__send__ :remove_const, :ClassFolder - end - end - - def test_class_with_nested_inline_class - with_autoloading_fixtures do - assert_kind_of Class, ClassFolder::InlineClass - Object.__send__ :remove_const, :ClassFolder - end - end - - def test_class_with_nested_inline_subclass_of_parent - with_autoloading_fixtures do - assert_kind_of Class, ClassFolder::ClassFolderSubclass - assert_kind_of Class, ClassFolder - assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder - Object.__send__ :remove_const, :ClassFolder - end - end - - def test_nested_class_can_access_sibling - with_autoloading_fixtures do - sibling = ModuleFolder::NestedClass.class_eval "NestedSibling" - assert defined?(ModuleFolder::NestedSibling) - assert_equal ModuleFolder::NestedSibling, sibling - Object.__send__ :remove_const, :ModuleFolder - end - end - - def test_doesnt_break_normal_require - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) - original_path = $:.dup - original_features = $".dup - $:.push(path) - - with_autoloading_fixtures do - # The _ = assignments are to prevent warnings - _ = RequiresConstant - assert defined?(RequiresConstant) - assert defined?(LoadedConstant) - ActiveSupport::Dependencies.clear - _ = RequiresConstant - assert defined?(RequiresConstant) - assert defined?(LoadedConstant) - end - ensure - remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant) - $".replace(original_features) - $:.replace(original_path) - end - - def test_doesnt_break_normal_require_nested - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) - original_path = $:.dup - original_features = $".dup - $:.push(path) - - with_autoloading_fixtures do - # The _ = assignments are to prevent warnings - _ = LoadsConstant - assert defined?(LoadsConstant) - assert defined?(LoadedConstant) - ActiveSupport::Dependencies.clear - _ = LoadsConstant - assert defined?(LoadsConstant) - assert defined?(LoadedConstant) - end - ensure - remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant) - $".replace(original_features) - $:.replace(original_path) - end - - def test_require_returns_true_when_file_not_yet_required - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) - original_path = $:.dup - original_features = $".dup - $:.push(path) - - with_loading do - assert_equal true, require('loaded_constant') - end - ensure - remove_constants(:LoadedConstant) - $".replace(original_features) - $:.replace(original_path) - end - - def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) - original_path = $:.dup - original_features = $".dup - $:.push(path) - - with_loading do - Object.module_eval "module LoadedConstant; end" - assert_equal true, require('loaded_constant') - end - ensure - remove_constants(:LoadedConstant) - $".replace(original_features) - $:.replace(original_path) - end - - def test_require_returns_false_when_file_already_required - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) - original_path = $:.dup - original_features = $".dup - $:.push(path) - - with_loading do - require 'loaded_constant' - assert_equal false, require('loaded_constant') - end - ensure - remove_constants(:LoadedConstant) - $".replace(original_features) - $:.replace(original_path) - end - - def test_require_raises_load_error_when_file_not_found - with_loading do - assert_raise(LoadError) { require 'this_file_dont_exist_dude' } - end - ensure - remove_constants(:LoadedConstant) - end - - def test_load_returns_true_when_file_found - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) - original_path = $:.dup - original_features = $".dup - $:.push(path) - - with_loading do - assert_equal true, load('loaded_constant.rb') - assert_equal true, load('loaded_constant.rb') - end - ensure - remove_constants(:LoadedConstant) - $".replace(original_features) - $:.replace(original_path) - end - - def test_load_raises_load_error_when_file_not_found - with_loading do - assert_raise(LoadError) { load 'this_file_dont_exist_dude.rb' } - end - ensure - remove_constants(:LoadedConstant) - end - - def failing_test_access_thru_and_upwards_fails - with_autoloading_fixtures do - assert ! defined?(ModuleFolder) - assert_raise(NameError) { ModuleFolder::Object } - assert_raise(NameError) { ModuleFolder::NestedClass::Object } - Object.__send__ :remove_const, :ModuleFolder - end - end - - def test_non_existing_const_raises_name_error_with_fully_qualified_name - with_autoloading_fixtures do - e = assert_raise(NameError) { A::DoesNotExist.nil? } - assert_equal "uninitialized constant A::DoesNotExist", e.message - - e = assert_raise(NameError) { A::B::DoesNotExist.nil? } - assert_equal "uninitialized constant A::B::DoesNotExist", e.message - end - end - - def test_smart_name_error_strings - e = assert_raise NameError do - Object.module_eval "ImaginaryObject" - end - assert_includes "uninitialized constant ImaginaryObject", e.message - end - - def test_loadable_constants_for_path_should_handle_empty_autoloads - assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path('hello') - end - - def test_loadable_constants_for_path_should_handle_relative_paths - fake_root = 'dependencies' - relative_root = File.dirname(__FILE__) + '/dependencies' - ['', '/'].each do |suffix| - with_loading fake_root + suffix do - assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + '/a/b') - end - end - end - - def test_loadable_constants_for_path_should_provide_all_results - fake_root = '/usr/apps/backpack' - with_loading fake_root, fake_root + '/lib' do - root = ActiveSupport::Dependencies.autoload_paths.first - assert_equal ["Lib::A::B", "A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + '/lib/a/b') - end - end - - def test_loadable_constants_for_path_should_uniq_results - fake_root = '/usr/apps/backpack/lib' - with_loading fake_root, fake_root + '/' do - root = ActiveSupport::Dependencies.autoload_paths.first - assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + '/a/b') - end - end - - def test_loadable_constants_with_load_path_without_trailing_slash - path = File.dirname(__FILE__) + '/autoloading_fixtures/class_folder/inline_class.rb' - with_loading 'autoloading_fixtures/class/' do - assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path(path) - end - end - - def test_qualified_const_defined - assert ActiveSupport::Dependencies.qualified_const_defined?("Object") - assert ActiveSupport::Dependencies.qualified_const_defined?("::Object") - assert ActiveSupport::Dependencies.qualified_const_defined?("::Object::Kernel") - assert ActiveSupport::Dependencies.qualified_const_defined?("::ActiveSupport::TestCase") - end - - def test_qualified_const_defined_should_not_call_const_missing - ModuleWithMissing.missing_count = 0 - assert ! ActiveSupport::Dependencies.qualified_const_defined?("ModuleWithMissing::A") - assert_equal 0, ModuleWithMissing.missing_count - assert ! ActiveSupport::Dependencies.qualified_const_defined?("ModuleWithMissing::A::B") - assert_equal 0, ModuleWithMissing.missing_count - end - - def test_qualified_const_defined_explodes_with_invalid_const_name - assert_raises(NameError) { ActiveSupport::Dependencies.qualified_const_defined?("invalid") } - end - - def test_autoloaded? - with_autoloading_fixtures do - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - - assert ActiveSupport::Dependencies.autoloaded?(ModuleFolder) - - assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder") - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - - assert ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) - - assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder") - assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - - assert ActiveSupport::Dependencies.autoloaded?("::ModuleFolder") - assert ActiveSupport::Dependencies.autoloaded?(:ModuleFolder) - - # Anonymous modules aren't autoloaded. - assert !ActiveSupport::Dependencies.autoloaded?(Module.new) - - nil_name = Module.new - def nil_name.name() nil end - assert !ActiveSupport::Dependencies.autoloaded?(nil_name) - - Object.class_eval { remove_const :ModuleFolder } - end - end - - def test_qualified_name_for - assert_equal "A", ActiveSupport::Dependencies.qualified_name_for(Object, :A) - assert_equal "A", ActiveSupport::Dependencies.qualified_name_for(:Object, :A) - assert_equal "A", ActiveSupport::Dependencies.qualified_name_for("Object", :A) - assert_equal "A", ActiveSupport::Dependencies.qualified_name_for("::Object", :A) - - assert_equal "ActiveSupport::Dependencies::A", ActiveSupport::Dependencies.qualified_name_for(:'ActiveSupport::Dependencies', :A) - assert_equal "ActiveSupport::Dependencies::A", ActiveSupport::Dependencies.qualified_name_for(ActiveSupport::Dependencies, :A) - end - - def test_file_search - with_loading 'dependencies' do - root = ActiveSupport::Dependencies.autoload_paths.first - assert_equal nil, ActiveSupport::Dependencies.search_for_file('service_three') - assert_equal nil, ActiveSupport::Dependencies.search_for_file('service_three.rb') - assert_equal root + '/service_one.rb', ActiveSupport::Dependencies.search_for_file('service_one') - assert_equal root + '/service_one.rb', ActiveSupport::Dependencies.search_for_file('service_one.rb') - end - end - - def test_file_search_uses_first_in_load_path - with_loading 'dependencies', 'autoloading_fixtures' do - deps, autoload = ActiveSupport::Dependencies.autoload_paths - assert_match %r/dependencies/, deps - assert_match %r/autoloading_fixtures/, autoload - - assert_equal deps + '/conflict.rb', ActiveSupport::Dependencies.search_for_file('conflict') - end - with_loading 'autoloading_fixtures', 'dependencies' do - autoload, deps = ActiveSupport::Dependencies.autoload_paths - assert_match %r/dependencies/, deps - assert_match %r/autoloading_fixtures/, autoload - - assert_equal autoload + '/conflict.rb', ActiveSupport::Dependencies.search_for_file('conflict') - end - - end - - def test_custom_const_missing_should_work - Object.module_eval <<-end_eval, __FILE__, __LINE__ + 1 - module ModuleWithCustomConstMissing - def self.const_missing(name) - const_set name, name.to_s.hash - end - - module A - end - end - end_eval - - with_autoloading_fixtures do - assert_kind_of Integer, ::ModuleWithCustomConstMissing::B - assert_kind_of Module, ::ModuleWithCustomConstMissing::A - assert_kind_of String, ::ModuleWithCustomConstMissing::A::B - end - end - - def test_const_missing_in_anonymous_modules_loads_top_level_constants - with_autoloading_fixtures do - # class_eval STRING pushes the class to the nesting of the eval'ed code. - klass = Class.new.class_eval "E" - assert_equal E, klass - end - end - - def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object - with_autoloading_fixtures do - require_dependency 'e' - - mod = Module.new - e = assert_raise(NameError) { mod::E } - assert_equal 'E cannot be autoloaded from an anonymous class or module', e.message - end - end - - def test_removal_from_tree_should_be_detected - with_loading 'dependencies' do - c = ServiceOne - ActiveSupport::Dependencies.clear - assert ! defined?(ServiceOne) - e = assert_raise ArgumentError do - ActiveSupport::Dependencies.load_missing_constant(c, :FakeMissing) - end - assert_match %r{ServiceOne has been removed from the module tree}i, e.message - end - end - - def test_references_should_work - with_loading 'dependencies' do - c = ActiveSupport::Dependencies.reference("ServiceOne") - service_one_first = ServiceOne - assert_equal service_one_first, c.get("ServiceOne") - ActiveSupport::Dependencies.clear - assert ! defined?(ServiceOne) - - service_one_second = ServiceOne - assert_not_equal service_one_first, c.get("ServiceOne") - assert_equal service_one_second, c.get("ServiceOne") - end - end - - def test_constantize_shortcut_for_cached_constant_lookups - with_loading 'dependencies' do - assert_equal ServiceOne, ActiveSupport::Dependencies.constantize("ServiceOne") - end - end - - def test_nested_load_error_isnt_rescued - with_loading 'dependencies' do - assert_raise(MissingSourceFile) do - RequiresNonexistent1 - end - end - end - - def test_autoload_once_paths_do_not_add_to_autoloaded_constants - with_autoloading_fixtures do - ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths.dup - - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder) - - 1 if ModuleFolder::NestedClass # 1 if to avoid warning - assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) - end - ensure - Object.class_eval { remove_const :ModuleFolder } - ActiveSupport::Dependencies.autoload_once_paths = [] - end - - def test_autoload_once_pathnames_do_not_add_to_autoloaded_constants - with_autoloading_fixtures do - pathnames = ActiveSupport::Dependencies.autoload_paths.collect{|p| Pathname.new(p)} - ActiveSupport::Dependencies.autoload_paths = pathnames - ActiveSupport::Dependencies.autoload_once_paths = pathnames - - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder) - - 1 if ModuleFolder::NestedClass # 1 if to avoid warning - assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) - end - ensure - Object.class_eval { remove_const :ModuleFolder } - ActiveSupport::Dependencies.autoload_once_paths = [] - end - - def test_application_should_special_case_application_controller - with_autoloading_fixtures do - require_dependency 'application' - assert_equal 10, ApplicationController - assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController) - end - end - - def test_preexisting_constants_are_not_marked_as_autoloaded - with_autoloading_fixtures do - require_dependency 'e' - assert ActiveSupport::Dependencies.autoloaded?(:E) - ActiveSupport::Dependencies.clear - end - - Object.const_set :E, Class.new - with_autoloading_fixtures do - require_dependency 'e' - assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!" - ActiveSupport::Dependencies.clear - end - - ensure - Object.class_eval { remove_const :E } - end - - def test_constants_in_capitalized_nesting_marked_as_autoloaded - with_autoloading_fixtures do - ActiveSupport::Dependencies.load_missing_constant(HTML, "SomeClass") - - assert ActiveSupport::Dependencies.autoloaded?("HTML::SomeClass") - end - end - - def test_unloadable - with_autoloading_fixtures do - Object.const_set :M, Module.new - M.unloadable - - ActiveSupport::Dependencies.clear - assert ! defined?(M) - - Object.const_set :M, Module.new - ActiveSupport::Dependencies.clear - assert ! defined?(M), "Dependencies should unload unloadable constants each time" - end - end - - def test_unloadable_should_fail_with_anonymous_modules - with_autoloading_fixtures do - m = Module.new - assert_raise(ArgumentError) { m.unloadable } - end - end - - def test_unloadable_should_return_change_flag - with_autoloading_fixtures do - Object.const_set :M, Module.new - assert_equal true, M.unloadable - assert_equal false, M.unloadable - end - ensure - Object.class_eval { remove_const :M } - end - - def test_unloadable_constants_should_receive_callback - Object.const_set :C, Class.new - C.unloadable - C.expects(:before_remove_const).once - assert C.respond_to?(:before_remove_const) - ActiveSupport::Dependencies.clear - assert !defined?(C) - ensure - Object.class_eval { remove_const :C } if defined?(C) - end - - def test_new_contants_in_without_constants - assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { }) - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } - end - - def test_new_constants_in_with_a_single_constant - assert_equal ["Hello"], ActiveSupport::Dependencies.new_constants_in(Object) { - Object.const_set :Hello, 10 - }.map(&:to_s) - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } - ensure - Object.class_eval { remove_const :Hello } - end - - def test_new_constants_in_with_nesting - outer = ActiveSupport::Dependencies.new_constants_in(Object) do - Object.const_set :OuterBefore, 10 - - assert_equal ["Inner"], ActiveSupport::Dependencies.new_constants_in(Object) { - Object.const_set :Inner, 20 - }.map(&:to_s) - - Object.const_set :OuterAfter, 30 - end - - assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s) - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } - ensure - %w(OuterBefore Inner OuterAfter).each do |name| - Object.class_eval { remove_const name if const_defined?(name) } - end - end - - def test_new_constants_in_module - Object.const_set :M, Module.new - - outer = ActiveSupport::Dependencies.new_constants_in(M) do - M.const_set :OuterBefore, 10 - - inner = ActiveSupport::Dependencies.new_constants_in(M) do - M.const_set :Inner, 20 - end - assert_equal ["M::Inner"], inner - - M.const_set :OuterAfter, 30 - end - assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } - ensure - Object.class_eval { remove_const :M } - end - - def test_new_constants_in_module_using_name - outer = ActiveSupport::Dependencies.new_constants_in(:M) do - Object.const_set :M, Module.new - M.const_set :OuterBefore, 10 - - inner = ActiveSupport::Dependencies.new_constants_in(:M) do - M.const_set :Inner, 20 - end - assert_equal ["M::Inner"], inner - - M.const_set :OuterAfter, 30 - end - assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort - assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } - ensure - Object.class_eval { remove_const :M } - end - - def test_new_constants_in_with_inherited_constants - m = ActiveSupport::Dependencies.new_constants_in(:Object) do - Object.class_eval { include ModuleWithConstant } - end - assert_equal [], m - end - - def test_new_constants_in_with_illegal_module_name_raises_correct_error - assert_raise(NameError) do - ActiveSupport::Dependencies.new_constants_in("Illegal-Name") {} - end - end - - def test_file_with_multiple_constants_and_require_dependency - with_autoloading_fixtures do - assert ! defined?(MultipleConstantFile) - assert ! defined?(SiblingConstant) - - require_dependency 'multiple_constant_file' - assert defined?(MultipleConstantFile) - assert defined?(SiblingConstant) - assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile) - assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant) - - ActiveSupport::Dependencies.clear - - assert ! defined?(MultipleConstantFile) - assert ! defined?(SiblingConstant) - end - end - - def test_file_with_multiple_constants_and_auto_loading - with_autoloading_fixtures do - assert ! defined?(MultipleConstantFile) - assert ! defined?(SiblingConstant) - - assert_equal 10, MultipleConstantFile - - assert defined?(MultipleConstantFile) - assert defined?(SiblingConstant) - assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile) - assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant) - - ActiveSupport::Dependencies.clear - - assert ! defined?(MultipleConstantFile) - assert ! defined?(SiblingConstant) - end - end - - def test_nested_file_with_multiple_constants_and_require_dependency - with_autoloading_fixtures do - assert ! defined?(ClassFolder::NestedClass) - assert ! defined?(ClassFolder::SiblingClass) - - require_dependency 'class_folder/nested_class' - - assert defined?(ClassFolder::NestedClass) - assert defined?(ClassFolder::SiblingClass) - assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::NestedClass") - assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::SiblingClass") - - ActiveSupport::Dependencies.clear - - assert ! defined?(ClassFolder::NestedClass) - assert ! defined?(ClassFolder::SiblingClass) - end - end - - def test_nested_file_with_multiple_constants_and_auto_loading - with_autoloading_fixtures do - assert ! defined?(ClassFolder::NestedClass) - assert ! defined?(ClassFolder::SiblingClass) - - assert_kind_of Class, ClassFolder::NestedClass - - assert defined?(ClassFolder::NestedClass) - assert defined?(ClassFolder::SiblingClass) - assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::NestedClass") - assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::SiblingClass") - - ActiveSupport::Dependencies.clear - - assert ! defined?(ClassFolder::NestedClass) - assert ! defined?(ClassFolder::SiblingClass) - end - end - - def test_autoload_doesnt_shadow_no_method_error_with_relative_constant - with_autoloading_fixtures do - assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!" - 2.times do - assert_raise(NoMethodError) { RaisesNoMethodError } - assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!" - end - end - - ensure - Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) } - end - - def test_autoload_doesnt_shadow_no_method_error_with_absolute_constant - with_autoloading_fixtures do - assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!" - 2.times do - assert_raise(NoMethodError) { ::RaisesNoMethodError } - assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!" - end - end - - ensure - Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) } - end - - def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load - with_autoloading_fixtures do - ActiveSupport::Dependencies.mechanism = :require - 2.times do - assert_raise(NameError) { assert_equal 123, ::RaisesNameError::FooBarBaz } - end - end - end - - def test_autoload_doesnt_shadow_name_error - with_autoloading_fixtures do - Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError) - 2.times do - e = assert_raise NameError do - ::RaisesNameError::FooBarBaz.object_id - end - assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message - assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!" - end - - assert !defined?(::RaisesNameError) - 2.times do - assert_raise(NameError) { ::RaisesNameError } - assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!" - end - end - - ensure - Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) } - end - - def test_remove_constant_handles_double_colon_at_start - Object.const_set 'DeleteMe', Module.new - DeleteMe.const_set 'OrMe', Module.new - ActiveSupport::Dependencies.remove_constant "::DeleteMe::OrMe" - assert ! defined?(DeleteMe::OrMe) - assert defined?(DeleteMe) - ActiveSupport::Dependencies.remove_constant "::DeleteMe" - assert ! defined?(DeleteMe) - end - - def test_remove_constant_does_not_trigger_loading_autoloads - constant = 'ShouldNotBeAutoloaded' - Object.class_eval do - autoload constant, File.expand_path('../autoloading_fixtures/should_not_be_required', __FILE__) - end - - assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant" - assert !defined?(ShouldNotBeAutoloaded) - end - - def test_remove_constant_does_not_autoload_already_removed_parents_as_a_side_effect - with_autoloading_fixtures do - _ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context" - _ = ::A::B # assignment to silence parse-time warning "possibly useless use of :: in void context" - ActiveSupport::Dependencies.remove_constant('A') - ActiveSupport::Dependencies.remove_constant('A::B') - assert !defined?(A) - end - end - - def test_load_once_constants_should_not_be_unloaded - with_autoloading_fixtures do - ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths - _ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context" - assert defined?(A) - ActiveSupport::Dependencies.clear - assert defined?(A) - end - ensure - ActiveSupport::Dependencies.autoload_once_paths = [] - Object.class_eval { remove_const :A if const_defined?(:A) } - end - - def test_access_unloaded_constants_for_reload - with_autoloading_fixtures do - assert_kind_of Module, A - assert_kind_of Class, A::B # Necessary to load A::B for the test - ActiveSupport::Dependencies.mark_for_unload(A::B) - ActiveSupport::Dependencies.remove_unloadable_constants! - - A::B # Make sure no circular dependency error - end - end - - - def test_autoload_once_paths_should_behave_when_recursively_loading - with_loading 'dependencies', 'autoloading_fixtures' do - ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last] - assert !defined?(CrossSiteDependency) - assert_nothing_raised { CrossSiteDepender.nil? } - assert defined?(CrossSiteDependency) - assert !ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency), - "CrossSiteDependency shouldn't be marked as autoloaded!" - ActiveSupport::Dependencies.clear - assert defined?(CrossSiteDependency), - "CrossSiteDependency shouldn't have been unloaded!" - end - ensure - ActiveSupport::Dependencies.autoload_once_paths = [] - end - - def test_hook_called_multiple_times - assert_nothing_raised { ActiveSupport::Dependencies.hook! } - end - - def test_unhook - ActiveSupport::Dependencies.unhook! - assert !Module.new.respond_to?(:const_missing_without_dependencies) - assert !Module.new.respond_to?(:load_without_new_constant_marking) - ensure - ActiveSupport::Dependencies.hook! - end - -private - def remove_constants(*constants) - constants.each do |constant| - Object.send(:remove_const, constant) if Object.const_defined?(constant) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/dependencies_test_helpers.rb b/app/server/ruby/vendor/activesupport/test/dependencies_test_helpers.rb deleted file mode 100644 index 9268512a97..0000000000 --- a/app/server/ruby/vendor/activesupport/test/dependencies_test_helpers.rb +++ /dev/null @@ -1,27 +0,0 @@ -module DependenciesTestHelpers - def with_loading(*from) - old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load - this_dir = File.dirname(__FILE__) - parent_dir = File.dirname(this_dir) - path_copy = $LOAD_PATH.dup - $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir) - prior_autoload_paths = ActiveSupport::Dependencies.autoload_paths - ActiveSupport::Dependencies.autoload_paths = from.collect { |f| "#{this_dir}/#{f}" } - yield - ensure - $LOAD_PATH.replace(path_copy) - ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths - ActiveSupport::Dependencies.mechanism = old_mechanism - ActiveSupport::Dependencies.explicitly_unloadable_constants = [] - end - - def with_autoloading_fixtures(&block) - with_loading 'autoloading_fixtures', &block - end - - def remove_constants(*constants) - constants.each do |constant| - Object.send(:remove_const, constant) if Object.const_defined?(constant) - end - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/deprecation/proxy_wrappers_test.rb b/app/server/ruby/vendor/activesupport/test/deprecation/proxy_wrappers_test.rb deleted file mode 100644 index e4f0f0f7c2..0000000000 --- a/app/server/ruby/vendor/activesupport/test/deprecation/proxy_wrappers_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'abstract_unit' -require 'active_support/deprecation' - -class ProxyWrappersTest < ActiveSupport::TestCase - Waffles = false - NewWaffles = :hamburgers - - def test_deprecated_object_proxy_doesnt_wrap_falsy_objects - proxy = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(nil, "message") - assert !proxy - end - - def test_deprecated_instance_variable_proxy_doesnt_wrap_falsy_objects - proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(nil, :waffles) - assert !proxy - end - - def test_deprecated_constant_proxy_doesnt_wrap_falsy_objects - proxy = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(Waffles, NewWaffles) - assert !proxy - end -end diff --git a/app/server/ruby/vendor/activesupport/test/deprecation_test.rb b/app/server/ruby/vendor/activesupport/test/deprecation_test.rb deleted file mode 100644 index ee1c69502e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/deprecation_test.rb +++ /dev/null @@ -1,358 +0,0 @@ -require 'abstract_unit' - -class Deprecatee - def initialize - @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request) - @_request = 'there we go' - end - def request; @_request end - def old_request; @request end - - def partially(foo = nil) - ActiveSupport::Deprecation.warn('calling with foo=nil is out') if foo.nil? - end - - def not() 2 end - def none() 1 end - def one(a) a end - def multi(a,b,c) [a,b,c] end - deprecate :none, :one, :multi - - def a; end - def b; end - def c; end - def d; end - def e; end - deprecate :a, :b, :c => :e, :d => "you now need to do something extra for this one" - - def f=(v); end - deprecate :f= - - module B - C = 1 - end - A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Deprecatee::A', 'Deprecatee::B::C') -end - - -class DeprecationTest < ActiveSupport::TestCase - def setup - # Track the last warning. - @old_behavior = ActiveSupport::Deprecation.behavior - @last_message = nil - ActiveSupport::Deprecation.behavior = Proc.new { |message| @last_message = message } - - @dtc = Deprecatee.new - end - - def teardown - ActiveSupport::Deprecation.behavior = @old_behavior - end - - def test_inline_deprecation_warning - assert_deprecated(/foo=nil/) do - @dtc.partially - end - end - - def test_undeprecated - assert_not_deprecated do - assert_equal 2, @dtc.not - end - end - - def test_deprecate_class_method - assert_deprecated(/none is deprecated/) do - assert_equal 1, @dtc.none - end - - assert_deprecated(/one is deprecated/) do - assert_equal 1, @dtc.one(1) - end - - assert_deprecated(/multi is deprecated/) do - assert_equal [1,2,3], @dtc.multi(1,2,3) - end - end - - def test_deprecate_object - deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, ':bomb:') - assert_deprecated(/:bomb:/) { deprecated_object.to_s } - end - - def test_nil_behavior_is_ignored - ActiveSupport::Deprecation.behavior = nil - assert_deprecated(/foo=nil/) { @dtc.partially } - end - - def test_several_behaviors - @a, @b = nil, nil - - ActiveSupport::Deprecation.behavior = [ - Proc.new { |msg, callstack| @a = msg }, - Proc.new { |msg, callstack| @b = msg } - ] - - @dtc.partially - assert_match(/foo=nil/, @a) - assert_match(/foo=nil/, @b) - end - - def test_raise_behaviour - ActiveSupport::Deprecation.behavior = :raise - - message = 'Revise this deprecated stuff now!' - callstack = %w(foo bar baz) - - e = assert_raise ActiveSupport::DeprecationException do - ActiveSupport::Deprecation.behavior.first.call(message, callstack) - end - assert_equal message, e.message - assert_equal callstack, e.backtrace - end - - def test_default_stderr_behavior - ActiveSupport::Deprecation.behavior = :stderr - behavior = ActiveSupport::Deprecation.behavior.first - - content = capture(:stderr) { - assert_nil behavior.call('Some error!', ['call stack!']) - } - assert_match(/Some error!/, content) - assert_match(/call stack!/, content) - end - - def test_default_stderr_behavior_with_warn_method - ActiveSupport::Deprecation.behavior = :stderr - - content = capture(:stderr) { - ActiveSupport::Deprecation.warn('Instance error!', ['instance call stack!']) - } - - assert_match(/Instance error!/, content) - assert_match(/instance call stack!/, content) - end - - def test_default_silence_behavior - ActiveSupport::Deprecation.behavior = :silence - behavior = ActiveSupport::Deprecation.behavior.first - - stderr_output = capture(:stderr) { - assert_nil behavior.call('Some error!', ['call stack!']) - } - assert stderr_output.blank? - end - - def test_deprecated_instance_variable_proxy - assert_not_deprecated { @dtc.request.size } - - assert_deprecated('@request.size') { assert_equal @dtc.request.size, @dtc.old_request.size } - assert_deprecated('@request.to_s') { assert_equal @dtc.request.to_s, @dtc.old_request.to_s } - end - - def test_deprecated_instance_variable_proxy_shouldnt_warn_on_inspect - assert_not_deprecated { assert_equal @dtc.request.inspect, @dtc.old_request.inspect } - end - - def test_deprecated_constant_proxy - assert_not_deprecated { Deprecatee::B::C } - assert_deprecated('Deprecatee::A') { assert_equal Deprecatee::B::C, Deprecatee::A } - assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class } - end - - def test_assert_deprecation_without_match - assert_deprecated do - @dtc.partially - end - end - - def test_assert_deprecated_matches_any_warning - assert_deprecated 'abc' do - ActiveSupport::Deprecation.warn 'abc' - ActiveSupport::Deprecation.warn 'def' - end - rescue Minitest::Assertion - flunk 'assert_deprecated should match any warning in block, not just the last one' - end - - def test_assert_not_deprecated_returns_result_of_block - assert_equal 123, assert_not_deprecated { 123 } - end - - def test_assert_deprecated_returns_result_of_block - result = assert_deprecated('abc') do - ActiveSupport::Deprecation.warn 'abc' - 123 - end - assert_equal 123, result - end - - def test_assert_deprecated_warn_work_with_default_behavior - ActiveSupport::Deprecation.instance_variable_set('@behavior' , nil) - assert_deprecated('abc') do - ActiveSupport::Deprecation.warn 'abc' - end - end - - def test_silence - ActiveSupport::Deprecation.silence do - assert_not_deprecated { @dtc.partially } - end - - ActiveSupport::Deprecation.silenced = true - assert_not_deprecated { @dtc.partially } - ActiveSupport::Deprecation.silenced = false - end - - def test_deprecation_without_explanation - assert_deprecated { @dtc.a } - assert_deprecated { @dtc.b } - assert_deprecated { @dtc.f = :foo } - end - - def test_deprecation_with_alternate_method - assert_deprecated(/use e instead/) { @dtc.c } - end - - def test_deprecation_with_explicit_message - assert_deprecated(/you now need to do something extra for this one/) { @dtc.d } - end - - def test_deprecation_in_other_object - messages = [] - - klass = Class.new do - delegate :warn, :behavior=, to: ActiveSupport::Deprecation - end - - o = klass.new - o.behavior = Proc.new { |message, callstack| messages << message } - assert_difference("messages.size") do - o.warn("warning") - end - end - - def test_deprecated_method_with_custom_method_warning - deprecator = deprecator_with_messages - - class << deprecator - private - def deprecated_method_warning(method, message) - "deprecator.deprecated_method_warning.#{method}" - end - end - - deprecatee = Class.new do - def method - end - deprecate :method, deprecator: deprecator - end - - deprecatee.new.method - assert deprecator.messages.first.match("DEPRECATION WARNING: deprecator.deprecated_method_warning.method") - end - - def test_deprecate_with_custom_deprecator - custom_deprecator = mock('Deprecator') do - expects(:deprecation_warning) - end - - klass = Class.new do - def method - end - deprecate :method, deprecator: custom_deprecator - end - - klass.new.method - end - - def test_deprecated_constant_with_deprecator_given - deprecator = deprecator_with_messages - klass = Class.new - klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new('klass::OLD', 'Object', deprecator) ) - assert_difference("deprecator.messages.size") do - klass::OLD.to_s - end - end - - def test_deprecated_instance_variable_with_instance_deprecator - deprecator = deprecator_with_messages - - klass = Class.new() do - def initialize(deprecator) - @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator) - @_request = :a_request - end - def request; @_request end - def old_request; @request end - end - - assert_difference("deprecator.messages.size") { klass.new(deprecator).old_request.to_s } - end - - def test_deprecated_instance_variable_with_given_deprecator - deprecator = deprecator_with_messages - - klass = Class.new do - define_method(:initialize) do - @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator) - @_request = :a_request - end - def request; @_request end - def old_request; @request end - end - - assert_difference("deprecator.messages.size") { klass.new.old_request.to_s } - end - - def test_delegate_deprecator_instance - klass = Class.new do - attr_reader :last_message - delegate :warn, :behavior=, to: ActiveSupport::Deprecation - - def initialize - self.behavior = [Proc.new { |message| @last_message = message }] - end - - def deprecated_method - warn(deprecated_method_warning(:deprecated_method, "You are calling deprecated method")) - end - - private - def deprecated_method_warning(method_name, message = nil) - message || "#{method_name} is deprecated and will be removed from This Library" - end - end - - object = klass.new - object.deprecated_method - assert_match(/You are calling deprecated method/, object.last_message) - end - - def test_default_gem_name - deprecator = ActiveSupport::Deprecation.new - - deprecator.send(:deprecated_method_warning, :deprecated_method, "You are calling deprecated method").tap do |message| - assert_match(/is deprecated and will be removed from Rails/, message) - end - end - - def test_custom_gem_name - deprecator = ActiveSupport::Deprecation.new('2.0', 'Custom') - - deprecator.send(:deprecated_method_warning, :deprecated_method, "You are calling deprecated method").tap do |message| - assert_match(/is deprecated and will be removed from Custom/, message) - end - end - - private - def deprecator_with_messages - klass = Class.new(ActiveSupport::Deprecation) - deprecator = klass.new - deprecator.behavior = Proc.new{|message, callstack| deprecator.messages << message} - def deprecator.messages - @messages ||= [] - end - deprecator - end -end diff --git a/app/server/ruby/vendor/activesupport/test/descendants_tracker_test_cases.rb b/app/server/ruby/vendor/activesupport/test/descendants_tracker_test_cases.rb deleted file mode 100644 index 69e046998e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/descendants_tracker_test_cases.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'set' - -module DescendantsTrackerTestCases - class Parent - extend ActiveSupport::DescendantsTracker - end - - class Child1 < Parent - end - - class Child2 < Parent - end - - class Grandchild1 < Child1 - end - - class Grandchild2 < Child1 - end - - ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2] - - def test_descendants - assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants - assert_equal_sets [Grandchild1, Grandchild2], Child1.descendants - assert_equal_sets [], Child2.descendants - end - - def test_direct_descendants - assert_equal_sets [Child1, Child2], Parent.direct_descendants - assert_equal_sets [Grandchild1, Grandchild2], Child1.direct_descendants - assert_equal_sets [], Child2.direct_descendants - end - - def test_clear - mark_as_autoloaded(*ALL) do - ActiveSupport::DescendantsTracker.clear - ALL.each do |k| - assert ActiveSupport::DescendantsTracker.descendants(k).empty? - end - end - end - - protected - - def assert_equal_sets(expected, actual) - assert_equal Set.new(expected), Set.new(actual) - end - - def mark_as_autoloaded(*klasses) - # If ActiveSupport::Dependencies is not loaded, forget about autoloading. - # This allows using AS::DescendantsTracker without AS::Dependencies. - if defined? ActiveSupport::Dependencies - old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup - ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name) - end - - old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup - old_descendants.each { |k, v| old_descendants[k] = v.dup } - - yield - ensure - ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded if defined? ActiveSupport::Dependencies - ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/descendants_tracker_with_autoloading_test.rb b/app/server/ruby/vendor/activesupport/test/descendants_tracker_with_autoloading_test.rb deleted file mode 100644 index a2ae066a21..0000000000 --- a/app/server/ruby/vendor/activesupport/test/descendants_tracker_with_autoloading_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'abstract_unit' -require 'active_support/descendants_tracker' -require 'active_support/dependencies' -require 'descendants_tracker_test_cases' - -class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase - include DescendantsTrackerTestCases - - def test_clear_with_autoloaded_parent_children_and_grandchildren - mark_as_autoloaded(*ALL) do - ActiveSupport::DescendantsTracker.clear - ALL.each do |k| - assert ActiveSupport::DescendantsTracker.descendants(k).empty? - end - end - end - - def test_clear_with_autoloaded_children_and_grandchildren - mark_as_autoloaded Child1, Grandchild1, Grandchild2 do - ActiveSupport::DescendantsTracker.clear - assert_equal_sets [Child2], Parent.descendants - assert_equal_sets [], Child2.descendants - end - end - - def test_clear_with_autoloaded_grandchildren - mark_as_autoloaded Grandchild1, Grandchild2 do - ActiveSupport::DescendantsTracker.clear - assert_equal_sets [Child1, Child2], Parent.descendants - assert_equal_sets [], Child1.descendants - assert_equal_sets [], Child2.descendants - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/descendants_tracker_without_autoloading_test.rb b/app/server/ruby/vendor/activesupport/test/descendants_tracker_without_autoloading_test.rb deleted file mode 100644 index 00b449af51..0000000000 --- a/app/server/ruby/vendor/activesupport/test/descendants_tracker_without_autoloading_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'abstract_unit' -require 'active_support/descendants_tracker' -require 'descendants_tracker_test_cases' - -class DescendantsTrackerWithoutAutoloadingTest < ActiveSupport::TestCase - include DescendantsTrackerTestCases - - # Regression test for #8422. https://github.com/rails/rails/issues/8442 - def test_clear_without_autoloaded_singleton_parent - mark_as_autoloaded do - parent_instance = Parent.new - parent_instance.singleton_class.descendants - ActiveSupport::DescendantsTracker.clear - assert !ActiveSupport::DescendantsTracker.class_variable_get(:@@direct_descendants).key?(parent_instance.singleton_class) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/file_update_checker_test.rb b/app/server/ruby/vendor/activesupport/test/file_update_checker_test.rb deleted file mode 100644 index bd1df0f858..0000000000 --- a/app/server/ruby/vendor/activesupport/test/file_update_checker_test.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'abstract_unit' -require 'fileutils' -require 'thread' - -MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__) - -class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase - FILES = %w(1.txt 2.txt 3.txt) - - def setup - FileUtils.mkdir_p("tmp_watcher") - FileUtils.touch(FILES) - end - - def teardown - FileUtils.rm_rf("tmp_watcher") - FileUtils.rm_rf(FILES) - end - - def test_should_not_execute_the_block_if_no_paths_are_given - i = 0 - checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 } - checker.execute_if_updated - assert_equal 0, i - end - - def test_should_not_invoke_the_block_if_no_file_has_changed - i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - 5.times { assert !checker.execute_if_updated } - assert_equal 0, i - end - - def test_should_invoke_the_block_if_a_file_has_changed - i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - sleep(1) - FileUtils.touch(FILES) - assert checker.execute_if_updated - assert_equal 1, i - end - - def test_should_be_robust_enough_to_handle_deleted_files - i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - FileUtils.rm(FILES) - assert checker.execute_if_updated - assert_equal 1, i - end - - def test_should_be_robust_to_handle_files_with_wrong_modified_time - i = 0 - now = Time.now - time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future - File.utime time, time, FILES[2] - - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - - sleep(1) - FileUtils.touch(FILES[0..1]) - - assert checker.execute_if_updated - assert_equal 1, i - end - - def test_should_cache_updated_result_until_execute - i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - assert !checker.updated? - - sleep(1) - FileUtils.touch(FILES) - - assert checker.updated? - checker.execute - assert !checker.updated? - end - - def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob - i = 0 - checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => [:txt]){ i += 1 } - FileUtils.cd "tmp_watcher" do - FileUtils.touch(FILES) - end - assert checker.execute_if_updated - assert_equal 1, i - end - - def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob - i = 0 - checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => :rb){ i += 1 } - FileUtils.cd "tmp_watcher" do - FileUtils.touch(FILES) - end - assert !checker.execute_if_updated - assert_equal 0, i - end - - def test_should_not_block_if_a_strange_filename_used - FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,") - FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" }) - - test = Thread.new do - ActiveSupport::FileUpdateChecker.new([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 } - Thread.exit - end - test.priority = -1 - test.join(5) - - assert !test.alive? - end -end diff --git a/app/server/ruby/vendor/activesupport/test/fixtures/autoload/another_class.rb b/app/server/ruby/vendor/activesupport/test/fixtures/autoload/another_class.rb deleted file mode 100644 index a240b3de41..0000000000 --- a/app/server/ruby/vendor/activesupport/test/fixtures/autoload/another_class.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Fixtures::AnotherClass -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/fixtures/autoload/some_class.rb b/app/server/ruby/vendor/activesupport/test/fixtures/autoload/some_class.rb deleted file mode 100644 index 13b3c73ef5..0000000000 --- a/app/server/ruby/vendor/activesupport/test/fixtures/autoload/some_class.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Fixtures::Autoload::SomeClass -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_doctype.dtd b/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_doctype.dtd deleted file mode 100644 index 89480496ef..0000000000 --- a/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_doctype.dtd +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_entities.txt b/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_entities.txt deleted file mode 100644 index 0337fdaa08..0000000000 --- a/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_entities.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_include.txt b/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_include.txt deleted file mode 100644 index 239ca3afaf..0000000000 --- a/app/server/ruby/vendor/activesupport/test/fixtures/xml/jdom_include.txt +++ /dev/null @@ -1 +0,0 @@ -include me diff --git a/app/server/ruby/vendor/activesupport/test/gzip_test.rb b/app/server/ruby/vendor/activesupport/test/gzip_test.rb deleted file mode 100644 index 0e3cf3b429..0000000000 --- a/app/server/ruby/vendor/activesupport/test/gzip_test.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/blank' - -class GzipTest < ActiveSupport::TestCase - def test_compress_should_decompress_to_the_same_value - assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World")) - assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::NO_COMPRESSION)) - assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::BEST_SPEED)) - assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::BEST_COMPRESSION)) - assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, Zlib::FILTERED)) - assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, Zlib::HUFFMAN_ONLY)) - assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, nil)) - end - - def test_compress_should_return_a_binary_string - compressed = ActiveSupport::Gzip.compress('') - - assert_equal Encoding.find('binary'), compressed.encoding - assert !compressed.blank?, "a compressed blank string should not be blank" - end - - def test_compress_should_return_gzipped_string_by_compression_level - source_string = "Hello World"*100 - - gzipped_by_speed = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_SPEED) - assert_equal 1, Zlib::GzipReader.new(StringIO.new(gzipped_by_speed)).level - - gzipped_by_best_compression = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_COMPRESSION) - assert_equal 9, Zlib::GzipReader.new(StringIO.new(gzipped_by_best_compression)).level - - assert_equal true, (gzipped_by_best_compression.bytesize < gzipped_by_speed.bytesize) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/i18n_test.rb b/app/server/ruby/vendor/activesupport/test/i18n_test.rb deleted file mode 100644 index 5ef59b6e6b..0000000000 --- a/app/server/ruby/vendor/activesupport/test/i18n_test.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'abstract_unit' -require 'active_support/time' -require 'active_support/core_ext/array/conversions' - -class I18nTest < ActiveSupport::TestCase - def setup - @date = Date.parse("2008-7-2") - @time = Time.utc(2008, 7, 2, 16, 47, 1) - end - - def test_time_zone_localization_with_default_format - now = Time.local(2000) - assert_equal now.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(now) - end - - def test_date_localization_should_use_default_format - assert_equal @date.strftime("%Y-%m-%d"), I18n.localize(@date) - end - - def test_date_localization_with_default_format - assert_equal @date.strftime("%Y-%m-%d"), I18n.localize(@date, :format => :default) - end - - def test_date_localization_with_short_format - assert_equal @date.strftime("%b %d"), I18n.localize(@date, :format => :short) - end - - def test_date_localization_with_long_format - assert_equal @date.strftime("%B %d, %Y"), I18n.localize(@date, :format => :long) - end - - def test_time_localization_should_use_default_format - assert_equal @time.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(@time) - end - - def test_time_localization_with_default_format - assert_equal @time.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(@time, :format => :default) - end - - def test_time_localization_with_short_format - assert_equal @time.strftime("%d %b %H:%M"), I18n.localize(@time, :format => :short) - end - - def test_time_localization_with_long_format - assert_equal @time.strftime("%B %d, %Y %H:%M"), I18n.localize(@time, :format => :long) - end - - def test_day_names - assert_equal Date::DAYNAMES, I18n.translate(:'date.day_names') - end - - def test_abbr_day_names - assert_equal Date::ABBR_DAYNAMES, I18n.translate(:'date.abbr_day_names') - end - - def test_month_names - assert_equal Date::MONTHNAMES, I18n.translate(:'date.month_names') - end - - def test_abbr_month_names - assert_equal Date::ABBR_MONTHNAMES, I18n.translate(:'date.abbr_month_names') - end - - def test_date_order - assert_equal %w(year month day), I18n.translate(:'date.order') - end - - def test_time_am - assert_equal 'am', I18n.translate(:'time.am') - end - - def test_time_pm - assert_equal 'pm', I18n.translate(:'time.pm') - end - - def test_words_connector - assert_equal ', ', I18n.translate(:'support.array.words_connector') - end - - def test_two_words_connector - assert_equal ' and ', I18n.translate(:'support.array.two_words_connector') - end - - def test_last_word_connector - assert_equal ', and ', I18n.translate(:'support.array.last_word_connector') - end - - def test_to_sentence - default_two_words_connector = I18n.translate(:'support.array.two_words_connector') - default_last_word_connector = I18n.translate(:'support.array.last_word_connector') - assert_equal 'a, b, and c', %w[a b c].to_sentence - I18n.backend.store_translations 'en', :support => { :array => { :two_words_connector => ' & ' } } - assert_equal 'a & b', %w[a b].to_sentence - I18n.backend.store_translations 'en', :support => { :array => { :last_word_connector => ' and ' } } - assert_equal 'a, b and c', %w[a b c].to_sentence - ensure - I18n.backend.store_translations 'en', :support => { :array => { :two_words_connector => default_two_words_connector } } - I18n.backend.store_translations 'en', :support => { :array => { :last_word_connector => default_last_word_connector } } - end - - def test_to_sentence_with_empty_i18n_store - I18n.backend.store_translations 'empty', {} - assert_equal 'a, b, and c', %w[a b c].to_sentence(locale: 'empty') - end -end diff --git a/app/server/ruby/vendor/activesupport/test/inflector_test.rb b/app/server/ruby/vendor/activesupport/test/inflector_test.rb deleted file mode 100644 index 35967ba656..0000000000 --- a/app/server/ruby/vendor/activesupport/test/inflector_test.rb +++ /dev/null @@ -1,523 +0,0 @@ -require 'abstract_unit' -require 'active_support/inflector' - -require 'inflector_test_cases' -require 'constantize_test_cases' - -class InflectorTest < ActiveSupport::TestCase - include InflectorTestCases - include ConstantizeTestCases - - def test_pluralize_plurals - assert_equal "plurals", ActiveSupport::Inflector.pluralize("plurals") - assert_equal "Plurals", ActiveSupport::Inflector.pluralize("Plurals") - end - - def test_pluralize_empty_string - assert_equal "", ActiveSupport::Inflector.pluralize("") - end - - ActiveSupport::Inflector.inflections.uncountable.each do |word| - define_method "test_uncountability_of_#{word}" do - assert_equal word, ActiveSupport::Inflector.singularize(word) - assert_equal word, ActiveSupport::Inflector.pluralize(word) - assert_equal ActiveSupport::Inflector.pluralize(word), ActiveSupport::Inflector.singularize(word) - end - end - - def test_uncountable_word_is_not_greedy - with_dup do - uncountable_word = "ors" - countable_word = "sponsor" - - ActiveSupport::Inflector.inflections.uncountable << uncountable_word - - assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word) - assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word) - assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word) - - assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word) - assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word) - assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word)) - end - end - - SingularToPlural.each do |singular, plural| - define_method "test_pluralize_singular_#{singular}" do - assert_equal(plural, ActiveSupport::Inflector.pluralize(singular)) - assert_equal(plural.capitalize, ActiveSupport::Inflector.pluralize(singular.capitalize)) - end - end - - SingularToPlural.each do |singular, plural| - define_method "test_singularize_plural_#{plural}" do - assert_equal(singular, ActiveSupport::Inflector.singularize(plural)) - assert_equal(singular.capitalize, ActiveSupport::Inflector.singularize(plural.capitalize)) - end - end - - SingularToPlural.each do |singular, plural| - define_method "test_pluralize_plural_#{plural}" do - assert_equal(plural, ActiveSupport::Inflector.pluralize(plural)) - assert_equal(plural.capitalize, ActiveSupport::Inflector.pluralize(plural.capitalize)) - end - - define_method "test_singularize_singular_#{singular}" do - assert_equal(singular, ActiveSupport::Inflector.singularize(singular)) - assert_equal(singular.capitalize, ActiveSupport::Inflector.singularize(singular.capitalize)) - end - end - - - def test_overwrite_previous_inflectors - with_dup do - assert_equal("series", ActiveSupport::Inflector.singularize("series")) - ActiveSupport::Inflector.inflections.singular "series", "serie" - assert_equal("serie", ActiveSupport::Inflector.singularize("series")) - end - end - - MixtureToTitleCase.each_with_index do |(before, titleized), index| - define_method "test_titleize_mixture_to_title_case_#{index}" do - assert_equal(titleized, ActiveSupport::Inflector.titleize(before), "mixture \ - to TitleCase failed for #{before}") - end - end - - def test_camelize - CamelToUnderscore.each do |camel, underscore| - assert_equal(camel, ActiveSupport::Inflector.camelize(underscore)) - end - end - - def test_camelize_with_lower_downcases_the_first_letter - assert_equal('capital', ActiveSupport::Inflector.camelize('Capital', false)) - end - - def test_camelize_with_underscores - assert_equal("CamelCase", ActiveSupport::Inflector.camelize('Camel_Case')) - end - - def test_acronyms - ActiveSupport::Inflector.inflections do |inflect| - inflect.acronym("API") - inflect.acronym("HTML") - inflect.acronym("HTTP") - inflect.acronym("RESTful") - inflect.acronym("W3C") - inflect.acronym("PhD") - inflect.acronym("RoR") - inflect.acronym("SSL") - end - - # camelize underscore humanize titleize - [ - ["API", "api", "API", "API"], - ["APIController", "api_controller", "API controller", "API Controller"], - ["Nokogiri::HTML", "nokogiri/html", "Nokogiri/HTML", "Nokogiri/HTML"], - ["HTTPAPI", "http_api", "HTTP API", "HTTP API"], - ["HTTP::Get", "http/get", "HTTP/get", "HTTP/Get"], - ["SSLError", "ssl_error", "SSL error", "SSL Error"], - ["RESTful", "restful", "RESTful", "RESTful"], - ["RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"], - ["IHeartW3C", "i_heart_w3c", "I heart W3C", "I Heart W3C"], - ["PhDRequired", "phd_required", "PhD required", "PhD Required"], - ["IRoRU", "i_ror_u", "I RoR u", "I RoR U"], - ["RESTfulHTTPAPI", "restful_http_api", "RESTful HTTP API", "RESTful HTTP API"], - - # misdirection - ["Capistrano", "capistrano", "Capistrano", "Capistrano"], - ["CapiController", "capi_controller", "Capi controller", "Capi Controller"], - ["HttpsApis", "https_apis", "Https apis", "Https Apis"], - ["Html5", "html5", "Html5", "Html5"], - ["Restfully", "restfully", "Restfully", "Restfully"], - ["RoRails", "ro_rails", "Ro rails", "Ro Rails"] - ].each do |camel, under, human, title| - assert_equal(camel, ActiveSupport::Inflector.camelize(under)) - assert_equal(camel, ActiveSupport::Inflector.camelize(camel)) - assert_equal(under, ActiveSupport::Inflector.underscore(under)) - assert_equal(under, ActiveSupport::Inflector.underscore(camel)) - assert_equal(title, ActiveSupport::Inflector.titleize(under)) - assert_equal(title, ActiveSupport::Inflector.titleize(camel)) - assert_equal(human, ActiveSupport::Inflector.humanize(under)) - end - end - - def test_acronym_override - ActiveSupport::Inflector.inflections do |inflect| - inflect.acronym("API") - inflect.acronym("LegacyApi") - end - - assert_equal("LegacyApi", ActiveSupport::Inflector.camelize("legacyapi")) - assert_equal("LegacyAPI", ActiveSupport::Inflector.camelize("legacy_api")) - assert_equal("SomeLegacyApi", ActiveSupport::Inflector.camelize("some_legacyapi")) - assert_equal("Nonlegacyapi", ActiveSupport::Inflector.camelize("nonlegacyapi")) - end - - def test_acronyms_camelize_lower - ActiveSupport::Inflector.inflections do |inflect| - inflect.acronym("API") - inflect.acronym("HTML") - end - - assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("html_api", false)) - assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("htmlAPI", false)) - assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("HTMLAPI", false)) - end - - def test_underscore_acronym_sequence - ActiveSupport::Inflector.inflections do |inflect| - inflect.acronym("API") - inflect.acronym("JSON") - inflect.acronym("HTML") - end - - assert_equal("json_html_api", ActiveSupport::Inflector.underscore("JSONHTMLAPI")) - end - - def test_underscore - CamelToUnderscore.each do |camel, underscore| - assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) - end - CamelToUnderscoreWithoutReverse.each do |camel, underscore| - assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) - end - end - - def test_camelize_with_module - CamelWithModuleToUnderscoreWithSlash.each do |camel, underscore| - assert_equal(camel, ActiveSupport::Inflector.camelize(underscore)) - end - end - - def test_underscore_with_slashes - CamelWithModuleToUnderscoreWithSlash.each do |camel, underscore| - assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) - end - end - - def test_demodulize - assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account") - assert_equal "Account", ActiveSupport::Inflector.demodulize("Account") - assert_equal "", ActiveSupport::Inflector.demodulize("") - end - - def test_deconstantize - assert_equal "MyApplication::Billing", ActiveSupport::Inflector.deconstantize("MyApplication::Billing::Account") - assert_equal "::MyApplication::Billing", ActiveSupport::Inflector.deconstantize("::MyApplication::Billing::Account") - - assert_equal "MyApplication", ActiveSupport::Inflector.deconstantize("MyApplication::Billing") - assert_equal "::MyApplication", ActiveSupport::Inflector.deconstantize("::MyApplication::Billing") - - assert_equal "", ActiveSupport::Inflector.deconstantize("Account") - assert_equal "", ActiveSupport::Inflector.deconstantize("::Account") - assert_equal "", ActiveSupport::Inflector.deconstantize("") - end - - def test_foreign_key - ClassNameToForeignKeyWithUnderscore.each do |klass, foreign_key| - assert_equal(foreign_key, ActiveSupport::Inflector.foreign_key(klass)) - end - - ClassNameToForeignKeyWithoutUnderscore.each do |klass, foreign_key| - assert_equal(foreign_key, ActiveSupport::Inflector.foreign_key(klass, false)) - end - end - - def test_tableize - ClassNameToTableName.each do |class_name, table_name| - assert_equal(table_name, ActiveSupport::Inflector.tableize(class_name)) - end - end - -# FIXME: get following tests to pass on jruby, currently skipped -# -# Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes -# required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby -# causing our tests to error out. -# related bug http://jira.codehaus.org/browse/JRUBY-7194 - def test_parameterize - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" - StringToParameterized.each do |some_string, parameterized_string| - assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string)) - end - end - - def test_parameterize_and_normalize - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" - StringToParameterizedAndNormalized.each do |some_string, parameterized_string| - assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string)) - end - end - - def test_parameterize_with_custom_separator - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" - StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| - assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) - end - end - - def test_parameterize_with_multi_character_separator - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" - StringToParameterized.each do |some_string, parameterized_string| - assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__')) - end - end - - def test_classify - ClassNameToTableName.each do |class_name, table_name| - assert_equal(class_name, ActiveSupport::Inflector.classify(table_name)) - assert_equal(class_name, ActiveSupport::Inflector.classify("table_prefix." + table_name)) - end - end - - def test_classify_with_symbol - assert_nothing_raised do - assert_equal 'FooBar', ActiveSupport::Inflector.classify(:foo_bars) - end - end - - def test_classify_with_leading_schema_name - assert_equal 'FooBar', ActiveSupport::Inflector.classify('schema.foo_bar') - end - - def test_humanize - UnderscoreToHuman.each do |underscore, human| - assert_equal(human, ActiveSupport::Inflector.humanize(underscore)) - end - end - - def test_humanize_without_capitalize - UnderscoreToHumanWithoutCapitalize.each do |underscore, human| - assert_equal(human, ActiveSupport::Inflector.humanize(underscore, capitalize: false)) - end - end - - def test_humanize_by_rule - ActiveSupport::Inflector.inflections do |inflect| - inflect.human(/_cnt$/i, '\1_count') - inflect.human(/^prefx_/i, '\1') - end - assert_equal("Jargon count", ActiveSupport::Inflector.humanize("jargon_cnt")) - assert_equal("Request", ActiveSupport::Inflector.humanize("prefx_request")) - end - - def test_humanize_by_string - ActiveSupport::Inflector.inflections do |inflect| - inflect.human("col_rpted_bugs", "Reported bugs") - end - assert_equal("Reported bugs", ActiveSupport::Inflector.humanize("col_rpted_bugs")) - assert_equal("Col rpted bugs", ActiveSupport::Inflector.humanize("COL_rpted_bugs")) - end - - def test_constantize - run_constantize_tests_on do |string| - ActiveSupport::Inflector.constantize(string) - end - end - - def test_safe_constantize - run_safe_constantize_tests_on do |string| - ActiveSupport::Inflector.safe_constantize(string) - end - end - - def test_ordinal - OrdinalNumbers.each do |number, ordinalized| - assert_equal(ordinalized, number + ActiveSupport::Inflector.ordinal(number)) - end - end - - def test_ordinalize - OrdinalNumbers.each do |number, ordinalized| - assert_equal(ordinalized, ActiveSupport::Inflector.ordinalize(number)) - end - end - - def test_dasherize - UnderscoresToDashes.each do |underscored, dasherized| - assert_equal(dasherized, ActiveSupport::Inflector.dasherize(underscored)) - end - end - - def test_underscore_as_reverse_of_dasherize - UnderscoresToDashes.each_key do |underscored| - assert_equal(underscored, ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.dasherize(underscored))) - end - end - - def test_underscore_to_lower_camel - UnderscoreToLowerCamel.each do |underscored, lower_camel| - assert_equal(lower_camel, ActiveSupport::Inflector.camelize(underscored, false)) - end - end - - def test_symbol_to_lower_camel - SymbolToLowerCamel.each do |symbol, lower_camel| - assert_equal(lower_camel, ActiveSupport::Inflector.camelize(symbol, false)) - end - end - - %w{plurals singulars uncountables humans}.each do |inflection_type| - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def test_clear_#{inflection_type} - with_dup do - ActiveSupport::Inflector.inflections.clear :#{inflection_type} - assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\" - end - end - RUBY - end - - def test_inflector_locality - ActiveSupport::Inflector.inflections(:es) do |inflect| - inflect.plural(/$/, 's') - inflect.plural(/z$/i, 'ces') - - inflect.singular(/s$/, '') - inflect.singular(/es$/, '') - - inflect.irregular('el', 'los') - end - - assert_equal('hijos', 'hijo'.pluralize(:es)) - assert_equal('luces', 'luz'.pluralize(:es)) - assert_equal('luzs', 'luz'.pluralize) - - assert_equal('sociedad', 'sociedades'.singularize(:es)) - assert_equal('sociedade', 'sociedades'.singularize) - - assert_equal('los', 'el'.pluralize(:es)) - assert_equal('els', 'el'.pluralize) - - ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear } - - assert ActiveSupport::Inflector.inflections(:es).plurals.empty? - assert ActiveSupport::Inflector.inflections(:es).singulars.empty? - assert !ActiveSupport::Inflector.inflections.plurals.empty? - assert !ActiveSupport::Inflector.inflections.singulars.empty? - end - - def test_clear_all - with_dup do - ActiveSupport::Inflector.inflections do |inflect| - # ensure any data is present - inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/(database)s$/i, '\1') - inflect.uncountable('series') - inflect.human("col_rpted_bugs", "Reported bugs") - - inflect.clear :all - - assert inflect.plurals.empty? - assert inflect.singulars.empty? - assert inflect.uncountables.empty? - assert inflect.humans.empty? - end - end - end - - def test_clear_with_default - with_dup do - ActiveSupport::Inflector.inflections do |inflect| - # ensure any data is present - inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/(database)s$/i, '\1') - inflect.uncountable('series') - inflect.human("col_rpted_bugs", "Reported bugs") - - inflect.clear - - assert inflect.plurals.empty? - assert inflect.singulars.empty? - assert inflect.uncountables.empty? - assert inflect.humans.empty? - end - end - end - - Irregularities.each do |singular, plural| - define_method("test_irregularity_between_#{singular}_and_#{plural}") do - with_dup do - ActiveSupport::Inflector.inflections do |inflect| - inflect.irregular(singular, plural) - assert_equal singular, ActiveSupport::Inflector.singularize(plural) - assert_equal plural, ActiveSupport::Inflector.pluralize(singular) - end - end - end - end - - Irregularities.each do |singular, plural| - define_method("test_pluralize_of_irregularity_#{plural}_should_be_the_same") do - with_dup do - ActiveSupport::Inflector.inflections do |inflect| - inflect.irregular(singular, plural) - assert_equal plural, ActiveSupport::Inflector.pluralize(plural) - end - end - end - end - - Irregularities.each do |singular, plural| - define_method("test_singularize_of_irregularity_#{singular}_should_be_the_same") do - with_dup do - ActiveSupport::Inflector.inflections do |inflect| - inflect.irregular(singular, plural) - assert_equal singular, ActiveSupport::Inflector.singularize(singular) - end - end - end - end - - [ :all, [] ].each do |scope| - ActiveSupport::Inflector.inflections do |inflect| - define_method("test_clear_inflections_with_#{scope.kind_of?(Array) ? "no_arguments" : scope}") do - # save all the inflections - singulars, plurals, uncountables = inflect.singulars, inflect.plurals, inflect.uncountables - - # clear all the inflections - inflect.clear(*scope) - - assert_equal [], inflect.singulars - assert_equal [], inflect.plurals - assert_equal [], inflect.uncountables - - # restore all the inflections - singulars.reverse.each { |singular| inflect.singular(*singular) } - plurals.reverse.each { |plural| inflect.plural(*plural) } - inflect.uncountable(uncountables) - - assert_equal singulars, inflect.singulars - assert_equal plurals, inflect.plurals - assert_equal uncountables, inflect.uncountables - end - end - end - - %w(plurals singulars uncountables humans acronyms).each do |scope| - ActiveSupport::Inflector.inflections do |inflect| - define_method("test_clear_inflections_with_#{scope}") do - with_dup do - # clear the inflections - inflect.clear(scope) - assert_equal [], inflect.send(scope) - end - end - end - end - - # Dups the singleton and yields, restoring the original inflections later. - # Use this in tests what modify the state of the singleton. - # - # This helper is implemented by setting @__instance__ because in some tests - # there are module functions that access ActiveSupport::Inflector.inflections, - # so we need to replace the singleton itself. - def with_dup - original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__) - ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup) - ensure - ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/inflector_test_cases.rb b/app/server/ruby/vendor/activesupport/test/inflector_test_cases.rb deleted file mode 100644 index dd03a61176..0000000000 --- a/app/server/ruby/vendor/activesupport/test/inflector_test_cases.rb +++ /dev/null @@ -1,321 +0,0 @@ -# encoding: utf-8 - -module InflectorTestCases - SingularToPlural = { - "search" => "searches", - "switch" => "switches", - "fix" => "fixes", - "box" => "boxes", - "process" => "processes", - "address" => "addresses", - "case" => "cases", - "stack" => "stacks", - "wish" => "wishes", - "fish" => "fish", - "jeans" => "jeans", - "funky jeans" => "funky jeans", - "my money" => "my money", - - "category" => "categories", - "query" => "queries", - "ability" => "abilities", - "agency" => "agencies", - "movie" => "movies", - - "archive" => "archives", - - "index" => "indices", - - "wife" => "wives", - "safe" => "saves", - "half" => "halves", - - "move" => "moves", - - "salesperson" => "salespeople", - "person" => "people", - - "spokesman" => "spokesmen", - "man" => "men", - "woman" => "women", - - "basis" => "bases", - "diagnosis" => "diagnoses", - "diagnosis_a" => "diagnosis_as", - - "datum" => "data", - "medium" => "media", - "stadium" => "stadia", - "analysis" => "analyses", - "my_analysis" => "my_analyses", - - "node_child" => "node_children", - "child" => "children", - - "experience" => "experiences", - "day" => "days", - - "comment" => "comments", - "foobar" => "foobars", - "newsletter" => "newsletters", - - "old_news" => "old_news", - "news" => "news", - - "series" => "series", - "miniseries" => "miniseries", - "species" => "species", - - "quiz" => "quizzes", - - "perspective" => "perspectives", - - "ox" => "oxen", - "photo" => "photos", - "buffalo" => "buffaloes", - "tomato" => "tomatoes", - "dwarf" => "dwarves", - "elf" => "elves", - "information" => "information", - "equipment" => "equipment", - "bus" => "buses", - "status" => "statuses", - "status_code" => "status_codes", - "mouse" => "mice", - - "louse" => "lice", - "house" => "houses", - "octopus" => "octopi", - "virus" => "viri", - "alias" => "aliases", - "portfolio" => "portfolios", - - "vertex" => "vertices", - "matrix" => "matrices", - "matrix_fu" => "matrix_fus", - - "axis" => "axes", - "taxi" => "taxis", # prevents regression - "testis" => "testes", - "crisis" => "crises", - - "rice" => "rice", - "shoe" => "shoes", - - "horse" => "horses", - "prize" => "prizes", - "edge" => "edges", - - "database" => "databases", - - # regression tests against improper inflection regexes - "|ice" => "|ices", - "|ouse" => "|ouses", - "slice" => "slices", - "police" => "police" - } - - CamelToUnderscore = { - "Product" => "product", - "SpecialGuest" => "special_guest", - "ApplicationController" => "application_controller", - "Area51Controller" => "area51_controller" - } - - UnderscoreToLowerCamel = { - "product" => "product", - "special_guest" => "specialGuest", - "application_controller" => "applicationController", - "area51_controller" => "area51Controller" - } - - SymbolToLowerCamel = { - :product => 'product', - :special_guest => 'specialGuest', - :application_controller => 'applicationController', - :area51_controller => 'area51Controller' - } - - CamelToUnderscoreWithoutReverse = { - "HTMLTidy" => "html_tidy", - "HTMLTidyGenerator" => "html_tidy_generator", - "FreeBSD" => "free_bsd", - "HTML" => "html", - } - - CamelWithModuleToUnderscoreWithSlash = { - "Admin::Product" => "admin/product", - "Users::Commission::Department" => "users/commission/department", - "UsersSection::CommissionDepartment" => "users_section/commission_department", - } - - ClassNameToForeignKeyWithUnderscore = { - "Person" => "person_id", - "MyApplication::Billing::Account" => "account_id" - } - - ClassNameToForeignKeyWithoutUnderscore = { - "Person" => "personid", - "MyApplication::Billing::Account" => "accountid" - } - - ClassNameToTableName = { - "PrimarySpokesman" => "primary_spokesmen", - "NodeChild" => "node_children" - } - - StringToParameterized = { - "Donald E. Knuth" => "donald-e-knuth", - "Random text with *(bad)* characters" => "random-text-with-bad-characters", - "Allow_Under_Scores" => "allow_under_scores", - "Trailing bad characters!@#" => "trailing-bad-characters", - "!@#Leading bad characters" => "leading-bad-characters", - "Squeeze separators" => "squeeze-separators", - "Test with + sign" => "test-with-sign", - "Test with malformed utf8 \251" => "test-with-malformed-utf8" - } - - StringToParameterizeWithNoSeparator = { - "Donald E. Knuth" => "donaldeknuth", - "With-some-dashes" => "with-some-dashes", - "Random text with *(bad)* characters" => "randomtextwithbadcharacters", - "Trailing bad characters!@#" => "trailingbadcharacters", - "!@#Leading bad characters" => "leadingbadcharacters", - "Squeeze separators" => "squeezeseparators", - "Test with + sign" => "testwithsign", - "Test with malformed utf8 \251" => "testwithmalformedutf8" - } - - StringToParameterizeWithUnderscore = { - "Donald E. Knuth" => "donald_e_knuth", - "Random text with *(bad)* characters" => "random_text_with_bad_characters", - "With-some-dashes" => "with-some-dashes", - "Retain_underscore" => "retain_underscore", - "Trailing bad characters!@#" => "trailing_bad_characters", - "!@#Leading bad characters" => "leading_bad_characters", - "Squeeze separators" => "squeeze_separators", - "Test with + sign" => "test_with_sign", - "Test with malformed utf8 \251" => "test_with_malformed_utf8" - } - - StringToParameterizedAndNormalized = { - "Malmö" => "malmo", - "Garçons" => "garcons", - "Ops\331" => "opsu", - "Ærøskøbing" => "aeroskobing", - "Aßlar" => "asslar", - "Japanese: 日本語" => "japanese" - } - - UnderscoreToHuman = { - "employee_salary" => "Employee salary", - "employee_id" => "Employee", - "underground" => "Underground" - } - - UnderscoreToHumanWithoutCapitalize = { - "employee_salary" => "employee salary", - "employee_id" => "employee", - "underground" => "underground" - } - - MixtureToTitleCase = { - 'active_record' => 'Active Record', - 'ActiveRecord' => 'Active Record', - 'action web service' => 'Action Web Service', - 'Action Web Service' => 'Action Web Service', - 'Action web service' => 'Action Web Service', - 'actionwebservice' => 'Actionwebservice', - 'Actionwebservice' => 'Actionwebservice', - "david's code" => "David's Code", - "David's code" => "David's Code", - "david's Code" => "David's Code", - "sgt. pepper's" => "Sgt. Pepper's", - "i've just seen a face" => "I've Just Seen A Face", - "maybe you'll be there" => "Maybe You'll Be There", - "¿por qué?" => '¿Por Qué?', - "Fred’s" => "Fred’s", - "Fred`s" => "Fred`s" - } - - OrdinalNumbers = { - "-1" => "-1st", - "-2" => "-2nd", - "-3" => "-3rd", - "-4" => "-4th", - "-5" => "-5th", - "-6" => "-6th", - "-7" => "-7th", - "-8" => "-8th", - "-9" => "-9th", - "-10" => "-10th", - "-11" => "-11th", - "-12" => "-12th", - "-13" => "-13th", - "-14" => "-14th", - "-20" => "-20th", - "-21" => "-21st", - "-22" => "-22nd", - "-23" => "-23rd", - "-24" => "-24th", - "-100" => "-100th", - "-101" => "-101st", - "-102" => "-102nd", - "-103" => "-103rd", - "-104" => "-104th", - "-110" => "-110th", - "-111" => "-111th", - "-112" => "-112th", - "-113" => "-113th", - "-1000" => "-1000th", - "-1001" => "-1001st", - "0" => "0th", - "1" => "1st", - "2" => "2nd", - "3" => "3rd", - "4" => "4th", - "5" => "5th", - "6" => "6th", - "7" => "7th", - "8" => "8th", - "9" => "9th", - "10" => "10th", - "11" => "11th", - "12" => "12th", - "13" => "13th", - "14" => "14th", - "20" => "20th", - "21" => "21st", - "22" => "22nd", - "23" => "23rd", - "24" => "24th", - "100" => "100th", - "101" => "101st", - "102" => "102nd", - "103" => "103rd", - "104" => "104th", - "110" => "110th", - "111" => "111th", - "112" => "112th", - "113" => "113th", - "1000" => "1000th", - "1001" => "1001st" - } - - UnderscoresToDashes = { - "street" => "street", - "street_address" => "street-address", - "person_street_address" => "person-street-address" - } - - Irregularities = { - 'person' => 'people', - 'man' => 'men', - 'child' => 'children', - 'sex' => 'sexes', - 'move' => 'moves', - 'cow' => 'kine', # Test inflections with different starting letters - 'zombie' => 'zombies', - 'genus' => 'genera' - } -end diff --git a/app/server/ruby/vendor/activesupport/test/json/decoding_test.rb b/app/server/ruby/vendor/activesupport/test/json/decoding_test.rb deleted file mode 100644 index 07d7e530ca..0000000000 --- a/app/server/ruby/vendor/activesupport/test/json/decoding_test.rb +++ /dev/null @@ -1,105 +0,0 @@ -# encoding: utf-8 -require 'abstract_unit' -require 'active_support/json' -require 'active_support/time' - -class TestJSONDecoding < ActiveSupport::TestCase - class Foo - def self.json_create(object) - "Foo" - end - end - - TESTS = { - %q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}}, - %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}}, - %q({"returnTo":{"\/categories":1}}) => {"returnTo" => {"/categories" => 1}}, - %({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]}, - %({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]}, - %({"a": "'", "b": "5,000"}) => {"a" => "'", "b" => "5,000"}, - %({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"}, - # multibyte - %({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"}, - %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)}, - %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)}, - %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)], - %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)], - # no time zone - %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"}, - # invalid date - %({"a": "1089-10-40"}) => {'a' => "1089-10-40"}, - # xmlschema date notation - %({"a": "2009-08-10T19:01:02Z"}) => {'a' => Time.utc(2009, 8, 10, 19, 1, 2)}, - %({"a": "2009-08-10T19:01:02+02:00"}) => {'a' => Time.utc(2009, 8, 10, 17, 1, 2)}, - %({"a": "2009-08-10T19:01:02-05:00"}) => {'a' => Time.utc(2009, 8, 11, 00, 1, 2)}, - # needs to be *exact* - %({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "}, - %({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"}, - %([]) => [], - %({}) => {}, - %({"a":1}) => {"a" => 1}, - %({"a": ""}) => {"a" => ""}, - %({"a":"\\""}) => {"a" => "\""}, - %({"a": null}) => {"a" => nil}, - %({"a": true}) => {"a" => true}, - %({"a": false}) => {"a" => false}, - %q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""}, - %q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"}, - %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => ""}, - %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"}, - %q({"a": "\u003cbr /\u003e"}) => {'a' => "
"}, - %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["","",""]}, - # test combination of dates and escaped or unicode encoded data in arrays - %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) => - [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}], - %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) => - [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}, - {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}], - # tests escaping of "\n" char with Yaml backend - %q({"a":"\n"}) => {"a"=>"\n"}, - %q({"a":"\u000a"}) => {"a"=>"\n"}, - %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}, - # prevent json unmarshalling - %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"}, - # json "fragments" - these are invalid JSON, but ActionPack relies on this - %q("a string") => "a string", - %q(1.1) => 1.1, - %q(1) => 1, - %q(-1) => -1, - %q(true) => true, - %q(false) => false, - %q(null) => nil - } - - TESTS.each_with_index do |(json, expected), index| - test "json decodes #{index}" do - prev = ActiveSupport.parse_json_times - ActiveSupport.parse_json_times = true - silence_warnings do - assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \ - failed for #{json}" - end - ActiveSupport.parse_json_times = prev - end - end - - test "json decodes time json with time parsing disabled" do - prev = ActiveSupport.parse_json_times - ActiveSupport.parse_json_times = false - expected = {"a" => "2007-01-01 01:12:34 Z"} - assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"})) - ActiveSupport.parse_json_times = prev - end - - def test_failed_json_decoding - assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) } - assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) } - assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) } - assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) } - end - - def test_cannot_pass_unsupported_options - assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) } - end -end - diff --git a/app/server/ruby/vendor/activesupport/test/json/encoding_test.rb b/app/server/ruby/vendor/activesupport/test/json/encoding_test.rb deleted file mode 100644 index f22d7b8b02..0000000000 --- a/app/server/ruby/vendor/activesupport/test/json/encoding_test.rb +++ /dev/null @@ -1,546 +0,0 @@ -# encoding: utf-8 -require 'securerandom' -require 'abstract_unit' -require 'active_support/core_ext/string/inflections' -require 'active_support/json' -require 'active_support/time' - -class TestJSONEncoding < ActiveSupport::TestCase - class Foo - def initialize(a, b) - @a, @b = a, b - end - end - - class Hashlike - def to_hash - { :foo => "hello", :bar => "world" } - end - end - - class Custom - def initialize(serialized) - @serialized = serialized - end - - def as_json(options = nil) - @serialized - end - end - - class CustomWithOptions - attr_accessor :foo, :bar - - def as_json(options={}) - options[:only] = %w(foo bar) - super(options) - end - end - - class OptionsTest - def as_json(options = :default) - options - end - end - - class HashWithAsJson < Hash - attr_accessor :as_json_called - - def initialize(*) - super - end - - def as_json(options={}) - @as_json_called = true - super - end - end - - TrueTests = [[ true, %(true) ]] - FalseTests = [[ false, %(false) ]] - NilTests = [[ nil, %(null) ]] - NumericTests = [[ 1, %(1) ], - [ 2.5, %(2.5) ], - [ 0.0/0.0, %(null) ], - [ 1.0/0.0, %(null) ], - [ -1.0/0.0, %(null) ], - [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ], - [ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]] - - StringTests = [[ 'this is the ', %("this is the \\u003cstring\\u003e")], - [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], - [ 'http://test.host/posts/1', %("http://test.host/posts/1")], - [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029", - %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]] - - ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ], - [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] - - RangeTests = [[ 1..2, %("1..2")], - [ 1...2, %("1...2")], - [ 1.5..2.5, %("1.5..2.5")]] - - SymbolTests = [[ :a, %("a") ], - [ :this, %("this") ], - [ :"a b", %("a b") ]] - - ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]] - HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]] - CustomTests = [[ Custom.new("custom"), '"custom"' ], - [ Custom.new(nil), 'null' ], - [ Custom.new(:a), '"a"' ], - [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ], - [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ], - [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ], - [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]] - - RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']] - - DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]] - TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] - DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] - - StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]] - StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]] - StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]] - StandardStringTests = [[ 'this is the ', %("this is the ")]] - - def sorted_json(json) - return json unless json =~ /^\{.*\}$/ - '{' + json[1..-2].split(',').sort.join(',') + '}' - end - - constants.grep(/Tests$/).each do |class_tests| - define_method("test_#{class_tests[0..-6].underscore}") do - begin - prev = ActiveSupport.use_standard_json_time_format - - ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/ - ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/ - self.class.const_get(class_tests).each do |pair| - assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first)) - end - ensure - ActiveSupport.escape_html_entities_in_json = false - ActiveSupport.use_standard_json_time_format = prev - end - end - end - - def test_process_status - # There doesn't seem to be a good way to get a handle on a Process::Status object without actually - # creating a child process, hence this to populate $? - system("not_a_real_program_#{SecureRandom.hex}") - assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?) - end - - def test_hash_encoding - assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b) - assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1) - assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode('a' => [1,2]) - assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2) - - assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d)) - end - - def test_utf8_string_encoded_properly - result = ActiveSupport::JSON.encode('€2.99') - assert_equal '"€2.99"', result - assert_equal(Encoding::UTF_8, result.encoding) - - result = ActiveSupport::JSON.encode('✎☺') - assert_equal '"✎☺"', result - assert_equal(Encoding::UTF_8, result.encoding) - end - - def test_non_utf8_string_transcodes - s = '二'.encode('Shift_JIS') - result = ActiveSupport::JSON.encode(s) - assert_equal '"二"', result - assert_equal Encoding::UTF_8, result.encoding - end - - def test_wide_utf8_chars - w = '𠜎' - result = ActiveSupport::JSON.encode(w) - assert_equal '"𠜎"', result - end - - def test_wide_utf8_roundtrip - hash = { string: "𐒑" } - json = ActiveSupport::JSON.encode(hash) - decoded_hash = ActiveSupport::JSON.decode(json) - assert_equal "𐒑", decoded_hash['string'] - end - - def test_reading_encode_big_decimal_as_string_option - assert_deprecated do - assert ActiveSupport.encode_big_decimal_as_string - end - end - - def test_setting_deprecated_encode_big_decimal_as_string_option - assert_raise(NotImplementedError) do - ActiveSupport.encode_big_decimal_as_string = true - end - - assert_raise(NotImplementedError) do - ActiveSupport.encode_big_decimal_as_string = false - end - end - - def test_exception_raised_when_encoding_circular_reference_in_array - a = [1] - a << a - assert_deprecated do - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } - end - end - - def test_exception_raised_when_encoding_circular_reference_in_hash - a = { :name => 'foo' } - a[:next] = a - assert_deprecated do - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } - end - end - - def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array - a = { :name => 'foo', :sub => [] } - a[:sub] << a - assert_deprecated do - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } - end - end - - def test_hash_key_identifiers_are_always_quoted - values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"} - assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values)) - end - - def test_hash_should_allow_key_filtering_with_only - assert_equal %({"a":1}), ActiveSupport::JSON.encode({'a' => 1, :b => 2, :c => 3}, :only => 'a') - end - - def test_hash_should_allow_key_filtering_with_except - assert_equal %({"b":2}), ActiveSupport::JSON.encode({'foo' => 'bar', :b => 2, :c => 3}, :except => ['foo', :c]) - end - - def test_time_to_json_includes_local_offset - with_standard_json_time_format(true) do - with_env_tz 'US/Eastern' do - assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) - end - end - end - - def test_hash_with_time_to_json - with_standard_json_time_format(false) do - assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json - end - end - - def test_nested_hash_with_float - assert_nothing_raised do - hash = { - "CHI" => { - :display_name => "chicago", - :latitude => 123.234 - } - } - ActiveSupport::JSON.encode(hash) - end - end - - def test_hash_like_with_options - h = Hashlike.new - json = h.to_json :only => [:foo] - - assert_equal({"foo"=>"hello"}, JSON.parse(json)) - end - - def test_object_to_json_with_options - obj = Object.new - obj.instance_variable_set :@foo, "hello" - obj.instance_variable_set :@bar, "world" - json = obj.to_json :only => ["foo"] - - assert_equal({"foo"=>"hello"}, JSON.parse(json)) - end - - def test_struct_to_json_with_options - struct = Struct.new(:foo, :bar).new - struct.foo = "hello" - struct.bar = "world" - json = struct.to_json :only => [:foo] - - assert_equal({"foo"=>"hello"}, JSON.parse(json)) - end - - def test_hash_should_pass_encoding_options_to_children_in_as_json - person = { - :name => 'John', - :address => { - :city => 'London', - :country => 'UK' - } - } - json = person.as_json :only => [:address, :city] - - assert_equal({ 'address' => { 'city' => 'London' }}, json) - end - - def test_hash_should_pass_encoding_options_to_children_in_to_json - person = { - :name => 'John', - :address => { - :city => 'London', - :country => 'UK' - } - } - json = person.to_json :only => [:address, :city] - - assert_equal(%({"address":{"city":"London"}}), json) - end - - def test_array_should_pass_encoding_options_to_children_in_as_json - people = [ - { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, - { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} - ] - json = people.as_json :only => [:address, :city] - expected = [ - { 'address' => { 'city' => 'London' }}, - { 'address' => { 'city' => 'Paris' }} - ] - - assert_equal(expected, json) - end - - def test_array_should_pass_encoding_options_to_children_in_to_json - people = [ - { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, - { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} - ] - json = people.to_json :only => [:address, :city] - - assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) - end - - People = Class.new(BasicObject) do - include Enumerable - def initialize() - @people = [ - { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, - { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} - ] - end - def each(*, &blk) - @people.each do |p| - yield p if blk - p - end.each - end - end - - def test_enumerable_should_generate_json_with_as_json - json = People.new.as_json :only => [:address, :city] - expected = [ - { 'address' => { 'city' => 'London' }}, - { 'address' => { 'city' => 'Paris' }} - ] - - assert_equal(expected, json) - end - - def test_enumerable_should_generate_json_with_to_json - json = People.new.to_json :only => [:address, :city] - assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) - end - - def test_enumerable_should_pass_encoding_options_to_children_in_as_json - json = People.new.each.as_json :only => [:address, :city] - expected = [ - { 'address' => { 'city' => 'London' }}, - { 'address' => { 'city' => 'Paris' }} - ] - - assert_equal(expected, json) - end - - def test_enumerable_should_pass_encoding_options_to_children_in_to_json - json = People.new.each.to_json :only => [:address, :city] - - assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) - end - - def test_hash_to_json_should_not_keep_options_around - f = CustomWithOptions.new - f.foo = "hello" - f.bar = "world" - - hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}} - assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"}, - "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json)) - end - - def test_array_to_json_should_not_keep_options_around - f = CustomWithOptions.new - f.foo = "hello" - f.bar = "world" - - array = [f, {"foo" => "other_foo", "test" => "other_test"}] - assert_equal([{"foo"=>"hello","bar"=>"world"}, - {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json)) - end - - def test_hash_as_json_without_options - json = { foo: OptionsTest.new }.as_json - assert_equal({"foo" => :default}, json) - end - - def test_array_as_json_without_options - json = [ OptionsTest.new ].as_json - assert_equal([:default], json) - end - - def test_struct_encoding - Struct.new('UserNameAndEmail', :name, :email) - Struct.new('UserNameAndDate', :name, :date) - Struct.new('Custom', :name, :sub) - user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com' - user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01) - custom = Struct::Custom.new 'David', user_birthday - - - json_strings = "" - json_string_and_date = "" - json_custom = "" - - assert_nothing_raised do - json_strings = user_email.to_json - json_string_and_date = user_birthday.to_json - json_custom = custom.to_json - end - - assert_equal({"name" => "David", - "sub" => { - "name" => "David", - "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom)) - - assert_equal({"name" => "David", "email" => "sample@example.com"}, - ActiveSupport::JSON.decode(json_strings)) - - assert_equal({"name" => "David", "date" => "2010-01-01"}, - ActiveSupport::JSON.decode(json_string_and_date)) - end - - def test_nil_true_and_false_represented_as_themselves - assert_equal nil, nil.as_json - assert_equal true, true.as_json - assert_equal false, false.as_json - end - - def test_json_gem_dump_by_passing_active_support_encoder - h = HashWithAsJson.new - h[:foo] = "hello" - h[:bar] = "world" - - assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h) - assert_nil h.as_json_called - end - - def test_json_gem_generate_by_passing_active_support_encoder - h = HashWithAsJson.new - h[:foo] = "hello" - h[:bar] = "world" - - assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h) - assert_nil h.as_json_called - end - - def test_json_gem_pretty_generate_by_passing_active_support_encoder - h = HashWithAsJson.new - h[:foo] = "hello" - h[:bar] = "world" - - assert_equal <2) - end - - test "Generating a key of the default length" do - derived_key = @generator.generate_key("some_salt") - assert_kind_of String, derived_key - assert_equal OpenSSL::Digest::SHA1.new.block_length, derived_key.length, "Should have generated a key of the default size" - end - - test "Generating a key of an alternative length" do - derived_key = @generator.generate_key("some_salt", 32) - assert_kind_of String, derived_key - assert_equal 32, derived_key.length, "Should have generated a key of the right size" - end -end - -end diff --git a/app/server/ruby/vendor/activesupport/test/lazy_load_hooks_test.rb b/app/server/ruby/vendor/activesupport/test/lazy_load_hooks_test.rb deleted file mode 100644 index 7851634dbf..0000000000 --- a/app/server/ruby/vendor/activesupport/test/lazy_load_hooks_test.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'abstract_unit' - -class LazyLoadHooksTest < ActiveSupport::TestCase - def test_basic_hook - i = 0 - ActiveSupport.on_load(:basic_hook) { i += 1 } - ActiveSupport.run_load_hooks(:basic_hook) - assert_equal 1, i - end - - def test_basic_hook_with_two_registrations - i = 0 - ActiveSupport.on_load(:basic_hook_with_two) { i += incr } - assert_equal 0, i - ActiveSupport.run_load_hooks(:basic_hook_with_two, FakeContext.new(2)) - assert_equal 2, i - ActiveSupport.run_load_hooks(:basic_hook_with_two, FakeContext.new(5)) - assert_equal 7, i - end - - def test_hook_registered_after_run - i = 0 - ActiveSupport.run_load_hooks(:registered_after) - assert_equal 0, i - ActiveSupport.on_load(:registered_after) { i += 1 } - assert_equal 1, i - end - - def test_hook_registered_after_run_with_two_registrations - i = 0 - ActiveSupport.run_load_hooks(:registered_after_with_two, FakeContext.new(2)) - ActiveSupport.run_load_hooks(:registered_after_with_two, FakeContext.new(5)) - assert_equal 0, i - ActiveSupport.on_load(:registered_after_with_two) { i += incr } - assert_equal 7, i - end - - def test_hook_registered_interleaved_run_with_two_registrations - i = 0 - ActiveSupport.run_load_hooks(:registered_interleaved_with_two, FakeContext.new(2)) - assert_equal 0, i - ActiveSupport.on_load(:registered_interleaved_with_two) { i += incr } - assert_equal 2, i - ActiveSupport.run_load_hooks(:registered_interleaved_with_two, FakeContext.new(5)) - assert_equal 7, i - end - - def test_hook_receives_a_context - i = 0 - ActiveSupport.on_load(:contextual) { i += incr } - assert_equal 0, i - ActiveSupport.run_load_hooks(:contextual, FakeContext.new(2)) - assert_equal 2, i - end - - def test_hook_receives_a_context_afterward - i = 0 - ActiveSupport.run_load_hooks(:contextual_after, FakeContext.new(2)) - assert_equal 0, i - ActiveSupport.on_load(:contextual_after) { i += incr } - assert_equal 2, i - end - - def test_hook_with_yield_true - i = 0 - ActiveSupport.on_load(:contextual_yield, :yield => true) do |obj| - i += obj.incr + incr_amt - end - assert_equal 0, i - ActiveSupport.run_load_hooks(:contextual_yield, FakeContext.new(2)) - assert_equal 7, i - end - - def test_hook_with_yield_true_afterward - i = 0 - ActiveSupport.run_load_hooks(:contextual_yield_after, FakeContext.new(2)) - assert_equal 0, i - ActiveSupport.on_load(:contextual_yield_after, :yield => true) do |obj| - i += obj.incr + incr_amt - end - assert_equal 7, i - end - -private - - def incr_amt - 5 - end - - class FakeContext - attr_reader :incr - def initialize(incr) - @incr = incr - end - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/load_paths_test.rb b/app/server/ruby/vendor/activesupport/test/load_paths_test.rb deleted file mode 100644 index ac617a9fd8..0000000000 --- a/app/server/ruby/vendor/activesupport/test/load_paths_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'abstract_unit' - -class LoadPathsTest < ActiveSupport::TestCase - def test_uniq_load_paths - load_paths_count = $LOAD_PATH.inject({}) { |paths, path| - expanded_path = File.expand_path(path) - paths[expanded_path] ||= 0 - paths[expanded_path] += 1 - paths - } - load_paths_count[File.expand_path('../../lib', __FILE__)] -= 1 - - load_paths_count.select! { |k, v| v > 1 } - assert load_paths_count.empty?, load_paths_count.inspect - end -end diff --git a/app/server/ruby/vendor/activesupport/test/log_subscriber_test.rb b/app/server/ruby/vendor/activesupport/test/log_subscriber_test.rb deleted file mode 100644 index 2a0e8d20ed..0000000000 --- a/app/server/ruby/vendor/activesupport/test/log_subscriber_test.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'abstract_unit' -require 'active_support/log_subscriber/test_helper' - -class MyLogSubscriber < ActiveSupport::LogSubscriber - attr_reader :event - - def some_event(event) - @event = event - info event.name - end - - def foo(event) - debug "debug" - info { "info" } - warn "warn" - end - - def bar(event) - info "#{color("cool", :red)}, #{color("isn't it?", :blue, true)}" - end - - def puke(event) - raise "puke" - end -end - -class SyncLogSubscriberTest < ActiveSupport::TestCase - include ActiveSupport::LogSubscriber::TestHelper - - def setup - super - @log_subscriber = MyLogSubscriber.new - end - - def teardown - super - ActiveSupport::LogSubscriber.log_subscribers.clear - end - - def instrument(*args, &block) - ActiveSupport::Notifications.instrument(*args, &block) - end - - def test_proxies_method_to_rails_logger - @log_subscriber.foo(nil) - assert_equal %w(debug), @logger.logged(:debug) - assert_equal %w(info), @logger.logged(:info) - assert_equal %w(warn), @logger.logged(:warn) - end - - def test_set_color_for_messages - ActiveSupport::LogSubscriber.colorize_logging = true - @log_subscriber.bar(nil) - assert_equal "\e[31mcool\e[0m, \e[1m\e[34misn't it?\e[0m", @logger.logged(:info).last - end - - def test_does_not_set_color_if_colorize_logging_is_set_to_false - @log_subscriber.bar(nil) - assert_equal "cool, isn't it?", @logger.logged(:info).last - end - - def test_event_is_sent_to_the_registered_class - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - instrument "some_event.my_log_subscriber" - wait - assert_equal %w(some_event.my_log_subscriber), @logger.logged(:info) - end - - def test_event_is_an_active_support_notifications_event - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - instrument "some_event.my_log_subscriber" - wait - assert_kind_of ActiveSupport::Notifications::Event, @log_subscriber.event - end - - def test_does_not_send_the_event_if_it_doesnt_match_the_class - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - instrument "unknown_event.my_log_subscriber" - wait - # If we get here, it means that NoMethodError was not raised. - end - - def test_does_not_send_the_event_if_logger_is_nil - ActiveSupport::LogSubscriber.logger = nil - @log_subscriber.expects(:some_event).never - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - instrument "some_event.my_log_subscriber" - wait - end - - def test_does_not_fail_with_non_namespaced_events - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - instrument "whatever" - wait - end - - def test_flushes_loggers - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - ActiveSupport::LogSubscriber.flush_all! - assert_equal 1, @logger.flush_count - end - - def test_flushes_the_same_logger_just_once - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - ActiveSupport::LogSubscriber.attach_to :another, @log_subscriber - ActiveSupport::LogSubscriber.flush_all! - wait - assert_equal 1, @logger.flush_count - end - - def test_logging_does_not_die_on_failures - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - instrument "puke.my_log_subscriber" - instrument "some_event.my_log_subscriber" - wait - - assert_equal 1, @logger.logged(:info).size - assert_equal 'some_event.my_log_subscriber', @logger.logged(:info).last - - assert_equal 1, @logger.logged(:error).size - assert_match 'Could not log "puke.my_log_subscriber" event. RuntimeError: puke', @logger.logged(:error).last - end -end diff --git a/app/server/ruby/vendor/activesupport/test/logger_test.rb b/app/server/ruby/vendor/activesupport/test/logger_test.rb deleted file mode 100644 index d2801849ca..0000000000 --- a/app/server/ruby/vendor/activesupport/test/logger_test.rb +++ /dev/null @@ -1,133 +0,0 @@ -require 'abstract_unit' -require 'multibyte_test_helpers' -require 'stringio' -require 'fileutils' -require 'tempfile' - -class LoggerTest < ActiveSupport::TestCase - include MultibyteTestHelpers - - Logger = ActiveSupport::Logger - - def setup - @message = "A debug message" - @integer_message = 12345 - @output = StringIO.new - @logger = Logger.new(@output) - end - - def test_write_binary_data_to_existing_file - t = Tempfile.new ['development', 'log'] - t.binmode - t.write 'hi mom!' - t.close - - f = File.open(t.path, 'w') - f.binmode - - logger = Logger.new f - logger.level = Logger::DEBUG - - str = "\x80" - str.force_encoding("ASCII-8BIT") - - logger.add Logger::DEBUG, str - ensure - logger.close - t.close true - end - - def test_write_binary_data_create_file - fname = File.join Dir.tmpdir, 'lol', 'rofl.log' - FileUtils.mkdir_p File.dirname(fname) - f = File.open(fname, 'w') - f.binmode - - logger = Logger.new f - logger.level = Logger::DEBUG - - str = "\x80" - str.force_encoding("ASCII-8BIT") - - logger.add Logger::DEBUG, str - ensure - logger.close - File.unlink fname - end - - def test_should_log_debugging_message_when_debugging - @logger.level = Logger::DEBUG - @logger.add(Logger::DEBUG, @message) - assert @output.string.include?(@message) - end - - def test_should_not_log_debug_messages_when_log_level_is_info - @logger.level = Logger::INFO - @logger.add(Logger::DEBUG, @message) - assert ! @output.string.include?(@message) - end - - def test_should_add_message_passed_as_block_when_using_add - @logger.level = Logger::INFO - @logger.add(Logger::INFO) {@message} - assert @output.string.include?(@message) - end - - def test_should_add_message_passed_as_block_when_using_shortcut - @logger.level = Logger::INFO - @logger.info {@message} - assert @output.string.include?(@message) - end - - def test_should_convert_message_to_string - @logger.level = Logger::INFO - @logger.info @integer_message - assert @output.string.include?(@integer_message.to_s) - end - - def test_should_convert_message_to_string_when_passed_in_block - @logger.level = Logger::INFO - @logger.info {@integer_message} - assert @output.string.include?(@integer_message.to_s) - end - - def test_should_not_evaluate_block_if_message_wont_be_logged - @logger.level = Logger::INFO - evaluated = false - @logger.add(Logger::DEBUG) {evaluated = true} - assert evaluated == false - end - - def test_should_not_mutate_message - message_copy = @message.dup - @logger.info @message - assert_equal message_copy, @message - end - - def test_should_know_if_its_loglevel_is_below_a_given_level - Logger::Severity.constants.each do |level| - next if level.to_s == 'UNKNOWN' - @logger.level = Logger::Severity.const_get(level) - 1 - assert @logger.send("#{level.downcase}?"), "didn't know if it was #{level.downcase}? or below" - end - end - - def test_buffer_multibyte - @logger.info(UNICODE_STRING) - @logger.info(BYTE_STRING) - assert @output.string.include?(UNICODE_STRING) - byte_string = @output.string.dup - byte_string.force_encoding("ASCII-8BIT") - assert byte_string.include?(BYTE_STRING) - end - - def test_silencing_everything_but_errors - @logger.silence do - @logger.debug "NOT THERE" - @logger.error "THIS IS HERE" - end - - assert !@output.string.include?("NOT THERE") - assert @output.string.include?("THIS IS HERE") - end -end diff --git a/app/server/ruby/vendor/activesupport/test/message_encryptor_test.rb b/app/server/ruby/vendor/activesupport/test/message_encryptor_test.rb deleted file mode 100644 index b6c0a08b05..0000000000 --- a/app/server/ruby/vendor/activesupport/test/message_encryptor_test.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'abstract_unit' - -begin - require 'openssl' - OpenSSL::Digest::SHA1 -rescue LoadError, NameError - $stderr.puts "Skipping MessageEncryptor test: broken OpenSSL install" -else - -require 'active_support/time' -require 'active_support/json' - -class MessageEncryptorTest < ActiveSupport::TestCase - class JSONSerializer - def dump(value) - ActiveSupport::JSON.encode(value) - end - - def load(value) - ActiveSupport::JSON.decode(value) - end - end - - def setup - @secret = SecureRandom.hex(64) - @verifier = ActiveSupport::MessageVerifier.new(@secret, :serializer => ActiveSupport::MessageEncryptor::NullSerializer) - @encryptor = ActiveSupport::MessageEncryptor.new(@secret) - @data = { :some => "data", :now => Time.local(2010) } - end - - def test_encrypting_twice_yields_differing_cipher_text - first_message = @encryptor.encrypt_and_sign(@data).split("--").first - second_message = @encryptor.encrypt_and_sign(@data).split("--").first - assert_not_equal first_message, second_message - end - - def test_messing_with_either_encrypted_values_causes_failure - text, iv = @verifier.verify(@encryptor.encrypt_and_sign(@data)).split("--") - assert_not_decrypted([iv, text] * "--") - assert_not_decrypted([text, munge(iv)] * "--") - assert_not_decrypted([munge(text), iv] * "--") - assert_not_decrypted([munge(text), munge(iv)] * "--") - end - - def test_messing_with_verified_values_causes_failures - text, iv = @encryptor.encrypt_and_sign(@data).split("--") - assert_not_verified([iv, text] * "--") - assert_not_verified([text, munge(iv)] * "--") - assert_not_verified([munge(text), iv] * "--") - assert_not_verified([munge(text), munge(iv)] * "--") - end - - def test_signed_round_tripping - message = @encryptor.encrypt_and_sign(@data) - assert_equal @data, @encryptor.decrypt_and_verify(message) - end - - def test_alternative_serialization_method - prev = ActiveSupport.use_standard_json_time_format - ActiveSupport.use_standard_json_time_format = true - encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new) - message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) }) - exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" } - assert_equal exp, encryptor.decrypt_and_verify(message) - ensure - ActiveSupport.use_standard_json_time_format = prev - end - - def test_message_obeys_strict_encoding - bad_encoding_characters = "\n!@#" - message, iv = @encryptor.encrypt_and_sign("This is a very \n\nhumble string"+bad_encoding_characters) - - assert_not_decrypted("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}") - assert_not_verified("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}") - - assert_not_decrypted([iv, message] * bad_encoding_characters) - assert_not_verified([iv, message] * bad_encoding_characters) - end - - private - - def assert_not_decrypted(value) - assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do - @encryptor.decrypt_and_verify(@verifier.generate(value)) - end - end - - def assert_not_verified(value) - assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do - @encryptor.decrypt_and_verify(value) - end - end - - def munge(base64_string) - bits = ::Base64.strict_decode64(base64_string) - bits.reverse! - ::Base64.strict_encode64(bits) - end -end - -end diff --git a/app/server/ruby/vendor/activesupport/test/message_verifier_test.rb b/app/server/ruby/vendor/activesupport/test/message_verifier_test.rb deleted file mode 100644 index a5748d28ba..0000000000 --- a/app/server/ruby/vendor/activesupport/test/message_verifier_test.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'abstract_unit' - -begin - require 'openssl' - OpenSSL::Digest::SHA1 -rescue LoadError, NameError - $stderr.puts "Skipping MessageVerifier test: broken OpenSSL install" -else - -require 'active_support/time' -require 'active_support/json' - -class MessageVerifierTest < ActiveSupport::TestCase - - class JSONSerializer - def dump(value) - ActiveSupport::JSON.encode(value) - end - - def load(value) - ActiveSupport::JSON.decode(value) - end - end - - def setup - @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!") - @data = { :some => "data", :now => Time.local(2010) } - end - - def test_simple_round_tripping - message = @verifier.generate(@data) - assert_equal @data, @verifier.verify(message) - end - - def test_missing_signature_raises - assert_not_verified(nil) - assert_not_verified("") - end - - def test_tampered_data_raises - data, hash = @verifier.generate(@data).split("--") - assert_not_verified("#{data.reverse}--#{hash}") - assert_not_verified("#{data}--#{hash.reverse}") - assert_not_verified("purejunk") - end - - def test_alternative_serialization_method - prev = ActiveSupport.use_standard_json_time_format - ActiveSupport.use_standard_json_time_format = true - verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new) - message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) }) - exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" } - assert_equal exp, verifier.verify(message) - ensure - ActiveSupport.use_standard_json_time_format = prev - end - - def test_raise_error_when_argument_class_is_not_loaded - # To generate the valid message below: - # - # AutoloadClass = Struct.new(:foo) - # valid_message = @verifier.generate(foo: AutoloadClass.new('foo')) - # - valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914" - exception = assert_raise(ArgumentError, NameError) do - @verifier.verify(valid_message) - end - assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass", - "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message - end - - def assert_not_verified(message) - assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do - @verifier.verify(message) - end - end -end - -end diff --git a/app/server/ruby/vendor/activesupport/test/multibyte_chars_test.rb b/app/server/ruby/vendor/activesupport/test/multibyte_chars_test.rb deleted file mode 100644 index 659fceb852..0000000000 --- a/app/server/ruby/vendor/activesupport/test/multibyte_chars_test.rb +++ /dev/null @@ -1,713 +0,0 @@ -# encoding: utf-8 -require 'abstract_unit' -require 'multibyte_test_helpers' -require 'active_support/core_ext/string/multibyte' - -class String - def __method_for_multibyte_testing_with_integer_result; 1; end - def __method_for_multibyte_testing; 'result'; end - def __method_for_multibyte_testing!; 'result'; end - def __method_for_multibyte_testing_that_returns_nil!; end -end - -class MultibyteCharsTest < ActiveSupport::TestCase - include MultibyteTestHelpers - - def setup - @proxy_class = ActiveSupport::Multibyte::Chars - @chars = @proxy_class.new UNICODE_STRING - end - - def test_wraps_the_original_string - assert_equal UNICODE_STRING, @chars.to_s - assert_equal UNICODE_STRING, @chars.wrapped_string - end - - def test_should_allow_method_calls_to_string - assert_nothing_raised do - @chars.__method_for_multibyte_testing - end - assert_raise NoMethodError do - @chars.__unknown_method - end - end - - def test_forwarded_method_calls_should_return_new_chars_instance - assert_kind_of @proxy_class, @chars.__method_for_multibyte_testing - assert_not_equal @chars.object_id, @chars.__method_for_multibyte_testing.object_id - end - - def test_forwarded_bang_method_calls_should_return_the_original_chars_instance_when_result_is_not_nil - assert_kind_of @proxy_class, @chars.__method_for_multibyte_testing! - assert_equal @chars.object_id, @chars.__method_for_multibyte_testing!.object_id - end - - def test_forwarded_bang_method_calls_should_return_nil_when_result_is_nil - assert_nil @chars.__method_for_multibyte_testing_that_returns_nil! - end - - def test_methods_are_forwarded_to_wrapped_string_for_byte_strings - original_encoding = BYTE_STRING.encoding - assert_equal BYTE_STRING.length, BYTE_STRING.mb_chars.length - ensure - BYTE_STRING.force_encoding(original_encoding) - end - - def test_forwarded_method_with_non_string_result_should_be_returned_vertabim - assert_equal ''.__method_for_multibyte_testing_with_integer_result, @chars.__method_for_multibyte_testing_with_integer_result - end - - def test_should_concatenate - mb_a = 'a'.mb_chars - mb_b = 'b'.mb_chars - assert_equal 'ab', mb_a + 'b' - assert_equal 'ab', 'a' + mb_b - assert_equal 'ab', mb_a + mb_b - - assert_equal 'ab', mb_a << 'b' - assert_equal 'ab', 'a' << mb_b - assert_equal 'abb', mb_a << mb_b - end - - def test_consumes_utf8_strings - assert @proxy_class.consumes?(UNICODE_STRING) - assert @proxy_class.consumes?(ASCII_STRING) - assert !@proxy_class.consumes?(BYTE_STRING) - end - - def test_concatenation_should_return_a_proxy_class_instance - assert_equal ActiveSupport::Multibyte.proxy_class, ('a'.mb_chars + 'b').class - assert_equal ActiveSupport::Multibyte.proxy_class, ('a'.mb_chars << 'b').class - end - - def test_ascii_strings_are_treated_at_utf8_strings - assert_equal ActiveSupport::Multibyte.proxy_class, ASCII_STRING.mb_chars.class - end - - def test_concatenate_should_return_proxy_instance - assert(('a'.mb_chars + 'b').kind_of?(@proxy_class)) - assert(('a'.mb_chars + 'b'.mb_chars).kind_of?(@proxy_class)) - assert(('a'.mb_chars << 'b').kind_of?(@proxy_class)) - assert(('a'.mb_chars << 'b'.mb_chars).kind_of?(@proxy_class)) - end - - def test_should_return_string_as_json - assert_equal UNICODE_STRING, @chars.as_json - end -end - -class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase - include MultibyteTestHelpers - - def setup - @chars = UNICODE_STRING.dup.mb_chars - # Ruby 1.9 only supports basic whitespace - @whitespace = "\n\t " - @byte_order_mark = [65279].pack('U') - end - - def test_split_should_return_an_array_of_chars_instances - @chars.split(//).each do |character| - assert_kind_of ActiveSupport::Multibyte.proxy_class, character - end - end - - %w{capitalize downcase lstrip reverse rstrip swapcase upcase}.each do |method| - class_eval(<<-EOTESTS, __FILE__, __LINE__ + 1) - def test_#{method}_bang_should_return_self_when_modifying_wrapped_string - chars = ' él piDió Un bUen café ' - assert_equal chars.object_id, chars.send("#{method}!").object_id - end - - def test_#{method}_bang_should_change_wrapped_string - original = ' él piDió Un bUen café ' - proxy = chars(original.dup) - proxy.send("#{method}!") - assert_not_equal original, proxy.to_s - end - EOTESTS - end - - def test_tidy_bytes_bang_should_return_self - assert_equal @chars.object_id, @chars.tidy_bytes!.object_id - end - - def test_tidy_bytes_bang_should_change_wrapped_string - original = " Un bUen café \x92" - proxy = chars(original.dup) - proxy.tidy_bytes! - assert_not_equal original, proxy.to_s - end - - def test_unicode_string_should_have_utf8_encoding - assert_equal Encoding::UTF_8, UNICODE_STRING.encoding - end - - def test_identity - assert_equal @chars, @chars - assert @chars.eql?(@chars) - assert !@chars.eql?(UNICODE_STRING) - end - - def test_string_methods_are_chainable - assert chars('').insert(0, '').kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').rjust(1).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').ljust(1).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').center(1).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').rstrip.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').lstrip.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').strip.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').reverse.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars(' ').slice(0).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').limit(0).kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').upcase.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').downcase.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').capitalize.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').normalize.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').decompose.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').compose.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').tidy_bytes.kind_of?(ActiveSupport::Multibyte.proxy_class) - assert chars('').swapcase.kind_of?(ActiveSupport::Multibyte.proxy_class) - end - - def test_should_be_equal_to_the_wrapped_string - assert_equal UNICODE_STRING, @chars - assert_equal @chars, UNICODE_STRING - end - - def test_should_not_be_equal_to_an_other_string - assert_not_equal @chars, 'other' - assert_not_equal 'other', @chars - end - - def test_sortability - words = %w(builder armor zebra).sort_by { |s| s.mb_chars } - assert_equal %w(armor builder zebra), words - end - - def test_should_return_character_offset_for_regexp_matches - assert_nil(@chars =~ /wrong/u) - assert_equal 0, (@chars =~ /こ/u) - assert_equal 0, (@chars =~ /こに/u) - assert_equal 1, (@chars =~ /に/u) - assert_equal 2, (@chars =~ /ち/u) - assert_equal 3, (@chars =~ /わ/u) - end - - def test_should_use_character_offsets_for_insert_offsets - assert_equal '', ''.mb_chars.insert(0, '') - assert_equal 'こわにちわ', @chars.insert(1, 'わ') - assert_equal 'こわわわにちわ', @chars.insert(2, 'わわ') - assert_equal 'わこわわわにちわ', @chars.insert(0, 'わ') - assert_equal 'わこわわわにちわ', @chars.wrapped_string - end - - def test_insert_should_be_destructive - @chars.insert(1, 'わ') - assert_equal 'こわにちわ', @chars - end - - def test_insert_throws_index_error - assert_raise(IndexError) { @chars.insert(-12, 'わ')} - assert_raise(IndexError) { @chars.insert(12, 'わ') } - end - - def test_should_know_if_one_includes_the_other - assert @chars.include?('') - assert @chars.include?('ち') - assert @chars.include?('わ') - assert !@chars.include?('こちわ') - assert !@chars.include?('a') - end - - def test_include_raises_when_nil_is_passed - assert_raises TypeError, NoMethodError, "Expected chars.include?(nil) to raise TypeError or NoMethodError" do - @chars.include?(nil) - end - end - - def test_index_should_return_character_offset - assert_nil @chars.index('u') - assert_equal 0, @chars.index('こに') - assert_equal 2, @chars.index('ち') - assert_equal 2, @chars.index('ち', -2) - assert_equal nil, @chars.index('ち', -1) - assert_equal 3, @chars.index('わ') - assert_equal 5, 'ééxééx'.mb_chars.index('x', 4) - end - - def test_rindex_should_return_character_offset - assert_nil @chars.rindex('u') - assert_equal 1, @chars.rindex('に') - assert_equal 2, @chars.rindex('ち', -2) - assert_nil @chars.rindex('ち', -3) - assert_equal 6, 'Café périferôl'.mb_chars.rindex('é') - assert_equal 13, 'Café périferôl'.mb_chars.rindex(/\w/u) - end - - def test_indexed_insert_should_take_character_offsets - @chars[2] = 'a' - assert_equal 'こにaわ', @chars - @chars[2] = 'ηη' - assert_equal 'こにηηわ', @chars - @chars[3, 2] = 'λλλ' - assert_equal 'こにηλλλ', @chars - @chars[1, 0] = "λ" - assert_equal 'こλにηλλλ', @chars - @chars[4..6] = "ηη" - assert_equal 'こλにηηη', @chars - @chars[/ηη/] = "λλλ" - assert_equal 'こλにλλλη', @chars - @chars[/(λλ)(.)/, 2] = "α" - assert_equal 'こλにλλαη', @chars - @chars["α"] = "¢" - assert_equal 'こλにλλ¢η', @chars - @chars["λλ"] = "ααα" - assert_equal 'こλにααα¢η', @chars - end - - def test_indexed_insert_should_raise_on_index_overflow - before = @chars.to_s - assert_raise(IndexError) { @chars[10] = 'a' } - assert_raise(IndexError) { @chars[10, 4] = 'a' } - assert_raise(IndexError) { @chars[/ii/] = 'a' } - assert_raise(IndexError) { @chars[/()/, 10] = 'a' } - assert_equal before, @chars - end - - def test_indexed_insert_should_raise_on_range_overflow - before = @chars.to_s - assert_raise(RangeError) { @chars[10..12] = 'a' } - assert_equal before, @chars - end - - def test_rjust_should_raise_argument_errors_on_bad_arguments - assert_raise(ArgumentError) { @chars.rjust(10, '') } - assert_raise(ArgumentError) { @chars.rjust } - end - - def test_rjust_should_count_characters_instead_of_bytes - assert_equal UNICODE_STRING, @chars.rjust(-3) - assert_equal UNICODE_STRING, @chars.rjust(0) - assert_equal UNICODE_STRING, @chars.rjust(4) - assert_equal " #{UNICODE_STRING}", @chars.rjust(5) - assert_equal " #{UNICODE_STRING}", @chars.rjust(7) - assert_equal "---#{UNICODE_STRING}", @chars.rjust(7, '-') - assert_equal "ααα#{UNICODE_STRING}", @chars.rjust(7, 'α') - assert_equal "aba#{UNICODE_STRING}", @chars.rjust(7, 'ab') - assert_equal "αηα#{UNICODE_STRING}", @chars.rjust(7, 'αη') - assert_equal "αηαη#{UNICODE_STRING}", @chars.rjust(8, 'αη') - end - - def test_ljust_should_raise_argument_errors_on_bad_arguments - assert_raise(ArgumentError) { @chars.ljust(10, '') } - assert_raise(ArgumentError) { @chars.ljust } - end - - def test_ljust_should_count_characters_instead_of_bytes - assert_equal UNICODE_STRING, @chars.ljust(-3) - assert_equal UNICODE_STRING, @chars.ljust(0) - assert_equal UNICODE_STRING, @chars.ljust(4) - assert_equal "#{UNICODE_STRING} ", @chars.ljust(5) - assert_equal "#{UNICODE_STRING} ", @chars.ljust(7) - assert_equal "#{UNICODE_STRING}---", @chars.ljust(7, '-') - assert_equal "#{UNICODE_STRING}ααα", @chars.ljust(7, 'α') - assert_equal "#{UNICODE_STRING}aba", @chars.ljust(7, 'ab') - assert_equal "#{UNICODE_STRING}αηα", @chars.ljust(7, 'αη') - assert_equal "#{UNICODE_STRING}αηαη", @chars.ljust(8, 'αη') - end - - def test_center_should_raise_argument_errors_on_bad_arguments - assert_raise(ArgumentError) { @chars.center(10, '') } - assert_raise(ArgumentError) { @chars.center } - end - - def test_center_should_count_characters_instead_of_bytes - assert_equal UNICODE_STRING, @chars.center(-3) - assert_equal UNICODE_STRING, @chars.center(0) - assert_equal UNICODE_STRING, @chars.center(4) - assert_equal "#{UNICODE_STRING} ", @chars.center(5) - assert_equal " #{UNICODE_STRING} ", @chars.center(6) - assert_equal " #{UNICODE_STRING} ", @chars.center(7) - assert_equal "--#{UNICODE_STRING}--", @chars.center(8, '-') - assert_equal "--#{UNICODE_STRING}---", @chars.center(9, '-') - assert_equal "αα#{UNICODE_STRING}αα", @chars.center(8, 'α') - assert_equal "αα#{UNICODE_STRING}ααα", @chars.center(9, 'α') - assert_equal "a#{UNICODE_STRING}ab", @chars.center(7, 'ab') - assert_equal "ab#{UNICODE_STRING}ab", @chars.center(8, 'ab') - assert_equal "abab#{UNICODE_STRING}abab", @chars.center(12, 'ab') - assert_equal "α#{UNICODE_STRING}αη", @chars.center(7, 'αη') - assert_equal "αη#{UNICODE_STRING}αη", @chars.center(8, 'αη') - end - - def test_lstrip_strips_whitespace_from_the_left_of_the_string - assert_equal UNICODE_STRING, UNICODE_STRING.mb_chars.lstrip - assert_equal UNICODE_STRING, (@whitespace + UNICODE_STRING).mb_chars.lstrip - assert_equal UNICODE_STRING + @whitespace, (@whitespace + UNICODE_STRING + @whitespace).mb_chars.lstrip - end - - def test_rstrip_strips_whitespace_from_the_right_of_the_string - assert_equal UNICODE_STRING, UNICODE_STRING.mb_chars.rstrip - assert_equal UNICODE_STRING, (UNICODE_STRING + @whitespace).mb_chars.rstrip - assert_equal @whitespace + UNICODE_STRING, (@whitespace + UNICODE_STRING + @whitespace).mb_chars.rstrip - end - - def test_strip_strips_whitespace - assert_equal UNICODE_STRING, UNICODE_STRING.mb_chars.strip - assert_equal UNICODE_STRING, (@whitespace + UNICODE_STRING).mb_chars.strip - assert_equal UNICODE_STRING, (UNICODE_STRING + @whitespace).mb_chars.strip - assert_equal UNICODE_STRING, (@whitespace + UNICODE_STRING + @whitespace).mb_chars.strip - end - - def test_stripping_whitespace_leaves_whitespace_within_the_string_intact - string_with_whitespace = UNICODE_STRING + @whitespace + UNICODE_STRING - assert_equal string_with_whitespace, string_with_whitespace.mb_chars.strip - assert_equal string_with_whitespace, string_with_whitespace.mb_chars.lstrip - assert_equal string_with_whitespace, string_with_whitespace.mb_chars.rstrip - end - - def test_size_returns_characters_instead_of_bytes - assert_equal 0, ''.mb_chars.size - assert_equal 4, @chars.size - assert_equal 4, @chars.length - assert_equal 5, ASCII_STRING.mb_chars.size - end - - def test_reverse_reverses_characters - assert_equal '', ''.mb_chars.reverse - assert_equal 'わちにこ', @chars.reverse - end - - def test_reverse_should_work_with_normalized_strings - str = 'bös' - reversed_str = 'söb' - assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse - assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse - assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse - assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).reverse - assert_equal chars(reversed_str).decompose, chars(str).decompose.reverse - assert_equal chars(reversed_str).compose, chars(str).compose.reverse - end - - def test_slice_should_take_character_offsets - assert_equal nil, ''.mb_chars.slice(0) - assert_equal 'こ', @chars.slice(0) - assert_equal 'わ', @chars.slice(3) - assert_equal nil, ''.mb_chars.slice(-1..1) - assert_equal nil, ''.mb_chars.slice(-1, 1) - assert_equal '', ''.mb_chars.slice(0..10) - assert_equal 'にちわ', @chars.slice(1..3) - assert_equal 'にちわ', @chars.slice(1, 3) - assert_equal 'こ', @chars.slice(0, 1) - assert_equal 'ちわ', @chars.slice(2..10) - assert_equal '', @chars.slice(4..10) - assert_equal 'に', @chars.slice(/に/u) - assert_equal 'にち', @chars.slice(/に./u) - assert_equal nil, @chars.slice(/unknown/u) - assert_equal 'にち', @chars.slice(/(にち)/u, 1) - assert_equal nil, @chars.slice(/(にち)/u, 2) - assert_equal nil, @chars.slice(7..6) - end - - def test_slice_bang_returns_sliced_out_substring - assert_equal 'にち', @chars.slice!(1..2) - end - - def test_slice_bang_removes_the_slice_from_the_receiver - chars = 'úüù'.mb_chars - chars.slice!(0,2) - assert_equal 'ù', chars - end - - def test_slice_should_throw_exceptions_on_invalid_arguments - assert_raise(TypeError) { @chars.slice(2..3, 1) } - assert_raise(TypeError) { @chars.slice(1, 2..3) } - assert_raise(ArgumentError) { @chars.slice(1, 1, 1) } - end - - def test_ord_should_return_unicode_value_for_first_character - assert_equal 12371, @chars.ord - end - - def test_upcase_should_upcase_ascii_characters - assert_equal '', ''.mb_chars.upcase - assert_equal 'ABC', 'aBc'.mb_chars.upcase - end - - def test_downcase_should_downcase_ascii_characters - assert_equal '', ''.mb_chars.downcase - assert_equal 'abc', 'aBc'.mb_chars.downcase - end - - def test_swapcase_should_swap_ascii_characters - assert_equal '', ''.mb_chars.swapcase - assert_equal 'AbC', 'aBc'.mb_chars.swapcase - end - - def test_capitalize_should_work_on_ascii_characters - assert_equal '', ''.mb_chars.capitalize - assert_equal 'Abc', 'abc'.mb_chars.capitalize - end - - def test_titleize_should_work_on_ascii_characters - assert_equal '', ''.mb_chars.titleize - assert_equal 'Abc Abc', 'abc abc'.mb_chars.titleize - end - - def test_respond_to_knows_which_methods_the_proxy_responds_to - assert ''.mb_chars.respond_to?(:slice) # Defined on Chars - assert ''.mb_chars.respond_to?(:capitalize!) # Defined on Chars - assert ''.mb_chars.respond_to?(:gsub) # Defined on String - assert !''.mb_chars.respond_to?(:undefined_method) # Not defined - end - - def test_method_works_for_proxyed_methods - assert_equal 'll', 'hello'.mb_chars.method(:slice).call(2..3) # Defined on Chars - chars = 'hello'.mb_chars - assert_equal 'Hello', chars.method(:capitalize!).call # Defined on Chars - assert_equal 'Hello', chars - assert_equal 'jello', 'hello'.mb_chars.method(:gsub).call(/h/, 'j') # Defined on String - assert_raise(NameError){ ''.mb_chars.method(:undefined_method) } # Not defined - end - - def test_acts_like_string - assert 'Bambi'.mb_chars.acts_like_string? - end -end - -# The default Multibyte Chars proxy has more features than the normal string implementation. Tests -# for the implementation of these features should run on all Ruby versions and shouldn't be tested -# through the proxy methods. -class MultibyteCharsExtrasTest < ActiveSupport::TestCase - include MultibyteTestHelpers - - def test_upcase_should_be_unicode_aware - assert_equal "АБВГД\0F", chars("аБвгд\0f").upcase - assert_equal 'こにちわ', chars('こにちわ').upcase - end - - def test_downcase_should_be_unicode_aware - assert_equal "абвгд\0f", chars("аБвгд\0F").downcase - assert_equal 'こにちわ', chars('こにちわ').downcase - end - - def test_swapcase_should_be_unicode_aware - assert_equal "аaéÜ\0f", chars("АAÉü\0F").swapcase - assert_equal 'こにちわ', chars('こにちわ').swapcase - end - - def test_capitalize_should_be_unicode_aware - { 'аБвг аБвг' => 'Абвг абвг', - 'аБвг АБВГ' => 'Абвг абвг', - 'АБВГ АБВГ' => 'Абвг абвг', - '' => '' }.each do |f,t| - assert_equal t, chars(f).capitalize - end - end - - def test_titleize_should_be_unicode_aware - assert_equal "Él Que Se Enteró", chars("ÉL QUE SE ENTERÓ").titleize - assert_equal "Абвг Абвг", chars("аБвг аБвг").titleize - end - - def test_titleize_should_not_affect_characters_that_do_not_case_fold - assert_equal "日本語", chars("日本語").titleize - end - - def test_limit_should_not_break_on_blank_strings - example = chars('') - assert_equal example, example.limit(0) - assert_equal example, example.limit(1) - end - - def test_limit_should_work_on_a_multibyte_string - example = chars(UNICODE_STRING) - bytesize = UNICODE_STRING.bytesize - - assert_equal UNICODE_STRING, example.limit(bytesize) - assert_equal '', example.limit(0) - assert_equal '', example.limit(1) - assert_equal 'こ', example.limit(3) - assert_equal 'こに', example.limit(6) - assert_equal 'こに', example.limit(8) - assert_equal 'こにち', example.limit(9) - assert_equal 'こにちわ', example.limit(50) - end - - def test_limit_should_work_on_an_ascii_string - ascii = chars(ASCII_STRING) - assert_equal ASCII_STRING, ascii.limit(ASCII_STRING.length) - assert_equal '', ascii.limit(0) - assert_equal 'o', ascii.limit(1) - assert_equal 'oh', ascii.limit(2) - assert_equal 'ohay', ascii.limit(4) - assert_equal 'ohayo', ascii.limit(50) - end - - def test_limit_should_keep_under_the_specified_byte_limit - example = chars(UNICODE_STRING) - (1..UNICODE_STRING.length).each do |limit| - assert example.limit(limit).to_s.length <= limit - end - end - - def test_composition_exclusion_is_set_up_properly - # Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly - qa = [0x915, 0x93c].pack('U*') - assert_equal qa, chars(qa).normalize(:c) - end - - # Test for the Public Review Issue #29, bad explanation of composition might lead to a - # bad implementation: http://www.unicode.org/review/pr-29.html - def test_normalization_C_pri_29 - [ - [0x0B47, 0x0300, 0x0B3E], - [0x1100, 0x0300, 0x1161] - ].map { |c| c.pack('U*') }.each do |c| - assert_equal_codepoints c, chars(c).normalize(:c) - end - end - - def test_normalization_shouldnt_strip_null_bytes - null_byte_str = "Test\0test" - - assert_equal null_byte_str, chars(null_byte_str).normalize(:kc) - assert_equal null_byte_str, chars(null_byte_str).normalize(:c) - assert_equal null_byte_str, chars(null_byte_str).normalize(:d) - assert_equal null_byte_str, chars(null_byte_str).normalize(:kd) - assert_equal null_byte_str, chars(null_byte_str).decompose - assert_equal null_byte_str, chars(null_byte_str).compose - end - - def test_simple_normalization - comp_str = [ - 44, # LATIN CAPITAL LETTER D - 307, # COMBINING DOT ABOVE - 328, # COMBINING OGONEK - 323 # COMBINING DOT BELOW - ].pack("U*") - - assert_equal_codepoints '', chars('').normalize - assert_equal_codepoints [44,105,106,328,323].pack("U*"), chars(comp_str).normalize(:kc).to_s - assert_equal_codepoints [44,307,328,323].pack("U*"), chars(comp_str).normalize(:c).to_s - assert_equal_codepoints [44,307,110,780,78,769].pack("U*"), chars(comp_str).normalize(:d).to_s - assert_equal_codepoints [44,105,106,110,780,78,769].pack("U*"), chars(comp_str).normalize(:kd).to_s - end - - def test_should_compute_grapheme_length - [ - ['', 0], - ['abc', 3], - ['こにちわ', 4], - [[0x0924, 0x094D, 0x0930].pack('U*'), 2], - [%w(cr lf), 1], - [%w(l l), 1], - [%w(l v), 1], - [%w(l lv), 1], - [%w(l lvt), 1], - [%w(lv v), 1], - [%w(lv t), 1], - [%w(v v), 1], - [%w(v t), 1], - [%w(lvt t), 1], - [%w(t t), 1], - [%w(n extend), 1], - [%w(n n), 2], - [%w(n cr lf n), 3], - [%w(n l v t), 2] - ].each do |input, expected_length| - if input.kind_of?(Array) - str = string_from_classes(input) - else - str = input - end - assert_equal expected_length, chars(str).grapheme_length - end - end - - def test_tidy_bytes_should_tidy_bytes - - single_byte_cases = { - "\x21" => "!", # Valid ASCII byte, low - "\x41" => "A", # Valid ASCII byte, mid - "\x7E" => "~", # Valid ASCII byte, high - "\x80" => "€", # Continuation byte, low (cp125) - "\x94" => "”", # Continuation byte, mid (cp125) - "\x9F" => "Ÿ", # Continuation byte, high (cp125) - "\xC0" => "À", # Overlong encoding, start of 2-byte sequence, but codepoint < 128 - "\xC1" => "Á", # Overlong encoding, start of 2-byte sequence, but codepoint < 128 - "\xC2" => "Â", # Start of 2-byte sequence, low - "\xC8" => "È", # Start of 2-byte sequence, mid - "\xDF" => "ß", # Start of 2-byte sequence, high - "\xE0" => "à", # Start of 3-byte sequence, low - "\xE8" => "è", # Start of 3-byte sequence, mid - "\xEF" => "ï", # Start of 3-byte sequence, high - "\xF0" => "ð", # Start of 4-byte sequence - "\xF1" => "ñ", # Unused byte - "\xFF" => "ÿ", # Restricted byte - "\x00" => "\x00" # null char - } - - single_byte_cases.each do |bad, good| - assert_equal good, chars(bad).tidy_bytes.to_s - assert_equal "#{good}#{good}", chars("#{bad}#{bad}").tidy_bytes - assert_equal "#{good}#{good}#{good}", chars("#{bad}#{bad}#{bad}").tidy_bytes - assert_equal "#{good}a", chars("#{bad}a").tidy_bytes - assert_equal "#{good}á", chars("#{bad}á").tidy_bytes - assert_equal "a#{good}a", chars("a#{bad}a").tidy_bytes - assert_equal "á#{good}á", chars("á#{bad}á").tidy_bytes - assert_equal "a#{good}", chars("a#{bad}").tidy_bytes - assert_equal "á#{good}", chars("á#{bad}").tidy_bytes - end - - byte_string = "\270\236\010\210\245" - tidy_string = [0xb8, 0x17e, 0x8, 0x2c6, 0xa5].pack('U*') - assert_equal_codepoints tidy_string, chars(byte_string).tidy_bytes - assert_nothing_raised { chars(byte_string).tidy_bytes.to_s.unpack('U*') } - - # UTF-8 leading byte followed by too few continuation bytes - assert_equal_codepoints "\xc3\xb0\xc2\xa5\xc2\xa4\x21", chars("\xf0\xa5\xa4\x21").tidy_bytes - end - - def test_tidy_bytes_should_forcibly_tidy_bytes_if_specified - byte_string = "\xF0\xA5\xA4\xA4" # valid as both CP-1252 and UTF-8, but with different interpretations. - assert_not_equal "𥤤", chars(byte_string).tidy_bytes - # Forcible conversion to UTF-8 - assert_equal "𥤤", chars(byte_string).tidy_bytes(true) - end - - def test_class_is_not_forwarded - assert_equal BYTE_STRING.dup.mb_chars.class, ActiveSupport::Multibyte::Chars - end - - private - - def string_from_classes(classes) - # Characters from the character classes as described in UAX #29 - character_from_class = { - :l => 0x1100, :v => 0x1160, :t => 0x11A8, :lv => 0xAC00, :lvt => 0xAC01, :cr => 0x000D, :lf => 0x000A, - :extend => 0x094D, :n => 0x64 - } - classes.collect do |k| - character_from_class[k.intern] - end.pack('U*') - end -end - -class MultibyteInternalsTest < ActiveSupport::TestCase - include MultibyteTestHelpers - - test "Chars translates a character offset to a byte offset" do - example = chars("Puisque c'était son erreur, il m'a aidé") - [ - [0, 0], - [3, 3], - [12, 11], - [14, 13], - [41, 39] - ].each do |byte_offset, character_offset| - assert_equal character_offset, example.send(:translate_offset, byte_offset), - "Expected byte offset #{byte_offset} to translate to #{character_offset}" - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/multibyte_conformance.rb b/app/server/ruby/vendor/activesupport/test/multibyte_conformance.rb deleted file mode 100644 index 2baf724da4..0000000000 --- a/app/server/ruby/vendor/activesupport/test/multibyte_conformance.rb +++ /dev/null @@ -1,129 +0,0 @@ -# encoding: utf-8 - -require 'abstract_unit' -require 'multibyte_test_helpers' - -require 'fileutils' -require 'open-uri' -require 'tmpdir' - -class Downloader - def self.download(from, to) - unless File.exist?(to) - $stderr.puts "Downloading #{from} to #{to}" - unless File.exist?(File.dirname(to)) - system "mkdir -p #{File.dirname(to)}" - end - open(from) do |source| - File.open(to, 'w') do |target| - source.each_line do |l| - target.write l - end - end - end - end - end -end - -class MultibyteConformanceTest < ActiveSupport::TestCase - include MultibyteTestHelpers - - UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" - UNIDATA_FILE = '/NormalizationTest.txt' - CACHE_DIR = File.join(Dir.tmpdir, 'cache') - - def setup - FileUtils.mkdir_p(CACHE_DIR) - Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE) - @proxy = ActiveSupport::Multibyte::Chars - end - - def test_normalizations_C - each_line_of_norm_tests do |*cols| - col1, col2, col3, col4, col5, comment = *cols - - # CONFORMANCE: - # 1. The following invariants must be true for all conformant implementations - # - # NFC - # c2 == NFC(c1) == NFC(c2) == NFC(c3) - assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}" - assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}" - assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}" - # - # c4 == NFC(c4) == NFC(c5) - assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}" - end - end - - def test_normalizations_D - each_line_of_norm_tests do |*cols| - col1, col2, col3, col4, col5, comment = *cols - # - # NFD - # c3 == NFD(c1) == NFD(c2) == NFD(c3) - assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}" - assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}" - assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}" - # c5 == NFD(c4) == NFD(c5) - assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}" - end - end - - def test_normalizations_KC - each_line_of_norm_tests do | *cols | - col1, col2, col3, col4, col5, comment = *cols - # - # NFKC - # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) - assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}" - assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}" - end - end - - def test_normalizations_KD - each_line_of_norm_tests do | *cols | - col1, col2, col3, col4, col5, comment = *cols - # - # NFKD - # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) - assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}" - assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}" - end - end - - protected - def each_line_of_norm_tests(&block) - lines = 0 - max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile - File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f | - until f.eof? || (max_test_lines > 38 and lines > max_test_lines) - lines += 1 - line = f.gets.chomp! - next if (line.empty? || line =~ /^\#/) - - cols, comment = line.split("#") - cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? } - next unless cols.length == 5 - - # codepoints are in hex in the test suite, pack wants them as integers - cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") } - cols << comment - - yield(*cols) - end - end - end - - def inspect_codepoints(str) - str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ') - end -end \ No newline at end of file diff --git a/app/server/ruby/vendor/activesupport/test/multibyte_test_helpers.rb b/app/server/ruby/vendor/activesupport/test/multibyte_test_helpers.rb deleted file mode 100644 index fdbe2f4350..0000000000 --- a/app/server/ruby/vendor/activesupport/test/multibyte_test_helpers.rb +++ /dev/null @@ -1,19 +0,0 @@ -# encoding: utf-8 - -module MultibyteTestHelpers - UNICODE_STRING = 'こにちわ' - ASCII_STRING = 'ohayo' - BYTE_STRING = "\270\236\010\210\245".force_encoding("ASCII-8BIT") - - def chars(str) - ActiveSupport::Multibyte::Chars.new(str) - end - - def inspect_codepoints(str) - str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ') - end - - def assert_equal_codepoints(expected, actual, message=nil) - assert_equal(inspect_codepoints(expected), inspect_codepoints(actual), message) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/multibyte_unicode_database_test.rb b/app/server/ruby/vendor/activesupport/test/multibyte_unicode_database_test.rb deleted file mode 100644 index bec65daf50..0000000000 --- a/app/server/ruby/vendor/activesupport/test/multibyte_unicode_database_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -# encoding: utf-8 -require 'abstract_unit' - - -class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase - - include ActiveSupport::Multibyte::Unicode - - def setup - @ucd = UnicodeDatabase.new - end - - UnicodeDatabase::ATTRIBUTES.each do |attribute| - define_method "test_lazy_loading_on_attribute_access_of_#{attribute}" do - @ucd.expects(:load) - @ucd.send(attribute) - end - end - - def test_load - @ucd.load - UnicodeDatabase::ATTRIBUTES.each do |attribute| - assert @ucd.send(attribute).length > 1 - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/notifications/evented_notification_test.rb b/app/server/ruby/vendor/activesupport/test/notifications/evented_notification_test.rb deleted file mode 100644 index f690ad43fc..0000000000 --- a/app/server/ruby/vendor/activesupport/test/notifications/evented_notification_test.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'abstract_unit' - -module ActiveSupport - module Notifications - class EventedTest < ActiveSupport::TestCase - class Listener - attr_reader :events - - def initialize - @events = [] - end - - def start(name, id, payload) - @events << [:start, name, id, payload] - end - - def finish(name, id, payload) - @events << [:finish, name, id, payload] - end - end - - class ListenerWithTimedSupport < Listener - def call(name, start, finish, id, payload) - @events << [:call, name, start, finish, id, payload] - end - end - - def test_evented_listener - notifier = Fanout.new - listener = Listener.new - notifier.subscribe 'hi', listener - notifier.start 'hi', 1, {} - notifier.start 'hi', 2, {} - notifier.finish 'hi', 2, {} - notifier.finish 'hi', 1, {} - - assert_equal 4, listener.events.length - assert_equal [ - [:start, 'hi', 1, {}], - [:start, 'hi', 2, {}], - [:finish, 'hi', 2, {}], - [:finish, 'hi', 1, {}], - ], listener.events - end - - def test_evented_listener_no_events - notifier = Fanout.new - listener = Listener.new - notifier.subscribe 'hi', listener - notifier.start 'world', 1, {} - assert_equal 0, listener.events.length - end - - def test_listen_to_everything - notifier = Fanout.new - listener = Listener.new - notifier.subscribe nil, listener - notifier.start 'hello', 1, {} - notifier.start 'world', 1, {} - notifier.finish 'world', 1, {} - notifier.finish 'hello', 1, {} - - assert_equal 4, listener.events.length - assert_equal [ - [:start, 'hello', 1, {}], - [:start, 'world', 1, {}], - [:finish, 'world', 1, {}], - [:finish, 'hello', 1, {}], - ], listener.events - end - - def test_evented_listener_priority - notifier = Fanout.new - listener = ListenerWithTimedSupport.new - notifier.subscribe 'hi', listener - - notifier.start 'hi', 1, {} - notifier.finish 'hi', 1, {} - - assert_equal [ - [:start, 'hi', 1, {}], - [:finish, 'hi', 1, {}] - ], listener.events - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/notifications/instrumenter_test.rb b/app/server/ruby/vendor/activesupport/test/notifications/instrumenter_test.rb deleted file mode 100644 index f46e96f636..0000000000 --- a/app/server/ruby/vendor/activesupport/test/notifications/instrumenter_test.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'abstract_unit' -require 'active_support/notifications/instrumenter' - -module ActiveSupport - module Notifications - class InstrumenterTest < ActiveSupport::TestCase - class TestNotifier - attr_reader :starts, :finishes - - def initialize - @starts = [] - @finishes = [] - end - - def start(*args); @starts << args; end - def finish(*args); @finishes << args; end - end - - attr_reader :instrumenter, :notifier, :payload - - def setup - super - @notifier = TestNotifier.new - @instrumenter = Instrumenter.new @notifier - @payload = { :foo => Object.new } - end - - def test_instrument - called = false - instrumenter.instrument("foo", payload) { - called = true - } - - assert called - end - - def test_instrument_yields_the_payload_for_further_modification - assert_equal 2, instrumenter.instrument("awesome") { |p| p[:result] = 1 + 1 } - assert_equal 1, notifier.finishes.size - name, _, payload = notifier.finishes.first - assert_equal "awesome", name - assert_equal Hash[:result => 2], payload - end - - def test_start - instrumenter.start("foo", payload) - assert_equal [["foo", instrumenter.id, payload]], notifier.starts - assert_predicate notifier.finishes, :empty? - end - - def test_finish - instrumenter.finish("foo", payload) - assert_equal [["foo", instrumenter.id, payload]], notifier.finishes - assert_predicate notifier.starts, :empty? - end - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/notifications_test.rb b/app/server/ruby/vendor/activesupport/test/notifications_test.rb deleted file mode 100644 index f729f0a95b..0000000000 --- a/app/server/ruby/vendor/activesupport/test/notifications_test.rb +++ /dev/null @@ -1,266 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/module/delegation' - -module Notifications - class TestCase < ActiveSupport::TestCase - def setup - @old_notifier = ActiveSupport::Notifications.notifier - @notifier = ActiveSupport::Notifications::Fanout.new - ActiveSupport::Notifications.notifier = @notifier - @events = [] - @named_events = [] - @subscription = @notifier.subscribe { |*args| @events << event(*args) } - @named_subscription = @notifier.subscribe("named.subscription") { |*args| @named_events << event(*args) } - end - - def teardown - ActiveSupport::Notifications.notifier = @old_notifier - end - - private - - def event(*args) - ActiveSupport::Notifications::Event.new(*args) - end - end - - class SubscribedTest < TestCase - def test_subscribed - name = "foo" - name2 = name * 2 - expected = [name, name] - - events = [] - callback = lambda {|*_| events << _.first} - ActiveSupport::Notifications.subscribed(callback, name) do - ActiveSupport::Notifications.instrument(name) - ActiveSupport::Notifications.instrument(name2) - ActiveSupport::Notifications.instrument(name) - end - assert_equal expected, events - - ActiveSupport::Notifications.instrument(name) - assert_equal expected, events - end - end - - class UnsubscribeTest < TestCase - def test_unsubscribing_removes_a_subscription - @notifier.publish :foo - @notifier.wait - assert_equal [[:foo]], @events - @notifier.unsubscribe(@subscription) - @notifier.publish :foo - @notifier.wait - assert_equal [[:foo]], @events - end - - def test_unsubscribing_by_name_removes_a_subscription - @notifier.publish "named.subscription", :foo - @notifier.wait - assert_equal [["named.subscription", :foo]], @named_events - @notifier.unsubscribe("named.subscription") - @notifier.publish "named.subscription", :foo - @notifier.wait - assert_equal [["named.subscription", :foo]], @named_events - end - - def test_unsubscribing_by_name_leaves_the_other_subscriptions - @notifier.publish "named.subscription", :foo - @notifier.wait - assert_equal [["named.subscription", :foo]], @events - @notifier.unsubscribe("named.subscription") - @notifier.publish "named.subscription", :foo - @notifier.wait - assert_equal [["named.subscription", :foo], ["named.subscription", :foo]], @events - end - - private - def event(*args) - args - end - end - - class TestSubscriber - attr_reader :starts, :finishes, :publishes - - def initialize - @starts = [] - @finishes = [] - @publishes = [] - end - - def start(*args); @starts << args; end - def finish(*args); @finishes << args; end - def publish(*args); @publishes << args; end - end - - class SyncPubSubTest < TestCase - def test_events_are_published_to_a_listener - @notifier.publish :foo - @notifier.wait - assert_equal [[:foo]], @events - end - - def test_publishing_multiple_times_works - @notifier.publish :foo - @notifier.publish :foo - @notifier.wait - assert_equal [[:foo], [:foo]], @events - end - - def test_publishing_after_a_new_subscribe_works - @notifier.publish :foo - @notifier.publish :foo - - @notifier.subscribe("not_existent") do |*args| - @events << ActiveSupport::Notifications::Event.new(*args) - end - - @notifier.publish :foo - @notifier.publish :foo - @notifier.wait - - assert_equal [[:foo]] * 4, @events - end - - def test_log_subscriber_with_string - events = [] - @notifier.subscribe('1') { |*args| events << args } - - @notifier.publish '1' - @notifier.publish '1.a' - @notifier.publish 'a.1' - @notifier.wait - - assert_equal [['1']], events - end - - def test_log_subscriber_with_pattern - events = [] - @notifier.subscribe(/\d/) { |*args| events << args } - - @notifier.publish '1' - @notifier.publish 'a.1' - @notifier.publish '1.a' - @notifier.wait - - assert_equal [['1'], ['a.1'], ['1.a']], events - end - - def test_multiple_log_subscribers - @another = [] - @notifier.subscribe { |*args| @another << args } - @notifier.publish :foo - @notifier.wait - - assert_equal [[:foo]], @events - assert_equal [[:foo]], @another - end - - def test_publish_with_subscriber - subscriber = TestSubscriber.new - @notifier.subscribe nil, subscriber - @notifier.publish :foo - - assert_equal [[:foo]], subscriber.publishes - end - - private - def event(*args) - args - end - end - - class InstrumentationTest < TestCase - delegate :instrument, :to => ActiveSupport::Notifications - - def test_instrument_returns_block_result - assert_equal 2, instrument(:awesome) { 1 + 1 } - end - - def test_instrument_yields_the_payload_for_further_modification - assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 } - assert_equal 1, @events.size - assert_equal :awesome, @events.first.name - assert_equal Hash[:result => 2], @events.first.payload - end - - def test_instrumenter_exposes_its_id - assert_equal 20, ActiveSupport::Notifications.instrumenter.id.size - end - - def test_nested_events_can_be_instrumented - instrument(:awesome, :payload => "notifications") do - instrument(:wot, :payload => "child") do - 1 + 1 - end - - assert_equal 1, @events.size - assert_equal :wot, @events.first.name - assert_equal Hash[:payload => "child"], @events.first.payload - end - - assert_equal 2, @events.size - assert_equal :awesome, @events.last.name - assert_equal Hash[:payload => "notifications"], @events.last.payload - end - - def test_instrument_publishes_when_exception_is_raised - begin - instrument(:awesome, :payload => "notifications") do - raise "FAIL" - end - rescue RuntimeError => e - assert_equal "FAIL", e.message - end - - assert_equal 1, @events.size - assert_equal Hash[:payload => "notifications", - :exception => ["RuntimeError", "FAIL"]], @events.last.payload - end - - def test_event_is_pushed_even_without_block - instrument(:awesome, :payload => "notifications") - assert_equal 1, @events.size - assert_equal :awesome, @events.last.name - assert_equal Hash[:payload => "notifications"], @events.last.payload - end - end - - class EventTest < TestCase - def test_events_are_initialized_with_details - time = Time.now - event = event(:foo, time, time + 0.01, random_id, {}) - - assert_equal :foo, event.name - assert_equal time, event.time - assert_in_delta 10.0, event.duration, 0.00001 - end - - def test_events_consumes_information_given_as_payload - event = event(:foo, Time.now, Time.now + 1, random_id, :payload => :bar) - assert_equal Hash[:payload => :bar], event.payload - end - - def test_event_is_parent_based_on_children - time = Time.utc(2009, 01, 01, 0, 0, 1) - - parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {}) - child = event(:foo, time, time + 10, random_id, {}) - not_child = event(:foo, time, time + 100, random_id, {}) - - parent.children << child - - assert parent.parent_of?(child) - assert !child.parent_of?(parent) - assert !parent.parent_of?(not_child) - assert !not_child.parent_of?(parent) - end - - protected - def random_id - @random_id ||= SecureRandom.hex(10) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/number_helper_i18n_test.rb b/app/server/ruby/vendor/activesupport/test/number_helper_i18n_test.rb deleted file mode 100644 index 65aecece71..0000000000 --- a/app/server/ruby/vendor/activesupport/test/number_helper_i18n_test.rb +++ /dev/null @@ -1,156 +0,0 @@ -require 'abstract_unit' -require 'active_support/number_helper' - -module ActiveSupport - class NumberHelperI18nTest < ActiveSupport::TestCase - include ActiveSupport::NumberHelper - - def setup - I18n.backend.store_translations 'ts', - :number => { - :format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false }, - :currency => { :format => { :unit => '&$', :format => '%u - %n', :negative_format => '(%u - %n)', :precision => 2 } }, - :human => { - :format => { - :precision => 2, - :significant => true, - :strip_insignificant_zeros => true - }, - :storage_units => { - :format => "%n %u", - :units => { - :byte => "b", - :kb => "k" - } - }, - :decimal_units => { - :format => "%n %u", - :units => { - :deci => {:one => "Tenth", :other => "Tenths"}, - :unit => "u", - :ten => {:one => "Ten", :other => "Tens"}, - :thousand => "t", - :million => "m", - :billion =>"b", - :trillion =>"t" , - :quadrillion =>"q" - } - } - }, - :percentage => { :format => {:delimiter => '', :precision => 2, :strip_insignificant_zeros => true} }, - :precision => { :format => {:delimiter => '', :significant => true} } - }, - :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - end - - def test_number_to_i18n_currency - assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts')) - assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts')) - assert_equal("-10.00 - &$", number_to_currency(-10, :locale => 'ts', :format => "%n - %u")) - end - - def test_number_to_currency_with_empty_i18n_store - I18n.backend.store_translations 'empty', {} - - assert_equal("$10.00", number_to_currency(10, :locale => 'empty')) - assert_equal("-$10.00", number_to_currency(-10, :locale => 'empty')) - end - - def test_locale_default_format_has_precedence_over_helper_defaults - I18n.backend.store_translations 'ts', - { :number => { :format => { :separator => ";" } } } - - assert_equal("&$ - 10;00", number_to_currency(10, :locale => 'ts')) - end - - def test_number_to_currency_without_currency_negative_format - I18n.backend.store_translations 'no_negative_format', :number => { - :currency => { :format => { :unit => '@', :format => '%n %u' } } - } - - assert_equal("-10.00 @", number_to_currency(-10, :locale => 'no_negative_format')) - end - - def test_number_with_i18n_precision - #Delimiter was set to "" - assert_equal("10000", number_to_rounded(10000, :locale => 'ts')) - - #Precision inherited and significant was set - assert_equal("1.00", number_to_rounded(1.0, :locale => 'ts')) - end - - def test_number_with_i18n_precision_and_empty_i18n_store - I18n.backend.store_translations 'empty', {} - - assert_equal("123456789.123", number_to_rounded(123456789.123456789, :locale => 'empty')) - assert_equal("1.000", number_to_rounded(1.0000, :locale => 'empty')) - end - - def test_number_with_i18n_delimiter - #Delimiter "," and separator "." - assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'ts')) - end - - def test_number_with_i18n_delimiter_and_empty_i18n_store - I18n.backend.store_translations 'empty', {} - - assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'empty')) - end - - def test_number_to_i18n_percentage - # to see if strip_insignificant_zeros is true - assert_equal("1%", number_to_percentage(1, :locale => 'ts')) - # precision is 2, significant should be inherited - assert_equal("1.24%", number_to_percentage(1.2434, :locale => 'ts')) - # no delimiter - assert_equal("12434%", number_to_percentage(12434, :locale => 'ts')) - end - - def test_number_to_i18n_percentage_and_empty_i18n_store - I18n.backend.store_translations 'empty', {} - - assert_equal("1.000%", number_to_percentage(1, :locale => 'empty')) - assert_equal("1.243%", number_to_percentage(1.2434, :locale => 'empty')) - assert_equal("12434.000%", number_to_percentage(12434, :locale => 'empty')) - end - - def test_number_to_i18n_human_size - #b for bytes and k for kbytes - assert_equal("2 k", number_to_human_size(2048, :locale => 'ts')) - assert_equal("42 b", number_to_human_size(42, :locale => 'ts')) - end - - def test_number_to_i18n_human_size_with_empty_i18n_store - I18n.backend.store_translations 'empty', {} - - assert_equal("2 KB", number_to_human_size(2048, :locale => 'empty')) - assert_equal("42 Bytes", number_to_human_size(42, :locale => 'empty')) - end - - def test_number_to_human_with_default_translation_scope - #Using t for thousand - assert_equal "2 t", number_to_human(2000, :locale => 'ts') - #Significant was set to true with precision 2, using b for billion - assert_equal "1.2 b", number_to_human(1234567890, :locale => 'ts') - #Using pluralization (Ten/Tens and Tenth/Tenths) - assert_equal "1 Tenth", number_to_human(0.1, :locale => 'ts') - assert_equal "1.3 Tenth", number_to_human(0.134, :locale => 'ts') - assert_equal "2 Tenths", number_to_human(0.2, :locale => 'ts') - assert_equal "1 Ten", number_to_human(10, :locale => 'ts') - assert_equal "1.2 Ten", number_to_human(12, :locale => 'ts') - assert_equal "2 Tens", number_to_human(20, :locale => 'ts') - end - - def test_number_to_human_with_empty_i18n_store - I18n.backend.store_translations 'empty', {} - - assert_equal "2 Thousand", number_to_human(2000, :locale => 'empty') - assert_equal "1.23 Billion", number_to_human(1234567890, :locale => 'empty') - end - - def test_number_to_human_with_custom_translation_scope - #Significant was set to true with precision 2, with custom translated units - assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human) - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/number_helper_test.rb b/app/server/ruby/vendor/activesupport/test/number_helper_test.rb deleted file mode 100644 index 9bdb92024e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/number_helper_test.rb +++ /dev/null @@ -1,391 +0,0 @@ -require 'abstract_unit' -require 'active_support/number_helper' - -module ActiveSupport - module NumberHelper - class NumberHelperTest < ActiveSupport::TestCase - - class TestClassWithInstanceNumberHelpers - include ActiveSupport::NumberHelper - end - - class TestClassWithClassNumberHelpers - extend ActiveSupport::NumberHelper - end - - def setup - @instance_with_helpers = TestClassWithInstanceNumberHelpers.new - end - - def kilobytes(number) - number * 1024 - end - - def megabytes(number) - kilobytes(number) * 1024 - end - - def gigabytes(number) - megabytes(number) * 1024 - end - - def terabytes(number) - gigabytes(number) * 1024 - end - - def test_number_to_phone - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal("555-1234", number_helper.number_to_phone(5551234)) - assert_equal("800-555-1212", number_helper.number_to_phone(8005551212)) - assert_equal("(800) 555-1212", number_helper.number_to_phone(8005551212, {:area_code => true})) - assert_equal("", number_helper.number_to_phone("", {:area_code => true})) - assert_equal("800 555 1212", number_helper.number_to_phone(8005551212, {:delimiter => " "})) - assert_equal("(800) 555-1212 x 123", number_helper.number_to_phone(8005551212, {:area_code => true, :extension => 123})) - assert_equal("800-555-1212", number_helper.number_to_phone(8005551212, :extension => " ")) - assert_equal("555.1212", number_helper.number_to_phone(5551212, :delimiter => '.')) - assert_equal("800-555-1212", number_helper.number_to_phone("8005551212")) - assert_equal("+1-800-555-1212", number_helper.number_to_phone(8005551212, :country_code => 1)) - assert_equal("+18005551212", number_helper.number_to_phone(8005551212, :country_code => 1, :delimiter => '')) - assert_equal("22-555-1212", number_helper.number_to_phone(225551212)) - assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, :country_code => 45)) - end - end - - def test_number_to_currency - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal("$1,234,567,890.50", number_helper.number_to_currency(1234567890.50)) - assert_equal("$1,234,567,890.51", number_helper.number_to_currency(1234567890.506)) - assert_equal("-$1,234,567,890.50", number_helper.number_to_currency(-1234567890.50)) - assert_equal("-$ 1,234,567,890.50", number_helper.number_to_currency(-1234567890.50, {:format => "%u %n"})) - assert_equal("($1,234,567,890.50)", number_helper.number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"})) - assert_equal("$1,234,567,892", number_helper.number_to_currency(1234567891.50, {:precision => 0})) - assert_equal("$1,234,567,890.5", number_helper.number_to_currency(1234567890.50, {:precision => 1})) - assert_equal("£1234567890,50", number_helper.number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""})) - assert_equal("$1,234,567,890.50", number_helper.number_to_currency("1234567890.50")) - assert_equal("1,234,567,890.50 Kč", number_helper.number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) - assert_equal("1,234,567,890.50 - Kč", number_helper.number_to_currency("-1234567890.50", {:unit => "Kč", :format => "%n %u", :negative_format => "%n - %u"})) - assert_equal("0.00", number_helper.number_to_currency(+0.0, {:unit => "", :negative_format => "(%n)"})) - assert_equal("(0.00)", number_helper.number_to_currency(-0.0, {:unit => "", :negative_format => "(%n)"})) - end - end - - def test_number_to_percentage - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal("100.000%", number_helper.number_to_percentage(100)) - assert_equal("100%", number_helper.number_to_percentage(100, {:precision => 0})) - assert_equal("302.06%", number_helper.number_to_percentage(302.0574, {:precision => 2})) - assert_equal("100.000%", number_helper.number_to_percentage("100")) - assert_equal("1000.000%", number_helper.number_to_percentage("1000")) - assert_equal("123.4%", number_helper.number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true)) - assert_equal("1.000,000%", number_helper.number_to_percentage(1000, :delimiter => '.', :separator => ',')) - assert_equal("1000.000 %", number_helper.number_to_percentage(1000, :format => "%n %")) - assert_equal("98a%", number_helper.number_to_percentage("98a")) - assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN)) - assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY)) - end - end - - def test_to_delimited - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal("12,345,678", number_helper.number_to_delimited(12345678)) - assert_equal("0", number_helper.number_to_delimited(0)) - assert_equal("123", number_helper.number_to_delimited(123)) - assert_equal("123,456", number_helper.number_to_delimited(123456)) - assert_equal("123,456.78", number_helper.number_to_delimited(123456.78)) - assert_equal("123,456.789", number_helper.number_to_delimited(123456.789)) - assert_equal("123,456.78901", number_helper.number_to_delimited(123456.78901)) - assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901)) - assert_equal("0.78901", number_helper.number_to_delimited(0.78901)) - assert_equal("123,456.78", number_helper.number_to_delimited("123456.78")) - end - end - - def test_to_delimited_with_options_hash - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '12 345 678', number_helper.number_to_delimited(12345678, :delimiter => ' ') - assert_equal '12,345,678-05', number_helper.number_to_delimited(12345678.05, :separator => '-') - assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :separator => ',', :delimiter => '.') - end - end - - def test_to_rounded - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal("-111.235", number_helper.number_to_rounded(-111.2346)) - assert_equal("111.235", number_helper.number_to_rounded(111.2346)) - assert_equal("31.83", number_helper.number_to_rounded(31.825, :precision => 2)) - assert_equal("111.23", number_helper.number_to_rounded(111.2346, :precision => 2)) - assert_equal("111.00", number_helper.number_to_rounded(111, :precision => 2)) - assert_equal("111.235", number_helper.number_to_rounded("111.2346")) - assert_equal("31.83", number_helper.number_to_rounded("31.825", :precision => 2)) - assert_equal("3268", number_helper.number_to_rounded((32.6751 * 100.00), :precision => 0)) - assert_equal("112", number_helper.number_to_rounded(111.50, :precision => 0)) - assert_equal("1234567892", number_helper.number_to_rounded(1234567891.50, :precision => 0)) - assert_equal("0", number_helper.number_to_rounded(0, :precision => 0)) - assert_equal("0.00100", number_helper.number_to_rounded(0.001, :precision => 5)) - assert_equal("0.001", number_helper.number_to_rounded(0.00111, :precision => 3)) - assert_equal("10.00", number_helper.number_to_rounded(9.995, :precision => 2)) - assert_equal("11.00", number_helper.number_to_rounded(10.995, :precision => 2)) - assert_equal("0.00", number_helper.number_to_rounded(-0.001, :precision => 2)) - - assert_equal("111.23460000000000000000", number_helper.number_to_rounded(111.2346, :precision => 20)) - assert_equal("111.23460000000000000000", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 20)) - assert_equal("111.23460000000000000000", number_helper.number_to_rounded('111.2346', :precision => 20)) - assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), :precision => 20)) - assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded('111.2346', :precision => 100)) - end - end - - def test_to_rounded_with_custom_delimiter_and_separator - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '31,83', number_helper.number_to_rounded(31.825, :precision => 2, :separator => ',') - assert_equal '1.231,83', number_helper.number_to_rounded(1231.825, :precision => 2, :separator => ',', :delimiter => '.') - end - end - - def test_to_rounded_with_significant_digits - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal "124000", number_helper.number_to_rounded(123987, :precision => 3, :significant => true) - assert_equal "120000000", number_helper.number_to_rounded(123987876, :precision => 2, :significant => true ) - assert_equal "40000", number_helper.number_to_rounded("43523", :precision => 1, :significant => true ) - assert_equal "9775", number_helper.number_to_rounded(9775, :precision => 4, :significant => true ) - assert_equal "5.4", number_helper.number_to_rounded(5.3923, :precision => 2, :significant => true ) - assert_equal "5", number_helper.number_to_rounded(5.3923, :precision => 1, :significant => true ) - assert_equal "1", number_helper.number_to_rounded(1.232, :precision => 1, :significant => true ) - assert_equal "7", number_helper.number_to_rounded(7, :precision => 1, :significant => true ) - assert_equal "1", number_helper.number_to_rounded(1, :precision => 1, :significant => true ) - assert_equal "53", number_helper.number_to_rounded(52.7923, :precision => 2, :significant => true ) - assert_equal "9775.00", number_helper.number_to_rounded(9775, :precision => 6, :significant => true ) - assert_equal "5.392900", number_helper.number_to_rounded(5.3929, :precision => 7, :significant => true ) - assert_equal "0.0", number_helper.number_to_rounded(0, :precision => 2, :significant => true ) - assert_equal "0", number_helper.number_to_rounded(0, :precision => 1, :significant => true ) - assert_equal "0.0001", number_helper.number_to_rounded(0.0001, :precision => 1, :significant => true ) - assert_equal "0.000100", number_helper.number_to_rounded(0.0001, :precision => 3, :significant => true ) - assert_equal "0.0001", number_helper.number_to_rounded(0.0001111, :precision => 1, :significant => true ) - assert_equal "10.0", number_helper.number_to_rounded(9.995, :precision => 3, :significant => true) - assert_equal "9.99", number_helper.number_to_rounded(9.994, :precision => 3, :significant => true) - assert_equal "11.0", number_helper.number_to_rounded(10.995, :precision => 3, :significant => true) - - assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775, :precision => 20, :significant => true ) - assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775.0, :precision => 20, :significant => true ) - assert_equal "9775.0000000000000000", number_helper.number_to_rounded(Rational(9775, 1), :precision => 20, :significant => true ) - assert_equal "97.750000000000000000", number_helper.number_to_rounded(Rational(9775, 100), :precision => 20, :significant => true ) - assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), :precision => 20, :significant => true ) - assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", :precision => 20, :significant => true ) - assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", :precision => 100, :significant => true ) - end - end - - def test_to_rounded_with_strip_insignificant_zeros - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal "9775.43", number_helper.number_to_rounded(9775.43, :precision => 4, :strip_insignificant_zeros => true ) - assert_equal "9775.2", number_helper.number_to_rounded(9775.2, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - assert_equal "0", number_helper.number_to_rounded(0, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - end - end - - def test_to_rounded_with_significant_true_and_zero_precision - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - # Zero precision with significant is a mistake (would always return zero), - # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) - assert_equal "124", number_helper.number_to_rounded(123.987, :precision => 0, :significant => true) - assert_equal "12", number_helper.number_to_rounded(12, :precision => 0, :significant => true ) - assert_equal "12", number_helper.number_to_rounded("12.3", :precision => 0, :significant => true ) - end - end - - def test_number_number_to_human_size - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '0 Bytes', number_helper.number_to_human_size(0) - assert_equal '1 Byte', number_helper.number_to_human_size(1) - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265) - assert_equal '123 Bytes', number_helper.number_to_human_size(123.0) - assert_equal '123 Bytes', number_helper.number_to_human_size(123) - assert_equal '1.21 KB', number_helper.number_to_human_size(1234) - assert_equal '12.1 KB', number_helper.number_to_human_size(12345) - assert_equal '1.18 MB', number_helper.number_to_human_size(1234567) - assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890) - assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123) - assert_equal '1030 TB', number_helper.number_to_human_size(terabytes(1026)) - assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444)) - assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023)) - assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3)) - assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2) - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4) - assert_equal '123 Bytes', number_helper.number_to_human_size('123') - assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2) - assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4) - assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 Byte', number_helper.number_to_human_size(1.1) - assert_equal '10 Bytes', number_helper.number_to_human_size(10) - end - end - - def test_number_to_human_size_with_si_prefix - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si) - assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si) - assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si) - assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si) - assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si) - assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si) - assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si) - assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si) - end - end - - def test_number_to_human_size_with_options_hash - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2) - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4) - assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2) - assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4) - assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 TB', number_helper.number_to_human_size(1234567890123, :precision => 1) - assert_equal '500 MB', number_helper.number_to_human_size(524288000, :precision=>3) - assert_equal '10 MB', number_helper.number_to_human_size(9961472, :precision=>0) - assert_equal '40 KB', number_helper.number_to_human_size(41010, :precision => 1) - assert_equal '40 KB', number_helper.number_to_human_size(41100, :precision => 2) - assert_equal '1.0 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false) - assert_equal '1.012 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false) - assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0 - end - end - - def test_number_to_human_size_with_custom_delimiter_and_separator - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :separator => ',') - assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4, :separator => ',') - assert_equal '1.000,1 TB', number_helper.number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',') - end - end - - def test_number_to_human - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '-123', number_helper.number_to_human(-123) - assert_equal '-0.5', number_helper.number_to_human(-0.5) - assert_equal '0', number_helper.number_to_human(0) - assert_equal '0.5', number_helper.number_to_human(0.5) - assert_equal '123', number_helper.number_to_human(123) - assert_equal '1.23 Thousand', number_helper.number_to_human(1234) - assert_equal '12.3 Thousand', number_helper.number_to_human(12345) - assert_equal '1.23 Million', number_helper.number_to_human(1234567) - assert_equal '1.23 Billion', number_helper.number_to_human(1234567890) - assert_equal '1.23 Trillion', number_helper.number_to_human(1234567890123) - assert_equal '1.23 Quadrillion', number_helper.number_to_human(1234567890123456) - assert_equal '1230 Quadrillion', number_helper.number_to_human(1234567890123456789) - assert_equal '490 Thousand', number_helper.number_to_human(489939, :precision => 2) - assert_equal '489.9 Thousand', number_helper.number_to_human(489939, :precision => 4) - assert_equal '489 Thousand', number_helper.number_to_human(489000, :precision => 4) - assert_equal '489.0 Thousand', number_helper.number_to_human(489000, :precision => 4, :strip_insignificant_zeros => false) - assert_equal '1.2346 Million', number_helper.number_to_human(1234567, :precision => 4, :significant => false) - assert_equal '1,2 Million', number_helper.number_to_human(1234567, :precision => 1, :significant => false, :separator => ',') - assert_equal '1 Million', number_helper.number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false - end - end - - def test_number_to_human_with_custom_units - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - #Only integers - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123 lt', number_helper.number_to_human(123456, :units => volume) - assert_equal '12 ml', number_helper.number_to_human(12, :units => volume) - assert_equal '1.23 m3', number_helper.number_to_human(1234567, :units => volume) - - #Including fractionals - distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - assert_equal '1.23 mm', number_helper.number_to_human(0.00123, :units => distance) - assert_equal '1.23 cm', number_helper.number_to_human(0.0123, :units => distance) - assert_equal '1.23 dm', number_helper.number_to_human(0.123, :units => distance) - assert_equal '1.23 m', number_helper.number_to_human(1.23, :units => distance) - assert_equal '1.23 dam', number_helper.number_to_human(12.3, :units => distance) - assert_equal '1.23 hm', number_helper.number_to_human(123, :units => distance) - assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance) - assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance) - assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance) - assert_equal '12.3 km', number_helper.number_to_human(12300, :units => distance) - - #The quantifiers don't need to be a continuous sequence - gangster = {:hundred => "hundred bucks", :million => "thousand quids"} - assert_equal '1 hundred bucks', number_helper.number_to_human(100, :units => gangster) - assert_equal '25 hundred bucks', number_helper.number_to_human(2500, :units => gangster) - assert_equal '25 thousand quids', number_helper.number_to_human(25000000, :units => gangster) - assert_equal '12300 thousand quids', number_helper.number_to_human(12345000000, :units => gangster) - - #Spaces are stripped from the resulting string - assert_equal '4', number_helper.number_to_human(4, :units => {:unit => "", :ten => 'tens '}) - assert_equal '4.5 tens', number_helper.number_to_human(45, :units => {:unit => "", :ten => ' tens '}) - end - end - - def test_number_to_human_with_custom_units_that_are_missing_the_needed_key - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '123', number_helper.number_to_human(123, units: { thousand: 'k'}) - assert_equal '123', number_helper.number_to_human(123, units: {}) - end - end - - def test_number_to_human_with_custom_format - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '123 times Thousand', number_helper.number_to_human(123456, :format => "%n times %u") - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123.lt', number_helper.number_to_human(123456, :units => volume, :format => "%n.%u") - end - end - - def test_number_helpers_should_return_nil_when_given_nil - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_nil number_helper.number_to_phone(nil) - assert_nil number_helper.number_to_currency(nil) - assert_nil number_helper.number_to_percentage(nil) - assert_nil number_helper.number_to_delimited(nil) - assert_nil number_helper.number_to_rounded(nil) - assert_nil number_helper.number_to_human_size(nil) - assert_nil number_helper.number_to_human(nil) - end - end - - def test_number_helpers_do_not_mutate_options_hash - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - options = { 'raise' => true } - - number_helper.number_to_phone(1, options) - assert_equal({ 'raise' => true }, options) - - number_helper.number_to_currency(1, options) - assert_equal({ 'raise' => true }, options) - - number_helper.number_to_percentage(1, options) - assert_equal({ 'raise' => true }, options) - - number_helper.number_to_delimited(1, options) - assert_equal({ 'raise' => true }, options) - - number_helper.number_to_rounded(1, options) - assert_equal({ 'raise' => true }, options) - - number_helper.number_to_human_size(1, options) - assert_equal({ 'raise' => true }, options) - - number_helper.number_to_human(1, options) - assert_equal({ 'raise' => true }, options) - end - end - - def test_number_helpers_should_return_non_numeric_param_unchanged - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal("+1-x x 123", number_helper.number_to_phone("x", :country_code => 1, :extension => 123)) - assert_equal("x", number_helper.number_to_phone("x")) - assert_equal("$x.", number_helper.number_to_currency("x.")) - assert_equal("$x", number_helper.number_to_currency("x")) - assert_equal("x%", number_helper.number_to_percentage("x")) - assert_equal("x", number_helper.number_to_delimited("x")) - assert_equal("x.", number_helper.number_to_rounded("x.")) - assert_equal("x", number_helper.number_to_rounded("x")) - assert_equal "x", number_helper.number_to_human_size('x') - assert_equal "x", number_helper.number_to_human('x') - end - end - - end - end -end diff --git a/app/server/ruby/vendor/activesupport/test/option_merger_test.rb b/app/server/ruby/vendor/activesupport/test/option_merger_test.rb deleted file mode 100644 index 9d139b61b8..0000000000 --- a/app/server/ruby/vendor/activesupport/test/option_merger_test.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/with_options' - -class OptionMergerTest < ActiveSupport::TestCase - def setup - @options = {:hello => 'world'} - end - - def test_method_with_options_merges_options_when_options_are_present - local_options = {:cool => true} - - with_options(@options) do |o| - assert_equal local_options, method_with_options(local_options) - assert_equal @options.merge(local_options), - o.method_with_options(local_options) - end - end - - def test_method_with_options_appends_options_when_options_are_missing - with_options(@options) do |o| - assert_equal Hash.new, method_with_options - assert_equal @options, o.method_with_options - end - end - - def test_method_with_options_allows_to_overwrite_options - local_options = {:hello => 'moon'} - assert_equal @options.keys, local_options.keys - - with_options(@options) do |o| - assert_equal local_options, method_with_options(local_options) - assert_equal @options.merge(local_options), - o.method_with_options(local_options) - assert_equal local_options, o.method_with_options(local_options) - end - with_options(local_options) do |o| - assert_equal local_options.merge(@options), - o.method_with_options(@options) - end - end - - def test_nested_method_with_options_containing_hashes_merge - with_options :conditions => { :method => :get } do |outer| - outer.with_options :conditions => { :domain => "www" } do |inner| - expected = { :conditions => { :method => :get, :domain => "www" } } - assert_equal expected, inner.method_with_options - end - end - end - - def test_nested_method_with_options_containing_hashes_overwrite - with_options :conditions => { :method => :get, :domain => "www" } do |outer| - outer.with_options :conditions => { :method => :post } do |inner| - expected = { :conditions => { :method => :post, :domain => "www" } } - assert_equal expected, inner.method_with_options - end - end - end - - def test_nested_method_with_options_containing_hashes_going_deep - with_options :html => { :class => "foo", :style => { :margin => 0, :display => "block" } } do |outer| - outer.with_options :html => { :title => "bar", :style => { :margin => "1em", :color => "#fff" } } do |inner| - expected = { :html => { :class => "foo", :title => "bar", :style => { :margin => "1em", :display => "block", :color => "#fff" } } } - assert_equal expected, inner.method_with_options - end - end - end - - def test_nested_method_with_options_using_lambda - local_lambda = lambda { { :lambda => true } } - with_options(@options) do |o| - assert_equal @options.merge(local_lambda.call), - o.method_with_options(local_lambda).call - end - end - - # Needed when counting objects with the ObjectSpace - def test_option_merger_class_method - assert_equal ActiveSupport::OptionMerger, ActiveSupport::OptionMerger.new('', '').class - end - - private - def method_with_options(options = {}) - options - end -end diff --git a/app/server/ruby/vendor/activesupport/test/ordered_hash_test.rb b/app/server/ruby/vendor/activesupport/test/ordered_hash_test.rb deleted file mode 100644 index 460a61613e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/ordered_hash_test.rb +++ /dev/null @@ -1,322 +0,0 @@ -require 'abstract_unit' -require 'active_support/json' -require 'active_support/core_ext/object/json' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/array/extract_options' - -class OrderedHashTest < ActiveSupport::TestCase - def setup - @keys = %w( blue green red pink orange ) - @values = %w( 000099 009900 aa0000 cc0066 cc6633 ) - @hash = Hash.new - @ordered_hash = ActiveSupport::OrderedHash.new - - @keys.each_with_index do |key, index| - @hash[key] = @values[index] - @ordered_hash[key] = @values[index] - end - end - - def test_order - assert_equal @keys, @ordered_hash.keys - assert_equal @values, @ordered_hash.values - end - - def test_access - assert @hash.all? { |k, v| @ordered_hash[k] == v } - end - - def test_assignment - key, value = 'purple', '5422a8' - - @ordered_hash[key] = value - assert_equal @keys.length + 1, @ordered_hash.length - assert_equal key, @ordered_hash.keys.last - assert_equal value, @ordered_hash.values.last - assert_equal value, @ordered_hash[key] - end - - def test_delete - key, value = 'white', 'ffffff' - bad_key = 'black' - - @ordered_hash[key] = value - assert_equal @keys.length + 1, @ordered_hash.length - assert_equal @ordered_hash.keys.length, @ordered_hash.length - - assert_equal value, @ordered_hash.delete(key) - assert_equal @keys.length, @ordered_hash.length - assert_equal @ordered_hash.keys.length, @ordered_hash.length - - assert_nil @ordered_hash.delete(bad_key) - end - - def test_to_hash - assert_same @ordered_hash, @ordered_hash.to_hash - end - - def test_to_a - assert_equal @keys.zip(@values), @ordered_hash.to_a - end - - def test_has_key - assert_equal true, @ordered_hash.has_key?('blue') - assert_equal true, @ordered_hash.key?('blue') - assert_equal true, @ordered_hash.include?('blue') - assert_equal true, @ordered_hash.member?('blue') - - assert_equal false, @ordered_hash.has_key?('indigo') - assert_equal false, @ordered_hash.key?('indigo') - assert_equal false, @ordered_hash.include?('indigo') - assert_equal false, @ordered_hash.member?('indigo') - end - - def test_has_value - assert_equal true, @ordered_hash.has_value?('000099') - assert_equal true, @ordered_hash.value?('000099') - assert_equal false, @ordered_hash.has_value?('ABCABC') - assert_equal false, @ordered_hash.value?('ABCABC') - end - - def test_each_key - keys = [] - assert_equal @ordered_hash, @ordered_hash.each_key { |k| keys << k } - assert_equal @keys, keys - assert_kind_of Enumerator, @ordered_hash.each_key - end - - def test_each_value - values = [] - assert_equal @ordered_hash, @ordered_hash.each_value { |v| values << v } - assert_equal @values, values - assert_kind_of Enumerator, @ordered_hash.each_value - end - - def test_each - values = [] - assert_equal @ordered_hash, @ordered_hash.each {|key, value| values << value} - assert_equal @values, values - assert_kind_of Enumerator, @ordered_hash.each - end - - def test_each_with_index - @ordered_hash.each_with_index { |pair, index| assert_equal [@keys[index], @values[index]], pair} - end - - def test_each_pair - values = [] - keys = [] - @ordered_hash.each_pair do |key, value| - keys << key - values << value - end - assert_equal @values, values - assert_equal @keys, keys - assert_kind_of Enumerator, @ordered_hash.each_pair - end - - def test_find_all - assert_equal @keys, @ordered_hash.find_all { true }.map(&:first) - end - - def test_select - new_ordered_hash = @ordered_hash.select { true } - assert_equal @keys, new_ordered_hash.map(&:first) - assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash - end - - def test_delete_if - copy = @ordered_hash.dup - copy.delete('pink') - assert_equal copy, @ordered_hash.delete_if { |k, _| k == 'pink' } - assert !@ordered_hash.keys.include?('pink') - end - - def test_reject! - (copy = @ordered_hash.dup).delete('pink') - @ordered_hash.reject! { |k, _| k == 'pink' } - assert_equal copy, @ordered_hash - assert !@ordered_hash.keys.include?('pink') - end - - def test_reject - copy = @ordered_hash.dup - new_ordered_hash = @ordered_hash.reject { |k, _| k == 'pink' } - assert_equal copy, @ordered_hash - assert !new_ordered_hash.keys.include?('pink') - assert @ordered_hash.keys.include?('pink') - assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash - end - - def test_clear - @ordered_hash.clear - assert_equal [], @ordered_hash.keys - end - - def test_merge - other_hash = ActiveSupport::OrderedHash.new - other_hash['purple'] = '800080' - other_hash['violet'] = 'ee82ee' - merged = @ordered_hash.merge other_hash - assert_equal merged.length, @ordered_hash.length + other_hash.length - assert_equal @keys + ['purple', 'violet'], merged.keys - end - - def test_merge_with_block - hash = ActiveSupport::OrderedHash.new - hash[:a] = 0 - hash[:b] = 0 - merged = hash.merge(:b => 2, :c => 7) do |key, old_value, new_value| - new_value + 1 - end - - assert_equal 0, merged[:a] - assert_equal 3, merged[:b] - assert_equal 7, merged[:c] - end - - def test_merge_bang_with_block - hash = ActiveSupport::OrderedHash.new - hash[:a] = 0 - hash[:b] = 0 - hash.merge!(:a => 1, :c => 7) do |key, old_value, new_value| - new_value + 3 - end - - assert_equal 4, hash[:a] - assert_equal 0, hash[:b] - assert_equal 7, hash[:c] - end - - def test_shift - pair = @ordered_hash.shift - assert_equal [@keys.first, @values.first], pair - assert !@ordered_hash.keys.include?(pair.first) - end - - def test_keys - original = @ordered_hash.keys.dup - @ordered_hash.keys.pop - assert_equal original, @ordered_hash.keys - end - - def test_inspect - assert @ordered_hash.inspect.include?(@hash.inspect) - end - - def test_json - ordered_hash = ActiveSupport::OrderedHash[:foo, :bar] - hash = Hash[:foo, :bar] - assert_equal ordered_hash.to_json, hash.to_json - end - - def test_alternate_initialization_with_splat - alternate = ActiveSupport::OrderedHash[1,2,3,4] - assert_kind_of ActiveSupport::OrderedHash, alternate - assert_equal [1, 3], alternate.keys - end - - def test_alternate_initialization_with_array - alternate = ActiveSupport::OrderedHash[ [ - [1, 2], - [3, 4], - [ 'missing value' ] - ]] - - assert_kind_of ActiveSupport::OrderedHash, alternate - assert_equal [1, 3, 'missing value'], alternate.keys - assert_equal [2, 4, nil ], alternate.values - end - - def test_alternate_initialization_raises_exception_on_odd_length_args - assert_raises ArgumentError do - ActiveSupport::OrderedHash[1,2,3,4,5] - end - end - - def test_replace_updates_keys - @other_ordered_hash = ActiveSupport::OrderedHash[:black, '000000', :white, '000000'] - original = @ordered_hash.replace(@other_ordered_hash) - assert_same original, @ordered_hash - assert_equal @other_ordered_hash.keys, @ordered_hash.keys - end - - def test_nested_under_indifferent_access - flash = {:a => ActiveSupport::OrderedHash[:b, 1, :c, 2]}.with_indifferent_access - assert_kind_of ActiveSupport::OrderedHash, flash[:a] - end - - def test_each_after_yaml_serialization - assert_equal @values, YAML.load(YAML.dump(@ordered_hash)).values - end - - def test_each_when_yielding_to_block_with_splat - hash_values = [] - ordered_hash_values = [] - - @hash.each { |*v| hash_values << v } - @ordered_hash.each { |*v| ordered_hash_values << v } - - assert_equal hash_values.sort, ordered_hash_values.sort - end - - def test_each_pair_when_yielding_to_block_with_splat - hash_values = [] - ordered_hash_values = [] - - @hash.each_pair { |*v| hash_values << v } - @ordered_hash.each_pair { |*v| ordered_hash_values << v } - - assert_equal hash_values.sort, ordered_hash_values.sort - end - - def test_order_after_yaml_serialization - @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash)) - - assert_equal @keys, @deserialized_ordered_hash.keys - assert_equal @values, @deserialized_ordered_hash.values - end - - def test_order_after_yaml_serialization_with_nested_arrays - @ordered_hash[:array] = %w(a b c) - - @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash)) - - assert_equal @ordered_hash.keys, @deserialized_ordered_hash.keys - assert_equal @ordered_hash.values, @deserialized_ordered_hash.values - end - - def test_psych_serialize - @deserialized_ordered_hash = Psych.load(Psych.dump(@ordered_hash)) - - values = @deserialized_ordered_hash.map { |_, value| value } - assert_equal @values, values - end - - def test_psych_serialize_tag - yaml = Psych.dump(@ordered_hash) - assert_match '!omap', yaml - end - - def test_has_yaml_tag - @ordered_hash[:array] = %w(a b c) - assert_match '!omap', YAML.dump(@ordered_hash) - end - - def test_update_sets_keys - @updated_ordered_hash = ActiveSupport::OrderedHash.new - @updated_ordered_hash.update(:name => "Bob") - assert_equal [:name], @updated_ordered_hash.keys - end - - def test_invert - expected = ActiveSupport::OrderedHash[@values.zip(@keys)] - assert_equal expected, @ordered_hash.invert - assert_equal @values.zip(@keys), @ordered_hash.invert.to_a - end - - def test_extractable - @ordered_hash[:rails] = "snowman" - assert_equal @ordered_hash, [1, 2, @ordered_hash].extract_options! - end -end diff --git a/app/server/ruby/vendor/activesupport/test/ordered_options_test.rb b/app/server/ruby/vendor/activesupport/test/ordered_options_test.rb deleted file mode 100644 index fdc745b23b..0000000000 --- a/app/server/ruby/vendor/activesupport/test/ordered_options_test.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'abstract_unit' -require 'active_support/ordered_options' - -class OrderedOptionsTest < ActiveSupport::TestCase - def test_usage - a = ActiveSupport::OrderedOptions.new - - assert_nil a[:not_set] - - a[:allow_concurrency] = true - assert_equal 1, a.size - assert a[:allow_concurrency] - - a[:allow_concurrency] = false - assert_equal 1, a.size - assert !a[:allow_concurrency] - - a["else_where"] = 56 - assert_equal 2, a.size - assert_equal 56, a[:else_where] - end - - def test_looping - a = ActiveSupport::OrderedOptions.new - - a[:allow_concurrency] = true - a["else_where"] = 56 - - test = [[:allow_concurrency, true], [:else_where, 56]] - - a.each_with_index do |(key, value), index| - assert_equal test[index].first, key - assert_equal test[index].last, value - end - end - - def test_method_access - a = ActiveSupport::OrderedOptions.new - - assert_nil a.not_set - - a.allow_concurrency = true - assert_equal 1, a.size - assert a.allow_concurrency - - a.allow_concurrency = false - assert_equal 1, a.size - assert !a.allow_concurrency - - a.else_where = 56 - assert_equal 2, a.size - assert_equal 56, a.else_where - end - - def test_inheritable_options_continues_lookup_in_parent - parent = ActiveSupport::OrderedOptions.new - parent[:foo] = true - - child = ActiveSupport::InheritableOptions.new(parent) - assert child.foo - end - - def test_inheritable_options_can_override_parent - parent = ActiveSupport::OrderedOptions.new - parent[:foo] = :bar - - child = ActiveSupport::InheritableOptions.new(parent) - child[:foo] = :baz - - assert_equal :baz, child.foo - end - - def test_inheritable_options_inheritable_copy - original = ActiveSupport::InheritableOptions.new - copy = original.inheritable_copy - - assert copy.kind_of?(original.class) - assert_not_equal copy.object_id, original.object_id - end - - def test_introspection - a = ActiveSupport::OrderedOptions.new - assert a.respond_to?(:blah) - assert a.respond_to?(:blah=) - assert_equal 42, a.method(:blah=).call(42) - assert_equal 42, a.method(:blah).call - end -end diff --git a/app/server/ruby/vendor/activesupport/test/rescuable_test.rb b/app/server/ruby/vendor/activesupport/test/rescuable_test.rb deleted file mode 100644 index ec9d231125..0000000000 --- a/app/server/ruby/vendor/activesupport/test/rescuable_test.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'abstract_unit' - -class WraithAttack < StandardError -end - -class NuclearExplosion < StandardError -end - -class MadRonon < StandardError -end - -class CoolError < StandardError -end - -class Stargate - attr_accessor :result - - include ActiveSupport::Rescuable - - rescue_from WraithAttack, :with => :sos_first - - rescue_from WraithAttack, :with => :sos - - rescue_from 'NuclearExplosion' do - @result = 'alldead' - end - - rescue_from MadRonon do |e| - @result = e.message - end - - def dispatch(method) - send(method) - rescue Exception => e - rescue_with_handler(e) - end - - def attack - raise WraithAttack - end - - def nuke - raise NuclearExplosion - end - - def ronanize - raise MadRonon.new("dex") - end - - def sos - @result = 'killed' - end - - def sos_first - @result = 'sos_first' - end - -end - -class CoolStargate < Stargate - attr_accessor :result - - include ActiveSupport::Rescuable - - rescue_from CoolError, :with => :sos_cool_error - - def sos_cool_error - @result = 'sos_cool_error' - end -end - - -class RescuableTest < ActiveSupport::TestCase - def setup - @stargate = Stargate.new - @cool_stargate = CoolStargate.new - end - - def test_rescue_from_with_method - @stargate.dispatch :attack - assert_equal 'killed', @stargate.result - end - - def test_rescue_from_with_block - @stargate.dispatch :nuke - assert_equal 'alldead', @stargate.result - end - - def test_rescue_from_with_block_with_args - @stargate.dispatch :ronanize - assert_equal 'dex', @stargate.result - end - - def test_rescues_defined_later_are_added_at_end_of_the_rescue_handlers_array - expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon"] - result = @stargate.send(:rescue_handlers).collect {|e| e.first} - assert_equal expected, result - end - - def test_children_should_inherit_rescue_definitions_from_parents_and_child_rescue_should_be_appended - expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "CoolError"] - result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first} - assert_equal expected, result - end -end diff --git a/app/server/ruby/vendor/activesupport/test/safe_buffer_test.rb b/app/server/ruby/vendor/activesupport/test/safe_buffer_test.rb deleted file mode 100644 index efa9d5e61f..0000000000 --- a/app/server/ruby/vendor/activesupport/test/safe_buffer_test.rb +++ /dev/null @@ -1,168 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/string/inflections' -require 'yaml' - -class SafeBufferTest < ActiveSupport::TestCase - def setup - @buffer = ActiveSupport::SafeBuffer.new - end - - def test_titleize - assert_equal 'Foo', "foo".html_safe.titleize - end - - test "Should look like a string" do - assert @buffer.is_a?(String) - assert_equal "", @buffer - end - - test "Should escape a raw string which is passed to them" do - @buffer << "') - - # calling gsub! makes the dirty flag true - assert !x.html_safe?, "should not be safe" - - # getting a slice of it - y = x[0..-1] - - # should still be unsafe - assert !y.html_safe?, "should not be safe" - end - - test 'Should work with interpolation (array argument)' do - x = 'foo %s bar'.html_safe % ['qux'] - assert_equal 'foo qux bar', x - end - - test 'Should work with interpolation (hash argument)' do - x = 'foo %{x} bar'.html_safe % { x: 'qux' } - assert_equal 'foo qux bar', x - end - - test 'Should escape unsafe interpolated args' do - x = 'foo %{x} bar'.html_safe % { x: '
' } - assert_equal 'foo <br/> bar', x - end - - test 'Should not escape safe interpolated args' do - x = 'foo %{x} bar'.html_safe % { x: '
'.html_safe } - assert_equal 'foo
bar', x - end - - test 'Should interpolate to a safe string' do - x = 'foo %{x} bar'.html_safe % { x: 'qux' } - assert x.html_safe?, 'should be safe' - end -end diff --git a/app/server/ruby/vendor/activesupport/test/string_inquirer_test.rb b/app/server/ruby/vendor/activesupport/test/string_inquirer_test.rb deleted file mode 100644 index a2ed577eb0..0000000000 --- a/app/server/ruby/vendor/activesupport/test/string_inquirer_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'abstract_unit' - -class StringInquirerTest < ActiveSupport::TestCase - def setup - @string_inquirer = ActiveSupport::StringInquirer.new('production') - end - - def test_match - assert @string_inquirer.production? - end - - def test_miss - assert_not @string_inquirer.development? - end - - def test_missing_question_mark - assert_raise(NoMethodError) { @string_inquirer.production } - end - - def test_respond_to - assert_respond_to @string_inquirer, :development? - end -end diff --git a/app/server/ruby/vendor/activesupport/test/subscriber_test.rb b/app/server/ruby/vendor/activesupport/test/subscriber_test.rb deleted file mode 100644 index 253411aa3d..0000000000 --- a/app/server/ruby/vendor/activesupport/test/subscriber_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'abstract_unit' -require 'active_support/subscriber' - -class TestSubscriber < ActiveSupport::Subscriber - attach_to :doodle - - cattr_reader :event - - def self.clear - @@event = nil - end - - def open_party(event) - @@event = event - end - - private - - def private_party(event) - @@event = event - end -end - -class SubscriberTest < ActiveSupport::TestCase - def setup - TestSubscriber.clear - end - - def test_attaches_subscribers - ActiveSupport::Notifications.instrument("open_party.doodle") - - assert_equal "open_party.doodle", TestSubscriber.event.name - end - - def test_does_not_attach_private_methods - ActiveSupport::Notifications.instrument("private_party.doodle") - - assert_nil TestSubscriber.event - end -end diff --git a/app/server/ruby/vendor/activesupport/test/tagged_logging_test.rb b/app/server/ruby/vendor/activesupport/test/tagged_logging_test.rb deleted file mode 100644 index 27f629474e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/tagged_logging_test.rb +++ /dev/null @@ -1,102 +0,0 @@ -require 'abstract_unit' -require 'active_support/logger' -require 'active_support/tagged_logging' - -class TaggedLoggingTest < ActiveSupport::TestCase - class MyLogger < ::ActiveSupport::Logger - def flush(*) - info "[FLUSHED]" - end - end - - setup do - @output = StringIO.new - @logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@output)) - end - - test 'sets logger.formatter if missing and extends it with a tagging API' do - logger = Logger.new(StringIO.new) - assert_nil logger.formatter - ActiveSupport::TaggedLogging.new(logger) - assert_not_nil logger.formatter - assert logger.formatter.respond_to?(:tagged) - end - - test "tagged once" do - @logger.tagged("BCX") { @logger.info "Funky time" } - assert_equal "[BCX] Funky time\n", @output.string - end - - test "tagged twice" do - @logger.tagged("BCX") { @logger.tagged("Jason") { @logger.info "Funky time" } } - assert_equal "[BCX] [Jason] Funky time\n", @output.string - end - - test "tagged thrice at once" do - @logger.tagged("BCX", "Jason", "New") { @logger.info "Funky time" } - assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string - end - - test "tagged are flattened" do - @logger.tagged("BCX", %w(Jason New)) { @logger.info "Funky time" } - assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string - end - - test "push and pop tags directly" do - assert_equal %w(A B C), @logger.push_tags('A', ['B', ' ', ['C']]) - @logger.info 'a' - assert_equal %w(C), @logger.pop_tags - @logger.info 'b' - assert_equal %w(B), @logger.pop_tags(1) - @logger.info 'c' - assert_equal [], @logger.clear_tags! - @logger.info 'd' - assert_equal "[A] [B] [C] a\n[A] [B] b\n[A] c\nd\n", @output.string - end - - test "does not strip message content" do - @logger.info " Hello" - assert_equal " Hello\n", @output.string - end - - test "provides access to the logger instance" do - @logger.tagged("BCX") { |logger| logger.info "Funky time" } - assert_equal "[BCX] Funky time\n", @output.string - end - - test "tagged once with blank and nil" do - @logger.tagged(nil, "", "New") { @logger.info "Funky time" } - assert_equal "[New] Funky time\n", @output.string - end - - test "keeps each tag in their own thread" do - @logger.tagged("BCX") do - Thread.new do - @logger.tagged("OMG") { @logger.info "Cool story bro" } - end.join - @logger.info "Funky time" - end - assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string - end - - test "cleans up the taggings on flush" do - @logger.tagged("BCX") do - Thread.new do - @logger.tagged("OMG") do - @logger.flush - @logger.info "Cool story bro" - end - end.join - end - assert_equal "[FLUSHED]\nCool story bro\n", @output.string - end - - test "mixed levels of tagging" do - @logger.tagged("BCX") do - @logger.tagged("Jason") { @logger.info "Funky time" } - @logger.info "Junky time!" - end - - assert_equal "[BCX] [Jason] Funky time\n[BCX] Junky time!\n", @output.string - end -end diff --git a/app/server/ruby/vendor/activesupport/test/test_test.rb b/app/server/ruby/vendor/activesupport/test/test_test.rb deleted file mode 100644 index 0fa08c0e3a..0000000000 --- a/app/server/ruby/vendor/activesupport/test/test_test.rb +++ /dev/null @@ -1,220 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/date' -require 'active_support/core_ext/numeric/time' - -class AssertDifferenceTest < ActiveSupport::TestCase - def setup - @object = Class.new do - attr_accessor :num - def increment - self.num += 1 - end - - def decrement - self.num -= 1 - end - end.new - @object.num = 0 - end - - def test_assert_not - assert_equal true, assert_not(nil) - assert_equal true, assert_not(false) - - e = assert_raises(Minitest::Assertion) { assert_not true } - assert_equal 'Expected true to be nil or false', e.message - - e = assert_raises(Minitest::Assertion) { assert_not true, 'custom' } - assert_equal 'custom', e.message - end - - def test_assert_no_difference - assert_no_difference '@object.num' do - # ... - end - end - - def test_assert_difference - assert_difference '@object.num', +1 do - @object.increment - end - end - - def test_assert_difference_with_implicit_difference - assert_difference '@object.num' do - @object.increment - end - end - - def test_arbitrary_expression - assert_difference '@object.num + 1', +2 do - @object.increment - @object.increment - end - end - - def test_negative_differences - assert_difference '@object.num', -1 do - @object.decrement - end - end - - def test_expression_is_evaluated_in_the_appropriate_scope - silence_warnings do - local_scope = local_scope = 'foo' - assert_difference('local_scope; @object.num') { @object.increment } - end - end - - def test_array_of_expressions - assert_difference [ '@object.num', '@object.num + 1' ], +1 do - @object.increment - end - end - - def test_array_of_expressions_identify_failure - assert_raises(Minitest::Assertion) do - assert_difference ['@object.num', '1 + 1'] do - @object.increment - end - end - end - - def test_array_of_expressions_identify_failure_when_message_provided - assert_raises(Minitest::Assertion) do - assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do - @object.increment - end - end - end -end - -class AlsoDoingNothingTest < ActiveSupport::TestCase -end - -# Setup and teardown callbacks. -class SetupAndTeardownTest < ActiveSupport::TestCase - setup :reset_callback_record, :foo - teardown :foo, :sentinel - - def test_inherited_setup_callbacks - assert_equal [:reset_callback_record, :foo], self.class._setup_callbacks.map(&:raw_filter) - assert_equal [:foo], @called_back - assert_equal [:foo, :sentinel], self.class._teardown_callbacks.map(&:raw_filter) - end - - def setup - end - - def teardown - end - - protected - - def reset_callback_record - @called_back = [] - end - - def foo - @called_back << :foo - end - - def sentinel - assert_equal [:foo], @called_back - end -end - -class SubclassSetupAndTeardownTest < SetupAndTeardownTest - setup :bar - teardown :bar - - def test_inherited_setup_callbacks - assert_equal [:reset_callback_record, :foo, :bar], self.class._setup_callbacks.map(&:raw_filter) - assert_equal [:foo, :bar], @called_back - assert_equal [:foo, :sentinel, :bar], self.class._teardown_callbacks.map(&:raw_filter) - end - - protected - def bar - @called_back << :bar - end - - def sentinel - assert_equal [:foo, :bar, :bar], @called_back - end -end - -class TestCaseTaggedLoggingTest < ActiveSupport::TestCase - def before_setup - require 'stringio' - @out = StringIO.new - self.tagged_logger = ActiveSupport::TaggedLogging.new(Logger.new(@out)) - super - end - - def test_logs_tagged_with_current_test_case - assert_match "#{self.class}: #{name}\n", @out.string - end -end - -class TimeHelperTest < ActiveSupport::TestCase - setup do - Time.stubs now: Time.now - end - - teardown do - travel_back - end - - def test_time_helper_travel - expected_time = Time.now + 1.day - travel 1.day - - assert_equal expected_time, Time.now - assert_equal expected_time.to_date, Date.today - end - - def test_time_helper_travel_with_block - expected_time = Time.now + 1.day - - travel 1.day do - assert_equal expected_time, Time.now - assert_equal expected_time.to_date, Date.today - end - - assert_not_equal expected_time, Time.now - assert_not_equal expected_time.to_date, Date.today - end - - def test_time_helper_travel_to - expected_time = Time.new(2004, 11, 24, 01, 04, 44) - travel_to expected_time - - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - end - - def test_time_helper_travel_to_with_block - expected_time = Time.new(2004, 11, 24, 01, 04, 44) - - travel_to expected_time do - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - end - - assert_not_equal expected_time, Time.now - assert_not_equal Date.new(2004, 11, 24), Date.today - end - - def test_time_helper_travel_back - expected_time = Time.new(2004, 11, 24, 01, 04, 44) - - travel_to expected_time - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - travel_back - - assert_not_equal expected_time, Time.now - assert_not_equal Date.new(2004, 11, 24), Date.today - end -end diff --git a/app/server/ruby/vendor/activesupport/test/testing/constant_lookup_test.rb b/app/server/ruby/vendor/activesupport/test/testing/constant_lookup_test.rb deleted file mode 100644 index 71a9561189..0000000000 --- a/app/server/ruby/vendor/activesupport/test/testing/constant_lookup_test.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'abstract_unit' -require 'dependencies_test_helpers' - -class Foo; end -class Bar < Foo - def index; end - def self.index; end -end -module FooBar; end - -class ConstantLookupTest < ActiveSupport::TestCase - include ActiveSupport::Testing::ConstantLookup - include DependenciesTestHelpers - - def find_foo(name) - self.class.determine_constant_from_test_name(name) do |constant| - Class === constant && constant < Foo - end - end - - def find_module(name) - self.class.determine_constant_from_test_name(name) do |constant| - Module === constant - end - end - - def test_find_bar_from_foo - assert_equal Bar, find_foo("Bar") - assert_equal Bar, find_foo("Bar::index") - assert_equal Bar, find_foo("Bar::index::authenticated") - assert_equal Bar, find_foo("BarTest") - assert_equal Bar, find_foo("BarTest::index") - assert_equal Bar, find_foo("BarTest::index::authenticated") - end - - def test_find_module - assert_equal FooBar, find_module("FooBar") - assert_equal FooBar, find_module("FooBar::index") - assert_equal FooBar, find_module("FooBar::index::authenticated") - assert_equal FooBar, find_module("FooBarTest") - assert_equal FooBar, find_module("FooBarTest::index") - assert_equal FooBar, find_module("FooBarTest::index::authenticated") - end - - def test_returns_nil_when_cant_find_foo - assert_nil find_foo("DoesntExist") - assert_nil find_foo("DoesntExistTest") - assert_nil find_foo("DoesntExist::Nadda") - assert_nil find_foo("DoesntExist::Nadda::Nope") - assert_nil find_foo("DoesntExist::Nadda::Nope::NotHere") - end - - def test_returns_nil_when_cant_find_module - assert_nil find_module("DoesntExist") - assert_nil find_module("DoesntExistTest") - assert_nil find_module("DoesntExist::Nadda") - assert_nil find_module("DoesntExist::Nadda::Nope") - assert_nil find_module("DoesntExist::Nadda::Nope::NotHere") - end - - def test_does_not_swallow_exception_on_no_method_error - assert_raises(NoMethodError) { - with_autoloading_fixtures { - self.class.determine_constant_from_test_name("RaisesNoMethodError") - } - } - end -end diff --git a/app/server/ruby/vendor/activesupport/test/time_zone_test.rb b/app/server/ruby/vendor/activesupport/test/time_zone_test.rb deleted file mode 100644 index 79ec57af2b..0000000000 --- a/app/server/ruby/vendor/activesupport/test/time_zone_test.rb +++ /dev/null @@ -1,418 +0,0 @@ -require 'abstract_unit' -require 'active_support/time' - -class TimeZoneTest < ActiveSupport::TestCase - def test_utc_to_local - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_equal Time.utc(1999, 12, 31, 19), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500 - assert_equal Time.utc(2000, 6, 30, 20), zone.utc_to_local(Time.utc(2000, 7)) # dst offset -0400 - end - - def test_local_to_utc - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_equal Time.utc(2000, 1, 1, 5), zone.local_to_utc(Time.utc(2000, 1)) # standard offset -0500 - assert_equal Time.utc(2000, 7, 1, 4), zone.local_to_utc(Time.utc(2000, 7)) # dst offset -0400 - end - - def test_period_for_local - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_instance_of TZInfo::TimezonePeriod, zone.period_for_local(Time.utc(2000)) - end - - ActiveSupport::TimeZone::MAPPING.keys.each do |name| - define_method("test_map_#{name.downcase.gsub(/[^a-z]/, '_')}_to_tzinfo") do - zone = ActiveSupport::TimeZone[name] - assert_respond_to zone.tzinfo, :period_for_local - end - end - - def test_from_integer_to_map - assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[-28800] # PST - end - - def test_from_duration_to_map - assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[-480.minutes] # PST - end - - ActiveSupport::TimeZone.all.each do |zone| - name = zone.name.downcase.gsub(/[^a-z]/, '_') - define_method("test_from_#{name}_to_map") do - assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[zone.name] - end - - define_method("test_utc_offset_for_#{name}") do - period = zone.tzinfo.current_period - assert_equal period.utc_offset, zone.utc_offset - end - end - - def test_now - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup - def zone.time_now; Time.local(2000); end - assert_instance_of ActiveSupport::TimeWithZone, zone.now - assert_equal Time.utc(2000,1,1,5), zone.now.utc - assert_equal Time.utc(2000), zone.now.time - assert_equal zone, zone.now.time_zone - end - end - - def test_now_enforces_spring_dst_rules - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup - def zone.time_now - Time.local(2006,4,2,2) # 2AM springs forward to 3AM - end - - assert_equal Time.utc(2006,4,2,3), zone.now.time - assert_equal true, zone.now.dst? - end - end - - def test_now_enforces_fall_dst_rules - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup - def zone.time_now - Time.at(1162098000) # equivalent to 1AM DST - end - assert_equal Time.utc(2006,10,29,1), zone.now.time - assert_equal true, zone.now.dst? - end - end - - def test_unknown_timezones_delegation_to_tzinfo - zone = ActiveSupport::TimeZone['America/Montevideo'] - assert_equal ActiveSupport::TimeZone, zone.class - assert_equal zone.object_id, ActiveSupport::TimeZone['America/Montevideo'].object_id - assert_equal Time.utc(2010, 1, 31, 22), zone.utc_to_local(Time.utc(2010, 2)) # daylight saving offset -0200 - assert_equal Time.utc(2010, 3, 31, 21), zone.utc_to_local(Time.utc(2010, 4)) # standard offset -0300 - end - - def test_today - travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today - travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today - travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today - travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today - travel_back - end - - def test_tomorrow - travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow - travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow - travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow - travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow - travel_back - end - - def test_yesterday - travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST - assert_equal Date.new(1999, 12, 30), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday - travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday - travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday - travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday - travel_back - end - - def test_travel_to_a_date - with_env_tz do - Time.use_zone('Hawaii') do - date = Date.new(2014, 2, 18) - time = date.midnight - - travel_to date do - assert_equal date, Date.current - assert_equal time, Time.current - end - end - end - end - - def test_travel_to_travels_back_and_reraises_if_the_block_raises - ts = Time.current - 1.second - - travel_to ts do - raise - end - - flunk # ensure travel_to re-raises - rescue - assert_not_equal ts, Time.current - end - - def test_local - time = ActiveSupport::TimeZone["Hawaii"].local(2007, 2, 5, 15, 30, 45) - assert_equal Time.utc(2007, 2, 5, 15, 30, 45), time.time - assert_equal ActiveSupport::TimeZone["Hawaii"], time.time_zone - end - - def test_local_with_old_date - time = ActiveSupport::TimeZone["Hawaii"].local(1850, 2, 5, 15, 30, 45) - assert_equal [45,30,15,5,2,1850], time.to_a[0,6] - assert_equal ActiveSupport::TimeZone["Hawaii"], time.time_zone - end - - def test_local_enforces_spring_dst_rules - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.local(2006,4,2,1,59,59) # 1 second before DST start - assert_equal Time.utc(2006,4,2,1,59,59), twz.time - assert_equal Time.utc(2006,4,2,6,59,59), twz.utc - assert_equal false, twz.dst? - assert_equal 'EST', twz.zone - twz2 = zone.local(2006,4,2,2) # 2AM does not exist because at 2AM, time springs forward to 3AM - assert_equal Time.utc(2006,4,2,3), twz2.time # twz is created for 3AM - assert_equal Time.utc(2006,4,2,7), twz2.utc - assert_equal true, twz2.dst? - assert_equal 'EDT', twz2.zone - twz3 = zone.local(2006,4,2,2,30) # 2:30AM does not exist because at 2AM, time springs forward to 3AM - assert_equal Time.utc(2006,4,2,3,30), twz3.time # twz is created for 3:30AM - assert_equal Time.utc(2006,4,2,7,30), twz3.utc - assert_equal true, twz3.dst? - assert_equal 'EDT', twz3.zone - end - - def test_local_enforces_fall_dst_rules - # 1AM during fall DST transition is ambiguous, it could be either DST or non-DST 1AM - # Mirroring Time.local behavior, this method selects the DST time - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.local(2006,10,29,1) - assert_equal Time.utc(2006,10,29,1), twz.time - assert_equal Time.utc(2006,10,29,5), twz.utc - assert_equal true, twz.dst? - assert_equal 'EDT', twz.zone - end - - def test_at - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - secs = 946684800.0 - twz = zone.at(secs) - assert_equal Time.utc(1999,12,31,19), twz.time - assert_equal Time.utc(2000), twz.utc - assert_equal zone, twz.time_zone - assert_equal secs, twz.to_f - end - - def test_at_with_old_date - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - secs = DateTime.civil(1850).to_f - twz = zone.at(secs) - assert_equal [1850, 1, 1, 0], [twz.utc.year, twz.utc.mon, twz.utc.day, twz.utc.hour] - assert_equal zone, twz.time_zone - assert_equal secs, twz.to_f - end - - def test_parse - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse('1999-12-31 19:00:00') - assert_equal Time.utc(1999,12,31,19), twz.time - assert_equal Time.utc(2000), twz.utc - assert_equal zone, twz.time_zone - end - - def test_parse_string_with_timezone - (-11..13).each do |timezone_offset| - zone = ActiveSupport::TimeZone[timezone_offset] - twz = zone.parse('1999-12-31 19:00:00') - assert_equal twz, zone.parse(twz.to_s) - end - end - - def test_parse_with_old_date - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse('1883-12-31 19:00:00') - assert_equal [0,0,19,31,12,1883], twz.to_a[0,6] - assert_equal zone, twz.time_zone - end - - def test_parse_far_future_date_with_time_zone_offset_in_string - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse('2050-12-31 19:00:00 -10:00') # i.e., 2050-01-01 05:00:00 UTC - assert_equal [0,0,0,1,1,2051], twz.to_a[0,6] - assert_equal zone, twz.time_zone - end - - def test_parse_returns_nil_when_string_without_date_information_is_passed_in - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_nil zone.parse('foobar') - assert_nil zone.parse(' ') - end - - def test_parse_with_incomplete_date - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - zone.stubs(:now).returns zone.local(1999,12,31) - twz = zone.parse('19:00:00') - assert_equal Time.utc(1999,12,31,19), twz.time - end - - def test_parse_should_not_black_out_system_timezone_dst_jump - with_env_tz('EET') do - zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] - twz = zone.parse('2012-03-25 03:29:00') - assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6] - end - end - - def test_parse_should_black_out_app_timezone_dst_jump - with_env_tz('EET') do - zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] - twz = zone.parse('2012-03-11 02:29:00') - assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6] - end - end - - def test_parse_with_missing_time_components - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - zone.stubs(:now).returns zone.local(1999, 12, 31, 12, 59, 59) - twz = zone.parse('2012-12-01') - assert_equal Time.utc(2012, 12, 1), twz.time - end - - def test_parse_with_javascript_date - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse("Mon May 28 2012 00:00:00 GMT-0700 (PDT)") - assert_equal Time.utc(2012, 5, 28, 7, 0, 0), twz.utc - end - - def test_parse_doesnt_use_local_dst - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['UTC'] - twz = zone.parse('2013-03-10 02:00:00') - assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time - end - end - - def test_parse_handles_dst_jump - with_env_tz 'US/Eastern' do - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = zone.parse('2013-03-10 02:00:00') - assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time - end - end - - def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize - tzinfo = TZInfo::Timezone.get('America/New_York') - zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo) - assert_equal nil, zone.instance_variable_get('@utc_offset') - assert_equal(-18_000, zone.utc_offset) - end - - def test_seconds_to_utc_offset_with_colon - assert_equal "-06:00", ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) - assert_equal "+00:00", ActiveSupport::TimeZone.seconds_to_utc_offset(0) - assert_equal "+05:00", ActiveSupport::TimeZone.seconds_to_utc_offset(18_000) - end - - def test_seconds_to_utc_offset_without_colon - assert_equal "-0600", ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600, false) - assert_equal "+0000", ActiveSupport::TimeZone.seconds_to_utc_offset(0, false) - assert_equal "+0500", ActiveSupport::TimeZone.seconds_to_utc_offset(18_000, false) - end - - def test_seconds_to_utc_offset_with_negative_offset - assert_equal "-01:00", ActiveSupport::TimeZone.seconds_to_utc_offset(-3_600) - assert_equal "-00:59", ActiveSupport::TimeZone.seconds_to_utc_offset(-3_599) - assert_equal "-05:30", ActiveSupport::TimeZone.seconds_to_utc_offset(-19_800) - end - - def test_formatted_offset_positive - zone = ActiveSupport::TimeZone['New Delhi'] - assert_equal "+05:30", zone.formatted_offset - assert_equal "+0530", zone.formatted_offset(false) - end - - def test_formatted_offset_negative - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert_equal "-05:00", zone.formatted_offset - assert_equal "-0500", zone.formatted_offset(false) - end - - def test_z_format_strings - zone = ActiveSupport::TimeZone['Tokyo'] - twz = zone.now - assert_equal '+0900', twz.strftime('%z') - assert_equal '+09:00', twz.strftime('%:z') - assert_equal '+09:00:00', twz.strftime('%::z') - end - - def test_formatted_offset_zero - zone = ActiveSupport::TimeZone['London'] - assert_equal "+00:00", zone.formatted_offset - assert_equal "UTC", zone.formatted_offset(true, 'UTC') - end - - def test_zone_compare - zone1 = ActiveSupport::TimeZone['Central Time (US & Canada)'] # offset -0600 - zone2 = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] # offset -0500 - assert zone1 < zone2 - assert zone2 > zone1 - assert zone1 == zone1 - end - - def test_zone_match - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - assert zone =~ /Eastern/ - assert zone =~ /New_York/ - assert zone !~ /Nonexistent_Place/ - end - - def test_to_s - assert_equal "(GMT+05:30) New Delhi", ActiveSupport::TimeZone['New Delhi'].to_s - end - - def test_all_sorted - all = ActiveSupport::TimeZone.all - 1.upto( all.length-1 ) do |i| - assert all[i-1] < all[i] - end - end - - def test_index - assert_nil ActiveSupport::TimeZone["bogus"] - assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone["Central Time (US & Canada)"] - assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[8] - assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] } - end - - def test_unknown_zone_should_have_tzinfo_but_exception_on_utc_offset - zone = ActiveSupport::TimeZone.create("bogus") - assert_instance_of TZInfo::TimezoneProxy, zone.tzinfo - assert_raise(TZInfo::InvalidTimezoneIdentifier) { zone.utc_offset } - end - - def test_unknown_zone_with_utc_offset - zone = ActiveSupport::TimeZone.create("bogus", -21_600) - assert_equal(-21_600, zone.utc_offset) - end - - def test_unknown_zones_dont_store_mapping_keys - ActiveSupport::TimeZone["bogus"] - assert !ActiveSupport::TimeZone.zones_map.key?("bogus") - end - - def test_new - assert_equal ActiveSupport::TimeZone["Central Time (US & Canada)"], ActiveSupport::TimeZone.new("Central Time (US & Canada)") - end - - def test_us_zones - assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"]) - assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"]) - end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end -end diff --git a/app/server/ruby/vendor/activesupport/test/transliterate_test.rb b/app/server/ruby/vendor/activesupport/test/transliterate_test.rb deleted file mode 100644 index e0f85f4e7c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/transliterate_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -# encoding: utf-8 -require 'abstract_unit' -require 'active_support/inflector/transliterate' - -class TransliterateTest < ActiveSupport::TestCase - def test_transliterate_should_not_change_ascii_chars - (0..127).each do |byte| - char = [byte].pack("U") - assert_equal char, ActiveSupport::Inflector.transliterate(char) - end - end - - def test_transliterate_should_approximate_ascii - # create string with range of Unicode"s western characters with - # diacritics, excluding the division and multiplication signs which for - # some reason or other are floating in the middle of all the letters. - string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include?(c)}.pack("U*") - string.each_char do |char| - assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(char) - end - end - - def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8 - char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS - I18n.backend.store_translations(:de, :i18n => {:transliterate => {:rule => {"ü" => "ue"}}}) - default_locale, I18n.locale = I18n.locale, :de - assert_equal "ue", ActiveSupport::Inflector.transliterate(char) - ensure - I18n.locale = default_locale - end - - def test_transliterate_should_allow_a_custom_replacement_char - assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*") - end -end diff --git a/app/server/ruby/vendor/activesupport/test/xml_mini/jdom_engine_test.rb b/app/server/ruby/vendor/activesupport/test/xml_mini/jdom_engine_test.rb deleted file mode 100644 index ed4de8aba2..0000000000 --- a/app/server/ruby/vendor/activesupport/test/xml_mini/jdom_engine_test.rb +++ /dev/null @@ -1,188 +0,0 @@ -if RUBY_PLATFORM =~ /java/ - require 'abstract_unit' - require 'active_support/xml_mini' - require 'active_support/core_ext/hash/conversions' - - - class JDOMEngineTest < ActiveSupport::TestCase - include ActiveSupport - - FILES_DIR = File.dirname(__FILE__) + '/../fixtures/xml' - - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'JDOM' - end - - def teardown - XmlMini.backend = @default_backend - end - - def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) - - - - - eoxml - assert hash.has_key?('blog') - assert hash['blog'].has_key?('logo') - - file = hash['blog']['logo'] - assert_equal 'logo.png', file.original_filename - assert_equal 'image/png', file.content_type - end - - def test_not_allowed_to_expand_entities_to_files - attack_xml = <<-EOT - - ]> - x&a; - EOT - assert_equal 'x', Hash.from_xml(attack_xml)["member"] - end - - def test_not_allowed_to_expand_parameter_entities_to_files - attack_xml = <<-EOT - - %b; - ]> - x&a; - EOT - assert_raise Java::OrgXmlSax::SAXParseException do - assert_equal 'x', Hash.from_xml(attack_xml)["member"] - end - end - - - def test_not_allowed_to_load_external_doctypes - attack_xml = <<-EOT - - x&a; - EOT - assert_equal 'x', Hash.from_xml(attack_xml)["member"] - end - - def test_exception_thrown_on_expansion_attack - assert_raise Java::OrgXmlSax::SAXParseException do - attack_xml = <<-EOT - - - - - - - - ]> - - &a; - - EOT - Hash.from_xml(attack_xml) - end - end - - def test_setting_JDOM_as_backend - XmlMini.backend = 'JDOM' - assert_equal XmlMini_JDOM, XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - - - a post - another post - - - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - - - - - eoxml - end - - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - - hello world - - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - - - hello everyone - - - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) - end - end - -else - # don't run these test because we aren't running in JRuby -end diff --git a/app/server/ruby/vendor/activesupport/test/xml_mini/libxml_engine_test.rb b/app/server/ruby/vendor/activesupport/test/xml_mini/libxml_engine_test.rb deleted file mode 100644 index a8df2e1f7b..0000000000 --- a/app/server/ruby/vendor/activesupport/test/xml_mini/libxml_engine_test.rb +++ /dev/null @@ -1,204 +0,0 @@ -begin - require 'libxml' -rescue LoadError - # Skip libxml tests -else -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/conversions' - -class LibxmlEngineTest < ActiveSupport::TestCase - include ActiveSupport - - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'LibXML' - - LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do - end - - def teardown - XmlMini.backend = @default_backend - end - - def test_exception_thrown_on_expansion_attack - assert_raise LibXML::XML::Error do - attack_xml = %{ - - - - - - - - ]> - - &a; - - } - Hash.from_xml(attack_xml) - end - end - - def test_setting_libxml_as_backend - XmlMini.backend = 'LibXML' - assert_equal XmlMini_LibXML, XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - - - a post - another post - - - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - - - - - eoxml - end - - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - - hello world - - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - - - hello everyone - - - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - end - - def test_parse_from_io - io = StringIO.new(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - assert_equal_rexml(io) - end - - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) - - - hello - morning - - - eoxml - end - - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) - end -end - -end diff --git a/app/server/ruby/vendor/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/app/server/ruby/vendor/activesupport/test/xml_mini/libxmlsax_engine_test.rb deleted file mode 100644 index d6d90639e2..0000000000 --- a/app/server/ruby/vendor/activesupport/test/xml_mini/libxmlsax_engine_test.rb +++ /dev/null @@ -1,195 +0,0 @@ -begin - require 'libxml' -rescue LoadError - # Skip libxml tests -else -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/conversions' - -class LibXMLSAXEngineTest < ActiveSupport::TestCase - include ActiveSupport - - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'LibXMLSAX' - end - - def teardown - XmlMini.backend = @default_backend - end - - def test_exception_thrown_on_expansion_attack - assert_raise LibXML::XML::Error do - attack_xml = <<-EOT - - - - - - - - - ]> - - &a; - - EOT - - Hash.from_xml(attack_xml) - end - end - - def test_setting_libxml_as_backend - XmlMini.backend = 'LibXMLSAX' - assert_equal XmlMini_LibXMLSAX, XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - - - a post - another post - - - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - - - - - eoxml - end - - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - - hello world - - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - - - hello everyone - - - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - end - - def test_parse_from_io - io = StringIO.new(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - assert_equal_rexml(io) - end - - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) - - - hello - morning - - - eoxml - end - - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) - end -end - -end diff --git a/app/server/ruby/vendor/activesupport/test/xml_mini/nokogiri_engine_test.rb b/app/server/ruby/vendor/activesupport/test/xml_mini/nokogiri_engine_test.rb deleted file mode 100644 index 2e962576b5..0000000000 --- a/app/server/ruby/vendor/activesupport/test/xml_mini/nokogiri_engine_test.rb +++ /dev/null @@ -1,217 +0,0 @@ -begin - require 'nokogiri' -rescue LoadError - # Skip nokogiri tests -else -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/conversions' - -class NokogiriEngineTest < ActiveSupport::TestCase - include ActiveSupport - - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'Nokogiri' - end - - def teardown - XmlMini.backend = @default_backend - end - - def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) - - - - - eoxml - assert hash.has_key?('blog') - assert hash['blog'].has_key?('logo') - - file = hash['blog']['logo'] - assert_equal 'logo.png', file.original_filename - assert_equal 'image/png', file.content_type - end - - def test_exception_thrown_on_expansion_attack - assert_raise Nokogiri::XML::SyntaxError do - attack_xml = <<-EOT - - - - - - - - - ]> - - &a; - - EOT - Hash.from_xml(attack_xml) - end - end - - def test_setting_nokogiri_as_backend - XmlMini.backend = 'Nokogiri' - assert_equal XmlMini_Nokogiri, XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - - - a post - another post - - - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - - - - - eoxml - end - - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - - hello world - - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - - - hello everyone - - - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - end - - def test_parse_from_io - io = StringIO.new(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - assert_equal_rexml(io) - end - - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) - - - hello - morning - - - eoxml - end - - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) - end -end - -end diff --git a/app/server/ruby/vendor/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/app/server/ruby/vendor/activesupport/test/xml_mini/nokogirisax_engine_test.rb deleted file mode 100644 index 4f078f31e0..0000000000 --- a/app/server/ruby/vendor/activesupport/test/xml_mini/nokogirisax_engine_test.rb +++ /dev/null @@ -1,218 +0,0 @@ -begin - require 'nokogiri' -rescue LoadError - # Skip nokogiri tests -else -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/core_ext/hash/conversions' - -class NokogiriSAXEngineTest < ActiveSupport::TestCase - include ActiveSupport - - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'NokogiriSAX' - end - - def teardown - XmlMini.backend = @default_backend - end - - def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) - - - - - eoxml - assert hash.has_key?('blog') - assert hash['blog'].has_key?('logo') - - file = hash['blog']['logo'] - assert_equal 'logo.png', file.original_filename - assert_equal 'image/png', file.content_type - end - - def test_exception_thrown_on_expansion_attack - assert_raise RuntimeError do - attack_xml = <<-EOT - - - - - - - - - ]> - - &a; - - EOT - - Hash.from_xml(attack_xml) - end - end - - def test_setting_nokogirisax_as_backend - XmlMini.backend = 'NokogiriSAX' - assert_equal XmlMini_NokogiriSAX, XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - - - a post - another post - - - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - - - - - eoxml - end - - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - - hello world - - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - - - hello everyone - - - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - end - - def test_parse_from_io - io = StringIO.new(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - assert_equal_rexml(io) - end - - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) - - - - - - eoxml - end - - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) - - - hello - morning - - - eoxml - end - - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) - - - - eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) - end -end - -end diff --git a/app/server/ruby/vendor/activesupport/test/xml_mini/rexml_engine_test.rb b/app/server/ruby/vendor/activesupport/test/xml_mini/rexml_engine_test.rb deleted file mode 100644 index 0c1f11803c..0000000000 --- a/app/server/ruby/vendor/activesupport/test/xml_mini/rexml_engine_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'abstract_unit' -require 'active_support/xml_mini' - -class REXMLEngineTest < ActiveSupport::TestCase - include ActiveSupport - - def test_default_is_rexml - assert_equal XmlMini_REXML, XmlMini.backend - end - - def test_set_rexml_as_backend - XmlMini.backend = 'REXML' - assert_equal XmlMini_REXML, XmlMini.backend - end - - def test_parse_from_io - XmlMini.backend = 'REXML' - io = StringIO.new(<<-eoxml) - - good - - hello everyone - - morning - - eoxml - assert_equal_rexml(io) - end - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) - end -end diff --git a/app/server/ruby/vendor/activesupport/test/xml_mini_test.rb b/app/server/ruby/vendor/activesupport/test/xml_mini_test.rb deleted file mode 100644 index 753effb54e..0000000000 --- a/app/server/ruby/vendor/activesupport/test/xml_mini_test.rb +++ /dev/null @@ -1,296 +0,0 @@ -require 'abstract_unit' -require 'active_support/xml_mini' -require 'active_support/builder' - -module XmlMiniTest - class RenameKeyTest < ActiveSupport::TestCase - def test_rename_key_dasherizes_by_default - assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key") - end - - def test_rename_key_does_nothing_with_dasherize_true - assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true) - end - - def test_rename_key_does_nothing_with_dasherize_false - assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false) - end - - def test_rename_key_camelizes_with_camelize_true - assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true) - end - - def test_rename_key_lower_camelizes_with_camelize_lower - assert_equal "myKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :lower) - end - - def test_rename_key_lower_camelizes_with_camelize_upper - assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :upper) - end - - def test_rename_key_does_not_dasherize_leading_underscores - assert_equal "_id", ActiveSupport::XmlMini.rename_key("_id") - end - - def test_rename_key_with_leading_underscore_dasherizes_interior_underscores - assert_equal "_my-key", ActiveSupport::XmlMini.rename_key("_my_key") - end - - def test_rename_key_does_not_dasherize_trailing_underscores - assert_equal "id_", ActiveSupport::XmlMini.rename_key("id_") - end - - def test_rename_key_with_trailing_underscore_dasherizes_interior_underscores - assert_equal "my-key_", ActiveSupport::XmlMini.rename_key("my_key_") - end - - def test_rename_key_does_not_dasherize_multiple_leading_underscores - assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id") - end - - def test_rename_key_does_not_dasherize_multiple_trailing_underscores - assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__") - end - end - - class ToTagTest < ActiveSupport::TestCase - def assert_xml(xml) - assert_equal xml, @options[:builder].target! - end - - def setup - @xml = ActiveSupport::XmlMini - @options = {:skip_instruct => true, :builder => Builder::XmlMarkup.new} - end - - test "#to_tag accepts a callable object and passes options with the builder" do - @xml.to_tag(:some_tag, lambda {|o| o[:builder].br }, @options) - assert_xml "
" - end - - test "#to_tag accepts a callable object and passes options and tag name" do - @xml.to_tag(:tag, lambda {|o, t| o[:builder].b(t) }, @options) - assert_xml "tag" - end - - test "#to_tag accepts an object responding to #to_xml and passes the options, where :root is key" do - obj = Object.new - obj.instance_eval do - def to_xml(options) options[:builder].yo(options[:root].to_s) end - end - - @xml.to_tag(:tag, obj, @options) - assert_xml "tag" - end - - test "#to_tag accepts arbitrary objects responding to #to_str" do - @xml.to_tag(:b, "Howdy", @options) - assert_xml "Howdy" - end - - test "#to_tag should dasherize the space when passed a string with spaces as a key" do - @xml.to_tag("New York", 33, @options) - assert_xml "33" - end - - test "#to_tag should dasherize the space when passed a symbol with spaces as a key" do - @xml.to_tag(:"New York", 33, @options) - assert_xml "33" - end - # TODO: test the remaining functions hidden in #to_tag. - end - - class WithBackendTest < ActiveSupport::TestCase - module REXML end - module LibXML end - module Nokogiri end - - setup do - @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend - end - - teardown do - ActiveSupport::XmlMini.backend = @default_backend - end - - test "#with_backend should switch backend and then switch back" do - @xml.backend = REXML - @xml.with_backend(LibXML) do - assert_equal LibXML, @xml.backend - @xml.with_backend(Nokogiri) do - assert_equal Nokogiri, @xml.backend - end - assert_equal LibXML, @xml.backend - end - assert_equal REXML, @xml.backend - end - - test "backend switch inside #with_backend block" do - @xml.with_backend(LibXML) do - @xml.backend = REXML - assert_equal REXML, @xml.backend - end - assert_equal REXML, @xml.backend - end - end - - class ThreadSafetyTest < ActiveSupport::TestCase - module REXML end - module LibXML end - - setup do - @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend - end - - teardown do - ActiveSupport::XmlMini.backend = @default_backend - end - - test "#with_backend should be thread-safe" do - @xml.backend = REXML - t = Thread.new do - @xml.with_backend(LibXML) { sleep 1 } - end - sleep 0.1 while t.status != "sleep" - - # We should get `old_backend` here even while another - # thread is using `new_backend`. - assert_equal REXML, @xml.backend - end - - test "nested #with_backend should be thread-safe" do - @xml.with_backend(REXML) do - t = Thread.new do - @xml.with_backend(LibXML) { sleep 1 } - end - sleep 0.1 while t.status != "sleep" - - assert_equal REXML, @xml.backend - end - end - end - - class ParsingTest < ActiveSupport::TestCase - def setup - @parsing = ActiveSupport::XmlMini::PARSING - end - - def test_symbol - parser = @parsing['symbol'] - assert_equal :symbol, parser.call('symbol') - assert_equal :symbol, parser.call(:symbol) - assert_equal :'123', parser.call(123) - assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } - end - - def test_date - parser = @parsing['date'] - assert_equal Date.new(2013,11,12), parser.call("2013-11-12T0211Z") - assert_raises(TypeError) { parser.call(1384190018) } - assert_raises(ArgumentError) { parser.call("not really a date") } - end - - def test_datetime - parser = @parsing['datetime'] - assert_equal Time.new(2013,11,12,02,11,00,0), parser.call("2013-11-12T02:11:00Z") - assert_equal DateTime.new(2013,11,12), parser.call("2013-11-12T0211Z") - assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T02:11Z") - assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T11:11+9") - assert_raises(ArgumentError) { parser.call("1384190018") } - end - - def test_integer - parser = @parsing['integer'] - assert_equal 123, parser.call(123) - assert_equal 123, parser.call(123.003) - assert_equal 123, parser.call("123") - assert_equal 0, parser.call("") - assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } - end - - def test_float - parser = @parsing['float'] - assert_equal 123, parser.call("123") - assert_equal 123.003, parser.call("123.003") - assert_equal 123.0, parser.call("123,003") - assert_equal 0.0, parser.call("") - assert_equal 123, parser.call(123) - assert_equal 123.05, parser.call(123.05) - assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } - end - - def test_decimal - parser = @parsing['decimal'] - assert_equal 123, parser.call("123") - assert_equal 123.003, parser.call("123.003") - assert_equal 123.0, parser.call("123,003") - assert_equal 0.0, parser.call("") - assert_equal 123, parser.call(123) - assert_raises(ArgumentError) { parser.call(123.04) } - assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } - end - - def test_boolean - parser = @parsing['boolean'] - [1, true, "1"].each do |value| - assert parser.call(value) - end - - [0, false, "0"].each do |value| - assert_not parser.call(value) - end - end - - def test_string - parser = @parsing['string'] - assert_equal "123", parser.call(123) - assert_equal "123", parser.call("123") - assert_equal "[]", parser.call("[]") - assert_equal "[]", parser.call([]) - assert_equal "{}", parser.call({}) - assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } - end - - def test_yaml - yaml = < [ - {"sku"=>"BL394D", "quantity"=>4, "description"=>"Basketball"} - ] - } - parser = @parsing['yaml'] - assert_equal(expected, parser.call(yaml)) - assert_equal({1 => 'test'}, parser.call({1 => 'test'})) - assert_equal({"1 => 'test'"=>nil}, parser.call("{1 => 'test'}")) - end - - def test_base64Binary_and_binary - base64 = < 'base64') - assert_equal "IGNORED INPUT", parser.call("IGNORED INPUT", {}) - end - end -end diff --git a/app/server/ruby/vendor/atomic/.gitignore b/app/server/ruby/vendor/atomic/.gitignore deleted file mode 100755 index 5f3f73d5aa..0000000000 --- a/app/server/ruby/vendor/atomic/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.*.sw? -lib/atomic_reference.jar -/nbproject -ext/*.bundle -ext/*.so -ext/*.jar -pkg -*.gem diff --git a/app/server/ruby/vendor/atomic/.travis.yml b/app/server/ruby/vendor/atomic/.travis.yml deleted file mode 100755 index 315bff925e..0000000000 --- a/app/server/ruby/vendor/atomic/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: ruby -rvm: - - 2.0.0 - - 1.9.3 - - 1.8.7 - - jruby-18mode # JRuby in 1.8 mode - - jruby-19mode # JRuby in 1.9 mode - - rbx-2 -jdk: - - oraclejdk8 diff --git a/app/server/ruby/vendor/atomic/Gemfile b/app/server/ruby/vendor/atomic/Gemfile deleted file mode 100755 index 55034a9e7e..0000000000 --- a/app/server/ruby/vendor/atomic/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source "https://rubygems.org" - -gem 'rake-compiler' -gem 'minitest', '>= 5.0.0', :group => :development diff --git a/app/server/ruby/vendor/atomic/LICENSE b/app/server/ruby/vendor/atomic/LICENSE deleted file mode 100755 index 533671897f..0000000000 --- a/app/server/ruby/vendor/atomic/LICENSE +++ /dev/null @@ -1,144 +0,0 @@ -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as -defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that -is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that -control, are controlled by, or are under common control with that entity. For the purposes -of this definition, "control" means (i) the power, direct or indirect, to cause the -direction or management of such entity, whether by contract or otherwise, or (ii) ownership -of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of -such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by -this License. - -"Source" form shall mean the preferred form for making modifications, including but not -limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of -a Source form, including but not limited to compiled object code, generated documentation, -and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available -under the License, as indicated by a copyright notice that is included in or attached to the -work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on -(or derived from) the Work and for which the editorial revisions, annotations, elaborations, -or other modifications represent, as a whole, an original work of authorship. For the -purposes of this License, Derivative Works shall not include works that remain separable -from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works -thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work -and any modifications or additions to that Work or Derivative Works thereof, that is -intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by -an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the -purposes of this definition, "submitted" means any form of electronic, verbal, or written -communication sent to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and issue tracking -systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and -improving the Work, but excluding communication that is conspicuously marked or otherwise -designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a -Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each -Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, -royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such Derivative -Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each -Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, -royalty-free, irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license -applies only to those patent claims licensable by such Contributor that are necessarily -infringed by their Contribution(s) alone or by combination of their Contribution(s) with the -Work to which such Contribution(s) was submitted. If You institute patent litigation against -any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or -a Contribution incorporated within the Work constitutes direct or contributory patent -infringement, then any patent licenses granted to You under this License for that Work shall -terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works -thereof in any medium, with or without modifications, and in Source or Object form, provided -that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; -and - -You must cause any modified files to carry prominent notices stating that You changed the -files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all -copyright, patent, trademark, and attribution notices from the Source form of the Work, -excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative -Works that You distribute must include a readable copy of the attribution notices contained -within such NOTICE file, excluding those notices that do not pertain to any part of the -Derivative Works, in at least one of the following places: within a NOTICE text file -distributed as part of the Derivative Works; within the Source form or documentation, if -provided along with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of the NOTICE -file are for informational purposes only and do not modify the License. You may add Your own -attribution notices within Derivative Works that You distribute, alongside or as an addendum -to the NOTICE text from the Work, provided that such additional attribution notices cannot -be construed as modifying the License. You may add Your own copyright statement to Your -modifications and may provide additional or different license terms and conditions for use, -reproduction, or distribution of Your modifications, or for any such Derivative Works as a -whole, provided Your use, reproduction, and distribution of the Work otherwise complies with -the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution -intentionally submitted for inclusion in the Work by You to the Licensor shall be under the -terms and conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of any -separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for reasonable and -customary use in describing the origin of the Work and reproducing the content of the NOTICE -file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, -Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" -BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, -without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, -MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for -determining the appropriateness of using or redistributing the Work and assume any risks -associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort -(including negligence), contract, or otherwise, unless required by applicable law (such as -deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, or -consequential damages of any character arising as a result of this License or out of the use -or inability to use the Work (including but not limited to damages for loss of goodwill, -work stoppage, computer failure or malfunction, or any and all other commercial damages or -losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative -Works thereof, You may choose to offer, and charge a fee for, acceptance of support, -warranty, indemnity, or other liability obligations and/or rights consistent with this -License. However, in accepting such obligations, You may act only on Your own behalf and on -Your sole responsibility, not on behalf of any other Contributor, and only if You agree to -indemnify, defend, and hold each Contributor harmless for any liability incurred by, or -claims asserted against, such Contributor by reason of your accepting any such warranty or -additional liability. - -END OF TERMS AND CONDITIONS diff --git a/app/server/ruby/vendor/atomic/README.md b/app/server/ruby/vendor/atomic/README.md deleted file mode 100755 index 6001f9fbb6..0000000000 --- a/app/server/ruby/vendor/atomic/README.md +++ /dev/null @@ -1,53 +0,0 @@ -atomic: An atomic reference implementation for JRuby, Rubinius, and MRI. -======================================================================== - -[![Build Status](https://travis-ci.org/headius/ruby-atomic.png?branch=master)](https://travis-ci.org/headius/ruby-atomic) - -Summary -======= - -This library provides: - -* an Atomic class that guarantees atomic updates to its contained value - -The Atomic class provides accessors for the contained "value" plus two update methods: - -* update will run the provided block, passing the current value and replacing it with the block result if the value has not been changed in the meantime. It may run the block repeatedly if there are other concurrent updates in progress. -* try_update will run the provided block, passing the current value and replacing it with the block result. If the value changes before the update can happen, it will throw an Atomic::ConcurrentUpdateError. - -The atomic repository is at http://github.com/headius/ruby-atomic. - -Usage -===== - -The simplest way to use "atomic" is to call the "update" or "try_update" methods. - -"try_update" and "update" both call the given block, passing the current value and using the block's result as the new value. If the value is updated by another thread before the block completes, "try update" raises a ConcurrentUpdateError and "update" retries the block. Because "update" may call the block several times when multiple threads are all updating the same value, the block's logic should be kept as simple as possible. - -```ruby -require 'atomic' - -my_atomic = Atomic.new(0) -my_atomic.update {|v| v + 1} -begin - my_atomic.try_update {|v| v + 1} -rescue Atomic::ConcurrentUpdateError => cue - # deal with it (retry, propagate, etc) -end -``` - -It's also possible to use the regular get/set operations on the Atomic, if you want to avoid the exception and respond to contended changes in some other way. - -```ruby -my_atomic = Atomic.new(0) -my_atomic.value # => 0 -my_atomic.value = 1 -my_atomic.swap(2) # => 1 -my_atomic.compare_and_swap(2, 3) # => true, updated to 3 -my_atomic.compare_and_swap(2, 3) # => false, current is not 2 -``` - -Building -======== - -As of 1.1.0, JDK8 is required to build the atomic gem, since it attempts to use the new atomic Unsafe.getAndSetObject method only in JDK8. The resulting code should still work fine as far back as Java 5. diff --git a/app/server/ruby/vendor/atomic/Rakefile b/app/server/ruby/vendor/atomic/Rakefile deleted file mode 100755 index a0e109c75b..0000000000 --- a/app/server/ruby/vendor/atomic/Rakefile +++ /dev/null @@ -1,63 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'rake' -require 'rake/testtask' - -task :default => :test - -desc "Run tests" -Rake::TestTask.new :test do |t| - t.libs << "lib" - t.libs << "ext" - t.test_files = FileList["test/**/*.rb"] -end - -desc "Run benchmarks" -task :bench do - exec "ruby -Ilib -Iext test/bench_atomic.rb" -end - -if defined?(JRUBY_VERSION) - require 'ant' - - directory "pkg/classes" - - desc "Clean up build artifacts" - task :clean do - rm_rf "pkg/classes" - rm_rf "lib/refqueue.jar" - end - - desc "Compile the extension" - task :compile_java => "pkg/classes" do |t| - ant.javac :srcdir => "ext", :destdir => t.prerequisites.first, - :source => "1.5", :target => "1.5", :debug => true, - :classpath => "${java.class.path}:${sun.boot.class.path}" - end - - desc "Build the jar" - task :jar => :compile_java do - ant.jar :basedir => "pkg/classes", :destfile => "lib/atomic_reference.jar", :includes => "**/*.class" - end - - task :compile => :jar -else - require "rake/extensiontask" - Rake::ExtensionTask.new "atomic" do |ext| - ext.ext_dir = 'ext' - ext.name ='atomic_reference' - end -end - -task :package => :compile -task :test => :compile diff --git a/app/server/ruby/vendor/atomic/atomic.gemspec b/app/server/ruby/vendor/atomic/atomic.gemspec deleted file mode 100755 index 3f65f940e7..0000000000 --- a/app/server/ruby/vendor/atomic/atomic.gemspec +++ /dev/null @@ -1,24 +0,0 @@ -# -*- encoding: utf-8 -*- - -# Update these to get proper version and commit history - -Gem::Specification.new do |s| - s.name = %q{atomic} - s.version = "1.1.16" - s.authors = ["Charles Oliver Nutter", "MenTaLguY", "Sokolov Yura"] - s.date = Time.now.strftime('%Y-%m-%d') - s.summary = "An atomic reference implementation for JRuby, Rubinius, and MRI" - s.description = s.summary - s.email = ["headius@headius.com", "mental@rydia.net", "funny.falcon@gmail.com"] - s.homepage = "http://github.com/headius/ruby-atomic" - s.require_paths = ["lib"] - s.licenses = ["Apache-2.0"] - s.test_files = Dir["test/test*.rb"] - if defined?(JRUBY_VERSION) - s.files = Dir['lib/atomic_reference.jar'] - s.platform = 'java' - else - s.extensions = 'ext/extconf.rb' - end - s.files += `git ls-files`.lines.map(&:chomp) -end diff --git a/app/server/ruby/vendor/atomic/examples/atomic_example.rb b/app/server/ruby/vendor/atomic/examples/atomic_example.rb deleted file mode 100755 index 115cd862ca..0000000000 --- a/app/server/ruby/vendor/atomic/examples/atomic_example.rb +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'atomic' - -my_atomic = Atomic.new(0) -my_atomic.update {|v| v + 1} -puts "new value: #{my_atomic.value}" - -begin - my_atomic.try_update {|v| v + 1} -rescue Atomic::ConcurrentUpdateError => cue - # deal with it (retry, propagate, etc) -end -puts "new value: #{my_atomic.value}" diff --git a/app/server/ruby/vendor/atomic/examples/bench_atomic.rb b/app/server/ruby/vendor/atomic/examples/bench_atomic.rb deleted file mode 100755 index 09a7b06b22..0000000000 --- a/app/server/ruby/vendor/atomic/examples/bench_atomic.rb +++ /dev/null @@ -1,121 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'benchmark' -require 'atomic' -require 'thread' -Thread.abort_on_exception = true - -$go = false # for synchronizing parallel threads - -# number of updates on the value -N = ARGV[1] ? ARGV[1].to_i : 100_000 - -# number of threads for parallel test -M = ARGV[0] ? ARGV[0].to_i : 100 - - -puts "*** Sequential updates ***" -Benchmark.bm(10) do |x| - value = 0 - x.report "no lock" do - N.times do - value += 1 - end - end - - @lock = Mutex.new - x.report "mutex" do - value = 0 - N.times do - @lock.synchronize do - value += 1 - end - end - end - - @atom = Atomic.new(0) - x.report "atomic" do - N.times do - @atom.update{|x| x += 1} - end - end -end - -def para_setup(num_threads, count, &block) - if num_threads % 2 > 0 - raise ArgumentError, "num_threads must be a multiple of two" - end - raise ArgumentError, "need block" unless block_given? - - # Keep those threads together - tg = ThreadGroup.new - - num_threads.times do |i| - diff = (i % 2 == 0) ? 1 : -1 - - t = Thread.new do - nil until $go - count.times do - yield diff - end - end - - tg.add(t) - end - - # Make sure all threads are started - while tg.list.find{|t| t.status != "run"} - Thread.pass - end - - # For good measure - GC.start - - tg -end - -def para_run(tg) - $go = true - tg.list.each{|t| t.join} - $go = false -end - -puts "*** Parallel updates ***" -Benchmark.bm(10) do |bm| - # This is not secure - value = 0 - tg = para_setup(M, N/M) do |diff| - value += diff - end - bm.report("no lock"){ para_run(tg) } - - - value = 0 - @lock = Mutex.new - tg = para_setup(M, N/M) do |diff| - @lock.synchronize do - value += diff - end - end - bm.report("mutex"){ para_run(tg) } - raise unless value == 0 - - - @atom = Atomic.new(0) - tg = para_setup(M, N/M) do |diff| - @atom.update{|x| x + diff} - end - bm.report("atomic"){ para_run(tg) } - raise unless @atom.value == 0 - -end diff --git a/app/server/ruby/vendor/atomic/ext/AtomicReferenceService.java b/app/server/ruby/vendor/atomic/ext/AtomicReferenceService.java deleted file mode 100755 index 8ecd08776f..0000000000 --- a/app/server/ruby/vendor/atomic/ext/AtomicReferenceService.java +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import java.io.IOException; - -import org.jruby.Ruby; -import org.jruby.runtime.load.BasicLibraryService; - -public class AtomicReferenceService implements BasicLibraryService { - public boolean basicLoad(final Ruby runtime) throws IOException { - new org.jruby.ext.atomic.AtomicReferenceLibrary().load(runtime, false); - return true; - } -} - diff --git a/app/server/ruby/vendor/atomic/ext/extconf.rb b/app/server/ruby/vendor/atomic/ext/extconf.rb deleted file mode 100755 index 6baa607fe7..0000000000 --- a/app/server/ruby/vendor/atomic/ext/extconf.rb +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'mkmf' -extension_name = 'atomic_reference' -dir_config(extension_name) - -have_header "libkern/OSAtomic.h" - -def compiler_is_gcc - if CONFIG["GCC"] && CONFIG["GCC"] != "" - return true - elsif ( # This could stand to be more generic... but I am afraid. - CONFIG["CC"] =~ /\bgcc\b/ - ) - return true - end - return false -end - - -if compiler_is_gcc - case CONFIG["arch"] - when /mswin32|mingw|solaris/ - $CFLAGS += " -march=native" - when 'i686-linux' - $CFLAGS += " -march=i686" - end -end - -try_run(< #{i + 1}" - end - - atomic.set(min_8) - min_8.downto(min_8 - 2) do |i| - assert atomic.compare_and_swap(i, i-1), "CAS failed for numeric #{i} => #{i - 1}" - end - - # 64-bit idempotent Fixnum (MRI, Rubinius) - max_64 = 2**62 - 1 - min_64 = -(2**62) - - atomic.set(max_64) - max_64.upto(max_64 + 2) do |i| - assert atomic.compare_and_swap(i, i+1), "CAS failed for numeric #{i} => #{i + 1}" - end - - atomic.set(min_64) - min_64.downto(min_64 - 2) do |i| - assert atomic.compare_and_swap(i, i-1), "CAS failed for numeric #{i} => #{i - 1}" - end - - # 64-bit overflow into Bignum (JRuby) - max_64 = 2**63 - 1 - min_64 = (-2**63) - - atomic.set(max_64) - max_64.upto(max_64 + 2) do |i| - assert atomic.compare_and_swap(i, i+1), "CAS failed for numeric #{i} => #{i + 1}" - end - - atomic.set(min_64) - min_64.downto(min_64 - 2) do |i| - assert atomic.compare_and_swap(i, i-1), "CAS failed for numeric #{i} => #{i - 1}" - end - - # non-idempotent Float (JRuby, Rubinius, MRI < 2.0.0 or 32-bit) - atomic.set(1.0 + 0.1) - assert atomic.compare_and_set(1.0 + 0.1, 1.2), "CAS failed for #{1.0 + 0.1} => 1.2" - - # Bignum - atomic.set(2**100) - assert atomic.compare_and_set(2**100, 0), "CAS failed for #{2**100} => 0" - - # Rational - require 'rational' unless ''.respond_to? :to_r - atomic.set(Rational(1,3)) - assert atomic.compare_and_set(Rational(1,3), 0), "CAS failed for #{Rational(1,3)} => 0" - - # Complex - require 'complex' unless ''.respond_to? :to_c - atomic.set(Complex(1,2)) - assert atomic.compare_and_set(Complex(1,2), 0), "CAS failed for #{Complex(1,2)} => 0" - end -end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/CONTRIBUTING.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/CONTRIBUTING.md new file mode 100644 index 0000000000..a865b48b3a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/CONTRIBUTING.md @@ -0,0 +1,134 @@ +# Contributing to Concurrent Ruby + +You want to contribute? Thank you! Concurrent Ruby is work of [many contributors](https://github.com/ruby-concurrency/concurrent-ruby/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/ruby-concurrency/concurrent-ruby/pulls), [propose features and discuss issues](https://github.com/ruby-concurrency/concurrent-ruby/issues). When in doubt, ask a question in the [Concurrent Ruby gitter.im chatroom](https://gitter.im/ruby-concurrency/concurrent-ruby). + +#### Find Something to Work on + +If you want to contribute but aren't sure what to work on, there are tasks marked with [**looking-for-contributor**](https://github.com/ruby-concurrency/concurrent-ruby/issues?q=is%3Aissue+is%3Aopen+label%3Alooking-for-contributor) label. Complete list of tasks can be found on [issues page](https://github.com/ruby-concurrency/concurrent-ruby/issues). We appreciate your help. + +Before starting, feel free to chat with us on [gitter](https://gitter.im/ruby-concurrency/concurrent-ruby). + +#### Fork the Project + +Fork the [project on Github](https://github.com/ruby-concurrency/concurrent-ruby) and check out your copy. + +``` +git clone https://github.com/contributor/concurrent-ruby.git +cd concurrent-ruby +git remote add upstream https://github.com/ruby-concurrency/concurrent-ruby.git +``` + +#### Create a Topic Branch + +Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. + +``` +git checkout master +git pull upstream master +git checkout -b my-feature-branch +``` + +#### Bundle Install, Build and Test + + +Ensure that you can build the project and run tests. + +Please note that Concurrent Ruby includes native Java and (optional) C extensions. When building you will need the appropriate build tools for your platform. If you don't have the build tools installed that's OK. The tests can still be run. A few tests may fail, but these are just the tests that verify that the extensions exist. You can usually ignore these locally if you are working on the pure Ruby parts of the library (which is most of the lib). + +``` +bundle install +bundle exec rake +``` + +#### Write Tests + +Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [specs](https://github.com/ruby-concurrency/concurrent-ruby/tree/master/spec). + +We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. + +#### Write Code + +Implement your feature or bug fix. + +Make sure that `bundle exec rake` completes without errors. + +#### Follow the Guidelines + +There are a few guidelines which we follow when adding features. Consider that submissions which do not follow these guidelines will require modification before acceptance. + +* **No downstream dependencies:** Concurrent Ruby is a foundational library used by major projects like [Rails](http://rubyonrails.org/). Our downstream dependencies become everyone's dependencies. Because we cannot guarantee that downstream projects meet our development standards, it's best for everyone if we simply aviod dependencies. +* **Do not monkey patch Ruby:** Changing Ruby for our convenience affects every gem in every project that uses Concurrent Ruby. Monkey patching Ruby may change the behavior of other libraries in unexpected ways and destabilize projects which depend on us. +* **Do not pollute the global namespace:** Putting all our code within the `Concurrent` module guarantees that there will be no namespace collisions with other gems or the projects which depend on us. +* **No global state:** We are removing global state we have. +* **Minimize per-object configuration:** Ruby makes programmers happy. One of Ruby's charms is its simplicity. Concurrent Ruby aims to mirror this simplicity. Advanced configuration options are encouraged when they provide value, but every abstraction should have reasonable defaults that meet the needs of most users. +* **Provide explicit behavior and guarantees:** Our APIs should be concrete and clearly define what they do (and don't do). Users of Concurrent Ruby should never be surprised by unexpected behavior or be given guarantees we cannot keep. +* **Eat our own dog food:** Concurrent Ruby provides a rich set of low-level (internal and public) classes which provide strong guarantees and are optimized for performance across platforms. All our high-level abstractions should make use of these tools. + +#### Write Documentation + +Document any external behavior in the [README](README.md). + +#### Commit Changes + +Make sure git knows your name and email address: + +``` +git config --global user.name "Your Name" +git config --global user.email "contributor@example.com" +``` + +Writing good commit logs is important. A commit log should describe what changed and why. + +``` +git add ... +git commit +``` + +#### Push + +``` +git push origin my-feature-branch +``` + +#### Make a Pull Request + +Go to https://github.com/contributor/concurrent-ruby and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. + +#### Rebase + +If you've been working on a change for a while, rebase with upstream/master. + +``` +git fetch upstream +git rebase upstream/master +git push origin my-feature-branch -f +``` + +#### Update CHANGELOG + +Update the [CHANGELOG](CHANGELOG.md) with a description of what you have changed. + +#### Check on Your Pull Request + +Go back to your pull request after a few minutes and see whether it passed muster with GitHub Actions. Everything should look green, otherwise fix issues (amending your commit). + +Please note that testing concurrency is hard. Very hard. We have a few tests that occasionally fail due (mostly) to incorrect synchronization within the test itself. If everything passes locally but you see an error on CI, it's possibly you've become victim to one of the tests. Don't worry, the Concurrent Ruby team reviews the tests output of all failed CI runs and will let you know if the failing test is unrelated to your commit. + +#### IntelliJ + +To setup this project with IntelliJ Ultimate, this worked well: +* Save any local changes to `ext/concurrent-ruby` +* `rm -rf ext/concurrent-ruby` +* Open the root `concurrent-ruby` folder as a Ruby module +* Set `lib/concurrent-ruby` as `lib/concurrent-ruby-edge` as sources +* `git checkout ext/concurrent-ruby` +* Import `ext/concurrent-ruby` as Java module + +If it's imported directly without removing `ext/concurrent-ruby` then the whole project is recognized as a Java module, +and `require` resolution, etc does not work. + +#### Thank You + +Please do know that we really appreciate and value your time and work. We love you, really. + +And also, a special thank you to the [Volt](https://github.com/voltrb/volt) team for writing an awesome guide for contributors. We have borrowed liberally from theirs. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/ISSUE_TEMPLATE.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..5581516ec5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ + + + +``` +* Operating system: linux / mac / win +* Ruby implementation: Ruby / JRuby / TruffleRuby +* `concurrent-ruby` version: x.y.z +* `concurrent-ruby-ext` installed: yes / no +* `concurrent-ruby-edge` used: yes / no +``` diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/ci.yml b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/ci.yml new file mode 100644 index 0000000000..01be2df2bd --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI +on: [push, pull_request] +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + ruby: [2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, jruby, truffleruby] + + env: + JAVA_OPTS: '-Xmx1024m' + RUBYOPT: '-w' + JRUBY_OPTS: '--dev' + + name: "Tests: Ruby ${{ matrix.ruby }}" + steps: + - name: Clone Repo + uses: actions/checkout@v3 + - name: Setup Ruby ${{ matrix.ruby }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Run tests + run: bundle exec rake ci + + isolated: + name: "Test isolated" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ruby: [ 2.3, 3.2 ] # oldest and latest CRuby + env: + RUBYOPT: '-w' + steps: + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - run: bundle exec rake compile + - run: bundle exec rake spec:isolated diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/docs.yml b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/docs.yml new file mode 100644 index 0000000000..34dab01fdb --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/docs.yml @@ -0,0 +1,48 @@ +name: Publish Docs to GitHub Pages +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +# Allow one concurrent deployment +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + html: + runs-on: ubuntu-latest + env: + BUNDLE_WITH: "documentation" + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6 + bundler-cache: true + + - run: ruby support/generate_docs.rb + + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: docs + + deploy: + permissions: + pages: write + id-token: write + needs: [html] + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/experimental.yml b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/experimental.yml new file mode 100644 index 0000000000..6e469a4a1d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.github/workflows/experimental.yml @@ -0,0 +1,32 @@ +name: Experimental Rubies CI Run +on: + schedule: + - cron: '0 0 * * *' # Runs every day at midnight + workflow_dispatch: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + continue-on-error: true + + strategy: + matrix: + ruby: [head, jruby-head, truffleruby-head] + + env: + JAVA_OPTS: '-Xmx1024m' + RUBYOPT: '-w' + JRUBY_OPTS: '--dev' + + name: "Tests: Experimental Ruby ${{ matrix.ruby }}" + steps: + - name: Clone Repo + uses: actions/checkout@v3 + - name: Setup Ruby ${{ matrix.ruby }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Run tests + run: bundle exec rake ci diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/.gitignore b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.gitignore new file mode 100644 index 0000000000..7bee1a714f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.gitignore @@ -0,0 +1,33 @@ +# Project +/Gemfile.lock +/.bundle +/tmp/* +/coverage +/pkg + +# Yard documentation +/.yardoc + +# IDEs' files +*.iml +*.tmproj +.idea + +# Local Ruby settings +/.rspec-local +/.rvmrc +/.ruby-version +/.ruby-gemset +/vendor + +# junk +.DS_Store +.githubtoken + +# Rspec created files +/spec/examples.txt + +# Compiled files +/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +/lib/concurrent-ruby/concurrent/**/concurrent_ruby_ext.* +/lib/concurrent-ruby/concurrent/concurrent_ruby_ext.* diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/.rspec b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.rspec new file mode 100644 index 0000000000..500b6e0447 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.rspec @@ -0,0 +1,5 @@ +-I lib-edge +--require spec_helper +--color +--warnings +--format documentation diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/.yardopts b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.yardopts new file mode 100644 index 0000000000..5cb6ee5b9f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/.yardopts @@ -0,0 +1,5 @@ +--error:" use `bundle exec rake yard` instead" +--output-dir tmp +-- +no-lib +- diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/CHANGELOG.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/CHANGELOG.md new file mode 100644 index 0000000000..17a2a64153 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/CHANGELOG.md @@ -0,0 +1,561 @@ +## Current + +## Release v1.2.2 (24 Feb 2023) + +* (#993) Fix arguments passed to `Concurrent::Map`'s `default_proc`. + +## Release v1.2.1 (24 Feb 2023) + +* (#990) Add missing `require 'fiber'` for `FiberLocalVar`. +* (#989) Optimize `Concurrent::Map#[]` on CRuby by letting the backing Hash handle the `default_proc`. + +## Release v1.2.0 (23 Jan 2023) + +* (#962) Fix ReentrantReadWriteLock to use the same granularity for locals as for Mutex it uses. +* (#983) Add FiberLocalVar +* (#934) concurrent-ruby now supports requiring individual classes (public classes listed in the docs), e.g., `require 'concurrent/map'` +* (#976) Let `Promises.any_fulfilled_future` take an `Event` +* Improve documentation of various classes +* (#975) Set the Ruby compatibility version at 2.3 +* (#972) Remove Rubinius-related code + +## Release v1.1.10 (22 Mar 2022) + +concurrent-ruby: + +* (#951) Set the Ruby compatibility version at 2.2 +* (#939, #933) The `caller_runs` fallback policy no longer blocks reads from the job queue by worker threads +* (#938, #761, #652) You can now explicitly `prune_pool` a thread pool (Sylvain Joyeux) +* (#937, #757, #670) We switched the Yahoo stock API for demos to Alpha Vantage (Gustavo Caso) +* (#932, #931) We changed how `SafeTaskExecutor` handles local jump errors (Aaron Jensen) +* (#927) You can use keyword arguments in your initialize when using `Async` (Matt Larraz) +* (#926, #639) We removed timeout from `TimerTask` because it wasn't sound, and now it's a no-op with a warning (Jacob Atzen) +* (#919) If you double-lock a re-entrant read-write lock, we promote to locked for writing (zp yuan) +* (#915) `monotonic_time` now accepts an optional unit parameter, as Ruby's `clock_gettime` (Jean Boussier) + +## Release v1.1.9 (5 Jun 2021) + +concurrent-ruby: + +* (#866) Child promise state not set to :pending immediately after #execute when parent has completed +* (#905, #872) Fix RubyNonConcurrentPriorityQueue#delete method +* (2df0337d) Make sure locks are not shared on shared when objects are dup/cloned +* (#900, #906, #796, #847, #911) Fix Concurrent::Set tread-safety issues on CRuby +* (#907) Add new ConcurrentMap backend for TruffleRuby + +## Release v1.1.8 (20 January 2021) + +concurrent-ruby: + +* (#885) Fix race condition in TVar for stale reads +* (#884) RubyThreadLocalVar: Do not iterate over hash which might conflict with new pair addition + +## Release v1.1.7 (6 August 2020) + +concurrent-ruby: + +* (#879) Consider falsy value on `Concurrent::Map#compute_if_absent` for fast non-blocking path +* (#876) Reset Async queue on forking, makes Async fork-safe +* (#856) Avoid running problematic code in RubyThreadLocalVar on MRI that occasionally results in segfault +* (#853) Introduce ThreadPoolExecutor without a Queue + +## Release v1.1.6, edge v0.6.0 (10 Feb 2020) + +concurrent-ruby: + +* (#841) Concurrent.disable_at_exit_handlers! is no longer needed and was deprecated. +* (#841) AbstractExecutorService#auto_terminate= was deprecated and has no effect. + Set :auto_terminate option instead when executor is initialized. + +## Release v1.1.6.pre1, edge v0.6.0.pre1 (26 Jan 2020) + +concurrent-ruby: + +* (#828) Allow to name executors, the name is also used to name their threads +* (#838) Implement #dup and #clone for structs +* (#821) Safer finalizers for thread local variables +* Documentation fixes +* (#814) Use Ruby's Etc.nprocessors if available +* (#812) Fix directory structure not to mess with packaging tools +* (#840) Fix termination of pools on JRuby + +concurrent-ruby-edge: + +* Add WrappingExecutor (#830) + +## Release v1.1.5, edge v0.5.0 (10 Mar 2019) + +concurrent-ruby: + +* fix potential leak of context on JRuby and Java 7 + +concurrent-ruby-edge: + +* Add finalized Concurrent::Cancellation +* Add finalized Concurrent::Throttle +* Add finalized Concurrent::Promises::Channel +* Add new Concurrent::ErlangActor + +## Release v1.1.4 (14 Dec 2018) + +* (#780) Remove java_alias of 'submit' method of Runnable to let executor service work on java 11 +* (#776) Fix NameError on defining a struct with a name which is already taken in an ancestor + +## Release v1.1.3 (7 Nov 2018) + +* (#775) fix partial require of the gem (although not officially supported) + +## Release v1.1.2 (6 Nov 2018) + +* (#773) more defensive 1.9.3 support + +## Release v1.1.1, edge v0.4.1 (1 Nov 2018) + +* (#768) add support for 1.9.3 back + +## Release v1.1.0, edge v0.4.0 (31 OCt 2018) (yanked) + +* (#768) yanked because of issues with removed 1.9.3 support + +## Release v1.1.0.pre2, edge v0.4.0.pre2 (18 Sep 2018) + +concurrent-ruby: + +* fixed documentation and README links +* fix Set for TruffleRuby and Rubinius +* use properly supported TruffleRuby APIs + +concurrent-ruby-edge: + +* add Promises.zip_futures_over_on + +## Release v1.1.0.pre1, edge v0.4.0.pre1 (15 Aug 2018) + +concurrent-ruby: + +* requires at least Ruby 2.0 +* [Promises](http://ruby-concurrency.github.io/concurrent-ruby/1.1.0/Concurrent/Promises.html) + are moved from `concurrent-ruby-edge` to `concurrent-ruby` +* Add support for TruffleRuby + * (#734) Fix Array/Hash/Set construction broken on TruffleRuby + * AtomicReference fixed +* CI stabilization +* remove sharp dependency edge -> core +* remove warnings +* documentation updates +* Exchanger is no longer documented as edge since it was already available in + `concurrent-ruby` +* (#644) Fix Map#each and #each_pair not returning enumerator outside of MRI +* (#659) Edge promises fail during error handling +* (#741) Raise on recursive Delay#value call +* (#727) #717 fix global IO executor on JRuby +* (#740) Drop support for CRuby 1.9, JRuby 1.7, Rubinius. +* (#737) Move AtomicMarkableReference out of Edge +* (#708) Prefer platform specific memory barriers +* (#735) Fix wrong expected exception in channel spec assertion +* (#729) Allow executor option in `Promise#then` +* (#725) fix timeout check to use timeout_interval +* (#719) update engine detection +* (#660) Add specs for Promise#zip/Promise.zip ordering +* (#654) Promise.zip execution changes +* (#666) Add thread safe set implementation +* (#651) #699 #to_s, #inspect should not output negative object IDs. +* (#685) Avoid RSpec warnings about raise_error +* (#680) Avoid RSpec monkey patching, persist spec results locally, use RSpec + v3.7.0 +* (#665) Initialize the monitor for new subarrays on Rubinius +* (#661) Fix error handling in edge promises + +concurrent-ruby-edge: + +* (#659) Edge promises fail during error handling +* Edge files clearly separated in `lib-edge` +* added ReInclude + +## Release v1.0.5, edge v0.3.1 (26 Feb 2017) + +concurrent-ruby: + +* Documentation for Event and Semaphore +* Use Unsafe#fullFence and #loadFence directly since the shortcuts were removed in JRuby +* Do not depend on org.jruby.util.unsafe.UnsafeHolder + +concurrent-ruby-edge: + +* (#620) Actors on Pool raise an error +* (#624) Delayed promises did not interact correctly with flatting + * Fix arguments yielded by callback methods +* Overridable default executor in promises factory methods +* Asking actor to terminate will always resolve to `true` + +## Release v1.0.4, edge v0.3.0 (27 Dec 2016) + +concurrent-ruby: + +* Nothing + +concurrent-ruby-edge: + +* New promises' API renamed, lots of improvements, edge bumped to 0.3.0 + * **Incompatible** with previous 0.2.3 version + * see https://github.com/ruby-concurrency/concurrent-ruby/pull/522 + +## Release v1.0.3 (17 Dec 2016) + +* Trigger execution of flattened delayed futures +* Avoid forking for processor_count if possible +* Semaphore Mutex and JRuby parity +* Adds Map#each as alias to Map#each_pair +* Fix uninitialized instance variables +* Make Fixnum, Bignum merger ready +* Allows Promise#then to receive an executor +* TimerSet now survives a fork +* Reject promise on any exception +* Allow ThreadLocalVar to be initialized with a block +* Support Alpha with `Concurrent::processor_count` +* Fixes format-security error when compiling ruby_193_compatible.h +* Concurrent::Atom#swap fixed: reraise the exceptions from block + +## Release v1.0.2 (2 May 2016) + +* Fix bug with `Concurrent::Map` MRI backend `#inspect` method +* Fix bug with `Concurrent::Map` MRI backend using `Hash#value?` +* Improved documentation and examples +* Minor updates to Edge + +## Release v1.0.1 (27 February 2016) + +* Fix "uninitialized constant Concurrent::ReentrantReadWriteLock" error. +* Better handling of `autoload` vs. `require`. +* Improved API for Edge `Future` zipping. +* Fix reference leak in Edge `Future` constructor . +* Fix bug which prevented thread pools from surviving a `fork`. +* Fix bug in which `TimerTask` did not correctly specify all its dependencies. +* Improved support for JRuby+Truffle +* Improved error messages. +* Improved documentation. +* Updated README and CONTRIBUTING. + +## Release v1.0.0 (13 November 2015) + +* Rename `attr_volatile_with_cas` to `attr_atomic` +* Add `clear_each` to `LockFreeStack` +* Update `AtomicReference` documentation +* Further updates and improvements to the synchronization layer. +* Performance and memory usage performance with `Actor` logging. +* Fixed `ThreadPoolExecutor` task count methods. +* Improved `Async` performance for both short and long-lived objects. +* Fixed bug in `LockFreeLinkedSet`. +* Fixed bug in which `Agent#await` triggered a validation failure. +* Further `Channel` updates. +* Adopted a project Code of Conduct +* Cleared interpreter warnings +* Fixed bug in `ThreadPoolExecutor` task count methods +* Fixed bug in 'LockFreeLinkedSet' +* Improved Java extension loading +* Handle Exception children in Edge::Future +* Continued improvements to channel +* Removed interpreter warnings. +* Shared constants now in `lib/concurrent/constants.rb` +* Refactored many tests. +* Improved synchronization layer/memory model documentation. +* Bug fix in Edge `Future#flat` +* Brand new `Channel` implementation in Edge gem. +* Simplification of `RubySingleThreadExecutor` +* `Async` improvements + - Each object uses its own `SingleThreadExecutor` instead of the global thread pool. + - No longers supports executor injection + - Much better documentation +* `Atom` updates + - No longer `Dereferenceable` + - Now `Observable` + - Added a `#reset` method +* Brand new `Agent` API and implementation. Now functionally equivalent to Clojure. +* Continued improvements to the synchronization layer +* Merged in the `thread_safe` gem + - `Concurrent::Array` + - `Concurrent::Hash` + - `Concurrent::Map` (formerly ThreadSafe::Cache) + - `Concurrent::Tuple` +* Minor improvements to Concurrent::Map +* Complete rewrite of `Exchanger` +* Removed all deprecated code (classes, methods, constants, etc.) +* Updated Agent, MutexAtomic, and BufferedChannel to inherit from Synchronization::Object. +* Many improved tests +* Some internal reorganization + +## Release v0.9.1 (09 August 2015) + +* Fixed a Rubiniux bug in synchronization object +* Fixed all interpreter warnings (except circular references) +* Fixed require statements when requiring `Atom` alone +* Significantly improved `ThreadLocalVar` on non-JRuby platforms +* Fixed error handling in Edge `Concurrent.zip` +* `AtomicFixnum` methods `#increment` and `#decrement` now support optional delta +* New `AtomicFixnum#update` method +* Minor optimizations in `ReadWriteLock` +* New `ReentrantReadWriteLock` class +* `ThreadLocalVar#bind` method is now public +* Refactored many tests + +## Release v0.9.0 (10 July 2015) + +* Updated `AtomicReference` + - `AtomicReference#try_update` now simply returns instead of raising exception + - `AtomicReference#try_update!` was added to raise exceptions if an update + fails. Note: this is the same behavior as the old `try_update` +* Pure Java implementations of + - `AtomicBoolean` + - `AtomicFixnum` + - `Semaphore` +* Fixed bug when pruning Ruby thread pools +* Fixed bug in time calculations within `ScheduledTask` +* Default `count` in `CountDownLatch` to 1 +* Use monotonic clock for all timers via `Concurrent.monotonic_time` + - Use `Process.clock_gettime(Process::CLOCK_MONOTONIC)` when available + - Fallback to `java.lang.System.nanoTime()` on unsupported JRuby versions + - Pure Ruby implementation for everything else + - Effects `Concurrent.timer`, `Concurrent.timeout`, `TimerSet`, `TimerTask`, and `ScheduledTask` +* Deprecated all clock-time based timer scheduling + - Only support scheduling by delay + - Effects `Concurrent.timer`, `TimerSet`, and `ScheduledTask` +* Added new `ReadWriteLock` class +* Consistent `at_exit` behavior for Java and Ruby thread pools. +* Added `at_exit` handler to Ruby thread pools (already in Java thread pools) + - Ruby handler stores the object id and retrieves from `ObjectSpace` + - JRuby disables `ObjectSpace` by default so that handler stores the object reference +* Added a `:stop_on_exit` option to thread pools to enable/disable `at_exit` handler +* Updated thread pool docs to better explain shutting down thread pools +* Simpler `:executor` option syntax for all abstractions which support this option +* Added `Executor#auto_terminate?` predicate method (for thread pools) +* Added `at_exit` handler to `TimerSet` +* Simplified auto-termination of the global executors + - Can now disable auto-termination of global executors + - Added shutdown/kill/wait_for_termination variants for global executors +* Can now disable auto-termination for *all* executors (the nuclear option) +* Simplified auto-termination of the global executors +* Deprecated terms "task pool" and "operation pool" + - New terms are "io executor" and "fast executor" + - New functions added with new names + - Deprecation warnings added to functions referencing old names +* Moved all thread pool related functions from `Concurrent::Configuration` to `Concurrent` + - Old functions still exist with deprecation warnings + - New functions have updated names as appropriate +* All high-level abstractions default to the "io executor" +* Fixed bug in `Actor` causing it to prematurely warm global thread pools on gem load + - This also fixed a `RejectedExecutionError` bug when running with minitest/autorun via JRuby +* Moved global logger up to the `Concurrent` namespace and refactored the code +* Optimized the performance of `Delay` + - Fixed a bug in which no executor option on construction caused block execution on a global thread pool +* Numerous improvements and bug fixes to `TimerSet` +* Fixed deadlock of `Future` when the handler raises Exception +* Added shared specs for more classes +* New concurrency abstractions including: + - `Atom` + - `Maybe` + - `ImmutableStruct` + - `MutableStruct` + - `SettableStruct` +* Created an Edge gem for unstable abstractions including + - `Actor` + - `Agent` + - `Channel` + - `Exchanger` + - `LazyRegister` + - **new Future Framework** - unified + implementation of Futures and Promises which combines Features of previous `Future`, + `Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively + new synchronization layer to make all the paths **lock-free** with exception of blocking threads on `#wait`. + It offers better performance and does not block threads when not required. +* Actor framework changes: + - fixed reset loop in Pool + - Pool can use any actor as a worker, abstract worker class is no longer needed. + - Actor events not have format `[:event_name, *payload]` instead of just the Symbol. + - Actor now uses new Future/Promise Framework instead of `IVar` for better interoperability + - Behaviour definition array was simplified to `[BehaviourClass1, [BehaviourClass2, *initialization_args]]` + - Linking behavior responds to :linked message by returning array of linked actors + - Supervised behavior is removed in favour of just Linking + - RestartingContext is supervised by default now, `supervise: true` is not required any more + - Events can be private and public, so far only difference is that Linking will + pass to linked actors only public messages. Adding private :restarting and + :resetting events which are send before the actor restarts or resets allowing + to add callbacks to cleanup current child actors. + - Print also object_id in Reference to_s + - Add AbstractContext#default_executor to be able to override executor class wide + - Add basic IO example + - Documentation somewhat improved + - All messages should have same priority. It's now possible to send `actor << job1 << job2 << :terminate!` and + be sure that both jobs are processed first. +* Refactored `Channel` to use newer synchronization objects +* Added `#reset` and `#cancel` methods to `TimerSet` +* Added `#cancel` method to `Future` and `ScheduledTask` +* Refactored `TimerSet` to use `ScheduledTask` +* Updated `Async` with a factory that initializes the object +* Deprecated `Concurrent.timer` and `Concurrent.timeout` +* Reduced max threads on pure-Ruby thread pools (abends around 14751 threads) +* Moved many private/internal classes/modules into "namespace" modules +* Removed brute-force killing of threads in tests +* Fixed a thread pool bug when the operating system cannot allocate more threads + +## Release v0.8.0 (25 January 2015) + +* C extension for MRI have been extracted into the `concurrent-ruby-ext` companion gem. + Please see the README for more detail. +* Better variable isolation in `Promise` and `Future` via an `:args` option +* Continued to update intermittently failing tests + +## Release v0.7.2 (24 January 2015) + +* New `Semaphore` class based on [java.util.concurrent.Semaphore](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html) +* New `Promise.all?` and `Promise.any?` class methods +* Renamed `:overflow_policy` on thread pools to `:fallback_policy` +* Thread pools still accept the `:overflow_policy` option but display a warning +* Thread pools now implement `fallback_policy` behavior when not running (rather than universally rejecting tasks) +* Fixed minor `set_deref_options` constructor bug in `Promise` class +* Fixed minor `require` bug in `ThreadLocalVar` class +* Fixed race condition bug in `TimerSet` class +* Fixed race condition bug in `TimerSet` class +* Fixed signal bug in `TimerSet#post` method +* Numerous non-functional updates to clear warning when running in debug mode +* Fixed more intermittently failing tests +* Tests now run on new Travis build environment +* Multiple documentation updates + +## Release v0.7.1 (4 December 2014) + +Please see the [roadmap](https://github.com/ruby-concurrency/concurrent-ruby/issues/142) for more information on the next planned release. + +* Added `flat_map` method to `Promise` +* Added `zip` method to `Promise` +* Fixed bug with logging in `Actor` +* Improvements to `Promise` tests +* Removed actor-experimental warning +* Added an `IndirectImmediateExecutor` class +* Allow disabling auto termination of global executors +* Fix thread leaking in `ThreadLocalVar` (uses `Ref` gem on non-JRuby systems) +* Fix thread leaking when pruning pure-Ruby thread pools +* Prevent `Actor` from using an `ImmediateExecutor` (causes deadlock) +* Added missing synchronizations to `TimerSet` +* Fixed bug with return value of `Concurrent::Actor::Utils::Pool#ask` +* Fixed timing bug in `TimerTask` +* Fixed bug when creating a `JavaThreadPoolExecutor` with minimum pool size of zero +* Removed confusing warning when not using native extenstions +* Improved documentation + +## Release v0.7.0 (13 August 2014) + +* Merge the [atomic](https://github.com/ruby-concurrency/atomic) gem + - Pure Ruby `MutexAtomic` atomic reference class + - Platform native atomic reference classes `CAtomic`, `JavaAtomic`, and `RbxAtomic` + - Automated [build process](https://github.com/ruby-concurrency/rake-compiler-dev-box) + - Fat binary releases for [multiple platforms](https://rubygems.org/gems/concurrent-ruby/versions) including Windows (32/64), Linux (32/64), OS X (64-bit), Solaris (64-bit), and JRuby +* C native `CAtomicBoolean` +* C native `CAtomicFixnum` +* Refactored intermittently failing tests +* Added `dataflow!` and `dataflow_with!` methods to match `Future#value!` method +* Better handling of timeout in `Agent` +* Actor Improvements + - Fine-grained implementation using chain of behaviors. Each behavior is responsible for single aspect like: `Termination`, `Pausing`, `Linking`, `Supervising`, etc. Users can create custom Actors easily based on their needs. + - Supervision was added. `RestartingContext` will pause on error waiting on its supervisor to decide what to do next ( options are `:terminate!`, `:resume!`, `:reset!`, `:restart!`). Supervising behavior also supports strategies `:one_for_one` and `:one_for_all`. + - Linking was added to be able to monitor actor's events like: `:terminated`, `:paused`, `:restarted`, etc. + - Dead letter routing added. Rejected envelopes are collected in a configurable actor (default: `Concurrent::Actor.root.ask!(:dead_letter_routing)`) + - Old `Actor` class removed and replaced by new implementation previously called `Actress`. `Actress` was kept as an alias for `Actor` to keep compatibility. + - `Utils::Broadcast` actor which allows Publish–subscribe pattern. +* More executors for managing serialized operations + - `SerializedExecution` mixin module + - `SerializedExecutionDelegator` for serializing *any* executor +* Updated `Async` with serialized execution +* Updated `ImmediateExecutor` and `PerThreadExecutor` with full executor service lifecycle +* Added a `Delay` to root `Actress` initialization +* Minor bug fixes to thread pools +* Refactored many intermittently failing specs +* Removed Java interop warning `executor.rb:148 warning: ambiguous Java methods found, using submit(java.lang.Runnable)` +* Fixed minor bug in `RubyCachedThreadPool` overflow policy +* Updated tests to use [RSpec 3.0](http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3) +* Removed deprecated `Actor` class +* Better support for Rubinius + +## Release v0.6.1 (14 June 2014) + +* Many improvements to `Concurrent::Actress` +* Bug fixes to `Concurrent::RubyThreadPoolExecutor` +* Fixed several brittle tests +* Moved documentation to http://ruby-concurrency.github.io/concurrent-ruby/frames.html + +## Release v0.6.0 (25 May 2014) + +* Added `Concurrent::Observable` to encapsulate our thread safe observer sets +* Improvements to new `Channel` +* Major improvements to `CachedThreadPool` and `FixedThreadPool` +* Added `SingleThreadExecutor` +* Added `Current::timer` function +* Added `TimerSet` executor +* Added `AtomicBoolean` +* `ScheduledTask` refactoring +* Pure Ruby and JRuby-optimized `PriorityQueue` classes +* Updated `Agent` behavior to more closely match Clojure +* Observer sets support block callbacks to the `add_observer` method +* New algorithm for thread creation in `RubyThreadPoolExecutor` +* Minor API updates to `Event` +* Rewritten `TimerTask` now an `Executor` instead of a `Runnable` +* Fixed many brittle specs +* Renamed `FixedThreadPool` and `CachedThreadPool` to `RubyFixedThreadPool` and `RubyCachedThreadPool` +* Created JRuby optimized `JavaFixedThreadPool` and `JavaCachedThreadPool` +* Consolidated fixed thread pool tests into `spec/concurrent/fixed_thread_pool_shared.rb` and `spec/concurrent/cached_thread_pool_shared.rb` +* `FixedThreadPool` now subclasses `RubyFixedThreadPool` or `JavaFixedThreadPool` as appropriate +* `CachedThreadPool` now subclasses `RubyCachedThreadPool` or `JavaCachedThreadPool` as appropriate +* New `Delay` class +* `Concurrent::processor_count` helper function +* New `Async` module +* Renamed `NullThreadPool` to `PerThreadExecutor` +* Deprecated `Channel` (we are planning a new implementation based on [Go](http://golangtutorials.blogspot.com/2011/06/channels-in-go.html)) +* Added gem-level [configuration](http://robots.thoughtbot.com/mygem-configure-block) +* Deprecated `$GLOBAL_THREAD_POOL` in lieu of gem-level configuration +* Removed support for Ruby [1.9.2](https://www.ruby-lang.org/en/news/2013/12/17/maintenance-of-1-8-7-and-1-9-2/) +* New `RubyThreadPoolExecutor` and `JavaThreadPoolExecutor` classes +* All thread pools now extend the appropriate thread pool executor classes +* All thread pools now support `:overflow_policy` (based on Java's [reject policies](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html)) +* Deprecated `UsesGlobalThreadPool` in lieu of explicit `:executor` option (dependency injection) on `Future`, `Promise`, and `Agent` +* Added `Concurrent::dataflow_with(executor, *inputs)` method to support executor dependency injection for dataflow +* Software transactional memory with `TVar` and `Concurrent::atomically` +* First implementation of [new, high-performance](https://github.com/ruby-concurrency/concurrent-ruby/pull/49) `Channel` +* `Actor` is deprecated in favor of new experimental actor implementation [#73](https://github.com/ruby-concurrency/concurrent-ruby/pull/73). To avoid namespace collision it is living in `Actress` namespace until `Actor` is removed in next release. + +## Release v0.5.0 + +This is the most significant release of this gem since its inception. This release includes many improvements and optimizations. It also includes several bug fixes. The major areas of focus for this release were: + +* Stability improvements on Ruby versions with thread-level parallelism ([JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) +* Creation of new low-level concurrency abstractions +* Internal refactoring to use the new low-level abstractions + +Most of these updates had no effect on the gem API. There are a few notable exceptions which were unavoidable. Please read the [release notes](API-Updates-in-v0.5.0) for more information. + +Specific changes include: + +* New class `IVar` +* New class `MVar` +* New class `ThreadLocalVar` +* New class `AtomicFixnum` +* New class method `dataflow` +* New class `Condition` +* New class `CountDownLatch` +* New class `DependencyCounter` +* New class `SafeTaskExecutor` +* New class `CopyOnNotifyObserverSet` +* New class `CopyOnWriteObserverSet` +* `Future` updated with `execute` API +* `ScheduledTask` updated with `execute` API +* New `Promise` API +* `Future` now extends `IVar` +* `Postable#post?` now returns an `IVar` +* Thread safety fixes to `Dereferenceable` +* Thread safety fixes to `Obligation` +* Thread safety fixes to `Supervisor` +* Thread safety fixes to `Event` +* Various other thread safety (race condition) fixes +* Refactored brittle tests +* Implemented pending tests +* Added JRuby and Rubinius as Travis CI build targets +* Added [CodeClimate](https://codeclimate.com/) code review +* Improved YARD documentation diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/CODE_OF_CONDUCT.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..99230fa9ac --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/CODE_OF_CONDUCT.md @@ -0,0 +1,71 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project +a harassment-free experience for *everyone*. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the project +* Showing empathy towards other project members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project. Examples of +representing a project include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at `concurrent-ruby-conduct@pitr.ch`. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/Gemfile b/app/server/ruby/vendor/concurrent-ruby-1.2.2/Gemfile new file mode 100644 index 0000000000..b336031b75 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/Gemfile @@ -0,0 +1,36 @@ +source 'https://rubygems.org' + +require File.join(File.dirname(__FILE__), 'lib/concurrent-ruby/concurrent/version') +require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby-edge/concurrent/edge/version') + +no_path = ENV['NO_PATH'] +options = no_path ? {} : { path: '.' } + +gem 'concurrent-ruby', Concurrent::VERSION, options +gem 'concurrent-ruby-edge', Concurrent::EDGE_VERSION, options +gem 'concurrent-ruby-ext', Concurrent::VERSION, options.merge(platform: :mri) + +group :development do + gem 'rake', '~> 13.0' + gem 'rake-compiler', '~> 1.0', '>= 1.0.7' + gem 'rake-compiler-dock', '~> 1.0' + gem 'pry', '~> 0.11', platforms: :mri +end + +group :documentation, optional: true do + gem 'yard', '~> 0.9.0', require: false + gem 'redcarpet', '~> 3.0', platforms: :mri # understands github markdown + gem 'md-ruby-eval', '~> 0.6' +end + +group :testing do + gem 'rspec', '~> 3.7' + gem 'timecop', '~> 0.9' + gem 'sigdump', require: false +end + +# made opt-in since it will not install on jruby 1.7 +group :coverage, optional: !ENV['COVERAGE'] do + gem 'simplecov', '~> 0.16.0', require: false + gem 'coveralls', '~> 0.8.2', require: false +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/LICENSE.txt b/app/server/ruby/vendor/concurrent-ruby-1.2.2/LICENSE.txt new file mode 100644 index 0000000000..1026f28d0b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) Jerry D'Antonio -- released under the MIT license. + +http://www.opensource.org/licenses/mit-license.php + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/README.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/README.md new file mode 100644 index 0000000000..15f011b988 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/README.md @@ -0,0 +1,405 @@ +# Concurrent Ruby + +[![Gem Version](https://badge.fury.io/rb/concurrent-ruby.svg)](http://badge.fury.io/rb/concurrent-ruby) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT) +[![Gitter chat](https://img.shields.io/badge/IRC%20(gitter)-devs%20%26%20users-brightgreen.svg)](https://gitter.im/ruby-concurrency/concurrent-ruby) + +Modern concurrency tools for Ruby. Inspired by +[Erlang](http://www.erlang.org/doc/reference_manual/processes.html), +[Clojure](http://clojure.org/concurrent_programming), +[Scala](http://akka.io/), +[Haskell](http://www.haskell.org/haskellwiki/Applications_and_libraries/Concurrency_and_parallelism#Concurrent_Haskell), +[F#](http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx), +[C#](http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx), +[Java](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html), +and classic concurrency patterns. + + + +The design goals of this gem are: + +* Be an 'unopinionated' toolbox that provides useful utilities without debating which is better + or why +* Remain free of external gem dependencies +* Stay true to the spirit of the languages providing inspiration +* But implement in a way that makes sense for Ruby +* Keep the semantics as idiomatic Ruby as possible +* Support features that make sense in Ruby +* Exclude features that don't make sense in Ruby +* Be small, lean, and loosely coupled +* Thread-safety +* Backward compatibility + +## Contributing + +**This gem depends on +[contributions](https://github.com/ruby-concurrency/concurrent-ruby/graphs/contributors) and we +appreciate your help. Would you like to contribute? Great! Have a look at +[issues with `looking-for-contributor` label](https://github.com/ruby-concurrency/concurrent-ruby/issues?q=is%3Aissue+is%3Aopen+label%3Alooking-for-contributor).** And if you pick something up let us know on the issue. + +You can also get started by triaging issues which may include reproducing bug reports or asking for vital information, such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to concurrent-ruby on CodeTriage](https://www.codetriage.com/ruby-concurrency/concurrent-ruby). [![Open Source Helpers](https://www.codetriage.com/ruby-concurrency/concurrent-ruby/badges/users.svg)](https://www.codetriage.com/ruby-concurrency/concurrent-ruby) + +## Thread Safety + +*Concurrent Ruby makes one of the strongest thread safety guarantees of any Ruby concurrency +library, providing consistent behavior and guarantees on all three main Ruby interpreters +(MRI/CRuby, JRuby, TruffleRuby).* + +Every abstraction in this library is thread safe. Specific thread safety guarantees are documented +with each abstraction. + +It is critical to remember, however, that Ruby is a language of mutable references. *No* +concurrency library for Ruby can ever prevent the user from making thread safety mistakes (such as +sharing a mutable object between threads and modifying it on both threads) or from creating +deadlocks through incorrect use of locks. All the library can do is provide safe abstractions which +encourage safe practices. Concurrent Ruby provides more safe concurrency abstractions than any +other Ruby library, many of which support the mantra of +["Do not communicate by sharing memory; instead, share memory by communicating"](https://blog.golang.org/share-memory-by-communicating). +Concurrent Ruby is also the only Ruby library which provides a full suite of thread safe and +immutable variable types and data structures. + +We've also initiated discussion to document the [memory model](docs-source/synchronization.md) of Ruby which +would provide consistent behaviour and guarantees on all three main Ruby interpreters +(MRI/CRuby, JRuby, TruffleRuby). + +## Features & Documentation + +**The primary site for documentation is the automatically generated +[API documentation](http://ruby-concurrency.github.io/concurrent-ruby/index.html) which is up to +date with latest release.** This readme matches the master so may contain new stuff not yet +released. + +We also have a [IRC (gitter)](https://gitter.im/ruby-concurrency/concurrent-ruby). + +### Versioning + +* `concurrent-ruby` uses [Semantic Versioning](http://semver.org/) +* `concurrent-ruby-ext` has always same version as `concurrent-ruby` +* `concurrent-ruby-edge` will always be 0.y.z therefore following + [point 4](http://semver.org/#spec-item-4) applies *"Major version zero + (0.y.z) is for initial development. Anything may change at any time. The + public API should not be considered stable."* However we additionally use + following rules: + * Minor version increment means incompatible changes were made + * Patch version increment means only compatible changes were made + + +#### General-purpose Concurrency Abstractions + +* [Async](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Async.html): + A mixin module that provides simple asynchronous behavior to a class. Loosely based on Erlang's + [gen_server](http://www.erlang.org/doc/man/gen_server.html). +* [ScheduledTask](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ScheduledTask.html): + Like a Future scheduled for a specific future time. +* [TimerTask](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/TimerTask.html): + A Thread that periodically wakes up to perform work at regular intervals. +* [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html): + Unified implementation of futures and promises which combines features of previous `Future`, + `Promise`, `IVar`, `Event`, `dataflow`, `Delay`, and (partially) `TimerTask` into a single + framework. It extensively uses the new synchronization layer to make all the features + **non-blocking** and **lock-free**, with the exception of obviously blocking operations like + `#wait`, `#value`. It also offers better performance. + +#### Thread-safe Value Objects, Structures, and Collections + +Collection classes that were originally part of the (deprecated) `thread_safe` gem: + +* [Array](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Array.html) A thread-safe + subclass of Ruby's standard [Array](http://ruby-doc.org/core/Array.html). +* [Hash](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Hash.html) A thread-safe + subclass of Ruby's standard [Hash](http://ruby-doc.org/core/Hash.html). +* [Set](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Set.html) A thread-safe + subclass of Ruby's standard [Set](http://ruby-doc.org/stdlib-2.4.0/libdoc/set/rdoc/Set.html). +* [Map](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Map.html) A hash-like object + that should have much better performance characteristics, especially under high concurrency, + than `Concurrent::Hash`. +* [Tuple](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Tuple.html) A fixed size + array with volatile (synchronized, thread safe) getters/setters. + +Value objects inspired by other languages: + +* [Maybe](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Maybe.html) A thread-safe, + immutable object representing an optional value, based on + [Haskell Data.Maybe](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html). + +Structure classes derived from Ruby's [Struct](http://ruby-doc.org/core/Struct.html): + +* [ImmutableStruct](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ImmutableStruct.html) + Immutable struct where values are set at construction and cannot be changed later. +* [MutableStruct](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/MutableStruct.html) + Synchronized, mutable struct where values can be safely changed at any time. +* [SettableStruct](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/SettableStruct.html) + Synchronized, write-once struct where values can be set at most once, either at construction + or any time thereafter. + +Thread-safe variables: + +* [Agent](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Agent.html): A way to + manage shared, mutable, *asynchronous*, independent state. Based on Clojure's + [Agent](http://clojure.org/agents). +* [Atom](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Atom.html): A way to manage + shared, mutable, *synchronous*, independent state. Based on Clojure's + [Atom](http://clojure.org/atoms). +* [AtomicBoolean](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/AtomicBoolean.html) + A boolean value that can be updated atomically. +* [AtomicFixnum](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/AtomicFixnum.html) + A numeric value that can be updated atomically. +* [AtomicReference](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/AtomicReference.html) + An object reference that may be updated atomically. +* [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Exchanger.html) + A synchronization point at which threads can pair and swap elements within pairs. Based on + Java's [Exchanger](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html). +* [MVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/MVar.html) A synchronized + single element container. Based on Haskell's + [MVar](https://hackage.haskell.org/package/base-4.8.1.0/docs/Control-Concurrent-MVar.html) and + Scala's [MVar](http://docs.typelevel.org/api/scalaz/nightly/index.html#scalaz.concurrent.MVar$). +* [ThreadLocalVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadLocalVar.html) + A variable where the value is different for each thread. +* [TVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/TVar.html) A transactional + variable implementing software transactional memory (STM). Based on Clojure's + [Ref](http://clojure.org/refs). + +#### Java-inspired ThreadPools and Other Executors + +* See the [thread pool](http://ruby-concurrency.github.io/concurrent-ruby/master/file.thread_pools.html) + overview, which also contains a list of other Executors available. + +#### Thread Synchronization Classes and Algorithms + +* [CountDownLatch](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/CountDownLatch.html) + A synchronization object that allows one thread to wait on multiple other threads. +* [CyclicBarrier](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/CyclicBarrier.html) + A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. +* [Event](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Event.html) Old school + kernel-style event. +* [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ReadWriteLock.html) + A lock that supports multiple readers but only one writer. +* [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ReentrantReadWriteLock.html) + A read/write lock with reentrant and upgrade features. +* [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Semaphore.html) + A counting-based locking mechanism that uses permits. +* [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/AtomicMarkableReference.html) + +#### Deprecated + +Deprecated features are still available and bugs are being fixed, but new features will not be added. + +* ~~[Future](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Future.html): + An asynchronous operation that produces a value.~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). + * ~~[.dataflow](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent.html#dataflow-class_method): + Built on Futures, Dataflow allows you to create a task that will be scheduled when all of + its data dependencies are available.~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). +* ~~[Promise](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promise.html): Similar + to Futures, with more features.~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). +* ~~[Delay](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Delay.html) Lazy evaluation + of a block yielding an immutable result. Based on Clojure's + [delay](https://clojuredocs.org/clojure.core/delay).~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). +* ~~[IVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/IVar.html) Similar to a + "future" but can be manually assigned once, after which it becomes immutable.~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). + +### Edge Features + +These are available in the `concurrent-ruby-edge` companion gem. + +These features are under active development and may change frequently. They are expected not to +keep backward compatibility (there may also lack tests and documentation). Semantic versions will +be obeyed though. Features developed in `concurrent-ruby-edge` are expected to move to +`concurrent-ruby` when final. + +* [Actor](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Actor.html): Implements + the Actor Model, where concurrent actors exchange messages. + *Status: Partial documentation and tests; depends on new future/promise framework; stability is good.* +* [Channel](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Channel.html): + Communicating Sequential Processes ([CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)). + Functionally equivalent to Go [channels](https://tour.golang.org/concurrency/2) with additional + inspiration from Clojure [core.async](https://clojure.github.io/core.async/). + *Status: Partial documentation and tests.* +* [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/LazyRegister.html) +* [LockFreeLinkedSet](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Edge/LockFreeLinkedSet.html) + *Status: will be moved to core soon.* +* [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/LockFreeStack.html) + *Status: missing documentation and tests.* +* [Promises::Channel](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises/Channel.html) + A first in first out channel that accepts messages with push family of methods and returns + messages with pop family of methods. + Pop and push operations can be represented as futures, see `#pop_op` and `#push_op`. + The capacity of the channel can be limited to support back pressure, use capacity option in `#initialize`. + `#pop` method blocks ans `#pop_op` returns pending future if there is no message in the channel. + If the capacity is limited the `#push` method blocks and `#push_op` returns pending future. +* [Cancellation](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Cancellation.html) + The Cancellation abstraction provides cooperative cancellation. + + The standard methods `Thread#raise` of `Thread#kill` available in Ruby + are very dangerous (see linked the blog posts bellow). + Therefore concurrent-ruby provides an alternative. + + * + * + * + + It provides an object which represents a task which can be executed, + the task has to get the reference to the object and periodically cooperatively check that it is not cancelled. + Good practices to make tasks cancellable: + * check cancellation every cycle of a loop which does significant work, + * do all blocking actions in a loop with a timeout then on timeout check cancellation + and if ok block again with the timeout +* [Throttle](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Throttle.html) + A tool managing concurrency level of tasks. +* [ErlangActor](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ErlangActor.html) + Actor implementation which precisely matches Erlang actor behaviour. + Requires at least Ruby 2.1 otherwise it's not loaded. +* [WrappingExecutor](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/WrappingExecutor.html) + A delegating executor which modifies each task before the task is given to + the target executor it delegates to. + +## Supported Ruby versions + +* MRI 2.3 and above +* Latest JRuby 9000 +* Latest TruffleRuby + +## Usage + +Everything within this gem can be loaded simply by requiring it: + +```ruby +require 'concurrent' +``` + +You can also require a specific abstraction [part of the public documentation](https://ruby-concurrency.github.io/concurrent-ruby/master/index.html) since concurrent-ruby 1.2.0, for example: +```ruby +require 'concurrent/map' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/executor/fixed_thread_pool' +``` + +To use the tools in the Edge gem it must be required separately: + +```ruby +require 'concurrent-edge' +``` + +If the library does not behave as expected, `Concurrent.use_stdlib_logger(Logger::DEBUG)` could +help to reveal the problem. + +## Installation + +```shell +gem install concurrent-ruby +``` + +or add the following line to Gemfile: + +```ruby +gem 'concurrent-ruby', require: 'concurrent' +``` + +and run `bundle install` from your shell. + +### Edge Gem Installation + +The Edge gem must be installed separately from the core gem: + +```shell +gem install concurrent-ruby-edge +``` + +or add the following line to Gemfile: + +```ruby +gem 'concurrent-ruby-edge', require: 'concurrent-edge' +``` + +and run `bundle install` from your shell. + + +### C Extensions for MRI + +Potential performance improvements may be achieved under MRI by installing optional C extensions. +To minimise installation errors the C extensions are available in the `concurrent-ruby-ext` +extension gem. `concurrent-ruby` and `concurrent-ruby-ext` are always released together with same +version. Simply install the extension gem too: + +```ruby +gem install concurrent-ruby-ext +``` + +or add the following line to Gemfile: + +```ruby +gem 'concurrent-ruby-ext' +``` + +and run `bundle install` from your shell. + +In code it is only necessary to + +```ruby +require 'concurrent' +``` + +The `concurrent-ruby` gem will automatically detect the presence of the `concurrent-ruby-ext` gem +and load the appropriate C extensions. + +#### Note For gem developers + +No gems should depend on `concurrent-ruby-ext`. Doing so will force C extensions on your users. The +best practice is to depend on `concurrent-ruby` and let users to decide if they want C extensions. + +## Building the gem + +### Requirements + +* Recent CRuby +* JRuby, `rbenv install jruby-9.2.17.0` +* Set env variable `CONCURRENT_JRUBY_HOME` to point to it, e.g. `/usr/local/opt/rbenv/versions/jruby-9.2.17.0` +* Install Docker, required for Windows builds + +### Publishing the Gem + +* Update `version.rb` +* Update the CHANGELOG +* Add the new version to `docs-source/signpost.md`. Needs to be done only if there are visible changes in the documentation. +* Commit (and push) the changes. +* Use `bundle exec rake release` to release the gem. + It consists of `['release:checks', 'release:build', 'release:test', 'release:publish']` steps. + It will ask at the end before publishing anything. Steps can also be executed individually. + +## Maintainers + +* [Benoit Daloze](https://github.com/eregon) +* [Matthew Draper](https://github.com/matthewd) +* [Rafael França](https://github.com/rafaelfranca) +* [Samuel Williams](https://github.com/ioquatix) + +### Special Thanks to + +* [Jerry D'Antonio](https://github.com/jdantonio) for creating the gem +* [Brian Durand](https://github.com/bdurand) for the `ref` gem +* [Charles Oliver Nutter](https://github.com/headius) for the `atomic` and `thread_safe` gems +* [thedarkone](https://github.com/thedarkone) for the `thread_safe` gem + +to the past maintainers + +* [Chris Seaton](https://github.com/chrisseaton) +* [Petr Chalupa](https://github.com/pitr-ch) +* [Michele Della Torre](https://github.com/mighe) +* [Paweł Obrok](https://github.com/obrok) +* [Lucas Allan](https://github.com/lucasallan) + +and to [Ruby Association](https://www.ruby.or.jp/en/) for sponsoring a project +["Enhancing Ruby’s concurrency tooling"](https://www.ruby.or.jp/en/news/20181106) in 2018. + +## License and Copyright + +*Concurrent Ruby* is free software released under the +[MIT License](http://www.opensource.org/licenses/MIT). + +The *Concurrent Ruby* [logo](https://raw.githubusercontent.com/ruby-concurrency/concurrent-ruby/master/docs-source/logo/concurrent-ruby-logo-300x300.png) was +designed by [David Jones](https://twitter.com/zombyboy). It is Copyright © 2014 +[Jerry D'Antonio](https://twitter.com/jerrydantonio). All Rights Reserved. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/Rakefile b/app/server/ruby/vendor/concurrent-ruby-1.2.2/Rakefile new file mode 100644 index 0000000000..f167f4659e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/Rakefile @@ -0,0 +1,319 @@ +require_relative 'lib/concurrent-ruby/concurrent/version' +require_relative 'lib/concurrent-ruby-edge/concurrent/edge/version' +require_relative 'lib/concurrent-ruby/concurrent/utility/engine' + +core_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby.gemspec') +ext_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby-ext.gemspec') +edge_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby-edge.gemspec') + +require 'rake/javaextensiontask' + +ENV['JRUBY_HOME'] = ENV['CONCURRENT_JRUBY_HOME'] if ENV['CONCURRENT_JRUBY_HOME'] && !Concurrent.on_jruby? + +Rake::JavaExtensionTask.new('concurrent_ruby', core_gemspec) do |ext| + ext.ext_dir = 'ext/concurrent-ruby' + ext.lib_dir = 'lib/concurrent-ruby/concurrent' +end + +unless Concurrent.on_jruby? || Concurrent.on_truffleruby? + require 'rake/extensiontask' + + Rake::ExtensionTask.new('concurrent_ruby_ext', ext_gemspec) do |ext| + ext.ext_dir = 'ext/concurrent-ruby-ext' + ext.lib_dir = 'lib/concurrent-ruby/concurrent' + ext.source_pattern = '*.{c,h}' + + ext.cross_compile = true + ext.cross_platform = ['x86-mingw32', 'x64-mingw32'] + end +end + +require 'rake_compiler_dock' +namespace :repackage do + desc '* with Windows fat distributions' + task :all do + Dir.chdir(__dir__) do + # store gems in vendor cache for docker + Bundler.with_original_env do + sh 'bundle package' + end + + # build only the jar file not the whole gem for java platform, the jar is part the concurrent-ruby-x.y.z.gem + Rake::Task['lib/concurrent-ruby/concurrent/concurrent_ruby.jar'].invoke + + # build all gem files + %w[x86-mingw32 x64-mingw32].each do |plat| + RakeCompilerDock.sh( + "bundle install --local && bundle exec rake native:#{plat} gem --trace", + platform: plat, + options: ['--privileged'], # otherwise the directory in the image is empty + runas: false) + end + end + end +end + +require 'rubygems' +require 'rubygems/package_task' + +Gem::PackageTask.new(core_gemspec) {} if core_gemspec +Gem::PackageTask.new(ext_gemspec) {} if ext_gemspec && !Concurrent.on_jruby? +Gem::PackageTask.new(edge_gemspec) {} if edge_gemspec + +CLEAN.include( + 'lib/concurrent-ruby/concurrent/concurrent_ruby_ext.*', + 'lib/concurrent-ruby/concurrent/2.*', + 'lib/concurrent-ruby/concurrent/*.jar') + +begin + require 'rspec' + require 'rspec/core/rake_task' + + RSpec::Core::RakeTask.new(:spec) + + namespace :spec do + desc '* Configured for ci' + RSpec::Core::RakeTask.new(:ci) do |t| + options = %w[ --color + --backtrace + --order defined + --format documentation ] + t.rspec_opts = [*options].join(' ') + end + + desc '* test packaged and installed gems instead of local files' + task :installed do + Bundler.with_original_env do + Dir.chdir(__dir__) do + sh "gem install pkg/concurrent-ruby-#{Concurrent::VERSION}.gem" + sh "gem install pkg/concurrent-ruby-ext-#{Concurrent::VERSION}.gem" if Concurrent.on_cruby? + sh "gem install pkg/concurrent-ruby-edge-#{Concurrent::EDGE_VERSION}.gem" + ENV['NO_PATH'] = 'true' + sh 'bundle update' + sh 'bundle exec rake spec:ci' + end + end + end + end + + desc 'executed in CI' + task :ci => [:compile, 'spec:ci'] + + desc 'run each spec file in a separate process to help find missing requires' + task 'spec:isolated' do + glob = "#{ENV['DIR'] || 'spec'}/**/*_spec.rb" + from = ENV['FROM'] + env = { 'ISOLATED' => 'true' } + Dir[glob].each do |spec| + next if from and from != spec + from = nil if from == spec + + sh env, 'rspec', spec + end + end + + task :default => [:clobber, :compile, :spec] +rescue LoadError => e + puts 'RSpec is not installed, skipping test task definitions: ' + e.message +end + +current_yard_version_name = Concurrent::VERSION + +begin + require 'yard' + require 'md_ruby_eval' + require_relative 'support/yard_full_types' + + common_yard_options = ['--no-yardopts', + '--no-document', + '--no-private', + '--embed-mixins', + '--markup', 'markdown', + '--title', 'Concurrent Ruby', + '--template', 'default', + '--template-path', 'yard-template', + '--default-return', 'undocumented'] + + desc 'Generate YARD Documentation (signpost, master)' + task :yard => ['yard:signpost', 'yard:master'] + + namespace :yard do + + desc '* eval markdown files' + task :eval_md do + Dir.chdir File.join(__dir__, 'docs-source') do + sh 'bundle exec md-ruby-eval --auto' + end + end + + task :update_readme do + Dir.chdir __dir__ do + content = File.read(File.join('README.md')). + gsub(/\[([\w ]+)\]\(http:\/\/ruby-concurrency\.github\.io\/concurrent-ruby\/master\/.*\)/) do |_| + case $1 + when 'LockFreeLinkedSet' + "{Concurrent::Edge::#{$1} #{$1}}" + when '.dataflow' + '{Concurrent.dataflow Concurrent.dataflow}' + when 'thread pool' + '{file:thread_pools.md thread pool}' + else + "{Concurrent::#{$1} #{$1}}" + end + end + FileUtils.mkpath 'tmp' + File.write 'tmp/README.md', content + end + end + + define_yard_task = -> name do + output_dir = "docs/#{name}" + + removal_name = "remove.#{name}" + task removal_name do + Dir.chdir __dir__ do + FileUtils.rm_rf output_dir + end + end + + desc "* of #{name} into subdir #{name}" + YARD::Rake::YardocTask.new(name) do |yard| + yard.options.push( + '--output-dir', output_dir, + '--main', 'tmp/README.md', + *common_yard_options) + yard.files = ['./lib/concurrent-ruby/**/*.rb', + './lib/concurrent-ruby-edge/**/*.rb', + './ext/concurrent_ruby_ext/**/*.c', + '-', + 'docs-source/thread_pools.md', + 'docs-source/promises.out.md', + 'docs-source/medium-example.out.rb', + 'LICENSE.txt', + 'CHANGELOG.md'] + end + Rake::Task[name].prerequisites.push removal_name, + # 'yard:eval_md', + 'yard:update_readme' + end + + define_yard_task.call current_yard_version_name + define_yard_task.call 'master' + + desc "* signpost for versions" + YARD::Rake::YardocTask.new(:signpost) do |yard| + yard.options.push( + '--output-dir', 'docs', + '--main', 'docs-source/signpost.md', + *common_yard_options) + yard.files = ['no-lib'] + end + end + +rescue LoadError => e + puts 'YARD is not installed, skipping documentation task definitions: ' + e.message +end + +desc 'build, test, and publish the gem' +task :release => ['release:checks', 'release:build', 'release:test', 'release:publish'] + +namespace :release do + # Depends on environment of @pitr-ch + + task :checks do + Dir.chdir(__dir__) do + sh 'test -z "$(git status --porcelain)"' do |ok, res| + unless ok + begin + status = `git status --porcelain` + STDOUT.puts 'There are local changes that you might want to commit.', status, 'Continue? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + exit 1 if input == 'n' + end + end + sh 'git fetch' + sh 'test $(git show-ref --verify --hash refs/heads/master) = ' + + '$(git show-ref --verify --hash refs/remotes/origin/master)' do |ok, res| + unless ok + begin + STDOUT.puts 'Local master branch is not pushed to origin.', 'Continue? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + exit 1 if input == 'n' + end + end + end + end + + desc '* build all *.gem files necessary for release' + task :build => [:clobber, 'repackage:all'] + + desc '* test actual installed gems instead of cloned repository on MRI and JRuby' + task :test do + Dir.chdir(__dir__) do + puts "Testing with the installed gem" + + Bundler.with_original_env do + sh 'ruby -v' + sh 'bundle exec rake spec:installed' + + env = { "PATH" => "#{ENV['CONCURRENT_JRUBY_HOME']}/bin:#{ENV['PATH']}" } + sh env, 'ruby -v' + sh env, 'bundle exec rake spec:installed' + end + + puts 'Windows build is untested' + end + end + + desc '* do all nested steps' + task :publish => ['publish:ask', 'publish:tag', 'publish:rubygems', 'publish:post_steps'] + + namespace :publish do + publish_base = true + publish_edge = false + + task :ask do + begin + STDOUT.puts 'Do you want to publish anything now? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + exit 1 if input == 'n' + begin + STDOUT.puts 'It will publish `concurrent-ruby`. Do you want to publish `concurrent-ruby-edge`? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + publish_edge = input == 'y' + end + + desc '** tag HEAD with current version and push to github' + task :tag => :ask do + Dir.chdir(__dir__) do + sh "git tag v#{Concurrent::VERSION}" if publish_base + sh "git push origin v#{Concurrent::VERSION}" if publish_base + sh "git tag edge-v#{Concurrent::EDGE_VERSION}" if publish_edge + sh "git push origin edge-v#{Concurrent::EDGE_VERSION}" if publish_edge + end + end + + desc '** push all *.gem files to rubygems' + task :rubygems => :ask do + Dir.chdir(__dir__) do + sh "gem push pkg/concurrent-ruby-#{Concurrent::VERSION}.gem" if publish_base + sh "gem push pkg/concurrent-ruby-edge-#{Concurrent::EDGE_VERSION}.gem" if publish_edge + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}.gem" if publish_base + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}-x64-mingw32.gem" if publish_base + sh "gem push pkg/concurrent-ruby-ext-#{Concurrent::VERSION}-x86-mingw32.gem" if publish_base + end + end + + desc '** print post release steps' + task :post_steps do + # TODO: (petr 05-Jun-2021) automate and renew the process + puts 'Manually: create a release on GitHub with relevant changelog part' + puts 'Manually: send email same as release with relevant changelog part' + puts 'Manually: tweet' + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby-edge.gemspec b/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby-edge.gemspec new file mode 100644 index 0000000000..02383d3468 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby-edge.gemspec @@ -0,0 +1,29 @@ +require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby/concurrent/version') +require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby-edge/concurrent/edge/version') + +Gem::Specification.new do |s| + git_files = `git ls-files`.split("\n") + + s.name = 'concurrent-ruby-edge' + s.version = Concurrent::EDGE_VERSION + s.platform = Gem::Platform::RUBY + s.authors = ["Jerry D'Antonio", 'Petr Chalupa', 'The Ruby Concurrency Team'] + s.email = 'concurrent-ruby@googlegroups.com' + s.homepage = 'http://www.concurrent-ruby.com' + s.summary = 'Edge features and additions to the concurrent-ruby gem.' + s.license = 'MIT' + s.date = Time.now.strftime('%Y-%m-%d') + s.files = Dir['lib/concurrent-ruby-edge/**/*.rb'] & git_files + s.extra_rdoc_files = Dir['README*', 'LICENSE*', 'CHANGELOG*'] + s.require_paths = ['lib/concurrent-ruby-edge'] + s.description = <<-TXT +These features are under active development and may change frequently. They are expected not to +keep backward compatibility (there may also lack tests and documentation). Semantic versions will +be obeyed though. Features developed in `concurrent-ruby-edge` are expected to move to `concurrent-ruby` when final. +Please see http://concurrent-ruby.com for more information. + TXT + + s.required_ruby_version = '>= 2.3' + + s.add_runtime_dependency 'concurrent-ruby', "~> #{Concurrent::VERSION}" +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby-ext.gemspec b/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby-ext.gemspec new file mode 100644 index 0000000000..534822de09 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby-ext.gemspec @@ -0,0 +1,27 @@ +require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby/concurrent/version') + +Gem::Specification.new do |s| + s.name = 'concurrent-ruby-ext' + s.version = Concurrent::VERSION + s.platform = Gem::Platform::RUBY + s.authors = ["Jerry D'Antonio", 'The Ruby Concurrency Team'] + s.email = 'concurrent-ruby@googlegroups.com' + s.homepage = 'http://www.concurrent-ruby.com' + s.summary = 'C extensions to optimize concurrent-ruby under MRI.' + s.license = 'MIT' + s.date = Time.now.strftime('%Y-%m-%d') + + s.description = <<-EOF + C extensions to optimize the concurrent-ruby gem when running under MRI. + Please see http://concurrent-ruby.com for more information. + EOF + + s.files = Dir['ext/**/*.{h,c,cpp}'] + s.extra_rdoc_files = Dir['README*', 'LICENSE*', 'CHANGELOG*'] + s.require_paths = ['lib'] + s.extensions = 'ext/concurrent-ruby-ext/extconf.rb' + + s.required_ruby_version = '>= 2.3' + + s.add_runtime_dependency 'concurrent-ruby', "= #{Concurrent::VERSION}" +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby.gemspec b/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby.gemspec new file mode 100644 index 0000000000..863201b541 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/concurrent-ruby.gemspec @@ -0,0 +1,30 @@ +require File.join(File.dirname(__FILE__ ), 'lib/concurrent-ruby/concurrent/version') + +Gem::Specification.new do |s| + git_files = `git ls-files`.split("\n") + + s.name = 'concurrent-ruby' + s.version = Concurrent::VERSION + s.platform = Gem::Platform::RUBY + s.authors = ["Jerry D'Antonio", 'Petr Chalupa', 'The Ruby Concurrency Team'] + s.email = 'concurrent-ruby@googlegroups.com' + s.homepage = 'http://www.concurrent-ruby.com' + s.summary = 'Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.' + s.license = 'MIT' + s.date = Time.now.strftime('%Y-%m-%d') + s.files = [*Dir['lib/concurrent-ruby/**/*.rb'] & git_files, + *Dir['ext/concurrent-ruby/**/*'] & git_files, + 'Rakefile', + 'Gemfile', + 'lib/concurrent-ruby/concurrent/concurrent_ruby.jar' + ] + s.extra_rdoc_files = Dir['README*', 'LICENSE*', 'CHANGELOG*'] + s.require_paths = ['lib/concurrent-ruby'] + s.description = <<-TXT.gsub(/^ +/, '') + Modern concurrency tools including agents, futures, promises, thread pools, actors, supervisors, and more. + Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency patterns. + TXT + s.metadata["source_code_uri"] = "https://github.com/ruby-concurrency/concurrent-ruby" + s.metadata["changelog_uri"] = "https://github.com/ruby-concurrency/concurrent-ruby/blob/master/CHANGELOG.md" + s.required_ruby_version = '>= 2.3' +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/celluloid_benchmark.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/celluloid_benchmark.rb new file mode 100644 index 0000000000..dea20e553e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/celluloid_benchmark.rb @@ -0,0 +1,105 @@ +require 'benchmark' +require 'concurrent/actor' + +require 'celluloid' +require 'celluloid/autostart' + +# require 'stackprof' +# require 'profiler' + +logger = Logger.new($stderr) +logger.level = Logger::INFO +Concurrent.configuration.logger = lambda do |level, progname, message = nil, &block| + logger.add level, message, progname, &block +end + +scale = 1 +ADD_TO = (100 * scale).to_i +counts_size = (500 * scale).to_i +adders_size = (500 * scale).to_i + +class Counter + include Celluloid + + def initialize(adders, i) + @adders = adders + @i = i + end + + def counting(count, ivar) + if count < ADD_TO + @adders[(@i+1) % @adders.size].counting count+1, ivar + else + ivar.set count + end + end +end if defined? Celluloid + +threads = [] + +# Profiler__.start_profile +# StackProf.run(mode: :cpu, +# interval: 10, +# out: File.join(File.dirname(__FILE__), 'stackprof-cpu-myapp.dump')) do +Benchmark.bmbm(10) do |b| + [2, adders_size, adders_size*2, adders_size*3].each do |adders_size| + + b.report(format('%5d %4d %s', ADD_TO*counts_size, adders_size, 'concurrent')) do + counts = Array.new(counts_size) { [0, Concurrent::IVar.new] } + adders = Array.new(adders_size) do |i| + Concurrent::Actor::Utils::AdHoc.spawn("adder#{i}") do + lambda do |(count, ivar)| + if count < ADD_TO + adders[(i+1) % adders_size].tell [count+1, ivar] + else + ivar.set count + end + end + end + end + + counts.each_with_index do |count, i| + adders[i % adders_size].tell count + end + + counts.each do |count, ivar| + raise unless ivar.value >= ADD_TO + end + + threads << Thread.list.size + + adders.each { |a| a << :terminate! } + end + + if defined? Celluloid + b.report(format('%5d %4d %s', ADD_TO*counts_size, adders_size, 'celluloid')) do + counts = [] + counts_size.times { counts << [0, Concurrent::IVar.new] } + + adders = [] + adders_size.times do |i| + adders << Counter.new(adders, i) + end + + counts.each_with_index do |count, i| + adders[i % adders_size].counting *count + end + + counts.each do |count, ivar| + raise unless ivar.value >= ADD_TO + end + + threads << Thread.list.size + + adders.each(&:terminate) + end + end + end +end +# end +# Profiler__.stop_profile + +p threads + +# Profiler__.print_profile $stdout + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/define.in.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/define.in.rb new file mode 100644 index 0000000000..565ff6c5a4 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/define.in.rb @@ -0,0 +1,36 @@ +Message = Struct.new :action, :value # + +class AnActor < Concurrent::Actor::RestartingContext + def initialize(init) + @counter = init + end + + # override #on_message to define actor's behaviour on message received + def on_message(message) + case message.action + when :add + @counter = @counter + message.value + when :subtract + @counter = @counter - message.value + when :value + @counter + else + pass + end + end + + # set counter to zero when there is an error + def on_event(event) + if event == :reset + @counter = 0 # ignore initial value + end + end +end # + +an_actor = AnActor.spawn name: 'an_actor', args: 10 # +an_actor << Message.new(:add, 1) << Message.new(:subtract, 2) # +an_actor.ask!(Message.new(:value, nil)) +an_actor << :boo << Message.new(:add, 1) # +an_actor.ask!(Message.new(:value, nil)) +an_actor << :terminate! + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/define.out.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/define.out.rb new file mode 100644 index 0000000000..57823e8929 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/define.out.rb @@ -0,0 +1,37 @@ +Message = Struct.new :action, :value + +class AnActor < Concurrent::Actor::RestartingContext + def initialize(init) + @counter = init + end + + # override #on_message to define actor's behaviour on message received + def on_message(message) + case message.action + when :add + @counter = @counter + message.value + when :subtract + @counter = @counter - message.value + when :value + @counter + else + pass + end + end + + # set counter to zero when there is an error + def on_event(event) + if event == :reset + @counter = 0 # ignore initial value + end + end +end + +an_actor = AnActor.spawn name: 'an_actor', args: 10 +an_actor << Message.new(:add, 1) << Message.new(:subtract, 2) +an_actor.ask!(Message.new(:value, nil)) # => 9 +an_actor << :boo << Message.new(:add, 1) +an_actor.ask!(Message.new(:value, nil)) # => 1 +an_actor << :terminate! + # => # + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/examples.in.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/examples.in.rb new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/examples.in.rb @@ -0,0 +1,3 @@ + + + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/examples.out.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/examples.out.rb new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/examples.out.rb @@ -0,0 +1,3 @@ + + + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/format.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/format.rb new file mode 100644 index 0000000000..4ac84b0e99 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/format.rb @@ -0,0 +1,76 @@ +require 'rubygems' +require 'bundler/setup' +require 'pry' +require 'pp' + +root = File.dirname(File.expand_path(Process.argv0)) +input_paths = if ARGV.empty? + Dir.glob("#{root}/*.in.rb") + else + ARGV + end.map { |p| File.expand_path p } + +input_paths.each_with_index do |input_path, i| + + pid = fork do + require_relative 'init' + + begin + output_path = input_path.gsub /\.in\.rb$/, '.out.rb' + input = File.readlines(input_path) + + chunks = [] + line = '' + + while !input.empty? + line += input.shift + if Pry::Code.complete_expression? line + chunks << line + line = '' + end + end + + raise unless line.empty? + + chunks.map! { |chunk| [chunk, [chunk.split($/).size, 1].max] } + environment = Module.new.send :binding + evaluate = ->(code, line) do + eval(code, environment, input_path, line) + end + + indent = 50 + + line_count = 1 + output = '' + chunks.each do |chunk, lines| + result = evaluate.(chunk, line_count) + unless chunk.strip.empty? || chunk =~ /\A *#/ + pre_lines = chunk.lines.to_a + last_line = pre_lines.pop + output << pre_lines.join + + if last_line =~ /\#$/ + output << last_line.gsub(/\#$/, '') + else + if last_line.size < indent && result.inspect.size < indent + output << "%-#{indent}s %s" % [last_line.chomp, "# => #{result.inspect}\n"] + else + output << last_line << " # => #{result.inspect}\n" + end + end + else + output << chunk + end + line_count += lines + end + + puts "#{input_path}\n -> #{output_path}" + #puts output + File.write(output_path, output) + rescue => ex + puts "#{ex} (#{ex.class})\n#{ex.backtrace * "\n"}" + end + end + + Process.wait pid +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/init.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/init.rb new file mode 100644 index 0000000000..30261d1892 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/init.rb @@ -0,0 +1,2 @@ +require 'concurrent' +require 'concurrent-edge' diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/io.in.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/io.in.rb new file mode 100644 index 0000000000..6bbc1a07c6 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/io.in.rb @@ -0,0 +1,49 @@ +require 'concurrent' + +# logger = Logger.new(STDOUT) +# Concurrent.configuration.logger = logger.method(:add) + +# First option is to use operation pool + +class ActorDoingIO < Concurrent::Actor::RestartingContext + def on_message(message) + # do IO operation + end + + def default_executor + Concurrent.global_io_executor + end +end # + +actor_doing_io = ActorDoingIO.spawn :actor_doing_io +actor_doing_io.executor == Concurrent.global_io_executor + +# It can be also built into a pool so there is not too many IO operations + +class IOWorker < Concurrent::Actor::Context + def on_message(io_job) + # do IO work + sleep 0.1 + puts "#{path} second:#{(Time.now.to_f*100).floor} message:#{io_job}" + end + + def default_executor + Concurrent.global_io_executor + end +end # + +pool = Concurrent::Actor::Utils::Pool.spawn('pool', 2) do |index| + IOWorker.spawn(name: "worker-#{index}") +end + +pool << 1 << 2 << 3 << 4 << 5 << 6 + +# prints two lines each second +# /pool/worker-0 second:1414677666 message:1 +# /pool/worker-1 second:1414677666 message:2 +# /pool/worker-0 second:1414677667 message:3 +# /pool/worker-1 second:1414677667 message:4 +# /pool/worker-0 second:1414677668 message:5 +# /pool/worker-1 second:1414677668 message:6 + +sleep 1 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/io.out.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/io.out.rb new file mode 100644 index 0000000000..b96cfc72a0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/io.out.rb @@ -0,0 +1,53 @@ +require 'concurrent' # => false + +# logger = Logger.new(STDOUT) +# Concurrent.configuration.logger = logger.method(:add) + +# First option is to use operation pool + +class ActorDoingIO < Concurrent::Actor::RestartingContext + def on_message(message) + # do IO operation + end + + def default_executor + Concurrent.global_io_executor + end +end + +actor_doing_io = ActorDoingIO.spawn :actor_doing_io + # => # +actor_doing_io.executor == Concurrent.global_io_executor + # => true + +# It can be also built into a pool so there is not too many IO operations + +class IOWorker < Concurrent::Actor::Context + def on_message(io_job) + # do IO work + sleep 0.1 + puts "#{path} second:#{(Time.now.to_f*100).floor} message:#{io_job}" + end + + def default_executor + Concurrent.global_io_executor + end +end + +pool = Concurrent::Actor::Utils::Pool.spawn('pool', 2) do |index| + IOWorker.spawn(name: "worker-#{index}") +end + # => # + +pool << 1 << 2 << 3 << 4 << 5 << 6 + # => # + +# prints two lines each second +# /pool/worker-0 second:1414677666 message:1 +# /pool/worker-1 second:1414677666 message:2 +# /pool/worker-0 second:1414677667 message:3 +# /pool/worker-1 second:1414677667 message:4 +# /pool/worker-0 second:1414677668 message:5 +# /pool/worker-1 second:1414677668 message:6 + +sleep 1 # => 1 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/main.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/main.md new file mode 100644 index 0000000000..43cf72798b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/main.md @@ -0,0 +1,228 @@ +# Actor model + +- Light-weighted running on thread-pool. +- Inspired by Akka and Erlang. +- Modular. + +This Actor model implementation makes actors very cheap to create and discard. +Thousands of actors can be created, allowing you to break the program into smaller +maintainable pieces, without violating the single responsibility principle. + +## What is an actor model? + +Actor-based concurrency is all the rage in some circles. Originally described in 1973, the actor model is a paradigm +for creating asynchronous, concurrent objects that is becoming increasingly popular. Much has changed since actors +were first written about four decades ago, which has led to a serious fragmentation within the actor community. +There is *no* universally accepted, strict definition of "actor" and actor implementations differ widely between +languages and libraries. + +[Wiki](http://en.wikipedia.org/wiki/Actor_model) definition is pretty good: +_The actor model in computer science is a mathematical model of concurrent computation +that treats **actors** as the universal primitives of concurrent digital computation: +in response to a message that it receives, an actor can make local decisions, +create more actors, send more messages, and determine how to respond to the next +message received._ + +## Why? + +Concurrency is hard to get right, actors are one of many ways how to simplify the problem. + +## Quick example + +An example: + +```ruby +class Counter < Concurrent::Actor::Context + # Include context of an actor which gives this class access to reference + # and other information about the actor + + # use initialize as you wish + def initialize(initial_value) + @count = initial_value + end + + # override on_message to define actor's behaviour + def on_message(message) + if Integer === message + @count += message + end + end +end # + +# Create new actor naming the instance 'first'. +# Return value is a reference to the actor, the actual actor is never returned. +counter = Counter.spawn(:first, 5) + +# Tell a message and forget returning self. +counter.tell(1) +counter << 1 +# (First counter now contains 7.) + +# Send a messages asking for a result. +counter.ask(0).class +counter.ask(0).value +``` + +{include:file:docs-source/actor/quick.out.rb} + +## Spawning actors + +- {Concurrent::Actor.spawn} and {Concurrent::Actor.spawn!} +- {Concurrent::Actor::AbstractContext.spawn} and {Concurrent::Actor::AbstractContext.spawn!} + +## Sending messages + +- {Concurrent::Actor::Reference#tell} + {include:Concurrent::Actor::Reference#tell} +- {Concurrent::Actor::Reference#ask} + {include:Concurrent::Actor::Reference#ask} +- {Concurrent::Actor::Reference#ask!} + {include:Concurrent::Actor::Reference#ask!} + +Messages are processed in same order as they are sent by a sender. It may interleaved with +messages from other senders though. + +### Immutability + +Messages sent between actors should be **immutable**. Gems like + +- [Algebrick](https://github.com/pitr-ch/algebrick) - Typed struct on steroids based on + algebraic types and pattern matching +- [Hamster](https://github.com/hamstergem/hamster) - Efficient, Immutable, Thread-Safe + Collection classes for Ruby + +are very helpful. + +{include:file:docs-source/actor/messaging.out.rb} + +## Actor definition + +{include:Concurrent::Actor::AbstractContext} + +## Reference + +{include:Actor::Reference} + +## Garbage collection + +Spawned actor cannot be garbage-collected until it's terminated. There is a reference held in the parent actor. + +## Parent-child relationship, name, and path + +- {Core#name} + {include:Actor::Core#name} +- {Core#path} + {include:Actor::Core#path} +- {Core#parent} + {include:Actor::Core#parent} + +## Behaviour + +{include:Actor::Behaviour} + +## IO cooperation + +Actors are running on shared thread poll which allows user to create many actors cheaply. +Downside is that these actors cannot be directly used to do IO or other blocking operations. +Blocking operations could starve the `default_task_pool`. However there are two options: + +- Create an regular actor which will schedule blocking operations in `global_operation_pool` + (which is intended for blocking operations) sending results back to self in messages. +- Create an actor using `global_operation_pool` instead of `global_task_pool`, e.g. + `AnIOActor.spawn name: :blocking, executor: Concurrent.configuration.global_operation_pool`. + +### Example + +{include:file:docs-source/actor/io.out.rb} + +## Dead letter routing + +see {AbstractContext#dead_letter_routing} description: + +> {include:Actor::AbstractContext#dead_letter_routing} + +## FAQ + +### What happens if I try to supervise using a normal Context? + +Alleged supervisor will receive errors from its supervised actors. They'll have to be handled manually. + +### How to change supervision strategy? + +Use option `behaviour_definition: Behaviour.restarting_behaviour_definition(:resume!)` or +`behaviour_definition: Behaviour.restarting_behaviour_definition(:reset!, :one_for_all)` + +### How to change behaviors? + +Any existing behavior can be subclassed + +### How to implement custom restarting? + +By subclassing {Behaviour::Pausing} and overriding {Behaviour::Pausing#restart!}. Implementing +{AbstractContext#on_event} could be also considered. + +_We'll be happy to answer any other questions, +just [open an Issue](https://github.com/ruby-concurrency/concurrent-ruby/issues/new) or find us on +https://gitter.im/ruby-concurrency/concurrent-ruby._ + +## Speed + +Simple benchmark Actor vs Celluloid, the numbers are looking good +but you know how it is with benchmarks. Source code is in +`examples/actor/celluloid_benchmark.rb`. It sends numbers between x actors +and adding 1 until certain limit is reached. + +Benchmark legend: + +- mes. - number of messages send between the actors +- act. - number of actors exchanging the messages +- impl. - which gem is used + +### JRUBY + + Rehearsal --------------------------------------------------------- + 50000 2 concurrent 26.140000 0.610000 26.750000 ( 7.761000) + 50000 2 celluloid 41.440000 5.270000 46.710000 ( 17.535000) + 50000 500 concurrent 11.340000 0.180000 11.520000 ( 3.498000) + 50000 500 celluloid 19.310000 10.680000 29.990000 ( 14.619000) + 50000 1000 concurrent 10.640000 0.180000 10.820000 ( 3.563000) + 50000 1000 celluloid 17.840000 19.850000 37.690000 ( 18.892000) + 50000 1500 concurrent 14.120000 0.290000 14.410000 ( 4.618000) + 50000 1500 celluloid 19.060000 28.920000 47.980000 ( 25.185000) + ---------------------------------------------- total: 225.870000sec + + mes. act. impl. user system total real + 50000 2 concurrent 7.320000 0.530000 7.850000 ( 3.637000) + 50000 2 celluloid 13.780000 4.730000 18.510000 ( 10.756000) + 50000 500 concurrent 9.270000 0.140000 9.410000 ( 3.020000) + 50000 500 celluloid 16.540000 10.920000 27.460000 ( 14.308000) + 50000 1000 concurrent 9.970000 0.160000 10.130000 ( 3.445000) + 50000 1000 celluloid 15.930000 20.840000 36.770000 ( 18.272000) + 50000 1500 concurrent 11.580000 0.240000 11.820000 ( 3.723000) + 50000 1500 celluloid 19.440000 29.060000 48.500000 ( 25.227000) (1) + +### MRI 2.1.0 + + Rehearsal --------------------------------------------------------- + 50000 2 concurrent 4.180000 0.080000 4.260000 ( 4.269435) + 50000 2 celluloid 7.740000 3.100000 10.840000 ( 10.043875) + 50000 500 concurrent 5.900000 1.310000 7.210000 ( 6.565067) + 50000 500 celluloid 12.820000 5.810000 18.630000 ( 17.320765) + 50000 1000 concurrent 6.080000 1.640000 7.720000 ( 6.931294) + 50000 1000 celluloid 17.130000 8.320000 25.450000 ( 23.786146) + 50000 1500 concurrent 6.940000 2.030000 8.970000 ( 7.927330) + 50000 1500 celluloid 20.980000 12.040000 33.020000 ( 30.849578) + ---------------------------------------------- total: 116.100000sec + + mes. act. impl. user system total real + 50000 2 concurrent 3.730000 0.100000 3.830000 ( 3.822688) + 50000 2 celluloid 7.900000 2.910000 10.810000 ( 9.924014) + 50000 500 concurrent 5.420000 1.230000 6.650000 ( 6.025579) + 50000 500 celluloid 12.720000 5.540000 18.260000 ( 16.889517) + 50000 1000 concurrent 5.420000 0.910000 6.330000 ( 5.896689) + 50000 1000 celluloid 16.090000 8.040000 24.130000 ( 22.347102) + 50000 1500 concurrent 5.580000 0.760000 6.340000 ( 6.038535) + 50000 1500 celluloid 20.000000 11.680000 31.680000 ( 29.590774) (1) + +*Note (1):* Celluloid is using thread per actor so this bench is creating about 1500 +native threads. Actor is using constant number of threads. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/messaging.in.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/messaging.in.rb new file mode 100644 index 0000000000..8474481810 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/messaging.in.rb @@ -0,0 +1,32 @@ +require 'algebrick' + +# Actor message protocol definition with Algebrick +Protocol = Algebrick.type do + variants Add = type { fields! a: Numeric, b: Numeric }, + Subtract = type { fields! a: Numeric, b: Numeric } +end + +class Calculator < Concurrent::Actor::RestartingContext + include Algebrick::Matching + + def on_message(message) + # pattern matching on the message with deconstruction + # ~ marks values which are passed to the block + match message, + (on Add.(~any, ~any) do |a, b| + a + b + end), + # or using multi-assignment + (on ~Subtract do |(a, b)| + a - b + end) + end +end # + +calculator = Calculator.spawn('calculator') +addition = calculator.ask Add[1, 2] +subtraction = calculator.ask Subtract[1, 0.5] +results = (addition & subtraction) +results.value! + +calculator.ask! :terminate! diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/messaging.out.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/messaging.out.rb new file mode 100644 index 0000000000..64ec90c86e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/messaging.out.rb @@ -0,0 +1,36 @@ +require 'algebrick' # => true + +# Actor message protocol definition with Algebrick +Protocol = Algebrick.type do + variants Add = type { fields! a: Numeric, b: Numeric }, + Subtract = type { fields! a: Numeric, b: Numeric } +end # => Protocol(Add | Subtract) + +class Calculator < Concurrent::Actor::RestartingContext + include Algebrick::Matching + + def on_message(message) + # pattern matching on the message with deconstruction + # ~ marks values which are passed to the block + match message, + (on Add.(~any, ~any) do |a, b| + a + b + end), + # or using multi-assignment + (on ~Subtract do |(a, b)| + a - b + end) + end +end + +calculator = Calculator.spawn('calculator') + # => # +addition = calculator.ask Add[1, 2] + # => <#Concurrent::Promises::Future:0x7fbedc05f7b0 pending> +subtraction = calculator.ask Subtract[1, 0.5] + # => <#Concurrent::Promises::Future:0x7fbedd891388 pending> +results = (addition & subtraction) + # => <#Concurrent::Promises::Future:0x7fbedc04eeb0 pending> +results.value! # => [3, 0.5] + +calculator.ask! :terminate! # => true diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/quick.in.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/quick.in.rb new file mode 100644 index 0000000000..85af9cdeb8 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/quick.in.rb @@ -0,0 +1,30 @@ +class Adder < Concurrent::Actor::RestartingContext + def initialize(init) + @count = init + end + + def on_message(message) + case message + when :add + @count += 1 + else + # pass to ErrorsOnUnknownMessage behaviour, which will just fail + pass + end + end +end # + +# `link: true` makes the actor linked to root actor and supervised +# which is default behavior +adder = Adder.spawn(name: :adder, link: true, args: [1]) +adder.parent + +# tell and forget +adder.tell(:add).tell(:add) +# ask to get result +adder.ask!(:add) +# fail the actor +adder.ask!(:bad) rescue $! +# actor is restarted with initial values +adder.ask!(:add) +adder.ask!(:terminate!) diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/quick.out.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/quick.out.rb new file mode 100644 index 0000000000..dbfa8d3513 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/quick.out.rb @@ -0,0 +1,34 @@ +class Adder < Concurrent::Actor::RestartingContext + def initialize(init) + @count = init + end + + def on_message(message) + case message + when :add + @count += 1 + else + # pass to ErrorsOnUnknownMessage behaviour, which will just fail + pass + end + end +end + +# `link: true` makes the actor linked to root actor and supervised +# which is default behavior +adder = Adder.spawn(name: :adder, link: true, args: [1]) + # => # +adder.parent + # => # + +# tell and forget +adder.tell(:add).tell(:add) + # => # +# ask to get result +adder.ask!(:add) # => 4 +# fail the actor +adder.ask!(:bad) rescue $! + # => #> +# actor is restarted with initial values +adder.ask!(:add) # => 2 +adder.ask!(:terminate!) # => true diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/supervision_tree.in.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/supervision_tree.in.rb new file mode 100644 index 0000000000..21a7b52890 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/supervision_tree.in.rb @@ -0,0 +1,68 @@ + +class Master < Concurrent::Actor::RestartingContext + def initialize + # create listener a supervised child of master + @listener = Listener.spawn(name: 'listener1', supervise: true) + end + + def on_message(msg) + command, *args = msg + case command + when :listener + @listener + when :reset, :terminated, :resumed, :paused + log(DEBUG) { " got #{msg} from #{envelope.sender}"} + else + pass + end + end + + # TODO this should be a part of a behaviour, it ensures that children are restarted/paused etc. when theirs parents are + def on_event(event) + event_name, _ = event + case event_name + when :resetting, :restarting + @listener << :terminate! + when Exception, :paused + @listener << :pause! + when :resumed + @listener << :resume! + end + end +end # + +class Listener < Concurrent::Actor::RestartingContext + def initialize + @number = (rand() * 100).to_i + end + + def on_message(msg) + case msg + when :number + @number + else + pass + end + end + +end # + +master = Master.spawn(name: 'master', supervise: true) +listener = master.ask!(:listener) +listener.ask!(:number) +# crash the listener which is supervised by master, it's restarted automatically reporting a different number +listener.tell(:crash) +listener.ask!(:number) + +master << :crash + +sleep 0.1 + +# ask for listener again, old one is terminated with master and replaced with new one +listener.ask!(:terminated?) +listener = master.ask!(:listener) +listener.ask!(:number) + +master.ask!(:terminate!) + +sleep 0.1 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/supervision_tree.out.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/supervision_tree.out.rb new file mode 100644 index 0000000000..602ce18711 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/actor/supervision_tree.out.rb @@ -0,0 +1,73 @@ + +class Master < Concurrent::Actor::RestartingContext + def initialize + # create listener a supervised child of master + @listener = Listener.spawn(name: 'listener1', supervise: true) + end + + def on_message(msg) + command, *args = msg + case command + when :listener + @listener + when :reset, :terminated, :resumed, :paused + log(DEBUG) { " got #{msg} from #{envelope.sender}"} + else + pass + end + end + + # TODO this should be a part of a behaviour, it ensures that children are restarted/paused etc. when theirs parents are + def on_event(event) + event_name, _ = event + case event_name + when :resetting, :restarting + @listener << :terminate! + when Exception, :paused + @listener << :pause! + when :resumed + @listener << :resume! + end + end +end + +class Listener < Concurrent::Actor::RestartingContext + def initialize + @number = (rand() * 100).to_i + end + + def on_message(msg) + case msg + when :number + @number + else + pass + end + end + +end + +master = Master.spawn(name: 'master', supervise: true) + # => # +listener = master.ask!(:listener) + # => # +listener.ask!(:number) # => 39 +# crash the listener which is supervised by master, it's restarted automatically reporting a different number +listener.tell(:crash) + # => # +listener.ask!(:number) # => 73 + +master << :crash + # => # + +sleep 0.1 # => 0 + +# ask for listener again, old one is terminated with master and replaced with new one +listener.ask!(:terminated?) # => true +listener = master.ask!(:listener) + # => # +listener.ask!(:number) # => 72 + +master.ask!(:terminate!) # => true + +sleep 0.1 # => 0 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.in.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.in.md new file mode 100644 index 0000000000..e6ad87a411 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.in.md @@ -0,0 +1,158 @@ + +## Examples + +**Run async task until cancelled** + +Create cancellation and then run work in a background thread until it is cancelled. + +```ruby +cancellation, origin = Concurrent::Cancellation.new +# - origin is used for cancelling, resolve it to cancel +# - cancellation is passed down to tasks for cooperative cancellation +async_task = Concurrent::Promises.future(cancellation) do |cancellation| + # Do work repeatedly until it is cancelled + do_stuff until cancellation.canceled? + :stopped_gracefully +end + +sleep 0.01 +# Wait a bit then stop the thread by resolving the origin of the cancellation +origin.resolve +async_task.value! +``` + +Or let it raise an error. + +```ruby +cancellation, origin = Concurrent::Cancellation.new +async_task = Concurrent::Promises.future(cancellation) do |cancellation| + # Do work repeatedly until it is cancelled + while true + cancellation.check! + do_stuff + end +end + +sleep 0.01 +# Wait a bit then stop the thread by resolving the origin of the cancellation +origin.resolve +async_task.result +``` + +**Run additional tasks on Cancellation** + +Cancellation can also be used to log or plan re-execution. + +```ruby +cancellation.origin.chain do + # This block is executed after the Cancellation is cancelled + # It can then log cancellation or e.g. plan new re-execution +end +``` + +**Run only for limited time – Timeout replacement** + +Execute task for a given time then finish. +Instead of letting Cancellation crate its own origin, it can be passed in as argument. +The passed in origin is scheduled to be resolved in given time which then cancels the Cancellation. + +```ruby +timeout = Concurrent::Cancellation.new Concurrent::Promises.schedule(0.02) +# or using shortcut helper method +timeout = Concurrent::Cancellation.timeout 0.02 +count = Concurrent::AtomicFixnum.new +Concurrent.global_io_executor.post(timeout) do |timeout| + # do stuff until cancelled + count.increment until timeout.canceled? +end # + +timeout.origin.wait +count.value # => 177576 +``` + +**Parallel background processing with single cancellation** + +Each task tries to count to 1000 but there is a randomly failing test. The +tasks share single cancellation, when one of them fails it cancels the others. +The failing tasks ends with an error, the other tasks are gracefully cancelled. + +```ruby +cancellation, origin = Concurrent::Cancellation.new +tasks = 4.times.map do |i| + Concurrent::Promises.future(cancellation, origin, i) do |cancellation, origin, i| + count = 0 + 100.times do + break count = :cancelled if cancellation.canceled? + count += 1 + sleep 0.001 + if rand > 0.95 + origin.resolve # cancel + raise 'random error' + end + count + end + end +end +Concurrent::Promises.zip(*tasks).result # +# => [false, +# [:cancelled, nil, :cancelled, :cancelled], +# [nil, #, nil, nil]] +``` + +Without the randomly failing part it produces following. + +```ruby +cancellation, origin = Concurrent::Cancellation.new +tasks = 4.times.map do |i| + Concurrent::Promises.future(cancellation, origin, i) do |cancellation, origin, i| + count = 0 + 100.times do + break count = :cancelled if cancellation.canceled? + count += 1 + sleep 0.001 + # if rand > 0.95 + # origin.resolve + # raise 'random error' + # end + count + end + end +end +Concurrent::Promises.zip(*tasks).result +``` + +**Combine cancellations** + +The combination created by joining two cancellations cancels when the first or the other does. + +```ruby +cancellation_a, origin_a = Concurrent::Cancellation.new +cancellation_b, origin_b = Concurrent::Cancellation.new +combined_cancellation = cancellation_a.join(cancellation_b) + +origin_a.resolve + +cancellation_a.canceled? +cancellation_b.canceled? +combined_cancellation.canceled? +``` + +If a different rule for joining is needed, the source can be combined manually. +The manually created cancellation cancels when both the first and the other cancels. + +```ruby +cancellation_a, origin_a = Concurrent::Cancellation.new +cancellation_b, origin_b = Concurrent::Cancellation.new +# cancels only when both a and b is cancelled +combined_cancellation = Concurrent::Cancellation.new origin_a & origin_b + +origin_a.resolve + +cancellation_a.canceled? #=> true +cancellation_b.canceled? #=> false +combined_cancellation.canceled? #=> false + +origin_b.resolve +combined_cancellation.canceled? #=> true +``` + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.init.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.init.rb new file mode 100644 index 0000000000..f77a83d605 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.init.rb @@ -0,0 +1,6 @@ +require 'concurrent-edge' + +def do_stuff(*args) + sleep 0.01 + :stuff +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.out.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.out.md new file mode 100644 index 0000000000..f2579dc9c8 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/cancellation.out.md @@ -0,0 +1,192 @@ + +## Examples + +**Run async task until cancelled** + +Create cancellation and then run work in a background thread until it is cancelled. + +```ruby +cancellation, origin = Concurrent::Cancellation.new +# => # +# - origin is used for cancelling, resolve it to cancel +# - cancellation is passed down to tasks for cooperative cancellation +async_task = Concurrent::Promises.future(cancellation) do |cancellation| + # Do work repeatedly until it is cancelled + do_stuff until cancellation.canceled? + :stopped_gracefully +end +# => # + +sleep 0.01 # => 0 +# Wait a bit then stop the thread by resolving the origin of the cancellation +origin.resolve +# => # +async_task.value! # => :stopped_gracefully +``` + +Or let it raise an error. + +```ruby +cancellation, origin = Concurrent::Cancellation.new +# => # +async_task = Concurrent::Promises.future(cancellation) do |cancellation| + # Do work repeatedly until it is cancelled + while true + cancellation.check! + do_stuff + end +end +# => # + +sleep 0.01 # => 0 +# Wait a bit then stop the thread by resolving the origin of the cancellation +origin.resolve +# => # +async_task.result +# => [false, +# nil, +# #] +``` + +**Run additional tasks on Cancellation** + +Cancellation can also be used to log or plan re-execution. + +```ruby +cancellation.origin.chain do + # This block is executed after the Cancellation is cancelled + # It can then log cancellation or e.g. plan new re-execution +end +# => # +``` + +**Run only for limited time – Timeout replacement** + +Execute task for a given time then finish. +Instead of letting Cancellation crate its own origin, it can be passed in as argument. +The passed in origin is scheduled to be resolved in given time which then cancels the Cancellation. + +```ruby +timeout = Concurrent::Cancellation.new Concurrent::Promises.schedule(0.02) +# => # +# or using shortcut helper method +timeout = Concurrent::Cancellation.timeout 0.02 +# => # +count = Concurrent::AtomicFixnum.new +# => # +Concurrent.global_io_executor.post(timeout) do |timeout| + # do stuff until cancelled + count.increment until timeout.canceled? +end + +timeout.origin.wait +# => # +count.value # => 177576 +``` + +**Parallel background processing with single cancellation** + +Each task tries to count to 1000 but there is a randomly failing test. The +tasks share single cancellation, when one of them fails it cancels the others. +The failing tasks ends with an error, the other tasks are gracefully cancelled. + +```ruby +cancellation, origin = Concurrent::Cancellation.new +# => # +tasks = 4.times.map do |i| + Concurrent::Promises.future(cancellation, origin, i) do |cancellation, origin, i| + count = 0 + 100.times do + break count = :cancelled if cancellation.canceled? + count += 1 + sleep 0.001 + if rand > 0.95 + origin.resolve # cancel + raise 'random error' + end + count + end + end +end +# => [#, +# #, +# #, +# #] +Concurrent::Promises.zip(*tasks).result +# => [false, +# [:cancelled, nil, :cancelled, :cancelled], +# [nil, #, nil, nil]] +``` + +Without the randomly failing part it produces following. + +```ruby +cancellation, origin = Concurrent::Cancellation.new +# => # +tasks = 4.times.map do |i| + Concurrent::Promises.future(cancellation, origin, i) do |cancellation, origin, i| + count = 0 + 100.times do + break count = :cancelled if cancellation.canceled? + count += 1 + sleep 0.001 + # if rand > 0.95 + # origin.resolve + # raise 'random error' + # end + count + end + end +end +# => [#, +# #, +# #, +# #] +Concurrent::Promises.zip(*tasks).result +# => [true, [100, 100, 100, 100], nil] +``` + +**Combine cancellations** + +The combination created by joining two cancellations cancels when the first or the other does. + +```ruby +cancellation_a, origin_a = Concurrent::Cancellation.new +# => # +cancellation_b, origin_b = Concurrent::Cancellation.new +# => # +combined_cancellation = cancellation_a.join(cancellation_b) +# => # + +origin_a.resolve +# => # + +cancellation_a.canceled? # => true +cancellation_b.canceled? # => false +combined_cancellation.canceled? # => true +``` + +If a different rule for joining is needed, the source can be combined manually. +The manually created cancellation cancels when both the first and the other cancels. + +```ruby +cancellation_a, origin_a = Concurrent::Cancellation.new +# => # +cancellation_b, origin_b = Concurrent::Cancellation.new +# => # +# cancels only when both a and b is cancelled +combined_cancellation = Concurrent::Cancellation.new origin_a & origin_b +# => # + +origin_a.resolve +# => # + +cancellation_a.canceled? #=> true +cancellation_b.canceled? #=> false +combined_cancellation.canceled? #=> false + +origin_b.resolve +# => # +combined_cancellation.canceled? #=> true +``` + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.in.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.in.md new file mode 100644 index 0000000000..095c45140e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.in.md @@ -0,0 +1,225 @@ +## Examples + +Let's start by creating a channel with a capacity of 2 messages. + +```ruby +ch = Concurrent::Promises::Channel.new 2 +``` + +We push 3 messages, +then it can be observed that the last thread pushing is sleeping +since the channel is full. + +```ruby +threads = Array.new(3) { |i| Thread.new { ch.push message: i } } # +sleep 0.01 # let the threads run +threads +``` + +When message is popped the last thread continues and finishes as well. + +```ruby +ch.pop +threads.map(&:join) +``` + +Same principle applies to popping as well. +There are now 2 messages int he channel. +Lets create 3 threads trying to pop a message, +one will be blocked until new messages is pushed. + +```ruby +threads = Array.new(3) { |i| Thread.new { ch.pop } } # +sleep 0.01 # let the threads run +threads +ch.push message: 3 +threads.map(&:value) +``` + +### Promises integration + +However this channel is implemented to **integrate with promises** +therefore all operations can be represented as futures. + +```ruby +ch = Concurrent::Promises::Channel.new 2 +push_operations = Array.new(3) { |i| ch.push_op message: i } +``` + +> We do not have to sleep here letting the futures execute as Threads. +Since there is capacity for 2 messages the Promises are immediately resolved +without ever allocating a Thread to execute. +Push and pop operations are often more efficient. +The remaining pending push operation will also never require another thread, +instead it will resolve when a message is popped from the channel +making a space for a new message. + +```ruby +ch.pop_op.value! +push_operations.map(&:value!) + +pop_operations = Array.new(3) { |i| ch.pop_op } +ch.push message: 3 # (push|pop) can be freely mixed with (push_o|pop_op) +pop_operations.map(&:value) +``` + +### Selecting over channels + +A selection over channels can be created with the `.select_channel` factory method. It +will be fulfilled with a first message available in any of the channels. It +returns a pair to be able to find out which channel had the message available. + +```ruby +ch1 = Concurrent::Promises::Channel.new 2 +ch2 = Concurrent::Promises::Channel.new 2 +ch1.push 1 +ch2.push 2 + +Concurrent::Promises::Channel.select([ch1, ch2]) +ch1.select(ch2) + +Concurrent::Promises.future { 3 + 4 }.then_channel_push(ch1) +Concurrent::Promises::Channel. + # or `ch1.select_op(ch2)` would be equivalent + select_op([ch1, ch2]). + then('got number %03d from ch%d') { |(channel, value), format| + format format, value, [ch1, ch2].index(channel).succ + }.value! +``` + +### `try_` variants + +All blocking operations ({#pop}, {#push}, {#select}) have non-blocking variant +with `try_` prefix. +They always return immediately and indicate either success or failure. + +```ruby +ch +ch.try_push 1 +ch.try_push 2 +ch.try_push 3 +ch.try_pop +ch.try_pop +ch.try_pop +``` + +### Timeouts + +All blocking operations ({#pop}, {#push}, {#select}) have a timeout option. +Similar to `try_` variants it will indicate success or timing out, +when the timeout option is used. + +```ruby +ch +ch.push 1, 0.01 +ch.push 2, 0.01 +ch.push 3, 0.01 +ch.pop 0.01 +ch.pop 0.01 +ch.pop 0.01 +``` + +### Backpressure + +Most importantly the channel can be used to create systems with backpressure. +A self adjusting system where the producers will slow down +if the consumers are not keeping up. + +```ruby +channel = Concurrent::Promises::Channel.new 2 +log = Concurrent::Array.new + +producers = Array.new 2 do |i| + Thread.new(i) do |i| + 4.times do |j| + log.push format "producer %d pushing %d", i, j + channel.push [i, j] + end + end +end + +consumers = Array.new 4 do |i| + Thread.new(i) do |consumer| + 2.times do |j| + from, message = channel.pop + log.push format "consumer %d got %d. payload %d from producer %d", + consumer, j, message, from + do_stuff + end + end +end + +# wait for all to finish +producers.map(&:join) +consumers.map(&:join) +# investigate log +log +``` + +The producers are much faster than consumers +(since they `do_stuff` which takes some time) +but as it can be seen from the log they fill the channel +and then they slow down +until there is space available in the channel. + +If permanent allocation of threads to the producers and consumers has to be avoided, +the threads can be replaced with promises +that run a thread pool. + +```ruby +channel = Concurrent::Promises::Channel.new 2 +log = Concurrent::Array.new + +def produce(channel, log, producer, i) + log.push format "producer %d pushing %d", producer, i + channel.push_op([producer, i]).then do + i + 1 < 4 ? produce(channel, log, producer, i + 1) : :done + end +end + +def consume(channel, log, consumer, i) + channel.pop_op.then(consumer, i) do |(from, message), consumer, i| + log.push format "consumer %d got %d. payload %d from producer %d", + consumer, i, message, from + do_stuff + i + 1 < 2 ? consume(channel, log, consumer, i + 1) : :done + end +end + +producers = Array.new 2 do |i| + Concurrent::Promises.future(channel, log, i) { |*args| produce *args, 0 }.run +end + +consumers = Array.new 4 do |i| + Concurrent::Promises.future(channel, log, i) { |*args| consume *args, 0 }.run +end + +# wait for all to finish +producers.map(&:value!) +consumers.map(&:value!) +# investigate log +log +``` + +### Synchronization of workers by passing a value + +If the capacity of the channel is zero +then any push operation will succeed only +when there is a matching pop operation +which can take the message. +The operations have to be paired to succeed. + +```ruby +channel = Concurrent::Promises::Channel.new 0 +thread = Thread.new { channel.pop }; sleep 0.01 # +# allow the thread to go to sleep +thread +# succeeds because there is matching pop operation waiting in the thread +channel.try_push(:v1) +# remains pending, since there is no matching operation +push = channel.push_op(:v2) +thread.value +# the push operation resolves as a pairing pop is called +channel.pop +push +``` diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.init.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.init.rb new file mode 100644 index 0000000000..f77a83d605 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.init.rb @@ -0,0 +1,6 @@ +require 'concurrent-edge' + +def do_stuff(*args) + sleep 0.01 + :stuff +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.md new file mode 100644 index 0000000000..4fc9cb8d74 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.md @@ -0,0 +1,283 @@ +# Channels and Goroutines + +Channels, popularized by the [Go programming language](https://golang.org/doc/effective_go.html#channels), are a modern variation of [communicating sequential processes (CSP)](https://en.wikipedia.org/wiki/Communicating_sequential_processes). CSP was first proposed by C. A. R. Hoare in 1978. The Go philosophy on concurrency is: + +> Do not communicate by sharing memory; instead, share memory by communicating. + +As [Rob Pike](https://en.wikipedia.org/wiki/Rob_Pike) eloquently explains in his [Concurrency Is Not Parallelism](https://vimeo.com/49718712) conference talk, concurrency is the "composition of independently executing things." Combining these two ideas, channels are a queue-like mechanism that can be used to communicate between independently executing things. + +The channel implementation in this library was highly influenced by Go, but also incorporates ideas from Clojure's [core.async](https://clojure.github.io/core.async/) library. Runtime differences aside, this channel library is functionally equivalent to Go and even includes a few features Go does not. + +### Example Programs + +Every code example in the channel chapters of both [A Tour of Go](https://tour.golang.org/welcome/1) and [Go By Example](https://gobyexample.com/) has been reproduced in Ruby. The code can be found in the [examples](https://github.com/ruby-concurrency/concurrent-ruby/tree/master/examples) directory of the source repository. Many of those examples appear in the documentation below, but many do not. They are a valuable resource for learning how to use channels. + +### Additional Resources + +* "A Tour of Go" [channels exercises](https://tour.golang.org/concurrency/2) +* "Go By Example" [channels exercises](https://gobyexample.com/channels) +* "Effective Go" [concurrency chapters](https://golang.org/doc/effective_go.html#concurrency) +* "Concurrency Is Not Parallelims" [conference presentation](https://vimeo.com/49718712) by Rob Pike, principal designer of Go +* "Clojure core.async Channels" [blog post](http://clojure.com/blog/2013/06/28/clojure-core-async-channels.html) by Rich Hickey, inventor of Clojure +* Clojure core.async [API reference](https://clojure.github.io/core.async/) + +## Goroutines + +The Go programming language uses "goroutines" as the core concurrency mechanism. A goroutine is little more than an independently executing function, multiplexed with all other goroutines onto a thread pool managed by the runtime. Ruby has a very different runtime so true goroutines are not possible. Instead, a {.go} method is provided for running a block asynchronously, multiplexed onto a special thread pool reserved just for `Channel` operations. This is similar to what Clojure does with the `go` function from the [core.async](https://clojure.github.io/core.async/#clojure.core.async/go) library. + +```ruby +puts "Main thread: #{Thread.current}" + +Concurrent::Channel.go do + puts "Goroutine thread: #{Thread.current}" +end + +# Main thread: # +# Goroutine thread: # +``` + +Although it is possible to use `Channel.go` independent of channels or to use channels with other asynchronous processing tools (such as `Future` and `Actor`), mixing the tools is not advised. Most high-level asynchronous processing tools already have a queue-like mechanism built in. Adding channels to the mix may indicate a design flaw. Conversely, `Channel.go` provides no mechanism for coordination and communication. That's what channels are for. Additionally, strictly using `Channel.go` along with channels provides an opportunity for future optimizations, such as Clojure's inversion of control (IOC) threads. + +## Channel Basics + +Channels are "pipes" through which values can be sent. They are thread safe and naturally concurrent. When shared between goroutines they provide a communication mechanism for coordinating asynchronous actions. + +The core channel operations are {#put} and {#take} (aliased as {#send} and {#receive}, respectively). The former function inserts a value into a channel where the latter removes a value. By default these are blocking operations. A call to `put` will block until the channel is ready to receive the value. Similarly, a call to `take` will block until a value is available to be removed. + +The following, simple example creates a channel, launches a goroutine from which a value is placed into the channel, then reads that value from the channel. When run this example will display "ping" in the console. + +```ruby +messages = Concurrent::Channel.new + +Concurrent::Channel.go do + messages.put 'ping' +end + +msg = messages.take +puts msg +``` + +By default, channels are *unbuffered*, meaning that they have a capacity of zero and only accept puts and takes when both a putting and a taking thread are available. If a `put` is started when there is no taker thread the call will block. As soon as another thread calls `take` the exchange will occur and both calls will return on their respective threads. Similarly, if a `take` is started when there is no putting thread the call will block until another thread calls `put`. + +The following, slightly more complex example, concurrently sums two different halves of a list then combines the results. It uses an unbuffered channel to pass the results from the two goroutines back to the main thread. The main thread blocks on the two `take` calls until the worker goroutines are done. This example also uses the convenience aliases {#<<} and {#~}. Since channels in Go are part of the language, channel operations are performed using special channel operators rather than functions. These operators help clearly indicate that channel operations are being performed. The operator overloads `<<` for `put` and `~` for `take` help reinforce this idea in Ruby. + +```ruby +def sum(a, c) + sum = a.reduce(0, &:+) + c << sum # `<<` is an alias for `put` or `send` +end + +a = [7, 2, 8, -9, 4, 0] +l = a.length / 2 +c = Concurrent::Channel.new + +Concurrent::Channel.go { sum(a[-l, l], c) } +Concurrent::Channel.go { sum(a[0, l], c) } +x, y = ~c, ~c # `~` is an alias for `take` or `receive` + +puts [x, y, x+y].join(' ') +``` + +## Channel Buffering + +One common channel variation is a *buffered* channel. A buffered channel has a finite number of slots in the buffer which can be filled. Putting threads can put values into the channel even if there is no taking threads, up to the point where the buffer is filled. Once a buffer becomes full the normal blocking behavior resumes. A buffered channel is created by giving a `:capacity` option on channel creation: + +The following example creates a buffered channel with two slots. It then makes two `put` calls, adding values to the channel. These calls do not block because the buffer has room. Were a third `put` call to be made before an `take` calls, the third `put` would block. + +```ruby +ch = Concurrent::Channel.new(capacity: 2) +ch << 1 +ch << 2 + +puts ~ch +puts ~ch +``` + +## Channel Synchronization + +The main purpose of channels is to synchronize operations across goroutines. One common pattern for this is to create a `capacity: 1` buffered channel which is used to signal that work is complete. The following example calls a `worker` function on a goroutine and passes it a "done" channel. The main thread then calls `take` on the "done" channel and blocks until signaled. + +```ruby +def worker(done_channel) + print "working...\n" + sleep(1) + print "done\n" + + done_channel << true +end + +done = Concurrent::Channel.new(capacity: 1) +Concurrent::Channel.go{ worker(done) } + +~done # block until signaled +``` + +## Multichannel Select + +Often it is necessary for a single thread to operate on more than one channel. The {.select} method facilitates multivariate channel operations. The `select` method takes a block and passes through a special "selector" object as the first block parameter. The selector can then be used to specify various channel operations. The `select` call will block until one of the operations occurs. If a block is provided for the triggered clause (required for some clauses, optional for others) the block will then be called. Finally, the `select` call will immediately exit, guaranteeing that only one of the select clauses will trigger. + +The following example spawns two goroutines, each of which goes to sleep before putting a value onto a channel. The main thread loops twice over a `select` and, in each loop, takes a value off of whichever channel returns one first. + +```ruby +c1 = Concurrent::Channel.new +c2 = Concurrent::Channel.new + +Concurrent::Channel.go do + sleep(1) + c1 << 'one' +end + +Concurrent::Channel.go do + sleep(2) + c1 << 'two' +end + +2.times do + Concurrent::Channel.select do |s| + s.take(c1) { |msg| print "received #{msg}\n" } + s.take(c2) { |msg| print "received #{msg}\n" } + end +end +``` + +The output from the above example is: + +``` +received one +received two +``` + +The next example calculates the first 10 fibonacci numbers, passing them to the main thread via a channel. The fibonacci function puts each calculated value onto a channel while simultaneously listening to a different channel for the signal to stop. This example uses the `case` method on the selector object. This is just a convenience method for `put` and `take`, allowing the Ruby code to look more like Go. + +```ruby +def fibonacci(c, quit) + x, y = 0, 1 + loop do + Concurrent::Channel.select do |s| + s.case(c, :<<, x) { x, y = y, x+y; x } # alias for `s.put` + s.case(quit, :~) do # alias for `s.take` + puts 'quit' + return + end + end + end +end + +c = Concurrent::Channel.new +quit = Concurrent::Channel.new + +Concurrent::Channel.go do + 10.times { puts ~c } + quit << 0 +end + +fibonacci(c, quit) +``` + +## Closing and Iterating Over Channels + +Newly created channels are in an "open" state. Open channels can receive values via `put` operations. When a program is done with a channel it can be closed by calling the `#close` method. Once a channel is closed it will no longer allow values to be `put`. If the channel is buffered and values are in the buffer when the channel is closed, the remaining values can still be removed via `take` operations. + +The `Channel` class implements an {#each} method which can be used to retrieve successive values from the channel. The `each` method is a blocking method. When the channel is open and there are no values in the buffer, `each` will block until a new item is `put`. The `each` method will not exit until the channel is closed. + +The following example launches a goroutine which calculates several fibonacci values and puts them into a channel. The main thread uses the `each` method to retrieve all the values successively and display them in the console. Once the fibonacci goroutine is done it closes the channel which subsequently causes the `each` iteration to end, unblocking the main thread. + +```ruby +def fibonacci(n, c) + x, y = 0, 1 + (1..n).each do + c << x + x, y = y, x+y + end + c.close +end + +chan = Concurrent::Channel.new(capacity: 10) +Concurrent::Channel.go { fibonacci(chan.capacity, chan) } +chan.each { |i| puts i } +``` + +`Channel` also includes Ruby's [Enumerable](http://ruby-doc.org/core-2.2.3/Enumerable.html) mixin, allowing for a wide range of list comprehensions. Since the `Enumerable` methods iterate over the entire set of objects they can only complete once the channel is closed. Calling a method from `Enumerable` on an open channel will cause the method to block until the channel is closed. + +## Timers and Tickers + +A {.timer} is a specialized channel which triggers at a predefined time, specified as a number of seconds in the future. It is similar in concept to a {Concurrent::ScheduledTask} but operates as a channel and can fully participate in all channel operations. + +The following code example creates two timers with different delay values. The first timer is allowed to expire (trigger) by having the main thread perform a `take` on it. When the timer expires it puts a {Concurrent::Channel::Tick} object into its buffer and closes. The second timer is listened to on a goroutine but the it never expires: the main thread stops (closes) the timer before it expires. Note that the goroutine in this example blocks forever and never exits. Since the timer is closed it never puts the `Tick` into its buffer. + +```ruby +timer1 = Concurrent::Channel.timer(2) + +~timer1 +puts 'Timer 1 expired' + +timer2 = Concurrent::Channel.timer(1) +Concurrent::Channel.go do + ~timer2 + print "Timer 2 expired\n" +end + +stop2 = timer2.stop # alias for `close` +print "Timer 2 stopped\n" if stop2 +``` + +A {.ticker} is a specialized channel which triggers over and over again at a predefined interval, specified as a number of seconds between ticks. It is similar in concept to a {Concurrent::TimerTask} but operates as a channel and can fully participate in all channel operations. + +The following example creates a ticker which triggers every half-second. A goroutine iterates over the ticker using the `each` method, printing the tick at every interval. When the main thread stops (closes) the ticker the `each` call ends and the goroutine exits. + +```ruby +ticker = Concurrent::Channel.ticker(0.5) +Concurrent::Channel.go do + ticker.each do |tick| + print "Tick at #{tick}\n" + end +end + +sleep(1.6) +ticker.stop # alias for `close` +print "Ticker stopped\n" +``` + +## Default Selection + +As with a Ruby `case` statement, a `Channel.select` statement will accept a `default` clause which will trigger if none of the other clauses trigger. Not surprisingly, the `default` clause must be the last clause in a `select` block. + +```ruby +tick = Concurrent::Channel.tick(0.1) # alias for `ticker` +boom = Concurrent::Channel.after(0.5) # alias for `timer` + +loop do + Concurrent::Channel.select do |s| + s.take(tick) { print "tick.\n" } + s.take(boom) do + print "BOOM!\n" + exit + end + s.default do + print " .\n" + sleep(0.05) + end + end +end +``` + +The output of this code example is: + +``` +. +. +tick. +. +. +tick. +. +. +tick. +. +. +tick. +. +. +tick. +BOOM! +``` diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.out.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.out.md new file mode 100644 index 0000000000..28bb1eaad2 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/channel.out.md @@ -0,0 +1,313 @@ +## Examples + +Let's start by creating a channel with a capacity of 2 messages. + +```ruby +ch = Concurrent::Promises::Channel.new 2 +# => # +``` + +We push 3 messages, +then it can be observed that the last thread pushing is sleeping +since the channel is full. + +```ruby +threads = Array.new(3) { |i| Thread.new { ch.push message: i } } +sleep 0.01 # let the threads run +threads +# => [#, +# #, +# #] +``` + +When message is popped the last thread continues and finishes as well. + +```ruby +ch.pop # => {:message=>1} +threads.map(&:join) +# => [#, +# #, +# #] +``` + +Same principle applies to popping as well. +There are now 2 messages int he channel. +Lets create 3 threads trying to pop a message, +one will be blocked until new messages is pushed. + +```ruby +threads = Array.new(3) { |i| Thread.new { ch.pop } } +sleep 0.01 # let the threads run +threads +# => [#, +# #, +# #] +ch.push message: 3 +# => # +threads.map(&:value) +# => [{:message=>0}, {:message=>2}, {:message=>3}] +``` + +### Promises integration + +However this channel is implemented to **integrate with promises** +therefore all operations can be represented as futures. + +```ruby +ch = Concurrent::Promises::Channel.new 2 +# => # +push_operations = Array.new(3) { |i| ch.push_op message: i } +# => [#>, +# #>, +# #] +``` + +> We do not have to sleep here letting the futures execute as Threads. +Since there is capacity for 2 messages the Promises are immediately resolved +without ever allocating a Thread to execute. +Push and pop operations are often more efficient. +The remaining pending push operation will also never require another thread, +instead it will resolve when a message is popped from the channel +making a space for a new message. + +```ruby +ch.pop_op.value! # => {:message=>0} +push_operations.map(&:value!) +# => [#, +# #, +# #] + +pop_operations = Array.new(3) { |i| ch.pop_op } +# => [#1}>, +# #2}>, +# #] +ch.push message: 3 # (push|pop) can be freely mixed with (push_o|pop_op) +pop_operations.map(&:value) +# => [{:message=>1}, {:message=>2}, {:message=>3}] +``` + +### Selecting over channels + +A selection over channels can be created with the `.select_channel` factory method. It +will be fulfilled with a first message available in any of the channels. It +returns a pair to be able to find out which channel had the message available. + +```ruby +ch1 = Concurrent::Promises::Channel.new 2 +# => # +ch2 = Concurrent::Promises::Channel.new 2 +# => # +ch1.push 1 +# => # +ch2.push 2 +# => # + +Concurrent::Promises::Channel.select([ch1, ch2]) +# => [#, 1] +ch1.select(ch2) +# => [#, 2] + +Concurrent::Promises.future { 3 + 4 }.then_channel_push(ch1) +# => # +Concurrent::Promises::Channel. + # or `ch1.select_op(ch2)` would be equivalent + select_op([ch1, ch2]). + then('got number %03d from ch%d') { |(channel, value), format| + format format, value, [ch1, ch2].index(channel).succ + }.value! # => "got number 007 from ch1" +``` + +### `try_` variants + +All blocking operations ({#pop}, {#push}, {#select}) have non-blocking variant +with `try_` prefix. +They always return immediately and indicate either success or failure. + +```ruby +ch +# => # +ch.try_push 1 # => true +ch.try_push 2 # => true +ch.try_push 3 # => false +ch.try_pop # => 1 +ch.try_pop # => 2 +ch.try_pop # => nil +``` + +### Timeouts + +All blocking operations ({#pop}, {#push}, {#select}) have a timeout option. +Similar to `try_` variants it will indicate success or timing out, +when the timeout option is used. + +```ruby +ch +# => # +ch.push 1, 0.01 # => true +ch.push 2, 0.01 # => true +ch.push 3, 0.01 # => false +ch.pop 0.01 # => 1 +ch.pop 0.01 # => 2 +ch.pop 0.01 # => nil +``` + +### Backpressure + +Most importantly the channel can be used to create systems with backpressure. +A self adjusting system where the producers will slow down +if the consumers are not keeping up. + +```ruby +channel = Concurrent::Promises::Channel.new 2 +# => # +log = Concurrent::Array.new # => [] + +producers = Array.new 2 do |i| + Thread.new(i) do |i| + 4.times do |j| + log.push format "producer %d pushing %d", i, j + channel.push [i, j] + end + end +end +# => [#, +# #] + +consumers = Array.new 4 do |i| + Thread.new(i) do |consumer| + 2.times do |j| + from, message = channel.pop + log.push format "consumer %d got %d. payload %d from producer %d", + consumer, j, message, from + do_stuff + end + end +end +# => [#, +# #, +# #, +# #] + +# wait for all to finish +producers.map(&:join) +# => [#, +# #] +consumers.map(&:join) +# => [#, +# #, +# #, +# #] +# investigate log +log +# => ["producer 0 pushing 0", +# "producer 0 pushing 1", +# "producer 0 pushing 2", +# "producer 1 pushing 0", +# "consumer 0 got 0. payload 0 from producer 0", +# "producer 0 pushing 3", +# "consumer 1 got 0. payload 1 from producer 0", +# "consumer 2 got 0. payload 2 from producer 0", +# "consumer 3 got 0. payload 0 from producer 1", +# "producer 1 pushing 1", +# "producer 1 pushing 2", +# "consumer 1 got 1. payload 3 from producer 0", +# "producer 1 pushing 3", +# "consumer 2 got 1. payload 1 from producer 1", +# "consumer 3 got 1. payload 2 from producer 1", +# "consumer 0 got 1. payload 3 from producer 1"] +``` + +The producers are much faster than consumers +(since they `do_stuff` which takes some time) +but as it can be seen from the log they fill the channel +and then they slow down +until there is space available in the channel. + +If permanent allocation of threads to the producers and consumers has to be avoided, +the threads can be replaced with promises +that run a thread pool. + +```ruby +channel = Concurrent::Promises::Channel.new 2 +# => # +log = Concurrent::Array.new # => [] + +def produce(channel, log, producer, i) + log.push format "producer %d pushing %d", producer, i + channel.push_op([producer, i]).then do + i + 1 < 4 ? produce(channel, log, producer, i + 1) : :done + end +end # => :produce + +def consume(channel, log, consumer, i) + channel.pop_op.then(consumer, i) do |(from, message), consumer, i| + log.push format "consumer %d got %d. payload %d from producer %d", + consumer, i, message, from + do_stuff + i + 1 < 2 ? consume(channel, log, consumer, i + 1) : :done + end +end # => :consume + +producers = Array.new 2 do |i| + Concurrent::Promises.future(channel, log, i) { |*args| produce *args, 0 }.run +end +# => [#, +# #] + +consumers = Array.new 4 do |i| + Concurrent::Promises.future(channel, log, i) { |*args| consume *args, 0 }.run +end +# => [#, +# #, +# #, +# #] + +# wait for all to finish +producers.map(&:value!) # => [:done, :done] +consumers.map(&:value!) # => [:done, :done, :done, :done] +# investigate log +log +# => ["producer 0 pushing 0", +# "producer 1 pushing 0", +# "producer 1 pushing 1", +# "consumer 1 got 0. payload 0 from producer 1", +# "consumer 2 got 0. payload 1 from producer 1", +# "producer 0 pushing 1", +# "producer 0 pushing 2", +# "producer 0 pushing 3", +# "producer 1 pushing 2", +# "consumer 0 got 0. payload 0 from producer 0", +# "consumer 3 got 0. payload 1 from producer 0", +# "producer 1 pushing 3", +# "consumer 2 got 1. payload 2 from producer 0", +# "consumer 1 got 1. payload 3 from producer 0", +# "consumer 3 got 1. payload 3 from producer 1", +# "consumer 0 got 1. payload 2 from producer 1"] +``` + +### Synchronization of workers by passing a value + +If the capacity of the channel is zero +then any push operation will succeed only +when there is a matching pop operation +which can take the message. +The operations have to be paired to succeed. + +```ruby +channel = Concurrent::Promises::Channel.new 0 +# => # +thread = Thread.new { channel.pop }; sleep 0.01 +# allow the thread to go to sleep +thread +# => # +# succeeds because there is matching pop operation waiting in the thread +channel.try_push(:v1) # => true +# remains pending, since there is no matching operation +push = channel.push_op(:v2) +# => # +thread.value # => :v1 +# the push operation resolves as a pairing pop is called +channel.pop # => :v2 +push +# => #> +``` diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/dataflow.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/dataflow.md new file mode 100644 index 0000000000..a34207ee3c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/dataflow.md @@ -0,0 +1,183 @@ +Data dependencies are `Future` values. The dataflow task itself is also a `Future` value, so you can build up a graph of these tasks, each of which is run when all the data and other tasks it depends on are available or completed. + +Our syntax is somewhat related to that of Akka's `flow` and Habanero Java's `DataDrivenFuture`. However unlike Akka we don't schedule a task at all until it is ready to run, and unlike Habanero Java we pass the data values into the task instead of dereferencing them again in the task. + +The theory of dataflow goes back to the 70s. In the terminology of the literature, our implementation is coarse-grained, in that each task can be many instructions, and dynamic in that you can create more tasks within other tasks. + +### Example + +A dataflow task is created with the `dataflow` method, passing in a block. + +```ruby +task = Concurrent::dataflow { 14 } +``` + +This produces a simple `Future` value. The task will run immediately, as it has no dependencies. We can also specify `Future` values that must be available before a task will run. When we do this we get the value of those futures passed to our block. + +```ruby +a = Concurrent::dataflow { 1 } +b = Concurrent::dataflow { 2 } +c = Concurrent::dataflow(a, b) { |av, bv| av + bv } +``` + +Using the `dataflow` method you can build up a directed acyclic graph (DAG) of tasks that depend on each other, and have the tasks run as soon as their dependencies are ready and there is CPU capacity to schedule them. This can help you create a program that uses more of the CPU resources available to you. + +### Derivation + +This section describes how we could derive dataflow from other primitives in this library. + +Consider a naive fibonacci calculator. + +```ruby +def fib(n) + if n < 2 + n + else + fib(n - 1) + fib(n - 2) + end +end + +puts fib(14) #=> 377 +``` + +We could modify this to use futures. + +```ruby +def fib(n) + if n < 2 + Concurrent::Future.new { n } + else + n1 = fib(n - 1).execute + n2 = fib(n - 2).execute + Concurrent::Future.new { n1.value + n2.value } + end +end + +f = fib(14) #=> # # 377 +``` + +One of the drawbacks of this approach is that all the futures start, and then most of them immediately block on their dependencies. We know that there's no point executing those futures until their dependencies are ready, so let's not execute each future until all their dependencies are ready. + +To do this we'll create an object that counts the number of times it observes a future finishing before it does something - and for us that something will be to execute the next future. + +```ruby +class CountingObserver + + def initialize(count, &block) + @count = count + @block = block + end + + def update(time, value, reason) + @count -= 1 + + if @count <= 0 + @block.call() + end + end + +end + +def fib(n) + if n < 2 + Concurrent::Future.new { n }.execute + else + n1 = fib(n - 1) + n2 = fib(n - 2) + + result = Concurrent::Future.new { n1.value + n2.value } + + barrier = CountingObserver.new(2) { result.execute } + n1.add_observer barrier + n2.add_observer barrier + + n1.execute + n2.execute + + result + end +end +``` + +We can wrap this up in a dataflow utility. + +```ruby +f = fib(14) #=> # 377 + +def dataflow(*inputs, &block) + result = Concurrent::Future.new(&block) + + if inputs.empty? + result.execute + else + barrier = CountingObserver.new(inputs.size) { result.execute } + + inputs.each do |input| + input.add_observer barrier + end + end + + result +end + +def fib(n) + if n < 2 + dataflow { n } + else + n1 = fib(n - 1) + n2 = fib(n - 2) + dataflow(n1, n2) { n1.value + n2.value } + end +end + +f = fib(14) #=> # 377 +``` + +Since we know that the futures the dataflow computation depends on are already going to be available when the future is executed, we might as well pass the values into the block so we don't have to reference the futures inside the block. This allows us to write the dataflow block as straight non-concurrent code without reference to futures. + +```ruby +def dataflow(*inputs, &block) + result = Concurrent::Future.new do + values = inputs.map { |input| input.value } + block.call(*values) + end + + if inputs.empty? + result.execute + else + barrier = CountingObserver.new(inputs.size) { result.execute } + + inputs.each do |input| + input.add_observer barrier + end + end + + result +end + +def fib(n) + if n < 2 + Concurrent::dataflow { n } + else + n1 = fib(n - 1) + n2 = fib(n - 2) + Concurrent::dataflow(n1, n2) { |v1, v2| v1 + v2 } + end +end + +f = fib(14) #=> # 377 +``` diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/dataflow_top_stock_calc.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/dataflow_top_stock_calc.md new file mode 100644 index 0000000000..3dc4355a83 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/dataflow_top_stock_calc.md @@ -0,0 +1,109 @@ +This program determines which stock had the highest price in a given year. +It as an example from chapter 1 "Introduction", section 1.2 "What's Scala?" of the book +[Programming Scala: Tackle Multi-Core Complexity on the Java Virtual Machine](http://pragprog.com/book/vsscala/programming-scala). + +## What It Does + +This program takes a list of one or more stock symbols and a year. It then concurrently +obtains the relevant stock data from Alpha Vantage service for each symbol. Once all +the data has been retrieved the program determines which stock had the highest year-end +closing price. + +To use this example you need to obtain a free api key in [AlphaVantage](https://www.alphavantage.co/support/#api-key). + + +#### The Ruby Code + +```ruby +require 'concurrent' +require 'csv' +require 'open-uri' + +def get_year_end_closing(symbol, year, api_key) + uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv" + data = [] + csv = URI.parse(uri).read + if csv.include?('call frequency') + return :rate_limit_exceeded + end + CSV.parse(csv, headers: true) do |row| + data << row['close'].to_f if row['timestamp'].include?(year.to_s) + end + price = data.max + [symbol, price] +end + +def get_top_stock(symbols, year, timeout = 10) + api_key = ENV['ALPHAVANTAGE_KEY'] + abort(error_message) unless api_key + + stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year, api_key) }} + Concurrent::dataflow(*stock_prices) { |*prices| + next :rate_limit_exceeded if prices.include?(:rate_limit_exceeded) + prices.reduce(['', 0.0]){|highest, price| price.last > highest.last ? price : highest} + }.value(timeout) +end + +def error_message + <<~EOF + PLEASE provide a Alpha Vantage api key for the example to work + usage: + ALPHAVANTAGE_KEY=YOUR_API_KEY bundle exec ruby top-stock-scala/top-stock.rb + EOF +end + +symbols = ['AAPL', 'GOOG', 'IBM', 'ORCL', 'MSFT'] +year = 2018 + +result = get_top_stock(symbols, year) + +if result == :rate_limit_exceeded + puts "API rate limit exceeded" +else + top_stock, highest_price = result + puts "Top stock of #{year} is #{top_stock} closing at price $#{highest_price}" +end +``` + +#### The Scala Code + +```scala +//START:PART1 +import scala.actors._ +import Actor._ + +val symbols = List( "AAPL", "GOOG", "IBM", "JAVA", "MSFT") +val receiver = self +val year = 2008 + +symbols.foreach { symbol => + actor { receiver ! getYearEndClosing(symbol, year) } +} + +val (topStock, highestPrice) = getTopStock(symbols.length) + +printf("Top stock of %d is %s closing at price %f\n", year, topStock, highestPrice) +//END:PART1 + +//START:PART2 +def getYearEndClosing(symbol : String, year : Int) = { + val url = "http://ichart.finance.yahoo.com/table.csv?s=" + + symbol + "&a=11&b=01&c=" + year + "&d=11&e=31&f=" + year + "&g=m" + + val data = io.Source.fromURL(url).mkString + val price = data.split("\n")(1).split(",")(4).toDouble + (symbol, price) +} +//END:PART2 + +//START:PART3 +def getTopStock(count : Int) : (String, Double) = { + (1 to count).foldLeft("", 0.0) { (previousHigh, index) => + receiveWithin(10000) { + case (symbol : String, price : Double) => + if (price > previousHigh._2) (symbol, price) else previousHigh + } + } +} +//START:PART3 +``` diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.in.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.in.md new file mode 100644 index 0000000000..d8f7a6da02 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.in.md @@ -0,0 +1,278 @@ +## Examples + +The simplest example is to use the actor as an asynchronous execution. +Although, `Promises.future { 1 + 1 }` is better suited for that purpose. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread, name: 'addition') { 1 + 1 } +actor.terminated.value! +``` + +Let's send some messages and maintain some internal state +which is what actors are good for. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread, name: 'sum') do + sum = 0 # internal state + # receive and sum the messages until the actor gets :done + while true + message = receive + break if message == :done + # if the message is asked and not only told, + # reply with the current sum (has no effect if actor was not asked) + reply sum += message + end + # The final value of the actor + sum +end +``` + +The actor can be either told a message asynchronously, +or asked. The ask method will block until actor replies. + +```ruby +# tell returns immediately returning the actor +actor.tell(1).tell(1) +# blocks, waiting for the answer +actor.ask 10 +# stop the actor +actor.tell :done +# The final value of the actor +actor.terminated.value! +``` + +### Actor types + +There are two types of actors. +The type is specified when calling spawn as a first argument, +`Concurrent::ErlangActor.spawn(type: :on_thread, ...` or +`Concurrent::ErlangActor.spawn(type: :on_pool, ...`. + +The main difference is in how receive method returns. + +- `:on_thread` it blocks the thread until message is available, + then it returns or calls the provided block first. + +- However, `:on_pool` it has to free up the thread on the receive + call back to the pool. Therefore the call to receive ends the + execution of current scope. The receive has to be given block + or blocks that act as a continuations and are called + when there is message available. + +Let's have a look at how the bodies of actors differ between the types: + +```ruby +ping = Concurrent::ErlangActor.spawn(type: :on_thread) { reply receive } +ping.ask 42 +``` + +It first calls receive, which blocks the thread of the actor. +When it returns the received message is passed an an argument to reply, +which replies the same value back to the ask method. +Then the actor terminates normally, because there is nothing else to do. + +However when running on pool a block with code which should be evaluated +after the message is received has to be provided. + +```ruby +ping = Concurrent::ErlangActor.spawn(type: :on_pool) { receive { |m| reply m } } +ping.ask 42 +``` + +It starts by calling receive which will remember the given block for later +execution when a message is available and stops executing the current scope. +Later when a message becomes available the previously provided block is given +the message and called. The result of the block is the final value of the +normally terminated actor. + +The direct blocking style of `:on_thread` is simpler to write and more straight +forward however it has limitations. Each `:on_thread` actor creates a Thread +taking time and resources. +There is also a limited number of threads the Ruby process can create +so you may hit the limit and fail to create more threads and therefore actors. + +Since the `:on_pool` actor runs on a poll of threads, its creations +is faster and cheaper and it does not create new threads. +Therefore there is no limit (only RAM) on how many actors can be created. + +To simplify, if you need only few actors `:on_thread` is fine. +However if you will be creating hundreds of actors or +they will be short-lived `:on_pool` should be used. + +### Receiving messages + +Simplest message receive. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread) { receive } +actor.tell :m +actor.terminated.value! +``` + +which also works for actor on pool, +because if no block is given it will use a default block `{ |v| v }` + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_pool) { receive { |v| v } } +# can simply be following +actor = Concurrent::ErlangActor.spawn(type: :on_pool) { receive } +actor.tell :m +actor.terminated.value! +``` + +The received message type can be limited. + +```ruby +Concurrent::ErlangActor. + spawn(type: :on_thread) { receive(Numeric).succ }. + tell('junk'). # ignored message + tell(42). + terminated.value! +``` + +On pool it requires a block. + +```ruby +Concurrent::ErlangActor. + spawn(type: :on_pool) { receive(Numeric) { |v| v.succ } }. + tell('junk'). # ignored message + tell(42). + terminated.value! +``` + +By the way, the body written for on pool actor will work for on thread actor +as well. + +```ruby +Concurrent::ErlangActor. + spawn(type: :on_thread) { receive(Numeric) { |v| v.succ } }. + tell('junk'). # ignored message + tell(42). + terminated.value! +``` + +The `receive` method can be also used to dispatch based on the received message. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread) do + while true + receive(on(Symbol) { |s| reply s.to_s }, + on(And[Numeric, -> v { v >= 0 }]) { |v| reply v.succ }, + # put last works as else + on(ANY) do |v| + reply :bad_message + terminate [:bad_message, v] + end) + end +end +actor.ask 1 +actor.ask 2 +actor.ask :value +# this malformed message will terminate the actor +actor.ask -1 +# the actor is no longer alive, so ask fails +actor.ask "junk" rescue $! +actor.terminated.result +``` + +And a same thing for the actor on pool. +Since it cannot loop it will call the body method repeatedly. + +```ruby +module Behaviour + def body + receive(on(Symbol) do |s| + reply s.to_s + body # call again + end, + on(And[Numeric, -> v { v >= 0 }]) do |v| + reply v.succ + body # call again + end, + # put last works as else + on(ANY) do |v| + reply :bad_message + terminate [:bad_message, v] + end) + end +end + +actor = Concurrent::ErlangActor.spawn(type: :on_pool, environment: Behaviour) { body } +actor.ask 1 +actor.ask 2 +actor.ask :value +# this malformed message will terminate the actor +actor.ask -1 +# the actor is no longer alive, so ask fails +actor.ask "junk" rescue $! +actor.terminated.result +``` + +Since the behavior is stable in this case we can simplify with the `:keep` option +that will keep the receive rules until another receive is called +replacing the kept rules. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_pool) do + receive(on(Symbol) { |s| reply s.to_s }, + on(And[Numeric, -> v { v >= 0 }]) { |v| reply v.succ }, + # put last works as else + on(ANY) do |v| + reply :bad_message + terminate [:bad_message, v] + end, + keep: true) +end +actor.ask 1 +actor.ask 2 +actor.ask :value +# this malformed message will terminate the actor +actor.ask -1 +# the actor is no longer alive, so ask fails +actor.ask "junk" rescue $! +actor.terminated.result +``` + +### Erlang behaviour + +The actor matches Erlang processes in behaviour. +Therefore it supports the usual Erlang actor linking, monitoring, exit behaviour, etc. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread) do + spawn(link: true) do # equivalent of spawn_link in Erlang + terminate :err # equivalent of exit in Erlang + end + trap # equivalent of process_flag(trap_exit, true) + receive +end +actor.terminated.value! +``` + +The methods have same or very similar name to be easily found. +The one exception from the original Erlang naming is exit. +To avoid clashing with `Kernel#exit` it's called `terminate`. + +Until there is more information available here, the chapters listed below from +a book [lern you some Erlang](https://learnyousomeerlang.com) +are excellent source of information. +The Ruby ErlangActor implementation has same behaviour. + +- [Links](https://learnyousomeerlang.com/errors-and-processes#links) +- [It's a trap](https://learnyousomeerlang.com/errors-and-processes#its-a-trap) +- [Monitors](https://learnyousomeerlang.com/errors-and-processes#monitors) + +If anything behaves differently than in Erlang, please file an issue. + +### Chapters or points to be added + +* More erlang behaviour examples. +* The mailbox can be bounded in size, + then the tell and ask will block until there is space available in the mailbox. + Useful for building systems with backpressure. +* `#tell_op` and `ask_op` method examples, integration with promises. +* Best practice: always use timeout, + and do something if the message does not arrive, don't leave the actor stuck. +* Best practice: drop and log unrecognized messages, + or be even more defensive and terminate. +* Environment definition for actors. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.init.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.init.rb new file mode 100644 index 0000000000..820501f27e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.init.rb @@ -0,0 +1,9 @@ +require 'concurrent-edge' + +include Concurrent::ErlangActor::EnvironmentConstants + +def do_stuff(*args) + sleep 0.01 + :stuff +end + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.out.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.out.md new file mode 100644 index 0000000000..f062557a4a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/erlang_actor.out.md @@ -0,0 +1,300 @@ +## Examples + +The simplest example is to use the actor as an asynchronous execution. +Although, `Promises.future { 1 + 1 }` is better suited for that purpose. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread, name: 'addition') { 1 + 1 } +# => # +actor.terminated.value! # => 2 +``` + +Let's send some messages and maintain some internal state +which is what actors are good for. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread, name: 'sum') do + sum = 0 # internal state + # receive and sum the messages until the actor gets :done + while true + message = receive + break if message == :done + # if the message is asked and not only told, + # reply with the current sum (has no effect if actor was not asked) + reply sum += message + end + # The final value of the actor + sum +end +# => # +``` + +The actor can be either told a message asynchronously, +or asked. The ask method will block until actor replies. + +```ruby +# tell returns immediately returning the actor +actor.tell(1).tell(1) +# => # +# blocks, waiting for the answer +actor.ask 10 # => 12 +# stop the actor +actor.tell :done +# => # +# The final value of the actor +actor.terminated.value! # => 12 +``` + +### Actor types + +There are two types of actors. +The type is specified when calling spawn as a first argument, +`Concurrent::ErlangActor.spawn(type: :on_thread, ...` or +`Concurrent::ErlangActor.spawn(type: :on_pool, ...`. + +The main difference is in how receive method returns. + +- `:on_thread` it blocks the thread until message is available, + then it returns or calls the provided block first. + +- However, `:on_pool` it has to free up the thread on the receive + call back to the pool. Therefore the call to receive ends the + execution of current scope. The receive has to be given block + or blocks that act as a continuations and are called + when there is message available. + +Let's have a look at how the bodies of actors differ between the types: + +```ruby +ping = Concurrent::ErlangActor.spawn(type: :on_thread) { reply receive } +# => # +ping.ask 42 # => 42 +``` + +It first calls receive, which blocks the thread of the actor. +When it returns the received message is passed an an argument to reply, +which replies the same value back to the ask method. +Then the actor terminates normally, because there is nothing else to do. + +However when running on pool a block with code which should be evaluated +after the message is received has to be provided. + +```ruby +ping = Concurrent::ErlangActor.spawn(type: :on_pool) { receive { |m| reply m } } +# => # +ping.ask 42 # => 42 +``` + +It starts by calling receive which will remember the given block for later +execution when a message is available and stops executing the current scope. +Later when a message becomes available the previously provided block is given +the message and called. The result of the block is the final value of the +normally terminated actor. + +The direct blocking style of `:on_thread` is simpler to write and more straight +forward however it has limitations. Each `:on_thread` actor creates a Thread +taking time and resources. +There is also a limited number of threads the Ruby process can create +so you may hit the limit and fail to create more threads and therefore actors. + +Since the `:on_pool` actor runs on a poll of threads, its creations +is faster and cheaper and it does not create new threads. +Therefore there is no limit (only RAM) on how many actors can be created. + +To simplify, if you need only few actors `:on_thread` is fine. +However if you will be creating hundreds of actors or +they will be short-lived `:on_pool` should be used. + +### Receiving messages + +Simplest message receive. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread) { receive } +# => # +actor.tell :m +# => # +actor.terminated.value! # => :m +``` + +which also works for actor on pool, +because if no block is given it will use a default block `{ |v| v }` + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_pool) { receive { |v| v } } +# => # +# can simply be following +actor = Concurrent::ErlangActor.spawn(type: :on_pool) { receive } +# => # +actor.tell :m +# => # +actor.terminated.value! # => :m +``` + +The received message type can be limited. + +```ruby +Concurrent::ErlangActor. + spawn(type: :on_thread) { receive(Numeric).succ }. + tell('junk'). # ignored message + tell(42). + terminated.value! # => 43 +``` + +On pool it requires a block. + +```ruby +Concurrent::ErlangActor. + spawn(type: :on_pool) { receive(Numeric) { |v| v.succ } }. + tell('junk'). # ignored message + tell(42). + terminated.value! # => 43 +``` + +By the way, the body written for on pool actor will work for on thread actor +as well. + +```ruby +Concurrent::ErlangActor. + spawn(type: :on_thread) { receive(Numeric) { |v| v.succ } }. + tell('junk'). # ignored message + tell(42). + terminated.value! # => 43 +``` + +The `receive` method can be also used to dispatch based on the received message. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread) do + while true + receive(on(Symbol) { |s| reply s.to_s }, + on(And[Numeric, -> v { v >= 0 }]) { |v| reply v.succ }, + # put last works as else + on(ANY) do |v| + reply :bad_message + terminate [:bad_message, v] + end) + end +end +# => # +actor.ask 1 # => 2 +actor.ask 2 # => 3 +actor.ask :value # => "value" +# this malformed message will terminate the actor +actor.ask -1 # => :bad_message +# the actor is no longer alive, so ask fails +actor.ask "junk" rescue $! +# => #> +actor.terminated.result # => [false, nil, [:bad_message, -1]] +``` + +And a same thing for the actor on pool. +Since it cannot loop it will call the body method repeatedly. + +```ruby +module Behaviour + def body + receive(on(Symbol) do |s| + reply s.to_s + body # call again + end, + on(And[Numeric, -> v { v >= 0 }]) do |v| + reply v.succ + body # call again + end, + # put last works as else + on(ANY) do |v| + reply :bad_message + terminate [:bad_message, v] + end) + end +end # => :body + +actor = Concurrent::ErlangActor.spawn(type: :on_pool, environment: Behaviour) { body } +# => # +actor.ask 1 # => 2 +actor.ask 2 # => 3 +actor.ask :value # => "value" +# this malformed message will terminate the actor +actor.ask -1 # => :bad_message +# the actor is no longer alive, so ask fails +actor.ask "junk" rescue $! +# => #> +actor.terminated.result # => [false, nil, [:bad_message, -1]] +``` + +Since the behavior is stable in this case we can simplify with the `:keep` option +that will keep the receive rules until another receive is called +replacing the kept rules. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_pool) do + receive(on(Symbol) { |s| reply s.to_s }, + on(And[Numeric, -> v { v >= 0 }]) { |v| reply v.succ }, + # put last works as else + on(ANY) do |v| + reply :bad_message + terminate [:bad_message, v] + end, + keep: true) +end +# => # +actor.ask 1 # => 2 +actor.ask 2 # => 3 +actor.ask :value # => "value" +# this malformed message will terminate the actor +actor.ask -1 # => :bad_message +# the actor is no longer alive, so ask fails +actor.ask "junk" rescue $! +# => #> +actor.terminated.result # => [false, nil, [:bad_message, -1]] +``` + +### Erlang behaviour + +The actor matches Erlang processes in behaviour. +Therefore it supports the usual Erlang actor linking, monitoring, exit behaviour, etc. + +```ruby +actor = Concurrent::ErlangActor.spawn(type: :on_thread) do + spawn(link: true) do # equivalent of spawn_link in Erlang + terminate :err # equivalent of exit in Erlang + end + trap # equivalent of process_flag(trap_exit, true) + receive +end +# => # +actor.terminated.value! +# => #, +# @reason=:err> +``` + +The methods have same or very similar name to be easily found. +The one exception from the original Erlang naming is exit. +To avoid clashing with `Kernel#exit` it's called `terminate`. + +Until there is more information available here, the chapters listed below from +a book [lern you some Erlang](https://learnyousomeerlang.com) +are excellent source of information. +The Ruby ErlangActor implementation has same behaviour. + +- [Links](https://learnyousomeerlang.com/errors-and-processes#links) +- [It's a trap](https://learnyousomeerlang.com/errors-and-processes#its-a-trap) +- [Monitors](https://learnyousomeerlang.com/errors-and-processes#monitors) + +If anything behaves differently than in Erlang, please file an issue. + +### Chapters or points to be added + +* More erlang behaviour examples. +* The mailbox can be bounded in size, + then the tell and ask will block until there is space available in the mailbox. + Useful for building systems with backpressure. +* `#tell_op` and `ask_op` method examples, integration with promises. +* Best practice: always use timeout, + and do something if the message does not arrive, don't leave the actor stuck. +* Best practice: drop and log unrecognized messages, + or be even more defensive and terminate. +* Environment definition for actors. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/future.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/future.md new file mode 100644 index 0000000000..d7db160219 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/future.md @@ -0,0 +1,113 @@ +`Future` is inspired by [Clojure's](http://clojure.org/) [future](http://clojuredocs.org/clojure_core/clojure.core/future) function. A future represents a promise to complete an action at some time in the future. The action is atomic and permanent. The idea behind a future is to send an operation for asynchronous completion, do other stuff, then return and retrieve the result of the async operation at a later time. `Future`s run on the global thread pool. + +```cucumber +Feature: + As a highly responsive Ruby application + I want long-running tasks on a separate thread + So I can perform other tasks without waiting +``` + +`Future`s have several possible states: *:unscheduled*, *:pending*, *:processing*, *:rejected*, or *:fulfilled*. These are also aggregated as `#incomplete?` and `#complete?`. When a `Future` is created it is set to *:unscheduled*. Once the `#execute` method is called the state becomes *:pending*. Once a job is pulled from the thread pool's queue and is given to a thread for processing (often immediately upon `#post`) the state becomes *:processing*. The future will remain in this state until processing is complete. A future that is in the *:unscheduled*, *:pending*, or *:processing* is considered `#incomplete?`. A `#complete?` `Future` is either *:rejected*, indicating that an exception was thrown during processing, or *:fulfilled*, indicating success. If a `Future` is *:fulfilled* its `#value` will be updated to reflect the result of the operation. If *:rejected* the `reason` will be updated with a reference to the thrown exception. The predicate methods `#unscheduled?`, `#pending?`, `#rejected?`, and `#fulfilled?` can be called at any time to obtain the state of the `Future`, as can the `#state` method, which returns a symbol. + +Retrieving the value of a `Future` is done through the `#value` (alias: `#deref`) method. Obtaining the value of a `Future` is a potentially blocking operation. When a `Future` is *:rejected* a call to `#value` will return `nil` immediately. When a `Future` is *:fulfilled* a call to `#value` will immediately return the current value. When a `Future` is *:pending* a call to `#value` will block until the `Future` is either *:rejected* or *:fulfilled*. A *timeout* value can be passed to `#value` to limit how long the call will block. If `nil` the call will block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the maximum number of seconds to block. + +The constructor can also be given zero or more processing options. Currently the only supported options are those recognized by the [Dereferenceable](Dereferenceable) module. + +The `Future` class also includes the behavior of the Ruby standard library [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html) module, but does so in a thread-safe way. On fulfillment or rejection all observers will be notified according to the normal `Observable` behavior. The observer callback function will be called with three parameters: the `Time` of fulfillment/rejection, the final `value`, and the final `reason`. Observers added after fulfillment/rejection will still be notified as normal. The notification will occur on the same thread that processed the job. + +### Examples + +A fulfilled example: + +```ruby +require 'concurrent' +require 'csv' +require 'open-uri' + +class Ticker + def get_year_end_closing(symbol, year, api_key) + uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv" + data = [] + csv = URI.parse(uri).read + if csv.include?('call frequency') + return :rate_limit_exceeded + end + CSV.parse(csv, headers: true) do |row| + data << row['close'].to_f if row['timestamp'].include?(year.to_s) + end + year_end = data.first + year_end + rescue => e + p e + end +end + +api_key = ENV['ALPHAVANTAGE_KEY'] +abort(error_message) unless api_key + +# Future +price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013, api_key) } +p price.state #=> :pending +p price.pending? #=> true +p price.value(0) #=> nil (does not block) + +sleep(1) # do other stuff + +p price.value #=> 63.65 (after blocking if necessary) +p price.state #=> :fulfilled +p price.fulfilled? #=> true +p price.value #=> 63.65 +``` + + + +A rejected example: + +```ruby +count = Concurrent::Future.execute{ sleep(10); raise StandardError.new("Boom!") } +count.state #=> :pending +count.pending? #=> true + +count.value #=> nil (after blocking) +count.rejected? #=> true +count.reason #=> # +``` + + + + + +An example with observation: + +```ruby +class Ticker + Stock = Struct.new(:symbol, :name, :exchange) + + def update(time, value, reason) + ticker = value.collect do |symbol| + Stock.new(symbol['symbol'], symbol['name'], symbol['exch']) + end + + output = ticker.join("\n") + print "#{output}\n" + end +end + +yahoo = Ticker.new('YAHOO') +future = Concurrent::Future.new { yahoo.update.suggested_symbols } +future.add_observer(Ticker.new) +future.execute + +# do important stuff... + +#>> # +#>> # +#>> # +#>> # +#>> # +#>> # +#>> # +#>> # +#>> # +#>> # +``` diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/future.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/future.rb new file mode 100644 index 0000000000..087a148963 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/future.rb @@ -0,0 +1,37 @@ +require 'concurrent' +require 'csv' +require 'open-uri' + +class Ticker + def get_year_end_closing(symbol, year, api_key) + uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv" + data = [] + csv = URI.parse(uri).read + if csv.include?('call frequency') + return :rate_limit_exceeded + end + CSV.parse(csv, headers: true) do |row| + data << row['close'].to_f if row['timestamp'].include?(year.to_s) + end + year_end = data.first + year_end + rescue => e + p e + end +end + +api_key = ENV['ALPHAVANTAGE_KEY'] +abort(error_message) unless api_key + +# Future +price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013, api_key) } +p price.state #=> :pending +p price.pending? #=> true +p price.value(0) #=> nil (does not block) + +sleep(1) # do other stuff + +p price.value #=> 63.65 (after blocking if necessary) +p price.state #=> :fulfilled +p price.fulfilled? #=> true +p price.value #=> 63.65 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/implementation-absolute.png b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/implementation-absolute.png new file mode 100644 index 0000000000000000000000000000000000000000..d31af119ae0633a2a29a4e5634f4e089786258cf GIT binary patch literal 109909 zcmeFZWpJIlk~V5)NX(2eQ_M^;Gcz-LnHghdh#6yMW@d<)nVIcnW`Ex2o4Ir5+^K#3 z+<$kfdR3`ZDyiS@R!gh9ABD-wiXp<`z=42(AWDb}D}sPPu7QAn2f{#o_Ox-_^n-wa zGFS)+$x8?c5y?9MOf9TUKtL$bjr8@I?40Z!Oi=XohsSBC;2c~PLqj7K^}D*ed%DMo z#`MSZGgGv+x3Io!^?^XRKe?9>;h=l94R*tzw3Nd$!K*|Q#n80;E`}&RLH>eUkdHyZ z{QWx)$nx?&8Ki*yAg4g1xFTCh2WUS%9IOLWI3Z~5(+u$$4Ur2<;2T6gJ(ver@d3QJ zH&@DdKd2#v;1Uy&i-aZQurAc$>(7zgvD-)#NK$ZcaLA#A*L>xmt&yGhxiv$`$@ab1 z+@G(x{qb{XzcoW&1X{5mh~Wf@3k}RIEOKBm3P?c1p+oJn!F#qxDP5q5gR3c9>WdLZ zjEtE~^@;k$QW(X4`uF%!frF0{2Bt_bfcJsnLqWZlz%a53K|z6fLj`*~g7j6%1J20G z>pvw3YbUPZ2m(S+`qvNCt$^PJ1VjKtLRe7Q4fL!7*-d#q^W(#XJ?=L-oBR}DiEYUb zQVj}z6tzBztH(v6Lb{WF4$cUEh5S{U5auF@gY$n`46{p8AF>#%ie|HR;0J- z{Dp6Hg17|IuUyUXiTUJuQVS?DmV;^H*Jp2;+{_Paj)!`t{f3GDjgmeM zEa`8p>y!8YHL!ou-2Wd8ELIreFBAO23Mz*g{eHr_H9}**-JAHhwpl5vYKgVkdmfkF zwDlI_uXXW+Q_HN_%hB$(kd@`alKsp}ua99nIw>+pHbZisVclKg%7?aWXM%7=AIj5) zd`7{HxoHL9&pFDnak2{ zpfwR>SU;j~97FRt9;#Ja!zG!BEWt8+c*yRk6>WUo7Sqy$nsQnSs>_ODd`a@GX5(hu zFTZqx_j$?oMbLU@@OmV+8^3FQ0;uXMh%P}<|G>u*5fehBw!#HYtB|z#HXvb&W!SaJFTR~Vv%qvB- z&0eghTtw?rc8{}Ta}z#8=k?3*E*E2PAM4_FaFB+2N@85|{WD5Rk#M1M zO37``2q|O8%hGkq!bLM6H`@3~n~yW19F*QsjwrL5Jb+#`|BLcNO618^%cgIAIfD91 z*}~we_cuv;15#wJ!H~n=xe7^ja$c7O{LI&54Gvg()|bbXnx&IM{05szgST2Y%I760D7f#UccjNgv=C>o;cvG*(xsG(H|V3w<~p?tjRwf3ulHm-yC z*JQW?OYcT_RB*WDPcZ=}ZtcNU>9usxTyEUdIAsbc-0zUm4V!7DHwncxFd^hpRxg+` z-r{3eHdr#yQu1$vgxkN=j@!^l^a#Cg2}~Ji1S|}YZYyg}kN94zjC8)f`UF1kjr4wK zb>c_RmhI&oS@1_I`HBykRlYD!=%vJPJ7NUsq+p+O{oIFN=sEv#^ApoB9TRWe1riV1 z>$o;K!u~wz%kE0|dsi_5>5H1@tU1|Ch_~99NX=$#@jKpq6@jObsdTSal!*0p78~A+;iZo$>NrG`mbvEJx*?eEM6WD-IF66qs1}`!8S25($Qf>Uv z%}11Xa#uI0{q|#WJ%A26vh>piB3BZ z)@R!+bA}fE;id*khh+epP`ySU?WL==PgI=Aw+2_ z&*Sn3%81K$yVEEyBcS&w-BZ#ZysbTsmAz&AE8IhOhh|@9#lrhXch%HQvl}UoJQ{Gp zHUMok+H>d$!+gmr$$sk7>)J8GK&ZY&5QFc214U_ufI)D5!x`|GtUM^1p1JwHU|dTz zeYX&-=we7QQ?hiMe$&h^;F*!Y5?b^wi@?=kqyl}yo&CcF=V!tgRf(Z`x&tXeS=qqi zKpayYhMq5M0$+A$i4?()k0;Lx3rWeIEi3-spDLZaB=<9-ukA;USolaF-$W0Uoue4a z0L?Fq%0=Ef5KQokH1Nm=flt8_dYx3Lzb@!#ObyKEk~bwJB(Ux>ghu1u?5tVuG25yw z-hW zpR@7QP$5IVz6~$L`}TovutU>3oBLlQ?=GqgX0hHZFJw1vZJ)8C=5DbYr9u@#B=lGa zY}ZQk3f~eM^dT%>=&{uox%>csaa3AScm+mn?&#U-h2V6V087oPC04^C>Wr-<*%GBO zeIt`1=YrU3$Wb^nlqr|{9h;ul)Nk*%KH3c_S8b*;gId=vP7~}4p+l5S+QvZ-&zgdI z^#U*X{1P3_aR#wHs0NbPTF40!9Qyo3g_nT%j@U6}mY1(YnG?pavSPqUVi zXd3qOHfncr6(gi?w@X3Kz7Xh9FtPK@WNxXLut;G>xgri9(!Z8pILVmk$25#Kh7RYl zo5l@5nyE12-i>>d5t~@K8&Im2IfRg_QaseA8IWtcM-^YhL`N7X_4?802AglT zub=k_-##W|SR$T<{pe(Psl>+!PgPRlQ1^?yaZV&xrc}7=E#i@TmFpw05bEPGPprR& zZ4fA7a3XM`QhUUqT9II?*Zeh0#+r$O;a0fbh)NBs7V-t_e2gvC8geVZ>BdD)4XR;=#4b}s+O zSpp$u<5y?!Rtl`Xso(Xl-+vE6g~qWWxQ?lN9>{H<4C!d+x>cZC0Acic{?7K~+K8#r zv;&M<-he#+n6E~LzrGc@>~Jfzz;!vmxe4ZbJtFiL%T!ISiUFWbWRR)TEuvXYExe&6 zsZTUvchlTGg9zv`e#gW((Q2*Xvx*~w1Y}#rKitnRD=-k~ym}=W&i-|Ag7ohS?M2M| z>+rpR=y!KwZHvr=CbP>fxj`axqOGhDn_`B_t~W+*O`}#w=|WS}why`lSn%EB+kP#o z4DNiEW!HB#=sebapm@dOS&81B(*3@&7(HTx4DtA9AbKS42=S2Wd-*g0-bNGdi_7KU z_OA|qxsC^3x{w+zo=vsoZ^mBZzCa_h=P+nKl@kYd;lh5vO!1p)gpG^@aLgDU=<2;F09suyxg$5G4eALw zL!lkK9ZqWbs`F5apuM*5GkC)BNqn6=Q=>^K`VM6D#8B^TXoP)iD>von;au^AmBjSE z3FdiVFqRnnI65`&>A?@$@cKToH?!t-#^HulaOhQYXU`5GtTwbe;%r)cIB~N*c~*pJBlr)2$9>V zfSII~h@$Fxu~XiQ*+{~(JRUd_;J!b0mOQ-aCbn9|Qfd}B+oue6kw%H?i;as4xji@7 zrjMJzn7(iuZKtJ>iqmTb4@sBRN_Fe|f*`^jG zv+U0Ajn30M(;-_Hr$0YXWi2DT#oinp7B$1vV_g!lwC=Pyu4|V!GtE40Eh8c^|h!5KXw{H=fZPn4LWB5xZj8x_nlAZtnQn z7~k24-pMGhX;z&XG|IYy9DJKrUxObxT0c%~mM%Tvn|~weX`x(7j^nMh_TcruR(7ny zY&>vZL1o?0gEbpCVsvjO=N$ws_yHrJFFZah{jXAehoYD={Ac|g8NH={Pi80YOj%4e z5AFcR26j?oM*IIdv_XytW%fn>igU!lWbo@2hTYG+m@(L(%f2)yreEqdRoNj!xf436 ziSq|<#&$KLoJ;#XG{Tt~cTK=mc=qF%xEi?|^H*CrgMMu>;X$exk2R>4LEUxN(;U66 zhtdtuY+28C@oZV(Un&jDQ5$X^7{V$zjB^}fHN$e{qerUfbQEumS^r+F!i~V0a2p6G z9;e|&DbZ?9(-a~d!n%p4X<_mB`cv{8@^|0AnL-K;vu4Iei0^Yu<2N*e7<;d!@jHdw{LRs!zyBC36R=T_XB3wpe^^*Z=26(Ao#BZ@LK1oEE|O+JIwvU9{2>3Yz2aia-<2-MOU zIY39(Q{;6U&V8;uycDmbma_f!RdKaOztbmUdZFiiSJG4Cccub7@Tq1+JX0#Q;aqL6 z5gkn(TXV)>5oa(m-RJ za^K7Nmd#vZ%xPS!WilkQWlf(*McUUY>fTWXuSw$Sdz&uEn7f2*Z!Q)cIrn>A;9A8j zU%3B5+tjAbdU{-fIQ|Ji&+r-4)6;z#pNkj3g9xtIEaH@?;N4YM)c`tADL%=Qx=m-hk9QngauTVf0c~C&LyeHAGR1VbG0NR z6VYtR!K*j!F}(^SHTNeHaji^?mOE^}f$#+kd4#7(3Mljp&Lm+Iu9o1QyJq&y@`o3)jN z$q*saR8VL+upvjYHEpS}DnM`0v~dq3BeW*F^VdK>(4{+^mY#AhNYGdaKY4|d5@FLz z#hy6{k`~D&kkWX&Yc3uu;3d4*=#b;Ymh+)ua@$p2r4G^CCXj3XnUShyni{4aoog}F zyBBK9>w=Fr{oJP2pjZ&SJTeFS5UZ-DKT_Hhx9@zn0vvq){obpv%|-@md}Nb+rYT`j zV*U&KIW^a#rTE(uB|L7c!=xewoaKIQ2)2Ro>=br^H{&yFM5MnYI%&I4MXzGlU1yc1 zV@qiRNl+@sk;=V6fX#x4O?sYi8HSCOO-n;Z<7kkcf@?yj_NCsaqiRLt*vQB& z>7LRa@_Hw=v!X_KY-ES~&gg0wZ9pzsuu5}Cya|tYy5>gIeeY;jqw1}VByMhz4yEy4 znhZx0Z294?L~;`gxqOoN6R+t9@tk^1SWl zsOglYX`rpuO0*M|`ud8i*MX!$F_^^5aeri;Tb7=~c3HgrptaIYQZZYsZg4$+Th?Dy zuaI-XIG9nCp~Qw4q`HwNscJs>_$Oh&Qt`&XBXYX@_?N3;+@CYO#P%C*`I}tn`aBau z+Jov<*0x7S4!HX3m0iJz&C=}L6}IIG7i!#UghDe*Z|{sw4U@HGlU+Ynx+ujS+gY4} zge|WQ&ILLpwmpPQg@X#ic8Z0A{e2qLgXWbZqgy?1B*Uas%Ap;SJ+}ix;MkA}iTP(J zH}*3mpG^5z3r0fV%qfw(IdL6tA-cI+S@s!R0s6-?kzhs@1+-A>uXi?+YT~TL=cesc za9DHM(AyhFoTvvW^N?TG>+8x zI#7{{4r;0P3}#Wo?~%@3+S=RU#wqUTMx5aZ z?_XLB)JvSFxH-{&8K;k%zK6X&tl)MT7B!I-l*Y@o-x)6_(ve^*qb(=BDq?x`F;oYy zZkAFOBr=gdVT~j^Mujs8)B{AElVGdkCTe_QXwLO5^5S~Gj;k2g)8y)v@u8wcHt=zQ zZ#;g1YelK^rgJ!!GWsD{MY)5&f3T|Tu_csAUvQ4jBhpb%eRRh$H$v?(vO7{KEmBjp zU zo+pLm{4s7KUEc>i6}N5WE!gQnW+2VPC^Mg_(NZD9DooAIT=T$WbjLAk3}w-p&iqHG z*9GfX3p&}n2_+Sspv8OCRM#ZO8W7%^ExM;Jal3?$bK@77ObqtYwKZq|BoE*QpE%g~}x^vCcIz>!4ik4{yrZ`zZr5lZ+B?<(>S z;N^dqCOM?_nzgf58JLtaj=NN!w&YQ4wH}*UVJDfi4eR=m5tio5jIn14c`c&t68Wa?(X$8H0vCc z;5(bi>c5fvLPtqBglw2JFlq$WQ0RnmJ`Uc86tGkPI%vkX5vbrDh;;MrbT3G&MjY2B zv%d2$?D>WtEKWY@+;0_hy!Wx-KQ&XW1Mm;b_KbG@p$VUAH>%}*BCZl1_49F*PjahL z4r^LhuVk&oOlSY>lB$X6^3OT~RMIz^=72?qL^OEX#HC_gukx=Le!o@PefB z2;^zi9GTZ_o1QR_9?qMla(rRBn-xuW;bryKX_j30Yr*&)Gg|e-toslddb%JvZu+8X zV`j38%zVM;MNv^pBgdr%?DXKT4=a97ORh?4bJX}&+y%7s#6hlxzA|+=?+Cx!i0YCP zA8sJdc#pq#Us56LV!kwu92P{G3^TTS*eF#We9glEm#ONadFI7r!;LEIK#;Tjll1N^ zB)NcNova(3<0>9wROh7R@v0|+XDJ3@yl3t2=Dl`Vh|L;hFhP-&BMfM!H2EIs7Iw5s zE4VreT#p*1RV|5F$Q5mu)z@a-q5Y^<%RO-idp?Vpb`3h`ADku+L}V5w|7MAR?Tn4t zC$v1@@&WHFpC+i&e)d8rXdwYBDJf~c+hF7!#KU;UWw|VLC6%u-ADo!N?hA@RTm4Sg zSkWK6L(N<|vsLBfXY!9YSl4})@j z|GOye#WVIb5*Bp5+LqP=(nN%@@su8o^*dwpI@N(k0j0x8SfSnFPoo-e8SlYw^!P#i zyx^Qs<}QMb4WCidEC!LnE9Yymf{pieg&N%D5h5oyH>_v9Gc~TxSO#beb6&>{+uk&l zwQ@bel^)~NiziaOp4@*l>oXae?ERGp1SM2hz$>lOW8rw zLW~GC{V2Q|BxR2`vQ0Gf{%>?_`J(Z$2l#vgQU(&6oDA3NFn?lhQ%o6VeYf5LtG^C%k{8i{7{lqkahQL_m3^z;Rc93h3zTQv3nFqk~$J{ZMp_;vRw#1`c(OMCw(=?8t_l|Q$uQ#fP{^UkIF8k15~ z9>}R}gZZrq5BvH(q{X`tAopZ^FIg}(lt_r=na%2V^vfCX5PpQ?wRFfkFv`Lp4Mk}Z z^;c#8Fp95_)=Lbq36{&mDuvv&?=zKPj9`)FPB*4-0lH**U?{e4P@i$p>nBzp;#gbb zI7x7}T!&f)u10{EvkxFSaYm>ebz`W_d160`wJ|j`4l~J$PlyW$LFBc&nSIoWnT`Ri z!gtq%yS-XLpTS%Bem-R0_xN#d6Z?nZ<6Vsom%$4e%5KNvqgnIT-sH~gH!CmAfd_ZZ z$1*vB3tPAi-{5?t%EztLrn*X#q(+uG`cl!32%_eT&b!u1$6A|%6Ar#)|UwPq?MaKM| z=)uQ*H6ncY+I}+MoZUC-;Gyidib0L*VhqD41Qc?GS`v1(YV-t74j5*}h*4nc#C7kB z5t&;$JQ3@MOZY+be#N@r2sUhC9F1yka|rtR>YaVFT(^zUhPAJ9_f6ItK<6-1FrE^e ze`>^raREI4GCZ>Buc(FY^0aGHxeWg{>AEa0fBhvxuZxiLJ@fZ^)!e9C5?w0A zxT+A7>H2RPd~g3W@ULZMVa>QtN4(7Hi>71gEz9l5MrqPdKO$m-%Si8#0t#y>5`42s z)H7kga07EaO0}~pF<#9#k>$1b3AbW|st+*rL?!MmM}I2jUh^>S6TyyNYQVd>VzTbe zM}zJac13yk&YV9~4jr1Y=1FeGd2Mk_T^#O5~b)N_4;^NW%PD$o&B(L5Ri7pgr&P9d~35Xsl8Ck3gYn!}qdxeOK|B6vgp`DvYc#_9>$plRrs!2yTnz4=)QQOX>d45)$%#!$ z*dB6^%0CcX0qr%-cG=rE@0w>ZS{wGI55=_DJ>1O|&Xt!|m)LS)-#)Lh1=Wg;9tF_j zyv4bDE|8#qSF0?go&zX+z_O`76e(v;7`=DksivL##)ywR=em25Lmc@{CqyJwmAyox z<5t`y@D{Xu1DN~04h!uU=6$3m?(5L4U@P3!^9bErd&H5)%LU&(HProDr8Sd7Z)H1<$FCIb)3%$t+8J7w z3oBb*_^0U43;}JzF-+t@n8-~pAA%F!0pELzdif3TJJ?hj1idHN+P2R!CMXz@{}qJn zJg<@Wk!m!oeB!yT8nFGREmEo3Vj!RDa_~znH zLzeHNX^Z34?pMd;uwyay0-`tHu|~}SZ}#xv6H}{hW`5^79{R!OZ)@xN-JP3u7lp0f z)YeZ2SqbL(-kwXe$kWX+Lo$F_zI?i7-u&A23#T-IwlKEaO(hSHyF>SmN9q1k#^gGz z5wmKa6`*hS`2O;!!C+wg2UU|{f!@23-GlqowB;Fqp(lI{h;;uZIHYJn%QEE2?5D(l zwIaO*KYAF$-c9rY`v-7`MeU0K3(GFPB2f(!hkmOf7E960U3iup0evm#BIyk|Oo za1?F-8PyG_)%%ly8>A=}Wi&++zE6H!su9{z6j>oxFU)~&8{b#1OUe6mrm>BQSER_B zEmn`qz08;};GzLf)9BFDOR>oh|utds3W=gd4K_DnAJ6B4HC?&ZjZH(2MclPM$T z@#iNB393-X*r~%KC{F~xt=YB7O!jluu!eVH9=pdd`Xr_||rr6V$B!TCwBUyRa+GV>^@eJpc(EMWF99nda*Zi z;Nw-fWkQjNuLkIQbnq+I2BM^*Pzw38PJ|;Y(JktcH$eHC*=PuHkR%!Ry?<2wk%QKd zPVmGm1jk>83~T|7%4*#Q5$uz!dBc$(MQ2pxIs-2&#^F44>_nSkkEgQjCkTyJ8b$(H zsPH*Yn>;^wz&kiz2Fo~r>83m#r+#?(&EbHdxR>CoKP=+2>vLQc&w-Dmaxsy*SZA?Yn^ z1Huuw`XGx%GhWz{LVE(U3n!Re6(SZ5ZZ$XWdI>%%U3GZ4M@|V)#mZ=V*ECARWu|e3 zfg>*Ch6NRi>tqMi#pXi$LVw(dmJi^&+?hKa_vn?wJ84&>-WI~fHGYP45qXTp%&Qb) zr!ek<&jB#t;Wh-2x71hZt*4O_(9buWMTPIX?tRkxn^}Fu7^jK|%gEgd@^@k?oNM)9 zo2Gb9zKSIydU}W6%Y^kkz{O+N8(xRLj}+5?y}b@cHi)c|tM}-?3v}dzs&9U*(dS|nkgS_=3`HNu0xp@TZ8WIAhl8!m!DPaxT z%Vv&K?#gY(4(kzD1Q-2=hs+zY^BB#D+8c{9I?>%`%Rt-v3=;c>{lm6|CB`D9(ujpI zrhLWZmQuF7gPD{zd|F}ij7VY(_JiV& zRV>`-TEPYHsml|Pvo|o63>Do$yP-O)jfSTkD?_b%5COI|1MdgVU(ya*;lmCYZC!@K zYm%crj46b*ZlQ#q^*)^P1mLxv7}zQb=(EVNTO9go?t?p;ZSU1~HUJm7#^4OqmCQP=kT_Yrd-M?a+{w$_}OD`U~ z#%9EmqKW&<@|trza>#lUomJ`h10rsYD| z=KY4U`SxX|!b8IQQwtQ%79yFS@(w3=%C{Fa5EtL01kG3<-UVB~Yis(9u6D+ll#sYC z_+l3P*uL~kDcv)gcO?EXJ!XFs`K|(+LBf^CihMWnB*&|P2RGVJ>npJFPB)JB`L2!-{ z(IVQs<5sEwyyVwqD=cN3%X%t!gs~S84Fk}nC@J?jg87`IKlL0p3=-Zf@H{~`3TGcV!Y5A`F45W{Em^YjxgGviUhX7~1x7#l>xXTtD}l zp0>u^JE?-rPaM-D2R2`lp;+QvymOnL4T_V2blz-%4bm7A3Q~0J9ok6N=gnt2%5yHb zA+QjDwby-Ddt38Bt@e?W4fmi@l~4%5uZouTqd`J3@b>T^&{Wi&qx2`xaZkU~tEL^9 zCHD~}MiltY&rEJ^b`h3B;Z{VtEA!I8sBKr-2Ip&c9r;*9ae(dHU%IQP*O%9-15hiC-6r^ z!7!uzIAR)CIP_2`QO(AJI^r>mGJy)Bj7P5$0hRgy|igCZ1RuIe~QaHJ1xd*4|(jy+AhDgs=k@<-!I`3=c z2>~ntb9$T1x|go=T_|?-ejLcvw_hFO1c0WD)Zp(4<>-`51mVcHyqjO!D^v)v4s6zg zblUniOMLq7$llX`qPwO`{+9W;_6m(TLE7^D5F&6nM?c2!y^KDJphtBJQxmwn@g<(~ zF-qbDy%po6)@6S)+uWY%6-7`N3O5@2#;mYc8QaSi!A_6#)V0Hke&dz%49ed%=epT2 z3>fLowAycL_2H(tP@MRk02^gq+>=epC->S{Up5yOgz|33fMnB#dcbaHV~6hT{sW_y zoVtsGT02P5IBzCe#Kqh@EzKoy{zuErEz9=hF)C>SOa68 zxyKH)X&Y{lHk0Cftm%evIlW+mHo$W5Jca;i+BEe7an^1q;yRu5OL!7D*x^dB5U|ay zAyU9gEeFXKVD>9KJqh=&3VXBS9zVIzt?v&`T}6xM^qqKzPY%yScgN{(4v!zsbYtLj z#ot@i)}rK!CWH@UTG<4;v>$uwD7;1Qd?zK&k_iJFcQ^1pkUoTO!z_7fQczPDYx;2m zRu3u)U^rBy+ehYuDEi&{o3Cxt0ayV(3?95{-&&h%i{Hd@S0GrW$#TLOw~fTf&g2Jk zT=h_QGdm13v?34Jd(2*n!ZR<+qDn2ji&%v1J~O$*KX7B_5so!r_{}4Kc_sC&e^}6a zAqTt7Dm+K^{H|XQ!)g-amt!=^aMbF&EdCO=2cFLEhFe=4{3fnLZL4cB#&T~c zV?f3Qg#dZ?-D4$2fA)MZj)-mTu|)Zs$r{;256f$)n<;v3P9K&;SXCY2O7Q+Weo(8r z$b=SnK76x^>rQA)#{2pO>Cv6r)S@F!h)elhCN$R8KyzaFGwQhc<%$0g_u;UxL4|wL zQ}BS^V9qa{sm)tL{JY<8M|@+N$GBK0+X;Zty3TNO2p`yYQ=gV8Zmhd-dJNpo_Ve$p z?N6L=ascl8{mEWaqoMF6ixZf&`Lrz-im3aYKc%LC;_=4b?Eo_3p>LO%{7=r%f zek8?BrF98J*$#_@ys4l0^zJZsWTMO9Z0oKd9lAwI43kPsb^Eeym2>1U)qy9`IOS`%o@W zgw?f@AC0H?|KU)0;#Jrdm2HXhq?h8xSdMwFH=Xrf!i=ThZanY4uz3NbMC-62raSC;<0WX{~Pe;U=%`z`BT(+4014tLqUu5C=eIEf23d59y~BDwc1&&xE$4t-&FXL8IvcM@O97}3xFNT*lN$YIK)o#wu_YM zJM}oI8(l>YTlf7a>S<^0W-6?+xi;lZY-a_6d2EVG_7RK8iDicE?itrahd(wsBtQn= z!2qx{8$jB|p0l(mx}tglA0Jm~KX)qLGx$o2K6=N@+@6ekit)-DorPhvWq|A zS@jFfRqQ>SaR!ap!32n0>5{>G`wVOT*O^CoT|hes<&8l3!t$1ipWbP1Z7RPOZIIz( zRPV^K&z~i4cKsx>eHd2~aT&j>!~z&tgTCK3R;x;(h%$5&s6u^Gr@{>I*JLCQ%?Dg^ zJ_lAIp*L?SAE1}8drfr7uhTw{dEr@F4jm_vx)FJ{=%oVsKHBMe!`<}>$j65;$B#1~ zN;+<3H`DV#ZGSPTg-#x6R*}5SgdSJ7MxV>tJnW=L4Db=g$oy#i42OsM4gLe91_sky zmYHS_)#2MXGw4@DY#XwchGi+mkA5L&Go?d(8CCt`gR5b|hivR7Dh;bs^L53VZM1_R zy5qL_*y6K5$%vo~cbnpSMy_Vlu~^C)x+oXE=IZaKBe#-u998|L=MI_IWg3=an_?MM zYSIXP&!DfH?iIZj-WcIQI@JV>1EJF_#bN8I?~fJ7u%9(c^9mc}gdw}z&P1=nJgT$j z4;2SZIHtZU?GD>cVO(j7p5VRv%X_wJ( z0cKwA1dPbHN4c_P()BE%1E6(YE0PV4*J0mp2^Rwi^Lrt)R6`)dj8%HB+88T&&4rz#xF2@V9`9P`aI@t*Kyp;A@Xzlb&W zsx`MW{Y27=MoUHTG37RWM0!{0V}2ERcecD+_aFx92xU5hnwXcj(@jl8=0!^I5wFy; zNx35(Wh3}U=}B!;tkm2L23&Pne!z(}oC$HAN)8&!=P!)rFsG$-$7~k~pDL%Rx;hKS zdIBvy{dD5UTkS^C<+3TUNu7N2ldz?9BI*#r7ZfaT%k4!IL`oKu_}1l!5;974_8gEZ zDradAw^#dNix2JdN0awpV?`j!nUrq0wLiYPrW5VJVVbJH!x9KRN60R*Krd1->LQkg zOgB(?Xana?L!EyFV$*j;)iWBnw%17@Qf zJh>YyZn}OJalx{9y1EbBcydg;$a+Cfc=goh=Ylz)ww^31z`1_ZRpa?myIf>)D8+yo z-DKHaCS9IB@nEX3^ft`8>NYNtJ7ZHrb1T^1!oZfm-k8%V%`@v-pHDTZm}H<eWz#s8#M-D93Y`N0KZWG9Jx&)Ckb^xLC~G_IfRRtJv!X%g5PV)+29N8b9EA> z=RUpTX1J9jnXISYNGS7U!K-LwxSzl1G24~k(aXO3K|1Aq18AFFzgXKIxls)??x^utr>q5DFGu|{J|$II(97EIM|peMP!IGV zYo-oj5?BumIiGaGT4_X2CsCfyI=uof1-nGspsvt!br_@>Nkm~yC|ql#1XcE(*Wo(# zij0TU_y&x_Xtm+YY>b$Dk~!CVA?XD4)4E4}9+R;z?7~`&XE}u3UiJl0a?M-`ZWV*u z7i^Ad%{S?xk4!0>r#lQ`b=Fb(+Fe3TA7qu~4W1AImLEtweg42xfrgEc5<&+VjX++~ z^wf{<1`5%eRxHB$bA@-~gy8kEetebd)o*>l}MIp0o}&-$xwO_Os^m>0&zbQpd(PRqb;-)b@1oj-LqHVgj?|YHAgijwohIm z7H?8!SbJ)Z?9igOpJN_ZArvNz&VTLT38ful{gw8n?7cY1X?(Wmawh zINjh?uYB$2tx}cdi+rJ*|16NW>B08-(0LjxKEFUuDfw;r3LJBE$PcW-#aSv0R51ZO*jXU?J76ffVNG(1I?>`6!;fCL;pQ}^y!`9#ubRREn3T$SJ2`jYXIP!UuVwj}a1Qs0$!>kx4V*i=X425MJR3ju8PGAJXKRWN$GW7BhJX?FL zOL-}n)Cx^cg79^v;z%*buzg0{=(HnC+DP?G_33jg_fkDQOboT9$Jq4Z;qWyV0!ya& zM4&TFdYP4e<_`Y@;!M5$#{Zvh0sN!W@Y`?dwE;$~rP?uVv~WgBE{ab;RUoXJEP-&^ z2eE9cFR2apJ3n2RieLg03?^ zZ>Rs0SNM<4?)|@BfB4ekReCYxv&vY1-iNwEcNakoBZWP0<^UQqTJ9+@<~TGWQ{YCfUqL=T>tOdp$75_S#}V`Fzgb$w!=(Qk?KqJO1hvTr z9hPtY#7iB`OHtqt?)84eXL*WoZ%TZX8xtc;L*kD~mTlO5ixC!38y~El+zj&UjE_LD zTp`oI`8zkM&xQK=9tovu$WLo9y5Kf${WE~;<$4IirH$y6nJV(xfa#L_v>RGbORB%% zRn~v=5&t@^e%jY9>oCAHKdaDvl^y zHxLMJ!QBGE-CcqNcemi~?gV!T?(XjH?(PmDcthjP>&(oZ_wGw(*5XIgtGepcK3l&n zXR+_uv^3P8{6Ft2$Q&mA#?v|K7HE|KP8kbo8AiEM61v|rDpl$WDz5Q)=d_~Pc>{-K zB6({)04-2MQTX8_2KM$$ue zgpyBOeARPDnO;M9Y26K@E>!JJ#sN(}fFaACQoVNew;Br2H{Sllun*(;3>;z{DV|dH zetX*5o6CB2lzJs!t0^JNJWVc=^o$AyHigw{lT+W2{|uiq(t7RNjNA8LhDSg^P^;Dh zyS%!}yLElJS=u`;tBP9X#sBdRMv5PNU|_)YdPdA{!)ZyntDjK>WZqDPwkO!P`suxa zaEE75^uGxclGrUt-x1)l<%KCz#H>N@DE3>WEq8dZFIMSBmzUGbf-Y)~fH@p>FaYx5 zz(@1Ha=cy_@yqWJAe2i^NFP6IW@pV?dvdVWKQjZ5_II_gRi_V>gt?>j+6+fNo~|{~ ze~07yhpwXbo1Ily_j!9!Yqrg~0XzS_k3{f(x^It5t~U#+@@i^WgOC-!cbzD?dwLrA z8j`yazsC~fN_|4k?2h)nCOseQU9)bZZ71a;21%&jJMh-t7lme5fH+Ufrzp$Vq%x?N zOHo$%?FD1S6~0`)-2XnkNN*Jd#MGHI(k(EP;C*{Kr=08G)`z$J;@*Pvg}1D`5VAt| zo}IC=5dOPkp!mCT)6Pg9wzIZE<98hY89b)?y)t(26 zKK|t8e}ALL1!8L6NiAvhdXx(EU%`H*jlaI>tV(_QT-1};gl_HA-1rf*Geg+PBR%;j z^1t1R4ja_mBsN|;e9&!($rnC32pb}s7t}6ct~t|af}kM8SMX0~>OU#_Zbya z$@TgAdvG{xqS$IG*2Jlzi+N)&rwxhqK|jSv2B1QFl< z0h&qlM3!QOmik|>(<}Y0o)l@tz<;(p9+v@+WPP~06Ptds0CGw7C!{spa$i5I;etv$$e#F#o-tQLDJDM#>^w|7ubi36cW@l%!-+;db$WIMg z%Voo_oFx)@JI{6ZqsL@eeGdiSiNWjaqT0DM!Q`QiTOYC!f4;R=LY1<6tuEAb-m!Gz{(E<%Y-` z+@|Y(O0B731y?{_lfRKZJUax>dT24h&Gx9$YX7jPcLUJGpS z^)30}qOeb8k&6r+0PtB0L)c`p5Ib=f-U0ZP5g}=A7$<&VT9Gsdu4mpPqmIl#@N;{V z_p9EiC6Ony_(y;76&wAM+h_83wSY|O_Y?anKibr}tO%#lWa^!ZC=NrvjXe|m53?Cx zrFI$RJajjktXkD-`!pm z3PC3fuEo&-&z$j<{C~^mZzlT3Uos8&(Xe_O0cRZg2E>4alLYLK{RR#+>TZ|G2pSK2YPoak>q+>kc6Cny=!Rn%<&_gMp{_g&0-9kLtUhA>yyuE9_ z$`P}(sKu34RoZPKt#I8MGiFoS^df{MeE5)P1d-J=>(UB_E~})cnsDdnN+_N7e!tBn zM{k1ZEm&P_#`~uk-cv`&&%GA4x3kD$SVUM}>EMg;u>;2UgnO$~KKOwxs*#e7JDG7D z%_j6==qr}apV>U?-fd}Q`OB5A&et2dx@+2Q*AQ-pTpaMKtQ(8@P3ooTF^HjSRhOZi zPWnX1Zag5Tvv-C2yvR_RJNZnS!}GPF3^MZNd7ZhsPkCGN$r&VapjeOeiMam6wLlJO zta_m+jqX51VIbIWrCt}y65W^06h2vd)oS@h2Jomx-inF1(YuN6dL6$5E~VkArNtdZ zS~TB%D$Vpp>0>rdwhLzP!lSYgeVSHMj6BeBg*4gnUehEu4sX8dawi0Iv14N3F-ak5 zAihB>^IKnBrqP~a(rF`@%rr54zM(pzBIpZic<|NT$;2S>&n*M;m?q89* zVPiv0F>M)?maW^i=xW`6vHxPFxXb>t`k9Nm-nuGDu%Z_br|6^O{@hN6w zNBRjb*fQhB=traoc}nHS3Ws6(pixmi_I=hDZkF)FFwg3hic<&!Qn6Otv7wlO%7vpN z<;q6;wykJwYTMP`$EE}}b0$?Zw^Ji4CKl#$IWvW`Q;jl`!QqxY;}n>Yft&@&kk>=e z)&?drLTP7np4OF|hLPqE4d%hX^>49)xqsDsmo!m(309a*D+I23=W_MZKDrIh_2eO5Z9M;8&65dc& z?!@0f=B|>Ilvhy74q1&*j_wMO@0u{3LZR8RJY8(e$#2Jq9Kw}Xl?TtMztd(-t2r8R z{w6cP`=(^RtiN>C6}KZUSp)ZUWc-#<0q;Wpk^B2z{KTA2D5To}lWAHW!~z|r`B(yT zcMawim#ac64BN82hCKCiJVmsYtD%p-Th%a>3-dbDPVd*$sg#3j;&I&u<;NP%F}bbP zpv6HAH1WF$wWei1jD2zY2YDHmywbrY#WetppokM>7#^G&HN zU7#AW?}qh@1IK_u_`U+4A(tmk+OG{n(%``Fct#6OwhM`Hads^+P1~BLcUX4>c&jXg z1EPGe7A9Y@sYWG5LUFcOxp;^+e=J9?8ErdrTw#m|h_xXa?5l{Z{(zk)30%oT_`$i6z z`WKtAtw2QNvJ$qDYPOf)UGRVEaY_!B|l`Le*tA}K~*dMU5h&|Xju(zs~I%u?9uIo6j zTa>*OU81krSF|c4+_kIZoVwGO+#Q7dKoo0f>C^szqlaawc&Re1yL%i@r!j`d#BV9m zu&BMNkJ(1ml-GN9V}dYM*)p&FM0yY~K1k>M!oH5^l7-tgQMeM%pOY1TdH5@gEg`rt zqlC?+sqE;2%v7$7mSOW_H6S}WybfCFk=eO9%YtRBJ!VQp$)r<+q-55%1bKQ4%cc3V zxw7xh-(i7nBgyP{Cf*bvA|A<3y?*GoBkUa82y%IDOA#Y#V%;nC-fG#MT}#)LSI{PR z9!bh(c@rI^qOt)zP*>^ue{uXbJ5kv=3$PQ8DU%B>HTHgMd*Q@ zx&FfnV*5pUM8p5gKJSU~8d04fJdK1$xpmOK(*x9jr>6=1p;WeMdFh->HF6|FmVFwf9{d$X5^9lR7)~mcZdHV}(hmys)wWjnY|!ELLEgR0sV?=@dDCDo_eXI&uuh&>jzsv-zk72Z zWx91Lb!`@gK$;-3KdhYqc4UGs(<#rP;%sXB{DAXpnrtGfAPCO*!n)iIph10|LN&L z&z@lQ$a#C9!-bU~_-`oS&ZiPxkdAqQ=xGocxrEz zZ*VEQn<9>)a_d><&4>@IN?5V$e%cwMn(un$r5TY?h>Ek@w%|;e=_gp6c;+TR8%k_$ z=dNNtpb|?BxIww-UMC{F(2H1$Uf@08Yq~mLrHKl+bK~cE1cu};G(jYMzkXv}(`Sxl zmu`ET7k4t1e$yn^9Z! zResgJ>>xahQk-6^N4io$5B_4}!Ugn}j?|imIimQM_`9zo^0iQCtmX?4!e{@0FW7>R zEgCWGbA;LcXR~rPYmEZ7(*786rR2kqzsq#)r)`>~VG{AN zfwqb9QRz2p9X^?UI_X|}%IWvg%$b``0)zWho%mt^)V3IAueO+^$_Mgo<>vPdmFySA z97$gYJ}Ykb&t4+~`}LeE8zx=GLoZNeMtG00ySu9^-^mIcr(b7h95H^uoH28etWaG6 zIH1X}viY*(j+3f6lV$3QC+WR)FK$qk`BSyQ z5aKDf-IqR68=uG ztZVOYZ1r=%Wp^3E-tSJ2-}pMqiUIJjNr5^W;+=~;51USp_@9dMNqBjAx9MT%Re7U^ z2r+$>yLwEIi()Jc-L4#5X$`-f3coNLP_L2FbHVTRojgN|%ZAzVxWCd&KF&eD@I1a~ z#?*a+bC~rSb$*dQC(MWIGt&P@`S%V}jdLA@(JuFgLFER3rYX!ww{4H^M=X9&R5>&i zL;QDp?)xd`QI$PZSnyfZf${Z7uFcNm;l>fK$7@uKRCTLm_>$)~jkx_*it!e(5xy%; zmktL@gwE+!CaoF%f%W~vT0fo1i#=g?(1Z}o=|UEU-bTf7&oSTUn%6A%7om~EGSgCuS<4y zt88HviwMnT-rv16V5|+;mJpUT(#sl*>RWd&~@NN4zTEQ;An!XD2mcwqjKZ7;2ZMDX}j$Ll5D~c#-Se z=rrql)F=Yph`wN`b|eB(;WUwOw8PBId{xFxo5J0 zOfQdWqpxU-hAmBjjA%c(8~bO}*DkbD90GoI$nNTSG1Kt!9@aQ!@3=B9PD(09c7ZS^ z_6yZyLBV@(A@gvipa5!AfCr%-+03IkoAu6nDfIL zc(d^JuNrsK_Q2je>iE8zbun`uPi}mLtI=5CI1>k{?Ep#ob?;Z&1nrdor}r80ho1EC zVMF%Vk;k~9m6z;oKKa510*bYpkId_2#m*;cUEBe><{#(&J()V_B_Pigv*D` z6(LRucWV2M+=&A_U-mgcA>cKYPYb}5-wAjkr=yR?pU=^?{$R$*pu-t}zUgv~ZFN^J z3x)(%X?wT54felj`A@Q51Pkyuce%q9f*YYCS7qQZ!P;KVj`_!J?bak+K3)wM$KI~S zScGYYB>en5sZ1=a(@iUbAk}D+G~a#wr8dbmH#W60Otrk#hk4;pxR$K_JzV=KwwMM9f6psmLrZlk_M7ms2%U)8k_eicP|1(85ApL2+uel&1dKv% zM3tlKbwN+!B_~FBIRLB;gdn;Jl^~SxabfLZO8Y8D!ey$aT9hijSdzuYHeJc4#NBkt zDK8;qrk{z~wcQOFF_#q%D$rNok`<&4Lq!C4cG- z%mfoC@ubw(i_p3t{R825652CLbY+e4cFxc=8?P+(32$^%7v)Foo}pQ27iGSKbUNN= zu^A7_MC9Zju(7eDUY4F$8M*WRKi&k)UZ?xb!eU6*y+CtPz)_3Gy3>eQ zpLD@Y-Y3XF#qMAuGnogjU;ZIMoiMh6 zZ8kD-DkpVvaE>E{xwmC17;rXH|Bv?i*T-%p)R6wfy#u}GXUfO^X?={t zSmZ-lxD0c}ZXih0DN6Xl+z}Wc<{D1Y~Kkg_< zjGPe@a2Svcogn!8t)N}OtSM116o0~B+`d%|^qvtHYg>q%XSH-l#Yo}y{gq0;HEm(+ zJ5+!m%Zkf$2L_|=;&x=!ir$&=EN@6*RL(!B^#?^jtDO`0z1p;&=tdd=Q;^5(%hNVS zv%JHF(L4X~CyHvm9?eiIKh1?$?nk>UzRA?>fCj%eKgD3+Alx}3)tVVL|Ec(WF{QI4~Ah!$G`i zkzwIxPh-%Y2n6cP2@4otRvj_UZ{rzBNx?HQF~wwMoiqA&mN~a~t_CevnO<~-)e|ha z{;@5;-(iqkPtXdOrxvHosDBmlgA2`w{3*T}zow2-uE)LW4H4@CzFC9kbDMoQ)PP2AiRON|+xd)pTfIioE}i zvm@*l3JMAV>!2Bfbt7(-Zv&kgYP^#&d;Zf5Mi+3s!U^`-XhMCzIE6!D{Hru|zR+Iq zx;wlY_ZP(H_T7BO%`Hy&R-CBJGwK9{#zb zUolN7HiIUN01WUgV$-umLpifAzq94HNd*2KPamZqk0c5TJ1j1=gp2IQdzH``aw|J< zy*4%uRW`LvyBtxJ$yNY@X)f96TTo3iUOyoyxn?Xd)Uvdg?#ZAG!$Ajo|EW8mO;*ZJ+sygLvd%9u+RayP+}C@mIDj9oW<=B< zG)Hy?N1#U&J_Ve4Vvhl*G?PZfbok>4-C*4x!j2A_8Ho0*&o}~SwKa*(TFTp#^7Uko z?CNiZ5ukjW)2qaQnNs;Q>bdorKWYg)!jSeX*L>4Ler1?M=h<%Yol^5euvs9<;M4ZI zi>;AyxGrjU?SZDW8&#@4I`-Ea@`goc^~UU|am#^{C@CjP+`H-|kJ9Q{p}Tzzhgh{U z+l&v+qW#|OXqpO2I=ys8V+45o3v5!D7%Uuv<uGKo0>V{6shLy5@EKrv`@rdj$08Q!w zV3hn(2qbL8Cn8>!gsN`zWM-477vK=#9c6dplI5+g=-n!}%&KSUl*Atl1KP-L>n?qI zrfxcW@hBHT?KUI|uiNm?7N^RryV_2(o0|vLvkHp&*JO7`4`$VboJ;%G+f*4e5`;PU zr*6u$1ZOzm@DjNG@>TH#Pw1>VcJo#=*g0F*A3egUyi4S_3b@*qIfnB1rx>x$lrh_Q z^^eS{@ggXO+KaBG7?m1*mv1%(>Z+zm$lHhqV zCuCT>wqNpeUKvYhj<}8U2bXJY4DWK4+Z`_EIp`#k*h^FO2UV&&=q&8bA1o{{D6&5? zx(AH!hZrf7A!Eh3Ssgkx&1n4< z#1>s%`*13GMOA-NvK9dPBRY5WS&O1QYW4w5XCUVOWIIhV@-`JJb7%T0L=2VP&j@|@6*55jYbx|z`D@zc4$S)$JTo_`n zQeZcZSLQfah{+89arx1(?K9XcGlY({vS{AADiwvv5T|IgjGF+ME(G}NXFoH7ZBv!z zF{SgQ)zhxjC5xu|>#?HyuYanGcaZIHj;yO9h;uDM>RBd)PmeQn9-*})IL-$08AHbg zZ7)BDB&>x6Gd@nW*xyk(-jrIJ1k!mJ1+|Z^I21J0=B}XoxXULDnS0#aT$h4T-wljf z8?6VC)J$$YaH;D#YJ~9JtRxV;tU#5EvL~`o8eomV-c^d;mAVpb=oGywg3VkNaNX#_ zVVycv61Cwh927W9VC7C*vG2F4rkkXtGnc9jQ?Ju6G!LC)ouEu5obL{I^lm)7FdW`E zcO28&M~5XS(V1OC_XD!r7S_gOOP{Qol8RM24pg)iKJY&>Qc0^rgM%G$526D)4WHGA z&cT}u1GX2>gR*bEsolBqeeUja|7Q7xp}+L&rLz?y@7Q7 zX2PTex1}w;S92vt+OI+zmBe|lWa>;S9Q9QTpkbHk7raE?YCcRDtLC0m%R`WJc?6dA zxK4$)nsQvj)h2txv6P}p=g}C_0oSq*IXp2YviYcqLag18IW%SeQ_ygGfY9dh5UCY{ zF(__S!euVpxMtC_yo%uYS2tpToz8}A49#1K?}?@7rA!E|otBuf(mYFe?L6JsMrENu zy&|FrG2RKmn2HRj<6oST8}^Dsq*J`yGOOvsrT9$?c{)cm*!!&5lNGsgb{lh{xMSW6 zcZ)fGCTn5cnCNDC_#{N6JINJ^ zCrpyq^x)K#xA(>#y}Tm~Ul++8z8nnezr=o$F<~u3Pn?udYo*)Ei{_f%^u!5VTgPpu z@Is*YX{pP*4bQ-BTxnlvc}1dCJUXytEz<}4f-fEtJs4KeRwGnGtgwP9nH7XuxJoF|FVe*gz3gR>%{%+ zpc!`f5ZeW8T0hB#ch!T25Od)w=*$S=4(5AgPhs!<8G>m!fW>wwU3L~SH-EYu`^sk+ zQO=8@)|xS5s%^-kv017hq}%G6sc=gZpg#$2*3jypYkgzxB{3YF_qXy zSU_r~bDvEWdU8i3s~glYxz?ijy3Xi($HnAh_LO6;jFto1RtTFF){fl!-AvEQ~(#;wGGYlB;*%2%j-r56HfH&+bCx)NB=q&qLCW2o3|X5?W;N4SdKY_xAeTC@@Bw7J6Dsr zXA<3de94;pkp6Oyutg>7uW^Ff1*IM0V{BGx>-IEdSQ-(zhYU{bQz){byJowK%l7F_ zA{?1E4)Co1Zy4qOD!J^BA!wSTA$ZLW3HG!as}p?hnJ!!~iDwMk-QGWPjcME!Nvu7nkNZt&^o_fBg&yO4zE(2_R! zIMqm2j|?BGncGk$PQ1OWCF1uu`;fYWhnh*uLcJ)Q22nN%MS`@(I8d@or~rTI!g*}U zp3G-STJy!#>5C?|b>&IQECtl@o^akN*S;Qo zX(nPK_BlU`*KsE{Bwn zb!?{Ql`Sz{1wtH)xqdViZdq>9wEm{$ula?~<+ax1Co{+Gqld&wo}L?_vV@K^m^HF2 zRYRk0H)M-~JEs(sGl`oWiGE(ji1U%c8eu$+y)+@8hn!C}vd8VI;7-p6u}e-SE&|E8 zrEdqQQ2m)eZH9fEc<)s@7gEPY4}QnF6(kfYva-B9sCFmUy3?V1VDv!SDS}v+#OFN9 zfT(@*lQG2-zgBGMS!w)A|qog-lc0&NaN8U&7PHQWoi z+In!_k4^M(J`@alV*5A#tE@`#6bA%St@0o%TDouzENP)gihI?^eI+)?Sdh@#s>6?-3`x9f`Jsu zX&diBeBnlzXu3#eyp!E?VZlajKN_;LvZ*o2LXT-%Tg=3>V!2i0qOE;P6RSQ4qsf3) zxAQm-wJmr_StNHFd{Yjch=!}#cv@v5HUYOM-WJ({GGf@+W`hDeS-wT1#9hy#bQyr) zNlD&vqr@h=PPAshC&rI4>M1$bP5*@0BM5+N{tMw?iTm`vh5?M3qgmaw>|Zr6OU=PYbq5+xWyL;j%c*kSS-6uaCn(tV{D#uTm9lX)^yud{@Gonw;X9nbr~>13B&v@9mem zGwJt(Er1|Ye%mS8pr0MQPr8nr!MO^6 z!|tlIk*yW4U@gi!LZX@m!q4|Q!`jRY=3e|CL6@V4womYfGM_vro-a3f8kLS&)>l)& zKY^^EkK$4rk)5}qXf2JdK%J?O%hvGXjV`cC$R&Vztfc&^lYD)WYGSygwDf4-*Mc)q ziDM00y|~Am@_#|mO?G!75xF>8N1Y)&PDwuZ{qm$NmW5@7^;{ccpllEbX&2mZiVG2Y z8Ko=nI|?;iC{Z&cI2MwQ^(bgN&dp2KTnSOl5(m1nSelwHcboSSh_#f`Z)${vD~kY> zf8{1{eGzu%)M!Q01Nst^TQ5gTI_(S1uSuqo?v#5&m*VNnZ$C;l@keS6G=Wd0PI6NP zA~YXtCr&aCM)1t1TMXrFP?n;<8Dyyr*C4Ly4NF2uXxmBCMe9fCxc{VtjgS-|efh*t zNwDlH1gWpD9}^q9vurcI#co~(7)ndC6h~Pb@28o&Z}Yen`!|>TJB=fN40r9=xYPSI z(LDcvxkO=11W5d}o{3c@QHsdhJ-JxN;RSeqt^dKj>Q;YI8t$UNC>(|MY>ls9()`NW z0qRjm*ly#drdfHNUV|rQ0a)vt+j0};x@-dRYdjM5Ao$2s?oFjW)2ct^?E$%i*;r4s zD#RQxF}+nH=J|8_MJ-TpuI*sv6;s}4(5ab^j{xr1=T-w8$1J<_!=wHc<|1HER=&s0 zuK795dgD(?wRDC(}R@m9uz#@>@WGf_4KPreI!Q{%qj>YcEoi_J? z7hD8{kPDRCiU|n`We5CZMI>MEsD+pu36*M=%(HyB^L|!sM6ft<3~7!0de@!eCMxtN zb^fW{hJSMx8_&&Sn|b?V9p6rwT(EG;NZ0;`BK%_^bDAIs?TGhn^zcU39Oz~MZ9Zpt z*fyHq2Hpq$pB^OuMhF7^?oiCRl-ij2A!6bDTDuy17RU0R9FrZQV*lbN{6m9WWPzaO z*E;ye%zP-M^%olr)Bed<8|%iN&8d8kX;~_$zoMzXc}R;HjC{Qc+sSsF;**lUa@TC` z)5nV}pCv*(L1u}+Xs3Vi9T-t}fX6#2q3poyw343Vr20eGGQ#uI8b}S-h~WRU%4`7v zScV;fm^r2g!9Wp<>R-zE8m}FMCU=ZSd=JdM(EsM?f9~499Y{X`OyoAFD-p1BaFi_6 zuXjt1uBXkBh5HL7=fM;2E?!tj$B!LI6K+JBNyyfft!^Mcf2Q5BZqXB39i$PPkHa34 zv&7UH9T=n`rfEY^mgltbqa;O8mmUrM1_wG%sJ+<-RX@0bwFXm+e=syMxIQ z%!o(^J0)bAEXhF*-&xrmsjWGnXTc_KuTQe?V*A8LcP=JK zSOu=1EO@)p=e>8^W)LeYj`&yC&cE%d?-nGyn`!BCRB>nLP8(w6cLUV8VNKwCtc2%@ zIsGr1R=az)b}msc(&JShzcv!EvRPq58r9XA`!ti-#y#u<^((}9c2E2^RjJooCW&Mk zxosSfo5+WyG&|^@Ij(>Q^};GQeZb!vPZGrgcJyMGEizsErU-l^6L?e zM!mKlC<6kVXZMPx^XjqWs-3fM^>R$>2q;V027)bS7!C8lyzV`h_d;wW!{VduyA&Hw zdw_~oeb@SeeKSJky1UkSGc-Mk+JVJ3x4hIwXQl!*hRSLoULLo?%dT+$GYcT6)y)y6 zW*>Kvno*RJ%q3^flQ-kEW03cMq0vMPPKmnF4AV1O-cQ3_ zt))n>NZ$9}?zS9~(M&C~;9Gb7Khhxj4sx}BBRZ)KD(kvM!fGt4tx$dKjG}+=s6aJz za}zC)Sbyy4e&!c`6jUV+ETAXoCxloYk}QvZddF<3#rsqXy1a7z?m7OnR6VwYotAbS z7QWbAjTYKQX|uASMz=CK#*5oZHg$&F<6ZigREk8;=sP1T5n#~6FcSh`SSuFk$ZD9i zr`{*yp!6-%7>%zQnR7C5zjRQ#*j3d1fN04PK5YY9FFHo zzt8I8YVu?#KI&kTL6K52Fld)nErnJ5qqK3{UIu+8bDHYH_sx~8;>-(6RW`ls`Y{0# zTY}c16J@aX!#W?!g3nRA?5?Y+~ z=rw#Yz70jFMNxo0DzXzB0e6ibAAK7hs>NzIk~P1VwsI-qQwj!AO?H^^_p2}HB1E_W z;Q?}yh^xP2#ZC( zSc)uNI7A=jZpR&B5ONdn+=$Q(w{rfm)S?~~C)sOJ^Dr=uf zl~3DzY<}_T154+FA+sbT50&|oDG+v^)?3yhj3SN~X%zKN!L@dx_}ZV=H|@yQ{8WBc zG520&CD+JGRM*c3m|i4E(4Ovs8hARb{5K{O06k1D5pjQ5bmV66SrvMHxCG)&wL7)O zI*Qk(+XxHG%M`cR!&nusvbZz-vDBQnGkf9KYs_1PAKKn+RUDEP(g(b^{n1~w=w|n4 zsox`2;Wsx(e@fikZ>L&-PIw&}8#`!GY{DPHTG*(T@~eRc3F7_KgCwU2#l6}i3N8G% zZ&};@4xCuc6-&0wH&2b> zasKAjVF?N#<;hF*y90?KFA^(P2eiv#|W~(bhD=;Y0f0-#PuZQqjv!wVqQ752W@;! zh;2&)`r)%n*AxMhvqS|F;i~5~xkrTk{5@~o`A^r$Zc#t}U@-9J+;F*ZUbMnp>6q&==E6y8hmJJh6$wXBND* z%}9hws8Nm76QJkL+%RPv)Cuio&Q#>eQl2CT1D(+u_TTa`9D38*r9)zxUgc(;HgB1z zKPC2Zf13K@<%N^j=0)gW@}59_|9J>vc%bU)%WCJryj)k;pB@5HNl=F4ZkWz zb2uZ?&FnWW6>tHt3K=BY`9qoZamdArC)(_5p6Odx{OwOtavG_6$g7~dT&3XIExZx$jb zI2d{siOD4CP;jgMvft7n9?vqMi;V!ZnMyuKqd_q7-b``yaY{g6+Q{#D4t@yn)l1I@{)FaZe5zSuL2 zlTR!DTSIJ1QyE8O(#!EbAA=>I%S)cQC?5%)WiPR8us%)XTBFLAd#eXG5!U5YwDODR z8Ylk+RFf0dEC(NGlf}EpJ%f;p1tr^Td<{d%!rq({TiRA(K?8%piqkUXR)joIR z^?@$ESecF{gC|BnK{c}CeqytZ(9qyTZ`eAIdS4xlaQUqbb5|a6n zoP2J&2g8wQ!6`+t%G^-VuUsve$8e|XvQ8H)e=(Yduzsm?$)f>QIP>tj>YDPAnU3MZ zy{NSQSESgcZCvb04umH1)+?h*jm1WDo)prmFw7e-VuuENpn;SDUX7~CMP0Y)Wro6r zo}2Wd?jb~Y^b!Ys`=jLDLm1A_4ciY%1K2fAoP00a*h>z11Aa#b!>eT8BG31V>u+~& z5eQ#*38UpZ%XeWWqjaSPstQ5pFu5O*@mzGj?^MZ(ac3_K&>QFi@6tBu^D;)L?D9vF zO-43#miV{8|2s!Kd2c$+>C1n)fX>y z{F0`}(3gFZo}4)B5Lw!Y#6^hyc3|-2>uEB$7T@quQ!X&eFrP%lGJkq*(RaN*0l=ep zYrT$sc1q(mgp`B%R3J%d?J!AY36r$C5q#L`+^+MAHf#U$QwPf6PlzdKyr z&0LrI_t>&)es>b9*QD`E3E#v59S0(;s4?}|x+Qj(V^sacl3JOz-&du9vf4WBycek_ z%hh^t&XtIh_9_V29g!9=PTdn;Kd4TOuEk}AWPq1*>kDrR(Pk7c5-fb}4I1SxdZ_fwv4JnKU zgT=?PDn|QVb0e@sSm)eV4wS>gI=)yy--wvS}1*MqK-4~7So5THDd+3j*kgGPlb zK@k62rLpZis*~}~Ewblr4L<7~XB+^*;#Kd&;lJ=*RzrhkLkA zps&!G*`7PhmPK3<%L1umaF+T|P}WpPjs*HAH^ShE-B&!emUterBr_ObVo^W;K&gDOgQ5#a{rL8ap5Ej ziPMF2TP=PkpPvkxF8kg}HS+D%u>J=q>*lhknV0UXbGhmYLrq+rMtVNVQ47@q`N~J2Fam$4?fTH`(7Uz z-+#RDf_={3tM*#=y7xXWB5&+{kagzN;IPiYpOCXSm5(xR+$1HkEsFy#CBagQi&-Bj zbI5W_*8JL4*{k3|8l<6ZZTQ}rzbuIPeF>$2zQES8g4eRHw~# zh!r|?e?dU___u+D2Ba#1N$qOebX+wI>}OALin+ad{VaKER-dw-LVY9VN+EUkl~QU~ zWp9Ro-}C37K#g^v;_m)5?$HC2y*;%ZC|&DQHyoFLQL1P{xy|wTZJwtDd3MXjlK%Ng zAw|xm>y;)YQ>XL|@A&tSh2k}`nls^t{?#EXFgsgw(_vlgbhj7R7J^6c;@`jde-P*Y zJ%q#VI_FaQcDI8DkCNdttYrPy*}IDx)s86GizhG15tRPFw+`wrsK<(Z&GF$@#6JpA zaYpO&TW#R_6u7{#quZC4Tt$ty9 zTvV8r=D1qri{&&lmLeRtX{miXPZnKbV|})J6KqZlSA4u2PBsaGnk{zpP=WoKj?wtm?#*9{IYQ}nGHa;hESiX zm{9O`Yp9uC@=5fA+rt3l?s&sM(9o4I0;cggS%CgeUHnP`!O=^;);zI!ML{Otua)rr zTWMu-c2VJ)Wn)mc7szs0_)$mQAzSR5(kI<4hS>ZQbObP%0+PLR%YoND^me;{u-!&~ z`)@^U!DoEWIDNj>{`zcl_uy&GSHC)Y!cKi}Qb2az3}%5#_%(++6*Y9UKr3SBWLz}4 zcs%+>9>#HqCehj<+R>fTB6u`9RGyWEIj38L5lq7RRg!o|>HfR^FYd*qQV%tG@^jJh z-{F7w58cH$q~9y!42$J(h}xBsFW*@}J)R*~21sU4O?L_Pb;XFMx-%ynQuKoR^$gcV z83|hw{?s<8`HmFZY#8%4t~-6N^TL|og)m9+_t$MePqL+?tO?boulc}(pWcV?qc6h| zw(84_<>7<34$jtOy1(W%9*3Vs=Zc~8c^L0)(LN3j$+fvcgAo)Q&CI2X>K0dI?DF-| z*miRDs>Qjc)-P<1H@uxIOieVuy-f=E&c)Yn%IB;OtCx87hiN!XJd>twdUtH^MZhk3 z&3*o~We#Fgr=T#3`u8#~w}2yWYUL#|PMNa4w+BcVcaP(*|8)+xEPrtZziP zghn)z3OkX9agRm3u|(dW9OhJ_jG)8c^C!YT!RQ*sc9=TycuQE@1~=~xQ?a_twrZKs z!pcP*aqo^Lx6flNlk4}--zz^gR`_D3^Kc_U$SA6iAAAmcM&U$8v!>Tm^^l8*1SV-^ zTfU^YdZHg09i3it@K_(+|FL2Fadl?yPMUZZ@UeIAe5JcmCl6wh*32p#v!>lb{I9`e z){Mx{bsUp!9YWslq!~IJ8I%w0@cJ)bRC|^HJ~-}0s6+Mh_sB2szU&kCBRj72l!{#* zFCkGZN!=cUTQyxol^jg?R5#eu*ta&`4Cfm1sRemb`n4-=0NXf_!r;Xx@jM%jji|EW z#@yWx8Qb5}ox=Cu%x(15=aYn=)tvE+a}A1|X;lva3}TVXrb}jq?e*gEk;CETbiqFG z#MGwrD-=mcL;I36%``0d2>IQR+)NWR(FST+?V40U9uOpF;xPEc?$AxeCGYX6{?^lu zg`H>LcqT@ahP^_&l&4)-97i;1`bq|7b5RG)*mV|pCq+6k$&xh-nH_urIhp$04Jj0a zUr;o0-j#eGxSF~n<5An-HSgL&YfXuo?t3t2rqJzsb%es9TI@i|)vhy-wMN6OdqJgd z=sguS6h4K_rz(AO8^TaWT75@<7`FB!J6k;C3olhi7mK?QpJ@-PxRwz`epn>6 zvDpf$#X@Vd>b+YEv1-~2TVwySeaV-#K|`}VZnavo_v_I+sZ8<;X~xJM)vWTxl}7M? zW(mEB)Hg5cl6Y4JszsOasgbv$rI8x=#56pjnf8di)r;2gZke~os8@(`q_^Y4H$8id z0MEJW8D|u&0Z*~#5^Zji2g<001udTtg*;J!)6W(27vMphu)EVsT4HX)*W6~>?w^e^ z;%3BTNudl5eU3ElAnViZ zESc6N`e{!jxB(#1hZo;++oB{5@Vi5+Mhscb)O<}?#h#ZHyvL7y-Ed4;pyT9r$gI6q zjFK6czxVY2cH;kfroLim)9;TC(wWYG4rtOnbiXu>dP7?}Ne=V_pkWc8pw9?lKkCsF zx-*YRJeju5XsZG#&IdEje{POLzgEtjj|P6MSQg!Kpj-SGQBGYIdBEscIZ8So`l2oh zy)HY`u6c6V!0@{dAsL&~^LF)8KCeEs*ht>mn(;4G?~Ct!)=)3CT51T=ub7xvw}Aa{ zVad7sEbudi3Mnp{h~G;cxBtVDW2Qq)nu=N>DCkloa28R|Y~(@9(7Ex@i@0EPTrKC0 zHya#Pj7?gy@W-TZhwPe+zj7atnC7SWq-J7f$>G4PiZn)&X6`H22? zt&NuJZAxQihAFdrS(XL`y?va%&Y&)5-;gd+;7>Z&bv?oG40->E9iN{p!Jai`4h`$! z$M3A$>ouRXw}wAt=hG=<>1!=2r%kl|`obaH<`CRb92oVoZa>{kd+TdEp20A-+u=MP zdg6P#bqQl7$mdtC+Sr}Y&ax=pN(;kXy%|h!?q|6C)9|-W;S^nQ{{vBf8;Da=Tbjl- zZe;U1eZ{bG`l5IkPN~Zqj9M4@GG-2?q?$JgJ{~bgwHso7E-$=KGeBGPN{3;}QAx3i z8N{NPc}u58C6n#!r~vfy2hF;1QR^n7b`M_D(d^5gJ5RmQ%q?#0RNH88s!Awd;xT{z zYOy7hv|6OCD=74A9U2lYx&Z?3TM|!6_ zTYNOwtCp<;qrqZReAmW_-JV}zWd)Suh4)BWv`x9c+%8QWdfu`eE-E1-Bcyzlty(z~ zGF^e&Aa6bg@RCUiHqjdw#}*fB=?~t6BwwuH)^D_yo>JZ)cZ;z(IrojOp7RRlUoyOX zO=iArNa3GkDR5a!<^Wz+owQq3d*sK+@W5YX(qMOV!1J3B{<6J+f6OeY!OE73b0G(n zk8lQg6M<2?c^jjT9(59H^ZSd9vo1&<~452d=^8#{qS(Gh|HOv^zYa z$7wPPF&x^=Ii>8#ish3cQ6?o`I=+M4_d{=5jtB!KDQmuF~LQz;&Q%`JF{$EKXD|I*#Rc^t&3*QQkmL1RtP@8e%AO1LauF{iaCHM zNyl+%AD7QX!sx(?x(LH;nYp^1cnrk}u*P{Fy(_k1v(O9vvyN!iB*jx`%raX=`P}y} zt5}iZv8k*|=Q7r6@(M~x=pp~ZLrlZk8ZvfaW`gs+Z+fpS?3GqHU&`6VHunb$MR%8BPN^X??lmAPWZ0+@n{5N53B#@UayB69Do*`gkuy|88b#i5|o%vZ#W zbqF=l7v3dCpwslx$SXRp7U8vVOrm>c0uYEMk?zVlP6J6+2)ki%It4Q(1S8 zO9BWvuJ&@=q-X1-gl{|kz%`LJSr@FJoGJl$$Er?08B3ok5-hGr(Qq1ep(c|GRk7?% zYtwTNH#mnb1Uqcs7Nd4!78DYEMr`4H%@BTIrmg0MYu1;DRE-xHjXUShrG$eW5kocY zFdruu&nyr!EA38H%oXuKMOuzlzB=%^Lhh^pe1*d8e9i0e49714)IAK(v-*>%haYD> z)l45Fc?$)6UO4e3)%{5MmVM>-ITF!^HnC;QW6T%6f7(Bu6Y}WMof>MHB;&h7kR(~- z#Gr9Yi!vkV@DX}efT3eg%4n6x)>Jt<_=qUIQ7IGpG+jvaHRGu5%-gl_ zJ3d4O!$rqCd@9i1H8ppb+1B*OkhWu8oiZcCjjd+7+R*d+qErf@#VGqv5@8?Nb490& zI}>a8+up-BR{L_A90zA}K92ebn$$;)CCubQ13>=^i$;uI=XbpkN*A0G0KXM_IKT zUJb2IaBYEvsdxYFO_Nxs6hFp!)<3sS(+^l{g|=?RJ#odnJ6;6nn+ea>*TuuLP;fef zOI?iPh2mIwVQyy)yR1}U^}E`gC{lF27K4MThG1j4up?dl*G_Z1KWp}&r%|p!7d08B zo(+jg#P9d(-&w*cQf1o42c~@&dn%usycS5qiB;y72;%?%vH)ZAD33M;nAJ)L`G}}{^r8R}i!N13&&&K>&8fV9XfJ-1gJq-UIVp~8$ z!$gCOul8Eksz;q}D#_V}CAc-Z^?BbxY2%{C?yKbby?RVN8hd#S;nQ@~_%nZ6J}m|1sg~lrj#o^KiRSOjo*$E$o(k1` zne~v3hA2&K|6{g1wFQ+s8FM||SLU)WuNFM-`FLord#gV5rikw%2J!Ety$-XOl^3RE z$CBZV-&2259LTENY$)d=N!=9R_2y$#)Vst7>q-6(*4gooRwOTkClB6|)z^dl%fF+x z(ROl9swBk*?h%{>-lqoeWt^mpZT~~Y`2@g~8U;$VeeYNTN5$p+NAZ^p3|@VKbFEX( zYjphje=UfA@t1jhOg|i-jQ%fi#aPeDKZ=()ygH5{iG2CZAj3`j+vEI}C|K|}4I{hL zR)${rF0Y2e%?aE{C}@wcNkZ$BkQ@$onw*?suuH(J;h=_Tjps>TehhZaSx^Evmbvp- zy4^@Svt?eFMpA>XiXr)D2D@?g$>=(`zs?A9$m0^57NkQ--qx7s_GVQwtez_yiud2G zx{+Q(#`!ei$HPlcUdCi(o!ufSNWdA0Sq(Lkk)J8;8&REua5yU(+rxyddcH~isC9WT znS>0_7JU)kdExcz;VrXs8j{+=NpHb&)k+1-1S zoP#X&@6{B~cgoIhy?^|r^uo8=P2$~;T^K!a`TWV56EQblpT92;Mu5dMImp>5*3JyKmYPA;Muf218 ztWk>7FuvlovPE9X@jB=%Xsz(MLXt~qwW6ip-A|teep%Io9Rp|2#pIUO+ec>gYHYv9 ze9x`dxH1$)iU#)oxgAvT+W$ZF*|$ZGLrFt)1xy zzlL=pijKd|`I*X5M!-bNN)eJB05lO=5rW0Ts>%O=@Y>`!w{P=bF(x5`#Gli*L1_893KyCZ`cN2;dvYh05tVayiB2dxAOSU z`21gYA7aDS%xYFFAYjjbADOQRSKOXKHNm=ll2A};wlFS)!%gLHOEkp-sA6YC$%c1- zXi&d4z##ErN3DFW0*8gy49;i-%KW(v{#Uv-y8-z#RSESJdQ|iF+}QIL5`egzPu!5g zu}-NO(P{dZnc~d@dO$x$eIk5yXYEQnIChz=ewT;5a7Gb&W)ds%C)W3$_B;S~uc*kX z*JSuJCStslyP3;1%75pVyI8sX*pz z%UkR$21Sm|KjHsH1pwd;+>TIqV#+L`+xB(@_LZmT=CqSY^+{%w>mGRn0aAAeo4V!-o$ciYBzWC1R=(|JY*q#TB z;Ky96H#5cRG0tq+xawaNa~=QHsZ*N+Hti+IKzM?fC>1mhERV9rvGp6h_qbFP1rB5S7U0RoI=D7==o=rIJ_2KSj=@W&$q$ zqH*AI6-D6g5tUNRWruUxk6)gkHt1~S>e;kU@;S#wrS7V9Zu$SY^Zw_Dv1I@aP1GzW z8il;1nKe#-`qUD`dZ#))2e?qEbMyPrUrai7129dM!}}}89dr%=GqUlE_@fz(qP234 zbx}4&-t2#E@eeC&CL%Tc>h(c|BaqJ)zWIzOH|{Y6fHqL?g=a z{nj;PMbnN~IKYwKh1RR7@N#!WDEjyT$AiDvwPHRn$%wAL1oRA0n<3CM%5L=^^fo-V z^5M7(dGZ&(^F=14PEGI?ouEU@u5T_x1L*F~xpJj6ukJbwne0~=Fp8}|p#F+A#g;&p@%R7+Z5t%_3+r_O6JZ!inh}QZUhWTSPMJ!FRvqHY8IJh zh>S^6A%&nN{L}fV)b>17qA>S1<}wQCef`&0vH=%^t?EFKy$NczpRn3Z;4jKTeO%(0 zXD-^h?EW+SKLQ~h_jRoOq4}iW^mnpPt|8HO#L26B1yr~kJ@;SyMd!Xv*KsrE(OH_W z!o1H#3SJNN{_(a4`Kty+=zg&HO%(ImF(B(S&3hMM^Yr-#VlCy0M_Yqb=`Th zvJ)Wr%j>X-054C)2xpwPfW+BaHILp$~*3uBkRBpwi7e7tbypTLD%v6hg3 z&u`TBeYii+yxP<;2c@4(y!eYR%mZn_rfX5Vdy^G2$Exx!^@xehZk_!^zj0p_Z0IPs z8G?AAq8=d3oUP$ErjW-Ru38u{!Q6KHtI2>~t|JikrzbZP%YSX>#r66^99~I7O0hh> zjWD7`ik{l$6GxlV&vb5lbNP$vFu1R~Vm!2?lm`d8&$a=aZ*Zx^c%qMpxCX!&YA(

WINtT~_(aNMut{lK&V%Qo;GWxaY$)uau6gHRCz^;FDIkW?FI zp%K2nfEs>(=Bt*LQ*$jP62^JPFKb|Z+4peZ-iCoQ3;G!m=YQ|{x-nVEJa7fsOm|{~ z*H058i@?6B6tVg{uTQyu>ckoyCYaj2A^t%;US<7I{pbPuSRGh=g)z|gQjXP5HxF_m zPF|S8x!zlX_7{tKs|S>ahg*8A{02rLTHs|==xMse#H1%tFt6B>sMWWQPw|4oT3R$y zYofNjd^9D{A6{?iu(0ut-@GTmPeHl9hHI`f^XwKSMJyE6Eox5VI#W8AO%$4Y09@5?ao|oV-uS=yIoyk15@5t1j_MjAmq=KXrblXIr z(7%ZEct)Fi%LNdV|KG)gXZn@G%MD)J@VI~@^m&Ki>q(Z9w<(>KoOEJMFL{*X}cZ`qe2M_FW zi`yv}GV&{gviCh(oU>8Tn^KVI=Iji^r)UEUd+reZ!mEMN|4z@^!^LQj#I6#iPrjoB zq2DmlfA?usB=fy<;U^-tIO7onfwpEa5v#7Nb^B(|x|FmWH`nqAZjSoQ_Nlqbw4!)0 zRAqO499)8s;!1YL+ z0Oyl05)PiG6sCMdD)MJl{Cd3lMP!#2h55Nk!%3!gZD??jo|6GvB}qSV6-`l;{#*TD z5^sGm^sR7;2C)e8sqxV0qEX+NulUf%$RR@>MfmkBgHIJ#4QKb0;TQG$2NM^|%OWt51XrA+?fw@CPyq?}dLd669?>`tSNYRm2M>SEFn;%T9 z7Pq-HxpHRmxE-@c*%Pu_7!MoNDy?u2-GrIg?a!HhvBf9^JmcPe7&@Y|ly zuQ`&8S{0k5c~)mT6FL+w3VpCG-JY&GtPR}(YQYIZci{NDgbibBkOnCEJ z7YLvcJ-k-BHdyusf=>x!w5V1sy#_pSlx;8fo{aE}URJRv*j3jZF&<^8&M#8wGO??* zE5dE&oW3W4)+qhOP@o|ibit*DC(1<_icRB;ISDc6khHKU03^z(r55h|AeEoKk6Y z9G(7gku!bsnAbfSe0Gj`D}_z_UCY*c;Q{rQ-z#F*DR-yoB!hr2coS{&eZ@=xuwb8H1fSBju``1?Mwb&?0-i0XPI=S# zfG=G|lbFiIX?*Ay1COwkzz-OeU5J^#q`VDOP$K#A90$k9Y`t3|rOJD5>gQQ45~3#{ zq3Rn0OJ#`du=y&VLZ9f+xFWUa%eZ%vJ)CHY3*p84)x^zX6RJ>xQX7)wOoh`##MrvEiN7i7Sqg}})npkNHAH5f1g zdeEV2@Ps03-)?!wk`-Bj4&g{{HQ$%T?@fthsEN>eq2qc(7jy~+uzkxpeRF8w;=KX- z8;%Od>KiGwL-_fV>_9adha-@N&Q-@?fROO%qLBpr@qHKQ`K@bPLW+k zD_A7j_(_&q(cbX!>!Huso3Y_vs3Zh^o8KdodQJ_Z&G9lzz{~pL*1gm5ED)I~o~1hy zGCZ0uOBz)|;@U|z-W27%{X`k%qqIgz@J@mj(I6UqzzEX=V|t&~H81Zg(HTB&5SXOx zQtP z9Q+y-Kz%f1X88+C!R#!PHA46!f}j8>8N`f2{a`t5i!=ykbqiS>1Fr@h(!q-1 zR{)w82nc9^4bJ&2jPz#B0?n*#>Hm%I|M)QVBQ(Q@6HH|Ow~&kI@lsZLoJ_U{$y;Bw zJ4Hf67kBK60+iYj*{K>GXpls!RmlQBmq>QYVl6%@dNz4};F2$Hy>|y0kq<$l@c>C< zKHUIOW21dWkAv=wMUT#H477-yef@&YeIMbNegbL-+@jr79*jh7h#g;iSFBB*X=YI? z6+SdY+mkW?kzv0^E?l5|Eh!&}XBiLbt5c*9TGP06Fle27ljrqepaSc0D1uRdkrD$Q zyBY}mupu&{J}FgB<0Q7>xAQmub^@$3JK;4tGCpI!YjoDnmTWA$U*AL})+j%{Q_MkR zocdb6%?(=ac@H4J2x=B%vU~Y;^nHS5X5UD_f02pk2P2qO|0v+3wj#*FQ~Ls3$P zwfyJ4d_v>$>qg8ONlQ0NEChA{3IeP%@c7n%FsgF3??`VykI$EbL$>vq9sch^DHMF?bb)J!Ru~~oGEt$NwA>4o{JpfV50urEJsKJ5Ak_N2<*K63Y z5wpTKcABW2!Vh+{eGpmA4^ju7CM;_pV#yL)&W)%>6GEFr+q8uke0QFNH4Qx7&>dn& zE*wyH@1AK+ihge1$W&rtYbdk*uC5fza;+fenQ+X-T;RA48vd z+fIX2iH~{IiG8M15uW<679YXfs25Q&rfKbD*YS{x*!x$J6!Kmq_Ivpiy)eOtRyXpi zAlQ`gFwg-dIozY03<6a)G-y}BgeKn4%=R3IibsDBLAF`j= zf1wp)Ol8fSosl*yq<=s);75+WjmX`CI3e_CFrj_Y^~Prro4AuhTv180T1ivYCDg)i zUN#*O!PLc{ZM*Nd$9vT6t7VfEd#l4k){5wPsJm_l!f5cqkS&g$a37c{pwWt0$2?AQ z+{hAn{@+~ytZ88WFj`wg8-yhkl(7dZTP)K5HG};_S9=pFP9WZA=SSCrkHs*f2x2M& zA1xQX_jk7#)fb|XMc@aTtOc|z$)Jp9TQ$+T~ZHt<1VH_$c2w087 ze4xy{#*9{8JkPg?L=tv}AQYQCKKd?Ztyph(9EJ%ftiQw&0ini$@K@ifs}2c;O}o9d z0$k69QO`+4!PXZPfkt+B+Q=e5T*LNgYqes@v^Lqx>FsHb;HDI6t+PCNA)}*C{sAa0 zx~C`d^K)HodRYZKN$O!Vb$h;%9efR&lJdh69|CCLsoQ`k!Z3z8+;mrQThPeLV|eT! zwTDe0Q_`%;uvnLlKDp+l;;Wp-_+OTd^uyjHXBgCYuaQ?(%AB?othOtw0_>d_t0M4U zdu2)@azBh?G_vFcZ}}VoTcdJKT&4I-;wgoE`N>JBCv+NR6=D1l{y$ zsT5E@CM{PohA)SosE8Uhl+vjhOwn!@hTgFr;F9pWP!n z4xt;ne{Df~9vCwjo1`H7o8<_Q5M!`8R9D*!4A(OyJ+PhC$d|iFa-LL}9qTgF%HdjF zJ9&z0-Ic*y>FuqxX8c9JF7_woNfVPtP_rDWVoaKNQ@PWDqm@F~>Z2@vgy{mv4GWdD zCWdn0GXJvI@azSS?E4~qrW4CxoKzf&11ae_PQS%Xc~h&Sy}dI%^O-B9;kklHfrrYe14}yFhr#0o8Hgm=(QVk05P0-OTWV4V*Bij@`5o&`(TYzCw2)QO^>8HM|kZ z8NKAkarhoe)Q>*WQcn%CTsdVvqV2x_kkhf|vsRTq*XxBm?Ju$t0ms)ZG!fW9eIv zHKMitR@H2-p^H8);nSbE|36d40H)k|q}99CBkyk(F*0JCtK(t^GCPX3`T4mBpm$iQ zYrnNKG1PTutVI1+920_3kyXRca=zD2*<@jptNYQNga4tz2ON0|MDb{;rb%wT{cesdhi`-{;ig zy{Wt3w@1D_hdG3EX;WJuKLyZq4+IXyy37tzBRiW2ap zo33kQUDUlk==LGz>$^Y}f^~4u;=jtQaUknxCIpJck2nkQLwlRDFdL*?`+Ny=ihMx| z9~Cbbzvh=v^dRg><&mI)Op|FgI20RL|IHYk#@6pJZ?`insi)`hYD7D#;-!7pho~k? zsI*vxycw$=yYoX|(D|~@zOr5#@!yxe~O!DVptZSl$=vPz1_R1H31~p5GbdX$0u6yBXrqZjGc2#`vpy=|KC>^gZ3K|}WdJ+|Ts9&q8>Lvdbb0v^Tin6DHD=u!Y zvES4bo*i;P-vrsnT3LgBscC>+O6iv0;(FW!*?37HG|4fnH$-Hp)?3reWP8XW#XB9& z$F3p%6lx5witDo-?I}eeGJ$i;bYecDqhn-X0KUgr+iA3b2U)}W5`KKp!4XbycAxA^ z|A#Q;Aq7MW20keGEH^h-HZL#Fxi@f9dj9s9DWez9 z`al$uh;HVgy8<^CgTZ=dRGwpOa^CLH%9?+rzo6>IP&#@iLDd-2BH$oo1VFX$c#|i_ zAxLZXc=<7)Ur%qkPkv@Zy>ulBZwzLF1dMFDa=CXqIQJY8S;~73^z5>gszNi1= zoPo!g0VT!wnn+RK$}>bRgOLc-Sr?juhLX1+ABomHL-Uwp^gSFaWCCa5{x8X`h!6+@ zbO4|SGIOy*FHmVDQl}nkTwHakU&^jh5C~z;&^}jP2eQ;Zy%TQ*C}W<9`f^i2EJY$>U8>Q+imArmX@XY zdX<5Ff2LnZ*JgLeIvjU46VpqwU~EKbl^h~V@Z~wSQTgB?c^u`1Rlbf6%NLqF@ByOo zhq?{z?cM3^85{2UGg+SJY;HK>EBE|o%T|_8HzUd`YQAL0;p`CGj3k1li%E?W^@yu< zX710K;4Ui-EpE9JGk7s zM5a_nZP*%IDvN4*0eq3($)43_15qiJi;ux}#daoZgHllP()#;pj?-$C)d!s7s`$~0 z@5Y6}^t?8j8(xl~L*g#tmWnlVnj0!z2sE7EIqH7uCVe$r{8>)^rB<9i zXp5#FifoZ+a39*_9a`iNMnR*9rqE_6>xI zTJSn=OggAJCC+=t36)NoZdpWuh`Nf_d6`~m3RQBRY7j4x0a5M?XplJj_Eqno!PUyZ zY69krL2S&%jmH6z+WI~vKN@F{h%0PpeHYr&)jd^&S*Mk?|z;XCABJ`O?YY1sw% zH2r3=c}OaH`mPZ&CYL`*A2y_uBQk?Ddc2?cn~YE|=`iz9gTk-VpY%E2glA(bV^%g+ z>Vvnu>;VFnf=0F76z$>m%zXJSAqtue9+;vIwb6y!O#~(j`?b~UeDP6XOT4C%Xv0u3Xkdzo6=|j6i#PZ zo#np7lDa1MIKhaTN|5(J>jRvYjIH8Y5H@P7{B3n+Gb4h95)%+27%E-ukF=(#k9eAAbpC z`}e`8H*xqhIu5T>r>JP&bI~9D*3PX(n4D*#nRK-1+J4(3;orKG^6RHJG9o;4`o>z| zIgdZD7_Zu1=vJMk9LxB94zCa_>3RtM*c&eLUJfOPwR=zmKAkZ=9pFHo8`Of(tr+3h3^hE{Np1r4d2*EaTKt#i z1&I?ET2qT-htKp5#ecxlaC!3u?fZJ5n!7wPirZ#?eM&q979++Sh{2DXgoMFiI&x1YiI4b$kEig zhl|9v^E7-TFHQpm>jmc->d}PwEl3sVudAm{^FzO}3!#pQ z6rk)JOBbc1Pn(mO1B?7uRa48y0e2DWlr$dhHBMPQ*cym&3@@i@$%Dhrs}K z+kAg)8O6I^@i+kHk41jv|QAczaa)nH1S zwcoO}DD1nDvziC=_T2~R5JHpJ$Z$^9EAz6&>5GGJABuX_`0aAy<6!Eet?v3Foq7h@ zjjUevd!mLpYI)*Uv~N$Xmmgfm2;Zx$JUyUgC6}z@<25|=wNiruk!NvZa?|2eRjzzm zAQLF?*pAeHCHEu>otRylb+~Yfi<@~>fWnZQ7uy`%`MU=TDiFb&g+%OljFDyXeUehL)`t<-!>(cLY;cJ^GuD+GDyRRaEmEYpH@Ge{kl_pOms+< zIJo#u(jSA`;S^QS$<-WM@_Ic~(O{bZXwT6(iFut0VY;@#8~-dE%nG#HbPvz@5<%aq%7G5rF? z`HIO^JD7cAIlSX?SO9R_hLIV<;oQ#7N<2M3pflx#Kg2GGz}TpP-41xxGEba>F| z<(O#gMu3xZP(kvUu?G5er3lK!bZQ#XoYTt&dV{B941TaQUeSu{`TkM(VLXM*Bg2m$ znPuv@Sd1RjP8zB7Z8ZxRYdP>54yP0QSz^CPCVFrO z0_6H3H6XxZO&kiHL#E-chR%H+vIa+^@$1QWv{y8oX6;jz2WeB>oYkpi1rxN+;+_}> z@$}bv2qjuOqg60!w923N2=8hFQin(6?SD=d$TD(?I6QkvH2b!b;$g5X_rux5Vnv2< z_Q#J49S{8}vY@Q2RlNdtm03SVMrI}^K3HB^NrqDE;Db^}P|vTpYJrk5>ny8BOuuaM ze$f#N!jzF;t(gOxn3$N6hv%>@_@NX1xBD<30ft$}BhHifUINh3hc)r$V69r%JiBDbK!F45_a##&F7PaQ5C zcjiK7h5Z5oA>r<3@pHTtU&S~m403W9I4xAiq&Z?%q&sf^bK4SLq|C22s6Bg z48YE6oX!&*ZEew>pk}lo;#Rm)+@s@em3NnlZKOO{n}04)3)VfBp&`x4GdnuEjLT>< zh&7_WF7lJ(x5YXzA?bjB_eM|5Q!dX@B;Xo)uW@b~O^`H%8pYKLzE3!^S%o%fpzwp3 zyx`dFifo&0RcQ9aeziRhT)mbyU}qN6t<@r9l3Ste?gA7Q01g}^$NjsH>a#i$D8}G~ zTfLjnfMB1p!9t~|VQ#U~p4D`wXpn6`tokBF9<8inI0otZQNZ-8eZWKE%{5N|g8@?G z_!}g{rG#%RUF`>G3|TQFk~;JCBRe(unC#0w@8|>7CHq8N$@&EW_shll4RzwVA#-eX z&m^t~*Veee_)i~p3Fw9FuMKJW!4?EQRFK`2gry!J3Ame*Tb1fi{#4nk^v?{eIDYNxqHCT#O8i?CUx5yEhi%b0;cm8MB1%j?B8Td$XK(czZ;Kgv@5$8kjYuZMSnf8z*D}lA z=GJ4mG3HToZ+)5}Nsvxknlk_Gv~8ME0V~>lG_P#V&5t zK9DtH%VBKze)+1+HeLIhx|tS*lbGoB;e?6pmOF14kTLw;`#vMPhB%%1JSx@=D2*-> z>8=D2;hd0f*DIn-nbgtC`-CID1I!Q-P4s2*KG|M+R%NU3=@3y#vD;Vc?;Ck{ZW=C# z8Wl~s?=xpzOg9JieZ6oidkmWXN7vDY2X&eP+w5{+u+g(*Hf7l=R7Mq;*Nwdrgn`Lz z9dP!2${^d8d~|&$Ff4CI4y)2;OxZs?w4h%9i(E>iEaNT%V~-W^RO+v&&p!ih`XfuH zq>4C!#mG&JYlik!&^iF1|M_S$hHf=3FVOmkIE(iyETO%SnHDJRpk8&u?r zG~Bp=P%P1aUiL+IcTJ?&H0BIpRxvG5ib3L-ZUP_>USbT+-HC!I#~3XhVGV`Roqh;K z0Z-$G;_24Nq~pA=sNq#&8OlsT(fW(C+hOC^=9#WU&Dh#MPaMq~x}WP7!8Zw2@j>w9 zpzB5n`$P^r@&4wet^O>5(!IoNWh+{ztHdrhk?(o~B3T*?hm^wF%twXV=1;%tLG4?j zKZsmjYc)t4!WNf(`?4g9uUtg1;sl`3N@f@1nkTbalP@{M)_bk)8yW>3mpTS4Dg2$k<{D|ZD?yc)AL5-n*lTw3JnWyqd-yrJ@ zb0!0Z4hp;>3~HL|0=kVDUvn(30F<3f zK+W0NNaM6Bg8-@bs{`f*K0hHdIt&=DzmMWPM%!I!Yah4R*Ob0Nm*-D*V$$uLE0Lqy zWOC4i#K2HsAqlJ+FPEU^RajQo1x``j_v;RQ64mV+OQy!_y-vS+v0xKJq_F&s2!y!l5h=~jFi*+-{Wg)HgF0yGRl{bt4rTq1Ag-u*lu0wik~ZX?}pd& zrU{8*-Ha^MhG=iGr?c8FBQz^qyi@1oN-t-0&HC~&7qcW*#dxL?4~L>w@&(MV4HyLC zWR-m9ZIgFyW3@j+dE)5@J7+`S1`b4k8^B$8d2Bv-W}zP}fc-s0b9|m&U5KAAP}uNr z2^q=vY4;v0W?8H}F8Sq4`CyvyL8v7cFysCWWbKf&H23Jn^8}S0ud%6(z9pm^J`1Rr zmAyarWF^qWu786yge(22*nTW(N@g|MlVpB=Cf|u*_Oe8|6iF!+K6S0jd-e>P0g57g=&FL@nEvcU}=0r z=-g(%WNY~;Q`N4k>UHWA`});R zkVdhxNY1aCEwbTV3cAMhgPjSNH_8dnF(&ux05H^?JuWTyt+9G`wJx&gx%Z>%s$W8e z`bBY`(%jzKk5}QGItFFW0cNaVhNQv``=Oq^o_#o_n6_7fn@$Do&@~A$H4F7O)sO{X zCuW2KDBL_L3vd7ptZ<@^d(+|jA3*5Y7&X%O23Lf`z`r#W&uj_}`H*^HS$fd>NX4;W z$@`|J3@lLhii7&E&u?2F=-MY-JK_ydCy?o|B5y#lZl_ zYh^Idv0<{dZOVLmz6Fv0s%IGcOujgNr(fd`62>HFH2vlGJN!O_JXIK+OqK>GpH)rL z$*+dl4bQ1d=H014fNuw3K%~)N?;B(+)pvr{*psfUl182S$xQ!8fSg$WJ;TL9E-8=z zD)SRG#CT)f7*-9IjQ8y^3`~jC_n>Tr&4jN zH2bV_NoP3--~?_->@!71I_StfXB?zRqQGBTTVEMd*uRec+b7?-jtV2hsqk;8oF%!(1Y0j?7jxf}=tRuQC&0&EZlsS;Do1u<@S7ttw*=-e@Lh>3=CuMd~&T*kSxZ%fV5&-C7jD71wNk*^%xdO z|Eu+ZV8*H{Q2>CU!mAH#0}WvOxfDDMD@ZB|d=W=%?3*4Mq}ObvXEf1jJ*pG)vP9QN z&|H5WN&=u=+FOPH?$Q9ocQVOp?dy&%)Vsi-b*%2E2z|pRv(50BD535?H>ZO;U*R7g z@LyAa|5%SL+wY>D)Q6Y0|M;(XSDY3dsn{NP8)v%xcR~%yy^}c6*}uGB^g(YY=Dhz+ zblmPbZeG^TEtIu;~uQddiwr=$AK>rj-^`m#1HI==1m{AaKk-pXZL0Rs{&S5j9-Eok z6JR>z>&3-(q5s(-i(Op{A9@;twosYv8&)zQ??5a#P zC^5}elH&Z-Px3DumCw+#vp!cbPu;58ZuLLk3Uu85mdhTMvpMd{Wvx~7|DRk&`-b)) zcaK^tG@_)SGMRaMQr((fE~>^o1eKu=b8kw7dH%>;JH}{L^1swIl@`bcw9|b*WVoHP zGR`V7mrCMwhcr8Y-UPx#n!iFh%I8^}U)4EV)QNix@exYt6k>(a*C!d*$+) z;vBHd6*&&m{Lvn3UdW2(G9&iv1&AM4^aB1R+7+|)67&Q>L*;gj8Z zdj8#QFvu>lFAdM{mvUN&DGuu{5b4-fH zLOy;}TY78HTmo6YY{Sjr3XKJh_lf7iVt@L4~5p%|nDh54w zl*>f^ampv`WZBYJEUkSMu$0nL_J-a3 zhn=jY(Eh9O^3al$8oCZmzI*TZ!|9hsms7PGWTS+AH6I~nA5!Rqz=t(GY}SWa;wy1h zkOxwae>F@WJNEM`PNb)fIM%Uy*aTU2ns2XzlQc{y?kqA)gs*V&YfH70$G=Y7et zmXw_*e>2x&oNmf{HeR)D@>@85ZWo=R_2b8qeUVGtN{q`YTlPNLPkJ`uQiu*{n|sxM zS`ySZ$G0;?{-3UTAcWjLx?oz0nqFVHq8WHyv_Lz;J1_p5sa%PEniGyI)5^GDp)5_@ zI$Qi)R0>ojYVz>HGMDOjMX8d;u|YYvyOuy5=2a}vzpobXjU146#XW}IJa*M;xt!HC zK(Ty5u-h{_?bOh5v>bHY0%+Cv7fRc?X~&f`lvZGLQ49sz3=-EL+_i3LRHM;$9{>8G zd)cu8MJjmj@+E#jVh!SN_1k{5b_DHs-VDJXj-H&o__R{OGHUowvv z-C^#+f3E^uRF-O|i7wnDsT-a)KNhN8q%bit!NtofMVJb}XqoT;Z{b}Q?9A{@kd;f} z=~&2#47`pm(SORpcPe_?)sV=aKsS9-V&{99sT^3XX4Ao?4p_tR+$R6Diq^H+YJ2N) z_X-7YnZhEvrmAXeKYw^*wIeMQQeeLZfbnwgXQM8MHzq%xN={29A1;5XQb(=cv*jm@ zEYWP_$Z(JTT*{$>N^0K{djsyfYPT4o{y=lc-1%9@n#JImqZTc*>2>8+ zIU70E%Xzo5#jUdMl7K_e-bKp@-W`+e1bRM!0vAz8E_|ynxm})*02}Opzn7jM5*LVpK#RB!x0B0B0cqGeQ^hrj zM<#PKOmTuLVnA`Re>2?IVJ=lO9~E_6-*mV75}!7(nVsD!)@0k z(wFx4ug5EZZ8%+^miDv%lheVO4pvQB3%`Dk;YNRzt(QyDLVpy~fW(!7wH-lDAF!_8 z7V)~p?)%Dc7-1$+-f-T@@T*&mYm2&&SV~Lj zkeYJBY2hOd`)4=cYG;TO{4kv*!?v0N%k`BRE-o&0D=Vv~P|ILDzz@kro`EAh<0(QC z_Q+F{pyM0#sbSUzKu1k_tT}(zNN6wxr2puxQ#NhWALK%`pFfF|=84Nd-`+%gXDix( z-Ng`VL@6{y^Oj?99txftt2SR4P=GDMztAo6^Dfsb#yLrkb;GHpW88B;=e|Ski zR~vsma!!4S$C7zdF8d6}AjA89>e4=71USG=ie^>XNl=Aw$qy&< z>Cw)LvXUo8so|zO?KV?n8v{>0S7?u@%F-Y@Ve_O zG8#{3%Mf-EdCef;l+6_vn$FM3efyU;ujs{yeSwh`FB&+^nU5*$WqMRC&|ounDSUeH zXYRQ2ik2(Fv4z|3)~^#fRchR@4fCB6}ok<{vXvlE$fezC;KNDKzWnda-Gke&iV_ko zC#R(NI;;WmCXMCoiVu-Yk42LMJ(c6Ky>TF8spWfiB)`)|q6hsFcEiyz7n``%vhmIq zcEkC`wX^}17V$+o6-QRHy1ZkU0i*jaSO1NCYWvo|#$+>MZG$;z^!t_aXnU^tOo|NbKDgQu$Cf7}D`mRk`(U%h+Tf=jwu zxR=*m+bJ2&OjZqCXTyYaDQE5gHh*OI24t@u`{{JEYwBRe=(5jpP&bQTxf?-I&zvCK z)fqwXLL+45yIw|+SkH-L6*(IZWOQ}V2 ztjQy8w9~avn@_BB#!8M1J5~F?z!rD~P&T*PS`h2YXjuKcvMH!|PBEEXQKAsZJN2lu z!0CEP$AcMWLR(6=@=i$x2yA!ME8pGddK$g`bha+FC39_?Fgt&owcgd#wZGh~nk3i) zSXlk{#E1JWOz2_!!@jjFZDh8w1{GI&Y%b|5>xGNuH=iHlSFyP)QoYL&67=FdMdg5z zZZ7E!eEB(kT~E>(y?Sr1@aUzAXWj4EJpvZM$IJH_$}#gD0V!XC0lUgp>}qLcb9VI? z;jl=M4d>AygGi`dZKEpAJ)gZcm9<4lt23H_mN2}xh`y`-ZhzgcZIa^siNTG*)g%!b zVuPi%yTM0RaQpN(rReaKg|Xx`8|jxqy8-@UCM8hyiirbs4e`yu-0>z2L!bblRhpY* zxE3)>hNe6T29hVYkCu}AOs?w~Ai4#p%peY4cT^eGhzU5;1aLk17?e|(1gOb*Z~ znX6ElCq13;)7r+%>0Q6V1{#UOk0@mS4!d+C6g;EtNX+l;?MgT-<9rw_hvpj-#J&w7C5XaeIc7x~$f zp*_&Ql&4*b#OMEf`sL8RKmrgq+4zUR07?p^JMm03DmmvjMU&M#c6C+Ip_(qtwh!Ck zqoE9NcWv0T8KkpfpueJpJ1$_coVUiue7~%x##=cm(0#_^*KX2sP-scW4b>85QWRw} z^s%0eCIMxp>itqFZvLX(pN}fFysCfJq5S&^errzfV`p~itjA{g4R$JVfW)70c(mn) zLp=@e`~HO<%~sv@^`~)$#a%u95Sw}3g;38|I=Iuq!wyIOeYlHBl#vWYwT&$F9b2ED z|MG*o2wA+5hBn{xnVwetS?QiGg$n#^^;tdKrVJqF*D~kxIb8VnTL2N4eMkO2k?%Ia zHE+quLY3mf;%4EZ1+G3~&%gGY(aR#!BTWeC*jr&tG}V(wP^fLRNzqORi0`o4Wb24_A$t z-)7!5T3 zeXKSeN{6$GNzwSz@FAo-fRd%?Rx{#zMed`5*3@q8UN@=Ep4y7l z^i4>QJI%;Q;(n>b=9=s<+LtK#tLp@=MnCP4oOaO9poAb84hXu0CG~8>IV;GrXTdvB zSGjUh-flhcKc=x;>tZ);2v|t7>M{=fQ|#LL#4*ZUajJBaO#_U}(#LKgmRK3X6v`Kb zyPrnSlsSa5o8K|cwAeRdD?4x;fF<0G&D*^Cx9%x~Ch;v4KzKmE+wOt3P=FwP!N7DB zw?P^kJ>uAM{-xRExPC>}xiX9H2ucj$o}AXn5%+cn+3mKLQsr^{6-#{{Vwjh%; z|JVQ1hVGOafl*GOCw@x6Aq4f-oagkR*ibJ(Fyfcw+K8q{+?$~BR{b}8JmioaW;H#e z>ijzb*}w(enP<0)lA~mvr(v#ytxjy@<&+|*oqPLp`?<4UFP7)mzF7bD;l1bUzXwe9 z+YGvL{fZl>`HggaOWxmfY-ZDEG8pgZJ4C%jx*b4%mW8O090ywdj-#)T6=eTC-UWV* z4yaY+984|!1weT|vU7dDeCt`s%!S3Fq#UJMWQbvDe-FgldfuZ(@7oa%y*$7FLDjY( zVA6s&?SaAXXbw#G){+5LB`H~NwZ!2&?9)!Af!`@N-|{753tNsSeIlWOdi*|81W<-^ zL5PGulz?_=r#if_Jqg53d>Tk1+%2EP8sNct9T=yVTDaMLsr!w~QSp6Kr?g@eyl0CB z#2_b_P*Q9I`(ro*D^`F1Ou_t@!Hq-f?iJ*+I5{E|CaCMvjA%HWQ-CW(ee3^~abBPmL zbxv*n$r^BxGeEzJEu{HMRcNe~rfaRJ^mX9y{h;j(wOrd)wZWtbaR(2vU&T1!f#AW% zfT4|9$AGjEW&{wpXPMd~KpokXYO0bxDE(i7fGz$$&QoHOxIwqJYN<>i!Z>AN?KbYjn z4!`_M58R2Dx2b+Q2z9x?OU7=`^?XGc;8}yw46{70qHR42emix)5%;#D!Tp980$i-L zK{F;QfUz(2`%zAZ4;fs+sq^kGo>tyQz@YkW*oJc)zna2a;(^?8jIODT6lsHrPPUeO zLXV5yMvvRX@)@M)@sJ<;Nb&9Ck3e^!{mm1?)ih@}4y_O)=?HVx`JgQEsH{`Zzf0xB zT8ZtIce+js4EeT{HgDh1T#o>;ZO3W1i_7u-IST-X#szxbRgAJouPs!rUK5%ftUCBF z?F70l*l7n(?)2^M6(T^8|F>^<*N1dfE-qY7^7~HnQe^SnDMzH|(}9ZMR7KPdfvk|J?FqY}7YAJ}m`qID*Qa#x z-QkmQxs=^t_tcb9cT2-^(Nylv`Z1~#C$RdS#tLb+N`46Y58fJ7f78)azB`=|m z!?NxN)Fr2Re4K+3FywUT)Z=INXZpV?{f^svL@@q61ud~l{ze^@+Ip-r=i>P-9Uws zEHsoT=W`gxnf`nqX^oeL4~K`OrHYhhDko2P<-KRgW^6ugAuGRM6yI6O8+BPi-@AKvT@wUPV6)kuL%xAmp z_CUlzwsDOC-#4khxtFXE)h9l=sSm3FcV=0f83ViL6Wn)l{8?b7dMXNl_=Kw@xJ4H;Y+4gSa$%~QX_ z5zxkb`i+VRfDBsIQ_Id}fYE`nPknWqWcTghh!n>;$t|3Eg&VmUn@6{|`#WoGVF|x? zbU{ISTh@otmD6m}hq3`;{V~?_t+?p=TizkfUD1~;gfqk>vedk`iGri-Tzb~1RhaH zeW(C=O;Nh3U`^T+1re0}`dd2ft2UjHzcl~jade)DxP zeB==vxV4Us4SRM;+_a@Of1-&k*`({iqw?^^h)P@&goI+{&diZ__AE!~+K z1JYf1AWuEA@rGzN%<*5dFuHqU<<_&-Zbp8#viS`BYdC)#euxjII*e8l68Q}& zaJ$nC_K}>pviv(On|Bn^9$h}TRp zd`K0L3@A(&9x41MH?Pt+T0G=T`RUuU=V70(Y`oiB6rcf&jzPF8SWsH~QaBV5M1_2K zNx_MTFBj~@C$6PTbe9SS*|K}c#9qSv%fWz`U>%0L>w4jv-Cxgo-P&t^Q!Ahd#>50U zdm_}mzzTrZEFGg~Ov z7R)ETCnNo?_=hrHW>#W?eubH*OJt|%69d5Cz5f0whniS_uozpe7&|Zj1|Dy~NSX?MxW}9H)bW!vp04s$Yt68zuAM*IslB+|pkArudUNa0b=NhRR>Qe$&4xT07d&uAabpCd zWgn3~#?DBv9NmcjZm;q8DFH9aJiPuGioOUVR^AhPT6;S!2Yq8mr8{vVq%T#eGpT=} zV04eYAAlb`^Mw&tZwb@-oO}58MkBbf?rC_EsY9?0>W&AzPssg}3nB!Ajv&|1axa$$?hM-LG z*nJ~+@!J%hrv^7*Rv-RWT>-4`sofgKX;yDo|_v18aMTh8s&Q} zD*w6c?=ay>KmBmnYuJZ6#~5`wZDGT8Fjlj3V$^R^DL}O!WB*NN?>=R3!%Na)qsbBP zjL;h<_Y9GZ;R<0%rcIWyLa}2Hs`LZbi(~yGre@TyYTl*=(*}k9b0t}NIIAfpn>@@> zJhx6TaGI|Q_p=S(YHbc*^6U*Y_zOKp42?!fK%7I?1qokwg_QFj6`k3HfStT*!^6To z5We(jF_&)>Hg4H~It4&r2m@Qwy13>82AM^W&z1k`J+f&hR<1qc?~WQP`5QvqIMj5e z>isbVV$K`NulZjW+^0nrT6dyaH}LCnOf^`n2=y3*6jycB*`QEQHsQi{;&ros4I-9l$swdTYVjxH4xf3#JfV| zk}e}S3Rad?JGB=^cAeYk*3P2JEcm4AjZg5lHb4!f;wz+P+S2Fe)n&+6EhA+>lgd`N z78ck`Zx2x6iC?509DCN%s=bZ|SD*}*>w+Y0!i=16^Sr|=^gDH+K%L0!fQ;fIVBDkf`B zvu={7u?f5<4Bo`e(c`$rh5gxLU}a=5ufo3@=Ek=Za#UE?2VHZ@p1C@EP-1)P>|(;v z_%|Ng+5Egwinx^zxJJ`HcQ99g^)^z!e|)x=o=gxmS$g?4XE_lz1LEf zIx<~)+l7+<#XirJIlO+M2~X%mn85eU;J`+kS^N@yQI#FL~0rR-o8O&&NA<@M&VH z*Xhy_RX<$NA(Z=R8^#|C@`}gm2 z>?Ra-fcoH)s2SB>K!Zyj+<3mW*mxPZS?W=y!c4IojVfZ~T~m()a9MtO8mkgR+|h>r zj*6(hcroRgTm)z7a-II3EIj6^iA?Aqy>fZNu9g+8uK|hfYNl4^oUx znSwL2xCzf65y-F$ujViN*{&NzTz4O@U6Z^hjM3rfWaRBe zQh)jbm~c7{BbiTn?C#!L`OYyf1<0X;BEs%*4xvj5hI9`!UxzM@kRMjKWPHqKjQd}Q zX3(MVJTce_E$7SjRrRB9xs}x$5?tdmP{x&w12vBNO2@1gO);iZ_ZL)L#Qq4j(cO|K z&39v$kG?m^)zzxMOqzX4Q}5X5yOzo)H&NrdcT*!Aw6B)zh5X|ZM@UEQviXjU5ndZ& zPeTz_k@>Jqr2F83ECr`GF6&ni)SFECY2RdJ+zbTa0@B*S*?+zR;N=XA6rPGm?pozQ zomsl`#cbeou6~o?IcWD8=j-!l);@d)8}>av0~59ELZZt*$}4Htk(PDGTO@Lymt~fi zq}X=xpm=%a-A~zm+TP2EuH3(PY|I=B%F30BSS4>V%ajedRhJ5&`@80fL{HbG{t)oW zTEHZ$sHj{q_CRt#{&PFmXyDS63+fRnZ4=yJJ${(oz(jv?8F6KkU*t8EFI--*fzzdc z=EBQN&(0IBCXF)n$;$?88~oc_W;$CNe&ds0=bO^8xWEjUhrwG|liDT;zn6szoS~dG zcYCs&w>E*<8e!=dmHzUsFu4x0X8w{Duf+)M?4^7z?9r7{!!-`!JY6GkiTtPd0|Z%F z%M%Xa!SoeZw)FaFvT1b+wfqWEJmK7&gPet!XFjWDIJctnea~90#9*Pe49cmEy(Ocs zd8?g8EXTbtO3wAk_M`6E7b?`qZbQL2&zneDgr;?caZ(s|yiD4o=VQIe_)KbCm@uSg z<HRr`T1nKVkx95 z=%_c|z05J3+F8o$wZ79*dbC=3BS#;<_8?iK7^^|=IN-)ny7Uazo^w0vO*$$ZrWwh` z)5XWZD?=__Yh=F-j+n^#M!LYFDUc`GByfV zZAR#!;u2Q8vFYaKT}_WC2T=WKua-=gRVv^AHTTDrCzK1I7(T$Dt8_mHoLg9kO#wT^ zNTNK_24N^BQYn_Mdv4@nLP2-OxxzZ|)y4l|%SO zf-#-5XT>J@R)Y2u>%WKaw}B2qu0LH4!8FLI4Y1PJ=vBB-jbdExa*u6SqmTfaJHPW6 z(ihtB{vjHvt`iSi9$Bp9=%@S~U=&t?_1*OkG_Mf2ktLpG*lA@ux{2tr8Jl zFR?B0UAo@iHSU-5ANdYGO0jivP;Wd!p@Me}Hg#NQa|RZ|JtbOy^+BWHn#a8`{a}W1 z`*uawadbtvQe#R(xt7;dr}NuESDTCOQ^Nhq_P8r}Q4~^v1Las3#x}&x%~vX&;nj#S z%;5VkCqG#(tCGkWjIpsN7YED-aF0b5l5#c_82eT=+Z$ZKH*Iv!!K_9^gcHqZYo;Lo zy~BfBV#TdegPjuNez>>@#3<5p%!|}Dx|B1u#NROP2j#JFN2QpNdVk=r6VT6U6(%+{ zAyjBco?Sa23{}zdh4)*?^M|iI!?80&{mny9zN!3ns6U8fmN9Oc9;%6-xuVMmHTDQk zCmylU(MUD+NPpQtJm`At!r@(y-uZEnTWwWpFpktf==(afP{P4*DqjWZY<{7D-@r9P zznw@`>_sR!Fxd}a;e-rQwIMlb!xiNzXBfF=OZv&4adnn8cfDa zHNM8>Z?3JjN+CQZ72yDHCKdL3sZ|l_^#fd}hB4$ce*Uc}{?`P@T4SnFy3nQNA4Y~% zq=%ez|E^Sg^x^d$b&joufJ9bUxTqAr#3`#ZP^rj|3sQ3DWAoKGLfbvVIz^?{ z{vHg{CIg>+9;+;gncN)9W3w~%@S`41M_8_IHic^=v!uodjtce+HJsdIRd9W80c21E z(c|HNydxw?7DKVtlZe-H0j{?y%4dzr)+@02Xnk@!HQan7EN+?1y<*@#rRh2`FQPNp zhC0GM^5FGALeA|;9%<0z$wKU?+kXNW!xQ-IH>RI)=Jw>b5|P6Guy)NFJ>kt*P9gto z-wc1K%r*6F8MUJ}Rxf3xJrM9siwytl%l@tOrqq8O=^5R$-mqb^%%-n%hY+6|d(OPM zObZ$VjS&^xC~=xRqw^Vz2vxy1ptS=gxzqcJGIt z8`L>P*H3j16hOPa4yDN7gjvN5L~s8^_#G(xEUW2#WXXIW8r1Q;7szy2?p8Tx_+tv& z!&%mJlgoUUHu4e^eU|D)ghwD$W8CR!`ySfp_{xoE*$rAn+3TZH_|K(T!9BQbImtJ( z4OFJKt988ip#fW}Y*?6b1nto!Z%2&_^n1<#q)iP9@#%)%MD_%GFPB*9gzp<#D1I~r z=}_xBzTXo_B6?Y=$IN|=J#tjuBMS)Ugh{pBY{CnF{b~5_F_WiDhB0MMpEJu$(=y9l z%U9p(unRhvx@eV&#a%;;AQaorulfnUGQ3t`ncJDOkOrV z=`a^ok`a~yJavt8fzCOP!ITsdCG{BFsVbtV{H)nkuCb|V;50mQzuxB&fB*7cmMrFw zvtUN_)kf)6lsG+w^+y?!SFT4kXJPj08fgv=arXL_!LR(WhMs?-}?Y0RAVi)I|)_GQ{)9NotIuq zw=6 zpGSNwtytM^=%#PJj-zJxA4WRMX% zF%oub&vH zn$QQ-3N7LDHKOBe*@JUMY=71#K4d4Ug%VRi={Z__h86VfGe-LMvEqe<|31i0s)0GI9vXXOmhVw`qg{Zp@n+zBmWqnd3oM#b-?U7#Njt#r4)Mz! zAe$;eb&nkzvlk`-N7npulaiR0EE>UJ=nup*5w#oBzZH069J#d&~fqQF2%{>EE#9 zZ)mkJ-BhI>^S+2)uko8BYTb)0EJCH}BHqZTn5@*t`iob0CYyxk2OBgp@ylO}%V^O4 zy+dlY6!Pl$!2xTC*hR$5&yoi5;yHfYc^&unS^AveO^+`ZqSx4Umf-d#ZgPmEPMZ$9 zVcx#=pNTncB(9+!D$d(m-3eo>0)J6y7XU&&z3`bJh~Lxk4E9~ZHCGS|q>w_;?h~%m z69HwDVeWa*&Gs0kE=ae>hL`9^NL3nBP23sE<@OHB6=6F0V-w3ZI=km<08u#kN}G3O zxjfmEbXX@JSsK-URn*Y&+hFey$2wYgdxN!jju~~jgst{%;lDnpPWL5d{B3hJ2SnSR z$WA>b{GzZxxIl6-njh+cqHv{eb1txomr;;&L40IOK8LrBU~bdyrk7FpW}mzF_ACZ2 zIP2ksLoU~&K2Q&TzqRiyVW2wlxRePZCODZl3U^)qqFL{E1mAIiFDm^HY!TZ0AFCZ*)=0Tt80dWx4895x+W@ z@zU7#^ax`L-?`FvIkR^62bJMKA*UYlm>{hOtH_=rECtmWJ)2kNO<&q{?|?YVhKjCP8>!+@+O)XSfcZL zO%M>>0(tLJT^iTG!fm~baYxI8o>)EG)*H(S4MYDCvJ=J|j2s6o%DjbiOksYv4( zjm$NO)(}tMl2$lU&*9_+iZ!$c$n_jk5hx^J+dj1^mdkEZ8N??k#h#lw9VeSY9LW+5 zvHp)F0tAsyFY>c?!oL2db9y55F)i+h{rj+u*d8x@e?txOlrGAj&hk@+aalnYBg;xi z0EJs&X8TPKT>w6R4PRH*ondIJl=3tRms}!NFBv+H{t; zHUz9Hln(*%8f(l_7>k#-K|su`h+gkjgJp`b9fVKWOjS5LA0M4xPX_2bD>BbOY#*CJ zk{mQyTki(-1x%d?9WPwVt<>=@XuoaHHHSWM8$f*QaiYoNQi&&DSjOTDXmu}TN*-?!Jc zwrf|@0kUl+W`PA+I*&G;a}rBOzw75BAR-*PsJU}#fL#;p_2oT;zGUQoeXHBorJUlZ z0*j^F_(lH(K2nsQ7)0=IJf(qYAakr72gA$jx(CVQKwj)R=2zA@3qSh>u{u5~Xzirn z`OuwO7aNg}6~xaDTsCjE8`&rpw`dD_UJYwwu7iv|{wELuHC=iJVFPdb7|vL*iyp+l zeq0j~g!--@FdLs$VBJ&Ujs zFGT_@0uDb1RN#>2RZ@6*QG>*Twt>O?2A2xAXB0`pqgU{6DZ`5ekm+>=nXC@*&b-$6 zW^QzFP!$3xNlMVFgdBjafo`hn&8K4!TTPq6l4+A5x}#gJ&nW)IoNS7ok{aEX-r>yM zy>09V86LHN|B@snvu0W=I)p+%OQe`5?|PST93ox&M4#67gzryG2K+04Vl9Rl8A@Nk z-|=y)AwZv9+jIJIgp5?C3Do_b*$U3B*Zl?MELPGLrz7G=MDka(zk8EfU^c!j85Af= z^ClW0lczILO7LO>x6)`IYHA(%FKz%Fi~g7{il?hbn96Tk6}4`r59q{@4|8Y57BiH; z=EWmzK)oeqPPW*{FFx;iAqdC!ANXtj5ZP)Qd=kDt8E5K7mmJEU;9o9?}?jMfZzo<0QSj)AoHiAS{5`3ap4lmyt$Trv6XokC^f9 zu-HoAsJe&4d>ugt+E0T;hIo|B26=KEl068)+~XZeXm*t`f?DWG-~(Cejc~1Y@__jO zPe+>P!8p%ug4gzIoI}H<`GYQG%Pjns^87&%VdE<-TL0pI>G8!jb9lVt`+{i8w(mNA zDm^8Wi`@MRhKGpPK4CUq6=W5oJOLnCp~*=0D)7bYo`#M946)V5SmU9_=7s8(X18Cq z0rXucz&Czi7s5sPT<)-vCu4G~2>nEK4;CdpkmoRgu&X)>VRtJ`NmqFNA)q>gj?1Wf zPARXvDX92z1|Oa4Mm)a^3hv^y$PMn13<(s|kk@NXdf6kGoATyyzU033tgJizwMTd- zTk+eL?k$lhbZmYIF<92WcgT&ttFWAb|TU1BzyjpfC#LR3u(GZ&B zMwn%_kzruYcP8d6nQCv5^G+fAW6u{GAv3(dw=|9}wo5PVMeyIX@M9N100`z@DRfln z`cb3V;^#2U))yeHlQ9Nr=OUFQQ%yr^mffe9GF^9)n`@#6xeuU`o~1&pB zwb0aLfG@C<@cJUN13^Cq6j?_jWl&Dsccx|h6vb_9q;_JDaQn>#UQBw$>mxw0kmrRY zD3S(WAQQqcAor}6upRqbq-Qt{9I(dFZ&Eu6t}B6}*-@N&EkBvx@aO`#GGsmx9Iio# zH4#H&M;MAYvx^Cz_(7K48yb2BG{g|HL<J;aW|-h^_1{zRizCQ|WoqIX&0wh^*)Ln~K-_7FRIVJXQVdd%;Zb;kSnsce z3H9554@YiS>Y^@qs$`5gpVzg@FIx@4S@fJB1t$QbGrQFD+GXZfwN;mZTZc;@4^bSJ zUgYh8aMq71$((O01bSt*-H0;d+!zdV_p`(yfIl3~UG*6~@-li>U;v7Dj& z%x?$KToY?j^NlS~y^A;{EVD5-GU7Ql@}YQLM#d+aL+gx}Llgbt5_^kv0(NXr4N0*z zJA$?k6LEE|0*z}QHETE*-eP{SJGSaUnr0DwX})qzI2hsxCwLaF3PM>z;-^YAk$UGg zlfG@fP;dG`*cYQX@5RsCU|F&?@l17`;SXgot6}GZXX2IuI=&lFB0$JM-c=2P$758?;IXowht`9TX$Pb{Xxt<3} zCcwnuYxlJmp!&g?%jbGd?r%{!0H?dACZSWy(OJAS_k$i_q~B&;fIKEa-mE|}^AHlb z)u}8J50y!RmudZ&dQ{_ld>X<;Vk!D60NL5L4mt*6*v(|~%5!lC;K!+BH(SI+{F3qu z(Ye`0J%WW^r5cfX@+~sWx~(pDDP$7n7FiN^Sn`tUUd8#BfbmQc0Lx}&8w0^y7bDiZ ztiC|KOBJxhf-KSp%@*ut3B*9K*CaN8MA(IV=k!rjosyxd>TCJAPKyY@O$n=-Jy$W8WZ)Dx|Gl0T8Db`)x*bi`GpJ&Wrw})^om)6DI*=eGDU32y9C)mYI!k9J1$@=TJHsu`x9d+lmWWv6 z$?H4vX=#FTYqMdKASS)dqaT7OhkxwDPTFao7x^J+G1qPy zKOeyaA{}68)%q5TK^I(PkFrUG&t6yU9%EHNc8t-;kEl*kn-)jHYm4!i1B_(6Y@|I%Re;m z0Gxc{F_sA;{M5Ka|1r@5U=9|KPqXpTNKQ2})97(Cuf+n)%lZWhNJ$cm&@B(eBGr() z-WcCSd2CQ=>$u$(C1uU5%phAEXinzqV~2Q6uvdAN4{jSjq}peH|79#ri$qpMyo6q; zlEG7+*4gKaFJ$Qt0{%8B)Lu-)b=0m}5^cA}fpp#wf4&LVQ*keVV!g-U^0~yjR-As1 zK#NIcFZpWM<{W*<`4T&PLLeHri`UDYBmGD2#$TSOkI1z?PwHIro3n8`lucd{MS74n z7{%ljUKTd2*$cmj%YdB^@0j18fB}VDD24EQrUX$dY^3vf@|JiokVo|Mqx(DaaBCC2 zUV8ng1;=H-RdKqkHSc-rs1@VWTRRdom+^g^r4jUYxtEl*mjNQ%n-*NHHuY!UWaYjO>ltyDc)B?iE<; zOIr+E!3Y~FipfdaRE$D4e+TPmFo&y7yoB1DG;O?wjf65*p zvG9GO`Qb`6T9v*Y$y%26WoAftb=J2R@Yn`b05RrRuZwmuAH+`BB1~H%rdIotx39^3 z-o6FHg(Ej%G-f7fhxUfGM_-$pu5TV1VAdtLMS>3!Sk4kN+7dy0*fHW-6~{O9^G^Q?pFpuQccr0ub7;Tdqy*n zIHJEVq#aw)sZ8%0dhyliO)PyE33+769q~^3k@qTgEBz#{NXjRh)}MU8agSKzSIlfdbp8}VQ5+U#h*bi)SuB!|qL zMfuBtj(T^t*1Fk^yDb(SJ%U;++4JT+U(Ekg{Qs>~;?FGRS$ZHoY?;|ZU%Cy552b4o z`X@PNHQ8RR-ZsK<&G3E!jQUz-p-qQE9>&A*))#ArhEgs!h+i-!PWsrQW9b*|asxrY zx}$Fa083}y8lj|Yv>?R%s5@Au>=yfL0$X!xLGb}{6jLQ?HZI(NR;!|7?CjRvl6SB{ z#knQB;{EG$@{6o#3@1Uw?S7QIhGyJzO@ggJ-xoVHA-!;sJk1yAUD0B4G?+Rao0#sl zh4o_C_m;Y&;ZIujpPO2_Te*%zS+%-ExzlwG^vom{ zYZ7i-uxWJ0OVP0PIa z0>>#!Fnlc)guf_D5gou_O<+BJk@+ZyiiY(XK6h2MD`Ej<6J3m{B z(xs2>n3XLU8=rH2E|shP{v7ZEk8t2rOIIn-f*Hdx|BSWqj%pDs=g)PTo=KLht)|G= z;-)aaIoO-D`ygQL<+VWtVZSdy%qtxyI!pdtUQi_g=2w`}S#m>ihen$KzJ- z$7`K)o@c(!OV3ouSYAD#OkNHBq8RKHwTxX5ow+oGtfqW26BOc{op&v|T8UYCZP*>| zw7DeVBXu_R?#nEs&qn#>LVi)^;B0_%9T74&j7-?|ry$0*_611n-KSCDoqcIr?8-|4 zQjos6<|-fgL$Yh_wK>a!pml}oYmw%gg(tte_WuU%{*6lNGiTM$7`xqCyJOqOOFG-r z@anYB(Cj(#SnAu(*|hwJLLOLyZZ@ViQ@i{Z$PmNs$N7wCViq@RWVgjyBW-mMowq-; zj}tvJuXd8_C9pD)FHUyMm)zMC#aZ|O&xG(woBY~*o^E1NZvTw? zmA#)s=uDO0y&~6f*F9fcGxAfc@~0(0TZB7&-~)tQwU}1xl*c+ZnS6ZN7QDw3kxxYW zcE*IH5XZq)kxxHqcah!`Wv@n(!E0(jEyI61C;^ugv{StjS;tIsEg;R3=*hTCwiUsP z)SR_2)|H#8;2@%*vad%)p08+?5kl7>Kl4pzmKT3ZMV6O0EZ6Z)z@p0C$7%VIQ6x13 zW%P{Jf_Dyt|H?2PtV~q5VqnpZpK6Sfvpm}6)e>Wg8Os_1`^fMlj+?KTuTkHNZZel< zOm4+)A3oxA^mlDe4+OXH4k>F3&WzE;&=%LJMu{VF1wshtWJT&Z?_h|5VpE{0#WHa;mWz#KF z18`Brr6SZr)@bF8N9dSc{KFv!FhS&y)kFS|E*Kzsi5bu!#Li){S|5bDHG0PNhFS*C zA;8A_XFx$5gx(!1Kgg4iPs@E?R76?+Y{*|dv^gXoV1CF6V^{@Pn4feyA@*O|pLZP)#CWv`Wq`Cf2}~UWWQrByjgn!S0Z(Ak6k zC=bCbayIMgx?_5_V#C%$xPQzKaLqTmb#OCg>*m|LAVvjWxtCp|yv}~5W7sWbiMq?v zmuaxp_{ht9urmkHtde~O%@tXcfAXpKpy$iQ{izX7&N9rw+NpZkeM4`2omSfv4vI_h zdTMRmow}6k<2$EdNh6Phw#5I_O?Z>K*^>B%;+eUF;r(Cq6p-9>pl4?E6fS4a=)6<* zPCjW}WOXVl3)SSw-&J9;#@B$F=e5V4ZN{%0?#4AM9>DRGrtEanepKDTd8HF$*4$ob zvZa|_8)NHY#g&cR+CU4~&vy756%#59s!|l1BfVG~_EOkB2>jP20DRqDq7hvIo&67Y zx72gaAf&GER%Bi{vJ_i-k(t#nwY({EMW?V&*qv%7>0^mHaLCd}^>$68qz{UL_WY9a zmcEl!{Ga8_e)xx z%m)vaeR@(c<<_?~ka>GUH6?KLfb@M(X4~S230}gbONCF)z@i9P>Lvthdpl=G@Xc-l z)@#BAcK*7-B?id-@ZjrzJB97CfMvyq5P>Jc^?Uce1xQ;oh&h7(=MK zl(+3Nt~OAPd$>kn52*HvNYwIJsAy-|xjaOE1IETu&Tfs$BSmqlPZNm4A5|NtmaXWS z9+2wiOlCmFDxZ`5J?Zf+qvi)FFgEs6=+OgYbT)Q-N9URCpmed0*Szyph?3TW!uFPO zF!x+r+LBUI|09h6HKW|M7A0G68Mjj--qXrA^XPv2FFO9Kq967EIHI0srjx34du_bg z`g(YY^&#da%WAJOGqboO&gzEdqRSIVAhJIv#yw2yLD)RzEcMY2 zOcqlt9xrOC!)G41HJc0Y{)2C)t5>Avx#l@L7$;J%6 zT^HVyoM;xq-_ohS9*(~g(946+&ZAtQrgCp7Y-AVw85_LpVzYb7(`cGdBx2Xzn;o&guR&cK8!b>A$u^0G?M-iEOppN^ye4mgKL` zh?aRFbCvW$#`H^}v^D?%5jzX~^>vgk0UCr?OMS=wdr<@(C^gR4_rxKd)kq1>p?2ld z=`gzs$Hm!)snso#e^Q+R#6<6f!`2ebzwlJO2R}``aW7_L))lDh`yBR2e4zgGMe_~q z6bn~3x2AH}*|Fjti`gYw3c1C0ET^z-W{G(Bf$A~#P{H{|YTG-LSF)qc4y#*-b10kQ zLxktbAs3=s?&aD>1EO=E^*u??BjX1Bd5YgR8hXP&+;?NtX|DI}J0^gDwTA|Yfes(N za}ihIaqEe5pH8Q_s8DA1nFyjQ79e939f;UF((6is)Yi4U#38DU>>K^d@$vCv$^=gx zm(Qb;f`WqK6*1Tdy>S*bns&PN-O-0d@&zm@`0RIq!=C3Cep_~Paixs z_!s}3UJu58!=+RGnKr2s(g3_XDeEJNNf2Ku(s?)3O06_dwOSE2YItdIjYI?q?lE?q zt=nAg`#8#3`DybwXS-3}jbqtH`3uPNcy$JU)l?vWJTWin=j#O&x8h!6Ws^@N&tIxN z`raL5@P~Xe`0Mc$5NmNL2ku9aAuGk#NAMw;vUmDrOgw8(*yo6Umd-FpzJb=jvela( zmmhG!G++$lAbc|_x`mt|G~esI)erqmC*s7HAua@Ss*k506$7D!N}+O_8wTw8sxt#< zgu=|-7}CC2;XmhQWPZeZ>p@6W~XG3qN8c@REQ;WX&_av%s@;AYk^nnzKS9k#eRwaC^(huBi|pI`@`*h(CSIFQeTQ&Q%uP#g&YQ ze`W^18iCafz@raff%dox;HfXQ-37=6uwswRQ{oQGWZXi3`^M&`fW0zbaq<$NmvwUf z>M{DMl7Z#K5DLfJ0P%akl9ip4U(veoL5WnlxJea6kTYEc;^YA@Sib{lGqLAA=>YO< z;(uGO(gWQtz~QKgO)vvax*m&mGEjL`wCOF(I>+k4O({OvvKU-s$jTQawl_NC@)bk)N-OrQ}$Pkp|m zONNh71?VigyP6vxIo$9I&A-8Bbv5u{I(Om`O?oJca=1gfDKh=>UB1TKKAuZlPw7c} zD5Rx9kb1;U;a09~Tk>q7GY_v-Nqo!jEv2bcv`yoGqydv9wkpURY>j0qK zI8VsY=LwFi1`;nmQ#TUSpSZ3s4`fUFZEc`ry6^{_nhxs#sgA+SOk(D-r`_e;p+$o=OVrozx%858D6K`c=G?y?=hy1 zF$3`hpn5Jtd!DVzsMdpXj5J-HL%sW^W+OUD@)gPok1Z~No~{gD;KP_jRT?y#sS>g# zMqO`dcD~PuJ^j7V2UzGgzO)(xm^wVswl#_FBA75{o2JNU2biQS&r$NE`il)(lWjQ1 zB@(#G%h$#?u;xH?14Iqfyr374GP5MVPVv!K7}}fT z9JRtz)OrRW^wG?gJ-7?6s0-Gmqj+17p^Q8z+yxYKMa!_;7VxJeZW%J{cvqgv<^vui; z0{;6;_aYEYis1OSLcU3(n$=3#^V!q@LKiu8QC|Qoq3#A=9Pi10IM?H+vTwcEbZgv& zsj3`%+E4*<4cBv6e5xLb6B2a%i`LPI20YLPFZ{sQQXAD>{%_Ni1+tmmwk4aMJ=$*P zOAgdLY-i0Av`9S_>boXknT%`<@RVPK0$1`8xttSe$;;P7ASZz@xHn9%UEA#-fSw1+$j|+3BrmS_a`0XY4I)i2Jgh7!4z7~hRX$wNP6`nN+2ICjV zJWyrOqFGb97UMor$;Cb!;3+7CU16UH8FFVf`ft2JH4otPJT}XJ-BkdnF6VxgB08fR zYo1-4RpFY*g@qZk5j=2fF(_SR6){G~-E@5+fcEg#OI4Ms^wufUb7Ums?2i8&y`L%s zKxL|becr#@TGdR|pjNiqJ-1qrjCHUN%N~<>KGbvK-DHH66ed||5o#U8IbnW=z?+kp zlDavbo3H#QJ-5@{{^|s$3;#j@aG&EvTV$}}(~QQCflfp2o8xK6$s(~mj>yUVmdS`C z3sSyQuN$WJ8A*VPbL>Ay%iR+fG*WP2sxnPi`E&49dS-jl@b15U-L<=97n^-T0+z#g!6)CT9JId-~+CxWC|Fkp~y^ zH_Y0r0OH;EL}#CP&49UJ0n>;VdW?0h|;*GEC*0orn7lV^6< zAE}k?vr<(?f#ma=7w%-8isklh5@VulQ_~XUnpsnZCfowE{}F1D z9-!-L9M8zN`(QxJBf8`$ncgOT{-WloL2Whz?iXX~q74!BhmI zmOU=Us+FDEo|_2Htf?&S_-}U;Rjb$Z;V;Lpd_w|N6i9CUj_zQd!5kno(;dL65>FHR z1IUx^8t8G^8Y*o5AYVBkEfQjPjuNuQ&5a#q>!zyn<)%*j!6BPK5{i&7Lj8uf)e#)} z_DXTbMvuf+a8BU*ixFWZS#Ra!;EP5iN;hiMl@ANOiIBUGI!?Yz-N!X55fP3J%RBNM z+{M<~_?v0vzk82}qf|{u-I4D@FR|QZz($HbY8zx5m&k<=_aOGQXhyAg?;tOKnFVwX z)+9Tji34b?FyjrqH5#yfTPxvpQrOxH0?nts%Bkc(Cd5$#z^KtfINkXEHs|&Ss^^pV zSMR4NqE=Wjn@^z>nr=Ry%cSKCa(#t=a8K5nSM>~A&a zI$VIkZb78$pz53tmyEYw5 z9{RH0_}(}%RGM*iMFkPS?Cf5%@wDW%jLL#km5y2eUEFwp*Q}HS@7l3eoOzUw(0h5l z*NG?$@xNLzkY1nTQS!4>#8gc6l&OwJ&!a?{Hy*n1&2esff`|Mt^oBwev`Q)gRZssH zZc6`5LDXj zMp&|Pv4<``uG8PLcpoy~;G>zZr@5&w*~pCw?Ey^Y@3b3W(9Fz6^f$3$0I4${ zKQ+C>s~XDd?GC#;Hy9ecVt9H8E9sPTK1R`Oh&=4trbx0dtAK$q;JbngnX{vtX|7&A zLv083Ob*Csu!V=!dUBF#hgAf}aEkmy%@+un-f$J>1`I!Prz%y*w&k~XaINg=ZYax<8vawFdi!rJ6!puf1lgpLowF-kNjXQ_VA@~hOIIV+U_R207n-r3^}g(?EY8!{HE^+cN{wwKFN#-{wbAT5rw@qhR_YgyU<@ z*y#}FxX=nO!87uLQ?`Md8(1aD#qb_m(sb7|G4|D3tWrss$;lrl3h-KWHC+RQ9@6|7 z_XL8CQKkr$-dy(`D88(MaPQH2UJ4(+KG*$a^-$3IL)9uRZlw2Zxd_;v8O`qXye1W) z!}9ENwH!2&#hx|K+4oJf3lU(086)W0Z#wsoI^Z9@I%~i1b9;b?0Mu^3=}}oN`&7-+ zn`D~qTPo41A%;V02GR;bklN$u9rvNR?oaH2qgzAn{8A4F-A47SU7JlhzL|SV|7wq% z(#=$`;d}+h=FwjFC`zMi4b+q{h7@1q{ef211weEa>=u5X^OqV5D?8>K%?&FQUcu`` z)q53Nzky1tA=9$nB)swVNGa z7OG(lxCr05wp2dA&G@dH`Vgx`=B1b-@(oDlwPd@BeYwNoc&%WnnspT`nP^d&`yfoI z`ll5sT?$z1fwRq>zeb@_Kd@wpE5E@GCDw(`cpYNmv7#d3j^O)CYNaV=t`3N`d1Zd= zWC^~Jb$uVvmZW3WKse_MMMqv94_>@xoWeC#Q8cyfT z>%$-6pS{z-#D_MO{HQ-VuF-)Cs14s7kthJ#YSBd`muYN*m3Y(9GIi|<=UlEqxD=IJ z>>O?Jyx-A*a8p9j48@I$1bbI=&lvzCa;=|ApALk5xz^vAN3ANmAzYj^59K7nhRr!C zKeUJch$JAE11URuet#Nq^?G%GBXGdN*h=r_~;T^0kLZcCuA^J zac9F$bb@>bUP|9(nsjA2RujDa0?d6bfA3x)RLYRu z+;^o1)cnbRS61K6n=0lLulJja*CcwU`oFo1QXM)z?Hu62xlnu#G85@Q_?SP+u{oyF zSmTlgNV|@v@e6Peq3i%+OMF#g02QYSAyou^Yjkr|6W~=RpR9r3?H*kX5TCwT#l8gR zOn=T*XC~_FWfk~G9{maEYl7)aua~fpk=eqB16644)ei3CaO=y#U%3?UjV}WwT-_2KZAq+o2hmXPq`BdeM*n*}{#9$u z4kfCVwba|*NvYw;e=1&|8v-G+Uxo+x@b{W=Zx&c$SeNfvfnhNahCCc(`zjB$m%?#; z)wxP}{p&T;)Wd+j(pr9duk$wz4p=WAKtW6hZ(pl(n|W$JLS6MRmuDkrSQV-a8iWa= zk`MS+1k(h#RP^MnmkhsD^f2kZ$j|jY{-&dSi)cmf-XOi5uE|Hv18ryjW2LHE z4KrtnpYG^keszEl!NfC_sf4f(1FhQ$+@}OFd1ZfqcwDHZ1=bT@ICRsJ_DyA;=;+}- z@(jJqmKA~#MqLj3w-pry2x;Mc&;Qbm@VjGr$hbPIU2x`9M-PAwu{$ed1*V`3Ly`L_ zaz__%3M0NF`zdZYE}5(puQ%kIn4pb*?Tg}bfd!U<Gm^1J?@b?M6{nC90k{gDigUND}+DttEn5Cw`P-zDc8|98cnBoC@`!#DFXPFId zrp?67L8|c0)D*6X(|%y3rs9z|UEbLpG)k0d4Ol{a4GQhNZZ}=B@(yR_K+D64hL1ma zgX8a$GzKR_%EBhOLSYLl^oFzl&r537tAyUNXzd#G~kLbE-o}ABWn{V~?%I zTn}6?DrtTgZD{W>?|fIF=$j$<6x=%-pL&2JhZOtA25AYtMl)#0zer zcRhTG?pDb+jrcJd3_$-H=7o>f;bR=(96A@mvScZIp+4-%E(vFd{sTyAgMx+siaJ>D)tqST&ZCR~ z2=4Z{c;ny?gW;RU0noO(W1F!h?PNc3V*S(N4*xXT*2jR&qp#exNNTE!{2-zct2s?k z#2vRD=yz*+F@MMV`4z~C2PMVUB;TZ+NwS`yW3mO>5N!NW4g#max+bB_ngnKMrrJVI z6^xcwi9Ap_{2;ol-eoczlH*9-gA-NMC@LaIWGwRmwl`t2dGJ?D?fa@ib^Tv&`L@G- z*I8?@(G9HY^Lg(f)EDP^kw*pkHNkwhQQL6bA1y1mL8hNDharv++V~}$!>5T4!$u@_l z)>fJygo7BVMHLSiqc)PhaC3f9dp>{u5Y?}yghv3=F1K88#NS&lu;=ef@v9bLHwFXF z7GB81gS7=Cy6};_Q@2KHQnX$c(U=8Y+`S|k{s`Ut3|-SRg+Nu8k#s*~P!tBU zzl17o@Q*IBlhFDe-2Ua;udz}8UBIOSd^;Q3b|r1OEL@T&KTCUNCc1&zE_jHi?(;*` zT-x$lz2(AWXP@xTVCwI`fIeYBL~iMoMd{=x8P87ZcV6?9#iI$gH!~K;DaB6nADylH zCim~28KnBRe2d!s7u)<)9|F3Rw4|19iLvCnQ7DS{p5}5Ix&?_!QE@^I6r8m{BiEeD z#yicII4u6guwMs!p01i-_e?^|Uj_e}hvahv?>uh)ZtF>SKgOX~{{Fd3?%?pRTq)cs4jr)7B7wUI}^I#^inuL$_MMh=R`?&iRw(Yxwub6sG0uwb@!_ zqlP-%Xu)7^&hr)8df~fsOe7Jfp;b=LboJYM=To$StoE-ot4`%~vFUn7#2rbpk9pE^ z(%kJnefRApov!7%XqB#ey7{e}0eATYHot)Lw_*Ez1CCKS-PRwYa$E8^QI4|#QUmcH zN;=Cp=6U8~l!qGi5CmTl#XK)EXK!&@4YS1h1Q zD;1lZ^E{P2`{m1gmtEZm7Ddj+{gL%VbAcQdcBqb7UG-dqIb*b4R5wexI zaAm>DjiwDrQg$a|To69xjyT7#iU~V&myLA~c-TA`HX7Z@I7rDOj3b2+yf>gk0d?r0 zjN*!wg!0=9o#~H~`O_bd^o(2zguN#X5Q!Q^VUo3jFG(-0SA&({MF@`$J6~lup4fNw zCE{+JPp+kPemDLe!5VeUF}IVi{FdYDm`dmDdIU>JBSru{DJj#7sy;bCI92NA**R<1 zw`LlNvqd#3z!y9tNlyn=TrT#1sOKWJD`cPrEzOo=JC!=AeYo(pU6O%lP9fQTsWVS7 zDehql%p=aB3#}4tU80yLQ?y#1$pOt(i=NpBCBig-8!%_nqH~vrDUS8&glNxVmK}OZ zI+C4O8E8)#DU0Lo``=$=s6O}VZo|Ag^G)Ish)~j2%Q0C$;=c2l=1tGN4?<=GZUw1% znNb{fYLu;%%|FRVaNd8;I?FUm@3h{u!KHM`z@9f*j&EiQ^I;Wt1Pl};H&~Q+?LPcu z@0MP^_~?BRYBREp+E*=7X1~hmyRf8uPgpNfO{2itZ3R^In*U6d@agz3MsBpJY`Zvb znq8Gg#ne%_z@SmlcC1Z!-i&7{%U1PZ&e3*w6Lyc zHzX6E1S!X^D}K3E&gs&_Yia&;bTYQiOe|c`-2DagDm=zVc*wWN-Ju;kA5)s~q1ezP zDofZ+VPV3gzDLM&3@n9N_#oGJYr;aaC|3#o57kcAoX<yyS%cd=y!~AxZCI*ui31Y#%&9pHZtaW?mR=X|Al|V( z@x!DBIO9EWyZu?8jzd9H{<^{zN^TvM7s?biJf|!;iQj>#T9suz?2f8aP?jl1+u&Za zMORzCR3yJczQrpgV{OqqzHQ&XWB~Pvb@X-Hx6csSQs>hz&jH1Ix=Lzr==5$9SXY-FP zY+s!6LmA|oWg6&Qvp88OTu6X0YJ6`ZDR5l18WACNS8H<@>~&mr*upc}44Uu|E#Lu) zTjo^vPldMKSfo-87XQY;WpDjLqA2Y3Adwm^Q>*9BR;=_! zPegt6dYJ{e)m{&DQO(ND?Z!D&4LWOB&H&FBzo=L!Z*~tp|J}L~sbHB4(Tn#h9a^%5Qv=48St}3OB2cb!-uyL)z3GtQ`TbN|`wFe__4YE^LI-KJrH*%p zwN)$I83`bh!uNf@@bSfrYD?O}d6Xlf8T!Q*E*I(@J_Zr4g zE_a{eVufpKKM~bVd`%@(RkbS%D|@-5tULqmxBffY-aGzJ+ERtb+-%_?F|ykrb~1+5 zN(XBl~{7+8yQRPI9f{bb90=J+8Y|eeGvdP}P*ZB|%=N#;TwlF<9a5kg_5w zd#k#IIDOfMtqV1;F@HnmvX*;@W`SG%P35KDvr)l1=A@^*XDv(a#>v44JO*8_eUA*i zD)C!`n#3q}TkjXOSJstrxx0zG*%s6_x1t@NAXX$lZSy8VJAa4s-8%&{?`EB6ilHaT zIf&azN#%SSsxV=Rw(@}&CIbt17a#bQ3NN|^3=p>y9IJ7y;2qJZewYb&dW$Ql|0yFu zH$rJuw=tOsf6~@ZsjzZhq)d3R#0EjdGN1y#ybu^BD+Ha5l_}ohNp=4z&g>s}*_lC# z-ozYb1qFD_T zzDua)@)|p(BB7Z~i1hqt0&B+naH0JmWWR5G4_IC&bjjQEis{i*7}*5z4%B-<3#IK2 z-f`E=Q?a{i>1K-WYL07(Ge|*H_pZgl+debG{0i}H)H5hsB>JocQt7jBlY{vIfF~&G z-Ot_H6kHQFYFk@i;z#G(@NR*e)R)0>%s(64|a3$uA>+ zFdID%(q-u9M^%+HUkpVKUxnpkM}lGT^0A-#*}BA<==R)s5i2&%L!c*yj-NS@rZwtf zx-_+aV%a@XTFgZkPlCC!)c4i9<2I=8O%^{Bf7Qc_+|Fg>n_R)&ACv8 zx^*&zl~RmiK52YdKVQ?;uvFn{Le5=n@Pl4R%E;5T%-y%K=|UYZvL_w5kw4UYTtFdN z*BqPkC0S0q@(!C_1(mkim<6Mokc&ogmQk6lQqHXacq(l>Z zGWmfIem&{!`t0fAp#FO^k1um7m?27Yb=5Ma`LJT$mj5O0#NPN!Mx8*a%!VHGHYw`6}rlCEfkHpN9rqv zTlH{F1cMrOkrVy$cq^aab3|@#h`izP2^DY*-EKo+DqOM|4-S0gW+$^J(FODzo+`7a zayyH}EtVU#h~k+BS5P86;?xZXxQNv8;XB;|3j0y5j!Bra*VMY#8I7)sYFGEM2Z4$+ zB(othv==HN6nm85X_}+CHX6g=id-m|YOeSG4u?N*A9za!w7|pw$2jV_T9ldQS{lrv zA0u?%uo>|@=NWdb-ACI@i zp1wJ%8_~0)F8-E$#6Y+J#?5bF%=X7i2frZZ11Z&NWz5xq=T_bWug+{6|EIg_%&9~m zN)rphhORWX`i909!&@yJ?9izv)p_*g{(02o5SwMsv+$Ob#?kx_w)WB=djO*FAGYmu z1pB#8Cq~(94n#2>y|fGXDz&1zo5+1m7MnC~l~e7t+5O2T*z4)G9GzmO8Ci{?d@@gr z{l)`a3lkS-xzYRY?+yPNB=mp9xzkaHawL_BT_i#RB%CzwMd~-HxUPMLv zD>$^?a3|KZ$%TR8`~SIPlcRGY`Gq%}R;dg{AI(40sGQ;Xw_|&;TVz;8|J`SxOkdzg zM|XF6aI_b&mlfi^+0407t%;h9G!6%1bYK~3qxoHQP}!SBJ{FLFsEn2LF9`d?`%?xE zi_jOM&%CqYod3jdh*$mDqR&aW3k%Ln>-wso?H42eDT?GTpPB3QJD8zQOtVL2%*Iw4`xmpwxcuer5f%_9o zJ2N)(b&;bFwpUvBZ`-8N7gi;31ZdFA#S-7YuVuHijlf>_TvzA2t^c5~`cm?0u26y4 z@G!D0VgkGMDj~#Izf6H_f97)ecRTIZ{YG2?sMz>969z;=UHq+VD^_-X>ex5Ckh3;n z6}h?d(>uY8UPdB7N)6P2^wmSl4nnIcx(EIe-&1wt*y-5bHrw`kR|C60$!y3q+W(~1 z`(ADt)A}2vc{gPDB>75jv&@xLbeaKw-X{LUwp)N$ zcUkP4*a+Fu0ZbhH`72_3bb1DsxDNH-m`02(fcCIbJNqI$kr z31c!VS`81Fi;3S#?YcR7RT}ZAN#jblSQEyMP`?Lp!C$r7DE^0)`vYj`BfirFpn`Y< zs__VDV}xBg$UoR zQ&kZhGkd8mj@P!*L<;ba-jL#l+@k&8MhCu)4pK<3!*cF}+Psb#l5{ducWoQh zLw}^z#DVn>@_nTR0$>O{|B+>phzyVsZb-hlCUMy&*Q~k5xUiSgS+loh_1&)P`uDC! zP3YXXiM6P@>Iq1rqF(u5>Slpt+cFD#so2)O4kqS2v(&UgbAp9;Gc#6)j5UL#XV;KU zBiA+O$E})LTh>f&@yELMWM-9GeRf4%V)+|iu#4>0sR~F^LX|DKEt4I;U|qSB_gPi2HunkrOU?!boIigE2Sj?|lxx38rc}06IE7 z;+j>T^imai_aGm-di2$&zaG`? zG=N#}R?dWaHn1pGD7q~5%56PYv_R#fg#)5)iaKvHE5Acfehl&Q2TT3j z7yZ{>Qvh$y&uYz^h;)2*>qGjzp^99G`i}GqaJ7psMF*05V|AtrQ8e2y`5NLt;>HQx zn-BiJrW9aJa}^Vz9)6(tB$!u^=L386wk$KPn#3wEJwOchLB6B`G(ZhFR?r4Z+xrT! znqxy}s-uto^|Tm3(?q?mQ9X;rU}cDl`Uj7(vhSDA^!W^zK6 zReps^hTTgikivQ3*3>UD{mV}MKl1@l*xwU^=X$FOtv`)+UMmvT<<2ccHd(l%v$Fxd zbtvpHse+B&9WUAHD(r$iC|?k5O1k(Kb?Vb4}>+6SGE}xBn=C{zeq`(iPc=d!U~F{4@S*9d})! z3VWe^-fHlt%3A1+;5YFnDM-YfFEWE?ecmlQhgc^vGX0fL0dtIQHdQJ}?0BZCY1@&L zjl`mauGnEIAuz6ewt#&i!FU~xHLHTQr1Vjr-IV9jYu5kEWu31K>%tE}#o&F}V@Dm+ z*U$~)RxLSe?)m6+K@JViLM&lF$pkQ1-l|7FPpA!du|5+;Xiy~6h;M^J2GS!@b z41^Nwx|Mp`8cb~mO>YgF?x^#MwKvmz4430RSsWc0XMFZNX<)73neAVe<$sm6Q$W*#5%akntTlciHT@{+9$X z0}>SIcaitNS5CO2VhftFIks1+0AQ^h8E@Cj!zAMEq}Z<8?ElAWa*8^q>TN&bG4+dO z{wrtshtgH{$rzcpOyq*xlc%7A%-CGOie{h&W!vhh^s|%0K zsZ8AX;1+@XUjs7JSwh;Q{51dE-JXgb{7ea!QasEX23F~dD^^kWRb2Q)Jb&der$#z% z=d{#c%?Kcue-y+#z!r2;_PFQJiOtaiIV%;Y zxUFkGzlF8>s}u5dtL%W!(EdKkxx(ZQqd(|alpxcHIw>cZVU6r zS^v}fKeh$jlmP@9U#Mf6>E`M>?bJ+$H`mofECczgnuf=< z{mtN|GkPOniYK2StHDX8RNkTUS9Ib=JDy3C6>84`0ClB1B{Vsc4YJHjJ&?gt78E$Z zl=U~~ioUORbPplkdMM;F#nJjDy(Px6b#<*0a%yza_7qb zaz*#6;JwNOfOJn4+lS9S7PyWlKx66Q8PHZs(_hA3mH-U#Dw1}IEETzs93G`9EUYJzsv7E7DcFKyT6x=*>#VMBQ9Z;tT%m^C!XE?6 zhDSvwBz&21`K`51x!LqI8t;$T1@k*^W>e+l(n8gc+9ra3Pw+N@UB=A%iN#~ zlra9bD-gOBFqmFUSx`ib{GtHG&8}I=kVm3A^QMYXW@o-Wt`RE-hvra01e%`Oy6-j@}SKWxAb6k z?QsWlCg3{hukiVuUH0nJF}dlI^&82EuEGrj;}*PdWzY{Tn>GY2y?IoefIsn|Ve}PA zv3Y3QRDEI+Az_5TvN-C)RE;+2$lXs0W-Q#ez6s%{{9gO_@7V#*Pk|OTnGY#}dc-5% z6>i*Oo*S%2qvfY*3`mp-P21Bqd1@K1tlYNj&a~X=@3Vs6fhRqajKoVc5b~R^%6tEP z)vVWn$e`67J${hA2z}Tz5N!}d>YN6SA+2lBs>s7et2L0dzxBi*>OHoUQJFsX3>Yx~Ko%fmQ@wR2cr+pg>UNlTPqOuq` zA%s)|Va(Zt9~3`c8(1?ZBLI_k;ylKT;_B5LWLSiQ)@NqtS=Ah- zH_QDy+G(p;OPqB9qO@_>CQrj30XDtM02*I5-=PTDH+=^q=1gczt_vKjY(fR3fQ>dT z24J@pj8-YLBlH2?1eKqo9L}AYUqeBdrXBJ(AKt62MXUW&%)cTU;(aSMd`Q=^s&fR}E=il*s3TCkDp$PSvxF|Hg>l?TaKey53>pVKUw#OmBPo*Xz;h-zMa zE_OV(3ihFe03BMe)PY?{|F};l9QNTD_?oEpe&3;k%cL$}RJdDQ@{HT;z2y&g(_)s< zCA$pp(Aql*znD7yCwKs8w&qV9`6|q>V*Hd=5)*_TR>0woLuj>O?x_lShDv9QAOo>% zDuETVYP`1QR{IjQ8oT)x^Y9CPY|7TRydQUD*19AwFK-cbB4@OtaZzO^F{vu%ES`(& zx||KoqDk}l!#hKksKG{j|AXt8cG#KmL*LhTX1>C64xs~E)x-42eThOX1+Rl=ai?e^ z=&jyU^DJ|llbiL1Bp<@9iSVY;cJO5<6@y$aA6FCmw|G*oM&j}=>n~$(aZX(amo-U; zv^^`XBo;lmxB6K_YTO%E`y7QSi;?y5XlTo7N0JM{$Ta^MN0CiHJ>N?+w zRz(;=17jB%WUjtS_uCBP&7L$YP&sY|Ua32;zuz}5mONNKh|niLkhx}0E6@Fcr9Rkz zCoQOG1OIYwxfiBjzw7H4iOX(rXY6;4PQ3}UXd?A%=#yhdA2f{;%v_LEvTRSDCq&SK zYAB3tE+^X(2iugUiLK}59X{hFN1vl+otoEI%BF1y4z)^86dk&6Ark5l5r|6S=J;p{ zY^@eHB;;-VlES}I8EIS4AKs%;Ny$CgXYI4;UN3MhMoRfhrBdBq3|!ew5VK4JGAV0A z&8qUwX<|01m>5Bjg@9#s6|Qwh_71VZW&)mlDjk`h_nLxUfh%KHV_~ttqmQ?W9RxH; zJw{dC#E25Ylg2r;ie@n($$ce0Azwj$V0chZ3^@uEaZO?qTcoNrN?F@emduj8dk4=J<6^J3nB;6dLaT?2? zuN+3|R|6==xsLNkkPx3$3>?mQj82WNe|d7xPyQ?ghl#1m$6%cT3@vPZXv@U6dnE1JBsxN5=AS04da+O<|^2RW#m^#pEzZM-6#+EjmKB`Q2_;dyw%1n@_N zecU?-S2T3pE8iZoRMAsENHWESN|+-j4-do5?f%p-7q>A&<6gRK%X-DUH%V3G5(vwc zn^#iuAYnI6aE~F1b>AYYIq3KU6W^4Xup{UCSUhRQxo_{d_i<=(r-X9W)?k&~4!dd1$WoQ%wR`c^%8<`gUn6ZI0EX7=?9%k}sCtGFSWoI!_KS6pth;u5CV3etA9u$m`B7b+ zP&X9wU^)^fXQ`9fuU}szPuP?E;~uZBwrysC2YRWl#OGiBYEaVcit~VDyQ%0R7(TDx z*~^jJTdMRfx*SLhm8*`E|F6C8jB08N+EzrtMiCJODe46kF1<-j1d-lLfY77}Av8fc z0kNS}5$RoeLX+MCC<@X`2uKZx)JQWSNNDeg_r6!|UHbj}{(K*6krftepR>=LJu}Zd zduDbN-e)PO^HSMr!|$RGhVw4~xOn(YqQYAy^4N%aOAcQgthe!qUtrrYLqGd;{ST1M z(1d!LcmKP4{9hRO0?5!Gr}2Sl?<~lCOACX(xtSN`g895<=(q63Q`%=XKdyFTCGZyL zPEtMU?Qd8Sfcyd+{*k(hc;Z00MU8oHQ>jr5A%K~=cR;@qg=X5f!LdyW)BIKhiGTwCohHP^yiqx!2!gDV_m z2WwCM7vB6po*z{xk6crDI$pppr*%SXTxMmlnI&uWIvecw^Q0u79AYW<`L=7i{AZs$ z{Pxx%$Bh3JTKs1%c-5C7}j(^qO-Hkmd*pLC{ z>Vvbn97Abn(QWFP(UWLxv%t#AAuBaAk%0&=+bkvBpcbsLsS1LAtI3%J{GueN(aeHNE71*bbrU^cE&cSsx=)D-OX~;Wt6xi=;e$vh+ zKft%)f)QW6LC(O+=oSX={kb#Nw41!Nl(NtWvFxR>X^X_JO(UR7x${XX2TME;3h$WZ z)Of8&8P6WklkfWV{x9t@+evb;E!rB(tBB<{IKF(7rDG%a)wUf|J5Zp^>ralr9o{}X zT4v$&TgeviH`iS`QX~ICe?>O}>ax8*kj%f5yw1=74jEs2v>J|SN^`^A*!DvNE%UC9x5Rq;v|aqK;sC1q*Y#{E+dzlShh-5e%|3EMJ@MPBLwUiVNC8%x_<%2pJE9<#Y~&;dGPa;1|Ig>A?jg-ZJ<$LaSjd<|_KT~faeegK0ecFyYQ_5(qSG~^K05m!Dr zDiSSjIG9iBwq$UMGMHxnd&d8L6u<^_0o7aTY)(kXgsb+#XRL%iG`LkODK`{mXKk^~ zHKhk2`TvgkA%GV=fce0DQRvtF?W6|JS*a!tHp&x!(E{hMAdX9uLbSI(d7b?0n*etz z;0Nfx$`X3?Zw;Xe6QGbV_sj*J-%RxZ*pb4CE#nn|tbqd@@y}NPWNF-@kU3`8>VDRO z{_~#ydj1!qzys@Vllu-Hkod3b1&dL*JRX)%mtV%glg*bgl6aE;ztz!Cu%u$1RGvu4nH{Z9cEH~H*i{Z^$Y z%OKJTIWR76+K2wkB=Rt|U@u_;+Tt0Jc(Qio|SiimIJM5Fy$|zLJu3W_%Q#R6Yh_)L* zl8^8)$Yz!(Q;(r4x3&<{R`QZVLk5q{9U%*B{@Ram9uyw^)=BDKha$mG9I0KD$QJB4 z;2Jc0Y>dx9mvNgrLSLTJqWr;ChQYnl^xF@Ynf*vxN09!6e3DcCx;r+}xQ%4fzVBtu z%kMff?PhhnVqvPPX_!99*I>+hSb?&cN>rV~qH!nZp;d~f$2ekS@|-4j1Q$V0P-HEi z*O;$F@kmkIJgD6XTY6rZ);0&cJ3U`H!R|YIEH%(Kl}E-hs357y*wSLHhBw8v+i8Cl zgsEoHCdfm^t-n%YnDS>xQptXU-(Dm;B}pCpNT88fFMqs2kp|N9kaEiGf+3gBiXH!2 zZWY84Hxe%%WW5j-cLDDO8zLr)?S^II6!x~-l!;gqLQGw*jOe&OI=j82uNa4Lm3vFF zUB?Je!LIk^<1V_}hYc<)%(<1yiKWPPZ-rf*o!Bngjt%T3a1DG@!@#JI=}&zt$g=S4 zT{XrZ9h-;|Jy$Yhx~bV-n!WdV$?Fl2>X4lV-rb961U=R-2_n)0ukcpJY+0mq=S|=! zW_cxPB}o8AVvk1dtD{yt1$Vj}(YH%O_>Jywp z0$t29v9F!TI$A$WN!a-)66_nKk0Q`ZCiFfEhzMJ^Dx2|dVzY3++u^`$mq_0N=-DK- z0Iaix{@VTCCIys(LB?*9H=89()UHZ$)iS2U0wjnh1*x{5HAEEL zw%i+#jkshQn*+LZiY@p&6@O6QO?&q{A4WmLf0z%vMGG#>H_7LA4r}a9XNlu+L3-)i zPO%-U4 z^oC7~;a0EeO8Ek8^2n7wchX~)w*0Xg)$~c9kG1`)qCPHJM)?X*`iAUTLng%Wb}8G_ zn_p@$yny$5p%WZOzT$^;ER1vUK4L!*=^Au%=R|T}C)kEI`w1As1{$Iw(5ziihe*98 zh3Yh+An;6r)$5A$w5dQ-yGtw%(v6zs5Gl)4Xq7S>ix$T>2^e7L2A~vdTspEd!syA3I=aTmoVlmvqtzp1j3jY6p)TSivE=GYkj)=Q z5-w0t!36rUIZKdhX{)7KE89u4+oNWCrE6d!6?Sv^?@aU*7^qJfTUAm$roOH^5vnN7XucI%JnFt;9+C21_I-A~Wq38&LG4nnP!O zX|2b#VgL;AoGE_~DK9Q{+aT*Z9lr?Urs=%8*w%BrZdaT&QRuF`)?Xar6L6N7K*Xu4 zl8Jl9+OiTQZI(_LYlr)1fKx~;o4p?uyE_*JlDgg{NY1WU)Tp=vOg`?oBe0-}OwV>i zy*67T?uEF0ls(j+c0_0WDw$MQGok{RygwOfk168I=4U^gRAw&7{zF~pJb(a+su?~5 z=A^HlSK~PHG@|82#>R_K5edHMudeo`M{XXMB=TdZC_oXYpSp;v)l>Jb#|mJk%ag^U zLN3;E0q%^;G-K;w;Ovf;-$I@}mKbh5nF8ONvaZQ?EqJ!38VQIKMIG9TS7$TA4;?GW z&)#Vuqp`Ntwo}nkG<7ojEFWi};^`Uy=o!=^J)DZydabnx(npCcIzV}6xi#2LT z-N$62!=I>&E4OR#JLA)=454}hqtwfvR}21;$qS!isjphV#(mem;ONYb`k*bF+rD%9 ztNsiFKsbL)E>!j&bjb23clq#Dmz+V$-y^?0Aqvk{owo>5N{%yx`!6IG9J8;K7nPCb zO1Hav3h3SE$L!$Zy78@|D!FpPs^}}(<{;uaX zIk^OKI?g;tu(&$!@FxxbIM7E)8>i!YH@U`+p4b>^^Hv#)A7vQhOcX#@YvauPnp)3Q z;=9vVb-EuKatu6ef{rfVO^Z*+cmzPyGlS>40q7p%CBWOSx4kd-7erP2Hj#v9rF+5% z*t!GS7I)eFBK3+)Ka3O7BOiKkeMqOsfJ~-7({_Su`9C?jjJ!L ztrKDZm^N~K%2L9*d~4jeeg3xB9+!|KwKc7obeWRPJZVo-y?bc50=|`UUfd0kY?TWl&7uA`XPdzH>4URrskhN8Cba>4ncO+O zVxfnqu5(%4tp#}y8%TvkkzaIaxXO?LU;u zo#0$D6WMt}RlJHhKO;kv_Zl`E=Iv;Dq4VS8*c*Q*wFJLqa1d+qPLcIcbzWYs8y(6W z_;$U{ruEBtdc}+3L-{~UQjyzSpRz?gT^Gi;A>4$KOvc%yd&8$9R5Q2QsmDd<@*;wN z8KhD|u0Ql2aNE5s&>jPH9{BSY9~S@?NzO)q`^Ya71u5Q6g%au8E@32oIiBKRS^-MG zH$lCf0>*#=zxa4b$zw?*SNt-yjxwFV< zQBhMma`*~%q=9eNnrw*v^i)+C6sAsF=zU!Dw;|UA&0|c(FsvTmD;L)J zrHyckR}qG8&wg6dW*yqKzQIhL9IXRZhkU8;en7GsuhA!Nq$DRcjBf_SMf_gHjWm8! zKRn8pF6YD{CN21?Wbv8rp+&Rp!`aAu1Eq;e*jn*~kT(**uW2Wi8gJP@@XepOiR8E} zvs;n`l5*GGxB`uHSV4ZzDa@4sfVL;$YF*Ljwg-(Q>}B~Yty>C%$+R)8S4>45UFZ?7 zBsk?!v4qQGp~#oj2aM&Z=Z8ecLmtPf9cUY*k^-JBJ3acw9%Tdx8I!G;gO6IS79L4Y zUzK}cU{s2}oTAO$bFXtf-^#x-ZDPH_ans4DX4I6wX1T<(OY)AK&t2DBrD(rpqh*-@ z)5vX}bV+vLTp_2D{Qh!iwd12Eotm04RWz}B+R9&P!*sboF5}2{ z+VIn)wG5+)dNt($#8;!!SAVW5hVz#X54mw(Hbx#`)~yzf@^0)TgZdRQg$#4v)%q~?D-eA4K0W=9>frk@@ z-@Owwgm)^&R3L|tcGVJJ5qg3G>31N$qHAhF(*6sMhOtzbCF)8ZtGIpK%;&Rr4&>c5>zov7LF7LvIFK9`eCdpF)%^Cf zRsF>6OpXAeZb_wLQ~eZsJ&h+)XL*aA@vYZU?FAb6SBJXxDh&|v=-9HmFH@!aFnm&D zbCi=q1l1N&)gQAeA#OS*g4nt#`H4fVJZ(oBKQo;;ztyuiRjWu&8%sH!vfp>IDn#T} zH?+8}Xxewf#aZOAzw2ke2^n&hYimQSeNTK-?rHe2Hb2!7M-NAYBc>t@`I1If|geW2Q56BmBwfZ4Ep z1txF?vY7I=h9k)%Ep-C{N~cesp0I!ynilDh{j=oEK5<7CY$7W8rAR4;MiN0v@uE+z~bOcPZI0K*xR4-suKY?hl!MSTG0o6wJ?Bl!7EN=9FZX}Q*l)W6@A~#H% zCL01L7^E#_mFNXg6;XV7S}W(A=!aH^C5JD%F9~J(*iUbMKCX}EwxcWB&!rpk@4i8^ zLEe*W5uKkHoIBanPmW-#yf?#&*75F_TV4+9?%Ld%ytTR3VuL<)a0gH5Jpg}0&L=D# zH2jF*=cX%A@@({C`cTgm`$6j=+vCTQ?|;qFWEJ<0l??BW5-jCxNMb1*-mD6tS~8jn znCG(}92|HZm#Z%QZQz`oaU+-5*tkF~v?*}rCE^u?q$;p2AC+Sojeg7lnaLTY`9xssP7aA%1{=`#e9+Q;9|*q-Cn#WN1YSnO=kv znRfT)8&ur)0h-(T;%jP-lLANPjyO8V7oBeWQU?lT=+Ku|A1fY&DSF;woY%~$l5bMB zsGPi!?TZh5jbKC+>ZgjLCIrlCO=+}-k&YULeo_ZrOWz6Vx-N+dO&wsD>z}nsn;io# zePiL-B$F8i88grkQjm;`T^v%4F&tgKiJaCIPm?LerPgMXb^;=5?uF-SC7$cybQ(}(7(9BQR`tOM{s5}Gi@>mG- ze@JowHf4K>d|luFHUFRH;xP4@%fMYOb&36|9Po$OUI48^$3_1yh-05K)Aicr2Bg{L QL%@%!lGd$a1*?bu0~pan<^TWy literal 0 HcmV?d00001 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/implementation-scalability.png b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/implementation-scalability.png new file mode 100644 index 0000000000000000000000000000000000000000..8027382f3829e0ca372e6cb10b38dda49839ffa0 GIT binary patch literal 118048 zcmeEuMQ|O<5~WxcS!6M@WQ%1nGqYqdGcz+Yv&GEJ%*@Qp%&aSo{9nw(Y@YutW;YX2 zw-$Xny0bI$R8?l>sSp_{VK`_^XdoaUI8hM+IUpeLH6S2R|1S`qS6W$a`+$IeX-xR} zWkmV;@nvkS3{A`pfPlzR^t83^#XypJi8R*W1_gX4s?A%YA%DJhf$2gkNVM^RRmUa0*`=|pNC4uKue1W zw7k4e0?uPIz{=AgBFB{64%|l#4P^@viU(ZtJVS8y1K$zJ9{|=z4dMz?bO0mb!InJU z2dqoRyF`!gC~68mqzQ5O<~x!zb{CEWP6P@H3O*SBmZuP~HL?>sx26j|*|ztV?fOvpJ1$Y&lfzR6E)lnAHKLFD_0>~m$+Be2(b;hB~&KA}0 zKTRasf9pHUIq8|VLYEF z15>ZcVeVdcvaPzTkl68~yHl5)xT(%Dw zmzW3S?+uiY){&(A_+#AA)2@DAz_-`#31Q>HI7br$F8VKsI4er7m^zVWZ zBY=baY!U`0jQjg5@ju_V4Dny2$A<iF4I3-?#Sp`v-deVN+A1rL_+ z{j0*fKi_DC^>n3Y`t6P7AnY0Gkw2;9B#Lsj+^lTlq-(MgPpjkhXv*P;K-2z?J0SV;vm=%Z&!$}%k$`U);jN{$8KRTxIx(0T z(Wyc?oTB&0^5B>yBDOG8=6V2J0)eFIW7E@nzxY~*qCTB1a1-#1qwaQI&)cic*( zqGtVSbV%SJR#fL(JBM_)br*y5E#cRe@AtVu+&=gQ%AiC!md>o5@W`+WL+s~!D0lJ` zVuA9lUdQP|IYYf|oZ1-Y`PCvUMq$s0#uXoF>KBli0)YjFSA~=dA?)}2QUo}ZBx;SY z$v1#pp1*0Nhgh-?KcaDQY2Od4P%)gImZ^31pb})Oof(8gx^J7?UYuzgc<=dGGbBfr zy!#(*AoWhFgTHd5zV|1KJNq}~yM;N3zIok_&&JrEN=S%?$9AByy=+ZJZMTPY72-WX zA83WVcH#dL+Z^=8DO$Vi!*~>wc)0@=$>grOzQ9MlKQWl68esb%8el5{Tl7NGQvQu< zuYPI59>CxE%uvye%eS~0PBMx{NS8b}z{2a_YR`G`)+2Yf0b4xio4v<~gzu9rtsPDn znN34VLi`DJpKZ~s)@G#BLaOcz&Avv1gWcyh2a%8&21S}>mG*T;<+Lgw#hntWN=!25 ziN=@5<1$ciJ5t?taY;(NbFxKnJnPp&46#6j#Zzchy$e@GUG@_8A9R~WbGJ=uQ`Qyo z>QVi37!<*sCqhzYyqr|Rv`#1VMb39boeemrGrnAjPrgmyvtSX$xD;cRoUI6xdxqec zZ2VPV94s|1)QRWOc{O1zr3$CiX}M$?`swq2 zOs&GK?4Wa$2C+#N-+}8c8YmVCm}TA0hmR?kyM*-gf~NuYC zMd`sqqizcmHAxv7SN(E%RrmVSvEqXZlWc7--5Zgz0rODr@g1unuTZs(6K9-7)rPN) z_k)xgvisvNEmu~SLUZBDO3pmj+B*_Dq4UY7agbQDKVY^7hk~V(??a)a zh$H>u1>K}m9?)871CFzA#ACe}DOHoG5Jivul({%=u)U>bldfYd3{vQT*?#;|*R@1by1#|u>oP_!f4T|fC%mg;fX>SZC$;P#C6WEzoBd}T?P=v6f?m&&?49a+lhI#~<(j+{( zn^Wd{@!}G-;rx~l7y9bFf?h0lm^xO~JUdw~w3n%^-n+S%p9iz)h@@UCX5~qOAtn4x z4`6(Av1DVTH>0)8Nv{=m>CtRj5Alez^N54wkL7qtXrt90vY<&#O^8Lywf#RE zR_$ceYUXRgdlT2Zg!gARdt&c9cunV3Ib8H98fYYOEd_C!+O8XSS`G4_N|YB2cgoaU zC9Pfnn}RkyN`g%xO@a-RSb^!s{4-B;yPkvOYT2hOH6XaEu$R%1ZK0%PsmzEPxKYd! z5)!Z+7j`U;)@azSYQD(g1s*u{>95)xY5}@y4#0s582v0AR`pgC7%}cFozX~N8Q`Zq z?d`Jq$!CIeS#BbCwg%X@ju%b@B35Sig-(}MIqD5D`ts;6tGNz4uY(^)fzREE0h{Yj zSmrkgv*b$iwJZy7EQ#<#eENAzIXDHfuEr-7h;Uw2FlgZE$oRHJKlzUV-eAt#B?t&w zag3}CbknmW2PdlV(ub>0>ptnu)^V4DhV!HxPWEsOlpSI;7Uer|A+@F4sNsTd5;_qT zH=WO-&gMOvB(JG)vTIhImguV%rcK&G!R!uctWyq|-)qoUaWF7g)Z}*3$Ew}NLBc!p zhc8)kKtJxw88_l@AKlw0AuNN@!)DHNU{}-vcIzc|&)8PKn>?P9H+EPJUTn67;`D4; zdQ?smBsSP#+9XknF|O(0WZx;1x1%x9xyF93<+`>Iv${R5lo`lA4J<3j1N$BR*iWn# zJ=pRSq1-@pAacLo)+=A z3Aj=glD6-s$RGx}P(v$KPc z+4QY-WK_18hDbp%gn8{Wa4iYVga5{3)Z}d`KfVq&=|GrUz-;>$TbEt?i(WTN86{gt0yk2nc;6o6X?6!6;^LZvGqFyE6-Xo~3P}{kE~S%NDNQJ- zTO?knw)xh;WMkR<&QgsmVCL(XG!p4~;RhT$UP^%EPPN`(0jRU?u~2W-hT|DVnIfKW zpMNMLKwXU5VOlQ6djUfHyVQ2dd@U8#0^r5Q>UmBHVnYe0L~qi{nv@%-Ho$pcn+vAe z!{^d^D{_^{!{r-aRKT2@EAHEWAX+M}eKtRy(H!^3v?3%3QIlmuQ_x0Q1WX0c8d9rr z9u@*!E;~P40KSf4aC}aNmyVwf7FnsKy1DY6cu)x zvT5#&mKa6E&`@aWD`mS%5XZENZ#rQzGWH1LAjz(pRd!?@ouz$fO6V_+XMGJ8Zr6v6 zCo**wWR8}rfjLAG-Lnrr^(wD6DQ0ALf4(U3T`6DHh14-~=vrUjq|~CPhJDQy6ziR} zfrflU-PT%o>M%rzBA!G!6O`qRNd~3_hmDE4uX#a0dlN}Zj|3IIg`F&sl+K>u!5M05 zJ_s(#9E_l$oA|{M)Xq1N17LI}3eObOo!ucH%Xmn;ij>*p{+7`ZBlwYOABBjoafOCuun6b zj$Xz;J4;A>g>kuS?rhk$_F0h<0)qiE%3S7NKn5;| z>r_A%v!0WqMtMRH7L~gOBj{8WBp>-jyK^h+BpkrV`z9EEp!X_>6HF_ZfK=MCD9N&p zQj7~t36%8+S(}Lxd{gQn^6I8+%ovf~V%c`Zf)klk-Ss-ls&@!voa)AL_kinFm#;s8+murn+0sPTH%FhD;>2tCuss z??UIUgJubVSwGgj-~-qPcpet&cu{K+nhA&^4c z*D|eKyg@czy`IeaqfJlzS*mPZFNZPkq$`H-o{Q*0htKkZ-9<0IiR^X2%%?@E59N{T zl2hzRLvf%Z92|hyN7mP~ZVS7rux=EdFi#iQ>3!_BRO1X) zdP0QdJB)9#@2Y9`Goj%{!HX~s1t_o4E4djC4Zju`nk(^#h@yR1MOvL5x`JC_pe*_J zkTJQ;QUE?WbPOTzmrbq;hIZM7U zcs;Q^aO8_lPABa0)~Z&=Ez$X{7%=}}V4ApEN|jpb~KU2e=6G2(#+&c!;Y1T>Z%$=Uf{ z_J6d1aUQywS$)l4o8dv}FJmWn465H4tGOo^hUsnObhB*E&ls~m7c&fG)M%buvi(uV zMS2|#P;6X^9}WFEq*6F)uJjHVKxzXNO*i;R*g!!k}htgs=?>9nPz@&?HP&clh!Q0}6)mio&Q!6$GaO`4A8yuvvhnVVP>}Ht_|zZ#nrhB$Y1A09Mw#j_8&F4GbjxYJqqzSKcwH)J{Rmf z7#dcj&todmTJ#j$G){O4jJDBkp=@Cr?BrH8SUj0+x)giq;!abF5`9l}D^=wO9AO!@ zhE|Q4a-B?TT7)ya?axi#Myakwpwjhy0HQT4*<&6i5t@bVS@$^kX8NnpR2`(F>jgeC5j&6-Yq8PJ_XW*l1z9H%9M*`Ow(A1H5>4d+J{#t zDW=Ds*M$cqEI{9S9^S^S>RIXWR&pgQzaCTlfDE#oBTVI9ADT!_TORP3%5a;bkxPqb%z)QO4JZ@=@=Pu2SRFnfsR}jbIg!;;mXr$HpD-& zea4W%7Spsz*`XX~4o@&$54%jJ3gjujkl0W~FXucmh{GFcJ6F6*r=NbfwX+BHL*0-y z?nt9s^*<;as5ROn59kjNYsl=LUxo0mX4aY$jU?~Xse4G^-kco55>dXJ)P%d>6myfW zkNt6vqL%U2#d-B^T2uRjwYfQma>4WKGl5!(S9+~+%82Z!IAf~Z$&6MZ*bj#)GYzrg zJKO3yO?e1gk0o3E-tr8gRypHhwA9!*tumJMezAr=33JBtbAHpZj#GhYC*tKkp?)z< zlV&+Jwet7_6_pfM1%2Y`Sce?y=IB26YI3ZxJm;7NN#}Kg1N1eLVc=3awS&sh*qFG& zZwD%ucrADWZ)Qj*e9Gdjh@tt z5d^JN{KG$B&kkEUiP)8fN5`37n@f`DN%S_n?NcWBUU7;hj&J`=Vx;w=W@;B&o3y7h zluk?ZZdjH{gozZ)7bz#o1#>Fd3h_x4&52351=+ODZi|%<(hpe{!beG(4uu&OcwYP) zM0spJv~_`az{>Y|Hr!}6$qqW_N-fE@?=2HamB#?k2Z{WuI!f)dU|5eEUi(3rf;HV9 zp#c4#s6q+=!9r5CNWxBElky?Pm1r(0hS?ePyAo-GhmGry73y;_OPykLwL|pd(&ts% z5r=rdD+*1@*p2=VxCJM7!f~lvT=IcvgZ>?o?B499bI0pL8G5^$gxvf0UtYyDAxVVw zFJb(O@JEkqY85;AEAo~4uA@aA+Gkpbd&=`J2T$UbL1Jdj*=iQ7C+LT1tjGLq(M+`s zktopl%zdhys+XLuuXha^#T#v|Wz6k`&>J-E)YpouUEU4}6492QvB|W^XO4PW`IM63 za|X*oI5H)#h26OC&UM14ftX!xeh#h8Q8h7!duSrTts%~hBidx!Z4e*BpEmLb)lF<= zi%x<}(#5w-@g{6zKa~V++3#NU!avQGr@`QVGfF9eht7W)!`3OHGtzy*Ffd;htAFR& zl5A9@VA=_ROdq4SNVNTeu?MNIV)zk6nK=|nQR@ta&$*Voovr_^^=@mW@>(W`%h^DT z2EMF&jNzC-6m|&bSr}mWHNAGMs9s!XLFOjK1kz3=-o<@H!No_YYNTJqZHNMC6*hf8hP9b}8NA6S{ z{nq1w$E<}Ze~DhaRgoedmOU{=M`xty7WM&4y9oh)Sa+7;cJei=bmZt2IS}QxTRsmx z;Qd6BefGDdTm4;*l-JcS82tIphj~BR266bQ61o-zFJRQNuI!2sl^`Jx(ONvW-}(Tw zrOLhKBvVe+O}mhw4Q#*<)a#j{;@=uC70Bffu8i=a6v5vnaTtvvM*=-k5~jT<_mf`q zS^V7c9EfIcn=Gp^*UJFiIxf|V)SBp+G${hFb^GxcTIEGFI^S(29|}5RM$Rl|Q$p|c zQl<0`@*6e8YUL$Wh%sK5C-+-qZ>tYSw8-kjA4%#rLn_1b&_p;7N5&hz&#T$ntJvE| zE`J5-y!hg=i2_F+j~n;WzLxm9$~@Mh@GPWkC<^ffXZu4;I%C?ZfmuALHtkNy@uR_y z>rOfh2r*;m@k>g(;#WE$Q)4+yOw70UlZ2BS-OgdS9T*A#ASt@L45nC1v}XAERDIvf z^kbO1q9I&s#jHFIO3VXPl%dimD!b)moMVn=;;z?KdTJ$!gEyxq8J%DttwTtE#K1uN zf#_6uYDDu0{@NjK4VV3NRAQZcliPuS=>1%F#Dig4al&N*D=jgU1LEpLVN;+n}V?Vh^_h9 z0!_PjBK7?a@ft}-wo=p>96XWWMv;hmSSB4-hvI&_j_a$H+z7`e+gOV8Q^&W?t!t51 z{!#J!;QQNoY4J=YX8Ty?Sh1cHyfdHV`_D|DnDtCcU+%?hrZCtA_@1wGDzH? z3-Q!06(cp4#zCioM?XqeZE}yKDZUP`rf>_2 zsTsn#R_&vo2(!uE{r!I2A49fxOxLR!zjiV&?QBazIgcGjiLQWk zf5st$VjrPG=Lh?`Kvmyo@$IzeRFXUQ38?0ciAO}U4RMEeB*_UXGV|wa#3doUIK1|~ zy9+}FF-Oa_%{tb+RT}de!KiUy@R*_^c+#lB`|}{%D}>bOshV(pr(w>!a4A93Pn-8# z@`dXr)BJjMNgpIq``iM2949OXV__A5O_@Z5F`^0s!8RKp71YqfLfLQ#DelcBcwg6j z5Eq+`pmJd~LhC}c(v7f@5FTcqFZg^Zkz0qHMInPD>T`x;jH)-p75?DgBRD109_evF z5Q5T5{vNL&U~}$yS8iF6XE{XqN|0EX?2yW0SCI|hzs^_InMvJHz7U1J_PnKZkL-Tc zktY&S%Cj+jNiZHx?7kDzk-IeIwgfN$EazO%!}nlx9TE*DQTZl*E##vO6DTo6qs zkGfszy6JogYQY^xeq3XGoaVXd#D8pBJRxCZ|02|9fZB+XOVRWm+6Z`fNdCA&9vUC! zm6D>iY|n07pU5R@UAcJ8WtR+`wml#4#h9hl8-VTXR8g)nhkH0;=8w@3G((gF3^;(s z64t^>S7caWbP^Yo2R`vlh&&k4v|Ts|+rgqR>t!D`uWYkD3F~n<9yB$4ZHj4D{?c}` z$GU04pt-WGfv)ZzUJKHgvc3XK-Ap)J$mg|3on)Wc^6dq!ikHSS__Z)zQpCtZh=4`*tsY?Mu`OO8Pniz*algNS zYnek{+*hr(Bd6b9qOfY&TGNp za-*5m-VcUN4ZP4&2=9?}f(574AVi%Gbe0%hzZ%|}?9Tg5Vjgp5a7|hV+1}lu7l7(p zs#>C>Q*G}W>4v;d1=cz<2ak&-P9iZwlWE}K2@oVqiP|&IiNZ^nh+H*IPfzPSpAJYi zNlNFGAXz0Z-IXrgEfMyXv-%S>se1u{Db;92&D+c*8+)nb8ZanwYYxh^Fu%H=`zUmp zdB1%pAn0<^M(Z;gL=a!hs*-sbokRw#8-wX0SK~Cft+-<&Vx^l7cDUFyo${wwmlFhv8m_J(bB^NFx!IfTQTI4}ZGbDN`G8=-b^$VF5q?bb` zg+FX{0h}PFt>rKLmR3RTcSr_qqt*jI;mH~$!QmX#pJO%TvYPIMap9zzvuO3A##}-> zJrC!oPkHU7^SvH%5GVP-s4q@Msoim7OOFk^$w(6+!=|yDV1f1s5G=%vcDs1n;p-iX zL8IZ*dLVU=Ecm_AO0WCqsOIQ64S3TDAcp<+6VoFX-T>DN)RGBo8E)0tLGK#C;#h)+ zW0?&fcc3@=aK6;x0h0)U#ei<3DEw0&&7v@~WtMORKeO%4ZPTzzc7&PRETn&_g3Qd$ zeH^*5AGi^hFLnUGLC1hA-{4d?}20t)f;;-h5K49z&l|$kr@pmIl?F z(xPNZ#Gv7ewbasY`{}&BxN_=k_wia2<2(@BOmIqTt!^w3wlr<^2W=?TO|T*$#I(Lp zU~7g#)+#DL|CCiUnSvq38R-?jG}6PYhJ{W~JASoFbH$k0;W#6oJ}dw-J3QFQ$k1rI z68n6fMdOBUlYy|h9JC?i62}@#!^hy0&IT#VxcKL%c!Z+Kh zxmSH8t}xPK%NfI0Ec?5r>SPuT8ofF=V^`d4ugOkXGv@&^f7i92zIV&hcH0^Zk#UMLDt}6F+Ov}o{3yxbl}P)u)P3QklPdK?=gcZ9cMcFAxY}pk>}hj5$TaCOMB_X zM?E!{xO_^oEy=y*xS*$%P_p#nzuKSZZ4YSRrnUSM#99|TUck-i@50GfIl}_74>-r| zW{8{m%Di@$ZhuO>xv`;=hW=QW?2*~Jt=XY^DI~DjLYC)Tcmdm zfoLPyUsQ89Ru-0j%ZD{bkr_0OFCyLhlrMfg)v-`N`)jPqRFZs86+Ndwy2r`a>sZ0D zrn{2|$L!TzKLctk$=A(E1l)M`D@UY%RtKVJ%Yzh;Jb*c zyZkp@xvFLkG`B>HdyTT5uh&qUUpG_6GeQP&C%byJl!NoK&c(!jh}FOgeS`;0s#1mE zZPgv-JftKvWfuy&Dq@B$E#bDg6@2AJ30z(0Vr-eerdr!{_+rvd2R)Q$g{EtnVR_V7 zJ6_%~G>yeytk=~HAB-`j$!~%3PTp~$p5~0Y-{Iy92>Wr}9HOY}*!g5?{wP~_p$h$0 zy^`F2{@_1v@c3jqI+lmy%M{>s^OV7(OeNr@+(utx9wJG zz1}Ak8R5>G?X`B)iSg0g$@!sIv_`d@1%nA6Bs zgZw-BA#7mQ=kJWa1#y@{L(pET^}8k3j~}tj)sN7KeP=yS?~PANzU=8Ed`4(7(%uYf z!k##_{-0SCB_D6KF|f+wQsmn1)2X%Xk10IAzC8Ugtv009{_wkn6zIVD+3@r*Wb4Ng`~k<* zkvA_=!RVEQFEc*KLH1?^Y@>Fna|{?C*Q*f^7%DsIv2?sq1eh&9w-s73;a8#UekI}3 zuU#%*cnkD^chY;li(d~l_Z8~y`qZ)~z)CL~Yc1Z9T!hpd zmUU~))Uef5uVaa+)(D~}g@-sO#!&6f7^RaRA_}4POC$$(JSxD$q8O6UvQ4BpsA7Hn zr3xB8b&kw;{E$66nVUQ6>vObO|2f1H4L^iFL~MZ&)2#bG1m+GQvn#*iRg`cle{8KM zb9jP(Uws=C_sp*JJ@Z6LF5DTdm_1>stbi!|4SIjbu8UEv8Rb8moDxf@NnJoV|Ac%< zQJmaqPvY3FY(?2gNSSV(PWRKOQWSDS%PlG=fcCODZXi|Y*f=74jw6;!Z^E1F=0v3l zOTA-xgK4Z)LxMD=v5V{s#=0JAV*cgr0eeh5as~^MU+K9fbY}80lRERL<&8dA zT%e}tb#D26z>!lO_Wd2oj-t(Jqdm*b|M`4ivcb%QvT+CM5{)N8noA#jmEktsn1xK! z8SU_ChP>N%jc7#)Oh<0MyCrL7{7#ry;jj(l1Y1ls|4uoDpwcV1mNu-jBuY zV4YZmXRGB*OfqC3^1rru_AygQ5~Btj23ZrT08~w{>S7!q4gyrkXE95qjWn#<4eO+Z zH)N*=*rqo&aIXp*+Nm!$D`}Z?9))mN-qW}k$9zKV&W~VYE!_%@gF5QW7RJxx&Xh>3 z-xOxIs0(0A%SlTB^^*Z30v1N_C*t>Cfy=a_&CVDRWNmdJTtb{s(=k^9)7-1mq&&H9 z_PMM3BF(i|Z|}rcRNt$s50H=_I{B{@Q#p6U@fADkLk!4TUMrCIIeevN7sQS~_ror! zbn5w9EBthpB&++)ERHN;16{!(v353$5!1H6qK*LeQ)H(vYW=3);Kw9jOpo5=$j zL0Q4C{mp@@EaJB@=Tn1RrUGP%?ypMQ5nf8758C~bCFNcf z18~pzDpq1-q$pZ+%)LBDG+GuzwoK_P)R_=Xg8{$KrtXsDLOxck1LyvYM@T{1$*~=T>!VyTGLxY zV~FlB7E4a!ymFrFoZ{8h$lB(p(&FKl6i11(!eU>3_$2De6{vmw($cn&;{?r@UjxnS5)rOhcoQ56$qS zBc5nOo8^N`k4S?!Krhma3a!9YMnwM;1o(dzk$U(m5QlwMx6;;qaes?c4CGPl^qRGX zbaJ*HX5Ux5l|+E&l|S4t0P}e0SAuIib2d2@kjUUrd86uqs-_(M=qQ7Cw;(V7Kv~GA z`38DQ+&B14xuOgAU}p=WvzD>Iz>@oF^sTqq33CtUHxs<}VqklNvAM&T`t8Pu+wV4Oa*mXVyS;I-Ua!7rHm`;C zF7}znB4!W(G2-w8w_vwm3sq1&{wU2}`?A1bpm1>rHML*U2Mx2DIT=1?Lf(pii5fQ~ zB*fE;&e>%$&iO|4y791aJAzzjdr)sgVjXC=Pm67khzoyRAl%9WL;4`pzJJqdQZ#)~ z#K@SHL$ZgZJd8N;3L$Dq_Po+{Cg%NM@x7~cPWWWxlJ^pE=hf34z#S?08*D|BRlj04 zvl)DK(SX2govkeHqo$_nde~wBl&vIVfX)GDrO8C(7DG+O;Y($SS@#Pb6^%}gQm5oV z4qoP2tJGP{jdkL#G|oGQ@!^e-hh4!s0=ls}0yqgq9taWaofz#p!4~Q9N!eMd$>pJY-hVj&Dms=@p`Nt}R z$1n;C6XgzD97i0skO_#w>wAoexB0gh66F^^N;g`>aI;uC`xiLm@X;0`$FyZgh@1Rn zf2@KN(ZDB^XE0g$gTBD6>Bvqbci2VuGnZWwV#*g9Ha&7lOgd0k!N3Db75G8bc`-`O z^#gaQ)mrl5K>JM@?Nh(6=JVzQ$_|_7s%gO=%%}J}baqSom@e$ACM*m%X*jCCzJ zOkjN}UCb>ht&o^b{{9&g(-M8g#I&Wfq2iwi;P}!<<}rp)XF3a_$DpvAvv*|qhT?dd z`ZXkg&0JT=_Kk`m0#asi0pvQR{u{4@BQ}5b_52CoNuUkOi;-(F&0o#(p|QqQ=6Mc2 z0=V*?V1&lvUcvn3IofLR`)fJHK}&c?Rz@PxQ)B~qJtoUhFK(Zn@I{oFJKb33K(FAy zVR=IuqmnV_4AR^9*o-yFdNUuxdkC5Tx|tHMRtG21+(Ds`#4MesT#eO1f^w$q>{o0U z-J#Oek9a!JwG1?KJ!*#bjus?Re`oJA%kB-hFzpKiGe3q`yq7j=C*qsF{4oY@I~tXM zLup8i<%<{*W4h&SgznBlAAAR zt!B>Am}|LC)%D;s#HZQ46~Y9LOjdE0*~FJ_gMJxCoVP`aT0MAi>_=8TcI)>EeE*Fk zU-2q$bsQvx9ksXGRsa-}iOju*Rur~X(M{dUV?Kz6R$#mXv+HLm?U>k^Gd{u$ebBwV zMkb+Qsvea{)N~R0>ttGVFa)RHha)w>ax>d#OWAh{Z&7{UvtpFovoei3jvneT-a$&? zqLq+=bv(-jKFbPBlIE?y;1Q*{t7-ph{ z`?X!cad&bcner|vlpq$yh4rcI4k^ufoekmoC*HTzmY@&VLVXL%ttUZu1KB!3JoS~T z2Okwwl^nXj8RWEP>y(S1o|m0u^M@I}g2ykMe2)`doQ)yJmmX{o@|mtOYMxWk)Q@3y zMA%;ET|Rp6e(o{h=!afr5f6lXl6M=E$AN-)+RA^HwSo?j;+5*ZC4E+)U>tiqm1K5n=S>&)PJ zC*?i<0Yhmigbb?CXUPvb-6!(^k4|ye_mju~O9En3tmQASYGZJ|7BcjmCAeh()jl}o z;DjEED#C$9xe9pKh(%&o(h$N@-eO-g?D||C0Hxq=$jJ&LEY;}3&27Qz0j3gm!35Xx ze6q3|lHAy{&5sYO-0U{Jv@PW1#JqxOCKK(Dxg;>`{$z_=DHy%o?n65;>s$C}JgZyM z;0fs##n?kNbK=;TeP9iIk=imAuY$fH-G)odm`eK;ngpi;9yEQVftmyA0LiQu*h@RC zJ*(%0=_0gx{Z?r71(YO^EM?W-9~uI{>9B^yv$38mlxRaV(s{KH4ska2d&|oLK&UrTCh>Y4ie>K zrtRFO1n3S4k+`ay@qC$A*T!!ldl>c#;W9HdCucHJYGtdwk)_E_T>hv`cOWBP`g~`yr7MEbJyG1f1V;SLf z3boSBH>xmJnaX&2>PW86T}Wr@c8kg8(4eQ!D?N}FXsqAP24yTMO~A`sjG0y6dce{e z+I=~4LyUBv+q~CP^U3Lyp9F6M{OrzQHkRFC7CqHmZ5)_!DOh}&roA~XP_Ze7dO&)W z_q_G8op~;6rNX;s$Qm$gSr%!o_OmrkP{nC1n#_EDV%ZQ~Oj)q%E@WS;ikm~f1;2)?wbPt!hMUA%PEOh+!}{qv zGrqVW(Dmj2C>-@YEYyH2u!C^jgiAEE#(LecLDV;iaXNwpZy_klYVCI~2Az^ePRjtk#+WQTu8ju!+E@{QJ?$t98a$k?iZfTsye%xR~3DO@wc zFw|$=Q?~WrhkL&~3c?%~)_o7aShzWP9_@TsgASvNp*602C?(>qPLjwzNsa+5R3c775L&`vBQBs-@Hk1$7UDFGztoo6)=49{FA0*pDO zEDZSY*VI#&zcVjTt|Jdt06kBGh=-deI7(4Wff&S!S$+`!FL2-IYcU@v9%0qC>vpwl->h8I>p z05KW|7n6LW(w4j3qlYv~+RY%ZLf%{n_vK~WZ^)17jf)4VEkm;87i}H6^?ZnNNs^tk zmhCh{_qmPjTb&qU^_v!alT6?B$hCBL3@Rx)sgzg?jdI=_@ap4fvn{zyhqz|oj@*kMSV{X49$fkl zkzH*V3Bhik6f5N{&;RIkWmxN`PhHFx8jRW)AEu-4Gu~`k2&r`6;Wdy$T?>#ty)Gqa#H9y zrzAP584(0K_Huyn(OTrUP5qdrc>6SVRpT+@+P-R|+_r2S$^GV;eTEJPtZ_Kl-3V#= z-RSpR(r+c1eh{qmw*hS$aI~b`VYC&E+*g6&i=$gZyfz)Ie!^0ujbi~GDkd`|c0$Dt zcGcQ7A;!CRMXrV`&u27N0d&u6XnDev@C10*CXeW~C3C0EW)}&Dpu+^cO}n!)bIacD zDN&;&{42uf=NP?U2l?UnIsGMR6*g3 zcPP}t)Fo%Fw_7OkBt9HcHF&j=&gN}izUl=g~E&EDReLFgw)vVEK>OKn@ z9?HMXCkF6DyynHxYjEM9?xI(~0hHP*Ak!JQ=>-|K6;wUNU5dW$#Vs}^IekMqy?(c_ zP9i~%qe${mIF<8;CIz1v$wG(jcC zuWpWXzgynyE3Y(~_7BOCYwJMav2K2t%4U$g&DkUEAt2t-5jAOItpteJCm|}@qQ5L0 zt#e7|x~Sg9CQ7Eu5ml;Q-y7F{^a12ff)MoL zp{o;LcdX-lNiqPj?eG?ZD_nWufV7ef@<|x330c1qz;E!URLe4I+9Rs>yjBjp%4UW70`<^6=U0=b%`!0zU($4#d>1NO~0qMT5 z0qlO9!v+2ADVf^~p#{LP9a~pVakp2}Ch2LAJN`SUwq9%W#5LV7J5~v z_9-@LtL;N{NoRY;TzOw0$~djypCPjpG_85w+p{zgGE0KoMeZvo6%FBq?6h^o(ZvW@ z#9W;s!bPfVrGsjmkO+vWF9eMkKj#^9mS}USX~KOfHg-~D(Db_Hcy~OuB3#CbxjFtR zF)ch{Do|5J)%cemZ27(@|I?ojLmwR(#`9|4vCAqS}79 zO``sBef%`Gwk!fGu#M-?m*&Q%y5)NQQ}u(CT-^=FGL0U5wlF{WJa3caK5M@YrZ82t z-Mv!04N)F!GoHfG?ZRos6OmI_CH&X5h3(lBdfy-!W-90_c-Xs1{Hxg=IJW<{etLn3 zd!wrhaI40Ce=N)H@OTOx2l-6<991xyLnWIBocajOo}i?P!NLP~h+2|$^s>AYdR0lu zLmS>UzmW83Zxe=CM6doAN2`_}$^L#0lIwdkz-DQL+`@D%U&~R=U}4tZSG2A=oJ%p> zG?3&hQ%UUIytZI9xi2FY5@rp2Sc7I+0qvK8;tVn%n+cu|5z)s1*H^(z@~=9OO#P@B-M!#b_V7n=<+2k?W%x>OB!&`yYzsFDyquFI zel_O`x=qIm{-zs4Yl0a|gsUI2FD}&?*;yBIoib&!C@ISOyD02R__Jng`xZ0!Qf8sh zb06va=`MwN>fxQ_?(R+UE=*u7jT4dr@h86YDn;AfYi-kmo?R6S)sQ$;XPMBBiIr<5 zw=3sKiz%_MQOak*iRfj*jh3_!4=4Mo^tG69t0n=J3eX)?ze{R<9Lp-ure?(N$MhylN zVg`=P7IFgA-u)4X2LwRWB`N$1LkkIWx)O$UqVi@@U^MO9RWLxXeCl=M$ z*-I$qJ;c?#YPL&o7?2uVhM$|2JRtmO-mT@}mf3uWSWy(BO7@4R1X8{`FpM>%0 ztf8NO)cx;Gi1U;BFYJMg5A=`i|E2da{=>1y{FVaoSB`yG5!q)BsmKZh>>ua)w;loR z&#?r4gtPzH=ihqt`#(|my`(qLU%ue;uN>abB1U5weawIS=imC=j8B#1c0eKgoovtZ z^Uv7?aG?_agbx3vJMB5-&y4XLGcd@%iT}^_V2D4*Iq=4q{M}yRuAeH2dLiWj`Zw|a zxjy~pfP$ao^l<-Tsedr+BP;!>lK-1d*Y_!)2hi5>Mc^PB7b*2u|3ye=Sh4>MA^&1! zqBJ0jf%acjSd5Ti*K@OzIe8-_0SyEUPRLAU0D8H&7w_u-MWgkHk~6Kj?Fn$Yu6Fx? zQ=5_BAGQBOw)*!5s{^9<%?4KIxuV7xXX$lFpo{{FM#$6Q$HKTYG@i)gIZI$a@}*FM zC+^N-hE~{!g9(AV%imAv&@Mte>YY2wCVfdrD!krmq#SS*cH~;%vSwe=a)WX?b>hjq z?C|Vvw9!&Rl-qSp8U{R^23D^d)`CZE+3K7$Ep2(W{Z%8xw8T)>fo?;lCfyCJ^40T3 zij!1UU5WV=hDVWX=E^_OT0P#M>(7;w5{+Mitm_pld*?{h0xCi0(}QTaxdeDL|1f?3 zy`ldC8~FOe1X5o4QLSjcu*!%nh6rm_krin+x}BtT5~|_`jI# zA2;Jq_{0iF7INXi*D=5oZeLtssTZg}2}b2!~$g{#xc&LESP z>lWs}O(!V^D;sD8E8PklGkb`3Ey#~aq=hSxUmGYPmC}yEzqNmsb^hxpqy6QR&dIqm zVx6lk1+s8@+9G~EGI5XZAqIvA(_~gfr~{lvSka;y@moR2@vCL}QowheuFBY%y8T~U zdx`xsC~SXw_~)!f|5;AD5ws#&wX9wHit6~-kDPS1HP_6twzh62_F3d}i@7@QO)ZRj z!j5vbT92dFaK@KE6Y@X!ddsLdx2$V4NRZ&}4uJ%Bm*B3!9fA`yxVuAecMtARxD*oH z-7P@j?qB8f+jrbPC*5Q411LtZYd>o*nRBkS!=Gd+kml#8xE4UnV+a4kFu*V2EEgKCU6y~Qgpm!u-7uKb;rk`S zR##6fMyv8jK~^N2>VLUEe*591*?#*0Gt2L{4i?65IGNEHq|xC+gx4^n{JkrYO32IJ zQ+iqcG--%{#Avqi1 zufPhoRiH;uGoI7G-4=4%CmSKz=s7x35tFR0xR%}>tODjbSv<$tjifNUUQJ3cyIt&r zZYyL9WQsS1S}>EsMb%C*4T5-b!Ne7;7|AdvkrLtq_? zJb|DhldKJrp*6@}V2*bqGHNw)?~Tu~u*&r*Qg*s+nnCmbG%LSJ@r`_$cWY%)p6{P# z_e26bLh)1Bz5+Ju4n9!xM6ym#dKRFj|M1+u0j5-r+nhS*7q;MyZdFnI()+<{x1o`q z`u*gPE7-PTF+Y3rH>khz?SFWDaOW+x-GSFzAFl(Jm->5(&nrjW<2Gj-cgg&?6fa02 z0Wi0y4GEs>^WSqH6U)2%uy~c`SICkA&vWuC&_WPuJ!Qts{c1@%NggbVif_1>oR-*R zF?_}|X5_BVHBw?ri$mpH6#B;k*v%bXBG`cM%bj3rjJ8(KD&_No(bKEF$+-Iftl}U4 z_Y%*jCYxtDToS#_6)IXjz`-No_1q``Fbr-ut0(XLp?c`R@ zQbvgp`H@b#OYSPE7}uj6XPdUp-&&2N?kAmb>?ECZbE=Ou;SLxNq*_(Q)CDT^07Q

*E8x7kSmx6}_UCQD7JF^yrS@CqZ9hsw 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

Qi67gN39F)5?M<>? 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 literal 0 HcmV?d00001 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/ruby-absolute.png b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/images/tvar/ruby-absolute.png new file mode 100644 index 0000000000000000000000000000000000000000..9639541ed05391b90dc977da336b1d6f71e996f3 GIT binary patch literal 102338 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%0D=X8UK+`|-nm_SkU3W&EXd=5u$$VUD>%h< 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 literal 0 HcmV?d00001 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/logo/concurrent-ruby-logo-200x200.png b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/logo/concurrent-ruby-logo-200x200.png new file mode 100644 index 0000000000000000000000000000000000000000..cf992cc40be615882d8c92cc8042cf065e703c89 GIT binary patch literal 26508 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 I>zopr03sFY>Hq)$ literal 0 HcmV?d00001 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.in.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.in.rb new file mode 100644 index 0000000000..1c38f9f49d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.in.rb @@ -0,0 +1,273 @@ +require 'concurrent-edge' + +# This little bit more complicated commented example aims to +# demonstrate some of the capabilities of concurrent-ruby new abstractions. +# +# It is a concurrent processing pipeline which on one side has several web crawlers. +# They are searching the web for data and filling buffer. +# On the other side there are data processors which are pop the data from buffer. +# They are processing the data and storing results into a DB +# which has limited concurrency level. +# Some of the parts like Web and DB are just stubs. +# Each part logs and increments counters to keep some stats about the pipeline. +# There is also a periodical readout of the stats into log scheduled. +# +# Schema of the pipeline: +# +# web-crawlers -> buffer -> data-processing -> DB +# \____________________________\_____\___> logging + +# TODO (pitr-ch 10-Mar-2019): replace with a better more realistic example using +# * actors for limited concurrency with state - local DB connection +# * throttled futures for REST API - limiting server load + +# The central logger is defined first. +# It has state like the logger instance, therefore the actor is used. +# It is better to exactly define the communication protocol of the logging actor. +# It will only understand these messages. +Log = Concurrent::ImmutableStruct.new :severity, :message +SetLevel = Concurrent::ImmutableStruct.new :level + +require 'logger' +require 'stringio' + +# Including actor constants so this scope understands ANY etc. +include Concurrent::ErlangActor::EnvironmentConstants +# The logger does not need a dedicated thread, let's use a pool. +LOGGING = Concurrent::ErlangActor.spawn Logger::FATAL, + type: :on_pool, + name: 'logger' do |level| + # a Logger instance with nicer formatting is created + @logger = Logger.new($captured_out) + @logger.level = level + @logger.formatter = lambda do |severity, datetime, progname, msg| + formatted_message = case msg + when String + msg + when Exception + format "%s (%s)\n%s", + msg.message, msg.class, (msg.backtrace || []).join("\n") + else + msg.inspect + end + format "[%s] %5s -- %s: %s\n", + datetime.strftime('%Y-%m-%d %H:%M:%S.%L'), + severity, + progname, + formatted_message + end + + # definition of the logging actor behaviour + receive( + # log messages + on(Log) { |message| @logger.log message.severity, message.message }, + # change level + on(SetLevel) { |message| @logger.level = message.level }, + # It is a good practice to read and log bad messages, + # otherwise they would accumulate in the inbox. + on(ANY) { |message| @logger.error bad_message: message }, + # The logger has static behaviour, therefore keep can be used, and the actor + # will behave the same with each message received as defined below. + keep: true) +end + +# testing the logger works as expected +LOGGING.tell Log[Logger::FATAL, :tornado] +LOGGING.tell Log[Logger::INFO, :wind] +LOGGING.tell SetLevel[Logger::DEBUG] +LOGGING.tell Log[Logger::INFO, :breeze] + +sleep 0.05 # the logging is asynchronous, we need to wait a bit until it's written +get_captured_output + +# the logging could be wrapped in a method +def log(severity, message) + LOGGING.tell Log[severity, message] + true +end + +include Logger::Severity +log INFO, 'alive' +sleep 0.05 +get_captured_output + + +# The stub which will represent the web +module Web + @counter = Concurrent::AtomicFixnum.new + + def self.search + sleep 0.01 + @counter.increment.to_s(16) + end +end # + +# The cancellation which will be used to cancel the whole processing pipeline. +@cancellation, origin = Concurrent::Cancellation.new + +# Buffer for work +buffer_capacity = 10 +@buffer = Concurrent::Promises::Channel.new buffer_capacity +web_crawler_count = 4 + +# Track the number of data provided by each crawler +crawler_data_counter = Array.new(web_crawler_count) do |i| + # this is accessed by multiple threads so it should be a tread-safe counter + Concurrent::AtomicFixnum.new +end # +# the array is frozen which makes it immutable, +# therefore safe to use when concurrently accessed. +# Otherwise if it was being modified it wound has to be Concurrent::Array to make it safe. +crawler_data_counter.freeze + +# The web crawlers are defined directly with threads to start the example simply. +# They search the web and immediately as they find something they push +# the data into the buffer. +# The push will block if the buffer is full, +# regulating how fast is the work being found. +# This is called backpressure. +crawlers = Array.new web_crawler_count do |i| + Thread.new do + while true + # crawl the web until cancelled + break if @cancellation.canceled? + # will block and slow down the crawler if the buffer is full + data = Web.search + until @buffer.push data, 0.1 + # It is a good practice to use timeouts on all blocking operations + # If the pipeline is cancelled and the data-processors finish + # before taking data from buffer a crawler could get stack on this push. + break if @cancellation.canceled? + end + # it pushed data, increment its counter + crawler_data_counter[i].increment + log DEBUG, "crawler #{i} found #{data}" + end + end +end.freeze + +# So far only the crawlers looking for data are defined +# pushing data into the buffer. +# The data processing definition follows. +# Threads are not used again directly but rather the data processing +# is defined using Futures. +# Even though that makes the definition more complicated +# it has a big advantage that data processors will not require a Thread each +# but they will share and run on a Thread pool. +# That removes an important limitation of the total number of threads process can have, +# which can be an issue in larger systems. +# This example would be fine with using the Threads +# however it would not demonstrate the more advanced usage then. + +# The data processing stores results in a DB, +# therefore the stub definition of a database precedes the data processing. +module DB + @data = Concurrent::Map.new + + # increment a counter for char + def self.add(char, count) + @data.compute char do |old| + (old || 0) + count + end + true + end + + # return the stored data as Hash + def self.data + @data.each_pair.reduce({}) { |h, (k, v)| h.update k => v } + end +end + +# Lets assume that instead having this DB +# we have limited number of connections +# and therefore there is a limit on +# how many threads can communicate with the DB at the same time. +# The throttle is created to limit the number of concurrent access to DB. +@db_throttle = Concurrent::Throttle.new 4 + +# The data processing definition follows +data_processing_count = 20 # this could actually be thousands if required + +# track the number of data received by data processors +@data_processing_counters = Array.new data_processing_count do + Concurrent::AtomicFixnum.new +end.freeze + +def data_processing(i) + # pop_op returns a future which is fulfilled with a message from buffer + # when a message is valuable. + @buffer.pop_op.then_on(:fast) do |data| + # then we process the message on :fast pool since this has no blocking + log DEBUG, "data-processor #{i} got #{data}" + @data_processing_counters[i].increment + sleep 0.1 # simulate it actually doing something which take some time + # find the most frequent char + data.chars. + group_by { |v| v }. + map { |ch, arr| [ch, arr.size] }. + max_by { |ch, size| size } + end.then_on(@db_throttle.on(:io)) do |char, count| + # the db access has to be limited therefore the db_throttle is used + # DBs use io therefore this part is executed on global thread pool wor :io + DB.add char, count + end.then_on(:fast) do |_| + # last section executes back on :fast executor + # checks if it was cancelled + # if not then it calls itself recursively + # which in combination with #run will turn this into infinite data processing + # (until cancelled) + # The #run will keep flatting to the inner future as long the value is a future. + if @cancellation.canceled? + # return something else then future, #run will stop executing + :done + else + # continue running with a future returned by data_processing + data_processing i + end + end +end # + +# create the data processors +data_processors = Array.new data_processing_count do |i| + data_processing(i).run +end + +# Some statics are collected in crawler_data_counter +# and @data_processing_counters. +# Schedule a periodical readout to a log. +def readout(crawler_data_counter) + # schedule readout in 0.4 sec or on cancellation + (@cancellation.origin | Concurrent::Promises.schedule(0.4)).then do + log INFO, + "\ncrawlers found: #{crawler_data_counter.map(&:value).join(', ')}\n" + + "data processors consumed: #{@data_processing_counters.map(&:value).join(', ')}" + end.then do + # reschedule if not cancelled + readout crawler_data_counter unless @cancellation.canceled? + end +end + +# start the periodical readouts +readouts = readout(crawler_data_counter).run + +sleep 2 # let the whole processing pipeline work +# cancel everything +origin.resolve + +# wait for everything to stop +crawlers.each(&:join) +data_processors.each(&:wait!)[0..10] +readouts.wait! + +# terminate the logger +Concurrent::ErlangActor.terminate LOGGING, :cancelled +LOGGING.terminated.wait + +# inspect collected char frequencies +DB.data + +# see the logger output +get_captured_output + + + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.init.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.init.rb new file mode 100644 index 0000000000..67283a3adf --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.init.rb @@ -0,0 +1,13 @@ +$captured_out = [] + +def $captured_out.write(str) + push str +end + +def $captured_out.close +end + +def get_captured_output + size = $captured_out.size + $captured_out.shift(size).join +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.out.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.out.rb new file mode 100644 index 0000000000..aacc2942e9 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/medium-example.out.rb @@ -0,0 +1,881 @@ +require 'concurrent-edge' # => true + +# This little bit more complicated commented example aims to +# demonstrate some of the capabilities of concurrent-ruby new abstractions. + +# It is a concurrent processing pipeline which on one side has several web crawlers. +# They are searching the web for data and filling buffer. +# On the other side there are data processors which are pop the data from buffer. +# They are processing the data and storing results into a DB +# which has limited concurrency level. +# Some of the parts like Web and DB are just stubs. +# Each part logs and increments counters to keep some stats about the pipeline. +# There is also a periodical readout of the stats into log scheduled. + +# Schema of the pipeline: + +# web-crawlers -> buffer -> data-processing -> DB +# \____________________________\_____\___> logging + +# TODO (pitr-ch 10-Mar-2019): replace with a better more realistic example using +# * actors for limited concurrency with state - local DB connection +# * throttled futures for REST API - limiting server load + +# The central logger is defined first. +# It has state like the logger instance, therefore the actor is used. +# It is better to exactly define the communication protocol of the logging actor. +# It will only understand these messages. +Log = Concurrent::ImmutableStruct.new :severity, :message +# => Log +SetLevel = Concurrent::ImmutableStruct.new :level +# => SetLevel + +require 'logger' # => false +require 'stringio' # => false + +# Including actor constants so this scope understands ANY etc. +include Concurrent::ErlangActor::EnvironmentConstants +# => Object +# The logger does not need a dedicated thread, let's use a pool. +LOGGING = Concurrent::ErlangActor.spawn Logger::FATAL, + type: :on_pool, + name: 'logger' do |level| + # a Logger instance with nicer formatting is created + @logger = Logger.new($captured_out) + @logger.level = level + @logger.formatter = lambda do |severity, datetime, progname, msg| + formatted_message = case msg + when String + msg + when Exception + format "%s (%s)\n%s", + msg.message, msg.class, (msg.backtrace || []).join("\n") + else + msg.inspect + end + format "[%s] %5s -- %s: %s\n", + datetime.strftime('%Y-%m-%d %H:%M:%S.%L'), + severity, + progname, + formatted_message + end + + # definition of the logging actor behaviour + receive( + # log messages + on(Log) { |message| @logger.log message.severity, message.message }, + # change level + on(SetLevel) { |message| @logger.level = message.level }, + # It is a good practice to read and log bad messages, + # otherwise they would accumulate in the inbox. + on(ANY) { |message| @logger.error bad_message: message }, + # The logger has static behaviour, therefore keep can be used, and the actor + # will behave the same with each message received as defined below. + keep: true) +end +# => # + +# testing the logger works as expected +LOGGING.tell Log[Logger::FATAL, :tornado] +# => # +LOGGING.tell Log[Logger::INFO, :wind] +# => # +LOGGING.tell SetLevel[Logger::DEBUG] +# => # +LOGGING.tell Log[Logger::INFO, :breeze] +# => # + +sleep 0.05 # the logging is asynchronous, we need to wait a bit until it's written +get_captured_output +# => "[2020-01-26 16:21:45.815] FATAL -- : :tornado\n" + +# "[2020-01-26 16:21:45.816] INFO -- : :breeze\n" + +# the logging could be wrapped in a method +def log(severity, message) + LOGGING.tell Log[severity, message] + true +end # => :log + +include Logger::Severity # => Object +log INFO, 'alive' # => true +sleep 0.05 # => 0 +get_captured_output +# => "[2020-01-26 16:21:45.866] INFO -- : alive\n" + + +# The stub which will represent the web +module Web + @counter = Concurrent::AtomicFixnum.new + + def self.search + sleep 0.01 + @counter.increment.to_s(16) + end +end + +# The cancellation which will be used to cancel the whole processing pipeline. +@cancellation, origin = Concurrent::Cancellation.new +# => # + +# Buffer for work +buffer_capacity = 10 # => 10 +@buffer = Concurrent::Promises::Channel.new buffer_capacity +# => # +web_crawler_count = 4 # => 4 + +# Track the number of data provided by each crawler +crawler_data_counter = Array.new(web_crawler_count) do |i| + # this is accessed by multiple threads so it should be a tread-safe counter + Concurrent::AtomicFixnum.new +end +# the array is frozen which makes it immutable, +# therefore safe to use when concurrently accessed. +# Otherwise if it was being modified it wound has to be Concurrent::Array to make it safe. +crawler_data_counter.freeze +# => [#, +# #, +# #, +# #] + +# The web crawlers are defined directly with threads to start the example simply. +# They search the web and immediately as they find something they push +# the data into the buffer. +# The push will block if the buffer is full, +# regulating how fast is the work being found. +# This is called backpressure. +crawlers = Array.new web_crawler_count do |i| + Thread.new do + while true + # crawl the web until cancelled + break if @cancellation.canceled? + # will block and slow down the crawler if the buffer is full + data = Web.search + until @buffer.push data, 0.1 + # It is a good practice to use timeouts on all blocking operations + # If the pipeline is cancelled and the data-processors finish + # before taking data from buffer a crawler could get stack on this push. + break if @cancellation.canceled? + end + # it pushed data, increment its counter + crawler_data_counter[i].increment + log DEBUG, "crawler #{i} found #{data}" + end + end +end.freeze +# => [#, +# #, +# #, +# #] + +# So far only the crawlers looking for data are defined +# pushing data into the buffer. +# The data processing definition follows. +# Threads are not used again directly but rather the data processing +# is defined using Futures. +# Even though that makes the definition more complicated +# it has a big advantage that data processors will not require a Thread each +# but they will share and run on a Thread pool. +# That removes an important limitation of the total number of threads process can have, +# which can be an issue in larger systems. +# This example would be fine with using the Threads +# however it would not demonstrate the more advanced usage then. + +# The data processing stores results in a DB, +# therefore the stub definition of a database precedes the data processing. +module DB + @data = Concurrent::Map.new + + # increment a counter for char + def self.add(char, count) + @data.compute char do |old| + (old || 0) + count + end + true + end + + # return the stored data as Hash + def self.data + @data.each_pair.reduce({}) { |h, (k, v)| h.update k => v } + end +end # => :data + +# Lets assume that instead having this DB +# we have limited number of connections +# and therefore there is a limit on +# how many threads can communicate with the DB at the same time. +# The throttle is created to limit the number of concurrent access to DB. +@db_throttle = Concurrent::Throttle.new 4 +# => # + +# The data processing definition follows +data_processing_count = 20 # this could actually be thousands if required + +# track the number of data received by data processors +@data_processing_counters = Array.new data_processing_count do + Concurrent::AtomicFixnum.new +end.freeze +# => [#, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #] + +def data_processing(i) + # pop_op returns a future which is fulfilled with a message from buffer + # when a message is valuable. + @buffer.pop_op.then_on(:fast) do |data| + # then we process the message on :fast pool since this has no blocking + log DEBUG, "data-processor #{i} got #{data}" + @data_processing_counters[i].increment + sleep 0.1 # simulate it actually doing something which take some time + # find the most frequent char + data.chars. + group_by { |v| v }. + map { |ch, arr| [ch, arr.size] }. + max_by { |ch, size| size } + end.then_on(@db_throttle.on(:io)) do |char, count| + # the db access has to be limited therefore the db_throttle is used + # DBs use io therefore this part is executed on global thread pool wor :io + DB.add char, count + end.then_on(:fast) do |_| + # last section executes back on :fast executor + # checks if it was cancelled + # if not then it calls itself recursively + # which in combination with #run will turn this into infinite data processing + # (until cancelled) + # The #run will keep flatting to the inner future as long the value is a future. + if @cancellation.canceled? + # return something else then future, #run will stop executing + :done + else + # continue running with a future returned by data_processing + data_processing i + end + end +end + +# create the data processors +data_processors = Array.new data_processing_count do |i| + data_processing(i).run +end +# => [#, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #] + +# Some statics are collected in crawler_data_counter +# and @data_processing_counters. +# Schedule a periodical readout to a log. +def readout(crawler_data_counter) + # schedule readout in 0.4 sec or on cancellation + (@cancellation.origin | Concurrent::Promises.schedule(0.4)).then do + log INFO, + "\ncrawlers found: #{crawler_data_counter.map(&:value).join(', ')}\n" + + "data processors consumed: #{@data_processing_counters.map(&:value).join(', ')}" + end.then do + # reschedule if not cancelled + readout crawler_data_counter unless @cancellation.canceled? + end +end # => :readout + +# start the periodical readouts +readouts = readout(crawler_data_counter).run +# => # + +sleep 2 # let the whole processing pipeline work +# cancel everything +origin.resolve +# => # + +# wait for everything to stop +crawlers.each(&:join) +# => [#, +# #, +# #, +# #] +data_processors.each(&:wait!)[0..10] +# => [#, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #, +# #] +readouts.wait! +# => # + +# terminate the logger +Concurrent::ErlangActor.terminate LOGGING, :cancelled +# => true +LOGGING.terminated.wait +# => # + +# inspect collected char frequencies +DB.data +# => {"1"=>18, +# "3"=>18, +# "4"=>18, +# "2"=>18, +# "5"=>18, +# "8"=>18, +# "6"=>18, +# "7"=>18, +# "a"=>18, +# "b"=>18, +# "c"=>18, +# "9"=>18, +# "f"=>3, +# "e"=>18, +# "d"=>18} + +# see the logger output +get_captured_output +# => "[2020-01-26 16:21:45.940] DEBUG -- : crawler 0 found 1\n" + +# "[2020-01-26 16:21:45.940] DEBUG -- : crawler 2 found 2\n" + +# "[2020-01-26 16:21:45.940] DEBUG -- : crawler 1 found 3\n" + +# "[2020-01-26 16:21:45.941] DEBUG -- : data-processor 2 got 3\n" + +# "[2020-01-26 16:21:45.941] DEBUG -- : crawler 3 found 4\n" + +# "[2020-01-26 16:21:45.941] DEBUG -- : data-processor 3 got 4\n" + +# "[2020-01-26 16:21:45.942] DEBUG -- : data-processor 0 got 1\n" + +# "[2020-01-26 16:21:45.942] DEBUG -- : data-processor 1 got 2\n" + +# "[2020-01-26 16:21:45.950] DEBUG -- : crawler 1 found 5\n" + +# "[2020-01-26 16:21:45.951] DEBUG -- : crawler 3 found 6\n" + +# "[2020-01-26 16:21:45.951] DEBUG -- : crawler 0 found 7\n" + +# "[2020-01-26 16:21:45.951] DEBUG -- : crawler 2 found 8\n" + +# "[2020-01-26 16:21:45.951] DEBUG -- : data-processor 7 got 8\n" + +# "[2020-01-26 16:21:45.952] DEBUG -- : data-processor 4 got 5\n" + +# "[2020-01-26 16:21:45.952] DEBUG -- : data-processor 5 got 6\n" + +# "[2020-01-26 16:21:45.952] DEBUG -- : data-processor 6 got 7\n" + +# "[2020-01-26 16:21:45.961] DEBUG -- : crawler 1 found 9\n" + +# "[2020-01-26 16:21:45.961] DEBUG -- : crawler 3 found a\n" + +# "[2020-01-26 16:21:45.962] DEBUG -- : data-processor 9 got a\n" + +# "[2020-01-26 16:21:45.962] DEBUG -- : crawler 0 found b\n" + +# "[2020-01-26 16:21:45.962] DEBUG -- : crawler 2 found c\n" + +# "[2020-01-26 16:21:45.962] DEBUG -- : data-processor 11 got c\n" + +# "[2020-01-26 16:21:45.963] DEBUG -- : data-processor 8 got 9\n" + +# "[2020-01-26 16:21:45.963] DEBUG -- : data-processor 10 got b\n" + +# "[2020-01-26 16:21:45.971] DEBUG -- : crawler 1 found d\n" + +# "[2020-01-26 16:21:45.972] DEBUG -- : crawler 3 found e\n" + +# "[2020-01-26 16:21:45.972] DEBUG -- : crawler 0 found f\n" + +# "[2020-01-26 16:21:45.972] DEBUG -- : crawler 2 found 10\n" + +# "[2020-01-26 16:21:45.982] DEBUG -- : crawler 3 found 11\n" + +# "[2020-01-26 16:21:45.983] DEBUG -- : crawler 2 found 12\n" + +# "[2020-01-26 16:21:45.983] DEBUG -- : crawler 0 found 13\n" + +# "[2020-01-26 16:21:45.983] DEBUG -- : crawler 1 found 14\n" + +# "[2020-01-26 16:21:45.993] DEBUG -- : crawler 0 found 15\n" + +# "[2020-01-26 16:21:45.993] DEBUG -- : crawler 1 found 16\n" + +# "[2020-01-26 16:21:45.993] DEBUG -- : crawler 2 found 17\n" + +# "[2020-01-26 16:21:45.994] DEBUG -- : crawler 3 found 18\n" + +# "[2020-01-26 16:21:46.003] DEBUG -- : crawler 0 found 19\n" + +# "[2020-01-26 16:21:46.003] DEBUG -- : crawler 2 found 1a\n" + +# "[2020-01-26 16:21:46.004] DEBUG -- : crawler 3 found 1b\n" + +# "[2020-01-26 16:21:46.004] DEBUG -- : crawler 1 found 1c\n" + +# "[2020-01-26 16:21:46.014] DEBUG -- : crawler 2 found 1d\n" + +# "[2020-01-26 16:21:46.014] DEBUG -- : crawler 0 found 1e\n" + +# "[2020-01-26 16:21:46.040] DEBUG -- : data-processor 12 got d\n" + +# "[2020-01-26 16:21:46.041] DEBUG -- : data-processor 13 got e\n" + +# "[2020-01-26 16:21:46.041] DEBUG -- : data-processor 14 got f\n" + +# "[2020-01-26 16:21:46.041] DEBUG -- : data-processor 15 got 10\n" + +# "[2020-01-26 16:21:46.050] DEBUG -- : data-processor 16 got 11\n" + +# "[2020-01-26 16:21:46.051] DEBUG -- : data-processor 17 got 12\n" + +# "[2020-01-26 16:21:46.055] DEBUG -- : data-processor 18 got 13\n" + +# "[2020-01-26 16:21:46.055] DEBUG -- : data-processor 19 got 14\n" + +# "[2020-01-26 16:21:46.065] DEBUG -- : data-processor 0 got 15\n" + +# "[2020-01-26 16:21:46.065] DEBUG -- : data-processor 2 got 16\n" + +# "[2020-01-26 16:21:46.065] DEBUG -- : data-processor 3 got 17\n" + +# "[2020-01-26 16:21:46.066] DEBUG -- : crawler 1 found 1f\n" + +# "[2020-01-26 16:21:46.066] DEBUG -- : crawler 3 found 20\n" + +# "[2020-01-26 16:21:46.066] DEBUG -- : crawler 2 found 21\n" + +# "[2020-01-26 16:21:46.067] DEBUG -- : crawler 0 found 22\n" + +# "[2020-01-26 16:21:46.067] DEBUG -- : data-processor 1 got 18\n" + +# "[2020-01-26 16:21:46.073] DEBUG -- : crawler 3 found 23\n" + +# "[2020-01-26 16:21:46.074] DEBUG -- : crawler 2 found 24\n" + +# "[2020-01-26 16:21:46.074] DEBUG -- : crawler 0 found 25\n" + +# "[2020-01-26 16:21:46.075] DEBUG -- : crawler 1 found 26\n" + +# "[2020-01-26 16:21:46.141] DEBUG -- : data-processor 4 got 19\n" + +# "[2020-01-26 16:21:46.142] DEBUG -- : data-processor 7 got 1a\n" + +# "[2020-01-26 16:21:46.142] DEBUG -- : data-processor 5 got 1b\n" + +# "[2020-01-26 16:21:46.143] DEBUG -- : data-processor 6 got 1c\n" + +# "[2020-01-26 16:21:46.151] DEBUG -- : data-processor 9 got 1d\n" + +# "[2020-01-26 16:21:46.152] DEBUG -- : crawler 3 found 27\n" + +# "[2020-01-26 16:21:46.152] DEBUG -- : crawler 0 found 28\n" + +# "[2020-01-26 16:21:46.152] DEBUG -- : crawler 2 found 29\n" + +# "[2020-01-26 16:21:46.153] DEBUG -- : crawler 1 found 2a\n" + +# "[2020-01-26 16:21:46.153] DEBUG -- : data-processor 10 got 1e\n" + +# "[2020-01-26 16:21:46.155] DEBUG -- : data-processor 11 got 1f\n" + +# "[2020-01-26 16:21:46.156] DEBUG -- : data-processor 8 got 20\n" + +# "[2020-01-26 16:21:46.163] DEBUG -- : data-processor 15 got 21\n" + +# "[2020-01-26 16:21:46.164] DEBUG -- : data-processor 14 got 22\n" + +# "[2020-01-26 16:21:46.164] DEBUG -- : data-processor 13 got 23\n" + +# "[2020-01-26 16:21:46.165] DEBUG -- : data-processor 12 got 24\n" + +# "[2020-01-26 16:21:46.165] DEBUG -- : crawler 0 found 2b\n" + +# "[2020-01-26 16:21:46.165] DEBUG -- : crawler 3 found 2c\n" + +# "[2020-01-26 16:21:46.166] DEBUG -- : crawler 2 found 2d\n" + +# "[2020-01-26 16:21:46.166] DEBUG -- : crawler 1 found 2e\n" + +# "[2020-01-26 16:21:46.246] DEBUG -- : data-processor 16 got 25\n" + +# "[2020-01-26 16:21:46.246] DEBUG -- : crawler 0 found 2f\n" + +# "[2020-01-26 16:21:46.247] DEBUG -- : crawler 2 found 30\n" + +# "[2020-01-26 16:21:46.247] DEBUG -- : crawler 1 found 31\n" + +# "[2020-01-26 16:21:46.247] DEBUG -- : crawler 3 found 32\n" + +# "[2020-01-26 16:21:46.247] DEBUG -- : data-processor 17 got 26\n" + +# "[2020-01-26 16:21:46.248] DEBUG -- : data-processor 18 got 27\n" + +# "[2020-01-26 16:21:46.248] DEBUG -- : data-processor 19 got 28\n" + +# "[2020-01-26 16:21:46.252] DEBUG -- : data-processor 0 got 29\n" + +# "[2020-01-26 16:21:46.253] DEBUG -- : data-processor 2 got 2a\n" + +# "[2020-01-26 16:21:46.256] DEBUG -- : crawler 0 found 33\n" + +# "[2020-01-26 16:21:46.256] DEBUG -- : crawler 2 found 34\n" + +# "[2020-01-26 16:21:46.257] DEBUG -- : crawler 1 found 35\n" + +# "[2020-01-26 16:21:46.257] DEBUG -- : crawler 3 found 36\n" + +# "[2020-01-26 16:21:46.259] DEBUG -- : data-processor 3 got 2b\n" + +# "[2020-01-26 16:21:46.260] DEBUG -- : data-processor 1 got 2c\n" + +# "[2020-01-26 16:21:46.267] DEBUG -- : data-processor 4 got 2d\n" + +# "[2020-01-26 16:21:46.268] DEBUG -- : crawler 0 found 37\n" + +# "[2020-01-26 16:21:46.268] DEBUG -- : crawler 2 found 38\n" + +# "[2020-01-26 16:21:46.269] DEBUG -- : crawler 1 found 39\n" + +# "[2020-01-26 16:21:46.269] DEBUG -- : crawler 3 found 3a\n" + +# "[2020-01-26 16:21:46.270] DEBUG -- : data-processor 6 got 2e\n" + +# "[2020-01-26 16:21:46.270] DEBUG -- : data-processor 7 got 2f\n" + +# "[2020-01-26 16:21:46.270] DEBUG -- : data-processor 5 got 30\n" + +# "[2020-01-26 16:21:46.278] DEBUG -- : crawler 0 found 3b\n" + +# "[2020-01-26 16:21:46.278] DEBUG -- : crawler 2 found 3c\n" + +# "[2020-01-26 16:21:46.279] DEBUG -- : crawler 1 found 3d\n" + +# "[2020-01-26 16:21:46.279] DEBUG -- : crawler 3 found 3e\n" + +# "[2020-01-26 16:21:46.329] INFO -- : \n" + +# "crawlers found: 16, 15, 16, 15\n" + +# "data processors consumed: 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2\n" + +# "[2020-01-26 16:21:46.346] DEBUG -- : data-processor 9 got 31\n" + +# "[2020-01-26 16:21:46.347] DEBUG -- : data-processor 10 got 32\n" + +# "[2020-01-26 16:21:46.347] DEBUG -- : data-processor 11 got 33\n" + +# "[2020-01-26 16:21:46.348] DEBUG -- : data-processor 8 got 34\n" + +# "[2020-01-26 16:21:46.354] DEBUG -- : data-processor 13 got 35\n" + +# "[2020-01-26 16:21:46.354] DEBUG -- : crawler 0 found 3f\n" + +# "[2020-01-26 16:21:46.355] DEBUG -- : crawler 2 found 40\n" + +# "[2020-01-26 16:21:46.355] DEBUG -- : crawler 1 found 41\n" + +# "[2020-01-26 16:21:46.356] DEBUG -- : data-processor 15 got 36\n" + +# "[2020-01-26 16:21:46.356] DEBUG -- : crawler 3 found 42\n" + +# "[2020-01-26 16:21:46.361] DEBUG -- : data-processor 12 got 37\n" + +# "[2020-01-26 16:21:46.361] DEBUG -- : data-processor 14 got 38\n" + +# "[2020-01-26 16:21:46.366] DEBUG -- : crawler 0 found 43\n" + +# "[2020-01-26 16:21:46.367] DEBUG -- : crawler 1 found 44\n" + +# "[2020-01-26 16:21:46.367] DEBUG -- : crawler 2 found 45\n" + +# "[2020-01-26 16:21:46.367] DEBUG -- : crawler 3 found 46\n" + +# "[2020-01-26 16:21:46.368] DEBUG -- : data-processor 16 got 39\n" + +# "[2020-01-26 16:21:46.369] DEBUG -- : data-processor 17 got 3a\n" + +# "[2020-01-26 16:21:46.369] DEBUG -- : data-processor 18 got 3b\n" + +# "[2020-01-26 16:21:46.370] DEBUG -- : data-processor 19 got 3c\n" + +# "[2020-01-26 16:21:46.447] DEBUG -- : data-processor 0 got 3d\n" + +# "[2020-01-26 16:21:46.448] DEBUG -- : crawler 3 found 47\n" + +# "[2020-01-26 16:21:46.448] DEBUG -- : crawler 0 found 48\n" + +# "[2020-01-26 16:21:46.448] DEBUG -- : crawler 1 found 49\n" + +# "[2020-01-26 16:21:46.449] DEBUG -- : crawler 2 found 4a\n" + +# "[2020-01-26 16:21:46.449] DEBUG -- : data-processor 2 got 3e\n" + +# "[2020-01-26 16:21:46.449] DEBUG -- : data-processor 3 got 3f\n" + +# "[2020-01-26 16:21:46.451] DEBUG -- : data-processor 1 got 40\n" + +# "[2020-01-26 16:21:46.455] DEBUG -- : data-processor 4 got 41\n" + +# "[2020-01-26 16:21:46.455] DEBUG -- : data-processor 5 got 42\n" + +# "[2020-01-26 16:21:46.458] DEBUG -- : crawler 3 found 4b\n" + +# "[2020-01-26 16:21:46.459] DEBUG -- : crawler 0 found 4c\n" + +# "[2020-01-26 16:21:46.459] DEBUG -- : crawler 1 found 4d\n" + +# "[2020-01-26 16:21:46.459] DEBUG -- : crawler 2 found 4e\n" + +# "[2020-01-26 16:21:46.462] DEBUG -- : data-processor 6 got 43\n" + +# "[2020-01-26 16:21:46.465] DEBUG -- : data-processor 7 got 44\n" + +# "[2020-01-26 16:21:46.470] DEBUG -- : data-processor 9 got 45\n" + +# "[2020-01-26 16:21:46.470] DEBUG -- : crawler 0 found 50\n" + +# "[2020-01-26 16:21:46.470] DEBUG -- : crawler 1 found 51\n" + +# "[2020-01-26 16:21:46.471] DEBUG -- : data-processor 8 got 46\n" + +# "[2020-01-26 16:21:46.471] DEBUG -- : crawler 3 found 4f\n" + +# "[2020-01-26 16:21:46.471] DEBUG -- : crawler 2 found 52\n" + +# "[2020-01-26 16:21:46.471] DEBUG -- : data-processor 11 got 47\n" + +# "[2020-01-26 16:21:46.472] DEBUG -- : data-processor 10 got 48\n" + +# "[2020-01-26 16:21:46.480] DEBUG -- : crawler 0 found 53\n" + +# "[2020-01-26 16:21:46.480] DEBUG -- : crawler 1 found 54\n" + +# "[2020-01-26 16:21:46.480] DEBUG -- : crawler 2 found 55\n" + +# "[2020-01-26 16:21:46.481] DEBUG -- : crawler 3 found 56\n" + +# "[2020-01-26 16:21:46.548] DEBUG -- : data-processor 13 got 49\n" + +# "[2020-01-26 16:21:46.549] DEBUG -- : data-processor 15 got 4a\n" + +# "[2020-01-26 16:21:46.549] DEBUG -- : data-processor 12 got 4b\n" + +# "[2020-01-26 16:21:46.554] DEBUG -- : data-processor 14 got 4c\n" + +# "[2020-01-26 16:21:46.556] DEBUG -- : data-processor 19 got 4d\n" + +# "[2020-01-26 16:21:46.557] DEBUG -- : crawler 0 found 57\n" + +# "[2020-01-26 16:21:46.557] DEBUG -- : data-processor 18 got 4e\n" + +# "[2020-01-26 16:21:46.557] DEBUG -- : crawler 1 found 58\n" + +# "[2020-01-26 16:21:46.558] DEBUG -- : crawler 2 found 59\n" + +# "[2020-01-26 16:21:46.558] DEBUG -- : crawler 3 found 5a\n" + +# "[2020-01-26 16:21:46.564] DEBUG -- : data-processor 16 got 4f\n" + +# "[2020-01-26 16:21:46.566] DEBUG -- : data-processor 17 got 50\n" + +# "[2020-01-26 16:21:46.568] DEBUG -- : crawler 0 found 5b\n" + +# "[2020-01-26 16:21:46.569] DEBUG -- : crawler 1 found 5c\n" + +# "[2020-01-26 16:21:46.569] DEBUG -- : crawler 2 found 5d\n" + +# "[2020-01-26 16:21:46.569] DEBUG -- : crawler 3 found 5e\n" + +# "[2020-01-26 16:21:46.570] DEBUG -- : data-processor 0 got 51\n" + +# "[2020-01-26 16:21:46.571] DEBUG -- : data-processor 2 got 52\n" + +# "[2020-01-26 16:21:46.571] DEBUG -- : data-processor 3 got 53\n" + +# "[2020-01-26 16:21:46.571] DEBUG -- : data-processor 1 got 54\n" + +# "[2020-01-26 16:21:46.651] DEBUG -- : data-processor 4 got 55\n" + +# "[2020-01-26 16:21:46.651] DEBUG -- : crawler 0 found 5f\n" + +# "[2020-01-26 16:21:46.651] DEBUG -- : crawler 1 found 60\n" + +# "[2020-01-26 16:21:46.651] DEBUG -- : crawler 3 found 61\n" + +# "[2020-01-26 16:21:46.652] DEBUG -- : crawler 2 found 62\n" + +# "[2020-01-26 16:21:46.652] DEBUG -- : data-processor 5 got 56\n" + +# "[2020-01-26 16:21:46.652] DEBUG -- : data-processor 6 got 57\n" + +# "[2020-01-26 16:21:46.656] DEBUG -- : data-processor 7 got 58\n" + +# "[2020-01-26 16:21:46.661] DEBUG -- : data-processor 9 got 59\n" + +# "[2020-01-26 16:21:46.661] DEBUG -- : data-processor 11 got 5a\n" + +# "[2020-01-26 16:21:46.662] DEBUG -- : crawler 3 found 63\n" + +# "[2020-01-26 16:21:46.662] DEBUG -- : crawler 1 found 64\n" + +# "[2020-01-26 16:21:46.662] DEBUG -- : crawler 0 found 65\n" + +# "[2020-01-26 16:21:46.662] DEBUG -- : crawler 2 found 66\n" + +# "[2020-01-26 16:21:46.664] DEBUG -- : data-processor 8 got 5b\n" + +# "[2020-01-26 16:21:46.667] DEBUG -- : data-processor 10 got 5c\n" + +# "[2020-01-26 16:21:46.672] DEBUG -- : data-processor 12 got 5d\n" + +# "[2020-01-26 16:21:46.673] DEBUG -- : crawler 1 found 67\n" + +# "[2020-01-26 16:21:46.673] DEBUG -- : crawler 0 found 68\n" + +# "[2020-01-26 16:21:46.673] DEBUG -- : data-processor 13 got 5e\n" + +# "[2020-01-26 16:21:46.673] DEBUG -- : data-processor 15 got 5f\n" + +# "[2020-01-26 16:21:46.673] DEBUG -- : crawler 3 found 69\n" + +# "[2020-01-26 16:21:46.674] DEBUG -- : data-processor 14 got 60\n" + +# "[2020-01-26 16:21:46.674] DEBUG -- : crawler 2 found 6a\n" + +# "[2020-01-26 16:21:46.682] DEBUG -- : crawler 0 found 6b\n" + +# "[2020-01-26 16:21:46.683] DEBUG -- : crawler 3 found 6c\n" + +# "[2020-01-26 16:21:46.683] DEBUG -- : crawler 1 found 6d\n" + +# "[2020-01-26 16:21:46.683] DEBUG -- : crawler 2 found 6e\n" + +# "[2020-01-26 16:21:46.729] INFO -- : \n" + +# "crawlers found: 28, 27, 28, 27\n" + +# "data processors consumed: 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4\n" + +# "[2020-01-26 16:21:46.751] DEBUG -- : data-processor 19 got 61\n" + +# "[2020-01-26 16:21:46.757] DEBUG -- : data-processor 18 got 62\n" + +# "[2020-01-26 16:21:46.757] DEBUG -- : data-processor 16 got 63\n" + +# "[2020-01-26 16:21:46.760] DEBUG -- : data-processor 17 got 64\n" + +# "[2020-01-26 16:21:46.766] DEBUG -- : data-processor 0 got 65\n" + +# "[2020-01-26 16:21:46.766] DEBUG -- : crawler 2 found 6f\n" + +# "[2020-01-26 16:21:46.767] DEBUG -- : crawler 3 found 70\n" + +# "[2020-01-26 16:21:46.767] DEBUG -- : crawler 1 found 71\n" + +# "[2020-01-26 16:21:46.767] DEBUG -- : crawler 0 found 72\n" + +# "[2020-01-26 16:21:46.768] DEBUG -- : data-processor 3 got 66\n" + +# "[2020-01-26 16:21:46.768] DEBUG -- : data-processor 2 got 67\n" + +# "[2020-01-26 16:21:46.768] DEBUG -- : data-processor 1 got 68\n" + +# "[2020-01-26 16:21:46.772] DEBUG -- : data-processor 4 got 69\n" + +# "[2020-01-26 16:21:46.777] DEBUG -- : data-processor 6 got 6a\n" + +# "[2020-01-26 16:21:46.778] DEBUG -- : data-processor 5 got 6b\n" + +# "[2020-01-26 16:21:46.778] DEBUG -- : data-processor 7 got 6c\n" + +# "[2020-01-26 16:21:46.778] DEBUG -- : crawler 1 found 73\n" + +# "[2020-01-26 16:21:46.778] DEBUG -- : crawler 0 found 74\n" + +# "[2020-01-26 16:21:46.779] DEBUG -- : crawler 2 found 75\n" + +# "[2020-01-26 16:21:46.779] DEBUG -- : crawler 3 found 76\n" + +# "[2020-01-26 16:21:46.853] DEBUG -- : data-processor 9 got 6d\n" + +# "[2020-01-26 16:21:46.854] DEBUG -- : crawler 1 found 77\n" + +# "[2020-01-26 16:21:46.854] DEBUG -- : crawler 0 found 78\n" + +# "[2020-01-26 16:21:46.856] DEBUG -- : crawler 2 found 79\n" + +# "[2020-01-26 16:21:46.856] DEBUG -- : crawler 3 found 7a\n" + +# "[2020-01-26 16:21:46.861] DEBUG -- : data-processor 11 got 6e\n" + +# "[2020-01-26 16:21:46.861] DEBUG -- : data-processor 10 got 6f\n" + +# "[2020-01-26 16:21:46.861] DEBUG -- : data-processor 8 got 70\n" + +# "[2020-01-26 16:21:46.863] DEBUG -- : crawler 1 found 7b\n" + +# "[2020-01-26 16:21:46.866] DEBUG -- : crawler 0 found 7c\n" + +# "[2020-01-26 16:21:46.866] DEBUG -- : crawler 2 found 7d\n" + +# "[2020-01-26 16:21:46.867] DEBUG -- : crawler 3 found 7e\n" + +# "[2020-01-26 16:21:46.867] DEBUG -- : data-processor 12 got 71\n" + +# "[2020-01-26 16:21:46.867] DEBUG -- : data-processor 14 got 72\n" + +# "[2020-01-26 16:21:46.868] DEBUG -- : data-processor 13 got 73\n" + +# "[2020-01-26 16:21:46.868] DEBUG -- : data-processor 15 got 74\n" + +# "[2020-01-26 16:21:46.879] DEBUG -- : data-processor 19 got 75\n" + +# "[2020-01-26 16:21:46.879] DEBUG -- : crawler 1 found 7f\n" + +# "[2020-01-26 16:21:46.879] DEBUG -- : data-processor 17 got 76\n" + +# "[2020-01-26 16:21:46.880] DEBUG -- : data-processor 18 got 77\n" + +# "[2020-01-26 16:21:46.880] DEBUG -- : crawler 0 found 80\n" + +# "[2020-01-26 16:21:46.880] DEBUG -- : crawler 2 found 81\n" + +# "[2020-01-26 16:21:46.881] DEBUG -- : crawler 3 found 82\n" + +# "[2020-01-26 16:21:46.881] DEBUG -- : data-processor 16 got 78\n" + +# "[2020-01-26 16:21:46.889] DEBUG -- : crawler 1 found 83\n" + +# "[2020-01-26 16:21:46.889] DEBUG -- : crawler 0 found 84\n" + +# "[2020-01-26 16:21:46.889] DEBUG -- : crawler 2 found 85\n" + +# "[2020-01-26 16:21:46.890] DEBUG -- : crawler 3 found 86\n" + +# "[2020-01-26 16:21:46.954] DEBUG -- : data-processor 0 got 79\n" + +# "[2020-01-26 16:21:46.961] DEBUG -- : data-processor 3 got 7a\n" + +# "[2020-01-26 16:21:46.962] DEBUG -- : data-processor 2 got 7b\n" + +# "[2020-01-26 16:21:46.962] DEBUG -- : data-processor 1 got 7c\n" + +# "[2020-01-26 16:21:46.967] DEBUG -- : data-processor 4 got 7d\n" + +# "[2020-01-26 16:21:46.968] DEBUG -- : crawler 2 found 87\n" + +# "[2020-01-26 16:21:46.968] DEBUG -- : crawler 3 found 88\n" + +# "[2020-01-26 16:21:46.969] DEBUG -- : crawler 1 found 89\n" + +# "[2020-01-26 16:21:46.969] DEBUG -- : crawler 0 found 8a\n" + +# "[2020-01-26 16:21:46.969] DEBUG -- : data-processor 6 got 7e\n" + +# "[2020-01-26 16:21:46.969] DEBUG -- : data-processor 5 got 7f\n" + +# "[2020-01-26 16:21:46.970] DEBUG -- : data-processor 7 got 80\n" + +# "[2020-01-26 16:21:46.978] DEBUG -- : crawler 2 found 8b\n" + +# "[2020-01-26 16:21:46.978] DEBUG -- : crawler 3 found 8c\n" + +# "[2020-01-26 16:21:46.978] DEBUG -- : crawler 1 found 8d\n" + +# "[2020-01-26 16:21:46.979] DEBUG -- : crawler 0 found 8e\n" + +# "[2020-01-26 16:21:46.979] DEBUG -- : data-processor 9 got 81\n" + +# "[2020-01-26 16:21:46.980] DEBUG -- : data-processor 11 got 82\n" + +# "[2020-01-26 16:21:46.980] DEBUG -- : data-processor 8 got 83\n" + +# "[2020-01-26 16:21:46.980] DEBUG -- : data-processor 10 got 84\n" + +# "[2020-01-26 16:21:47.056] DEBUG -- : data-processor 12 got 85\n" + +# "[2020-01-26 16:21:47.057] DEBUG -- : crawler 2 found 8f\n" + +# "[2020-01-26 16:21:47.057] DEBUG -- : crawler 3 found 90\n" + +# "[2020-01-26 16:21:47.057] DEBUG -- : crawler 1 found 91\n" + +# "[2020-01-26 16:21:47.058] DEBUG -- : crawler 0 found 92\n" + +# "[2020-01-26 16:21:47.062] DEBUG -- : data-processor 14 got 86\n" + +# "[2020-01-26 16:21:47.066] DEBUG -- : data-processor 13 got 87\n" + +# "[2020-01-26 16:21:47.066] DEBUG -- : data-processor 15 got 88\n" + +# "[2020-01-26 16:21:47.067] DEBUG -- : crawler 2 found 93\n" + +# "[2020-01-26 16:21:47.067] DEBUG -- : crawler 3 found 94\n" + +# "[2020-01-26 16:21:47.068] DEBUG -- : crawler 1 found 95\n" + +# "[2020-01-26 16:21:47.068] DEBUG -- : crawler 0 found 96\n" + +# "[2020-01-26 16:21:47.068] DEBUG -- : data-processor 19 got 89\n" + +# "[2020-01-26 16:21:47.069] DEBUG -- : data-processor 18 got 8a\n" + +# "[2020-01-26 16:21:47.069] DEBUG -- : data-processor 16 got 8b\n" + +# "[2020-01-26 16:21:47.070] DEBUG -- : data-processor 17 got 8c\n" + +# "[2020-01-26 16:21:47.080] DEBUG -- : data-processor 0 got 8d\n" + +# "[2020-01-26 16:21:47.080] DEBUG -- : data-processor 3 got 8e\n" + +# "[2020-01-26 16:21:47.081] DEBUG -- : crawler 2 found 97\n" + +# "[2020-01-26 16:21:47.081] DEBUG -- : crawler 3 found 98\n" + +# "[2020-01-26 16:21:47.081] DEBUG -- : crawler 1 found 99\n" + +# "[2020-01-26 16:21:47.082] DEBUG -- : crawler 0 found 9a\n" + +# "[2020-01-26 16:21:47.082] DEBUG -- : data-processor 2 got 8f\n" + +# "[2020-01-26 16:21:47.082] DEBUG -- : data-processor 1 got 90\n" + +# "[2020-01-26 16:21:47.091] DEBUG -- : crawler 2 found 9b\n" + +# "[2020-01-26 16:21:47.092] DEBUG -- : crawler 1 found 9c\n" + +# "[2020-01-26 16:21:47.092] DEBUG -- : crawler 3 found 9d\n" + +# "[2020-01-26 16:21:47.092] DEBUG -- : crawler 0 found 9e\n" + +# "[2020-01-26 16:21:47.133] INFO -- : \n" + +# "crawlers found: 40, 39, 40, 39\n" + +# "data processors consumed: 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7\n" + +# "[2020-01-26 16:21:47.161] DEBUG -- : data-processor 4 got 91\n" + +# "[2020-01-26 16:21:47.162] DEBUG -- : data-processor 5 got 92\n" + +# "[2020-01-26 16:21:47.168] DEBUG -- : data-processor 6 got 93\n" + +# "[2020-01-26 16:21:47.169] DEBUG -- : data-processor 7 got 94\n" + +# "[2020-01-26 16:21:47.170] DEBUG -- : data-processor 8 got 95\n" + +# "[2020-01-26 16:21:47.170] DEBUG -- : crawler 2 found 9f\n" + +# "[2020-01-26 16:21:47.170] DEBUG -- : crawler 1 found a0\n" + +# "[2020-01-26 16:21:47.170] DEBUG -- : crawler 3 found a1\n" + +# "[2020-01-26 16:21:47.171] DEBUG -- : crawler 0 found a2\n" + +# "[2020-01-26 16:21:47.171] DEBUG -- : data-processor 11 got 96\n" + +# "[2020-01-26 16:21:47.171] DEBUG -- : data-processor 10 got 97\n" + +# "[2020-01-26 16:21:47.173] DEBUG -- : data-processor 9 got 98\n" + +# "[2020-01-26 16:21:47.179] DEBUG -- : crawler 2 found a3\n" + +# "[2020-01-26 16:21:47.180] DEBUG -- : crawler 1 found a4\n" + +# "[2020-01-26 16:21:47.180] DEBUG -- : crawler 3 found a5\n" + +# "[2020-01-26 16:21:47.180] DEBUG -- : data-processor 12 got 99\n" + +# "[2020-01-26 16:21:47.182] DEBUG -- : data-processor 14 got 9a\n" + +# "[2020-01-26 16:21:47.183] DEBUG -- : data-processor 13 got 9b\n" + +# "[2020-01-26 16:21:47.184] DEBUG -- : data-processor 15 got 9c\n" + +# "[2020-01-26 16:21:47.185] DEBUG -- : crawler 0 found a6\n" + +# "[2020-01-26 16:21:47.192] DEBUG -- : crawler 2 found a7\n" + +# "[2020-01-26 16:21:47.192] DEBUG -- : crawler 1 found a8\n" + +# "[2020-01-26 16:21:47.193] DEBUG -- : crawler 3 found a9\n" + +# "[2020-01-26 16:21:47.194] DEBUG -- : crawler 0 found aa\n" + +# "[2020-01-26 16:21:47.205] DEBUG -- : crawler 2 found ab\n" + +# "[2020-01-26 16:21:47.205] DEBUG -- : crawler 0 found ac\n" + +# "[2020-01-26 16:21:47.205] DEBUG -- : crawler 1 found ad\n" + +# "[2020-01-26 16:21:47.262] DEBUG -- : data-processor 19 got 9d\n" + +# "[2020-01-26 16:21:47.266] DEBUG -- : data-processor 18 got 9e\n" + +# "[2020-01-26 16:21:47.272] DEBUG -- : data-processor 16 got 9f\n" + +# "[2020-01-26 16:21:47.273] DEBUG -- : data-processor 17 got a0\n" + +# "[2020-01-26 16:21:47.273] DEBUG -- : data-processor 0 got a1\n" + +# "[2020-01-26 16:21:47.273] DEBUG -- : data-processor 1 got a2\n" + +# "[2020-01-26 16:21:47.274] DEBUG -- : data-processor 2 got a3\n" + +# "[2020-01-26 16:21:47.278] DEBUG -- : data-processor 3 got a4\n" + +# "[2020-01-26 16:21:47.278] DEBUG -- : crawler 3 found ae\n" + +# "[2020-01-26 16:21:47.279] DEBUG -- : crawler 2 found af\n" + +# "[2020-01-26 16:21:47.279] DEBUG -- : crawler 1 found b0\n" + +# "[2020-01-26 16:21:47.279] DEBUG -- : crawler 0 found b1\n" + +# "[2020-01-26 16:21:47.280] DEBUG -- : data-processor 4 got a5\n" + +# "[2020-01-26 16:21:47.283] DEBUG -- : data-processor 5 got a6\n" + +# "[2020-01-26 16:21:47.283] DEBUG -- : data-processor 8 got a7\n" + +# "[2020-01-26 16:21:47.289] DEBUG -- : crawler 2 found b2\n" + +# "[2020-01-26 16:21:47.289] DEBUG -- : crawler 3 found b3\n" + +# "[2020-01-26 16:21:47.289] DEBUG -- : crawler 1 found b4\n" + +# "[2020-01-26 16:21:47.290] DEBUG -- : crawler 0 found b5\n" + +# "[2020-01-26 16:21:47.290] DEBUG -- : data-processor 6 got a8\n" + +# "[2020-01-26 16:21:47.367] DEBUG -- : data-processor 11 got a9\n" + +# "[2020-01-26 16:21:47.369] DEBUG -- : data-processor 10 got aa\n" + +# "[2020-01-26 16:21:47.373] DEBUG -- : data-processor 7 got ab\n" + +# "[2020-01-26 16:21:47.376] DEBUG -- : data-processor 9 got ac\n" + +# "[2020-01-26 16:21:47.377] DEBUG -- : crawler 3 found b6\n" + +# "[2020-01-26 16:21:47.377] DEBUG -- : crawler 0 found b7\n" + +# "[2020-01-26 16:21:47.378] DEBUG -- : crawler 1 found b8\n" + +# "[2020-01-26 16:21:47.378] DEBUG -- : crawler 2 found b9\n" + +# "[2020-01-26 16:21:47.378] DEBUG -- : data-processor 12 got ad\n" + +# "[2020-01-26 16:21:47.378] DEBUG -- : data-processor 14 got ae\n" + +# "[2020-01-26 16:21:47.379] DEBUG -- : data-processor 13 got af\n" + +# "[2020-01-26 16:21:47.379] DEBUG -- : data-processor 15 got b0\n" + +# "[2020-01-26 16:21:47.381] DEBUG -- : data-processor 19 got b1\n" + +# "[2020-01-26 16:21:47.383] DEBUG -- : data-processor 18 got b2\n" + +# "[2020-01-26 16:21:47.388] DEBUG -- : crawler 1 found ba\n" + +# "[2020-01-26 16:21:47.388] DEBUG -- : crawler 2 found bb\n" + +# "[2020-01-26 16:21:47.389] DEBUG -- : data-processor 1 got b3\n" + +# "[2020-01-26 16:21:47.392] DEBUG -- : crawler 3 found bc\n" + +# "[2020-01-26 16:21:47.392] DEBUG -- : crawler 0 found bd\n" + +# "[2020-01-26 16:21:47.392] DEBUG -- : data-processor 16 got b4\n" + +# "[2020-01-26 16:21:47.400] DEBUG -- : crawler 1 found be\n" + +# "[2020-01-26 16:21:47.400] DEBUG -- : crawler 2 found bf\n" + +# "[2020-01-26 16:21:47.400] DEBUG -- : crawler 3 found c0\n" + +# "[2020-01-26 16:21:47.400] DEBUG -- : crawler 0 found c1\n" + +# "[2020-01-26 16:21:47.411] DEBUG -- : crawler 2 found c2\n" + +# "[2020-01-26 16:21:47.411] DEBUG -- : crawler 3 found c3\n" + +# "[2020-01-26 16:21:47.412] DEBUG -- : crawler 0 found c4\n" + +# "[2020-01-26 16:21:47.412] DEBUG -- : crawler 1 found c5\n" + +# "[2020-01-26 16:21:47.467] DEBUG -- : data-processor 0 got b5\n" + +# "[2020-01-26 16:21:47.469] DEBUG -- : data-processor 17 got b6\n" + +# "[2020-01-26 16:21:47.475] DEBUG -- : data-processor 2 got b7\n" + +# "[2020-01-26 16:21:47.477] DEBUG -- : data-processor 3 got b8\n" + +# "[2020-01-26 16:21:47.478] DEBUG -- : data-processor 4 got b9\n" + +# "[2020-01-26 16:21:47.478] DEBUG -- : data-processor 5 got ba\n" + +# "[2020-01-26 16:21:47.479] DEBUG -- : data-processor 8 got bb\n" + +# "[2020-01-26 16:21:47.481] DEBUG -- : data-processor 6 got bc\n" + +# "[2020-01-26 16:21:47.481] DEBUG -- : crawler 2 found c6\n" + +# "[2020-01-26 16:21:47.482] DEBUG -- : crawler 3 found c7\n" + +# "[2020-01-26 16:21:47.482] DEBUG -- : crawler 0 found c8\n" + +# "[2020-01-26 16:21:47.482] DEBUG -- : crawler 1 found c9\n" + +# "[2020-01-26 16:21:47.483] DEBUG -- : data-processor 11 got bd\n" + +# "[2020-01-26 16:21:47.483] DEBUG -- : data-processor 10 got be\n" + +# "[2020-01-26 16:21:47.490] DEBUG -- : data-processor 7 got bf\n" + +# "[2020-01-26 16:21:47.492] DEBUG -- : crawler 1 found ca\n" + +# "[2020-01-26 16:21:47.493] DEBUG -- : data-processor 9 got c0\n" + +# "[2020-01-26 16:21:47.493] DEBUG -- : crawler 2 found cb\n" + +# "[2020-01-26 16:21:47.494] DEBUG -- : crawler 3 found cc\n" + +# "[2020-01-26 16:21:47.494] DEBUG -- : crawler 0 found cd\n" + +# "[2020-01-26 16:21:47.534] INFO -- : \n" + +# "crawlers found: 52, 51, 52, 50\n" + +# "data processors consumed: 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, 9, 9, 9\n" + +# "[2020-01-26 16:21:47.571] DEBUG -- : data-processor 13 got c1\n" + +# "[2020-01-26 16:21:47.572] DEBUG -- : data-processor 14 got c2\n" + +# "[2020-01-26 16:21:47.577] DEBUG -- : data-processor 15 got c3\n" + +# "[2020-01-26 16:21:47.581] DEBUG -- : data-processor 12 got c4\n" + +# "[2020-01-26 16:21:47.582] DEBUG -- : data-processor 19 got c5\n" + +# "[2020-01-26 16:21:47.583] DEBUG -- : data-processor 18 got c6\n" + +# "[2020-01-26 16:21:47.583] DEBUG -- : crawler 1 found ce\n" + +# "[2020-01-26 16:21:47.583] DEBUG -- : crawler 3 found cf\n" + +# "[2020-01-26 16:21:47.584] DEBUG -- : crawler 0 found d0\n" + +# "[2020-01-26 16:21:47.585] DEBUG -- : crawler 2 found d1\n" + +# "[2020-01-26 16:21:47.585] DEBUG -- : data-processor 1 got c7\n" + +# "[2020-01-26 16:21:47.585] DEBUG -- : data-processor 16 got c8\n" + +# "[2020-01-26 16:21:47.586] DEBUG -- : data-processor 17 got c9\n" + +# "[2020-01-26 16:21:47.586] DEBUG -- : data-processor 0 got ca\n" + +# "[2020-01-26 16:21:47.592] DEBUG -- : crawler 3 found d2\n" + +# "[2020-01-26 16:21:47.593] DEBUG -- : crawler 1 found d3\n" + +# "[2020-01-26 16:21:47.593] DEBUG -- : crawler 0 found d4\n" + +# "[2020-01-26 16:21:47.593] DEBUG -- : crawler 2 found d5\n" + +# "[2020-01-26 16:21:47.595] DEBUG -- : data-processor 2 got cb\n" + +# "[2020-01-26 16:21:47.598] DEBUG -- : data-processor 4 got cc\n" + +# "[2020-01-26 16:21:47.605] DEBUG -- : crawler 3 found d6\n" + +# "[2020-01-26 16:21:47.605] DEBUG -- : crawler 1 found d7\n" + +# "[2020-01-26 16:21:47.606] DEBUG -- : crawler 0 found d8\n" + +# "[2020-01-26 16:21:47.606] DEBUG -- : crawler 2 found d9\n" + +# "[2020-01-26 16:21:47.615] DEBUG -- : crawler 0 found da\n" + +# "[2020-01-26 16:21:47.616] DEBUG -- : crawler 2 found db\n" + +# "[2020-01-26 16:21:47.616] DEBUG -- : crawler 1 found dc\n" + +# "[2020-01-26 16:21:47.671] DEBUG -- : data-processor 3 got cd\n" + +# "[2020-01-26 16:21:47.672] DEBUG -- : data-processor 5 got ce\n" + +# "[2020-01-26 16:21:47.678] DEBUG -- : data-processor 8 got cf\n" + +# "[2020-01-26 16:21:47.684] DEBUG -- : data-processor 6 got d0\n" + +# "[2020-01-26 16:21:47.685] DEBUG -- : data-processor 11 got d1\n" + +# "[2020-01-26 16:21:47.685] DEBUG -- : data-processor 10 got d2\n" + +# "[2020-01-26 16:21:47.686] DEBUG -- : data-processor 7 got d3\n" + +# "[2020-01-26 16:21:47.686] DEBUG -- : data-processor 9 got d4\n" + +# "[2020-01-26 16:21:47.686] DEBUG -- : crawler 3 found dd\n" + +# "[2020-01-26 16:21:47.686] DEBUG -- : crawler 0 found de\n" + +# "[2020-01-26 16:21:47.687] DEBUG -- : data-processor 14 got d5\n" + +# "[2020-01-26 16:21:47.687] DEBUG -- : data-processor 13 got d6\n" + +# "[2020-01-26 16:21:47.687] DEBUG -- : crawler 2 found df\n" + +# "[2020-01-26 16:21:47.687] DEBUG -- : crawler 1 found e0\n" + +# "[2020-01-26 16:21:47.696] DEBUG -- : crawler 3 found e1\n" + +# "[2020-01-26 16:21:47.696] DEBUG -- : crawler 0 found e2\n" + +# "[2020-01-26 16:21:47.696] DEBUG -- : crawler 2 found e3\n" + +# "[2020-01-26 16:21:47.697] DEBUG -- : crawler 1 found e4\n" + +# "[2020-01-26 16:21:47.697] DEBUG -- : data-processor 15 got d7\n" + +# "[2020-01-26 16:21:47.697] DEBUG -- : data-processor 12 got d8\n" + +# "[2020-01-26 16:21:47.772] DEBUG -- : data-processor 19 got d9\n" + +# "[2020-01-26 16:21:47.773] DEBUG -- : data-processor 16 got da\n" + +# "[2020-01-26 16:21:47.782] DEBUG -- : data-processor 0 got db\n" + +# "[2020-01-26 16:21:47.782] DEBUG -- : crawler 3 found e5\n" + +# "[2020-01-26 16:21:47.782] DEBUG -- : crawler 0 found e6\n" + +# "[2020-01-26 16:21:47.783] DEBUG -- : crawler 1 found e7\n" + +# "[2020-01-26 16:21:47.783] DEBUG -- : crawler 2 found e8\n" + +# "[2020-01-26 16:21:47.784] DEBUG -- : data-processor 17 got dc\n" + +# "[2020-01-26 16:21:47.785] DEBUG -- : data-processor 18 got dd\n" + +# "[2020-01-26 16:21:47.785] DEBUG -- : data-processor 1 got de\n" + +# "[2020-01-26 16:21:47.785] DEBUG -- : data-processor 2 got df\n" + +# "[2020-01-26 16:21:47.786] DEBUG -- : data-processor 4 got e0\n" + +# "[2020-01-26 16:21:47.786] DEBUG -- : data-processor 5 got e1\n" + +# "[2020-01-26 16:21:47.786] DEBUG -- : data-processor 3 got e2\n" + +# "[2020-01-26 16:21:47.792] DEBUG -- : crawler 0 found e9\n" + +# "[2020-01-26 16:21:47.792] DEBUG -- : crawler 1 found ea\n" + +# "[2020-01-26 16:21:47.792] DEBUG -- : crawler 3 found eb\n" + +# "[2020-01-26 16:21:47.793] DEBUG -- : crawler 2 found ec\n" + +# "[2020-01-26 16:21:47.797] DEBUG -- : data-processor 8 got e3\n" + +# "[2020-01-26 16:21:47.799] DEBUG -- : data-processor 6 got e4\n" + +# "[2020-01-26 16:21:47.802] DEBUG -- : crawler 0 found ed\n" + +# "[2020-01-26 16:21:47.802] DEBUG -- : crawler 1 found ee\n" + +# "[2020-01-26 16:21:47.803] DEBUG -- : crawler 3 found ef\n" + +# "[2020-01-26 16:21:47.803] DEBUG -- : crawler 2 found f0\n" + +# "[2020-01-26 16:21:47.812] DEBUG -- : crawler 0 found f1\n" + +# "[2020-01-26 16:21:47.813] DEBUG -- : crawler 1 found f2\n" + +# "[2020-01-26 16:21:47.813] DEBUG -- : crawler 3 found f3\n" + +# "[2020-01-26 16:21:47.813] DEBUG -- : crawler 2 found f4\n" + +# "[2020-01-26 16:21:47.875] DEBUG -- : data-processor 11 got e5\n" + +# "[2020-01-26 16:21:47.875] DEBUG -- : data-processor 10 got e6\n" + +# "[2020-01-26 16:21:47.882] DEBUG -- : data-processor 7 got e7\n" + +# "[2020-01-26 16:21:47.884] DEBUG -- : data-processor 13 got e8\n" + +# "[2020-01-26 16:21:47.885] DEBUG -- : data-processor 9 got e9\n" + +# "[2020-01-26 16:21:47.888] DEBUG -- : data-processor 14 got ea\n" + +# "[2020-01-26 16:21:47.889] DEBUG -- : data-processor 12 got eb\n" + +# "[2020-01-26 16:21:47.889] DEBUG -- : data-processor 15 got ec\n" + +# "[2020-01-26 16:21:47.889] DEBUG -- : data-processor 19 got ed\n" + +# "[2020-01-26 16:21:47.889] DEBUG -- : data-processor 16 got ee\n" + +# "[2020-01-26 16:21:47.889] DEBUG -- : crawler 1 found f5\n" + +# "[2020-01-26 16:21:47.890] DEBUG -- : crawler 3 found f6\n" + +# "[2020-01-26 16:21:47.890] DEBUG -- : crawler 0 found f7\n" + +# "[2020-01-26 16:21:47.890] DEBUG -- : crawler 2 found f8\n" + +# "[2020-01-26 16:21:47.899] DEBUG -- : crawler 1 found f9\n" + +# "[2020-01-26 16:21:47.899] DEBUG -- : crawler 3 found fa\n" + +# "[2020-01-26 16:21:47.900] DEBUG -- : crawler 0 found fb\n" + +# "[2020-01-26 16:21:47.900] DEBUG -- : data-processor 0 got ef\n" + +# "[2020-01-26 16:21:47.902] DEBUG -- : data-processor 17 got f0\n" + +# "[2020-01-26 16:21:47.930] INFO -- : \n" + +# "crawlers found: 64, 63, 63, 61\n" + +# "data processors consumed: 13, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11, 12\n" + +# "[2020-01-26 16:21:47.975] DEBUG -- : data-processor 18 got f1\n" + +# "[2020-01-26 16:21:48.000] DEBUG -- : crawler 2 found fc\n" + +# "[2020-01-26 16:21:48.011] DEBUG -- : crawler 1 found fd\n" + +# "[2020-01-26 16:21:48.011] DEBUG -- : crawler 3 found fe\n" + +# "[2020-01-26 16:21:48.011] DEBUG -- : crawler 0 found ff\n" + + + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises-main.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises-main.md new file mode 100644 index 0000000000..81a4f316cc --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises-main.md @@ -0,0 +1,60 @@ +Promises is a new framework unifying former tools `Concurrent::Future`, +`Concurrent::Promise`, `Concurrent::IVar`, `Concurrent::Event`, +`Concurrent.dataflow`, `Delay`, and `TimerTask` of concurrent-ruby. It +extensively uses the new synchronization layer to make all the methods +*lock-free* (with the exception of obviously blocking operations like `#wait`, +`#value`, etc.). As a result it lowers danger of deadlocking and offers +better performance. + +It provides similar tools as other promise libraries do, users coming from +other languages and other promise libraries will find the same tools here +(probably named differently though). The naming conventions were borrowed +heavily from JS promises. + +This framework, however, is not just a re-implementation of other promise +library, it draws inspiration from many other promise libraries, adds new +ideas, and is integrated with other abstractions like actors and channels. + +Therefore it is likely that user will find a suitable solution for a problem in +this framework. If the problem is simple user can pick one suitable +abstraction, e.g. just promises or actors. If the problem is complex user can +combine parts (promises, channels, actors) which were designed to work together +well to a solution. Rather than having to combine fragilely independent tools. + +This framework allows its users to: + +- Process tasks asynchronously +- Chain, branch, and zip the asynchronous tasks together + - Therefore, to create directed acyclic graph (hereafter DAG) of tasks +- Create delayed tasks (or delayed DAG of tasks) +- Create scheduled tasks (or delayed DAG of tasks) +- Deal with errors through rejections +- Reduce danger of deadlocking +- Control the concurrency level of tasks +- Simulate thread-like processing without occupying threads + - It allows to create tens of thousands simulations on one thread + pool + - It works well on all Ruby implementations +- Use actors to maintain isolated states and to seamlessly combine + it with promises +- Build parallel processing stream system with back + pressure (parts, which are not keeping up, signal to the other parts of the + system to slow down). + +**The {file:doc/promises.out.md guide} is best place to start with promises.** + +# Main classes + +The main public user-facing classes are {Concurrent::Promises::Event} and +{Concurrent::Promises::Future} which share common ancestor +{Concurrent::Promises::AbstractEventFuture}. + +**{Concurrent::Promises::AbstractEventFuture}:** +> {include:Concurrent::Promises::AbstractEventFuture} + +**{Concurrent::Promises::Event}:** +> {include:Concurrent::Promises::Event} + +**{Concurrent::Promises::Future}:** +> {include:Concurrent::Promises::Future} + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.in.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.in.md new file mode 100644 index 0000000000..f85d634455 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.in.md @@ -0,0 +1,974 @@ +# Basics + +## Factory methods + +Future and Event are created indirectly with constructor methods in +FactoryMethods. They are not designed for inheritance but rather for +composition. + +```ruby +Concurrent::Promises::FactoryMethods.instance_methods(false).sort +``` + +The module can be included or extended where needed. + +```ruby +Class.new do + include Concurrent::Promises::FactoryMethods + + def a_method + resolvable_event + end +end.new.a_method + +mod = Module.new do + extend Concurrent::Promises::FactoryMethods +end # +mod.resolvable_event +``` + +The default executor can be changed by overriding `default_executor` method +inherited from `Concurrent::Promises::FactoryMethods`. + +```ruby +mod = Module.new do + extend Concurrent::Promises::FactoryMethods + def self.default_executor + :fast + end +end # +mod.future { 1 }.default_executor +Concurrent::Promises.future { 1 }.default_executor +``` + + +The module is already extended into {Concurrent::Promises} for convenience. + +```ruby +Concurrent::Promises.resolvable_event +``` + +## Asynchronous task + +The most basic use-case of the framework is asynchronous processing. A task can +be processed asynchronously by using a `future` factory method. The block will +be executed on an internal thread pool. + +Arguments of `future` are passed to the block and evaluation starts immediately. + +```ruby +future = Concurrent::Promises.future(0.1) do |duration| + sleep duration + :result +end +future.value +``` + +Asks if the future is resolved, here it will be still in the middle of the +sleep call. + +```ruby +future.resolved? +``` + +Retrieving the value will block until the future is **resolved**. + +```ruby +future.value +future.resolved? +``` + +If the task fails, we talk about the future being **rejected**. + +```ruby +future = Concurrent::Promises.future { sleep 0.01; raise 'Boom' } +``` + +There is no result, the future was rejected with a reason. + +```ruby +future.value +future.reason +``` + +It can be forced to raise the reason for rejection when retrieving the value. + +```ruby +begin + future.value! +rescue => e + e +end +``` + +Which is the same as `future.value! rescue $!` which will be used hereafter. + +Or it can be used directly as argument for raise, since it implements exception +method. + +```ruby +raise future rescue $! +``` + +## States + +Let's define an inspection helper for methods. + +```ruby +def inspect_methods(*methods, of:) + methods.reduce({}) { |h, m| h.update m => of.send(m) } +end # +``` + +Event has a `pending` and a `resolved` state. + +```ruby +event = Concurrent::Promises.resolvable_event # +inspect_methods(:state, :pending?, :resolved?, of: event) + +event.resolve # +inspect_methods(:state, :pending?, :resolved?, of: event) +``` + +Future's `resolved` state is further specified to be `fulfilled` or `rejected`. + +```ruby +future = Concurrent::Promises.resolvable_future # +inspect_methods(:state, :pending?, :resolved?, :fulfilled?, :rejected?, + of: future) + +future.fulfill :value # +inspect_methods(:state, :pending?, :resolved?, :fulfilled?, :rejected?, + :result, :value, :reason, of: future) + +future = Concurrent::Promises.rejected_future StandardError.new # +inspect_methods(:state, :pending?, :resolved?, :fulfilled?, :rejected?, + :result, :value, :reason, of: future) +``` + +## Direct creation of resolved futures + +When an existing value has to be wrapped in a future it does not have to go +through evaluation as follows. + +```ruby +Concurrent::Promises.future { sleep 0.01; :value } +``` + +Instead, it can be created directly as already-resolved: + +```ruby +Concurrent::Promises.fulfilled_future(:value) +Concurrent::Promises.rejected_future(StandardError.new('Ups')) +Concurrent::Promises.resolved_future(true, :value, nil) +Concurrent::Promises.resolved_future(false, nil, StandardError.new('Ups')) +``` + +## Chaining + +A big advantage of promises is the ability to chain tasks together without blocking +the current thread. + +```ruby +Concurrent::Promises. + future(2) { |v| v.succ }. + then(&:succ). + value! +``` + +As `future` factory method takes an argument, so does the `then` method. Any +supplied arguments are passed to the block, and the library ensures that they +are visible to the block. + +```ruby +Concurrent::Promises. + future('3') { |s| s.to_i }. + then(2) { |v, arg| v + arg }. + value +Concurrent::Promises. + fulfilled_future('3'). + then(&:to_i). + then(2, &:+). + value +Concurrent::Promises. + fulfilled_future(1). + chain(2) { |fulfilled, value, reason, arg| value + arg }. + value +``` + +Passing the arguments in (similarly as for a thread `Thread.new(arg) { |arg| +do_stuff arg }`) is **required**. Both of the following bad examples may break: + +```ruby +arg = 1 +Thread.new { do_stuff arg } +Concurrent::Promises.future { do_stuff arg } +``` + +Correct: + +```ruby +arg = 1 +Thread.new(arg) { |arg| do_stuff arg } +Concurrent::Promises.future(arg) { |arg| do_stuff arg } +``` + +## Branching, and zipping + +Besides chaining it can also be branched. + +```ruby +head = Concurrent::Promises.fulfilled_future -1 # +branch1 = head.then(&:abs) # +branch2 = head.then(&:succ).then(&:succ) # + +branch1.value! +branch2.value! +``` + +It can be combined back to one future by zipping (`zip`, `&`). + +```ruby +branch1.zip(branch2).value! +(branch1 & branch2). + then { |a, b| a + b }. + value! +(branch1 & branch2). + then(&:+). + value! +Concurrent::Promises. + zip(branch1, branch2, branch1). + then { |*values| values.reduce(&:+) }. + value! +``` + +Instead of zipping only the first one can be taken, if needed. + +```ruby +Concurrent::Promises.any(branch1, branch2).value! +(branch1 | branch2).value! +``` + +## Blocking methods + +In these examples we have used blocking methods like `value` extensively for +their convenience, however in practice is better to avoid them and continue +chaining. + +If they need to be used (e.g. when integrating with threads), `value!` is a +better option over `value` when rejections are not dealt with differently. +Otherwise the rejections are not handled and probably silently forgotten. + +## Error handling + +When a task in the chain fails, the rejection propagates down the +chain without executing the tasks created with `then`. + +```ruby +Concurrent::Promises. + fulfilled_future(Object.new). + then(&:succ). + then(&:succ). + result +``` + +As `then` chained tasks execute only on fulfilled futures, there is a `rescue` +method which chains a task which is executed only when the future is rejected. +It can be used to recover from rejection. + +Using rescue to fulfill to 0 instead of the error. + +```ruby +Concurrent::Promises. + fulfilled_future(Object.new). + then(&:succ). + then(&:succ). + rescue { |err| 0 }. + result +``` + +Rescue not executed when there is no rejection. + +```ruby +Concurrent::Promises. + fulfilled_future(1). + then(&:succ). + then(&:succ). + rescue { |e| 0 }. + result +``` + +Tasks added with `chain` are always evaluated. + +```ruby +Concurrent::Promises. + fulfilled_future(1). + chain { |fulfilled, value, reason| fulfilled ? value : reason }. + value! +Concurrent::Promises. + rejected_future(StandardError.new('Ups')). + chain { |fulfilled, value, reason| fulfilled ? value : reason }. + value! +``` + +Zip is rejected if any of the zipped futures is. + +```ruby +rejected_zip = Concurrent::Promises.zip( + Concurrent::Promises.fulfilled_future(1), + Concurrent::Promises.rejected_future(StandardError.new('Ups'))) +rejected_zip.result +rejected_zip. + rescue { |reason1, reason2| (reason1 || reason2).message }. + value +``` + +## Delayed futures + +Delayed futures will not evaluate until asked by `touch` or other method +requiring resolution. + +```ruby +future = Concurrent::Promises.delay { sleep 0.01; 'lazy' } +sleep 0.01 # +future.resolved? +future.touch +sleep 0.02 # +future.resolved? +``` + +All blocking methods like `wait`, `value` call `touch` and trigger evaluation. + +```ruby +Concurrent::Promises.delay { :value }.value +``` + +It propagates up through the chain, allowing whole or partial lazy chains. + +```ruby +head = Concurrent::Promises.delay { 1 } # +branch1 = head.then(&:succ) # +branch2 = head.delay.then(&:succ) # +join = branch1 & branch2 # + +sleep 0.01 # +``` + +Nothing resolves. + +```ruby +[head, branch1, branch2, join].map(&:resolved?) +``` + +Force `branch1` evaluation. + +```ruby +branch1.value +sleep 0.01 # +[head, branch1, branch2, join].map(&:resolved?) +``` + +Force evaluation of both by calling `value` on `join`. + +```ruby +join.value +[head, branch1, branch2, join].map(&:resolved?) +``` + +## Flatting + +Sometimes it is needed to wait for an inner future. An apparent solution is to wait +inside the future `Concurrent::Promises.future { Concurrent::Promises.future { 1+1 }.value }.value`. +However, as mentioned before, `value` calls should be **avoided** to avoid +blocking threads. Therefore there is a `#flat` method which is a correct solution +in this situation and does not block any thread. + +```ruby +Concurrent::Promises.future { Concurrent::Promises.future { 1+1 } }.flat.value! +``` + +A more complicated example. + +```ruby +Concurrent::Promises. + future { Concurrent::Promises.future { Concurrent::Promises.future { 1 + 1 } } }. + flat(1). + then { |future| future.then(&:succ) }. + flat(1). + value! +``` + +## Scheduling + +Tasks can be planned to be executed with a time delay. + +Schedule task to be executed in 0.1 seconds. + +```ruby +scheduled = Concurrent::Promises.schedule(0.1) { 1 } +scheduled.resolved? +``` + +Value will become available after 0.1 seconds. + +```ruby +scheduled.value +``` + +It can be used in the chain as well, where the delay is counted from the moment +its parent resolves. Therefore, the following future will be resolved in 0.2 seconds. + +```ruby +future = Concurrent::Promises. + future { sleep 0.01; :result }. + schedule(0.01). + then(&:to_s). + value! +``` + +Time can be used as well. + +```ruby +Concurrent::Promises.schedule(Time.now + 10) { :val } +``` + +## Resolvable Future and Event: + +Sometimes it is required to resolve a future externally, in these cases +`resolvable_future` and `resolvable_event` factory methods can be used. See +{Concurrent::Promises::ResolvableFuture} and +{Concurrent::Promises::ResolvableEvent}. + +```ruby +future = Concurrent::Promises.resolvable_future +``` + +The thread will be blocked until the future is resolved + +```ruby +thread = Thread.new { future.value } # +future.fulfill 1 +thread.value +``` + +A future can be resolved only once. + +```ruby +future.fulfill 1 rescue $! +future.fulfill 2, false +``` + +## How are promises executed? + +Promises use global pools to execute the tasks. Therefore each task may run on +different threads which implies that users have to be careful not to depend on +Thread-local variables (or they have to be set at the beginning of the task and +cleaned up at the end of the task). + +Since the tasks are running on may different threads of the thread pool, it's +better to follow following rules: + +- Use only data passed via arguments or values of parent futures, to + have better control over what are futures accessing. +- The data passed in and out of futures is easier to deal with if it is + immutable or at least treated as such. +- Any mutable and mutated object accessed by more than one thread or future + must be thread-safe, see {Concurrent::Array}, {Concurrent::Hash}, and + {Concurrent::Map}. (The value of a future may be consumed by many futures.) +- Futures can access outside objects, but they have to be thread-safe. + +> *TODO: This part to be extended* + +# Advanced + +## Callbacks + +```ruby +queue = Queue.new +future = Concurrent::Promises.delay { 1 + 1 } + +future.on_fulfillment { queue << 1 } # evaluated asynchronously +future.on_fulfillment! { queue << 2 } # evaluated on resolving thread + +queue.empty? +future.value +queue.pop +queue.pop +``` + +## Using executors + +Factory methods, chain, and callback methods all have other versions of them +which takes an executor argument. + +It takes an instance of an executor, or a symbol which is a shortcut for the +two global pools in concurrent-ruby. `:fast` for short and non-blocking tasks +and `:io` for long-running and blocking tasks. + +```ruby +Concurrent::Promises.future_on(:fast) { 2 }. + then_on(:io) { File.read __FILE__ }. + value.size +``` + +## Run (simulated process) + +Similar to flatting is running. When `run` is called on a future it will flat +indefinitely as long the future fulfils into a `Future` value. It can be used +to simulate a thread-like processing without actually occupying the thread. + +```ruby +count = lambda do |v| + v += 1 + v < 5 ? Concurrent::Promises.future_on(:fast, v, &count) : v +end +400.times. + map { Concurrent::Promises.future_on(:fast, 0, &count).run.value! }. + all? { |v| v == 5 } +``` + +Therefore the above example finished fine on the the `:fast` thread pool even +though it has much fewer threads than are simulated in the simulated process. + +# Interoperability + +## Actors + +Create an actor which takes received numbers and returns the number squared. + +```ruby +actor = Concurrent::Actor::Utils::AdHoc.spawn :square do + -> v { v ** 2 } +end +``` + +Send result of `1+1` to the actor, and add 2 to the result sent back from the +actor. + +```ruby +Concurrent::Promises. + future { 1 + 1 }. + then_ask(actor). + then { |v| v + 2 }. + value! +``` + +So `(1 + 1)**2 + 2 = 6`. + +The `ask` method returns future. + +```ruby +actor.ask(2).then(&:succ).value! +``` + +## Channel + +There is an implementation of channel as well. Let's start by creating a +channel with a capacity of 2 messages. + +```ruby +ch1 = Concurrent::Promises::Channel.new 2 +``` + +We push 3 messages, it can be observed that the last future representing the +push is not fulfilled since the capacity prevents it. When the work which fills +the channel depends on the futures created by push it can be used to create +backpressure – the filling work is delayed until the channel has space for +more messages. + +```ruby +pushes = 3.times.map { |i| ch1.push_op i } +ch1.pop_op.value! +pushes +``` + +A selection over channels can be created with the `.select_channel` factory method. It +will be fulfilled with a first message available in any of the channels. It +returns a pair to be able to find out which channel had the message available. + +```ruby +ch2 = Concurrent::Promises::Channel.new 2 +result = Concurrent::Promises::Channel.select_op([ch1, ch2]) +result.value! + +Concurrent::Promises.future { 1+1 }.then_channel_push(ch1) +result = ( + Concurrent::Promises.fulfilled_future('%02d') & + Concurrent::Promises::Channel.select_op([ch1, ch2])). + then { |format, (channel, value)| format format, value } # +result.value! +``` + +## ProcessingActor + +There is also a new implementation of actors based on the Channel and the +ability of promises to simulate processes. The actor runs as a process but also +does not occupy a thread per actor as the previously-described Concurrent::Actor +implementation. This implementation is close to Erlang actors, therefore OTP +can be ported for this actors (and it's planned). + +The simplest actor is one which just computes without even receiving a +message. + +```ruby +actor = Concurrent::ProcessingActor.act(an_argument = 2) do |actor, number| + number ** 3 +end +actor.termination.value! +``` +Let's receive some messages though. + +```ruby +add_2_messages = Concurrent::ProcessingActor.act do |actor| + # Receive two messages then terminate normally with the sum. + (actor.receive & actor.receive).then do |a, b| + a + b + end +end +add_2_messages.tell_op 1 +add_2_messages.termination.resolved? +add_2_messages.tell_op 3 +add_2_messages.termination.value! +``` + +Actors can also be used to apply backpressure to a producer. Let's start by +defining an actor which a mailbox of size 2. + +```ruby +slow_counter = -> (actor, count) do + actor.receive.then do |command, number| + sleep 0.01 + case command + when :add + slow_counter.call actor, count + number + when :done + # terminate + count + end + end +end + +actor = Concurrent::ProcessingActor.act_listening( + Concurrent::Promises::Channel.new(2), + 0, + &slow_counter) +``` + +Now we can create a producer which will push messages only when there is a +space available in the mailbox. We use promises to free a thread during waiting +on a free space in the mailbox. + +```ruby +produce = -> receiver, i do + if i < 10 + receiver. + # send a message to the actor, resolves only after the message is + # accepted by the actor's mailbox + tell_op([:add, i]). + # send incremented message when the above message is accepted + then(i+1, &produce) + else + receiver.tell_op(:done) + # do not continue + end +end + +Concurrent::Promises.future(actor, 0, &produce).run.wait! + +actor.termination.value! +``` + + +# Use-cases + +## Simple background processing + +```ruby +Concurrent::Promises.future { do_stuff } +``` + +## Parallel background processing + +```ruby +tasks = 4.times.map { |i| Concurrent::Promises.future(i) { |i| i*2 } } +Concurrent::Promises.zip(*tasks).value! +``` + +## Actor background processing + +Actors are mainly keep and isolate state, they should stay responsive not being +blocked by a longer running computations. It desirable to offload the work to +stateless promises. + +Lets define an actor which will process jobs, while staying responsive, and +tracking the number of tasks being processed. + +```ruby +class Computer < Concurrent::Actor::RestartingContext + def initialize + super() + @jobs = {} + end + + def on_message(msg) + command, *args = msg + case command + # new job to process + when :run + job = args[0] + @jobs[job] = envelope.future + # Process asynchronously and send message back when done. + Concurrent::Promises.future(&job).chain(job) do |fulfilled, value, reason, job| + self.tell [:done, job, fulfilled, value, reason] + end + # Do not make return value of this method to be answer of this message. + # We are answering later in :done by resolving the future kept in @jobs. + Concurrent::Actor::Behaviour::MESSAGE_PROCESSED + when :done + job, fulfilled, value, reason = *args + future = @jobs.delete job + # Answer the job's result. + future.resolve fulfilled, value, reason + when :status + { running_jobs: @jobs.size } + else + # Continue to fail with unknown message. + pass + end + end +end +``` + +Create the computer actor and send it 3 jobs. + +```ruby +computer = Concurrent::Actor.spawn Computer, :computer +results = 3.times.map { computer.ask [:run, -> { sleep 0.01; :result }] } +computer.ask(:status).value! +results.map(&:value!) +``` +## Solving the Thread count limit by thread simulation + +Sometimes an application requires to process a lot of tasks concurrently. If +the number of concurrent tasks is high enough than it is not possible to create +a Thread for each of them. A partially satisfactory solution could be to use +Fibers, but that solution locks the application on MRI since other Ruby +implementations are using threads for each Fiber. + +This library provides a {Concurrent::Promises::Future#run} method on a future +to simulate threads without actually accepting one all the time. The run method +is similar to {Concurrent::Promises::Future#flat} but it will keep flattening +until it's fulfilled with non future value, then the value is taken as a result +of the process simulated by `run`. + +```ruby +body = lambda do |v| + # Some computation step of the process + new_v = v + 1 + # Is the process finished? + if new_v < 5 + # Continue computing with new value, does not have to be recursive. + # It just has to return a future. + Concurrent::Promises.future(new_v, &body) + else + # The process is finished, fulfill the final value with `new_v`. + new_v + end +end +Concurrent::Promises.future(0, &body).run.value! # => 5 +``` + +This solution works well an any Ruby implementation. + +> *TODO: More examples to be added.* + +## Throttling concurrency + +By creating an actor managing the resource we can control how many threads is +accessing the resource. In this case one at the time. + +```ruby +data = Array.new(10) { |i| '*' * i } +DB = Concurrent::Actor::Utils::AdHoc.spawn :db, data do |data| + lambda do |message| + # pretending that this queries a DB + data[message] + end +end + +concurrent_jobs = 11.times.map do |v| + DB. + # ask the DB with the `v`, only one at the time, rest is parallel + ask(v). + # get size of the string, rejects for 11 + then(&:size). + # translate error to a value (message of the exception) + rescue { |reason| reason.message } +end # + +Concurrent::Promises.zip(*concurrent_jobs).value! +``` + +Often there is more then one DB connections, then the pool can be used. + +```ruby +pool_size = 5 + +DB_POOL = Concurrent::Actor::Utils::Pool.spawn!('DB-pool', pool_size) do |index| + # DB connection constructor + Concurrent::Actor::Utils::AdHoc.spawn( + name: "connection-#{index}", + args: [data]) do |data| + lambda do |message| + # pretending that this queries a DB + data[message] + end + end +end + +concurrent_jobs = 11.times.map do |v| + DB_POOL. + # ask the DB with the `v`, only one at the time, rest is parallel + ask(v). + # get size of the string, rejects for 11 + then(&:size). + # translate error to a value (message of the exception) + rescue { |reason| reason.message } +end # + +Concurrent::Promises.zip(*concurrent_jobs).value! +``` + +In other cases the DB adapter maintains its internal connection pool and we +just need to limit concurrent access to the DB's API to avoid the calls being +blocked. + +Lets pretend that the `#[]` method on `DB_INTERNAL_POOL` is using the internal +pool of size 3. We create throttle with the same size + +```ruby +DB_INTERNAL_POOL = Concurrent::Array.new data + +max_tree = Concurrent::Throttle.new 3 + +futures = 11.times.map do |i| + max_tree. + # throttled tasks, at most 3 simultaneous calls of [] on the database + future { DB_INTERNAL_POOL[i] }. + # un-throttled tasks, unlimited concurrency + then { |starts| starts.size }. + rescue { |reason| reason.message } +end # + +futures.map(&:value!) +``` + +## Long stream of tasks, applying backpressure + +Let's assume that we are querying an API for data and the queries can be faster +than we are able to process them. This example shows how to use channel as a +buffer and how to apply backpressure to slow down the queries. + +```ruby +require 'json' # + +channel = Concurrent::Promises::Channel.new 6 +cancellation, origin = Concurrent::Cancellation.new + +def query_random_text(cancellation, channel) + Concurrent::Promises.future do + # for simplicity the query is omitted + # url = 'some api' + # Net::HTTP.get(URI(url)) + sleep 0.01 + { 'message' => + 'Lorem ipsum rhoncus scelerisque vulputate diam inceptos' + }.to_json + end.then_flat_event(cancellation) do |value, cancellation| + # The push to channel is fulfilled only after the message is successfully + # published to the channel, therefore it will not continue querying until + # current message is pushed. + cancellation.origin | channel.push_op(value) + # It could wait on the push indefinitely if the token is not checked + # here with `or` (the pipe). + end.then(cancellation) do |cancellation| + # query again after the message is pushed to buffer + query_random_text(cancellation, channel) unless cancellation.canceled? + end +end + +words = [] +words_throttle = Concurrent::Throttle.new 1 + +def count_words_in_random_text(cancellation, channel, words, words_throttle) + channel.pop_op.then do |response| + string = JSON.load(response)['message'] + # processing is slower than querying + sleep 0.02 + words_count = string.scan(/\w+/).size + end.then_on(words_throttle.on(:io), words) do |words_count, words| + # safe since throttled to only 1 task at a time + words << words_count + end.then_on(:io, cancellation) do |_, cancellation| + # count words in next message + unless cancellation.canceled? + count_words_in_random_text(cancellation, channel, words, words_throttle) + end + end +end + +query_processes = 3.times.map do + Concurrent::Promises.future(cancellation, channel, &method(:query_random_text)).run +end + +word_counter_processes = 2.times.map do + Concurrent::Promises.future(cancellation, channel, words, words_throttle, + &method(:count_words_in_random_text)).run +end + +sleep 0.05 # +``` + +Let it run for a while, then cancel it, and ensure that the runs were all fulfilled +(therefore ended) after the cancellation. Finally, print the result. + +```ruby +origin.resolve +query_processes.map(&:wait!) +word_counter_processes.map(&:wait!) +words +``` + +Compared to using threads directly, this is highly configurable and composable +solution. + + +## Periodic task + +A periodically executed task can be creating by combining `schedule`, `run` and `Cancellation`. + +```ruby +repeating_scheduled_task = -> interval, cancellation, task do + Concurrent::Promises. + # Schedule the task. + schedule(interval, cancellation, &task). + # If successful schedule again. + # Alternatively use chain to schedule always. + then { repeating_scheduled_task.call(interval, cancellation, task) } +end + +cancellation, origin = Concurrent::Cancellation.new + +task = -> cancellation do + 5.times do + cancellation.check! + do_stuff + end +end + +result = Concurrent::Promises.future(0.1, cancellation, task, &repeating_scheduled_task).run +sleep 0.03 # +origin.resolve +result.result +``` + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.init.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.init.rb new file mode 100644 index 0000000000..f77a83d605 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.init.rb @@ -0,0 +1,6 @@ +require 'concurrent-edge' + +def do_stuff(*args) + sleep 0.01 + :stuff +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.out.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.out.md new file mode 100644 index 0000000000..deb4b42288 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/promises.out.md @@ -0,0 +1,1140 @@ +# Basics + +## Factory methods + +Future and Event are created indirectly with constructor methods in +FactoryMethods. They are not designed for inheritance but rather for +composition. + +```ruby +Concurrent::Promises::FactoryMethods.instance_methods(false).sort +# => [:any, +# :any_event, +# :any_event_on, +# :any_fulfilled_future, +# :any_fulfilled_future_on, +# :any_resolved_future, +# :any_resolved_future_on, +# :delay, +# :delay_on, +# :fulfilled_future, +# :future, +# :future_on, +# :make_future, +# :rejected_future, +# :resolvable_event, +# :resolvable_event_on, +# :resolvable_future, +# :resolvable_future_on, +# :resolved_event, +# :resolved_future, +# :schedule, +# :schedule_on, +# :zip, +# :zip_events, +# :zip_events_on, +# :zip_futures, +# :zip_futures_on, +# :zip_futures_over, +# :zip_futures_over_on] +``` + +The module can be included or extended where needed. + +```ruby +Class.new do + include Concurrent::Promises::FactoryMethods + + def a_method + resolvable_event + end +end.new.a_method +# => # + +mod = Module.new do + extend Concurrent::Promises::FactoryMethods +end +mod.resolvable_event +# => # +``` + +The default executor can be changed by overriding `default_executor` method +inherited from `Concurrent::Promises::FactoryMethods`. + +```ruby +mod = Module.new do + extend Concurrent::Promises::FactoryMethods + def self.default_executor + :fast + end +end +mod.future { 1 }.default_executor # => :fast +Concurrent::Promises.future { 1 }.default_executor +# => :io +``` + + +The module is already extended into {Concurrent::Promises} for convenience. + +```ruby +Concurrent::Promises.resolvable_event +# => # +``` + +## Asynchronous task + +The most basic use-case of the framework is asynchronous processing. A task can +be processed asynchronously by using a `future` factory method. The block will +be executed on an internal thread pool. + +Arguments of `future` are passed to the block and evaluation starts immediately. + +```ruby +future = Concurrent::Promises.future(0.1) do |duration| + sleep duration + :result +end +# => # +future.value # => :result +``` + +Asks if the future is resolved, here it will be still in the middle of the +sleep call. + +```ruby +future.resolved? # => true +``` + +Retrieving the value will block until the future is **resolved**. + +```ruby +future.value # => :result +future.resolved? # => true +``` + +If the task fails, we talk about the future being **rejected**. + +```ruby +future = Concurrent::Promises.future { sleep 0.01; raise 'Boom' } +# => # +``` + +There is no result, the future was rejected with a reason. + +```ruby +future.value # => nil +future.reason # => # +``` + +It can be forced to raise the reason for rejection when retrieving the value. + +```ruby +begin + future.value! +rescue => e + e +end # => # +``` + +Which is the same as `future.value! rescue $!` which will be used hereafter. + +Or it can be used directly as argument for raise, since it implements exception +method. + +```ruby +raise future rescue $! # => # +``` + +## States + +Let's define an inspection helper for methods. + +```ruby +def inspect_methods(*methods, of:) + methods.reduce({}) { |h, m| h.update m => of.send(m) } +end +``` + +Event has a `pending` and a `resolved` state. + +```ruby +event = Concurrent::Promises.resolvable_event +inspect_methods(:state, :pending?, :resolved?, of: event) +# => {:state=>:pending, :pending?=>true, :resolved?=>false} + +event.resolve +inspect_methods(:state, :pending?, :resolved?, of: event) +# => {:state=>:resolved, :pending?=>false, :resolved?=>true} +``` + +Future's `resolved` state is further specified to be `fulfilled` or `rejected`. + +```ruby +future = Concurrent::Promises.resolvable_future +inspect_methods(:state, :pending?, :resolved?, :fulfilled?, :rejected?, + of: future) +# => {:state=>:pending, +# :pending?=>true, +# :resolved?=>false, +# :fulfilled?=>false, +# :rejected?=>false} + +future.fulfill :value +inspect_methods(:state, :pending?, :resolved?, :fulfilled?, :rejected?, + :result, :value, :reason, of: future) +# => {:state=>:fulfilled, +# :pending?=>false, +# :resolved?=>true, +# :fulfilled?=>true, +# :rejected?=>false, +# :result=>[true, :value, nil], +# :value=>:value, +# :reason=>nil} + +future = Concurrent::Promises.rejected_future StandardError.new +inspect_methods(:state, :pending?, :resolved?, :fulfilled?, :rejected?, + :result, :value, :reason, of: future) +# => {:state=>:rejected, +# :pending?=>false, +# :resolved?=>true, +# :fulfilled?=>false, +# :rejected?=>true, +# :result=>[false, nil, #], +# :value=>nil, +# :reason=>#} +``` + +## Direct creation of resolved futures + +When an existing value has to be wrapped in a future it does not have to go +through evaluation as follows. + +```ruby +Concurrent::Promises.future { sleep 0.01; :value } +# => # +``` + +Instead, it can be created directly as already-resolved: + +```ruby +Concurrent::Promises.fulfilled_future(:value) +# => # +Concurrent::Promises.rejected_future(StandardError.new('Ups')) +# => #> +Concurrent::Promises.resolved_future(true, :value, nil) +# => # +Concurrent::Promises.resolved_future(false, nil, StandardError.new('Ups')) +# => #> +``` + +## Chaining + +A big advantage of promises is the ability to chain tasks together without blocking +the current thread. + +```ruby +Concurrent::Promises. + future(2) { |v| v.succ }. + then(&:succ). + value! # => 4 +``` + +As `future` factory method takes an argument, so does the `then` method. Any +supplied arguments are passed to the block, and the library ensures that they +are visible to the block. + +```ruby +Concurrent::Promises. + future('3') { |s| s.to_i }. + then(2) { |v, arg| v + arg }. + value # => 5 +Concurrent::Promises. + fulfilled_future('3'). + then(&:to_i). + then(2, &:+). + value # => 5 +Concurrent::Promises. + fulfilled_future(1). + chain(2) { |fulfilled, value, reason, arg| value + arg }. + value # => 3 +``` + +Passing the arguments in (similarly as for a thread `Thread.new(arg) { |arg| +do_stuff arg }`) is **required**. Both of the following bad examples may break: + +```ruby +arg = 1 # => 1 +Thread.new { do_stuff arg } +# => # +Concurrent::Promises.future { do_stuff arg } +# => # +``` + +Correct: + +```ruby +arg = 1 # => 1 +Thread.new(arg) { |arg| do_stuff arg } +# => # +Concurrent::Promises.future(arg) { |arg| do_stuff arg } +# => # +``` + +## Branching, and zipping + +Besides chaining it can also be branched. + +```ruby +head = Concurrent::Promises.fulfilled_future -1 +branch1 = head.then(&:abs) +branch2 = head.then(&:succ).then(&:succ) + +branch1.value! # => 1 +branch2.value! # => 1 +``` + +It can be combined back to one future by zipping (`zip`, `&`). + +```ruby +branch1.zip(branch2).value! # => [1, 1] +(branch1 & branch2). + then { |a, b| a + b }. + value! # => 2 +(branch1 & branch2). + then(&:+). + value! # => 2 +Concurrent::Promises. + zip(branch1, branch2, branch1). + then { |*values| values.reduce(&:+) }. + value! # => 3 +``` + +Instead of zipping only the first one can be taken, if needed. + +```ruby +Concurrent::Promises.any(branch1, branch2).value! +# => 1 +(branch1 | branch2).value! # => 1 +``` + +## Blocking methods + +In these examples we have used blocking methods like `value` extensively for +their convenience, however in practice is better to avoid them and continue +chaining. + +If they need to be used (e.g. when integrating with threads), `value!` is a +better option over `value` when rejections are not dealt with differently. +Otherwise the rejections are not handled and probably silently forgotten. + +## Error handling + +When a task in the chain fails, the rejection propagates down the +chain without executing the tasks created with `then`. + +```ruby +Concurrent::Promises. + fulfilled_future(Object.new). + then(&:succ). + then(&:succ). + result +# => [false, +# nil, +# #>] +``` + +As `then` chained tasks execute only on fulfilled futures, there is a `rescue` +method which chains a task which is executed only when the future is rejected. +It can be used to recover from rejection. + +Using rescue to fulfill to 0 instead of the error. + +```ruby +Concurrent::Promises. + fulfilled_future(Object.new). + then(&:succ). + then(&:succ). + rescue { |err| 0 }. + result # => [true, 0, nil] +``` + +Rescue not executed when there is no rejection. + +```ruby +Concurrent::Promises. + fulfilled_future(1). + then(&:succ). + then(&:succ). + rescue { |e| 0 }. + result # => [true, 3, nil] +``` + +Tasks added with `chain` are always evaluated. + +```ruby +Concurrent::Promises. + fulfilled_future(1). + chain { |fulfilled, value, reason| fulfilled ? value : reason }. + value! # => 1 +Concurrent::Promises. + rejected_future(StandardError.new('Ups')). + chain { |fulfilled, value, reason| fulfilled ? value : reason }. + value! # => # +``` + +Zip is rejected if any of the zipped futures is. + +```ruby +rejected_zip = Concurrent::Promises.zip( + Concurrent::Promises.fulfilled_future(1), + Concurrent::Promises.rejected_future(StandardError.new('Ups'))) +# => #]> +rejected_zip.result +# => [false, [1, nil], [nil, #]] +rejected_zip. + rescue { |reason1, reason2| (reason1 || reason2).message }. + value # => "Ups" +``` + +## Delayed futures + +Delayed futures will not evaluate until asked by `touch` or other method +requiring resolution. + +```ruby +future = Concurrent::Promises.delay { sleep 0.01; 'lazy' } +# => # +sleep 0.01 +future.resolved? # => false +future.touch +# => # +sleep 0.02 +future.resolved? # => true +``` + +All blocking methods like `wait`, `value` call `touch` and trigger evaluation. + +```ruby +Concurrent::Promises.delay { :value }.value +# => :value +``` + +It propagates up through the chain, allowing whole or partial lazy chains. + +```ruby +head = Concurrent::Promises.delay { 1 } +branch1 = head.then(&:succ) +branch2 = head.delay.then(&:succ) +join = branch1 & branch2 + +sleep 0.01 +``` + +Nothing resolves. + +```ruby +[head, branch1, branch2, join].map(&:resolved?) +# => [false, false, false, false] +``` + +Force `branch1` evaluation. + +```ruby +branch1.value # => 2 +sleep 0.01 +[head, branch1, branch2, join].map(&:resolved?) +# => [true, true, false, false] +``` + +Force evaluation of both by calling `value` on `join`. + +```ruby +join.value # => [2, 2] +[head, branch1, branch2, join].map(&:resolved?) +# => [true, true, true, true] +``` + +## Flatting + +Sometimes it is needed to wait for an inner future. An apparent solution is to wait +inside the future `Concurrent::Promises.future { Concurrent::Promises.future { 1+1 }.value }.value`. +However, as mentioned before, `value` calls should be **avoided** to avoid +blocking threads. Therefore there is a `#flat` method which is a correct solution +in this situation and does not block any thread. + +```ruby +Concurrent::Promises.future { Concurrent::Promises.future { 1+1 } }.flat.value! +# => 2 +``` + +A more complicated example. + +```ruby +Concurrent::Promises. + future { Concurrent::Promises.future { Concurrent::Promises.future { 1 + 1 } } }. + flat(1). + then { |future| future.then(&:succ) }. + flat(1). + value! # => 3 +``` + +## Scheduling + +Tasks can be planned to be executed with a time delay. + +Schedule task to be executed in 0.1 seconds. + +```ruby +scheduled = Concurrent::Promises.schedule(0.1) { 1 } +# => # +scheduled.resolved? # => false +``` + +Value will become available after 0.1 seconds. + +```ruby +scheduled.value # => 1 +``` + +It can be used in the chain as well, where the delay is counted from the moment +its parent resolves. Therefore, the following future will be resolved in 0.2 seconds. + +```ruby +future = Concurrent::Promises. + future { sleep 0.01; :result }. + schedule(0.01). + then(&:to_s). + value! # => "result" +``` + +Time can be used as well. + +```ruby +Concurrent::Promises.schedule(Time.now + 10) { :val } +# => # +``` + +## Resolvable Future and Event: + +Sometimes it is required to resolve a future externally, in these cases +`resolvable_future` and `resolvable_event` factory methods can be used. See +{Concurrent::Promises::ResolvableFuture} and +{Concurrent::Promises::ResolvableEvent}. + +```ruby +future = Concurrent::Promises.resolvable_future +# => # +``` + +The thread will be blocked until the future is resolved + +```ruby +thread = Thread.new { future.value } +future.fulfill 1 +# => # +thread.value # => 1 +``` + +A future can be resolved only once. + +```ruby +future.fulfill 1 rescue $! +# => #[true, 1, nil], :new_result=>[true, 1, nil]}> +future.fulfill 2, false # => false +``` + +## How are promises executed? + +Promises use global pools to execute the tasks. Therefore each task may run on +different threads which implies that users have to be careful not to depend on +Thread-local variables (or they have to be set at the beginning of the task and +cleaned up at the end of the task). + +Since the tasks are running on may different threads of the thread pool, it's +better to follow following rules: + +- Use only data passed via arguments or values of parent futures, to + have better control over what are futures accessing. +- The data passed in and out of futures is easier to deal with if it is + immutable or at least treated as such. +- Any mutable and mutated object accessed by more than one thread or future + must be thread-safe, see {Concurrent::Array}, {Concurrent::Hash}, and + {Concurrent::Map}. (The value of a future may be consumed by many futures.) +- Futures can access outside objects, but they have to be thread-safe. + +> *TODO: This part to be extended* + +# Advanced + +## Callbacks + +```ruby +queue = Queue.new # => # +future = Concurrent::Promises.delay { 1 + 1 } +# => # + +future.on_fulfillment { queue << 1 } # evaluated asynchronously +future.on_fulfillment! { queue << 2 } # evaluated on resolving thread + +queue.empty? # => true +future.value # => 2 +queue.pop # => 2 +queue.pop # => 1 +``` + +## Using executors + +Factory methods, chain, and callback methods all have other versions of them +which takes an executor argument. + +It takes an instance of an executor, or a symbol which is a shortcut for the +two global pools in concurrent-ruby. `:fast` for short and non-blocking tasks +and `:io` for long-running and blocking tasks. + +```ruby +Concurrent::Promises.future_on(:fast) { 2 }. + then_on(:io) { File.read __FILE__ }. + value.size # => 25384 +``` + +## Run (simulated process) + +Similar to flatting is running. When `run` is called on a future it will flat +indefinitely as long the future fulfils into a `Future` value. It can be used +to simulate a thread-like processing without actually occupying the thread. + +```ruby +count = lambda do |v| + v += 1 + v < 5 ? Concurrent::Promises.future_on(:fast, v, &count) : v +end +# => # +400.times. + map { Concurrent::Promises.future_on(:fast, 0, &count).run.value! }. + all? { |v| v == 5 } # => true +``` + +Therefore the above example finished fine on the the `:fast` thread pool even +though it has much fewer threads than are simulated in the simulated process. + +# Interoperability + +## Actors + +Create an actor which takes received numbers and returns the number squared. + +```ruby +actor = Concurrent::Actor::Utils::AdHoc.spawn :square do + -> v { v ** 2 } +end +# => # +``` + +Send result of `1+1` to the actor, and add 2 to the result sent back from the +actor. + +```ruby +Concurrent::Promises. + future { 1 + 1 }. + then_ask(actor). + then { |v| v + 2 }. + value! # => 6 +``` + +So `(1 + 1)**2 + 2 = 6`. + +The `ask` method returns future. + +```ruby +actor.ask(2).then(&:succ).value! # => 5 +``` + +## Channel + +There is an implementation of channel as well. Let's start by creating a +channel with a capacity of 2 messages. + +```ruby +ch1 = Concurrent::Promises::Channel.new 2 +# => # +``` + +We push 3 messages, it can be observed that the last future representing the +push is not fulfilled since the capacity prevents it. When the work which fills +the channel depends on the futures created by push it can be used to create +backpressure – the filling work is delayed until the channel has space for +more messages. + +```ruby +pushes = 3.times.map { |i| ch1.push_op i } +# => [#>, +# #>, +# #] +ch1.pop_op.value! # => 0 +pushes +# => [#>, +# #>, +# #>] +``` + +A selection over channels can be created with the `.select_channel` factory method. It +will be fulfilled with a first message available in any of the channels. It +returns a pair to be able to find out which channel had the message available. + +```ruby +ch2 = Concurrent::Promises::Channel.new 2 +# => # +result = Concurrent::Promises::Channel.select_op([ch1, ch2]) +# => #, 1]> +result.value! +# => [#, 1] + +Concurrent::Promises.future { 1+1 }.then_channel_push(ch1) +# => # +result = ( + Concurrent::Promises.fulfilled_future('%02d') & + Concurrent::Promises::Channel.select_op([ch1, ch2])). + then { |format, (channel, value)| format format, value } +result.value! # => "02" +``` + +## ProcessingActor + +There is also a new implementation of actors based on the Channel and the +ability of promises to simulate processes. The actor runs as a process but also +does not occupy a thread per actor as the previously-described Concurrent::Actor +implementation. This implementation is close to Erlang actors, therefore OTP +can be ported for this actors (and it's planned). + +The simplest actor is one which just computes without even receiving a +message. + +```ruby +actor = Concurrent::ProcessingActor.act(an_argument = 2) do |actor, number| + number ** 3 +end +# => # +actor.termination.value! # => 8 +``` +Let's receive some messages though. + +```ruby +add_2_messages = Concurrent::ProcessingActor.act do |actor| + # Receive two messages then terminate normally with the sum. + (actor.receive & actor.receive).then do |a, b| + a + b + end +end +# => # +add_2_messages.tell_op 1 +# => # +add_2_messages.termination.resolved? # => false +add_2_messages.tell_op 3 +# => # +add_2_messages.termination.value! # => 4 +``` + +Actors can also be used to apply backpressure to a producer. Let's start by +defining an actor which a mailbox of size 2. + +```ruby +slow_counter = -> (actor, count) do + actor.receive.then do |command, number| + sleep 0.01 + case command + when :add + slow_counter.call actor, count + number + when :done + # terminate + count + end + end +end +# => # + +actor = Concurrent::ProcessingActor.act_listening( + Concurrent::Promises::Channel.new(2), + 0, + &slow_counter) +# => # +``` + +Now we can create a producer which will push messages only when there is a +space available in the mailbox. We use promises to free a thread during waiting +on a free space in the mailbox. + +```ruby +produce = -> receiver, i do + if i < 10 + receiver. + # send a message to the actor, resolves only after the message is + # accepted by the actor's mailbox + tell_op([:add, i]). + # send incremented message when the above message is accepted + then(i+1, &produce) + else + receiver.tell_op(:done) + # do not continue + end +end +# => # + +Concurrent::Promises.future(actor, 0, &produce).run.wait! +# => #> + +actor.termination.value! # => 45 +``` + + +# Use-cases + +## Simple background processing + +```ruby +Concurrent::Promises.future { do_stuff } +# => # +``` + +## Parallel background processing + +```ruby +tasks = 4.times.map { |i| Concurrent::Promises.future(i) { |i| i*2 } } +# => [#, +# #, +# #, +# #] +Concurrent::Promises.zip(*tasks).value! +# => [0, 2, 4, 6] +``` + +## Actor background processing + +Actors are mainly keep and isolate state, they should stay responsive not being +blocked by a longer running computations. It desirable to offload the work to +stateless promises. + +Lets define an actor which will process jobs, while staying responsive, and +tracking the number of tasks being processed. + +```ruby +class Computer < Concurrent::Actor::RestartingContext + def initialize + super() + @jobs = {} + end + + def on_message(msg) + command, *args = msg + case command + # new job to process + when :run + job = args[0] + @jobs[job] = envelope.future + # Process asynchronously and send message back when done. + Concurrent::Promises.future(&job).chain(job) do |fulfilled, value, reason, job| + self.tell [:done, job, fulfilled, value, reason] + end + # Do not make return value of this method to be answer of this message. + # We are answering later in :done by resolving the future kept in @jobs. + Concurrent::Actor::Behaviour::MESSAGE_PROCESSED + when :done + job, fulfilled, value, reason = *args + future = @jobs.delete job + # Answer the job's result. + future.resolve fulfilled, value, reason + when :status + { running_jobs: @jobs.size } + else + # Continue to fail with unknown message. + pass + end + end +end # => :on_message +``` + +Create the computer actor and send it 3 jobs. + +```ruby +computer = Concurrent::Actor.spawn Computer, :computer +# => # +results = 3.times.map { computer.ask [:run, -> { sleep 0.01; :result }] } +# => [#, +# #, +# #] +computer.ask(:status).value! # => {:running_jobs=>3} +results.map(&:value!) # => [:result, :result, :result] +``` +## Solving the Thread count limit by thread simulation + +Sometimes an application requires to process a lot of tasks concurrently. If +the number of concurrent tasks is high enough than it is not possible to create +a Thread for each of them. A partially satisfactory solution could be to use +Fibers, but that solution locks the application on MRI since other Ruby +implementations are using threads for each Fiber. + +This library provides a {Concurrent::Promises::Future#run} method on a future +to simulate threads without actually accepting one all the time. The run method +is similar to {Concurrent::Promises::Future#flat} but it will keep flattening +until it's fulfilled with non future value, then the value is taken as a result +of the process simulated by `run`. + +```ruby +body = lambda do |v| + # Some computation step of the process + new_v = v + 1 + # Is the process finished? + if new_v < 5 + # Continue computing with new value, does not have to be recursive. + # It just has to return a future. + Concurrent::Promises.future(new_v, &body) + else + # The process is finished, fulfill the final value with `new_v`. + new_v + end +end +# => # +Concurrent::Promises.future(0, &body).run.value! # => 5 +``` + +This solution works well an any Ruby implementation. + +> *TODO: More examples to be added.* + +## Throttling concurrency + +By creating an actor managing the resource we can control how many threads is +accessing the resource. In this case one at the time. + +```ruby +data = Array.new(10) { |i| '*' * i } +# => ["", +# "*", +# "**", +# "***", +# "****", +# "*****", +# "******", +# "*******", +# "********", +# "*********"] +DB = Concurrent::Actor::Utils::AdHoc.spawn :db, data do |data| + lambda do |message| + # pretending that this queries a DB + data[message] + end +end +# => # + +concurrent_jobs = 11.times.map do |v| + DB. + # ask the DB with the `v`, only one at the time, rest is parallel + ask(v). + # get size of the string, rejects for 11 + then(&:size). + # translate error to a value (message of the exception) + rescue { |reason| reason.message } +end + +Concurrent::Promises.zip(*concurrent_jobs).value! +# => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "undefined method `size' for nil:NilClass"] +``` + +Often there is more then one DB connections, then the pool can be used. + +```ruby +pool_size = 5 # => 5 + +DB_POOL = Concurrent::Actor::Utils::Pool.spawn!('DB-pool', pool_size) do |index| + # DB connection constructor + Concurrent::Actor::Utils::AdHoc.spawn( + name: "connection-#{index}", + args: [data]) do |data| + lambda do |message| + # pretending that this queries a DB + data[message] + end + end +end +# => # + +concurrent_jobs = 11.times.map do |v| + DB_POOL. + # ask the DB with the `v`, only one at the time, rest is parallel + ask(v). + # get size of the string, rejects for 11 + then(&:size). + # translate error to a value (message of the exception) + rescue { |reason| reason.message } +end + +Concurrent::Promises.zip(*concurrent_jobs).value! +# => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "undefined method `size' for nil:NilClass"] +``` + +In other cases the DB adapter maintains its internal connection pool and we +just need to limit concurrent access to the DB's API to avoid the calls being +blocked. + +Lets pretend that the `#[]` method on `DB_INTERNAL_POOL` is using the internal +pool of size 3. We create throttle with the same size + +```ruby +DB_INTERNAL_POOL = Concurrent::Array.new data +# => ["", +# "*", +# "**", +# "***", +# "****", +# "*****", +# "******", +# "*******", +# "********", +# "*********"] + +max_tree = Concurrent::Throttle.new 3 +# => # + +futures = 11.times.map do |i| + max_tree. + # throttled tasks, at most 3 simultaneous calls of [] on the database + future { DB_INTERNAL_POOL[i] }. + # un-throttled tasks, unlimited concurrency + then { |starts| starts.size }. + rescue { |reason| reason.message } +end + +futures.map(&:value!) +# => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "undefined method `size' for nil:NilClass"] +``` + +## Long stream of tasks, applying backpressure + +Let's assume that we are querying an API for data and the queries can be faster +than we are able to process them. This example shows how to use channel as a +buffer and how to apply backpressure to slow down the queries. + +```ruby +require 'json' + +channel = Concurrent::Promises::Channel.new 6 +# => # +cancellation, origin = Concurrent::Cancellation.new +# => # + +def query_random_text(cancellation, channel) + Concurrent::Promises.future do + # for simplicity the query is omitted + # url = 'some api' + # Net::HTTP.get(URI(url)) + sleep 0.01 + { 'message' => + 'Lorem ipsum rhoncus scelerisque vulputate diam inceptos' + }.to_json + end.then_flat_event(cancellation) do |value, cancellation| + # The push to channel is fulfilled only after the message is successfully + # published to the channel, therefore it will not continue querying until + # current message is pushed. + cancellation.origin | channel.push_op(value) + # It could wait on the push indefinitely if the token is not checked + # here with `or` (the pipe). + end.then(cancellation) do |cancellation| + # query again after the message is pushed to buffer + query_random_text(cancellation, channel) unless cancellation.canceled? + end +end # => :query_random_text + +words = [] # => [] +words_throttle = Concurrent::Throttle.new 1 +# => # + +def count_words_in_random_text(cancellation, channel, words, words_throttle) + channel.pop_op.then do |response| + string = JSON.load(response)['message'] + # processing is slower than querying + sleep 0.02 + words_count = string.scan(/\w+/).size + end.then_on(words_throttle.on(:io), words) do |words_count, words| + # safe since throttled to only 1 task at a time + words << words_count + end.then_on(:io, cancellation) do |_, cancellation| + # count words in next message + unless cancellation.canceled? + count_words_in_random_text(cancellation, channel, words, words_throttle) + end + end +end # => :count_words_in_random_text + +query_processes = 3.times.map do + Concurrent::Promises.future(cancellation, channel, &method(:query_random_text)).run +end +# => [#, +# #, +# #] + +word_counter_processes = 2.times.map do + Concurrent::Promises.future(cancellation, channel, words, words_throttle, + &method(:count_words_in_random_text)).run +end +# => [#, +# #] + +sleep 0.05 +``` + +Let it run for a while, then cancel it, and ensure that the runs were all fulfilled +(therefore ended) after the cancellation. Finally, print the result. + +```ruby +origin.resolve +# => # +query_processes.map(&:wait!) +# => [#, +# #, +# #] +word_counter_processes.map(&:wait!) +# => [#, +# #] +words # => [7, 7, 7, 7] +``` + +Compared to using threads directly, this is highly configurable and composable +solution. + + +## Periodic task + +A periodically executed task can be creating by combining `schedule`, `run` and `Cancellation`. + +```ruby +repeating_scheduled_task = -> interval, cancellation, task do + Concurrent::Promises. + # Schedule the task. + schedule(interval, cancellation, &task). + # If successful schedule again. + # Alternatively use chain to schedule always. + then { repeating_scheduled_task.call(interval, cancellation, task) } +end +# => # + +cancellation, origin = Concurrent::Cancellation.new +# => # + +task = -> cancellation do + 5.times do + cancellation.check! + do_stuff + end +end +# => # + +result = Concurrent::Promises.future(0.1, cancellation, task, &repeating_scheduled_task).run +# => # +sleep 0.03 +origin.resolve +# => # +result.result +# => [false, +# nil, +# #] +``` + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/ruby-association-final-report.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/ruby-association-final-report.md new file mode 100644 index 0000000000..f8bda0e8e3 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/ruby-association-final-report.md @@ -0,0 +1,314 @@ +# Final report + +I started working on the project 6. Dec +and I have continued working on the project until 8. February as planned. +I have worked on following abstractions Throttle, Cancellation, Channel, and ErlangActor. + +The code developed during this project is available in +. +The documentation is available at +. + +## Throttle + +The Throttle implementation originally had special APIs +to interact with other abstractions like promises. +However it was impractical and the API felt cumbersome. +Therefore the Throttle was finalized with much smaller API surface. +Capacity can be still directly acquired from the Throttle +and then released. + +The more common usage of the Throttle is with a proxy executor +`a_throttle.on(Concurrent.global_io_executor)`. +Anything executed on the proxy executor will be throttled and +execute on the given executor. There can be more than one proxy executors. +All abstractions which execute tasks have option to specify executor, +therefore the proxy executor can be injected to any abstraction +throttling its concurrency level. + +The abstraction is released in `concurrent-ruby-edge-0.5.0`. +For more details see the documentation +. + +## Cancellation + +The Cancellation abstraction provides cooperative cancellation. + +The Cancellation abstraction was originally consisting of 2 classes, +during its finalization it was however simplified +to be just a combination of Cancellation object +and an origin which is regular Event or Future, +which improves compose-ability greatly. +Any Event or Future can be easily turned into cancellation. + +The standard methods `Thread#raise` of `Thread#kill` available in Ruby +are very dangerous (see linked the blog posts bellow). +Therefore concurrent-ruby provides an alternative. +* +* +* + +It provides an object which represents a cancellation event +which can be shared between tasks. +The task has to get the reference to the object +and periodically cooperatively check that it is not cancelled. + +The abstraction is released in `concurrent-ruby-edge-0.5.0`. +For more details see the documentation +. + +## Channel + +The channel implementation is inspired by Go. +However this implementation is more flexible. +It has 3 major operations pop, push and select as expected. +Where each operation has 3 variants. +`try_(pop|push|select)` which never blocks and returns always immediately. +`(pop|push|select)` which blocks current thread until it can be done +or until it times out. +`(pop|push|select)_op` which returns Future representing the operation, +which can be easily composed with other asynchronous tasks. + +The abstraction is released in `concurrent-ruby-edge-0.5.0`. +For more details see the documentation +. + +## Erlang actors + +The actor implementation matches the Erlang's implementation. +The Erlang compatible implementation was chosen for two reasons. +First reason was to make porting of the Erlang's +[OTP](https://learnyousomeerlang.com/what-is-otp) library possible. +OTP is time proven library and even a philosophy how to write reliable concurrent applications. +Second reason was +that there is an intersection between Ruby and Elixir programmers. +Elixir runs on Erlang's VM and the programmers are familiar with OTP, +therefore they will be able to reuse their knowledge in Ruby. + +Mainly: + +* The `exit/1` and `exit/2` + functions are reimplemented with the same arguments and effects. + Even though the methods are named `terminate` to avoid collision with `Kernel#exit`.r + This re-implementation adds that the termination event not only sends signals to linked actors + but it is represented as a Future + which is fulfilled with the final value of the actor or rejected with the reason of abnormal termination. + +* The linking and monitoring between actors (called processes in Erlang) is re-implemented. + Functions `link`, `unlink`, `spawn_link`, `monitor`, `demonitor`, `spawn_monitor` + have equivalent counterparts + `link`, `unlink`, `spawn link:true`, `monitor`, `demonitor`, `spawn monitor: true`. + All the options applicable in this implementation are supported, + they effects are the same + and the ordering of signal and messages is the same as on Erlang. + +* This implementation has two functionally equivalent types of actors + `spawn type: on_thread, ...` and `spawn type: on_pool, ...`. + They differ in the execution mode. + First requires its own thread second runs on shared thread pool + therefore allowing to have millions of short or long lived actors if required. + +* Message and signal ordering of messages between two actors has same guarantee as in Erlang. + Messages and signals from A are always received by B in the order they were send. + (Signals are internal messages produced by methods like `link`.) + The ordering guarantee does not scale up to more than 2 actors in Erlang nor in this implementation. + +* Even though Ruby does not have pattern matching, this implementation provides `receive` + which is functionally equivalent. + (It is sometimes more cumbersome to use though.) + +Exit behaviour, linking, and monitoring is very well described by +[the chapter of the book "learn you some Erlang"](https://learnyousomeerlang.com/errors-and-processes). +This implementation matches the behaviours described there. + +Erlang method documentation can be found at +. + +### Actor execution modes - types + +The actors can be written in 2 modes. First will require it's own thread, +second will run on a thread pool. +Please see +[Actor types section](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ErlangActor.html) +for more details. + +### Ordering + +Especially ordering guarantees are not easy to get correct. +As an example lets have a look at the reasoning behind implementation of monitoring. +Understanding of the monitors in Erlang actors is necessary for the following part. + +When `#monitor` is called in actor A it sends a Monitor signal to actor B. +The actor B will then send a Down signal to A when it terminates. +Actor is not processing any messages or signals when after it terminates. +Therefore the monitor method needs to also check if B terminated. + +Lets reason about the ordering between sending the signal Monitor and checking termination of B. +If A first checks termination of B sending Monitor signal only if B is not terminated +then A can never get a reply if B terminates after A checks its termination and before A sends Monitor signal. +Therefore A needs to first optimistically send a Monitor signal and then check if B terminated. +If B already terminated then we do not expect it to send a Down signal, +instead the `#monitor` places Down message with reason NoActor immediately into A's mailbox. + +We will now move our focus to B considering the case when A send the signal +and the termination check of B was false. +The normal case is that B gets the Monitor signal and processes it +remembering it is monitored. +Then on termination B sends a Down signal with the reason for termination to A. +The more interesting case is when the actor B gets the Monitor signal into its mailbox +but it is terminated before it can process it. +In that case, +since we know that A did no see B terminated, +we have to process the Monitor signal even if terminated and send a corresponding Down signal to A. +Therefore the B actor termination does two main operations in the following order: +it resolves its termination future (terminates) which is used by A in monitor to do the check, +it drains its mailbox looking for signals which have to be replied to. +The mailbox draining has to happen after termination is resolved +otherwise it could happen before A sends its Monitor signal which could then go unanswered. + + B drains > A sends Monitor signal > A termination check false > B terminates + # the Monitor signal is never processed by B + +Therefore we have concluded that A has send the monitor signal first +then check B's termination and B has to terminate first +(resolve its termination future) then drain signals from mailbox. +With this ordering following cases can happen: + + A sends Monitor signal > A termination check false > B terminates > B drains and replies + A sends Monitor signal > B terminates > A termination check true therefore A places Down itself + B terminates > A sends Monitor signal > A termination check true therefore A places Down itself + +Where in each case A gets the Down message. + +There is one last problem which could happen, +the Down message could be received twice by A. +It could happen in the last two sequences +where A detects B's termination +and where we did not consider B's drain for simplicity. +The last two sequences should actually be: + + A sends Monitor signal > B terminates > A termination check true therefore A places Down itself > B drains and replies + B terminates > A sends Monitor signal > A termination check true therefore A places Down itself > B drains and replies + A sends Monitor signal > B terminates > B drains and replies > A termination check true therefore A places Down itself + B terminates > A sends Monitor signal > B drains and replies > A termination check true therefore A places Down itself + B terminates > B drains > A sends Monitor signal > A termination check true therefore A places Down itself + +In the first four orderings the drain is happening after monitor call sends Monitor signal in A +therefore the draining will send Down signal +because it cannot know if A send itself Down message about B's termination. +The A actor has to prevent the duplication. +In its state it stores an information about the active monitors (set by the `#monitor`), +when a Down message arrives it is deleted +therefore any subsequent Down messages are ignored. +Both monitor call in A and the draining in B sends Down signal with a NoActor reason +so it does not matter which arrives first. + +This was a reasoning for the actor monitoring implementation. +Other actor features like linking, demonitoring, etc. required similar approach. + +The abstraction is ready for release. +For more details about usage see the API documentation +. + +## Integration + +Integration of concurrency abstractions was a motivation of the project. +I've added Promises library to the concurrent-ruby in the past +which can represent future computations and values +and therefore can be used as a connecting element between abstractions. + +```ruby +an_actor.ask_op(:payload).then_flat { |reply| a_channel.push_op reply } +``` + +In the example above an actor is asked with a payload, +which is represented as a Future object. +When the Future is resolved with a reply +it executes the block with the reply argument +usually defined by `then` method. +In this case `then_flat` needs to be used +because we want a Future representing the value of the inner push operation +pushing the reply into a channel. +All the operations in this example are done asynchronously on a thread pool. + +Usual direct thread blocking mode is also always supported. +The following example does the same but uses the current Thread to do the work. + +```ruby +reply = an_actor.ask(:payload) # blocks until it replies +a_channel.push reply # blocks if there is no space in the channel. +``` + +In addition all blocking operations support timeouts, +since it is a good practice to give each blocking operation a timeout +and try to recover if it takes too long. +It usually prevents the whole application from hitting a deadlock, +or at least it can give developer idea what is going wrong +if timeouts are logged. + +Promises are also used instead of state flags. +So for example termination of actor is not implemented as simple `#terminated? #=> true or false` method +but `#terminated` returns a future which is resolved when the Actor terminates. +More over if it is fulfilled it means actor terminated normally with a `actor.terminated.value` +and when it is rejected it means that the actor terminated abnormally because of `actor.terminated.reason`. +That again allows to integrate with other abstractions, e.g. + +```ruby +actor.terminated.value! # block current thread until actor terminates or raise reason if any +actor.terminated.then(actor) { |value, actor| a_logger.debug "#{actor} terminated with #{value}" } +``` + +Besides chaining and connecting abstractions together, +concurrency level of all abstractions executing tasks can be manages with the Throttle abstraction. + +```ruby +throttle = Concurrent::Throttle.new 10 +1000.times do + Thread.new do + actor = Concurrent::ErlangActor.spawn_actor type: :on_pool, executor: throttle.on(:io) do + receive(keep: true) { |m| reply m } + end + actor.ask :ping + Concurrent::Promises.future_on(throttle.on(:fast)) { 1 + 1 }.then(&:succ) + end +end +``` + +In the example above the throttle ensures that +there is at most 10 actors or futures processing message or executing their bodies. +Notice that this works not only across abstractions but also across thread pools. +The actor is running on the global thread pool for blocking operations - `:io` +and the futures are executing on the global thread poll for `:fast` non-blocking operations. + +This is of course not an exhaustive list of possible ways how the abstractions can be integrated +but rather few examples to give a feeling what is possible. +Please also see an executable +[example](http://ruby-concurrency.github.io/concurrent-ruby/master/file.medium-example.out.html) +using the integrations. + +## What was not finished + +The original proposal also contained a work steeling thread pool +which would improve performance of small non-blocking tasks. +It would not provide any new functionality to the users. +Therefore for lack of time I decided to postpone this for some later time. + +## Release + +All the work done during the project is released in `concurrent-ruby-edge` version 0.5.0 to Ruby users. +After some time when feedback is gathered the abstractions will be released in the stable core - `concurrent-ruby`. +This is necessary because anything released in the core has to stay backward compatible, +therefore it would prevent even minor improvements to the API. +No big changes to the APIs are expected. + +## After the project + +During the project it become apparent that there will not be much time left +to focus on propagation of the new abstractions. +I've rather decided to focus on the abstraction development +and completion of all their API documentation. + +I plan to turn my attention +to letting Ruby community know about the project and the new features after the project ends. +I will record four introductory videos for each abstraction, +since it appears to me that it become a better platform to reach wider audience then writing blog posts. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/ruby-association-intermediate-report.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/ruby-association-intermediate-report.md new file mode 100644 index 0000000000..a8e40f29c8 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/ruby-association-intermediate-report.md @@ -0,0 +1,99 @@ +# Intermediate report + +I started working on the project 6. Dec. +Since then I have worked on Throttle, Cancellation, Channel, and Actor. +I did not yet start working on the planned job stealing pool. + +The code is pushed in +and the generated documentation for this branch can be found at +. + +## Throttle + +The Throttle implementation originally had special APIs +to interact with other abstractions like promises. +However it was impractical and the API felt cumbersome. +Therefore the Throttle was finalized with much smaller API surface. +Capacity can be still directly acquired from the Throttle +and then released. + +The more common usage of the Throttle is with a proxy executor +`a_throttle.on(Concurrent.global_io_executor)`. +Anything executed on the proxy executor will be throttled and +execute on the given executor. There can be more than one proxy executors. +All abstractions which execute tasks have option to specify executor, +therefore the proxy executor can be injected to any abstraction +throttling its concurrency level. + +The abstraction is ready for release. +For more details see the documentation +. + +## Cancellation + +The Cancellation abstraction provides cooperative cancellation. + +The Cancellation abstraction was originally consisting of 2 classes, +during its finalization it was however simplified +to be just a combination of Cancellation object +and an origin which is regular Event or Future, +which improves compose-ability greatly. +Any Event or Future can be easily turned into cancellation. + +The standard methods `Thread#raise` of `Thread#kill` available in Ruby +are very dangerous (see linked the blog posts bellow). +Therefore concurrent-ruby provides an alternative. +* +* +* + +It provides an object which represents a cancellation event +which can be shared between tasks. +The task has to get the reference to the object +and periodically cooperatively check that it is not cancelled. + +The abstraction is ready for release. +For more details see the documentation +. + +## Channel + +The channel implementation is inspired by Go. +However this implementation is more flexible. +It has 3 major operations pop, push and select as expected. +Where each operation has 3 variants. +`try_(pop|push|select)` which never blocks and returns always immediately. +`(pop|push|select)` which blocks current thread until it can be done +or until it times out. +`(pop|push|select)_op` which returns Future representing the operation, +which can be easily composed with other asynchronous tasks. + +The abstraction is ready for release. +For more details see the documentation +. + +## Actor + +I've refreshed my knowledge about Erlang actors +and started working on the implementation, +which will match the Erlangs behaviour. +(The goal is to make possible to port OTP later, not part of this project.) +Originally, I have planned to only implement the process using +Simulated process implemented by `Future#run`. +However that makes the body of the actors most complex, +therefore I started to considering to implement 3 modes +to give more freedom to the users. +- Backing each actor by a its thread. + Offers simpler body of the actor and can be used + if the number of these actors is limited and they are long-running. +- Stack-less. Each message calls a method on the actor. + Can run on a thread pool, + therefore the number of the actors is not limited. + They can be short-lived as well. + However since they are stack-less they are not suitable + for complicated actor bodies which often change behavior. +- Simulated process. No limitations but it is harder to write + the bodies of the actors for users. + +I will see what modes remaining time allows me to implement. + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/signpost.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/signpost.md new file mode 100644 index 0000000000..cff62de7d7 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/signpost.md @@ -0,0 +1,12 @@ +# ConcurrentRuby API documentation + +Pick a `concurrent-ruby` version: + +* [master](./master/index.html) +* [1.2.0 with edge 0.7.0](./1.2.0/index.html) +* [1.1.10 with edge 0.6.0](./1.1.10/index.html) +* [1.1.9 with edge 0.6.0](./1.1.9/index.html) +* [1.1.8 with edge 0.6.0](./1.1.8/index.html) +* [1.1.7 with edge 0.6.0](./1.1.7/index.html) +* [1.1.6 with edge 0.6.0](./1.1.6/index.html) +* [1.1.5 with edge 0.5.0](./1.1.5/index.html) diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/synchronization-notes.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/synchronization-notes.md new file mode 100644 index 0000000000..14d8570762 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/synchronization-notes.md @@ -0,0 +1,64 @@ +# Concurrent Ruby Notes + +## Locks + +Concurrent Ruby also has an internal extension of `Object` called +`LockableObject`, which provides same synchronization primitives as Java's +Object: `synchronize(&block)`, `wait(timeout = nil)`, +`wait_until(timeout = nil, &condition)`, `signal`, `broadcast`. This class is +intended for internal use in `concurrent-ruby` only and it does not support +subclassing (since it cannot protect its lock from its children, for more +details see [this article](http://wiki.apidesign.org/wiki/Java_Monitor)). It has +minimal interface to be able to use directly locking available on given +platforms. + +For non-internal use there is `Lock` and `Condition` implementation in +`Synchronization` namespace, a condition can be obtained with `new_condition` +method on `Lock`. So far their implementation is naive and requires more work. +API is not expected to change. + +## Method names conventions + +Methods starting with `ns_` are marking methods that are not using +synchronization by themselves, they have to be used inside synchronize block. +They are usually used in pairs to separate the synchronization from behavior and +to allow to call methods in the same object without double locking. + +``` ruby +class Node + # ... + def left + synchronize { ns_left } + end + + def right + synchronize { ns_right } + end + + def to_a + # avoids double locking + synchronize { [ns_left, ns_right] } + end + + private + + def ns_left + @left + end + + def ns_right + @right + end + # ... +end +``` +## Piggybacking + +Any write executed before volatile write based on program-order is visible to +the volatile read as well, which allows +[piggybacking](http://stackoverflow.com/questions/8769570/volatile-piggyback-is-this-enough-for-visiblity). +Because it creates synchronizes-with (JMM term) order between volatile write +and read, which participates in creating happens-before order. + +This trick is used in some of the abstractions, to avoid unnecessary +synchronization or volatile declarations. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/synchronization.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/synchronization.md new file mode 100644 index 0000000000..bf34ba9d99 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/synchronization.md @@ -0,0 +1,5 @@ +# Synchronization + +[This document](https://docs.google.com/document/d/1pVzU8w_QF44YzUCCab990Q_WZOdhpKolCIHaiXG-sPw/edit?usp=sharing) +is moved to Google documents. It will be moved here once final and stabilized. + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/thread_pools.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/thread_pools.md new file mode 100644 index 0000000000..396699f241 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/thread_pools.md @@ -0,0 +1,179 @@ +# Thread Pools + +A Thread Pool is an abstraction that you can give a unit of work to, and the work will be executed by one of possibly several threads in the pool. One motivation for using thread pools is the overhead of creating and destroying threads. Creating a pool of reusable worker threads then repeatedly re-using threads from the pool can have huge performance benefits for a long-running application like a service. + +`concurrent-ruby` also offers some higher level abstractions than thread pools. For many problems, you will be better served by using one of these -- if you are thinking of using a thread pool, we especially recommend you look at and understand {Concurrent::Future}s before deciding to use thread pools directly instead. Futures are implemented using thread pools, but offer a higher level abstraction. + +But there are some problems for which directly using a thread pool is an appropriate solution. Or, you may wish to make your own thread pool to run Futures on, to be separate or have different characteristics than the global thread pool that Futures run on by default. + +Thread pools are considered 'executors' -- an object you can give a unit of work to, to have it executed. In fact, thread pools are the main kind of executor you will see - others are mainly for testing or odd edge cases. In some documentation or source code you'll see reference to an 'executor' -- this is commonly a thread pool, or else something similar that executes units of work (usually supplied as Ruby blocks). + +## FixedThreadPool + +A {Concurrent::FixedThreadPool} contains a fixed number of threads. When you give a unit of work to it, an available thread will be used to execute. + +~~~ruby +pool = Concurrent::FixedThreadPool.new(5) # 5 threads +pool.post do + # some parallel work +end +# As with all thread pools, execution resumes immediately here in the caller thread, +# while work is concurrently being done in the thread pool, at some possibly future point. +~~~ + +What happens if you post new work when all (e.g.) 5 threads are currently busy? It will be added to a queue, and executed when a thread becomes available. In a `FixedThreadPool`, if you post work to the pool much faster than the work can be completed, the queue may grow without bounds, as the work piles up in the holding queue, using up memory without bounds. To limit the queue and apply some form of 'back pressure' instead, you can use the more configurable `ThreadPoolExecutor` (See below). + +If you'd like to base the number of threads in the pool on the number of processors available, your code can consult [Concurrent.processor_count](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent.html#processor_count-class_method). + + +The `FixedThreadPool` is based on the semantics used in Java for [java.util.concurrent.Executors.newFixedThreadPool(int nThreads)](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool(int)) + +## CachedThreadPool + +A {Concurrent::CachedThreadPool} will create as many threads as necessary for work posted to it. If you post work to a `CachedThreadPool` when all its existing threads are busy, it will create a new thread to execute that work, and then keep that thread cached for future work. Cached threads are reclaimed (destroyed) after they are idle for a while. + +CachedThreadPools typically improve the performance of programs that execute many short-lived asynchronous tasks. + +~~~ruby +pool = Concurrent::CachedThreadPool.new +pool.post do + # some parallel work +end +~~~ + +The behavior of `CachedThreadPool` is based on Java's [java.util.concurrent.Executors.newCachedThreadPool()](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html#newCachedThreadPool()) + +If you'd like to configure a maximum number of threads, you can use the more general configurable `ThreadPoolExecutor`. + +## ThreadPoolExecutor + +A {Concurrent::ThreadPoolExecutor} is a general-purpose thread pool that can be configured to have various behaviors. + +A `ThreadPoolExecutor` will automatically adjust the pool size according to the bounds set by `min-threads` and `max-threads`. +When a new task is submitted and fewer than `min-threads` threads are running, a new thread is created to handle the request, even if other worker threads are idle. +If there are more than `min-threads` but less than `max-threads` threads running, a new thread will be created only if the queue is full. + +The `CachedThreadPool` and `FixedThreadPool` are simply `ThreadPoolExecutors` with certain configuration pre-determined. For instance, to create a `ThreadPoolExecutor` that works just like a `FixedThreadPool.new 5`, you could: + +~~~ruby +pool = Concurrent::ThreadPoolExecutor.new( + min_threads: 5, + max_threads: 5, + max_queue: 0 # unbounded work queue +) +~~~ + +If you want to provide a maximum queue size, you may also consider the `fallback_policy` which defines what will happen if work is posted to a pool when the queue of waiting work has reached the maximum size and no new threads can be created. Available policies: + +* abort: Raise a `Concurrent::RejectedExecutionError` exception and discard the task. (default policy) +* discard: Silently discard the task and return nil as the task result. +* caller_runs: The work will be executed in the thread of the caller, instead of being given to another thread in the pool. + +For example: + +~~~ruby +pool = Concurrent::ThreadPoolExecutor.new( + min_threads: 5, + max_threads: 5, + max_queue: 100, + fallback_policy: :caller_runs +) +~~~ + +You can create something similar to a `CachedThreadPool`, but with a maximum number of threads and a bounded queue. +A new thread will be created for the first 3 tasks submitted, and then, once the queue is full, up to an additional 7 threads (10 total) will be created. +If all 10 threads are busy and 100 tasks are already queued, additional tasks will be rejected. + +~~~ruby +pool = Concurrent::ThreadPoolExecutor.new( + min_threads: 3, # create up to 3 threads before queueing tasks + max_threads: 10, # create at most 10 threads + max_queue: 100, # at most 100 jobs waiting in the queue +) +~~~ + +ThreadPoolExecutors with `min_threads` and `max_threads` set to different values will ordinarily reclaim idle threads. You can supply an `idletime` argument, number of seconds that a thread may be idle before being reclaimed. The default is 60 seconds. + +`concurrent-ruby` thread pools are based on designs from `java.util.concurrent` -- a well-designed, stable, scalable, and battle-tested concurrency library. The `ThreadPoolExecutor` is based on Java [java.util.concurrent.ThreadPoolExecutor](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html), and is in fact implemented with a Java ThreadPoolExecutor when running under JRuby. For more information on the design and concepts, you may find the Java documentation helpful: + +* http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html +* http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html +* http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html + +## Thread Pool Status and Shutdown + +A running thread pool can be shutdown in an orderly or disruptive manner. Once a thread pool has been shutdown it cannot be started again. + +The `shutdown` method can be used to initiate an orderly shutdown of the thread pool. All new post calls will be handled according to the `fallback_policy` (i.e. failing with a RejectedExecutionError by default). Threads in the pool will continue to process all in-progress work and will process all tasks still in the queue. + +The `kill` method can be used to immediately shutdown the pool. All new post calls will be handled according to the `fallback_policy`. Ruby's `Thread.kill` will be called on all threads in the pool, aborting all in-progress work. Tasks in the queue will be discarded. + +The method `wait_for_termination` can be used to block and wait for pool shutdown to complete. This is useful when shutting down an application and ensuring the app doesn't exit before pool processing is complete. The method wait_for_termination will block for a maximum of the given number of seconds then return true (if shutdown completed successfully) or false (if it was still ongoing). When the timeout value is `nil` the call will block indefinitely. Calling `wait_for_termination` on a stopped thread pool will immediately return true. + +~~~ruby +# tell the pool to shutdown in an orderly fashion, allowing in progress work to complete +pool.shutdown +# now wait for all work to complete, wait as long as it takes +pool.wait_for_termination +~~~ + +You can check for current pool status: + +~~~ruby +pool.running? +pool.shuttingdown? # in process of shutting down, can't take any more work +pool.shutdown? # it's done +~~~ + +The `shutdown?` method will return true for a stopped pool, regardless of whether the pool was stopped with `shutdown` or `kill`. + +## Other Executors + + There are several other thread pools and executors in the `concurrent-ruby` library. See the API documentation for more information: + + * {Concurrent::CachedThreadPool} + * {Concurrent::FixedThreadPool} + * {Concurrent::ImmediateExecutor} + * {Concurrent::SimpleExecutorService} + * {Concurrent::SafeTaskExecutor} + * {Concurrent::SerializedExecution} + * {Concurrent::SerializedExecutionDelegator} + * {Concurrent::SingleThreadExecutor} + * {Concurrent::ThreadPoolExecutor} + * {Concurrent::TimerSet} + +## Global Thread Pools + +Concurrent Ruby provides several global thread pools. Higher-level abstractions use global thread pools, by default, for running asynchronous operations without creating new threads more often than necessary. These executors are lazy-loaded so they do not create overhead when not needed. The global executors may also be accessed directly if desired. For more information regarding the global thread pools and their configuration, refer to the [API documentation](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Configuration.html). + +When using a higher-level abstraction, which ordinarily uses a global thread pool, you may wish to instead supply your own thread pool, for separation of work, or to control the thread pool behavior with configuration. + +~~~ruby +pool = Concurrent::ThreadPoolExecutor.new( + :min_threads => [2, Concurrent.processor_count].max, + :max_threads => [2, Concurrent.processor_count].max, + :max_queue => [2, Concurrent.processor_count].max * 5, + :fallback_policy => :caller_runs +) + +future = Future.execute(:executor => pool) do + #work +end +~~~ + +## Forking + +Some Ruby versions allow the Ruby process to be [forked](http://ruby-doc.org/core-2.3.0/Process.html#method-c-fork). Generally, mixing threading and forking is an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern). Threading and forking are both concurrency techniques and mixing the two is rarely beneficial. Moreover, threads created before the fork become unusable ("dead") in the forked process. This aspect of forking is a significant issue for any application or library which spawns threads. It is strongly advised that applications using `ThreadPoolExecutor` do **not** also fork. Since Concurrent Ruby is a foundational library often used by gems which are in turn used by other applications, it is impossible to predict or prevent upstream forking. Concurrent Ruby therefore makes a few guarantees about the behavior of `ThreadPoolExecutor` after forking. + +*Concurrent Ruby guarantees that jobs post on the parent process will be handled on the parent process; the child process does not inherit any jobs at the time of the fork. Concurrent Ruby also guarantees that thread pools copied to the child process will continue to function normally.* + +When a fork occurs the `ThreadPoolExecutor` in the *forking* process takes no special actions whatsoever. It has no way of knowing that a fork occurred. It proceeds to process its jobs as normal and makes no attempt whatsoever to distribute those jobs to the forked process(es). + +When a `ThreadPoolExecutor` in the *forked* process detects that a fork has occurred it immediately takes the following actions: + +* Clears all pending jobs from its queue (assuming they will be handled by the *forking* process). +* Deletes all worker threads (they will have died during the fork). +* Resets all job counters (these counts will be reflected in the *forking* process). +* Begins posting new jobs as normal. + +These actions guarantee that all in-flight jobs are processed normally in the forking process and that thread pools, including the global thread pools, remain functional in the forked process(es). diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.in.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.in.md new file mode 100644 index 0000000000..ee2b317fc0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.in.md @@ -0,0 +1,141 @@ + +## Examples + +**Limiting concurrency level of a concurrently executed block to two** + +```ruby +max_two = Concurrent::Throttle.new 2 + +# used to track concurrency level +concurrency_level = Concurrent::AtomicFixnum.new +job = -> do + # increase the current level at the beginning of the throttled block + concurrency_level.increment + # work, takes some time + do_stuff + # read the current concurrency level + current_concurrency_level = concurrency_level.value + # decrement the concurrency level back at the end of the block + concurrency_level.decrement + # return the observed concurrency level + current_concurrency_level +end # + +# create 10 threads running concurrently the jobs +Array.new(10) do + Thread.new do + max_two.acquire(&job) + end +# wait for all the threads to finish and read the observed +# concurrency level in each of them +end.map(&:value) # => [2, 2, 1, 1, 1, 2, 2, 2, 2, 1] +``` +Notice that the returned array has no number bigger than 2 therefore +the concurrency level of the block with the `do_stuff` was never bigger than 2. + +```ruby +# runs a block, and returns he observed concurrency level during the execution +def monitor_concurrency_level(concurrency_level, &block) + concurrency_level.increment + block.call + current_concurrency_level = concurrency_level.value + concurrency_level.decrement + # return the observed concurrency level + return current_concurrency_level +end # + +throttle = Concurrent::Throttle.new 3 +concurrency_level = Concurrent::AtomicFixnum.new + +Array.new(10) do |i| + # create throttled future + throttle.future(i) do |arg| + monitor_concurrency_level(concurrency_level) { do_stuff arg } + # fulfill with the observed concurrency level + end +# collect observed concurrency levels +end.map(&:value!) # => [3, 2, 1, 2, 1, 3, 3, 1, 2, 1] +``` +The concurrency level does not rise above 3. + +It works by setting the executor of the future created from the throttle. +The executor is a proxy executor for the `Concurrent::Promises.default_executor` +which can be obtained using {Concurrent::Throttle#on} method. +Therefore the above example could be instead more explicitly written as follows + +```ruby +# ... +Array.new(10) do |i| + # create throttled future + Concurrent::Promises.future_on(throttle.on(Concurrent::Promises.default_executor)) do + # ... + end +end.map(&:value!) # +``` + +Anything executed on the proxy executor is throttled. +A throttle can have more proxy executors for different executors, +all jobs share the same capacity provided by the throttle. + +Since the proxy executor becomes the executor of the future, +any chained futures will also be throttled. +It can be changed by using different executor. +It the following example the first 2 futures in the chain are throttled, +the last is not. + +```ruby +concurrency_level_throttled = Concurrent::AtomicFixnum.new # +concurrency_level_unthrottled = Concurrent::AtomicFixnum.new # +Array.new(10) do |i| + throttle.future(i) do + monitor_concurrency_level(concurrency_level_throttled) { do_stuff } + end.then do |v| + [v, monitor_concurrency_level(concurrency_level_throttled) { do_stuff }] + end.then_on(:io) do |l1, l2| + [l1, l2, monitor_concurrency_level(concurrency_level_unthrottled) { 5.times { do_stuff } }] + end +end.map(&:value!) # +# => [[3, 3, 7], +# [3, 2, 9], +# [3, 3, 10], +# [3, 3, 6], +# [3, 3, 5], +# [3, 3, 8], +# [3, 3, 3], +# [3, 3, 4], +# [3, 2, 2], +# [3, 1, 1]] +``` + +In the output you can see that the first 2 columns do not cross the 3 capacity limit +and the last column which is untroubled does. + +TODO (pitr-ch 20-Dec-2018): example with virtual throttled executor, +throttling only part of promises chain. + +**Other abstraction** + +The proxy executor created with throttle can be used with other abstractions as well +and combined. + +```ruby +concurrency_level = Concurrent::AtomicFixnum.new # +futures = Array.new(5) do |i| + # create throttled future + throttle.future(i) do |arg| + monitor_concurrency_level(concurrency_level) { do_stuff arg } + # fulfill with the observed concurrency level + end +end # +agents = Array.new(5) do |i| + agent = Concurrent::Agent.new 0 + # execute agent update on throttled executor + agent.send_via(throttle.on(:io)) { monitor_concurrency_level(concurrency_level_throttled) { do_stuff } } + agent +end # +futures.map(&:value!) # => [3, 3, 3, 2, 1] +agents.each { |a| a.await }.map(&:value) # +# => [3, 2, 3, 3, 1] +``` + +There is no observed concurrency level above 3. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.init.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.init.rb new file mode 100644 index 0000000000..f77a83d605 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.init.rb @@ -0,0 +1,6 @@ +require 'concurrent-edge' + +def do_stuff(*args) + sleep 0.01 + :stuff +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.out.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.out.md new file mode 100644 index 0000000000..7ff59137ed --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/throttle.out.md @@ -0,0 +1,145 @@ + +## Examples + +**Limiting concurrency level of a concurrently executed block to two** + +```ruby +max_two = Concurrent::Throttle.new 2 +# => # + +# used to track concurrency level +concurrency_level = Concurrent::AtomicFixnum.new +# => # +job = -> do + # increase the current level at the beginning of the throttled block + concurrency_level.increment + # work, takes some time + do_stuff + # read the current concurrency level + current_concurrency_level = concurrency_level.value + # decrement the concurrency level back at the end of the block + concurrency_level.decrement + # return the observed concurrency level + current_concurrency_level +end + +# create 10 threads running concurrently the jobs +Array.new(10) do + Thread.new do + max_two.acquire(&job) + end +# wait for all the threads to finish and read the observed +# concurrency level in each of them +end.map(&:value) # => [2, 2, 1, 1, 1, 2, 2, 2, 2, 1] +``` +Notice that the returned array has no number bigger than 2 therefore +the concurrency level of the block with the `do_stuff` was never bigger than 2. + +```ruby +# runs a block, and returns he observed concurrency level during the execution +def monitor_concurrency_level(concurrency_level, &block) + concurrency_level.increment + block.call + current_concurrency_level = concurrency_level.value + concurrency_level.decrement + # return the observed concurrency level + return current_concurrency_level +end + +throttle = Concurrent::Throttle.new 3 +# => # +concurrency_level = Concurrent::AtomicFixnum.new +# => # + +Array.new(10) do |i| + # create throttled future + throttle.future(i) do |arg| + monitor_concurrency_level(concurrency_level) { do_stuff arg } + # fulfill with the observed concurrency level + end +# collect observed concurrency levels +end.map(&:value!) # => [3, 2, 1, 2, 1, 3, 3, 1, 2, 1] +``` +The concurrency level does not rise above 3. + +It works by setting the executor of the future created from the throttle. +The executor is a proxy executor for the `Concurrent::Promises.default_executor` +which can be obtained using {Concurrent::Throttle#on} method. +Therefore the above example could be instead more explicitly written as follows + +```ruby +# ... +Array.new(10) do |i| + # create throttled future + Concurrent::Promises.future_on(throttle.on(Concurrent::Promises.default_executor)) do + # ... + end +end.map(&:value!) +``` + +Anything executed on the proxy executor is throttled. +A throttle can have more proxy executors for different executors, +all jobs share the same capacity provided by the throttle. + +Since the proxy executor becomes the executor of the future, +any chained futures will also be throttled. +It can be changed by using different executor. +It the following example the first 2 futures in the chain are throttled, +the last is not. + +```ruby +concurrency_level_throttled = Concurrent::AtomicFixnum.new +concurrency_level_unthrottled = Concurrent::AtomicFixnum.new +Array.new(10) do |i| + throttle.future(i) do + monitor_concurrency_level(concurrency_level_throttled) { do_stuff } + end.then do |v| + [v, monitor_concurrency_level(concurrency_level_throttled) { do_stuff }] + end.then_on(:io) do |l1, l2| + [l1, l2, monitor_concurrency_level(concurrency_level_unthrottled) { 5.times { do_stuff } }] + end +end.map(&:value!) +# => [[3, 3, 7], +# [3, 2, 9], +# [3, 3, 10], +# [3, 3, 6], +# [3, 3, 5], +# [3, 3, 8], +# [3, 3, 3], +# [3, 3, 4], +# [3, 2, 2], +# [3, 1, 1]] +``` + +In the output you can see that the first 2 columns do not cross the 3 capacity limit +and the last column which is untroubled does. + +TODO (pitr-ch 20-Dec-2018): example with virtual throttled executor, +throttling only part of promises chain. + +**Other abstraction** + +The proxy executor created with throttle can be used with other abstractions as well +and combined. + +```ruby +concurrency_level = Concurrent::AtomicFixnum.new +futures = Array.new(5) do |i| + # create throttled future + throttle.future(i) do |arg| + monitor_concurrency_level(concurrency_level) { do_stuff arg } + # fulfill with the observed concurrency level + end +end +agents = Array.new(5) do |i| + agent = Concurrent::Agent.new 0 + # execute agent update on throttled executor + agent.send_via(throttle.on(:io)) { monitor_concurrency_level(concurrency_level_throttled) { do_stuff } } + agent +end +futures.map(&:value!) # => [3, 3, 3, 2, 1] +agents.each { |a| a.await }.map(&:value) +# => [3, 2, 3, 3, 1] +``` + +There is no observed concurrency level above 3. diff --git a/app/server/ruby/vendor/i18n/test/test_data/locales/invalid/empty.yml b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/.gitignore similarity index 100% rename from app/server/ruby/vendor/i18n/test/test_data/locales/invalid/empty.yml rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/.gitignore diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/README.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/README.md new file mode 100644 index 0000000000..4a05000f57 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/README.md @@ -0,0 +1,115 @@ +# Top Stock - Scala + +This program determines which stock had the highest price in a given year. +It as an example from chapter 1 "Introduction", section 1.2 "What's Scala?" of the book +[Programming Scala: Tackle Multi-Core Complexity on the Java Virtual Machine](http://pragprog.com/book/vsscala/programming-scala). + +## What It Does + +This program takes a list of one or more stock symbols and a year. It then concurrently +obtains the relevant stock data from Alpha Vantage service for each symbol. Once all +the data has been retrieved the program determines which stock had the highest year-end +closing price. + +To use this example you need to obtain a free api key in [AlphaVantage](https://www.alphavantage.co/support/#api-key). + +https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=AAPL&apikey=1234567&datatype=csv" + +### Run It + +This example can be run from the console. From the root of the repo run: + +> $ ALPHAVANTAGE_KEY=YOUR_API_KEY bundle exec ruby top-stock-scala/top-stock.rb + +The output should be: + +> Top stock of 2008 is GOOG closing at price $307.65 + +#### The Ruby Code + +```ruby +require 'concurrent' +require 'csv' +require 'open-uri' + +def get_year_end_closing(symbol, year, api_key) + uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv" + data = [] + URI.open(uri) do |f| + CSV.parse(f, headers: true) do |row| + data << row['close'] if row['timestamp'].include?(year.to_s) + end + end + price = data.max + price.to_f + [symbol, price.to_f] +end + +def get_top_stock(symbols, year, timeout = 10) + api_key = ENV['ALPHAVANTAGE_KEY'] + abort(error_message) unless api_key + + stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year, api_key) }} + Concurrent::dataflow(*stock_prices) { |*prices| + prices.reduce(['', 0.0]){|highest, price| price.last > highest.last ? price : highest} + }.value(timeout) +end + +def error_message + <<~EOF + PLEASE provide a Alpha Vantage api key for the example to work + usage: + ALPHAVANTAGE_KEY=YOUR_API_KEY bundle exec ruby top-stock-scala/top-stock.rb + EOF +end + +symbols = ['AAPL', 'GOOG', 'IBM', 'ORCL', 'MSFT'] +year = 2008 + +top_stock, highest_price = get_top_stock(symbols, year) + +puts "Top stock of #{year} is #{top_stock} closing at price $#{highest_price}" +``` + +#### The Scala Code + +```scala +//START:PART1 +import scala.actors._ +import Actor._ + +val symbols = List( "AAPL", "GOOG", "IBM", "JAVA", "MSFT") +val receiver = self +val year = 2008 + +symbols.foreach { symbol => + actor { receiver ! getYearEndClosing(symbol, year) } +} + +val (topStock, highestPrice) = getTopStock(symbols.length) + +printf("Top stock of %d is %s closing at price %f\n", year, topStock, highestPrice) +//END:PART1 + +//START:PART2 +def getYearEndClosing(symbol : String, year : Int) = { + val url = "http://ichart.finance.yahoo.com/table.csv?s=" + + symbol + "&a=11&b=01&c=" + year + "&d=11&e=31&f=" + year + "&g=m" + + val data = io.Source.fromURL(url).mkString + val price = data.split("\n")(1).split(",")(4).toDouble + (symbol, price) +} +//END:PART2 + +//START:PART3 +def getTopStock(count : Int) : (String, Double) = { + (1 to count).foldLeft("", 0.0) { (previousHigh, index) => + receiveWithin(10000) { + case (symbol : String, price : Double) => + if (price > previousHigh._2) (symbol, price) else previousHigh + } + } +} +//END:PART3 +``` diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/top-stock.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/top-stock.rb new file mode 100644 index 0000000000..3ffcf6432d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/top-stock-scala/top-stock.rb @@ -0,0 +1,48 @@ +require 'concurrent' +require 'csv' +require 'open-uri' + +def get_year_end_closing(symbol, year, api_key) + uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv" + data = [] + csv = URI.parse(uri).read + if csv.include?('call frequency') + return :rate_limit_exceeded + end + CSV.parse(csv, headers: true) do |row| + data << row['close'].to_f if row['timestamp'].include?(year.to_s) + end + price = data.max + [symbol, price] +end + +def get_top_stock(symbols, year, timeout = 10) + api_key = ENV['ALPHAVANTAGE_KEY'] + abort(error_message) unless api_key + + stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year, api_key) }} + Concurrent::dataflow(*stock_prices) { |*prices| + next :rate_limit_exceeded if prices.include?(:rate_limit_exceeded) + prices.reduce(['', 0.0]){|highest, price| price.last > highest.last ? price : highest} + }.value(timeout) +end + +def error_message + <<~EOF + PLEASE provide a Alpha Vantage api key for the example to work + usage: + ALPHAVANTAGE_KEY=YOUR_API_KEY bundle exec ruby top-stock-scala/top-stock.rb + EOF +end + +symbols = ['AAPL', 'GOOG', 'IBM', 'ORCL', 'MSFT'] +year = 2018 + +result = get_top_stock(symbols, year) + +if result == :rate_limit_exceeded + puts "API rate limit exceeded" +else + top_stock, highest_price = result + puts "Top stock of #{year} is #{top_stock} closing at price $#{highest_price}" +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/tvar.md b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/tvar.md new file mode 100644 index 0000000000..5b836475fc --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/docs-source/tvar.md @@ -0,0 +1,211 @@ +`TVar` and `atomically` implement a software transactional memory. A `TVar` is a +single item container that always contains exactly one value. The `atomically` +method allows you to modify a set of `TVar` objects with the guarantee that all +of the updates are collectively atomic - they either all happen or none of them +do - consistent - a `TVar` will never enter an illegal state - and isolated - +atomic blocks never interfere with each other when they are running. You may +recognise these properties from database transactions. + +There are some very important and unusual semantics that you must be aware of: + +* Most importantly, the block that you pass to `atomically` may be executed more +than once. In most cases your code should be free of side-effects, except for +via `TVar`. + +* If an exception escapes an `atomically` block it will abort the transaction. + +* It is undefined behaviour to use `callcc` or `Fiber` with `atomically`. + +* If you create a new thread within an `atomically`, it will not be part of +the transaction. Creating a thread counts as a side-effect. + +We implement nested transactions by flattening. + +We only support strong isolation if you use the API correctly. In order words, +we do not support strong isolation. + +Our implementation uses a very simple algorithm that locks each `TVar` when it +is first read or written. If it cannot lock a `TVar` it aborts and retries. +There is no contention manager so competing transactions may retry eternally. + +```ruby +require 'concurrent' + +v1 = Concurrent::TVar.new(0) +v2 = Concurrent::TVar.new(0) + +2.times.map{ + Thread.new do + while true + Concurrent::atomically do + t1 = v1.value + t2 = v2.value + raise [t1, t2].inspect if t1 != t2 # detect zombie transactions + end + end + end + + Thread.new do + 100_000.times do + Concurrent::atomically do + v1.value += 1 + v2.value += 1 + end + end + end +}.each { |t| p t.join } +``` + +However, the inconsistent reads are detected correctly at commit time. This +means the script below will always print `[2000000, 200000]`. + +```ruby +require 'concurrent' + +v1 = Concurrent::TVar.new(0) +v2 = Concurrent::TVar.new(0) + +2.times.map{ + Thread.new do + while true + Concurrent::atomically do + t1 = v1.value + t2 = v2.value + end + end + end + + Thread.new do + 100_000.times do + Concurrent::atomically do + v1.value += 1 + v2.value += 1 + end + end + end +}.each { |t| p t.join } + +p [v1.value, v2.value] +``` + +This is called a lack of *opacity*. In the future we will look at more advanced +algorithms, contention management and using existing Java implementations when +in JRuby. + +## Motivation + +Consider an application that transfers money between bank accounts. We want to +transfer money from one account to another. It is very important that we don't +lose any money! But it is also important that we can handle many account +transfers at the same time, so we run them concurrently, and probably also in +parallel. + +This code shows us transferring ten pounds from one account to another. + +```ruby +a = BankAccount.new(100_000) +b = BankAccount.new(100) + +a.value -= 10 +b.value += 10 +``` + +Before we even start to talk about to talk about concurrency and parallelism, is +this code safe? What happens if after removing money from account a, we get an +exception? It's a slightly contrived example, but if the account totals were +very large, adding to them could involve the stack allocation of a `BigNum`, and +so could cause out of memory exceptions. In that case the money would have +disappeared from account a, but not appeared in account b. Disaster! + +So what do we really need to do? + +```ruby +a = BankAccount.new(100_000) +b = BankAccount.new(100) + +original_a = a.value +a.value -= 10 + +begin + b.value += 10 +rescue e => + a.value = original_a + raise e +end +``` + +This rescues any exceptions raised when setting b and will roll back the change +we have already made to b. We'll keep this rescue code in mind, but we'll leave +it out of future examples for simplicity. + +That might have made the code work when it only runs sequentially. Lets start to +consider some concurrency. It's obvious that we want to make the transfer of +money mutually exclusive with any other transfers - in order words it is a +critical section. + +The usual solution to this would be to use a lock. + +```ruby +lock.synchronize do + a.value -= 10 + b.value += 10 +end +``` + +That should work. Except we said we'd like these transfer to run concurrently, +and in parallel. With a single lock like that we'll only let one transfer take +place at a time. Perhaps we need more locks? We could have one per account: + +```ruby +a.lock.synchronize do + b.lock.synchronize do + a.value -= 10 + b.value += 10 + end +end +``` + +However this is vulnerable to deadlock. If we tried to transfer from a to b, at +the same time as from b to a, it's possible that the first transfer locks a, the +second transfer locks b, and then they both sit there waiting forever to get the +other lock. Perhaps we can solve that by applying a total ordering to the locks +and always acquire them in the same order? + +```ruby +locks_needed = [a.lock, b.lock] +locks_in_order = locks_needed.sort{ |x, y| x.number <=> y.number } + +locks_in_order[0].synchronize do + locks_in_order[1].synchronize do + a.value -= 10 + b.value += 10 + end +end +``` + +That might work. But we need to know exactly what locks we're going to need +before we start. If there were conditions in side the transfer this might be +more complicated. We also need to remember the rescue code we had above to deal +with exceptions. This is getting out of hand - and it's where `TVar` comes in. + +We'll model the accounts as `TVar` - transactional variable, and instead of +locks we'll use `Concurrent::atomically`. + +```ruby +a = TVar.new(100_000) +b = TVar.new(100) + +Concurrent::atomically do + a.value -= 10 + b.value += 10 +end +``` + +That short piece of code effectively solves all the concerns we identified +above. How it does it is described in the reference above. You just need to be +happy that any two `atomically` blocks (we call them transactions) that use an +overlapping set of `TVar` objects will appear to have happened as if there was a +big global lock on them, and that if any exception is raised in the block, it +will be as if the block never happened. But also keep in mind the important +points we detailed right at the start of the article about side effects and +repeated execution. diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/buffered-channels.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/buffered-channels.rb new file mode 100755 index 0000000000..a372f1faa0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/buffered-channels.rb @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## A Tour of Go: Buffered Channels +# https://tour.golang.org/concurrency/3 + +ch = Channel.new(capacity: 2) +ch << 1 +ch << 2 + +puts ~ch +puts ~ch + +__END__ +1 +2 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/channels.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/channels.rb new file mode 100755 index 0000000000..be27aaf232 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/channels.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## A Tour of Go: Channels +# https://tour.golang.org/concurrency/2 + +def sum(a, c) + sum = a.reduce(0, &:+) + c << sum # `<<` is an alias for `put` or `send` +end + +a = [7, 2, 8, -9, 4, 0] +l = a.length / 2 +c = Channel.new + +Channel.go { sum(a[-l, l], c) } +Channel.go { sum(a[0, l], c) } +x, y = ~c, ~c # `~` is an alias for `take` or `receive` + +puts [x, y, x+y].join(' ') + +__END__ +-5 17 12 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/default-selection.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/default-selection.rb new file mode 100755 index 0000000000..1c05f36792 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/default-selection.rb @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## A Tour of Go: Default Selection +# https://tour.golang.org/concurrency/6 + +tick = Channel.tick(0.1) +boom = Channel.after(0.5) + +loop do + Channel.select do |s| + s.take(tick) { |t| print "tick.\n" if t } + s.take(boom) do + print "BOOM!\n" + exit + end + s.default do + print " .\n" + sleep(0.05) + end + end +end + +__END__ + . + . +tick. + . + . +tick. + . + . +tick. + . + . +tick. + . + . +tick. +BOOM! diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/equivalent-binary-trees.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/equivalent-binary-trees.rb new file mode 100755 index 0000000000..2f1d594d3a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/equivalent-binary-trees.rb @@ -0,0 +1,70 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## A Tour of Go: Equivalent Binary Trees +# https://tour.golang.org/concurrency/8 + +Tree = Struct.new(:value, :left, :right) + +def new_tree(n, size = 10) + values = [*1..size].collect{|i| i * n }.sample(size) + root = Tree.new(values.shift) + + inserter = ->(current, new) do + if new.value <= current.value + if current.left.nil? + current.left = new + else + inserter.call(current.left, new) + end + else + if current.right.nil? + current.right = new + else + inserter.call(current.right, new) + end + end + end + + while value = values.shift do + inserter.call(root, Tree.new(value)) + end + + root +end + +def walk(tree, channel) + _walk = ->(t, ch) do + return unless t + _walk.call(t.left, ch) + ch << t.value + _walk.call(t.right, ch) + end + + _walk.call(tree, channel) + channel.close +end + +def same(t1, t2) + ch1 = Channel.new + ch2 = Channel.new + + Channel.go { walk(t1, ch1) } + Channel.go { walk(t2, ch2) } + + ch1.each do |v| + return false unless v == ~ch2 + end + + return true +end + +puts same(new_tree(1), new_tree(1)) +puts same(new_tree(1), new_tree(2)) + +__END__ +true +false diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/range-and-close.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/range-and-close.rb new file mode 100755 index 0000000000..a1bd581727 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/range-and-close.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## A Tour of Go: Range and Close +# https://tour.golang.org/concurrency/4 + +def fibonacci(n, c) + x, y = 0, 1 + (1..n).each do + c << x + x, y = y, x+y + end + c.close +end + +c = Channel.new(capacity: 10) +Channel.go { fibonacci(c.capacity, c) } +c.each { |i| puts i } + +__END__ +0 +1 +1 +2 +3 +5 +8 +13 +21 +34 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/select.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/select.rb new file mode 100755 index 0000000000..476a735886 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/a-tour-of-go-channels/select.rb @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## A Tour of Go: Select +# https://tour.golang.org/concurrency/5 + +def fibonacci(c, quit) + x, y = 0, 1 + loop do + Channel.select do |s| + s.case(c, :<<, x) { x, y = y, x+y; x } # alias for `s.put` + s.case(quit, :~) do # alias for `s.take` + puts 'quit' + return + end + end + end +end + +c = Channel.new +quit = Channel.new + +Channel.go do + 10.times { puts ~c } + quit << 0 +end + +fibonacci(c, quit) + +__END__ +0 +1 +1 +2 +3 +5 +8 +13 +21 +34 +quit diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/actor_stress_test.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/actor_stress_test.rb new file mode 100755 index 0000000000..ac68acc66c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/actor_stress_test.rb @@ -0,0 +1,140 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'benchmark' +require 'optparse' +require 'thread' +require 'rspec/expectations' + +require 'concurrent/actor' + +class ActorStressTester + include ::RSpec::Matchers + + TESTS_PER_RUN = 5 + THREADS_PER_TEST = 10 + LOOPS_PER_THREAD = 25 + + class Ping < Concurrent::Actor::Context + def initialize(queue) + @queue = queue + end + + def on_message(message) + case message + when :child + Concurrent::Actor::Utils::AdHoc.spawn(:pong, @queue) do |queue| + -> m { queue << m } + end + else + @queue << message + message + end + end + end + + def initialize(opts = {}) + @tests = opts.fetch(:tests, TESTS_PER_RUN) + @threads = opts.fetch(:threads, THREADS_PER_TEST) + @loops = opts.fetch(:loops, LOOPS_PER_THREAD) + end + + def run + plural = ->(number){ number == 1 ? '' : 's' } + + puts "Running #{@tests} test#{plural.call(@tests)} " + + "with #{@threads} thread#{plural.call(@threads)} each " + + "and #{@loops} loop#{plural.call(@loops)} per thread..." + + Benchmark.bmbm do |bm| + @tests.times do + bm.report do + test(@threads, @loops) + end + end + end + end + + def test(threads, loops) + (1..threads).collect do + Thread.new do + loops.times do + + queue = Queue.new + actor = Ping.spawn(:ping, queue) + + core = Concurrent::Actor.root.send(:core) + children = core.instance_variable_get(:@children) + expect(children).to include(actor) + + actor << 'a' << 1 + expect(queue.pop).to eq 'a' + expect(actor.ask(2).value).to eq 2 + + expect(actor.parent).to eq Concurrent::Actor.root + expect(Concurrent::Actor.root.path).to eq '/' + expect(actor.path).to eq '/ping' + + child = actor.ask(:child).value + expect(child.path).to eq '/ping/pong' + + queue.clear + child.ask(3) + expect(queue.pop).to eq 3 + + actor << :terminate! + #expect(actor.ask(:blow_up).wait).to be_rejected + expect(actor.ask(:blow_up).wait).to be_failed + terminate_actors(actor, child) + end + end + end.each(&:join) + end + + def terminate_actors(*actors) + actors.each do |actor| + unless actor.ask!(:terminated?) + actor.ask!(:terminate!) + end + end + end +end + +# def trace! +# set_trace_func proc { |event, file, line, id, binding, classname| +# # thread = eval('Thread.current', binding).object_id.to_s(16) +# printf "%8s %20s %20s %s %s:%-2d\n", event, id, classname, nil, file, line +# } +# yield +# ensure +# set_trace_func nil +# end + +if $0 == __FILE__ + + options = {} + + OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options]" + + opts.on("--tests=TESTS", "Number of tests per run") do |value| + options[:tests] = value.to_i + end + + opts.on("--threads=THREADS", "Number of threads per test") do |value| + options[:threads] = value.to_i + end + + opts.on("--loops=LOOPS", "Number of loops per thread") do |value| + options[:loops] = value.to_i + end + + opts.on("-h", "--help", "Prints this help") do + puts opts + exit + end + end.parse! + + ActorStressTester.new(options).run +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/atomic_example.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/atomic_example.rb new file mode 100755 index 0000000000..5216048e7f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/atomic_example.rb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'concurrent/atomics' + +my_atomic = Concurrent::AtomicReference.new(0) +my_atomic.update {|v| v + 1} +puts "new value: #{my_atomic.value}" + +begin + my_atomic.try_update {|v| v + 1} +rescue Concurrent::Atomic::ConcurrentUpdateError => cue + # deal with it (retry, propagate, etc) +end +puts "new value: #{my_atomic.value}" diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_async.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_async.rb new file mode 100755 index 0000000000..371bbe55e8 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_async.rb @@ -0,0 +1,227 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'benchmark' +require 'benchmark/ips' + +require 'concurrent' +require 'celluloid' + +class CelluloidClass + include Celluloid + def foo(latch = nil) + latch.count_down if latch + end +end + +class AsyncClass + include Concurrent::Async + def foo(latch = nil) + latch.count_down if latch + end +end + +IPS_NUM = 100 +BMBM_NUM = 100_000 +SMALL_BMBM = 250 + +puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" +puts "Long-lived objects" +puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" +puts "" + +Benchmark.ips do |bm| + celluloid = CelluloidClass.new + bm.report('celluloid') do + latch = Concurrent::CountDownLatch.new(IPS_NUM) + IPS_NUM.times { celluloid.async.foo(latch) } + latch.wait + end + + async = AsyncClass.new + bm.report('async') do + latch = Concurrent::CountDownLatch.new(IPS_NUM) + IPS_NUM.times { async.async.foo(latch) } + latch.wait + end + + bm.compare! +end + +Benchmark.bmbm do |bm| + celluloid = CelluloidClass.new + bm.report('celluloid') do + latch = Concurrent::CountDownLatch.new(BMBM_NUM) + BMBM_NUM.times { celluloid.async.foo(latch) } + latch.wait + end + + async = AsyncClass.new + bm.report('async') do + latch = Concurrent::CountDownLatch.new(BMBM_NUM) + BMBM_NUM.times { async.async.foo(latch) } + latch.wait + end +end + +puts "" +puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" +puts "Short-lived objects" +puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" +puts "" + +Benchmark.ips do |bm| + bm.report('future') do + latch = Concurrent::CountDownLatch.new(IPS_NUM) + IPS_NUM.times do + Concurrent::Future.execute { latch.count_down } + end + latch.wait + end + + async = AsyncClass.new + bm.report('async') do + latch = Concurrent::CountDownLatch.new(IPS_NUM) + IPS_NUM.times { AsyncClass.new.async.foo(latch) } + latch.wait + end + + bm.compare! +end + +Benchmark.bmbm do |bm| + bm.report('celluloid') do + latch = Concurrent::CountDownLatch.new(SMALL_BMBM) + SMALL_BMBM.times { CelluloidClass.new.async.foo(latch) } + latch.wait + end + + bm.report('async') do + latch = Concurrent::CountDownLatch.new(SMALL_BMBM) + SMALL_BMBM.times { AsyncClass.new.async.foo(latch) } + latch.wait + end +end + +__END__ + +=========================================================== + Async Benchmarks +=========================================================== + + Computer: + + * OS X Yosemite +- Version 10.10.4 +* MacBook Pro +- Retina, 13-inch, Early 2015 +* Processor 3.1 GHz Intel Core i7 +* Memory 16 GB 1867 MHz DDR3 +* Physical Volumes: + - Apple SSD SM0512G +- 500 GB + +=========================================================== + ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14] +=========================================================== + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Long-lived objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Calculating ------------------------------------- + celluloid 22.000 i/100ms + async 37.000 i/100ms +------------------------------------------------- + celluloid 239.639 (±10.8%) i/s - 1.188k + async 374.885 (± 2.7%) i/s - 1.887k + +Comparison: + async: 374.9 i/s + celluloid: 239.6 i/s - 1.56x slower + +Rehearsal --------------------------------------------- +celluloid 3.910000 0.540000 4.450000 ( 4.455316) +async 2.730000 0.010000 2.740000 ( 2.736720) +------------------------------------ total: 7.190000sec + + user system total real +celluloid 3.880000 0.550000 4.430000 ( 4.435163) +async 2.740000 0.010000 2.750000 ( 2.750706) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Short-lived objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Calculating ------------------------------------- + future 19.000 i/100ms + async 19.000 i/100ms +------------------------------------------------- + future 191.738 (± 3.7%) i/s - 969.000 + async 188.085 (± 4.3%) i/s - 950.000 + +Comparison: + future: 191.7 i/s + async: 188.1 i/s - 1.02x slower + +Rehearsal --------------------------------------------- +celluloid 0.110000 0.020000 0.130000 ( 0.131996) +async 0.040000 0.010000 0.050000 ( 0.037236) +------------------------------------ total: 0.180000sec + + user system total real +celluloid 0.160000 0.040000 0.200000 ( 0.186817) +async 0.040000 0.010000 0.050000 ( 0.051579) + +=========================================================== +jruby 9.0.1.0 (2.2.2) 2015-09-02 583f336 Java HotSpot(TM) 64-Bit Server VM 25.45-b02 on 1.8.0_45-b14 +jit [darwin-x86_64] +=========================================================== + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Long-lived objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Calculating ------------------------------------- + celluloid 1.000 i/100ms + async 14.000 i/100ms +------------------------------------------------- + celluloid 139.631 (±42.3%) i/s - 473.000 + async 883.424 (±26.6%) i/s - 3.514k + +Comparison: + async: 883.4 i/s + celluloid: 139.6 i/s - 6.33x slower + +Rehearsal --------------------------------------------- +celluloid 7.420000 1.930000 9.350000 ( 6.625224) +async 2.630000 0.210000 2.840000 ( 1.574823) +----------------------------------- total: 12.190000sec + + user system total real +celluloid 5.910000 1.720000 7.630000 ( 5.995677) +async 2.610000 0.190000 2.800000 ( 1.594092) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Short-lived objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Calculating ------------------------------------- + future 40.000 i/100ms + async 48.000 i/100ms +------------------------------------------------- + future 640.057 (± 4.8%) i/s - 3.200k + async 570.240 (± 4.7%) i/s - 2.880k + +Comparison: + future: 640.1 i/s + async: 570.2 i/s - 1.12x slower + +Rehearsal --------------------------------------------- +celluloid 1.420000 0.090000 1.510000 ( 0.523106) +async 0.020000 0.000000 0.020000 ( 0.006935) +------------------------------------ total: 1.530000sec + + user system total real +celluloid 0.620000 0.100000 0.720000 ( 0.293182) +async 0.020000 0.000000 0.020000 ( 0.007434) diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic.rb new file mode 100755 index 0000000000..ee9c74d33a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic.rb @@ -0,0 +1,135 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'benchmark' +require 'rbconfig' +require 'thread' + +require 'concurrent/atomics' + +if RUBY_PLATFORM != 'java' && ! defined? Concurrent::CAtomicReference + warn "[WARN] C extensions not loaded!" +end + +Thread.abort_on_exception = true + +$go = false # for synchronizing parallel threads + +# number of updates on the value +N = ARGV[1] ? ARGV[1].to_i : 100_000 + +# number of threads for parallel test +M = ARGV[0] ? ARGV[0].to_i : 10 + +# list of platform-specific implementations +ATOMICS = [ + 'MutexAtomicReference', + 'CAtomicReference', + 'JavaAtomicReference', +] + +puts "Testing with #{RbConfig::CONFIG['ruby_install_name']} #{RUBY_VERSION}" + +puts +puts '*** Sequential updates ***' +Benchmark.bm(10) do |x| + value = 0 + x.report 'no lock' do + N.times do + value += 1 + end + end + + @lock = Mutex.new + x.report 'mutex' do + value = 0 + N.times do + @lock.synchronize do + value += 1 + end + end + end + + ATOMICS.each do |clazz| + if Concurrent.const_defined? clazz + @atom = Concurrent.const_get(clazz).new(0) + x.report clazz do + N.times do + @atom.update{|x| x += 1} + end + end + end + end +end + +def para_setup(num_threads, count, &block) + if num_threads % 2 > 0 + raise ArgumentError, 'num_threads must be a multiple of two' + end + raise ArgumentError, 'need block' unless block_given? + + # Keep those threads together + tg = ThreadGroup.new + + num_threads.times do |i| + diff = (i % 2 == 0) ? 1 : -1 + + t = Thread.new do + nil until $go + count.times do + yield diff + end + end + + tg.add(t) + end + + # Make sure all threads are started + while tg.list.find{|t| t.status != 'run'} + Thread.pass + end + + # For good measure + GC.start + + tg +end + +def para_run(tg) + $go = true + tg.list.each{|t| t.join} + $go = false +end + +puts +puts '*** Parallel updates ***' +Benchmark.bm(10) do |bm| + # This is not secure + value = 0 + tg = para_setup(M, N/M) do |diff| + value += diff + end + bm.report('no lock'){ para_run(tg) } + + value = 0 + @lock = Mutex.new + tg = para_setup(M, N/M) do |diff| + @lock.synchronize do + value += diff + end + end + bm.report('mutex'){ para_run(tg) } + raise unless value == 0 + + ATOMICS.each do |clazz| + if Concurrent.const_defined? clazz + @atom = Concurrent.const_get(clazz).new(0) + tg = para_setup(M, N/M) do |diff| + @atom.update{|x| x + diff} + end + bm.report(clazz){ para_run(tg) } + raise unless @atom.value == 0 + end + end +end diff --git a/app/server/ruby/vendor/atomic/examples/bench_atomic_1.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_1.rb similarity index 78% rename from app/server/ruby/vendor/atomic/examples/bench_atomic_1.rb rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_1.rb index ee5eb1e188..f3f09b08e0 100755 --- a/app/server/ruby/vendor/atomic/examples/bench_atomic_1.rb +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_1.rb @@ -1,23 +1,17 @@ #!/usr/bin/env ruby -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -$: << File.expand_path('../../lib', __FILE__) +#$: << File.expand_path('../../lib', __FILE__) require 'optparse' require 'thread' require 'benchmark' -require 'atomic' +require 'concurrent/atomics' + +unless defined? Concurrent::CAtomicReference + warn "[ERROR] C extensions not loaded!" + exit(1) +end Thread.abort_on_exception = true @@ -115,7 +109,7 @@ def para_prepare(&block) $tg = nil if $conf[:lock] == "atomic" - $atom = Atomic.new(0) + $atom = Concurrent::AtomicReference.new(0) $tg = para_prepare do |diff| $atom.update do |x| slow_down diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_boolean.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_boolean.rb new file mode 100755 index 0000000000..783aba8ce3 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_boolean.rb @@ -0,0 +1,41 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'concurrent/atomics' +require 'benchmark' +require 'rbconfig' + +THREADS = 1 +TESTS = 10_000_000 + +def atomic_test(clazz, opts = {}) + threads = opts.fetch(:threads, 5) + tests = opts.fetch(:tests, 100) + + atomic = clazz.new + latch = Concurrent::CountDownLatch.new(threads) + + print "Testing with #{clazz}...\n" + Benchmark.bmbm do |bm| + bm.report do + threads.times do |i| + Thread.new do + tests.times{ atomic.value = true } + latch.count_down + end + end + latch.wait + end + end +end + +puts "Testing with #{RbConfig::CONFIG['ruby_install_name']} #{RUBY_VERSION}" + +atomic_test(Concurrent::MutexAtomicBoolean, threads: THREADS, tests: TESTS) + +if defined? Concurrent::CAtomicBoolean + atomic_test(Concurrent::CAtomicBoolean, threads: THREADS, tests: TESTS) +elsif RUBY_PLATFORM == 'java' + atomic_test(Concurrent::JavaAtomicBoolean, threads: THREADS, tests: TESTS) +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_fixnum.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_fixnum.rb new file mode 100755 index 0000000000..c64f6e5a02 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_atomic_fixnum.rb @@ -0,0 +1,41 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'concurrent/atomics' +require 'benchmark' +require 'rbconfig' + +THREADS = 1 +TESTS = 10_000_000 + +def atomic_test(clazz, opts = {}) + threads = opts.fetch(:threads, 5) + tests = opts.fetch(:tests, 100) + + num = clazz.new + latch = Concurrent::CountDownLatch.new(threads) + + print "Testing with #{clazz}...\n" + Benchmark.bmbm do |bm| + bm.report do + threads.times do |i| + Thread.new do + tests.times{ num.up } + latch.count_down + end + end + latch.wait + end + end +end + +puts "Testing with #{RbConfig::CONFIG['ruby_install_name']} #{RUBY_VERSION}" + +atomic_test(Concurrent::MutexAtomicFixnum, threads: THREADS, tests: TESTS) + +if defined? Concurrent::CAtomicFixnum + atomic_test(Concurrent::CAtomicFixnum, threads: THREADS, tests: TESTS) +elsif RUBY_PLATFORM == 'java' + atomic_test(Concurrent::JavaAtomicFixnum, threads: THREADS, tests: TESTS) +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_map.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_map.rb new file mode 100755 index 0000000000..b31c9958e0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_map.rb @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby + +require 'benchmark/ips' +require 'concurrent/map' + +hash = {} +map = Concurrent::Map.new + +ENTRIES = 10_000 + +ENTRIES.times do |i| + hash[i] = i + map[i] = i +end + +TESTS = 1_000 +key = 2732 # srand(0) and rand(10_000) + +Benchmark.ips do |results| + results.report('Hash#[]') do + hash[key] + end + + results.report('Map#[]') do + map[key] + end + + results.report('Hash#each_pair') do + hash.each_pair { |k,v| v } + end + + results.report('Map#each_pair') do + map.each_pair { |k,v| v } + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_new_futures.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_new_futures.rb new file mode 100755 index 0000000000..ef25fabbe0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_new_futures.rb @@ -0,0 +1,72 @@ +#!/usr/bin/env ruby + +require 'benchmark/ips' +require 'concurrent' +require 'concurrent-edge' + +raise 'concurrent-ext not loaded' if Concurrent.on_cruby? && Concurrent.c_extensions_loaded? + +scale = 1 +time = 10 * scale +warmup = 2 * scale +warmup *= 10 if Concurrent.on_jruby? + +Benchmark.ips(time, warmup) do |x| + x.report('flat-old') do + Concurrent::Promise.execute { 1 }.flat_map { |v| Concurrent::Promise.execute { v + 2 } }.value! + end + x.report('flat-new') do + Concurrent::Promises.future(:fast) { 1 }.then { |v| Concurrent::Promises.future(:fast) { v + 2 } }.flat.value! + end + x.compare! +end + +Benchmark.ips(time, warmup) do |x| + x.report('status-old') { f = Concurrent::Promise.execute { nil }; 100.times { f.complete? } } + x.report('status-new') { f = Concurrent::Promises.future(:fast) { nil }; 100.times { f.resolved? } } + x.compare! +end + +Benchmark.ips(time, warmup) do |x| + of = Concurrent::Promise.execute { 1 } + nf = Concurrent::Promises.fulfilled_future(1, :fast) + x.report('value-old') { of.value! } + x.report('value-new') { nf.value! } + x.compare! +end + +Benchmark.ips(time, warmup) do |x| + x.report('graph-old') do + head = Concurrent::Promise.fulfill(1) + 10.times do + branch1 = head.then(&:succ) + branch2 = head.then(&:succ).then(&:succ) + head = Concurrent::Promise.zip(branch1, branch2).then { |a, b| a + b } + end + head.value! + end + x.report('graph-new') do + head = Concurrent::Promises.fulfilled_future(1, :fast) + 10.times do + branch1 = head.then(&:succ) + branch2 = head.then(&:succ).then(&:succ) + head = (branch1 & branch2).then { |a, b| a + b } + end + head.value! + end + x.compare! +end + +Benchmark.ips(time, warmup) do |x| + x.report('immediate-old') { Concurrent::Promise.fulfill(nil).value! } + x.report('immediate-new') { Concurrent::Promises.fulfilled_future(nil, :fast).value! } + x.compare! +end + +Benchmark.ips(time, warmup) do |x| + of = Concurrent::Promise.execute { 1 } + nf = Concurrent::Promises.fulfilled_future(1, :fast) + x.report('then-old') { 50.times.reduce(of) { |nf, _| nf.then(&:succ) }.value! } + x.report('then-new') { 50.times.reduce(nf) { |nf, _| nf.then(&:succ) }.value! } + x.compare! +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_read_write_lock.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_read_write_lock.rb new file mode 100755 index 0000000000..e64d3f6c98 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_read_write_lock.rb @@ -0,0 +1,139 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'concurrent/atomic/read_write_lock' +require 'benchmark' +require 'optparse' +require 'ostruct' + +$options = OpenStruct.new +$options.threads = 100 +$options.interleave = false +$options.compare = false + +OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options]" + + opts.on('-t', '--threads=THREADS', OptionParser::DecimalInteger, "Number of threads per test (default #{$options.threads})") do |value| + $options.threads = value + end + + opts.on('-i', '--[no-]interleave', 'Interleave output to check for starvation') do |value| + $options.interleave = value + end + + opts.on('-c', '--[no-]compare', 'Compare with other implementations') do |value| + $options.compare = value + end + + opts.on('-h', '--help', 'Prints this help') do + puts opts + exit + end +end.parse! + +def jruby? + RUBY_ENGINE == "jruby" +end + +# for performance comparison with ReadWriteLock +class SimpleMutex + def initialize; @mutex = Mutex.new; end + def with_read_lock + @mutex.synchronize { yield } + end + alias :with_write_lock :with_read_lock +end + +# for seeing whether my correctness test is doing anything... +# and for seeing how great the overhead of the test is +# (apart from the cost of locking) +class FreeAndEasy + def with_read_lock + yield # thread safety is for the birds... I prefer to live dangerously + end + alias :with_write_lock :with_read_lock +end + +if jruby? + # the Java platform comes with a read-write lock implementation + # performance is very close to ReadWriteLock, but just a *bit* slower + require 'java' + class JavaReadWriteLock + def initialize + @lock = java.util.concurrent.locks.ReentrantReadWriteLock.new + end + def with_read_lock + @lock.read_lock.lock + result = yield + @lock.read_lock.unlock + result + end + def with_write_lock + @lock.write_lock.lock + result = yield + @lock.write_lock.unlock + result + end + end +end + +def test(lock) + puts "READ INTENSIVE (80% read, 20% write):" + single_test(lock, ($options.threads * 0.8).floor, ($options.threads * 0.2).floor) + puts "WRITE INTENSIVE (80% write, 20% read):" + single_test(lock, ($options.threads * 0.2).floor, ($options.threads * 0.8).floor) + puts "BALANCED (50% read, 50% write):" + single_test(lock, ($options.threads * 0.5).floor, ($options.threads * 0.5).floor) +end + +def single_test(lock, n_readers, n_writers, reader_iterations=50, writer_iterations=50, reader_sleep=0.001, writer_sleep=0.001) + puts "Testing #{lock.class} with #{n_readers} readers and #{n_writers} writers. Readers iterate #{reader_iterations} times, sleeping #{reader_sleep}s each time, writers iterate #{writer_iterations} times, sleeping #{writer_sleep}s each time" + mutex = Mutex.new + bad = false + data = 0 + + result = Benchmark.measure do + readers = n_readers.times.collect do + Thread.new do + reader_iterations.times do + lock.with_read_lock do + print "r" if $options.interleave + mutex.synchronize { bad = true } if (data % 2) != 0 + sleep(reader_sleep) + mutex.synchronize { bad = true } if (data % 2) != 0 + end + end + end + end + writers = n_writers.times.collect do + Thread.new do + writer_iterations.times do + lock.with_write_lock do + print "w" if $options.interleave + # invariant: other threads should NEVER see "data" as an odd number + value = (data += 1) + # if a reader runs right now, this invariant will be violated + sleep(writer_sleep) + # this looks like a strange way to increment twice; + # it's designed so that if 2 writers run at the same time, at least + # one increment will be lost, and we can detect that at the end + data = value+1 + end + end + end + end + + readers.each { |t| t.join } + writers.each { |t| t.join } + puts "BAD!!! Readers+writers overlapped!" if mutex.synchronize { bad } + puts "BAD!!! Writers overlapped!" if data != (n_writers * writer_iterations * 2) + end + puts result +end + +test(Concurrent::ReadWriteLock.new) +test(JavaReadWriteLock.new) if $options.compare && jruby? +test(SimpleMutex.new) if $options.compare +test(FreeAndEasy.new) if $options.compare diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_structs.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_structs.rb new file mode 100755 index 0000000000..9491647aa4 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/benchmark_structs.rb @@ -0,0 +1,100 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'benchmark' +require 'concurrent' + +n = 500_000 + +StructPair = Struct.new(:left, :right) +SafePair = Concurrent::MutableStruct.new(:left, :right) +FinalPair = Concurrent::SettableStruct.new(:left, :right) +ImmutablePair = Concurrent::ImmutableStruct.new(:left, :right) + +array_pair = [true, false].freeze +struct_pair = StructPair.new(true, false) +safe_pair = SafePair.new(true, false) +final_pair = FinalPair.new(true, false) +immutable = ImmutablePair.new(true, false) + +puts "Object creation...\n" +Benchmark.bmbm do |x| + x.report('create frozen array') { n.times{ [true, false].freeze } } + x.report('create frozen struct') { n.times{ StructPair.new(true, false).freeze } } + x.report('create mutable struct') { n.times{ SafePair.new(true, false) } } + x.report('create settable struct') { n.times{ FinalPair.new(true, false) } } + x.report('create immutable struct') { n.times{ ImmutablePair.new(true, false) } } +end + +puts "\n" + +puts "Object access...\n" +Benchmark.bmbm do |x| + x.report('read from frozen array') { n.times{ array_pair.last } } + x.report('read from frozen struct') { n.times{ struct_pair.right } } + x.report('read from mutable struct') { n.times{ safe_pair.right } } + x.report('read from settable struct') { n.times{ final_pair.right } } + x.report('read from immutable struct') { n.times{ immutable.right } } +end + +puts "\n" + +puts "Enumeration...\n" +Benchmark.bmbm do |x| + x.report('iterate over frozen array') { n.times{ array_pair.each{ nil } } } + x.report('iterate over frozen struct') { n.times{ struct_pair.each{ nil } } } + x.report('iterate over mutable struct') { n.times{ safe_pair.each{ nil } } } + x.report('iterate over settable struct') { n.times{ final_pair.each{ nil } } } + x.report('iterate over immutable struct') { n.times{ immutable.each{ nil } } } +end + +__END__ + +Object creation... +Rehearsal ----------------------------------------------------------- +create frozen array 0.090000 0.000000 0.090000 ( 0.091262) +create frozen struct 0.180000 0.000000 0.180000 ( 0.179993) +create mutable struct 2.030000 0.000000 2.030000 ( 2.052071) +create settable struct 2.070000 0.000000 2.070000 ( 2.080022) +create immutable struct 0.710000 0.000000 0.710000 ( 0.716877) +-------------------------------------------------- total: 5.080000sec + + user system total real +create frozen array 0.100000 0.000000 0.100000 ( 0.097776) +create frozen struct 0.190000 0.000000 0.190000 ( 0.186287) +create mutable struct 2.020000 0.010000 2.030000 ( 2.032391) +create settable struct 2.030000 0.000000 2.030000 ( 2.031631) +create immutable struct 0.690000 0.000000 0.690000 ( 0.695010) + +Object access... +Rehearsal -------------------------------------------------------------- +read from frozen array 0.060000 0.000000 0.060000 ( 0.060430) +read from frozen struct 0.060000 0.000000 0.060000 ( 0.058978) +read from mutable struct 0.440000 0.000000 0.440000 ( 0.454071) +read from settable struct 0.460000 0.000000 0.460000 ( 0.457699) +read from immutable struct 0.120000 0.000000 0.120000 ( 0.126701) +----------------------------------------------------- total: 1.140000sec + + user system total real +read from frozen array 0.060000 0.000000 0.060000 ( 0.063006) +read from frozen struct 0.060000 0.000000 0.060000 ( 0.094203) +read from mutable struct 0.420000 0.000000 0.420000 ( 0.468304) +read from settable struct 0.410000 0.000000 0.410000 ( 0.452446) +read from immutable struct 0.110000 0.010000 0.120000 ( 0.127030) + +Enumeration... +Rehearsal ----------------------------------------------------------------- +iterate over frozen array 0.170000 0.000000 0.170000 ( 0.176898) +iterate over frozen struct 0.160000 0.000000 0.160000 ( 0.160786) +iterate over mutable struct 1.520000 0.000000 1.520000 ( 1.627013) +iterate over settable struct 1.500000 0.010000 1.510000 ( 1.525163) +iterate over immutable struct 0.990000 0.000000 0.990000 ( 1.006201) +-------------------------------------------------------- total: 4.350000sec + + user system total real +iterate over frozen array 0.170000 0.000000 0.170000 ( 0.167927) +iterate over frozen struct 0.150000 0.000000 0.150000 ( 0.157328) +iterate over mutable struct 1.450000 0.000000 1.450000 ( 1.462654) +iterate over settable struct 1.460000 0.000000 1.460000 ( 1.480270) +iterate over immutable struct 0.940000 0.010000 0.950000 ( 0.955633) diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/format.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/format.rb new file mode 100644 index 0000000000..ca40fb6469 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/format.rb @@ -0,0 +1,75 @@ +require 'rubygems' +require 'bundler/setup' +require 'pry' +require 'pp' + +input_paths = if ARGV.empty? + Dir.glob("#{File.dirname(__FILE__)}/*.in.rb") + else + ARGV + end.map { |p| File.expand_path p } + +input_paths.each_with_index do |input_path, i| + + pid = fork do + require_relative 'init' + + begin + output_path = input_path.gsub /\.in\.rb$/, '.out.rb' + input = File.readlines(input_path) + + chunks = [] + line = '' + + while !input.empty? + line += input.shift + if Pry::Code.complete_expression? line + chunks << line + line = '' + end + end + + raise unless line.empty? + + chunks.map! { |chunk| [chunk, [chunk.split($/).size, 1].max] } + environment = Module.new.send :binding + evaluate = ->(code, line) do + eval(code, environment, input_path, line) + end + + indent = 50 + + line_count = 1 + output = '' + chunks.each do |chunk, lines| + result = evaluate.(chunk, line_count) + unless chunk.strip.empty? || chunk =~ /\A *#/ + pre_lines = chunk.lines.to_a + last_line = pre_lines.pop + output << pre_lines.join + + if last_line =~ /\#$/ + output << last_line.gsub(/\#$/, '') + else + if last_line.size < indent && result.inspect.size < indent + output << "%-#{indent}s %s" % [last_line.chomp, "# => #{result.inspect}\n"] + else + output << last_line << " # => #{result.inspect}\n" + end + end + else + output << chunk + end + line_count += lines + end + + puts "#{input_path}\n -> #{output_path}" + #puts output + File.write(output_path, output) + rescue => ex + puts "#{ex} (#{ex.class})\n#{ex.backtrace * "\n"}" + end + end + + Process.wait pid +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-buffering.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-buffering.rb new file mode 100755 index 0000000000..ec9ac2532d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-buffering.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Channel Buffering +# https://gobyexample.com/channel-buffering + +messages = Channel.new(capacity: 2) # buffered + +messages.put 'buffered' +messages.put 'channel' + +puts messages.take +puts messages.take + +__END__ +buffered +channel diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-directions.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-directions.rb new file mode 100755 index 0000000000..4e1169ae58 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-directions.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Channel Direction +# https://gobyexample.com/channel-directions + +# we can't force a channel to go only one direction w/i a function +# but we can replicate the actual functionality from the example + +def ping(pings, msg) + pings << msg +end + +def pong(pings, pongs) + msg = ~pings + pongs << msg +end + +pings = Channel.new(capacity: 1) # buffered +pongs = Channel.new(capacity: 1) # buffered + +ping(pings, 'passed message') +pong(pings, pongs) + +puts ~pongs + +__END__ +passed message diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-synchronization.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-synchronization.rb new file mode 100755 index 0000000000..3e869b40cc --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channel-synchronization.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Channel Synchronizatio +# https://gobyexample.com/channel-synchronization + +def worker(done_channel) + print "working...\n" + sleep(1) + print "done\n" + + done_channel << true # alias for `#put` +end + +done = Channel.new(capacity: 1) # buffered +Channel.go{ worker(done) } + +~done # alias for `#take` + +__END__ +working... +done diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channels.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channels.rb new file mode 100755 index 0000000000..cfb30f011e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/channels.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Unbuffered Channel +# https://gobyexample.com/channels + +messages = Channel.new # unbuffered + +Channel.go do + messages.put 'ping' +end + +msg = messages.take +puts msg + +__END__ +ping diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/closing-channels.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/closing-channels.rb new file mode 100755 index 0000000000..33d8656311 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/closing-channels.rb @@ -0,0 +1,45 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Closing Channels +# https://gobyexample.com/closing-channels + +validator = ->(v){ v.is_a? Numeric } +jobs = Channel.new(buffer: :buffered, capacity: 5, + validator: validator) +done = Channel.new(buffer: :unbuffered) + +Channel.go_loop do + j, more = jobs.next + if more + print "received job #{j}\n" + true # loop again + else + print "received all jobs\n" + done << true + false # exit the loop + end +end + +(1..3).each do |i| + jobs << i + print "sent job #{i}\n" + Thread.pass # give the worker a chance to run +end + +jobs.close +print "sent all jobs\n" +~done + +__END__ +sent job 1 +received job 1 +sent job 2 +received job 2 +sent job 3 +received job 3 +sent all jobs +received all jobs diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/non-blocking-channel-operations.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/non-blocking-channel-operations.rb new file mode 100755 index 0000000000..71eeaef659 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/non-blocking-channel-operations.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Non-Blocking Channel Operations +# https://gobyexample.com/non-blocking-channel-operations + +messages = Channel.new # unbuffered +signals = Channel.new # unbuffered + +Channel.select do |s| + s.take(messages) { |msg| print "received message #{msg}\n" } + s.default { print "no message received\n" } +end + +message = 'hi' +Channel.select do |s| + s.put(messages, message) { |msg| print "sent message #{msg}\n" } + s.default { print "no message sent\n" } +end + +Channel.select do |s| + s.case(messages, :~) { |msg| print "received message #{msg}\n" } # alias for `s.take` + s.case(signals, :~) { |sig| print "received signal #{sig}\n" } # alias for `s.take` + s.default { print "no activity\n" } +end + +__END__ +no message received +no message sent +no activity diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/range-over-channels.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/range-over-channels.rb new file mode 100755 index 0000000000..c638a5cef4 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/range-over-channels.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Range over Channels +# https://gobyexample.com/range-over-channels + +queue = Channel.new(capacity: 2) # buffered +queue << 'one' +queue << 'two' +queue.close + +queue.each do |elem| + print "#{elem}\n" +end + +__END__ +one +two diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/rate-limiting.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/rate-limiting.rb new file mode 100755 index 0000000000..780be05978 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/rate-limiting.rb @@ -0,0 +1,61 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +require 'time' + +Channel = Concurrent::Channel + +## Go by Example: Rate Limiting +# https://gobyexample.com/rate-limiting + +requests = Channel.new(buffer: :buffered, capacity: 5) +(1..5).each do |i| + requests << i +end +requests.close + +limiter = Channel.ticker(0.2) +requests.each do |req| + print "request #{req} #{Channel::Tick.new}\n" if ~limiter +end +print "\n" + +bursty_limiter = Channel.new(buffer: :buffered, capacity: 3) +(1..3).each do + bursty_limiter << Channel::Tick.new +end + +ticker = Channel.ticker(0.2) +Channel.go do + ticker.each do |t| + bursty_limiter << t + end +end + +bursty_requests = Channel.new(buffer: :buffered, capacity: 5) +(1..5).each do |i| + bursty_requests << i +end +bursty_requests.close + +bursty_requests.each do |req| + ~bursty_limiter + print "request #{req} #{Channel::Tick.new}\n" +end + +limiter.close +ticker.close + +__END__ +request 1 2012-10-19 00:38:18.687438 +0000 UTC +request 2 2012-10-19 00:38:18.887471 +0000 UTC +request 3 2012-10-19 00:38:19.087238 +0000 UTC +request 4 2012-10-19 00:38:19.287338 +0000 UTC +request 5 2012-10-19 00:38:19.487331 +0000 UTC + +request 1 2012-10-19 00:38:20.487578 +0000 UTC +request 2 2012-10-19 00:38:20.487645 +0000 UTC +request 3 2012-10-19 00:38:20.487676 +0000 UTC +request 4 2012-10-19 00:38:20.687483 +0000 UTC +request 5 2012-10-19 00:38:20.887542 +0000 UTC diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/select.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/select.rb new file mode 100755 index 0000000000..0848c8ef42 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/select.rb @@ -0,0 +1,32 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Select +# https://gobyexample.com/select + +c1 = Channel.new # unbuffered +c2 = Channel.new # unbuffered + +Channel.go do + sleep(1) + c1 << 'one' +end + +Channel.go do + sleep(2) + c1 << 'two' +end + +2.times do + Channel.select do |s| + s.take(c1) { |msg| print "received #{msg}\n" } + s.take(c2) { |msg| print "received #{msg}\n" } + end +end + +__END__ +received one +received two diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/ticker.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/ticker.rb new file mode 100755 index 0000000000..6e6701444f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/ticker.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Tickers +# https://gobyexample.com/tickers + +ticker = Channel.ticker(0.5) +Channel.go do + ticker.each do |tick| + print "Tick at #{tick}\n" if tick + end +end + +sleep(1.6) +ticker.stop +print "Ticker stopped\n" + +__END__ +Tick at 2012-09-23 11:29:56.487625 -0700 PDT +Tick at 2012-09-23 11:29:56.988063 -0700 PDT +Tick at 2012-09-23 11:29:57.488076 -0700 PDT +Ticker stopped diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/timeouts.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/timeouts.rb new file mode 100755 index 0000000000..0568924ca0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/timeouts.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Timeouts +# https://gobyexample.com/timeouts + +c1 = Channel.new(capacity: 1) # buffered +Channel.go do + sleep(2) + c1 << 'result 1' +end + +Channel.select do |s| + s.take(c1) { |msg| print "#{msg}\n" } + s.after(1) { print "timeout 1\n" } +end + +c2 = Channel.new(capacity: 1) # buffered +Channel.go do + sleep(2) + c2 << 'result 2' +end + +Channel.select do |s| + s.take(c2) { |msg| print "#{msg}\n" } + s.after(3) { print "timeout 2\n" } +end + +__END__ +timeout 1 +result 2 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/timers.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/timers.rb new file mode 100755 index 0000000000..2e667c0729 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/timers.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Timers +# https://gobyexample.com/timers + +timer1 = Channel.timer(2) + +puts 'Timer 1 expired' if ~timer1 + +timer2 = Channel.timer(1) +Channel.go do + print "Timer 2 expired\n" if ~timer2 +end + +stop2 = timer2.stop +print "Timer 2 stopped\n" if stop2 + +__END__ +Timer 1 expired +Timer 2 stopped diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/worker-pools.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/worker-pools.rb new file mode 100755 index 0000000000..4d5c5d1f13 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/go-by-example-channels/worker-pools.rb @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../../lib', __FILE__) +require 'concurrent-edge' +Channel = Concurrent::Channel + +## Go by Example: Go by Example: Worker Pools +# https://gobyexample.com/worker-pools + +def worker(id, jobs, results) + jobs.each do |j| + print "worker #{id} processing job #{j}\n" + sleep(1) + results << j * 2 + end +end + +jobs = Channel.new(buffer: :buffered, capacity: 100) +results = Channel.new(buffer: :buffered, capacity: 100) + +(1..3).each do |w| + Channel.go { worker(w, jobs, results) } +end + +(1..9).each do |j| + jobs << j +end +jobs.close + +(1..9).each do + ~results +end + +__END__ +worker 1 processing job 1 +worker 2 processing job 2 +worker 3 processing job 3 +worker 1 processing job 4 +worker 2 processing job 5 +worker 3 processing job 6 +worker 1 processing job 7 +worker 2 processing job 8 +worker 3 processing job 9 diff --git a/app/server/ruby/vendor/atomic/examples/graph_atomic_bench.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/graph_atomic_bench.rb similarity index 59% rename from app/server/ruby/vendor/atomic/examples/graph_atomic_bench.rb rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/graph_atomic_bench.rb index d118073a51..c50da17374 100755 --- a/app/server/ruby/vendor/atomic/examples/graph_atomic_bench.rb +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/graph_atomic_bench.rb @@ -1,16 +1,6 @@ #!/usr/bin/env ruby -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +#$: << File.expand_path('../../lib', __FILE__) require 'optparse' @@ -33,13 +23,13 @@ if conf[:vary] == "threads" - # Vary the number of concurrent threads that update the value. + # Varies the number of concurrent threads that update the value. # # There is a total count of 1mio updates that is distributed # between the number of threads. # - # A pair number of threads is used so that even add and odd substract 1. - # This avoid creating instances for Bignum since the number should + # A doubled number of threads is used so that even adds 1 and odd subtracts 1. + # This avoids creating instances for Bignum since the number should # stay in the Fixnum range. # (1..100).each do |i| @@ -47,7 +37,7 @@ ret = [] 10.times do - ret << `ruby ./bench_atomic_1.rb -l #{conf[:lock]} -t #{i}`.to_f + ret << `ruby #{File.dirname(__FILE__)}/benchmark_atomic_1.rb -l #{conf[:lock]} -t #{i}`.to_f end line = ([i] + ret).join(', ') @@ -70,7 +60,7 @@ ret = [] 10.times do - ret << `ruby ./bench_atomic_1.rb -l #{conf[:lock]} -s #{i}`.to_f + ret << `ruby #{File.dirname(__FILE__)}/benchmark_atomic_1.rb -l #{conf[:lock]} -s #{i}`.to_f end line = ([i] + ret).join(', ') diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/init.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/init.rb new file mode 100644 index 0000000000..03c91f7c17 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/init.rb @@ -0,0 +1,7 @@ +require 'concurrent-edge' + +def do_stuff(*args) + :stuff +end + +Concurrent.use_simple_logger Logger::DEBUG diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/stress_ruby_thread_pool.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/stress_ruby_thread_pool.rb new file mode 100755 index 0000000000..df6308a0c9 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/stress_ruby_thread_pool.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +$: << File.expand_path('../../lib', __FILE__) + +require 'benchmark' +require 'concurrent/executors' + +COUNT = 100_000 + +executor = Concurrent::CachedThreadPool.new +latch = Concurrent::CountDownLatch.new + +COUNT.times { executor.post{ nil } } + +#COUNT.times do |i| +# executor.post{ nil } +# sleep(0.01) if i % 1000 == 0 +#end + +executor.post{ latch.count_down } +latch.wait + +puts "Max length: #{executor.max_length}" if executor.respond_to?(:max_length) +puts "Largest length: #{executor.largest_length}" if executor.respond_to?(:largest_length) +puts "Scheduled task count: #{executor.scheduled_task_count}" if executor.respond_to?(:scheduled_task_count) +puts "Completed task count: #{executor.completed_task_count}" if executor.respond_to?(:completed_task_count) diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/thread_local_memory_usage.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/thread_local_memory_usage.rb new file mode 100755 index 0000000000..cf96632bdf --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/thread_local_memory_usage.rb @@ -0,0 +1,74 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +$DEBUG_TLV = true +require 'concurrent' +require 'concurrent/atomic/thread_local_var' +require 'benchmark' +require 'thread' + +include Concurrent + +# if we hold on to vars, but threads die, space used for TLVs should be recovered + +def test_thread_gc(vars) + threads = 500.times.collect do + Thread.new do + vars.each do |var| + var.value = 1 + end + end + end + threads.each(&:join) +end + +puts "BEFORE THREAD GC TEST:" +puts "Ruby heap pages: #{GC.stat[:heap_length]}, Other malloc'd bytes: #{GC.stat[:malloc_increase]}" + +vars = 500.times.collect { ThreadLocalVar.new(0) } +200.times do + test_thread_gc(vars) + GC.start +end + +puts "AFTER THREAD GC TEST:" +puts "Ruby heap pages: #{GC.stat[:heap_length]}, Other malloc'd bytes: #{GC.stat[:malloc_increase]}" + +# if we hold on to threads, but drop TLVs, space used should be reused by allocated TLVs + +def tlv_gc_test_loop(queue) + while true + var = queue.pop + return if var.nil? + var.value = 1 + end +end + +def test_tlv_gc(queues) + 500.times do + var = ThreadLocalVar.new(0) + queues.each { |q| q << var } + end +end + +puts +puts "BEFORE TLV GC TEST:" +puts "Ruby heap pages: #{GC.stat[:heap_length]}, Other malloc'd bytes: #{GC.stat[:malloc_increase]}" + +queues = 500.times.collect { Queue.new } +threads = queues.map do |queue| + Thread.new do + tlv_gc_test_loop(queue) + end +end + +200.times do + test_tlv_gc(queues) + GC.start +end +queues.each { |q| q << nil } +threads.each(&:join) + +puts "AFTER TLV GC TEST:" +puts "Ruby heap pages: #{GC.stat[:heap_length]}, Other malloc'd bytes: #{GC.stat[:malloc_increase]}" diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/thread_local_var_bench.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/thread_local_var_bench.rb new file mode 100755 index 0000000000..34b28cf1ed --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/thread_local_var_bench.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby + +#$: << File.expand_path('../../lib', __FILE__) + +require 'concurrent' +require 'concurrent/atomic/thread_local_var' +require 'benchmark' + +include Concurrent + +N_THREADS = 100 +N_VARS = 100 + +vars = N_VARS.times.collect { ThreadLocalVar.new(0) } + +def test_threadlocal_perf(vars) + threads = N_THREADS.times.collect do + Thread.new do + 10000.times do + index = rand(N_VARS) + var = vars[index] + var.value = var.value + 1 + end + end + end + threads.each(&:join) +end + +Benchmark.bmbm do |bm| + bm.report('ThreadLocalVar') { test_threadlocal_perf(vars) } +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/who.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/who.rb new file mode 100755 index 0000000000..9a7799300e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/examples/who.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby + +require 'net/http' +require 'json' + +# http://www.schneems.com/blogs/2015-09-30-reverse-rubygems/ + +gem_name = "concurrent-ruby" + +def rubygems_get(gem_name: "", endpoint: "") + path = File.join("/api/v1/gems/", gem_name, endpoint).chomp("/") + ".json" + JSON.parse(Net::HTTP.get("rubygems.org", path)) +end + +results = rubygems_get(gem_name: gem_name, endpoint: "reverse_dependencies") + +weighted_results = {} +results.each do |name| + begin + weighted_results[name] = rubygems_get(gem_name: name)["downloads"] + rescue => e + puts "#{name} #{e.message}" + end +end + +weighted_results.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50).each_with_index do |(k, v), i| + puts "#{i}) #{k}: #{v}" +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_boolean.c b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_boolean.c new file mode 100644 index 0000000000..d4913df56a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_boolean.c @@ -0,0 +1,46 @@ +#include + +#include "atomic_boolean.h" +#include "atomic_reference.h" + +void atomic_boolean_mark(void *value) { + rb_gc_mark_maybe((VALUE) value); +} + +VALUE atomic_boolean_allocate(VALUE klass) { + return rb_data_object_wrap(klass, (void *) Qfalse, atomic_boolean_mark, NULL); +} + +VALUE method_atomic_boolean_initialize(int argc, VALUE* argv, VALUE self) { + VALUE value = Qfalse; + rb_check_arity(argc, 0, 1); + if (argc == 1) value = TRUTHY(argv[0]); + DATA_PTR(self) = (void *) value; + return(self); +} + +VALUE method_atomic_boolean_value(VALUE self) { + return(ir_get(self)); +} + +VALUE method_atomic_boolean_value_set(VALUE self, VALUE value) { + VALUE new_value = TRUTHY(value); + return(ir_set(self, new_value)); +} + +VALUE method_atomic_boolean_true_question(VALUE self) { + return(method_atomic_boolean_value(self)); +} + +VALUE method_atomic_boolean_false_question(VALUE self) { + VALUE current = method_atomic_boolean_value(self); + return(current == Qfalse ? Qtrue : Qfalse); +} + +VALUE method_atomic_boolean_make_true(VALUE self) { + return(ir_compare_and_set(self, Qfalse, Qtrue)); +} + +VALUE method_atomic_boolean_make_false(VALUE self) { + return(ir_compare_and_set(self, Qtrue, Qfalse)); +} diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_boolean.h b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_boolean.h new file mode 100644 index 0000000000..71e7d059ef --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_boolean.h @@ -0,0 +1,16 @@ +#ifndef __ATOMIC_BOOLEAN_H__ +#define __ATOMIC_BOOLEAN_H__ + +#define TRUTHY(value)(value == Qfalse || value == Qnil ? Qfalse : Qtrue) + +void atomic_boolean_mark(void*); +VALUE atomic_boolean_allocate(VALUE); +VALUE method_atomic_boolean_initialize(int, VALUE*, VALUE); +VALUE method_atomic_boolean_value(VALUE); +VALUE method_atomic_boolean_value_set(VALUE, VALUE); +VALUE method_atomic_boolean_true_question(VALUE); +VALUE method_atomic_boolean_false_question(VALUE); +VALUE method_atomic_boolean_make_true(VALUE); +VALUE method_atomic_boolean_make_false(VALUE); + +#endif diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_fixnum.c b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_fixnum.c new file mode 100644 index 0000000000..faa0ac0acc --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_fixnum.c @@ -0,0 +1,72 @@ +#include + +#include "atomic_fixnum.h" +#include "atomic_reference.h" + +void atomic_fixnum_mark(void *value) { + rb_gc_mark_maybe((VALUE) value); +} + +VALUE atomic_fixnum_allocate(VALUE klass) { + return rb_data_object_wrap(klass, (void *) Qnil, atomic_fixnum_mark, NULL); +} + +VALUE method_atomic_fixnum_initialize(int argc, VALUE* argv, VALUE self) { + VALUE value = LL2NUM(0); + rb_check_arity(argc, 0, 1); + if (argc == 1) { + Check_Type(argv[0], T_FIXNUM); + value = argv[0]; + } + DATA_PTR(self) = (void *) value; + return(self); +} + +VALUE method_atomic_fixnum_value(VALUE self) { + return (VALUE) DATA_PTR(self); +} + +VALUE method_atomic_fixnum_value_set(VALUE self, VALUE value) { + Check_Type(value, T_FIXNUM); + DATA_PTR(self) = (void *) value; + return(value); +} + +VALUE method_atomic_fixnum_increment(int argc, VALUE* argv, VALUE self) { + long long value = NUM2LL((VALUE) DATA_PTR(self)); + long long delta = 1; + rb_check_arity(argc, 0, 1); + if (argc == 1) { + Check_Type(argv[0], T_FIXNUM); + delta = NUM2LL(argv[0]); + } + return method_atomic_fixnum_value_set(self, LL2NUM(value + delta)); +} + +VALUE method_atomic_fixnum_decrement(int argc, VALUE* argv, VALUE self) { + long long value = NUM2LL((VALUE) DATA_PTR(self)); + long long delta = 1; + rb_check_arity(argc, 0, 1); + if (argc == 1) { + Check_Type(argv[0], T_FIXNUM); + delta = NUM2LL(argv[0]); + } + return method_atomic_fixnum_value_set(self, LL2NUM(value - delta)); +} + +VALUE method_atomic_fixnum_compare_and_set(VALUE self, VALUE rb_expect, VALUE rb_update) { + Check_Type(rb_expect, T_FIXNUM); + Check_Type(rb_update, T_FIXNUM); + return ir_compare_and_set(self, rb_expect, rb_update); +} + +VALUE method_atomic_fixnum_update(VALUE self) { + VALUE old_value, new_value; + for (;;) { + old_value = method_atomic_fixnum_value(self); + new_value = rb_yield(old_value); + if (ir_compare_and_set(self, old_value, new_value) == Qtrue) { + return new_value; + } + } +} diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_fixnum.h b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_fixnum.h new file mode 100644 index 0000000000..157e14fc4b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_fixnum.h @@ -0,0 +1,14 @@ +#ifndef __ATOMIC_FIXNUM_H__ +#define __ATOMIC_FIXNUM_H__ + +void atomic_fixnum_mark(void*); +VALUE atomic_fixnum_allocate(VALUE); +VALUE method_atomic_fixnum_initialize(int, VALUE*, VALUE); +VALUE method_atomic_fixnum_value(VALUE); +VALUE method_atomic_fixnum_value_set(VALUE, VALUE); +VALUE method_atomic_fixnum_increment(int, VALUE*, VALUE); +VALUE method_atomic_fixnum_decrement(int, VALUE*, VALUE); +VALUE method_atomic_fixnum_compare_and_set(VALUE, VALUE, VALUE); +VALUE method_atomic_fixnum_update(VALUE); + +#endif diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_reference.c b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_reference.c new file mode 100644 index 0000000000..e03e07eaed --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_reference.c @@ -0,0 +1,113 @@ +#include +/*#if defined(__sun)*/ +/*#include */ +/*#endif*/ + +/*#ifdef HAVE_LIBKERN_OSATOMIC_H*/ +/*#include */ +/*#endif*/ + +/* +Following the wisdom of postgres, we opt to use platform specific memory barriers when available. +These are generally more performant. In this PR, we add specific cases for i386, x86_64. + +In the future, we could look at using pg's atomics library directly: +https://github.com/postgres/postgres/tree/9d4649ca49416111aee2c84b7e4441a0b7aa2fac/src/include/port/atomics + +Point of contact @ianks +*/ + +#include "atomic_reference.h" + +#if (defined(__i386__) || defined(__i386)) && (defined(__GNUC__) || defined(__INTEL_COMPILER)) +#define memory_barrier() \ + __asm__ __volatile__ ("lock; addl $0,0(%%esp)" : : : "memory", "cc") +#elif defined(__x86_64__) && (defined(__GNUC__) || defined(__INTEL_COMPILER)) +#define memory_barrier() \ + __asm__ __volatile__ ("lock; addl $0,0(%%rsp)" : : : "memory", "cc") +#elif defined(HAVE_GCC__ATOMIC_INT32_CAS) +#define memory_barrier() \ + __atomic_thread_fence(__ATOMIC_SEQ_CST) +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) +#define memory_barrier() \ + __sync_synchronize(); +#elif defined _MSC_VER +#define memory_barrier() \ + MemoryBarrier(); +#elif __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 +#define memory_barrier() \ + OSMemoryBarrier(); +#endif + +void ir_mark(void *value) { + rb_gc_mark_maybe((VALUE) value); +} + +VALUE ir_alloc(VALUE klass) { + return rb_data_object_wrap(klass, (void *) Qnil, ir_mark, NULL); +} + +VALUE ir_initialize(int argc, VALUE* argv, VALUE self) { + VALUE value = Qnil; + if (rb_scan_args(argc, argv, "01", &value) == 1) { + value = argv[0]; + } + DATA_PTR(self) = (void *) value; + return Qnil; +} + +VALUE ir_get(VALUE self) { + memory_barrier(); + return (VALUE) DATA_PTR(self); +} + +VALUE ir_set(VALUE self, VALUE new_value) { + DATA_PTR(self) = (void *) new_value; + memory_barrier(); + return new_value; +} + +VALUE ir_get_and_set(VALUE self, VALUE new_value) { + VALUE old_value; + for (;;) { + old_value = ir_get(self); + if (ir_compare_and_set(self, old_value, new_value) == Qtrue) { + return old_value; + } + } +} + +VALUE ir_compare_and_set(volatile VALUE self, VALUE expect_value, VALUE new_value) { +#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 + if (OSAtomicCompareAndSwap64(expect_value, new_value, &DATA_PTR(self))) { + return Qtrue; + } +#elif defined(__sun) + /* Assuming VALUE is uintptr_t */ + /* Based on the definition of uintptr_t from /usr/include/sys/int_types.h */ +#if defined(_LP64) || defined(_I32LPx) + /* 64-bit: uintptr_t === unsigned long */ + if (atomic_cas_ulong((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) { + return Qtrue; + } +#else + /* 32-bit: uintptr_t === unsigned int */ + if (atomic_cas_uint((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) { + return Qtrue; + } +#endif +#elif defined _MSC_VER && defined _M_AMD64 + if (InterlockedCompareExchange64((LONGLONG*)&DATA_PTR(self), new_value, expect_value)) { + return Qtrue; + } +#elif defined _MSC_VER && defined _M_IX86 + if (InterlockedCompareExchange((LONG*)&DATA_PTR(self), new_value, expect_value)) { + return Qtrue; + } +#else + if (__sync_bool_compare_and_swap(&DATA_PTR(self), expect_value, new_value)) { + return Qtrue; + } +#endif + return Qfalse; +} diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_reference.h b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_reference.h new file mode 100644 index 0000000000..1f33414a91 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/atomic_reference.h @@ -0,0 +1,20 @@ +#ifndef __ATOMIC_REFERENCE_H__ +#define __ATOMIC_REFERENCE_H__ + +#if defined(__sun) +#include +#endif + +#ifdef HAVE_LIBKERN_OSATOMIC_H +#include +#endif + +void ir_mark(void*); +VALUE ir_alloc(VALUE); +VALUE ir_initialize(int, VALUE*, VALUE); +VALUE ir_get(VALUE); +VALUE ir_set(VALUE, VALUE); +VALUE ir_get_and_set(VALUE, VALUE); +VALUE ir_compare_and_set(volatile VALUE, VALUE, VALUE); + +#endif diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/extconf.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/extconf.rb new file mode 100644 index 0000000000..2d2cd8290b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/extconf.rb @@ -0,0 +1,27 @@ +require 'fileutils' +require 'mkmf' + +unless RUBY_ENGINE == "ruby" + File.write("Makefile", dummy_makefile($srcdir).join("")) + exit +end + +extension_name = 'concurrent_ruby_ext' + +dir_config(extension_name) +have_header "libkern/OSAtomic.h" + +compiler_is_gcc = (CONFIG["GCC"] && !CONFIG["GCC"].empty?) || + # This could stand to be more generic... but I am afraid. + CONFIG["CC"] =~ /\bgcc\b/ + +if compiler_is_gcc + case CONFIG["arch"] + when /mswin32|mingw|solaris/ + $CFLAGS += " -march=native" + when 'i686-linux' + $CFLAGS += " -march=i686" + end +end + +create_makefile File.join('concurrent', extension_name) diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/rb_concurrent.c b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/rb_concurrent.c new file mode 100644 index 0000000000..3625e13b1e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby-ext/rb_concurrent.c @@ -0,0 +1,58 @@ +#include + +#include "atomic_reference.h" +#include "atomic_boolean.h" +#include "atomic_fixnum.h" + +// module and class definitions + +static VALUE rb_mConcurrent; +static VALUE rb_cAtomicReference; +static VALUE rb_cAtomicBoolean; +static VALUE rb_cAtomicFixnum; + +// Init_extension + +void Init_concurrent_ruby_ext(void) { + + // define modules and classes + rb_mConcurrent = rb_define_module("Concurrent"); + rb_cAtomicReference = rb_define_class_under(rb_mConcurrent, "CAtomicReference", rb_cObject); + rb_cAtomicBoolean = rb_define_class_under(rb_mConcurrent, "CAtomicBoolean", rb_cObject); + rb_cAtomicFixnum = rb_define_class_under(rb_mConcurrent, "CAtomicFixnum", rb_cObject); + + // CAtomicReference + rb_define_alloc_func(rb_cAtomicReference, ir_alloc); + rb_define_method(rb_cAtomicReference, "initialize", ir_initialize, -1); + rb_define_method(rb_cAtomicReference, "get", ir_get, 0); + rb_define_method(rb_cAtomicReference, "set", ir_set, 1); + rb_define_method(rb_cAtomicReference, "get_and_set", ir_get_and_set, 1); + rb_define_method(rb_cAtomicReference, "_compare_and_set", ir_compare_and_set, 2); + rb_define_alias(rb_cAtomicReference, "value", "get"); + rb_define_alias(rb_cAtomicReference, "value=", "set"); + rb_define_alias(rb_cAtomicReference, "swap", "get_and_set"); + + // CAtomicBoolean + rb_define_alloc_func(rb_cAtomicBoolean, atomic_boolean_allocate); + rb_define_method(rb_cAtomicBoolean, "initialize", method_atomic_boolean_initialize, -1); + rb_define_method(rb_cAtomicBoolean, "value", method_atomic_boolean_value, 0); + rb_define_method(rb_cAtomicBoolean, "value=", method_atomic_boolean_value_set, 1); + rb_define_method(rb_cAtomicBoolean, "true?", method_atomic_boolean_true_question, 0); + rb_define_method(rb_cAtomicBoolean, "false?", method_atomic_boolean_false_question, 0); + rb_define_method(rb_cAtomicBoolean, "make_true", method_atomic_boolean_make_true, 0); + rb_define_method(rb_cAtomicBoolean, "make_false", method_atomic_boolean_make_false, 0); + + // CAtomicFixnum + rb_define_const(rb_cAtomicFixnum, "MIN_VALUE", LL2NUM(LLONG_MIN)); + rb_define_const(rb_cAtomicFixnum, "MAX_VALUE", LL2NUM(LLONG_MAX)); + rb_define_alloc_func(rb_cAtomicFixnum, atomic_fixnum_allocate); + rb_define_method(rb_cAtomicFixnum, "initialize", method_atomic_fixnum_initialize, -1); + rb_define_method(rb_cAtomicFixnum, "value", method_atomic_fixnum_value, 0); + rb_define_method(rb_cAtomicFixnum, "value=", method_atomic_fixnum_value_set, 1); + rb_define_method(rb_cAtomicFixnum, "increment", method_atomic_fixnum_increment, -1); + rb_define_method(rb_cAtomicFixnum, "decrement", method_atomic_fixnum_decrement, -1); + rb_define_method(rb_cAtomicFixnum, "compare_and_set", method_atomic_fixnum_compare_and_set, 2); + rb_define_method(rb_cAtomicFixnum, "update", method_atomic_fixnum_update, 0); + rb_define_alias(rb_cAtomicFixnum, "up", "increment"); + rb_define_alias(rb_cAtomicFixnum, "down", "decrement"); +} diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/ConcurrentRubyService.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/ConcurrentRubyService.java new file mode 100644 index 0000000000..fb6be96d37 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/ConcurrentRubyService.java @@ -0,0 +1,17 @@ +import org.jruby.Ruby; +import org.jruby.runtime.load.BasicLibraryService; + +import java.io.IOException; + +public class ConcurrentRubyService implements BasicLibraryService { + + public boolean basicLoad(final Ruby runtime) throws IOException { + new com.concurrent_ruby.ext.AtomicReferenceLibrary().load(runtime, false); + new com.concurrent_ruby.ext.JavaAtomicBooleanLibrary().load(runtime, false); + new com.concurrent_ruby.ext.JavaAtomicFixnumLibrary().load(runtime, false); + new com.concurrent_ruby.ext.JavaSemaphoreLibrary().load(runtime, false); + new com.concurrent_ruby.ext.SynchronizationLibrary().load(runtime, false); + new com.concurrent_ruby.ext.JRubyMapBackendLibrary().load(runtime, false); + return true; + } +} diff --git a/app/server/ruby/vendor/atomic/ext/org/jruby/ext/atomic/AtomicReferenceLibrary.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java old mode 100755 new mode 100644 similarity index 90% rename from app/server/ruby/vendor/atomic/ext/org/jruby/ext/atomic/AtomicReferenceLibrary.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java index f549d93d0c..dfa9e7704e --- a/app/server/ruby/vendor/atomic/ext/org/jruby/ext/atomic/AtomicReferenceLibrary.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java @@ -1,16 +1,4 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package org.jruby.ext.atomic; +package com.concurrent_ruby.ext; import java.lang.reflect.Field; import java.io.IOException; @@ -36,7 +24,8 @@ */ public class AtomicReferenceLibrary implements Library { public void load(Ruby runtime, boolean wrap) throws IOException { - RubyClass atomicCls = runtime.defineClass("Atomic", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicReference", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); try { sun.misc.Unsafe.class.getMethod("getAndSetObject", Object.class); atomicCls.setAllocator(JRUBYREFERENCE8_ALLOCATOR); diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java new file mode 100644 index 0000000000..a09f9162ee --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java @@ -0,0 +1,248 @@ +package com.concurrent_ruby.ext; + +import org.jruby.*; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import com.concurrent_ruby.ext.jsr166e.ConcurrentHashMap; +import com.concurrent_ruby.ext.jsr166e.ConcurrentHashMapV8; +import com.concurrent_ruby.ext.jsr166e.nounsafe.*; +import org.jruby.runtime.Block; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; + +import java.io.IOException; +import java.util.Map; + +import static org.jruby.runtime.Visibility.PRIVATE; + +/** + * Native Java implementation to avoid the JI overhead. + * + * @author thedarkone + */ +public class JRubyMapBackendLibrary implements Library { + public void load(Ruby runtime, boolean wrap) throws IOException { + + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyModule thread_safeMod = concurrentMod.defineModuleUnder("Collection"); + RubyClass jrubyRefClass = thread_safeMod.defineClassUnder("JRubyMapBackend", runtime.getObject(), BACKEND_ALLOCATOR); + jrubyRefClass.setAllocator(BACKEND_ALLOCATOR); + jrubyRefClass.defineAnnotatedMethods(JRubyMapBackend.class); + } + + private static final ObjectAllocator BACKEND_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRubyMapBackend(runtime, klazz); + } + }; + + @JRubyClass(name="JRubyMapBackend", parent="Object") + public static class JRubyMapBackend extends RubyObject { + // Defaults used by the CHM + static final int DEFAULT_INITIAL_CAPACITY = 16; + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + public static final boolean CAN_USE_UNSAFE_CHM = canUseUnsafeCHM(); + + private ConcurrentHashMap map; + + private static ConcurrentHashMap newCHM(int initialCapacity, float loadFactor) { + if (CAN_USE_UNSAFE_CHM) { + return new ConcurrentHashMapV8(initialCapacity, loadFactor); + } else { + return new com.concurrent_ruby.ext.jsr166e.nounsafe.ConcurrentHashMapV8(initialCapacity, loadFactor); + } + } + + private static ConcurrentHashMap newCHM() { + return newCHM(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + private static boolean canUseUnsafeCHM() { + try { + new com.concurrent_ruby.ext.jsr166e.ConcurrentHashMapV8(); // force class load and initialization + return true; + } catch (Throwable t) { // ensuring we really do catch everything + // Doug's Unsafe setup errors always have this "Could not ini.." message + if (isCausedBySecurityException(t)) { + return false; + } + throw (t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t)); + } + } + + private static boolean isCausedBySecurityException(Throwable t) { + while (t != null) { + if ((t.getMessage() != null && t.getMessage().contains("Could not initialize intrinsics")) || t instanceof SecurityException) { + return true; + } + t = t.getCause(); + } + return false; + } + + public JRubyMapBackend(Ruby runtime, RubyClass klass) { + super(runtime, klass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context) { + map = newCHM(); + return context.getRuntime().getNil(); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject options) { + map = toCHM(context, options); + return context.getRuntime().getNil(); + } + + private ConcurrentHashMap toCHM(ThreadContext context, IRubyObject options) { + Ruby runtime = context.getRuntime(); + if (!options.isNil() && options.respondsTo("[]")) { + IRubyObject rInitialCapacity = options.callMethod(context, "[]", runtime.newSymbol("initial_capacity")); + IRubyObject rLoadFactor = options.callMethod(context, "[]", runtime.newSymbol("load_factor")); + int initialCapacity = !rInitialCapacity.isNil() ? RubyNumeric.num2int(rInitialCapacity.convertToInteger()) : DEFAULT_INITIAL_CAPACITY; + float loadFactor = !rLoadFactor.isNil() ? (float)RubyNumeric.num2dbl(rLoadFactor.convertToFloat()) : DEFAULT_LOAD_FACTOR; + return newCHM(initialCapacity, loadFactor); + } else { + return newCHM(); + } + } + + @JRubyMethod(name = "[]", required = 1) + public IRubyObject op_aref(ThreadContext context, IRubyObject key) { + IRubyObject value; + return ((value = map.get(key)) == null) ? context.getRuntime().getNil() : value; + } + + @JRubyMethod(name = {"[]="}, required = 2) + public IRubyObject op_aset(IRubyObject key, IRubyObject value) { + map.put(key, value); + return value; + } + + @JRubyMethod + public IRubyObject put_if_absent(IRubyObject key, IRubyObject value) { + IRubyObject result = map.putIfAbsent(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject compute_if_absent(final ThreadContext context, final IRubyObject key, final Block block) { + return map.computeIfAbsent(key, new ConcurrentHashMap.Fun() { + @Override + public IRubyObject apply(IRubyObject key) { + return block.yieldSpecific(context); + } + }); + } + + @JRubyMethod + public IRubyObject compute_if_present(final ThreadContext context, final IRubyObject key, final Block block) { + IRubyObject result = map.computeIfPresent(key, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject key, IRubyObject oldValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject compute(final ThreadContext context, final IRubyObject key, final Block block) { + IRubyObject result = map.compute(key, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject key, IRubyObject oldValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject merge_pair(final ThreadContext context, final IRubyObject key, final IRubyObject value, final Block block) { + IRubyObject result = map.merge(key, value, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject oldValue, IRubyObject newValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public RubyBoolean replace_pair(IRubyObject key, IRubyObject oldValue, IRubyObject newValue) { + return getRuntime().newBoolean(map.replace(key, oldValue, newValue)); + } + + @JRubyMethod(name = "key?", required = 1) + public RubyBoolean has_key_p(IRubyObject key) { + return map.containsKey(key) ? getRuntime().getTrue() : getRuntime().getFalse(); + } + + @JRubyMethod + public IRubyObject key(IRubyObject value) { + final IRubyObject key = map.findKey(value); + return key == null ? getRuntime().getNil() : key; + } + + @JRubyMethod + public IRubyObject replace_if_exists(IRubyObject key, IRubyObject value) { + IRubyObject result = map.replace(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject get_and_set(IRubyObject key, IRubyObject value) { + IRubyObject result = map.put(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject delete(IRubyObject key) { + IRubyObject result = map.remove(key); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public RubyBoolean delete_pair(IRubyObject key, IRubyObject value) { + return getRuntime().newBoolean(map.remove(key, value)); + } + + @JRubyMethod + public IRubyObject clear() { + map.clear(); + return this; + } + + @JRubyMethod + public IRubyObject each_pair(ThreadContext context, Block block) { + for (Map.Entry entry : map.entrySet()) { + block.yieldSpecific(context, entry.getKey(), entry.getValue()); + } + return this; + } + + @JRubyMethod + public RubyFixnum size(ThreadContext context) { + return context.getRuntime().newFixnum(map.size()); + } + + @JRubyMethod + public IRubyObject get_or_default(IRubyObject key, IRubyObject defaultValue) { + return map.getValueOrDefault(key, defaultValue); + } + + @JRubyMethod(visibility = PRIVATE) + public JRubyMapBackend initialize_copy(ThreadContext context, IRubyObject other) { + map = newCHM(); + return this; + } + } +} diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java new file mode 100644 index 0000000000..b56607626c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java @@ -0,0 +1,93 @@ +package com.concurrent_ruby.ext; + +import org.jruby.Ruby; +import org.jruby.RubyBoolean; +import org.jruby.RubyClass; +import org.jruby.RubyModule; +import org.jruby.RubyNil; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +public class JavaAtomicBooleanLibrary implements Library { + + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicBoolean", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); + atomicCls.defineAnnotatedMethods(JavaAtomicBoolean.class); + } + + private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JavaAtomicBoolean(runtime, klazz); + } + }; + + @JRubyClass(name = "JavaAtomicBoolean", parent = "Object") + public static class JavaAtomicBoolean extends RubyObject { + + private AtomicBoolean atomicBoolean; + + public JavaAtomicBoolean(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject value) { + atomicBoolean = new AtomicBoolean(convertRubyBooleanToJavaBoolean(value)); + return context.nil; + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context) { + atomicBoolean = new AtomicBoolean(); + return context.nil; + } + + @JRubyMethod(name = "value") + public IRubyObject value() { + return getRuntime().newBoolean(atomicBoolean.get()); + } + + @JRubyMethod(name = "true?") + public IRubyObject isAtomicTrue() { + return getRuntime().newBoolean(atomicBoolean.get()); + } + + @JRubyMethod(name = "false?") + public IRubyObject isAtomicFalse() { + return getRuntime().newBoolean((atomicBoolean.get() == false)); + } + + @JRubyMethod(name = "value=") + public IRubyObject setAtomic(ThreadContext context, IRubyObject newValue) { + atomicBoolean.set(convertRubyBooleanToJavaBoolean(newValue)); + return context.nil; + } + + @JRubyMethod(name = "make_true") + public IRubyObject makeTrue() { + return getRuntime().newBoolean(atomicBoolean.compareAndSet(false, true)); + } + + @JRubyMethod(name = "make_false") + public IRubyObject makeFalse() { + return getRuntime().newBoolean(atomicBoolean.compareAndSet(true, false)); + } + + private boolean convertRubyBooleanToJavaBoolean(IRubyObject newValue) { + if (newValue instanceof RubyBoolean.False || newValue instanceof RubyNil) { + return false; + } else { + return true; + } + } + } +} diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java new file mode 100644 index 0000000000..672bfc048b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java @@ -0,0 +1,113 @@ +package com.concurrent_ruby.ext; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyFixnum; +import org.jruby.RubyModule; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; +import org.jruby.runtime.Block; + +public class JavaAtomicFixnumLibrary implements Library { + + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicFixnum", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); + + atomicCls.defineAnnotatedMethods(JavaAtomicFixnum.class); + } + + private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JavaAtomicFixnum(runtime, klazz); + } + }; + + @JRubyClass(name = "JavaAtomicFixnum", parent = "Object") + public static class JavaAtomicFixnum extends RubyObject { + + private AtomicLong atomicLong; + + public JavaAtomicFixnum(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context) { + this.atomicLong = new AtomicLong(0); + return context.nil; + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject value) { + this.atomicLong = new AtomicLong(rubyFixnumToLong(value)); + return context.nil; + } + + @JRubyMethod(name = "value") + public IRubyObject getValue() { + return getRuntime().newFixnum(atomicLong.get()); + } + + @JRubyMethod(name = "value=") + public IRubyObject setValue(ThreadContext context, IRubyObject newValue) { + atomicLong.set(rubyFixnumToLong(newValue)); + return context.nil; + } + + @JRubyMethod(name = {"increment", "up"}) + public IRubyObject increment() { + return getRuntime().newFixnum(atomicLong.incrementAndGet()); + } + + @JRubyMethod(name = {"increment", "up"}) + public IRubyObject increment(IRubyObject value) { + long delta = rubyFixnumToLong(value); + return getRuntime().newFixnum(atomicLong.addAndGet(delta)); + } + + @JRubyMethod(name = {"decrement", "down"}) + public IRubyObject decrement() { + return getRuntime().newFixnum(atomicLong.decrementAndGet()); + } + + @JRubyMethod(name = {"decrement", "down"}) + public IRubyObject decrement(IRubyObject value) { + long delta = rubyFixnumToLong(value); + return getRuntime().newFixnum(atomicLong.addAndGet(-delta)); + } + + @JRubyMethod(name = "compare_and_set") + public IRubyObject compareAndSet(ThreadContext context, IRubyObject expect, IRubyObject update) { + return getRuntime().newBoolean(atomicLong.compareAndSet(rubyFixnumToLong(expect), rubyFixnumToLong(update))); + } + + @JRubyMethod + public IRubyObject update(ThreadContext context, Block block) { + for (;;) { + long _oldValue = atomicLong.get(); + IRubyObject oldValue = getRuntime().newFixnum(_oldValue); + IRubyObject newValue = block.yield(context, oldValue); + if (atomicLong.compareAndSet(_oldValue, rubyFixnumToLong(newValue))) { + return newValue; + } + } + } + + private long rubyFixnumToLong(IRubyObject value) { + if (value instanceof RubyFixnum) { + RubyFixnum fixNum = (RubyFixnum) value; + return fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError("value must be a Fixnum"); + } + } + } +} diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java new file mode 100644 index 0000000000..d887f25047 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java @@ -0,0 +1,189 @@ +package com.concurrent_ruby.ext; + +import java.io.IOException; +import java.util.concurrent.Semaphore; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyFixnum; +import org.jruby.RubyModule; +import org.jruby.RubyNumeric; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.Block; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +public class JavaSemaphoreLibrary { + + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyClass atomicCls = concurrentMod.defineClassUnder("JavaSemaphore", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); + + atomicCls.defineAnnotatedMethods(JavaSemaphore.class); + } + + private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JavaSemaphore(runtime, klazz); + } + }; + + @JRubyClass(name = "JavaSemaphore", parent = "Object") + public static class JavaSemaphore extends RubyObject { + + private JRubySemaphore semaphore; + + public JavaSemaphore(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject value) { + this.semaphore = new JRubySemaphore(rubyFixnumInt(value, "count")); + return context.nil; + } + + @JRubyMethod + public IRubyObject acquire(ThreadContext context, final Block block) throws InterruptedException { + return this.acquire(context, 1, block); + } + + @JRubyMethod + public IRubyObject acquire(ThreadContext context, IRubyObject permits, final Block block) throws InterruptedException { + return this.acquire(context, rubyFixnumToPositiveInt(permits, "permits"), block); + } + + @JRubyMethod(name = "available_permits") + public IRubyObject availablePermits(ThreadContext context) { + return getRuntime().newFixnum(this.semaphore.availablePermits()); + } + + @JRubyMethod(name = "drain_permits") + public IRubyObject drainPermits(ThreadContext context) { + return getRuntime().newFixnum(this.semaphore.drainPermits()); + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context, final Block block) throws InterruptedException { + int permitsInt = 1; + boolean acquired = semaphore.tryAcquire(permitsInt); + + return triedAcquire(context, permitsInt, acquired, block); + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits, final Block block) throws InterruptedException { + int permitsInt = rubyFixnumToPositiveInt(permits, "permits"); + boolean acquired = semaphore.tryAcquire(permitsInt); + + return triedAcquire(context, permitsInt, acquired, block); + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits, IRubyObject timeout, final Block block) throws InterruptedException { + int permitsInt = rubyFixnumToPositiveInt(permits, "permits"); + boolean acquired = semaphore.tryAcquire( + permitsInt, + rubyNumericToLong(timeout, "timeout"), + java.util.concurrent.TimeUnit.SECONDS + ); + + return triedAcquire(context, permitsInt, acquired, block); + } + + @JRubyMethod + public IRubyObject release(ThreadContext context) { + this.semaphore.release(1); + return getRuntime().newBoolean(true); + } + + @JRubyMethod + public IRubyObject release(ThreadContext context, IRubyObject permits) { + this.semaphore.release(rubyFixnumToPositiveInt(permits, "permits")); + return getRuntime().newBoolean(true); + } + + @JRubyMethod(name = "reduce_permits") + public IRubyObject reducePermits(ThreadContext context, IRubyObject reduction) throws InterruptedException { + this.semaphore.publicReducePermits(rubyFixnumToNonNegativeInt(reduction, "reduction")); + return context.nil; + } + + private IRubyObject acquire(ThreadContext context, int permits, final Block block) throws InterruptedException { + this.semaphore.acquire(permits); + + if (!block.isGiven()) return context.nil; + + try { + return block.yieldSpecific(context); + } finally { + this.semaphore.release(permits); + } + } + + private IRubyObject triedAcquire(ThreadContext context, int permits, boolean acquired, final Block block) { + if (!block.isGiven()) return getRuntime().newBoolean(acquired); + if (!acquired) return context.nil; + + try { + return block.yieldSpecific(context); + } finally { + this.semaphore.release(permits); + } + } + + private int rubyFixnumInt(IRubyObject value, String paramName) { + if (value instanceof RubyFixnum) { + RubyFixnum fixNum = (RubyFixnum) value; + return (int) fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError(paramName + " must be integer"); + } + } + + private int rubyFixnumToNonNegativeInt(IRubyObject value, String paramName) { + if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() >= 0) { + RubyFixnum fixNum = (RubyFixnum) value; + return (int) fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError(paramName + " must be a non-negative integer"); + } + } + + private int rubyFixnumToPositiveInt(IRubyObject value, String paramName) { + if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() > 0) { + RubyFixnum fixNum = (RubyFixnum) value; + return (int) fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError(paramName + " must be an integer greater than zero"); + } + } + + private long rubyNumericToLong(IRubyObject value, String paramName) { + if (value instanceof RubyNumeric && ((RubyNumeric) value).getDoubleValue() > 0) { + RubyNumeric fixNum = (RubyNumeric) value; + return fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError(paramName + " must be a float greater than zero"); + } + } + + class JRubySemaphore extends Semaphore { + + public JRubySemaphore(int permits) { + super(permits); + } + + public JRubySemaphore(int permits, boolean value) { + super(permits, value); + } + + public void publicReducePermits(int i) { + reducePermits(i); + } + + } + } +} diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java new file mode 100644 index 0000000000..f0c75ee407 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java @@ -0,0 +1,292 @@ +package com.concurrent_ruby.ext; + +import org.jruby.Ruby; +import org.jruby.RubyBasicObject; +import org.jruby.RubyClass; +import org.jruby.RubyModule; +import org.jruby.RubyObject; +import org.jruby.RubyThread; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.Block; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.Visibility; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; +import sun.misc.Unsafe; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class SynchronizationLibrary implements Library { + + private static final Unsafe UNSAFE = loadUnsafe(); + private static final boolean FULL_FENCE = supportsFences(); + + private static Unsafe loadUnsafe() { + try { + Class ncdfe = Class.forName("sun.misc.Unsafe"); + Field f = ncdfe.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get((java.lang.Object) null); + } catch (Exception var2) { + return null; + } catch (NoClassDefFoundError var3) { + return null; + } + } + + private static boolean supportsFences() { + if (UNSAFE == null) { + return false; + } else { + try { + Method m = UNSAFE.getClass().getDeclaredMethod("fullFence", new Class[0]); + if (m != null) { + return true; + } + } catch (Exception var1) { + // nothing + } + + return false; + } + } + + private static final ObjectAllocator OBJECT_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new Object(runtime, klazz); + } + }; + + private static final ObjectAllocator ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new AbstractLockableObject(runtime, klazz); + } + }; + + private static final ObjectAllocator JRUBY_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRubyLockableObject(runtime, klazz); + } + }; + + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule synchronizationModule = runtime. + defineModule("Concurrent"). + defineModuleUnder("Synchronization"); + + RubyModule jrubyAttrVolatileModule = synchronizationModule.defineModuleUnder("JRubyAttrVolatile"); + jrubyAttrVolatileModule.defineAnnotatedMethods(JRubyAttrVolatile.class); + + defineClass(runtime, synchronizationModule, "AbstractObject", "Object", + Object.class, OBJECT_ALLOCATOR); + + defineClass(runtime, synchronizationModule, "Object", "AbstractLockableObject", + AbstractLockableObject.class, ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR); + + defineClass(runtime, synchronizationModule, "AbstractLockableObject", "JRubyLockableObject", + JRubyLockableObject.class, JRUBY_LOCKABLE_OBJECT_ALLOCATOR); + + defineClass(runtime, synchronizationModule, "Object", "JRuby", + JRuby.class, new ObjectAllocator() { + @Override + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRuby(runtime, klazz); + } + }); + } + + private RubyClass defineClass( + Ruby runtime, + RubyModule namespace, + String parentName, + String name, + Class javaImplementation, + ObjectAllocator allocator) { + final RubyClass parentClass = namespace.getClass(parentName); + + if (parentClass == null) { + System.out.println("not found " + parentName); + throw runtime.newRuntimeError(namespace.toString() + "::" + parentName + " is missing"); + } + + final RubyClass newClass = namespace.defineClassUnder(name, parentClass, allocator); + newClass.defineAnnotatedMethods(javaImplementation); + return newClass; + } + + // Facts: + // - all ivar reads are without any synchronisation of fences see + // https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java#L110-110 + // - writes depend on UnsafeHolder.U, null -> SynchronizedVariableAccessor, !null -> StampedVariableAccessor + // SynchronizedVariableAccessor wraps with synchronized block, StampedVariableAccessor uses fullFence or + // volatilePut + // TODO (pitr 16-Sep-2015): what do we do in Java 9 ? + + // module JRubyAttrVolatile + public static class JRubyAttrVolatile { + + // volatile threadContext is used as a memory barrier per the JVM memory model happens-before semantic + // on volatile fields. any volatile field could have been used but using the thread context is an + // attempt to avoid code elimination. + private static volatile int volatileField; + + @JRubyMethod(name = "full_memory_barrier", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject module) { + // Prevent reordering of ivar writes with publication of this instance + if (!FULL_FENCE) { + // Assuming that following volatile read and write is not eliminated it simulates fullFence. + // If it's eliminated it'll cause problems only on non-x86 platforms. + // http://shipilev.net/blog/2014/jmm-pragmatics/#_happens_before_test_your_understanding + final int volatileRead = volatileField; + volatileField = context.getLine(); + } else { + UNSAFE.fullFence(); + } + return context.nil; + } + + @JRubyMethod(name = "instance_variable_get_volatile", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject instanceVariableGetVolatile( + ThreadContext context, + IRubyObject module, + IRubyObject self, + IRubyObject name) { + // Ensure we ses latest value with loadFence + if (!FULL_FENCE) { + // piggybacking on volatile read, simulating loadFence + final int volatileRead = volatileField; + return ((RubyBasicObject) self).instance_variable_get(context, name); + } else { + UNSAFE.loadFence(); + return ((RubyBasicObject) self).instance_variable_get(context, name); + } + } + + @JRubyMethod(name = "instance_variable_set_volatile", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject InstanceVariableSetVolatile( + ThreadContext context, + IRubyObject module, + IRubyObject self, + IRubyObject name, + IRubyObject value) { + // Ensure we make last update visible + if (!FULL_FENCE) { + // piggybacking on volatile write, simulating storeFence + final IRubyObject result = ((RubyBasicObject) self).instance_variable_set(name, value); + volatileField = context.getLine(); + return result; + } else { + // JRuby uses StampedVariableAccessor which calls fullFence + // so no additional steps needed. + // See https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java#L151-L159 + return ((RubyBasicObject) self).instance_variable_set(name, value); + } + } + } + + @JRubyClass(name = "Object", parent = "AbstractObject") + public static class Object extends RubyObject { + + public Object(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + } + + @JRubyClass(name = "AbstractLockableObject", parent = "Object") + public static class AbstractLockableObject extends Object { + + public AbstractLockableObject(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + } + + @JRubyClass(name = "JRubyLockableObject", parent = "AbstractLockableObject") + public static class JRubyLockableObject extends AbstractLockableObject { + + public JRubyLockableObject(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod(name = "synchronize", visibility = Visibility.PROTECTED) + public IRubyObject rubySynchronize(ThreadContext context, Block block) { + synchronized (this) { + return block.yield(context, null); + } + } + + @JRubyMethod(name = "ns_wait", optional = 1, visibility = Visibility.PROTECTED) + public IRubyObject nsWait(ThreadContext context, IRubyObject[] args) { + Ruby runtime = context.runtime; + if (args.length > 1) { + throw runtime.newArgumentError(args.length, 1); + } + Double timeout = null; + if (args.length > 0 && !args[0].isNil()) { + timeout = args[0].convertToFloat().getDoubleValue(); + if (timeout < 0) { + throw runtime.newArgumentError("time interval must be positive"); + } + } + if (Thread.interrupted()) { + throw runtime.newConcurrencyError("thread interrupted"); + } + boolean success = false; + try { + success = context.getThread().wait_timeout(this, timeout); + } catch (InterruptedException ie) { + throw runtime.newConcurrencyError(ie.getLocalizedMessage()); + } finally { + // An interrupt or timeout may have caused us to miss + // a notify that we consumed, so do another notify in + // case someone else is available to pick it up. + if (!success) { + this.notify(); + } + } + return this; + } + + @JRubyMethod(name = "ns_signal", visibility = Visibility.PROTECTED) + public IRubyObject nsSignal(ThreadContext context) { + notify(); + return this; + } + + @JRubyMethod(name = "ns_broadcast", visibility = Visibility.PROTECTED) + public IRubyObject nsBroadcast(ThreadContext context) { + notifyAll(); + return this; + } + } + + @JRubyClass(name = "JRuby") + public static class JRuby extends RubyObject { + public JRuby(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod(name = "sleep_interruptibly", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject sleepInterruptibly(final ThreadContext context, IRubyObject receiver, final Block block) { + try { + context.getThread().executeBlockingTask(new RubyThread.BlockingTask() { + @Override + public void run() throws InterruptedException { + block.call(context); + } + + @Override + public void wakeup() { + context.getThread().getNativeThread().interrupt(); + } + }); + } catch (InterruptedException e) { + throw context.runtime.newThreadError("interrupted in Concurrent::Synchronization::JRuby.sleep_interruptibly"); + } + return context.nil; + } + } +} diff --git a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMap.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java similarity index 96% rename from app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMap.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java index 90ae1a2497..e11e15aa4e 100644 --- a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMap.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java @@ -1,4 +1,4 @@ -package org.jruby.ext.thread_safe.jsr166e; +package com.concurrent_ruby.ext.jsr166e; import java.util.Map; import java.util.Set; diff --git a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java similarity index 99% rename from app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java index c85b989297..86aa4eb062 100644 --- a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java @@ -6,13 +6,13 @@ // This is based on the 1.79 version. -package org.jruby.ext.thread_safe.jsr166e; +package com.concurrent_ruby.ext.jsr166e; import org.jruby.RubyClass; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.exceptions.RaiseException; -import org.jruby.ext.thread_safe.jsr166y.ThreadLocalRandom; +import com.concurrent_ruby.ext.jsr166y.ThreadLocalRandom; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; diff --git a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java similarity index 99% rename from app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java index 22d0cbc956..47a923c8d6 100644 --- a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java @@ -6,7 +6,7 @@ // This is based on 1.9 version. -package org.jruby.ext.thread_safe.jsr166e; +package com.concurrent_ruby.ext.jsr166e; import java.util.concurrent.atomic.AtomicLong; import java.io.IOException; import java.io.Serializable; diff --git a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java similarity index 99% rename from app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java index 8651cdcb55..93a277fb35 100644 --- a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java @@ -6,7 +6,7 @@ // This is based on 1.5 version. -package org.jruby.ext.thread_safe.jsr166e; +package com.concurrent_ruby.ext.jsr166e; import java.util.Random; /** diff --git a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/ConcurrentHashMapV8.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java similarity index 99% rename from app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/ConcurrentHashMapV8.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java index 4e1e33a697..b7fc5a9375 100644 --- a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/ConcurrentHashMapV8.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java @@ -6,14 +6,14 @@ // This is based on the 1.79 version. -package org.jruby.ext.thread_safe.jsr166e.nounsafe; +package com.concurrent_ruby.ext.jsr166e.nounsafe; import org.jruby.RubyClass; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.exceptions.RaiseException; -import org.jruby.ext.thread_safe.jsr166e.ConcurrentHashMap; -import org.jruby.ext.thread_safe.jsr166y.ThreadLocalRandom; +import com.concurrent_ruby.ext.jsr166e.ConcurrentHashMap; +import com.concurrent_ruby.ext.jsr166y.ThreadLocalRandom; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -3797,4 +3797,4 @@ public boolean equals(Object o) { (containsAll(c) && c.containsAll(this)))); } } -} \ No newline at end of file +} diff --git a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/LongAdder.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java similarity index 99% rename from app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/LongAdder.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java index e7a1c5f8f3..ecf552a23c 100644 --- a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/LongAdder.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java @@ -6,7 +6,7 @@ // This is based on 1.9 version. -package org.jruby.ext.thread_safe.jsr166e.nounsafe; +package com.concurrent_ruby.ext.jsr166e.nounsafe; import java.util.concurrent.atomic.AtomicLong; import java.io.IOException; diff --git a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/Striped64.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java similarity index 99% rename from app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/Striped64.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java index ee69567e29..f52164242a 100644 --- a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/Striped64.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java @@ -6,7 +6,7 @@ // This is based on 1.5 version. -package org.jruby.ext.thread_safe.jsr166e.nounsafe; +package com.concurrent_ruby.ext.jsr166e.nounsafe; import java.util.Random; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; @@ -288,4 +288,4 @@ final void internalReset(long initialValue) { } } } -} \ No newline at end of file +} diff --git a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java similarity index 99% rename from app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java rename to app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java index 81ba441b4f..3ea409ffc4 100644 --- a/app/server/ruby/vendor/thread_safe/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java @@ -6,7 +6,7 @@ // This is based on 1.16 version -package org.jruby.ext.thread_safe.jsr166y; +package com.concurrent_ruby.ext.jsr166y; import java.util.Random; diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent-ruby.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent-ruby.rb new file mode 100644 index 0000000000..e9a3dea4ab --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent-ruby.rb @@ -0,0 +1,5 @@ +# This file is here so that there is a file with the same name as the gem that +# can be required by Bundler.require. Applications should normally +# require 'concurrent'. + +require_relative "concurrent" diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent.rb new file mode 100644 index 0000000000..87de46f1b8 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent.rb @@ -0,0 +1,134 @@ +require 'concurrent/version' +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/configuration' + +require 'concurrent/atomics' +require 'concurrent/executors' +require 'concurrent/synchronization' + +require 'concurrent/atomic/atomic_markable_reference' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/agent' +require 'concurrent/atom' +require 'concurrent/array' +require 'concurrent/hash' +require 'concurrent/set' +require 'concurrent/map' +require 'concurrent/tuple' +require 'concurrent/async' +require 'concurrent/dataflow' +require 'concurrent/delay' +require 'concurrent/exchanger' +require 'concurrent/future' +require 'concurrent/immutable_struct' +require 'concurrent/ivar' +require 'concurrent/maybe' +require 'concurrent/mutable_struct' +require 'concurrent/mvar' +require 'concurrent/promise' +require 'concurrent/scheduled_task' +require 'concurrent/settable_struct' +require 'concurrent/timer_task' +require 'concurrent/tvar' +require 'concurrent/promises' + +require 'concurrent/thread_safe/synchronized_delegator' +require 'concurrent/thread_safe/util' + +require 'concurrent/options' + +# @!macro internal_implementation_note +# +# @note **Private Implementation:** This abstraction is a private, internal +# implementation detail. It should never be used directly. + +# @!macro monotonic_clock_warning +# +# @note Time calculations on all platforms and languages are sensitive to +# changes to the system clock. To alleviate the potential problems +# associated with changing the system clock while an application is running, +# most modern operating systems provide a monotonic clock that operates +# independently of the system clock. A monotonic clock cannot be used to +# determine human-friendly clock times. A monotonic clock is used exclusively +# for calculating time intervals. Not all Ruby platforms provide access to an +# operating system monotonic clock. On these platforms a pure-Ruby monotonic +# clock will be used as a fallback. An operating system monotonic clock is both +# faster and more reliable than the pure-Ruby implementation. The pure-Ruby +# implementation should be fast and reliable enough for most non-realtime +# operations. At this time the common Ruby platforms that provide access to an +# operating system monotonic clock are MRI 2.1 and above and JRuby (all versions). +# +# @see http://linux.die.net/man/3/clock_gettime Linux clock_gettime(3) + +# @!macro copy_options +# +# ## Copy Options +# +# Object references in Ruby are mutable. This can lead to serious +# problems when the {#value} of an object is a mutable reference. Which +# is always the case unless the value is a `Fixnum`, `Symbol`, or similar +# "primitive" data type. Each instance can be configured with a few +# options that can help protect the program from potentially dangerous +# operations. Each of these options can be optionally set when the object +# instance is created: +# +# * `:dup_on_deref` When true the object will call the `#dup` method on +# the `value` object every time the `#value` method is called +# (default: false) +# * `:freeze_on_deref` When true the object will call the `#freeze` +# method on the `value` object every time the `#value` method is called +# (default: false) +# * `:copy_on_deref` When given a `Proc` object the `Proc` will be run +# every time the `#value` method is called. The `Proc` will be given +# the current `value` as its only argument and the result returned by +# the block will be the return value of the `#value` call. When `nil` +# this option will be ignored (default: nil) +# +# When multiple deref options are set the order of operations is strictly defined. +# The order of deref operations is: +# * `:copy_on_deref` +# * `:dup_on_deref` +# * `:freeze_on_deref` +# +# Because of this ordering there is no need to `#freeze` an object created by a +# provided `:copy_on_deref` block. Simply set `:freeze_on_deref` to `true`. +# Setting both `:dup_on_deref` to `true` and `:freeze_on_deref` to `true` is +# as close to the behavior of a "pure" functional language (like Erlang, Clojure, +# or Haskell) as we are likely to get in Ruby. + +# @!macro deref_options +# +# @option opts [Boolean] :dup_on_deref (false) Call `#dup` before +# returning the data from {#value} +# @option opts [Boolean] :freeze_on_deref (false) Call `#freeze` before +# returning the data from {#value} +# @option opts [Proc] :copy_on_deref (nil) When calling the {#value} +# method, call the given proc passing the internal value as the sole +# argument then return the new value returned from the proc. + +# @!macro executor_and_deref_options +# +# @param [Hash] opts the options used to define the behavior at update and deref +# and to specify the executor on which to perform actions +# @option opts [Executor] :executor when set use the given `Executor` instance. +# Three special values are also supported: `:io` returns the global pool for +# long, blocking (IO) tasks, `:fast` returns the global pool for short, fast +# operations, and `:immediate` returns the global `ImmediateExecutor` object. +# @!macro deref_options + +# @!macro warn.edge +# @api Edge +# @note **Edge Features** are under active development and may change frequently. +# +# - Deprecations are not added before incompatible changes. +# - Edge version: _major_ is always 0, _minor_ bump means incompatible change, +# _patch_ bump means compatible change. +# - Edge features may also lack tests and documentation. +# - Features developed in `concurrent-ruby-edge` are expected to move +# to `concurrent-ruby` when finalised. + + +# {include:file:README.md} +module Concurrent +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/.gitignore b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/agent.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/agent.rb new file mode 100644 index 0000000000..2d32926ba1 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/agent.rb @@ -0,0 +1,588 @@ +require 'concurrent/configuration' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/thread_local_var' +require 'concurrent/collection/copy_on_write_observer_set' +require 'concurrent/concern/observable' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # `Agent` is inspired by Clojure's [agent](http://clojure.org/agents) + # function. An agent is a shared, mutable variable providing independent, + # uncoordinated, *asynchronous* change of individual values. Best used when + # the value will undergo frequent, complex updates. Suitable when the result + # of an update does not need to be known immediately. `Agent` is (mostly) + # functionally equivalent to Clojure's agent, except where the runtime + # prevents parity. + # + # Agents are reactive, not autonomous - there is no imperative message loop + # and no blocking receive. The state of an Agent should be itself immutable + # and the `#value` of an Agent is always immediately available for reading by + # any thread without any messages, i.e. observation does not require + # cooperation or coordination. + # + # Agent action dispatches are made using the various `#send` methods. These + # methods always return immediately. At some point later, in another thread, + # the following will happen: + # + # 1. The given `action` will be applied to the state of the Agent and the + # `args`, if any were supplied. + # 2. The return value of `action` will be passed to the validator lambda, + # if one has been set on the Agent. + # 3. If the validator succeeds or if no validator was given, the return value + # of the given `action` will become the new `#value` of the Agent. See + # `#initialize` for details. + # 4. If any observers were added to the Agent, they will be notified. See + # `#add_observer` for details. + # 5. If during the `action` execution any other dispatches are made (directly + # or indirectly), they will be held until after the `#value` of the Agent + # has been changed. + # + # If any exceptions are thrown by an action function, no nested dispatches + # will occur, and the exception will be cached in the Agent itself. When an + # Agent has errors cached, any subsequent interactions will immediately throw + # an exception, until the agent's errors are cleared. Agent errors can be + # examined with `#error` and the agent restarted with `#restart`. + # + # The actions of all Agents get interleaved amongst threads in a thread pool. + # At any point in time, at most one action for each Agent is being executed. + # Actions dispatched to an agent from another single agent or thread will + # occur in the order they were sent, potentially interleaved with actions + # dispatched to the same agent from other sources. The `#send` method should + # be used for actions that are CPU limited, while the `#send_off` method is + # appropriate for actions that may block on IO. + # + # Unlike in Clojure, `Agent` cannot participate in `Concurrent::TVar` transactions. + # + # ## Example + # + # ``` + # def next_fibonacci(set = nil) + # return [0, 1] if set.nil? + # set + [set[-2..-1].reduce{|sum,x| sum + x }] + # end + # + # # create an agent with an initial value + # agent = Concurrent::Agent.new(next_fibonacci) + # + # # send a few update requests + # 5.times do + # agent.send{|set| next_fibonacci(set) } + # end + # + # # wait for them to complete + # agent.await + # + # # get the current value + # agent.value #=> [0, 1, 1, 2, 3, 5, 8] + # ``` + # + # ## Observation + # + # Agents support observers through the {Concurrent::Observable} mixin module. + # Notification of observers occurs every time an action dispatch returns and + # the new value is successfully validated. Observation will *not* occur if the + # action raises an exception, if validation fails, or when a {#restart} occurs. + # + # When notified the observer will receive three arguments: `time`, `old_value`, + # and `new_value`. The `time` argument is the time at which the value change + # occurred. The `old_value` is the value of the Agent when the action began + # processing. The `new_value` is the value to which the Agent was set when the + # action completed. Note that `old_value` and `new_value` may be the same. + # This is not an error. It simply means that the action returned the same + # value. + # + # ## Nested Actions + # + # It is possible for an Agent action to post further actions back to itself. + # The nested actions will be enqueued normally then processed *after* the + # outer action completes, in the order they were sent, possibly interleaved + # with action dispatches from other threads. Nested actions never deadlock + # with one another and a failure in a nested action will never affect the + # outer action. + # + # Nested actions can be called using the Agent reference from the enclosing + # scope or by passing the reference in as a "send" argument. Nested actions + # cannot be post using `self` from within the action block/proc/lambda; `self` + # in this context will not reference the Agent. The preferred method for + # dispatching nested actions is to pass the Agent as an argument. This allows + # Ruby to more effectively manage the closing scope. + # + # Prefer this: + # + # ``` + # agent = Concurrent::Agent.new(0) + # agent.send(agent) do |value, this| + # this.send {|v| v + 42 } + # 3.14 + # end + # agent.value #=> 45.14 + # ``` + # + # Over this: + # + # ``` + # agent = Concurrent::Agent.new(0) + # agent.send do |value| + # agent.send {|v| v + 42 } + # 3.14 + # end + # ``` + # + # @!macro agent_await_warning + # + # **NOTE** Never, *under any circumstances*, call any of the "await" methods + # ({#await}, {#await_for}, {#await_for!}, and {#wait}) from within an action + # block/proc/lambda. The call will block the Agent and will always fail. + # Calling either {#await} or {#wait} (with a timeout of `nil`) will + # hopelessly deadlock the Agent with no possibility of recovery. + # + # @!macro thread_safe_variable_comparison + # + # @see http://clojure.org/Agents Clojure Agents + # @see http://clojure.org/state Values and Change - Clojure's approach to Identity and State + class Agent < Synchronization::LockableObject + include Concern::Observable + + ERROR_MODES = [:continue, :fail].freeze + private_constant :ERROR_MODES + + AWAIT_FLAG = ::Object.new + private_constant :AWAIT_FLAG + + AWAIT_ACTION = ->(value, latch) { latch.count_down; AWAIT_FLAG } + private_constant :AWAIT_ACTION + + DEFAULT_ERROR_HANDLER = ->(agent, error) { nil } + private_constant :DEFAULT_ERROR_HANDLER + + DEFAULT_VALIDATOR = ->(value) { true } + private_constant :DEFAULT_VALIDATOR + + Job = Struct.new(:action, :args, :executor, :caller) + private_constant :Job + + # Raised during action processing or any other time in an Agent's lifecycle. + class Error < StandardError + def initialize(message = nil) + message ||= 'agent must be restarted before jobs can post' + super(message) + end + end + + # Raised when a new value obtained during action processing or at `#restart` + # fails validation. + class ValidationError < Error + def initialize(message = nil) + message ||= 'invalid value' + super(message) + end + end + + # The error mode this Agent is operating in. See {#initialize} for details. + attr_reader :error_mode + + # Create a new `Agent` with the given initial value and options. + # + # The `:validator` option must be `nil` or a side-effect free proc/lambda + # which takes one argument. On any intended value change the validator, if + # provided, will be called. If the new value is invalid the validator should + # return `false` or raise an error. + # + # The `:error_handler` option must be `nil` or a proc/lambda which takes two + # arguments. When an action raises an error or validation fails, either by + # returning false or raising an error, the error handler will be called. The + # arguments to the error handler will be a reference to the agent itself and + # the error object which was raised. + # + # The `:error_mode` may be either `:continue` (the default if an error + # handler is given) or `:fail` (the default if error handler nil or not + # given). + # + # If an action being run by the agent throws an error or doesn't pass + # validation the error handler, if present, will be called. After the + # handler executes if the error mode is `:continue` the Agent will continue + # as if neither the action that caused the error nor the error itself ever + # happened. + # + # If the mode is `:fail` the Agent will become {#failed?} and will stop + # accepting new action dispatches. Any previously queued actions will be + # held until {#restart} is called. The {#value} method will still work, + # returning the value of the Agent before the error. + # + # @param [Object] initial the initial value + # @param [Hash] opts the configuration options + # + # @option opts [Symbol] :error_mode either `:continue` or `:fail` + # @option opts [nil, Proc] :error_handler the (optional) error handler + # @option opts [nil, Proc] :validator the (optional) validation procedure + def initialize(initial, opts = {}) + super() + synchronize { ns_initialize(initial, opts) } + end + + # The current value (state) of the Agent, irrespective of any pending or + # in-progress actions. The value is always available and is non-blocking. + # + # @return [Object] the current value + def value + @current.value # TODO (pitr 12-Sep-2015): broken unsafe read? + end + + alias_method :deref, :value + + # When {#failed?} and {#error_mode} is `:fail`, returns the error object + # which caused the failure, else `nil`. When {#error_mode} is `:continue` + # will *always* return `nil`. + # + # @return [nil, Error] the error which caused the failure when {#failed?} + def error + @error.value + end + + alias_method :reason, :error + + # @!macro agent_send + # + # Dispatches an action to the Agent and returns immediately. Subsequently, + # in a thread from a thread pool, the {#value} will be set to the return + # value of the action. Action dispatches are only allowed when the Agent + # is not {#failed?}. + # + # The action must be a block/proc/lambda which takes 1 or more arguments. + # The first argument is the current {#value} of the Agent. Any arguments + # passed to the send method via the `args` parameter will be passed to the + # action as the remaining arguments. The action must return the new value + # of the Agent. + # + # * {#send} and {#send!} should be used for actions that are CPU limited + # * {#send_off}, {#send_off!}, and {#<<} are appropriate for actions that + # may block on IO + # * {#send_via} and {#send_via!} are used when a specific executor is to + # be used for the action + # + # @param [Array] args zero or more arguments to be passed to + # the action + # @param [Proc] action the action dispatch to be enqueued + # + # @yield [agent, value, *args] process the old value and return the new + # @yieldparam [Object] value the current {#value} of the Agent + # @yieldparam [Array] args zero or more arguments to pass to the + # action + # @yieldreturn [Object] the new value of the Agent + # + # @!macro send_return + # @return [Boolean] true if the action is successfully enqueued, false if + # the Agent is {#failed?} + def send(*args, &action) + enqueue_action_job(action, args, Concurrent.global_fast_executor) + end + + # @!macro agent_send + # + # @!macro send_bang_return_and_raise + # @return [Boolean] true if the action is successfully enqueued + # @raise [Concurrent::Agent::Error] if the Agent is {#failed?} + def send!(*args, &action) + raise Error.new unless send(*args, &action) + true + end + + # @!macro agent_send + # @!macro send_return + def send_off(*args, &action) + enqueue_action_job(action, args, Concurrent.global_io_executor) + end + + alias_method :post, :send_off + + # @!macro agent_send + # @!macro send_bang_return_and_raise + def send_off!(*args, &action) + raise Error.new unless send_off(*args, &action) + true + end + + # @!macro agent_send + # @!macro send_return + # @param [Concurrent::ExecutorService] executor the executor on which the + # action is to be dispatched + def send_via(executor, *args, &action) + enqueue_action_job(action, args, executor) + end + + # @!macro agent_send + # @!macro send_bang_return_and_raise + # @param [Concurrent::ExecutorService] executor the executor on which the + # action is to be dispatched + def send_via!(executor, *args, &action) + raise Error.new unless send_via(executor, *args, &action) + true + end + + # Dispatches an action to the Agent and returns immediately. Subsequently, + # in a thread from a thread pool, the {#value} will be set to the return + # value of the action. Appropriate for actions that may block on IO. + # + # @param [Proc] action the action dispatch to be enqueued + # @return [Concurrent::Agent] self + # @see #send_off + def <<(action) + send_off(&action) + self + end + + # Blocks the current thread (indefinitely!) until all actions dispatched + # thus far, from this thread or nested by the Agent, have occurred. Will + # block when {#failed?}. Will never return if a failed Agent is {#restart} + # with `:clear_actions` true. + # + # Returns a reference to `self` to support method chaining: + # + # ``` + # current_value = agent.await.value + # ``` + # + # @return [Boolean] self + # + # @!macro agent_await_warning + def await + wait(nil) + self + end + + # Blocks the current thread until all actions dispatched thus far, from this + # thread or nested by the Agent, have occurred, or the timeout (in seconds) + # has elapsed. + # + # @param [Float] timeout the maximum number of seconds to wait + # @return [Boolean] true if all actions complete before timeout else false + # + # @!macro agent_await_warning + def await_for(timeout) + wait(timeout.to_f) + end + + # Blocks the current thread until all actions dispatched thus far, from this + # thread or nested by the Agent, have occurred, or the timeout (in seconds) + # has elapsed. + # + # @param [Float] timeout the maximum number of seconds to wait + # @return [Boolean] true if all actions complete before timeout + # + # @raise [Concurrent::TimeoutError] when timout is reached + # + # @!macro agent_await_warning + def await_for!(timeout) + raise Concurrent::TimeoutError unless wait(timeout.to_f) + true + end + + # Blocks the current thread until all actions dispatched thus far, from this + # thread or nested by the Agent, have occurred, or the timeout (in seconds) + # has elapsed. Will block indefinitely when timeout is nil or not given. + # + # Provided mainly for consistency with other classes in this library. Prefer + # the various `await` methods instead. + # + # @param [Float] timeout the maximum number of seconds to wait + # @return [Boolean] true if all actions complete before timeout else false + # + # @!macro agent_await_warning + def wait(timeout = nil) + latch = Concurrent::CountDownLatch.new(1) + enqueue_await_job(latch) + latch.wait(timeout) + end + + # Is the Agent in a failed state? + # + # @see #restart + def failed? + !@error.value.nil? + end + + alias_method :stopped?, :failed? + + # When an Agent is {#failed?}, changes the Agent {#value} to `new_value` + # then un-fails the Agent so that action dispatches are allowed again. If + # the `:clear_actions` option is give and true, any actions queued on the + # Agent that were being held while it was failed will be discarded, + # otherwise those held actions will proceed. The `new_value` must pass the + # validator if any, or `restart` will raise an exception and the Agent will + # remain failed with its old {#value} and {#error}. Observers, if any, will + # not be notified of the new state. + # + # @param [Object] new_value the new value for the Agent once restarted + # @param [Hash] opts the configuration options + # @option opts [Symbol] :clear_actions true if all enqueued but unprocessed + # actions should be discarded on restart, else false (default: false) + # @return [Boolean] true + # + # @raise [Concurrent:AgentError] when not failed + def restart(new_value, opts = {}) + clear_actions = opts.fetch(:clear_actions, false) + synchronize do + raise Error.new('agent is not failed') unless failed? + raise ValidationError unless ns_validate(new_value) + @current.value = new_value + @error.value = nil + @queue.clear if clear_actions + ns_post_next_job unless @queue.empty? + end + true + end + + class << self + + # Blocks the current thread (indefinitely!) until all actions dispatched + # thus far to all the given Agents, from this thread or nested by the + # given Agents, have occurred. Will block when any of the agents are + # failed. Will never return if a failed Agent is restart with + # `:clear_actions` true. + # + # @param [Array] agents the Agents on which to wait + # @return [Boolean] true + # + # @!macro agent_await_warning + def await(*agents) + agents.each { |agent| agent.await } + true + end + + # Blocks the current thread until all actions dispatched thus far to all + # the given Agents, from this thread or nested by the given Agents, have + # occurred, or the timeout (in seconds) has elapsed. + # + # @param [Float] timeout the maximum number of seconds to wait + # @param [Array] agents the Agents on which to wait + # @return [Boolean] true if all actions complete before timeout else false + # + # @!macro agent_await_warning + def await_for(timeout, *agents) + end_at = Concurrent.monotonic_time + timeout.to_f + ok = agents.length.times do |i| + break false if (delay = end_at - Concurrent.monotonic_time) < 0 + break false unless agents[i].await_for(delay) + end + !!ok + end + + # Blocks the current thread until all actions dispatched thus far to all + # the given Agents, from this thread or nested by the given Agents, have + # occurred, or the timeout (in seconds) has elapsed. + # + # @param [Float] timeout the maximum number of seconds to wait + # @param [Array] agents the Agents on which to wait + # @return [Boolean] true if all actions complete before timeout + # + # @raise [Concurrent::TimeoutError] when timout is reached + # @!macro agent_await_warning + def await_for!(timeout, *agents) + raise Concurrent::TimeoutError unless await_for(timeout, *agents) + true + end + end + + private + + def ns_initialize(initial, opts) + @error_mode = opts[:error_mode] + @error_handler = opts[:error_handler] + + if @error_mode && !ERROR_MODES.include?(@error_mode) + raise ArgumentError.new('unrecognized error mode') + elsif @error_mode.nil? + @error_mode = @error_handler ? :continue : :fail + end + + @error_handler ||= DEFAULT_ERROR_HANDLER + @validator = opts.fetch(:validator, DEFAULT_VALIDATOR) + @current = Concurrent::AtomicReference.new(initial) + @error = Concurrent::AtomicReference.new(nil) + @caller = Concurrent::ThreadLocalVar.new(nil) + @queue = [] + + self.observers = Collection::CopyOnNotifyObserverSet.new + end + + def enqueue_action_job(action, args, executor) + raise ArgumentError.new('no action given') unless action + job = Job.new(action, args, executor, @caller.value || Thread.current.object_id) + synchronize { ns_enqueue_job(job) } + end + + def enqueue_await_job(latch) + synchronize do + if (index = ns_find_last_job_for_thread) + job = Job.new(AWAIT_ACTION, [latch], Concurrent.global_immediate_executor, + Thread.current.object_id) + ns_enqueue_job(job, index+1) + else + latch.count_down + true + end + end + end + + def ns_enqueue_job(job, index = nil) + # a non-nil index means this is an await job + return false if index.nil? && failed? + index ||= @queue.length + @queue.insert(index, job) + # if this is the only job, post to executor + ns_post_next_job if @queue.length == 1 + true + end + + def ns_post_next_job + @queue.first.executor.post { execute_next_job } + end + + def execute_next_job + job = synchronize { @queue.first } + old_value = @current.value + + @caller.value = job.caller # for nested actions + new_value = job.action.call(old_value, *job.args) + @caller.value = nil + + return if new_value == AWAIT_FLAG + + if ns_validate(new_value) + @current.value = new_value + observers.notify_observers(Time.now, old_value, new_value) + else + handle_error(ValidationError.new) + end + rescue => error + handle_error(error) + ensure + synchronize do + @queue.shift + unless failed? || @queue.empty? + ns_post_next_job + end + end + end + + def ns_validate(value) + @validator.call(value) + rescue + false + end + + def handle_error(error) + # stop new jobs from posting + @error.value = error if @error_mode == :fail + @error_handler.call(self, error) + rescue + # do nothing + end + + def ns_find_last_job_for_thread + @queue.rindex { |job| job.caller == Thread.current.object_id } + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/array.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/array.rb new file mode 100644 index 0000000000..96434a288d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/array.rb @@ -0,0 +1,56 @@ +require 'concurrent/utility/engine' +require 'concurrent/thread_safe/util' + +module Concurrent + + # @!macro concurrent_array + # + # A thread-safe subclass of Array. This version locks against the object + # itself for every method call, ensuring only one thread can be reading + # or writing at a time. This includes iteration methods like `#each`. + # + # @note `a += b` is **not** a **thread-safe** operation on + # `Concurrent::Array`. It reads array `a`, then it creates new `Concurrent::Array` + # which is concatenation of `a` and `b`, then it writes the concatenation to `a`. + # The read and write are independent operations they do not form a single atomic + # operation therefore when two `+=` operations are executed concurrently updates + # may be lost. Use `#concat` instead. + # + # @see http://ruby-doc.org/core/Array.html Ruby standard library `Array` + + # @!macro internal_implementation_note + ArrayImplementation = case + when Concurrent.on_cruby? + # Array is thread-safe in practice because CRuby runs + # threads one at a time and does not do context + # switching during the execution of C functions. + ::Array + + when Concurrent.on_jruby? + require 'jruby/synchronized' + + class JRubyArray < ::Array + include JRuby::Synchronized + end + JRubyArray + + when Concurrent.on_truffleruby? + require 'concurrent/thread_safe/util/data_structures' + + class TruffleRubyArray < ::Array + end + + ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubyArray + TruffleRubyArray + + else + warn 'Possibly unsupported Ruby implementation' + ::Array + end + private_constant :ArrayImplementation + + # @!macro concurrent_array + class Array < ArrayImplementation + end + +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/async.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/async.rb new file mode 100644 index 0000000000..f9f8adf00d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/async.rb @@ -0,0 +1,449 @@ +require 'concurrent/configuration' +require 'concurrent/ivar' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # A mixin module that provides simple asynchronous behavior to a class, + # turning it into a simple actor. Loosely based on Erlang's + # [gen_server](http://www.erlang.org/doc/man/gen_server.html), but without + # supervision or linking. + # + # A more feature-rich {Concurrent::Actor} is also available when the + # capabilities of `Async` are too limited. + # + # ```cucumber + # Feature: + # As a stateful, plain old Ruby class + # I want safe, asynchronous behavior + # So my long-running methods don't block the main thread + # ``` + # + # The `Async` module is a way to mix simple yet powerful asynchronous + # capabilities into any plain old Ruby object or class, turning each object + # into a simple Actor. Method calls are processed on a background thread. The + # caller is free to perform other actions while processing occurs in the + # background. + # + # Method calls to the asynchronous object are made via two proxy methods: + # `async` (alias `cast`) and `await` (alias `call`). These proxy methods post + # the method call to the object's background thread and return a "future" + # which will eventually contain the result of the method call. + # + # This behavior is loosely patterned after Erlang's `gen_server` behavior. + # When an Erlang module implements the `gen_server` behavior it becomes + # inherently asynchronous. The `start` or `start_link` function spawns a + # process (similar to a thread but much more lightweight and efficient) and + # returns the ID of the process. Using the process ID, other processes can + # send messages to the `gen_server` via the `cast` and `call` methods. Unlike + # Erlang's `gen_server`, however, `Async` classes do not support linking or + # supervision trees. + # + # ## Basic Usage + # + # When this module is mixed into a class, objects of the class become inherently + # asynchronous. Each object gets its own background thread on which to post + # asynchronous method calls. Asynchronous method calls are executed in the + # background one at a time in the order they are received. + # + # To create an asynchronous class, simply mix in the `Concurrent::Async` module: + # + # ``` + # class Hello + # include Concurrent::Async + # + # def hello(name) + # "Hello, #{name}!" + # end + # end + # ``` + # + # Mixing this module into a class provides each object two proxy methods: + # `async` and `await`. These methods are thread safe with respect to the + # enclosing object. The former proxy allows methods to be called + # asynchronously by posting to the object's internal thread. The latter proxy + # allows a method to be called synchronously but does so safely with respect + # to any pending asynchronous method calls and ensures proper ordering. Both + # methods return a {Concurrent::IVar} which can be inspected for the result + # of the proxied method call. Calling a method with `async` will return a + # `:pending` `IVar` whereas `await` will return a `:complete` `IVar`. + # + # ``` + # class Echo + # include Concurrent::Async + # + # def echo(msg) + # print "#{msg}\n" + # end + # end + # + # horn = Echo.new + # horn.echo('zero') # synchronous, not thread-safe + # # returns the actual return value of the method + # + # horn.async.echo('one') # asynchronous, non-blocking, thread-safe + # # returns an IVar in the :pending state + # + # horn.await.echo('two') # synchronous, blocking, thread-safe + # # returns an IVar in the :complete state + # ``` + # + # ## Let It Fail + # + # The `async` and `await` proxy methods have built-in error protection based + # on Erlang's famous "let it fail" philosophy. Instance methods should not be + # programmed defensively. When an exception is raised by a delegated method + # the proxy will rescue the exception, expose it to the caller as the `reason` + # attribute of the returned future, then process the next method call. + # + # ## Calling Methods Internally + # + # External method calls should *always* use the `async` and `await` proxy + # methods. When one method calls another method, the `async` proxy should + # rarely be used and the `await` proxy should *never* be used. + # + # When an object calls one of its own methods using the `await` proxy the + # second call will be enqueued *behind* the currently running method call. + # Any attempt to wait on the result will fail as the second call will never + # run until after the current call completes. + # + # Calling a method using the `await` proxy from within a method that was + # itself called using `async` or `await` will irreversibly deadlock the + # object. Do *not* do this, ever. + # + # ## Instance Variables and Attribute Accessors + # + # Instance variables do not need to be thread-safe so long as they are private. + # Asynchronous method calls are processed in the order they are received and + # are processed one at a time. Therefore private instance variables can only + # be accessed by one thread at a time. This is inherently thread-safe. + # + # When using private instance variables within asynchronous methods, the best + # practice is to read the instance variable into a local variable at the start + # of the method then update the instance variable at the *end* of the method. + # This way, should an exception be raised during method execution the internal + # state of the object will not have been changed. + # + # ### Reader Attributes + # + # The use of `attr_reader` is discouraged. Internal state exposed externally, + # when necessary, should be done through accessor methods. The instance + # variables exposed by these methods *must* be thread-safe, or they must be + # called using the `async` and `await` proxy methods. These two approaches are + # subtly different. + # + # When internal state is accessed via the `async` and `await` proxy methods, + # the returned value represents the object's state *at the time the call is + # processed*, which may *not* be the state of the object at the time the call + # is made. + # + # To get the state *at the current* time, irrespective of an enqueued method + # calls, a reader method must be called directly. This is inherently unsafe + # unless the instance variable is itself thread-safe, preferably using one + # of the thread-safe classes within this library. Because the thread-safe + # classes within this library are internally-locking or non-locking, they can + # be safely used from within asynchronous methods without causing deadlocks. + # + # Generally speaking, the best practice is to *not* expose internal state via + # reader methods. The best practice is to simply use the method's return value. + # + # ### Writer Attributes + # + # Writer attributes should never be used with asynchronous classes. Changing + # the state externally, even when done in the thread-safe way, is not logically + # consistent. Changes to state need to be timed with respect to all asynchronous + # method calls which my be in-process or enqueued. The only safe practice is to + # pass all necessary data to each method as arguments and let the method update + # the internal state as necessary. + # + # ## Class Constants, Variables, and Methods + # + # ### Class Constants + # + # Class constants do not need to be thread-safe. Since they are read-only and + # immutable they may be safely read both externally and from within + # asynchronous methods. + # + # ### Class Variables + # + # Class variables should be avoided. Class variables represent shared state. + # Shared state is anathema to concurrency. Should there be a need to share + # state using class variables they *must* be thread-safe, preferably + # using the thread-safe classes within this library. When updating class + # variables, never assign a new value/object to the variable itself. Assignment + # is not thread-safe in Ruby. Instead, use the thread-safe update functions + # of the variable itself to change the value. + # + # The best practice is to *never* use class variables with `Async` classes. + # + # ### Class Methods + # + # Class methods which are pure functions are safe. Class methods which modify + # class variables should be avoided, for all the reasons listed above. + # + # ## An Important Note About Thread Safe Guarantees + # + # > Thread safe guarantees can only be made when asynchronous method calls + # > are not mixed with direct method calls. Use only direct method calls + # > when the object is used exclusively on a single thread. Use only + # > `async` and `await` when the object is shared between threads. Once you + # > call a method using `async` or `await`, you should no longer call methods + # > directly on the object. Use `async` and `await` exclusively from then on. + # + # @example + # + # class Echo + # include Concurrent::Async + # + # def echo(msg) + # print "#{msg}\n" + # end + # end + # + # horn = Echo.new + # horn.echo('zero') # synchronous, not thread-safe + # # returns the actual return value of the method + # + # horn.async.echo('one') # asynchronous, non-blocking, thread-safe + # # returns an IVar in the :pending state + # + # horn.await.echo('two') # synchronous, blocking, thread-safe + # # returns an IVar in the :complete state + # + # @see Concurrent::Actor + # @see https://en.wikipedia.org/wiki/Actor_model "Actor Model" at Wikipedia + # @see http://www.erlang.org/doc/man/gen_server.html Erlang gen_server + # @see http://c2.com/cgi/wiki?LetItCrash "Let It Crash" at http://c2.com/ + module Async + + # @!method self.new(*args, &block) + # + # Instanciate a new object and ensure proper initialization of the + # synchronization mechanisms. + # + # @param [Array] args Zero or more arguments to be passed to the + # object's initializer. + # @param [Proc] block Optional block to pass to the object's initializer. + # @return [Object] A properly initialized object of the asynchronous class. + + # Check for the presence of a method on an object and determine if a given + # set of arguments matches the required arity. + # + # @param [Object] obj the object to check against + # @param [Symbol] method the method to check the object for + # @param [Array] args zero or more arguments for the arity check + # + # @raise [NameError] the object does not respond to `method` method + # @raise [ArgumentError] the given `args` do not match the arity of `method` + # + # @note This check is imperfect because of the way Ruby reports the arity of + # methods with a variable number of arguments. It is possible to determine + # if too few arguments are given but impossible to determine if too many + # arguments are given. This check may also fail to recognize dynamic behavior + # of the object, such as methods simulated with `method_missing`. + # + # @see http://www.ruby-doc.org/core-2.1.1/Method.html#method-i-arity Method#arity + # @see http://ruby-doc.org/core-2.1.0/Object.html#method-i-respond_to-3F Object#respond_to? + # @see http://www.ruby-doc.org/core-2.1.0/BasicObject.html#method-i-method_missing BasicObject#method_missing + # + # @!visibility private + def self.validate_argc(obj, method, *args) + argc = args.length + arity = obj.method(method).arity + + if arity >= 0 && argc != arity + raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity})") + elsif arity < 0 && (arity = (arity + 1).abs) > argc + raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity}..*)") + end + end + + # @!visibility private + def self.included(base) + base.singleton_class.send(:alias_method, :original_new, :new) + base.extend(ClassMethods) + super(base) + end + + # @!visibility private + module ClassMethods + def new(*args, &block) + obj = original_new(*args, &block) + obj.send(:init_synchronization) + obj + end + ruby2_keywords :new if respond_to?(:ruby2_keywords, true) + end + private_constant :ClassMethods + + # Delegates asynchronous, thread-safe method calls to the wrapped object. + # + # @!visibility private + class AsyncDelegator < Synchronization::LockableObject + safe_initialization! + + # Create a new delegator object wrapping the given delegate. + # + # @param [Object] delegate the object to wrap and delegate method calls to + def initialize(delegate) + super() + @delegate = delegate + @queue = [] + @executor = Concurrent.global_io_executor + @ruby_pid = $$ + end + + # Delegates method calls to the wrapped object. + # + # @param [Symbol] method the method being called + # @param [Array] args zero or more arguments to the method + # + # @return [IVar] the result of the method call + # + # @raise [NameError] the object does not respond to `method` method + # @raise [ArgumentError] the given `args` do not match the arity of `method` + def method_missing(method, *args, &block) + super unless @delegate.respond_to?(method) + Async::validate_argc(@delegate, method, *args) + + ivar = Concurrent::IVar.new + synchronize do + reset_if_forked + @queue.push [ivar, method, args, block] + @executor.post { perform } if @queue.length == 1 + end + + ivar + end + + # Check whether the method is responsive + # + # @param [Symbol] method the method being called + def respond_to_missing?(method, include_private = false) + @delegate.respond_to?(method) || super + end + + # Perform all enqueued tasks. + # + # This method must be called from within the executor. It must not be + # called while already running. It will loop until the queue is empty. + def perform + loop do + ivar, method, args, block = synchronize { @queue.first } + break unless ivar # queue is empty + + begin + ivar.set(@delegate.send(method, *args, &block)) + rescue => error + ivar.fail(error) + end + + synchronize do + @queue.shift + return if @queue.empty? + end + end + end + + def reset_if_forked + if $$ != @ruby_pid + @queue.clear + @ruby_pid = $$ + end + end + end + private_constant :AsyncDelegator + + # Delegates synchronous, thread-safe method calls to the wrapped object. + # + # @!visibility private + class AwaitDelegator + + # Create a new delegator object wrapping the given delegate. + # + # @param [AsyncDelegator] delegate the object to wrap and delegate method calls to + def initialize(delegate) + @delegate = delegate + end + + # Delegates method calls to the wrapped object. + # + # @param [Symbol] method the method being called + # @param [Array] args zero or more arguments to the method + # + # @return [IVar] the result of the method call + # + # @raise [NameError] the object does not respond to `method` method + # @raise [ArgumentError] the given `args` do not match the arity of `method` + def method_missing(method, *args, &block) + ivar = @delegate.send(method, *args, &block) + ivar.wait + ivar + end + + # Check whether the method is responsive + # + # @param [Symbol] method the method being called + def respond_to_missing?(method, include_private = false) + @delegate.respond_to?(method) || super + end + end + private_constant :AwaitDelegator + + # Causes the chained method call to be performed asynchronously on the + # object's thread. The delegated method will return a future in the + # `:pending` state and the method call will have been scheduled on the + # object's thread. The final disposition of the method call can be obtained + # by inspecting the returned future. + # + # @!macro async_thread_safety_warning + # @note The method call is guaranteed to be thread safe with respect to + # all other method calls against the same object that are called with + # either `async` or `await`. The mutable nature of Ruby references + # (and object orientation in general) prevent any other thread safety + # guarantees. Do NOT mix direct method calls with delegated method calls. + # Use *only* delegated method calls when sharing the object between threads. + # + # @return [Concurrent::IVar] the pending result of the asynchronous operation + # + # @raise [NameError] the object does not respond to the requested method + # @raise [ArgumentError] the given `args` do not match the arity of + # the requested method + def async + @__async_delegator__ + end + alias_method :cast, :async + + # Causes the chained method call to be performed synchronously on the + # current thread. The delegated will return a future in either the + # `:fulfilled` or `:rejected` state and the delegated method will have + # completed. The final disposition of the delegated method can be obtained + # by inspecting the returned future. + # + # @!macro async_thread_safety_warning + # + # @return [Concurrent::IVar] the completed result of the synchronous operation + # + # @raise [NameError] the object does not respond to the requested method + # @raise [ArgumentError] the given `args` do not match the arity of the + # requested method + def await + @__await_delegator__ + end + alias_method :call, :await + + # Initialize the internal serializer and other stnchronization mechanisms. + # + # @note This method *must* be called immediately upon object construction. + # This is the only way thread-safe initialization can be guaranteed. + # + # @!visibility private + def init_synchronization + return self if defined?(@__async_initialized__) && @__async_initialized__ + @__async_initialized__ = true + @__async_delegator__ = AsyncDelegator.new(self) + @__await_delegator__ = AwaitDelegator.new(@__async_delegator__) + self + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atom.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atom.rb new file mode 100644 index 0000000000..1074006d76 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atom.rb @@ -0,0 +1,222 @@ +require 'concurrent/atomic/atomic_reference' +require 'concurrent/collection/copy_on_notify_observer_set' +require 'concurrent/concern/observable' +require 'concurrent/synchronization/object' + +# @!macro thread_safe_variable_comparison +# +# ## Thread-safe Variable Classes +# +# Each of the thread-safe variable classes is designed to solve a different +# problem. In general: +# +# * *{Concurrent::Agent}:* Shared, mutable variable providing independent, +# uncoordinated, *asynchronous* change of individual values. Best used when +# the value will undergo frequent, complex updates. Suitable when the result +# of an update does not need to be known immediately. +# * *{Concurrent::Atom}:* Shared, mutable variable providing independent, +# uncoordinated, *synchronous* change of individual values. Best used when +# the value will undergo frequent reads but only occasional, though complex, +# updates. Suitable when the result of an update must be known immediately. +# * *{Concurrent::AtomicReference}:* A simple object reference that can be updated +# atomically. Updates are synchronous but fast. Best used when updates a +# simple set operations. Not suitable when updates are complex. +# {Concurrent::AtomicBoolean} and {Concurrent::AtomicFixnum} are similar +# but optimized for the given data type. +# * *{Concurrent::Exchanger}:* Shared, stateless synchronization point. Used +# when two or more threads need to exchange data. The threads will pair then +# block on each other until the exchange is complete. +# * *{Concurrent::MVar}:* Shared synchronization point. Used when one thread +# must give a value to another, which must take the value. The threads will +# block on each other until the exchange is complete. +# * *{Concurrent::ThreadLocalVar}:* Shared, mutable, isolated variable which +# holds a different value for each thread which has access. Often used as +# an instance variable in objects which must maintain different state +# for different threads. +# * *{Concurrent::TVar}:* Shared, mutable variables which provide +# *coordinated*, *synchronous*, change of *many* stated. Used when multiple +# value must change together, in an all-or-nothing transaction. + + +module Concurrent + + # Atoms provide a way to manage shared, synchronous, independent state. + # + # An atom is initialized with an initial value and an optional validation + # proc. At any time the value of the atom can be synchronously and safely + # changed. If a validator is given at construction then any new value + # will be checked against the validator and will be rejected if the + # validator returns false or raises an exception. + # + # There are two ways to change the value of an atom: {#compare_and_set} and + # {#swap}. The former will set the new value if and only if it validates and + # the current value matches the new value. The latter will atomically set the + # new value to the result of running the given block if and only if that + # value validates. + # + # ## Example + # + # ``` + # def next_fibonacci(set = nil) + # return [0, 1] if set.nil? + # set + [set[-2..-1].reduce{|sum,x| sum + x }] + # end + # + # # create an atom with an initial value + # atom = Concurrent::Atom.new(next_fibonacci) + # + # # send a few update requests + # 5.times do + # atom.swap{|set| next_fibonacci(set) } + # end + # + # # get the current value + # atom.value #=> [0, 1, 1, 2, 3, 5, 8] + # ``` + # + # ## Observation + # + # Atoms support observers through the {Concurrent::Observable} mixin module. + # Notification of observers occurs every time the value of the Atom changes. + # When notified the observer will receive three arguments: `time`, `old_value`, + # and `new_value`. The `time` argument is the time at which the value change + # occurred. The `old_value` is the value of the Atom when the change began + # The `new_value` is the value to which the Atom was set when the change + # completed. Note that `old_value` and `new_value` may be the same. This is + # not an error. It simply means that the change operation returned the same + # value. + # + # Unlike in Clojure, `Atom` cannot participate in {Concurrent::TVar} transactions. + # + # @!macro thread_safe_variable_comparison + # + # @see http://clojure.org/atoms Clojure Atoms + # @see http://clojure.org/state Values and Change - Clojure's approach to Identity and State + class Atom < Synchronization::Object + include Concern::Observable + + safe_initialization! + attr_atomic(:value) + private :value=, :swap_value, :compare_and_set_value, :update_value + public :value + alias_method :deref, :value + + # @!method value + # The current value of the atom. + # + # @return [Object] The current value. + + # Create a new atom with the given initial value. + # + # @param [Object] value The initial value + # @param [Hash] opts The options used to configure the atom + # @option opts [Proc] :validator (nil) Optional proc used to validate new + # values. It must accept one and only one argument which will be the + # intended new value. The validator will return true if the new value + # is acceptable else return false (preferrably) or raise an exception. + # + # @!macro deref_options + # + # @raise [ArgumentError] if the validator is not a `Proc` (when given) + def initialize(value, opts = {}) + super() + @Validator = opts.fetch(:validator, -> v { true }) + self.observers = Collection::CopyOnNotifyObserverSet.new + self.value = value + end + + # Atomically swaps the value of atom using the given block. The current + # value will be passed to the block, as will any arguments passed as + # arguments to the function. The new value will be validated against the + # (optional) validator proc given at construction. If validation fails the + # value will not be changed. + # + # Internally, {#swap} reads the current value, applies the block to it, and + # attempts to compare-and-set it in. Since another thread may have changed + # the value in the intervening time, it may have to retry, and does so in a + # spin loop. The net effect is that the value will always be the result of + # the application of the supplied block to a current value, atomically. + # However, because the block might be called multiple times, it must be free + # of side effects. + # + # @note The given block may be called multiple times, and thus should be free + # of side effects. + # + # @param [Object] args Zero or more arguments passed to the block. + # + # @yield [value, args] Calculates a new value for the atom based on the + # current value and any supplied arguments. + # @yieldparam value [Object] The current value of the atom. + # @yieldparam args [Object] All arguments passed to the function, in order. + # @yieldreturn [Object] The intended new value of the atom. + # + # @return [Object] The final value of the atom after all operations and + # validations are complete. + # + # @raise [ArgumentError] When no block is given. + def swap(*args) + raise ArgumentError.new('no block given') unless block_given? + + loop do + old_value = value + new_value = yield(old_value, *args) + begin + break old_value unless valid?(new_value) + break new_value if compare_and_set(old_value, new_value) + rescue + break old_value + end + end + end + + # Atomically sets the value of atom to the new value if and only if the + # current value of the atom is identical to the old value and the new + # value successfully validates against the (optional) validator given + # at construction. + # + # @param [Object] old_value The expected current value. + # @param [Object] new_value The intended new value. + # + # @return [Boolean] True if the value is changed else false. + def compare_and_set(old_value, new_value) + if valid?(new_value) && compare_and_set_value(old_value, new_value) + observers.notify_observers(Time.now, old_value, new_value) + true + else + false + end + end + + # Atomically sets the value of atom to the new value without regard for the + # current value so long as the new value successfully validates against the + # (optional) validator given at construction. + # + # @param [Object] new_value The intended new value. + # + # @return [Object] The final value of the atom after all operations and + # validations are complete. + def reset(new_value) + old_value = value + if valid?(new_value) + self.value = new_value + observers.notify_observers(Time.now, old_value, new_value) + new_value + else + old_value + end + end + + private + + # Is the new value valid? + # + # @param [Object] new_value The intended new value. + # @return [Boolean] false if the validator function returns false or raises + # an exception else true + def valid?(new_value) + @Validator.call(new_value) + rescue + false + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_boolean.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_boolean.rb new file mode 100644 index 0000000000..f775691a2a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_boolean.rb @@ -0,0 +1,127 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/atomic/mutex_atomic_boolean' + +module Concurrent + + ################################################################### + + # @!macro atomic_boolean_method_initialize + # + # Creates a new `AtomicBoolean` with the given initial value. + # + # @param [Boolean] initial the initial value + + # @!macro atomic_boolean_method_value_get + # + # Retrieves the current `Boolean` value. + # + # @return [Boolean] the current value + + # @!macro atomic_boolean_method_value_set + # + # Explicitly sets the value. + # + # @param [Boolean] value the new value to be set + # + # @return [Boolean] the current value + + # @!macro atomic_boolean_method_true_question + # + # Is the current value `true` + # + # @return [Boolean] true if the current value is `true`, else false + + # @!macro atomic_boolean_method_false_question + # + # Is the current value `false` + # + # @return [Boolean] true if the current value is `false`, else false + + # @!macro atomic_boolean_method_make_true + # + # Explicitly sets the value to true. + # + # @return [Boolean] true if value has changed, otherwise false + + # @!macro atomic_boolean_method_make_false + # + # Explicitly sets the value to false. + # + # @return [Boolean] true if value has changed, otherwise false + + ################################################################### + + # @!macro atomic_boolean_public_api + # + # @!method initialize(initial = false) + # @!macro atomic_boolean_method_initialize + # + # @!method value + # @!macro atomic_boolean_method_value_get + # + # @!method value=(value) + # @!macro atomic_boolean_method_value_set + # + # @!method true? + # @!macro atomic_boolean_method_true_question + # + # @!method false? + # @!macro atomic_boolean_method_false_question + # + # @!method make_true + # @!macro atomic_boolean_method_make_true + # + # @!method make_false + # @!macro atomic_boolean_method_make_false + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + AtomicBooleanImplementation = case + when Concurrent.on_cruby? && Concurrent.c_extensions_loaded? + CAtomicBoolean + when Concurrent.on_jruby? + JavaAtomicBoolean + else + MutexAtomicBoolean + end + private_constant :AtomicBooleanImplementation + + # @!macro atomic_boolean + # + # A boolean value that can be updated atomically. Reads and writes to an atomic + # boolean and thread-safe and guaranteed to succeed. Reads and writes may block + # briefly but no explicit locking is required. + # + # @!macro thread_safe_variable_comparison + # + # Performance: + # + # ``` + # Testing with ruby 2.1.2 + # Testing with Concurrent::MutexAtomicBoolean... + # 2.790000 0.000000 2.790000 ( 2.791454) + # Testing with Concurrent::CAtomicBoolean... + # 0.740000 0.000000 0.740000 ( 0.740206) + # + # Testing with jruby 1.9.3 + # Testing with Concurrent::MutexAtomicBoolean... + # 5.240000 2.520000 7.760000 ( 3.683000) + # Testing with Concurrent::JavaAtomicBoolean... + # 3.340000 0.010000 3.350000 ( 0.855000) + # ``` + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html java.util.concurrent.atomic.AtomicBoolean + # + # @!macro atomic_boolean_public_api + class AtomicBoolean < AtomicBooleanImplementation + # @return [String] Short string representation. + def to_s + format '%s value:%s>', super[0..-2], value + end + + alias_method :inspect, :to_s + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_fixnum.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_fixnum.rb new file mode 100644 index 0000000000..26cd05d869 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_fixnum.rb @@ -0,0 +1,144 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/atomic/mutex_atomic_fixnum' + +module Concurrent + + ################################################################### + + # @!macro atomic_fixnum_method_initialize + # + # Creates a new `AtomicFixnum` with the given initial value. + # + # @param [Fixnum] initial the initial value + # @raise [ArgumentError] if the initial value is not a `Fixnum` + + # @!macro atomic_fixnum_method_value_get + # + # Retrieves the current `Fixnum` value. + # + # @return [Fixnum] the current value + + # @!macro atomic_fixnum_method_value_set + # + # Explicitly sets the value. + # + # @param [Fixnum] value the new value to be set + # + # @return [Fixnum] the current value + # + # @raise [ArgumentError] if the new value is not a `Fixnum` + + # @!macro atomic_fixnum_method_increment + # + # Increases the current value by the given amount (defaults to 1). + # + # @param [Fixnum] delta the amount by which to increase the current value + # + # @return [Fixnum] the current value after incrementation + + # @!macro atomic_fixnum_method_decrement + # + # Decreases the current value by the given amount (defaults to 1). + # + # @param [Fixnum] delta the amount by which to decrease the current value + # + # @return [Fixnum] the current value after decrementation + + # @!macro atomic_fixnum_method_compare_and_set + # + # Atomically sets the value to the given updated value if the current + # value == the expected value. + # + # @param [Fixnum] expect the expected value + # @param [Fixnum] update the new value + # + # @return [Boolean] true if the value was updated else false + + # @!macro atomic_fixnum_method_update + # + # Pass the current value to the given block, replacing it + # with the block's result. May retry if the value changes + # during the block's execution. + # + # @yield [Object] Calculate a new value for the atomic reference using + # given (old) value + # @yieldparam [Object] old_value the starting value of the atomic reference + # + # @return [Object] the new value + + ################################################################### + + # @!macro atomic_fixnum_public_api + # + # @!method initialize(initial = 0) + # @!macro atomic_fixnum_method_initialize + # + # @!method value + # @!macro atomic_fixnum_method_value_get + # + # @!method value=(value) + # @!macro atomic_fixnum_method_value_set + # + # @!method increment(delta = 1) + # @!macro atomic_fixnum_method_increment + # + # @!method decrement(delta = 1) + # @!macro atomic_fixnum_method_decrement + # + # @!method compare_and_set(expect, update) + # @!macro atomic_fixnum_method_compare_and_set + # + # @!method update + # @!macro atomic_fixnum_method_update + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + AtomicFixnumImplementation = case + when Concurrent.on_cruby? && Concurrent.c_extensions_loaded? + CAtomicFixnum + when Concurrent.on_jruby? + JavaAtomicFixnum + else + MutexAtomicFixnum + end + private_constant :AtomicFixnumImplementation + + # @!macro atomic_fixnum + # + # A numeric value that can be updated atomically. Reads and writes to an atomic + # fixnum and thread-safe and guaranteed to succeed. Reads and writes may block + # briefly but no explicit locking is required. + # + # @!macro thread_safe_variable_comparison + # + # Performance: + # + # ``` + # Testing with ruby 2.1.2 + # Testing with Concurrent::MutexAtomicFixnum... + # 3.130000 0.000000 3.130000 ( 3.136505) + # Testing with Concurrent::CAtomicFixnum... + # 0.790000 0.000000 0.790000 ( 0.785550) + # + # Testing with jruby 1.9.3 + # Testing with Concurrent::MutexAtomicFixnum... + # 5.460000 2.460000 7.920000 ( 3.715000) + # Testing with Concurrent::JavaAtomicFixnum... + # 4.520000 0.030000 4.550000 ( 1.187000) + # ``` + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicLong.html java.util.concurrent.atomic.AtomicLong + # + # @!macro atomic_fixnum_public_api + class AtomicFixnum < AtomicFixnumImplementation + # @return [String] Short string representation. + def to_s + format '%s value:%s>', super[0..-2], value + end + + alias_method :inspect, :to_s + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_markable_reference.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_markable_reference.rb new file mode 100644 index 0000000000..e16be65772 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_markable_reference.rb @@ -0,0 +1,167 @@ +require 'concurrent/errors' +require 'concurrent/synchronization/object' + +module Concurrent + # An atomic reference which maintains an object reference along with a mark bit + # that can be updated atomically. + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicMarkableReference.html + # java.util.concurrent.atomic.AtomicMarkableReference + class AtomicMarkableReference < ::Concurrent::Synchronization::Object + + attr_atomic(:reference) + private :reference, :reference=, :swap_reference, :compare_and_set_reference, :update_reference + + def initialize(value = nil, mark = false) + super() + self.reference = immutable_array(value, mark) + end + + # Atomically sets the value and mark to the given updated value and + # mark given both: + # - the current value == the expected value && + # - the current mark == the expected mark + # + # @param [Object] expected_val the expected value + # @param [Object] new_val the new value + # @param [Boolean] expected_mark the expected mark + # @param [Boolean] new_mark the new mark + # + # @return [Boolean] `true` if successful. A `false` return indicates + # that the actual value was not equal to the expected value or the + # actual mark was not equal to the expected mark + def compare_and_set(expected_val, new_val, expected_mark, new_mark) + # Memoize a valid reference to the current AtomicReference for + # later comparison. + current = reference + curr_val, curr_mark = current + + # Ensure that that the expected marks match. + return false unless expected_mark == curr_mark + + if expected_val.is_a? Numeric + # If the object is a numeric, we need to ensure we are comparing + # the numerical values + return false unless expected_val == curr_val + else + # Otherwise, we need to ensure we are comparing the object identity. + # Theoretically, this could be incorrect if a user monkey-patched + # `Object#equal?`, but they should know that they are playing with + # fire at that point. + return false unless expected_val.equal? curr_val + end + + prospect = immutable_array(new_val, new_mark) + + compare_and_set_reference current, prospect + end + + alias_method :compare_and_swap, :compare_and_set + + # Gets the current reference and marked values. + # + # @return [Array] the current reference and marked values + def get + reference + end + + # Gets the current value of the reference + # + # @return [Object] the current value of the reference + def value + reference[0] + end + + # Gets the current marked value + # + # @return [Boolean] the current marked value + def mark + reference[1] + end + + alias_method :marked?, :mark + + # _Unconditionally_ sets to the given value of both the reference and + # the mark. + # + # @param [Object] new_val the new value + # @param [Boolean] new_mark the new mark + # + # @return [Array] both the new value and the new mark + def set(new_val, new_mark) + self.reference = immutable_array(new_val, new_mark) + end + + # Pass the current value and marked state to the given block, replacing it + # with the block's results. May retry if the value changes during the + # block's execution. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and new mark + def update + loop do + old_val, old_mark = reference + new_val, new_mark = yield old_val, old_mark + + if compare_and_set old_val, new_val, old_mark, new_mark + return immutable_array(new_val, new_mark) + end + end + end + + # Pass the current value to the given block, replacing it + # with the block's result. Raise an exception if the update + # fails. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and marked state + # + # @raise [Concurrent::ConcurrentUpdateError] if the update fails + def try_update! + old_val, old_mark = reference + new_val, new_mark = yield old_val, old_mark + + unless compare_and_set old_val, new_val, old_mark, new_mark + fail ::Concurrent::ConcurrentUpdateError, + 'AtomicMarkableReference: Update failed due to race condition.', + 'Note: If you would like to guarantee an update, please use ' + + 'the `AtomicMarkableReference#update` method.' + end + + immutable_array(new_val, new_mark) + end + + # Pass the current value to the given block, replacing it with the + # block's result. Simply return nil if update fails. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and marked state, or nil if + # the update failed + def try_update + old_val, old_mark = reference + new_val, new_mark = yield old_val, old_mark + + return unless compare_and_set old_val, new_val, old_mark, new_mark + + immutable_array(new_val, new_mark) + end + + private + + def immutable_array(*args) + args.freeze + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_reference.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_reference.rb new file mode 100644 index 0000000000..bb5fb77459 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/atomic_reference.rb @@ -0,0 +1,135 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/atomic_reference/atomic_direct_update' +require 'concurrent/atomic_reference/numeric_cas_wrapper' +require 'concurrent/atomic_reference/mutex_atomic' + +# Shim for TruffleRuby::AtomicReference +if Concurrent.on_truffleruby? && !defined?(TruffleRuby::AtomicReference) + # @!visibility private + module TruffleRuby + AtomicReference = Truffle::AtomicReference + end +end + +module Concurrent + + # @!macro internal_implementation_note + AtomicReferenceImplementation = case + when Concurrent.on_cruby? && Concurrent.c_extensions_loaded? + # @!visibility private + # @!macro internal_implementation_note + class CAtomicReference + include AtomicDirectUpdate + include AtomicNumericCompareAndSetWrapper + alias_method :compare_and_swap, :compare_and_set + end + CAtomicReference + when Concurrent.on_jruby? + # @!visibility private + # @!macro internal_implementation_note + class JavaAtomicReference + include AtomicDirectUpdate + end + JavaAtomicReference + when Concurrent.on_truffleruby? + class TruffleRubyAtomicReference < TruffleRuby::AtomicReference + include AtomicDirectUpdate + alias_method :value, :get + alias_method :value=, :set + alias_method :compare_and_swap, :compare_and_set + alias_method :swap, :get_and_set + end + TruffleRubyAtomicReference + else + MutexAtomicReference + end + private_constant :AtomicReferenceImplementation + + # An object reference that may be updated atomically. All read and write + # operations have java volatile semantic. + # + # @!macro thread_safe_variable_comparison + # + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html + # + # @!method initialize(value = nil) + # @!macro atomic_reference_method_initialize + # @param [Object] value The initial value. + # + # @!method get + # @!macro atomic_reference_method_get + # Gets the current value. + # @return [Object] the current value + # + # @!method set(new_value) + # @!macro atomic_reference_method_set + # Sets to the given value. + # @param [Object] new_value the new value + # @return [Object] the new value + # + # @!method get_and_set(new_value) + # @!macro atomic_reference_method_get_and_set + # Atomically sets to the given value and returns the old value. + # @param [Object] new_value the new value + # @return [Object] the old value + # + # @!method compare_and_set(old_value, new_value) + # @!macro atomic_reference_method_compare_and_set + # + # Atomically sets the value to the given updated value if + # the current value == the expected value. + # + # @param [Object] old_value the expected value + # @param [Object] new_value the new value + # + # @return [Boolean] `true` if successful. A `false` return indicates + # that the actual value was not equal to the expected value. + # + # @!method update + # Pass the current value to the given block, replacing it + # with the block's result. May retry if the value changes + # during the block's execution. + # + # @yield [Object] Calculate a new value for the atomic reference using + # given (old) value + # @yieldparam [Object] old_value the starting value of the atomic reference + # @return [Object] the new value + # + # @!method try_update + # Pass the current value to the given block, replacing it + # with the block's result. Return nil if the update fails. + # + # @yield [Object] Calculate a new value for the atomic reference using + # given (old) value + # @yieldparam [Object] old_value the starting value of the atomic reference + # @note This method was altered to avoid raising an exception by default. + # Instead, this method now returns `nil` in case of failure. For more info, + # please see: https://github.com/ruby-concurrency/concurrent-ruby/pull/336 + # @return [Object] the new value, or nil if update failed + # + # @!method try_update! + # Pass the current value to the given block, replacing it + # with the block's result. Raise an exception if the update + # fails. + # + # @yield [Object] Calculate a new value for the atomic reference using + # given (old) value + # @yieldparam [Object] old_value the starting value of the atomic reference + # @note This behavior mimics the behavior of the original + # `AtomicReference#try_update` API. The reason this was changed was to + # avoid raising exceptions (which are inherently slow) by default. For more + # info: https://github.com/ruby-concurrency/concurrent-ruby/pull/336 + # @return [Object] the new value + # @raise [Concurrent::ConcurrentUpdateError] if the update fails + class AtomicReference < AtomicReferenceImplementation + + # @return [String] Short string representation. + def to_s + format '%s value:%s>', super[0..-2], get + end + + alias_method :inspect, :to_s + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/count_down_latch.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/count_down_latch.rb new file mode 100644 index 0000000000..d883aed6f2 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/count_down_latch.rb @@ -0,0 +1,100 @@ +require 'concurrent/utility/engine' +require 'concurrent/atomic/mutex_count_down_latch' +require 'concurrent/atomic/java_count_down_latch' + +module Concurrent + + ################################################################### + + # @!macro count_down_latch_method_initialize + # + # Create a new `CountDownLatch` with the initial `count`. + # + # @param [new] count the initial count + # + # @raise [ArgumentError] if `count` is not an integer or is less than zero + + # @!macro count_down_latch_method_wait + # + # Block on the latch until the counter reaches zero or until `timeout` is reached. + # + # @param [Fixnum] timeout the number of seconds to wait for the counter or `nil` + # to block indefinitely + # @return [Boolean] `true` if the `count` reaches zero else false on `timeout` + + # @!macro count_down_latch_method_count_down + # + # Signal the latch to decrement the counter. Will signal all blocked threads when + # the `count` reaches zero. + + # @!macro count_down_latch_method_count + # + # The current value of the counter. + # + # @return [Fixnum] the current value of the counter + + ################################################################### + + # @!macro count_down_latch_public_api + # + # @!method initialize(count = 1) + # @!macro count_down_latch_method_initialize + # + # @!method wait(timeout = nil) + # @!macro count_down_latch_method_wait + # + # @!method count_down + # @!macro count_down_latch_method_count_down + # + # @!method count + # @!macro count_down_latch_method_count + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + CountDownLatchImplementation = case + when Concurrent.on_jruby? + JavaCountDownLatch + else + MutexCountDownLatch + end + private_constant :CountDownLatchImplementation + + # @!macro count_down_latch + # + # A synchronization object that allows one thread to wait on multiple other threads. + # The thread that will wait creates a `CountDownLatch` and sets the initial value + # (normally equal to the number of other threads). The initiating thread passes the + # latch to the other threads then waits for the other threads by calling the `#wait` + # method. Each of the other threads calls `#count_down` when done with its work. + # When the latch counter reaches zero the waiting thread is unblocked and continues + # with its work. A `CountDownLatch` can be used only once. Its value cannot be reset. + # + # @!macro count_down_latch_public_api + # @example Waiter and Decrementer + # latch = Concurrent::CountDownLatch.new(3) + # + # waiter = Thread.new do + # latch.wait() + # puts ("Waiter released") + # end + # + # decrementer = Thread.new do + # sleep(1) + # latch.count_down + # puts latch.count + # + # sleep(1) + # latch.count_down + # puts latch.count + # + # sleep(1) + # latch.count_down + # puts latch.count + # end + # + # [waiter, decrementer].each(&:join) + class CountDownLatch < CountDownLatchImplementation + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/cyclic_barrier.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/cyclic_barrier.rb new file mode 100644 index 0000000000..9ebe29dd09 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/cyclic_barrier.rb @@ -0,0 +1,128 @@ +require 'concurrent/synchronization/lockable_object' +require 'concurrent/utility/native_integer' + +module Concurrent + + # A synchronization aid that allows a set of threads to all wait for each + # other to reach a common barrier point. + # @example + # barrier = Concurrent::CyclicBarrier.new(3) + # jobs = Array.new(3) { |i| -> { sleep i; p done: i } } + # process = -> (i) do + # # waiting to start at the same time + # barrier.wait + # # execute job + # jobs[i].call + # # wait for others to finish + # barrier.wait + # end + # threads = 2.times.map do |i| + # Thread.new(i, &process) + # end + # + # # use main as well + # process.call 2 + # + # # here we can be sure that all jobs are processed + class CyclicBarrier < Synchronization::LockableObject + + # @!visibility private + Generation = Struct.new(:status) + private_constant :Generation + + # Create a new `CyclicBarrier` that waits for `parties` threads + # + # @param [Fixnum] parties the number of parties + # @yield an optional block that will be executed that will be executed after + # the last thread arrives and before the others are released + # + # @raise [ArgumentError] if `parties` is not an integer or is less than zero + def initialize(parties, &block) + Utility::NativeInteger.ensure_integer_and_bounds parties + Utility::NativeInteger.ensure_positive_and_no_zero parties + + super(&nil) + synchronize { ns_initialize parties, &block } + end + + # @return [Fixnum] the number of threads needed to pass the barrier + def parties + synchronize { @parties } + end + + # @return [Fixnum] the number of threads currently waiting on the barrier + def number_waiting + synchronize { @number_waiting } + end + + # Blocks on the barrier until the number of waiting threads is equal to + # `parties` or until `timeout` is reached or `reset` is called + # If a block has been passed to the constructor, it will be executed once by + # the last arrived thread before releasing the others + # @param [Fixnum] timeout the number of seconds to wait for the counter or + # `nil` to block indefinitely + # @return [Boolean] `true` if the `count` reaches zero else false on + # `timeout` or on `reset` or if the barrier is broken + def wait(timeout = nil) + synchronize do + + return false unless @generation.status == :waiting + + @number_waiting += 1 + + if @number_waiting == @parties + @action.call if @action + ns_generation_done @generation, :fulfilled + true + else + generation = @generation + if ns_wait_until(timeout) { generation.status != :waiting } + generation.status == :fulfilled + else + ns_generation_done generation, :broken, false + false + end + end + end + end + + # resets the barrier to its initial state + # If there is at least one waiting thread, it will be woken up, the `wait` + # method will return false and the barrier will be broken + # If the barrier is broken, this method restores it to the original state + # + # @return [nil] + def reset + synchronize { ns_generation_done @generation, :reset } + end + + # A barrier can be broken when: + # - a thread called the `reset` method while at least one other thread was waiting + # - at least one thread timed out on `wait` method + # + # A broken barrier can be restored using `reset` it's safer to create a new one + # @return [Boolean] true if the barrier is broken otherwise false + def broken? + synchronize { @generation.status != :waiting } + end + + protected + + def ns_generation_done(generation, status, continue = true) + generation.status = status + ns_next_generation if continue + ns_broadcast + end + + def ns_next_generation + @generation = Generation.new(:waiting) + @number_waiting = 0 + end + + def ns_initialize(parties, &block) + @parties = parties + @action = block + ns_next_generation + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/event.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/event.rb new file mode 100644 index 0000000000..ccf84c9d1b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/event.rb @@ -0,0 +1,109 @@ +require 'thread' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # Old school kernel-style event reminiscent of Win32 programming in C++. + # + # When an `Event` is created it is in the `unset` state. Threads can choose to + # `#wait` on the event, blocking until released by another thread. When one + # thread wants to alert all blocking threads it calls the `#set` method which + # will then wake up all listeners. Once an `Event` has been set it remains set. + # New threads calling `#wait` will return immediately. An `Event` may be + # `#reset` at any time once it has been set. + # + # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx + # @example + # event = Concurrent::Event.new + # + # t1 = Thread.new do + # puts "t1 is waiting" + # event.wait(1) + # puts "event occurred" + # end + # + # t2 = Thread.new do + # puts "t2 calling set" + # event.set + # end + # + # [t1, t2].each(&:join) + # + # # prints: + # # t1 is waiting + # # t2 calling set + # # event occurred + class Event < Synchronization::LockableObject + + # Creates a new `Event` in the unset state. Threads calling `#wait` on the + # `Event` will block. + def initialize + super + synchronize { ns_initialize } + end + + # Is the object in the set state? + # + # @return [Boolean] indicating whether or not the `Event` has been set + def set? + synchronize { @set } + end + + # Trigger the event, setting the state to `set` and releasing all threads + # waiting on the event. Has no effect if the `Event` has already been set. + # + # @return [Boolean] should always return `true` + def set + synchronize { ns_set } + end + + def try? + synchronize { @set ? false : ns_set } + end + + # Reset a previously set event back to the `unset` state. + # Has no effect if the `Event` has not yet been set. + # + # @return [Boolean] should always return `true` + def reset + synchronize do + if @set + @set = false + @iteration +=1 + end + true + end + end + + # Wait a given number of seconds for the `Event` to be set by another + # thread. Will wait forever when no `timeout` value is given. Returns + # immediately if the `Event` has already been set. + # + # @return [Boolean] true if the `Event` was set before timeout else false + def wait(timeout = nil) + synchronize do + unless @set + iteration = @iteration + ns_wait_until(timeout) { iteration < @iteration || @set } + else + true + end + end + end + + protected + + def ns_set + unless @set + @set = true + ns_broadcast + end + true + end + + def ns_initialize + @set = false + @iteration = 0 + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/fiber_local_var.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/fiber_local_var.rb new file mode 100644 index 0000000000..e90fc24f9e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/fiber_local_var.rb @@ -0,0 +1,109 @@ +require 'concurrent/constants' +require_relative 'locals' + +module Concurrent + + # A `FiberLocalVar` is a variable where the value is different for each fiber. + # Each variable may have a default value, but when you modify the variable only + # the current fiber will ever see that change. + # + # This is similar to Ruby's built-in fiber-local variables (`Thread.current[:name]`), + # but with these major advantages: + # * `FiberLocalVar` has its own identity, it doesn't need a Symbol. + # * Each Ruby's built-in fiber-local variable leaks some memory forever (it's a Symbol held forever on the fiber), + # so it's only OK to create a small amount of them. + # `FiberLocalVar` has no such issue and it is fine to create many of them. + # * Ruby's built-in fiber-local variables leak forever the value set on each fiber (unless set to nil explicitly). + # `FiberLocalVar` automatically removes the mapping for each fiber once the `FiberLocalVar` instance is GC'd. + # + # @example + # v = FiberLocalVar.new(14) + # v.value #=> 14 + # v.value = 2 + # v.value #=> 2 + # + # @example + # v = FiberLocalVar.new(14) + # + # Fiber.new do + # v.value #=> 14 + # v.value = 1 + # v.value #=> 1 + # end.resume + # + # Fiber.new do + # v.value #=> 14 + # v.value = 2 + # v.value #=> 2 + # end.resume + # + # v.value #=> 14 + class FiberLocalVar + LOCALS = FiberLocals.new + + # Creates a fiber local variable. + # + # @param [Object] default the default value when otherwise unset + # @param [Proc] default_block Optional block that gets called to obtain the + # default value for each fiber + def initialize(default = nil, &default_block) + if default && block_given? + raise ArgumentError, "Cannot use both value and block as default value" + end + + if block_given? + @default_block = default_block + @default = nil + else + @default_block = nil + @default = default + end + + @index = LOCALS.next_index(self) + end + + # Returns the value in the current fiber's copy of this fiber-local variable. + # + # @return [Object] the current value + def value + LOCALS.fetch(@index) { default } + end + + # Sets the current fiber's copy of this fiber-local variable to the specified value. + # + # @param [Object] value the value to set + # @return [Object] the new value + def value=(value) + LOCALS.set(@index, value) + end + + # Bind the given value to fiber local storage during + # execution of the given block. + # + # @param [Object] value the value to bind + # @yield the operation to be performed with the bound variable + # @return [Object] the value + def bind(value) + if block_given? + old_value = self.value + self.value = value + begin + yield + ensure + self.value = old_value + end + end + end + + protected + + # @!visibility private + def default + if @default_block + self.value = @default_block.call + else + @default + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/java_count_down_latch.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/java_count_down_latch.rb new file mode 100644 index 0000000000..3c119bc32c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/java_count_down_latch.rb @@ -0,0 +1,43 @@ +if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + + module Concurrent + + # @!macro count_down_latch + # @!visibility private + # @!macro internal_implementation_note + class JavaCountDownLatch + + # @!macro count_down_latch_method_initialize + def initialize(count = 1) + Utility::NativeInteger.ensure_integer_and_bounds(count) + Utility::NativeInteger.ensure_positive(count) + @latch = java.util.concurrent.CountDownLatch.new(count) + end + + # @!macro count_down_latch_method_wait + def wait(timeout = nil) + result = nil + if timeout.nil? + Synchronization::JRuby.sleep_interruptibly { @latch.await } + result = true + else + Synchronization::JRuby.sleep_interruptibly do + result = @latch.await(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS) + end + end + result + end + + # @!macro count_down_latch_method_count_down + def count_down + @latch.countDown + end + + # @!macro count_down_latch_method_count + def count + @latch.getCount + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/locals.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/locals.rb new file mode 100644 index 0000000000..0a276aedd5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/locals.rb @@ -0,0 +1,189 @@ +require 'fiber' +require 'concurrent/utility/engine' +require 'concurrent/constants' + +module Concurrent + # @!visibility private + # @!macro internal_implementation_note + # + # An abstract implementation of local storage, with sub-classes for + # per-thread and per-fiber locals. + # + # Each execution context (EC, thread or fiber) has a lazily initialized array + # of local variable values. Each time a new local variable is created, we + # allocate an "index" for it. + # + # For example, if the allocated index is 1, that means slot #1 in EVERY EC's + # locals array will be used for the value of that variable. + # + # The good thing about using a per-EC structure to hold values, rather than + # a global, is that no synchronization is needed when reading and writing + # those values (since the structure is only ever accessed by a single + # thread). + # + # Of course, when a local variable is GC'd, 1) we need to recover its index + # for use by other new local variables (otherwise the locals arrays could + # get bigger and bigger with time), and 2) we need to null out all the + # references held in the now-unused slots (both to avoid blocking GC of those + # objects, and also to prevent "stale" values from being passed on to a new + # local when the index is reused). + # + # Because we need to null out freed slots, we need to keep references to + # ALL the locals arrays, so we can null out the appropriate slots in all of + # them. This is why we need to use a finalizer to clean up the locals array + # when the EC goes out of scope. + class AbstractLocals + def initialize + @free = [] + @lock = Mutex.new + @all_arrays = {} + @next = 0 + end + + def synchronize + @lock.synchronize { yield } + end + + if Concurrent.on_cruby? + def weak_synchronize + yield + end + else + alias_method :weak_synchronize, :synchronize + end + + def next_index(local) + index = synchronize do + if @free.empty? + @next += 1 + else + @free.pop + end + end + + # When the local goes out of scope, we should free the associated index + # and all values stored into it. + ObjectSpace.define_finalizer(local, local_finalizer(index)) + + index + end + + def free_index(index) + weak_synchronize do + # The cost of GC'ing a TLV is linear in the number of ECs using local + # variables. But that is natural! More ECs means more storage is used + # per local variable. So naturally more CPU time is required to free + # more storage. + # + # DO NOT use each_value which might conflict with new pair assignment + # into the hash in #set method. + @all_arrays.values.each do |locals| + locals[index] = nil + end + + # free index has to be published after the arrays are cleared: + @free << index + end + end + + def fetch(index) + locals = self.locals + value = locals ? locals[index] : nil + + if nil == value + yield + elsif NULL.equal?(value) + nil + else + value + end + end + + def set(index, value) + locals = self.locals! + locals[index] = (nil == value ? NULL : value) + + value + end + + private + + # When the local goes out of scope, clean up that slot across all locals currently assigned. + def local_finalizer(index) + proc do + free_index(index) + end + end + + # When a thread/fiber goes out of scope, remove the array from @all_arrays. + def thread_fiber_finalizer(array_object_id) + proc do + weak_synchronize do + @all_arrays.delete(array_object_id) + end + end + end + + # Returns the locals for the current scope, or nil if none exist. + def locals + raise NotImplementedError + end + + # Returns the locals for the current scope, creating them if necessary. + def locals! + raise NotImplementedError + end + end + + # @!visibility private + # @!macro internal_implementation_note + # An array-backed storage of indexed variables per thread. + class ThreadLocals < AbstractLocals + def locals + Thread.current.thread_variable_get(:concurrent_thread_locals) + end + + def locals! + thread = Thread.current + locals = thread.thread_variable_get(:concurrent_thread_locals) + + unless locals + locals = thread.thread_variable_set(:concurrent_thread_locals, []) + weak_synchronize do + @all_arrays[locals.object_id] = locals + end + # When the thread goes out of scope, we should delete the associated locals: + ObjectSpace.define_finalizer(thread, thread_fiber_finalizer(locals.object_id)) + end + + locals + end + end + + # @!visibility private + # @!macro internal_implementation_note + # An array-backed storage of indexed variables per fiber. + class FiberLocals < AbstractLocals + def locals + Thread.current[:concurrent_fiber_locals] + end + + def locals! + thread = Thread.current + locals = thread[:concurrent_fiber_locals] + + unless locals + locals = thread[:concurrent_fiber_locals] = [] + weak_synchronize do + @all_arrays[locals.object_id] = locals + end + # When the fiber goes out of scope, we should delete the associated locals: + ObjectSpace.define_finalizer(Fiber.current, thread_fiber_finalizer(locals.object_id)) + end + + locals + end + end + + private_constant :AbstractLocals, :ThreadLocals, :FiberLocals +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/lock_local_var.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/lock_local_var.rb new file mode 100644 index 0000000000..ebf23a2414 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/lock_local_var.rb @@ -0,0 +1,28 @@ +require 'concurrent/utility/engine' +require_relative 'fiber_local_var' +require_relative 'thread_local_var' + +module Concurrent + # @!visibility private + def self.mutex_owned_per_thread? + return false if Concurrent.on_jruby? || Concurrent.on_truffleruby? + + mutex = Mutex.new + # Lock the mutex: + mutex.synchronize do + # Check if the mutex is still owned in a child fiber: + Fiber.new { mutex.owned? }.resume + end + end + + if mutex_owned_per_thread? + LockLocalVar = ThreadLocalVar + else + LockLocalVar = FiberLocalVar + end + + # Either {FiberLocalVar} or {ThreadLocalVar} depending on whether Mutex (and Monitor) + # are held, respectively, per Fiber or per Thread. + class LockLocalVar + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_atomic_boolean.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_atomic_boolean.rb new file mode 100644 index 0000000000..015996b06f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_atomic_boolean.rb @@ -0,0 +1,68 @@ +require 'concurrent/synchronization/safe_initialization' + +module Concurrent + + # @!macro atomic_boolean + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicBoolean + extend Concurrent::Synchronization::SafeInitialization + + # @!macro atomic_boolean_method_initialize + def initialize(initial = false) + super() + @Lock = ::Mutex.new + @value = !!initial + end + + # @!macro atomic_boolean_method_value_get + def value + synchronize { @value } + end + + # @!macro atomic_boolean_method_value_set + def value=(value) + synchronize { @value = !!value } + end + + # @!macro atomic_boolean_method_true_question + def true? + synchronize { @value } + end + + # @!macro atomic_boolean_method_false_question + def false? + synchronize { !@value } + end + + # @!macro atomic_boolean_method_make_true + def make_true + synchronize { ns_make_value(true) } + end + + # @!macro atomic_boolean_method_make_false + def make_false + synchronize { ns_make_value(false) } + end + + protected + + # @!visibility private + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end + end + + private + + # @!visibility private + def ns_make_value(value) + old = @value + @value = value + old != @value + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_atomic_fixnum.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_atomic_fixnum.rb new file mode 100644 index 0000000000..0ca395579f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_atomic_fixnum.rb @@ -0,0 +1,81 @@ +require 'concurrent/synchronization/safe_initialization' +require 'concurrent/utility/native_integer' + +module Concurrent + + # @!macro atomic_fixnum + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicFixnum + extend Concurrent::Synchronization::SafeInitialization + + # @!macro atomic_fixnum_method_initialize + def initialize(initial = 0) + super() + @Lock = ::Mutex.new + ns_set(initial) + end + + # @!macro atomic_fixnum_method_value_get + def value + synchronize { @value } + end + + # @!macro atomic_fixnum_method_value_set + def value=(value) + synchronize { ns_set(value) } + end + + # @!macro atomic_fixnum_method_increment + def increment(delta = 1) + synchronize { ns_set(@value + delta.to_i) } + end + + alias_method :up, :increment + + # @!macro atomic_fixnum_method_decrement + def decrement(delta = 1) + synchronize { ns_set(@value - delta.to_i) } + end + + alias_method :down, :decrement + + # @!macro atomic_fixnum_method_compare_and_set + def compare_and_set(expect, update) + synchronize do + if @value == expect.to_i + @value = update.to_i + true + else + false + end + end + end + + # @!macro atomic_fixnum_method_update + def update + synchronize do + @value = yield @value + end + end + + protected + + # @!visibility private + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end + end + + private + + # @!visibility private + def ns_set(value) + Utility::NativeInteger.ensure_integer_and_bounds value + @value = value + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_count_down_latch.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_count_down_latch.rb new file mode 100644 index 0000000000..29aa1caa4f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_count_down_latch.rb @@ -0,0 +1,44 @@ +require 'concurrent/synchronization/lockable_object' +require 'concurrent/utility/native_integer' + +module Concurrent + + # @!macro count_down_latch + # @!visibility private + # @!macro internal_implementation_note + class MutexCountDownLatch < Synchronization::LockableObject + + # @!macro count_down_latch_method_initialize + def initialize(count = 1) + Utility::NativeInteger.ensure_integer_and_bounds count + Utility::NativeInteger.ensure_positive count + + super() + synchronize { ns_initialize count } + end + + # @!macro count_down_latch_method_wait + def wait(timeout = nil) + synchronize { ns_wait_until(timeout) { @count == 0 } } + end + + # @!macro count_down_latch_method_count_down + def count_down + synchronize do + @count -= 1 if @count > 0 + ns_broadcast if @count == 0 + end + end + + # @!macro count_down_latch_method_count + def count + synchronize { @count } + end + + protected + + def ns_initialize(count) + @count = count + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_semaphore.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_semaphore.rb new file mode 100644 index 0000000000..4347289f1e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/mutex_semaphore.rb @@ -0,0 +1,131 @@ +require 'concurrent/synchronization/lockable_object' +require 'concurrent/utility/native_integer' + +module Concurrent + + # @!macro semaphore + # @!visibility private + # @!macro internal_implementation_note + class MutexSemaphore < Synchronization::LockableObject + + # @!macro semaphore_method_initialize + def initialize(count) + Utility::NativeInteger.ensure_integer_and_bounds count + + super() + synchronize { ns_initialize count } + end + + # @!macro semaphore_method_acquire + def acquire(permits = 1) + Utility::NativeInteger.ensure_integer_and_bounds permits + Utility::NativeInteger.ensure_positive permits + + synchronize do + try_acquire_timed(permits, nil) + end + + return unless block_given? + + begin + yield + ensure + release(permits) + end + end + + # @!macro semaphore_method_available_permits + def available_permits + synchronize { @free } + end + + # @!macro semaphore_method_drain_permits + # + # Acquires and returns all permits that are immediately available. + # + # @return [Integer] + def drain_permits + synchronize do + @free.tap { |_| @free = 0 } + end + end + + # @!macro semaphore_method_try_acquire + def try_acquire(permits = 1, timeout = nil) + Utility::NativeInteger.ensure_integer_and_bounds permits + Utility::NativeInteger.ensure_positive permits + + acquired = synchronize do + if timeout.nil? + try_acquire_now(permits) + else + try_acquire_timed(permits, timeout) + end + end + + return acquired unless block_given? + return unless acquired + + begin + yield + ensure + release(permits) + end + end + + # @!macro semaphore_method_release + def release(permits = 1) + Utility::NativeInteger.ensure_integer_and_bounds permits + Utility::NativeInteger.ensure_positive permits + + synchronize do + @free += permits + permits.times { ns_signal } + end + nil + end + + # Shrinks the number of available permits by the indicated reduction. + # + # @param [Fixnum] reduction Number of permits to remove. + # + # @raise [ArgumentError] if `reduction` is not an integer or is negative + # + # @raise [ArgumentError] if `@free` - `@reduction` is less than zero + # + # @return [nil] + # + # @!visibility private + def reduce_permits(reduction) + Utility::NativeInteger.ensure_integer_and_bounds reduction + Utility::NativeInteger.ensure_positive reduction + + synchronize { @free -= reduction } + nil + end + + protected + + # @!visibility private + def ns_initialize(count) + @free = count + end + + private + + # @!visibility private + def try_acquire_now(permits) + if @free >= permits + @free -= permits + true + else + false + end + end + + # @!visibility private + def try_acquire_timed(permits, timeout) + ns_wait_until(timeout) { try_acquire_now(permits) } + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/read_write_lock.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/read_write_lock.rb new file mode 100644 index 0000000000..b26bd17a08 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/read_write_lock.rb @@ -0,0 +1,255 @@ +require 'thread' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/errors' +require 'concurrent/synchronization/object' +require 'concurrent/synchronization/lock' + +module Concurrent + + # Ruby read-write lock implementation + # + # Allows any number of concurrent readers, but only one concurrent writer + # (And if the "write" lock is taken, any readers who come along will have to wait) + # + # If readers are already active when a writer comes along, the writer will wait for + # all the readers to finish before going ahead. + # Any additional readers that come when the writer is already waiting, will also + # wait (so writers are not starved). + # + # This implementation is based on `java.util.concurrent.ReentrantReadWriteLock`. + # + # @example + # lock = Concurrent::ReadWriteLock.new + # lock.with_read_lock { data.retrieve } + # lock.with_write_lock { data.modify! } + # + # @note Do **not** try to acquire the write lock while already holding a read lock + # **or** try to acquire the write lock while you already have it. + # This will lead to deadlock + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock + class ReadWriteLock < Synchronization::Object + + # @!visibility private + WAITING_WRITER = 1 << 15 + + # @!visibility private + RUNNING_WRITER = 1 << 29 + + # @!visibility private + MAX_READERS = WAITING_WRITER - 1 + + # @!visibility private + MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1 + + safe_initialization! + + # Implementation notes: + # A goal is to make the uncontended path for both readers/writers lock-free + # Only if there is reader-writer or writer-writer contention, should locks be used + # Internal state is represented by a single integer ("counter"), and updated + # using atomic compare-and-swap operations + # When the counter is 0, the lock is free + # Each reader increments the counter by 1 when acquiring a read lock + # (and decrements by 1 when releasing the read lock) + # The counter is increased by (1 << 15) for each writer waiting to acquire the + # write lock, and by (1 << 29) if the write lock is taken + + # Create a new `ReadWriteLock` in the unlocked state. + def initialize + super() + @Counter = AtomicFixnum.new(0) # single integer which represents lock state + @ReadLock = Synchronization::Lock.new + @WriteLock = Synchronization::Lock.new + end + + # Execute a block operation within a read lock. + # + # @yield the task to be performed within the lock. + # + # @return [Object] the result of the block operation. + # + # @raise [ArgumentError] when no block is given. + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def with_read_lock + raise ArgumentError.new('no block given') unless block_given? + acquire_read_lock + begin + yield + ensure + release_read_lock + end + end + + # Execute a block operation within a write lock. + # + # @yield the task to be performed within the lock. + # + # @return [Object] the result of the block operation. + # + # @raise [ArgumentError] when no block is given. + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def with_write_lock + raise ArgumentError.new('no block given') unless block_given? + acquire_write_lock + begin + yield + ensure + release_write_lock + end + end + + # Acquire a read lock. If a write lock has been acquired will block until + # it is released. Will not block if other read locks have been acquired. + # + # @return [Boolean] true if the lock is successfully acquired + # + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def acquire_read_lock + while true + c = @Counter.value + raise ResourceLimitError.new('Too many reader threads') if max_readers?(c) + + # If a writer is waiting when we first queue up, we need to wait + if waiting_writer?(c) + @ReadLock.wait_until { !waiting_writer? } + + # after a reader has waited once, they are allowed to "barge" ahead of waiting writers + # but if a writer is *running*, the reader still needs to wait (naturally) + while true + c = @Counter.value + if running_writer?(c) + @ReadLock.wait_until { !running_writer? } + else + return if @Counter.compare_and_set(c, c+1) + end + end + else + break if @Counter.compare_and_set(c, c+1) + end + end + true + end + + # Release a previously acquired read lock. + # + # @return [Boolean] true if the lock is successfully released + def release_read_lock + while true + c = @Counter.value + if @Counter.compare_and_set(c, c-1) + # If one or more writers were waiting, and we were the last reader, wake a writer up + if waiting_writer?(c) && running_readers(c) == 1 + @WriteLock.signal + end + break + end + end + true + end + + # Acquire a write lock. Will block and wait for all active readers and writers. + # + # @return [Boolean] true if the lock is successfully acquired + # + # @raise [Concurrent::ResourceLimitError] if the maximum number of writers + # is exceeded. + def acquire_write_lock + while true + c = @Counter.value + raise ResourceLimitError.new('Too many writer threads') if max_writers?(c) + + if c == 0 # no readers OR writers running + # if we successfully swap the RUNNING_WRITER bit on, then we can go ahead + break if @Counter.compare_and_set(0, RUNNING_WRITER) + elsif @Counter.compare_and_set(c, c+WAITING_WRITER) + while true + # Now we have successfully incremented, so no more readers will be able to increment + # (they will wait instead) + # However, readers OR writers could decrement right here, OR another writer could increment + @WriteLock.wait_until do + # So we have to do another check inside the synchronized section + # If a writer OR reader is running, then go to sleep + c = @Counter.value + !running_writer?(c) && !running_readers?(c) + end + + # We just came out of a wait + # If we successfully turn the RUNNING_WRITER bit on with an atomic swap, + # Then we are OK to stop waiting and go ahead + # Otherwise go back and wait again + c = @Counter.value + break if !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER) + end + break + end + end + true + end + + # Release a previously acquired write lock. + # + # @return [Boolean] true if the lock is successfully released + def release_write_lock + return true unless running_writer? + c = @Counter.update { |counter| counter - RUNNING_WRITER } + @ReadLock.broadcast + @WriteLock.signal if waiting_writers(c) > 0 + true + end + + # Queries if the write lock is held by any thread. + # + # @return [Boolean] true if the write lock is held else false` + def write_locked? + @Counter.value >= RUNNING_WRITER + end + + # Queries whether any threads are waiting to acquire the read or write lock. + # + # @return [Boolean] true if any threads are waiting for a lock else false + def has_waiters? + waiting_writer?(@Counter.value) + end + + private + + # @!visibility private + def running_readers(c = @Counter.value) + c & MAX_READERS + end + + # @!visibility private + def running_readers?(c = @Counter.value) + (c & MAX_READERS) > 0 + end + + # @!visibility private + def running_writer?(c = @Counter.value) + c >= RUNNING_WRITER + end + + # @!visibility private + def waiting_writers(c = @Counter.value) + (c & MAX_WRITERS) / WAITING_WRITER + end + + # @!visibility private + def waiting_writer?(c = @Counter.value) + c >= WAITING_WRITER + end + + # @!visibility private + def max_readers?(c = @Counter.value) + (c & MAX_READERS) == MAX_READERS + end + + # @!visibility private + def max_writers?(c = @Counter.value) + (c & MAX_WRITERS) == MAX_WRITERS + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/reentrant_read_write_lock.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/reentrant_read_write_lock.rb new file mode 100644 index 0000000000..6d72a3a097 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/reentrant_read_write_lock.rb @@ -0,0 +1,379 @@ +require 'thread' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/errors' +require 'concurrent/synchronization/object' +require 'concurrent/synchronization/lock' +require 'concurrent/atomic/lock_local_var' + +module Concurrent + + # Re-entrant read-write lock implementation + # + # Allows any number of concurrent readers, but only one concurrent writer + # (And while the "write" lock is taken, no read locks can be obtained either. + # Hence, the write lock can also be called an "exclusive" lock.) + # + # If another thread has taken a read lock, any thread which wants a write lock + # will block until all the readers release their locks. However, once a thread + # starts waiting to obtain a write lock, any additional readers that come along + # will also wait (so writers are not starved). + # + # A thread can acquire both a read and write lock at the same time. A thread can + # also acquire a read lock OR a write lock more than once. Only when the read (or + # write) lock is released as many times as it was acquired, will the thread + # actually let it go, allowing other threads which might have been waiting + # to proceed. Therefore the lock can be upgraded by first acquiring + # read lock and then write lock and that the lock can be downgraded by first + # having both read and write lock a releasing just the write lock. + # + # If both read and write locks are acquired by the same thread, it is not strictly + # necessary to release them in the same order they were acquired. In other words, + # the following code is legal: + # + # @example + # lock = Concurrent::ReentrantReadWriteLock.new + # lock.acquire_write_lock + # lock.acquire_read_lock + # lock.release_write_lock + # # At this point, the current thread is holding only a read lock, not a write + # # lock. So other threads can take read locks, but not a write lock. + # lock.release_read_lock + # # Now the current thread is not holding either a read or write lock, so + # # another thread could potentially acquire a write lock. + # + # This implementation was inspired by `java.util.concurrent.ReentrantReadWriteLock`. + # + # @example + # lock = Concurrent::ReentrantReadWriteLock.new + # lock.with_read_lock { data.retrieve } + # lock.with_write_lock { data.modify! } + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock + class ReentrantReadWriteLock < Synchronization::Object + + # Implementation notes: + # + # A goal is to make the uncontended path for both readers/writers mutex-free + # Only if there is reader-writer or writer-writer contention, should mutexes be used + # Otherwise, a single CAS operation is all we need to acquire/release a lock + # + # Internal state is represented by a single integer ("counter"), and updated + # using atomic compare-and-swap operations + # When the counter is 0, the lock is free + # Each thread which has one OR MORE read locks increments the counter by 1 + # (and decrements by 1 when releasing the read lock) + # The counter is increased by (1 << 15) for each writer waiting to acquire the + # write lock, and by (1 << 29) if the write lock is taken + # + # Additionally, each thread uses a thread-local variable to count how many times + # it has acquired a read lock, AND how many times it has acquired a write lock. + # It uses a similar trick; an increment of 1 means a read lock was taken, and + # an increment of (1 << 15) means a write lock was taken + # This is what makes re-entrancy possible + # + # 2 rules are followed to ensure good liveness properties: + # 1) Once a writer has queued up and is waiting for a write lock, no other thread + # can take a lock without waiting + # 2) When a write lock is released, readers are given the "first chance" to wake + # up and acquire a read lock + # Following these rules means readers and writers tend to "take turns", so neither + # can starve the other, even under heavy contention + + # @!visibility private + READER_BITS = 15 + # @!visibility private + WRITER_BITS = 14 + + # Used with @Counter: + # @!visibility private + WAITING_WRITER = 1 << READER_BITS + # @!visibility private + RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS) + # @!visibility private + MAX_READERS = WAITING_WRITER - 1 + # @!visibility private + MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1 + + # Used with @HeldCount: + # @!visibility private + WRITE_LOCK_HELD = 1 << READER_BITS + # @!visibility private + READ_LOCK_MASK = WRITE_LOCK_HELD - 1 + # @!visibility private + WRITE_LOCK_MASK = MAX_WRITERS + + safe_initialization! + + # Create a new `ReentrantReadWriteLock` in the unlocked state. + def initialize + super() + @Counter = AtomicFixnum.new(0) # single integer which represents lock state + @ReadQueue = Synchronization::Lock.new # used to queue waiting readers + @WriteQueue = Synchronization::Lock.new # used to queue waiting writers + @HeldCount = LockLocalVar.new(0) # indicates # of R & W locks held by this thread + end + + # Execute a block operation within a read lock. + # + # @yield the task to be performed within the lock. + # + # @return [Object] the result of the block operation. + # + # @raise [ArgumentError] when no block is given. + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def with_read_lock + raise ArgumentError.new('no block given') unless block_given? + acquire_read_lock + begin + yield + ensure + release_read_lock + end + end + + # Execute a block operation within a write lock. + # + # @yield the task to be performed within the lock. + # + # @return [Object] the result of the block operation. + # + # @raise [ArgumentError] when no block is given. + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def with_write_lock + raise ArgumentError.new('no block given') unless block_given? + acquire_write_lock + begin + yield + ensure + release_write_lock + end + end + + # Acquire a read lock. If a write lock is held by another thread, will block + # until it is released. + # + # @return [Boolean] true if the lock is successfully acquired + # + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def acquire_read_lock + if (held = @HeldCount.value) > 0 + # If we already have a lock, there's no need to wait + if held & READ_LOCK_MASK == 0 + # But we do need to update the counter, if we were holding a write + # lock but not a read lock + @Counter.update { |c| c + 1 } + end + @HeldCount.value = held + 1 + return true + end + + while true + c = @Counter.value + raise ResourceLimitError.new('Too many reader threads') if max_readers?(c) + + # If a writer is waiting OR running when we first queue up, we need to wait + if waiting_or_running_writer?(c) + # Before going to sleep, check again with the ReadQueue mutex held + @ReadQueue.synchronize do + @ReadQueue.ns_wait if waiting_or_running_writer? + end + # Note: the above 'synchronize' block could have used #wait_until, + # but that waits repeatedly in a loop, checking the wait condition + # each time it wakes up (to protect against spurious wakeups) + # But we are already in a loop, which is only broken when we successfully + # acquire the lock! So we don't care about spurious wakeups, and would + # rather not pay the extra overhead of using #wait_until + + # After a reader has waited once, they are allowed to "barge" ahead of waiting writers + # But if a writer is *running*, the reader still needs to wait (naturally) + while true + c = @Counter.value + if running_writer?(c) + @ReadQueue.synchronize do + @ReadQueue.ns_wait if running_writer? + end + elsif @Counter.compare_and_set(c, c+1) + @HeldCount.value = held + 1 + return true + end + end + elsif @Counter.compare_and_set(c, c+1) + @HeldCount.value = held + 1 + return true + end + end + end + + # Try to acquire a read lock and return true if we succeed. If it cannot be + # acquired immediately, return false. + # + # @return [Boolean] true if the lock is successfully acquired + def try_read_lock + if (held = @HeldCount.value) > 0 + if held & READ_LOCK_MASK == 0 + # If we hold a write lock, but not a read lock... + @Counter.update { |c| c + 1 } + end + @HeldCount.value = held + 1 + return true + else + c = @Counter.value + if !waiting_or_running_writer?(c) && @Counter.compare_and_set(c, c+1) + @HeldCount.value = held + 1 + return true + end + end + false + end + + # Release a previously acquired read lock. + # + # @return [Boolean] true if the lock is successfully released + def release_read_lock + held = @HeldCount.value = @HeldCount.value - 1 + rlocks_held = held & READ_LOCK_MASK + if rlocks_held == 0 + c = @Counter.update { |counter| counter - 1 } + # If one or more writers were waiting, and we were the last reader, wake a writer up + if waiting_or_running_writer?(c) && running_readers(c) == 0 + @WriteQueue.signal + end + elsif rlocks_held == READ_LOCK_MASK + raise IllegalOperationError, "Cannot release a read lock which is not held" + end + true + end + + # Acquire a write lock. Will block and wait for all active readers and writers. + # + # @return [Boolean] true if the lock is successfully acquired + # + # @raise [Concurrent::ResourceLimitError] if the maximum number of writers + # is exceeded. + def acquire_write_lock + if (held = @HeldCount.value) >= WRITE_LOCK_HELD + # if we already have a write (exclusive) lock, there's no need to wait + @HeldCount.value = held + WRITE_LOCK_HELD + return true + end + + while true + c = @Counter.value + raise ResourceLimitError.new('Too many writer threads') if max_writers?(c) + + # To go ahead and take the lock without waiting, there must be no writer + # running right now, AND no writers who came before us still waiting to + # acquire the lock + # Additionally, if any read locks have been taken, we must hold all of them + if held > 0 && @Counter.compare_and_set(1, c+RUNNING_WRITER) + # If we are the only one reader and successfully swap the RUNNING_WRITER bit on, then we can go ahead + @HeldCount.value = held + WRITE_LOCK_HELD + return true + elsif @Counter.compare_and_set(c, c+WAITING_WRITER) + while true + # Now we have successfully incremented, so no more readers will be able to increment + # (they will wait instead) + # However, readers OR writers could decrement right here + @WriteQueue.synchronize do + # So we have to do another check inside the synchronized section + # If a writer OR another reader is running, then go to sleep + c = @Counter.value + @WriteQueue.ns_wait if running_writer?(c) || running_readers(c) != held + end + # Note: if you are thinking of replacing the above 'synchronize' block + # with #wait_until, read the comment in #acquire_read_lock first! + + # We just came out of a wait + # If we successfully turn the RUNNING_WRITER bit on with an atomic swap, + # then we are OK to stop waiting and go ahead + # Otherwise go back and wait again + c = @Counter.value + if !running_writer?(c) && + running_readers(c) == held && + @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER) + @HeldCount.value = held + WRITE_LOCK_HELD + return true + end + end + end + end + end + + # Try to acquire a write lock and return true if we succeed. If it cannot be + # acquired immediately, return false. + # + # @return [Boolean] true if the lock is successfully acquired + def try_write_lock + if (held = @HeldCount.value) >= WRITE_LOCK_HELD + @HeldCount.value = held + WRITE_LOCK_HELD + return true + else + c = @Counter.value + if !waiting_or_running_writer?(c) && + running_readers(c) == held && + @Counter.compare_and_set(c, c+RUNNING_WRITER) + @HeldCount.value = held + WRITE_LOCK_HELD + return true + end + end + false + end + + # Release a previously acquired write lock. + # + # @return [Boolean] true if the lock is successfully released + def release_write_lock + held = @HeldCount.value = @HeldCount.value - WRITE_LOCK_HELD + wlocks_held = held & WRITE_LOCK_MASK + if wlocks_held == 0 + c = @Counter.update { |counter| counter - RUNNING_WRITER } + @ReadQueue.broadcast + @WriteQueue.signal if waiting_writers(c) > 0 + elsif wlocks_held == WRITE_LOCK_MASK + raise IllegalOperationError, "Cannot release a write lock which is not held" + end + true + end + + private + + # @!visibility private + def running_readers(c = @Counter.value) + c & MAX_READERS + end + + # @!visibility private + def running_readers?(c = @Counter.value) + (c & MAX_READERS) > 0 + end + + # @!visibility private + def running_writer?(c = @Counter.value) + c >= RUNNING_WRITER + end + + # @!visibility private + def waiting_writers(c = @Counter.value) + (c & MAX_WRITERS) >> READER_BITS + end + + # @!visibility private + def waiting_or_running_writer?(c = @Counter.value) + c >= WAITING_WRITER + end + + # @!visibility private + def max_readers?(c = @Counter.value) + (c & MAX_READERS) == MAX_READERS + end + + # @!visibility private + def max_writers?(c = @Counter.value) + (c & MAX_WRITERS) == MAX_WRITERS + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/semaphore.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/semaphore.rb new file mode 100644 index 0000000000..f0799f0f41 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/semaphore.rb @@ -0,0 +1,163 @@ +require 'concurrent/atomic/mutex_semaphore' + +module Concurrent + + ################################################################### + + # @!macro semaphore_method_initialize + # + # Create a new `Semaphore` with the initial `count`. + # + # @param [Fixnum] count the initial count + # + # @raise [ArgumentError] if `count` is not an integer + + # @!macro semaphore_method_acquire + # + # Acquires the given number of permits from this semaphore, + # blocking until all are available. If a block is given, + # yields to it and releases the permits afterwards. + # + # @param [Fixnum] permits Number of permits to acquire + # + # @raise [ArgumentError] if `permits` is not an integer or is less than zero + # + # @return [nil, BasicObject] Without a block, `nil` is returned. If a block + # is given, its return value is returned. + + # @!macro semaphore_method_available_permits + # + # Returns the current number of permits available in this semaphore. + # + # @return [Integer] + + # @!macro semaphore_method_drain_permits + # + # Acquires and returns all permits that are immediately available. + # + # @return [Integer] + + # @!macro semaphore_method_try_acquire + # + # Acquires the given number of permits from this semaphore, + # only if all are available at the time of invocation or within + # `timeout` interval. If a block is given, yields to it if the permits + # were successfully acquired, and releases them afterward, returning the + # block's return value. + # + # @param [Fixnum] permits the number of permits to acquire + # + # @param [Fixnum] timeout the number of seconds to wait for the counter + # or `nil` to return immediately + # + # @raise [ArgumentError] if `permits` is not an integer or is less than zero + # + # @return [true, false, nil, BasicObject] `false` if no permits are + # available, `true` when acquired a permit. If a block is given, the + # block's return value is returned if the permits were acquired; if not, + # `nil` is returned. + + # @!macro semaphore_method_release + # + # Releases the given number of permits, returning them to the semaphore. + # + # @param [Fixnum] permits Number of permits to return to the semaphore. + # + # @raise [ArgumentError] if `permits` is not a number or is less than zero + # + # @return [nil] + + ################################################################### + + # @!macro semaphore_public_api + # + # @!method initialize(count) + # @!macro semaphore_method_initialize + # + # @!method acquire(permits = 1) + # @!macro semaphore_method_acquire + # + # @!method available_permits + # @!macro semaphore_method_available_permits + # + # @!method drain_permits + # @!macro semaphore_method_drain_permits + # + # @!method try_acquire(permits = 1, timeout = nil) + # @!macro semaphore_method_try_acquire + # + # @!method release(permits = 1) + # @!macro semaphore_method_release + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + SemaphoreImplementation = if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + JavaSemaphore + else + MutexSemaphore + end + private_constant :SemaphoreImplementation + + # @!macro semaphore + # + # A counting semaphore. Conceptually, a semaphore maintains a set of + # permits. Each {#acquire} blocks if necessary until a permit is + # available, and then takes it. Each {#release} adds a permit, potentially + # releasing a blocking acquirer. + # However, no actual permit objects are used; the Semaphore just keeps a + # count of the number available and acts accordingly. + # Alternatively, permits may be acquired within a block, and automatically + # released after the block finishes executing. + # + # @!macro semaphore_public_api + # @example + # semaphore = Concurrent::Semaphore.new(2) + # + # t1 = Thread.new do + # semaphore.acquire + # puts "Thread 1 acquired semaphore" + # end + # + # t2 = Thread.new do + # semaphore.acquire + # puts "Thread 2 acquired semaphore" + # end + # + # t3 = Thread.new do + # semaphore.acquire + # puts "Thread 3 acquired semaphore" + # end + # + # t4 = Thread.new do + # sleep(2) + # puts "Thread 4 releasing semaphore" + # semaphore.release + # end + # + # [t1, t2, t3, t4].each(&:join) + # + # # prints: + # # Thread 3 acquired semaphore + # # Thread 2 acquired semaphore + # # Thread 4 releasing semaphore + # # Thread 1 acquired semaphore + # + # @example + # semaphore = Concurrent::Semaphore.new(1) + # + # puts semaphore.available_permits + # semaphore.acquire do + # puts semaphore.available_permits + # end + # puts semaphore.available_permits + # + # # prints: + # # 1 + # # 0 + # # 1 + class Semaphore < SemaphoreImplementation + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/thread_local_var.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/thread_local_var.rb new file mode 100644 index 0000000000..3b7e12b5bb --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic/thread_local_var.rb @@ -0,0 +1,111 @@ +require 'concurrent/constants' +require_relative 'locals' + +module Concurrent + + # A `ThreadLocalVar` is a variable where the value is different for each thread. + # Each variable may have a default value, but when you modify the variable only + # the current thread will ever see that change. + # + # This is similar to Ruby's built-in thread-local variables (`Thread#thread_variable_get`), + # but with these major advantages: + # * `ThreadLocalVar` has its own identity, it doesn't need a Symbol. + # * Each Ruby's built-in thread-local variable leaks some memory forever (it's a Symbol held forever on the thread), + # so it's only OK to create a small amount of them. + # `ThreadLocalVar` has no such issue and it is fine to create many of them. + # * Ruby's built-in thread-local variables leak forever the value set on each thread (unless set to nil explicitly). + # `ThreadLocalVar` automatically removes the mapping for each thread once the `ThreadLocalVar` instance is GC'd. + # + # @!macro thread_safe_variable_comparison + # + # @example + # v = ThreadLocalVar.new(14) + # v.value #=> 14 + # v.value = 2 + # v.value #=> 2 + # + # @example + # v = ThreadLocalVar.new(14) + # + # t1 = Thread.new do + # v.value #=> 14 + # v.value = 1 + # v.value #=> 1 + # end + # + # t2 = Thread.new do + # v.value #=> 14 + # v.value = 2 + # v.value #=> 2 + # end + # + # v.value #=> 14 + class ThreadLocalVar + LOCALS = ThreadLocals.new + + # Creates a thread local variable. + # + # @param [Object] default the default value when otherwise unset + # @param [Proc] default_block Optional block that gets called to obtain the + # default value for each thread + def initialize(default = nil, &default_block) + if default && block_given? + raise ArgumentError, "Cannot use both value and block as default value" + end + + if block_given? + @default_block = default_block + @default = nil + else + @default_block = nil + @default = default + end + + @index = LOCALS.next_index(self) + end + + # Returns the value in the current thread's copy of this thread-local variable. + # + # @return [Object] the current value + def value + LOCALS.fetch(@index) { default } + end + + # Sets the current thread's copy of this thread-local variable to the specified value. + # + # @param [Object] value the value to set + # @return [Object] the new value + def value=(value) + LOCALS.set(@index, value) + end + + # Bind the given value to thread local storage during + # execution of the given block. + # + # @param [Object] value the value to bind + # @yield the operation to be performed with the bound variable + # @return [Object] the value + def bind(value) + if block_given? + old_value = self.value + self.value = value + begin + yield + ensure + self.value = old_value + end + end + end + + protected + + # @!visibility private + def default + if @default_block + self.value = @default_block.call + else + @default + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/atomic_direct_update.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/atomic_direct_update.rb new file mode 100644 index 0000000000..5d2d7edd4f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/atomic_direct_update.rb @@ -0,0 +1,37 @@ +require 'concurrent/errors' + +module Concurrent + + # Define update methods that use direct paths + # + # @!visibility private + # @!macro internal_implementation_note + module AtomicDirectUpdate + def update + true until compare_and_set(old_value = get, new_value = yield(old_value)) + new_value + end + + def try_update + old_value = get + new_value = yield old_value + + return unless compare_and_set old_value, new_value + + new_value + end + + def try_update! + old_value = get + new_value = yield old_value + unless compare_and_set(old_value, new_value) + if $VERBOSE + raise ConcurrentUpdateError, "Update failed" + else + raise ConcurrentUpdateError, "Update failed", ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE + end + end + new_value + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/mutex_atomic.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/mutex_atomic.rb new file mode 100644 index 0000000000..e5e2a6377d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/mutex_atomic.rb @@ -0,0 +1,67 @@ +require 'concurrent/atomic_reference/atomic_direct_update' +require 'concurrent/atomic_reference/numeric_cas_wrapper' +require 'concurrent/synchronization/safe_initialization' + +module Concurrent + + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicReference + extend Concurrent::Synchronization::SafeInitialization + include AtomicDirectUpdate + include AtomicNumericCompareAndSetWrapper + alias_method :compare_and_swap, :compare_and_set + + # @!macro atomic_reference_method_initialize + def initialize(value = nil) + super() + @Lock = ::Mutex.new + @value = value + end + + # @!macro atomic_reference_method_get + def get + synchronize { @value } + end + alias_method :value, :get + + # @!macro atomic_reference_method_set + def set(new_value) + synchronize { @value = new_value } + end + alias_method :value=, :set + + # @!macro atomic_reference_method_get_and_set + def get_and_set(new_value) + synchronize do + old_value = @value + @value = new_value + old_value + end + end + alias_method :swap, :get_and_set + + # @!macro atomic_reference_method_compare_and_set + def _compare_and_set(old_value, new_value) + synchronize do + if @value.equal? old_value + @value = new_value + true + else + false + end + end + end + + protected + + # @!visibility private + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb new file mode 100644 index 0000000000..709a382231 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb @@ -0,0 +1,28 @@ +module Concurrent + + # Special "compare and set" handling of numeric values. + # + # @!visibility private + # @!macro internal_implementation_note + module AtomicNumericCompareAndSetWrapper + + # @!macro atomic_reference_method_compare_and_set + def compare_and_set(old_value, new_value) + if old_value.kind_of? Numeric + while true + old = get + + return false unless old.kind_of? Numeric + + return false unless old == old_value + + result = _compare_and_set(old, new_value) + return result if result + end + else + _compare_and_set(old_value, new_value) + end + end + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomics.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomics.rb new file mode 100644 index 0000000000..16cbe66101 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/atomics.rb @@ -0,0 +1,10 @@ +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/atomic/cyclic_barrier' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/event' +require 'concurrent/atomic/read_write_lock' +require 'concurrent/atomic/reentrant_read_write_lock' +require 'concurrent/atomic/semaphore' +require 'concurrent/atomic/thread_local_var' diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/copy_on_notify_observer_set.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/copy_on_notify_observer_set.rb new file mode 100644 index 0000000000..7c700bd78a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/copy_on_notify_observer_set.rb @@ -0,0 +1,107 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + module Collection + + # A thread safe observer set implemented using copy-on-read approach: + # observers are added and removed from a thread safe collection; every time + # a notification is required the internal data structure is copied to + # prevent concurrency issues + # + # @api private + class CopyOnNotifyObserverSet < Synchronization::LockableObject + + def initialize + super() + synchronize { ns_initialize } + end + + # @!macro observable_add_observer + def add_observer(observer = nil, func = :update, &block) + if observer.nil? && block.nil? + raise ArgumentError, 'should pass observer as a first argument or block' + elsif observer && block + raise ArgumentError.new('cannot provide both an observer and a block') + end + + if block + observer = block + func = :call + end + + synchronize do + @observers[observer] = func + observer + end + end + + # @!macro observable_delete_observer + def delete_observer(observer) + synchronize do + @observers.delete(observer) + observer + end + end + + # @!macro observable_delete_observers + def delete_observers + synchronize do + @observers.clear + self + end + end + + # @!macro observable_count_observers + def count_observers + synchronize { @observers.count } + end + + # Notifies all registered observers with optional args + # @param [Object] args arguments to be passed to each observer + # @return [CopyOnWriteObserverSet] self + def notify_observers(*args, &block) + observers = duplicate_observers + notify_to(observers, *args, &block) + self + end + + # Notifies all registered observers with optional args and deletes them. + # + # @param [Object] args arguments to be passed to each observer + # @return [CopyOnWriteObserverSet] self + def notify_and_delete_observers(*args, &block) + observers = duplicate_and_clear_observers + notify_to(observers, *args, &block) + self + end + + protected + + def ns_initialize + @observers = {} + end + + private + + def duplicate_and_clear_observers + synchronize do + observers = @observers.dup + @observers.clear + observers + end + end + + def duplicate_observers + synchronize { @observers.dup } + end + + def notify_to(observers, *args) + raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty? + observers.each do |observer, function| + args = yield if block_given? + observer.send(function, *args) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/copy_on_write_observer_set.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/copy_on_write_observer_set.rb new file mode 100644 index 0000000000..bcb6750d41 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/copy_on_write_observer_set.rb @@ -0,0 +1,111 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + module Collection + + # A thread safe observer set implemented using copy-on-write approach: + # every time an observer is added or removed the whole internal data structure is + # duplicated and replaced with a new one. + # + # @api private + class CopyOnWriteObserverSet < Synchronization::LockableObject + + def initialize + super() + synchronize { ns_initialize } + end + + # @!macro observable_add_observer + def add_observer(observer = nil, func = :update, &block) + if observer.nil? && block.nil? + raise ArgumentError, 'should pass observer as a first argument or block' + elsif observer && block + raise ArgumentError.new('cannot provide both an observer and a block') + end + + if block + observer = block + func = :call + end + + synchronize do + new_observers = @observers.dup + new_observers[observer] = func + @observers = new_observers + observer + end + end + + # @!macro observable_delete_observer + def delete_observer(observer) + synchronize do + new_observers = @observers.dup + new_observers.delete(observer) + @observers = new_observers + observer + end + end + + # @!macro observable_delete_observers + def delete_observers + self.observers = {} + self + end + + # @!macro observable_count_observers + def count_observers + observers.count + end + + # Notifies all registered observers with optional args + # @param [Object] args arguments to be passed to each observer + # @return [CopyOnWriteObserverSet] self + def notify_observers(*args, &block) + notify_to(observers, *args, &block) + self + end + + # Notifies all registered observers with optional args and deletes them. + # + # @param [Object] args arguments to be passed to each observer + # @return [CopyOnWriteObserverSet] self + def notify_and_delete_observers(*args, &block) + old = clear_observers_and_return_old + notify_to(old, *args, &block) + self + end + + protected + + def ns_initialize + @observers = {} + end + + private + + def notify_to(observers, *args) + raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty? + observers.each do |observer, function| + args = yield if block_given? + observer.send(function, *args) + end + end + + def observers + synchronize { @observers } + end + + def observers=(new_set) + synchronize { @observers = new_set } + end + + def clear_observers_and_return_old + synchronize do + old_observers = @observers + @observers = {} + old_observers + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/java_non_concurrent_priority_queue.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/java_non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..2be9e4373a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/java_non_concurrent_priority_queue.rb @@ -0,0 +1,84 @@ +if Concurrent.on_jruby? + + module Concurrent + module Collection + + + # @!macro priority_queue + # + # @!visibility private + # @!macro internal_implementation_note + class JavaNonConcurrentPriorityQueue + + # @!macro priority_queue_method_initialize + def initialize(opts = {}) + order = opts.fetch(:order, :max) + if [:min, :low].include?(order) + @queue = java.util.PriorityQueue.new(11) # 11 is the default initial capacity + else + @queue = java.util.PriorityQueue.new(11, java.util.Collections.reverseOrder()) + end + end + + # @!macro priority_queue_method_clear + def clear + @queue.clear + true + end + + # @!macro priority_queue_method_delete + def delete(item) + found = false + while @queue.remove(item) do + found = true + end + found + end + + # @!macro priority_queue_method_empty + def empty? + @queue.size == 0 + end + + # @!macro priority_queue_method_include + def include?(item) + @queue.contains(item) + end + alias_method :has_priority?, :include? + + # @!macro priority_queue_method_length + def length + @queue.size + end + alias_method :size, :length + + # @!macro priority_queue_method_peek + def peek + @queue.peek + end + + # @!macro priority_queue_method_pop + def pop + @queue.poll + end + alias_method :deq, :pop + alias_method :shift, :pop + + # @!macro priority_queue_method_push + def push(item) + raise ArgumentError.new('cannot enqueue nil') if item.nil? + @queue.add(item) + end + alias_method :<<, :push + alias_method :enq, :push + + # @!macro priority_queue_method_from_list + def self.from_list(list, opts = {}) + queue = new(opts) + list.each{|item| queue << item } + queue + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/lock_free_stack.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/lock_free_stack.rb new file mode 100644 index 0000000000..3704410ba0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/lock_free_stack.rb @@ -0,0 +1,160 @@ +require 'concurrent/synchronization/object' + +module Concurrent + + # @!macro warn.edge + class LockFreeStack < Synchronization::Object + + safe_initialization! + + class Node + # TODO (pitr-ch 20-Dec-2016): Could be unified with Stack class? + + # @return [Node] + attr_reader :next_node + + # @return [Object] + attr_reader :value + + # @!visibility private + # allow to nil-ify to free GC when the entry is no longer relevant, not synchronised + attr_writer :value + + def initialize(value, next_node) + @value = value + @next_node = next_node + end + + singleton_class.send :alias_method, :[], :new + end + + # The singleton for empty node + EMPTY = Node[nil, nil] + def EMPTY.next_node + self + end + + attr_atomic(:head) + private :head, :head=, :swap_head, :compare_and_set_head, :update_head + + # @!visibility private + def self.of1(value) + new Node[value, EMPTY] + end + + # @!visibility private + def self.of2(value1, value2) + new Node[value1, Node[value2, EMPTY]] + end + + # @param [Node] head + def initialize(head = EMPTY) + super() + self.head = head + end + + # @param [Node] head + # @return [true, false] + def empty?(head = head()) + head.equal? EMPTY + end + + # @param [Node] head + # @param [Object] value + # @return [true, false] + def compare_and_push(head, value) + compare_and_set_head head, Node[value, head] + end + + # @param [Object] value + # @return [self] + def push(value) + while true + current_head = head + return self if compare_and_set_head current_head, Node[value, current_head] + end + end + + # @return [Node] + def peek + head + end + + # @param [Node] head + # @return [true, false] + def compare_and_pop(head) + compare_and_set_head head, head.next_node + end + + # @return [Object] + def pop + while true + current_head = head + return current_head.value if compare_and_set_head current_head, current_head.next_node + end + end + + # @param [Node] head + # @return [true, false] + def compare_and_clear(head) + compare_and_set_head head, EMPTY + end + + include Enumerable + + # @param [Node] head + # @return [self] + def each(head = nil) + return to_enum(:each, head) unless block_given? + it = head || peek + until it.equal?(EMPTY) + yield it.value + it = it.next_node + end + self + end + + # @return [true, false] + def clear + while true + current_head = head + return false if current_head == EMPTY + return true if compare_and_set_head current_head, EMPTY + end + end + + # @param [Node] head + # @return [true, false] + def clear_if(head) + compare_and_set_head head, EMPTY + end + + # @param [Node] head + # @param [Node] new_head + # @return [true, false] + def replace_if(head, new_head) + compare_and_set_head head, new_head + end + + # @return [self] + # @yield over the cleared stack + # @yieldparam [Object] value + def clear_each(&block) + while true + current_head = head + return self if current_head == EMPTY + if compare_and_set_head current_head, EMPTY + each current_head, &block + return self + end + end + end + + # @return [String] Short string representation. + def to_s + format '%s %s>', super[0..-2], to_a.to_s + end + + alias_method :inspect, :to_s + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/atomic_reference_map_backend.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/atomic_reference_map_backend.rb new file mode 100644 index 0000000000..dc5189389d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/atomic_reference_map_backend.rb @@ -0,0 +1,927 @@ +require 'concurrent/constants' +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/adder' +require 'concurrent/thread_safe/util/cheap_lockable' +require 'concurrent/thread_safe/util/power_of_two_tuple' +require 'concurrent/thread_safe/util/volatile' +require 'concurrent/thread_safe/util/xor_shift_random' + +module Concurrent + + # @!visibility private + module Collection + + # A Ruby port of the Doug Lea's jsr166e.ConcurrentHashMapV8 class version 1.59 + # available in public domain. + # + # Original source code available here: + # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ConcurrentHashMapV8.java?revision=1.59 + # + # The Ruby port skips out the +TreeBin+ (red-black trees for use in bins whose + # size exceeds a threshold). + # + # A hash table supporting full concurrency of retrievals and high expected + # concurrency for updates. However, even though all operations are + # thread-safe, retrieval operations do _not_ entail locking, and there is + # _not_ any support for locking the entire table in a way that prevents all + # access. + # + # Retrieval operations generally do not block, so may overlap with update + # operations. Retrievals reflect the results of the most recently _completed_ + # update operations holding upon their onset. (More formally, an update + # operation for a given key bears a _happens-before_ relation with any (non + # +nil+) retrieval for that key reporting the updated value.) For aggregate + # operations such as +clear()+, concurrent retrievals may reflect insertion or + # removal of only some entries. Similarly, the +each_pair+ iterator yields + # elements reflecting the state of the hash table at some point at or since + # the start of the +each_pair+. Bear in mind that the results of aggregate + # status methods including +size()+ and +empty?+} are typically useful only + # when a map is not undergoing concurrent updates in other threads. Otherwise + # the results of these methods reflect transient states that may be adequate + # for monitoring or estimation purposes, but not for program control. + # + # The table is dynamically expanded when there are too many collisions (i.e., + # keys that have distinct hash codes but fall into the same slot modulo the + # table size), with the expected average effect of maintaining roughly two + # bins per mapping (corresponding to a 0.75 load factor threshold for + # resizing). There may be much variance around this average as mappings are + # added and removed, but overall, this maintains a commonly accepted + # time/space tradeoff for hash tables. However, resizing this or any other + # kind of hash table may be a relatively slow operation. When possible, it is + # a good idea to provide a size estimate as an optional :initial_capacity + # initializer argument. An additional optional :load_factor constructor + # argument provides a further means of customizing initial table capacity by + # specifying the table density to be used in calculating the amount of space + # to allocate for the given number of elements. Note that using many keys with + # exactly the same +hash+ is a sure way to slow down performance of any hash + # table. + # + # ## Design overview + # + # The primary design goal of this hash table is to maintain concurrent + # readability (typically method +[]+, but also iteration and related methods) + # while minimizing update contention. Secondary goals are to keep space + # consumption about the same or better than plain +Hash+, and to support high + # initial insertion rates on an empty table by many threads. + # + # Each key-value mapping is held in a +Node+. The validation-based approach + # explained below leads to a lot of code sprawl because retry-control + # precludes factoring into smaller methods. + # + # The table is lazily initialized to a power-of-two size upon the first + # insertion. Each bin in the table normally contains a list of +Node+s (most + # often, the list has only zero or one +Node+). Table accesses require + # volatile/atomic reads, writes, and CASes. The lists of nodes within bins are + # always accurately traversable under volatile reads, so long as lookups check + # hash code and non-nullness of value before checking key equality. + # + # We use the top two bits of +Node+ hash fields for control purposes -- they + # are available anyway because of addressing constraints. As explained further + # below, these top bits are used as follows: + # + # - 00 - Normal + # - 01 - Locked + # - 11 - Locked and may have a thread waiting for lock + # - 10 - +Node+ is a forwarding node + # + # The lower 28 bits of each +Node+'s hash field contain a the key's hash code, + # except for forwarding nodes, for which the lower bits are zero (and so + # always have hash field == +MOVED+). + # + # Insertion (via +[]=+ or its variants) of the first node in an empty bin is + # performed by just CASing it to the bin. This is by far the most common case + # for put operations under most key/hash distributions. Other update + # operations (insert, delete, and replace) require locks. We do not want to + # waste the space required to associate a distinct lock object with each bin, + # so instead use the first node of a bin list itself as a lock. Blocking + # support for these locks relies +Concurrent::ThreadSafe::Util::CheapLockable. However, we also need a + # +try_lock+ construction, so we overlay these by using bits of the +Node+ + # hash field for lock control (see above), and so normally use builtin + # monitors only for blocking and signalling using + # +cheap_wait+/+cheap_broadcast+ constructions. See +Node#try_await_lock+. + # + # Using the first node of a list as a lock does not by itself suffice though: + # When a node is locked, any update must first validate that it is still the + # first node after locking it, and retry if not. Because new nodes are always + # appended to lists, once a node is first in a bin, it remains first until + # deleted or the bin becomes invalidated (upon resizing). However, operations + # that only conditionally update may inspect nodes until the point of update. + # This is a converse of sorts to the lazy locking technique described by + # Herlihy & Shavit. + # + # The main disadvantage of per-bin locks is that other update operations on + # other nodes in a bin list protected by the same lock can stall, for example + # when user +eql?+ or mapping functions take a long time. However, + # statistically, under random hash codes, this is not a common problem. + # Ideally, the frequency of nodes in bins follows a Poisson distribution + # (http://en.wikipedia.org/wiki/Poisson_distribution) with a parameter of + # about 0.5 on average, given the resizing threshold of 0.75, although with a + # large variance because of resizing granularity. Ignoring variance, the + # expected occurrences of list size k are (exp(-0.5) * pow(0.5, k) / + # factorial(k)). The first values are: + # + # - 0: 0.60653066 + # - 1: 0.30326533 + # - 2: 0.07581633 + # - 3: 0.01263606 + # - 4: 0.00157952 + # - 5: 0.00015795 + # - 6: 0.00001316 + # - 7: 0.00000094 + # - 8: 0.00000006 + # - more: less than 1 in ten million + # + # Lock contention probability for two threads accessing distinct elements is + # roughly 1 / (8 * #elements) under random hashes. + # + # The table is resized when occupancy exceeds a percentage threshold + # (nominally, 0.75, but see below). Only a single thread performs the resize + # (using field +size_control+, to arrange exclusion), but the table otherwise + # remains usable for reads and updates. Resizing proceeds by transferring + # bins, one by one, from the table to the next table. Because we are using + # power-of-two expansion, the elements from each bin must either stay at same + # index, or move with a power of two offset. We eliminate unnecessary node + # creation by catching cases where old nodes can be reused because their next + # fields won't change. On average, only about one-sixth of them need cloning + # when a table doubles. The nodes they replace will be garbage collectable as + # soon as they are no longer referenced by any reader thread that may be in + # the midst of concurrently traversing table. Upon transfer, the old table bin + # contains only a special forwarding node (with hash field +MOVED+) that + # contains the next table as its key. On encountering a forwarding node, + # access and update operations restart, using the new table. + # + # Each bin transfer requires its bin lock. However, unlike other cases, a + # transfer can skip a bin if it fails to acquire its lock, and revisit it + # later. Method +rebuild+ maintains a buffer of TRANSFER_BUFFER_SIZE bins that + # have been skipped because of failure to acquire a lock, and blocks only if + # none are available (i.e., only very rarely). The transfer operation must + # also ensure that all accessible bins in both the old and new table are + # usable by any traversal. When there are no lock acquisition failures, this + # is arranged simply by proceeding from the last bin (+table.size - 1+) up + # towards the first. Upon seeing a forwarding node, traversals arrange to move + # to the new table without revisiting nodes. However, when any node is skipped + # during a transfer, all earlier table bins may have become visible, so are + # initialized with a reverse-forwarding node back to the old table until the + # new ones are established. (This sometimes requires transiently locking a + # forwarding node, which is possible under the above encoding.) These more + # expensive mechanics trigger only when necessary. + # + # The traversal scheme also applies to partial traversals of + # ranges of bins (via an alternate Traverser constructor) + # to support partitioned aggregate operations. Also, read-only + # operations give up if ever forwarded to a null table, which + # provides support for shutdown-style clearing, which is also not + # currently implemented. + # + # Lazy table initialization minimizes footprint until first use. + # + # The element count is maintained using a +Concurrent::ThreadSafe::Util::Adder+, + # which avoids contention on updates but can encounter cache thrashing + # if read too frequently during concurrent access. To avoid reading so + # often, resizing is attempted either when a bin lock is + # contended, or upon adding to a bin already holding two or more + # nodes (checked before adding in the +x_if_absent+ methods, after + # adding in others). Under uniform hash distributions, the + # probability of this occurring at threshold is around 13%, + # meaning that only about 1 in 8 puts check threshold (and after + # resizing, many fewer do so). But this approximation has high + # variance for small table sizes, so we check on any collision + # for sizes <= 64. The bulk putAll operation further reduces + # contention by only committing count updates upon these size + # checks. + # + # @!visibility private + class AtomicReferenceMapBackend + + # @!visibility private + class Table < Concurrent::ThreadSafe::Util::PowerOfTwoTuple + def cas_new_node(i, hash, key, value) + cas(i, nil, Node.new(hash, key, value)) + end + + def try_to_cas_in_computed(i, hash, key) + succeeded = false + new_value = nil + new_node = Node.new(locked_hash = hash | LOCKED, key, NULL) + if cas(i, nil, new_node) + begin + if NULL == (new_value = yield(NULL)) + was_null = true + else + new_node.value = new_value + end + succeeded = true + ensure + volatile_set(i, nil) if !succeeded || was_null + new_node.unlock_via_hash(locked_hash, hash) + end + end + return succeeded, new_value + end + + def try_lock_via_hash(i, node, node_hash) + node.try_lock_via_hash(node_hash) do + yield if volatile_get(i) == node + end + end + + def delete_node_at(i, node, predecessor_node) + if predecessor_node + predecessor_node.next = node.next + else + volatile_set(i, node.next) + end + end + end + + # Key-value entry. Nodes with a hash field of +MOVED+ are special, and do + # not contain user keys or values. Otherwise, keys are never +nil+, and + # +NULL+ +value+ fields indicate that a node is in the process of being + # deleted or created. For purposes of read-only access, a key may be read + # before a value, but can only be used after checking value to be +!= NULL+. + # + # @!visibility private + class Node + extend Concurrent::ThreadSafe::Util::Volatile + attr_volatile :hash, :value, :next + + include Concurrent::ThreadSafe::Util::CheapLockable + + bit_shift = Concurrent::ThreadSafe::Util::FIXNUM_BIT_SIZE - 2 # need 2 bits for ourselves + # Encodings for special uses of Node hash fields. See above for explanation. + MOVED = ('10' << ('0' * bit_shift)).to_i(2) # hash field for forwarding nodes + LOCKED = ('01' << ('0' * bit_shift)).to_i(2) # set/tested only as a bit + WAITING = ('11' << ('0' * bit_shift)).to_i(2) # both bits set/tested together + HASH_BITS = ('00' << ('1' * bit_shift)).to_i(2) # usable bits of normal node hash + + SPIN_LOCK_ATTEMPTS = Concurrent::ThreadSafe::Util::CPU_COUNT > 1 ? Concurrent::ThreadSafe::Util::CPU_COUNT * 2 : 0 + + attr_reader :key + + def initialize(hash, key, value, next_node = nil) + super() + @key = key + self.lazy_set_hash(hash) + self.lazy_set_value(value) + self.next = next_node + end + + # Spins a while if +LOCKED+ bit set and this node is the first of its bin, + # and then sets +WAITING+ bits on hash field and blocks (once) if they are + # still set. It is OK for this method to return even if lock is not + # available upon exit, which enables these simple single-wait mechanics. + # + # The corresponding signalling operation is performed within callers: Upon + # detecting that +WAITING+ has been set when unlocking lock (via a failed + # CAS from non-waiting +LOCKED+ state), unlockers acquire the + # +cheap_synchronize+ lock and perform a +cheap_broadcast+. + def try_await_lock(table, i) + if table && i >= 0 && i < table.size # bounds check, TODO: why are we bounds checking? + spins = SPIN_LOCK_ATTEMPTS + randomizer = base_randomizer = Concurrent::ThreadSafe::Util::XorShiftRandom.get + while equal?(table.volatile_get(i)) && self.class.locked_hash?(my_hash = hash) + if spins >= 0 + if (randomizer = (randomizer >> 1)).even? # spin at random + if (spins -= 1) == 0 + Thread.pass # yield before blocking + else + randomizer = base_randomizer = Concurrent::ThreadSafe::Util::XorShiftRandom.xorshift(base_randomizer) if randomizer.zero? + end + end + elsif cas_hash(my_hash, my_hash | WAITING) + force_acquire_lock(table, i) + break + end + end + end + end + + def key?(key) + @key.eql?(key) + end + + def matches?(key, hash) + pure_hash == hash && key?(key) + end + + def pure_hash + hash & HASH_BITS + end + + def try_lock_via_hash(node_hash = hash) + if cas_hash(node_hash, locked_hash = node_hash | LOCKED) + begin + yield + ensure + unlock_via_hash(locked_hash, node_hash) + end + end + end + + def locked? + self.class.locked_hash?(hash) + end + + def unlock_via_hash(locked_hash, node_hash) + unless cas_hash(locked_hash, node_hash) + self.hash = node_hash + cheap_synchronize { cheap_broadcast } + end + end + + private + def force_acquire_lock(table, i) + cheap_synchronize do + if equal?(table.volatile_get(i)) && (hash & WAITING) == WAITING + cheap_wait + else + cheap_broadcast # possibly won race vs signaller + end + end + end + + class << self + def locked_hash?(hash) + (hash & LOCKED) != 0 + end + end + end + + # shorthands + MOVED = Node::MOVED + LOCKED = Node::LOCKED + WAITING = Node::WAITING + HASH_BITS = Node::HASH_BITS + + NOW_RESIZING = -1 + DEFAULT_CAPACITY = 16 + MAX_CAPACITY = Concurrent::ThreadSafe::Util::MAX_INT + + # The buffer size for skipped bins during transfers. The + # value is arbitrary but should be large enough to avoid + # most locking stalls during resizes. + TRANSFER_BUFFER_SIZE = 32 + + extend Concurrent::ThreadSafe::Util::Volatile + attr_volatile :table, # The array of bins. Lazily initialized upon first insertion. Size is always a power of two. + + # Table initialization and resizing control. When negative, the + # table is being initialized or resized. Otherwise, when table is + # null, holds the initial table size to use upon creation, or 0 + # for default. After initialization, holds the next element count + # value upon which to resize the table. + :size_control + + def initialize(options = nil) + super() + @counter = Concurrent::ThreadSafe::Util::Adder.new + initial_capacity = options && options[:initial_capacity] || DEFAULT_CAPACITY + self.size_control = (capacity = table_size_for(initial_capacity)) > MAX_CAPACITY ? MAX_CAPACITY : capacity + end + + def get_or_default(key, else_value = nil) + hash = key_hash(key) + current_table = table + while current_table + node = current_table.volatile_get_by_hash(hash) + current_table = + while node + if (node_hash = node.hash) == MOVED + break node.key + elsif (node_hash & HASH_BITS) == hash && node.key?(key) && NULL != (value = node.value) + return value + end + node = node.next + end + end + else_value + end + + def [](key) + get_or_default(key) + end + + def key?(key) + get_or_default(key, NULL) != NULL + end + + def []=(key, value) + get_and_set(key, value) + value + end + + def compute_if_absent(key) + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key) { yield } + if succeeded + increment_size + return new_value + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif NULL != (current_value = find_value_in_node_list(node, key, hash, node_hash & HASH_BITS)) + return current_value + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, value = attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) { yield } + return value if succeeded + end + end + end + + def compute_if_present(key) + new_value = nil + internal_replace(key) do |old_value| + if (new_value = yield(NULL == old_value ? nil : old_value)).nil? + NULL + else + new_value + end + end + new_value + end + + def compute(key) + internal_compute(key) do |old_value| + if (new_value = yield(NULL == old_value ? nil : old_value)).nil? + NULL + else + new_value + end + end + end + + def merge_pair(key, value) + internal_compute(key) do |old_value| + if NULL == old_value || !(value = yield(old_value)).nil? + value + else + NULL + end + end + end + + def replace_pair(key, old_value, new_value) + NULL != internal_replace(key, old_value) { new_value } + end + + def replace_if_exists(key, new_value) + if (result = internal_replace(key) { new_value }) && NULL != result + result + end + end + + def get_and_set(key, value) # internalPut in the original CHMV8 + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + if current_table.cas_new_node(i, hash, key, value) + increment_size + break + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, old_value = attempt_get_and_set(key, value, hash, current_table, i, node, node_hash) + break old_value if succeeded + end + end + end + + def delete(key) + replace_if_exists(key, NULL) + end + + def delete_pair(key, value) + result = internal_replace(key, value) { NULL } + if result && NULL != result + !!result + else + false + end + end + + def each_pair + return self unless current_table = table + current_table_size = base_size = current_table.size + i = base_index = 0 + while base_index < base_size + if node = current_table.volatile_get(i) + if node.hash == MOVED + current_table = node.key + current_table_size = current_table.size + else + begin + if NULL != (value = node.value) # skip deleted or special nodes + yield node.key, value + end + end while node = node.next + end + end + + if (i_with_base = i + base_size) < current_table_size + i = i_with_base # visit upper slots if present + else + i = base_index += 1 + end + end + self + end + + def size + (sum = @counter.sum) < 0 ? 0 : sum # ignore transient negative values + end + + def empty? + size == 0 + end + + # Implementation for clear. Steps through each bin, removing all nodes. + def clear + return self unless current_table = table + current_table_size = current_table.size + deleted_count = i = 0 + while i < current_table_size + if !(node = current_table.volatile_get(i)) + i += 1 + elsif (node_hash = node.hash) == MOVED + current_table = node.key + current_table_size = current_table.size + elsif Node.locked_hash?(node_hash) + decrement_size(deleted_count) # opportunistically update count + deleted_count = 0 + node.try_await_lock(current_table, i) + else + current_table.try_lock_via_hash(i, node, node_hash) do + begin + deleted_count += 1 if NULL != node.value # recheck under lock + node.value = nil + end while node = node.next + current_table.volatile_set(i, nil) + i += 1 + end + end + end + decrement_size(deleted_count) + self + end + + private + # Internal versions of the insertion methods, each a + # little more complicated than the last. All have + # the same basic structure: + # 1. If table uninitialized, create + # 2. If bin empty, try to CAS new node + # 3. If bin stale, use new table + # 4. Lock and validate; if valid, scan and add or update + # + # The others interweave other checks and/or alternative actions: + # * Plain +get_and_set+ checks for and performs resize after insertion. + # * compute_if_absent prescans for mapping without lock (and fails to add + # if present), which also makes pre-emptive resize checks worthwhile. + # + # Someday when details settle down a bit more, it might be worth + # some factoring to reduce sprawl. + def internal_replace(key, expected_old_value = NULL, &block) + hash = key_hash(key) + current_table = table + while current_table + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + break + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif (node_hash & HASH_BITS) != hash && !node.next # precheck + break # rules out possible existence + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, old_value = attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash, &block) + return old_value if succeeded + end + end + NULL + end + + def attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash) + current_table.try_lock_via_hash(i, node, node_hash) do + predecessor_node = nil + old_value = NULL + begin + if node.matches?(key, hash) && NULL != (current_value = node.value) + if NULL == expected_old_value || expected_old_value == current_value # NULL == expected_old_value means whatever value + old_value = current_value + if NULL == (node.value = yield(old_value)) + current_table.delete_node_at(i, node, predecessor_node) + decrement_size + end + end + break + end + + predecessor_node = node + end while node = node.next + + return true, old_value + end + end + + def find_value_in_node_list(node, key, hash, pure_hash) + do_check_for_resize = false + while true + if pure_hash == hash && node.key?(key) && NULL != (value = node.value) + return value + elsif node = node.next + do_check_for_resize = true # at least 2 nodes -> check for resize + pure_hash = node.pure_hash + else + return NULL + end + end + ensure + check_for_resize if do_check_for_resize + end + + def internal_compute(key, &block) + hash = key_hash(key) + current_table = table || initialize_table + while true + if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash))) + succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key, &block) + if succeeded + if NULL == new_value + break nil + else + increment_size + break new_value + end + end + elsif (node_hash = node.hash) == MOVED + current_table = node.key + elsif Node.locked_hash?(node_hash) + try_await_lock(current_table, i, node) + else + succeeded, new_value = attempt_compute(key, hash, current_table, i, node, node_hash, &block) + break new_value if succeeded + end + end + end + + def attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) + added = false + current_table.try_lock_via_hash(i, node, node_hash) do + while true + if node.matches?(key, hash) && NULL != (value = node.value) + return true, value + end + last = node + unless node = node.next + last.next = Node.new(hash, key, value = yield) + added = true + increment_size + return true, value + end + end + end + ensure + check_for_resize if added + end + + def attempt_compute(key, hash, current_table, i, node, node_hash) + added = false + current_table.try_lock_via_hash(i, node, node_hash) do + predecessor_node = nil + while true + if node.matches?(key, hash) && NULL != (value = node.value) + if NULL == (node.value = value = yield(value)) + current_table.delete_node_at(i, node, predecessor_node) + decrement_size + value = nil + end + return true, value + end + predecessor_node = node + unless node = node.next + if NULL == (value = yield(NULL)) + value = nil + else + predecessor_node.next = Node.new(hash, key, value) + added = true + increment_size + end + return true, value + end + end + end + ensure + check_for_resize if added + end + + def attempt_get_and_set(key, value, hash, current_table, i, node, node_hash) + node_nesting = nil + current_table.try_lock_via_hash(i, node, node_hash) do + node_nesting = 1 + old_value = nil + found_old_value = false + while node + if node.matches?(key, hash) && NULL != (old_value = node.value) + found_old_value = true + node.value = value + break + end + last = node + unless node = node.next + last.next = Node.new(hash, key, value) + break + end + node_nesting += 1 + end + + return true, old_value if found_old_value + increment_size + true + end + ensure + check_for_resize if node_nesting && (node_nesting > 1 || current_table.size <= 64) + end + + def initialize_copy(other) + super + @counter = Concurrent::ThreadSafe::Util::Adder.new + self.table = nil + self.size_control = (other_table = other.table) ? other_table.size : DEFAULT_CAPACITY + self + end + + def try_await_lock(current_table, i, node) + check_for_resize # try resizing if can't get lock + node.try_await_lock(current_table, i) + end + + def key_hash(key) + key.hash & HASH_BITS + end + + # Returns a power of two table size for the given desired capacity. + def table_size_for(entry_count) + size = 2 + size <<= 1 while size < entry_count + size + end + + # Initializes table, using the size recorded in +size_control+. + def initialize_table + until current_table ||= table + if (size_ctrl = size_control) == NOW_RESIZING + Thread.pass # lost initialization race; just spin + else + try_in_resize_lock(current_table, size_ctrl) do + initial_size = size_ctrl > 0 ? size_ctrl : DEFAULT_CAPACITY + current_table = self.table = Table.new(initial_size) + initial_size - (initial_size >> 2) # 75% load factor + end + end + end + current_table + end + + # If table is too small and not already resizing, creates next table and + # transfers bins. Rechecks occupancy after a transfer to see if another + # resize is already needed because resizings are lagging additions. + def check_for_resize + while (current_table = table) && MAX_CAPACITY > (table_size = current_table.size) && NOW_RESIZING != (size_ctrl = size_control) && size_ctrl < @counter.sum + try_in_resize_lock(current_table, size_ctrl) do + self.table = rebuild(current_table) + (table_size << 1) - (table_size >> 1) # 75% load factor + end + end + end + + def try_in_resize_lock(current_table, size_ctrl) + if cas_size_control(size_ctrl, NOW_RESIZING) + begin + if current_table == table # recheck under lock + size_ctrl = yield # get new size_control + end + ensure + self.size_control = size_ctrl + end + end + end + + # Moves and/or copies the nodes in each bin to new table. See above for explanation. + def rebuild(table) + old_table_size = table.size + new_table = table.next_in_size_table + # puts "#{old_table_size} -> #{new_table.size}" + forwarder = Node.new(MOVED, new_table, NULL) + rev_forwarder = nil + locked_indexes = nil # holds bins to revisit; nil until needed + locked_arr_idx = 0 + bin = old_table_size - 1 + i = bin + while true + if !(node = table.volatile_get(i)) + # no lock needed (or available) if bin >= 0, because we're not popping values from locked_indexes until we've run through the whole table + redo unless (bin >= 0 ? table.cas(i, nil, forwarder) : lock_and_clean_up_reverse_forwarders(table, old_table_size, new_table, i, forwarder)) + elsif Node.locked_hash?(node_hash = node.hash) + locked_indexes ||= ::Array.new + if bin < 0 && locked_arr_idx > 0 + locked_arr_idx -= 1 + i, locked_indexes[locked_arr_idx] = locked_indexes[locked_arr_idx], i # swap with another bin + redo + end + if bin < 0 || locked_indexes.size >= TRANSFER_BUFFER_SIZE + node.try_await_lock(table, i) # no other options -- block + redo + end + rev_forwarder ||= Node.new(MOVED, table, NULL) + redo unless table.volatile_get(i) == node && node.locked? # recheck before adding to list + locked_indexes << i + new_table.volatile_set(i, rev_forwarder) + new_table.volatile_set(i + old_table_size, rev_forwarder) + else + redo unless split_old_bin(table, new_table, i, node, node_hash, forwarder) + end + + if bin > 0 + i = (bin -= 1) + elsif locked_indexes && !locked_indexes.empty? + bin = -1 + i = locked_indexes.pop + locked_arr_idx = locked_indexes.size - 1 + else + return new_table + end + end + end + + def lock_and_clean_up_reverse_forwarders(old_table, old_table_size, new_table, i, forwarder) + # transiently use a locked forwarding node + locked_forwarder = Node.new(moved_locked_hash = MOVED | LOCKED, new_table, NULL) + if old_table.cas(i, nil, locked_forwarder) + new_table.volatile_set(i, nil) # kill the potential reverse forwarders + new_table.volatile_set(i + old_table_size, nil) # kill the potential reverse forwarders + old_table.volatile_set(i, forwarder) + locked_forwarder.unlock_via_hash(moved_locked_hash, MOVED) + true + end + end + + # Splits a normal bin with list headed by e into lo and hi parts; installs in given table. + def split_old_bin(table, new_table, i, node, node_hash, forwarder) + table.try_lock_via_hash(i, node, node_hash) do + split_bin(new_table, i, node, node_hash) + table.volatile_set(i, forwarder) + end + end + + def split_bin(new_table, i, node, node_hash) + bit = new_table.size >> 1 # bit to split on + run_bit = node_hash & bit + last_run = nil + low = nil + high = nil + current_node = node + # this optimises for the lowest amount of volatile writes and objects created + while current_node = current_node.next + unless (b = current_node.hash & bit) == run_bit + run_bit = b + last_run = current_node + end + end + if run_bit == 0 + low = last_run + else + high = last_run + end + current_node = node + until current_node == last_run + pure_hash = current_node.pure_hash + if (pure_hash & bit) == 0 + low = Node.new(pure_hash, current_node.key, current_node.value, low) + else + high = Node.new(pure_hash, current_node.key, current_node.value, high) + end + current_node = current_node.next + end + new_table.volatile_set(i, low) + new_table.volatile_set(i + bit, high) + end + + def increment_size + @counter.increment + end + + def decrement_size(by = 1) + @counter.add(-by) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/mri_map_backend.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/mri_map_backend.rb new file mode 100644 index 0000000000..e0cf9990c5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/mri_map_backend.rb @@ -0,0 +1,66 @@ +require 'thread' +require 'concurrent/collection/map/non_concurrent_map_backend' + +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class MriMapBackend < NonConcurrentMapBackend + + def initialize(options = nil, &default_proc) + super(options, &default_proc) + @write_lock = Mutex.new + end + + def []=(key, value) + @write_lock.synchronize { super } + end + + def compute_if_absent(key) + if NULL != (stored_value = @backend.fetch(key, NULL)) # fast non-blocking path for the most likely case + stored_value + else + @write_lock.synchronize { super } + end + end + + def compute_if_present(key) + @write_lock.synchronize { super } + end + + def compute(key) + @write_lock.synchronize { super } + end + + def merge_pair(key, value) + @write_lock.synchronize { super } + end + + def replace_pair(key, old_value, new_value) + @write_lock.synchronize { super } + end + + def replace_if_exists(key, new_value) + @write_lock.synchronize { super } + end + + def get_and_set(key, value) + @write_lock.synchronize { super } + end + + def delete(key) + @write_lock.synchronize { super } + end + + def delete_pair(key, value) + @write_lock.synchronize { super } + end + + def clear + @write_lock.synchronize { super } + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/non_concurrent_map_backend.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/non_concurrent_map_backend.rb new file mode 100644 index 0000000000..ca5fd9b48e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/non_concurrent_map_backend.rb @@ -0,0 +1,148 @@ +require 'concurrent/constants' + +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class NonConcurrentMapBackend + + # WARNING: all public methods of the class must operate on the @backend + # directly without calling each other. This is important because of the + # SynchronizedMapBackend which uses a non-reentrant mutex for performance + # reasons. + def initialize(options = nil, &default_proc) + validate_options_hash!(options) if options.kind_of?(::Hash) + set_backend(default_proc) + @default_proc = default_proc + end + + def [](key) + @backend[key] + end + + def []=(key, value) + @backend[key] = value + end + + def compute_if_absent(key) + if NULL != (stored_value = @backend.fetch(key, NULL)) + stored_value + else + @backend[key] = yield + end + end + + def replace_pair(key, old_value, new_value) + if pair?(key, old_value) + @backend[key] = new_value + true + else + false + end + end + + def replace_if_exists(key, new_value) + if NULL != (stored_value = @backend.fetch(key, NULL)) + @backend[key] = new_value + stored_value + end + end + + def compute_if_present(key) + if NULL != (stored_value = @backend.fetch(key, NULL)) + store_computed_value(key, yield(stored_value)) + end + end + + def compute(key) + store_computed_value(key, yield(get_or_default(key, nil))) + end + + def merge_pair(key, value) + if NULL == (stored_value = @backend.fetch(key, NULL)) + @backend[key] = value + else + store_computed_value(key, yield(stored_value)) + end + end + + def get_and_set(key, value) + stored_value = get_or_default(key, nil) + @backend[key] = value + stored_value + end + + def key?(key) + @backend.key?(key) + end + + def delete(key) + @backend.delete(key) + end + + def delete_pair(key, value) + if pair?(key, value) + @backend.delete(key) + true + else + false + end + end + + def clear + @backend.clear + self + end + + def each_pair + dupped_backend.each_pair do |k, v| + yield k, v + end + self + end + + def size + @backend.size + end + + def get_or_default(key, default_value) + @backend.fetch(key, default_value) + end + + private + + def set_backend(default_proc) + if default_proc + @backend = ::Hash.new { |_h, key| default_proc.call(self, key) } + else + @backend = {} + end + end + + def initialize_copy(other) + super + set_backend(@default_proc) + self + end + + def dupped_backend + @backend.dup + end + + def pair?(key, expected_value) + NULL != (stored_value = @backend.fetch(key, NULL)) && expected_value.equal?(stored_value) + end + + def store_computed_value(key, new_value) + if new_value.nil? + @backend.delete(key) + nil + else + @backend[key] = new_value + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/synchronized_map_backend.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/synchronized_map_backend.rb new file mode 100644 index 0000000000..190c8d98d9 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/synchronized_map_backend.rb @@ -0,0 +1,82 @@ +require 'concurrent/collection/map/non_concurrent_map_backend' + +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class SynchronizedMapBackend < NonConcurrentMapBackend + + require 'mutex_m' + include Mutex_m + # WARNING: Mutex_m is a non-reentrant lock, so the synchronized methods are + # not allowed to call each other. + + def [](key) + synchronize { super } + end + + def []=(key, value) + synchronize { super } + end + + def compute_if_absent(key) + synchronize { super } + end + + def compute_if_present(key) + synchronize { super } + end + + def compute(key) + synchronize { super } + end + + def merge_pair(key, value) + synchronize { super } + end + + def replace_pair(key, old_value, new_value) + synchronize { super } + end + + def replace_if_exists(key, new_value) + synchronize { super } + end + + def get_and_set(key, value) + synchronize { super } + end + + def key?(key) + synchronize { super } + end + + def delete(key) + synchronize { super } + end + + def delete_pair(key, value) + synchronize { super } + end + + def clear + synchronize { super } + end + + def size + synchronize { super } + end + + def get_or_default(key, default_value) + synchronize { super } + end + + private + def dupped_backend + synchronize { super } + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/truffleruby_map_backend.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/truffleruby_map_backend.rb new file mode 100644 index 0000000000..68a1b3884d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/map/truffleruby_map_backend.rb @@ -0,0 +1,14 @@ +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class TruffleRubyMapBackend < TruffleRuby::ConcurrentMap + def initialize(options = nil) + options ||= {} + super(initial_capacity: options[:initial_capacity], load_factor: options[:load_factor]) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/non_concurrent_priority_queue.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..694cd7ac7c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/non_concurrent_priority_queue.rb @@ -0,0 +1,143 @@ +require 'concurrent/utility/engine' +require 'concurrent/collection/java_non_concurrent_priority_queue' +require 'concurrent/collection/ruby_non_concurrent_priority_queue' + +module Concurrent + module Collection + + # @!visibility private + # @!macro internal_implementation_note + NonConcurrentPriorityQueueImplementation = case + when Concurrent.on_jruby? + JavaNonConcurrentPriorityQueue + else + RubyNonConcurrentPriorityQueue + end + private_constant :NonConcurrentPriorityQueueImplementation + + # @!macro priority_queue + # + # A queue collection in which the elements are sorted based on their + # comparison (spaceship) operator `<=>`. Items are added to the queue + # at a position relative to their priority. On removal the element + # with the "highest" priority is removed. By default the sort order is + # from highest to lowest, but a lowest-to-highest sort order can be + # set on construction. + # + # The API is based on the `Queue` class from the Ruby standard library. + # + # The pure Ruby implementation, `RubyNonConcurrentPriorityQueue` uses a heap algorithm + # stored in an array. The algorithm is based on the work of Robert Sedgewick + # and Kevin Wayne. + # + # The JRuby native implementation is a thin wrapper around the standard + # library `java.util.NonConcurrentPriorityQueue`. + # + # When running under JRuby the class `NonConcurrentPriorityQueue` extends `JavaNonConcurrentPriorityQueue`. + # When running under all other interpreters it extends `RubyNonConcurrentPriorityQueue`. + # + # @note This implementation is *not* thread safe. + # + # @see http://en.wikipedia.org/wiki/Priority_queue + # @see http://ruby-doc.org/stdlib-2.0.0/libdoc/thread/rdoc/Queue.html + # + # @see http://algs4.cs.princeton.edu/24pq/index.php#2.6 + # @see http://algs4.cs.princeton.edu/24pq/MaxPQ.java.html + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/PriorityQueue.html + # + # @!visibility private + class NonConcurrentPriorityQueue < NonConcurrentPriorityQueueImplementation + + alias_method :has_priority?, :include? + + alias_method :size, :length + + alias_method :deq, :pop + alias_method :shift, :pop + + alias_method :<<, :push + alias_method :enq, :push + + # @!method initialize(opts = {}) + # @!macro priority_queue_method_initialize + # + # Create a new priority queue with no items. + # + # @param [Hash] opts the options for creating the queue + # @option opts [Symbol] :order (:max) dictates the order in which items are + # stored: from highest to lowest when `:max` or `:high`; from lowest to + # highest when `:min` or `:low` + + # @!method clear + # @!macro priority_queue_method_clear + # + # Removes all of the elements from this priority queue. + + # @!method delete(item) + # @!macro priority_queue_method_delete + # + # Deletes all items from `self` that are equal to `item`. + # + # @param [Object] item the item to be removed from the queue + # @return [Object] true if the item is found else false + + # @!method empty? + # @!macro priority_queue_method_empty + # + # Returns `true` if `self` contains no elements. + # + # @return [Boolean] true if there are no items in the queue else false + + # @!method include?(item) + # @!macro priority_queue_method_include + # + # Returns `true` if the given item is present in `self` (that is, if any + # element == `item`), otherwise returns false. + # + # @param [Object] item the item to search for + # + # @return [Boolean] true if the item is found else false + + # @!method length + # @!macro priority_queue_method_length + # + # The current length of the queue. + # + # @return [Fixnum] the number of items in the queue + + # @!method peek + # @!macro priority_queue_method_peek + # + # Retrieves, but does not remove, the head of this queue, or returns `nil` + # if this queue is empty. + # + # @return [Object] the head of the queue or `nil` when empty + + # @!method pop + # @!macro priority_queue_method_pop + # + # Retrieves and removes the head of this queue, or returns `nil` if this + # queue is empty. + # + # @return [Object] the head of the queue or `nil` when empty + + # @!method push(item) + # @!macro priority_queue_method_push + # + # Inserts the specified element into this priority queue. + # + # @param [Object] item the item to insert onto the queue + + # @!method self.from_list(list, opts = {}) + # @!macro priority_queue_method_from_list + # + # Create a new priority queue from the given list. + # + # @param [Enumerable] list the list to build the queue from + # @param [Hash] opts the options for creating the queue + # + # @return [NonConcurrentPriorityQueue] the newly created and populated queue + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb new file mode 100644 index 0000000000..322b4ac2d9 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb @@ -0,0 +1,160 @@ +module Concurrent + module Collection + + # @!macro priority_queue + # + # @!visibility private + # @!macro internal_implementation_note + class RubyNonConcurrentPriorityQueue + + # @!macro priority_queue_method_initialize + def initialize(opts = {}) + order = opts.fetch(:order, :max) + @comparator = [:min, :low].include?(order) ? -1 : 1 + clear + end + + # @!macro priority_queue_method_clear + def clear + @queue = [nil] + @length = 0 + true + end + + # @!macro priority_queue_method_delete + def delete(item) + return false if empty? + original_length = @length + k = 1 + while k <= @length + if @queue[k] == item + swap(k, @length) + @length -= 1 + sink(k) || swim(k) + @queue.pop + else + k += 1 + end + end + @length != original_length + end + + # @!macro priority_queue_method_empty + def empty? + size == 0 + end + + # @!macro priority_queue_method_include + def include?(item) + @queue.include?(item) + end + alias_method :has_priority?, :include? + + # @!macro priority_queue_method_length + def length + @length + end + alias_method :size, :length + + # @!macro priority_queue_method_peek + def peek + empty? ? nil : @queue[1] + end + + # @!macro priority_queue_method_pop + def pop + return nil if empty? + max = @queue[1] + swap(1, @length) + @length -= 1 + sink(1) + @queue.pop + max + end + alias_method :deq, :pop + alias_method :shift, :pop + + # @!macro priority_queue_method_push + def push(item) + raise ArgumentError.new('cannot enqueue nil') if item.nil? + @length += 1 + @queue << item + swim(@length) + true + end + alias_method :<<, :push + alias_method :enq, :push + + # @!macro priority_queue_method_from_list + def self.from_list(list, opts = {}) + queue = new(opts) + list.each{|item| queue << item } + queue + end + + private + + # Exchange the values at the given indexes within the internal array. + # + # @param [Integer] x the first index to swap + # @param [Integer] y the second index to swap + # + # @!visibility private + def swap(x, y) + temp = @queue[x] + @queue[x] = @queue[y] + @queue[y] = temp + end + + # Are the items at the given indexes ordered based on the priority + # order specified at construction? + # + # @param [Integer] x the first index from which to retrieve a comparable value + # @param [Integer] y the second index from which to retrieve a comparable value + # + # @return [Boolean] true if the two elements are in the correct priority order + # else false + # + # @!visibility private + def ordered?(x, y) + (@queue[x] <=> @queue[y]) == @comparator + end + + # Percolate down to maintain heap invariant. + # + # @param [Integer] k the index at which to start the percolation + # + # @!visibility private + def sink(k) + success = false + + while (j = (2 * k)) <= @length do + j += 1 if j < @length && ! ordered?(j, j+1) + break if ordered?(k, j) + swap(k, j) + success = true + k = j + end + + success + end + + # Percolate up to maintain heap invariant. + # + # @param [Integer] k the index at which to start the percolation + # + # @!visibility private + def swim(k) + success = false + + while k > 1 && ! ordered?(k/2, k) do + swap(k, k/2) + k = k/2 + success = true + end + + success + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/deprecation.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/deprecation.rb new file mode 100644 index 0000000000..35ae4b2c9d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/deprecation.rb @@ -0,0 +1,34 @@ +require 'concurrent/concern/logging' + +module Concurrent + module Concern + + # @!visibility private + # @!macro internal_implementation_note + module Deprecation + # TODO require additional parameter: a version. Display when it'll be removed based on that. Error if not removed. + include Concern::Logging + + def deprecated(message, strip = 2) + caller_line = caller(strip).first if strip > 0 + klass = if Module === self + self + else + self.class + end + message = if strip > 0 + format("[DEPRECATED] %s\ncalled on: %s", message, caller_line) + else + format('[DEPRECATED] %s', message) + end + log WARN, klass.to_s, message + end + + def deprecated_method(old_name, new_name) + deprecated "`#{old_name}` is deprecated and it'll removed in next release, use `#{new_name}` instead", 3 + end + + extend self + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/dereferenceable.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/dereferenceable.rb new file mode 100644 index 0000000000..dc172ba74d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/dereferenceable.rb @@ -0,0 +1,73 @@ +module Concurrent + module Concern + + # Object references in Ruby are mutable. This can lead to serious problems when + # the `#value` of a concurrent object is a mutable reference. Which is always the + # case unless the value is a `Fixnum`, `Symbol`, or similar "primitive" data type. + # Most classes in this library that expose a `#value` getter method do so using the + # `Dereferenceable` mixin module. + # + # @!macro copy_options + module Dereferenceable + # NOTE: This module is going away in 2.0. In the mean time we need it to + # play nicely with the synchronization layer. This means that the + # including class SHOULD be synchronized and it MUST implement a + # `#synchronize` method. Not doing so will lead to runtime errors. + + # Return the value this object represents after applying the options specified + # by the `#set_deref_options` method. + # + # @return [Object] the current value of the object + def value + synchronize { apply_deref_options(@value) } + end + alias_method :deref, :value + + protected + + # Set the internal value of this object + # + # @param [Object] value the new value + def value=(value) + synchronize{ @value = value } + end + + # @!macro dereferenceable_set_deref_options + # Set the options which define the operations #value performs before + # returning data to the caller (dereferencing). + # + # @note Most classes that include this module will call `#set_deref_options` + # from within the constructor, thus allowing these options to be set at + # object creation. + # + # @param [Hash] opts the options defining dereference behavior. + # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data + # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data + # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing + # the internal value and returning the value returned from the proc + def set_deref_options(opts = {}) + synchronize{ ns_set_deref_options(opts) } + end + + # @!macro dereferenceable_set_deref_options + # @!visibility private + def ns_set_deref_options(opts) + @dup_on_deref = opts[:dup_on_deref] || opts[:dup] + @freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze] + @copy_on_deref = opts[:copy_on_deref] || opts[:copy] + @do_nothing_on_deref = !(@dup_on_deref || @freeze_on_deref || @copy_on_deref) + nil + end + + # @!visibility private + def apply_deref_options(value) + return nil if value.nil? + return value if @do_nothing_on_deref + value = @copy_on_deref.call(value) if @copy_on_deref + value = value.dup if @dup_on_deref + value = value.freeze if @freeze_on_deref + value + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/logging.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/logging.rb new file mode 100644 index 0000000000..568a539ebf --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/logging.rb @@ -0,0 +1,116 @@ +require 'logger' +require 'concurrent/atomic/atomic_reference' + +module Concurrent + module Concern + + # Include where logging is needed + # + # @!visibility private + module Logging + include Logger::Severity + + # Logs through {Concurrent.global_logger}, it can be overridden by setting @logger + # @param [Integer] level one of Logger::Severity constants + # @param [String] progname e.g. a path of an Actor + # @param [String, nil] message when nil block is used to generate the message + # @yieldreturn [String] a message + def log(level, progname, message = nil, &block) + logger = if defined?(@logger) && @logger + @logger + else + Concurrent.global_logger + end + logger.call level, progname, message, &block + rescue => error + $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" + + "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}" + end + end + end +end + +module Concurrent + extend Concern::Logging + + # @return [Logger] Logger with provided level and output. + def self.create_simple_logger(level = Logger::FATAL, output = $stderr) + # TODO (pitr-ch 24-Dec-2016): figure out why it had to be replaced, stdlogger was deadlocking + lambda do |severity, progname, message = nil, &block| + return false if severity < level + + message = block ? block.call : message + formatted_message = case message + when String + message + when Exception + format "%s (%s)\n%s", + message.message, message.class, (message.backtrace || []).join("\n") + else + message.inspect + end + + output.print format "[%s] %5s -- %s: %s\n", + Time.now.strftime('%Y-%m-%d %H:%M:%S.%L'), + Logger::SEV_LABEL[severity], + progname, + formatted_message + true + end + end + + # Use logger created by #create_simple_logger to log concurrent-ruby messages. + def self.use_simple_logger(level = Logger::FATAL, output = $stderr) + Concurrent.global_logger = create_simple_logger level, output + end + + # @return [Logger] Logger with provided level and output. + # @deprecated + def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr) + logger = Logger.new(output) + logger.level = level + logger.formatter = lambda do |severity, datetime, progname, msg| + formatted_message = case msg + when String + msg + when Exception + format "%s (%s)\n%s", + msg.message, msg.class, (msg.backtrace || []).join("\n") + else + msg.inspect + end + format "[%s] %5s -- %s: %s\n", + datetime.strftime('%Y-%m-%d %H:%M:%S.%L'), + severity, + progname, + formatted_message + end + + lambda do |loglevel, progname, message = nil, &block| + logger.add loglevel, message, progname, &block + end + end + + # Use logger created by #create_stdlib_logger to log concurrent-ruby messages. + # @deprecated + def self.use_stdlib_logger(level = Logger::FATAL, output = $stderr) + Concurrent.global_logger = create_stdlib_logger level, output + end + + # TODO (pitr-ch 27-Dec-2016): remove deadlocking stdlib_logger methods + + # Suppresses all output when used for logging. + NULL_LOGGER = lambda { |level, progname, message = nil, &block| } + + # @!visibility private + GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(Logger::WARN)) + private_constant :GLOBAL_LOGGER + + def self.global_logger + GLOBAL_LOGGER.value + end + + def self.global_logger=(value) + GLOBAL_LOGGER.value = value + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/obligation.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/obligation.rb new file mode 100644 index 0000000000..2c9ac12003 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/obligation.rb @@ -0,0 +1,220 @@ +require 'thread' +require 'timeout' + +require 'concurrent/atomic/event' +require 'concurrent/concern/dereferenceable' + +module Concurrent + module Concern + + module Obligation + include Concern::Dereferenceable + # NOTE: The Dereferenceable module is going away in 2.0. In the mean time + # we need it to place nicely with the synchronization layer. This means + # that the including class SHOULD be synchronized and it MUST implement a + # `#synchronize` method. Not doing so will lead to runtime errors. + + # Has the obligation been fulfilled? + # + # @return [Boolean] + def fulfilled? + state == :fulfilled + end + alias_method :realized?, :fulfilled? + + # Has the obligation been rejected? + # + # @return [Boolean] + def rejected? + state == :rejected + end + + # Is obligation completion still pending? + # + # @return [Boolean] + def pending? + state == :pending + end + + # Is the obligation still unscheduled? + # + # @return [Boolean] + def unscheduled? + state == :unscheduled + end + + # Has the obligation completed processing? + # + # @return [Boolean] + def complete? + [:fulfilled, :rejected].include? state + end + + # Is the obligation still awaiting completion of processing? + # + # @return [Boolean] + def incomplete? + ! complete? + end + + # The current value of the obligation. Will be `nil` while the state is + # pending or the operation has been rejected. + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Object] see Dereferenceable#deref + def value(timeout = nil) + wait timeout + deref + end + + # Wait until obligation is complete or the timeout has been reached. + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Obligation] self + def wait(timeout = nil) + event.wait(timeout) if timeout != 0 && incomplete? + self + end + + # Wait until obligation is complete or the timeout is reached. Will re-raise + # any exceptions raised during processing (but will not raise an exception + # on timeout). + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Obligation] self + # @raise [Exception] raises the reason when rejected + def wait!(timeout = nil) + wait(timeout).tap { raise self if rejected? } + end + alias_method :no_error!, :wait! + + # The current value of the obligation. Will be `nil` while the state is + # pending or the operation has been rejected. Will re-raise any exceptions + # raised during processing (but will not raise an exception on timeout). + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Object] see Dereferenceable#deref + # @raise [Exception] raises the reason when rejected + def value!(timeout = nil) + wait(timeout) + if rejected? + raise self + else + deref + end + end + + # The current state of the obligation. + # + # @return [Symbol] the current state + def state + synchronize { @state } + end + + # If an exception was raised during processing this will return the + # exception object. Will return `nil` when the state is pending or if + # the obligation has been successfully fulfilled. + # + # @return [Exception] the exception raised during processing or `nil` + def reason + synchronize { @reason } + end + + # @example allows Obligation to be risen + # rejected_ivar = Ivar.new.fail + # raise rejected_ivar + def exception(*args) + raise 'obligation is not rejected' unless rejected? + reason.exception(*args) + end + + protected + + # @!visibility private + def get_arguments_from(opts = {}) + [*opts.fetch(:args, [])] + end + + # @!visibility private + def init_obligation + @event = Event.new + @value = @reason = nil + end + + # @!visibility private + def event + @event + end + + # @!visibility private + def set_state(success, value, reason) + if success + @value = value + @state = :fulfilled + else + @reason = reason + @state = :rejected + end + end + + # @!visibility private + def state=(value) + synchronize { ns_set_state(value) } + end + + # Atomic compare and set operation + # State is set to `next_state` only if `current state == expected_current`. + # + # @param [Symbol] next_state + # @param [Symbol] expected_current + # + # @return [Boolean] true is state is changed, false otherwise + # + # @!visibility private + def compare_and_set_state(next_state, *expected_current) + synchronize do + if expected_current.include? @state + @state = next_state + true + else + false + end + end + end + + # Executes the block within mutex if current state is included in expected_states + # + # @return block value if executed, false otherwise + # + # @!visibility private + def if_state(*expected_states) + synchronize do + raise ArgumentError.new('no block given') unless block_given? + + if expected_states.include? @state + yield + else + false + end + end + end + + protected + + # Am I in the current state? + # + # @param [Symbol] expected The state to check against + # @return [Boolean] true if in the expected state else false + # + # @!visibility private + def ns_check_state?(expected) + @state == expected + end + + # @!visibility private + def ns_set_state(value) + @state = value + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/observable.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/observable.rb new file mode 100644 index 0000000000..b5132714bf --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/concern/observable.rb @@ -0,0 +1,110 @@ +require 'concurrent/collection/copy_on_notify_observer_set' +require 'concurrent/collection/copy_on_write_observer_set' + +module Concurrent + module Concern + + # The [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) is one + # of the most useful design patterns. + # + # The workflow is very simple: + # - an `observer` can register itself to a `subject` via a callback + # - many `observers` can be registered to the same `subject` + # - the `subject` notifies all registered observers when its status changes + # - an `observer` can deregister itself when is no more interested to receive + # event notifications + # + # In a single threaded environment the whole pattern is very easy: the + # `subject` can use a simple data structure to manage all its subscribed + # `observer`s and every `observer` can react directly to every event without + # caring about synchronization. + # + # In a multi threaded environment things are more complex. The `subject` must + # synchronize the access to its data structure and to do so currently we're + # using two specialized ObserverSet: {Concurrent::Concern::CopyOnWriteObserverSet} + # and {Concurrent::Concern::CopyOnNotifyObserverSet}. + # + # When implementing and `observer` there's a very important rule to remember: + # **there are no guarantees about the thread that will execute the callback** + # + # Let's take this example + # ``` + # class Observer + # def initialize + # @count = 0 + # end + # + # def update + # @count += 1 + # end + # end + # + # obs = Observer.new + # [obj1, obj2, obj3, obj4].each { |o| o.add_observer(obs) } + # # execute [obj1, obj2, obj3, obj4] + # ``` + # + # `obs` is wrong because the variable `@count` can be accessed by different + # threads at the same time, so it should be synchronized (using either a Mutex + # or an AtomicFixum) + module Observable + + # @!macro observable_add_observer + # + # Adds an observer to this set. If a block is passed, the observer will be + # created by this method and no other params should be passed. + # + # @param [Object] observer the observer to add + # @param [Symbol] func the function to call on the observer during notification. + # Default is :update + # @return [Object] the added observer + def add_observer(observer = nil, func = :update, &block) + observers.add_observer(observer, func, &block) + end + + # As `#add_observer` but can be used for chaining. + # + # @param [Object] observer the observer to add + # @param [Symbol] func the function to call on the observer during notification. + # @return [Observable] self + def with_observer(observer = nil, func = :update, &block) + add_observer(observer, func, &block) + self + end + + # @!macro observable_delete_observer + # + # Remove `observer` as an observer on this object so that it will no + # longer receive notifications. + # + # @param [Object] observer the observer to remove + # @return [Object] the deleted observer + def delete_observer(observer) + observers.delete_observer(observer) + end + + # @!macro observable_delete_observers + # + # Remove all observers associated with this object. + # + # @return [Observable] self + def delete_observers + observers.delete_observers + self + end + + # @!macro observable_count_observers + # + # Return the number of observers associated with this object. + # + # @return [Integer] the observers count + def count_observers + observers.count_observers + end + + protected + + attr_accessor :observers + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/configuration.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/configuration.rb new file mode 100644 index 0000000000..5571d39b0c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/configuration.rb @@ -0,0 +1,105 @@ +require 'thread' +require 'concurrent/delay' +require 'concurrent/errors' +require 'concurrent/concern/deprecation' +require 'concurrent/executor/immediate_executor' +require 'concurrent/executor/fixed_thread_pool' +require 'concurrent/executor/cached_thread_pool' +require 'concurrent/utility/processor_counter' + +module Concurrent + extend Concern::Deprecation + + autoload :Options, 'concurrent/options' + autoload :TimerSet, 'concurrent/executor/timer_set' + autoload :ThreadPoolExecutor, 'concurrent/executor/thread_pool_executor' + + # @!visibility private + GLOBAL_FAST_EXECUTOR = Delay.new { Concurrent.new_fast_executor } + private_constant :GLOBAL_FAST_EXECUTOR + + # @!visibility private + GLOBAL_IO_EXECUTOR = Delay.new { Concurrent.new_io_executor } + private_constant :GLOBAL_IO_EXECUTOR + + # @!visibility private + GLOBAL_TIMER_SET = Delay.new { TimerSet.new } + private_constant :GLOBAL_TIMER_SET + + # @!visibility private + GLOBAL_IMMEDIATE_EXECUTOR = ImmediateExecutor.new + private_constant :GLOBAL_IMMEDIATE_EXECUTOR + + # Disables AtExit handlers including pool auto-termination handlers. + # When disabled it will be the application programmer's responsibility + # to ensure that the handlers are shutdown properly prior to application + # exit by calling `AtExit.run` method. + # + # @note this option should be needed only because of `at_exit` ordering + # issues which may arise when running some of the testing frameworks. + # E.g. Minitest's test-suite runs itself in `at_exit` callback which + # executes after the pools are already terminated. Then auto termination + # needs to be disabled and called manually after test-suite ends. + # @note This method should *never* be called + # from within a gem. It should *only* be used from within the main + # application and even then it should be used only when necessary. + # @deprecated Has no effect since it is no longer needed, see https://github.com/ruby-concurrency/concurrent-ruby/pull/841. + # + def self.disable_at_exit_handlers! + deprecated "Method #disable_at_exit_handlers! has no effect since it is no longer needed, see https://github.com/ruby-concurrency/concurrent-ruby/pull/841." + end + + # Global thread pool optimized for short, fast *operations*. + # + # @return [ThreadPoolExecutor] the thread pool + def self.global_fast_executor + GLOBAL_FAST_EXECUTOR.value! + end + + # Global thread pool optimized for long, blocking (IO) *tasks*. + # + # @return [ThreadPoolExecutor] the thread pool + def self.global_io_executor + GLOBAL_IO_EXECUTOR.value! + end + + def self.global_immediate_executor + GLOBAL_IMMEDIATE_EXECUTOR + end + + # Global thread pool user for global *timers*. + # + # @return [Concurrent::TimerSet] the thread pool + def self.global_timer_set + GLOBAL_TIMER_SET.value! + end + + # General access point to global executors. + # @param [Symbol, Executor] executor_identifier symbols: + # - :fast - {Concurrent.global_fast_executor} + # - :io - {Concurrent.global_io_executor} + # - :immediate - {Concurrent.global_immediate_executor} + # @return [Executor] + def self.executor(executor_identifier) + Options.executor(executor_identifier) + end + + def self.new_fast_executor(opts = {}) + FixedThreadPool.new( + [2, Concurrent.processor_count].max, + auto_terminate: opts.fetch(:auto_terminate, true), + idletime: 60, # 1 minute + max_queue: 0, # unlimited + fallback_policy: :abort, # shouldn't matter -- 0 max queue + name: "fast" + ) + end + + def self.new_io_executor(opts = {}) + CachedThreadPool.new( + auto_terminate: opts.fetch(:auto_terminate, true), + fallback_policy: :abort, # shouldn't matter -- 0 max queue + name: "io" + ) + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/constants.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/constants.rb new file mode 100644 index 0000000000..676c2afb9a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/constants.rb @@ -0,0 +1,8 @@ +module Concurrent + + # Various classes within allows for +nil+ values to be stored, + # so a special +NULL+ token is required to indicate the "nil-ness". + # @!visibility private + NULL = ::Object.new + +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/dataflow.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/dataflow.rb new file mode 100644 index 0000000000..d55f19d850 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/dataflow.rb @@ -0,0 +1,81 @@ +require 'concurrent/future' +require 'concurrent/atomic/atomic_fixnum' + +module Concurrent + + # @!visibility private + class DependencyCounter # :nodoc: + + def initialize(count, &block) + @counter = AtomicFixnum.new(count) + @block = block + end + + def update(time, value, reason) + if @counter.decrement == 0 + @block.call + end + end + end + + # Dataflow allows you to create a task that will be scheduled when all of its data dependencies are available. + # {include:file:docs-source/dataflow.md} + # + # @param [Future] inputs zero or more `Future` operations that this dataflow depends upon + # + # @yield The operation to perform once all the dependencies are met + # @yieldparam [Future] inputs each of the `Future` inputs to the dataflow + # @yieldreturn [Object] the result of the block operation + # + # @return [Object] the result of all the operations + # + # @raise [ArgumentError] if no block is given + # @raise [ArgumentError] if any of the inputs are not `IVar`s + def dataflow(*inputs, &block) + dataflow_with(Concurrent.global_io_executor, *inputs, &block) + end + module_function :dataflow + + def dataflow_with(executor, *inputs, &block) + call_dataflow(:value, executor, *inputs, &block) + end + module_function :dataflow_with + + def dataflow!(*inputs, &block) + dataflow_with!(Concurrent.global_io_executor, *inputs, &block) + end + module_function :dataflow! + + def dataflow_with!(executor, *inputs, &block) + call_dataflow(:value!, executor, *inputs, &block) + end + module_function :dataflow_with! + + private + + def call_dataflow(method, executor, *inputs, &block) + raise ArgumentError.new('an executor must be provided') if executor.nil? + raise ArgumentError.new('no block given') unless block_given? + unless inputs.all? { |input| input.is_a? IVar } + raise ArgumentError.new("Not all dependencies are IVars.\nDependencies: #{ inputs.inspect }") + end + + result = Future.new(executor: executor) do + values = inputs.map { |input| input.send(method) } + block.call(*values) + end + + if inputs.empty? + result.execute + else + counter = DependencyCounter.new(inputs.size) { result.execute } + + inputs.each do |input| + input.add_observer counter + end + end + + result + end + module_function :call_dataflow +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/delay.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/delay.rb new file mode 100644 index 0000000000..923773cbca --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/delay.rb @@ -0,0 +1,199 @@ +require 'thread' +require 'concurrent/concern/obligation' +require 'concurrent/executor/immediate_executor' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # This file has circular require issues. It must be autoloaded here. + autoload :Options, 'concurrent/options' + + # Lazy evaluation of a block yielding an immutable result. Useful for + # expensive operations that may never be needed. It may be non-blocking, + # supports the `Concern::Obligation` interface, and accepts the injection of + # custom executor upon which to execute the block. Processing of + # block will be deferred until the first time `#value` is called. + # At that time the caller can choose to return immediately and let + # the block execute asynchronously, block indefinitely, or block + # with a timeout. + # + # When a `Delay` is created its state is set to `pending`. The value and + # reason are both `nil`. The first time the `#value` method is called the + # enclosed opration will be run and the calling thread will block. Other + # threads attempting to call `#value` will block as well. Once the operation + # is complete the *value* will be set to the result of the operation or the + # *reason* will be set to the raised exception, as appropriate. All threads + # blocked on `#value` will return. Subsequent calls to `#value` will immediately + # return the cached value. The operation will only be run once. This means that + # any side effects created by the operation will only happen once as well. + # + # `Delay` includes the `Concurrent::Concern::Dereferenceable` mixin to support thread + # safety of the reference returned by `#value`. + # + # @!macro copy_options + # + # @!macro delay_note_regarding_blocking + # @note The default behavior of `Delay` is to block indefinitely when + # calling either `value` or `wait`, executing the delayed operation on + # the current thread. This makes the `timeout` value completely + # irrelevant. To enable non-blocking behavior, use the `executor` + # constructor option. This will cause the delayed operation to be + # execute on the given executor, allowing the call to timeout. + # + # @see Concurrent::Concern::Dereferenceable + class Delay < Synchronization::LockableObject + include Concern::Obligation + + # NOTE: Because the global thread pools are lazy-loaded with these objects + # there is a performance hit every time we post a new task to one of these + # thread pools. Subsequently it is critical that `Delay` perform as fast + # as possible post-completion. This class has been highly optimized using + # the benchmark script `examples/lazy_and_delay.rb`. Do NOT attempt to + # DRY-up this class or perform other refactoring with running the + # benchmarks and ensuring that performance is not negatively impacted. + + # Create a new `Delay` in the `:pending` state. + # + # @!macro executor_and_deref_options + # + # @yield the delayed operation to perform + # + # @raise [ArgumentError] if no block is given + def initialize(opts = {}, &block) + raise ArgumentError.new('no block given') unless block_given? + super(&nil) + synchronize { ns_initialize(opts, &block) } + end + + # Return the value this object represents after applying the options + # specified by the `#set_deref_options` method. If the delayed operation + # raised an exception this method will return nil. The exception object + # can be accessed via the `#reason` method. + # + # @param [Numeric] timeout the maximum number of seconds to wait + # @return [Object] the current value of the object + # + # @!macro delay_note_regarding_blocking + def value(timeout = nil) + if @executor # TODO (pitr 12-Sep-2015): broken unsafe read? + super + else + # this function has been optimized for performance and + # should not be modified without running new benchmarks + synchronize do + execute = @evaluation_started = true unless @evaluation_started + if execute + begin + set_state(true, @task.call, nil) + rescue => ex + set_state(false, nil, ex) + end + elsif incomplete? + raise IllegalOperationError, 'Recursive call to #value during evaluation of the Delay' + end + end + if @do_nothing_on_deref + @value + else + apply_deref_options(@value) + end + end + end + + # Return the value this object represents after applying the options + # specified by the `#set_deref_options` method. If the delayed operation + # raised an exception, this method will raise that exception (even when) + # the operation has already been executed). + # + # @param [Numeric] timeout the maximum number of seconds to wait + # @return [Object] the current value of the object + # @raise [Exception] when `#rejected?` raises `#reason` + # + # @!macro delay_note_regarding_blocking + def value!(timeout = nil) + if @executor + super + else + result = value + raise @reason if @reason + result + end + end + + # Return the value this object represents after applying the options + # specified by the `#set_deref_options` method. + # + # @param [Integer] timeout (nil) the maximum number of seconds to wait for + # the value to be computed. When `nil` the caller will block indefinitely. + # + # @return [Object] self + # + # @!macro delay_note_regarding_blocking + def wait(timeout = nil) + if @executor + execute_task_once + super(timeout) + else + value + end + self + end + + # Reconfigures the block returning the value if still `#incomplete?` + # + # @yield the delayed operation to perform + # @return [true, false] if success + def reconfigure(&block) + synchronize do + raise ArgumentError.new('no block given') unless block_given? + unless @evaluation_started + @task = block + true + else + false + end + end + end + + protected + + def ns_initialize(opts, &block) + init_obligation + set_deref_options(opts) + @executor = opts[:executor] + + @task = block + @state = :pending + @evaluation_started = false + end + + private + + # @!visibility private + def execute_task_once # :nodoc: + # this function has been optimized for performance and + # should not be modified without running new benchmarks + execute = task = nil + synchronize do + execute = @evaluation_started = true unless @evaluation_started + task = @task + end + + if execute + executor = Options.executor_from_options(executor: @executor) + executor.post do + begin + result = task.call + success = true + rescue => ex + reason = ex + end + synchronize do + set_state(success, result, reason) + event.set + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/errors.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/errors.rb new file mode 100644 index 0000000000..74f1fc3dda --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/errors.rb @@ -0,0 +1,74 @@ +module Concurrent + + Error = Class.new(StandardError) + + # Raised when errors occur during configuration. + ConfigurationError = Class.new(Error) + + # Raised when an asynchronous operation is cancelled before execution. + CancelledOperationError = Class.new(Error) + + # Raised when a lifecycle method (such as `stop`) is called in an improper + # sequence or when the object is in an inappropriate state. + LifecycleError = Class.new(Error) + + # Raised when an attempt is made to violate an immutability guarantee. + ImmutabilityError = Class.new(Error) + + # Raised when an operation is attempted which is not legal given the + # receiver's current state + IllegalOperationError = Class.new(Error) + + # Raised when an object's methods are called when it has not been + # properly initialized. + InitializationError = Class.new(Error) + + # Raised when an object with a start/stop lifecycle has been started an + # excessive number of times. Often used in conjunction with a restart + # policy or strategy. + MaxRestartFrequencyError = Class.new(Error) + + # Raised when an attempt is made to modify an immutable object + # (such as an `IVar`) after its final state has been set. + class MultipleAssignmentError < Error + attr_reader :inspection_data + + def initialize(message = nil, inspection_data = nil) + @inspection_data = inspection_data + super message + end + + def inspect + format '%s %s>', super[0..-2], @inspection_data.inspect + end + end + + # Raised by an `Executor` when it is unable to process a given task, + # possibly because of a reject policy or other internal error. + RejectedExecutionError = Class.new(Error) + + # Raised when any finite resource, such as a lock counter, exceeds its + # maximum limit/threshold. + ResourceLimitError = Class.new(Error) + + # Raised when an operation times out. + TimeoutError = Class.new(Error) + + # Aggregates multiple exceptions. + class MultipleErrors < Error + attr_reader :errors + + def initialize(errors, message = "#{errors.size} errors") + @errors = errors + super [*message, + *errors.map { |e| [format('%s (%s)', e.message, e.class), *e.backtrace] }.flatten(1) + ].join("\n") + end + end + + # @!macro internal_implementation_note + class ConcurrentUpdateError < ThreadError + # frozen pre-allocated backtrace to speed ConcurrentUpdateError + CONC_UP_ERR_BACKTRACE = ['backtrace elided; set verbose to enable'].freeze + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/exchanger.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/exchanger.rb new file mode 100644 index 0000000000..a5405d2522 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/exchanger.rb @@ -0,0 +1,353 @@ +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/maybe' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/utility/engine' +require 'concurrent/utility/monotonic_time' + +module Concurrent + + # @!macro exchanger + # + # A synchronization point at which threads can pair and swap elements within + # pairs. Each thread presents some object on entry to the exchange method, + # matches with a partner thread, and receives its partner's object on return. + # + # @!macro thread_safe_variable_comparison + # + # This implementation is very simple, using only a single slot for each + # exchanger (unlike more advanced implementations which use an "arena"). + # This approach will work perfectly fine when there are only a few threads + # accessing a single `Exchanger`. Beyond a handful of threads the performance + # will degrade rapidly due to contention on the single slot, but the algorithm + # will remain correct. + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html java.util.concurrent.Exchanger + # @example + # + # exchanger = Concurrent::Exchanger.new + # + # threads = [ + # Thread.new { puts "first: " << exchanger.exchange('foo', 1) }, #=> "first: bar" + # Thread.new { puts "second: " << exchanger.exchange('bar', 1) } #=> "second: foo" + # ] + # threads.each {|t| t.join(2) } + + # @!visibility private + class AbstractExchanger < Synchronization::Object + + # @!visibility private + CANCEL = ::Object.new + private_constant :CANCEL + + def initialize + super + end + + # @!macro exchanger_method_do_exchange + # + # Waits for another thread to arrive at this exchange point (unless the + # current thread is interrupted), and then transfers the given object to + # it, receiving its object in return. The timeout value indicates the + # approximate number of seconds the method should block while waiting + # for the exchange. When the timeout value is `nil` the method will + # block indefinitely. + # + # @param [Object] value the value to exchange with another thread + # @param [Numeric, nil] timeout in seconds, `nil` blocks indefinitely + # + # @!macro exchanger_method_exchange + # + # In some edge cases when a `timeout` is given a return value of `nil` may be + # ambiguous. Specifically, if `nil` is a valid value in the exchange it will + # be impossible to tell whether `nil` is the actual return value or if it + # signifies timeout. When `nil` is a valid value in the exchange consider + # using {#exchange!} or {#try_exchange} instead. + # + # @return [Object] the value exchanged by the other thread or `nil` on timeout + def exchange(value, timeout = nil) + (value = do_exchange(value, timeout)) == CANCEL ? nil : value + end + + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_exchange_bang + # + # On timeout a {Concurrent::TimeoutError} exception will be raised. + # + # @return [Object] the value exchanged by the other thread + # @raise [Concurrent::TimeoutError] on timeout + def exchange!(value, timeout = nil) + if (value = do_exchange(value, timeout)) == CANCEL + raise Concurrent::TimeoutError + else + value + end + end + + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_try_exchange + # + # The return value will be a {Concurrent::Maybe} set to `Just` on success or + # `Nothing` on timeout. + # + # @return [Concurrent::Maybe] on success a `Just` maybe will be returned with + # the item exchanged by the other thread as `#value`; on timeout a + # `Nothing` maybe will be returned with {Concurrent::TimeoutError} as `#reason` + # + # @example + # + # exchanger = Concurrent::Exchanger.new + # + # result = exchanger.exchange(:foo, 0.5) + # + # if result.just? + # puts result.value #=> :bar + # else + # puts 'timeout' + # end + def try_exchange(value, timeout = nil) + if (value = do_exchange(value, timeout)) == CANCEL + Concurrent::Maybe.nothing(Concurrent::TimeoutError) + else + Concurrent::Maybe.just(value) + end + end + + private + + # @!macro exchanger_method_do_exchange + # + # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout + def do_exchange(value, timeout) + raise NotImplementedError + end + end + + # @!macro internal_implementation_note + # @!visibility private + class RubyExchanger < AbstractExchanger + # A simplified version of java.util.concurrent.Exchanger written by + # Doug Lea, Bill Scherer, and Michael Scott with assistance from members + # of JCP JSR-166 Expert Group and released to the public domain. It does + # not include the arena or the multi-processor spin loops. + # http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/Exchanger.java + + safe_initialization! + + class Node < Concurrent::Synchronization::Object + attr_atomic :value + safe_initialization! + + def initialize(item) + super() + @Item = item + @Latch = Concurrent::CountDownLatch.new + self.value = nil + end + + def latch + @Latch + end + + def item + @Item + end + end + private_constant :Node + + def initialize + super + end + + private + + attr_atomic(:slot) + + # @!macro exchanger_method_do_exchange + # + # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout + def do_exchange(value, timeout) + + # ALGORITHM + # + # From the original Java version: + # + # > The basic idea is to maintain a "slot", which is a reference to + # > a Node containing both an Item to offer and a "hole" waiting to + # > get filled in. If an incoming "occupying" thread sees that the + # > slot is null, it CAS'es (compareAndSets) a Node there and waits + # > for another to invoke exchange. That second "fulfilling" thread + # > sees that the slot is non-null, and so CASes it back to null, + # > also exchanging items by CASing the hole, plus waking up the + # > occupying thread if it is blocked. In each case CAS'es may + # > fail because a slot at first appears non-null but is null upon + # > CAS, or vice-versa. So threads may need to retry these + # > actions. + # + # This version: + # + # An exchange occurs between an "occupier" thread and a "fulfiller" thread. + # The "slot" is used to setup this interaction. The first thread in the + # exchange puts itself into the slot (occupies) and waits for a fulfiller. + # The second thread removes the occupier from the slot and attempts to + # perform the exchange. Removing the occupier also frees the slot for + # another occupier/fulfiller pair. + # + # Because the occupier and the fulfiller are operating independently and + # because there may be contention with other threads, any failed operation + # indicates contention. Both the occupier and the fulfiller operate within + # spin loops. Any failed actions along the happy path will cause the thread + # to repeat the loop and try again. + # + # When a timeout value is given the thread must be cognizant of time spent + # in the spin loop. The remaining time is checked every loop. When the time + # runs out the thread will exit. + # + # A "node" is the data structure used to perform the exchange. Only the + # occupier's node is necessary. It's the node used for the exchange. + # Each node has an "item," a "hole" (self), and a "latch." The item is the + # node's initial value. It never changes. It's what the fulfiller returns on + # success. The occupier's hole is where the fulfiller put its item. It's the + # item that the occupier returns on success. The latch is used for synchronization. + # Because a thread may act as either an occupier or fulfiller (or possibly + # both in periods of high contention) every thread creates a node when + # the exchange method is first called. + # + # The following steps occur within the spin loop. If any actions fail + # the thread will loop and try again, so long as there is time remaining. + # If time runs out the thread will return CANCEL. + # + # Check the slot for an occupier: + # + # * If the slot is empty try to occupy + # * If the slot is full try to fulfill + # + # Attempt to occupy: + # + # * Attempt to CAS myself into the slot + # * Go to sleep and wait to be woken by a fulfiller + # * If the sleep is successful then the fulfiller completed its happy path + # - Return the value from my hole (the value given by the fulfiller) + # * When the sleep fails (time ran out) attempt to cancel the operation + # - Attempt to CAS myself out of the hole + # - If successful there is no contention + # - Return CANCEL + # - On failure, I am competing with a fulfiller + # - Attempt to CAS my hole to CANCEL + # - On success + # - Let the fulfiller deal with my cancel + # - Return CANCEL + # - On failure the fulfiller has completed its happy path + # - Return th value from my hole (the fulfiller's value) + # + # Attempt to fulfill: + # + # * Attempt to CAS the occupier out of the slot + # - On failure loop again + # * Attempt to CAS my item into the occupier's hole + # - On failure the occupier is trying to cancel + # - Loop again + # - On success we are on the happy path + # - Wake the sleeping occupier + # - Return the occupier's item + + value = NULL if value.nil? # The sentinel allows nil to be a valid value + me = Node.new(value) # create my node in case I need to occupy + end_at = Concurrent.monotonic_time + timeout.to_f # The time to give up + + result = loop do + other = slot + if other && compare_and_set_slot(other, nil) + # try to fulfill + if other.compare_and_set_value(nil, value) + # happy path + other.latch.count_down + break other.item + end + elsif other.nil? && compare_and_set_slot(nil, me) + # try to occupy + timeout = end_at - Concurrent.monotonic_time if timeout + if me.latch.wait(timeout) + # happy path + break me.value + else + # attempt to remove myself from the slot + if compare_and_set_slot(me, nil) + break CANCEL + elsif !me.compare_and_set_value(nil, CANCEL) + # I've failed to block the fulfiller + break me.value + end + end + end + break CANCEL if timeout && Concurrent.monotonic_time >= end_at + end + + result == NULL ? nil : result + end + end + + if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + + # @!macro internal_implementation_note + # @!visibility private + class JavaExchanger < AbstractExchanger + + def initialize + @exchanger = java.util.concurrent.Exchanger.new + end + + private + + # @!macro exchanger_method_do_exchange + # + # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout + def do_exchange(value, timeout) + result = nil + if timeout.nil? + Synchronization::JRuby.sleep_interruptibly do + result = @exchanger.exchange(value) + end + else + Synchronization::JRuby.sleep_interruptibly do + result = @exchanger.exchange(value, 1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS) + end + end + result + rescue java.util.concurrent.TimeoutException + CANCEL + end + end + end + + # @!visibility private + # @!macro internal_implementation_note + ExchangerImplementation = case + when Concurrent.on_jruby? + JavaExchanger + else + RubyExchanger + end + private_constant :ExchangerImplementation + + # @!macro exchanger + class Exchanger < ExchangerImplementation + + # @!method initialize + # Creates exchanger instance + + # @!method exchange(value, timeout = nil) + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_exchange + + # @!method exchange!(value, timeout = nil) + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_exchange_bang + + # @!method try_exchange(value, timeout = nil) + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_try_exchange + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/abstract_executor_service.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/abstract_executor_service.rb new file mode 100644 index 0000000000..ac429531bf --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/abstract_executor_service.rb @@ -0,0 +1,131 @@ +require 'concurrent/errors' +require 'concurrent/concern/deprecation' +require 'concurrent/executor/executor_service' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # @!macro abstract_executor_service_public_api + # @!visibility private + class AbstractExecutorService < Synchronization::LockableObject + include ExecutorService + include Concern::Deprecation + + # The set of possible fallback policies that may be set at thread pool creation. + FALLBACK_POLICIES = [:abort, :discard, :caller_runs].freeze + + # @!macro executor_service_attr_reader_fallback_policy + attr_reader :fallback_policy + + attr_reader :name + + # Create a new thread pool. + def initialize(opts = {}, &block) + super(&nil) + synchronize do + @auto_terminate = opts.fetch(:auto_terminate, true) + @name = opts.fetch(:name) if opts.key?(:name) + ns_initialize(opts, &block) + end + end + + def to_s + name ? "#{super[0..-2]} name: #{name}>" : super + end + + # @!macro executor_service_method_shutdown + def shutdown + raise NotImplementedError + end + + # @!macro executor_service_method_kill + def kill + raise NotImplementedError + end + + # @!macro executor_service_method_wait_for_termination + def wait_for_termination(timeout = nil) + raise NotImplementedError + end + + # @!macro executor_service_method_running_question + def running? + synchronize { ns_running? } + end + + # @!macro executor_service_method_shuttingdown_question + def shuttingdown? + synchronize { ns_shuttingdown? } + end + + # @!macro executor_service_method_shutdown_question + def shutdown? + synchronize { ns_shutdown? } + end + + # @!macro executor_service_method_auto_terminate_question + def auto_terminate? + synchronize { @auto_terminate } + end + + # @!macro executor_service_method_auto_terminate_setter + def auto_terminate=(value) + deprecated "Method #auto_terminate= has no effect. Set :auto_terminate option when executor is initialized." + end + + private + + # Returns an action which executes the `fallback_policy` once the queue + # size reaches `max_queue`. The reason for the indirection of an action + # is so that the work can be deferred outside of synchronization. + # + # @param [Array] args the arguments to the task which is being handled. + # + # @!visibility private + def fallback_action(*args) + case fallback_policy + when :abort + lambda { raise RejectedExecutionError } + when :discard + lambda { false } + when :caller_runs + lambda { + begin + yield(*args) + rescue => ex + # let it fail + log DEBUG, ex + end + true + } + else + lambda { fail "Unknown fallback policy #{fallback_policy}" } + end + end + + def ns_execute(*args, &task) + raise NotImplementedError + end + + # @!macro executor_service_method_ns_shutdown_execution + # + # Callback method called when an orderly shutdown has completed. + # The default behavior is to signal all waiting threads. + def ns_shutdown_execution + # do nothing + end + + # @!macro executor_service_method_ns_kill_execution + # + # Callback method called when the executor has been killed. + # The default behavior is to do nothing. + def ns_kill_execution + # do nothing + end + + def ns_auto_terminate? + @auto_terminate + end + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/cached_thread_pool.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/cached_thread_pool.rb new file mode 100644 index 0000000000..de50ed1791 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/cached_thread_pool.rb @@ -0,0 +1,62 @@ +require 'concurrent/utility/engine' +require 'concurrent/executor/thread_pool_executor' + +module Concurrent + + # A thread pool that dynamically grows and shrinks to fit the current workload. + # New threads are created as needed, existing threads are reused, and threads + # that remain idle for too long are killed and removed from the pool. These + # pools are particularly suited to applications that perform a high volume of + # short-lived tasks. + # + # On creation a `CachedThreadPool` has zero running threads. New threads are + # created on the pool as new operations are `#post`. The size of the pool + # will grow until `#max_length` threads are in the pool or until the number + # of threads exceeds the number of running and pending operations. When a new + # operation is post to the pool the first available idle thread will be tasked + # with the new operation. + # + # Should a thread crash for any reason the thread will immediately be removed + # from the pool. Similarly, threads which remain idle for an extended period + # of time will be killed and reclaimed. Thus these thread pools are very + # efficient at reclaiming unused resources. + # + # The API and behavior of this class are based on Java's `CachedThreadPool` + # + # @!macro thread_pool_options + class CachedThreadPool < ThreadPoolExecutor + + # @!macro cached_thread_pool_method_initialize + # + # Create a new thread pool. + # + # @param [Hash] opts the options defining pool behavior. + # @option opts [Symbol] :fallback_policy (`:abort`) the fallback policy + # + # @raise [ArgumentError] if `fallback_policy` is not a known policy + # + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newCachedThreadPool-- + def initialize(opts = {}) + defaults = { idletime: DEFAULT_THREAD_IDLETIMEOUT } + overrides = { min_threads: 0, + max_threads: DEFAULT_MAX_POOL_SIZE, + max_queue: DEFAULT_MAX_QUEUE_SIZE } + super(defaults.merge(opts).merge(overrides)) + end + + private + + # @!macro cached_thread_pool_method_initialize + # @!visibility private + def ns_initialize(opts) + super(opts) + if Concurrent.on_jruby? + @max_queue = 0 + @executor = java.util.concurrent.Executors.newCachedThreadPool( + DaemonThreadFactory.new(ns_auto_terminate?)) + @executor.setRejectedExecutionHandler(FALLBACK_POLICY_CLASSES[@fallback_policy].new) + @executor.setKeepAliveTime(opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT), java.util.concurrent.TimeUnit::SECONDS) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/executor_service.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/executor_service.rb new file mode 100644 index 0000000000..7e344919e0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/executor_service.rb @@ -0,0 +1,185 @@ +require 'concurrent/concern/logging' + +module Concurrent + + ################################################################### + + # @!macro executor_service_method_post + # + # Submit a task to the executor for asynchronous processing. + # + # @param [Array] args zero or more arguments to be passed to the task + # + # @yield the asynchronous task to perform + # + # @return [Boolean] `true` if the task is queued, `false` if the executor + # is not running + # + # @raise [ArgumentError] if no task is given + + # @!macro executor_service_method_left_shift + # + # Submit a task to the executor for asynchronous processing. + # + # @param [Proc] task the asynchronous task to perform + # + # @return [self] returns itself + + # @!macro executor_service_method_can_overflow_question + # + # Does the task queue have a maximum size? + # + # @return [Boolean] True if the task queue has a maximum size else false. + + # @!macro executor_service_method_serialized_question + # + # Does this executor guarantee serialization of its operations? + # + # @return [Boolean] True if the executor guarantees that all operations + # will be post in the order they are received and no two operations may + # occur simultaneously. Else false. + + ################################################################### + + # @!macro executor_service_public_api + # + # @!method post(*args, &task) + # @!macro executor_service_method_post + # + # @!method <<(task) + # @!macro executor_service_method_left_shift + # + # @!method can_overflow? + # @!macro executor_service_method_can_overflow_question + # + # @!method serialized? + # @!macro executor_service_method_serialized_question + + ################################################################### + + # @!macro executor_service_attr_reader_fallback_policy + # @return [Symbol] The fallback policy in effect. Either `:abort`, `:discard`, or `:caller_runs`. + + # @!macro executor_service_method_shutdown + # + # Begin an orderly shutdown. Tasks already in the queue will be executed, + # but no new tasks will be accepted. Has no additional effect if the + # thread pool is not running. + + # @!macro executor_service_method_kill + # + # Begin an immediate shutdown. In-progress tasks will be allowed to + # complete but enqueued tasks will be dismissed and no new tasks + # will be accepted. Has no additional effect if the thread pool is + # not running. + + # @!macro executor_service_method_wait_for_termination + # + # Block until executor shutdown is complete or until `timeout` seconds have + # passed. + # + # @note Does not initiate shutdown or termination. Either `shutdown` or `kill` + # must be called before this method (or on another thread). + # + # @param [Integer] timeout the maximum number of seconds to wait for shutdown to complete + # + # @return [Boolean] `true` if shutdown complete or false on `timeout` + + # @!macro executor_service_method_running_question + # + # Is the executor running? + # + # @return [Boolean] `true` when running, `false` when shutting down or shutdown + + # @!macro executor_service_method_shuttingdown_question + # + # Is the executor shuttingdown? + # + # @return [Boolean] `true` when not running and not shutdown, else `false` + + # @!macro executor_service_method_shutdown_question + # + # Is the executor shutdown? + # + # @return [Boolean] `true` when shutdown, `false` when shutting down or running + + # @!macro executor_service_method_auto_terminate_question + # + # Is the executor auto-terminate when the application exits? + # + # @return [Boolean] `true` when auto-termination is enabled else `false`. + + # @!macro executor_service_method_auto_terminate_setter + # + # + # Set the auto-terminate behavior for this executor. + # @deprecated Has no effect + # @param [Boolean] value The new auto-terminate value to set for this executor. + # @return [Boolean] `true` when auto-termination is enabled else `false`. + + ################################################################### + + # @!macro abstract_executor_service_public_api + # + # @!macro executor_service_public_api + # + # @!attribute [r] fallback_policy + # @!macro executor_service_attr_reader_fallback_policy + # + # @!method shutdown + # @!macro executor_service_method_shutdown + # + # @!method kill + # @!macro executor_service_method_kill + # + # @!method wait_for_termination(timeout = nil) + # @!macro executor_service_method_wait_for_termination + # + # @!method running? + # @!macro executor_service_method_running_question + # + # @!method shuttingdown? + # @!macro executor_service_method_shuttingdown_question + # + # @!method shutdown? + # @!macro executor_service_method_shutdown_question + # + # @!method auto_terminate? + # @!macro executor_service_method_auto_terminate_question + # + # @!method auto_terminate=(value) + # @!macro executor_service_method_auto_terminate_setter + + ################################################################### + + # @!macro executor_service_public_api + # @!visibility private + module ExecutorService + include Concern::Logging + + # @!macro executor_service_method_post + def post(*args, &task) + raise NotImplementedError + end + + # @!macro executor_service_method_left_shift + def <<(task) + post(&task) + self + end + + # @!macro executor_service_method_can_overflow_question + # + # @note Always returns `false` + def can_overflow? + false + end + + # @!macro executor_service_method_serialized_question + # + # @note Always returns `false` + def serialized? + false + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/fixed_thread_pool.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/fixed_thread_pool.rb new file mode 100644 index 0000000000..4de512a5ff --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/fixed_thread_pool.rb @@ -0,0 +1,220 @@ +require 'concurrent/utility/engine' +require 'concurrent/executor/thread_pool_executor' + +module Concurrent + + # @!macro thread_pool_executor_constant_default_max_pool_size + # Default maximum number of threads that will be created in the pool. + + # @!macro thread_pool_executor_constant_default_min_pool_size + # Default minimum number of threads that will be retained in the pool. + + # @!macro thread_pool_executor_constant_default_max_queue_size + # Default maximum number of tasks that may be added to the task queue. + + # @!macro thread_pool_executor_constant_default_thread_timeout + # Default maximum number of seconds a thread in the pool may remain idle + # before being reclaimed. + + # @!macro thread_pool_executor_constant_default_synchronous + # Default value of the :synchronous option. + + # @!macro thread_pool_executor_attr_reader_max_length + # The maximum number of threads that may be created in the pool. + # @return [Integer] The maximum number of threads that may be created in the pool. + + # @!macro thread_pool_executor_attr_reader_min_length + # The minimum number of threads that may be retained in the pool. + # @return [Integer] The minimum number of threads that may be retained in the pool. + + # @!macro thread_pool_executor_attr_reader_largest_length + # The largest number of threads that have been created in the pool since construction. + # @return [Integer] The largest number of threads that have been created in the pool since construction. + + # @!macro thread_pool_executor_attr_reader_scheduled_task_count + # The number of tasks that have been scheduled for execution on the pool since construction. + # @return [Integer] The number of tasks that have been scheduled for execution on the pool since construction. + + # @!macro thread_pool_executor_attr_reader_completed_task_count + # The number of tasks that have been completed by the pool since construction. + # @return [Integer] The number of tasks that have been completed by the pool since construction. + + # @!macro thread_pool_executor_attr_reader_idletime + # The number of seconds that a thread may be idle before being reclaimed. + # @return [Integer] The number of seconds that a thread may be idle before being reclaimed. + + # @!macro thread_pool_executor_attr_reader_synchronous + # Whether or not a value of 0 for :max_queue option means the queue must perform direct hand-off or rather unbounded queue. + # @return [true, false] + + # @!macro thread_pool_executor_attr_reader_max_queue + # The maximum number of tasks that may be waiting in the work queue at any one time. + # When the queue size reaches `max_queue` subsequent tasks will be rejected in + # accordance with the configured `fallback_policy`. + # + # @return [Integer] The maximum number of tasks that may be waiting in the work queue at any one time. + # When the queue size reaches `max_queue` subsequent tasks will be rejected in + # accordance with the configured `fallback_policy`. + + # @!macro thread_pool_executor_attr_reader_length + # The number of threads currently in the pool. + # @return [Integer] The number of threads currently in the pool. + + # @!macro thread_pool_executor_attr_reader_queue_length + # The number of tasks in the queue awaiting execution. + # @return [Integer] The number of tasks in the queue awaiting execution. + + # @!macro thread_pool_executor_attr_reader_remaining_capacity + # Number of tasks that may be enqueued before reaching `max_queue` and rejecting + # new tasks. A value of -1 indicates that the queue may grow without bound. + # + # @return [Integer] Number of tasks that may be enqueued before reaching `max_queue` and rejecting + # new tasks. A value of -1 indicates that the queue may grow without bound. + + # @!macro thread_pool_executor_method_prune_pool + # Prune the thread pool of unneeded threads + # + # What is being pruned is controlled by the min_threads and idletime + # parameters passed at pool creation time + # + # This is a no-op on some pool implementation (e.g. the Java one). The Ruby + # pool will auto-prune each time a new job is posted. You will need to call + # this method explicitely in case your application post jobs in bursts (a + # lot of jobs and then nothing for long periods) + + # @!macro thread_pool_executor_public_api + # + # @!macro abstract_executor_service_public_api + # + # @!attribute [r] max_length + # @!macro thread_pool_executor_attr_reader_max_length + # + # @!attribute [r] min_length + # @!macro thread_pool_executor_attr_reader_min_length + # + # @!attribute [r] largest_length + # @!macro thread_pool_executor_attr_reader_largest_length + # + # @!attribute [r] scheduled_task_count + # @!macro thread_pool_executor_attr_reader_scheduled_task_count + # + # @!attribute [r] completed_task_count + # @!macro thread_pool_executor_attr_reader_completed_task_count + # + # @!attribute [r] idletime + # @!macro thread_pool_executor_attr_reader_idletime + # + # @!attribute [r] max_queue + # @!macro thread_pool_executor_attr_reader_max_queue + # + # @!attribute [r] length + # @!macro thread_pool_executor_attr_reader_length + # + # @!attribute [r] queue_length + # @!macro thread_pool_executor_attr_reader_queue_length + # + # @!attribute [r] remaining_capacity + # @!macro thread_pool_executor_attr_reader_remaining_capacity + # + # @!method can_overflow? + # @!macro executor_service_method_can_overflow_question + # + # @!method prune_pool + # @!macro thread_pool_executor_method_prune_pool + + + + + # @!macro thread_pool_options + # + # **Thread Pool Options** + # + # Thread pools support several configuration options: + # + # * `idletime`: The number of seconds that a thread may be idle before being reclaimed. + # * `name`: The name of the executor (optional). Printed in the executor's `#to_s` output and + # a `-worker-` name is given to its threads if supported by used Ruby + # implementation. `` is uniq for each thread. + # * `max_queue`: The maximum number of tasks that may be waiting in the work queue at + # any one time. When the queue size reaches `max_queue` and no new threads can be created, + # subsequent tasks will be rejected in accordance with the configured `fallback_policy`. + # * `auto_terminate`: When true (default), the threads started will be marked as daemon. + # * `fallback_policy`: The policy defining how rejected tasks are handled. + # + # Three fallback policies are supported: + # + # * `:abort`: Raise a `RejectedExecutionError` exception and discard the task. + # * `:discard`: Discard the task and return false. + # * `:caller_runs`: Execute the task on the calling thread. + # + # **Shutting Down Thread Pools** + # + # Killing a thread pool while tasks are still being processed, either by calling + # the `#kill` method or at application exit, will have unpredictable results. There + # is no way for the thread pool to know what resources are being used by the + # in-progress tasks. When those tasks are killed the impact on those resources + # cannot be predicted. The *best* practice is to explicitly shutdown all thread + # pools using the provided methods: + # + # * Call `#shutdown` to initiate an orderly termination of all in-progress tasks + # * Call `#wait_for_termination` with an appropriate timeout interval an allow + # the orderly shutdown to complete + # * Call `#kill` *only when* the thread pool fails to shutdown in the allotted time + # + # On some runtime platforms (most notably the JVM) the application will not + # exit until all thread pools have been shutdown. To prevent applications from + # "hanging" on exit, all threads can be marked as daemon according to the + # `:auto_terminate` option. + # + # ```ruby + # pool1 = Concurrent::FixedThreadPool.new(5) # threads will be marked as daemon + # pool2 = Concurrent::FixedThreadPool.new(5, auto_terminate: false) # mark threads as non-daemon + # ``` + # + # @note Failure to properly shutdown a thread pool can lead to unpredictable results. + # Please read *Shutting Down Thread Pools* for more information. + # + # @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html Java Tutorials: Thread Pools + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html Java Executors class + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html Java ExecutorService interface + # @see https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setDaemon-boolean- + + + + + + # @!macro fixed_thread_pool + # + # A thread pool that reuses a fixed number of threads operating off an unbounded queue. + # At any point, at most `num_threads` will be active processing tasks. When all threads are busy new + # tasks `#post` to the thread pool are enqueued until a thread becomes available. + # Should a thread crash for any reason the thread will immediately be removed + # from the pool and replaced. + # + # The API and behavior of this class are based on Java's `FixedThreadPool` + # + # @!macro thread_pool_options + class FixedThreadPool < ThreadPoolExecutor + + # @!macro fixed_thread_pool_method_initialize + # + # Create a new thread pool. + # + # @param [Integer] num_threads the number of threads to allocate + # @param [Hash] opts the options defining pool behavior. + # @option opts [Symbol] :fallback_policy (`:abort`) the fallback policy + # + # @raise [ArgumentError] if `num_threads` is less than or equal to zero + # @raise [ArgumentError] if `fallback_policy` is not a known policy + # + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- + def initialize(num_threads, opts = {}) + raise ArgumentError.new('number of threads must be greater than zero') if num_threads.to_i < 1 + defaults = { max_queue: DEFAULT_MAX_QUEUE_SIZE, + idletime: DEFAULT_THREAD_IDLETIMEOUT } + overrides = { min_threads: num_threads, + max_threads: num_threads } + super(defaults.merge(opts).merge(overrides)) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/immediate_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/immediate_executor.rb new file mode 100644 index 0000000000..282df7a059 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/immediate_executor.rb @@ -0,0 +1,66 @@ +require 'concurrent/atomic/event' +require 'concurrent/executor/abstract_executor_service' +require 'concurrent/executor/serial_executor_service' + +module Concurrent + + # An executor service which runs all operations on the current thread, + # blocking as necessary. Operations are performed in the order they are + # received and no two operations can be performed simultaneously. + # + # This executor service exists mainly for testing an debugging. When used + # it immediately runs every `#post` operation on the current thread, blocking + # that thread until the operation is complete. This can be very beneficial + # during testing because it makes all operations deterministic. + # + # @note Intended for use primarily in testing and debugging. + class ImmediateExecutor < AbstractExecutorService + include SerialExecutorService + + # Creates a new executor + def initialize + @stopped = Concurrent::Event.new + end + + # @!macro executor_service_method_post + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + return false unless running? + task.call(*args) + true + end + + # @!macro executor_service_method_left_shift + def <<(task) + post(&task) + self + end + + # @!macro executor_service_method_running_question + def running? + ! shutdown? + end + + # @!macro executor_service_method_shuttingdown_question + def shuttingdown? + false + end + + # @!macro executor_service_method_shutdown_question + def shutdown? + @stopped.set? + end + + # @!macro executor_service_method_shutdown + def shutdown + @stopped.set + true + end + alias_method :kill, :shutdown + + # @!macro executor_service_method_wait_for_termination + def wait_for_termination(timeout = nil) + @stopped.wait(timeout) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/indirect_immediate_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/indirect_immediate_executor.rb new file mode 100644 index 0000000000..4f9769fa3f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/indirect_immediate_executor.rb @@ -0,0 +1,44 @@ +require 'concurrent/executor/immediate_executor' +require 'concurrent/executor/simple_executor_service' + +module Concurrent + # An executor service which runs all operations on a new thread, blocking + # until it completes. Operations are performed in the order they are received + # and no two operations can be performed simultaneously. + # + # This executor service exists mainly for testing an debugging. When used it + # immediately runs every `#post` operation on a new thread, blocking the + # current thread until the operation is complete. This is similar to how the + # ImmediateExecutor works, but the operation has the full stack of the new + # thread at its disposal. This can be helpful when the operations will spawn + # more operations on the same executor and so on - such a situation might + # overflow the single stack in case of an ImmediateExecutor, which is + # inconsistent with how it would behave for a threaded executor. + # + # @note Intended for use primarily in testing and debugging. + class IndirectImmediateExecutor < ImmediateExecutor + # Creates a new executor + def initialize + super + @internal_executor = SimpleExecutorService.new + end + + # @!macro executor_service_method_post + def post(*args, &task) + raise ArgumentError.new("no block given") unless block_given? + return false unless running? + + event = Concurrent::Event.new + @internal_executor.post do + begin + task.call(*args) + ensure + event.set + end + end + event.wait + + true + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_executor_service.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_executor_service.rb new file mode 100644 index 0000000000..9a86385520 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_executor_service.rb @@ -0,0 +1,103 @@ +require 'concurrent/utility/engine' + +if Concurrent.on_jruby? + require 'concurrent/errors' + require 'concurrent/executor/abstract_executor_service' + + module Concurrent + + # @!macro abstract_executor_service_public_api + # @!visibility private + class JavaExecutorService < AbstractExecutorService + java_import 'java.lang.Runnable' + + FALLBACK_POLICY_CLASSES = { + abort: java.util.concurrent.ThreadPoolExecutor::AbortPolicy, + discard: java.util.concurrent.ThreadPoolExecutor::DiscardPolicy, + caller_runs: java.util.concurrent.ThreadPoolExecutor::CallerRunsPolicy + }.freeze + private_constant :FALLBACK_POLICY_CLASSES + + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + return fallback_action(*args, &task).call unless running? + @executor.submit Job.new(args, task) + true + rescue Java::JavaUtilConcurrent::RejectedExecutionException + raise RejectedExecutionError + end + + def wait_for_termination(timeout = nil) + if timeout.nil? + ok = @executor.awaitTermination(60, java.util.concurrent.TimeUnit::SECONDS) until ok + true + else + @executor.awaitTermination(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS) + end + end + + def shutdown + synchronize do + @executor.shutdown + nil + end + end + + def kill + synchronize do + @executor.shutdownNow + nil + end + end + + private + + def ns_running? + !(ns_shuttingdown? || ns_shutdown?) + end + + def ns_shuttingdown? + if @executor.respond_to? :isTerminating + @executor.isTerminating + else + false + end + end + + def ns_shutdown? + @executor.isShutdown || @executor.isTerminated + end + + class Job + include Runnable + def initialize(args, block) + @args = args + @block = block + end + + def run + @block.call(*@args) + end + end + private_constant :Job + end + + class DaemonThreadFactory + # hide include from YARD + send :include, java.util.concurrent.ThreadFactory + + def initialize(daemonize = true) + @daemonize = daemonize + end + + def newThread(runnable) + thread = java.util.concurrent.Executors.defaultThreadFactory().newThread(runnable) + thread.setDaemon(@daemonize) + return thread + end + end + + private_constant :DaemonThreadFactory + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_single_thread_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_single_thread_executor.rb new file mode 100644 index 0000000000..7aa24f2d72 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_single_thread_executor.rb @@ -0,0 +1,30 @@ +if Concurrent.on_jruby? + + require 'concurrent/executor/java_executor_service' + require 'concurrent/executor/serial_executor_service' + + module Concurrent + + # @!macro single_thread_executor + # @!macro abstract_executor_service_public_api + # @!visibility private + class JavaSingleThreadExecutor < JavaExecutorService + include SerialExecutorService + + # @!macro single_thread_executor_method_initialize + def initialize(opts = {}) + super(opts) + end + + private + + def ns_initialize(opts) + @executor = java.util.concurrent.Executors.newSingleThreadExecutor( + DaemonThreadFactory.new(ns_auto_terminate?) + ) + @fallback_policy = opts.fetch(:fallback_policy, :discard) + raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICY_CLASSES.keys.include?(@fallback_policy) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_thread_pool_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_thread_pool_executor.rb new file mode 100644 index 0000000000..1213a95fb0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/java_thread_pool_executor.rb @@ -0,0 +1,140 @@ +if Concurrent.on_jruby? + + require 'concurrent/executor/java_executor_service' + + module Concurrent + + # @!macro thread_pool_executor + # @!macro thread_pool_options + # @!visibility private + class JavaThreadPoolExecutor < JavaExecutorService + + # @!macro thread_pool_executor_constant_default_max_pool_size + DEFAULT_MAX_POOL_SIZE = java.lang.Integer::MAX_VALUE # 2147483647 + + # @!macro thread_pool_executor_constant_default_min_pool_size + DEFAULT_MIN_POOL_SIZE = 0 + + # @!macro thread_pool_executor_constant_default_max_queue_size + DEFAULT_MAX_QUEUE_SIZE = 0 + + # @!macro thread_pool_executor_constant_default_thread_timeout + DEFAULT_THREAD_IDLETIMEOUT = 60 + + # @!macro thread_pool_executor_constant_default_synchronous + DEFAULT_SYNCHRONOUS = false + + # @!macro thread_pool_executor_attr_reader_max_length + attr_reader :max_length + + # @!macro thread_pool_executor_attr_reader_max_queue + attr_reader :max_queue + + # @!macro thread_pool_executor_attr_reader_synchronous + attr_reader :synchronous + + # @!macro thread_pool_executor_method_initialize + def initialize(opts = {}) + super(opts) + end + + # @!macro executor_service_method_can_overflow_question + def can_overflow? + @max_queue != 0 + end + + # @!macro thread_pool_executor_attr_reader_min_length + def min_length + @executor.getCorePoolSize + end + + # @!macro thread_pool_executor_attr_reader_max_length + def max_length + @executor.getMaximumPoolSize + end + + # @!macro thread_pool_executor_attr_reader_length + def length + @executor.getPoolSize + end + + # @!macro thread_pool_executor_attr_reader_largest_length + def largest_length + @executor.getLargestPoolSize + end + + # @!macro thread_pool_executor_attr_reader_scheduled_task_count + def scheduled_task_count + @executor.getTaskCount + end + + # @!macro thread_pool_executor_attr_reader_completed_task_count + def completed_task_count + @executor.getCompletedTaskCount + end + + # @!macro thread_pool_executor_attr_reader_idletime + def idletime + @executor.getKeepAliveTime(java.util.concurrent.TimeUnit::SECONDS) + end + + # @!macro thread_pool_executor_attr_reader_queue_length + def queue_length + @executor.getQueue.size + end + + # @!macro thread_pool_executor_attr_reader_remaining_capacity + def remaining_capacity + @max_queue == 0 ? -1 : @executor.getQueue.remainingCapacity + end + + # @!macro executor_service_method_running_question + def running? + super && !@executor.isTerminating + end + + # @!macro thread_pool_executor_method_prune_pool + def prune_pool + end + + private + + def ns_initialize(opts) + min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i + max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i + idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i + @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i + @synchronous = opts.fetch(:synchronous, DEFAULT_SYNCHRONOUS) + @fallback_policy = opts.fetch(:fallback_policy, :abort) + + raise ArgumentError.new("`synchronous` cannot be set unless `max_queue` is 0") if @synchronous && @max_queue > 0 + raise ArgumentError.new("`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if max_length < DEFAULT_MIN_POOL_SIZE + raise ArgumentError.new("`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}") if max_length > DEFAULT_MAX_POOL_SIZE + raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if min_length < DEFAULT_MIN_POOL_SIZE + raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length + raise ArgumentError.new("#{fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICY_CLASSES.include?(@fallback_policy) + + if @max_queue == 0 + if @synchronous + queue = java.util.concurrent.SynchronousQueue.new + else + queue = java.util.concurrent.LinkedBlockingQueue.new + end + else + queue = java.util.concurrent.LinkedBlockingQueue.new(@max_queue) + end + + @executor = java.util.concurrent.ThreadPoolExecutor.new( + min_length, + max_length, + idletime, + java.util.concurrent.TimeUnit::SECONDS, + queue, + DaemonThreadFactory.new(ns_auto_terminate?), + FALLBACK_POLICY_CLASSES[@fallback_policy].new) + + end + end + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_executor_service.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_executor_service.rb new file mode 100644 index 0000000000..1f7301b947 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_executor_service.rb @@ -0,0 +1,82 @@ +require 'concurrent/executor/abstract_executor_service' +require 'concurrent/atomic/event' + +module Concurrent + + # @!macro abstract_executor_service_public_api + # @!visibility private + class RubyExecutorService < AbstractExecutorService + safe_initialization! + + def initialize(*args, &block) + super + @StopEvent = Event.new + @StoppedEvent = Event.new + end + + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + deferred_action = synchronize { + if running? + ns_execute(*args, &task) + else + fallback_action(*args, &task) + end + } + if deferred_action + deferred_action.call + else + true + end + end + + def shutdown + synchronize do + break unless running? + stop_event.set + ns_shutdown_execution + end + true + end + + def kill + synchronize do + break if shutdown? + stop_event.set + ns_kill_execution + stopped_event.set + end + true + end + + def wait_for_termination(timeout = nil) + stopped_event.wait(timeout) + end + + private + + def stop_event + @StopEvent + end + + def stopped_event + @StoppedEvent + end + + def ns_shutdown_execution + stopped_event.set + end + + def ns_running? + !stop_event.set? + end + + def ns_shuttingdown? + !(ns_running? || ns_shutdown?) + end + + def ns_shutdown? + stopped_event.set? + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_single_thread_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_single_thread_executor.rb new file mode 100644 index 0000000000..916337d4ba --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_single_thread_executor.rb @@ -0,0 +1,21 @@ +require 'concurrent/executor/ruby_thread_pool_executor' + +module Concurrent + + # @!macro single_thread_executor + # @!macro abstract_executor_service_public_api + # @!visibility private + class RubySingleThreadExecutor < RubyThreadPoolExecutor + + # @!macro single_thread_executor_method_initialize + def initialize(opts = {}) + super( + min_threads: 1, + max_threads: 1, + max_queue: 0, + idletime: DEFAULT_THREAD_IDLETIMEOUT, + fallback_policy: opts.fetch(:fallback_policy, :discard), + ) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_thread_pool_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_thread_pool_executor.rb new file mode 100644 index 0000000000..298dd7fed0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/ruby_thread_pool_executor.rb @@ -0,0 +1,366 @@ +require 'thread' +require 'concurrent/atomic/event' +require 'concurrent/concern/logging' +require 'concurrent/executor/ruby_executor_service' +require 'concurrent/utility/monotonic_time' + +module Concurrent + + # @!macro thread_pool_executor + # @!macro thread_pool_options + # @!visibility private + class RubyThreadPoolExecutor < RubyExecutorService + + # @!macro thread_pool_executor_constant_default_max_pool_size + DEFAULT_MAX_POOL_SIZE = 2_147_483_647 # java.lang.Integer::MAX_VALUE + + # @!macro thread_pool_executor_constant_default_min_pool_size + DEFAULT_MIN_POOL_SIZE = 0 + + # @!macro thread_pool_executor_constant_default_max_queue_size + DEFAULT_MAX_QUEUE_SIZE = 0 + + # @!macro thread_pool_executor_constant_default_thread_timeout + DEFAULT_THREAD_IDLETIMEOUT = 60 + + # @!macro thread_pool_executor_constant_default_synchronous + DEFAULT_SYNCHRONOUS = false + + # @!macro thread_pool_executor_attr_reader_max_length + attr_reader :max_length + + # @!macro thread_pool_executor_attr_reader_min_length + attr_reader :min_length + + # @!macro thread_pool_executor_attr_reader_idletime + attr_reader :idletime + + # @!macro thread_pool_executor_attr_reader_max_queue + attr_reader :max_queue + + # @!macro thread_pool_executor_attr_reader_synchronous + attr_reader :synchronous + + # @!macro thread_pool_executor_method_initialize + def initialize(opts = {}) + super(opts) + end + + # @!macro thread_pool_executor_attr_reader_largest_length + def largest_length + synchronize { @largest_length } + end + + # @!macro thread_pool_executor_attr_reader_scheduled_task_count + def scheduled_task_count + synchronize { @scheduled_task_count } + end + + # @!macro thread_pool_executor_attr_reader_completed_task_count + def completed_task_count + synchronize { @completed_task_count } + end + + # @!macro executor_service_method_can_overflow_question + def can_overflow? + synchronize { ns_limited_queue? } + end + + # @!macro thread_pool_executor_attr_reader_length + def length + synchronize { @pool.length } + end + + # @!macro thread_pool_executor_attr_reader_queue_length + def queue_length + synchronize { @queue.length } + end + + # @!macro thread_pool_executor_attr_reader_remaining_capacity + def remaining_capacity + synchronize do + if ns_limited_queue? + @max_queue - @queue.length + else + -1 + end + end + end + + # @!visibility private + def remove_busy_worker(worker) + synchronize { ns_remove_busy_worker worker } + end + + # @!visibility private + def ready_worker(worker, last_message) + synchronize { ns_ready_worker worker, last_message } + end + + # @!visibility private + def worker_died(worker) + synchronize { ns_worker_died worker } + end + + # @!visibility private + def worker_task_completed + synchronize { @completed_task_count += 1 } + end + + # @!macro thread_pool_executor_method_prune_pool + def prune_pool + synchronize { ns_prune_pool } + end + + private + + # @!visibility private + def ns_initialize(opts) + @min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i + @max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i + @idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i + @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i + @synchronous = opts.fetch(:synchronous, DEFAULT_SYNCHRONOUS) + @fallback_policy = opts.fetch(:fallback_policy, :abort) + + raise ArgumentError.new("`synchronous` cannot be set unless `max_queue` is 0") if @synchronous && @max_queue > 0 + raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy) + raise ArgumentError.new("`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @max_length < DEFAULT_MIN_POOL_SIZE + raise ArgumentError.new("`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}") if @max_length > DEFAULT_MAX_POOL_SIZE + raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @min_length < DEFAULT_MIN_POOL_SIZE + raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length + + @pool = [] # all workers + @ready = [] # used as a stash (most idle worker is at the start) + @queue = [] # used as queue + # @ready or @queue is empty at all times + @scheduled_task_count = 0 + @completed_task_count = 0 + @largest_length = 0 + @workers_counter = 0 + @ruby_pid = $$ # detects if Ruby has forked + + @gc_interval = opts.fetch(:gc_interval, @idletime / 2.0).to_i # undocumented + @next_gc_time = Concurrent.monotonic_time + @gc_interval + end + + # @!visibility private + def ns_limited_queue? + @max_queue != 0 + end + + # @!visibility private + def ns_execute(*args, &task) + ns_reset_if_forked + + if ns_assign_worker(*args, &task) || ns_enqueue(*args, &task) + @scheduled_task_count += 1 + else + return fallback_action(*args, &task) + end + + ns_prune_pool if @next_gc_time < Concurrent.monotonic_time + nil + end + + # @!visibility private + def ns_shutdown_execution + ns_reset_if_forked + + if @pool.empty? + # nothing to do + stopped_event.set + end + + if @queue.empty? + # no more tasks will be accepted, just stop all workers + @pool.each(&:stop) + end + end + + # @!visibility private + def ns_kill_execution + # TODO log out unprocessed tasks in queue + # TODO try to shutdown first? + @pool.each(&:kill) + @pool.clear + @ready.clear + end + + # tries to assign task to a worker, tries to get one from @ready or to create new one + # @return [true, false] if task is assigned to a worker + # + # @!visibility private + def ns_assign_worker(*args, &task) + # keep growing if the pool is not at the minimum yet + worker, _ = (@ready.pop if @pool.size >= @min_length) || ns_add_busy_worker + if worker + worker << [task, args] + true + else + false + end + rescue ThreadError + # Raised when the operating system refuses to create the new thread + return false + end + + # tries to enqueue task + # @return [true, false] if enqueued + # + # @!visibility private + def ns_enqueue(*args, &task) + return false if @synchronous + + if !ns_limited_queue? || @queue.size < @max_queue + @queue << [task, args] + true + else + false + end + end + + # @!visibility private + def ns_worker_died(worker) + ns_remove_busy_worker worker + replacement_worker = ns_add_busy_worker + ns_ready_worker replacement_worker, Concurrent.monotonic_time, false if replacement_worker + end + + # creates new worker which has to receive work to do after it's added + # @return [nil, Worker] nil of max capacity is reached + # + # @!visibility private + def ns_add_busy_worker + return if @pool.size >= @max_length + + @workers_counter += 1 + @pool << (worker = Worker.new(self, @workers_counter)) + @largest_length = @pool.length if @pool.length > @largest_length + worker + end + + # handle ready worker, giving it new job or assigning back to @ready + # + # @!visibility private + def ns_ready_worker(worker, last_message, success = true) + task_and_args = @queue.shift + if task_and_args + worker << task_and_args + else + # stop workers when !running?, do not return them to @ready + if running? + raise unless last_message + @ready.push([worker, last_message]) + else + worker.stop + end + end + end + + # removes a worker which is not in not tracked in @ready + # + # @!visibility private + def ns_remove_busy_worker(worker) + @pool.delete(worker) + stopped_event.set if @pool.empty? && !running? + true + end + + # try oldest worker if it is idle for enough time, it's returned back at the start + # + # @!visibility private + def ns_prune_pool + now = Concurrent.monotonic_time + stopped_workers = 0 + while !@ready.empty? && (@pool.size - stopped_workers > @min_length) + worker, last_message = @ready.first + if now - last_message > self.idletime + stopped_workers += 1 + @ready.shift + worker << :stop + else break + end + end + + @next_gc_time = Concurrent.monotonic_time + @gc_interval + end + + def ns_reset_if_forked + if $$ != @ruby_pid + @queue.clear + @ready.clear + @pool.clear + @scheduled_task_count = 0 + @completed_task_count = 0 + @largest_length = 0 + @workers_counter = 0 + @ruby_pid = $$ + end + end + + # @!visibility private + class Worker + include Concern::Logging + + def initialize(pool, id) + # instance variables accessed only under pool's lock so no need to sync here again + @queue = Queue.new + @pool = pool + @thread = create_worker @queue, pool, pool.idletime + + if @thread.respond_to?(:name=) + @thread.name = [pool.name, 'worker', id].compact.join('-') + end + end + + def <<(message) + @queue << message + end + + def stop + @queue << :stop + end + + def kill + @thread.kill + end + + private + + def create_worker(queue, pool, idletime) + Thread.new(queue, pool, idletime) do |my_queue, my_pool, my_idletime| + catch(:stop) do + loop do + + case message = my_queue.pop + when :stop + my_pool.remove_busy_worker(self) + throw :stop + + else + task, args = message + run_task my_pool, task, args + my_pool.ready_worker(self, Concurrent.monotonic_time) + end + end + end + end + end + + def run_task(pool, task, args) + task.call(*args) + pool.worker_task_completed + rescue => ex + # let it fail + log DEBUG, ex + rescue Exception => ex + log ERROR, ex + pool.worker_died(self) + throw :stop + end + end + + private_constant :Worker + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/safe_task_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/safe_task_executor.rb new file mode 100644 index 0000000000..f796b8571f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/safe_task_executor.rb @@ -0,0 +1,35 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # A simple utility class that executes a callable and returns and array of three elements: + # success - indicating if the callable has been executed without errors + # value - filled by the callable result if it has been executed without errors, nil otherwise + # reason - the error risen by the callable if it has been executed with errors, nil otherwise + class SafeTaskExecutor < Synchronization::LockableObject + + def initialize(task, opts = {}) + @task = task + @exception_class = opts.fetch(:rescue_exception, false) ? Exception : StandardError + super() # ensures visibility + end + + # @return [Array] + def execute(*args) + success = true + value = reason = nil + + synchronize do + begin + value = @task.call(*args) + success = true + rescue @exception_class => ex + reason = ex + success = false + end + end + + [success, value, reason] + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serial_executor_service.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serial_executor_service.rb new file mode 100644 index 0000000000..f1c38ecfa9 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serial_executor_service.rb @@ -0,0 +1,34 @@ +require 'concurrent/executor/executor_service' + +module Concurrent + + # Indicates that the including `ExecutorService` guarantees + # that all operations will occur in the order they are post and that no + # two operations may occur simultaneously. This module provides no + # functionality and provides no guarantees. That is the responsibility + # of the including class. This module exists solely to allow the including + # object to be interrogated for its serialization status. + # + # @example + # class Foo + # include Concurrent::SerialExecutor + # end + # + # foo = Foo.new + # + # foo.is_a? Concurrent::ExecutorService #=> true + # foo.is_a? Concurrent::SerialExecutor #=> true + # foo.serialized? #=> true + # + # @!visibility private + module SerialExecutorService + include ExecutorService + + # @!macro executor_service_method_serialized_question + # + # @note Always returns `true` + def serialized? + true + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serialized_execution.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serialized_execution.rb new file mode 100644 index 0000000000..4db7c7f0c2 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serialized_execution.rb @@ -0,0 +1,107 @@ +require 'concurrent/errors' +require 'concurrent/concern/logging' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # Ensures passed jobs in a serialized order never running at the same time. + class SerializedExecution < Synchronization::LockableObject + include Concern::Logging + + def initialize() + super() + synchronize { ns_initialize } + end + + Job = Struct.new(:executor, :args, :block) do + def call + block.call(*args) + end + end + + # Submit a task to the executor for asynchronous processing. + # + # @param [Executor] executor to be used for this job + # + # @param [Array] args zero or more arguments to be passed to the task + # + # @yield the asynchronous task to perform + # + # @return [Boolean] `true` if the task is queued, `false` if the executor + # is not running + # + # @raise [ArgumentError] if no task is given + def post(executor, *args, &task) + posts [[executor, args, task]] + true + end + + # As {#post} but allows to submit multiple tasks at once, it's guaranteed that they will not + # be interleaved by other tasks. + # + # @param [Array, Proc)>] posts array of triplets where + # first is a {ExecutorService}, second is array of args for task, third is a task (Proc) + def posts(posts) + # if can_overflow? + # raise ArgumentError, 'SerializedExecution does not support thread-pools which can overflow' + # end + + return nil if posts.empty? + + jobs = posts.map { |executor, args, task| Job.new executor, args, task } + + job_to_post = synchronize do + if @being_executed + @stash.push(*jobs) + nil + else + @being_executed = true + @stash.push(*jobs[1..-1]) + jobs.first + end + end + + call_job job_to_post if job_to_post + true + end + + private + + def ns_initialize + @being_executed = false + @stash = [] + end + + def call_job(job) + did_it_run = begin + job.executor.post { work(job) } + true + rescue RejectedExecutionError => ex + false + end + + # TODO not the best idea to run it myself + unless did_it_run + begin + work job + rescue => ex + # let it fail + log DEBUG, ex + end + end + end + + # ensures next job is executed if any is stashed + def work(job) + job.call + ensure + synchronize do + job = @stash.shift || (@being_executed = false) + end + + # TODO maybe be able to tell caching pool to just enqueue this job, because the current one end at the end + # of this block + call_job job if job + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serialized_execution_delegator.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serialized_execution_delegator.rb new file mode 100644 index 0000000000..8197781b52 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/serialized_execution_delegator.rb @@ -0,0 +1,28 @@ +require 'delegate' +require 'concurrent/executor/serial_executor_service' +require 'concurrent/executor/serialized_execution' + +module Concurrent + + # A wrapper/delegator for any `ExecutorService` that + # guarantees serialized execution of tasks. + # + # @see [SimpleDelegator](http://www.ruby-doc.org/stdlib-2.1.2/libdoc/delegate/rdoc/SimpleDelegator.html) + # @see Concurrent::SerializedExecution + class SerializedExecutionDelegator < SimpleDelegator + include SerialExecutorService + + def initialize(executor) + @executor = executor + @serializer = SerializedExecution.new + super(executor) + end + + # @!macro executor_service_method_post + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + return false unless running? + @serializer.post(@executor, *args, &task) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/simple_executor_service.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/simple_executor_service.rb new file mode 100644 index 0000000000..0bc62afd38 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/simple_executor_service.rb @@ -0,0 +1,103 @@ +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/atomic/event' +require 'concurrent/executor/executor_service' +require 'concurrent/executor/ruby_executor_service' + +module Concurrent + + # An executor service in which every operation spawns a new, + # independently operating thread. + # + # This is perhaps the most inefficient executor service in this + # library. It exists mainly for testing an debugging. Thread creation + # and management is expensive in Ruby and this executor performs no + # resource pooling. This can be very beneficial during testing and + # debugging because it decouples the using code from the underlying + # executor implementation. In production this executor will likely + # lead to suboptimal performance. + # + # @note Intended for use primarily in testing and debugging. + class SimpleExecutorService < RubyExecutorService + + # @!macro executor_service_method_post + def self.post(*args) + raise ArgumentError.new('no block given') unless block_given? + Thread.new(*args) do + Thread.current.abort_on_exception = false + yield(*args) + end + true + end + + # @!macro executor_service_method_left_shift + def self.<<(task) + post(&task) + self + end + + # @!macro executor_service_method_post + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + return false unless running? + @count.increment + Thread.new(*args) do + Thread.current.abort_on_exception = false + begin + yield(*args) + ensure + @count.decrement + @stopped.set if @running.false? && @count.value == 0 + end + end + end + + # @!macro executor_service_method_left_shift + def <<(task) + post(&task) + self + end + + # @!macro executor_service_method_running_question + def running? + @running.true? + end + + # @!macro executor_service_method_shuttingdown_question + def shuttingdown? + @running.false? && ! @stopped.set? + end + + # @!macro executor_service_method_shutdown_question + def shutdown? + @stopped.set? + end + + # @!macro executor_service_method_shutdown + def shutdown + @running.make_false + @stopped.set if @count.value == 0 + true + end + + # @!macro executor_service_method_kill + def kill + @running.make_false + @stopped.set + true + end + + # @!macro executor_service_method_wait_for_termination + def wait_for_termination(timeout = nil) + @stopped.wait(timeout) + end + + private + + def ns_initialize(*args) + @running = Concurrent::AtomicBoolean.new(true) + @stopped = Concurrent::Event.new + @count = Concurrent::AtomicFixnum.new(0) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/single_thread_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/single_thread_executor.rb new file mode 100644 index 0000000000..f1474ea9ff --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/single_thread_executor.rb @@ -0,0 +1,57 @@ +require 'concurrent/utility/engine' +require 'concurrent/executor/ruby_single_thread_executor' + +module Concurrent + + if Concurrent.on_jruby? + require 'concurrent/executor/java_single_thread_executor' + end + + SingleThreadExecutorImplementation = case + when Concurrent.on_jruby? + JavaSingleThreadExecutor + else + RubySingleThreadExecutor + end + private_constant :SingleThreadExecutorImplementation + + # @!macro single_thread_executor + # + # A thread pool with a single thread an unlimited queue. Should the thread + # die for any reason it will be removed and replaced, thus ensuring that + # the executor will always remain viable and available to process jobs. + # + # A common pattern for background processing is to create a single thread + # on which an infinite loop is run. The thread's loop blocks on an input + # source (perhaps blocking I/O or a queue) and processes each input as it + # is received. This pattern has several issues. The thread itself is highly + # susceptible to errors during processing. Also, the thread itself must be + # constantly monitored and restarted should it die. `SingleThreadExecutor` + # encapsulates all these bahaviors. The task processor is highly resilient + # to errors from within tasks. Also, should the thread die it will + # automatically be restarted. + # + # The API and behavior of this class are based on Java's `SingleThreadExecutor`. + # + # @!macro abstract_executor_service_public_api + class SingleThreadExecutor < SingleThreadExecutorImplementation + + # @!macro single_thread_executor_method_initialize + # + # Create a new thread pool. + # + # @option opts [Symbol] :fallback_policy (:discard) the policy for handling new + # tasks that are received when the queue size has reached + # `max_queue` or the executor has shut down + # + # @raise [ArgumentError] if `:fallback_policy` is not one of the values specified + # in `FALLBACK_POLICIES` + # + # @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html + + # @!method initialize(opts = {}) + # @!macro single_thread_executor_method_initialize + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/thread_pool_executor.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/thread_pool_executor.rb new file mode 100644 index 0000000000..253d46a9d1 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/thread_pool_executor.rb @@ -0,0 +1,88 @@ +require 'concurrent/utility/engine' +require 'concurrent/executor/ruby_thread_pool_executor' + +module Concurrent + + if Concurrent.on_jruby? + require 'concurrent/executor/java_thread_pool_executor' + end + + ThreadPoolExecutorImplementation = case + when Concurrent.on_jruby? + JavaThreadPoolExecutor + else + RubyThreadPoolExecutor + end + private_constant :ThreadPoolExecutorImplementation + + # @!macro thread_pool_executor + # + # An abstraction composed of one or more threads and a task queue. Tasks + # (blocks or `proc` objects) are submitted to the pool and added to the queue. + # The threads in the pool remove the tasks and execute them in the order + # they were received. + # + # A `ThreadPoolExecutor` will automatically adjust the pool size according + # to the bounds set by `min-threads` and `max-threads`. When a new task is + # submitted and fewer than `min-threads` threads are running, a new thread + # is created to handle the request, even if other worker threads are idle. + # If there are more than `min-threads` but less than `max-threads` threads + # running, a new thread will be created only if the queue is full. + # + # Threads that are idle for too long will be garbage collected, down to the + # configured minimum options. Should a thread crash it, too, will be garbage collected. + # + # `ThreadPoolExecutor` is based on the Java class of the same name. From + # the official Java documentation; + # + # > Thread pools address two different problems: they usually provide + # > improved performance when executing large numbers of asynchronous tasks, + # > due to reduced per-task invocation overhead, and they provide a means + # > of bounding and managing the resources, including threads, consumed + # > when executing a collection of tasks. Each ThreadPoolExecutor also + # > maintains some basic statistics, such as the number of completed tasks. + # > + # > To be useful across a wide range of contexts, this class provides many + # > adjustable parameters and extensibility hooks. However, programmers are + # > urged to use the more convenient Executors factory methods + # > [CachedThreadPool] (unbounded thread pool, with automatic thread reclamation), + # > [FixedThreadPool] (fixed size thread pool) and [SingleThreadExecutor] (single + # > background thread), that preconfigure settings for the most common usage + # > scenarios. + # + # @!macro thread_pool_options + # + # @!macro thread_pool_executor_public_api + class ThreadPoolExecutor < ThreadPoolExecutorImplementation + + # @!macro thread_pool_executor_method_initialize + # + # Create a new thread pool. + # + # @param [Hash] opts the options which configure the thread pool. + # + # @option opts [Integer] :max_threads (DEFAULT_MAX_POOL_SIZE) the maximum + # number of threads to be created + # @option opts [Integer] :min_threads (DEFAULT_MIN_POOL_SIZE) When a new task is submitted + # and fewer than `min_threads` are running, a new thread is created + # @option opts [Integer] :idletime (DEFAULT_THREAD_IDLETIMEOUT) the maximum + # number of seconds a thread may be idle before being reclaimed + # @option opts [Integer] :max_queue (DEFAULT_MAX_QUEUE_SIZE) the maximum + # number of tasks allowed in the work queue at any one time; a value of + # zero means the queue may grow without bound + # @option opts [Symbol] :fallback_policy (:abort) the policy for handling new + # tasks that are received when the queue size has reached + # `max_queue` or the executor has shut down + # @option opts [Boolean] :synchronous (DEFAULT_SYNCHRONOUS) whether or not a value of 0 + # for :max_queue means the queue must perform direct hand-off rather than unbounded. + # @raise [ArgumentError] if `:max_threads` is less than one + # @raise [ArgumentError] if `:min_threads` is less than zero + # @raise [ArgumentError] if `:fallback_policy` is not one of the values specified + # in `FALLBACK_POLICIES` + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html + + # @!method initialize(opts = {}) + # @!macro thread_pool_executor_method_initialize + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/timer_set.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/timer_set.rb new file mode 100644 index 0000000000..0dfaf1288c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executor/timer_set.rb @@ -0,0 +1,172 @@ +require 'concurrent/scheduled_task' +require 'concurrent/atomic/event' +require 'concurrent/collection/non_concurrent_priority_queue' +require 'concurrent/executor/executor_service' +require 'concurrent/executor/single_thread_executor' + +require 'concurrent/options' + +module Concurrent + + # Executes a collection of tasks, each after a given delay. A master task + # monitors the set and schedules each task for execution at the appropriate + # time. Tasks are run on the global thread pool or on the supplied executor. + # Each task is represented as a `ScheduledTask`. + # + # @see Concurrent::ScheduledTask + # + # @!macro monotonic_clock_warning + class TimerSet < RubyExecutorService + + # Create a new set of timed tasks. + # + # @!macro executor_options + # + # @param [Hash] opts the options used to specify the executor on which to perform actions + # @option opts [Executor] :executor when set use the given `Executor` instance. + # Three special values are also supported: `:task` returns the global task pool, + # `:operation` returns the global operation pool, and `:immediate` returns a new + # `ImmediateExecutor` object. + def initialize(opts = {}) + super(opts) + end + + # Post a task to be execute run after a given delay (in seconds). If the + # delay is less than 1/100th of a second the task will be immediately post + # to the executor. + # + # @param [Float] delay the number of seconds to wait for before executing the task. + # @param [Array] args the arguments passed to the task on execution. + # + # @yield the task to be performed. + # + # @return [Concurrent::ScheduledTask, false] IVar representing the task if the post + # is successful; false after shutdown. + # + # @raise [ArgumentError] if the intended execution time is not in the future. + # @raise [ArgumentError] if no block is given. + def post(delay, *args, &task) + raise ArgumentError.new('no block given') unless block_given? + return false unless running? + opts = { executor: @task_executor, + args: args, + timer_set: self } + task = ScheduledTask.execute(delay, opts, &task) # may raise exception + task.unscheduled? ? false : task + end + + # Begin an immediate shutdown. In-progress tasks will be allowed to + # complete but enqueued tasks will be dismissed and no new tasks + # will be accepted. Has no additional effect if the thread pool is + # not running. + def kill + shutdown + end + + private :<< + + private + + # Initialize the object. + # + # @param [Hash] opts the options to create the object with. + # @!visibility private + def ns_initialize(opts) + @queue = Collection::NonConcurrentPriorityQueue.new(order: :min) + @task_executor = Options.executor_from_options(opts) || Concurrent.global_io_executor + @timer_executor = SingleThreadExecutor.new + @condition = Event.new + @ruby_pid = $$ # detects if Ruby has forked + end + + # Post the task to the internal queue. + # + # @note This is intended as a callback method from ScheduledTask + # only. It is not intended to be used directly. Post a task + # by using the `SchedulesTask#execute` method. + # + # @!visibility private + def post_task(task) + synchronize { ns_post_task(task) } + end + + # @!visibility private + def ns_post_task(task) + return false unless ns_running? + ns_reset_if_forked + if (task.initial_delay) <= 0.01 + task.executor.post { task.process_task } + else + @queue.push(task) + # only post the process method when the queue is empty + @timer_executor.post(&method(:process_tasks)) if @queue.length == 1 + @condition.set + end + true + end + + # Remove the given task from the queue. + # + # @note This is intended as a callback method from `ScheduledTask` + # only. It is not intended to be used directly. Cancel a task + # by using the `ScheduledTask#cancel` method. + # + # @!visibility private + def remove_task(task) + synchronize { @queue.delete(task) } + end + + # `ExecutorService` callback called during shutdown. + # + # @!visibility private + def ns_shutdown_execution + ns_reset_if_forked + @queue.clear + @timer_executor.kill + stopped_event.set + end + + def ns_reset_if_forked + if $$ != @ruby_pid + @queue.clear + @condition.reset + @ruby_pid = $$ + end + end + + # Run a loop and execute tasks in the scheduled order and at the approximate + # scheduled time. If no tasks remain the thread will exit gracefully so that + # garbage collection can occur. If there are no ready tasks it will sleep + # for up to 60 seconds waiting for the next scheduled task. + # + # @!visibility private + def process_tasks + loop do + task = synchronize { @condition.reset; @queue.peek } + break unless task + + now = Concurrent.monotonic_time + diff = task.schedule_time - now + + if diff <= 0 + # We need to remove the task from the queue before passing + # it to the executor, to avoid race conditions where we pass + # the peek'ed task to the executor and then pop a different + # one that's been added in the meantime. + # + # Note that there's no race condition between the peek and + # this pop - this pop could retrieve a different task from + # the peek, but that task would be due to fire now anyway + # (because @queue is a priority queue, and this thread is + # the only reader, so whatever timer is at the head of the + # queue now must have the same pop time, or a closer one, as + # when we peeked). + task = synchronize { @queue.pop } + task.executor.post { task.process_task } + else + @condition.wait([diff, 60].min) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executors.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executors.rb new file mode 100644 index 0000000000..eb1972ce69 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/executors.rb @@ -0,0 +1,20 @@ +require 'concurrent/executor/abstract_executor_service' +require 'concurrent/executor/cached_thread_pool' +require 'concurrent/executor/executor_service' +require 'concurrent/executor/fixed_thread_pool' +require 'concurrent/executor/immediate_executor' +require 'concurrent/executor/indirect_immediate_executor' +require 'concurrent/executor/java_executor_service' +require 'concurrent/executor/java_single_thread_executor' +require 'concurrent/executor/java_thread_pool_executor' +require 'concurrent/executor/ruby_executor_service' +require 'concurrent/executor/ruby_single_thread_executor' +require 'concurrent/executor/ruby_thread_pool_executor' +require 'concurrent/executor/cached_thread_pool' +require 'concurrent/executor/safe_task_executor' +require 'concurrent/executor/serial_executor_service' +require 'concurrent/executor/serialized_execution' +require 'concurrent/executor/serialized_execution_delegator' +require 'concurrent/executor/single_thread_executor' +require 'concurrent/executor/thread_pool_executor' +require 'concurrent/executor/timer_set' diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/future.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/future.rb new file mode 100644 index 0000000000..1af182ecb2 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/future.rb @@ -0,0 +1,141 @@ +require 'thread' +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/ivar' +require 'concurrent/executor/safe_task_executor' + +require 'concurrent/options' + +# TODO (pitr-ch 14-Mar-2017): deprecate, Future, Promise, etc. + + +module Concurrent + + # {include:file:docs-source/future.md} + # + # @!macro copy_options + # + # @see http://ruby-doc.org/stdlib-2.1.1/libdoc/observer/rdoc/Observable.html Ruby Observable module + # @see http://clojuredocs.org/clojure_core/clojure.core/future Clojure's future function + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html java.util.concurrent.Future + class Future < IVar + + # Create a new `Future` in the `:unscheduled` state. + # + # @yield the asynchronous operation to perform + # + # @!macro executor_and_deref_options + # + # @option opts [object, Array] :args zero or more arguments to be passed the task + # block on execution + # + # @raise [ArgumentError] if no block is given + def initialize(opts = {}, &block) + raise ArgumentError.new('no block given') unless block_given? + super(NULL, opts.merge(__task_from_block__: block), &nil) + end + + # Execute an `:unscheduled` `Future`. Immediately sets the state to `:pending` and + # passes the block to a new thread/thread pool for eventual execution. + # Does nothing if the `Future` is in any state other than `:unscheduled`. + # + # @return [Future] a reference to `self` + # + # @example Instance and execute in separate steps + # future = Concurrent::Future.new{ sleep(1); 42 } + # future.state #=> :unscheduled + # future.execute + # future.state #=> :pending + # + # @example Instance and execute in one line + # future = Concurrent::Future.new{ sleep(1); 42 }.execute + # future.state #=> :pending + def execute + if compare_and_set_state(:pending, :unscheduled) + @executor.post{ safe_execute(@task, @args) } + self + end + end + + # Create a new `Future` object with the given block, execute it, and return the + # `:pending` object. + # + # @yield the asynchronous operation to perform + # + # @!macro executor_and_deref_options + # + # @option opts [object, Array] :args zero or more arguments to be passed the task + # block on execution + # + # @raise [ArgumentError] if no block is given + # + # @return [Future] the newly created `Future` in the `:pending` state + # + # @example + # future = Concurrent::Future.execute{ sleep(1); 42 } + # future.state #=> :pending + def self.execute(opts = {}, &block) + Future.new(opts, &block).execute + end + + # @!macro ivar_set_method + def set(value = NULL, &block) + check_for_block_or_value!(block_given?, value) + synchronize do + if @state != :unscheduled + raise MultipleAssignmentError + else + @task = block || Proc.new { value } + end + end + execute + end + + # Attempt to cancel the operation if it has not already processed. + # The operation can only be cancelled while still `pending`. It cannot + # be cancelled once it has begun processing or has completed. + # + # @return [Boolean] was the operation successfully cancelled. + def cancel + if compare_and_set_state(:cancelled, :pending) + complete(false, nil, CancelledOperationError.new) + true + else + false + end + end + + # Has the operation been successfully cancelled? + # + # @return [Boolean] + def cancelled? + state == :cancelled + end + + # Wait the given number of seconds for the operation to complete. + # On timeout attempt to cancel the operation. + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Boolean] true if the operation completed before the timeout + # else false + def wait_or_cancel(timeout) + wait(timeout) + if complete? + true + else + cancel + false + end + end + + protected + + def ns_initialize(value, opts) + super + @state = :unscheduled + @task = opts[:__task_from_block__] + @executor = Options.executor_from_options(opts) || Concurrent.global_io_executor + @args = get_arguments_from(opts) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/hash.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/hash.rb new file mode 100644 index 0000000000..7902fe9d29 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/hash.rb @@ -0,0 +1,50 @@ +require 'concurrent/utility/engine' +require 'concurrent/thread_safe/util' + +module Concurrent + + # @!macro concurrent_hash + # + # A thread-safe subclass of Hash. This version locks against the object + # itself for every method call, ensuring only one thread can be reading + # or writing at a time. This includes iteration methods like `#each`, + # which takes the lock repeatedly when reading an item. + # + # @see http://ruby-doc.org/core/Hash.html Ruby standard library `Hash` + + # @!macro internal_implementation_note + HashImplementation = case + when Concurrent.on_cruby? + # Hash is thread-safe in practice because CRuby runs + # threads one at a time and does not do context + # switching during the execution of C functions. + ::Hash + + when Concurrent.on_jruby? + require 'jruby/synchronized' + + class JRubyHash < ::Hash + include JRuby::Synchronized + end + JRubyHash + + when Concurrent.on_truffleruby? + require 'concurrent/thread_safe/util/data_structures' + + class TruffleRubyHash < ::Hash + end + + ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubyHash + TruffleRubyHash + + else + warn 'Possibly unsupported Ruby implementation' + ::Hash + end + private_constant :HashImplementation + + # @!macro concurrent_hash + class Hash < HashImplementation + end + +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/immutable_struct.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/immutable_struct.rb new file mode 100644 index 0000000000..48462e8375 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/immutable_struct.rb @@ -0,0 +1,101 @@ +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # A thread-safe, immutable variation of Ruby's standard `Struct`. + # + # @see http://ruby-doc.org/core/Struct.html Ruby standard library `Struct` + module ImmutableStruct + include Synchronization::AbstractStruct + + def self.included(base) + base.safe_initialization! + end + + # @!macro struct_values + def values + ns_values + end + + alias_method :to_a, :values + + # @!macro struct_values_at + def values_at(*indexes) + ns_values_at(indexes) + end + + # @!macro struct_inspect + def inspect + ns_inspect + end + + alias_method :to_s, :inspect + + # @!macro struct_merge + def merge(other, &block) + ns_merge(other, &block) + end + + # @!macro struct_to_h + def to_h + ns_to_h + end + + # @!macro struct_get + def [](member) + ns_get(member) + end + + # @!macro struct_equality + def ==(other) + ns_equality(other) + end + + # @!macro struct_each + def each(&block) + return enum_for(:each) unless block_given? + ns_each(&block) + end + + # @!macro struct_each_pair + def each_pair(&block) + return enum_for(:each_pair) unless block_given? + ns_each_pair(&block) + end + + # @!macro struct_select + def select(&block) + return enum_for(:select) unless block_given? + ns_select(&block) + end + + private + + # @!visibility private + def initialize_copy(original) + super(original) + ns_initialize_copy + end + + # @!macro struct_new + def self.new(*args, &block) + clazz_name = nil + if args.length == 0 + raise ArgumentError.new('wrong number of arguments (0 for 1+)') + elsif args.length > 0 && args.first.is_a?(String) + clazz_name = args.shift + end + FACTORY.define_struct(clazz_name, args, &block) + end + + FACTORY = Class.new(Synchronization::LockableObject) do + def define_struct(name, members, &block) + synchronize do + Synchronization::AbstractStruct.define_struct_class(ImmutableStruct, Synchronization::Object, name, members, &block) + end + end + end.new + private_constant :FACTORY + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/ivar.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/ivar.rb new file mode 100644 index 0000000000..4165038f89 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/ivar.rb @@ -0,0 +1,208 @@ +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/collection/copy_on_write_observer_set' +require 'concurrent/concern/obligation' +require 'concurrent/concern/observable' +require 'concurrent/executor/safe_task_executor' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # An `IVar` is like a future that you can assign. As a future is a value that + # is being computed that you can wait on, an `IVar` is a value that is waiting + # to be assigned, that you can wait on. `IVars` are single assignment and + # deterministic. + # + # Then, express futures as an asynchronous computation that assigns an `IVar`. + # The `IVar` becomes the primitive on which [futures](Future) and + # [dataflow](Dataflow) are built. + # + # An `IVar` is a single-element container that is normally created empty, and + # can only be set once. The I in `IVar` stands for immutable. Reading an + # `IVar` normally blocks until it is set. It is safe to set and read an `IVar` + # from different threads. + # + # If you want to have some parallel task set the value in an `IVar`, you want + # a `Future`. If you want to create a graph of parallel tasks all executed + # when the values they depend on are ready you want `dataflow`. `IVar` is + # generally a low-level primitive. + # + # ## Examples + # + # Create, set and get an `IVar` + # + # ```ruby + # ivar = Concurrent::IVar.new + # ivar.set 14 + # ivar.value #=> 14 + # ivar.set 2 # would now be an error + # ``` + # + # ## See Also + # + # 1. For the theory: Arvind, R. Nikhil, and K. Pingali. + # [I-Structures: Data structures for parallel computing](http://dl.acm.org/citation.cfm?id=69562). + # In Proceedings of Workshop on Graph Reduction, 1986. + # 2. For recent application: + # [DataDrivenFuture in Habanero Java from Rice](http://www.cs.rice.edu/~vs3/hjlib/doc/edu/rice/hj/api/HjDataDrivenFuture.html). + class IVar < Synchronization::LockableObject + include Concern::Obligation + include Concern::Observable + + # Create a new `IVar` in the `:pending` state with the (optional) initial value. + # + # @param [Object] value the initial value + # @param [Hash] opts the options to create a message with + # @option opts [String] :dup_on_deref (false) call `#dup` before returning + # the data + # @option opts [String] :freeze_on_deref (false) call `#freeze` before + # returning the data + # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing + # the internal value and returning the value returned from the proc + def initialize(value = NULL, opts = {}, &block) + if value != NULL && block_given? + raise ArgumentError.new('provide only a value or a block') + end + super(&nil) + synchronize { ns_initialize(value, opts, &block) } + end + + # Add an observer on this object that will receive notification on update. + # + # Upon completion the `IVar` will notify all observers in a thread-safe way. + # The `func` method of the observer will be called with three arguments: the + # `Time` at which the `Future` completed the asynchronous operation, the + # final `value` (or `nil` on rejection), and the final `reason` (or `nil` on + # fulfillment). + # + # @param [Object] observer the object that will be notified of changes + # @param [Symbol] func symbol naming the method to call when this + # `Observable` has changes` + def add_observer(observer = nil, func = :update, &block) + raise ArgumentError.new('cannot provide both an observer and a block') if observer && block + direct_notification = false + + if block + observer = block + func = :call + end + + synchronize do + if event.set? + direct_notification = true + else + observers.add_observer(observer, func) + end + end + + observer.send(func, Time.now, self.value, reason) if direct_notification + observer + end + + # @!macro ivar_set_method + # Set the `IVar` to a value and wake or notify all threads waiting on it. + # + # @!macro ivar_set_parameters_and_exceptions + # @param [Object] value the value to store in the `IVar` + # @yield A block operation to use for setting the value + # @raise [ArgumentError] if both a value and a block are given + # @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already + # been set or otherwise completed + # + # @return [IVar] self + def set(value = NULL) + check_for_block_or_value!(block_given?, value) + raise MultipleAssignmentError unless compare_and_set_state(:processing, :pending) + + begin + value = yield if block_given? + complete_without_notification(true, value, nil) + rescue => ex + complete_without_notification(false, nil, ex) + end + + notify_observers(self.value, reason) + self + end + + # @!macro ivar_fail_method + # Set the `IVar` to failed due to some error and wake or notify all threads waiting on it. + # + # @param [Object] reason for the failure + # @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already + # been set or otherwise completed + # @return [IVar] self + def fail(reason = StandardError.new) + complete(false, nil, reason) + end + + # Attempt to set the `IVar` with the given value or block. Return a + # boolean indicating the success or failure of the set operation. + # + # @!macro ivar_set_parameters_and_exceptions + # + # @return [Boolean] true if the value was set else false + def try_set(value = NULL, &block) + set(value, &block) + true + rescue MultipleAssignmentError + false + end + + protected + + # @!visibility private + def ns_initialize(value, opts) + value = yield if block_given? + init_obligation + self.observers = Collection::CopyOnWriteObserverSet.new + set_deref_options(opts) + + @state = :pending + if value != NULL + ns_complete_without_notification(true, value, nil) + end + end + + # @!visibility private + def safe_execute(task, args = []) + if compare_and_set_state(:processing, :pending) + success, val, reason = SafeTaskExecutor.new(task, rescue_exception: true).execute(*@args) + complete(success, val, reason) + yield(success, val, reason) if block_given? + end + end + + # @!visibility private + def complete(success, value, reason) + complete_without_notification(success, value, reason) + notify_observers(self.value, reason) + self + end + + # @!visibility private + def complete_without_notification(success, value, reason) + synchronize { ns_complete_without_notification(success, value, reason) } + self + end + + # @!visibility private + def notify_observers(value, reason) + observers.notify_and_delete_observers{ [Time.now, value, reason] } + end + + # @!visibility private + def ns_complete_without_notification(success, value, reason) + raise MultipleAssignmentError if [:fulfilled, :rejected].include? @state + set_state(success, value, reason) + event.set + end + + # @!visibility private + def check_for_block_or_value!(block_given, value) # :nodoc: + if (block_given && value != NULL) || (! block_given && value == NULL) + raise ArgumentError.new('must set with either a value or a block') + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/map.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/map.rb new file mode 100644 index 0000000000..1b22241954 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/map.rb @@ -0,0 +1,350 @@ +require 'thread' +require 'concurrent/constants' +require 'concurrent/utility/engine' + +module Concurrent + # @!visibility private + module Collection + + # @!visibility private + MapImplementation = case + when Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + # noinspection RubyResolve + JRubyMapBackend + when Concurrent.on_cruby? + require 'concurrent/collection/map/mri_map_backend' + MriMapBackend + when Concurrent.on_truffleruby? + if defined?(::TruffleRuby::ConcurrentMap) + require 'concurrent/collection/map/truffleruby_map_backend' + TruffleRubyMapBackend + else + require 'concurrent/collection/map/atomic_reference_map_backend' + AtomicReferenceMapBackend + end + else + warn 'Concurrent::Map: unsupported Ruby engine, using a fully synchronized Concurrent::Map implementation' + require 'concurrent/collection/map/synchronized_map_backend' + SynchronizedMapBackend + end + end + + # `Concurrent::Map` is a hash-like object and should have much better performance + # characteristics, especially under high concurrency, than `Concurrent::Hash`. + # However, `Concurrent::Map `is not strictly semantically equivalent to a ruby `Hash` + # -- for instance, it does not necessarily retain ordering by insertion time as `Hash` + # does. For most uses it should do fine though, and we recommend you consider + # `Concurrent::Map` instead of `Concurrent::Hash` for your concurrency-safe hash needs. + class Map < Collection::MapImplementation + + # @!macro map.atomic_method + # This method is atomic. + + # @!macro map.atomic_method_with_block + # This method is atomic. + # @note Atomic methods taking a block do not allow the `self` instance + # to be used within the block. Doing so will cause a deadlock. + + # @!method []=(key, value) + # Set a value with key + # @param [Object] key + # @param [Object] value + # @return [Object] the new value + + # @!method compute_if_absent(key) + # Compute and store new value for key if the key is absent. + # @param [Object] key + # @yield new value + # @yieldreturn [Object] new value + # @return [Object] new value or current value + # @!macro map.atomic_method_with_block + + # @!method compute_if_present(key) + # Compute and store new value for key if the key is present. + # @param [Object] key + # @yield new value + # @yieldparam old_value [Object] + # @yieldreturn [Object, nil] new value, when nil the key is removed + # @return [Object, nil] new value or nil + # @!macro map.atomic_method_with_block + + # @!method compute(key) + # Compute and store new value for key. + # @param [Object] key + # @yield compute new value from old one + # @yieldparam old_value [Object, nil] old_value, or nil when key is absent + # @yieldreturn [Object, nil] new value, when nil the key is removed + # @return [Object, nil] new value or nil + # @!macro map.atomic_method_with_block + + # @!method merge_pair(key, value) + # If the key is absent, the value is stored, otherwise new value is + # computed with a block. + # @param [Object] key + # @param [Object] value + # @yield compute new value from old one + # @yieldparam old_value [Object] old value + # @yieldreturn [Object, nil] new value, when nil the key is removed + # @return [Object, nil] new value or nil + # @!macro map.atomic_method_with_block + + # @!method replace_pair(key, old_value, new_value) + # Replaces old_value with new_value if key exists and current value + # matches old_value + # @param [Object] key + # @param [Object] old_value + # @param [Object] new_value + # @return [true, false] true if replaced + # @!macro map.atomic_method + + # @!method replace_if_exists(key, new_value) + # Replaces current value with new_value if key exists + # @param [Object] key + # @param [Object] new_value + # @return [Object, nil] old value or nil + # @!macro map.atomic_method + + # @!method get_and_set(key, value) + # Get the current value under key and set new value. + # @param [Object] key + # @param [Object] value + # @return [Object, nil] old value or nil when the key was absent + # @!macro map.atomic_method + + # @!method delete(key) + # Delete key and its value. + # @param [Object] key + # @return [Object, nil] old value or nil when the key was absent + # @!macro map.atomic_method + + # @!method delete_pair(key, value) + # Delete pair and its value if current value equals the provided value. + # @param [Object] key + # @param [Object] value + # @return [true, false] true if deleted + # @!macro map.atomic_method + + # NonConcurrentMapBackend handles default_proc natively + unless defined?(Collection::NonConcurrentMapBackend) and self < Collection::NonConcurrentMapBackend + + # @param [Hash, nil] options options to set the :initial_capacity or :load_factor. Ignored on some Rubies. + # @param [Proc] default_proc Optional block to compute the default value if the key is not set, like `Hash#default_proc` + def initialize(options = nil, &default_proc) + if options.kind_of?(::Hash) + validate_options_hash!(options) + else + options = nil + end + + super(options) + @default_proc = default_proc + end + + # Get a value with key + # @param [Object] key + # @return [Object] the value + def [](key) + if value = super # non-falsy value is an existing mapping, return it right away + value + # re-check is done with get_or_default(key, NULL) instead of a simple !key?(key) in order to avoid a race condition, whereby by the time the current thread gets to the key?(key) call + # a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrent +nil+ value + # would be returned) + # note: nil == value check is not technically necessary + elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL)) + @default_proc.call(self, key) + else + value + end + end + end + + alias_method :get, :[] + alias_method :put, :[]= + + # Get a value with key, or default_value when key is absent, + # or fail when no default_value is given. + # @param [Object] key + # @param [Object] default_value + # @yield default value for a key + # @yieldparam key [Object] + # @yieldreturn [Object] default value + # @return [Object] the value or default value + # @raise [KeyError] when key is missing and no default_value is provided + # @!macro map_method_not_atomic + # @note The "fetch-then-act" methods of `Map` are not atomic. `Map` is intended + # to be use as a concurrency primitive with strong happens-before + # guarantees. It is not intended to be used as a high-level abstraction + # supporting complex operations. All read and write operations are + # thread safe, but no guarantees are made regarding race conditions + # between the fetch operation and yielding to the block. Additionally, + # this method does not support recursion. This is due to internal + # constraints that are very unlikely to change in the near future. + def fetch(key, default_value = NULL) + if NULL != (value = get_or_default(key, NULL)) + value + elsif block_given? + yield key + elsif NULL != default_value + default_value + else + raise_fetch_no_key + end + end + + # Fetch value with key, or store default value when key is absent, + # or fail when no default_value is given. This is a two step operation, + # therefore not atomic. The store can overwrite other concurrently + # stored value. + # @param [Object] key + # @param [Object] default_value + # @yield default value for a key + # @yieldparam key [Object] + # @yieldreturn [Object] default value + # @return [Object] the value or default value + def fetch_or_store(key, default_value = NULL) + fetch(key) do + put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value)) + end + end + + # Insert value into map with key if key is absent in one atomic step. + # @param [Object] key + # @param [Object] value + # @return [Object, nil] the previous value when key was present or nil when there was no key + def put_if_absent(key, value) + computed = false + result = compute_if_absent(key) do + computed = true + value + end + computed ? nil : result + end unless method_defined?(:put_if_absent) + + # Is the value stored in the map. Iterates over all values. + # @param [Object] value + # @return [true, false] + def value?(value) + each_value do |v| + return true if value.equal?(v) + end + false + end + + # All keys + # @return [::Array] keys + def keys + arr = [] + each_pair { |k, v| arr << k } + arr + end unless method_defined?(:keys) + + # All values + # @return [::Array] values + def values + arr = [] + each_pair { |k, v| arr << v } + arr + end unless method_defined?(:values) + + # Iterates over each key. + # @yield for each key in the map + # @yieldparam key [Object] + # @return [self] + # @!macro map.atomic_method_with_block + def each_key + each_pair { |k, v| yield k } + end unless method_defined?(:each_key) + + # Iterates over each value. + # @yield for each value in the map + # @yieldparam value [Object] + # @return [self] + # @!macro map.atomic_method_with_block + def each_value + each_pair { |k, v| yield v } + end unless method_defined?(:each_value) + + # Iterates over each key value pair. + # @yield for each key value pair in the map + # @yieldparam key [Object] + # @yieldparam value [Object] + # @return [self] + # @!macro map.atomic_method_with_block + def each_pair + return enum_for :each_pair unless block_given? + super + end + + alias_method :each, :each_pair unless method_defined?(:each) + + # Find key of a value. + # @param [Object] value + # @return [Object, nil] key or nil when not found + def key(value) + each_pair { |k, v| return k if v == value } + nil + end unless method_defined?(:key) + + # Is map empty? + # @return [true, false] + def empty? + each_pair { |k, v| return false } + true + end unless method_defined?(:empty?) + + # The size of map. + # @return [Integer] size + def size + count = 0 + each_pair { |k, v| count += 1 } + count + end unless method_defined?(:size) + + # @!visibility private + def marshal_dump + raise TypeError, "can't dump hash with default proc" if @default_proc + h = {} + each_pair { |k, v| h[k] = v } + h + end + + # @!visibility private + def marshal_load(hash) + initialize + populate_from(hash) + end + + undef :freeze + + # @!visibility private + def inspect + format '%s entries=%d default_proc=%s>', to_s[0..-2], size.to_s, @default_proc.inspect + end + + private + + def raise_fetch_no_key + raise KeyError, 'key not found' + end + + def initialize_copy(other) + super + populate_from(other) + end + + def populate_from(hash) + hash.each_pair { |k, v| self[k] = v } + self + end + + def validate_options_hash!(options) + if (initial_capacity = options[:initial_capacity]) && (!initial_capacity.kind_of?(Integer) || initial_capacity < 0) + raise ArgumentError, ":initial_capacity must be a positive Integer" + end + if (load_factor = options[:load_factor]) && (!load_factor.kind_of?(Numeric) || load_factor <= 0 || load_factor > 1) + raise ArgumentError, ":load_factor must be a number between 0 and 1" + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/maybe.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/maybe.rb new file mode 100644 index 0000000000..317c82b86f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/maybe.rb @@ -0,0 +1,229 @@ +require 'concurrent/synchronization/object' + +module Concurrent + + # A `Maybe` encapsulates an optional value. A `Maybe` either contains a value + # of (represented as `Just`), or it is empty (represented as `Nothing`). Using + # `Maybe` is a good way to deal with errors or exceptional cases without + # resorting to drastic measures such as exceptions. + # + # `Maybe` is a replacement for the use of `nil` with better type checking. + # + # For compatibility with {Concurrent::Concern::Obligation} the predicate and + # accessor methods are aliased as `fulfilled?`, `rejected?`, `value`, and + # `reason`. + # + # ## Motivation + # + # A common pattern in languages with pattern matching, such as Erlang and + # Haskell, is to return *either* a value *or* an error from a function + # Consider this Erlang code: + # + # ```erlang + # case file:consult("data.dat") of + # {ok, Terms} -> do_something_useful(Terms); + # {error, Reason} -> lager:error(Reason) + # end. + # ``` + # + # In this example the standard library function `file:consult` returns a + # [tuple](http://erlang.org/doc/reference_manual/data_types.html#id69044) + # with two elements: an [atom](http://erlang.org/doc/reference_manual/data_types.html#id64134) + # (similar to a ruby symbol) and a variable containing ancillary data. On + # success it returns the atom `ok` and the data from the file. On failure it + # returns `error` and a string with an explanation of the problem. With this + # pattern there is no ambiguity regarding success or failure. If the file is + # empty the return value cannot be misinterpreted as an error. And when an + # error occurs the return value provides useful information. + # + # In Ruby we tend to return `nil` when an error occurs or else we raise an + # exception. Both of these idioms are problematic. Returning `nil` is + # ambiguous because `nil` may also be a valid value. It also lacks + # information pertaining to the nature of the error. Raising an exception + # is both expensive and usurps the normal flow of control. All of these + # problems can be solved with the use of a `Maybe`. + # + # A `Maybe` is unambiguous with regard to whether or not it contains a value. + # When `Just` it contains a value, when `Nothing` it does not. When `Just` + # the value it contains may be `nil`, which is perfectly valid. When + # `Nothing` the reason for the lack of a value is contained as well. The + # previous Erlang example can be duplicated in Ruby in a principled way by + # having functions return `Maybe` objects: + # + # ```ruby + # result = MyFileUtils.consult("data.dat") # returns a Maybe + # if result.just? + # do_something_useful(result.value) # or result.just + # else + # logger.error(result.reason) # or result.nothing + # end + # ``` + # + # @example Returning a Maybe from a Function + # module MyFileUtils + # def self.consult(path) + # file = File.open(path, 'r') + # Concurrent::Maybe.just(file.read) + # rescue => ex + # return Concurrent::Maybe.nothing(ex) + # ensure + # file.close if file + # end + # end + # + # maybe = MyFileUtils.consult('bogus.file') + # maybe.just? #=> false + # maybe.nothing? #=> true + # maybe.reason #=> # + # + # maybe = MyFileUtils.consult('README.md') + # maybe.just? #=> true + # maybe.nothing? #=> false + # maybe.value #=> "# Concurrent Ruby\n[![Gem Version..." + # + # @example Using Maybe with a Block + # result = Concurrent::Maybe.from do + # Client.find(10) # Client is an ActiveRecord model + # end + # + # # -- if the record was found + # result.just? #=> true + # result.value #=> # + # + # # -- if the record was not found + # result.just? #=> false + # result.reason #=> ActiveRecord::RecordNotFound + # + # @example Using Maybe with the Null Object Pattern + # # In a Rails controller... + # result = ClientService.new(10).find # returns a Maybe + # render json: result.or(NullClient.new) + # + # @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html Haskell Data.Maybe + # @see https://github.com/purescript/purescript-maybe/blob/master/docs/Data.Maybe.md PureScript Data.Maybe + class Maybe < Synchronization::Object + include Comparable + safe_initialization! + + # Indicates that the given attribute has not been set. + # When `Just` the {#nothing} getter will return `NONE`. + # When `Nothing` the {#just} getter will return `NONE`. + NONE = ::Object.new.freeze + + # The value of a `Maybe` when `Just`. Will be `NONE` when `Nothing`. + attr_reader :just + + # The reason for the `Maybe` when `Nothing`. Will be `NONE` when `Just`. + attr_reader :nothing + + private_class_method :new + + # Create a new `Maybe` using the given block. + # + # Runs the given block passing all function arguments to the block as block + # arguments. If the block runs to completion without raising an exception + # a new `Just` is created with the value set to the return value of the + # block. If the block raises an exception a new `Nothing` is created with + # the reason being set to the raised exception. + # + # @param [Array] args Zero or more arguments to pass to the block. + # @yield The block from which to create a new `Maybe`. + # @yieldparam [Array] args Zero or more block arguments passed as + # arguments to the function. + # + # @return [Maybe] The newly created object. + # + # @raise [ArgumentError] when no block given. + def self.from(*args) + raise ArgumentError.new('no block given') unless block_given? + begin + value = yield(*args) + return new(value, NONE) + rescue => ex + return new(NONE, ex) + end + end + + # Create a new `Just` with the given value. + # + # @param [Object] value The value to set for the new `Maybe` object. + # + # @return [Maybe] The newly created object. + def self.just(value) + return new(value, NONE) + end + + # Create a new `Nothing` with the given (optional) reason. + # + # @param [Exception] error The reason to set for the new `Maybe` object. + # When given a string a new `StandardError` will be created with the + # argument as the message. When no argument is given a new + # `StandardError` with an empty message will be created. + # + # @return [Maybe] The newly created object. + def self.nothing(error = '') + if error.is_a?(Exception) + nothing = error + else + nothing = StandardError.new(error.to_s) + end + return new(NONE, nothing) + end + + # Is this `Maybe` a `Just` (successfully fulfilled with a value)? + # + # @return [Boolean] True if `Just` or false if `Nothing`. + def just? + ! nothing? + end + alias :fulfilled? :just? + + # Is this `Maybe` a `nothing` (rejected with an exception upon fulfillment)? + # + # @return [Boolean] True if `Nothing` or false if `Just`. + def nothing? + @nothing != NONE + end + alias :rejected? :nothing? + + alias :value :just + + alias :reason :nothing + + # Comparison operator. + # + # @return [Integer] 0 if self and other are both `Nothing`; + # -1 if self is `Nothing` and other is `Just`; + # 1 if self is `Just` and other is nothing; + # `self.just <=> other.just` if both self and other are `Just`. + def <=>(other) + if nothing? + other.nothing? ? 0 : -1 + else + other.nothing? ? 1 : just <=> other.just + end + end + + # Return either the value of self or the given default value. + # + # @return [Object] The value of self when `Just`; else the given default. + def or(other) + just? ? just : other + end + + private + + # Create a new `Maybe` with the given attributes. + # + # @param [Object] just The value when `Just` else `NONE`. + # @param [Exception, Object] nothing The exception when `Nothing` else `NONE`. + # + # @return [Maybe] The new `Maybe`. + # + # @!visibility private + def initialize(just, nothing) + @just = just + @nothing = nothing + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/mutable_struct.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/mutable_struct.rb new file mode 100644 index 0000000000..5d0e9b9af5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/mutable_struct.rb @@ -0,0 +1,239 @@ +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # An thread-safe variation of Ruby's standard `Struct`. Values can be set at + # construction or safely changed at any time during the object's lifecycle. + # + # @see http://ruby-doc.org/core/Struct.html Ruby standard library `Struct` + module MutableStruct + include Synchronization::AbstractStruct + + # @!macro struct_new + # + # Factory for creating new struct classes. + # + # ``` + # new([class_name] [, member_name]+>) -> StructClass click to toggle source + # new([class_name] [, member_name]+>) {|StructClass| block } -> StructClass + # new(value, ...) -> obj + # StructClass[value, ...] -> obj + # ``` + # + # The first two forms are used to create a new struct subclass `class_name` + # that can contain a value for each member_name . This subclass can be + # used to create instances of the structure like any other Class . + # + # If the `class_name` is omitted an anonymous struct class will be created. + # Otherwise, the name of this struct will appear as a constant in the struct class, + # so it must be unique for all structs under this base class and must start with a + # capital letter. Assigning a struct class to a constant also gives the class + # the name of the constant. + # + # If a block is given it will be evaluated in the context of `StructClass`, passing + # the created class as a parameter. This is the recommended way to customize a struct. + # Subclassing an anonymous struct creates an extra anonymous class that will never be used. + # + # The last two forms create a new instance of a struct subclass. The number of value + # parameters must be less than or equal to the number of attributes defined for the + # struct. Unset parameters default to nil. Passing more parameters than number of attributes + # will raise an `ArgumentError`. + # + # @see http://ruby-doc.org/core/Struct.html#method-c-new Ruby standard library `Struct#new` + + # @!macro struct_values + # + # Returns the values for this struct as an Array. + # + # @return [Array] the values for this struct + # + def values + synchronize { ns_values } + end + alias_method :to_a, :values + + # @!macro struct_values_at + # + # Returns the struct member values for each selector as an Array. + # + # A selector may be either an Integer offset or a Range of offsets (as in `Array#values_at`). + # + # @param [Fixnum, Range] indexes the index(es) from which to obatin the values (in order) + def values_at(*indexes) + synchronize { ns_values_at(indexes) } + end + + # @!macro struct_inspect + # + # Describe the contents of this struct in a string. + # + # @return [String] the contents of this struct in a string + def inspect + synchronize { ns_inspect } + end + alias_method :to_s, :inspect + + # @!macro struct_merge + # + # Returns a new struct containing the contents of `other` and the contents + # of `self`. If no block is specified, the value for entries with duplicate + # keys will be that of `other`. Otherwise the value for each duplicate key + # is determined by calling the block with the key, its value in `self` and + # its value in `other`. + # + # @param [Hash] other the hash from which to set the new values + # @yield an options block for resolving duplicate keys + # @yieldparam [String, Symbol] member the name of the member which is duplicated + # @yieldparam [Object] selfvalue the value of the member in `self` + # @yieldparam [Object] othervalue the value of the member in `other` + # + # @return [Synchronization::AbstractStruct] a new struct with the new values + # + # @raise [ArgumentError] of given a member that is not defined in the struct + def merge(other, &block) + synchronize { ns_merge(other, &block) } + end + + # @!macro struct_to_h + # + # Returns a hash containing the names and values for the struct’s members. + # + # @return [Hash] the names and values for the struct’s members + def to_h + synchronize { ns_to_h } + end + + # @!macro struct_get + # + # Attribute Reference + # + # @param [Symbol, String, Integer] member the string or symbol name of the member + # for which to obtain the value or the member's index + # + # @return [Object] the value of the given struct member or the member at the given index. + # + # @raise [NameError] if the member does not exist + # @raise [IndexError] if the index is out of range. + def [](member) + synchronize { ns_get(member) } + end + + # @!macro struct_equality + # + # Equality + # + # @return [Boolean] true if other has the same struct subclass and has + # equal member values (according to `Object#==`) + def ==(other) + synchronize { ns_equality(other) } + end + + # @!macro struct_each + # + # Yields the value of each struct member in order. If no block is given + # an enumerator is returned. + # + # @yield the operation to be performed on each struct member + # @yieldparam [Object] value each struct value (in order) + def each(&block) + return enum_for(:each) unless block_given? + synchronize { ns_each(&block) } + end + + # @!macro struct_each_pair + # + # Yields the name and value of each struct member in order. If no block is + # given an enumerator is returned. + # + # @yield the operation to be performed on each struct member/value pair + # @yieldparam [Object] member each struct member (in order) + # @yieldparam [Object] value each struct value (in order) + def each_pair(&block) + return enum_for(:each_pair) unless block_given? + synchronize { ns_each_pair(&block) } + end + + # @!macro struct_select + # + # Yields each member value from the struct to the block and returns an Array + # containing the member values from the struct for which the given block + # returns a true value (equivalent to `Enumerable#select`). + # + # @yield the operation to be performed on each struct member + # @yieldparam [Object] value each struct value (in order) + # + # @return [Array] an array containing each value for which the block returns true + def select(&block) + return enum_for(:select) unless block_given? + synchronize { ns_select(&block) } + end + + # @!macro struct_set + # + # Attribute Assignment + # + # Sets the value of the given struct member or the member at the given index. + # + # @param [Symbol, String, Integer] member the string or symbol name of the member + # for which to obtain the value or the member's index + # + # @return [Object] the value of the given struct member or the member at the given index. + # + # @raise [NameError] if the name does not exist + # @raise [IndexError] if the index is out of range. + def []=(member, value) + if member.is_a? Integer + length = synchronize { @values.length } + if member >= length + raise IndexError.new("offset #{member} too large for struct(size:#{length})") + end + synchronize { @values[member] = value } + else + send("#{member}=", value) + end + rescue NoMethodError + raise NameError.new("no member '#{member}' in struct") + end + + private + + # @!visibility private + def initialize_copy(original) + synchronize do + super(original) + ns_initialize_copy + end + end + + # @!macro struct_new + def self.new(*args, &block) + clazz_name = nil + if args.length == 0 + raise ArgumentError.new('wrong number of arguments (0 for 1+)') + elsif args.length > 0 && args.first.is_a?(String) + clazz_name = args.shift + end + FACTORY.define_struct(clazz_name, args, &block) + end + + FACTORY = Class.new(Synchronization::LockableObject) do + def define_struct(name, members, &block) + synchronize do + clazz = Synchronization::AbstractStruct.define_struct_class(MutableStruct, Synchronization::LockableObject, name, members, &block) + members.each_with_index do |member, index| + clazz.send :remove_method, member + clazz.send(:define_method, member) do + synchronize { @values[index] } + end + clazz.send(:define_method, "#{member}=") do |value| + synchronize { @values[index] = value } + end + end + clazz + end + end + end.new + private_constant :FACTORY + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/mvar.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/mvar.rb new file mode 100644 index 0000000000..dfc41950cf --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/mvar.rb @@ -0,0 +1,242 @@ +require 'concurrent/concern/dereferenceable' +require 'concurrent/synchronization/object' + +module Concurrent + + # An `MVar` is a synchronized single element container. They are empty or + # contain one item. Taking a value from an empty `MVar` blocks, as does + # putting a value into a full one. You can either think of them as blocking + # queue of length one, or a special kind of mutable variable. + # + # On top of the fundamental `#put` and `#take` operations, we also provide a + # `#mutate` that is atomic with respect to operations on the same instance. + # These operations all support timeouts. + # + # We also support non-blocking operations `#try_put!` and `#try_take!`, a + # `#set!` that ignores existing values, a `#value` that returns the value + # without removing it or returns `MVar::EMPTY`, and a `#modify!` that yields + # `MVar::EMPTY` if the `MVar` is empty and can be used to set `MVar::EMPTY`. + # You shouldn't use these operations in the first instance. + # + # `MVar` is a [Dereferenceable](Dereferenceable). + # + # `MVar` is related to M-structures in Id, `MVar` in Haskell and `SyncVar` in Scala. + # + # Note that unlike the original Haskell paper, our `#take` is blocking. This is how + # Haskell and Scala do it today. + # + # @!macro copy_options + # + # ## See Also + # + # 1. P. Barth, R. Nikhil, and Arvind. [M-Structures: Extending a parallel, non- strict, functional language with state](http://dl.acm.org/citation.cfm?id=652538). In Proceedings of the 5th + # ACM Conference on Functional Programming Languages and Computer Architecture (FPCA), 1991. + # + # 2. S. Peyton Jones, A. Gordon, and S. Finne. [Concurrent Haskell](http://dl.acm.org/citation.cfm?id=237794). + # In Proceedings of the 23rd Symposium on Principles of Programming Languages + # (PoPL), 1996. + class MVar < Synchronization::Object + include Concern::Dereferenceable + safe_initialization! + + # Unique value that represents that an `MVar` was empty + EMPTY = ::Object.new + + # Unique value that represents that an `MVar` timed out before it was able + # to produce a value. + TIMEOUT = ::Object.new + + # Create a new `MVar`, either empty or with an initial value. + # + # @param [Hash] opts the options controlling how the future will be processed + # + # @!macro deref_options + def initialize(value = EMPTY, opts = {}) + @value = value + @mutex = Mutex.new + @empty_condition = ConditionVariable.new + @full_condition = ConditionVariable.new + set_deref_options(opts) + end + + # Remove the value from an `MVar`, leaving it empty, and blocking if there + # isn't a value. A timeout can be set to limit the time spent blocked, in + # which case it returns `TIMEOUT` if the time is exceeded. + # @return [Object] the value that was taken, or `TIMEOUT` + def take(timeout = nil) + @mutex.synchronize do + wait_for_full(timeout) + + # If we timed out we'll still be empty + if unlocked_full? + value = @value + @value = EMPTY + @empty_condition.signal + apply_deref_options(value) + else + TIMEOUT + end + end + end + + # acquires lock on the from an `MVAR`, yields the value to provided block, + # and release lock. A timeout can be set to limit the time spent blocked, + # in which case it returns `TIMEOUT` if the time is exceeded. + # @return [Object] the value returned by the block, or `TIMEOUT` + def borrow(timeout = nil) + @mutex.synchronize do + wait_for_full(timeout) + + # if we timeoud out we'll still be empty + if unlocked_full? + yield @value + else + TIMEOUT + end + end + end + + # Put a value into an `MVar`, blocking if there is already a value until + # it is empty. A timeout can be set to limit the time spent blocked, in + # which case it returns `TIMEOUT` if the time is exceeded. + # @return [Object] the value that was put, or `TIMEOUT` + def put(value, timeout = nil) + @mutex.synchronize do + wait_for_empty(timeout) + + # If we timed out we won't be empty + if unlocked_empty? + @value = value + @full_condition.signal + apply_deref_options(value) + else + TIMEOUT + end + end + end + + # Atomically `take`, yield the value to a block for transformation, and then + # `put` the transformed value. Returns the transformed value. A timeout can + # be set to limit the time spent blocked, in which case it returns `TIMEOUT` + # if the time is exceeded. + # @return [Object] the transformed value, or `TIMEOUT` + def modify(timeout = nil) + raise ArgumentError.new('no block given') unless block_given? + + @mutex.synchronize do + wait_for_full(timeout) + + # If we timed out we'll still be empty + if unlocked_full? + value = @value + @value = yield value + @full_condition.signal + apply_deref_options(value) + else + TIMEOUT + end + end + end + + # Non-blocking version of `take`, that returns `EMPTY` instead of blocking. + def try_take! + @mutex.synchronize do + if unlocked_full? + value = @value + @value = EMPTY + @empty_condition.signal + apply_deref_options(value) + else + EMPTY + end + end + end + + # Non-blocking version of `put`, that returns whether or not it was successful. + def try_put!(value) + @mutex.synchronize do + if unlocked_empty? + @value = value + @full_condition.signal + true + else + false + end + end + end + + # Non-blocking version of `put` that will overwrite an existing value. + def set!(value) + @mutex.synchronize do + old_value = @value + @value = value + @full_condition.signal + apply_deref_options(old_value) + end + end + + # Non-blocking version of `modify` that will yield with `EMPTY` if there is no value yet. + def modify! + raise ArgumentError.new('no block given') unless block_given? + + @mutex.synchronize do + value = @value + @value = yield value + if unlocked_empty? + @empty_condition.signal + else + @full_condition.signal + end + apply_deref_options(value) + end + end + + # Returns if the `MVar` is currently empty. + def empty? + @mutex.synchronize { @value == EMPTY } + end + + # Returns if the `MVar` currently contains a value. + def full? + !empty? + end + + protected + + def synchronize(&block) + @mutex.synchronize(&block) + end + + private + + def unlocked_empty? + @value == EMPTY + end + + def unlocked_full? + ! unlocked_empty? + end + + def wait_for_full(timeout) + wait_while(@full_condition, timeout) { unlocked_empty? } + end + + def wait_for_empty(timeout) + wait_while(@empty_condition, timeout) { unlocked_full? } + end + + def wait_while(condition, timeout) + if timeout.nil? + while yield + condition.wait(@mutex) + end + else + stop = Concurrent.monotonic_time + timeout + while yield && timeout > 0.0 + condition.wait(@mutex, timeout) + timeout = stop - Concurrent.monotonic_time + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/options.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/options.rb new file mode 100644 index 0000000000..bdd22a9df1 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/options.rb @@ -0,0 +1,42 @@ +require 'concurrent/configuration' + +module Concurrent + + # @!visibility private + module Options + + # Get the requested `Executor` based on the values set in the options hash. + # + # @param [Hash] opts the options defining the requested executor + # @option opts [Executor] :executor when set use the given `Executor` instance. + # Three special values are also supported: `:fast` returns the global fast executor, + # `:io` returns the global io executor, and `:immediate` returns a new + # `ImmediateExecutor` object. + # + # @return [Executor, nil] the requested thread pool, or nil when no option specified + # + # @!visibility private + def self.executor_from_options(opts = {}) # :nodoc: + if identifier = opts.fetch(:executor, nil) + executor(identifier) + else + nil + end + end + + def self.executor(executor_identifier) + case executor_identifier + when :fast + Concurrent.global_fast_executor + when :io + Concurrent.global_io_executor + when :immediate + Concurrent.global_immediate_executor + when Concurrent::ExecutorService + executor_identifier + else + raise ArgumentError, "executor not recognized by '#{executor_identifier}'" + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/promise.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/promise.rb new file mode 100644 index 0000000000..ccc47dd628 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/promise.rb @@ -0,0 +1,580 @@ +require 'thread' +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/ivar' +require 'concurrent/executor/safe_task_executor' + +require 'concurrent/options' + +module Concurrent + + PromiseExecutionError = Class.new(StandardError) + + # Promises are inspired by the JavaScript [Promises/A](http://wiki.commonjs.org/wiki/Promises/A) + # and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications. + # + # > A promise represents the eventual value returned from the single + # > completion of an operation. + # + # Promises are similar to futures and share many of the same behaviours. + # Promises are far more robust, however. Promises can be chained in a tree + # structure where each promise may have zero or more children. Promises are + # chained using the `then` method. The result of a call to `then` is always + # another promise. Promises are resolved asynchronously (with respect to the + # main thread) but in a strict order: parents are guaranteed to be resolved + # before their children, children before their younger siblings. The `then` + # method takes two parameters: an optional block to be executed upon parent + # resolution and an optional callable to be executed upon parent failure. The + # result of each promise is passed to each of its children upon resolution. + # When a promise is rejected all its children will be summarily rejected and + # will receive the reason. + # + # Promises have several possible states: *:unscheduled*, *:pending*, + # *:processing*, *:rejected*, or *:fulfilled*. These are also aggregated as + # `#incomplete?` and `#complete?`. When a Promise is created it is set to + # *:unscheduled*. Once the `#execute` method is called the state becomes + # *:pending*. Once a job is pulled from the thread pool's queue and is given + # to a thread for processing (often immediately upon `#post`) the state + # becomes *:processing*. The future will remain in this state until processing + # is complete. A future that is in the *:unscheduled*, *:pending*, or + # *:processing* is considered `#incomplete?`. A `#complete?` Promise is either + # *:rejected*, indicating that an exception was thrown during processing, or + # *:fulfilled*, indicating success. If a Promise is *:fulfilled* its `#value` + # will be updated to reflect the result of the operation. If *:rejected* the + # `reason` will be updated with a reference to the thrown exception. The + # predicate methods `#unscheduled?`, `#pending?`, `#rejected?`, and + # `#fulfilled?` can be called at any time to obtain the state of the Promise, + # as can the `#state` method, which returns a symbol. + # + # Retrieving the value of a promise is done through the `value` (alias: + # `deref`) method. Obtaining the value of a promise is a potentially blocking + # operation. When a promise is *rejected* a call to `value` will return `nil` + # immediately. When a promise is *fulfilled* a call to `value` will + # immediately return the current value. When a promise is *pending* a call to + # `value` will block until the promise is either *rejected* or *fulfilled*. A + # *timeout* value can be passed to `value` to limit how long the call will + # block. If `nil` the call will block indefinitely. If `0` the call will not + # block. Any other integer or float value will indicate the maximum number of + # seconds to block. + # + # Promises run on the global thread pool. + # + # @!macro copy_options + # + # ### Examples + # + # Start by requiring promises + # + # ```ruby + # require 'concurrent/promise' + # ``` + # + # Then create one + # + # ```ruby + # p = Concurrent::Promise.execute do + # # do something + # 42 + # end + # ``` + # + # Promises can be chained using the `then` method. The `then` method accepts a + # block and an executor, to be executed on fulfillment, and a callable argument to be executed + # on rejection. The result of the each promise is passed as the block argument + # to chained promises. + # + # ```ruby + # p = Concurrent::Promise.new{10}.then{|x| x * 2}.then{|result| result - 10 }.execute + # ``` + # + # And so on, and so on, and so on... + # + # ```ruby + # p = Concurrent::Promise.fulfill(20). + # then{|result| result - 10 }. + # then{|result| result * 3 }. + # then(executor: different_executor){|result| result % 5 }.execute + # ``` + # + # The initial state of a newly created Promise depends on the state of its parent: + # - if parent is *unscheduled* the child will be *unscheduled* + # - if parent is *pending* the child will be *pending* + # - if parent is *fulfilled* the child will be *pending* + # - if parent is *rejected* the child will be *pending* (but will ultimately be *rejected*) + # + # Promises are executed asynchronously from the main thread. By the time a + # child Promise finishes intialization it may be in a different state than its + # parent (by the time a child is created its parent may have completed + # execution and changed state). Despite being asynchronous, however, the order + # of execution of Promise objects in a chain (or tree) is strictly defined. + # + # There are multiple ways to create and execute a new `Promise`. Both ways + # provide identical behavior: + # + # ```ruby + # # create, operate, then execute + # p1 = Concurrent::Promise.new{ "Hello World!" } + # p1.state #=> :unscheduled + # p1.execute + # + # # create and immediately execute + # p2 = Concurrent::Promise.new{ "Hello World!" }.execute + # + # # execute during creation + # p3 = Concurrent::Promise.execute{ "Hello World!" } + # ``` + # + # Once the `execute` method is called a `Promise` becomes `pending`: + # + # ```ruby + # p = Concurrent::Promise.execute{ "Hello, world!" } + # p.state #=> :pending + # p.pending? #=> true + # ``` + # + # Wait a little bit, and the promise will resolve and provide a value: + # + # ```ruby + # p = Concurrent::Promise.execute{ "Hello, world!" } + # sleep(0.1) + # + # p.state #=> :fulfilled + # p.fulfilled? #=> true + # p.value #=> "Hello, world!" + # ``` + # + # If an exception occurs, the promise will be rejected and will provide + # a reason for the rejection: + # + # ```ruby + # p = Concurrent::Promise.execute{ raise StandardError.new("Here comes the Boom!") } + # sleep(0.1) + # + # p.state #=> :rejected + # p.rejected? #=> true + # p.reason #=> "#" + # ``` + # + # #### Rejection + # + # When a promise is rejected all its children will be rejected and will + # receive the rejection `reason` as the rejection callable parameter: + # + # ```ruby + # p = Concurrent::Promise.execute { Thread.pass; raise StandardError } + # + # c1 = p.then(-> reason { 42 }) + # c2 = p.then(-> reason { raise 'Boom!' }) + # + # c1.wait.state #=> :fulfilled + # c1.value #=> 45 + # c2.wait.state #=> :rejected + # c2.reason #=> # + # ``` + # + # Once a promise is rejected it will continue to accept children that will + # receive immediately rejection (they will be executed asynchronously). + # + # #### Aliases + # + # The `then` method is the most generic alias: it accepts a block to be + # executed upon parent fulfillment and a callable to be executed upon parent + # rejection. At least one of them should be passed. The default block is `{ + # |result| result }` that fulfills the child with the parent value. The + # default callable is `{ |reason| raise reason }` that rejects the child with + # the parent reason. + # + # - `on_success { |result| ... }` is the same as `then {|result| ... }` + # - `rescue { |reason| ... }` is the same as `then(Proc.new { |reason| ... } )` + # - `rescue` is aliased by `catch` and `on_error` + class Promise < IVar + + # Initialize a new Promise with the provided options. + # + # @!macro executor_and_deref_options + # + # @!macro promise_init_options + # + # @option opts [Promise] :parent the parent `Promise` when building a chain/tree + # @option opts [Proc] :on_fulfill fulfillment handler + # @option opts [Proc] :on_reject rejection handler + # @option opts [object, Array] :args zero or more arguments to be passed + # the task block on execution + # + # @yield The block operation to be performed asynchronously. + # + # @raise [ArgumentError] if no block is given + # + # @see http://wiki.commonjs.org/wiki/Promises/A + # @see http://promises-aplus.github.io/promises-spec/ + def initialize(opts = {}, &block) + opts.delete_if { |k, v| v.nil? } + super(NULL, opts.merge(__promise_body_from_block__: block), &nil) + end + + # Create a new `Promise` and fulfill it immediately. + # + # @!macro executor_and_deref_options + # + # @!macro promise_init_options + # + # @raise [ArgumentError] if no block is given + # + # @return [Promise] the newly created `Promise` + def self.fulfill(value, opts = {}) + Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, true, value, nil) } + end + + # Create a new `Promise` and reject it immediately. + # + # @!macro executor_and_deref_options + # + # @!macro promise_init_options + # + # @raise [ArgumentError] if no block is given + # + # @return [Promise] the newly created `Promise` + def self.reject(reason, opts = {}) + Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, false, nil, reason) } + end + + # Execute an `:unscheduled` `Promise`. Immediately sets the state to `:pending` and + # passes the block to a new thread/thread pool for eventual execution. + # Does nothing if the `Promise` is in any state other than `:unscheduled`. + # + # @return [Promise] a reference to `self` + def execute + if root? + if compare_and_set_state(:pending, :unscheduled) + set_pending + realize(@promise_body) + end + else + compare_and_set_state(:pending, :unscheduled) + @parent.execute + end + self + end + + # @!macro ivar_set_method + # + # @raise [Concurrent::PromiseExecutionError] if not the root promise + def set(value = NULL, &block) + raise PromiseExecutionError.new('supported only on root promise') unless root? + check_for_block_or_value!(block_given?, value) + synchronize do + if @state != :unscheduled + raise MultipleAssignmentError + else + @promise_body = block || Proc.new { |result| value } + end + end + execute + end + + # @!macro ivar_fail_method + # + # @raise [Concurrent::PromiseExecutionError] if not the root promise + def fail(reason = StandardError.new) + set { raise reason } + end + + # Create a new `Promise` object with the given block, execute it, and return the + # `:pending` object. + # + # @!macro executor_and_deref_options + # + # @!macro promise_init_options + # + # @return [Promise] the newly created `Promise` in the `:pending` state + # + # @raise [ArgumentError] if no block is given + # + # @example + # promise = Concurrent::Promise.execute{ sleep(1); 42 } + # promise.state #=> :pending + def self.execute(opts = {}, &block) + new(opts, &block).execute + end + + # Chain a new promise off the current promise. + # + # @return [Promise] the new promise + # @yield The block operation to be performed asynchronously. + # @overload then(rescuer, executor, &block) + # @param [Proc] rescuer An optional rescue block to be executed if the + # promise is rejected. + # @param [ThreadPool] executor An optional thread pool executor to be used + # in the new Promise + # @overload then(rescuer, executor: executor, &block) + # @param [Proc] rescuer An optional rescue block to be executed if the + # promise is rejected. + # @param [ThreadPool] executor An optional thread pool executor to be used + # in the new Promise + def then(*args, &block) + if args.last.is_a?(::Hash) + executor = args.pop[:executor] + rescuer = args.first + else + rescuer, executor = args + end + + executor ||= @executor + + raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given? + block = Proc.new { |result| result } unless block_given? + child = Promise.new( + parent: self, + executor: executor, + on_fulfill: block, + on_reject: rescuer + ) + + synchronize do + child.state = :pending if @state == :pending + child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled + child.on_reject(@reason) if @state == :rejected + @children << child + end + + child + end + + # Chain onto this promise an action to be undertaken on success + # (fulfillment). + # + # @yield The block to execute + # + # @return [Promise] self + def on_success(&block) + raise ArgumentError.new('no block given') unless block_given? + self.then(&block) + end + + # Chain onto this promise an action to be undertaken on failure + # (rejection). + # + # @yield The block to execute + # + # @return [Promise] self + def rescue(&block) + self.then(block) + end + + alias_method :catch, :rescue + alias_method :on_error, :rescue + + # Yield the successful result to the block that returns a promise. If that + # promise is also successful the result is the result of the yielded promise. + # If either part fails the whole also fails. + # + # @example + # Promise.execute { 1 }.flat_map { |v| Promise.execute { v + 2 } }.value! #=> 3 + # + # @return [Promise] + def flat_map(&block) + child = Promise.new( + parent: self, + executor: ImmediateExecutor.new, + ) + + on_error { |e| child.on_reject(e) } + on_success do |result1| + begin + inner = block.call(result1) + inner.execute + inner.on_success { |result2| child.on_fulfill(result2) } + inner.on_error { |e| child.on_reject(e) } + rescue => e + child.on_reject(e) + end + end + + child + end + + # Builds a promise that produces the result of promises in an Array + # and fails if any of them fails. + # + # @overload zip(*promises) + # @param [Array] promises + # + # @overload zip(*promises, opts) + # @param [Array] promises + # @param [Hash] opts the configuration options + # @option opts [Executor] :executor (ImmediateExecutor.new) when set use the given `Executor` instance. + # @option opts [Boolean] :execute (true) execute promise before returning + # + # @return [Promise] + def self.zip(*promises) + opts = promises.last.is_a?(::Hash) ? promises.pop.dup : {} + opts[:executor] ||= ImmediateExecutor.new + zero = if !opts.key?(:execute) || opts.delete(:execute) + fulfill([], opts) + else + Promise.new(opts) { [] } + end + + promises.reduce(zero) do |p1, p2| + p1.flat_map do |results| + p2.then do |next_result| + results << next_result + end + end + end + end + + # Builds a promise that produces the result of self and others in an Array + # and fails if any of them fails. + # + # @overload zip(*promises) + # @param [Array] others + # + # @overload zip(*promises, opts) + # @param [Array] others + # @param [Hash] opts the configuration options + # @option opts [Executor] :executor (ImmediateExecutor.new) when set use the given `Executor` instance. + # @option opts [Boolean] :execute (true) execute promise before returning + # + # @return [Promise] + def zip(*others) + self.class.zip(self, *others) + end + + # Aggregates a collection of promises and executes the `then` condition + # if all aggregated promises succeed. Executes the `rescue` handler with + # a `Concurrent::PromiseExecutionError` if any of the aggregated promises + # fail. Upon execution will execute any of the aggregate promises that + # were not already executed. + # + # @!macro promise_self_aggregate + # + # The returned promise will not yet have been executed. Additional `#then` + # and `#rescue` handlers may still be provided. Once the returned promise + # is execute the aggregate promises will be also be executed (if they have + # not been executed already). The results of the aggregate promises will + # be checked upon completion. The necessary `#then` and `#rescue` blocks + # on the aggregating promise will then be executed as appropriate. If the + # `#rescue` handlers are executed the raises exception will be + # `Concurrent::PromiseExecutionError`. + # + # @param [Array] promises Zero or more promises to aggregate + # @return [Promise] an unscheduled (not executed) promise that aggregates + # the promises given as arguments + def self.all?(*promises) + aggregate(:all?, *promises) + end + + # Aggregates a collection of promises and executes the `then` condition + # if any aggregated promises succeed. Executes the `rescue` handler with + # a `Concurrent::PromiseExecutionError` if any of the aggregated promises + # fail. Upon execution will execute any of the aggregate promises that + # were not already executed. + # + # @!macro promise_self_aggregate + def self.any?(*promises) + aggregate(:any?, *promises) + end + + protected + + def ns_initialize(value, opts) + super + + @executor = Options.executor_from_options(opts) || Concurrent.global_io_executor + @args = get_arguments_from(opts) + + @parent = opts.fetch(:parent) { nil } + @on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } } + @on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } } + + @promise_body = opts[:__promise_body_from_block__] || Proc.new { |result| result } + @state = :unscheduled + @children = [] + end + + # Aggregate a collection of zero or more promises under a composite promise, + # execute the aggregated promises and collect them into a standard Ruby array, + # call the given Ruby `Ennnumerable` predicate (such as `any?`, `all?`, `none?`, + # or `one?`) on the collection checking for the success or failure of each, + # then executing the composite's `#then` handlers if the predicate returns + # `true` or executing the composite's `#rescue` handlers if the predicate + # returns false. + # + # @!macro promise_self_aggregate + def self.aggregate(method, *promises) + composite = Promise.new do + completed = promises.collect do |promise| + promise.execute if promise.unscheduled? + promise.wait + promise + end + unless completed.empty? || completed.send(method){|promise| promise.fulfilled? } + raise PromiseExecutionError + end + end + composite + end + + # @!visibility private + def set_pending + synchronize do + @state = :pending + @children.each { |c| c.set_pending } + end + end + + # @!visibility private + def root? # :nodoc: + @parent.nil? + end + + # @!visibility private + def on_fulfill(result) + realize Proc.new { @on_fulfill.call(result) } + nil + end + + # @!visibility private + def on_reject(reason) + realize Proc.new { @on_reject.call(reason) } + nil + end + + # @!visibility private + def notify_child(child) + if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) } + if_state(:rejected) { child.on_reject(@reason) } + end + + # @!visibility private + def complete(success, value, reason) + children_to_notify = synchronize do + set_state!(success, value, reason) + @children.dup + end + + children_to_notify.each { |child| notify_child(child) } + observers.notify_and_delete_observers{ [Time.now, self.value, reason] } + end + + # @!visibility private + def realize(task) + @executor.post do + success, value, reason = SafeTaskExecutor.new(task, rescue_exception: true).execute(*@args) + complete(success, value, reason) + end + end + + # @!visibility private + def set_state!(success, value, reason) + set_state(success, value, reason) + event.set + end + + # @!visibility private + def synchronized_set_state!(success, value, reason) + synchronize { set_state!(success, value, reason) } + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/promises.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/promises.rb new file mode 100644 index 0000000000..3cd17055ca --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/promises.rb @@ -0,0 +1,2168 @@ +require 'concurrent/synchronization/object' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/collection/lock_free_stack' +require 'concurrent/configuration' +require 'concurrent/errors' +require 'concurrent/re_include' + +module Concurrent + + # {include:file:docs-source/promises-main.md} + module Promises + + # @!macro promises.param.default_executor + # @param [Executor, :io, :fast] default_executor Instance of an executor or a name of the + # global executor. Default executor propagates to chained futures unless overridden with + # executor parameter or changed with {AbstractEventFuture#with_default_executor}. + # + # @!macro promises.param.executor + # @param [Executor, :io, :fast] executor Instance of an executor or a name of the + # global executor. The task is executed on it, default executor remains unchanged. + # + # @!macro promises.param.args + # @param [Object] args arguments which are passed to the task when it's executed. + # (It might be prepended with other arguments, see the @yeild section). + # + # @!macro promises.shortcut.on + # Shortcut of {#$0_on} with default `:io` executor supplied. + # @see #$0_on + # + # @!macro promises.shortcut.using + # Shortcut of {#$0_using} with default `:io` executor supplied. + # @see #$0_using + # + # @!macro promise.param.task-future + # @yieldreturn will become result of the returned Future. + # Its returned value becomes {Future#value} fulfilling it, + # raised exception becomes {Future#reason} rejecting it. + # + # @!macro promise.param.callback + # @yieldreturn is forgotten. + + # Container of all {Future}, {Event} factory methods. They are never constructed directly with + # new. + module FactoryMethods + extend ReInclude + extend self + + module Configuration + # @return [Executor, :io, :fast] the executor which is used when none is supplied + # to a factory method. The method can be overridden in the receivers of + # `include FactoryMethod` + def default_executor + :io + end + end + + include Configuration + + # @!macro promises.shortcut.on + # @return [ResolvableEvent] + def resolvable_event + resolvable_event_on default_executor + end + + # Created resolvable event, user is responsible for resolving the event once by + # {Promises::ResolvableEvent#resolve}. + # + # @!macro promises.param.default_executor + # @return [ResolvableEvent] + def resolvable_event_on(default_executor = self.default_executor) + ResolvableEventPromise.new(default_executor).future + end + + # @!macro promises.shortcut.on + # @return [ResolvableFuture] + def resolvable_future + resolvable_future_on default_executor + end + + # Creates resolvable future, user is responsible for resolving the future once by + # {Promises::ResolvableFuture#resolve}, {Promises::ResolvableFuture#fulfill}, + # or {Promises::ResolvableFuture#reject} + # + # @!macro promises.param.default_executor + # @return [ResolvableFuture] + def resolvable_future_on(default_executor = self.default_executor) + ResolvableFuturePromise.new(default_executor).future + end + + # @!macro promises.shortcut.on + # @return [Future] + def future(*args, &task) + future_on(default_executor, *args, &task) + end + + # Constructs new Future which will be resolved after block is evaluated on default executor. + # Evaluation begins immediately. + # + # @!macro promises.param.default_executor + # @!macro promises.param.args + # @yield [*args] to the task. + # @!macro promise.param.task-future + # @return [Future] + def future_on(default_executor, *args, &task) + ImmediateEventPromise.new(default_executor).future.then(*args, &task) + end + + # Creates resolved future with will be either fulfilled with the given value or rejection with + # the given reason. + # + # @param [true, false] fulfilled + # @param [Object] value + # @param [Object] reason + # @!macro promises.param.default_executor + # @return [Future] + def resolved_future(fulfilled, value, reason, default_executor = self.default_executor) + ImmediateFuturePromise.new(default_executor, fulfilled, value, reason).future + end + + # Creates resolved future with will be fulfilled with the given value. + # + # @!macro promises.param.default_executor + # @param [Object] value + # @return [Future] + def fulfilled_future(value, default_executor = self.default_executor) + resolved_future true, value, nil, default_executor + end + + # Creates resolved future with will be rejected with the given reason. + # + # @!macro promises.param.default_executor + # @param [Object] reason + # @return [Future] + def rejected_future(reason, default_executor = self.default_executor) + resolved_future false, nil, reason, default_executor + end + + # Creates resolved event. + # + # @!macro promises.param.default_executor + # @return [Event] + def resolved_event(default_executor = self.default_executor) + ImmediateEventPromise.new(default_executor).event + end + + # General constructor. Behaves differently based on the argument's type. It's provided for convenience + # but it's better to be explicit. + # + # @see rejected_future, resolved_event, fulfilled_future + # @!macro promises.param.default_executor + # @return [Event, Future] + # + # @overload make_future(nil, default_executor = self.default_executor) + # @param [nil] nil + # @return [Event] resolved event. + # + # @overload make_future(a_future, default_executor = self.default_executor) + # @param [Future] a_future + # @return [Future] a future which will be resolved when a_future is. + # + # @overload make_future(an_event, default_executor = self.default_executor) + # @param [Event] an_event + # @return [Event] an event which will be resolved when an_event is. + # + # @overload make_future(exception, default_executor = self.default_executor) + # @param [Exception] exception + # @return [Future] a rejected future with the exception as its reason. + # + # @overload make_future(value, default_executor = self.default_executor) + # @param [Object] value when none of the above overloads fits + # @return [Future] a fulfilled future with the value. + def make_future(argument = nil, default_executor = self.default_executor) + case argument + when AbstractEventFuture + # returning wrapper would change nothing + argument + when Exception + rejected_future argument, default_executor + when nil + resolved_event default_executor + else + fulfilled_future argument, default_executor + end + end + + # @!macro promises.shortcut.on + # @return [Future, Event] + def delay(*args, &task) + delay_on default_executor, *args, &task + end + + # Creates new event or future which is resolved only after it is touched, + # see {Concurrent::AbstractEventFuture#touch}. + # + # @!macro promises.param.default_executor + # @overload delay_on(default_executor, *args, &task) + # If task is provided it returns a {Future} representing the result of the task. + # @!macro promises.param.args + # @yield [*args] to the task. + # @!macro promise.param.task-future + # @return [Future] + # @overload delay_on(default_executor) + # If no task is provided, it returns an {Event} + # @return [Event] + def delay_on(default_executor, *args, &task) + event = DelayPromise.new(default_executor).event + task ? event.chain(*args, &task) : event + end + + # @!macro promises.shortcut.on + # @return [Future, Event] + def schedule(intended_time, *args, &task) + schedule_on default_executor, intended_time, *args, &task + end + + # Creates new event or future which is resolved in intended_time. + # + # @!macro promises.param.default_executor + # @!macro promises.param.intended_time + # @param [Numeric, Time] intended_time `Numeric` means to run in `intended_time` seconds. + # `Time` means to run on `intended_time`. + # @overload schedule_on(default_executor, intended_time, *args, &task) + # If task is provided it returns a {Future} representing the result of the task. + # @!macro promises.param.args + # @yield [*args] to the task. + # @!macro promise.param.task-future + # @return [Future] + # @overload schedule_on(default_executor, intended_time) + # If no task is provided, it returns an {Event} + # @return [Event] + def schedule_on(default_executor, intended_time, *args, &task) + event = ScheduledPromise.new(default_executor, intended_time).event + task ? event.chain(*args, &task) : event + end + + # @!macro promises.shortcut.on + # @return [Future] + def zip_futures(*futures_and_or_events) + zip_futures_on default_executor, *futures_and_or_events + end + + # Creates new future which is resolved after all futures_and_or_events are resolved. + # Its value is array of zipped future values. Its reason is array of reasons for rejection. + # If there is an error it rejects. + # @!macro promises.event-conversion + # If event is supplied, which does not have value and can be only resolved, it's + # represented as `:fulfilled` with value `nil`. + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Future] + def zip_futures_on(default_executor, *futures_and_or_events) + ZipFuturesPromise.new_blocked_by(futures_and_or_events, default_executor).future + end + + alias_method :zip, :zip_futures + + # @!macro promises.shortcut.on + # @return [Event] + def zip_events(*futures_and_or_events) + zip_events_on default_executor, *futures_and_or_events + end + + # Creates new event which is resolved after all futures_and_or_events are resolved. + # (Future is resolved when fulfilled or rejected.) + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Event] + def zip_events_on(default_executor, *futures_and_or_events) + ZipEventsPromise.new_blocked_by(futures_and_or_events, default_executor).event + end + + # @!macro promises.shortcut.on + # @return [Future] + def any_resolved_future(*futures_and_or_events) + any_resolved_future_on default_executor, *futures_and_or_events + end + + alias_method :any, :any_resolved_future + + # Creates new future which is resolved after first futures_and_or_events is resolved. + # Its result equals result of the first resolved future. + # @!macro promises.any-touch + # If resolved it does not propagate {Concurrent::AbstractEventFuture#touch}, leaving delayed + # futures un-executed if they are not required any more. + # @!macro promises.event-conversion + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Future] + def any_resolved_future_on(default_executor, *futures_and_or_events) + AnyResolvedFuturePromise.new_blocked_by(futures_and_or_events, default_executor).future + end + + # @!macro promises.shortcut.on + # @return [Future] + def any_fulfilled_future(*futures_and_or_events) + any_fulfilled_future_on default_executor, *futures_and_or_events + end + + # Creates new future which is resolved after first of futures_and_or_events is fulfilled. + # Its result equals result of the first resolved future or if all futures_and_or_events reject, + # it has reason of the last resolved future. + # @!macro promises.any-touch + # @!macro promises.event-conversion + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Future] + def any_fulfilled_future_on(default_executor, *futures_and_or_events) + AnyFulfilledFuturePromise.new_blocked_by(futures_and_or_events, default_executor).future + end + + # @!macro promises.shortcut.on + # @return [Event] + def any_event(*futures_and_or_events) + any_event_on default_executor, *futures_and_or_events + end + + # Creates new event which becomes resolved after first of the futures_and_or_events resolves. + # @!macro promises.any-touch + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Event] + def any_event_on(default_executor, *futures_and_or_events) + AnyResolvedEventPromise.new_blocked_by(futures_and_or_events, default_executor).event + end + + # TODO consider adding first(count, *futures) + # TODO consider adding zip_by(slice, *futures) processing futures in slices + # TODO or rather a generic aggregator taking a function + end + + module InternalStates + # @!visibility private + class State + def resolved? + raise NotImplementedError + end + + def to_sym + raise NotImplementedError + end + end + + # @!visibility private + class Pending < State + def resolved? + false + end + + def to_sym + :pending + end + end + + # @!visibility private + class Reserved < Pending + end + + # @!visibility private + class ResolvedWithResult < State + def resolved? + true + end + + def to_sym + :resolved + end + + def result + [fulfilled?, value, reason] + end + + def fulfilled? + raise NotImplementedError + end + + def value + raise NotImplementedError + end + + def reason + raise NotImplementedError + end + + def apply + raise NotImplementedError + end + end + + # @!visibility private + class Fulfilled < ResolvedWithResult + + def initialize(value) + @Value = value + end + + def fulfilled? + true + end + + def apply(args, block) + block.call value, *args + end + + def value + @Value + end + + def reason + nil + end + + def to_sym + :fulfilled + end + end + + # @!visibility private + class FulfilledArray < Fulfilled + def apply(args, block) + block.call(*value, *args) + end + end + + # @!visibility private + class Rejected < ResolvedWithResult + def initialize(reason) + @Reason = reason + end + + def fulfilled? + false + end + + def value + nil + end + + def reason + @Reason + end + + def to_sym + :rejected + end + + def apply(args, block) + block.call reason, *args + end + end + + # @!visibility private + class PartiallyRejected < ResolvedWithResult + def initialize(value, reason) + super() + @Value = value + @Reason = reason + end + + def fulfilled? + false + end + + def to_sym + :rejected + end + + def value + @Value + end + + def reason + @Reason + end + + def apply(args, block) + block.call(*reason, *args) + end + end + + # @!visibility private + PENDING = Pending.new + # @!visibility private + RESERVED = Reserved.new + # @!visibility private + RESOLVED = Fulfilled.new(nil) + + def RESOLVED.to_sym + :resolved + end + end + + private_constant :InternalStates + + # @!macro promises.shortcut.event-future + # @see Event#$0 + # @see Future#$0 + + # @!macro promises.param.timeout + # @param [Numeric] timeout the maximum time in second to wait. + + # @!macro promises.warn.blocks + # @note This function potentially blocks current thread until the Future is resolved. + # Be careful it can deadlock. Try to chain instead. + + # Common ancestor of {Event} and {Future} classes, many shared methods are defined here. + class AbstractEventFuture < Synchronization::Object + safe_initialization! + attr_atomic(:internal_state) + private :internal_state=, :swap_internal_state, :compare_and_set_internal_state, :update_internal_state + # @!method internal_state + # @!visibility private + + include InternalStates + + def initialize(promise, default_executor) + super() + @Lock = Mutex.new + @Condition = ConditionVariable.new + @Promise = promise + @DefaultExecutor = default_executor + @Callbacks = LockFreeStack.new + @Waiters = AtomicFixnum.new 0 + self.internal_state = PENDING + end + + private :initialize + + # Returns its state. + # @return [Symbol] + # + # @overload an_event.state + # @return [:pending, :resolved] + # @overload a_future.state + # Both :fulfilled, :rejected implies :resolved. + # @return [:pending, :fulfilled, :rejected] + def state + internal_state.to_sym + end + + # Is it in pending state? + # @return [Boolean] + def pending? + !internal_state.resolved? + end + + # Is it in resolved state? + # @return [Boolean] + def resolved? + internal_state.resolved? + end + + # Propagates touch. Requests all the delayed futures, which it depends on, to be + # executed. This method is called by any other method requiring resolved state, like {#wait}. + # @return [self] + def touch + @Promise.touch + self + end + + # @!macro promises.touches + # Calls {Concurrent::AbstractEventFuture#touch}. + + # @!macro promises.method.wait + # Wait (block the Thread) until receiver is {#resolved?}. + # @!macro promises.touches + # + # @!macro promises.warn.blocks + # @!macro promises.param.timeout + # @return [self, true, false] self implies timeout was not used, true implies timeout was used + # and it was resolved, false implies it was not resolved within timeout. + def wait(timeout = nil) + result = wait_until_resolved(timeout) + timeout ? result : self + end + + # Returns default executor. + # @return [Executor] default executor + # @see #with_default_executor + # @see FactoryMethods#future_on + # @see FactoryMethods#resolvable_future + # @see FactoryMethods#any_fulfilled_future_on + # @see similar + def default_executor + @DefaultExecutor + end + + # @!macro promises.shortcut.on + # @return [Future] + def chain(*args, &task) + chain_on @DefaultExecutor, *args, &task + end + + # Chains the task to be executed asynchronously on executor after it is resolved. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @return [Future] + # @!macro promise.param.task-future + # + # @overload an_event.chain_on(executor, *args, &task) + # @yield [*args] to the task. + # @overload a_future.chain_on(executor, *args, &task) + # @yield [fulfilled, value, reason, *args] to the task. + # @yieldparam [true, false] fulfilled + # @yieldparam [Object] value + # @yieldparam [Object] reason + def chain_on(executor, *args, &task) + ChainPromise.new_blocked_by1(self, @DefaultExecutor, executor, args, &task).future + end + + # @return [String] Short string representation. + def to_s + format '%s %s>', super[0..-2], state + end + + alias_method :inspect, :to_s + + # Resolves the resolvable when receiver is resolved. + # + # @param [Resolvable] resolvable + # @return [self] + def chain_resolvable(resolvable) + on_resolution! { resolvable.resolve_with internal_state } + end + + alias_method :tangle, :chain_resolvable + + # @!macro promises.shortcut.using + # @return [self] + def on_resolution(*args, &callback) + on_resolution_using @DefaultExecutor, *args, &callback + end + + # Stores the callback to be executed synchronously on resolving thread after it is + # resolved. + # + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # + # @overload an_event.on_resolution!(*args, &callback) + # @yield [*args] to the callback. + # @overload a_future.on_resolution!(*args, &callback) + # @yield [fulfilled, value, reason, *args] to the callback. + # @yieldparam [true, false] fulfilled + # @yieldparam [Object] value + # @yieldparam [Object] reason + def on_resolution!(*args, &callback) + add_callback :callback_on_resolution, args, callback + end + + # Stores the callback to be executed asynchronously on executor after it is resolved. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # + # @overload an_event.on_resolution_using(executor, *args, &callback) + # @yield [*args] to the callback. + # @overload a_future.on_resolution_using(executor, *args, &callback) + # @yield [fulfilled, value, reason, *args] to the callback. + # @yieldparam [true, false] fulfilled + # @yieldparam [Object] value + # @yieldparam [Object] reason + def on_resolution_using(executor, *args, &callback) + add_callback :async_callback_on_resolution, executor, args, callback + end + + # @!macro promises.method.with_default_executor + # Crates new object with same class with the executor set as its new default executor. + # Any futures depending on it will use the new default executor. + # @!macro promises.shortcut.event-future + # @abstract + # @return [AbstractEventFuture] + def with_default_executor(executor) + raise NotImplementedError + end + + # @!visibility private + def resolve_with(state, raise_on_reassign = true, reserved = false) + if compare_and_set_internal_state(reserved ? RESERVED : PENDING, state) + # go to synchronized block only if there were waiting threads + @Lock.synchronize { @Condition.broadcast } unless @Waiters.value == 0 + call_callbacks state + else + return rejected_resolution(raise_on_reassign, state) + end + self + end + + # For inspection. + # @!visibility private + # @return [Array] + def blocks + @Callbacks.each_with_object([]) do |(method, args), promises| + promises.push(args[0]) if method == :callback_notify_blocked + end + end + + # For inspection. + # @!visibility private + def callbacks + @Callbacks.each.to_a + end + + # For inspection. + # @!visibility private + def promise + @Promise + end + + # For inspection. + # @!visibility private + def touched? + promise.touched? + end + + # For inspection. + # @!visibility private + def waiting_threads + @Waiters.each.to_a + end + + # @!visibility private + def add_callback_notify_blocked(promise, index) + add_callback :callback_notify_blocked, promise, index + end + + # @!visibility private + def add_callback_clear_delayed_node(node) + add_callback(:callback_clear_delayed_node, node) + end + + # @!visibility private + def with_hidden_resolvable + # TODO (pitr-ch 10-Dec-2018): documentation, better name if in edge + self + end + + private + + def add_callback(method, *args) + state = internal_state + if state.resolved? + call_callback method, state, args + else + @Callbacks.push [method, args] + state = internal_state + # take back if it was resolved in the meanwhile + call_callbacks state if state.resolved? + end + self + end + + def callback_clear_delayed_node(state, node) + node.value = nil + end + + # @return [Boolean] + def wait_until_resolved(timeout) + return true if resolved? + + touch + + @Lock.synchronize do + @Waiters.increment + begin + unless resolved? + @Condition.wait @Lock, timeout + end + ensure + # JRuby may raise ConcurrencyError + @Waiters.decrement + end + end + resolved? + end + + def call_callback(method, state, args) + self.send method, state, *args + end + + def call_callbacks(state) + method, args = @Callbacks.pop + while method + call_callback method, state, args + method, args = @Callbacks.pop + end + end + + def with_async(executor, *args, &block) + Concurrent.executor(executor).post(*args, &block) + end + + def async_callback_on_resolution(state, executor, args, callback) + with_async(executor, state, args, callback) do |st, ar, cb| + callback_on_resolution st, ar, cb + end + end + + def callback_notify_blocked(state, promise, index) + promise.on_blocker_resolution self, index + end + end + + # Represents an event which will happen in future (will be resolved). The event is either + # pending or resolved. It should be always resolved. Use {Future} to communicate rejections and + # cancellation. + class Event < AbstractEventFuture + + alias_method :then, :chain + + + # @!macro promises.method.zip + # Creates a new event or a future which will be resolved when receiver and other are. + # Returns an event if receiver and other are events, otherwise returns a future. + # If just one of the parties is Future then the result + # of the returned future is equal to the result of the supplied future. If both are futures + # then the result is as described in {FactoryMethods#zip_futures_on}. + # + # @return [Future, Event] + def zip(other) + if other.is_a?(Future) + ZipFutureEventPromise.new_blocked_by2(other, self, @DefaultExecutor).future + else + ZipEventEventPromise.new_blocked_by2(self, other, @DefaultExecutor).event + end + end + + alias_method :&, :zip + + # Creates a new event which will be resolved when the first of receiver, `event_or_future` + # resolves. + # + # @return [Event] + def any(event_or_future) + AnyResolvedEventPromise.new_blocked_by2(self, event_or_future, @DefaultExecutor).event + end + + alias_method :|, :any + + # Creates new event dependent on receiver which will not evaluate until touched, see {#touch}. + # In other words, it inserts delay into the chain of Futures making rest of it lazy evaluated. + # + # @return [Event] + def delay + event = DelayPromise.new(@DefaultExecutor).event + ZipEventEventPromise.new_blocked_by2(self, event, @DefaultExecutor).event + end + + # @!macro promise.method.schedule + # Creates new event dependent on receiver scheduled to execute on/in intended_time. + # In time is interpreted from the moment the receiver is resolved, therefore it inserts + # delay into the chain. + # + # @!macro promises.param.intended_time + # @return [Event] + def schedule(intended_time) + chain do + event = ScheduledPromise.new(@DefaultExecutor, intended_time).event + ZipEventEventPromise.new_blocked_by2(self, event, @DefaultExecutor).event + end.flat_event + end + + # Converts event to a future. The future is fulfilled when the event is resolved, the future may never fail. + # + # @return [Future] + def to_future + future = Promises.resolvable_future + ensure + chain_resolvable(future) + end + + # Returns self, since this is event + # @return [Event] + def to_event + self + end + + # @!macro promises.method.with_default_executor + # @return [Event] + def with_default_executor(executor) + EventWrapperPromise.new_blocked_by1(self, executor).event + end + + private + + def rejected_resolution(raise_on_reassign, state) + raise Concurrent::MultipleAssignmentError.new('Event can be resolved only once') if raise_on_reassign + return false + end + + def callback_on_resolution(state, args, callback) + callback.call(*args) + end + end + + # Represents a value which will become available in future. May reject with a reason instead, + # e.g. when the tasks raises an exception. + class Future < AbstractEventFuture + + # Is it in fulfilled state? + # @return [Boolean] + def fulfilled? + state = internal_state + state.resolved? && state.fulfilled? + end + + # Is it in rejected state? + # @return [Boolean] + def rejected? + state = internal_state + state.resolved? && !state.fulfilled? + end + + # @!macro promises.warn.nil + # @note Make sure returned `nil` is not confused with timeout, no value when rejected, + # no reason when fulfilled, etc. + # Use more exact methods if needed, like {#wait}, {#value!}, {#result}, etc. + + # @!macro promises.method.value + # Return value of the future. + # @!macro promises.touches + # + # @!macro promises.warn.blocks + # @!macro promises.warn.nil + # @!macro promises.param.timeout + # @!macro promises.param.timeout_value + # @param [Object] timeout_value a value returned by the method when it times out + # @return [Object, nil, timeout_value] the value of the Future when fulfilled, + # timeout_value on timeout, + # nil on rejection. + def value(timeout = nil, timeout_value = nil) + if wait_until_resolved timeout + internal_state.value + else + timeout_value + end + end + + # Returns reason of future's rejection. + # @!macro promises.touches + # + # @!macro promises.warn.blocks + # @!macro promises.warn.nil + # @!macro promises.param.timeout + # @!macro promises.param.timeout_value + # @return [Object, timeout_value] the reason, or timeout_value on timeout, or nil on fulfillment. + def reason(timeout = nil, timeout_value = nil) + if wait_until_resolved timeout + internal_state.reason + else + timeout_value + end + end + + # Returns triplet fulfilled?, value, reason. + # @!macro promises.touches + # + # @!macro promises.warn.blocks + # @!macro promises.param.timeout + # @return [Array(Boolean, Object, Object), nil] triplet of fulfilled?, value, reason, or nil + # on timeout. + def result(timeout = nil) + internal_state.result if wait_until_resolved timeout + end + + # @!macro promises.method.wait + # @raise [Exception] {#reason} on rejection + def wait!(timeout = nil) + result = wait_until_resolved!(timeout) + timeout ? result : self + end + + # @!macro promises.method.value + # @return [Object, nil, timeout_value] the value of the Future when fulfilled, + # or nil on rejection, + # or timeout_value on timeout. + # @raise [Exception] {#reason} on rejection + def value!(timeout = nil, timeout_value = nil) + if wait_until_resolved! timeout + internal_state.value + else + timeout_value + end + end + + # Allows rejected Future to be risen with `raise` method. + # If the reason is not an exception `Runtime.new(reason)` is returned. + # + # @example + # raise Promises.rejected_future(StandardError.new("boom")) + # raise Promises.rejected_future("or just boom") + # @raise [Concurrent::Error] when raising not rejected future + # @return [Exception] + def exception(*args) + raise Concurrent::Error, 'it is not rejected' unless rejected? + raise ArgumentError unless args.size <= 1 + reason = Array(internal_state.reason).flatten.compact + if reason.size > 1 + ex = Concurrent::MultipleErrors.new reason + ex.set_backtrace(caller) + ex + else + ex = if reason[0].respond_to? :exception + reason[0].exception(*args) + else + RuntimeError.new(reason[0]).exception(*args) + end + ex.set_backtrace Array(ex.backtrace) + caller + ex + end + end + + # @!macro promises.shortcut.on + # @return [Future] + def then(*args, &task) + then_on @DefaultExecutor, *args, &task + end + + # Chains the task to be executed asynchronously on executor after it fulfills. Does not run + # the task if it rejects. It will resolve though, triggering any dependent futures. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.task-future + # @return [Future] + # @yield [value, *args] to the task. + def then_on(executor, *args, &task) + ThenPromise.new_blocked_by1(self, @DefaultExecutor, executor, args, &task).future + end + + # @!macro promises.shortcut.on + # @return [Future] + def rescue(*args, &task) + rescue_on @DefaultExecutor, *args, &task + end + + # Chains the task to be executed asynchronously on executor after it rejects. Does not run + # the task if it fulfills. It will resolve though, triggering any dependent futures. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.task-future + # @return [Future] + # @yield [reason, *args] to the task. + def rescue_on(executor, *args, &task) + RescuePromise.new_blocked_by1(self, @DefaultExecutor, executor, args, &task).future + end + + # @!macro promises.method.zip + # @return [Future] + def zip(other) + if other.is_a?(Future) + ZipFuturesPromise.new_blocked_by2(self, other, @DefaultExecutor).future + else + ZipFutureEventPromise.new_blocked_by2(self, other, @DefaultExecutor).future + end + end + + alias_method :&, :zip + + # Creates a new event which will be resolved when the first of receiver, `event_or_future` + # resolves. Returning future will have value nil if event_or_future is event and resolves + # first. + # + # @return [Future] + def any(event_or_future) + AnyResolvedFuturePromise.new_blocked_by2(self, event_or_future, @DefaultExecutor).future + end + + alias_method :|, :any + + # Creates new future dependent on receiver which will not evaluate until touched, see {#touch}. + # In other words, it inserts delay into the chain of Futures making rest of it lazy evaluated. + # + # @return [Future] + def delay + event = DelayPromise.new(@DefaultExecutor).event + ZipFutureEventPromise.new_blocked_by2(self, event, @DefaultExecutor).future + end + + # @!macro promise.method.schedule + # @return [Future] + def schedule(intended_time) + chain do + event = ScheduledPromise.new(@DefaultExecutor, intended_time).event + ZipFutureEventPromise.new_blocked_by2(self, event, @DefaultExecutor).future + end.flat + end + + # @!macro promises.method.with_default_executor + # @return [Future] + def with_default_executor(executor) + FutureWrapperPromise.new_blocked_by1(self, executor).future + end + + # Creates new future which will have result of the future returned by receiver. If receiver + # rejects it will have its rejection. + # + # @param [Integer] level how many levels of futures should flatten + # @return [Future] + def flat_future(level = 1) + FlatFuturePromise.new_blocked_by1(self, level, @DefaultExecutor).future + end + + alias_method :flat, :flat_future + + # Creates new event which will be resolved when the returned event by receiver is. + # Be careful if the receiver rejects it will just resolve since Event does not hold reason. + # + # @return [Event] + def flat_event + FlatEventPromise.new_blocked_by1(self, @DefaultExecutor).event + end + + # @!macro promises.shortcut.using + # @return [self] + def on_fulfillment(*args, &callback) + on_fulfillment_using @DefaultExecutor, *args, &callback + end + + # Stores the callback to be executed synchronously on resolving thread after it is + # fulfilled. Does nothing on rejection. + # + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # @yield [value, *args] to the callback. + def on_fulfillment!(*args, &callback) + add_callback :callback_on_fulfillment, args, callback + end + + # Stores the callback to be executed asynchronously on executor after it is + # fulfilled. Does nothing on rejection. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # @yield [value, *args] to the callback. + def on_fulfillment_using(executor, *args, &callback) + add_callback :async_callback_on_fulfillment, executor, args, callback + end + + # @!macro promises.shortcut.using + # @return [self] + def on_rejection(*args, &callback) + on_rejection_using @DefaultExecutor, *args, &callback + end + + # Stores the callback to be executed synchronously on resolving thread after it is + # rejected. Does nothing on fulfillment. + # + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # @yield [reason, *args] to the callback. + def on_rejection!(*args, &callback) + add_callback :callback_on_rejection, args, callback + end + + # Stores the callback to be executed asynchronously on executor after it is + # rejected. Does nothing on fulfillment. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # @yield [reason, *args] to the callback. + def on_rejection_using(executor, *args, &callback) + add_callback :async_callback_on_rejection, executor, args, callback + end + + # Allows to use futures as green threads. The receiver has to evaluate to a future which + # represents what should be done next. It basically flattens indefinitely until non Future + # values is returned which becomes result of the returned future. Any encountered exception + # will become reason of the returned future. + # + # @return [Future] + # @param [#call(value)] run_test + # an object which when called returns either Future to keep running with + # or nil, then the run completes with the value. + # The run_test can be used to extract the Future from deeper structure, + # or to distinguish Future which is a resulting value from a future + # which is suppose to continue running. + # @example + # body = lambda do |v| + # v += 1 + # v < 5 ? Promises.future(v, &body) : v + # end + # Promises.future(0, &body).run.value! # => 5 + def run(run_test = method(:run_test)) + RunFuturePromise.new_blocked_by1(self, @DefaultExecutor, run_test).future + end + + # @!visibility private + def apply(args, block) + internal_state.apply args, block + end + + # Converts future to event which is resolved when future is resolved by fulfillment or rejection. + # + # @return [Event] + def to_event + event = Promises.resolvable_event + ensure + chain_resolvable(event) + end + + # Returns self, since this is a future + # @return [Future] + def to_future + self + end + + # @return [String] Short string representation. + def to_s + if resolved? + format '%s with %s>', super[0..-2], (fulfilled? ? value : reason).inspect + else + super + end + end + + alias_method :inspect, :to_s + + private + + def run_test(v) + v if v.is_a?(Future) + end + + def rejected_resolution(raise_on_reassign, state) + if raise_on_reassign + if internal_state == RESERVED + raise Concurrent::MultipleAssignmentError.new( + "Future can be resolved only once. It is already reserved.") + else + raise Concurrent::MultipleAssignmentError.new( + "Future can be resolved only once. It's #{result}, trying to set #{state.result}.", + current_result: result, + new_result: state.result) + end + end + return false + end + + def wait_until_resolved!(timeout = nil) + result = wait_until_resolved(timeout) + raise self if rejected? + result + end + + def async_callback_on_fulfillment(state, executor, args, callback) + with_async(executor, state, args, callback) do |st, ar, cb| + callback_on_fulfillment st, ar, cb + end + end + + def async_callback_on_rejection(state, executor, args, callback) + with_async(executor, state, args, callback) do |st, ar, cb| + callback_on_rejection st, ar, cb + end + end + + def callback_on_fulfillment(state, args, callback) + state.apply args, callback if state.fulfilled? + end + + def callback_on_rejection(state, args, callback) + state.apply args, callback unless state.fulfilled? + end + + def callback_on_resolution(state, args, callback) + callback.call(*state.result, *args) + end + + end + + # Marker module of Future, Event resolved manually. + module Resolvable + include InternalStates + end + + # A Event which can be resolved by user. + class ResolvableEvent < Event + include Resolvable + + # @!macro raise_on_reassign + # @raise [MultipleAssignmentError] when already resolved and raise_on_reassign is true. + + # @!macro promise.param.raise_on_reassign + # @param [Boolean] raise_on_reassign should method raise exception if already resolved + # @return [self, false] false is returned when raise_on_reassign is false and the receiver + # is already resolved. + # + + # Makes the event resolved, which triggers all dependent futures. + # + # @!macro promise.param.raise_on_reassign + # @!macro promise.param.reserved + # @param [true, false] reserved + # Set to true if the resolvable is {#reserve}d by you, + # marks resolution of reserved resolvable events and futures explicitly. + # Advanced feature, ignore unless you use {Resolvable#reserve} from edge. + def resolve(raise_on_reassign = true, reserved = false) + resolve_with RESOLVED, raise_on_reassign, reserved + end + + # Creates new event wrapping receiver, effectively hiding the resolve method. + # + # @return [Event] + def with_hidden_resolvable + @with_hidden_resolvable ||= EventWrapperPromise.new_blocked_by1(self, @DefaultExecutor).event + end + + # Behaves as {AbstractEventFuture#wait} but has one additional optional argument + # resolve_on_timeout. + # + # @param [true, false] resolve_on_timeout + # If it times out and the argument is true it will also resolve the event. + # @return [self, true, false] + # @see AbstractEventFuture#wait + def wait(timeout = nil, resolve_on_timeout = false) + super(timeout) or if resolve_on_timeout + # if it fails to resolve it was resolved in the meantime + # so return true as if there was no timeout + !resolve(false) + else + false + end + end + end + + # A Future which can be resolved by user. + class ResolvableFuture < Future + include Resolvable + + # Makes the future resolved with result of triplet `fulfilled?`, `value`, `reason`, + # which triggers all dependent futures. + # + # @param [true, false] fulfilled + # @param [Object] value + # @param [Object] reason + # @!macro promise.param.raise_on_reassign + # @!macro promise.param.reserved + def resolve(fulfilled = true, value = nil, reason = nil, raise_on_reassign = true, reserved = false) + resolve_with(fulfilled ? Fulfilled.new(value) : Rejected.new(reason), raise_on_reassign, reserved) + end + + # Makes the future fulfilled with `value`, + # which triggers all dependent futures. + # + # @param [Object] value + # @!macro promise.param.raise_on_reassign + # @!macro promise.param.reserved + def fulfill(value, raise_on_reassign = true, reserved = false) + resolve_with Fulfilled.new(value), raise_on_reassign, reserved + end + + # Makes the future rejected with `reason`, + # which triggers all dependent futures. + # + # @param [Object] reason + # @!macro promise.param.raise_on_reassign + # @!macro promise.param.reserved + def reject(reason, raise_on_reassign = true, reserved = false) + resolve_with Rejected.new(reason), raise_on_reassign, reserved + end + + # Evaluates the block and sets its result as future's value fulfilling, if the block raises + # an exception the future rejects with it. + # + # @yield [*args] to the block. + # @yieldreturn [Object] value + # @return [self] + def evaluate_to(*args, &block) + promise.evaluate_to(*args, block) + end + + # Evaluates the block and sets its result as future's value fulfilling, if the block raises + # an exception the future rejects with it. + # + # @yield [*args] to the block. + # @yieldreturn [Object] value + # @return [self] + # @raise [Exception] also raise reason on rejection. + def evaluate_to!(*args, &block) + promise.evaluate_to(*args, block).wait! + end + + # @!macro promises.resolvable.resolve_on_timeout + # @param [::Array(true, Object, nil), ::Array(false, nil, Exception), nil] resolve_on_timeout + # If it times out and the argument is not nil it will also resolve the future + # to the provided resolution. + + # Behaves as {AbstractEventFuture#wait} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [self, true, false] + # @see AbstractEventFuture#wait + def wait(timeout = nil, resolve_on_timeout = nil) + super(timeout) or if resolve_on_timeout + # if it fails to resolve it was resolved in the meantime + # so return true as if there was no timeout + !resolve(*resolve_on_timeout, false) + else + false + end + end + + # Behaves as {Future#wait!} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [self, true, false] + # @raise [Exception] {#reason} on rejection + # @see Future#wait! + def wait!(timeout = nil, resolve_on_timeout = nil) + super(timeout) or if resolve_on_timeout + if resolve(*resolve_on_timeout, false) + false + else + # if it fails to resolve it was resolved in the meantime + # so return true as if there was no timeout + raise self if rejected? + true + end + else + false + end + end + + # Behaves as {Future#value} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [Object, timeout_value, nil] + # @see Future#value + def value(timeout = nil, timeout_value = nil, resolve_on_timeout = nil) + if wait_until_resolved timeout + internal_state.value + else + if resolve_on_timeout + unless resolve(*resolve_on_timeout, false) + # if it fails to resolve it was resolved in the meantime + # so return value as if there was no timeout + return internal_state.value + end + end + timeout_value + end + end + + # Behaves as {Future#value!} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [Object, timeout_value, nil] + # @raise [Exception] {#reason} on rejection + # @see Future#value! + def value!(timeout = nil, timeout_value = nil, resolve_on_timeout = nil) + if wait_until_resolved! timeout + internal_state.value + else + if resolve_on_timeout + unless resolve(*resolve_on_timeout, false) + # if it fails to resolve it was resolved in the meantime + # so return value as if there was no timeout + raise self if rejected? + return internal_state.value + end + end + timeout_value + end + end + + # Behaves as {Future#reason} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [Exception, timeout_value, nil] + # @see Future#reason + def reason(timeout = nil, timeout_value = nil, resolve_on_timeout = nil) + if wait_until_resolved timeout + internal_state.reason + else + if resolve_on_timeout + unless resolve(*resolve_on_timeout, false) + # if it fails to resolve it was resolved in the meantime + # so return value as if there was no timeout + return internal_state.reason + end + end + timeout_value + end + end + + # Behaves as {Future#result} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [::Array(Boolean, Object, Exception), nil] + # @see Future#result + def result(timeout = nil, resolve_on_timeout = nil) + if wait_until_resolved timeout + internal_state.result + else + if resolve_on_timeout + unless resolve(*resolve_on_timeout, false) + # if it fails to resolve it was resolved in the meantime + # so return value as if there was no timeout + internal_state.result + end + end + # otherwise returns nil + end + end + + # Creates new future wrapping receiver, effectively hiding the resolve method and similar. + # + # @return [Future] + def with_hidden_resolvable + @with_hidden_resolvable ||= FutureWrapperPromise.new_blocked_by1(self, @DefaultExecutor).future + end + end + + # @abstract + # @private + class AbstractPromise < Synchronization::Object + safe_initialization! + include InternalStates + + def initialize(future) + super() + @Future = future + end + + def future + @Future + end + + alias_method :event, :future + + def default_executor + future.default_executor + end + + def state + future.state + end + + def touch + end + + def to_s + format '%s %s>', super[0..-2], @Future + end + + alias_method :inspect, :to_s + + def delayed_because + nil + end + + private + + def resolve_with(new_state, raise_on_reassign = true) + @Future.resolve_with(new_state, raise_on_reassign) + end + + # @return [Future] + def evaluate_to(*args, block) + resolve_with Fulfilled.new(block.call(*args)) + rescue Exception => error + resolve_with Rejected.new(error) + raise error unless error.is_a?(StandardError) + end + end + + class ResolvableEventPromise < AbstractPromise + def initialize(default_executor) + super ResolvableEvent.new(self, default_executor) + end + end + + class ResolvableFuturePromise < AbstractPromise + def initialize(default_executor) + super ResolvableFuture.new(self, default_executor) + end + + public :evaluate_to + end + + # @abstract + class InnerPromise < AbstractPromise + end + + # @abstract + class BlockedPromise < InnerPromise + + private_class_method :new + + def self.new_blocked_by1(blocker, *args, &block) + blocker_delayed = blocker.promise.delayed_because + promise = new(blocker_delayed, 1, *args, &block) + blocker.add_callback_notify_blocked promise, 0 + promise + end + + def self.new_blocked_by2(blocker1, blocker2, *args, &block) + blocker_delayed1 = blocker1.promise.delayed_because + blocker_delayed2 = blocker2.promise.delayed_because + delayed = if blocker_delayed1 && blocker_delayed2 + # TODO (pitr-ch 23-Dec-2016): use arrays when we know it will not grow (only flat adds delay) + LockFreeStack.of2(blocker_delayed1, blocker_delayed2) + else + blocker_delayed1 || blocker_delayed2 + end + promise = new(delayed, 2, *args, &block) + blocker1.add_callback_notify_blocked promise, 0 + blocker2.add_callback_notify_blocked promise, 1 + promise + end + + def self.new_blocked_by(blockers, *args, &block) + delayed = blockers.reduce(nil) { |d, f| add_delayed d, f.promise.delayed_because } + promise = new(delayed, blockers.size, *args, &block) + blockers.each_with_index { |f, i| f.add_callback_notify_blocked promise, i } + promise + end + + def self.add_delayed(delayed1, delayed2) + if delayed1 && delayed2 + delayed1.push delayed2 + delayed1 + else + delayed1 || delayed2 + end + end + + def initialize(delayed, blockers_count, future) + super(future) + @Delayed = delayed + @Countdown = AtomicFixnum.new blockers_count + end + + def on_blocker_resolution(future, index) + countdown = process_on_blocker_resolution(future, index) + resolvable = resolvable?(countdown, future, index) + + on_resolvable(future, index) if resolvable + end + + def delayed_because + @Delayed + end + + def touch + clear_and_propagate_touch + end + + # for inspection only + def blocked_by + blocked_by = [] + ObjectSpace.each_object(AbstractEventFuture) { |o| blocked_by.push o if o.blocks.include? self } + blocked_by + end + + private + + def clear_and_propagate_touch(stack_or_element = @Delayed) + return if stack_or_element.nil? + + if stack_or_element.is_a? LockFreeStack + stack_or_element.clear_each { |element| clear_and_propagate_touch element } + else + stack_or_element.touch unless stack_or_element.nil? # if still present + end + end + + # @return [true,false] if resolvable + def resolvable?(countdown, future, index) + countdown.zero? + end + + def process_on_blocker_resolution(future, index) + @Countdown.decrement + end + + def on_resolvable(resolved_future, index) + raise NotImplementedError + end + end + + # @abstract + class BlockedTaskPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor, executor, args, &task) + raise ArgumentError, 'no block given' unless block_given? + super delayed, 1, Future.new(self, default_executor) + @Executor = executor + @Task = task + @Args = args + end + + def executor + @Executor + end + end + + class ThenPromise < BlockedTaskPromise + private + + def initialize(delayed, blockers_count, default_executor, executor, args, &task) + super delayed, blockers_count, default_executor, executor, args, &task + end + + def on_resolvable(resolved_future, index) + if resolved_future.fulfilled? + Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task| + evaluate_to lambda { future.apply args, task } + end + else + resolve_with resolved_future.internal_state + end + end + end + + class RescuePromise < BlockedTaskPromise + private + + def initialize(delayed, blockers_count, default_executor, executor, args, &task) + super delayed, blockers_count, default_executor, executor, args, &task + end + + def on_resolvable(resolved_future, index) + if resolved_future.rejected? + Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task| + evaluate_to lambda { future.apply args, task } + end + else + resolve_with resolved_future.internal_state + end + end + end + + class ChainPromise < BlockedTaskPromise + private + + def on_resolvable(resolved_future, index) + if Future === resolved_future + Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task| + evaluate_to(*future.result, *args, task) + end + else + Concurrent.executor(@Executor).post(@Args, @Task) do |args, task| + evaluate_to(*args, task) + end + end + end + end + + # will be immediately resolved + class ImmediateEventPromise < InnerPromise + def initialize(default_executor) + super Event.new(self, default_executor).resolve_with(RESOLVED) + end + end + + class ImmediateFuturePromise < InnerPromise + def initialize(default_executor, fulfilled, value, reason) + super Future.new(self, default_executor). + resolve_with(fulfilled ? Fulfilled.new(value) : Rejected.new(reason)) + end + end + + class AbstractFlatPromise < BlockedPromise + + def initialize(delayed_because, blockers_count, event_or_future) + delayed = LockFreeStack.of1(self) + super(delayed, blockers_count, event_or_future) + # noinspection RubyArgCount + @Touched = AtomicBoolean.new false + @DelayedBecause = delayed_because || LockFreeStack.new + + event_or_future.add_callback_clear_delayed_node delayed.peek + end + + def touch + if @Touched.make_true + clear_and_propagate_touch @DelayedBecause + end + end + + private + + def touched? + @Touched.value + end + + def on_resolvable(resolved_future, index) + resolve_with resolved_future.internal_state + end + + def resolvable?(countdown, future, index) + !@Future.internal_state.resolved? && super(countdown, future, index) + end + + def add_delayed_of(future) + delayed = future.promise.delayed_because + if touched? + clear_and_propagate_touch delayed + else + BlockedPromise.add_delayed @DelayedBecause, delayed + clear_and_propagate_touch @DelayedBecause if touched? + end + end + + end + + class FlatEventPromise < AbstractFlatPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super delayed, 2, Event.new(self, default_executor) + end + + def process_on_blocker_resolution(future, index) + countdown = super(future, index) + if countdown.nonzero? + internal_state = future.internal_state + + unless internal_state.fulfilled? + resolve_with RESOLVED + return countdown + end + + value = internal_state.value + case value + when AbstractEventFuture + add_delayed_of value + value.add_callback_notify_blocked self, nil + countdown + else + resolve_with RESOLVED + end + end + countdown + end + + end + + class FlatFuturePromise < AbstractFlatPromise + + private + + def initialize(delayed, blockers_count, levels, default_executor) + raise ArgumentError, 'levels has to be higher than 0' if levels < 1 + # flat promise may result to a future having delayed futures, therefore we have to have empty stack + # to be able to add new delayed futures + super delayed || LockFreeStack.new, 1 + levels, Future.new(self, default_executor) + end + + def process_on_blocker_resolution(future, index) + countdown = super(future, index) + if countdown.nonzero? + internal_state = future.internal_state + + unless internal_state.fulfilled? + resolve_with internal_state + return countdown + end + + value = internal_state.value + case value + when AbstractEventFuture + add_delayed_of value + value.add_callback_notify_blocked self, nil + countdown + else + evaluate_to(lambda { raise TypeError, "returned value #{value.inspect} is not a Future" }) + end + end + countdown + end + + end + + class RunFuturePromise < AbstractFlatPromise + + private + + def initialize(delayed, blockers_count, default_executor, run_test) + super delayed, 1, Future.new(self, default_executor) + @RunTest = run_test + end + + def process_on_blocker_resolution(future, index) + internal_state = future.internal_state + + unless internal_state.fulfilled? + resolve_with internal_state + return 0 + end + + value = internal_state.value + continuation_future = @RunTest.call value + + if continuation_future + add_delayed_of continuation_future + continuation_future.add_callback_notify_blocked self, nil + else + resolve_with internal_state + end + + 1 + end + end + + class ZipEventEventPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor) + super delayed, 2, Event.new(self, default_executor) + end + + private + + def on_resolvable(resolved_future, index) + resolve_with RESOLVED + end + end + + class ZipFutureEventPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor) + super delayed, 2, Future.new(self, default_executor) + @result = nil + end + + private + + def process_on_blocker_resolution(future, index) + # first blocking is future, take its result + @result = future.internal_state if index == 0 + # super has to be called after above to piggyback on volatile @Countdown + super future, index + end + + def on_resolvable(resolved_future, index) + resolve_with @result + end + end + + class EventWrapperPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor) + super delayed, 1, Event.new(self, default_executor) + end + + private + + def on_resolvable(resolved_future, index) + resolve_with RESOLVED + end + end + + class FutureWrapperPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor) + super delayed, 1, Future.new(self, default_executor) + end + + private + + def on_resolvable(resolved_future, index) + resolve_with resolved_future.internal_state + end + end + + class ZipFuturesPromise < BlockedPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super(delayed, blockers_count, Future.new(self, default_executor)) + @Resolutions = ::Array.new(blockers_count, nil) + + on_resolvable nil, nil if blockers_count == 0 + end + + def process_on_blocker_resolution(future, index) + # TODO (pitr-ch 18-Dec-2016): Can we assume that array will never break under parallel access when never re-sized? + @Resolutions[index] = future.internal_state # has to be set before countdown in super + super future, index + end + + def on_resolvable(resolved_future, index) + all_fulfilled = true + values = ::Array.new(@Resolutions.size) + reasons = ::Array.new(@Resolutions.size) + + @Resolutions.each_with_index do |internal_state, i| + fulfilled, values[i], reasons[i] = internal_state.result + all_fulfilled &&= fulfilled + end + + if all_fulfilled + resolve_with FulfilledArray.new(values) + else + resolve_with PartiallyRejected.new(values, reasons) + end + end + end + + class ZipEventsPromise < BlockedPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super delayed, blockers_count, Event.new(self, default_executor) + + on_resolvable nil, nil if blockers_count == 0 + end + + def on_resolvable(resolved_future, index) + resolve_with RESOLVED + end + end + + # @abstract + class AbstractAnyPromise < BlockedPromise + end + + class AnyResolvedEventPromise < AbstractAnyPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super delayed, blockers_count, Event.new(self, default_executor) + end + + def resolvable?(countdown, future, index) + true + end + + def on_resolvable(resolved_future, index) + resolve_with RESOLVED, false + end + end + + class AnyResolvedFuturePromise < AbstractAnyPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super delayed, blockers_count, Future.new(self, default_executor) + end + + def resolvable?(countdown, future, index) + true + end + + def on_resolvable(resolved_future, index) + resolve_with resolved_future.internal_state, false + end + end + + class AnyFulfilledFuturePromise < AnyResolvedFuturePromise + + private + + def resolvable?(countdown, event_or_future, index) + (event_or_future.is_a?(Event) ? event_or_future.resolved? : event_or_future.fulfilled?) || + # inlined super from BlockedPromise + countdown.zero? + end + end + + class DelayPromise < InnerPromise + + def initialize(default_executor) + event = Event.new(self, default_executor) + @Delayed = LockFreeStack.of1(self) + super event + event.add_callback_clear_delayed_node @Delayed.peek + end + + def touch + @Future.resolve_with RESOLVED + end + + def delayed_because + @Delayed + end + + end + + class ScheduledPromise < InnerPromise + def intended_time + @IntendedTime + end + + def inspect + "#{to_s[0..-2]} intended_time: #{@IntendedTime}>" + end + + private + + def initialize(default_executor, intended_time) + super Event.new(self, default_executor) + + @IntendedTime = intended_time + + in_seconds = begin + now = Time.now + schedule_time = if @IntendedTime.is_a? Time + @IntendedTime + else + now + @IntendedTime + end + [0, schedule_time.to_f - now.to_f].max + end + + Concurrent.global_timer_set.post(in_seconds) do + @Future.resolve_with RESOLVED + end + end + end + + extend FactoryMethods + + private_constant :AbstractPromise, + :ResolvableEventPromise, + :ResolvableFuturePromise, + :InnerPromise, + :BlockedPromise, + :BlockedTaskPromise, + :ThenPromise, + :RescuePromise, + :ChainPromise, + :ImmediateEventPromise, + :ImmediateFuturePromise, + :AbstractFlatPromise, + :FlatFuturePromise, + :FlatEventPromise, + :RunFuturePromise, + :ZipEventEventPromise, + :ZipFutureEventPromise, + :EventWrapperPromise, + :FutureWrapperPromise, + :ZipFuturesPromise, + :ZipEventsPromise, + :AbstractAnyPromise, + :AnyResolvedFuturePromise, + :AnyFulfilledFuturePromise, + :AnyResolvedEventPromise, + :DelayPromise, + :ScheduledPromise + + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/re_include.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/re_include.rb new file mode 100644 index 0000000000..600bc6a535 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/re_include.rb @@ -0,0 +1,60 @@ +module Concurrent + + # Methods form module A included to a module B, which is already included into class C, + # will not be visible in the C class. If this module is extended to B then A's methods + # are correctly made visible to C. + # + # @example + # module A + # def a + # :a + # end + # end + # + # module B1 + # end + # + # class C1 + # include B1 + # end + # + # module B2 + # extend Concurrent::ReInclude + # end + # + # class C2 + # include B2 + # end + # + # B1.send :include, A + # B2.send :include, A + # + # C1.new.respond_to? :a # => false + # C2.new.respond_to? :a # => true + # + # @!visibility private + module ReInclude + # @!visibility private + def included(base) + (@re_include_to_bases ||= []) << [:include, base] + super(base) + end + + # @!visibility private + def extended(base) + (@re_include_to_bases ||= []) << [:extend, base] + super(base) + end + + # @!visibility private + def include(*modules) + result = super(*modules) + modules.reverse.each do |module_being_included| + (@re_include_to_bases ||= []).each do |method, mod| + mod.send method, module_being_included + end + end + result + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/scheduled_task.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/scheduled_task.rb new file mode 100644 index 0000000000..429fc0683c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/scheduled_task.rb @@ -0,0 +1,331 @@ +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/configuration' +require 'concurrent/ivar' +require 'concurrent/collection/copy_on_notify_observer_set' +require 'concurrent/utility/monotonic_time' + +require 'concurrent/options' + +module Concurrent + + # `ScheduledTask` is a close relative of `Concurrent::Future` but with one + # important difference: A `Future` is set to execute as soon as possible + # whereas a `ScheduledTask` is set to execute after a specified delay. This + # implementation is loosely based on Java's + # [ScheduledExecutorService](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html). + # It is a more feature-rich variant of {Concurrent.timer}. + # + # The *intended* schedule time of task execution is set on object construction + # with the `delay` argument. The delay is a numeric (floating point or integer) + # representing a number of seconds in the future. Any other value or a numeric + # equal to or less than zero will result in an exception. The *actual* schedule + # time of task execution is set when the `execute` method is called. + # + # The constructor can also be given zero or more processing options. Currently + # the only supported options are those recognized by the + # [Dereferenceable](Dereferenceable) module. + # + # The final constructor argument is a block representing the task to be performed. + # If no block is given an `ArgumentError` will be raised. + # + # **States** + # + # `ScheduledTask` mixes in the [Obligation](Obligation) module thus giving it + # "future" behavior. This includes the expected lifecycle states. `ScheduledTask` + # has one additional state, however. While the task (block) is being executed the + # state of the object will be `:processing`. This additional state is necessary + # because it has implications for task cancellation. + # + # **Cancellation** + # + # A `:pending` task can be cancelled using the `#cancel` method. A task in any + # other state, including `:processing`, cannot be cancelled. The `#cancel` + # method returns a boolean indicating the success of the cancellation attempt. + # A cancelled `ScheduledTask` cannot be restarted. It is immutable. + # + # **Obligation and Observation** + # + # The result of a `ScheduledTask` can be obtained either synchronously or + # asynchronously. `ScheduledTask` mixes in both the [Obligation](Obligation) + # module and the + # [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html) + # module from the Ruby standard library. With one exception `ScheduledTask` + # behaves identically to [Future](Observable) with regard to these modules. + # + # @!macro copy_options + # + # @example Basic usage + # + # require 'concurrent/scheduled_task' + # require 'csv' + # require 'open-uri' + # + # class Ticker + # def get_year_end_closing(symbol, year, api_key) + # uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv" + # data = [] + # csv = URI.parse(uri).read + # if csv.include?('call frequency') + # return :rate_limit_exceeded + # end + # CSV.parse(csv, headers: true) do |row| + # data << row['close'].to_f if row['timestamp'].include?(year.to_s) + # end + # year_end = data.first + # year_end + # rescue => e + # p e + # end + # end + # + # api_key = ENV['ALPHAVANTAGE_KEY'] + # abort(error_message) unless api_key + # + # # Future + # price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013, api_key) } + # price.state #=> :pending + # price.pending? #=> true + # price.value(0) #=> nil (does not block) + # + # sleep(1) # do other stuff + # + # price.value #=> 63.65 (after blocking if necessary) + # price.state #=> :fulfilled + # price.fulfilled? #=> true + # price.value #=> 63.65 + # + # @example Successful task execution + # + # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' } + # task.state #=> :unscheduled + # task.execute + # task.state #=> pending + # + # # wait for it... + # sleep(3) + # + # task.unscheduled? #=> false + # task.pending? #=> false + # task.fulfilled? #=> true + # task.rejected? #=> false + # task.value #=> 'What does the fox say?' + # + # @example One line creation and execution + # + # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }.execute + # task.state #=> pending + # + # task = Concurrent::ScheduledTask.execute(2){ 'What do you get when you multiply 6 by 9?' } + # task.state #=> pending + # + # @example Failed task execution + # + # task = Concurrent::ScheduledTask.execute(2){ raise StandardError.new('Call me maybe?') } + # task.pending? #=> true + # + # # wait for it... + # sleep(3) + # + # task.unscheduled? #=> false + # task.pending? #=> false + # task.fulfilled? #=> false + # task.rejected? #=> true + # task.value #=> nil + # task.reason #=> # + # + # @example Task execution with observation + # + # observer = Class.new{ + # def update(time, value, reason) + # puts "The task completed at #{time} with value '#{value}'" + # end + # }.new + # + # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' } + # task.add_observer(observer) + # task.execute + # task.pending? #=> true + # + # # wait for it... + # sleep(3) + # + # #>> The task completed at 2013-11-07 12:26:09 -0500 with value 'What does the fox say?' + # + # @!macro monotonic_clock_warning + # + # @see Concurrent.timer + class ScheduledTask < IVar + include Comparable + + # The executor on which to execute the task. + # @!visibility private + attr_reader :executor + + # Schedule a task for execution at a specified future time. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @yield the task to be performed + # + # @!macro executor_and_deref_options + # + # @option opts [object, Array] :args zero or more arguments to be passed the task + # block on execution + # + # @raise [ArgumentError] When no block is given + # @raise [ArgumentError] When given a time that is in the past + def initialize(delay, opts = {}, &task) + raise ArgumentError.new('no block given') unless block_given? + raise ArgumentError.new('seconds must be greater than zero') if delay.to_f < 0.0 + + super(NULL, opts, &nil) + + synchronize do + ns_set_state(:unscheduled) + @parent = opts.fetch(:timer_set, Concurrent.global_timer_set) + @args = get_arguments_from(opts) + @delay = delay.to_f + @task = task + @time = nil + @executor = Options.executor_from_options(opts) || Concurrent.global_io_executor + self.observers = Collection::CopyOnNotifyObserverSet.new + end + end + + # The `delay` value given at instanciation. + # + # @return [Float] the initial delay. + def initial_delay + synchronize { @delay } + end + + # The monotonic time at which the the task is scheduled to be executed. + # + # @return [Float] the schedule time or nil if `unscheduled` + def schedule_time + synchronize { @time } + end + + # Comparator which orders by schedule time. + # + # @!visibility private + def <=>(other) + schedule_time <=> other.schedule_time + end + + # Has the task been cancelled? + # + # @return [Boolean] true if the task is in the given state else false + def cancelled? + synchronize { ns_check_state?(:cancelled) } + end + + # In the task execution in progress? + # + # @return [Boolean] true if the task is in the given state else false + def processing? + synchronize { ns_check_state?(:processing) } + end + + # Cancel this task and prevent it from executing. A task can only be + # cancelled if it is pending or unscheduled. + # + # @return [Boolean] true if successfully cancelled else false + def cancel + if compare_and_set_state(:cancelled, :pending, :unscheduled) + complete(false, nil, CancelledOperationError.new) + # To avoid deadlocks this call must occur outside of #synchronize + # Changing the state above should prevent redundant calls + @parent.send(:remove_task, self) + else + false + end + end + + # Reschedule the task using the original delay and the current time. + # A task can only be reset while it is `:pending`. + # + # @return [Boolean] true if successfully rescheduled else false + def reset + synchronize{ ns_reschedule(@delay) } + end + + # Reschedule the task using the given delay and the current time. + # A task can only be reset while it is `:pending`. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @return [Boolean] true if successfully rescheduled else false + # + # @raise [ArgumentError] When given a time that is in the past + def reschedule(delay) + delay = delay.to_f + raise ArgumentError.new('seconds must be greater than zero') if delay < 0.0 + synchronize{ ns_reschedule(delay) } + end + + # Execute an `:unscheduled` `ScheduledTask`. Immediately sets the state to `:pending` + # and starts counting down toward execution. Does nothing if the `ScheduledTask` is + # in any state other than `:unscheduled`. + # + # @return [ScheduledTask] a reference to `self` + def execute + if compare_and_set_state(:pending, :unscheduled) + synchronize{ ns_schedule(@delay) } + end + self + end + + # Create a new `ScheduledTask` object with the given block, execute it, and return the + # `:pending` object. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @!macro executor_and_deref_options + # + # @return [ScheduledTask] the newly created `ScheduledTask` in the `:pending` state + # + # @raise [ArgumentError] if no block is given + def self.execute(delay, opts = {}, &task) + new(delay, opts, &task).execute + end + + # Execute the task. + # + # @!visibility private + def process_task + safe_execute(@task, @args) + end + + protected :set, :try_set, :fail, :complete + + protected + + # Schedule the task using the given delay and the current time. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @return [Boolean] true if successfully rescheduled else false + # + # @!visibility private + def ns_schedule(delay) + @delay = delay + @time = Concurrent.monotonic_time + @delay + @parent.send(:post_task, self) + end + + # Reschedule the task using the given delay and the current time. + # A task can only be reset while it is `:pending`. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @return [Boolean] true if successfully rescheduled else false + # + # @!visibility private + def ns_reschedule(delay) + return false unless ns_check_state?(:pending) + @parent.send(:remove_task, self) && ns_schedule(delay) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/set.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/set.rb new file mode 100644 index 0000000000..eee4effdfd --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/set.rb @@ -0,0 +1,64 @@ +require 'concurrent/utility/engine' +require 'concurrent/thread_safe/util' +require 'set' + +module Concurrent + + # @!macro concurrent_set + # + # A thread-safe subclass of Set. This version locks against the object + # itself for every method call, ensuring only one thread can be reading + # or writing at a time. This includes iteration methods like `#each`. + # + # @note `a += b` is **not** a **thread-safe** operation on + # `Concurrent::Set`. It reads Set `a`, then it creates new `Concurrent::Set` + # which is union of `a` and `b`, then it writes the union to `a`. + # The read and write are independent operations they do not form a single atomic + # operation therefore when two `+=` operations are executed concurrently updates + # may be lost. Use `#merge` instead. + # + # @see http://ruby-doc.org/stdlib-2.4.0/libdoc/set/rdoc/Set.html Ruby standard library `Set` + + # @!macro internal_implementation_note + SetImplementation = case + when Concurrent.on_cruby? + # The CRuby implementation of Set is written in Ruby itself and is + # not thread safe for certain methods. + require 'monitor' + require 'concurrent/thread_safe/util/data_structures' + + class CRubySet < ::Set + end + + ThreadSafe::Util.make_synchronized_on_cruby CRubySet + CRubySet + + when Concurrent.on_jruby? + require 'jruby/synchronized' + + class JRubySet < ::Set + include JRuby::Synchronized + end + + JRubySet + + when Concurrent.on_truffleruby? + require 'concurrent/thread_safe/util/data_structures' + + class TruffleRubySet < ::Set + end + + ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubySet + TruffleRubySet + + else + warn 'Possibly unsupported Ruby implementation' + ::Set + end + private_constant :SetImplementation + + # @!macro concurrent_set + class Set < SetImplementation + end +end + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/settable_struct.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/settable_struct.rb new file mode 100644 index 0000000000..99b85619fd --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/settable_struct.rb @@ -0,0 +1,139 @@ +require 'concurrent/errors' +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # An thread-safe, write-once variation of Ruby's standard `Struct`. + # Each member can have its value set at most once, either at construction + # or any time thereafter. Attempting to assign a value to a member + # that has already been set will result in a `Concurrent::ImmutabilityError`. + # + # @see http://ruby-doc.org/core/Struct.html Ruby standard library `Struct` + # @see http://en.wikipedia.org/wiki/Final_(Java) Java `final` keyword + module SettableStruct + include Synchronization::AbstractStruct + + # @!macro struct_values + def values + synchronize { ns_values } + end + alias_method :to_a, :values + + # @!macro struct_values_at + def values_at(*indexes) + synchronize { ns_values_at(indexes) } + end + + # @!macro struct_inspect + def inspect + synchronize { ns_inspect } + end + alias_method :to_s, :inspect + + # @!macro struct_merge + def merge(other, &block) + synchronize { ns_merge(other, &block) } + end + + # @!macro struct_to_h + def to_h + synchronize { ns_to_h } + end + + # @!macro struct_get + def [](member) + synchronize { ns_get(member) } + end + + # @!macro struct_equality + def ==(other) + synchronize { ns_equality(other) } + end + + # @!macro struct_each + def each(&block) + return enum_for(:each) unless block_given? + synchronize { ns_each(&block) } + end + + # @!macro struct_each_pair + def each_pair(&block) + return enum_for(:each_pair) unless block_given? + synchronize { ns_each_pair(&block) } + end + + # @!macro struct_select + def select(&block) + return enum_for(:select) unless block_given? + synchronize { ns_select(&block) } + end + + # @!macro struct_set + # + # @raise [Concurrent::ImmutabilityError] if the given member has already been set + def []=(member, value) + if member.is_a? Integer + length = synchronize { @values.length } + if member >= length + raise IndexError.new("offset #{member} too large for struct(size:#{length})") + end + synchronize do + unless @values[member].nil? + raise Concurrent::ImmutabilityError.new('struct member has already been set') + end + @values[member] = value + end + else + send("#{member}=", value) + end + rescue NoMethodError + raise NameError.new("no member '#{member}' in struct") + end + + private + + # @!visibility private + def initialize_copy(original) + synchronize do + super(original) + ns_initialize_copy + end + end + + # @!macro struct_new + def self.new(*args, &block) + clazz_name = nil + if args.length == 0 + raise ArgumentError.new('wrong number of arguments (0 for 1+)') + elsif args.length > 0 && args.first.is_a?(String) + clazz_name = args.shift + end + FACTORY.define_struct(clazz_name, args, &block) + end + + FACTORY = Class.new(Synchronization::LockableObject) do + def define_struct(name, members, &block) + synchronize do + clazz = Synchronization::AbstractStruct.define_struct_class(SettableStruct, Synchronization::LockableObject, name, members, &block) + members.each_with_index do |member, index| + clazz.send :remove_method, member if clazz.instance_methods.include? member + clazz.send(:define_method, member) do + synchronize { @values[index] } + end + clazz.send(:define_method, "#{member}=") do |value| + synchronize do + unless @values[index].nil? + raise Concurrent::ImmutabilityError.new('struct member has already been set') + end + @values[index] = value + end + end + end + clazz + end + end + end.new + private_constant :FACTORY + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization.rb new file mode 100644 index 0000000000..6d8cf4bd58 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization.rb @@ -0,0 +1,13 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/synchronization/object' +require 'concurrent/synchronization/lockable_object' +require 'concurrent/synchronization/condition' +require 'concurrent/synchronization/lock' + +module Concurrent + # @!visibility private + module Synchronization + end +end + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_lockable_object.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_lockable_object.rb new file mode 100644 index 0000000000..d9050b312f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_lockable_object.rb @@ -0,0 +1,102 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first +require 'concurrent/utility/monotonic_time' +require 'concurrent/synchronization/object' + +module Concurrent + module Synchronization + + # @!visibility private + class AbstractLockableObject < Synchronization::Object + + protected + + # @!macro synchronization_object_method_synchronize + # + # @yield runs the block synchronized against this object, + # equivalent of java's `synchronize(this) {}` + # @note can by made public in descendants if required by `public :synchronize` + def synchronize + raise NotImplementedError + end + + # @!macro synchronization_object_method_ns_wait_until + # + # Wait until condition is met or timeout passes, + # protects against spurious wake-ups. + # @param [Numeric, nil] timeout in seconds, `nil` means no timeout + # @yield condition to be met + # @yieldreturn [true, false] + # @return [true, false] if condition met + # @note only to be used inside synchronized block + # @note to provide direct access to this method in a descendant add method + # ``` + # def wait_until(timeout = nil, &condition) + # synchronize { ns_wait_until(timeout, &condition) } + # end + # ``` + def ns_wait_until(timeout = nil, &condition) + if timeout + wait_until = Concurrent.monotonic_time + timeout + loop do + now = Concurrent.monotonic_time + condition_result = condition.call + return condition_result if now >= wait_until || condition_result + ns_wait wait_until - now + end + else + ns_wait timeout until condition.call + true + end + end + + # @!macro synchronization_object_method_ns_wait + # + # Wait until another thread calls #signal or #broadcast, + # spurious wake-ups can happen. + # + # @param [Numeric, nil] timeout in seconds, `nil` means no timeout + # @return [self] + # @note only to be used inside synchronized block + # @note to provide direct access to this method in a descendant add method + # ``` + # def wait(timeout = nil) + # synchronize { ns_wait(timeout) } + # end + # ``` + def ns_wait(timeout = nil) + raise NotImplementedError + end + + # @!macro synchronization_object_method_ns_signal + # + # Signal one waiting thread. + # @return [self] + # @note only to be used inside synchronized block + # @note to provide direct access to this method in a descendant add method + # ``` + # def signal + # synchronize { ns_signal } + # end + # ``` + def ns_signal + raise NotImplementedError + end + + # @!macro synchronization_object_method_ns_broadcast + # + # Broadcast to all waiting threads. + # @return [self] + # @note only to be used inside synchronized block + # @note to provide direct access to this method in a descendant add method + # ``` + # def broadcast + # synchronize { ns_broadcast } + # end + # ``` + def ns_broadcast + raise NotImplementedError + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_object.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_object.rb new file mode 100644 index 0000000000..7cd2decf99 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_object.rb @@ -0,0 +1,22 @@ +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + class AbstractObject + def initialize + # nothing to do + end + + # @!visibility private + # @abstract + def full_memory_barrier + raise NotImplementedError + end + + def self.attr_volatile(*names) + raise NotImplementedError + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_struct.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_struct.rb new file mode 100644 index 0000000000..1fe90c1649 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/abstract_struct.rb @@ -0,0 +1,171 @@ +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + module AbstractStruct + + # @!visibility private + def initialize(*values) + super() + ns_initialize(*values) + end + + # @!macro struct_length + # + # Returns the number of struct members. + # + # @return [Fixnum] the number of struct members + def length + self.class::MEMBERS.length + end + alias_method :size, :length + + # @!macro struct_members + # + # Returns the struct members as an array of symbols. + # + # @return [Array] the struct members as an array of symbols + def members + self.class::MEMBERS.dup + end + + protected + + # @!macro struct_values + # + # @!visibility private + def ns_values + @values.dup + end + + # @!macro struct_values_at + # + # @!visibility private + def ns_values_at(indexes) + @values.values_at(*indexes) + end + + # @!macro struct_to_h + # + # @!visibility private + def ns_to_h + length.times.reduce({}){|memo, i| memo[self.class::MEMBERS[i]] = @values[i]; memo} + end + + # @!macro struct_get + # + # @!visibility private + def ns_get(member) + if member.is_a? Integer + if member >= @values.length + raise IndexError.new("offset #{member} too large for struct(size:#{@values.length})") + end + @values[member] + else + send(member) + end + rescue NoMethodError + raise NameError.new("no member '#{member}' in struct") + end + + # @!macro struct_equality + # + # @!visibility private + def ns_equality(other) + self.class == other.class && self.values == other.values + end + + # @!macro struct_each + # + # @!visibility private + def ns_each + values.each{|value| yield value } + end + + # @!macro struct_each_pair + # + # @!visibility private + def ns_each_pair + @values.length.times do |index| + yield self.class::MEMBERS[index], @values[index] + end + end + + # @!macro struct_select + # + # @!visibility private + def ns_select + values.select{|value| yield value } + end + + # @!macro struct_inspect + # + # @!visibility private + def ns_inspect + struct = pr_underscore(self.class.ancestors[1]) + clazz = ((self.class.to_s =~ /^#" + end + + # @!macro struct_merge + # + # @!visibility private + def ns_merge(other, &block) + self.class.new(*self.to_h.merge(other, &block).values) + end + + # @!visibility private + def ns_initialize_copy + @values = @values.map do |val| + begin + val.clone + rescue TypeError + val + end + end + end + + # @!visibility private + def pr_underscore(clazz) + word = clazz.to_s.dup # dup string to workaround JRuby 9.2.0.0 bug https://github.com/jruby/jruby/issues/5229 + word.gsub!(/::/, '/') + word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + word.tr!("-", "_") + word.downcase! + word + end + + # @!visibility private + def self.define_struct_class(parent, base, name, members, &block) + clazz = Class.new(base || Object) do + include parent + self.const_set(:MEMBERS, members.collect{|member| member.to_s.to_sym}.freeze) + def ns_initialize(*values) + raise ArgumentError.new('struct size differs') if values.length > length + @values = values.fill(nil, values.length..length-1) + end + end + unless name.nil? + begin + parent.send :remove_const, name if parent.const_defined?(name, false) + parent.const_set(name, clazz) + clazz + rescue NameError + raise NameError.new("identifier #{name} needs to be constant") + end + end + members.each_with_index do |member, index| + clazz.send :remove_method, member if clazz.instance_methods.include? member + clazz.send(:define_method, member) do + @values[index] + end + end + clazz.class_exec(&block) unless block.nil? + clazz.singleton_class.send :alias_method, :[], :new + clazz + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/condition.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/condition.rb new file mode 100644 index 0000000000..5daa68be8a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/condition.rb @@ -0,0 +1,62 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + module Synchronization + + # @!visibility private + # TODO (pitr-ch 04-Dec-2016): should be in edge + class Condition < LockableObject + safe_initialization! + + # TODO (pitr 12-Sep-2015): locks two objects, improve + # TODO (pitr 26-Sep-2015): study + # http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/util/concurrent/locks/AbstractQueuedSynchronizer.java#AbstractQueuedSynchronizer.Node + + singleton_class.send :alias_method, :private_new, :new + private_class_method :new + + def initialize(lock) + super() + @Lock = lock + end + + def wait(timeout = nil) + @Lock.synchronize { ns_wait(timeout) } + end + + def ns_wait(timeout = nil) + synchronize { super(timeout) } + end + + def wait_until(timeout = nil, &condition) + @Lock.synchronize { ns_wait_until(timeout, &condition) } + end + + def ns_wait_until(timeout = nil, &condition) + synchronize { super(timeout, &condition) } + end + + def signal + @Lock.synchronize { ns_signal } + end + + def ns_signal + synchronize { super } + end + + def broadcast + @Lock.synchronize { ns_broadcast } + end + + def ns_broadcast + synchronize { super } + end + end + + class LockableObject < LockableObjectImplementation + def new_condition + Condition.private_new(self) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/full_memory_barrier.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/full_memory_barrier.rb new file mode 100644 index 0000000000..139e08d854 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/full_memory_barrier.rb @@ -0,0 +1,29 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +module Concurrent + module Synchronization + case + when Concurrent.on_cruby? + def self.full_memory_barrier + # relying on undocumented behavior of CRuby, GVL acquire has lock which ensures visibility of ivars + # https://github.com/ruby/ruby/blob/ruby_2_2/thread_pthread.c#L204-L211 + end + + when Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + def self.full_memory_barrier + JRubyAttrVolatile.full_memory_barrier + end + + when Concurrent.on_truffleruby? + def self.full_memory_barrier + TruffleRuby.full_memory_barrier + end + + else + warn 'Possibly unsupported Ruby implementation' + def self.full_memory_barrier + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/jruby_lockable_object.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/jruby_lockable_object.rb new file mode 100644 index 0000000000..76930461bd --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/jruby_lockable_object.rb @@ -0,0 +1,15 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +module Concurrent + module Synchronization + + if Concurrent.on_jruby? + + # @!visibility private + # @!macro internal_implementation_note + class JRubyLockableObject < AbstractLockableObject + + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/lock.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/lock.rb new file mode 100644 index 0000000000..f90e0b5f76 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/lock.rb @@ -0,0 +1,38 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + module Synchronization + + # @!visibility private + # TODO (pitr-ch 04-Dec-2016): should be in edge + class Lock < LockableObject + # TODO use JavaReentrantLock on JRuby + + public :synchronize + + def wait(timeout = nil) + synchronize { ns_wait(timeout) } + end + + public :ns_wait + + def wait_until(timeout = nil, &condition) + synchronize { ns_wait_until(timeout, &condition) } + end + + public :ns_wait_until + + def signal + synchronize { ns_signal } + end + + public :ns_signal + + def broadcast + synchronize { ns_broadcast } + end + + public :ns_broadcast + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/lockable_object.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/lockable_object.rb new file mode 100644 index 0000000000..08d2ff66cd --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/lockable_object.rb @@ -0,0 +1,75 @@ +require 'concurrent/utility/engine' +require 'concurrent/synchronization/abstract_lockable_object' +require 'concurrent/synchronization/mutex_lockable_object' +require 'concurrent/synchronization/jruby_lockable_object' + +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + LockableObjectImplementation = case + when Concurrent.on_cruby? + MutexLockableObject + when Concurrent.on_jruby? + JRubyLockableObject + when Concurrent.on_truffleruby? + MutexLockableObject + else + warn 'Possibly unsupported Ruby implementation' + MonitorLockableObject + end + private_constant :LockableObjectImplementation + + # Safe synchronization under any Ruby implementation. + # It provides methods like {#synchronize}, {#wait}, {#signal} and {#broadcast}. + # Provides a single layer which can improve its implementation over time without changes needed to + # the classes using it. Use {Synchronization::Object} not this abstract class. + # + # @note this object does not support usage together with + # [`Thread#wakeup`](http://ruby-doc.org/core/Thread.html#method-i-wakeup) + # and [`Thread#raise`](http://ruby-doc.org/core/Thread.html#method-i-raise). + # `Thread#sleep` and `Thread#wakeup` will work as expected but mixing `Synchronization::Object#wait` and + # `Thread#wakeup` will not work on all platforms. + # + # @see Event implementation as an example of this class use + # + # @example simple + # class AnClass < Synchronization::Object + # def initialize + # super + # synchronize { @value = 'asd' } + # end + # + # def value + # synchronize { @value } + # end + # end + # + # @!visibility private + class LockableObject < LockableObjectImplementation + + # TODO (pitr 12-Sep-2015): make private for c-r, prohibit subclassing + # TODO (pitr 12-Sep-2015): we inherit too much ourselves :/ + + # @!method initialize(*args, &block) + # @!macro synchronization_object_method_initialize + + # @!method synchronize + # @!macro synchronization_object_method_synchronize + + # @!method wait_until(timeout = nil, &condition) + # @!macro synchronization_object_method_ns_wait_until + + # @!method wait(timeout = nil) + # @!macro synchronization_object_method_ns_wait + + # @!method signal + # @!macro synchronization_object_method_ns_signal + + # @!method broadcast + # @!macro synchronization_object_method_ns_broadcast + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/mutex_lockable_object.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/mutex_lockable_object.rb new file mode 100644 index 0000000000..acc9745a2e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/mutex_lockable_object.rb @@ -0,0 +1,89 @@ +require 'concurrent/synchronization/abstract_lockable_object' + +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + module ConditionSignalling + protected + + def ns_signal + @__Condition__.signal + self + end + + def ns_broadcast + @__Condition__.broadcast + self + end + end + + + # @!visibility private + # @!macro internal_implementation_note + class MutexLockableObject < AbstractLockableObject + include ConditionSignalling + + safe_initialization! + + def initialize + super() + @__Lock__ = ::Mutex.new + @__Condition__ = ::ConditionVariable.new + end + + def initialize_copy(other) + super + @__Lock__ = ::Mutex.new + @__Condition__ = ::ConditionVariable.new + end + + protected + + def synchronize + if @__Lock__.owned? + yield + else + @__Lock__.synchronize { yield } + end + end + + def ns_wait(timeout = nil) + @__Condition__.wait @__Lock__, timeout + self + end + end + + # @!visibility private + # @!macro internal_implementation_note + class MonitorLockableObject < AbstractLockableObject + include ConditionSignalling + + safe_initialization! + + def initialize + super() + @__Lock__ = ::Monitor.new + @__Condition__ = @__Lock__.new_cond + end + + def initialize_copy(other) + super + @__Lock__ = ::Monitor.new + @__Condition__ = @__Lock__.new_cond + end + + protected + + def synchronize # TODO may be a problem with lock.synchronize { lock.wait } + @__Lock__.synchronize { yield } + end + + def ns_wait(timeout = nil) + @__Condition__.wait timeout + self + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/object.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/object.rb new file mode 100644 index 0000000000..e839c9f188 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/object.rb @@ -0,0 +1,151 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/synchronization/safe_initialization' +require 'concurrent/synchronization/volatile' +require 'concurrent/atomic/atomic_reference' + +module Concurrent + module Synchronization + + # Abstract object providing final, volatile, ans CAS extensions to build other concurrent abstractions. + # - final instance variables see {Object.safe_initialization!} + # - volatile instance variables see {Object.attr_volatile} + # - volatile instance variables see {Object.attr_atomic} + # @!visibility private + class Object < AbstractObject + include Volatile + + # TODO make it a module if possible + + # @!method self.attr_volatile(*names) + # Creates methods for reading and writing (as `attr_accessor` does) to a instance variable with + # volatile (Java) semantic. The instance variable should be accessed only through generated methods. + # + # @param [::Array] names of the instance variables to be volatile + # @return [::Array] names of defined method names + + # Has to be called by children. + def initialize + super + __initialize_atomic_fields__ + end + + def self.safe_initialization! + extend SafeInitialization unless safe_initialization? + end + + def self.safe_initialization? + self.singleton_class < SafeInitialization + end + + # For testing purposes, quite slow. Injects assert code to new method which will raise if class instance contains + # any instance variables with CamelCase names and isn't {.safe_initialization?}. + # @raise when offend found + # @return [true] + def self.ensure_safe_initialization_when_final_fields_are_present + Object.class_eval do + def self.new(*args, &block) + object = super(*args, &block) + ensure + has_final_field = object.instance_variables.any? { |v| v.to_s =~ /^@[A-Z]/ } + if has_final_field && !safe_initialization? + raise "there was an instance of #{object.class} with final field but not marked with safe_initialization!" + end + end + end + true + end + + # Creates methods for reading and writing to a instance variable with + # volatile (Java) semantic as {.attr_volatile} does. + # The instance variable should be accessed oly through generated methods. + # This method generates following methods: `value`, `value=(new_value) #=> new_value`, + # `swap_value(new_value) #=> old_value`, + # `compare_and_set_value(expected, value) #=> true || false`, `update_value(&block)`. + # @param [::Array] names of the instance variables to be volatile with CAS. + # @return [::Array] names of defined method names. + # @!macro attr_atomic + # @!method $1 + # @return [Object] The $1. + # @!method $1=(new_$1) + # Set the $1. + # @return [Object] new_$1. + # @!method swap_$1(new_$1) + # Set the $1 to new_$1 and return the old $1. + # @return [Object] old $1 + # @!method compare_and_set_$1(expected_$1, new_$1) + # Sets the $1 to new_$1 if the current $1 is expected_$1 + # @return [true, false] + # @!method update_$1(&block) + # Updates the $1 using the block. + # @yield [Object] Calculate a new $1 using given (old) $1 + # @yieldparam [Object] old $1 + # @return [Object] new $1 + def self.attr_atomic(*names) + @__atomic_fields__ ||= [] + @__atomic_fields__ += names + safe_initialization! + define_initialize_atomic_fields + + names.each do |name| + ivar = :"@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }}" + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + #{ivar}.get + end + + def #{name}=(value) + #{ivar}.set value + end + + def swap_#{name}(value) + #{ivar}.swap value + end + + def compare_and_set_#{name}(expected, value) + #{ivar}.compare_and_set expected, value + end + + def update_#{name}(&block) + #{ivar}.update(&block) + end + RUBY + end + names.flat_map { |n| [n, :"#{n}=", :"swap_#{n}", :"compare_and_set_#{n}", :"update_#{n}"] } + end + + # @param [true, false] inherited should inherited volatile with CAS fields be returned? + # @return [::Array] Returns defined volatile with CAS fields on this class. + def self.atomic_attributes(inherited = true) + @__atomic_fields__ ||= [] + ((superclass.atomic_attributes if superclass.respond_to?(:atomic_attributes) && inherited) || []) + @__atomic_fields__ + end + + # @return [true, false] is the attribute with name atomic? + def self.atomic_attribute?(name) + atomic_attributes.include? name + end + + private + + def self.define_initialize_atomic_fields + assignments = @__atomic_fields__.map do |name| + "@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }} = Concurrent::AtomicReference.new(nil)" + end.join("\n") + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def __initialize_atomic_fields__ + super + #{assignments} + end + RUBY + end + + private_class_method :define_initialize_atomic_fields + + def __initialize_atomic_fields__ + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/safe_initialization.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/safe_initialization.rb new file mode 100644 index 0000000000..f785e35229 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/safe_initialization.rb @@ -0,0 +1,36 @@ +require 'concurrent/synchronization/full_memory_barrier' + +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + # + # By extending this module, a class and all its children are marked to be constructed safely. Meaning that + # all writes (ivar initializations) are made visible to all readers of newly constructed object. It ensures + # same behaviour as Java's final fields. + # + # Due to using Kernel#extend, the module is not included again if already present in the ancestors, + # which avoids extra overhead. + # + # @example + # class AClass < Concurrent::Synchronization::Object + # extend Concurrent::Synchronization::SafeInitialization + # + # def initialize + # @AFinalValue = 'value' # published safely, #foo will never return nil + # end + # + # def foo + # @AFinalValue + # end + # end + module SafeInitialization + def new(*args, &block) + super(*args, &block) + ensure + Concurrent::Synchronization.full_memory_barrier + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/volatile.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/volatile.rb new file mode 100644 index 0000000000..46e8ba6a48 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/synchronization/volatile.rb @@ -0,0 +1,101 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first +require 'concurrent/utility/engine' +require 'concurrent/synchronization/full_memory_barrier' + +module Concurrent + module Synchronization + + # Volatile adds the attr_volatile class method when included. + # + # @example + # class Foo + # include Concurrent::Synchronization::Volatile + # + # attr_volatile :bar + # + # def initialize + # self.bar = 1 + # end + # end + # + # foo = Foo.new + # foo.bar + # => 1 + # foo.bar = 2 + # => 2 + # + # @!visibility private + module Volatile + def self.included(base) + base.extend(ClassMethods) + end + + def full_memory_barrier + Synchronization.full_memory_barrier + end + + module ClassMethods + if Concurrent.on_cruby? + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + #{ivar} + end + + def #{name}=(value) + #{ivar} = value + end + RUBY + end + names.map { |n| [n, :"#{n}="] }.flatten + end + + elsif Concurrent.on_jruby? + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + ::Concurrent::Synchronization::JRubyAttrVolatile.instance_variable_get_volatile(self, :#{ivar}) + end + + def #{name}=(value) + ::Concurrent::Synchronization::JRubyAttrVolatile.instance_variable_set_volatile(self, :#{ivar}, value) + end + RUBY + + end + names.map { |n| [n, :"#{n}="] }.flatten + end + + else + warn 'Possibly unsupported Ruby implementation' unless Concurrent.on_truffleruby? + + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + ::Concurrent::Synchronization.full_memory_barrier + #{ivar} + end + + def #{name}=(value) + #{ivar} = value + ::Concurrent::Synchronization.full_memory_barrier + end + RUBY + end + + names.map { |n| [n, :"#{n}="] }.flatten + end + end + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/readme.txt b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/readme.txt new file mode 100644 index 0000000000..5c49d84493 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/readme.txt @@ -0,0 +1 @@ +// TODO this directory should be removed over time - remnant of thread_safe merge \ No newline at end of file diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/synchronized_delegator.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/synchronized_delegator.rb new file mode 100644 index 0000000000..019d84382d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/synchronized_delegator.rb @@ -0,0 +1,47 @@ +require 'delegate' +require 'monitor' + +module Concurrent + # This class provides a trivial way to synchronize all calls to a given object + # by wrapping it with a `Delegator` that performs `Monitor#enter/exit` calls + # around the delegated `#send`. Example: + # + # array = [] # not thread-safe on many impls + # array = SynchronizedDelegator.new([]) # thread-safe + # + # A simple `Monitor` provides a very coarse-grained way to synchronize a given + # object, in that it will cause synchronization for methods that have no need + # for it, but this is a trivial way to get thread-safety where none may exist + # currently on some implementations. + # + # This class is currently being considered for inclusion into stdlib, via + # https://bugs.ruby-lang.org/issues/8556 + # + # @!visibility private + class SynchronizedDelegator < SimpleDelegator + def setup + @old_abort = Thread.abort_on_exception + Thread.abort_on_exception = true + end + + def teardown + Thread.abort_on_exception = @old_abort + end + + def initialize(obj) + __setobj__(obj) + @monitor = Monitor.new + end + + def method_missing(method, *args, &block) + monitor = @monitor + begin + monitor.enter + super + ensure + monitor.exit + end + end + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util.rb new file mode 100644 index 0000000000..c67084a26f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util.rb @@ -0,0 +1,16 @@ +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # TODO (pitr-ch 15-Oct-2016): migrate to Utility::NativeInteger + FIXNUM_BIT_SIZE = (0.size * 8) - 2 + MAX_INT = (2 ** FIXNUM_BIT_SIZE) - 1 + # TODO (pitr-ch 15-Oct-2016): migrate to Utility::ProcessorCounter + CPU_COUNT = 16 # is there a way to determine this? + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/adder.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/adder.rb new file mode 100644 index 0000000000..7a6e8d5c0e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/adder.rb @@ -0,0 +1,74 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/striped64' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8 + # available in public domain. + # + # Original source code available here: + # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8 + # + # One or more variables that together maintain an initially zero + # sum. When updates (method +add+) are contended across threads, + # the set of variables may grow dynamically to reduce contention. + # Method +sum+ returns the current total combined across the + # variables maintaining the sum. + # + # This class is usually preferable to single +Atomic+ reference when + # multiple threads update a common sum that is used for purposes such + # as collecting statistics, not for fine-grained synchronization + # control. Under low update contention, the two classes have similar + # characteristics. But under high contention, expected throughput of + # this class is significantly higher, at the expense of higher space + # consumption. + # + # @!visibility private + class Adder < Striped64 + # Adds the given value. + def add(x) + if (current_cells = cells) || !cas_base_computed {|current_base| current_base + x} + was_uncontended = true + hash = hash_code + unless current_cells && (cell = current_cells.volatile_get_by_hash(hash)) && (was_uncontended = cell.cas_computed {|current_value| current_value + x}) + retry_update(x, hash, was_uncontended) {|current_value| current_value + x} + end + end + end + + def increment + add(1) + end + + def decrement + add(-1) + end + + # Returns the current sum. The returned value is _NOT_ an + # atomic snapshot: Invocation in the absence of concurrent + # updates returns an accurate result, but concurrent updates that + # occur while the sum is being calculated might not be + # incorporated. + def sum + x = base + if current_cells = cells + current_cells.each do |cell| + x += cell.value if cell + end + end + x + end + + def reset + internal_reset(0) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/cheap_lockable.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/cheap_lockable.rb new file mode 100644 index 0000000000..a07678df2e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/cheap_lockable.rb @@ -0,0 +1,81 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/volatile' +require 'concurrent/utility/engine' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # Provides a cheapest possible (mainly in terms of memory usage) +Mutex+ + # with the +ConditionVariable+ bundled in. + # + # Usage: + # class A + # include CheapLockable + # + # def do_exlusively + # cheap_synchronize { yield } + # end + # + # def wait_for_something + # cheap_synchronize do + # cheap_wait until resource_available? + # do_something + # cheap_broadcast # wake up others + # end + # end + # end + # + # @!visibility private + module CheapLockable + private + if Concurrent.on_jruby? + # Use Java's native synchronized (this) { wait(); notifyAll(); } to avoid the overhead of the extra Mutex objects + require 'jruby' + + def cheap_synchronize + JRuby.reference0(self).synchronized { yield } + end + + def cheap_wait + JRuby.reference0(self).wait + end + + def cheap_broadcast + JRuby.reference0(self).notify_all + end + else + require 'thread' + + extend Volatile + attr_volatile :mutex + + # Non-reentrant Mutex#syncrhonize + def cheap_synchronize + true until (my_mutex = mutex) || cas_mutex(nil, my_mutex = Mutex.new) + my_mutex.synchronize { yield } + end + + # Releases this object's +cheap_synchronize+ lock and goes to sleep waiting for other threads to +cheap_broadcast+, reacquires the lock on wakeup. + # Must only be called in +cheap_broadcast+'s block. + def cheap_wait + conditional_variable = @conditional_variable ||= ConditionVariable.new + conditional_variable.wait(mutex) + end + + # Wakes up all threads waiting for this object's +cheap_synchronize+ lock. + # Must only be called in +cheap_broadcast+'s block. + def cheap_broadcast + if conditional_variable = @conditional_variable + conditional_variable.broadcast + end + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/data_structures.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/data_structures.rb new file mode 100644 index 0000000000..01eb98f4aa --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/data_structures.rb @@ -0,0 +1,52 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/utility/engine' + +# Shim for TruffleRuby.synchronized +if Concurrent.on_truffleruby? && !TruffleRuby.respond_to?(:synchronized) + module TruffleRuby + def self.synchronized(object, &block) + Truffle::System.synchronized(object, &block) + end + end +end + +module Concurrent + module ThreadSafe + module Util + def self.make_synchronized_on_cruby(klass) + klass.class_eval do + def initialize(*args, &block) + @_monitor = Monitor.new + super + end + + def initialize_copy(other) + # make sure a copy is not sharing a monitor with the original object! + @_monitor = Monitor.new + super + end + end + + klass.superclass.instance_methods(false).each do |method| + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args) + monitor = @_monitor + monitor or raise("BUG: Internal monitor was not properly initialized. Please report this to the concurrent-ruby developers.") + monitor.synchronize { super } + end + RUBY + end + end + + def self.make_synchronized_on_truffleruby(klass) + klass.superclass.instance_methods(false).each do |method| + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) + TruffleRuby.synchronized(self) { super(*args, &block) } + end + RUBY + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/power_of_two_tuple.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/power_of_two_tuple.rb new file mode 100644 index 0000000000..b54be39c4c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/power_of_two_tuple.rb @@ -0,0 +1,38 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/tuple' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # @!visibility private + class PowerOfTwoTuple < Concurrent::Tuple + + def initialize(size) + raise ArgumentError, "size must be a power of 2 (#{size.inspect} provided)" unless size > 0 && size & (size - 1) == 0 + super(size) + end + + def hash_to_index(hash) + (size - 1) & hash + end + + def volatile_get_by_hash(hash) + volatile_get(hash_to_index(hash)) + end + + def volatile_set_by_hash(hash, value) + volatile_set(hash_to_index(hash), value) + end + + def next_in_size_table + self.class.new(size << 1) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/striped64.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/striped64.rb new file mode 100644 index 0000000000..4169c3d366 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/striped64.rb @@ -0,0 +1,246 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/power_of_two_tuple' +require 'concurrent/thread_safe/util/volatile' +require 'concurrent/thread_safe/util/xor_shift_random' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6 + # available in public domain. + # + # Original source code available here: + # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.6 + # + # Class holding common representation and mechanics for classes supporting + # dynamic striping on 64bit values. + # + # This class maintains a lazily-initialized table of atomically updated + # variables, plus an extra +base+ field. The table size is a power of two. + # Indexing uses masked per-thread hash codes. Nearly all methods on this + # class are private, accessed directly by subclasses. + # + # Table entries are of class +Cell+; a variant of AtomicLong padded to + # reduce cache contention on most processors. Padding is overkill for most + # Atomics because they are usually irregularly scattered in memory and thus + # don't interfere much with each other. But Atomic objects residing in + # arrays will tend to be placed adjacent to each other, and so will most + # often share cache lines (with a huge negative performance impact) without + # this precaution. + # + # In part because +Cell+s are relatively large, we avoid creating them until + # they are needed. When there is no contention, all updates are made to the + # +base+ field. Upon first contention (a failed CAS on +base+ update), the + # table is initialized to size 2. The table size is doubled upon further + # contention until reaching the nearest power of two greater than or equal + # to the number of CPUS. Table slots remain empty (+nil+) until they are + # needed. + # + # A single spinlock (+busy+) is used for initializing and resizing the + # table, as well as populating slots with new +Cell+s. There is no need for + # a blocking lock: When the lock is not available, threads try other slots + # (or the base). During these retries, there is increased contention and + # reduced locality, which is still better than alternatives. + # + # Per-thread hash codes are initialized to random values. Contention and/or + # table collisions are indicated by failed CASes when performing an update + # operation (see method +retry_update+). Upon a collision, if the table size + # is less than the capacity, it is doubled in size unless some other thread + # holds the lock. If a hashed slot is empty, and lock is available, a new + # +Cell+ is created. Otherwise, if the slot exists, a CAS is tried. Retries + # proceed by "double hashing", using a secondary hash (XorShift) to try to + # find a free slot. + # + # The table size is capped because, when there are more threads than CPUs, + # supposing that each thread were bound to a CPU, there would exist a + # perfect hash function mapping threads to slots that eliminates collisions. + # When we reach capacity, we search for this mapping by randomly varying the + # hash codes of colliding threads. Because search is random, and collisions + # only become known via CAS failures, convergence can be slow, and because + # threads are typically not bound to CPUS forever, may not occur at all. + # However, despite these limitations, observed contention rates are + # typically low in these cases. + # + # It is possible for a +Cell+ to become unused when threads that once hashed + # to it terminate, as well as in the case where doubling the table causes no + # thread to hash to it under expanded mask. We do not try to detect or + # remove such cells, under the assumption that for long-running instances, + # observed contention levels will recur, so the cells will eventually be + # needed again; and for short-lived ones, it does not matter. + # + # @!visibility private + class Striped64 + + # Padded variant of AtomicLong supporting only raw accesses plus CAS. + # The +value+ field is placed between pads, hoping that the JVM doesn't + # reorder them. + # + # Optimisation note: It would be possible to use a release-only + # form of CAS here, if it were provided. + # + # @!visibility private + class Cell < Concurrent::AtomicReference + + alias_method :cas, :compare_and_set + + def cas_computed + cas(current_value = value, yield(current_value)) + end + + # @!visibility private + def self.padding + # TODO: this only adds padding after the :value slot, need to find a way to add padding before the slot + # TODO (pitr-ch 28-Jul-2018): the padding instance vars may not be created + # hide from yardoc in a method + attr_reader :padding_0, :padding_1, :padding_2, :padding_3, :padding_4, :padding_5, :padding_6, :padding_7, :padding_8, :padding_9, :padding_10, :padding_11 + end + padding + end + + extend Volatile + attr_volatile :cells, # Table of cells. When non-null, size is a power of 2. + :base, # Base value, used mainly when there is no contention, but also as a fallback during table initialization races. Updated via CAS. + :busy # Spinlock (locked via CAS) used when resizing and/or creating Cells. + + alias_method :busy?, :busy + + def initialize + super() + self.busy = false + self.base = 0 + end + + # Handles cases of updates involving initialization, resizing, + # creating new Cells, and/or contention. See above for + # explanation. This method suffers the usual non-modularity + # problems of optimistic retry code, relying on rechecked sets of + # reads. + # + # Arguments: + # [+x+] + # the value + # [+hash_code+] + # hash code used + # [+x+] + # false if CAS failed before call + def retry_update(x, hash_code, was_uncontended) # :yields: current_value + hash = hash_code + collided = false # True if last slot nonempty + while true + if current_cells = cells + if !(cell = current_cells.volatile_get_by_hash(hash)) + if busy? + collided = false + else # Try to attach new Cell + if try_to_install_new_cell(Cell.new(x), hash) # Optimistically create and try to insert new cell + break + else + redo # Slot is now non-empty + end + end + elsif !was_uncontended # CAS already known to fail + was_uncontended = true # Continue after rehash + elsif cell.cas_computed {|current_value| yield current_value} + break + elsif current_cells.size >= CPU_COUNT || cells != current_cells # At max size or stale + collided = false + elsif collided && expand_table_unless_stale(current_cells) + collided = false + redo # Retry with expanded table + else + collided = true + end + hash = XorShiftRandom.xorshift(hash) + + elsif try_initialize_cells(x, hash) || cas_base_computed {|current_base| yield current_base} + break + end + end + self.hash_code = hash + end + + private + # Static per-thread hash code key. Shared across all instances to + # reduce Thread locals pollution and because adjustments due to + # collisions in one table are likely to be appropriate for + # others. + THREAD_LOCAL_KEY = "#{name}.hash_code".to_sym + + # A thread-local hash code accessor. The code is initially + # random, but may be set to a different value upon collisions. + def hash_code + Thread.current[THREAD_LOCAL_KEY] ||= XorShiftRandom.get + end + + def hash_code=(hash) + Thread.current[THREAD_LOCAL_KEY] = hash + end + + # Sets base and all +cells+ to the given value. + def internal_reset(initial_value) + current_cells = cells + self.base = initial_value + if current_cells + current_cells.each do |cell| + cell.value = initial_value if cell + end + end + end + + def cas_base_computed + cas_base(current_base = base, yield(current_base)) + end + + def free? + !busy? + end + + def try_initialize_cells(x, hash) + if free? && !cells + try_in_busy do + unless cells # Recheck under lock + new_cells = PowerOfTwoTuple.new(2) + new_cells.volatile_set_by_hash(hash, Cell.new(x)) + self.cells = new_cells + end + end + end + end + + def expand_table_unless_stale(current_cells) + try_in_busy do + if current_cells == cells # Recheck under lock + new_cells = current_cells.next_in_size_table + current_cells.each_with_index {|x, i| new_cells.volatile_set(i, x)} + self.cells = new_cells + end + end + end + + def try_to_install_new_cell(new_cell, hash) + try_in_busy do + # Recheck under lock + if (current_cells = cells) && !current_cells.volatile_get(i = current_cells.hash_to_index(hash)) + current_cells.volatile_set(i, new_cell) + end + end + end + + def try_in_busy + if cas_busy(false, true) + begin + yield + ensure + self.busy = false + end + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/volatile.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/volatile.rb new file mode 100644 index 0000000000..cdac2a396a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/volatile.rb @@ -0,0 +1,75 @@ +require 'concurrent/thread_safe/util' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # @!visibility private + module Volatile + + # Provides +volatile+ (in the JVM's sense) attribute accessors implemented + # atop of +Concurrent::AtomicReference+. + # + # Usage: + # class Foo + # extend Concurrent::ThreadSafe::Util::Volatile + # attr_volatile :foo, :bar + # + # def initialize(bar) + # super() # must super() into parent initializers before using the volatile attribute accessors + # self.bar = bar + # end + # + # def hello + # my_foo = foo # volatile read + # self.foo = 1 # volatile write + # cas_foo(1, 2) # => true | a strong CAS + # end + # end + def attr_volatile(*attr_names) + return if attr_names.empty? + include(Module.new do + atomic_ref_setup = attr_names.map {|attr_name| "@__#{attr_name} = Concurrent::AtomicReference.new"} + initialize_copy_setup = attr_names.zip(atomic_ref_setup).map do |attr_name, ref_setup| + "#{ref_setup}(other.instance_variable_get(:@__#{attr_name}).get)" + end + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def initialize(*) + super + #{atomic_ref_setup.join('; ')} + end + + def initialize_copy(other) + super + #{initialize_copy_setup.join('; ')} + end + RUBY_EVAL + + attr_names.each do |attr_name| + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{attr_name} + @__#{attr_name}.get + end + + def #{attr_name}=(value) + @__#{attr_name}.set(value) + end + + def compare_and_set_#{attr_name}(old_value, new_value) + @__#{attr_name}.compare_and_set(old_value, new_value) + end + RUBY_EVAL + + alias_method :"cas_#{attr_name}", :"compare_and_set_#{attr_name}" + alias_method :"lazy_set_#{attr_name}", :"#{attr_name}=" + end + end) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/xor_shift_random.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/xor_shift_random.rb new file mode 100644 index 0000000000..bdde2dd8b3 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/thread_safe/util/xor_shift_random.rb @@ -0,0 +1,50 @@ +require 'concurrent/thread_safe/util' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # A xorshift random number (positive +Fixnum+s) generator, provides + # reasonably cheap way to generate thread local random numbers without + # contending for the global +Kernel.rand+. + # + # Usage: + # x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed + # while true + # if (x = XorShiftRandom.xorshift).odd? # thread-localy generate a next random number + # do_something_at_random + # end + # end + module XorShiftRandom + extend self + MAX_XOR_SHIFTABLE_INT = MAX_INT - 1 + + # Generates an initial non-zero positive +Fixnum+ via +Kernel.rand+. + def get + Kernel.rand(MAX_XOR_SHIFTABLE_INT) + 1 # 0 can't be xorshifted + end + + # xorshift based on: http://www.jstatsoft.org/v08/i14/paper + if 0.size == 4 + # using the "yˆ=y>>a; yˆ=y<>c;" transform with the (a,b,c) tuple with values (3,1,14) to minimise Bignum overflows + def xorshift(x) + x ^= x >> 3 + x ^= (x << 1) & MAX_INT # cut-off Bignum overflow + x ^= x >> 14 + end + else + # using the "yˆ=y>>a; yˆ=y<>c;" transform with the (a,b,c) tuple with values (1,1,54) to minimise Bignum overflows + def xorshift(x) + x ^= x >> 1 + x ^= (x << 1) & MAX_INT # cut-off Bignum overflow + x ^= x >> 54 + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/timer_task.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/timer_task.rb new file mode 100644 index 0000000000..b69cfc8d8a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/timer_task.rb @@ -0,0 +1,311 @@ +require 'concurrent/collection/copy_on_notify_observer_set' +require 'concurrent/concern/dereferenceable' +require 'concurrent/concern/observable' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/executor/executor_service' +require 'concurrent/executor/ruby_executor_service' +require 'concurrent/executor/safe_task_executor' +require 'concurrent/scheduled_task' + +module Concurrent + + # A very common concurrency pattern is to run a thread that performs a task at + # regular intervals. The thread that performs the task sleeps for the given + # interval then wakes up and performs the task. Lather, rinse, repeat... This + # pattern causes two problems. First, it is difficult to test the business + # logic of the task because the task itself is tightly coupled with the + # concurrency logic. Second, an exception raised while performing the task can + # cause the entire thread to abend. In a long-running application where the + # task thread is intended to run for days/weeks/years a crashed task thread + # can pose a significant problem. `TimerTask` alleviates both problems. + # + # When a `TimerTask` is launched it starts a thread for monitoring the + # execution interval. The `TimerTask` thread does not perform the task, + # however. Instead, the TimerTask launches the task on a separate thread. + # Should the task experience an unrecoverable crash only the task thread will + # crash. This makes the `TimerTask` very fault tolerant. Additionally, the + # `TimerTask` thread can respond to the success or failure of the task, + # performing logging or ancillary operations. + # + # One other advantage of `TimerTask` is that it forces the business logic to + # be completely decoupled from the concurrency logic. The business logic can + # be tested separately then passed to the `TimerTask` for scheduling and + # running. + # + # In some cases it may be necessary for a `TimerTask` to affect its own + # execution cycle. To facilitate this, a reference to the TimerTask instance + # is passed as an argument to the provided block every time the task is + # executed. + # + # The `TimerTask` class includes the `Dereferenceable` mixin module so the + # result of the last execution is always available via the `#value` method. + # Dereferencing options can be passed to the `TimerTask` during construction or + # at any later time using the `#set_deref_options` method. + # + # `TimerTask` supports notification through the Ruby standard library + # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html + # Observable} module. On execution the `TimerTask` will notify the observers + # with three arguments: time of execution, the result of the block (or nil on + # failure), and any raised exceptions (or nil on success). + # + # @!macro copy_options + # + # @example Basic usage + # task = Concurrent::TimerTask.new{ puts 'Boom!' } + # task.execute + # + # task.execution_interval #=> 60 (default) + # + # # wait 60 seconds... + # #=> 'Boom!' + # + # task.shutdown #=> true + # + # @example Configuring `:execution_interval` + # task = Concurrent::TimerTask.new(execution_interval: 5) do + # puts 'Boom!' + # end + # + # task.execution_interval #=> 5 + # + # @example Immediate execution with `:run_now` + # task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' } + # task.execute + # + # #=> 'Boom!' + # + # @example Last `#value` and `Dereferenceable` mixin + # task = Concurrent::TimerTask.new( + # dup_on_deref: true, + # execution_interval: 5 + # ){ Time.now } + # + # task.execute + # Time.now #=> 2013-11-07 18:06:50 -0500 + # sleep(10) + # task.value #=> 2013-11-07 18:06:55 -0500 + # + # @example Controlling execution from within the block + # timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task| + # task.execution_interval.times{ print 'Boom! ' } + # print "\n" + # task.execution_interval += 1 + # if task.execution_interval > 5 + # puts 'Stopping...' + # task.shutdown + # end + # end + # + # timer_task.execute # blocking call - this task will stop itself + # #=> Boom! + # #=> Boom! Boom! + # #=> Boom! Boom! Boom! + # #=> Boom! Boom! Boom! Boom! + # #=> Boom! Boom! Boom! Boom! Boom! + # #=> Stopping... + # + # @example Observation + # class TaskObserver + # def update(time, result, ex) + # if result + # print "(#{time}) Execution successfully returned #{result}\n" + # else + # print "(#{time}) Execution failed with error #{ex}\n" + # end + # end + # end + # + # task = Concurrent::TimerTask.new(execution_interval: 1){ 42 } + # task.add_observer(TaskObserver.new) + # task.execute + # sleep 4 + # + # #=> (2013-10-13 19:08:58 -0400) Execution successfully returned 42 + # #=> (2013-10-13 19:08:59 -0400) Execution successfully returned 42 + # #=> (2013-10-13 19:09:00 -0400) Execution successfully returned 42 + # task.shutdown + # + # task = Concurrent::TimerTask.new(execution_interval: 1){ sleep } + # task.add_observer(TaskObserver.new) + # task.execute + # + # #=> (2013-10-13 19:07:25 -0400) Execution timed out + # #=> (2013-10-13 19:07:27 -0400) Execution timed out + # #=> (2013-10-13 19:07:29 -0400) Execution timed out + # task.shutdown + # + # task = Concurrent::TimerTask.new(execution_interval: 1){ raise StandardError } + # task.add_observer(TaskObserver.new) + # task.execute + # + # #=> (2013-10-13 19:09:37 -0400) Execution failed with error StandardError + # #=> (2013-10-13 19:09:38 -0400) Execution failed with error StandardError + # #=> (2013-10-13 19:09:39 -0400) Execution failed with error StandardError + # task.shutdown + # + # @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html + # @see http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html + class TimerTask < RubyExecutorService + include Concern::Dereferenceable + include Concern::Observable + + # Default `:execution_interval` in seconds. + EXECUTION_INTERVAL = 60 + + # Default `:timeout_interval` in seconds. + TIMEOUT_INTERVAL = 30 + + # Create a new TimerTask with the given task and configuration. + # + # @!macro timer_task_initialize + # @param [Hash] opts the options defining task execution. + # @option opts [Integer] :execution_interval number of seconds between + # task executions (default: EXECUTION_INTERVAL) + # @option opts [Boolean] :run_now Whether to run the task immediately + # upon instantiation or to wait until the first # execution_interval + # has passed (default: false) + # + # @!macro deref_options + # + # @raise ArgumentError when no block is given. + # + # @yield to the block after :execution_interval seconds have passed since + # the last yield + # @yieldparam task a reference to the `TimerTask` instance so that the + # block can control its own lifecycle. Necessary since `self` will + # refer to the execution context of the block rather than the running + # `TimerTask`. + # + # @return [TimerTask] the new `TimerTask` + def initialize(opts = {}, &task) + raise ArgumentError.new('no block given') unless block_given? + super + set_deref_options opts + end + + # Is the executor running? + # + # @return [Boolean] `true` when running, `false` when shutting down or shutdown + def running? + @running.true? + end + + # Execute a previously created `TimerTask`. + # + # @return [TimerTask] a reference to `self` + # + # @example Instance and execute in separate steps + # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" } + # task.running? #=> false + # task.execute + # task.running? #=> true + # + # @example Instance and execute in one line + # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }.execute + # task.running? #=> true + def execute + synchronize do + if @running.false? + @running.make_true + schedule_next_task(@run_now ? 0 : @execution_interval) + end + end + self + end + + # Create and execute a new `TimerTask`. + # + # @!macro timer_task_initialize + # + # @example + # task = Concurrent::TimerTask.execute(execution_interval: 10){ print "Hello World\n" } + # task.running? #=> true + def self.execute(opts = {}, &task) + TimerTask.new(opts, &task).execute + end + + # @!attribute [rw] execution_interval + # @return [Fixnum] Number of seconds after the task completes before the + # task is performed again. + def execution_interval + synchronize { @execution_interval } + end + + # @!attribute [rw] execution_interval + # @return [Fixnum] Number of seconds after the task completes before the + # task is performed again. + def execution_interval=(value) + if (value = value.to_f) <= 0.0 + raise ArgumentError.new('must be greater than zero') + else + synchronize { @execution_interval = value } + end + end + + # @!attribute [rw] timeout_interval + # @return [Fixnum] Number of seconds the task can run before it is + # considered to have failed. + def timeout_interval + warn 'TimerTask timeouts are now ignored as these were not able to be implemented correctly' + end + + # @!attribute [rw] timeout_interval + # @return [Fixnum] Number of seconds the task can run before it is + # considered to have failed. + def timeout_interval=(value) + warn 'TimerTask timeouts are now ignored as these were not able to be implemented correctly' + end + + private :post, :<< + + private + + def ns_initialize(opts, &task) + set_deref_options(opts) + + self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL + if opts[:timeout] || opts[:timeout_interval] + warn 'TimeTask timeouts are now ignored as these were not able to be implemented correctly' + end + @run_now = opts[:now] || opts[:run_now] + @executor = Concurrent::SafeTaskExecutor.new(task) + @running = Concurrent::AtomicBoolean.new(false) + @value = nil + + self.observers = Collection::CopyOnNotifyObserverSet.new + end + + # @!visibility private + def ns_shutdown_execution + @running.make_false + super + end + + # @!visibility private + def ns_kill_execution + @running.make_false + super + end + + # @!visibility private + def schedule_next_task(interval = execution_interval) + ScheduledTask.execute(interval, args: [Concurrent::Event.new], &method(:execute_task)) + nil + end + + # @!visibility private + def execute_task(completion) + return nil unless @running.true? + _success, value, reason = @executor.execute(self) + if completion.try? + self.value = value + schedule_next_task + time = Time.now + observers.notify_observers do + [time, self.value, reason] + end + end + nil + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/tuple.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/tuple.rb new file mode 100644 index 0000000000..56212cfd15 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/tuple.rb @@ -0,0 +1,82 @@ +require 'concurrent/atomic/atomic_reference' + +module Concurrent + + # A fixed size array with volatile (synchronized, thread safe) getters/setters. + # Mixes in Ruby's `Enumerable` module for enhanced search, sort, and traversal. + # + # @example + # tuple = Concurrent::Tuple.new(16) + # + # tuple.set(0, :foo) #=> :foo | volatile write + # tuple.get(0) #=> :foo | volatile read + # tuple.compare_and_set(0, :foo, :bar) #=> true | strong CAS + # tuple.cas(0, :foo, :baz) #=> false | strong CAS + # tuple.get(0) #=> :bar | volatile read + # + # @see https://en.wikipedia.org/wiki/Tuple Tuple entry at Wikipedia + # @see http://www.erlang.org/doc/reference_manual/data_types.html#id70396 Erlang Tuple + # @see http://ruby-doc.org/core-2.2.2/Enumerable.html Enumerable + class Tuple + include Enumerable + + # The (fixed) size of the tuple. + attr_reader :size + + # Create a new tuple of the given size. + # + # @param [Integer] size the number of elements in the tuple + def initialize(size) + @size = size + @tuple = tuple = ::Array.new(size) + i = 0 + while i < size + tuple[i] = Concurrent::AtomicReference.new + i += 1 + end + end + + # Get the value of the element at the given index. + # + # @param [Integer] i the index from which to retrieve the value + # @return [Object] the value at the given index or nil if the index is out of bounds + def get(i) + return nil if i >= @size || i < 0 + @tuple[i].get + end + alias_method :volatile_get, :get + + # Set the element at the given index to the given value + # + # @param [Integer] i the index for the element to set + # @param [Object] value the value to set at the given index + # + # @return [Object] the new value of the element at the given index or nil if the index is out of bounds + def set(i, value) + return nil if i >= @size || i < 0 + @tuple[i].set(value) + end + alias_method :volatile_set, :set + + # Set the value at the given index to the new value if and only if the current + # value matches the given old value. + # + # @param [Integer] i the index for the element to set + # @param [Object] old_value the value to compare against the current value + # @param [Object] new_value the value to set at the given index + # + # @return [Boolean] true if the value at the given element was set else false + def compare_and_set(i, old_value, new_value) + return false if i >= @size || i < 0 + @tuple[i].compare_and_set(old_value, new_value) + end + alias_method :cas, :compare_and_set + + # Calls the given block once for each element in self, passing that element as a parameter. + # + # @yieldparam [Object] ref the `Concurrent::AtomicReference` object at the current index + def each + @tuple.each {|ref| yield ref.get} + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/tvar.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/tvar.rb new file mode 100644 index 0000000000..5d02ef090f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/tvar.rb @@ -0,0 +1,222 @@ +require 'set' +require 'concurrent/synchronization/object' + +module Concurrent + + # A `TVar` is a transactional variable - a single-element container that + # is used as part of a transaction - see `Concurrent::atomically`. + # + # @!macro thread_safe_variable_comparison + # + # {include:file:docs-source/tvar.md} + class TVar < Synchronization::Object + safe_initialization! + + # Create a new `TVar` with an initial value. + def initialize(value) + @value = value + @lock = Mutex.new + end + + # Get the value of a `TVar`. + def value + Concurrent::atomically do + Transaction::current.read(self) + end + end + + # Set the value of a `TVar`. + def value=(value) + Concurrent::atomically do + Transaction::current.write(self, value) + end + end + + # @!visibility private + def unsafe_value # :nodoc: + @value + end + + # @!visibility private + def unsafe_value=(value) # :nodoc: + @value = value + end + + # @!visibility private + def unsafe_lock # :nodoc: + @lock + end + + end + + # Run a block that reads and writes `TVar`s as a single atomic transaction. + # With respect to the value of `TVar` objects, the transaction is atomic, in + # that it either happens or it does not, consistent, in that the `TVar` + # objects involved will never enter an illegal state, and isolated, in that + # transactions never interfere with each other. You may recognise these + # properties from database transactions. + # + # There are some very important and unusual semantics that you must be aware of: + # + # * Most importantly, the block that you pass to atomically may be executed + # more than once. In most cases your code should be free of + # side-effects, except for via TVar. + # + # * If an exception escapes an atomically block it will abort the transaction. + # + # * It is undefined behaviour to use callcc or Fiber with atomically. + # + # * If you create a new thread within an atomically, it will not be part of + # the transaction. Creating a thread counts as a side-effect. + # + # Transactions within transactions are flattened to a single transaction. + # + # @example + # a = new TVar(100_000) + # b = new TVar(100) + # + # Concurrent::atomically do + # a.value -= 10 + # b.value += 10 + # end + def atomically + raise ArgumentError.new('no block given') unless block_given? + + # Get the current transaction + + transaction = Transaction::current + + # Are we not already in a transaction (not nested)? + + if transaction.nil? + # New transaction + + begin + # Retry loop + + loop do + + # Create a new transaction + + transaction = Transaction.new + Transaction::current = transaction + + # Run the block, aborting on exceptions + + begin + result = yield + rescue Transaction::AbortError => e + transaction.abort + result = Transaction::ABORTED + rescue Transaction::LeaveError => e + transaction.abort + break result + rescue => e + transaction.abort + raise e + end + # If we can commit, break out of the loop + + if result != Transaction::ABORTED + if transaction.commit + break result + end + end + end + ensure + # Clear the current transaction + + Transaction::current = nil + end + else + # Nested transaction - flatten it and just run the block + + yield + end + end + + # Abort a currently running transaction - see `Concurrent::atomically`. + def abort_transaction + raise Transaction::AbortError.new + end + + # Leave a transaction without committing or aborting - see `Concurrent::atomically`. + def leave_transaction + raise Transaction::LeaveError.new + end + + module_function :atomically, :abort_transaction, :leave_transaction + + private + + # @!visibility private + class Transaction + + ABORTED = ::Object.new + + OpenEntry = Struct.new(:value, :modified) + + AbortError = Class.new(StandardError) + LeaveError = Class.new(StandardError) + + def initialize + @open_tvars = {} + end + + def read(tvar) + entry = open(tvar) + entry.value + end + + def write(tvar, value) + entry = open(tvar) + entry.modified = true + entry.value = value + end + + def open(tvar) + entry = @open_tvars[tvar] + + unless entry + unless tvar.unsafe_lock.try_lock + Concurrent::abort_transaction + end + + entry = OpenEntry.new(tvar.unsafe_value, false) + @open_tvars[tvar] = entry + end + + entry + end + + def abort + unlock + end + + def commit + @open_tvars.each do |tvar, entry| + if entry.modified + tvar.unsafe_value = entry.value + end + end + + unlock + end + + def unlock + @open_tvars.each_key do |tvar| + tvar.unsafe_lock.unlock + end + end + + def self.current + Thread.current[:current_tvar_transaction] + end + + def self.current=(transaction) + Thread.current[:current_tvar_transaction] = transaction + end + + end + +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/engine.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/engine.rb new file mode 100644 index 0000000000..0c574b2abb --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/engine.rb @@ -0,0 +1,45 @@ +module Concurrent + # @!visibility private + module Utility + + # @!visibility private + module EngineDetector + def on_cruby? + RUBY_ENGINE == 'ruby' + end + + def on_jruby? + RUBY_ENGINE == 'jruby' + end + + def on_truffleruby? + RUBY_ENGINE == 'truffleruby' + end + + def on_windows? + !(RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/).nil? + end + + def on_osx? + !(RbConfig::CONFIG['host_os'] =~ /darwin|mac os/).nil? + end + + def on_linux? + !(RbConfig::CONFIG['host_os'] =~ /linux/).nil? + end + + def ruby_version(version = RUBY_VERSION, comparison, major, minor, patch) + result = (version.split('.').map(&:to_i) <=> [major, minor, patch]) + comparisons = { :== => [0], + :>= => [1, 0], + :<= => [-1, 0], + :> => [1], + :< => [-1] } + comparisons.fetch(comparison).include? result + end + end + end + + # @!visibility private + extend Utility::EngineDetector +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/monotonic_time.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/monotonic_time.rb new file mode 100644 index 0000000000..1c987d8a41 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/monotonic_time.rb @@ -0,0 +1,19 @@ +module Concurrent + + # @!macro monotonic_get_time + # + # Returns the current time as tracked by the application monotonic clock. + # + # @param [Symbol] unit the time unit to be returned, can be either + # :float_second, :float_millisecond, :float_microsecond, :second, + # :millisecond, :microsecond, or :nanosecond default to :float_second. + # + # @return [Float] The current monotonic time since some unspecified + # starting point + # + # @!macro monotonic_clock_warning + def monotonic_time(unit = :float_second) + Process.clock_gettime(Process::CLOCK_MONOTONIC, unit) + end + module_function :monotonic_time +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/native_extension_loader.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/native_extension_loader.rb new file mode 100644 index 0000000000..bf7bab354e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/native_extension_loader.rb @@ -0,0 +1,77 @@ +require 'concurrent/utility/engine' +# Synchronization::AbstractObject must be defined before loading the extension +require 'concurrent/synchronization/abstract_object' + +module Concurrent + # @!visibility private + module Utility + # @!visibility private + module NativeExtensionLoader + + def allow_c_extensions? + Concurrent.on_cruby? + end + + def c_extensions_loaded? + defined?(@c_extensions_loaded) && @c_extensions_loaded + end + + def load_native_extensions + if Concurrent.on_cruby? && !c_extensions_loaded? + ['concurrent/concurrent_ruby_ext', + "concurrent/#{RUBY_VERSION[0..2]}/concurrent_ruby_ext" + ].each { |p| try_load_c_extension p } + end + + if Concurrent.on_jruby? && !java_extensions_loaded? + begin + require 'concurrent/concurrent_ruby.jar' + set_java_extensions_loaded + rescue LoadError => e + raise e, "Java extensions are required for JRuby.\n" + e.message, e.backtrace + end + end + end + + private + + def load_error_path(error) + if error.respond_to? :path + error.path + else + error.message.split(' -- ').last + end + end + + def set_c_extensions_loaded + @c_extensions_loaded = true + end + + def java_extensions_loaded? + defined?(@java_extensions_loaded) && @java_extensions_loaded + end + + def set_java_extensions_loaded + @java_extensions_loaded = true + end + + def try_load_c_extension(path) + require path + set_c_extensions_loaded + rescue LoadError => e + if load_error_path(e) == path + # move on with pure-Ruby implementations + # TODO (pitr-ch 12-Jul-2018): warning on verbose? + else + raise e + end + end + + end + end + + # @!visibility private + extend Utility::NativeExtensionLoader +end + +Concurrent.load_native_extensions diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/native_integer.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/native_integer.rb new file mode 100644 index 0000000000..de1cdc306a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/native_integer.rb @@ -0,0 +1,54 @@ +module Concurrent + # @!visibility private + module Utility + # @private + module NativeInteger + # http://stackoverflow.com/questions/535721/ruby-max-integer + MIN_VALUE = -(2**(0.size * 8 - 2)) + MAX_VALUE = (2**(0.size * 8 - 2) - 1) + + def ensure_upper_bound(value) + if value > MAX_VALUE + raise RangeError.new("#{value} is greater than the maximum value of #{MAX_VALUE}") + end + value + end + + def ensure_lower_bound(value) + if value < MIN_VALUE + raise RangeError.new("#{value} is less than the maximum value of #{MIN_VALUE}") + end + value + end + + def ensure_integer(value) + unless value.is_a?(Integer) + raise ArgumentError.new("#{value} is not an Integer") + end + value + end + + def ensure_integer_and_bounds(value) + ensure_integer value + ensure_upper_bound value + ensure_lower_bound value + end + + def ensure_positive(value) + if value < 0 + raise ArgumentError.new("#{value} cannot be negative") + end + value + end + + def ensure_positive_and_no_zero(value) + if value < 1 + raise ArgumentError.new("#{value} cannot be negative or zero") + end + value + end + + extend self + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/processor_counter.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/processor_counter.rb new file mode 100644 index 0000000000..986e2d5231 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/utility/processor_counter.rb @@ -0,0 +1,110 @@ +require 'etc' +require 'rbconfig' +require 'concurrent/delay' + +module Concurrent + # @!visibility private + module Utility + + # @!visibility private + class ProcessorCounter + def initialize + @processor_count = Delay.new { compute_processor_count } + @physical_processor_count = Delay.new { compute_physical_processor_count } + end + + def processor_count + @processor_count.value + end + + def physical_processor_count + @physical_processor_count.value + end + + private + + def compute_processor_count + if Concurrent.on_jruby? + java.lang.Runtime.getRuntime.availableProcessors + else + Etc.nprocessors + end + end + + def compute_physical_processor_count + ppc = case RbConfig::CONFIG["target_os"] + when /darwin\d\d/ + IO.popen("/usr/sbin/sysctl -n hw.physicalcpu", &:read).to_i + when /linux/ + cores = {} # unique physical ID / core ID combinations + phy = 0 + IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln| + if ln.start_with?("physical") + phy = ln[/\d+/] + elsif ln.start_with?("core") + cid = phy + ":" + ln[/\d+/] + cores[cid] = true if not cores[cid] + end + end + cores.count + when /mswin|mingw/ + require 'win32ole' + result_set = WIN32OLE.connect("winmgmts://").ExecQuery( + "select NumberOfCores from Win32_Processor") + result_set.to_enum.collect(&:NumberOfCores).reduce(:+) + else + processor_count + end + # fall back to logical count if physical info is invalid + ppc > 0 ? ppc : processor_count + rescue + return 1 + end + end + end + + # create the default ProcessorCounter on load + @processor_counter = Utility::ProcessorCounter.new + singleton_class.send :attr_reader, :processor_counter + + # Number of processors seen by the OS and used for process scheduling. For + # performance reasons the calculated value will be memoized on the first + # call. + # + # When running under JRuby the Java runtime call + # `java.lang.Runtime.getRuntime.availableProcessors` will be used. According + # to the Java documentation this "value may change during a particular + # invocation of the virtual machine... [applications] should therefore + # occasionally poll this property." Subsequently the result will NOT be + # memoized under JRuby. + # + # Otherwise Ruby's Etc.nprocessors will be used. + # + # @return [Integer] number of processors seen by the OS or Java runtime + # + # @see http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html#availableProcessors() + def self.processor_count + processor_counter.processor_count + end + + # Number of physical processor cores on the current system. For performance + # reasons the calculated value will be memoized on the first call. + # + # On Windows the Win32 API will be queried for the `NumberOfCores from + # Win32_Processor`. This will return the total number "of cores for the + # current instance of the processor." On Unix-like operating systems either + # the `hwprefs` or `sysctl` utility will be called in a subshell and the + # returned value will be used. In the rare case where none of these methods + # work or an exception is raised the function will simply return 1. + # + # @return [Integer] number physical processor cores on the current system + # + # @see https://github.com/grosser/parallel/blob/4fc8b89d08c7091fe0419ca8fba1ec3ce5a8d185/lib/parallel.rb + # + # @see http://msdn.microsoft.com/en-us/library/aa394373(v=vs.85).aspx + # @see http://www.unix.com/man-page/osx/1/HWPREFS/ + # @see http://linux.die.net/man/8/sysctl + def self.physical_processor_count + processor_counter.physical_processor_count + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/version.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/version.rb new file mode 100644 index 0000000000..d1c098956a --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/lib/concurrent/version.rb @@ -0,0 +1,3 @@ +module Concurrent + VERSION = '1.2.2' +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/.gitignore b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/.gitignore b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/actor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/actor_spec.rb new file mode 100644 index 0000000000..5bb639fed1 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/actor_spec.rb @@ -0,0 +1,341 @@ +require 'concurrent/actor' + +module Concurrent + module Actor + AdHoc = Utils::AdHoc + + # FIXME better tests! + + RSpec.describe 'Concurrent::Actor', edge: true do + + def terminate_actors(*actors) + actors.each do |actor| + unless actor.ask!(:terminated?) + actor.ask!(:terminate!) + end + end + end + + class Ping < Context + def initialize(queue) + @queue = queue + end + + def on_message(message) + case message + when :child + AdHoc.spawn!(:pong, @queue) { |queue| -> m { queue << m } } + else + @queue << message + message + end + end + end + + it 'forbids Immediate executor' do + expect { Utils::AdHoc.spawn! name: 'test', executor: ImmediateExecutor.new }.to raise_error(ArgumentError) + end + + describe 'spawning' do + describe 'Actor#spawn!' do + behaviour = -> v { -> _ { v } } + subjects = { spawn: -> { Actor.spawn!(AdHoc, :ping, 'arg', &behaviour) }, + context_spawn: -> { AdHoc.spawn!(:ping, 'arg', &behaviour) }, + spawn_by_hash: -> { Actor.spawn!(class: AdHoc, name: :ping, args: ['arg'], &behaviour) }, + context_spawn_by_hash: -> { AdHoc.spawn!(name: :ping, args: ['arg'], &behaviour) } } + + subjects.each do |desc, subject_definition| + describe desc do + subject(:actor, &subject_definition) + after { terminate_actors actor } + + describe '#path' do + subject { super().path } + it { is_expected.to eq '/ping' } + end + + describe '#parent' do + subject { super().parent } + it { is_expected.to eq Actor.root } + end + + describe '#name' do + subject { super().name } + it { is_expected.to eq 'ping' } + end + it('executor should be global') { expect(subject.executor).to eq Concurrent.global_io_executor } + + describe '#reference' do + subject { super().reference } + it { is_expected.to eq subject } + end + it 'returns arg' do + expect(subject.ask!(:anything)).to eq 'arg' + end + end + end + end + + it 'terminates on failed initialization' do + a = AdHoc.spawn(name: :fail, logger: Concurrent::NULL_LOGGER) { raise } + expect(a.ask(nil).wait.rejected?).to be_truthy + expect(a.ask!(:terminated?)).to be_truthy + end + + it 'terminates on failed initialization and raises with spawn!' do + expect do + AdHoc.spawn!(name: :fail, logger: Concurrent::NULL_LOGGER) { raise 'm' } + end.to raise_error(StandardError, 'm') + end + + it 'terminates on failed message processing' do + a = AdHoc.spawn!(name: :fail, logger: Concurrent::NULL_LOGGER) { -> _ { raise } } + expect(a.ask(nil).wait.rejected?).to be_truthy + expect(a.ask!(:terminated?)).to be_truthy + end + end + + describe 'messaging' do + subject { AdHoc.spawn!(:add) { c = 0; -> v { c = c + v } } } + specify do + subject.tell(1).tell(1) + subject << 1 << 1 + expect(subject.ask(0).value!).to eq 4 + end + after { terminate_actors subject } + end + + describe 'children' do + let(:parent) do + AdHoc.spawn!(:parent) do + -> message do + if message == :child + AdHoc.spawn!(:child) { -> _ { parent } } + else + children + end + end + end + end + + it 'has children set after a child is created' do + child = parent.ask!(:child) + expect(parent.ask!(nil)).to include(child) + expect(child.ask!(nil)).to eq parent + + terminate_actors parent, child + end + end + + describe 'envelope' do + subject { AdHoc.spawn!(:subject) { -> _ { envelope } } } + specify do + envelope = subject.ask!('a') + expect(envelope).to be_a_kind_of Envelope + expect(envelope.message).to eq 'a' + expect(envelope.future).to be_resolved + expect(envelope.future.value).to eq envelope + expect(envelope.sender).to eq Thread.current + terminate_actors subject + end + end + + describe 'termination' do + subject do + AdHoc.spawn!(:parent) do + child = AdHoc.spawn!(:child) { -> v { v } } + -> v { child } + end + end + + it 'terminates with all its children' do + child = subject.ask! :child + expect(subject.ask!(:terminated?)).to be_falsey + subject.ask(:terminate!).wait + expect(subject.ask!(:terminated?)).to be_truthy + expect(child.ask!(:terminated?)).to be_truthy + + terminate_actors subject, child + end + end + + describe 'dead letter routing' do + it 'logs by deafault' do + ping = Ping.spawn! :ping, [] + ping << :terminate! + ping << 'asd' + sleep 0.1 + # TODO + end + end + + describe 'message redirecting' do + let(:parent) do + AdHoc.spawn!(:parent) do + child = AdHoc.spawn!(:child) { -> m { m + 1 } } + -> message do + if message == :child + child + else + redirect child + end + end + end + end + + it 'is evaluated by child' do + expect(parent.ask!(1)).to eq 2 + end + end + + it 'links' do + queue = Queue.new + failure = nil + # FIXME this leads to weird message processing ordering + # failure = AdHoc.spawn!(:failure) { -> m { terminate! } } + monitor = AdHoc.spawn!(:monitor) do + failure = AdHoc.spawn!(:failure) { -> m { m } } + failure << :link + -> m { queue << [m, envelope.sender] } + end + failure << :hehe + failure << :terminate! + expect(queue.pop).to eq [[:terminated, nil], failure] + + terminate_actors monitor + end + + it 'links atomically' do + queue = Queue.new + failure = nil + monitor = AdHoc.spawn!(:monitor) do + failure = AdHoc.spawn!(name: :failure, link: true) { -> m { m } } + -> m { queue << [m, envelope.sender] } + end + + failure << :hehe + failure << :terminate! + expect(queue.pop).to eq [[:terminated, nil], failure] + + terminate_actors monitor + end + + describe 'pausing' do + it 'pauses on error and resumes' do + queue = Queue.new + resuming_behaviour = Behaviour.restarting_behaviour_definition(:resume!) + + test = AdHoc.spawn! name: :tester, behaviour_definition: resuming_behaviour do + actor = AdHoc.spawn! name: :pausing, behaviour_definition: Behaviour.restarting_behaviour_definition do + queue << :init + -> m { m == :add ? 1 : pass } + end + + actor << :link + queue << actor.ask!(:linked) + actor << nil + queue << actor.ask(:add) + + -> m { queue << m } + end + + expect(queue.pop).to eq :init + expect(queue.pop).to include(test) + expect(queue.pop.value).to eq 1 + expect(queue.pop).to eq :resumed + terminate_actors test + end + + it 'pauses on error and resets' do + queue = Queue.new + test = AdHoc.spawn! name: :tester, behaviour_definition: Behaviour.restarting_behaviour_definition do + actor = AdHoc.spawn! name: :pausing, behaviour_definition: Behaviour.restarting_behaviour_definition do + queue << :init + -> m { m == :object_id ? self.object_id : pass } + end + + queue << actor.ask!(:linked) + queue << actor.ask!(:object_id) + actor << nil + queue << actor.ask(:object_id) + + -> m do + queue << m + end + end + + expect(queue.pop).to eq :init + expect(queue.pop).to include(test) + first_id = queue.pop + second_id = queue.pop.value + expect(first_id).not_to eq second_id # context already reset + expect(queue.pop).to eq :init # rebuilds context + expect(queue.pop).to eq :reset + terminate_actors test + end + + it 'pauses on error and restarts' do + queue = Queue.new + resuming_behaviour = Behaviour.restarting_behaviour_definition.map do |c, *args| + if Behaviour::Supervising == c + [c, *[:restart!, :one_for_one]] + else + [c, *args] + end + end + + test = AdHoc.spawn! name: :tester, behaviour_definition: resuming_behaviour do + + actor = AdHoc.spawn! name: :pausing, + behaviour_definition: Behaviour.restarting_behaviour_definition do + queue << :init + -> m { m == :add ? 1 : pass } + end + + actor << :link + queue << actor.ask!(:linked) + actor << nil + queue << actor.ask(:add) + + -> m do + queue << m + end + end + + expect(queue.pop).to eq :init + expect(queue.pop).to include(test) + expect(queue.pop.wait.reason).to be_a_kind_of(ActorTerminated) + expect(queue.pop).to eq :init + expect(queue.pop).to eq :restarted + terminate_actors test + end + + end + + describe 'pool' do + it 'supports asks' do + children = Queue.new + pool = Concurrent::Actor::Utils::Pool.spawn! 'pool', 5 do |index| + worker = Concurrent::Actor::Utils::AdHoc.spawn! name: "worker-#{index}", supervised: true do + lambda do |message| + fail if message == :fail + 5 + message + end + end + children.push worker + worker + end + + 10.times { expect(pool.ask!(5)).to eq 10 } + expect(pool.ask(:fail).reason).to be_kind_of RuntimeError + expect(pool.ask!(5)).to eq 10 + expect(pool.ask!(:terminate!)).to be_truthy + 5.times { expect(children.pop.ask!(:terminated?)).to be_truthy } + + terminate_actors pool + end + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/agent_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/agent_spec.rb new file mode 100644 index 0000000000..1e0e331283 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/agent_spec.rb @@ -0,0 +1,1217 @@ +require 'concurrent/agent' +require 'concurrent/executor/single_thread_executor' +require 'concurrent/executor/immediate_executor' +require 'concurrent/executor/fixed_thread_pool' +require 'concurrent/atomic/count_down_latch' + +require_relative 'concern/observable_shared' + +module Concurrent + + RSpec.describe Agent do + + let!(:immediate) { Concurrent::ImmediateExecutor.new } + let!(:executor) { Concurrent::SingleThreadExecutor.new } + + context 'initialization' do + + it 'sets the initial value' do + subject = Agent.new(42) + expect(subject.value).to eq 42 + end + + it 'sets the initial error to nil' do + subject = Agent.new(42) + expect(subject.error).to be nil + end + + it 'sets the error mode when given a valid value' do + subject = Agent.new(42, error_mode: :fail) + expect(subject.error_mode).to eq :fail + end + + it 'defaults the error mode to :continue when an error handler is given' do + subject = Agent.new(42, error_handler: ->(value) { true }) + expect(subject.error_mode).to eq :continue + end + + it 'defaults the error mode to :fail when no error handler is given' do + subject = Agent.new(42) + expect(subject.error_mode).to eq :fail + end + + it 'raises an error when given an invalid error mode' do + expect { + Agent.new(42, error_mode: :bogus) + }.to raise_error(ArgumentError) + end + + it 'sets #failed? to false' do + subject = Agent.new(42) + expect(subject).to_not be_failed + expect(subject).to_not be_stopped + end + end + + context 'action processing' do + + specify 'the given block will be passed the current value' do + actual = nil + expected = 0 + subject = Agent.new(expected) + subject.send_via(immediate) { |value| actual = value } + expect(actual).to eq expected + end + + specify 'the given block will be passed any provided arguments' do + actual = nil + expected = [1, 2, 3, 4] + subject = Agent.new(0) + subject.send_via(immediate, *expected) { |_, *args| actual = args } + expect(actual).to eq expected + end + + specify 'the return value will be passed to the validator function' do + actual = nil + expected = 42 + validator = ->(new_value) { actual = new_value; true } + subject = Agent.new(0, validator: validator) + subject.send_via(immediate) { expected } + expect(actual).to eq expected + end + + specify 'upon validation the new value will be set to the block return value' do + expected = 42 + validator = ->(_new_value) { true } + subject = Agent.new(0, validator: validator) + subject.send_via(immediate) { expected } + expect(subject.value).to eq expected + end + + specify 'on success all observers will be notified' do + observer_class = Class.new do + def initialize(bucket) + @bucket = bucket + end + + def update(time, old_value, new_value) + @bucket.concat([time, old_value, new_value]) + end + end + + bucket = [] + subject = Agent.new(0) + subject.add_observer(observer_class.new(bucket)) + subject.send_via(immediate) { 42 } + + expect(bucket[0]).to be_a Time + expect(bucket[1]).to eq 0 + expect(bucket[2]).to eq 42 + end + + specify 'any recursive action dispatches will run after the value has been updated' do + subject = Agent.new(0) + + subject.send_via(executor, subject) do |v1, a1| + expect(v1).to eq 0 + a1.send_via(executor, a1) do |v2, a2| + expect(v2).to eq 1 + a1.send_via(executor, a2) do |v3, a3| + expect(v3).to eq 2 + 3 + end + 2 + end + 1 + end + expect(subject.await_for(1)).to eq true + expect(subject).to_not be_failed + end + + specify 'when the action raises an error the value will not change' do + expected = 0 + subject = Agent.new(expected) + subject.send_via(immediate) { raise StandardError } + expect(subject.value).to eq expected + end + + specify 'when the action raises an error the validator will not be called' do + validator_called = false + validator = ->(new_value) { validator_called = true } + subject = Agent.new(0, validator: validator) + subject.send_via(immediate) { raise StandareError } + expect(validator_called).to be false + end + + specify 'when validation returns false the value will not change' do + expected = 0 + validator = ->(new_value) { false } + subject = Agent.new(0, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.value).to eq expected + end + + specify 'when validation raises an error the value will not change' do + expected = 0 + validator = ->(new_value) { raise StandareError } + subject = Agent.new(0, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.value).to eq expected + end + + specify 'when the action raises an error the handler will be called' do + error_handler_called = false + error_handler = ->(agent, exception) { error_handler_called = true } + subject = Agent.new(0, error_handler: error_handler) + subject.send_via(immediate) { raise StandardError } + expect(error_handler_called).to be true + end + + specify 'when validation fails the handler will be called' do + error_handler_called = false + error_handler = ->(agent, exception) { error_handler_called = true } + validator = ->(new_value) { false } + subject = Agent.new(0, error_handler: error_handler, validator: validator) + subject.send_via(immediate) { 42 } + expect(error_handler_called).to be true + end + + specify 'when validation raises an error the handler will be called' do + error_handler_called = false + error_handler = ->(agent, exception) { error_handler_called = true } + validator = ->(new_value) { raise StandardError } + subject = Agent.new(0, error_handler: error_handler, validator: validator) + subject.send_via(immediate) { 42 } + expect(error_handler_called).to be true + end + end + + context 'validation' do + + it 'sets the new value when the validator returns true' do + expected = 42 + validator = ->(new_value) { true } + subject = Agent.new(0, validator: validator) + subject.send_via(immediate) { expected } + expect(subject.value).to eq expected + end + + it 'rejects the new value when the validator returns false' do + expected = 0 + validator = ->(new_value) { false } + subject = Agent.new(expected, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.value).to eq expected + end + + it 'rejects the new value when the validator raises an error' do + expected = 0 + validator = ->(new_value) { raise StandardError } + subject = Agent.new(expected, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.value).to eq expected + end + + it 'sets the error when the error mode is :fail and the validator returns false' do + validator = ->(new_value) { false } + subject = Agent.new(0, error_mode: :fail, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.error).to be_a Agent::ValidationError + end + + it 'sets the error when the error mode is :fail and the validator raises an error' do + validator = ->(new_value) { raise expected } + subject = Agent.new(0, error_mode: :fail, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.error).to be_a Agent::ValidationError + end + + it 'does not set an error when the error mode is :continue and the validator returns false' do + validator = ->(new_value) { false } + subject = Agent.new(0, error_mode: :continue, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.error).to be nil + end + + it 'does not set an error when the error mode is :continue and the validator raises an error' do + validator = ->(new_value) { raise StandardError } + subject = Agent.new(0, error_mode: :continue, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.error).to be nil + end + + it 'does not trigger observation when validation fails' do + observer_class = Class.new do + attr_reader :count + + def initialize + @count = 0 + end + + def update(time, old_value, new_value) + @count += 1 + end + end + + observer = observer_class.new + subject = Agent.new(0, validator: ->(new_value) { false }) + subject.add_observer(observer) + subject.send_via(immediate) { 42 } + + expect(observer.count).to eq 0 + end + end + + context 'error handling' do + + specify 'the agent will be passed to the handler' do + actual = nil + error_handler = ->(agent, error) { actual = agent } + subject = Agent.new(0, error_handler: error_handler) + subject.send_via(immediate) { raise StandardError } + expect(actual).to eq subject + end + + specify 'the exception will be passed to the handler' do + expected = StandardError.new + actual = nil + error_handler = ->(agent, error) { actual = error } + subject = Agent.new(0, error_handler: error_handler) + subject.send_via(immediate) { raise expected } + expect(actual).to eq expected + end + + specify 'does not trigger observation' do + observer_class = Class.new do + attr_reader :count + + def initialize + @count = 0 + end + + def update(time, old_value, new_value) + @count += 1 + end + end + + observer = observer_class.new + subject = Agent.new(0) + subject.add_observer(observer) + subject.send_via(immediate) { raise StandardError } + + expect(observer.count).to eq 0 + end + end + + context 'error mode' do + + context ':continue' do + + it 'does not set an error when the validator returns false' do + validator = ->(new_value) { false } + subject = Agent.new(0, error_mode: :continue, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.error).to be nil + end + + it 'does not set an error when the validator raises an error' do + validator = ->(new_value) { raise StandardError } + subject = Agent.new(0, error_mode: :continue, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.error).to be nil + end + + it 'does not set an error when the action raises an error' do + subject = Agent.new(0, error_mode: :continue) + subject.send_via(immediate) { raise StandardError } + expect(subject.error).to be nil + end + + it 'does not block further action processing' do + subject = Agent.new(0, error_mode: :continue) + subject.send_via(immediate) { raise StandardError } + subject.send_via(immediate) { 42 } + expect(subject.value).to eq 42 + end + + it 'sets #failed? to false' do + subject = Agent.new(0, error_mode: :continue) + subject.send_via(immediate) { raise StandardError } + expect(subject).to_not be_failed + end + end + + context ':fail' do + + it 'sets the error when the validator returns false' do + validator = ->(new_value) { false } + subject = Agent.new(0, error_mode: :fail, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.error).to be_a Agent::ValidationError + end + + it 'sets the error when the validator raises an error' do + validator = ->(new_value) { raise expected } + subject = Agent.new(0, error_mode: :fail, validator: validator) + subject.send_via(immediate) { 42 } + expect(subject.error).to be_a Agent::ValidationError + end + + it 'sets the error when the action raises an error' do + expected = StandardError.new + subject = Agent.new(0, error_mode: :fail) + subject.send_via(immediate) { raise expected } + expect(subject.error).to eq expected + end + + it 'blocks all further action processing until a restart' do + latch = Concurrent::CountDownLatch.new + expected = 42 + + subject = Agent.new(0, error_mode: :fail) + subject.send_via(immediate) { raise StandardError } + subject.send_via(executor) { latch.count_down; expected } + + latch.wait(0.1) + expect(subject.value).to eq 0 + + subject.restart(42) + latch.wait(0.1) + expect(subject.await_for(1)).to eq true + expect(subject.value).to eq expected + end + + it 'sets #failed? to true' do + subject = Agent.new(0, error_mode: :fail) + subject.send_via(immediate) { raise StandardError } + expect(subject).to be_failed + end + end + end + + context 'nested actions' do + + specify 'occur in the order they ar post' do + actual = [] + expected = [0, 1, 2, 3, 4] + latch = Concurrent::CountDownLatch.new + subject = Agent.new(0) + + subject.send_via(executor, subject) do |v1, a1| + a1.send_via(executor, a1) do |v2, a2| + a1.send_via(executor, a2) do |v3, a3| + a1.send_via(executor, a3) do |v4, a4| + a1.send_via(executor, a4) do |v5, a5| + actual << v5; latch.count_down + end + actual << v4; v4 + 1 + end + actual << v3; v3 + 1 + end + actual << v2; v2 + 1 + end + actual << v1; v1 + 1 + end + + latch.wait(2) + expect(subject.await_for(1)).to eq true + expect(actual).to eq expected + end + + specify 'work with immediate execution' do + actual = [] + expected = [0, 1, 2] + subject = Agent.new(0) + + subject.send_via(immediate) do |v1| + subject.send_via(immediate) do |v2| + subject.send_via(immediate) do |v3| + actual << v3 + end + actual << v2; v2 + 1 + end + actual << v1; v1 + 1 + end + + expect(actual).to eq expected + end + end + + context 'posting' do + + context 'with #send' do + + it 'returns true when the job is post' do + subject = Agent.new(0) + expect(subject.send { nil }).to be true + expect(subject.await_for(1)).to eq true + end + + it 'returns false when #failed?' do + subject = Agent.new(0) + allow(subject).to receive(:failed?).and_return(true) + expect(subject.send { nil }).to be false + expect(subject.await_for(1)).to eq true + end + + it 'posts to the global fast executor' do + subject = Agent.new(0) + expect(subject).to receive(:enqueue_action_job).with(anything, anything, Concurrent.global_fast_executor).and_call_original + subject.send { nil } + expect(subject.await_for(1)).to eq true + end + + it 'does not wait for the action to process' do + job_done = false + subject = Agent.new(0) + latch = CountDownLatch.new + subject.send { latch.wait; job_done = true } + expect(job_done).to be false + latch.count_down + expect(subject.await_for(1)).to eq true + end + end + + context 'with #send!' do + + it 'returns true when the job is post' do + subject = Agent.new(0) + expect(subject.send! { nil }).to be true + expect(subject.await_for(1)).to eq true + end + + it 'raises an error when #failed?' do + subject = Agent.new(0) + allow(subject).to receive(:failed?).and_return(true) + expect { + subject.send! { nil } + }.to raise_error(Agent::Error) + end + + it 'posts to the global fast executor' do + subject = Agent.new(0) + expect(subject).to receive(:enqueue_action_job).with(anything, anything, Concurrent.global_fast_executor).and_call_original + subject.send! { nil } + expect(subject.await_for(1)).to eq true + end + + it 'does not wait for the action to process' do + job_done = false + subject = Agent.new(0) + latch = CountDownLatch.new + subject.send! { latch.wait; job_done = true } + expect(job_done).to be false + latch.count_down + expect(subject.await_for(1)).to eq true + end + end + + context 'with #send_off' do + + it 'returns true when the job is post' do + subject = Agent.new(0) + expect(subject.send_off { nil }).to be true + expect(subject.await_for(1)).to eq true + end + + it 'returns false when #failed?' do + subject = Agent.new(0) + allow(subject).to receive(:failed?).and_return(true) + expect(subject.send_off { nil }).to be false + end + + it 'posts to the global io executor' do + subject = Agent.new(0) + expect(subject).to receive(:enqueue_action_job).with(anything, anything, Concurrent.global_io_executor).and_call_original + subject.send_off { nil } + expect(subject.await_for(1)).to eq true + end + + it 'does not wait for the action to process' do + job_done = false + subject = Agent.new(0) + latch = CountDownLatch.new + subject.send_off { latch.wait; job_done = true } + expect(job_done).to be false + latch.count_down + expect(subject.await_for(1)).to eq true + end + end + + context 'with #send_off!' do + + it 'returns true when the job is post' do + subject = Agent.new(0) + expect(subject.send_off! { nil }).to be true + expect(subject.await_for(1)).to eq true + end + + it 'raises an error when #failed?' do + subject = Agent.new(0) + allow(subject).to receive(:failed?).and_return(true) + expect { + subject.send_off! { nil } + }.to raise_error(Agent::Error) + end + + it 'posts to the global io executor' do + subject = Agent.new(0) + expect(subject).to receive(:enqueue_action_job).with(anything, anything, Concurrent.global_io_executor).and_call_original + subject.send_off! { nil } + expect(subject.await_for(1)).to eq true + end + + it 'does not wait for the action to process' do + job_done = false + subject = Agent.new(0) + latch = CountDownLatch.new + subject.send_off! { latch.wait; job_done = true } + expect(job_done).to be false + latch.count_down + expect(subject.await_for(1)).to eq true + end + end + + context 'with #send_via' do + + it 'returns true when the job is post' do + subject = Agent.new(0) + expect(subject.send_via(immediate) { nil }).to be true + end + + it 'returns false when #failed?' do + subject = Agent.new(0) + allow(subject).to receive(:failed?).and_return(true) + expect(subject.send_via(immediate) { nil }).to be false + end + + it 'posts to the given executor' do + expect(immediate).to receive(:post).with(any_args).and_call_original + subject = Agent.new(0) + subject.send_via(immediate) { nil } + end + end + + context 'with #send_via!' do + + it 'returns true when the job is post' do + subject = Agent.new(0) + expect(subject.send_via!(immediate) { nil }).to be true + end + + it 'raises an error when #failed?' do + subject = Agent.new(0) + allow(subject).to receive(:failed?).and_return(true) + expect { + subject.send_via!(immediate) { nil } + }.to raise_error(Agent::Error) + end + + it 'posts to the given executor' do + expect(immediate).to receive(:post).with(any_args).and_call_original + subject = Agent.new(0) + subject.send_via!(immediate) { nil } + end + end + + context 'with #post' do + + it 'returns true when the job is post' do + subject = Agent.new(0) + expect(subject.post { nil }).to be true + expect(subject.await_for(1)).to eq true + end + + it 'returns false when #failed?' do + subject = Agent.new(0) + allow(subject).to receive(:failed?).and_return(true) + expect(subject.post { nil }).to be false + end + + it 'posts to the global io executor' do + subject = Agent.new(0) + expect(subject).to receive(:enqueue_action_job).with(anything, anything, Concurrent.global_io_executor).and_call_original + subject.post { nil } + expect(subject.await_for(1)).to eq true + end + + it 'does not wait for the action to process' do + job_done = false + subject = Agent.new(0) + latch = CountDownLatch.new + subject.post { latch.wait; job_done = true } + expect(job_done).to be false + latch.count_down + expect(subject.await_for(1)).to eq true + end + end + + context 'with #<<' do + + it 'returns self when the job is post' do + subject = Agent.new(0) + expect(subject << proc { nil }).to be subject + expect(subject.await_for(1)).to eq true + end + + it 'returns self when #failed?' do + subject = Agent.new(0) + allow(subject).to receive(:failed?).and_return(true) + expect(subject << proc { nil }).to be subject + end + + it 'posts to the global io executor' do + subject = Agent.new(0) + expect(subject).to receive(:enqueue_action_job).with(anything, anything, Concurrent.global_io_executor).and_call_original + subject << proc { nil } + expect(subject.await_for(1)).to eq true + end + + it 'does not wait for the action to process' do + job_done = false + subject = Agent.new(0) + latch = CountDownLatch.new + subject << proc { latch.wait; job_done = true } + expect(job_done).to be false + latch.count_down + expect(subject.await_for(1)).to eq true + end + end + end + + context '#restart' do + + context 'when #failed?' do + + it 'raises an error if the new value is not valid' do + subject = Agent.new(0, error_mode: :fail, validator: ->(new_value) { false }) + subject.send_via(immediate) { raise StandardError } + + expect { + subject.restart(0) + }.to raise_error(Agent::Error) + end + + it 'sets the new value' do + subject = Agent.new(0, error_mode: :fail) + subject.send_via(immediate) { raise StandardError } + + subject.restart(42) + expect(subject.value).to eq 42 + end + + it 'clears the error' do + subject = Agent.new(0, error_mode: :fail) + subject.send_via(immediate) { raise StandardError } + + subject.restart(42) + expect(subject.error).to be nil + end + + it 'sets #failed? to true' do + subject = Agent.new(0, error_mode: :fail) + subject.send_via(immediate) { raise StandardError } + + subject.restart(42) + expect(subject).to_not be_failed + end + + it 'removes all actions from the queue when :clear_actions is true' do + latch = Concurrent::CountDownLatch.new + end_latch = Concurrent::CountDownLatch.new + subject = Agent.new(0, error_mode: :fail) + + subject.send_via(executor) { latch.wait; raise StandardError } + subject.send_via(executor) { end_latch.count_down } + + latch.count_down + 10.times { break if subject.failed?; sleep(0.1) } + + subject.restart(42, clear_actions: true) + result = end_latch.wait(0.1) + expect(result).to be false + expect(subject.await_for(1)).to eq true + end + + it 'does not clear the action queue when :clear_actions is false' do + latch = Concurrent::CountDownLatch.new + end_latch = Concurrent::CountDownLatch.new + subject = Agent.new(0, error_mode: :fail) + + subject.send_via(executor) { latch.wait; raise StandardError } + subject.send_via(executor) { end_latch.count_down } + + latch.count_down + 10.times { break if subject.failed?; sleep(0.1) } + + subject.restart(42, clear_actions: false) + result = end_latch.wait(3) + expect(result).to be true + expect(subject.await_for(1)).to eq true + end + + it 'does not clear the action queue when :clear_actions is not given' do + latch = Concurrent::CountDownLatch.new + end_latch = Concurrent::CountDownLatch.new + subject = Agent.new(0, error_mode: :fail) + + subject.send_via(executor) { latch.wait; raise StandardError } + subject.send_via(executor) { end_latch.count_down } + + latch.count_down + 10.times { break if subject.failed?; sleep(0.1) } + + subject.restart(42) + result = end_latch.wait(3) + expect(result).to be true + expect(subject.await_for(1)).to eq true + end + + it 'resumes action processing if actions are enqueued' do + count = 5 + latch = Concurrent::CountDownLatch.new + finish_latch = Concurrent::CountDownLatch.new(5) + subject = Agent.new(0, error_mode: :fail) + + subject.send_via(executor) { latch.wait; raise StandardError } + count.times { subject.send_via(executor) { finish_latch.count_down } } + + queue = subject.instance_variable_get(:@queue) + size = queue.size + expect(size).to be > 0 + + latch.count_down + 10.times { break if subject.failed?; sleep(0.1) } + + subject.restart(42, clear_actions: false) + expect(finish_latch.wait(5)).to be true + expect(subject.await_for(1)).to eq true + end + + it 'does not trigger observation' do + observer_class = Class.new do + attr_reader :count + + def initialize + @count = 0 + end + + def update(time, old_value, new_value) + @count += 1 + end + end + + observer = observer_class.new + subject = Agent.new(0, error_mode: :fail) + subject.add_observer(observer) + subject.send_via(immediate) { raise StandardError } + subject.restart(42) + + expect(observer.count).to eq 0 + end + end + + context 'when not #failed?' do + + it 'raises an error' do + subject = Agent.new(0) + expect { + subject.restart(0) + }.to raise_error(Agent::Error) + end + end + end + + context 'waiting' do + + context 'the await job' do + + it 'does not change the value' do + expected = 42 + subject = Agent.new(0) + subject.send_via(executor) { sleep(0.1); expected } + subject.await_for(1) + expect(subject.value).to eq expected + end + + it 'does not trigger the error mode' do + subject = Agent.new(10) + subject.send { |x| sleep(0.1); x + 1 } + subject.await_for(1) + + expect(subject.value).to eq 11 + expect(subject).to_not be_failed + expect(subject.error).to be nil + end + + it 'does not trigger observers' do + observer_class = Class.new do + attr_reader :count + + def initialize + @count = 0 + end + + def update(time, old_value, new_value) + @count += 1 + end + end + + observer = observer_class.new + subject = Agent.new(0) + subject.add_observer(observer) + subject.send_via(executor) { sleep(0.1); 42 } + subject.await_for(1) + + expect(observer.count).to eq 1 + end + + it 'waits for nested actions' do + bucket = [] + latch = Concurrent::CountDownLatch.new + executor = Concurrent::FixedThreadPool.new(3) + subject = Agent.new(0) + + subject.send_via(executor) do + subject.send_via(executor) do + subject.send_via(executor) do + bucket << 3 + end + latch.count_down + sleep(0.2) + bucket << 2 + end + bucket << 1 + end + latch.wait + + subject.await_for(5) + expect(bucket).to eq [1, 2, 3] + + executor.kill + expect(executor.wait_for_termination(pool_termination_timeout)).to eq true + end + end + + context 'with #await' do + + it 'returns self when there are no pending actions' do + subject = Agent.new(0) + expect(subject.await).to eq subject + expect(subject.await.value).to eq 0 + end + + it 'does not block on actions from other threads' do + latch = Concurrent::CountDownLatch.new + finish = Concurrent::CountDownLatch.new + subject = Agent.new(0) + in_thread do + subject.send_via(executor) { finish.wait } + latch.count_down + end + + latch.wait(0.1) + expect(subject.await_for(1)).to eq true + finish.count_down + end + + it 'blocks indefinitely' do + start = Concurrent.monotonic_time + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + expect(subject.await).to be_truthy + expect(Concurrent.monotonic_time - start).to be > 0.5 + end + + it 'returns true when all prior actions have processed' do + count = 0 + expected = 5 + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + expected.times { subject.send_via(executor) { count += 1 } } + subject.await + expect(count).to eq expected + end + + it 'blocks forever if restarted with :clear_actions true' do + pending('the timing is nearly impossible'); fail + subject = Agent.new(0, error_mode: :fail) + + t = in_thread do + subject.send_via(executor) { sleep(0.1) } + subject.send_via(executor) { raise StandardError } + subject.send_via(executor) { nil } + in_thread { subject.restart(42, clear_actions: true) } + subject.await + end + + thread_status = t.join(0.3) + expect(thread_status).to be nil + end + end + + context 'with #await_for' do + + it 'returns true when there are no pending actions' do + subject = Agent.new(0) + expect(subject.await_for(1)).to be true + end + + it 'does not block on actions from other threads' do + latch = Concurrent::CountDownLatch.new + finish = Concurrent::CountDownLatch.new + subject = Agent.new(0) + in_thread do + subject.send_via(executor) { finish.wait } + latch.count_down + end + + latch.wait(0.1) + expect(subject.await_for(0.1)).to be true + finish.count_down + end + + it 'returns true when all prior actions have processed' do + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + 5.times { subject.send_via(executor) { nil } } + expect(subject.await_for(10)).to be true + end + + it 'returns false on timeout' do + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + 5.times { subject.send_via(executor) { nil } } + expect(subject.await_for(0.1)).to be false + expect(subject.await_for(5)).to eq true + end + + it 'returns false if restarted with :clear_actions true' do + pending('the timing is nearly impossible'); fail + subject = Agent.new(0, error_mode: :fail) + + subject.send_via(executor) { sleep(0.1) } + subject.send_via(executor) { raise StandardError } + subject.send_via(executor) { nil } + + in_thread { subject.restart(42, clear_actions: true) } + ok = subject.await_for(0.2) + + expect(ok).to be false + end + end + + context 'with #await_for!' do + + it 'returns true when there are no pending actions' do + subject = Agent.new(0) + expect(subject.await_for!(1)).to be true + end + + it 'does not block on actions from other threads' do + latch = Concurrent::CountDownLatch.new + finish = Concurrent::CountDownLatch.new + subject = Agent.new(0) + in_thread do + subject.send_via(executor) { finish.wait } + latch.count_down + end + + latch.wait(0.1) + expect(subject.await_for!(0.1)).to be true + finish.count_down + end + + it 'returns true when all prior actions have processed' do + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + 5.times { subject.send_via(executor) { nil } } + expect(subject.await_for!(10)).to be true + end + + it 'raises an error on timeout' do + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + 5.times { subject.send_via(executor) { nil } } + expect { + subject.await_for!(0.1) + }.to raise_error(Concurrent::TimeoutError) + expect(subject.await_for(5)).to eq true + end + + it 'raises an error if restarted with :clear_actions true' do + pending('the timing is nearly impossible'); fail + subject = Agent.new(0, error_mode: :fail) + + subject.send_via(executor) { sleep(0.1) } + subject.send_via(executor) { raise StandardError } + subject.send_via(executor) { nil } + + in_thread { subject.restart(42, clear_actions: true) } + + expect { + subject.await_for!(0.2) + }.to raise_error(Concurrent::TimeoutError) + end + end + + context 'with #wait' do + + it 'returns true when there are no pending actions and timeout is nil' do + subject = Agent.new(0) + expect(subject.wait(nil)).to be true + end + + it 'returns true when there are no pending actions and a timeout is given' do + subject = Agent.new(0) + expect(subject.wait(1)).to be true + end + + it 'does not block on actions from other threads' do + latch = Concurrent::CountDownLatch.new + finish = Concurrent::CountDownLatch.new + subject = Agent.new(0) + in_thread do + subject.send_via(executor) { finish.wait } + latch.count_down + end + + latch.wait(0.1) + expect(subject.wait(0.1)).to be true + finish.count_down + end + + it 'blocks indefinitely when timeout is nil' do + start = Concurrent.monotonic_time + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + expect(subject.wait(nil)).to be true + expect(Concurrent.monotonic_time - start).to be > 0.5 + end + + it 'blocks forever when timeout is nil and restarted with :clear_actions true' do + pending('the timing is nearly impossible'); fail + subject = Agent.new(0, error_mode: :fail) + + t = in_thread do + subject.send_via(executor) { sleep(0.1) } + subject.send_via(executor) { raise StandardError } + subject.send_via(executor) { nil } + in_thread { subject.restart(42, clear_actions: true) } + subject.wait(nil) + end + + expect(t.join(0.3)).to be nil + end + + it 'returns true when all prior actions have processed' do + count = 0 + expected = 5 + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + expected.times { subject.send_via(executor) { count += 1 } } + subject.wait(nil) + expect(count).to eq expected + end + + it 'returns false on timeout' do + subject = Agent.new(0) + subject.send_via(executor) { sleep(1) } + 5.times { subject.send_via(executor) { nil } } + expect(subject.wait(0.1)).to be false + expect(subject.wait(5)).to eq true + end + + it 'returns false when timeout is given and restarted with :clear_actions true' do + pending('the timing is nearly impossible'); fail + subject = Agent.new(0, error_mode: :fail) + + subject.send_via(executor) { sleep(0.1) } + subject.send_via(executor) { raise StandardError } + subject.send_via(executor) { nil } + + in_thread { subject.restart(42, clear_actions: true) } + ok = subject.wait(0.2) + + expect(ok).to be false + end + end + + context 'with .await' do + + it 'returns true when all prior actions on all agents have processed' do + latch = Concurrent::CountDownLatch.new + agents = 3.times.collect { Agent.new(0) } + agents.each { |agent| agent.send_via(executor, latch) { |_, l| l.wait(1) } } + in_thread { latch.count_down } + ok = Agent.await(*agents) + expect(ok).to be true + end + end + + context 'with .await_for' do + + it 'returns true when there are no pending actions' do + agents = 3.times.collect { Agent.new(0) } + ok = Agent.await_for(1, *agents) + expect(ok).to be true + end + + it 'returns true when all prior actions for all agents have processed' do + latch = Concurrent::CountDownLatch.new + agents = 3.times.collect { Agent.new(0) } + agents.each { |agent| agent.send_via(executor, latch) { |_, l| l.wait(1) } } + in_thread { latch.count_down } + ok = Agent.await_for(5, *agents) + expect(ok).to be true + end + + it 'returns false on timeout' do + agents = 3.times.collect { Agent.new(0) } + agents.each { |agent| agent.send_via(executor) { sleep(0.3) } } + ok = Agent.await_for(0.1, *agents) + expect(ok).to be false + expect(Agent.await_for!(1, *agents)).to eq true + end + end + + context 'with await_for!' do + + it 'returns true when there are no pending actions' do + agents = 3.times.collect { Agent.new(0) } + ok = Agent.await_for!(1, *agents) + expect(ok).to be true + end + + it 'returns true when all prior actions for all agents have processed' do + latch = Concurrent::CountDownLatch.new + agents = 3.times.collect { Agent.new(0) } + agents.each { |agent| agent.send_via(executor, latch) { |_, l| l.wait(1) } } + in_thread { latch.count_down } + ok = Agent.await_for!(5, *agents) + expect(ok).to be true + end + + it 'raises an exception on timeout' do + agents = 3.times.collect { Agent.new(0) } + agents.each { |agent| agent.send_via(executor) { sleep(0.3) } } + expect { + Agent.await_for!(0.1, *agents) + }.to raise_error(Concurrent::TimeoutError) + expect(Agent.await_for!(1, *agents)).to eq true + end + end + end + + context :observable do + + subject { Agent.new(0) } + + def trigger_observable(observable) + observable.send_via(immediate) { 42 } + end + + it_behaves_like :observable + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/array_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/array_spec.rb new file mode 100644 index 0000000000..4f2997eb8e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/array_spec.rb @@ -0,0 +1,96 @@ +require 'concurrent/array' + +module Concurrent + RSpec.describe Array do + let!(:ary) { described_class.new } + + describe '.[]' do + describe 'when initializing with no arguments' do + it do + expect(described_class[]).to be_empty + end + end + + describe 'when initializing with arguments' do + it 'creates an array with the given objects' do + expect(described_class[:hello, :world]).to eq [:hello, :world] + end + end + end + + describe '.new' do + describe 'when initializing with no arguments' do + it do + expect(described_class.new).to be_empty + end + end + + describe 'when initializing with a size argument' do + let(:size) { 3 } + + it 'creates an array with size elements set to nil' do + expect(described_class.new(size)).to eq [nil, nil, nil] + end + + describe 'when initializing with a default value argument' do + let(:default_value) { :ruby } + + it 'creates an array with size elements set to the default value' do + expect(described_class.new(size, default_value)).to eq [:ruby, :ruby, :ruby] + end + end + + describe 'when initializing with a block argument' do + let(:block_argument) { proc { |index| :"ruby#{index}" } } + + it 'creates an array with size elements set to the default value' do + expect(described_class.new(size, &block_argument)).to eq [:ruby0, :ruby1, :ruby2] + end + end + end + + describe 'when initializing with another array as an argument' do + let(:other_array) { [:hello, :world] } + let(:fake_other_array) { double('Fake array', to_ary: other_array) } + + it 'creates a new array' do + expect(described_class.new(other_array)).to_not be other_array + end + + it 'creates an array with the same contents as the other array' do + expect(described_class.new(other_array)).to eq [:hello, :world] + end + + it 'creates an array with the results of calling #to_ary on the other array' do + expect(described_class.new(fake_other_array)).to eq [:hello, :world] + end + end + end + + context 'concurrency' do + it do + (1..Concurrent::ThreadSafe::Test::THREADS).map do |i| + in_thread(ary) do |ary| + 1000.times do + ary << i + ary.each { |x| x * 2 } + ary.shift + ary.last + end + end + end.map(&:join) + expect(ary).to be_empty + end + end + + describe '#slice' do + # This is mostly relevant on TruffleRuby + it 'correctly initializes the monitor' do + ary.concat([0, 1, 2, 3, 4, 5, 6, 7, 8]) + + sliced = ary.slice!(0..2) + expect { sliced[0] }.not_to raise_error + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/async_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/async_spec.rb new file mode 100644 index 0000000000..c0ebfe8e63 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/async_spec.rb @@ -0,0 +1,314 @@ +require 'concurrent/async' + +module Concurrent + RSpec.describe Async do + + let(:async_class) do + Class.new do + include Concurrent::Async + attr_accessor :accessor + def initialize(*args) + end + def echo(msg) + msg + end + def gather(first, second = nil) + return first, second + end + def boom(ex = StandardError.new) + raise ex + end + def wait(seconds) + sleep(seconds) + end + def with_block + yield + end + end + end + + subject do + async_class.new + end + + context 'object creation' do + + it 'delegates to the original constructor' do + args = [:foo, 'bar', 42] + expect(async_class).to receive(:original_new).once.with(*args).and_call_original + async_class.new(*args) + end + + specify 'passes all args to the original constructor' do + clazz = Class.new do + include Concurrent::Async + attr_reader :args + def initialize(*args) + @args = args + end + end + + object = clazz.new(:foo, :bar) + expect(object.args).to eq [:foo, :bar] + end + + specify 'passes a given block to the original constructor' do + clazz = Class.new do + include Concurrent::Async + attr_reader :block + def initialize(&block) + @block = yield + end + end + + object = clazz.new{ 42 } + expect(object.block).to eq 42 + end + + specify 'initializes synchronization' do + mock = async_class.new + allow(async_class).to receive(:original_new).and_return(mock) + expect(mock).to receive(:init_synchronization).once.with(no_args) + async_class.new + end + end + + context '#validate_argc' do + + subject do + Class.new { + def zero() nil; end + def three(a, b, c, &block) nil; end + def two_plus_two(a, b, c=nil, d=nil, &block) nil; end + def many(*args, &block) nil; end + }.new + end + + it 'raises an exception when the method is not defined' do + expect { + Async::validate_argc(subject, :bogus) + }.to raise_error(StandardError) + end + + it 'raises an exception for too many args on a zero arity method' do + expect { + Async::validate_argc(subject, :zero, 1, 2, 3) + }.to raise_error(ArgumentError) + end + + it 'does not raise an exception for correct zero arity' do + expect { + Async::validate_argc(subject, :zero) + }.not_to raise_error + end + + it 'raises an exception for too many args on a method with positive arity' do + expect { + Async::validate_argc(subject, :three, 1, 2, 3, 4) + }.to raise_error(ArgumentError) + end + + it 'raises an exception for too few args on a method with positive arity' do + expect { + Async::validate_argc(subject, :three, 1, 2) + }.to raise_error(ArgumentError) + end + + it 'does not raise an exception for correct positive arity' do + expect { + Async::validate_argc(subject, :three, 1, 2, 3) + }.not_to raise_error + end + + it 'raises an exception for too few args on a method with negative arity' do + expect { + Async::validate_argc(subject, :two_plus_two, 1) + }.to raise_error(ArgumentError) + end + + it 'does not raise an exception for correct negative arity' do + expect { + Async::validate_argc(subject, :two_plus_two, 1, 2) + Async::validate_argc(subject, :two_plus_two, 1, 2, 3, 4) + Async::validate_argc(subject, :two_plus_two, 1, 2, 3, 4, 5, 6) + + Async::validate_argc(subject, :many) + Async::validate_argc(subject, :many, 1, 2) + Async::validate_argc(subject, :many, 1, 2, 3, 4) + }.not_to raise_error + end + end + + context '#async' do + + it 'raises an error when calling a method that does not exist' do + expect { + subject.async.bogus + }.to raise_error(StandardError) + end + + it 'raises an error when passing too few arguments' do + expect { + subject.async.gather + }.to raise_error(ArgumentError) + end + + it 'raises an error when pasing too many arguments (arity >= 0)' do + expect { + subject.async.echo(1, 2, 3, 4, 5) + }.to raise_error(StandardError) + end + + it 'returns the existence of the method' do + expect(subject.async.respond_to?(:echo)).to be_truthy + expect(subject.async.respond_to?(:not_exist_method)).to be_falsy + end + + it 'returns a :pending IVar' do + val = subject.async.wait(1) + expect(val).to be_a Concurrent::IVar + expect(val).to be_pending + end + + it 'runs the future on the global executor' do + expect(Concurrent.global_io_executor).to receive(:post).with(any_args). + and_call_original + subject.async.echo(:foo) + end + + it 'sets the value on success' do + val = subject.async.echo(:foo) + expect(val.value).to eq :foo + expect(val).to be_fulfilled + end + + it 'sets the reason on failure' do + ex = ArgumentError.new + val = subject.async.boom(ex) + val.wait + expect(val.reason).to eq ex + expect(val).to be_rejected + end + + it 'sets the reason when giving too many optional arguments' do + val = subject.async.gather(1, 2, 3, 4, 5) + val.wait + expect(val.reason).to be_a StandardError + expect(val).to be_rejected + end + + it 'supports attribute accessors' do + subject.async.accessor = :foo + val = subject.async.accessor + expect(val.value).to eq :foo + expect(subject.accessor).to eq :foo + end + + it 'supports methods with blocks' do + val = subject.async.with_block{ :foo } + expect(val.value).to eq :foo + end + end + + context '#await' do + + it 'raises an error when calling a method that does not exist' do + expect { + subject.await.bogus + }.to raise_error(StandardError) + end + + it 'raises an error when passing too few arguments' do + expect { + subject.await.gather + }.to raise_error(ArgumentError) + end + + it 'raises an error when pasing too many arguments (arity >= 0)' do + expect { + subject.await.echo(1, 2, 3, 4, 5) + }.to raise_error(StandardError) + end + + it 'returns the existence of the method' do + expect(subject.await.respond_to?(:echo)).to be_truthy + expect(subject.await.respond_to?(:not_exist_method)).to be_falsy + end + + it 'returns a :fulfilled IVar' do + val = subject.await.echo(5) + expect(val).to be_a Concurrent::IVar + expect(val).to be_fulfilled + end + + it 'runs the future on the global executor' do + expect(Concurrent.global_io_executor).to receive(:post).with(any_args). + and_call_original + subject.await.echo(:foo) + end + + it 'sets the value on success' do + val = subject.await.echo(:foo) + expect(val.value).to eq :foo + expect(val).to be_fulfilled + end + + it 'sets the reason on failure' do + ex = ArgumentError.new + val = subject.await.boom(ex) + expect(val.reason).to eq ex + expect(val).to be_rejected + end + + it 'sets the reason when giving too many optional arguments' do + val = subject.await.gather(1, 2, 3, 4, 5) + expect(val.reason).to be_a StandardError + expect(val).to be_rejected + end + + it 'supports attribute accessors' do + subject.await.accessor = :foo + val = subject.await.accessor + expect(val.value).to eq :foo + expect(subject.accessor).to eq :foo + end + + it 'supports methods with blocks' do + val = subject.await.with_block{ :foo } + expect(val.value).to eq :foo + end + end + + context 'locking' do + + it 'uses the same lock for both #async and #await' do + object = Class.new { + include Concurrent::Async + attr_reader :bucket + def gather(seconds, first, *rest) + sleep(seconds) + (@bucket ||= []).concat([first]) + @bucket.concat(rest) + end + }.new + + object.async.gather(0.5, :a, :b) + object.await.gather(0, :c, :d) + expect(object.bucket).to eq [:a, :b, :c, :d] + end + end + + context 'fork safety' do + it 'does not hang when forked' do + skip "Platform does not support fork" unless Process.respond_to?(:fork) + object = Class.new { + include Concurrent::Async + def foo; end + }.new + object.async.foo + _, status = Process.waitpid2(fork {object.await.foo}) + expect(status.exitstatus).to eq 0 + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atom_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atom_spec.rb new file mode 100644 index 0000000000..4f4b43f534 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atom_spec.rb @@ -0,0 +1,210 @@ +require_relative 'concern/observable_shared' +require 'concurrent/atom' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/atomic_fixnum' + +module Concurrent + + RSpec.describe Atom do + + context 'construction' do + + it 'sets the initial value to the given value' do + atom = Atom.new(42) + expect(atom.value).to eq 42 + end + end + + context '#compare_and_set' do + + it 'sets the new value if the current value matches' do + atom = Atom.new(42) + atom.compare_and_set(42, :foo) + expect(atom.value).to eq :foo + end + + it 'returns true if the current value matches' do + atom = Atom.new(42) + expect(atom.compare_and_set(42, :foo)).to be true + end + + it 'rejects the new value if the current value does not match' do + atom = Atom.new(42) + atom.compare_and_set(:foo, 'bar') + expect(atom.value).to eq 42 + end + + it 'returns false if the current value does not match' do + atom = Atom.new(42) + expect(atom.compare_and_set(:foo, 'bar')).to be false + end + + it 'rejects the new value if the validator returns false' do + validator = ->(value){ false } + atom = Atom.new(42, validator: validator) + atom.compare_and_set(42, :foo) + expect(atom.value).to eq 42 + end + + it 'rejects the new value if the validator raises an exception' do + validator = ->(value){ raise StandardError } + atom = Atom.new(42, validator: validator) + atom.compare_and_set(42, :foo) + expect(atom.value).to eq 42 + end + + it 'returns false if the validator returns false' do + validator = ->(value){ false } + atom = Atom.new(42, validator: validator) + expect(atom.compare_and_set(42, :foo)).to be false + end + + it 'returns false if the validator raises an exception' do + validator = ->(value){ raise StandardError } + atom = Atom.new(42, validator: validator) + expect(atom.compare_and_set(42, :foo)).to be false + end + end + + context '#swap' do + + it 'raises an exception when no block is given' do + atom = Atom.new(42) + expect { + atom.swap + }.to raise_error(ArgumentError) + end + + it 'passes the current value to the block' do + actual = nil + expected = 42 + atom = Atom.new(expected) + atom.swap do |value| + actual = value + end + expect(actual).to eq expected + end + + it 'passes all arguments to the block' do + actual = nil + expected = [1, 2, 3] + atom = Atom.new(42) + atom.swap(*expected) do |value, *args| + actual = args + end + expect(actual).to eq expected + end + + it 'sets the new value to the result of the block' do + atom = Atom.new(42) + atom.swap{ :foo } + expect(atom.value).to eq :foo + end + + it 'rejects the new value if the validator returns false' do + validator = ->(value){ false } + atom = Atom.new(42, validator: validator) + atom.swap{ 100 } + expect(atom.value).to eq 42 + end + + it 'rejects the new value if the validator raises an exception' do + validator = ->(value){ raise StandardError } + atom = Atom.new(42, validator: validator) + atom.swap{ 100 } + expect(atom.value).to eq 42 + end + + it 'returns the new value on success' do + atom = Atom.new(42) + expect(atom.swap{ :foo }).to eq :foo + end + + it 'returns the old value if the validator returns false' do + validator = ->(value){ false } + atom = Atom.new(42, validator: validator) + expect(atom.swap{ 100 }).to eq 42 + end + + it 'returns the old value if the validator raises an exception' do + validator = ->(value){ raise StandardError } + atom = Atom.new(42, validator: validator) + expect(atom.swap{ 100 }).to eq 42 + end + + it 'calls the block more than once if the value changes underneath' do + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + counter = Concurrent::AtomicFixnum.new(0) + atom = Atom.new(0) + + t = in_thread do + atom.swap do |value| + latch1.count_down + latch2.wait(1) + counter.increment + 42 + end + end + + latch1.wait(1) + atom.swap{ 100 } + latch2.count_down + t.join(1) + + expect(counter.value).to be > 1 + end + + it 'reraises the exception from block' do + atom = Atom.new(0) + expect do + atom.swap do |value| + fail 'something went wrong' + end + end.to raise_error 'something went wrong' + end + end + + context '#reset' do + + it 'sets the new value' do + atom = Atom.new(42) + atom.reset(:foo) + expect(atom.value).to eq :foo + end + + it 'returns the new value on success' do + atom = Atom.new(42) + expect(atom.reset(:foo)).to eq :foo + end + + it 'returns the new value on success' do + atom = Atom.new(42) + expect(atom.reset(:foo)).to eq :foo + end + + it 'returns the old value if the validator returns false' do + validator = ->(value){ false } + atom = Atom.new(42, validator: validator) + expect(atom.reset(:foo)).to eq 42 + end + + it 'returns the old value if the validator raises an exception' do + validator = ->(value){ raise StandardError } + atom = Atom.new(42, validator: validator) + expect(atom.reset(:foo)).to eq 42 + end + end + + context :observable do + + subject { Atom.new(0) } + + def trigger_observable(observable) + observable.reset(42) + end + + it_behaves_like :observable + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_boolean_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_boolean_spec.rb new file mode 100644 index 0000000000..133b7f2ceb --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_boolean_spec.rb @@ -0,0 +1,186 @@ +require 'concurrent/atomic/atomic_boolean' + +RSpec.shared_examples :atomic_boolean do + + describe 'construction' do + + it 'sets the initial value' do + expect(described_class.new(true).value).to be true + end + + it 'defaults the initial value to false' do + expect(described_class.new.value).to be false + end + + it 'evaluates the truthiness of a true value' do + expect(described_class.new(10).value).to be true + end + + it 'evaluates the truthiness of a false value' do + expect(described_class.new(nil).value).to be false + end + end + + describe '#value' do + + it 'returns the current value' do + counter = described_class.new(true) + expect(counter.value).to be true + counter.make_false + expect(counter.value).to be false + counter.make_true + expect(counter.value).to be true + end + end + + describe '#value=' do + + it 'sets the #value to the given `Boolean`' do + atomic = described_class.new(true) + atomic.value = false + expect(atomic.value).to be false + end + + it 'returns the new value' do + atomic = described_class.new(false) + expect(atomic.value = true).to be true + end + + it 'evaluates the truthiness of a true value' do + atomic = described_class.new(false) + atomic.value = 10 + expect(atomic.value).to be true + end + + it 'evaluates the truthiness of a false value' do + atomic = described_class.new(true) + atomic.value = nil + expect(atomic.value).to be false + end + end + + describe '#true?' do + + specify { expect(described_class.new(true).true?).to be true } + + specify { expect(described_class.new(false).true?).to be false } + end + + describe '#false?' do + + specify { expect(described_class.new(true).false?).to be false } + + specify { expect(described_class.new(false).false?).to be true } + end + + describe '#make_true' do + + it 'makes a false value true and returns true' do + subject = described_class.new(false) + expect(subject.make_true).to be true + expect(subject.value).to be true + end + + it 'keeps a true value true and returns false' do + subject = described_class.new(true) + expect(subject.make_true).to be false + expect(subject.value).to be true + end + end + + describe '#make_false' do + + it 'makes a true value false and returns true' do + subject = described_class.new(true) + expect(subject.make_false).to be true + expect(subject.value).to be false + end + + it 'keeps a false value false and returns false' do + subject = described_class.new(false) + expect(subject.make_false).to be false + expect(subject.value).to be false + end + end +end + +module Concurrent + + RSpec.describe MutexAtomicBoolean do + + it_should_behave_like :atomic_boolean + + context 'instance methods' do + + before(:each) do + expect(subject).to receive(:synchronize).with(no_args).and_return(true) + end + + specify 'value is synchronized' do + subject.value + end + + specify 'value= is synchronized' do + subject.value = 10 + end + + specify 'true? is synchronized' do + subject.true? + end + + specify 'false? is synchronized' do + subject.false? + end + + specify 'make_true is synchronized' do + subject.make_true + end + + specify 'make_false is synchronized' do + subject.make_false + end + end + end + + if Concurrent.allow_c_extensions? + RSpec.describe CAtomicBoolean do + it_should_behave_like :atomic_boolean + end + end + + if Concurrent.on_jruby? + RSpec.describe JavaAtomicBoolean do + it_should_behave_like :atomic_boolean + end + end + + RSpec.describe AtomicBoolean do + if RUBY_ENGINE != 'ruby' + it 'does not load the C extension' do + expect(defined?(Concurrent::CAtomicBoolean)).to be_falsey + end + end + + if Concurrent.on_jruby? + it 'inherits from JavaAtomicBoolean' do + expect(AtomicBoolean.ancestors).to include(JavaAtomicBoolean) + end + elsif Concurrent.allow_c_extensions? + it 'inherits from CAtomicBoolean' do + expect(AtomicBoolean.ancestors).to include(CAtomicBoolean) + end + else + it 'inherits from MutexAtomicBoolean' do + expect(AtomicBoolean.ancestors).to include(MutexAtomicBoolean) + end + end + + describe '#to_s and #inspect' do + it 'includes the value' do + subject = described_class.new(true) + expect(subject.to_s).to include('true') + expect(subject.inspect).to include('true') + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_fixnum_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_fixnum_spec.rb new file mode 100644 index 0000000000..39259b13fe --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_fixnum_spec.rb @@ -0,0 +1,249 @@ +require 'concurrent/atomic/atomic_fixnum' + +RSpec.shared_examples :atomic_fixnum do + + context 'construction' do + + it 'sets the initial value' do + expect(described_class.new(10).value).to eq 10 + end + + it 'defaults the initial value to zero' do + expect(described_class.new.value).to eq 0 + end + + it 'raises an exception if the initial value is not a Fixnum' do + expect { described_class.new(10.01) }.to(raise_error { |error| + expect(error.class).to be(ArgumentError).or(be(TypeError)) + }) + end + end + + context '#value' do + + it 'returns the current value' do + counter = described_class.new(10) + expect(counter.value).to eq 10 + counter.increment + expect(counter.value).to eq 11 + counter.decrement + expect(counter.value).to eq 10 + end + end + + context '#value=' do + + it 'sets the #value to the given `Fixnum`' do + atomic = described_class.new(0) + atomic.value = 10 + expect(atomic.value).to eq 10 + end + + it 'returns the new value' do + atomic = described_class.new(0) + expect(atomic.value = 10).to eq 10 + end + + it 'raises and exception if the value is not a `Fixnum`' do + atomic = described_class.new(0) + expect { + atomic.value = 'foo' + }.to(raise_error { |error| + expect(error.class).to be(ArgumentError).or(be(TypeError)) + }) + end + end + + context '#increment' do + + it 'increases the value by one when no argument is given' do + counter = described_class.new(10) + 3.times{ counter.increment } + expect(counter.value).to eq 13 + end + + it 'returns the new value when no argument is given' do + counter = described_class.new(10) + expect(counter.increment).to eq 11 + end + + it 'increases the value by the given argument' do + counter = described_class.new(10) + counter.increment(5) + expect(counter.value).to eq 15 + end + + it 'returns the new value the given argument' do + counter = described_class.new(10) + expect(counter.increment(5)).to eq 15 + end + + it 'is aliased as #up' do + expect(described_class.new(10).up).to eq 11 + end + end + + context '#decrement' do + + it 'decreases the value by one when no argument is given' do + counter = described_class.new(10) + 3.times{ counter.decrement } + expect(counter.value).to eq 7 + end + + it 'returns the new value when no argument is given' do + counter = described_class.new(10) + expect(counter.decrement).to eq 9 + end + + it 'decreases the value by the given argument' do + counter = described_class.new(10) + counter.decrement(5) + expect(counter.value).to eq 5 + end + + it 'returns the new value the given argument' do + counter = described_class.new(10) + expect(counter.decrement(5)).to eq 5 + end + + it 'is aliased as #down' do + expect(described_class.new(10).down).to eq 9 + end + end + + context '#compare_and_set' do + + it 'returns false if the value is not found' do + expect(described_class.new(14).compare_and_set(2, 14)).to eq false + end + + it 'returns true if the value is found' do + expect(described_class.new(14).compare_and_set(14, 2)).to eq true + end + + it 'sets if the value is found' do + f = described_class.new(14) + f.compare_and_set(14, 2) + expect(f.value).to eq 2 + end + + it 'does not set if the value is not found' do + f = described_class.new(14) + f.compare_and_set(2, 12) + expect(f.value).to eq 14 + end + end + + context '#update' do + + it 'passes the current value to the block' do + atomic = described_class.new(1000) + atomic.update { |v| (expect(v).to eq 1000); 1 } + end + + it 'atomically sets the value to the return value from the block' do + atomic = described_class.new(1000) + atomic.update { |v| v + 1 } + expect(atomic.value).to eq 1001 + end + + it 'returns the new value' do + atomic = described_class.new(1000) + expect(atomic.update { |v| v + 1 }).to eq 1001 + end + end +end + +module Concurrent + + RSpec.describe MutexAtomicFixnum do + + it_should_behave_like :atomic_fixnum + + context 'construction' do + + it 'raises an exception if the initial value is too big' do + expect { + described_class.new(Utility::NativeInteger::MAX_VALUE + 1) + }.to raise_error(RangeError) + end + + it 'raises an exception if the initial value is too small' do + expect { + described_class.new(Utility::NativeInteger::MIN_VALUE - 1) + }.to raise_error(RangeError) + end + end + + context 'instance methods' do + + before(:each) do + expect(subject).to receive(:synchronize).with(no_args).and_call_original + end + + specify 'value is synchronized' do + subject.value + end + + specify 'value= is synchronized' do + subject.value = 10 + end + + specify 'increment is synchronized' do + subject.increment + end + + specify 'decrement is synchronized' do + subject.decrement + end + + specify 'compare_and_set is synchronized' do + subject.compare_and_set(14, 2) + end + end + end + + if Concurrent.allow_c_extensions? + RSpec.describe CAtomicFixnum do + it_should_behave_like :atomic_fixnum + end + end + + if Concurrent.on_jruby? + RSpec.describe JavaAtomicFixnum do + it_should_behave_like :atomic_fixnum + end + end + + RSpec.describe AtomicFixnum do + if RUBY_ENGINE != 'ruby' + it 'does not load the C extension' do + expect(defined?(Concurrent::CAtomicFixnum)).to be_falsey + end + end + + if Concurrent.on_jruby? + it 'inherits from JavaAtomicFixnum' do + expect(AtomicFixnum.ancestors).to include(JavaAtomicFixnum) + end + elsif Concurrent.allow_c_extensions? + it 'inherits from CAtomicFixnum' do + expect(AtomicFixnum.ancestors).to include(CAtomicFixnum) + end + else + it 'inherits from MutexAtomicFixnum' do + expect(AtomicFixnum.ancestors).to include(MutexAtomicFixnum) + end + end + + describe '#to_s and #inspect' do + it 'includes the value' do + subject = described_class.new(42) + expect(subject.to_s).to include('42') + expect(subject.inspect).to include('42') + end + end + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_markable_reference_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_markable_reference_spec.rb new file mode 100644 index 0000000000..fb1b04af98 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_markable_reference_spec.rb @@ -0,0 +1,154 @@ +require 'concurrent/atomic/atomic_markable_reference' + +RSpec.describe Concurrent::AtomicMarkableReference do + subject { described_class.new 1000, true } + + describe '.initialize' do + it 'constructs the object' do + expect(subject.value).to eq 1000 + expect(subject.marked?).to eq true + end + + it 'has sane defaults' do + amr = described_class.new + + expect(amr.value).to eq nil + expect(amr.marked?).to eq false + end + end + + describe '#set' do + it 'sets the value and mark' do + val, mark = subject.set 1001, true + + expect(subject.value).to eq 1001 + expect(subject.marked?).to eq true + expect(val).to eq 1001 + expect(mark).to eq true + end + end + + describe '#try_update!' do + it 'updates the value and mark' do + val, mark = subject.try_update! { |v, m| [v + 1, !m] } + + expect(subject.value).to eq 1001 + expect(val).to eq 1001 + expect(mark).to eq false + end + + it 'raises ConcurrentUpdateError when attempting to set inside of block' do + expect do + subject.try_update! do |v, m| + subject.set(1001, false) + [v + 1, !m] + end + end.to raise_error Concurrent::ConcurrentUpdateError + end + end + + describe '#try_update' do + it 'updates the value and mark' do + val, mark = subject.try_update { |v, m| [v + 1, !m] } + + expect(subject.value).to eq 1001 + expect(val).to eq 1001 + expect(mark).to eq false + end + + it 'returns nil when attempting to set inside of block' do + expect do + subject.try_update do |v, m| + subject.set(1001, false) + [v + 1, !m] + end.to eq nil + end + end + end + + describe '#update' do + it 'updates the value and mark' do + val, mark = subject.update { |v, m| [v + 1, !m] } + + expect(subject.value).to eq 1001 + expect(subject.marked?).to eq false + + expect(val).to eq 1001 + expect(mark).to eq false + end + + it 'retries until update succeeds' do + tries = 0 + + subject.update do |v, m| + tries += 1 + subject.set(1001, false) + [v + 1, !m] + end + + expect(tries).to eq 2 + end + end + + describe '#compare_and_set' do + context 'when objects have the same identity' do + it 'sets the value and mark' do + arr = [1, 2, 3] + subject.set(arr, true) + expect(subject.compare_and_set(arr, 1.2, true, false)).to be_truthy + end + end + + context 'when objects have the different identity' do + it 'it does not set the value or mark' do + subject.set([1, 2, 3], true) + expect(subject.compare_and_set([1, 2, 3], 1.2, true, false)) + .to be_falsey + end + + context 'when comparing Numeric objects' do + context 'Non-idepotent Float' do + it 'sets the value and mark' do + subject.set(1.0 + 0.1, true) + expect(subject.compare_and_set(1.0 + 0.1, 1.2, true, false)) + .to be_truthy + end + end + + context 'BigNum' do + it 'sets the value and mark' do + subject.set(2**100, false) + expect(subject.compare_and_set(2**100, 2**99, false, true)) + .to be_truthy + end + end + + context 'Rational' do + it 'sets the value and mark' do + require 'rational' unless ''.respond_to? :to_r + subject.set(Rational(1, 3), true) + comp = subject.compare_and_set(Rational(1, 3), + Rational(3, 1), + true, + false) + expect(comp).to be_truthy + end + end + end + + context 'Rational' do + it 'is successful' do + # Complex + require 'complex' unless ''.respond_to? :to_c + subject.set(Complex(1, 2), false) + comp = subject.compare_and_set(Complex(1, 2), + Complex(1, 3), + false, + true) + expect(comp) + .to be_truthy + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_reference_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_reference_spec.rb new file mode 100644 index 0000000000..2cc0caada5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/atomic_reference_spec.rb @@ -0,0 +1,207 @@ +require 'concurrent/atomic/atomic_reference' + +RSpec.shared_examples :atomic_reference do + + specify :test_construct do + atomic = described_class.new + expect(atomic.value).to be_nil + + atomic = described_class.new(0) + expect(atomic.value).to eq 0 + end + + specify :test_value do + atomic = described_class.new(0) + atomic.value = 1 + + expect(atomic.value).to eq 1 + end + + specify :test_update do + # use a number outside JRuby's fixnum cache range, to ensure identity is preserved + atomic = described_class.new(1000) + res = atomic.update { |v| v + 1 } + + expect(atomic.value).to eq 1001 + expect(res).to eq 1001 + end + + specify :test_try_update do + # use a number outside JRuby's fixnum cache range, to ensure identity is preserved + atomic = described_class.new(1000) + res = atomic.try_update { |v| v + 1 } + + expect(atomic.value).to eq 1001 + expect(res).to eq 1001 + end + + specify :test_try_update_bang do + # use a number outside JRuby's fixnum cache range, to ensure identity is preserved + atomic = described_class.new(1000) + res = atomic.try_update! { |v| v + 1 } + + expect(atomic.value).to eq 1001 + expect(res).to eq 1001 + end + + specify :test_swap do + atomic = described_class.new(1000) + res = atomic.swap(1001) + + expect(atomic.value).to eq 1001 + expect(res).to eq 1000 + end + + specify :test_try_update_fails do + # use a number outside JRuby's fixnum cache range, to ensure identity is preserved + atomic = described_class.new(1000) + expect( + # assigning within block exploits implementation detail for test + atomic.try_update { |v| atomic.value = 1001; v + 1 } + ).to be_falsey + end + + specify :test_try_update_bang_fails do + # use a number outside JRuby's fixnum cache range, to ensure identity is preserved + atomic = described_class.new(1000) + expect { + # assigning within block exploits implementation detail for test + atomic.try_update! { |v| atomic.value = 1001; v + 1 } + }.to raise_error Concurrent::ConcurrentUpdateError + end + + specify :test_update_retries do + tries = 0 + # use a number outside JRuby's fixnum cache range, to ensure identity is preserved + atomic = described_class.new(1000) + # assigning within block exploits implementation detail for test + atomic.update { |v| tries += 1; atomic.value = 1001; v + 1 } + + expect(tries).to eq 2 + end + + specify :test_numeric_cas do + atomic = described_class.new(0) + + # 9-bit idempotent Fixnum (JRuby) + max_8 = 2 ** 256 - 1 + min_8 = -(2 ** 256) + + atomic.set(max_8) + max_8.upto(max_8 + 2) do |i| + expect(atomic.compare_and_swap(i, i + 1)).to be_truthy, "CAS failed for numeric #{i} => #{i + 1}" + end + + atomic.set(min_8) + min_8.downto(min_8 - 2) do |i| + expect(atomic.compare_and_swap(i, i - 1)).to be_truthy, "CAS failed for numeric #{i} => #{i - 1}" + end + + # 64-bit idempotent Fixnum (MRI, TruffleRuby) + max_64 = 2 ** 62 - 1 + min_64 = -(2 ** 62) + + atomic.set(max_64) + max_64.upto(max_64 + 2) do |i| + expect(atomic.compare_and_swap(i, i + 1)).to be_truthy, "CAS failed for numeric #{i} => #{i + 1}" + end + + atomic.set(min_64) + min_64.downto(min_64 - 2) do |i| + expect(atomic.compare_and_swap(i, i - 1)).to be_truthy, "CAS failed for numeric #{i} => #{i - 1}" + end + + ## 64-bit overflow into Bignum (JRuby) + max_64 = 2 ** 63 - 1 + min_64 = (-2 ** 63) + + atomic.set(max_64) + max_64.upto(max_64 + 2) do |i| + expect(atomic.compare_and_swap(i, i + 1)).to be_truthy, "CAS failed for numeric #{i} => #{i + 1}" + end + + atomic.set(min_64) + min_64.downto(min_64 - 2) do |i| + expect(atomic.compare_and_swap(i, i - 1)).to be_truthy, "CAS failed for numeric #{i} => #{i - 1}" + end + + # non-idempotent Float (JRuby, MRI < 2.0.0 or 32-bit) + atomic.set(1.0 + 0.1) + expect(atomic.compare_and_set(1.0 + 0.1, 1.2)).to be_truthy, "CAS failed for #{1.0 + 0.1} => 1.2" + + # Bignum + atomic.set(2 ** 100) + expect(atomic.compare_and_set(2 ** 100, 0)).to be_truthy, "CAS failed for #{2 ** 100} => 0" + + # Rational + require 'rational' unless ''.respond_to? :to_r + atomic.set(Rational(1, 3)) + expect(atomic.compare_and_set(Rational(1, 3), 0)).to be_truthy, "CAS failed for #{Rational(1, 3)} => 0" + + # Complex + require 'complex' unless ''.respond_to? :to_c + atomic.set(Complex(1, 2)) + expect(atomic.compare_and_set(Complex(1, 2), 0)).to be_truthy, "CAS failed for #{Complex(1, 2)} => 0" + end +end + +module Concurrent + + RSpec.describe AtomicReference do + it_should_behave_like :atomic_reference + + describe '#to_s and #inspect' do + it 'includes the value' do + subject = described_class.new('kajhsd') + expect(subject.to_s).to include('kajhsd') + expect(subject.inspect).to include('kajhsd') + end + end + end + + RSpec.describe MutexAtomicReference do + it_should_behave_like :atomic_reference + end + + if Concurrent.allow_c_extensions? + RSpec.describe CAtomicReference do + it_should_behave_like :atomic_reference + end + end + if Concurrent.on_jruby? + RSpec.describe JavaAtomicReference do + it_should_behave_like :atomic_reference + end + end + if Concurrent.on_truffleruby? + RSpec.describe TruffleRubyAtomicReference do + it_should_behave_like :atomic_reference + end + end + + RSpec.describe AtomicReference do + if RUBY_ENGINE != 'ruby' + it 'does not load the C extension' do + expect(defined?(Concurrent::CAtomicReference)).to be_falsey + end + end + + if Concurrent.on_jruby? + it 'inherits from JavaAtomicReference' do + expect(described_class.ancestors).to include(Concurrent::JavaAtomicReference) + end + elsif Concurrent.allow_c_extensions? + it 'inherits from CAtomicReference' do + expect(described_class.ancestors).to include(Concurrent::CAtomicReference) + end + elsif Concurrent.on_truffleruby? + it 'inherits from TruffleRubyAtomicReference' do + expect(described_class.ancestors).to include(Concurrent::TruffleRubyAtomicReference) + end + else + it 'inherits from MutexAtomicReference' do + expect(described_class.ancestors).to include(Concurrent::MutexAtomicReference) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/count_down_latch_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/count_down_latch_spec.rb new file mode 100644 index 0000000000..5e041ff89c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/count_down_latch_spec.rb @@ -0,0 +1,179 @@ +require 'concurrent/atomic/count_down_latch' + +RSpec.shared_examples :count_down_latch do + + let(:latch) { described_class.new(3) } + let(:zero_count_latch) { described_class.new(0) } + + context '#initialize' do + + it 'raises an exception if the initial count is less than zero' do + expect { + described_class.new(-1) + }.to raise_error(ArgumentError) + end + + it 'raises an exception if the initial count is not an integer' do + expect { + described_class.new('foo') + }.to raise_error(ArgumentError) + end + + it 'defaults the count to 1' do + latch = described_class.new + expect(latch.count).to eq 1 + end + end + + describe '#count' do + + it 'should be the value passed to the constructor' do + expect(latch.count).to eq 3 + end + + it 'should be decreased after every count down' do + latch.count_down + expect(latch.count).to eq 2 + end + + it 'should not go below zero' do + 5.times { latch.count_down } + expect(latch.count).to eq 0 + end + end + + describe '#wait' do + + it 'blocks indefinitely, and is kill-able' do + t = in_thread(latch) { |l| l.wait } + is_sleeping t + end + + it 'blocks indefinitely with timeout, and is kill-able' do + t = in_thread(latch) { |l| l.wait 100 } + is_sleeping t + end + + context 'count set to zero' do + it 'should return true immediately' do + result = zero_count_latch.wait + expect(result).to be_truthy + end + + it 'should return true immediately with timeout' do + result = zero_count_latch.wait(5) + expect(result).to be_truthy + end + end + + context 'non zero count' do + + it 'should block thread until counter is set to zero' do + 3.times do + in_thread { sleep(0.1); latch.count_down } + end + + result = latch.wait + expect(result).to be_truthy + expect(latch.count).to eq 0 + end + + it 'should block until counter is set to zero with timeout' do + 3.times do + in_thread { sleep(0.1); latch.count_down } + end + + result = latch.wait(1) + expect(result).to be_truthy + expect(latch.count).to eq 0 + end + + it 'should block until timeout and return false when counter is not set to zero' do + result = latch.wait(0.1) + expect(result).to be_falsey + expect(latch.count).to eq 3 + end + end + end +end + +module Concurrent + + RSpec.describe MutexCountDownLatch do + + it_should_behave_like :count_down_latch + + context 'spurious wake ups' do + + subject { described_class.new(3) } + + before(:each) do + def subject.simulate_spurious_wake_up + synchronize do + ns_signal + ns_broadcast + end + end + end + + it 'should resist to spurious wake ups without timeout' do + latch = Concurrent::CountDownLatch.new(1) + expected = false + + t = in_thread do + latch.wait(1) + subject.wait + expected = true + end + + latch.count_down + t.join(0.1) + subject.simulate_spurious_wake_up + + t.join(0.1) + expect(expected).to be_falsey + end + + it 'should resist to spurious wake ups with timeout' do + start_latch = Concurrent::CountDownLatch.new(1) + finish_latch = Concurrent::CountDownLatch.new(1) + expected = false + + t = in_thread do + start_latch.wait(1) + subject.wait(0.5) + expected = true + finish_latch.count_down + end + + start_latch.count_down + t.join(0.1) + subject.simulate_spurious_wake_up + + t.join(0.1) + expect(expected).to be_falsey + + finish_latch.wait(1) + expect(expected).to be_truthy + end + end + end + + if Concurrent.on_jruby? + RSpec.describe JavaCountDownLatch do + it_should_behave_like :count_down_latch + end + end + + RSpec.describe CountDownLatch do + if Concurrent.on_jruby? + it 'inherits from JavaCountDownLatch' do + expect(CountDownLatch.ancestors).to include(JavaCountDownLatch) + end + else + it 'inherits from MutexCountDownLatch' do + expect(CountDownLatch.ancestors).to include(MutexCountDownLatch) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/cyclic_barrier_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/cyclic_barrier_spec.rb new file mode 100644 index 0000000000..3a2bb24260 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/cyclic_barrier_spec.rb @@ -0,0 +1,258 @@ +require 'concurrent/atomic/cyclic_barrier' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/atomic_fixnum' + +module Concurrent + + RSpec.describe CyclicBarrier do + + let(:parties) { 3 } + let!(:barrier) { described_class.new(3) } + + context '#initialize' do + + it 'raises an exception if the initial count is less than 1' do + expect { + described_class.new(0) + }.to raise_error(ArgumentError) + end + + it 'raises an exception if the initial count is not an integer' do + expect { + described_class.new('foo') + }.to raise_error(ArgumentError) + end + end + + describe '#parties' do + + it 'should be the value passed to the constructor' do + expect(barrier.parties).to eq 3 + end + + end + + describe '#number_waiting' do + context 'without any waiting thread' do + it 'should be equal to zero' do + expect(barrier.number_waiting).to eq 0 + end + end + + context 'with waiting threads' do + it 'should be equal to the waiting threads count' do + in_thread { barrier.wait } + in_thread { barrier.wait } + repeat_until_success { expect(barrier.number_waiting).to eq 2 } + end + end + end + + describe '#broken?' do + it 'should not be broken when created' do + expect(barrier.broken?).to eq false + end + + it 'should not be broken when reset is called without waiting thread' do + barrier.reset + expect(barrier.broken?).to eq false + end + end + + describe 'reset' do + it 'should release all waiting threads' do + start_latch = CountDownLatch.new(1) + continue_latch = CountDownLatch.new(1) + + in_thread do + start_latch.count_down + barrier.wait + continue_latch.count_down + end + + start_latch.wait(1) + barrier.reset + + expect(barrier).not_to be_broken + expect(barrier.number_waiting).to eq 0 + end + end + + describe '#wait' do + context 'without timeout' do + it 'should block the thread' do + t = in_thread { barrier.wait } + t.join(0.1) + + expect(t.status).to eq 'sleep' + end + + it 'should release all threads when their number matches the desired one' do + latch = CountDownLatch.new(parties) + + parties.times { in_thread { barrier.wait; latch.count_down } } + expect(latch.wait(1)).to be_truthy + expect(barrier.number_waiting).to eq 0 + expect(barrier).not_to be_broken + end + + it 'returns true when released' do + latch = CountDownLatch.new(parties) + + parties.times { in_thread { latch.count_down if barrier.wait == true } } + expect(latch.wait(1)).to be_truthy + end + + it 'executes the block once' do + counter = AtomicFixnum.new + barrier = described_class.new(parties) { counter.increment } + + latch = CountDownLatch.new(parties) + + parties.times { in_thread { latch.count_down if barrier.wait == true } } + expect(latch.wait(1)).to be_truthy + + expect(counter.value).to eq 1 + end + + it 'can be reused' do + first_latch = CountDownLatch.new(parties) + parties.times { in_thread { barrier.wait; first_latch.count_down } } + + latch = CountDownLatch.new(parties) + parties.times { in_thread { barrier.wait; latch.count_down } } + expect(latch.wait(1)).to be_truthy + end + + it 'return false if barrier has been reset' do + latch = CountDownLatch.new(1) + + t = in_thread { latch.count_down if barrier.wait == false } + t.join(0.1) + barrier.reset + expect(latch.wait(1)).to be_truthy + end + end + + context 'with timeout' do + context 'timeout not expiring' do + it 'should block the thread' do + t = in_thread { barrier.wait(1) } + t.join(0.1) + + expect(t.status).to eq 'sleep' + end + + it 'should release all threads when their number matches the desired one' do + latch = CountDownLatch.new(parties) + + parties.times { in_thread { barrier.wait(1); latch.count_down } } + expect(latch.wait(0.2)).to be_truthy + expect(barrier.number_waiting).to eq 0 + end + + it 'returns true when released' do + latch = CountDownLatch.new(parties) + + parties.times { in_thread { latch.count_down if barrier.wait(1) == true } } + expect(latch.wait(1)).to be_truthy + end + end + + context 'timeout expiring' do + + it 'returns false' do + latch = CountDownLatch.new(1) + + in_thread { latch.count_down if barrier.wait(0.1) == false } + expect(latch.wait(1)).to be_truthy + end + + it 'breaks the barrier and release all other threads' do + latch = CountDownLatch.new(2) + + in_thread { barrier.wait(0.1); latch.count_down } + in_thread { barrier.wait; latch.count_down } + + expect(latch.wait(1)).to be_truthy + expect(barrier).to be_broken + end + + it 'breaks the barrier and release all other threads 2' do + t1 = in_thread { barrier.wait(0.1) } + t2 = in_thread { barrier.wait(0.1) } + + [t1, t2].each(&:join) + + expect(barrier).to be_broken + end + + it 'does not execute the block on timeout' do + counter = AtomicFixnum.new + barrier = described_class.new(parties) { counter.increment } + + barrier.wait(0.1) + + expect(counter.value).to eq 0 + end + end + end + + context '#broken barrier' do + it 'should not accept new threads' do + t = in_thread { barrier.wait(0.01) } + join_with t + + expect(barrier).to be_broken + expect(barrier.wait).to be_falsey + end + + it 'can be reset' do + t = in_thread { barrier.wait(0.01) } + join_with t + + expect(barrier).to be_broken + + barrier.reset + + expect(barrier).not_to be_broken + end + end + end + + context 'spurious wake ups' do + + before(:each) do + def barrier.simulate_spurious_wake_up + synchronize do + ns_signal + ns_broadcast + end + end + end + + it 'should resist to spurious wake ups without timeout' do + @expected = false + t = in_thread { barrier.wait; @expected = true } + t.join(0.1) + + barrier.simulate_spurious_wake_up + + t.join(0.1) + expect(@expected).to be_falsey + end + + it 'should resist to spurious wake ups with timeout' do + @expected = false + t = in_thread { barrier.wait(0.5); @expected = true } + + t.join(0.1) + barrier.simulate_spurious_wake_up + + t.join(0.1) + expect(@expected).to be_falsey + + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/event_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/event_spec.rb new file mode 100644 index 0000000000..1aae227b87 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/event_spec.rb @@ -0,0 +1,186 @@ +require 'concurrent/atomic/event' +require 'concurrent/atomic/count_down_latch' + +module Concurrent + + RSpec.describe Event do + + subject{ Event.new } + + context '#initialize' do + + it 'sets the state to unset' do + expect(subject).not_to be_set + end + end + + context '#set?' do + + it 'returns true when the event has been set' do + subject.set + expect(subject).to be_set + end + + it 'returns false if the event is unset' do + expect(subject).not_to be_set + end + end + + context '#set' do + + it 'triggers the event' do + latch = CountDownLatch.new(1) + t = in_thread{ subject.wait.tap{ latch.count_down } } + t.join(0.1) + subject.set + expect(latch.wait(1)).to be true + end + + it 'sets the state to set' do + subject.set + expect(subject).to be_set + end + end + + context '#try?' do + + it 'triggers the event if not already set' do + subject.try? + expect(subject).to be_set + end + + it 'returns true if not previously set' do + expect(subject.try?).to be true + end + + it 'returns false if previously set' do + subject.set + expect(subject.try?).to be false + end + end + + context '#reset' do + + it 'does not change the state of an unset event' do + subject.reset + expect(subject).not_to be_set + end + + it 'does not trigger an unset event' do + latch = CountDownLatch.new(1) + in_thread{ subject.wait.tap{ latch.count_down } } + subject.reset + expect(latch.wait(0.1)).to be false + end + + it 'returns true when called on an unset event' do + expect(subject.reset).to be true + end + + it 'sets the state of a set event to unset' do + subject.set + expect(subject).to be_set + subject.reset + expect(subject).not_to be_set + end + + it 'returns true when called on a set event' do + subject.set + expect(subject).to be_set + expect(subject.reset).to be true + end + end + + context '#wait' do + + it 'returns immediately when the event has been set' do + subject.reset + latch = CountDownLatch.new(1) + subject.set + in_thread{ subject.wait(1000); latch.count_down } + expect(latch.wait(0.1)).to be true + end + + it 'returns true once the event is set' do + subject.set + expect(subject.wait).to be true + end + + it 'blocks indefinitely when the timer is nil' do + subject.reset + latch = CountDownLatch.new(1) + in_thread{ subject.wait.tap{ latch.count_down } } + expect(latch.wait(0.1)).to be false + subject.set + expect(latch.wait(0.1)).to be true + end + + it 'blocks indefinitely' do + in_thread{ subject.wait } + sleep 0.1 + end + + it 'stops waiting when the timer expires' do + subject.reset + latch = CountDownLatch.new(1) + in_thread{ subject.wait(0.2); latch.count_down } + expect(latch.wait(0.1)).to be false + expect(latch.wait).to be true + end + + it 'returns false when the timer expires' do + subject.reset + expect(subject.wait(1)).to be false + end + + it 'triggers multiple waiting threads' do + latch = CountDownLatch.new(5) + subject.reset + 5.times{ in_thread{ subject.wait; latch.count_down } } + subject.set + expect(latch.wait(0.2)).to be true + end + + it 'behaves appropriately if wait begins while #set is processing' do + subject = subject() + subject.reset + latch = CountDownLatch.new(5) + 5.times{ in_thread{ subject.wait(5) } } + subject.set + 5.times{ in_thread{ subject.wait; latch.count_down } } + expect(latch.wait(0.2)).to be true + end + end + + context 'spurious wake ups' do + + before(:each) do + def subject.simulate_spurious_wake_up + synchronize do + ns_signal + ns_broadcast + end + end + end + + it 'should resist to spurious wake ups without timeout' do + latch = CountDownLatch.new(1) + t = in_thread{ subject.wait.tap{ latch.count_down } } + t.join(0.1) + + subject.simulate_spurious_wake_up + expect(latch.wait(0.1)).to be false + end + + it 'should resist spurious wake ups with timeout' do + latch = CountDownLatch.new(1) + t = in_thread{ subject.wait(0.5); latch.count_down } + t.join(0.1) + + subject.simulate_spurious_wake_up + expect(latch.wait(0.1)).to be false + expect(latch.wait(1)).to be true + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/fiber_local_var_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/fiber_local_var_spec.rb new file mode 100644 index 0000000000..8c485fdba2 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/fiber_local_var_spec.rb @@ -0,0 +1,123 @@ +require 'concurrent/atomic/fiber_local_var' + +module Concurrent + + RSpec.describe FiberLocalVar do + + context '#initialize' do + + it 'can set an initial value' do + v = described_class.new(14) + expect(v.value).to eq 14 + end + + it 'sets nil as a default initial value' do + v = described_class.new + expect(v.value).to be_nil + end + + it 'sets the same initial value for all fibers' do + v = described_class.new(14) + f1 = in_fiber { v.value } + f2 = in_fiber { v.value } + expect(f1.resume).to eq 14 + expect(f2.resume).to eq 14 + end + + it 'can set a block to be called to get the initial value' do + v = described_class.new { 14 } + expect(v.value).to eq 14 + end + + context 'when attempting to set both an initial value and a block' do + it do + expect { described_class.new(14) { 14 } }.to raise_error(ArgumentError) + end + end + end + + context '#value' do + let(:v) { described_class.new(14) } + + it 'returns the current value' do + expect(v.value).to eq 14 + end + + it 'returns the value after modification' do + v.value = 2 + expect(v.value).to eq 2 + end + + context 'when using a block to initialize the value' do + it 'calls the block to initialize the value' do + block = proc { } + + expect(block).to receive(:call) + + v = described_class.new(&block) + v.value + end + + it 'sets the block return value as the current value' do + value = 13 + + v = described_class.new { value += 1 } + + v.value + expect(v.value).to be 14 + end + + it 'calls the block to initialize the value for each fiber' do + block = proc { } + + expect(block).to receive(:call).twice + + v = described_class.new(&block) + in_fiber { v.value }.resume + in_fiber { v.value }.resume + end + end + end + + context '#value=' do + let(:v) { described_class.new(14) } + + it 'sets a new value' do + v.value = 2 + expect(v.value).to eq 2 + end + + it 'returns the new value' do + expect(v.value = 2).to eq 2 + end + + it 'does not modify the initial value for other fibers' do + v.value = 2 + f = in_fiber { v.value } + expect(f.resume).to eq 14 + end + + it 'does not modify the value for other fibers' do + v.value = 3 + + f1 = in_fiber do + v.value = 1 + Fiber.yield + v.value + end + + f2 = in_fiber do + v.value = 2 + Fiber.yield + v.value + end + + f1.resume + f2.resume + + expect(f1.resume).to eq 1 + expect(f2.resume).to eq 2 + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/lock_local_var_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/lock_local_var_spec.rb new file mode 100644 index 0000000000..f134000475 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/lock_local_var_spec.rb @@ -0,0 +1,20 @@ +require 'concurrent/atomic/lock_local_var' + +module Concurrent + + RSpec.describe LockLocalVar do + mutex = Mutex.new + mutex_owned_per_thread = mutex.synchronize do + Fiber.new { mutex.owned? }.resume + end + + it "uses FiberLocalVar if Mutex is per Fiber", if: !mutex_owned_per_thread do + expect(LockLocalVar).to be(FiberLocalVar) + end + + it "uses ThreadLocalVar if Mutex is per Thread", if: mutex_owned_per_thread do + expect(LockLocalVar).to be(ThreadLocalVar) + end + end + +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/read_write_lock_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/read_write_lock_spec.rb new file mode 100644 index 0000000000..c2f6bd0344 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/read_write_lock_spec.rb @@ -0,0 +1,496 @@ +require 'concurrent/atomic/read_write_lock' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/atomic_boolean' + +module Concurrent + + RSpec.describe ReadWriteLock do + + context '#write_locked?' do + + it 'returns true when the write lock is held' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + + in_thread do + subject.with_write_lock do + latch_1.count_down + latch_2.wait(1) + end + end + + latch_1.wait(1) + expect(subject).to be_write_locked + latch_2.count_down + end + + it 'returns false when the write lock is not held' do + expect(subject).to_not be_write_locked + end + + it 'returns false when the write lock is not held but there are readers' do + latch = Concurrent::CountDownLatch.new(1) + + in_thread do + subject.with_read_lock do + latch.wait(1) + end + end + + expect(subject).to_not be_write_locked + latch.count_down + end + end + + context '#has_waiters?' do + + it 'returns false when no locks are held' do + expect(subject).to_not have_waiters + end + + it 'returns false when there are readers but no writers' do + latch = Concurrent::CountDownLatch.new(1) + + in_thread do + subject.with_read_lock do + latch.wait(1) + end + end + + expect(subject).to_not have_waiters + latch.count_down + end + + it 'returns true when the write lock is held and there are waiting readers' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + latch_3 = Concurrent::CountDownLatch.new(1) + + in_thread do + latch_1.wait(1) + subject.acquire_write_lock + latch_2.count_down + latch_3.wait(1) + subject.release_write_lock + end + + in_thread do + latch_2.wait(1) + subject.acquire_read_lock + subject.release_read_lock + end + + latch_1.count_down + latch_2.wait(1) + + expect(subject).to have_waiters + + latch_3.count_down + end + + it 'returns true when the write lock is held and there are waiting writers' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + latch_3 = Concurrent::CountDownLatch.new(1) + + t1 = in_thread do + latch_1.wait(1) + subject.acquire_write_lock + latch_2.count_down + latch_3.wait(1) + subject.release_write_lock + end + + t2 = in_thread do + latch_2.wait(1) + subject.acquire_write_lock + subject.release_write_lock + end + + latch_1.count_down + latch_2.wait(1) + + expect(subject).to have_waiters + + latch_3.count_down + + join_with [t1, t2] + end + end + + context '#with_read_lock' do + + it 'acquires the lock' do + expect(subject).to receive(:acquire_read_lock).with(no_args) + subject.with_read_lock { nil } + end + + it 'returns the value of the block operation' do + expected = 100 + actual = subject.with_read_lock { expected } + expect(actual).to eq expected + end + + it 'releases the lock' do + expect(subject).to receive(:release_read_lock).with(no_args) + subject.with_read_lock { nil } + end + + it 'raises an exception if no block is given' do + expect { + subject.with_read_lock + }.to raise_error(ArgumentError) + end + + it 'raises an exception if maximum lock limit is exceeded' do + counter = Concurrent::AtomicFixnum.new(ReadWriteLock::MAX_READERS) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + expect { + subject.with_read_lock { nil } + }.to raise_error(Concurrent::ResourceLimitError) + end + + it 'releases the lock when an exception is raised' do + expect(subject).to receive(:release_read_lock).with(any_args) + begin + subject.release_read_lock { raise StandardError } + rescue + end + end + end + + context '#with_write_lock' do + + it 'acquires the lock' do + expect(subject).to receive(:acquire_write_lock).with(no_args) + subject.with_write_lock { nil } + end + + it 'returns the value of the block operation' do + expected = 100 + actual = subject.with_write_lock { expected } + expect(actual).to eq expected + end + + it 'releases the lock' do + expect(subject).to receive(:release_write_lock).with(no_args) + subject.with_write_lock { nil } + end + + it 'raises an exception if no block is given' do + expect { + subject.with_write_lock + }.to raise_error(ArgumentError) + end + + it 'raises an exception if maximum lock limit is exceeded' do + counter = Concurrent::AtomicFixnum.new(ReadWriteLock::MAX_WRITERS) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + expect { + subject.with_write_lock { nil } + }.to raise_error(Concurrent::ResourceLimitError) + end + + it 'releases the lock when an exception is raised' do + expect(subject).to receive(:release_write_lock).with(any_args) + begin + subject.with_write_lock { raise StandardError } + rescue + end + end + end + + context '#acquire_read_lock' do + + it 'increments the lock count' do + counter = Concurrent::AtomicFixnum.new(0) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + subject.acquire_read_lock + expect(counter.value).to eq 1 + end + + it 'waits for a running writer to finish' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + latch_3 = Concurrent::CountDownLatch.new(1) + + write_flag = Concurrent::AtomicBoolean.new(false) + read_flag = Concurrent::AtomicBoolean.new(false) + + thread_1 = in_thread do + latch_1.wait(1) + subject.acquire_write_lock + latch_2.count_down + latch_3.wait(1) + write_flag.make_true + subject.release_write_lock + end + + thread_2 = in_thread do + latch_2.wait(1) + expect(write_flag.value).to be false + latch_3.count_down + subject.acquire_read_lock + expect(write_flag.value).to be true + read_flag.make_true + subject.release_read_lock + end + + latch_1.count_down + [thread_1, thread_2].each(&:join) + + expect(write_flag.value).to be true + expect(read_flag.value).to be true + end + + it 'does not wait for any running readers' do + counter = Concurrent::AtomicFixnum.new(0) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + latch_3 = Concurrent::CountDownLatch.new(1) + + read_flag_1 = Concurrent::AtomicBoolean.new(false) + read_flag_2 = Concurrent::AtomicBoolean.new(false) + + thread_1 = in_thread do + latch_1.wait(1) + subject.acquire_read_lock + expect(counter.value).to eq 1 + latch_2.count_down + latch_3.wait(1) + read_flag_1.make_true + subject.release_read_lock + end + + thread_2 = in_thread do + latch_2.wait(1) + expect(read_flag_1.value).to be false + subject.acquire_read_lock + expect(counter.value).to eq 2 + latch_3.count_down + read_flag_2.make_true + subject.release_read_lock + end + + latch_1.count_down + [thread_1, thread_2].each(&:join) + + expect(read_flag_1.value).to be true + expect(read_flag_2.value).to be true + expect(counter.value).to eq 0 + end + + it 'raises an exception if maximum lock limit is exceeded' do + counter = Concurrent::AtomicFixnum.new(ReadWriteLock::MAX_WRITERS) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + expect { + subject.acquire_write_lock { nil } + }.to raise_error(Concurrent::ResourceLimitError) + end + + it 'returns true if the lock is acquired' do + expect(subject.acquire_read_lock).to be true + end + end + + context '#release_read_lock' do + + it 'decrements the counter' do + counter = Concurrent::AtomicFixnum.new(0) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + subject.acquire_read_lock + expect(counter.value).to eq 1 + subject.release_read_lock + expect(counter.value).to eq 0 + end + + it 'unblocks waiting writers' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + write_flag = Concurrent::AtomicBoolean.new(false) + + thread = in_thread do + latch_1.wait(1) + latch_2.count_down + subject.acquire_write_lock + write_flag.make_true + subject.release_write_lock + end + + subject.acquire_read_lock + latch_1.count_down + latch_2.wait(1) + expect(write_flag.value).to be false + subject.release_read_lock + thread.join + expect(write_flag.value).to be true + end + + it 'returns true if the lock is released' do + subject.acquire_read_lock + expect(subject.release_read_lock).to be true + end + + it 'returns true if the lock was never set' do + expect(subject.release_read_lock).to be true + end + end + + context '#acquire_write_lock' do + + it 'increments the lock count' do + counter = Concurrent::AtomicFixnum.new(0) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + subject.acquire_write_lock + expect(counter.value).to be > 1 + end + + it 'waits for a running writer to finish' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + latch_3 = Concurrent::CountDownLatch.new(1) + + write_flag_1 = Concurrent::AtomicBoolean.new(false) + write_flag_2 = Concurrent::AtomicBoolean.new(false) + + thread_1 = in_thread do + latch_1.wait(1) + subject.acquire_write_lock + latch_2.count_down + latch_3.wait(1) + write_flag_1.make_true + subject.release_write_lock + end + + thread_2 = in_thread do + latch_2.wait(1) + expect(write_flag_1.value).to be false + latch_3.count_down + subject.acquire_write_lock + expect(write_flag_1.value).to be true + write_flag_2.make_true + subject.release_write_lock + end + + latch_1.count_down + [thread_1, thread_2].each(&:join) + + expect(write_flag_1.value).to be true + expect(write_flag_2.value).to be true + end + + it 'waits for a running reader to finish' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + latch_3 = Concurrent::CountDownLatch.new(1) + + read_flag = Concurrent::AtomicBoolean.new(false) + write_flag = Concurrent::AtomicBoolean.new(false) + + thread_1 = in_thread do + latch_1.wait(1) + subject.acquire_read_lock + latch_2.count_down + latch_3.wait(1) + read_flag.make_true + subject.release_read_lock + end + + thread_2 = in_thread do + latch_2.wait(1) + expect(read_flag.value).to be false + latch_3.count_down + subject.acquire_write_lock + expect(read_flag.value).to be true + write_flag.make_true + subject.release_write_lock + end + + latch_1.count_down + [thread_1, thread_2].each(&:join) + + expect(read_flag.value).to be true + expect(write_flag.value).to be true + end + + it 'raises an exception if maximum lock limit is exceeded' do + counter = Concurrent::AtomicFixnum.new(ReadWriteLock::MAX_WRITERS) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + expect { + subject.acquire_write_lock { nil } + }.to raise_error(Concurrent::ResourceLimitError) + end + + it 'returns true if the lock is acquired' do + expect(subject.acquire_write_lock).to be true + end + end + + context '#release_write_lock' do + + it 'decrements the counter' do + counter = Concurrent::AtomicFixnum.new(0) + allow(Concurrent::AtomicFixnum).to receive(:new).with(anything).and_return(counter) + subject.acquire_write_lock + expect(counter.value).to be > 1 + subject.release_write_lock + expect(counter.value).to eq 0 + end + + it 'unblocks waiting readers' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + read_flag = Concurrent::AtomicBoolean.new(false) + + thread = in_thread do + latch_1.wait(1) + latch_2.count_down + subject.acquire_read_lock + read_flag.make_true + subject.release_read_lock + end + + subject.acquire_write_lock + latch_1.count_down + latch_2.wait(1) + expect(read_flag.value).to be false + subject.release_write_lock + thread.join + expect(read_flag.value).to be true + end + + it 'unblocks waiting writers' do + latch_1 = Concurrent::CountDownLatch.new(1) + latch_2 = Concurrent::CountDownLatch.new(1) + write_flag = Concurrent::AtomicBoolean.new(false) + + thread = in_thread do + latch_1.wait(1) + latch_2.count_down + subject.acquire_write_lock + write_flag.make_true + subject.release_write_lock + end + + subject.acquire_write_lock + latch_1.count_down + latch_2.wait(1) + expect(write_flag.value).to be false + subject.release_write_lock + thread.join + expect(write_flag.value).to be true + end + + it 'returns true if the lock is released' do + subject.acquire_write_lock + expect(subject.release_write_lock).to be true + end + + it 'returns true if the lock was never set' do + expect(subject.release_write_lock).to be true + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/reentrant_read_write_lock_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/reentrant_read_write_lock_spec.rb new file mode 100644 index 0000000000..ea20a49811 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/reentrant_read_write_lock_spec.rb @@ -0,0 +1,561 @@ +require 'concurrent/utility/engine' +require 'concurrent/atomic/reentrant_read_write_lock' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/set' +require 'timeout' +require 'fiber' + +module Concurrent + BaseMatcher = RSpec::Matchers::BuiltIn::BaseMatcher + + class TrackedReentrantReadWriteLock < ReentrantReadWriteLock + def initialize(scope = Thread) + super() + + @scope = scope + @read_acquired = Concurrent::Set.new + @write_acquired = Concurrent::Set.new + end + + attr :read_acquired + attr :write_acquired + + def try_read_lock + if super + @read_acquired.add(@scope.current) + true + else + false + end + end + + def try_write_lock + if super + @write_acquired.add(@scope.current) + true + else + false + end + end + + def acquire_read_lock + if super + @read_acquired.add(@scope.current) + true + else + false + end + end + + def acquire_write_lock + if super + @write_acquired.add(@scope.current) + true + else + false + end + end + + def release_read_lock + super.tap do + @read_acquired.delete(@scope.current) + end + end + + def release_write_lock + super.tap do + @write_acquired.delete(@scope.current) + end + end + end + + # **************************************************************** + # First some custom matchers to make our tests all nice and pretty + # **************************************************************** + + class HoldLock + def initialize(lock) + @lock = lock + end + + def for_read + HoldReadLock.new(@lock) + end + + def for_write + HoldWriteLock.new(@lock) + end + + def for_both + HoldBoth.new(@lock) + end + end + + class HoldReadLock < BaseMatcher + def match(lock, scope) + ((lock.instance_eval { @Counter.value } & ReentrantReadWriteLock::MAX_READERS) != 0) && lock.read_acquired.include?(scope) + end + end + + class HoldWriteLock < BaseMatcher + def match(lock, scope) + ((lock.instance_eval { @Counter.value } & ReentrantReadWriteLock::RUNNING_WRITER) != 0) && lock.write_acquired.include?(scope) + end + end + + class HoldBoth < BaseMatcher + def match(lock, scope) + HoldReadLock.new(lock).matches?(scope) && HoldWriteLock.new(lock).matches?(scope) + end + end + + class BeFree < BaseMatcher + MASK = ReentrantReadWriteLock::MAX_READERS + ReentrantReadWriteLock::RUNNING_WRITER + + def matches?(lock) + (lock.instance_eval { @Counter.value } & MASK) == 0 + end + + def failure_message + "expected lock to be free" + end + end + + # ******************************************************* + + RSpec.shared_context TrackedReentrantReadWriteLock do + def hold(lock) + HoldLock.new(lock) + end + + def be_free + BeFree.new + end + + def wait_up_to(secs, &condition) + _end = Time.now + secs + while !condition.call && Time.now < _end + sleep(0.001) + end + end + end + + RSpec.describe ReentrantReadWriteLock do + include_context TrackedReentrantReadWriteLock + + let(:lock) { TrackedReentrantReadWriteLock.new } + + context "read lock" do + + it "allows other read locks to be acquired at the same time" do + lock # stupid RSpec 'let' is not thread-safe! + Timeout.timeout(3) do + got_lock = 10.times.collect { CountDownLatch.new } + threads = 10.times.collect do |n| + in_thread do + # Each thread takes the read lock and then waits for another one + # They will only finish if ALL of them get their read lock + expect(lock.acquire_read_lock).to be true + expect(Thread.current).to hold(lock).for_read + got_lock[n].count_down + got_lock[(n+1) % 10].wait + end + end + threads.each(&:join) + end + end + + it "can be acquired more than once" do + Timeout.timeout(3) do + 10.times { expect(lock.acquire_read_lock).to be true } + expect(Thread.current).to hold(lock).for_read + 10.times { expect(lock.release_read_lock).to be true } + expect(Thread.current).not_to hold(lock).for_read + expect(lock).to be_free + end + end + + it "can be acquired while holding a write lock" do + Timeout.timeout(3) do + expect(lock.acquire_write_lock).to be true + expect(Thread.current).to hold(lock).for_write + expect(lock.acquire_read_lock).to be true + expect(Thread.current).to hold(lock).for_both + expect(lock.release_read_lock).to be true + expect(Thread.current).to hold(lock).for_write + expect(Thread.current).not_to hold(lock).for_read + expect(lock.release_write_lock).to be true + expect(lock).to be_free + end + end + + it "can be upgraded to a write lock" do + Timeout.timeout(3) do + expect(lock.acquire_read_lock).to be true + expect(Thread.current).to hold(lock).for_read + # now we want to upgrade... + expect(lock.acquire_write_lock).to be true + expect(lock.release_read_lock).to be true + expect(Thread.current).to hold(lock).for_write + + expect(lock.release_write_lock).to be true + expect(lock).to be_free + end + end + + it "can be upgraded to a write lock when read lock acquired more than once" do + Timeout.timeout(3) do + expect(lock.acquire_read_lock).to be true + expect(lock.acquire_read_lock).to be true + expect(Thread.current).to hold(lock).for_read + + # now we want to upgrade... + expect(lock.acquire_write_lock).to be true + expect(lock.release_read_lock).to be true + expect(lock.release_read_lock).to be true + expect(Thread.current).to hold(lock).for_write + + expect(lock.release_write_lock).to be true + expect(lock).to be_free + end + end + + it "cannot be released when not held" do + expect { lock.release_read_lock }.to raise_error(IllegalOperationError) + end + + it "cannot be released more times than it was taken" do + Timeout.timeout(3) do + 2.times { lock.acquire_read_lock } + 2.times { lock.release_read_lock } + expect { lock.release_read_lock }.to raise_error(IllegalOperationError) + end + end + + it "wakes up waiting writers when the last read lock is released" do + latch1,latch2 = CountDownLatch.new(3),CountDownLatch.new + good = AtomicBoolean.new(false) + threads = [ + in_thread { lock.acquire_read_lock; latch1.count_down; latch2.wait; lock.release_read_lock }, + in_thread { lock.acquire_read_lock; latch1.count_down; latch2.wait; lock.release_read_lock }, + in_thread { lock.acquire_read_lock; latch1.count_down; latch2.wait; lock.release_read_lock }, + in_thread { latch1.wait; lock.acquire_write_lock; good.value = true } + ] + wait_up_to(0.2) { threads[3].status == 'sleep' } + # The last thread should be waiting to acquire a write lock now... + expect(threads[3].status).to eql "sleep" + expect(threads[3]).not_to hold(lock).for_write + expect(good.value).to be false + # Throw latch2 and the 3 readers will wake up and all release their read locks... + latch2.count_down + wait_up_to(0.2) { good.value } + expect(threads[3]).to hold(lock).for_write + expect(good.value).to be true + end + end + + context "write lock" do + it "cannot be acquired when another thread holds a write lock" do + latch = CountDownLatch.new + threads = [ + in_thread { lock.acquire_write_lock; latch.count_down }, + in_thread { latch.wait; lock.acquire_write_lock } + ] + expect { Timeout.timeout(1) { threads[0].join }}.not_to raise_error + expect(threads[0]).to hold(lock).for_write + expect(threads[1]).not_to hold(lock).for_write + wait_up_to(0.2) { threads[1].status == 'sleep' } + expect(threads[1].status).to eql "sleep" + end + + it "cannot be acquired when another thread holds a read lock" do + latch = CountDownLatch.new + threads = [ + in_thread { lock.acquire_read_lock; latch.count_down }, + in_thread { latch.wait; lock.acquire_write_lock } + ] + expect { Timeout.timeout(1) { threads[0].join }}.not_to raise_error + expect(threads[0]).to hold(lock).for_read + expect(threads[1]).not_to hold(lock).for_write + wait_up_to(0.2) { threads[1].status == 'sleep' } + expect(threads[1].status).to eql "sleep" + end + + it "can be acquired more than once" do + Timeout.timeout(3) do + 10.times { expect(lock.acquire_write_lock).to be true } + expect(Thread.current).to hold(lock).for_write + 10.times { expect(lock.release_write_lock).to be true } + expect(Thread.current).not_to hold(lock).for_write + expect(lock).to be_free + end + end + + it "can be acquired while holding a read lock" do + Timeout.timeout(3) do + expect(lock.acquire_read_lock).to be true + expect(Thread.current).to hold(lock).for_read + expect(lock.acquire_write_lock).to be true + expect(Thread.current).to hold(lock).for_both + expect(lock.release_write_lock).to be true + expect(Thread.current).to hold(lock).for_read + expect(Thread.current).not_to hold(lock).for_write + expect(lock.release_read_lock).to be true + expect(lock).to be_free + end + end + + it "can be downgraded to a read lock" do + Timeout.timeout(3) do + expect(lock.acquire_write_lock).to be true + expect(Thread.current).to hold(lock).for_write + # now we want to downgrade... + expect(lock.acquire_read_lock).to be true + expect(lock.release_write_lock).to be true + expect(Thread.current).to hold(lock).for_read + + expect(lock.release_read_lock).to be true + expect(lock).to be_free + end + end + + it "cannot be released when not held" do + expect { lock.release_write_lock }.to raise_error(IllegalOperationError) + end + + it "cannot be released more times than it was taken" do + Timeout.timeout(3) do + 2.times { lock.acquire_write_lock } + 2.times { lock.release_write_lock } + expect { lock.release_write_lock }.to raise_error(IllegalOperationError) + end + end + + it "wakes up waiting readers when the write lock is released" do + latch1,latch2 = CountDownLatch.new,CountDownLatch.new + good = AtomicFixnum.new(0) + threads = [ + in_thread { lock.acquire_write_lock; latch1.count_down; latch2.wait; lock.release_write_lock }, + in_thread { latch1.wait; lock.acquire_read_lock; good.update { |n| n+1 }}, + in_thread { latch1.wait; lock.acquire_read_lock; good.update { |n| n+1 }}, + in_thread { latch1.wait; lock.acquire_read_lock; good.update { |n| n+1 }} + ] + wait_up_to(0.2) { threads[3].status == 'sleep' } + # The last 3 threads should be waiting to acquire read locks now... + unless Concurrent.on_jruby? # flaky on JRuby + (1..3).each { |n| expect(threads[n].status).to eql "sleep" } + end + (1..3).each { |n| expect(threads[n]).not_to hold(lock).for_read } + # Throw latch2 and the writer will wake up and release its write lock... + latch2.count_down + wait_up_to(0.2) { good.value == 3 } + (1..3).each { |n| expect(threads[n]).to hold(lock).for_read } + end + + it "wakes up waiting writers when the write lock is released" do + latch1,latch2 = CountDownLatch.new,CountDownLatch.new + good = AtomicBoolean.new(false) + threads = [ + in_thread { lock.acquire_write_lock; latch1.count_down; latch2.wait; lock.release_write_lock }, + in_thread { latch1.wait; lock.acquire_write_lock; good.value = true }, + ] + wait_up_to(0.2) { threads[1].status == 'sleep' } + # The last thread should be waiting to acquire a write lock now... + unless Concurrent.on_jruby? # flaky on JRuby + expect(threads[1].status).to eql "sleep" + end + expect(threads[1]).not_to hold(lock).for_write + # Throw latch2 and the writer will wake up and release its write lock... + latch2.count_down + wait_up_to(0.2) { good.value } + expect(threads[1]).to hold(lock).for_write + end + end + + context "#with_read_lock" do + + it "acquires read block before yielding, then releases it" do + expect(lock).to be_free + lock.with_read_lock { expect(Thread.current).to hold(lock).for_read } + expect(lock).to be_free + end + + it "releases read lock if an exception is raised in block" do + expect { + lock.with_read_lock { raise "Bad" } + }.to raise_error(RuntimeError, 'Bad') + expect(lock).to be_free + expect(Thread.current).not_to hold(lock).for_read + end + end + + context "#with_write_lock" do + + it "acquires write block before yielding, then releases it" do + expect(lock).to be_free + lock.with_write_lock { expect(Thread.current).to hold(lock).for_write } + expect(lock).to be_free + end + + it "releases write lock if an exception is raised in block" do + expect { + lock.with_write_lock { raise "Bad" } + }.to raise_error(RuntimeError, 'Bad') + expect(lock).to be_free + expect(Thread.current).not_to hold(lock).for_write + end + end + + context "#try_read_lock" do + + it "returns false immediately if read lock cannot be obtained" do + Timeout.timeout(3) do + latch = CountDownLatch.new + in_thread { lock.acquire_write_lock; latch.count_down } + + latch.wait + expect { + Timeout.timeout(0.01) { expect(lock.try_read_lock).to be false } + }.not_to raise_error + expect(Thread.current).not_to hold(lock).for_read + end + end + + it "acquires read lock and returns true if it can do so without blocking" do + Timeout.timeout(3) do + latch = CountDownLatch.new + in_thread { lock.acquire_read_lock; latch.count_down } + + latch.wait + expect { + Timeout.timeout(0.01) { expect(lock.try_read_lock).to be true } + }.not_to raise_error + expect(lock).not_to be_free + expect(Thread.current).to hold(lock).for_read + end + end + + it "can acquire a read lock if a read lock is already held" do + Timeout.timeout(3) do + expect(lock.acquire_read_lock).to be true + expect(lock.try_read_lock).to be true + expect(Thread.current).to hold(lock).for_read + expect(lock.release_read_lock).to be true + expect(lock.release_read_lock).to be true + expect(Thread.current).not_to hold(lock).for_read + expect(lock).to be_free + end + end + + it "can acquire a read lock if a write lock is already held" do + Timeout.timeout(3) do + expect(lock.acquire_write_lock).to be true + expect(lock.try_read_lock).to be true + expect(Thread.current).to hold(lock).for_read + expect(lock.release_read_lock).to be true + expect(lock.release_write_lock).to be true + expect(Thread.current).not_to hold(lock).for_read + expect(lock).to be_free + end + end + end + + context "#try_write_lock" do + + it "returns false immediately if write lock cannot be obtained" do + Timeout.timeout(3) do + latch = CountDownLatch.new + in_thread { lock.acquire_write_lock; latch.count_down } + + latch.wait + expect { + Timeout.timeout(0.02) { expect(lock.try_write_lock).to be false } + }.not_to raise_error + expect(Thread.current).not_to hold(lock).for_write + end + end + + it "acquires write lock and returns true if it can do so without blocking" do + Timeout.timeout(3) do + expect { + Timeout.timeout(0.02) { expect(lock.try_write_lock).to be true } + }.not_to raise_error + expect(lock).not_to be_free + expect(Thread.current).to hold(lock).for_write + end + end + + it "can acquire a write lock if a read lock is already held" do + Timeout.timeout(3) do + expect(lock.acquire_read_lock).to be true + expect(lock.try_write_lock).to be true + expect(Thread.current).to hold(lock).for_write + expect(lock.release_write_lock).to be true + expect(lock.release_read_lock).to be true + expect(Thread.current).not_to hold(lock).for_write + expect(lock).to be_free + end + end + + it "can acquire a write lock if a write lock is already held" do + Timeout.timeout(3) do + expect(lock.acquire_write_lock).to be true + expect(lock.try_write_lock).to be true + expect(Thread.current).to hold(lock).for_write + expect(lock.release_write_lock).to be true + expect(lock.release_write_lock).to be true + expect(Thread.current).not_to hold(lock).for_write + expect(lock).to be_free + end + end + end + + it "can survive a torture test" do + count = 0 + writers = 5.times.collect do + in_thread do + 500.times do + lock.with_write_lock do + value = (count += 1) + sleep(0.0001) + count = value+1 + end + end + end + end + readers = 15.times.collect do + in_thread do + 500.times do + lock.with_read_lock { expect(count % 2).to eq 0 } + end + end + end + writers.each(&:join) + readers.each(&:join) + expect(count).to eq 5000 + end + end + + RSpec.describe Concurrent::ReentrantReadWriteLock, if: (Concurrent::LockLocalVar == Concurrent::FiberLocalVar) do + include_context TrackedReentrantReadWriteLock + + let(:lock) { Concurrent::TrackedReentrantReadWriteLock.new(Fiber) } + + it "can acquire locks in separate fibers" do + lock.with_read_lock do + expect(Fiber.current).to hold(lock).for_read + Fiber.new do + expect(Fiber.current).to_not hold(lock).for_read + + lock.with_read_lock do + expect(Fiber.current).to hold(lock).for_read + end + + end.resume + + expect(Fiber.current).to hold(lock).for_read + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/semaphore_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/semaphore_spec.rb new file mode 100644 index 0000000000..3aef3687c3 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/semaphore_spec.rb @@ -0,0 +1,355 @@ +require 'concurrent/atomic/semaphore' + +RSpec.shared_examples :semaphore do + let(:semaphore) { described_class.new(3) } + + describe '#initialize' do + it 'raises an exception if the initial count is not an integer' do + expect { + described_class.new('foo') + }.to raise_error(ArgumentError) + end + + context 'when initializing with 0' do + let(:semaphore) { described_class.new(0) } + + it do + expect(semaphore).to_not be nil + end + end + + context 'when initializing with -1' do + let(:semaphore) { described_class.new(-1) } + + it do + semaphore.release + expect(semaphore.available_permits).to eq 0 + end + end + end + + describe '#acquire' do + context 'without block' do + context 'permits available' do + it 'should return nil immediately' do + result = semaphore.acquire + expect(result).to be_nil + end + end + + context 'not enough permits available' do + it 'should block thread until permits are available' do + semaphore.drain_permits + in_thread { sleep(0.2); semaphore.release } + + result = semaphore.acquire + expect(result).to be_nil + expect(semaphore.available_permits).to eq 0 + end + end + + context 'when acquiring negative permits' do + it 'raises ArgumentError' do + expect { + semaphore.acquire(-1) + }.to raise_error(ArgumentError) + end + end + end + + context 'with block' do + context 'permits available' do + it 'should acquire permits, run the block, release permits, and return block return value' do + available_permits = semaphore.available_permits + yielded = false + expected_result = Object.new + + actual_result = semaphore.acquire do + expect(semaphore.available_permits).to eq(available_permits - 1) + yielded = true + expected_result + end + + expect(semaphore.available_permits).to eq(available_permits) + expect(yielded).to be true + expect(actual_result).to be(expected_result) + end + + it 'if the block raises, the permit is still released' do + expect { + expect { + semaphore.acquire do + raise 'boom' + end + }.to raise_error('boom') + }.to_not change { semaphore.available_permits } + end + end + + context 'not enough permits available' do + it 'should block thread until permits are available' do + yielded = false + semaphore.drain_permits + in_thread { sleep(0.2); semaphore.release } + expected_result = Object.new + + actual_result = semaphore.acquire do + yielded = true + expected_result + end + + expect(actual_result).to be(expected_result) + expect(yielded).to be true + expect(semaphore.available_permits).to eq 1 + end + end + + context 'when acquiring negative permits' do + it 'raises ArgumentError' do + expect { + expect { + semaphore.acquire(-1) do + raise 'block should never run' + end + }.to raise_error(ArgumentError) + }.not_to change { semaphore.available_permits } + end + end + end + end + + describe '#drain_permits' do + it 'drains all available permits' do + drained = semaphore.drain_permits + expect(drained).to eq 3 + expect(semaphore.available_permits).to eq 0 + end + + it 'drains nothing in no permits are available' do + semaphore.reduce_permits 3 + drained = semaphore.drain_permits + expect(drained).to eq 0 + end + end + + describe '#try_acquire' do + context 'without block' do + context 'without timeout' do + it 'acquires immediately if permits are available' do + result = semaphore.try_acquire(1) + expect(result).to be_truthy + end + + it 'returns false immediately in no permits are available' do + result = semaphore.try_acquire(20) + expect(result).to be_falsey + end + + context 'when trying to acquire negative permits' do + it do + expect { + semaphore.try_acquire(-1) + }.to raise_error(ArgumentError) + end + end + end + + context 'with timeout' do + it 'acquires immediately if permits are available' do + result = semaphore.try_acquire(1, 5) + expect(result).to be_truthy + end + + it 'acquires when permits are available within timeout' do + semaphore.drain_permits + in_thread { sleep 0.1; semaphore.release } + result = semaphore.try_acquire(1, 1) + expect(result).to be_truthy + end + + it 'returns false on timeout' do + semaphore.drain_permits + result = semaphore.try_acquire(1, 0.1) + expect(result).to be_falsey + end + end + end + + context 'with block' do + context 'without timeout' do + it 'acquires immediately if permits are available and returns block return value' do + yielded = false + available_permits = semaphore.available_permits + expected_result = Object.new + + actual_result = semaphore.try_acquire(1) do + yielded = true + expect(semaphore.available_permits).to eq(available_permits - 1) + expected_result + end + + expect(actual_result).to be(expected_result) + expect(yielded).to be true + expect(semaphore.available_permits).to eq available_permits + end + + it 'releases permit if block raises' do + expect { + expect { + semaphore.try_acquire(1) do + raise 'boom' + end + }.to raise_error('boom') + }.not_to change { semaphore.available_permits } + end + + it 'returns false immediately in no permits are available' do + expect { + result = semaphore.try_acquire(20) do + raise 'block should never run' + end + + expect(result).to be_falsey + }.not_to change { semaphore.available_permits } + end + + context 'when trying to acquire negative permits' do + it do + expect { + expect { + semaphore.try_acquire(-1) do + raise 'block should never run' + end + }.to raise_error(ArgumentError) + }.not_to change { semaphore.available_permits } + end + end + end + + context 'with timeout' do + it 'acquires immediately if permits are available, and returns block return value' do + expect { + yielded = false + expected_result = Object.new + + actual_result = semaphore.try_acquire(1, 5) do + yielded = true + expected_result + end + + expect(actual_result).to be(expected_result) + expect(yielded).to be true + }.not_to change { semaphore.available_permits } + end + + it 'releases permits if block raises' do + expect { + expect { + semaphore.try_acquire(1, 5) do + raise 'boom' + end + }.to raise_error('boom') + }.not_to change { semaphore.available_permits } + end + + it 'acquires when permits are available within timeout, and returns block return value' do + yielded = false + semaphore.drain_permits + in_thread { sleep 0.1; semaphore.release } + expected_result = Object.new + + actual_result = semaphore.try_acquire(1, 1) do + yielded = true + expected_result + end + + expect(actual_result).to be(expected_result) + expect(yielded).to be true + expect(semaphore.available_permits).to be 1 + end + + it 'returns false on timeout' do + semaphore.drain_permits + + result = semaphore.try_acquire(1, 0.1) do + raise 'block should never run' + end + + expect(result).to be_falsey + expect(semaphore.available_permits).to be 0 + end + end + end + end + + describe '#reduce_permits' do + it 'raises ArgumentError if reducing by negative number' do + expect { + semaphore.reduce_permits(-1) + }.to raise_error(ArgumentError) + end + + it 'reduces permits below zero' do + semaphore.reduce_permits 1003 + expect(semaphore.available_permits).to eq(-1000) + end + + it 'reduces permits' do + semaphore.reduce_permits 1 + expect(semaphore.available_permits).to eq 2 + semaphore.reduce_permits 2 + expect(semaphore.available_permits).to eq 0 + end + + it 'reduces zero permits' do + semaphore.reduce_permits 0 + expect(semaphore.available_permits).to eq 3 + end + end + + describe '#release' do + it 'increases the number of available permits by one' do + semaphore.release + expect(semaphore.available_permits).to eq 4 + end + + context 'when a number of permits is specified' do + it 'increases the number of available permits by the specified value' do + semaphore.release(2) + expect(semaphore.available_permits).to eq 5 + end + + context 'when permits is set to negative number' do + it do + expect { + semaphore.release(-1) + }.to raise_error(ArgumentError) + end + end + end + end +end + +module Concurrent + RSpec.describe MutexSemaphore do + it_should_behave_like :semaphore + end + + if Concurrent.on_jruby? + RSpec.describe JavaSemaphore do + it_should_behave_like :semaphore + end + end + + RSpec.describe Semaphore do + if Concurrent.on_jruby? + it 'inherits from JavaSemaphore' do + expect(Semaphore.ancestors).to include(JavaSemaphore) + end + else + it 'inherits from MutexSemaphore' do + expect(Semaphore.ancestors).to include(MutexSemaphore) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/thread_local_var_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/thread_local_var_spec.rb new file mode 100644 index 0000000000..906dba0286 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/atomic/thread_local_var_spec.rb @@ -0,0 +1,130 @@ +require 'concurrent/atomic/thread_local_var' +require 'concurrent/atomic/count_down_latch' + +module Concurrent + + RSpec.describe ThreadLocalVar do + + context '#initialize' do + + it 'can set an initial value' do + v = described_class.new(14) + expect(v.value).to eq 14 + end + + it 'sets nil as a default initial value' do + v = described_class.new + expect(v.value).to be_nil + end + + it 'sets the same initial value for all threads' do + v = described_class.new(14) + t1 = in_thread { v.value } + t2 = in_thread { v.value } + expect(t1.value).to eq 14 + expect(t2.value).to eq 14 + end + + it 'can set a block to be called to get the initial value' do + v = described_class.new { 14 } + expect(v.value).to eq 14 + end + + context 'when attempting to set both an initial value and a block' do + it do + expect { described_class.new(14) { 14 } }.to raise_error(ArgumentError) + end + end + end + + context '#value' do + let(:v) { described_class.new(14) } + + it 'returns the current value' do + expect(v.value).to eq 14 + end + + it 'returns the value after modification' do + v.value = 2 + expect(v.value).to eq 2 + end + + context 'when using a block to initialize the value' do + it 'calls the block to initialize the value' do + block = proc { } + + expect(block).to receive(:call) + + v = described_class.new(&block) + v.value + end + + it 'sets the block return value as the current value' do + value = 13 + + v = described_class.new { value += 1 } + + v.value + expect(v.value).to be 14 + end + + it 'calls the block to initialize the value for each thread' do + block = proc { } + + expect(block).to receive(:call).twice + + v = described_class.new(&block) + in_thread { v.value }.join + in_thread { v.value }.join + end + end + end + + context '#value=' do + let(:v) { described_class.new(14) } + + it 'sets a new value' do + v.value = 2 + expect(v.value).to eq 2 + end + + it 'returns the new value' do + expect(v.value = 2).to eq 2 + end + + it 'does not modify the initial value for other threads' do + v.value = 2 + t = in_thread { v.value } + expect(t.value).to eq 14 + end + + it 'does not modify the value for other threads' do + v.value = 3 + + b1 = CountDownLatch.new(2) + b2 = CountDownLatch.new(2) + + t1 = in_thread do + b1.count_down + b1.wait + v.value = 1 + b2.count_down + b2.wait + v.value + end + + t2 = in_thread do + b1.count_down + b1.wait + v.value = 2 + b2.count_down + b2.wait + v.value + end + + expect(t1.value).to eq 1 + expect(t2.value).to eq 2 + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/cancellation_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/cancellation_spec.rb new file mode 100644 index 0000000000..9ba2a3c361 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/cancellation_spec.rb @@ -0,0 +1,93 @@ +require 'concurrent/edge/cancellation' + +RSpec.describe 'Concurrent' do + describe 'Cancellation', edge: true do + specify 'basic' do + cancellation, origin = Concurrent::Cancellation.new + expect(cancellation.origin).to eq origin + expect(cancellation.to_s).to match(/Cancellation.*pending/) + + futures1 = ::Array.new(2) do + Concurrent::Promises.future(cancellation) do |c| + loop { Thread.pass; break if c.canceled? } + :done + end + end + + futures2 = ::Array.new(2) do + Concurrent::Promises.future(cancellation) do |c| + loop { c.check!; Thread.pass } rescue $! + end + end + + sleep 0.01 + origin.resolve + expect(cancellation.to_s).to match(/Cancellation.*canceled/) + + futures1.each do |future| + expect(future.value!).to eq :done + end + futures2.each do |future| + expect(future.value!).to be_a_kind_of Concurrent::CancelledOperationError + end + end + + specify do + cancellation, origin = Concurrent::Cancellation.new + origin.resolve + expect(cancellation.canceled?).to be_truthy + + cancellable_branch = Concurrent::Promises.delay { 1 } + expect((cancellable_branch | origin).value).to be_nil + expect(cancellable_branch.resolved?).to be_falsey + end + + specify do + _, source = Concurrent::Cancellation.new + + cancellable_branch = Concurrent::Promises.delay { 1 } + expect(Concurrent::Promises.any_resolved_future(cancellable_branch, source).value).to eq 1 + expect(cancellable_branch.resolved?).to be_truthy + end + + specify do + cancellation, origin = Concurrent::Cancellation.new(Concurrent::Promises.resolvable_future) + origin.resolve false, nil, err = StandardError.new('Cancelled') + expect(cancellation.canceled?).to be_truthy + + cancellable_branch = Concurrent::Promises.delay { 1 } + expect((cancellable_branch | origin.to_future).reason).to eq err + expect(cancellable_branch.resolved?).to be_falsey + end + + specify do + head = Concurrent::Promises.resolvable_future + cancellation, origin = Concurrent::Cancellation.new head.then(&:succ) + + futures = ::Array.new(2) do + Concurrent::Promises.future(cancellation) do |c| + loop { Thread.pass; break if c.canceled? } + :done + end + end + + head.fulfill 1 + futures.each do |future| + expect(future.value!).to eq :done + end + expect(origin.to_future.value!).to eq 2 + end + + specify '#join' do + cancellation_a, origin_a = Concurrent::Cancellation.new + cancellation_b, _ = Concurrent::Cancellation.new + combined_cancellation = cancellation_a.join(cancellation_b) + + origin_a.resolve + + expect(cancellation_a.canceled?).to be_truthy + expect(cancellation_b.canceled?).to be_falsey + expect(combined_cancellation.canceled?).to be_truthy + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/base_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/base_shared.rb new file mode 100644 index 0000000000..2a0dde38d1 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/base_shared.rb @@ -0,0 +1,134 @@ +RSpec.shared_examples :channel_buffer do + + specify do + expect(subject).to respond_to(:blocking?) + end + + context '#capacity' do + specify { expect(subject.capacity).to be >= 0 } + end + + context '#size' do + it 'returns zero upon initialization' do + expect(subject.size).to eq 0 + end + end + + context '#empty?' do + it 'returns true when empty' do + expect(subject).to be_empty + end + end + + context '#full?' do + it 'returns false when not full' do + expect(subject).to_not be_full + end + end + + context '#put' do + + it 'does not enqueue the item when closed' do + subject.close + subject.put(:foo) + expect(subject).to be_empty + end + + it 'returns false when closed' do + subject.close + expect(subject.put(:foo)).to be false + end + end + + context '#offer' do + + it 'returns true on success' do + subject # initialize on this thread + t = in_thread do + subject.take + end + t.join(0.1) + + expect(subject.offer(:foo)).to be true + end + + it 'does not enqueue the item when closed' do + subject.close + subject.offer(:foo) + expect(subject).to be_empty + end + + it 'returns false immediately when closed' do + subject.close + expect(subject.offer(:foo)).to be false + end + end + + context '#take' do + it 'returns Concurrent::NULL when closed' do + subject.close + expect(subject.take).to eq Concurrent::NULL + end + end + + context '#next' do + it 'returns Concurrent::NULL, false when closed' do + subject.close + item, more = subject.next + expect(item).to eq Concurrent::NULL + expect(more).to be false + end + end + + context '#poll' do + + it 'returns the next item immediately if available' do + subject # initialize on this thread + t = in_thread do + subject.put(42) + end + t.join(0.1) + + # TODO (pitr-ch 15-Oct-2016): fails on JRuby https://travis-ci.org/pitr-ch/concurrent-ruby/jobs/167937038 + expect(subject.poll).to eq 42 + end + + it 'returns Concurrent::NULL immediately if no item is available' do + expect(subject.poll).to eq Concurrent::NULL + end + + it 'returns Concurrent::NULL when closed' do + subject.close + expect(subject.poll).to eq Concurrent::NULL + end + end + + context '#close' do + + it 'sets #closed? to false' do + subject.close + expect(subject).to be_closed + end + + it 'returns true when not previously closed' do + expect(subject.close).to be true + end + + it 'returns false when already closed' do + subject.close + expect(subject.close).to be false + end + end + + context '#closed?' do + + it 'returns true when new' do + expect(subject).to_not be_closed + end + + it 'returns false after #close' do + subject.close + expect(subject).to be_closed + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/base_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/base_spec.rb new file mode 100644 index 0000000000..fa487a0b3d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/base_spec.rb @@ -0,0 +1,75 @@ +require_relative 'buffered_shared' +require 'concurrent/channel/buffer/base' + +module Concurrent::Channel::Buffer + + RSpec.describe Base, edge: true do + + subject { described_class.new } + + specify do + expect(subject.capacity).to eq 0 + end + + specify do + expect(subject).to be_blocking + end + + specify do + expect { + subject.size + }.to raise_error(NotImplementedError) + end + + specify do + expect { + subject.empty? + }.to raise_error(NotImplementedError) + end + + specify do + expect { + subject.full? + }.to raise_error(NotImplementedError) + end + + specify do + expect { + subject.put(42) + }.to raise_error(NotImplementedError) + end + + specify do + expect { + subject.offer(42) + }.to raise_error(NotImplementedError) + end + + specify do + expect { + subject.take + }.to raise_error(NotImplementedError) + end + + specify do + expect { + subject.poll + }.to raise_error(NotImplementedError) + end + + specify do + expect { + subject.next + }.to raise_error(NotImplementedError) + end + + specify do + expect(subject).to_not be_closed + end + + specify do + subject.close + expect(subject).to be_closed + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/buffered_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/buffered_shared.rb new file mode 100644 index 0000000000..a55a016ad0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/buffered_shared.rb @@ -0,0 +1,176 @@ +require_relative 'base_shared' +require 'concurrent/channel/buffer/buffered' + +RSpec.shared_examples :channel_buffered_buffer do + + it_behaves_like :channel_buffer + + context '#initialize' do + it 'raises an exception if size <= 0' do + expect { + described_class.new(0) + }.to raise_error(ArgumentError) + end + end + + context '#capacity' do + it 'returns the maximum capacity of the buffer' do + subject = described_class.new(10) + expect(subject.capacity).to eq 10 + end + end + + context '#size' do + + it 'is 0 when first created' do + expect(subject.size).to eq 0 + end + + it 'returns the number of items in the buffer' do + fill = subject.capacity / 2 + fill.times { subject.put(:foo) } + expect(subject.size).to eq fill + end + + it 'is 0 when there are taking threads but no putting threads' do + t = in_thread { subject.take } + t.join(0.1) + expect(subject.size).to eq 0 + t.kill # cleanup + end + end + + context '#empty?' do + it 'returns true when empty' do + subject = described_class.new(10) + expect(subject).to be_empty + end + end + + context '#put' do + + it 'enqueues the item when size > 0, not full, and not closed' do + subject.put(:foo) + expect(subject).to_not be_empty + end + + it 'returns true when the item is put' do + expect(subject.put(:foo)).to be true + end + end + + context '#offer' do + it 'enqueues the item immediately when not full and not closed' do + subject.offer(:foo) + expect(subject.take).to eq :foo + end + end + + context '#take' do + + it 'returns the first item when not empty' do + subject.put(:foo) + expect(subject.take).to eq :foo + end + + it 'blocks until not empty' do + subject # initialize on this thread + bucket = Concurrent::AtomicReference.new(nil) + t = in_thread do + bucket.value = subject.take + end + t.join(0.1) + + before = bucket.value + subject.put(42) + t.join(0.1) + after = bucket.value + + expect(before).to be nil + expect(after).to eq 42 + expect(t.status).to be false + end + + it 'returns Concurrent::NULL when closed and empty' do + subject.close + expect(subject.take).to eq Concurrent::NULL + end + end + + context '#next' do + + it 'blocks until not empty' do + subject # initialize on this thread + bucket = Concurrent::AtomicReference.new([]) + t = in_thread do + bucket.value = subject.next + end + t.join(0.1) + + before = bucket.value + subject.put(42) + t.join(0.1) + after = bucket.value + + expect(before).to eq [] + expect(after.first).to eq 42 + expect(after.last).to be true + expect(t.status).to be false + end + + it 'returns , true when there is only one item and not closed' do + subject.offer(42) + + item, more = subject.next + expect(item).to eq 42 + expect(more).to be true + end + + it 'returns , true when there are multiple items' do + subject.offer(:foo) + subject.offer(:bar) + subject.offer(:baz) + + item1, more1 = subject.next + item2, more2 = subject.next + item3, more3 = subject.next + + expect(item1).to eq :foo + expect(more1).to be true + + expect(item2).to eq :bar + expect(more2).to be true + + expect(item3).to eq :baz + expect(more3).to be true + end + + it 'returns , true when closed and last item' do + capacity = subject.capacity + expect(capacity).to be >= 1 + + capacity.times { subject.put(:foo) } + subject.close + + capacity.times do + item, more = subject.next + expect(item).to eq :foo + expect(more).to be true + end + end + + it 'returns Concurrent::NULL, false when closed and no items remain' do + capacity = subject.capacity + expect(capacity).to be >= 1 + + capacity.times { subject.put(:foo) } + subject.close + + capacity.times { subject.next } + + item, more = subject.next + expect(item).to eq Concurrent::NULL + expect(more).to be false + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/buffered_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/buffered_spec.rb new file mode 100644 index 0000000000..5eff41aadb --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/buffered_spec.rb @@ -0,0 +1,55 @@ +require_relative 'buffered_shared' + +module Concurrent::Channel::Buffer + + RSpec.describe Buffered, edge: true do + + let(:capacity) { 10 } + subject { described_class.new(capacity) } + + it_behaves_like :channel_buffered_buffer + + specify do + expect(subject).to be_blocking + end + + context '#full?' do + it 'returns true when at max capacity' do + subject = described_class.new(1) + subject.put(:foo) + expect(subject).to be_full + end + end + + context '#put' do + it 'blocks when at capacity until a thread is ready to take' do + subject = described_class.new(1) + subject.put(13) + bucket = Concurrent::AtomicReference.new(nil) + t = in_thread do + subject.put(42) + bucket.value = 42 + end + + t.join(0.1) + + before = bucket.value + subject.take + t.join(0.1) + after = bucket.value + + expect(before).to be nil + expect(after).to eq 42 + expect(t.status).to be false + end + end + + context '#offer' do + it 'returns false immediately when full' do + subject = described_class.new(1) + subject.put(:foo) + expect(subject.offer(:bar)).to be false + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/dropping_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/dropping_spec.rb new file mode 100644 index 0000000000..f96d7a43b4 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/dropping_spec.rb @@ -0,0 +1,49 @@ +require_relative 'buffered_shared' +require 'concurrent/channel/buffer/dropping' + +module Concurrent::Channel::Buffer + + RSpec.describe Dropping, edge: true do + + subject { described_class.new(10) } + + it_behaves_like :channel_buffered_buffer + + specify do + expect(subject).to_not be_blocking + end + + context '#put' do + + it 'does not block when full' do + subject = described_class.new(1) + 3.times {|i| expect(subject.put(i)).to be true } + end + + it 'drops the last value when full' do + subject = described_class.new(1) + 3.times{|i| subject.put(i)} + internal_buffer = subject.instance_variable_get(:@buffer) + expect(internal_buffer.size).to eq 1 + expect(internal_buffer.first).to eq 0 + end + end + + context '#offer' do + + it 'returns true immediately when full' do + subject = described_class.new(1) + subject.put(:foo) + expect(subject.offer(:bar)).to be true + end + + it 'drops the last value when full' do + subject = described_class.new(1) + 3.times{|i| subject.offer(i)} + internal_buffer = subject.instance_variable_get(:@buffer) + expect(internal_buffer.size).to eq 1 + expect(internal_buffer.first).to eq 0 + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/sliding_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/sliding_spec.rb new file mode 100644 index 0000000000..52642da4b6 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/sliding_spec.rb @@ -0,0 +1,49 @@ +require_relative 'buffered_shared' +require 'concurrent/channel/buffer/sliding' + +module Concurrent::Channel::Buffer + + RSpec.describe Sliding, edge: true do + + subject { described_class.new(10) } + + it_behaves_like :channel_buffered_buffer + + specify do + expect(subject).to_not be_blocking + end + + context '#put' do + + it 'does not block when full' do + subject = described_class.new(1) + 3.times {|i| expect(subject.put(i)).to be true } + end + + it 'drops the first value when full' do + subject = described_class.new(1) + 3.times{|i| subject.put(i)} + internal_buffer = subject.instance_variable_get(:@buffer) + expect(internal_buffer.size).to eq 1 + expect(internal_buffer.first).to eq 2 + end + end + + context '#offer' do + + it 'returns true immediately when full' do + subject = described_class.new(1) + subject.put(:foo) + expect(subject.offer(:bar)).to be true + end + + it 'drops the first value when full' do + subject = described_class.new(1) + 3.times{|i| subject.offer(i)} + internal_buffer = subject.instance_variable_get(:@buffer) + expect(internal_buffer.size).to eq 1 + expect(internal_buffer.first).to eq 2 + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/ticker_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/ticker_spec.rb new file mode 100644 index 0000000000..52eddde5da --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/ticker_spec.rb @@ -0,0 +1,61 @@ +require_relative 'timing_buffer_shared' +require 'concurrent/channel/buffer/ticker' + +module Concurrent::Channel::Buffer + + RSpec.describe Ticker, edge: true do + + let(:delay) { 0.1 } + subject { described_class.new(delay) } + + it_behaves_like :channel_timing_buffer + + context '#take' do + it 'triggers until closed' do + expected = 3 + actual = 0 + expected.times { actual += 1 if subject.take.is_a? Concurrent::Channel::Tick } + expect(actual).to eq expected + end + + it 'returns Concurrent::NULL when closed after trigger' do + subject.take + subject.close + expect(subject).to be_closed + expect(subject.take).to eq Concurrent::NULL + end + end + + context '#poll' do + it 'triggers until closed' do + expected = 3 + actual = 0 + expected.times do + until subject.poll.is_a?(Concurrent::Channel::Tick) + actual += 1 + end + end + end + end + + context '#next' do + it 'triggers until closed' do + expected = 3 + actual = 0 + expected.times { actual += 1 if subject.next.first.is_a? Concurrent::Channel::Tick } + expect(actual).to eq expected + end + + it 'returns true for more while open' do + _, more = subject.next + expect(more).to be true + end + + it 'returns false for more once closed' do + subject.close + _, more = subject.next + expect(more).to be false + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/timer_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/timer_spec.rb new file mode 100644 index 0000000000..6f035b4d55 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/timer_spec.rb @@ -0,0 +1,45 @@ +require_relative 'timing_buffer_shared' +require 'concurrent/channel/buffer/timer' +require 'concurrent/atomic/atomic_boolean' + +module Concurrent::Channel::Buffer + + RSpec.describe Timer, edge: true do + + let(:delay) { 0.1 } + subject { described_class.new(0.1) } + + it_behaves_like :channel_timing_buffer + + context '#take' do + it 'closes automatically on first take' do + expect(subject.take).to be_truthy + expect(subject).to be_closed + end + end + + context '#poll' do + it 'closes automatically on first take' do + loop do + break if subject.poll != Concurrent::NULL + end + expect(subject).to be_closed + end + end + + context '#next' do + it 'closes automatically on first take' do + loop do + value, _ = subject.next + break if value != Concurrent::NULL + end + expect(subject).to be_closed + end + + it 'returns false for more' do + _, more = subject.next + expect(more).to be false + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/timing_buffer_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/timing_buffer_shared.rb new file mode 100644 index 0000000000..15a3a7e1d5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/timing_buffer_shared.rb @@ -0,0 +1,168 @@ +require_relative 'base_shared' +require 'concurrent/atomic/atomic_boolean' + +RSpec.shared_examples :channel_timing_buffer do + + specify do + expect(subject).to be_blocking + end + + context '#capacity' do + specify do + expect(subject.capacity).to eq 1 + end + end + + context '#size' do + specify do + expect(subject.size).to eq 0 + end + end + + context '#empty?' do + specify do + expect(subject).to_not be_empty + end + end + + context '#full?' do + specify do + expect(subject).to be_full + end + end + + context '#put' do + specify do + expect(subject.put(:foo)).to be false + end + end + + context '#offer' do + specify do + expect(subject.offer(:foo)).to be false + end + end + + context '#take' do + + it 'blocks when the timer is not ready' do + actual = Concurrent::AtomicBoolean.new(false) + subject = described_class.new(10) + t = in_thread do + subject.take + actual.make_true + end + t.join(0.1) + actual = actual.value + t.kill # clean up + expect(actual).to be false + end + + it 'returns a Tick' do + subject = described_class.new(0.1) + expect(subject.take).to be_a Concurrent::Channel::Tick + end + + it 'triggers after the specified time interval' do + start = Concurrent::Channel::Tick.new.monotonic + subject = described_class.new(0.1) + actual = subject.take.monotonic + expect(actual - start).to be >= 0.1 + end + + it 'returns Concurrent::NULL when closed' do + subject.close + expect(subject.take).to eq Concurrent::NULL + end + end + + context '#poll' do + + it 'returns Concurrent::NULL when the timer is not ready' do + subject = described_class.new(0.1) + expect(subject.poll).to eq Concurrent::NULL + end + + it 'returns a Tick' do + subject = described_class.new(0.1) + sleep(0.2) + expect(subject.poll).to be_a Concurrent::Channel::Tick + end + + it 'returns Concurrent::NULL when closed' do + subject.close + expect(subject.poll).to eq Concurrent::NULL + end + + it 'triggers after the specified time interval' do + start = Concurrent::Channel::Tick.new.monotonic + subject = described_class.new(0.1) + sleep(0.2) + actual = subject.poll.monotonic + expect(actual - start).to be >= 0.1 + end + end + + context '#next' do + + it 'blocks when the timer is not ready' do + actual = Concurrent::AtomicBoolean.new(false) + subject = described_class.new(10) + t = in_thread do + subject.next + actual.make_true + end + t.join(0.1) + actual = actual.value + t.kill # clean up + expect(actual).to be false + end + + it 'returns a Tick when open' do + subject = described_class.new(0.1) + value, _ = subject.next + expect(value).to be_a Concurrent::Channel::Tick + end + + it 'returns Concurrent::NULL, false when closed' do + subject.close + expect(subject.take).to eq Concurrent::NULL + end + + it 'triggers after the specified time interval' do + start = Concurrent::Channel::Tick.new.monotonic + subject = described_class.new(0.1) + actual, _ = subject.next + expect(actual.monotonic - start).to be >= 0.1 + end + end + + context '#close' do + + it 'sets #closed? to false' do + subject.close + expect(subject).to be_closed + end + + it 'returns true when not previously closed' do + expect(subject.close).to be true + end + + it 'returns false when already closed' do + subject.close + expect(subject.close).to be false + end + end + + context '#closed?' do + + it 'returns true when new' do + expect(subject).to_not be_closed + end + + it 'returns false after #close' do + subject.close + expect(subject).to be_closed + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/unbuffered_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/unbuffered_spec.rb new file mode 100644 index 0000000000..7a3241f9e4 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/buffer/unbuffered_spec.rb @@ -0,0 +1,252 @@ +require_relative 'base_shared' +require 'concurrent/channel/buffer/unbuffered' + +module Concurrent::Channel::Buffer + + RSpec.describe Unbuffered, edge: true do + + subject { described_class.new } + + it_behaves_like :channel_buffer + + specify do + expect(subject).to be_blocking + end + + specify do + expect(subject.capacity).to eq 1 + end + + context '#size' do + + it 'is 0 when first created' do + expect(subject.size).to eq 0 + end + + it 'is 1 when a putting thread is waiting' do + t = in_thread { subject.put(:foo) } + t.join(0.1) + expect(subject.size).to eq 1 + t.kill # cleanup + end + + it 'is 0 when there are taking threads but no putting threads' do + t = in_thread { subject.take } + t.join(0.1) + expect(subject.size).to eq 0 + t.kill # cleanup + end + end + + context '#empty?' do + + it 'is true when there are no putting threads' do + expect(subject).to be_empty + end + + it 'is false when there are waiting putting threads' do + t = in_thread { subject.put(:foo) } + t.join(0.1) + expect(subject).to_not be_empty + t.kill # cleanup + end + end + + context '#full?' do + + it 'is false when there are no putting threads' do + expect(subject).to_not be_full + end + + it 'is false when there are waiting putting threads' do + t = in_thread { subject.put(:foo) } + t.join(0.1) + expect(subject).to be_full + t.kill # cleanup + end + end + + context '#put' do + + it 'does not enqueue the item when closed' do + subject.close + subject.put(:foo) + expect(subject).to be_empty + end + + it 'returns false when closed' do + subject.close + expect(subject.put(:foo)).to be false + end + + it 'blocks until a thread is ready to take' do + subject # initialize on this thread + bucket = Concurrent::AtomicReference.new(nil) + t = in_thread do + subject.put(42) + bucket.value = 42 + end + + t.join(0.1) + + before = bucket.value + subject.take + t.join(0.1) + after = bucket.value + + expect(before).to be nil + expect(after).to eq 42 + expect(t.status).to be false + end + + it 'delivers when closed after put starts' do + t = in_thread do + subject.put(:foo) + end + t.join(0.1) + subject.close + + item = subject.take + t.kill #clean up + + expect(item).to eq :foo + end + end + + context '#offer' do + + it 'returns false immediately when a put in in progress' do + subject # initialize on this thread + t = in_thread do + subject.put(:foo) # block the thread + end + t.join(0.1) + + ok = subject.offer(:bar) + subject.poll # release the blocked thread + + expect(ok).to be false + end + + it 'gives the item to a waiting taker and returns true' do + subject # initialize on this thread + bucket = Concurrent::AtomicReference.new(nil) + t = in_thread do + bucket.value = subject.take + end + t.join(0.1) + + before = bucket.value + ok = subject.offer(42) + t.join(0.1) + after = bucket.value + + expect(ok).to be true + expect(before).to be nil + expect(after).to eq 42 + end + end + + context '#take' do + + it 'returns false immediately when a put in in progress' do + subject # initialize on this thread + t = in_thread do + subject.put(:foo) # block the thread + end + t.join(0.1) + + ok = subject.offer(:bar) + subject.poll # release the blocked thread + + expect(ok).to be false + end + + it 'gives the item to a waiting taker and returns true' do + subject # initialize on this thread + bucket = Concurrent::AtomicReference.new(nil) + t = in_thread do + bucket.value = subject.take + end + t.join(0.1) + + before = bucket.value + ok = subject.offer(42) + t.join(0.1) + after = bucket.value + + expect(ok).to be true + expect(before).to be nil + expect(after).to eq 42 + end + end + + context '#next' do + + it 'blocks when no putting and returns , true when one arrives' do + subject # initialize on this thread + bucket = Concurrent::AtomicReference.new([]) + t = in_thread do + bucket.value = subject.next + end + t.join(0.1) + + before = bucket.value + subject.put(42) + t.join(0.1) + after = bucket.value + + expect(before).to eq [] + expect(after.first).to eq 42 + expect(after.last).to be true + expect(t.status).to be false + end + + it 'returns , true when there are multiple putting' do + subject # initialize on this thread + threads = 2.times.collect do + in_thread do + subject.put(42) + end + end + threads.each {|t| t.join(0.1)} + + item, more = subject.next + subject.poll # clear the channel + + expect(item).to eq 42 + expect(more).to be true + end + + it 'returns , true when closed and last item' do + t = in_thread do + subject.put(:foo) + end + t.join(0.1) + subject.close + + item, more = subject.next + t.kill #clean up + + expect(item).to eq :foo + expect(more).to be true + end + + it 'returns Concurrent::NULL, false when closed and no items remain' do + skip('flaky on truffleruby') if Concurrent.on_truffleruby? + + t = in_thread do + subject.put(:foo) + end + subject.close + + subject.next + item, more = subject.next + t.kill #clean up + + expect(item).to eq Concurrent::NULL + expect(more).to be false + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/integration_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/integration_spec.rb new file mode 100644 index 0000000000..01f490dc97 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/integration_spec.rb @@ -0,0 +1,252 @@ +require 'concurrent/utility/engine' + +RSpec.describe 'channel integration tests', edge: true do + + let!(:examples_root) { File.expand_path(File.join(File.dirname(__FILE__), '../../../examples')) } + + context 'A Tour of Go' do + + let!(:script_root) { File.join(examples_root, 'a-tour-of-go-channels') } + + specify 'channels.rb' do + expected = [-5, 17, 12] + result = `ruby #{File.join(script_root, 'channels.rb')}` + results = result.split(' ').map(&:chomp).collect{|i| i.to_i} + + expect($?.to_i).to eq 0 + expect(results.length).to eq 3 + expected.each do |n| + expect(results).to include(n) + end + end + + specify 'buffered-channels.rb' do +expected = <<-STDOUT +1 +2 +STDOUT + result = `ruby #{File.join(script_root, 'buffered-channels.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'range-and-close.rb' do +expected = <<-STDOUT +0 +1 +1 +2 +3 +5 +8 +13 +21 +34 +STDOUT + result = `ruby #{File.join(script_root, 'range-and-close.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'select.rb' do +expected = <<-STDOUT +0 +1 +1 +2 +3 +5 +8 +13 +21 +34 +quit +STDOUT + result = `ruby #{File.join(script_root, 'select.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'default-selection.rb' do + skip('flaky') if Concurrent.on_jruby? || Concurrent.on_truffleruby? +expected = <<-STDOUT + . + . +tick. + . + . +tick. + . + . +tick. + . + . +tick. + . + . +tick. +BOOM! +STDOUT + result = `ruby #{File.join(script_root, 'default-selection.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + end + + context 'Go By Example' do + + let!(:script_root) { File.join(examples_root, 'go-by-example-channels') } + + specify 'channels.rb' do +expected = <<-STDOUT +ping +STDOUT + result = `ruby #{File.join(script_root, 'channels.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'channel-buffering.rb' do +expected = <<-STDOUT +buffered +channel +STDOUT + result = `ruby #{File.join(script_root, 'channel-buffering.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'channel-synchronization.rb' do +expected = <<-STDOUT +working... +done +STDOUT + result = `ruby #{File.join(script_root, 'channel-synchronization.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'channel-directions.rb' do +expected = <<-STDOUT +passed message +STDOUT + result = `ruby #{File.join(script_root, 'channel-directions.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'select.rb' do +expected = <<-STDOUT +received one +received two +STDOUT + result = `ruby #{File.join(script_root, 'select.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'timeouts.rb' do +expected = <<-STDOUT +timeout 1 +result 2 +STDOUT + result = `ruby #{File.join(script_root, 'timeouts.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'non-blocking-channel-operations.rb' do +expected = <<-STDOUT +no message received +no message sent +no activity +STDOUT + result = `ruby #{File.join(script_root, 'non-blocking-channel-operations.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'closing-channels.rb' do + expected = [ + 'sent job 1', + 'received job 1', + 'sent job 2', + 'received job 2', + 'sent job 3', + 'received job 3', + 'sent all jobs', + 'received all jobs', + ] + result = `ruby #{File.join(script_root, 'closing-channels.rb')}` + expect($?.to_i).to eq 0 + expected.each do |line| + expect(result).to match(/^#{line}$/) + end + end + + specify 'range-over-channels.rb' do +expected = <<-STDOUT +one +two +STDOUT + result = `ruby #{File.join(script_root, 'range-over-channels.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'timers.rb' do +expected = <<-STDOUT +Timer 1 expired +Timer 2 stopped +STDOUT + result = `ruby #{File.join(script_root, 'timers.rb')}` + expect($?.to_i).to eq 0 + expect(result).to eq expected + end + + specify 'ticker.rb' do + result = `ruby #{File.join(script_root, 'ticker.rb')}` + results = result.lines.map(&:chomp) + + expect($?.to_i).to eq 0 + expect(results.length).to eq 4 + + (0..2).each do |i| + expect(results[i]).to match(/^Tick at /) + end + expect(results.last).to match('Ticker stopped') + end + + specify 'worker-pools.rb' do + expected = [ + /^worker \d processing job 1$/, + /^worker \d processing job 2$/, + /^worker \d processing job 3$/, + /^worker \d processing job 4$/, + /^worker \d processing job 5$/, + /^worker \d processing job 6$/, + /^worker \d processing job 7$/, + /^worker \d processing job 8$/, + /^worker \d processing job 9$/, + ] + result = `ruby #{File.join(script_root, 'worker-pools.rb')}` + expect($?.to_i).to eq 0 + expected.each do |regex| + expect(result).to match(regex) + end + end + + specify 'rate-limiting.rb' do + result = `ruby #{File.join(script_root, 'rate-limiting.rb')}` + results = result.lines.map(&:chomp) + + expect($?.to_i).to eq 0 + expect(results.length).to eq 11 + + (0..4).each do |i| + expect(results[i]).to match(/^request #{i+1}/) + expect(results[i+6]).to match(/^request #{i+1}/) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/tick_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/tick_spec.rb new file mode 100644 index 0000000000..fe3eb9a03e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel/tick_spec.rb @@ -0,0 +1,74 @@ +require 'concurrent/channel/tick' + +module Concurrent + + class Channel + + RSpec.describe Tick, edge: true do + + it 'initializes to current time when no argument given' do + allow(Concurrent).to receive(:monotonic_time).and_return(42) + subject = Tick.new + expect(subject.monotonic).to eq 42 + end + + it 'initializes to the given monotonic time' do + m = Concurrent.monotonic_time + subject = Tick.new(m) + expect(subject.monotonic).to eq m + end + + specify '#utc returns a Time object in UTC' do + t = subject.utc + expect(t).to be_a Time + expect(t.zone).to eq 'UTC' + end + + specify '#epoch returns the UTC time as epoch seconds' do + expect(subject.utc.to_f).to eq subject.epoch + end + + specify '#to_s formats as a time', :truffle_bug => true do + expect(subject.to_s).to match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6} \+\d{4} UTC/) + end + + context 'comparison' do + + it 'correctly compares to a Numeric (monotonic)' do + present = Concurrent.monotonic_time + past = present - 42 + future = present + 42 + + subject = Tick.new(present) + + expect(subject).to be < future + expect(subject).to be == present + expect(subject).to be > past + end + + it 'correctly compares to a Time' do + past = Time.now - 42*60*60 + future = Time.now + 42*60*60 + + subject = Tick.new + + expect(subject).to be < future + expect(subject).to be > past + end + + it 'correctly compares to a Tick' do + now = Concurrent.monotonic_time + present = Tick.new(now) + past = Tick.new(now - 42) + future = Tick.new(now + 42) + + subject = Tick.new(now) + + expect(subject).to be < future + expect(subject).to eq present + expect(subject).to be > past + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel_spec.rb new file mode 100644 index 0000000000..d5c442a567 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/channel_spec.rb @@ -0,0 +1,665 @@ +require 'concurrent/channel' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/executor/immediate_executor' + +module Concurrent + + RSpec.describe Channel, edge: true do + + context 'initialization' do + + it 'raises an exception when the :buffer is invalid' do + expect { + Channel.new(buffer: :bogus) + }.to raise_error(ArgumentError) + end + + it 'is :unbuffered when neither :buffer nore :capacity is given' do + expect(Channel::Buffer::Unbuffered).to receive(:new).with(no_args).and_call_original + Channel.new + end + + it 'is :unbuffered when :unbuffered is given' do + expect(Channel::Buffer::Unbuffered).to receive(:new).with(no_args).and_call_original + Channel.new(buffer: :unbuffered) + end + + it 'is :unbuffered when :buffered and capacity: 0' do + expect(Channel::Buffer::Unbuffered).to receive(:new).with(no_args).and_call_original + Channel.new(buffer: :buffered, capacity: 0) + end + + it 'raises an exception when both :unbuffered and :capacity are given' do + expect { + Channel.new(buffer: :unbuffered, capacity: 0) + }.to raise_error(ArgumentError) + end + + it 'is :buffered when :capacity > 0 and no :buffer given' do + expect(Channel::Buffer::Buffered).to receive(:new).with(5).and_call_original + Channel.new(capacity: 5) + end + + it 'is :buffered when :buffered given' do + expect(Channel::Buffer::Buffered).to receive(:new).with(5).and_call_original + Channel.new(buffer: :buffered, capacity: 5) + end + + it 'raises an exception when :buffered given without :capacity' do + expect { + Channel.new(buffer: :buffered) + }.to raise_error(ArgumentError) + end + + it 'raises an exception when :buffered and :capacity < 0' do + expect { + Channel.new(buffer: :buffered, capacity: -1) + }.to raise_error(ArgumentError) + end + + it 'is :dropping when :dropping and :capacity > 0' do + expect(Channel::Buffer::Dropping).to receive(:new).with(5).and_call_original + Channel.new(buffer: :dropping, capacity: 5) + end + + it 'raises an exception when :dropping given without :capacity' do + expect { + Channel.new(buffer: :dropping) + }.to raise_error(ArgumentError) + end + + it 'raises an exception when :dropping and :capacity < 1' do + expect { + Channel.new(buffer: :dropping, capacity: 0) + }.to raise_error(ArgumentError) + end + + it 'is :sliding when :sliding and :capacity > 0' do + expect(Channel::Buffer::Sliding).to receive(:new).with(5).and_call_original + Channel.new(buffer: :sliding, capacity: 5) + end + + it 'raises an exception when :sliding given without :capacity' do + expect { + Channel.new(buffer: :sliding) + }.to raise_error(ArgumentError) + end + + it 'raises an exception when :sliding and :capacity < 1' do + expect { + Channel.new(buffer: :sliding, capacity: 0) + }.to raise_error(ArgumentError) + end + + it 'uses the given buffer' do + buffer = Channel::Buffer::Buffered.new(10) + subject = Channel.new(buffer) + expect(subject).to receive(:put).with(42) + subject.put(42) + end + end + + context 'factories' do + + specify do + expect(Channel::Buffer::Ticker).to receive(:new).with(10).and_call_original + Channel.ticker(10) + end + + specify do + expect(Channel::Buffer::Timer).to receive(:new).with(10).and_call_original + Channel.timer(10) + end + end + + context '#put' do + + it 'returns true on success' do + subject = Channel.new(buffer: :buffered, capacity: 2) + expect(subject.put(:foo)).to be true + end + + it 'returns false on failure' do + subject = Channel.new(buffer: :buffered, capacity: 2) + subject.close + expect(subject.put(:foo)).to be false + end + + it 'rejects when the validator returns false' do + validator = ->(value) { false } + subject = Channel.new(capacity: 10, validator: validator) + expect(subject.put(42)).to be false + end + + it 'rejects when the validator raises an exception' do + validator = ->(value) { raise StandardError } + subject = Channel.new(capacity: 10, validator: validator) + expect(subject.put(42)).to be false + end + + it 'rejects nil' do + expect(subject.put(nil)).to be false + end + end + + context 'put!' do + + it 'returns true on success' do + subject = Channel.new(buffer: :buffered, capacity: 2) + expect(subject.put!(:foo)).to be true + end + + it 'raises an exception on failure' do + subject = Channel.new(buffer: :buffered, capacity: 2) + subject.close + expect { + subject.put!(:foo) + }.to raise_error(Channel::Error) + end + + it 'rejects when the validator returns false' do + validator = ->(value) { false } + subject = Channel.new(capacity: 10, validator: validator) + expect{ + subject.put!(42) + }.to raise_error(Channel::ValidationError) + end + + it 'rejects when the validator raises an exception' do + validator = ->(value) { raise StandardError } + subject = Channel.new(capacity: 10, validator: validator) + expect{ + subject.put!(42) + }.to raise_error(StandardError) + end + + it 'rejects nil' do + expect { + subject.put!(nil) + }.to raise_error(Channel::ValidationError) + end + end + + context 'put?' do + + it 'returns a just Maybe on success' do + subject = Channel.new(buffer: :buffered, capacity: 2) + result = subject.put?(:foo) + expect(result).to be_a Concurrent::Maybe + expect(result).to be_just + end + + it 'returns a nothing Maybe on failure' do + subject = Channel.new(buffer: :buffered, capacity: 2) + subject.close + result = subject.put?(:foo) + expect(result).to be_a Concurrent::Maybe + expect(result).to be_nothing + end + + it 'rejects when the validator returns false' do + validator = ->(value) { false } + subject = Channel.new(capacity: 10, validator: validator) + expect(subject.put?(42)).to be_nothing + end + + it 'rejects when the validator raises an exception' do + validator = ->(value) { false } + subject = Channel.new(capacity: 10, validator: validator) + expect(subject.put?(42)).to be_nothing + end + + it 'accepts nil' do + result = subject.put?(nil) + expect(result).to be_a Concurrent::Maybe + expect(result).to be_just + end + end + + context '#offer' do + + it 'returns true on success' do + subject = Channel.new(buffer: :buffered, capacity: 2) + expect(subject.offer(:foo)).to be true + end + + it 'returns false on failure' do + subject = Channel.new(buffer: :buffered, capacity: 2) + subject.close + expect(subject.offer(:foo)).to be false + end + + it 'rejects when the validator returns false' do + validator = ->(value) { false } + subject = Channel.new(capacity: 10, validator: validator) + expect(subject.offer(42)).to be false + end + + it 'rejects when the validator raises an exception' do + validator = ->(value) { raise StandardError } + subject = Channel.new(capacity: 10, validator: validator) + expect(subject.offer(42)).to be false + end + + it 'rejects nil' do + expect(subject.offer(nil)).to be false + end + end + + context 'offer!' do + + it 'returns true on success' do + subject = Channel.new(buffer: :buffered, capacity: 2) + expect(subject.offer!(:foo)).to be true + end + + it 'raises an exception on failure' do + subject = Channel.new(buffer: :buffered, capacity: 2) + subject.close + expect { + subject.offer!(:foo) + }.to raise_error(Channel::Error) + end + + it 'rejects when the validator returns false' do + validator = ->(value) { false } + subject = Channel.new(capacity: 10, validator: validator) + expect{ + subject.offer!(42) + }.to raise_error(Channel::ValidationError) + end + + it 'rejects when the validator raises an exception' do + validator = ->(value) { raise StandardError } + subject = Channel.new(capacity: 10, validator: validator) + expect{ + subject.offer!(42) + }.to raise_error(StandardError) + end + + it 'rejects nil' do + expect { + subject.offer!(nil) + }.to raise_error(Channel::ValidationError) + end + end + + context 'offer?' do + + it 'returns a just Maybe on success' do + subject = Channel.new(buffer: :buffered, capacity: 2) + result = subject.offer?(:foo) + expect(result).to be_a Concurrent::Maybe + expect(result).to be_just + end + + it 'returns a nothing Maybe on failure' do + subject = Channel.new(buffer: :buffered, capacity: 2) + subject.close + result = subject.offer?(:foo) + expect(result).to be_a Concurrent::Maybe + expect(result).to be_nothing + end + + it 'rejects when the validator returns false' do + validator = ->(value) { false } + subject = Channel.new(capacity: 10, validator: validator) + expect(subject.offer?(42)).to be_nothing + end + + it 'rejects when the validator raises an exception' do + validator = ->(value) { false } + subject = Channel.new(capacity: 10, validator: validator) + expect(subject.offer?(42)).to be_nothing + end + + it 'accepts nil' do + subject = Channel.new(buffer: :buffered, capacity: 2) + result = subject.offer?(nil) + expect(result).to be_a Concurrent::Maybe + expect(result).to be_just + end + end + + context '#take' do + + subject { Channel.new(buffer: :buffered, capacity: 2) } + + it 'takes the next item when not empty' do + subject.put(:foo) + expect(subject.take).to eq :foo + end + + it 'returns nil on failure' do + subject.close + expect(subject.take).to be nil + end + end + + context '#take!' do + + subject { Channel.new(buffer: :buffered, capacity: 2) } + + it 'takes the next item when not empty' do + subject.put(:foo) + expect(subject.take!).to eq :foo + end + + it 'raises an exception on failure' do + subject.close + expect { + subject.take! + }.to raise_error(Channel::Error) + end + end + + context '#take?' do + + subject { Channel.new(buffer: :buffered, capacity: 2) } + + it 'returns a just Maybe on success' do + subject.put(:foo) + result = subject.take? + expect(result).to be_a Concurrent::Maybe + expect(result).to be_just + expect(result.value).to eq :foo + end + + it 'returns a nothing Maybe on failure' do + subject.close + result = subject.take? + expect(result).to be_a Concurrent::Maybe + expect(result).to be_nothing + end + end + + context '#next' do + + subject { Channel.new(buffer: :buffered, capacity: 3) } + + it 'returns , true when there is one item' do + subject.put(:foo) + item, more = subject.next + expect(item).to eq :foo + expect(more).to be true + end + + it 'returns , true when there are multiple items' do + subject.put(:foo) + subject.put(:bar) + item, more = subject.next + subject.poll # clear the buffer + + expect(item).to eq :foo + expect(more).to be true + end + + it 'returns nil, false when empty and closed' do + subject.close + item, more = subject.next + expect(item).to be nil + expect(more).to be false + end + + it 'returns , true when closed and last item' do + capacity = subject.capacity + expect(capacity).to be >= 1 + + capacity.times { subject.put(:foo) } + subject.close + + capacity.times do + item, more = subject.next + expect(item).to eq :foo + expect(more).to be true + end + end + + it 'returns nil, false when closed and no items remain' do + capacity = subject.capacity + expect(capacity).to be >= 1 + + capacity.times { subject.put(:foo) } + subject.close + + capacity.times { subject.next } + + item, more = subject.next + expect(item).to be_nil + expect(more).to be false + end + end + + context '#next?' do + + subject { Channel.new(buffer: :buffered, capacity: 2) } + + it 'returns a just Maybe and true when there is one item' do + subject.put(:foo) + item, more = subject.next? + expect(item).to be_a Concurrent::Maybe + expect(item).to be_just + expect(item.value).to eq :foo + expect(more).to be true + end + + it 'returns a just Maybe, true when there are multiple items' do + subject.put(:foo) + subject.put(:bar) + item, more = subject.next? + subject.poll # clear the buffer + + expect(item).to be_a Concurrent::Maybe + expect(item).to be_just + expect(item.value).to eq :foo + expect(more).to be true + end + + it 'returns a nothing Maybe and false on failure' do + subject.close + item, more = subject.next? + expect(item).to be_a Concurrent::Maybe + expect(item).to be_nothing + expect(more).to be false + end + end + + context '#poll' do + + it 'returns the next item immediately if available' do + subject # initialize on this thread + t = in_thread do + subject.put(42) + end + t.join(0.1) + + expect(subject.poll).to eq 42 + end + + it 'returns nil immediately if no item is available' do + expect(subject.poll).to be nil + end + + it 'returns nil on failure' do + subject.close + expect(subject.poll).to be nil + end + end + + context '#poll!' do + + it 'returns the next item immediately if available' do + subject # initialize on this thread + t = in_thread do + subject.put(42) + end + t.join(0.1) + + expect(subject.poll!).to eq 42 + end + + it 'raises an exception immediately if no item is available' do + expect { + subject.poll! + }.to raise_error(Channel::Error) + end + + it 'raises an exception on failure' do + subject.close + expect { + subject.poll! + }.to raise_error(Channel::Error) + end + end + + context '#poll?' do + + it 'returns a just Maybe immediately if available' do + subject # initialize on this thread + t = in_thread do + subject.put(42) + end + t.join(0.1) + + result = subject.poll? + expect(result).to be_a Concurrent::Maybe + expect(result).to be_just + expect(result.value).to eq 42 + end + + it 'returns a nothing Maybe immediately if no item is available' do + result = subject.poll? + expect(result).to be_a Concurrent::Maybe + expect(result).to be_nothing + end + + it 'returns a nothing Maybe on failure' do + subject.close + result = subject.poll? + expect(result).to be_a Concurrent::Maybe + expect(result).to be_nothing + end + end + + context '.each' do + + it 'raises and exception when no block is given' do + expect { + subject.each + }.to raise_error(ArgumentError) + end + + it 'iterates until the channel is closed' do + expected = [13, 42, 2001] + subject = Channel.new(capacity: expected.length) + expected.each { |value| subject.put(value) } + subject.close + + actual = [] + subject.each { |value| actual << value } + expect(actual).to eq expected + end + end + + context 'goroutines' do + + let(:default_executor) { Channel.const_get(:GOROUTINES) } + + context '.go' do + + it 'raises an exception when no block is given' do + expect { + Channel.go + }.to raise_error(ArgumentError) + end + + specify do + expect(default_executor).to receive(:post).with(1, 2, 3) + Channel.go(1, 2, 3) { nil } + end + end + + context '.go_via' do + + it 'raises an exception when no block is given' do + expect { + Channel.go_via + }.to raise_error(ArgumentError) + end + + specify do + executor = ImmediateExecutor.new + expect(executor).to receive(:post).with(1, 2, 3) + Channel.go_via(executor, 1, 2, 3) { nil } + end + end + + context '.go_loop' do + + it 'raises an exception when no block is given' do + expect { + Channel.go_loop + }.to raise_error(ArgumentError) + end + + it 'loops until the block returns false' do + actual = 0 + expected = 3 + latch = Concurrent::CountDownLatch.new(expected) + Channel.go_loop do + actual += 1 + latch.count_down + actual < expected + end + + latch.wait(10) + expect(actual).to eq expected + end + end + + context '.go_loop_via' do + + it 'raises an exception when no block is given' do + expect { + Channel.go_loop_via + }.to raise_error(ArgumentError) + end + + it 'loops until the block returns false' do + actual = 0 + expected = 3 + executor = ImmediateExecutor.new + latch = Concurrent::CountDownLatch.new(expected) + Channel.go_loop_via(executor) do + actual += 1 + latch.count_down + actual < expected + end + + latch.wait(3) + expect(actual).to eq expected + end + end + end + + context 'select' do + + it 'raises an exception when no block is given' do + expect { + Channel.select + }.to raise_error(ArgumentError) + end + + it 'passes a selector to the block' do + actual = nil + Channel.select { |s| actual = s; s.error { } } + expect(actual).to be_a Channel::Selector + end + + specify do + expect_any_instance_of(Channel::Selector).to receive(:execute) + Channel.select { |s| s.error { } } + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/copy_on_notify_observer_set_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/copy_on_notify_observer_set_spec.rb new file mode 100644 index 0000000000..ba9c68ba14 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/copy_on_notify_observer_set_spec.rb @@ -0,0 +1,10 @@ +require 'concurrent/collection/copy_on_notify_observer_set' +require_relative 'observer_set_shared' + +module Concurrent + module Collection + RSpec.describe CopyOnNotifyObserverSet do + it_behaves_like 'an observer set' + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/copy_on_write_observer_set_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/copy_on_write_observer_set_spec.rb new file mode 100644 index 0000000000..3842f80a3e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/copy_on_write_observer_set_spec.rb @@ -0,0 +1,11 @@ +require 'concurrent/collection/copy_on_write_observer_set' +require_relative 'observer_set_shared' + +module Concurrent + module Collection + + RSpec.describe CopyOnWriteObserverSet do + it_behaves_like 'an observer set' + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/non_concurrent_priority_queue_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/non_concurrent_priority_queue_spec.rb new file mode 100644 index 0000000000..c9beebb513 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/non_concurrent_priority_queue_spec.rb @@ -0,0 +1,366 @@ +require 'concurrent/collection/non_concurrent_priority_queue' + +RSpec.shared_examples :priority_queue do + + subject{ described_class.new } + + context '#initialize' do + + it 'sorts from high to low when :order is :max' do + subject = described_class.from_list([2, 1, 4, 5, 3, 0], order: :max) + expect(subject.pop).to eq 5 + expect(subject.pop).to eq 4 + expect(subject.pop).to eq 3 + end + + it 'sorts from high to low when :order is :high' do + subject = described_class.new(order: :high) + [2, 1, 4, 5, 3, 0].each{|item| subject << item } + expect(subject.pop).to eq 5 + expect(subject.pop).to eq 4 + expect(subject.pop).to eq 3 + end + + it 'sorts from low to high when :order is :min' do + subject = described_class.from_list([2, 1, 4, 5, 3, 0], order: :min) + expect(subject.pop).to eq 0 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 2 + end + + it 'sorts from low to high when :order is :low' do + subject = described_class.new(order: :low) + [2, 1, 4, 5, 3, 0].each{|item| subject << item } + expect(subject.pop).to eq 0 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 2 + end + + it 'sorts from high to low by default' do + subject = described_class.new + subject = described_class.from_list([2, 1, 4, 5, 3, 0]) + expect(subject.pop).to eq 5 + expect(subject.pop).to eq 4 + expect(subject.pop).to eq 3 + end + end + + context '#clear' do + + it 'removes all items from a populated queue' do + 10.times{|i| subject << i} + subject.clear + expect(subject).to be_empty + end + + it 'has no effect on an empty queue' do + subject.clear + expect(subject).to be_empty + end + + specify { expect(subject.clear).to be_truthy } + end + + context '#delete' do + + it 'deletes the requested item when found' do + 10.times{|item| subject << item } + subject.delete(5) + expect(subject.pop).to eq 9 + expect(subject.pop).to eq 8 + expect(subject.pop).to eq 7 + expect(subject.pop).to eq 6 + expect(subject.pop).to eq 4 + expect(subject.pop).to eq 3 + expect(subject.pop).to eq 2 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 0 + end + + it 'deletes the requested item when it is the first element' do + 10.times{|item| subject << item } + subject.delete(9) + expect(subject.length).to eq 9 + expect(subject.pop).to eq 8 + expect(subject.pop).to eq 7 + expect(subject.pop).to eq 6 + expect(subject.pop).to eq 5 + expect(subject.pop).to eq 4 + expect(subject.pop).to eq 3 + expect(subject.pop).to eq 2 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 0 + end + + it 'deletes the requested item when it is the last element' do + 10.times{|item| subject << item } + subject.delete(2) + expect(subject.length).to eq 9 + expect(subject.pop).to eq 9 + expect(subject.pop).to eq 8 + expect(subject.pop).to eq 7 + expect(subject.pop).to eq 6 + expect(subject.pop).to eq 5 + expect(subject.pop).to eq 4 + expect(subject.pop).to eq 3 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 0 + end + + it 'deletes multiple matching items when present' do + [2, 1, 2, 2, 2, 3, 2].each{|item| subject << item } + subject.delete(2) + expect(subject.pop).to eq 3 + expect(subject.pop).to eq 1 + end + + it 'returns true when found' do + 10.times{|i| subject << i} + expect(subject.delete(2)).to be_truthy + end + + it 'returns false when not found' do + 10.times{|i| subject << i} + expect(subject.delete(100)).to be_falsey + end + + it 'returns false when called on an empty queue' do + expect(subject.delete(:foo)).to be_falsey + end + + def dequeue_all(queue) + queue.size.times.inject([]) do |acc, _| + acc << queue.pop + end + end + + it 'deletes the requested item when it is "smaller" than the last element' do + [ + 100, + 9, 90, + 7, 8, 70, 80, + 3, 4, 5, 6, 30, 40, 50, 60 + ].each do |item| + subject << item + end + + subject.delete(8) + + expect(subject.length).to eq 14 + expect(subject.pop).to eq 100 + expect(subject.pop).to eq 90 + expect(subject.pop).to eq 80 + expect(subject.pop).to eq 70 + expect(subject.pop).to eq 60 + expect(subject.pop).to eq 50 + expect(subject.pop).to eq 40 + expect(subject.pop).to eq 30 + expect(subject.pop).to eq 9 + expect(subject.pop).to eq 7 + expect(subject.pop).to eq 6 + expect(subject.pop).to eq 5 + expect(subject.pop).to eq 4 + expect(subject.pop).to eq 3 + end + end + + context '#empty?' do + + it 'returns true for an empty queue' do + expect(subject).to be_empty + end + + it 'returns false for a populated queue' do + 10.times{|i| subject << i} + expect(subject).not_to be_empty + end + end + + context '#include?' do + + it 'returns true if the item is found' do + 10.times{|i| subject << i} + expect(subject).to include(5) + end + + it 'returns false if the item is not found' do + 10.times{|i| subject << i} + expect(subject).not_to include(50) + end + + it 'returns false when the queue is empty' do + expect(subject).not_to include(1) + end + + it 'is aliased as #has_priority?' do + 10.times{|i| subject << i} + expect(subject).to have_priority(5) + end + end + + context '#length' do + + it 'returns the length of a populated queue' do + 10.times{|i| subject << i} + expect(subject.length).to eq 10 + end + + it 'returns zero when the queue is empty' do + expect(subject.length).to eq 0 + end + + it 'is aliased as #size' do + 10.times{|i| subject << i} + expect(subject.size).to eq 10 + end + end + + context '#peek' do + + it 'returns the item at the head of the queue' do + 10.times{|i| subject << i} + expect(subject.peek).to eq 9 + end + + it 'does not remove the item from the queue' do + 10.times{|i| subject << i} + subject.peek + expect(subject.length).to eq 10 + expect(subject).to include(9) + end + + it 'returns nil when the queue is empty' do + expect(subject.peek).to be_nil + end + end + + context '#pop' do + + it 'returns the item at the head of the queue' do + 10.times{|i| subject << i} + expect(subject.pop).to eq 9 + end + + it 'removes the item from the queue' do + 10.times{|i| subject << i} + subject.pop + expect(subject.length).to eq 9 + expect(subject).not_to include(9) + end + + it 'returns nil when the queue is empty' do + expect(subject.pop).to be_nil + end + + it 'returns nil when called multiple times while empty' do + 10.times do + expect(subject.pop).to be nil + end + end + + it 'is aliased as #deq' do + 10.times{|i| subject << i} + expect(subject.deq).to eq 9 + end + + it 'is aliased as #shift' do + 10.times{|i| subject << i} + expect(subject.shift).to eq 9 + end + end + + context '#push' do + + it 'raises an exception when attempting to enqueue nil' do + expect { + subject.push(nil) + }.to raise_error(ArgumentError) + end + + it 'adds the item to the queue' do + subject.push(1) + expect(subject).to include(1) + end + + it 'sorts the new item in priority order' do + 3.times{|i| subject << i} + expect(subject.pop).to eq 2 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 0 + end + + it 'arbitrarily orders equal items with respect to each other' do + 3.times{|i| subject << i} + subject.push(1) + expect(subject.pop).to eq 2 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 0 + end + + specify { expect(subject.push(10)).to be_truthy } + + it 'is aliased as <<' do + subject << 1 + expect(subject).to include(1) + end + + it 'is aliased as enq' do + subject.enq(1) + expect(subject).to include(1) + end + end + + context '.from_list' do + + it 'creates an empty queue from an empty list' do + subject = described_class.from_list([]) + expect(subject).to be_empty + end + + it 'creates a sorted, populated queue from an Array' do + subject = described_class.from_list([2, 1, 4, 5, 3, 0]) + expect(subject.pop).to eq 5 + expect(subject.pop).to eq 4 + expect(subject.pop).to eq 3 + expect(subject.pop).to eq 2 + expect(subject.pop).to eq 1 + expect(subject.pop).to eq 0 + end + + it 'creates a sorted, populated queue from a Hash' do + subject = described_class.from_list(two: 2, one: 1, three: 3, zero: 0) + expect(subject.length).to eq 4 + end + end +end + +module Concurrent + module Collection + + RSpec.describe RubyNonConcurrentPriorityQueue do + + it_should_behave_like :priority_queue + end + + if Concurrent.on_jruby? + + RSpec.describe JavaNonConcurrentPriorityQueue do + + it_should_behave_like :priority_queue + end + end + + RSpec.describe NonConcurrentPriorityQueue do + if Concurrent.on_jruby? + it 'inherits from JavaNonConcurrentPriorityQueue' do + expect(NonConcurrentPriorityQueue.ancestors).to include(JavaNonConcurrentPriorityQueue) + end + else + it 'inherits from RubyNonConcurrentPriorityQueue' do + expect(NonConcurrentPriorityQueue.ancestors).to include(RubyNonConcurrentPriorityQueue) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/observer_set_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/observer_set_shared.rb new file mode 100644 index 0000000000..c7ef99ea90 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection/observer_set_shared.rb @@ -0,0 +1,240 @@ +RSpec.shared_examples "an observer set" do + + let (:observer_set) { described_class.new } + let (:observer) { double('observer') } + let (:another_observer) { double('another observer') } + + describe '#add_observer' do + + context 'with arguments' do + it 'should return the observer' do + expect(observer_set.add_observer(observer, :a_method)).to eq(observer) + end + end + + context 'with a block' do + it 'should return the observer based on a block' do + observer = observer_set.add_observer { :block } + expect(observer.call).to eq(:block) + end + end + end + + describe '#notify_observers' do + it 'should return the observer set' do + expect(observer_set.notify_observers).to be(observer_set) + end + + context 'with a single observer' do + it 'should update a registered observer without arguments' do + expect(observer).to receive(:update).with(no_args) + + observer_set.add_observer(observer) + + observer_set.notify_observers + end + + it 'should update a registered observer with arguments' do + expect(observer).to receive(:update).with(1, 2, 3) + + observer_set.add_observer(observer) + + observer_set.notify_observers(1, 2, 3) + end + + it 'should notify an observer using the chosen method' do + expect(observer).to receive(:another_method).with('a string arg') + + observer_set.add_observer(observer, :another_method) + + observer_set.notify_observers('a string arg') + end + + it 'should notify an observer once using the last added method' do + expect(observer).to receive(:another_method).with(any_args).never + expect(observer).to receive(:yet_another_method).with('a string arg') + + observer_set.add_observer(observer, :another_method) + observer_set.add_observer(observer, :yet_another_method) + + observer_set.notify_observers('a string arg') + end + + it 'should notify an observer from a block' do + notification = double + expect(notification).to receive(:catch) + + observer_set.add_observer {|arg| arg.catch } + observer_set.notify_observers notification + end + + it 'can be called many times' do + expect(observer).to receive(:update).with(:an_arg).twice + expect(observer).to receive(:update).with(no_args).once + + observer_set.add_observer(observer) + + observer_set.notify_observers(:an_arg) + observer_set.notify_observers + observer_set.notify_observers(:an_arg) + end + end + + context 'with many observers' do + it 'should notify all observer using the chosen method' do + expect(observer).to receive(:a_method).with(4, 'a') + expect(another_observer).to receive(:update).with(4, 'a') + + observer_set.add_observer(observer, :a_method) + observer_set.add_observer(another_observer) + + observer_set.notify_observers(4, 'a') + end + end + + context 'with a block' do + + before(:each) do + allow(observer).to receive(:update).with(any_args) + allow(another_observer).to receive(:update).with(any_args) + end + + it 'calls the block once for every observer' do + + counter = double('block call counter') + expect(counter).to receive(:called).with(no_args).exactly(2).times + + observer_set.add_observer(observer) + observer_set.add_observer(another_observer) + + observer_set.notify_observers{ counter.called } + end + + it 'passes the block return value to the update method' do + + expect(observer).to receive(:update).with(1, 2, 3, 4) + observer_set.add_observer(observer) + observer_set.notify_observers{ [1, 2, 3, 4] } + end + + it 'accepts blocks returning a single value' do + + expect(observer).to receive(:update).with(:foo) + observer_set.add_observer(observer) + observer_set.notify_observers{ :foo } + end + + it 'accepts block return values that include arrays' do + + expect(observer).to receive(:update).with(1, [2, 3], 4) + observer_set.add_observer(observer) + observer_set.notify_observers{ [1, [2, 3], 4] } + end + + it 'raises an exception if given both arguments and a block' do + + observer_set.add_observer(observer) + + expect { + observer_set.notify_observers(1, 2, 3, 4){ nil } + }.to raise_error(ArgumentError) + end + end + end + + context '#count_observers' do + it 'should be zero after initialization' do + expect(observer_set.count_observers).to eq 0 + end + + it 'should be 1 after the first observer is added' do + observer_set.add_observer(observer) + expect(observer_set.count_observers).to eq 1 + end + + it 'should be 1 if the same observer is added many times' do + observer_set.add_observer(observer) + observer_set.add_observer(observer, :another_method) + observer_set.add_observer(observer, :yet_another_method) + + expect(observer_set.count_observers).to eq 1 + end + + it 'should be equal to the number of unique observers' do + observer_set.add_observer(observer) + observer_set.add_observer(another_observer) + observer_set.add_observer(double('observer 3')) + observer_set.add_observer(double('observer 4')) + + expect(observer_set.count_observers).to eq 4 + end + end + + describe '#delete_observer' do + it 'should not notify a deleted observer' do + expect(observer).to receive(:update).never + + observer_set.add_observer(observer) + observer_set.delete_observer(observer) + + observer_set.notify_observers + end + + it 'can delete a non added observer' do + observer_set.delete_observer(observer) + end + + it 'should return the observer' do + expect(observer_set.delete_observer(observer)).to be(observer) + end + end + + describe '#delete_observers' do + it 'should remove all observers' do + expect(observer).to receive(:update).never + expect(another_observer).to receive(:update).never + + observer_set.add_observer(observer) + observer_set.add_observer(another_observer) + + observer_set.delete_observers + + observer_set.notify_observers + end + + it 'should return the observer set' do + expect(observer_set.delete_observers).to be(observer_set) + end + end + + describe '#notify_and_delete_observers' do + before(:each) do + observer_set.add_observer(observer, :a_method) + observer_set.add_observer(another_observer) + + expect(observer).to receive(:a_method).with('args').once + expect(another_observer).to receive(:update).with('args').once + end + + it 'should notify all observers' do + observer_set.notify_and_delete_observers('args') + end + + it 'should clear observers' do + observer_set.notify_and_delete_observers('args') + + expect(observer_set.count_observers).to eq(0) + end + + it 'can be called many times without any other notification' do + observer_set.notify_and_delete_observers('args') + observer_set.notify_and_delete_observers('args') + observer_set.notify_and_delete_observers('args') + end + + it 'should return the observer set' do + expect(observer_set.notify_and_delete_observers('args')).to be(observer_set) + end + end + +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection_each_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection_each_shared.rb new file mode 100644 index 0000000000..6ebf4d8bfe --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/collection_each_shared.rb @@ -0,0 +1,61 @@ +RSpec.shared_examples :collection_each do + + it 'common' do + @cache.send(method) { |k, v| fail } + expect(@cache).to eq @cache.send(method) {} + @cache[:a] = 1 + + h = {} + @cache.send(method) { |k, v| h[k] = v } + expect({:a => 1}).to eq h + + @cache[:b] = 2 + h = {} + @cache.send(method) { |k, v| h[k] = v } + expect({:a => 1, :b => 2}).to eq h + end + + it 'pair iterator' do + @cache[:a] = 1 + @cache[:b] = 2 + i = 0 + r = @cache.send(method) do |k, v| + if i == 0 + i += 1 + next + elsif i == 1 + break :breaked + end + end + + expect(:breaked).to eq r + end + + it 'allows modification' do + @cache[:a] = 1 + @cache[:b] = 1 + @cache[:c] = 1 + + expect_size_change(1) do + @cache.send(method) do |k, v| + @cache[:z] = 1 + end + end + end + + context 'when no block is given' do + it 'returns an enumerator' do + @cache[:a] = 1 + @cache[:b] = 2 + + expect(@cache.send(method)).to be_a Enumerator + end + + it 'returns an object which is enumerable' do + @cache[:a] = 1 + @cache[:b] = 2 + + expect(@cache.send(method).to_a).to contain_exactly([:a, 1], [:b, 2]) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/dereferenceable_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/dereferenceable_shared.rb new file mode 100644 index 0000000000..929031e044 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/dereferenceable_shared.rb @@ -0,0 +1,139 @@ +require 'concurrent/atomic/count_down_latch' + +RSpec.shared_examples :dereferenceable do + + it 'defaults :dup_on_deref to false' do + value = 'value' + expect(value).not_to receive(:dup).with(any_args) + + subject = dereferenceable_subject(value) + subject.value + + subject = dereferenceable_subject(value, dup_on_deref: false) + subject.value + + subject = dereferenceable_subject(value, dup: false) + subject.value + end + + it 'calls #dup when the :dup_on_deref option is true' do + value = 'value' + + subject = dereferenceable_subject(value, dup_on_deref: true) + expect(subject.value.object_id).not_to eq value.object_id + + subject = dereferenceable_subject(value, dup: true) + expect(subject.value.object_id).not_to eq value.object_id + end + + it 'defaults :freeze_on_deref to false' do + value = 'value' + expect(value).not_to receive(:freeze).with(any_args) + + subject = dereferenceable_subject(value) + subject.value + + subject = dereferenceable_subject(value, freeze_on_deref: false) + subject.value + + subject = dereferenceable_subject(value, freeze: false) + subject.value + end + + it 'calls #freeze when the :freeze_on_deref option is true' do + value = 'value' + + subject = dereferenceable_subject(value, freeze_on_deref: true) + expect(subject.value).to be_frozen + + subject = dereferenceable_subject(value, freeze: true) + expect(subject.value).to be_frozen + end + + it 'defaults :copy_on_deref to nil' do + value = 'value' + + subject = dereferenceable_subject(value) + expect(subject.value.object_id).to eq(value.object_id) + + subject = dereferenceable_subject(value, copy_on_deref: nil) + expect(subject.value.object_id).to eq(value.object_id) + + subject = dereferenceable_subject(value, copy: nil) + expect(subject.value.object_id).to eq(value.object_id) + end + + it 'calls the block when the :copy_on_deref option is passed a proc' do + value = 'value' + copy = proc{|val| 'copy' } + + subject = dereferenceable_subject(value, copy_on_deref: copy) + expect(subject.value.object_id).not_to eq(value.object_id) + + subject = dereferenceable_subject(value, copy: copy) + expect(subject.value.object_id).not_to eq(value.object_id) + end + + it 'calls the :copy block first followed by #dup followed by #freeze' do + value = 'value' + copied = 'copied' + dup = 'dup' + frozen = 'frozen' + copy = proc{|val| copied } + + expect(copied).to receive(:dup).at_least(:once).with(no_args).and_return(dup) + expect(dup).to receive(:freeze).at_least(:once).with(no_args).and_return(frozen) + + subject = dereferenceable_subject(value, dup_on_deref: true, freeze_on_deref: true, copy_on_deref: copy) + expect(subject.value).to eq frozen + end + + it 'does not call #dup when #dup_on_deref is set and the value is nil' do + allow_message_expectations_on_nil + result = nil + expect(result).not_to receive(:dup).with(any_args) + subject = dereferenceable_subject(result, dup_on_deref: true) + subject.value + end + + it 'does not call #freeze when #freeze_on_deref is set and the value is nil' do + allow_message_expectations_on_nil + result = nil + expect(result).not_to receive(:freeze).with(any_args) + subject = dereferenceable_subject(result, freeze_on_deref: true) + subject.value + end + + it 'does not call the #copy_on_deref block when the value is nil' do + copier = proc { 42 } + subject = dereferenceable_subject(nil, copy_on_deref: copier) + expect(subject.value).to be_nil + end + + it 'supports dereference flags with observers' do + + if dereferenceable_subject(0).respond_to?(:add_observer) + latch = Concurrent::CountDownLatch.new + observer = Class.new do + def initialize(latch) + @latch = latch + end + def update(*args) + @latch.count_down + end + end.new(latch) + + result = 'result' + copier = proc { result } + expect(result).to receive(:dup).at_least(:once).and_return(result) + expect(result).to receive(:freeze).at_least(:once).and_return(result) + expect(copier).to receive(:call).at_least(:once).and_return(result) + + subject = dereferenceable_observable(dup_on_deref: true, freeze_on_deref: true, copy_on_deref: copier) + + subject.add_observer(observer) + execute_dereferenceable(subject) + latch.wait(1) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/obligation_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/obligation_shared.rb new file mode 100644 index 0000000000..0706922ede --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/obligation_shared.rb @@ -0,0 +1,100 @@ +RSpec.shared_examples :obligation do + + context '#state' do + + it 'is :pending when first created' do + f = pending_subject + expect(f.state).to eq(:pending) + expect(f).to be_pending + end + + it 'is :fulfilled when the handler completes' do + f = fulfilled_subject + expect(f.state).to eq(:fulfilled) + expect(f).to be_fulfilled + end + + it 'is :rejected when the handler raises an exception' do + f = rejected_subject + expect(f.state).to eq(:rejected) + expect(f).to be_rejected + end + end + + context '#value' do + + let!(:supports_timeout) { pending_subject.method(:value).arity != 0 } + + it 'returns nil when reaching the optional timeout value' do + if supports_timeout + f = pending_subject + expect(f.value(0)).to be_nil + expect(f).to be_pending + end + end + + it 'returns immediately when timeout is zero' do + if supports_timeout + expect(Concurrent).not_to receive(:timeout).with(any_args()) + f = pending_subject + expect(f.value(0)).to be_nil + expect(f).to be_pending + end + end + + it 'returns the value when fulfilled before timeout' do + if supports_timeout + f = pending_subject + expect(f.value(10)).to be_truthy + expect(f).to be_fulfilled + end + end + + it 'returns nil when timeout reached' do + if supports_timeout + f = pending_subject + expect(f.value(0.001)).to be_nil + expect(f).to be_pending + end + end + + it 'is nil when :pending' do + if supports_timeout + expected = pending_subject.value(0) + expect(expected).to be_nil + end + end + + it 'blocks the caller when :pending and timeout is nil' do + f = pending_subject + expect(f.value).to be_truthy + expect(f).to be_fulfilled + end + + it 'is nil when :rejected' do + expected = rejected_subject.value + expect(expected).to be_nil + end + + it 'is set to the return value of the block when :fulfilled' do + expected = fulfilled_subject.value + expect(expected).to eq fulfilled_value + end + end + + context '#reason' do + + it 'is nil when :pending' do + expect(pending_subject.reason).to be_nil + end + + it 'is nil when :fulfilled' do + expect(fulfilled_subject.reason).to be_nil + end + + it 'is set to error object of the exception when :rejected' do + expect(rejected_subject.reason).to be_a(Exception) + expect(rejected_subject.reason.to_s).to match(/#{rejected_reason}/) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/obligation_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/obligation_spec.rb new file mode 100644 index 0000000000..a103bf030b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/obligation_spec.rb @@ -0,0 +1,331 @@ +require 'concurrent/concern/obligation' + +module Concurrent + module Concern + + RSpec.describe Obligation do + + let (:obligation_class) do + Class.new(Synchronization::LockableObject) do + include Obligation + public :state=, :compare_and_set_state, :if_state + attr_writer :value, :reason + def initialize + super + set_deref_options + init_obligation + end + end + end + + let (:obligation) { obligation_class.new } + let (:event) { double 'event' } + + RSpec.shared_examples :incomplete do + + it 'should be not completed' do + expect(obligation).not_to be_complete + end + + it 'should be incomplete' do + expect(obligation).to be_incomplete + end + + methods = [:value, :value!, :no_error!] + methods.each do |method| + describe "##{method}" do + + it 'should return immediately if timeout is zero' do + result = obligation.send(method, 0) + if method == :no_error! + expect(result).to eq obligation + else + expect(result).to be_nil + end + end + + it 'should block on the event if timeout is not set' do + allow(obligation).to receive(:event).and_return(event) + expect(event).to receive(:wait).with(nil) + + obligation.send method + end + + it 'should block on the event if timeout is not zero' do + allow(obligation).to receive(:event).and_return(event) + expect(event).to receive(:wait).with(5) + + obligation.send(method, 5) + end + + end + end + end + + context 'unscheduled' do + before(:each) { obligation.state = :unscheduled } + it_should_behave_like :incomplete + end + + context 'pending' do + before(:each) { obligation.state = :pending } + it_should_behave_like :incomplete + end + + context 'fulfilled' do + + before(:each) do + obligation.send :set_state, true, 42, nil + allow(obligation).to receive(:event).and_return(event) + end + + it 'should be completed' do + expect(obligation).to be_complete + end + + it 'should be not incomplete' do + expect(obligation).not_to be_incomplete + end + + describe '#value' do + + it 'should return immediately if timeout is zero' do + expect(obligation.value(0)).to eq 42 + end + + it 'should return immediately if timeout is not set' do + expect(event).not_to receive(:wait) + + expect(obligation.value).to eq 42 + end + + it 'should return immediately if timeout is not zero' do + expect(event).not_to receive(:wait) + + expect(obligation.value(5)).to eq 42 + end + + end + + describe '#value!' do + + it 'should return immediately if timeout is zero' do + expect(obligation.value!(0)).to eq 42 + end + + it 'should return immediately if timeout is not set' do + expect(event).not_to receive(:wait) + + expect(obligation.value!).to eq 42 + end + + it 'should return immediately if timeout is not zero' do + expect(event).not_to receive(:wait) + + expect(obligation.value!(5)).to eq 42 + end + + end + + describe '#no_error!' do + + it 'should return immediately if timeout is zero' do + expect(obligation.no_error!(0)).to eq obligation + end + + it 'should return immediately if timeout is not set' do + expect(event).not_to receive(:wait) + + expect(obligation.no_error!).to eq obligation + end + + it 'should return immediately if timeout is not zero' do + expect(event).not_to receive(:wait) + + expect(obligation.no_error!(5)).to eq obligation + end + + end + + end + + context 'rejected' do + + before(:each) do + obligation.send :set_state, false, nil, (raise rescue $!) + allow(obligation).to receive(:event).and_return(event) + end + + it 'should be completed' do + expect(obligation).to be_complete + end + + it 'should be not incomplete' do + expect(obligation).not_to be_incomplete + end + + + describe '#value' do + + it 'should return immediately if timeout is zero' do + expect(event).not_to receive(:wait) + + expect(obligation.value(0)).to be_nil + end + + it 'should return immediately if timeout is not set' do + expect(event).not_to receive(:wait) + + expect(obligation.value).to be_nil + end + + it 'should return immediately if timeout is not zero' do + expect(event).not_to receive(:wait) + + expect(obligation.value(5)).to be_nil + end + + end + + describe '#value!' do + + it 'should return immediately if timeout is zero' do + expect(event).not_to receive(:wait) + + expect { obligation.value!(0) }.to raise_error StandardError + end + + it 'should return immediately if timeout is not set' do + expect(event).not_to receive(:wait) + + expect { obligation.value! }.to raise_error StandardError + end + + it 'should return immediately if timeout is not zero' do + expect(event).not_to receive(:wait) + + expect { obligation.value!(5) }.to raise_error StandardError + end + + end + + describe '#no_error!' do + + it 'should return immediately if timeout is zero' do + expect(event).not_to receive(:wait) + + expect { obligation.no_error!(0) }.to raise_error StandardError + end + + it 'should return immediately if timeout is not set' do + expect(event).not_to receive(:wait) + + expect { obligation.no_error! }.to raise_error StandardError + end + + it 'should return immediately if timeout is not zero' do + expect(event).not_to receive(:wait) + + expect { obligation.no_error!(5) }.to raise_error StandardError + end + + end + + end + + describe '#compare_and_set_state' do + + before(:each) { obligation.state = :unscheduled } + + context 'unexpected state' do + it 'should return false if state is not the expected one' do + expect(obligation.compare_and_set_state(:pending, :rejected)).to be_falsey + end + + it 'should not change the state if current is not the expected one' do + obligation.compare_and_set_state(:pending, :rejected) + expect(obligation.state).to eq :unscheduled + end + end + + context 'expected state' do + it 'should return true if state is the expected one' do + expect(obligation.compare_and_set_state(:pending, :unscheduled)).to be_truthy + end + + it 'should not change the state if current is not the expected one' do + obligation.compare_and_set_state(:pending, :unscheduled) + expect(obligation.state).to eq :pending + end + end + + end + + describe '#if_state' do + + before(:each) { obligation.state = :unscheduled } + + it 'should raise without block' do + expect { obligation.if_state(:pending) }.to raise_error(ArgumentError) + end + + it 'should return false if state is not expected' do + expect(obligation.if_state(:pending, :rejected) { 42 }).to be_falsey + end + + it 'should the block value if state is expected' do + expect(obligation.if_state(:rejected, :unscheduled) { 42 }).to eq 42 + end + + it 'should execute the block within the mutex' do + expect(obligation).to receive(:synchronize) + obligation.if_state(:unscheduled) { nil } + end + end + + context '#get_arguments_from' do + + it 'returns an empty array when opts is not given' do + args = obligation.send(:get_arguments_from) + expect(args).to be_a ::Array + expect(args).to be_empty + end + + it 'returns an empty array when opts is an empty hash' do + args = obligation.send(:get_arguments_from, {}) + expect(args).to be_a ::Array + expect(args).to be_empty + end + + it 'returns an empty array when there is no :args key' do + args = obligation.send(:get_arguments_from, foo: 'bar') + expect(args).to be_a ::Array + expect(args).to be_empty + end + + it 'returns an empty array when the :args key has a nil value' do + args = obligation.send(:get_arguments_from, args: nil) + expect(args).to be_a ::Array + expect(args).to be_empty + end + + it 'returns a one-element array when the :args key has a non-array value' do + args = obligation.send(:get_arguments_from, args: 'foo') + expect(args).to eq ['foo'] + end + + it 'returns an array when when the :args key has an array value' do + expected = [1, 2, 3, 4] + args = obligation.send(:get_arguments_from, args: expected) + expect(args).to eq expected + end + + it 'returns the given array when the :args key has a complex array value' do + expected = [(1..10).to_a, (20..30).to_a, (100..110).to_a] + args = obligation.send(:get_arguments_from, args: expected) + expect(args).to eq expected + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/observable_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/observable_shared.rb new file mode 100644 index 0000000000..583f5d4c15 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/observable_shared.rb @@ -0,0 +1,177 @@ +require 'concurrent/atomic/count_down_latch' + +RSpec.shared_examples :observable do + + let(:observer_set) do + subject.instance_variable_get(:@observers) + end + + let(:observer_class) do + Class.new do + def initialize(&block) + @block = block + end + def update(*args) + @block.call(*args) if @block + end + end + end + + let(:observer){ observer_class.new } + + let!(:observer_func){ :notify } + + let(:observer_with_func_class) do + Class.new do + def initialize(&block) + @block = block + end + def notify(*args) + @block.call(*args) if @block + end + end + end + + let(:observer_with_func){ observer_with_func_class.new } + + context '#add_observer' do + + it 'adds an observer if called before first notification' do + expect(observer_set).to receive(:add_observer).with(any_args) + subject.add_observer(observer) + end + + it 'adds an observer with :func if called before first notification' do + expect(observer_set).to receive(:add_observer).with(observer_with_func, :notify) + subject.add_observer(observer_with_func, observer_func) + end + + it 'creates an observer from a block if called before first notification' do + block = proc{ nil } + expect(observer_set).to receive(:add_observer).with(any_args) + subject.add_observer(&block) + end + + it 'raises an exception if not given an observer or a block' do + expect { + subject.add_observer + }.to raise_error(ArgumentError) + end + + it 'raises an exception when given both an observer and a block' do + expect { + subject.add_observer(observer){ nil } + }.to raise_error(ArgumentError) + end + end + + context '#delete_observer' do + + it 'deletes the given observer if called before first notification' do + expect(subject.count_observers).to eq 0 + subject.add_observer(observer) + expect(subject.count_observers).to eq 1 + subject.delete_observer(observer) + expect(subject.count_observers).to eq 0 + end + + it 'returns the removed observer if found in the observer set' do + subject.add_observer(observer) + expect(subject.delete_observer(observer)).to eq observer + end + + it 'returns the given observer even when not found in the observer set' do + expect(subject.delete_observer(observer)).to eq observer + end + end + + context '#delete_observers' do + + it 'deletes all observers when called before first notification' do + 5.times{ subject.add_observer(observer_class.new) } + expect(subject.count_observers).to eq 5 + subject.delete_observers + expect(subject.count_observers).to eq 0 + end + + it 'returns self' do + expect(subject.delete_observers).to eq subject + end + end + + context '#count_observers' do + + it 'returns zero for a new observable object' do + expect(subject.count_observers).to eq 0 + end + + it 'returns a count of registered observers if called before first notification' do + 5.times{ subject.add_observer(observer_class.new) } + expect(subject.count_observers).to eq 5 + end + + it 'returns zero after #delete_observers has been called' do + 5.times{ subject.add_observer(observer_class.new) } + subject.delete_observers + expect(subject.count_observers).to eq 0 + end + end + + context 'first notification' do + it 'calls the #update method on all observers without a specified :func' do + latch = Concurrent::CountDownLatch.new(5) + 5.times do + subject.add_observer(observer_class.new{ latch.count_down }) + end + trigger_observable(subject) + latch.wait(1) + expect(latch.count).to eq 0 + end + + it 'calls the appropriate function on all observers which specified a :func' do + latch = Concurrent::CountDownLatch.new(5) + 5.times do + obs = observer_with_func_class.new{ latch.count_down } + subject.add_observer(obs, observer_func) + end + trigger_observable(subject) + latch.wait(1) + expect(latch.count).to eq 0 + end + + it 'calls the proc for all observers added as a block' do + latch = Concurrent::CountDownLatch.new(5) + 5.times do + subject.add_observer{ latch.count_down } + end + trigger_observable(subject) + latch.wait(1) + expect(latch.count).to eq 0 + end + + it 'does not notify any observers removed with #delete_observer' do + latch = Concurrent::CountDownLatch.new(5) + + obs = observer_class.new{ latch.count_down } + subject.add_observer(obs) + subject.delete_observer(obs) + + trigger_observable(subject) + latch.wait(1) + expect(latch.count).to eq 5 + end + + it 'does not notify any observers after #delete_observers called' do + latch = Concurrent::CountDownLatch.new(5) + 5.times do + subject.add_observer(observer_class.new{ latch.count_down }) + end + + subject.delete_observers + + trigger_observable(subject) + latch.wait(1) + expect(latch.count).to eq 5 + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/observable_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/observable_spec.rb new file mode 100644 index 0000000000..7db31dff8d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/concern/observable_spec.rb @@ -0,0 +1,58 @@ +require 'concurrent/concern/observable' + +module Concurrent + module Concern + + RSpec.describe Observable do + + let (:described_class) do + Class.new do + include Observable + public :observers, :observers= + end + end + + let(:observer_set) { double(:observer_set) } + subject { described_class.new } + + before(:each) do + subject.observers = observer_set + end + + it 'does not initialize set by by default' do + expect(described_class.new.observers).to be_nil + end + + it 'uses the given observer set' do + expected = Collection::CopyOnWriteObserverSet.new + subject.observers = expected + expect(subject.observers).to eql expected + end + + it 'delegates #add_observer' do + expect(observer_set).to receive(:add_observer).with(:observer, :update) { |v| v } + expect(subject.add_observer(:observer)).to eq :observer + end + + it 'delegates #with_observer' do + expect(observer_set).to receive(:add_observer).with(:observer, :update) { |v| v } + expect(subject.with_observer(:observer)).to eq subject + end + + it 'delegates #delete_observer' do + expect(observer_set).to receive(:delete_observer).with(:observer) + subject.delete_observer(:observer) + end + + it 'delegates #delete_observers' do + expect(observer_set).to receive(:delete_observers).with(no_args) + subject.delete_observers + end + + it 'delegates #count_observers' do + expect(observer_set).to receive(:count_observers).with(no_args) + subject.count_observers + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/configuration_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/configuration_spec.rb new file mode 100644 index 0000000000..05cdf76588 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/configuration_spec.rb @@ -0,0 +1,26 @@ +require 'concurrent/configuration' + +module Concurrent + + RSpec.describe 'configuration' do + + context 'global executors' do + + it 'creates a global timer set' do + expect(Concurrent.global_timer_set).not_to be_nil + expect(Concurrent.global_timer_set).to respond_to(:post) + end + + it 'creates a global fast executor' do + expect(Concurrent.global_fast_executor).not_to be_nil + expect(Concurrent.global_fast_executor).to respond_to(:post) + end + + it 'creates a global io executor' do + expect(Concurrent.global_io_executor).not_to be_nil + expect(Concurrent.global_io_executor).to respond_to(:post) + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/dataflow_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/dataflow_spec.rb new file mode 100644 index 0000000000..7b857d397c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/dataflow_spec.rb @@ -0,0 +1,240 @@ +require 'concurrent/dataflow' +require 'concurrent/executor/simple_executor_service' + +module Concurrent + + RSpec.describe 'dataflow' do + + let(:executor) { ImmediateExecutor.new } + let(:root_executor) { SimpleExecutorService.new } + + it 'raises an exception when no block given' do + expect { Concurrent::dataflow }.to raise_error(ArgumentError) + expect { Concurrent::dataflow_with(root_executor) }.to raise_error(ArgumentError) + end + + specify '#dataflow uses the global fast executor' do + input = Future.execute{0} + expect(Concurrent).to receive(:dataflow_with).once. + with(Concurrent.global_io_executor, input) + Concurrent::dataflow(input){0} + end + + specify '#dataflow_with uses the given executor' do + skip('flaky on truffleruby') if Concurrent.on_truffleruby? + + input = Future.execute{0} + result = Future.new{0} + + expect(Future).to receive(:new).with(executor: root_executor).and_return(result) + Concurrent::dataflow_with(root_executor, input){0} + end + + specify '#dataflow_with raises an exception when no executor given' do + expect { + Concurrent::dataflow_with(nil){ nil } + }.to raise_error(ArgumentError) + end + + it 'accepts zero or more dependencies' do + Concurrent::dataflow(){0} + Concurrent::dataflow(Future.execute{0}){0} + Concurrent::dataflow(Future.execute{0}, Future.execute{0}){0} + + Concurrent::dataflow_with(root_executor, ){0} + Concurrent::dataflow_with(root_executor, Future.execute{0}){0} + Concurrent::dataflow_with(root_executor, Future.execute{0}, Future.execute{0}){0} + end + + it 'accepts uncompleted dependencies' do + d = Future.new(executor: executor){0} + Concurrent::dataflow(d){0} + d.execute + + d = Future.new(executor: executor){0} + Concurrent::dataflow_with(root_executor, d){0} + d.execute + end + + it 'accepts completed dependencies' do + d = Future.new(executor: executor){0} + d.execute + Concurrent::dataflow(d){0} + + d = Future.new(executor: executor){0} + d.execute + Concurrent::dataflow_with(root_executor, d){0} + end + + it 'raises an exception if any dependencies are not IVars' do + expect { Concurrent::dataflow(nil) }.to raise_error(ArgumentError) + expect { Concurrent::dataflow(Future.execute{0}, nil) }.to raise_error(ArgumentError) + expect { Concurrent::dataflow(nil, Future.execute{0}) }.to raise_error(ArgumentError) + + expect { Concurrent::dataflow_with(root_executor, nil) }.to raise_error(ArgumentError) + expect { Concurrent::dataflow_with(root_executor, Future.execute{0}, nil) }.to raise_error(ArgumentError) + expect { Concurrent::dataflow_with(root_executor, nil, Future.execute{0}) }.to raise_error(ArgumentError) + end + + it 'doesn\'t raise exceptions from dependencies, unless called with !' do + d1 = Concurrent::dataflow { raise 'd1 error' } + d2 = Concurrent::dataflow { raise 'd2 error' } + f = Concurrent::dataflow!(d1, d2) { |d1v, d2v| [d1v, d2v] } + expect { f.value! }.to raise_error(RuntimeError).with_message('d1 error') + + d1 = Concurrent::dataflow { raise 'd1 error' } + d2 = Concurrent::dataflow { raise 'd2 error' } + f = Concurrent::dataflow(d1, d2) { |d1v, d2v| [d1v, d2v] } + expect { f.value! }.to_not raise_error + end + + it 'returns a Future' do + expect(Concurrent::dataflow{0}).to be_a(Future) + expect(Concurrent::dataflow{0}).to be_a(Future) + end + + context 'does not schedule the Future' do + + specify 'if no dependencies are completed' do + d = Future.new(executor: executor){0} + f = Concurrent::dataflow(d){0} + expect(f).to be_unscheduled + d.execute + + d = Future.new(executor: executor){0} + f = Concurrent::dataflow_with(root_executor, d){0} + expect(f).to be_unscheduled + d.execute + end + + specify 'if one dependency of two is completed' do + d1 = Future.new(executor: executor){0} + d2 = Future.new(executor: executor){0} + f = Concurrent::dataflow(d1, d2){0} + d1.execute + expect(f).to be_unscheduled + d2.execute + + d1 = Future.new(executor: executor){0} + d2 = Future.new(executor: executor){0} + f = Concurrent::dataflow_with(root_executor, d1, d2){0} + d1.execute + expect(f).to be_unscheduled + d2.execute + end + end + + context 'schedules the Future when all dependencies are available' do + + specify 'if there is just one' do + d = Future.new(executor: executor){0} + f = Concurrent::dataflow(d){0} + d.execute + expect(f.value).to eq 0 + + d = Future.new(executor: executor){0} + f = Concurrent::dataflow_with(root_executor, d){0} + d.execute + expect(f.value).to eq 0 + end + + specify 'if there is more than one' do + d1 = Future.new(executor: executor){0} + d2 = Future.new(executor: executor){0} + f = Concurrent::dataflow(d1, d2){0} + d1.execute + d2.execute + expect(f.value).to eq 0 + + d1 = Future.new(executor: executor){0} + d2 = Future.new(executor: executor){0} + f = Concurrent::dataflow_with(root_executor, d1, d2){0} + d1.execute + d2.execute + expect(f.value).to eq 0 + end + end + + context 'counts already executed dependencies' do + + specify 'if there is just one' do + d = Future.new(executor: executor){0} + d.execute + f = Concurrent::dataflow(d){0} + expect(f.value).to eq 0 + + d = Future.new(executor: executor){0} + d.execute + f = Concurrent::dataflow_with(root_executor, d){0} + expect(f.value).to eq 0 + end + + specify 'if there is more than one' do + d1 = Future.new(executor: executor){0} + d2 = Future.new(executor: executor){0} + d1.execute + d2.execute + f = Concurrent::dataflow(d1, d2){0} + expect(f.value).to eq 0 + + d1 = Future.new(executor: executor){0} + d2 = Future.new(executor: executor){0} + d1.execute + d2.execute + f = Concurrent::dataflow_with(root_executor, d1, d2){0} + expect(f.value).to eq 0 + end + end + + context 'passes the values of dependencies into the block' do + + specify 'if there is just one' do + d = Future.new(executor: executor){14} + f = Concurrent::dataflow(d){|v| v } + d.execute + expect(f.value).to eq 14 + + d = Future.new(executor: executor){14} + f = Concurrent::dataflow_with(root_executor, d){|v| v } + d.execute + expect(f.value).to eq 14 + end + + specify 'if there is more than one' do + d1 = Future.new(executor: executor){14} + d2 = Future.new(executor: executor){2} + f = Concurrent::dataflow(d1, d2) {|v1, v2| v1 + v2} + d1.execute + d2.execute + expect(f.value).to eq 16 + + d1 = Future.new(executor: executor){14} + d2 = Future.new(executor: executor){2} + f = Concurrent::dataflow_with(root_executor, d1, d2) {|v1, v2| v1 + v2} + d1.execute + d2.execute + expect(f.value).to eq 16 + end + end + + context 'module function' do + + it 'can be called as Concurrent.dataflow and Concurrent.dataflow_with' do + + def fib_with_dot(n) + if n < 2 + Concurrent.dataflow { n } + else + n1 = fib_with_dot(n - 1) + n2 = fib_with_dot(n - 2) + Concurrent.dataflow_with(ImmediateExecutor.new, n1, n2) { n1.value + n2.value } + end + end + + expected = fib_with_dot(7) + expect(expected.value).to eq 13 + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/delay_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/delay_spec.rb new file mode 100644 index 0000000000..18000a985b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/delay_spec.rb @@ -0,0 +1,103 @@ +require 'concurrent/delay' +require_relative 'concern/dereferenceable_shared' +require_relative 'concern/obligation_shared' + +module Concurrent + + RSpec.describe Delay do + + context 'behavior' do + + # dereferenceable + + def dereferenceable_subject(value, opts = {}) + delay = Delay.new(opts){ value } + delay.tap{ delay.value } + end + + it_should_behave_like :dereferenceable + + # obligation + + let!(:fulfilled_value) { 10 } + let!(:rejected_reason) { StandardError.new('mojo jojo') } + + let(:pending_subject) do + Delay.new(executor: :fast){ sleep 0.1; fulfilled_value } + end + + let(:fulfilled_subject) do + delay = Delay.new{ fulfilled_value } + delay.tap{ delay.value } + end + + let(:rejected_subject) do + delay = Delay.new{ raise rejected_reason } + delay.tap{ delay.value } + end + + it_should_behave_like :obligation + end + + context '#initialize' do + + it 'sets the state to :pending' do + expect(Delay.new{ nil }.state).to eq :pending + expect(Delay.new{ nil }).to be_pending + end + + it 'raises an exception when no block given' do + expect { + Delay.new + }.to raise_error(ArgumentError) + end + end + + + context '#reconfigure' do + it 'returns value of block used in reconfiguration' do + expect(Delay.new { nil }.tap { |d| d.reconfigure { true } }.value).to be_truthy + end + + it 'returns false when process completed?' do + d = Delay.new { 1 } + expect(d.reconfigure { 2 }).to be_truthy + expect(d.value).to be 2 + expect(d.reconfigure { 3 }).to be_falsey + end + end + + context '#value' do + + let(:task){ proc{ nil } } + + it 'does not call the block before #value is called' do + expect(task).not_to receive(:call).with(any_args) + Delay.new(&task) + end + + it 'calls the block when #value is called' do + expect(task).to receive(:call).once.with(any_args).and_return(nil) + Delay.new(&task).value + end + + it 'only calls the block once no matter how often #value is called' do + expect(task).to receive(:call).once.with(any_args).and_return(nil) + delay = Delay.new(&task) + 5.times{ delay.value } + end + + it 'raises when called recursively' do + delay = Delay.new { delay.value } + expect { delay.value! }.to raise_error(IllegalOperationError) + expect(delay.reason).to be_a_kind_of(IllegalOperationError) + end + + it 'can be called twice' do + delay = Delay.new { 10 } + expect(delay.value).to eq 10 + expect(delay.value).to eq 10 + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/channel_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/channel_spec.rb new file mode 100644 index 0000000000..54df1179d2 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/channel_spec.rb @@ -0,0 +1,389 @@ +require 'concurrent/edge/channel' + +RSpec.describe 'Concurrent' do + describe 'Promises::Channel', edge: true do + specify "#capacity" do + channel = Concurrent::Promises::Channel.new 2 + expect(channel.capacity).to be 2 + end + + specify "#to_s" do + channel = Concurrent::Promises::Channel.new + expect(channel.to_s).to match(/Channel.*unlimited/) + channel = Concurrent::Promises::Channel.new 2 + expect(channel.to_s).to match(/Channel.*0.*2/) + channel.push :value + expect(channel.to_s).to match(/Channel.*1.*2/) + end + + specify "#(try_)push(_op)" do + channel = Concurrent::Promises::Channel.new 1 + + expect(channel.size).to eq 0 + expect(channel.try_push(:v1)).to be_truthy + expect(channel.size).to eq 1 + expect(channel.try_push(:v2)).to be_falsey + expect(channel.size).to eq 1 + + channel = Concurrent::Promises::Channel.new 1 + expect(channel.push(:v1)).to eq channel + expect(channel.size).to eq 1 + thread = in_thread { channel.push :v2 } + is_sleeping thread + expect(channel.size).to eq 1 + channel.pop + expect(channel.size).to eq 1 + expect(thread.value).to eq channel + channel.pop + expect(channel.size).to eq 0 + + channel = Concurrent::Promises::Channel.new 1 + expect(channel.push(:v1)).to eq channel + expect(channel.size).to eq 1 + thread = in_thread { channel.push :v2, 0.01 } + is_sleeping thread + expect(channel.size).to eq 1 + expect(thread.value).to eq false + channel.pop + expect(channel.size).to eq 0 + expect(channel.push(:v3, 0)).to eq true + expect(channel.size).to eq 1 + thread = in_thread { channel.push :v2, 1 } + is_sleeping thread + channel.pop + expect(channel.size).to eq 1 + expect(thread.value).to eq true + + channel = Concurrent::Promises::Channel.new 1 + expect(channel.push_op(:v1).value!).to eq channel + expect(channel.size).to eq 1 + push_op = channel.push_op :v2 + expect(channel.size).to eq 1 + expect(push_op.pending?).to be_truthy + channel.pop + expect(channel.size).to eq 1 + expect(push_op.value!).to eq channel + channel.pop + expect(channel.size).to eq 0 + end + + specify "#(try_)pop(_op)" do + channel = Concurrent::Promises::Channel.new 1 + channel.push :v1 + + expect(channel.size).to eq 1 + expect(channel.try_pop).to eq :v1 + expect(channel.size).to eq 0 + expect(channel.try_pop).to eq nil + expect(channel.size).to eq 0 + + channel = Concurrent::Promises::Channel.new 1 + channel.push :v1 + expect(channel.pop).to eq :v1 + expect(channel.size).to eq 0 + thread = in_thread { channel.pop } + is_sleeping thread + expect(channel.size).to eq 0 + channel.push :v2 + expect(thread.value).to eq :v2 + expect(channel.size).to eq 0 + + channel = Concurrent::Promises::Channel.new 1 + channel.push :v1 + expect(channel.pop).to eq :v1 + expect(channel.size).to eq 0 + thread = in_thread { channel.pop 0.01 } + is_sleeping thread + expect(channel.size).to eq 0 + expect(thread.value).to eq nil + channel.push :v2 + expect(channel.size).to eq 1 + expect(channel.pop).to eq :v2 + expect(channel.size).to eq 0 + thread = in_thread { channel.pop 1 } + is_sleeping thread + channel.push :v3 + expect(channel.size).to eq 0 + expect(thread.value).to eq :v3 + channel.push :v4 + expect(channel.pop(0)).to eq :v4 + + channel = Concurrent::Promises::Channel.new 1 + channel.push :v1 + expect(channel.pop_op.value!).to eq :v1 + expect(channel.size).to eq 0 + pop_op = channel.pop_op + expect(channel.size).to eq 0 + expect(pop_op.pending?).to be_truthy + channel.push :v2 + expect(channel.size).to eq 0 + expect(pop_op.value!).to eq :v2 + end + + specify "#(try_)pop(_op)_matching" do + channel = Concurrent::Promises::Channel.new 2 + channel.push 'junk' + channel.push :v1 + + expect(channel.size).to eq 2 + expect(channel.try_pop_matching(Symbol)).to eq :v1 + expect(channel.size).to eq 1 + expect(channel.try_pop_matching(Symbol)).to eq nil + expect(channel.size).to eq 1 + + channel = Concurrent::Promises::Channel.new 2 + channel.push 'junk' + channel.push :v1 + expect(channel.pop_matching(Symbol)).to eq :v1 + expect(channel.size).to eq 1 + thread = in_thread { channel.pop_matching(Symbol) } + is_sleeping thread + expect(channel.size).to eq 1 + channel.push 'junk' + channel.pop + channel.push :v2 + expect(thread.value).to eq :v2 + expect(channel.size).to eq 1 + + channel = Concurrent::Promises::Channel.new 2 + channel.push 'junk' + channel.push :v1 + expect(channel.pop_matching(Symbol)).to eq :v1 + expect(channel.size).to eq 1 + thread = in_thread { channel.pop_matching(Symbol, 0.01) } + is_sleeping thread + expect(channel.size).to eq 1 + expect(thread.value).to eq nil + channel.push :v2 + expect(channel.size).to eq 2 + expect(channel.pop_matching(Symbol)).to eq :v2 + expect(channel.size).to eq 1 + thread = in_thread { channel.pop_matching(Symbol,1) } + is_sleeping thread + channel.push :v3 + expect(channel.size).to eq 1 + expect(thread.value).to eq :v3 + channel.push :v4 + expect(channel.pop_matching(Symbol,0)).to eq :v4 + + channel = Concurrent::Promises::Channel.new 2 + channel.push 'junk' + channel.push :v1 + expect(channel.pop_op_matching(Symbol).value!).to eq :v1 + expect(channel.size).to eq 1 + pop_op = channel.pop_op_matching(Symbol) + expect(channel.size).to eq 1 + expect(pop_op.pending?).to be_truthy + channel.push :v2 + expect(channel.size).to eq 1 + expect(pop_op.value!).to eq :v2 + end + + specify "#(try_)select(_op)" do + channel1 = Concurrent::Promises::Channel.new 1 + channel2 = Concurrent::Promises::Channel.new 1 + + expect(channel1.try_select(channel2)).to eq nil + expect(Concurrent::Promises::Channel.try_select([channel1, channel2])).to eq nil + channel1.push :v1 + expect(channel1.try_select(channel2)).to eq [channel1, :v1] + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + + channel1 = Concurrent::Promises::Channel.new 1 + channel2 = Concurrent::Promises::Channel.new 1 + channel1.push :v1 + expect(Concurrent::Promises::Channel.select([channel1, channel2])).to eq [channel1, :v1] + channel1.push :v1 + expect(channel1.select(channel2)).to eq [channel1, :v1] + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + thread = in_thread { channel1.select(channel2) } + is_sleeping thread + expect(channel1.size).to eq 0 + channel2.push :v2 + expect(thread.value).to eq [channel2, :v2] + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + + channel1 = Concurrent::Promises::Channel.new 1 + channel2 = Concurrent::Promises::Channel.new 1 + channel1.push :v1 + expect(channel1.select(channel2)).to eq [channel1, :v1] + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + thread = in_thread { channel1.select(channel2, 0.01) } + is_sleeping thread + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + expect(thread.value).to eq nil + channel2.push :v2 + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 1 + expect(channel2.select(channel1)).to eq [channel2, :v2] + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + + channel1 = Concurrent::Promises::Channel.new 1 + channel2 = Concurrent::Promises::Channel.new 1 + channel1.push :v1 + expect(channel1.select_op(channel2).value!).to eq [channel1, :v1] + channel1.push :v1 + expect(Concurrent::Promises::Channel.select_op([channel1, channel2]).value!).to eq [channel1, :v1] + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + select_op = channel2.select_op(channel1) + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + expect(select_op.pending?).to be_truthy + channel2.push :v2 + expect(channel1.size).to eq 0 + expect(channel2.size).to eq 0 + expect(select_op.value!).to eq [channel2, :v2] + end + + def push_first(push_type, pop_type) + channel = Concurrent::Promises::Channel.new 0 + message = Object.new + + case push_type + when :push + thread = in_thread { channel.push message } + is_sleeping thread + when :push_op + push = channel.push_op message + expect(push.pending?).to eq true + else + raise + end + + expect(channel.size).to eq 0 + + case pop_type + when :try_pop + expect(channel.try_pop).to eq message + when :pop + expect(channel.pop).to eq message + when :pop_op + expect(channel.pop_op.value!).to eq message + else + raise + end + + expect(channel.size).to eq 0 + + case push_type + when :push + expect(thread.value).to eq channel + when :push_op + expect(push.value!).to eq channel + else + raise + end + end + + def pop_first(pop_type, push_type) + channel = Concurrent::Promises::Channel.new 0 + message = Object.new + + case pop_type + when :pop + thread = in_thread { channel.pop } + is_sleeping thread + when :pop_op + pop = channel.pop_op + expect(pop.pending?).to eq true + else + raise + end + + expect(channel.size).to eq 0 + + case push_type + when :try_push + expect(channel.try_push message).to eq true + when :push + expect(channel.push(message)).to eq channel + when :push_op + expect(channel.push_op(message).value!).to eq channel + else + raise + end + + expect(channel.size).to eq 0 + + case pop_type + when :pop + expect(thread.value).to eq message + when :pop_op + expect(pop.value!).to eq message + else + raise + end + end + + + specify 'exchanging' do + push_first :push, :try_pop + push_first :push, :pop + push_first :push, :pop_op + push_first :push_op, :try_pop + push_first :push_op, :pop + push_first :push_op, :pop_op + + pop_first :pop, :try_push + pop_first :pop, :push + pop_first :pop, :push_op + pop_first :pop_op, :try_push + pop_first :pop_op, :push + pop_first :pop_op, :push_op + + ch1 = Concurrent::Promises::Channel.new 0 + ch2 = Concurrent::Promises::Channel.new 0 + selection = ch1.select_op(ch2) + expect(ch2.try_push(:v3)).to be_truthy + expect(selection.value!).to eq [ch2, :v3] + end + + specify 'integration' do + ch1 = Concurrent::Promises::Channel.new + ch2 = Concurrent::Promises::Channel.new + ch3 = Concurrent::Promises::Channel.new + + add = -> *_ do + (ch1.pop_op & ch2.pop_op).then do |a, b| + if a == :done && b == :done + :done + else + # do not add again until push is done + ch3.push_op(a + b).then(&add) + end + end + end + + ch1.push_op 1 + ch2.push_op 2 + ch1.push_op 'a' + ch2.push_op 'b' + ch1.push_op nil + ch2.push_op true + + result = Concurrent::Promises.future(&add).run.result + expect(result[0..1]).to eq [false, nil] + expect(result[2]).to be_a_kind_of(NoMethodError) + expect(ch3.pop_op.value!).to eq 3 + expect(ch3.pop_op.value!).to eq 'ab' + + ch1.push_op 1 + ch2.push_op 2 + ch1.push_op 'a' + ch2.push_op 'b' + ch1.push_op :done + ch2.push_op :done + + expect(Concurrent::Promises.future(&add).run.result).to eq [true, :done, nil] + expect(ch3.pop_op.value!).to eq 3 + expect(ch3.pop_op.value!).to eq 'ab' + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/erlang_actor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/erlang_actor_spec.rb new file mode 100644 index 0000000000..ed94607dcd --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/erlang_actor_spec.rb @@ -0,0 +1,1031 @@ +require 'concurrent/edge/erlang_actor' + +RSpec.describe 'Concurrent' do + describe 'ErlangActor', edge: true do + # TODO (pitr-ch 06-Feb-2019): include constants instead + ANY ||= Concurrent::ErlangActor::ANY + TIMEOUT ||= Concurrent::ErlangActor::TIMEOUT + And ||= Concurrent::ErlangActor::And + identity = -> v { v } + + shared_examples 'erlang actor' do + + specify "run to termination" do + expect(Concurrent::ErlangActor.spawn(type: type) do + :v + end.terminated.value!).to eq :v + end + + specify "run to termination with arguments" do + expect(Concurrent::ErlangActor. + spawn(1, 2, type: type) { |a, b| a + b }.terminated.value!). + to eq 3 + end + + specify '#receive' do + succ = -> v { v.succ } + + [[[:v], -> { receive }, :v], + [[:v], -> { receive on(ANY, &identity) }, :v], + [[:v, 1], -> { receive Numeric }, 1], + [[:v, 1], -> { receive(Numeric, &succ) }, 2], + + [[:v], -> { receive Numeric, timeout: 0 }, nil], + [[:v], -> { receive(Numeric, timeout: 0, &succ) }, nil], + [[:v], -> { receive Numeric, timeout: 0, timeout_value: :timeout }, :timeout], + [[:v], -> { receive(Numeric, timeout: 0, timeout_value: :timeout, &succ) }, :timeout], + + [[:v, 1], -> { receive Numeric, timeout: 1 }, 1], + [[:v, 1], -> { receive(Numeric, timeout: 1, &succ) }, 2], + [[:v, 1], -> { receive Numeric, timeout: 1, timeout_value: :timeout }, 1], + [[:v, 1], -> { receive(Numeric, timeout: 1, timeout_value: :timeout, &succ) }, 2], + + [[:v], -> { receive on(Numeric, &identity), on(TIMEOUT, nil), timeout: 0 }, nil], + [[:v], -> { receive on(Numeric, &succ), on(TIMEOUT, nil), timeout: 0 }, nil], + [[:v], -> { receive on(Numeric, &identity), on(TIMEOUT, :timeout), timeout: 0 }, :timeout], + [[:v], -> { receive on(Numeric, &succ), on(TIMEOUT, :timeout), timeout: 0 }, :timeout], + + [[:v, 1], -> { receive on(Numeric, &identity), on(TIMEOUT, nil), timeout: 1 }, 1], + [[:v, 1], -> { receive on(Numeric, &succ), on(TIMEOUT, nil), timeout: 1 }, 2], + [[:v, 1], -> { receive on(Numeric, &identity), on(TIMEOUT, :timeout), timeout: 1 }, 1], + [[:v, 1], -> { receive on(Numeric, &succ), on(TIMEOUT, :timeout), timeout: 1 }, 2], + ].each_with_index do |(messages, body, result), i| + a = Concurrent::ErlangActor.spawn(type: type, &body) + messages.each { |m| a.tell m } + expect(a.terminated.value!).to eq(result), "body: #{body}" + end + end + + specify 'pid has name' do + actor = Concurrent::ErlangActor.spawn(type: type, name: 'test') {} + expect(actor.to_s).to match(/test/) + expect(actor.inspect).to match(/test/) + end + + specify "receives message" do + actor = Concurrent::ErlangActor.spawn(type: type, + &{ on_thread: -> { receive }, + on_pool: -> { receive on(ANY, &identity) } }.fetch(type)) + actor.tell :v + expect(actor.terminated.value!).to eq :v + end + + specify "receives message with matchers" do + body = { on_thread: + -> do + [receive(on(Symbol, &identity)), + receive(on(Numeric, &:succ)), + receive(on(Numeric, :got_it), timeout: 0, timeout_value: :nothing)] + end, + on_pool: + -> do + @arr = [] + receive(on(Symbol) do |v1| + @arr.push v1 + receive(on(Numeric) do |v2| + @arr << v2.succ + receive(on(Numeric, :got_it), on(TIMEOUT) { @arr << :nothing; @arr }, timeout: 0) + end) + end) + end } + actor = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + actor.tell 'junk' + actor.tell 1 + actor.tell :v + expect(actor.terminated.value!).to eq [:v, 2, :nothing] + end + + describe "monitoring" do + specify "(de)monitor" do + body_receive = { on_thread: + -> { receive }, + on_pool: + -> { receive { |v| v } } } + + body = { on_thread: + -> do + actor = receive + reference = monitor actor + monitored = monitoring? reference + demonitor reference + result = [monitored, monitoring?(reference)] + actor.tell :finish + result + end, + on_pool: + -> do + receive do |actor| + reference = monitor actor + monitored = monitoring? reference + demonitor reference + result = [monitored, monitoring?(reference)] + actor.tell :finish + result + end + end } + a1 = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + a2 = Concurrent::ErlangActor.spawn(type: type, &body_receive.fetch(type)) + a1.tell a2 + expect(a1.terminated.value!).to eq [true, false] + expect(a2.terminated.value!).to eq :finish + end + + specify "demonitor" do + body = { on_thread: + -> do + actor = receive + reference = monitor actor + monitored = monitoring? reference + actor.tell :done + actor.terminated.wait + demonitor = demonitor reference, :flush, :info + [monitored, monitoring?(reference), demonitor, receive(timeout: 0)] + end, + on_pool: + -> do + receive do |actor| + reference = monitor actor + monitored = monitoring? reference + actor.tell :done + actor.terminated.wait + demonitor = demonitor reference, :flush, :info + results = [monitored, monitoring?(reference), demonitor] + receive(on(ANY) { |v| [*results, v] }, + on(TIMEOUT) { [*results, nil] }, + timeout: 0) + end + end } + + a1 = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + body = { on_thread: -> { receive }, + on_pool: -> { receive(&identity) } } + a2 = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + a1.tell a2 + + a1.terminated.wait + expect(a1.terminated.value!).to eq [true, false, false, nil] + expect(a2.terminated.value!).to eq :done + end + + specify "demonitor should leave the down message in the inbox if it's already there" do + body = { on_thread: + -> do + actor = receive + reference = monitor actor + monitored = monitoring? reference + actor.tell :done + actor.terminated.wait + demonitor = demonitor reference, :info + [reference, monitored, monitoring?(reference), demonitor, receive(timeout: 0)] + end, + on_pool: + -> do + receive do |actor| + reference = monitor actor + monitored = monitoring? reference + actor.tell :done + actor.terminated.wait + demonitor = demonitor reference, :info + results = [reference, monitored, monitoring?(reference), demonitor] + receive(on(ANY) { |v| [*results, v] }, + on(TIMEOUT) { [*results, nil] }, + timeout: 0) + end + end } + + a1 = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + body = { on_thread: -> { receive }, + on_pool: -> { receive(&identity) } } + a2 = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + a1.tell a2 + + reference, monitored, monitoring, demonitor, message = a1.terminated.value! + expect(monitored).to eq true + expect(monitoring).to eq false + expect(demonitor).to eq false + expect(message).to eq Concurrent::ErlangActor::Down.new(a2, reference, :normal) + expect(a2.terminated.value!).to eq :done + end + + specify "notifications 1" do + body = { on_thread: + -> do + b = spawn { [:done, receive] } + ref = monitor b + b.tell 42 + [b, ref, receive] + end, + on_pool: + -> do + b = spawn { receive on(ANY) { |v| [:done, v] } } + ref = monitor b + b.tell 42 + receive on(ANY) { |v| [b, ref, v] } + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + b, ref, down = a.terminated.value! + expect(down).to eq Concurrent::ErlangActor::Down.new(b, ref, :normal) + expect(b.terminated.value!).to eq [:done, 42] + end + + specify "notifications 2" do + body = { on_thread: + -> do + b = spawn { :done } + b.terminated.wait + ref = monitor b + [b, ref, receive(timeout: 1, timeout_value: :timeout)] + end, + on_pool: + -> do + b = spawn { :done } + b.terminated.wait + ref = monitor b + receive(timeout: 1) { |v| [b, ref, v] } + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + b, ref, down = a.terminated.value! + expect(down).to eq Concurrent::ErlangActor::Down.new(b, ref, Concurrent::ErlangActor::NoActor.new(b)) + expect(b.terminated.value!).to eq :done + end + + # FIXME (pitr-ch 20-Jan-2019): test concurrent exit and monitor(), same for link + end + + describe 'linking' do + body_receive_test_linked = { on_thread: + -> { linked?(receive) }, + on_pool: + -> { receive { |a| linked? a } } } + + specify 'links' do + body1 = { on_thread: + -> do + actor = receive + link actor + linked = linked? actor + actor.tell pid + linked + end, + on_pool: + -> do + receive do |actor| + link actor + linked = linked? actor + actor.tell pid + linked + end + end } + + a1 = Concurrent::ErlangActor.spawn(type: type, &body1.fetch(type)) + a2 = Concurrent::ErlangActor.spawn(type: type, &body_receive_test_linked.fetch(type)) + + a1.tell a2 + expect(a1.terminated.value!).to be_truthy + expect(a2.terminated.value!).to be_truthy + end + + specify 'unlinks' do + body1 = { on_thread: + -> do + actor = receive + link actor + unlink actor + linked = linked? actor + actor.tell pid + linked + end, + on_pool: + -> do + receive do |actor| + link actor + unlink actor + linked = linked? actor + actor.tell pid + linked + end + end } + + a1 = Concurrent::ErlangActor.spawn(type: type, &body1.fetch(type)) + a2 = Concurrent::ErlangActor.spawn(type: type, &body_receive_test_linked.fetch(type)) + a1.tell a2 + expect(a1.terminated.value!).to be_falsey + expect(a2.terminated.value!).to be_falsey + end + + specify 'link dead' do + a = Concurrent::ErlangActor.spawn(type: type) do + b = spawn { :done } + b.terminated.wait + link b + end + expect { a.terminated.value! }.to raise_error Concurrent::ErlangActor::NoActor + end + + specify 'link dead when trapping' do + body1 = { on_thread: + -> do + b = spawn { :done } + b.terminated.wait + sleep 0.1 + trap + link b + [b, receive] + end, + on_pool: + -> do + b = spawn { :done } + b.terminated.wait + sleep 0.1 + trap + link b + receive { |v| [b, v] } + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body1.fetch(type)) + + b, captured = a.terminated.value! + expect(captured).to eq Concurrent::ErlangActor::Terminated.new(b, Concurrent::ErlangActor::NoActor.new(b)) + end + + + describe 'exit/1 when linked' do + # https://learnyousomeerlang.com/errors-and-processes#links + specify 1 do + body = { on_thread: + -> do + b = spawn(link: true) { :ok } + [receive(timeout: 0.01), b] + end, + on_pool: + -> do + b = spawn(link: true) { :ok } + receive(on(ANY) { |v| [v, b] }, + on(TIMEOUT) { |v| [nil, b] }, + timeout: 0.01) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + message, b = a.terminated.value! + expect(message).to eq nil + expect(b.terminated.value!).to eq :ok + end + + specify 2 do + body = { on_thread: + -> do + b = spawn(link: true) { :ok } + trap + [receive(timeout: 1), b] + end, + on_pool: + -> do + b = spawn(link: true) { :ok } + trap + receive(on(ANY) { |v| [v, b] }, + on(TIMEOUT) { |v| [nil, b] }, + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + message, b = a.terminated.value! + expect(message).to eq Concurrent::ErlangActor::Terminated.new(b, :normal) + expect(b.terminated.value!).to eq :ok + end + + specify 3 do + body = { on_thread: + -> do + spawn(link: true) { terminate :boom } + receive(timeout: 1) + end, + on_pool: + -> do + spawn(link: true) { terminate :boom } + receive(on(ANY) { |v| [v, b] }, + on(TIMEOUT) { |v| [nil, b] }, + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + expect(a.terminated.reason).to eq :boom + end + + specify 4 do + body = { on_thread: + -> do + b = spawn(link: true) { terminate :boom } + trap + [receive(timeout: 1), b] + end, + on_pool: + -> do + b = spawn(link: true) { terminate :boom } + trap + receive(on(ANY) { |v| [v, b] }, + on(TIMEOUT) { |v| [nil, b] }, + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + trapped_exit, b = a.terminated.value! + expect(trapped_exit).to eq Concurrent::ErlangActor::Terminated.new(b, :boom) + expect(b.terminated.reason).to eq :boom + end + + specify 5 do + body = { on_thread: + -> do + b = spawn(link: true) { terminate :normal, value: :ok } + [receive(timeout: 0.01), b] + end, + on_pool: + -> do + b = spawn(link: true) { terminate :normal, value: :ok } + receive(on(ANY) { |v| [v, b] }, + on(TIMEOUT) { |v| [nil, b] }, + timeout: 0.01) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + message, b = a.terminated.value! + expect(message).to eq nil + expect(b.terminated.value!).to eq :ok + end + + specify 6 do + body = { on_thread: + -> do + b = spawn(link: true) { terminate :normal, value: :ok } + trap + [receive(timeout: 1), b] + end, + on_pool: + -> do + b = spawn(link: true) { terminate :normal, value: :ok } + trap + receive(on(ANY) { |v| [v, b] }, + on(TIMEOUT) { |v| [nil, b] }, + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + message, b = a.terminated.value! + expect(message).to eq Concurrent::ErlangActor::Terminated.new(b, :normal) + expect(b.terminated.value!).to eq :ok + end + + specify 7 do + body = { on_thread: + -> do + spawn(link: true) { raise 'err' } + receive(timeout: 1) + end, + on_pool: + -> do + spawn(link: true) { raise 'err' } + receive(timeout: 1) { |v| v } + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + expect { a.terminated.value! }.to raise_error(RuntimeError, 'err') + end + + specify 8 do + body = { on_thread: + -> do + b = spawn(link: true) { raise 'err' } + trap + [receive(timeout: 1), b] + end, + on_pool: + -> do + b = spawn(link: true) { raise 'err' } + trap + receive(on(ANY) { |v| [v, b] }, + on(TIMEOUT) { |v| [nil, b] }, + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + trapped_exit, b = a.terminated.value! + expect(trapped_exit).to be_a Concurrent::ErlangActor::Terminated + expect(trapped_exit.from).to eq b + expect(trapped_exit.reason).to eq b.terminated.reason + expect(trapped_exit.reason).to be_a RuntimeError + expect(trapped_exit.reason.message).to eq 'err' + end + + specify 9 do + body = { on_thread: + -> do + b = spawn(link: true) { throw :uncaught } + trap + [receive(timeout: 1), b] + end, + on_pool: + -> do + b = spawn(link: true) { throw :uncaught } + trap + receive(on(ANY) { |v| [v, b] }, + on(TIMEOUT) { |v| [nil, b] }, + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + trapped_exit, b = a.terminated.value! + expect(trapped_exit).to be_a Concurrent::ErlangActor::Terminated + expect(trapped_exit.from).to eq b + expect(trapped_exit.reason).to eq b.terminated.reason + expect(trapped_exit.reason).to be_a ArgumentError + expect(trapped_exit.reason.message).to match(/uncaught throw :uncaught/) + end + end + + describe 'exit/2 when linked' do + # https://learnyousomeerlang.com/errors-and-processes#links + specify 1 do + body = { on_thread: + -> do + terminate pid, :normal # sends the signal to mailbox + # TODO (pitr-ch 17-Jan-2019): does erlang require receive to process signals? + receive(timeout: 0.01) + :continued + end, + on_pool: + -> do + terminate pid, :normal # sends the signal to mailbox + receive(on(ANY, :continued), + on(TIMEOUT, :timeout), + timeout: 0.01) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + expect(a.terminated.value!).to eq nil + end + + specify 2 do + body = { on_thread: + -> do + terminate pid, :normal + trap + receive(timeout: 0) + end, + on_pool: + -> do + terminate pid, :normal + trap + receive(on(ANY, &identity), on(TIMEOUT, nil), timeout: 0) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + captured_exit = a.terminated.value! + expect(captured_exit).to eq Concurrent::ErlangActor::Terminated.new(a, :normal) + end + + specify 3 do + body = { on_thread: + -> do + b = spawn(link: true) { receive timeout: 0.01, timeout_value: :timeout } + terminate b, :normal + b + end, + on_pool: + -> do + b = spawn(link: true) do + receive(on(ANY, :not_happening), + on(TIMEOUT, :timeout), + timeout: 0.01) + end + + terminate b, :normal + b + end } + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + b = a.terminated.value! + expect(b.terminated.value!).to eq :timeout + end + + specify 4 do + body = { on_thread: + -> do + b = spawn(link: true) { trap; receive timeout: 1, timeout_value: :timeout } + terminate b, :normal + b + end, + on_pool: + -> do + b = spawn(link: true) do + trap + receive(on(ANY, &identity), + on(TIMEOUT, :timeout), + timeout: 1) + end + + terminate b, :normal + b + end } + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + b = a.terminated.value! + expect(b.terminated.value!).to eq Concurrent::ErlangActor::Terminated.new(a, :normal) + end + + specify 5 do + body = { on_thread: + -> do + b = spawn(link: true) { receive timeout: 0.01; terminate :continued } + terminate b, :normal + trap + [b, receive(timeout: 1)] + end, + on_pool: + -> do + b = spawn(link: true) do + receive(on(ANY, :not_happening), + on(TIMEOUT) { terminate :continued }, + timeout: 0.01) + end + + terminate b, :normal + trap + receive(on(ANY) { |v| [b, v] }, on(TIMEOUT, :timeout), timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + b, captured = a.terminated.value! + expect(b.terminated.reason).to eq :continued + # normal is never send from b to a back + expect(captured).to eq Concurrent::ErlangActor::Terminated.new(b, :continued) + end + + specify 6 do + body = { on_thread: + -> do + b = spawn(link: true) { receive timeout: 1; :done } + terminate b, :remote_err + receive timeout: 1 + end, + on_pool: + -> do + b = spawn(link: true) { receive(on(ANY, :done), on(TIMEOUT, :timeout), timeout: 1) } + terminate b, :remote_err + receive(on(ANY) { |v| [b, v] }, + on(TIMEOUT, :timeout), + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + a.terminated.wait + expect(a.terminated.reason).to eq :remote_err + end + + specify 7 do + body = { on_thread: + -> do + b = spawn(link: true) { receive timeout: 1; :done } + terminate b, :remote_err + trap + [b, receive(timeout: 1)] + end, + on_pool: + -> do + b = spawn(link: true) { receive(on(ANY, :done), on(TIMEOUT, :timeout), timeout: 1) } + terminate b, :remote_err + trap + receive(on(ANY) { |v| [b, v] }, + on(TIMEOUT, :timeout), + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + b, captured = a.terminated.value! + expect(b.terminated.reason).to eq :remote_err + expect(captured.reason).to eq :remote_err + end + + specify 8 do + body = { on_thread: + -> do + b = spawn(link: true) { receive timeout: 1; :done } + terminate b, :kill + receive timeout: 1 + end, + on_pool: + -> do + b = spawn(link: true) { receive(on(ANY, :done), on(TIMEOUT, :done), timeout: 1) } + terminate b, :kill + receive(on(ANY, &identity), on(TIMEOUT, :timeout), timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + expect(a.terminated.reason).to eq :killed + end + + specify 9 do + body = { on_thread: + -> do + b = spawn(link: true) { receive timeout: 1; :done } + terminate b, :kill + trap + [b, receive(timeout: 1)] + end, + on_pool: + -> do + b = spawn(link: true) { receive(on(ANY, :done), on(TIMEOUT, :done), timeout: 1) } + terminate b, :kill + trap + receive(on(ANY) { |v| [b, v] }, on(TIMEOUT, :timeout), timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + b, captured = a.terminated.value! + expect(b.terminated.reason).to eq :killed + expect(captured.reason).to eq :killed + end + + specify 10 do + body = { on_thread: + -> do + terminate pid, :kill + receive timeout: 0 + end, + on_pool: + -> do + terminate pid, :kill + receive(on(ANY, :continued), on(TIMEOUT, :timeout), timeout: 0) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + expect(a.terminated.reason).to eq :killed + end + + specify 11 do + body = { on_thread: + -> do + terminate pid, :kill + trap + receive timeout: 0 + end, + on_pool: + -> do + terminate pid, :kill + trap + receive(on(ANY, &identity), on(TIMEOUT, :timeout), timeout: 0) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + expect(a.terminated.reason).to eq :killed + end + + # explained in + # http://erlang.org/pipermail/erlang-questions/2009-October/047241.html + + specify 12 do + body = { on_thread: + -> do + spawn(link: true) { terminate :kill } + receive timeout: 1 + end, + on_pool: + -> do + spawn(link: true) { terminate :kill } + receive(on(ANY, :continued), + on(TIMEOUT, :timeout), + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + expect(a.terminated.reason).to eq :kill + end + + specify 13 do + body = { on_thread: + -> do + b = spawn(link: true) { terminate :kill } + trap + [b, receive(timeout: 1)] + end, + on_pool: + -> do + b = spawn(link: true) { terminate :kill } + trap + receive(on(ANY) { |v| [b, v] }, + on(TIMEOUT, :timeout), + timeout: 1) + end } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + + b, captured = a.terminated.value! + + expect(b.terminated.reason).to eq :kill + expect(captured).to eq Concurrent::ErlangActor::Terminated.new(b, :kill) + end + + end + end + + specify 'spawn(link: true)' do + a = Concurrent::ErlangActor.spawn(type: type) do + b = spawn(link: true) { :v } + linked? b + end + expect(a.terminated.value!).to be_truthy + + a = Concurrent::ErlangActor.spawn(type: type) do + b = spawn { :v } + linked? b + end + expect(a.terminated.value!).to be_falsey + end + + specify 'termination' do + a = Concurrent::ErlangActor.spawn(type: type) { :v } + expect(a.terminated.value!).to eq :v + + a = Concurrent::ErlangActor.spawn(type: type) { raise 'err' } + expect { a.terminated.value! }.to raise_error(RuntimeError, 'err') + + a = Concurrent::ErlangActor.spawn(type: type) { terminate :normal, value: :val } + expect(a.terminated.value!).to eq :val + + a = Concurrent::ErlangActor.spawn(type: type) { terminate :er } + expect(a.terminated.reason).to eq :er + end + + describe 'asking' do + specify "replies" do + body = { on_thread: -> { reply receive }, + on_pool: -> { receive { |v| reply v } } } + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + expect(a.ask(:v)).to eq :v + + body = { on_thread: -> { v = receive; reply v; reply v; }, + on_pool: -> { receive { |v| reply v; reply v } } } + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + expect(a.ask(:v)).to eq :v + expect(a.terminated.value!).to be_falsey + + body = { on_thread: + -> do + v = receive + reply v + reply_resolution true, v.to_s, nil + end, + on_pool: + -> do + receive do |v| + reply v + reply_resolution true, v.to_s, nil + end + end } + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + expect(a.ask(:v)).to eq :v + expect(a.terminated.value!).to be_falsey + + body = { on_thread: -> { reply_resolution false, nil, receive }, + on_pool: -> { receive { |v| reply_resolution false, nil, v } } } + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + expect { a.ask(:err) }.to raise_error StandardError, 'err' + + body = { on_thread: -> { reply_resolution false, nil, receive }, + on_pool: -> { receive { |v| reply_resolution false, nil, v } } } + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + expect(a.ask_op(:err).reason).to eq :err + end + + specify "timing out" do + skip('flaky on truffleruby') if Concurrent.on_truffleruby? + + count_down = Concurrent::CountDownLatch.new + body = { on_thread: -> { m = receive; count_down.wait; reply m }, + on_pool: -> { receive { |m| count_down.wait; reply m } } } + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + expect(a.ask(:err, 0, 42)).to eq 42 + count_down.count_down + expect(a.terminated.value!).to eq false + + body = { on_thread: -> { reply receive }, + on_pool: -> { receive { |m| reply m } } } + b = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + expect(b.ask(:v, 1, 42)).to eq :v + expect(b.terminated.value!).to eq true + end + + specify "rejects on no reply" do + body = { on_thread: -> { receive; receive }, + on_pool: -> { receive { receive {} } } } + + a = Concurrent::ErlangActor.spawn(type: type, &body.fetch(type)) + expect(a.ask_op(:v).reason).to eq Concurrent::ErlangActor::NoReply + expect { raise a.ask_op(:v).wait }.to raise_error Concurrent::ErlangActor::NoActor + expect { raise a.ask(:v) }.to raise_error Concurrent::ErlangActor::NoActor + end + + end + end + + describe 'on thread' do + let(:type) { :on_thread } + it_behaves_like 'erlang actor' + + specify do + actor = Concurrent::ErlangActor.spawn(type: :on_thread) do + Thread.abort_on_exception = true + while true + receive on(Symbol) { |s| reply s.to_s }, + on(And[Numeric, -> v { v >= 0 }]) { |v| reply v.succ }, + # put last works as else + on(ANY) { |v| reply :bad_message; terminate [:bad_message, v] } + end + end + expect(actor.ask(1)).to eq 2 + expect(actor.ask(:value)).to eq 'value' + expect(actor.ask(-1)).to eq :bad_message + expect { actor.ask 'junk' }.to raise_error Concurrent::ErlangActor::NoActor + expect(actor.terminated.reason).to eq [:bad_message, -1] + end + end + + describe 'on pool' do + let(:type) { :on_pool } + it_behaves_like 'erlang actor' + + include Concurrent::ErlangActor::EnvironmentConstants + + specify "receives message repeatedly with keep" do + actor = Concurrent::ErlangActor.spawn(type: :on_pool) do + receive on(ANY) { |v| v == :done ? terminate(:normal, value: 42) : reply(v) }, + keep: true + end + expect(actor.ask(1)).to eq 1 + expect(actor.ask(2)).to eq 2 + actor.tell :done + expect(actor.terminated.value!).to eq 42 + end + + specify "class defined" do + definition_module = Module.new do + def start + @sum = 0 + receive on(Numeric, &method(:count)), + on(:done, &method(:stop)), + on(TIMEOUT, &method(:fail)), + keep: true, + timeout: 0.1 + end + + def count(message) + reply @sum += message + end + + def stop(_message) + terminate :normal, value: @sum + end + + def fail(_message) + terminate :timeout + end + end + definition_class = Class.new Concurrent::ErlangActor::Environment do + include definition_module + end + + actor = Concurrent::ErlangActor.spawn(type: :on_pool, environment: definition_class) { start } + actor.tell 1 + expect(actor.ask(2)).to eq 3 + actor.tell :done + expect(actor.terminated.value!).to eq 3 + + actor = Concurrent::ErlangActor.spawn(type: :on_pool, environment: definition_module) + actor.tell 1 + expect(actor.ask(2)).to eq 3 + expect(actor.terminated.reason).to eq :timeout + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/lock_free_linked_set_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/lock_free_linked_set_spec.rb new file mode 100644 index 0000000000..0019a31eb2 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/edge/lock_free_linked_set_spec.rb @@ -0,0 +1,201 @@ +require 'concurrent/edge/lock_free_linked_set' +require 'securerandom' + +RSpec.describe Concurrent::Edge::LockFreeLinkedSet, edge: true do + subject { described_class.new } + + describe '.new' do + context 'when passed default val' do + it 'uses the val arg as data for each node' do + set = described_class.new 3, true + expect(set.all? { |val| val == true }).to be_truthy + end + end + end + + describe '#add' do + it 'appends to the linked set' do + expect(subject.add 'test string1').to be true + end + + context 'in a multi-threaded environment' do + it 'adds the items to the set' do + to_insert = %w(one two three four five six) + + threads = ::Array.new(16) do + in_thread do + to_insert.each do |item| + subject.add item + end + end + end + + threads.each(&:join) + + to_insert.each do |item| + expect(subject.contains? item).to be true + end + end + end + end + + describe '#<<' do + it 'appends to the linked set and returns self' do + expect(subject << 'test string1').to be_a described_class + end + + it 'returns self regardless of whether it was logically added' do + subject << 'test string' + expect(subject << 'test string').to be_a described_class + end + end + + describe '#contains?' do + context 'when checking if set includes a value' do + it 'returns true if a value exists' do + subject << 'Concurrency... ooh! ahh!' + expect(subject.contains? 'Concurrency... ooh! ahh!').to eq true + end + + it 'compares object using Object#hash' do + val = 'Hash me.' + subject << val + expect(subject.contains? 'Hash me.').to eq true + end + + it 'returns false for values not in the set' do + subject << 'Concurrency... ooh! ahh!' + expect(subject.contains? 'Sequential... booh! nah!').to eq false + end + + context 'when set is empty' do + it 'does not break' do + expect(subject.contains? 'Nothing to see here.').to eq false + end + end + + context 'when set is long' do + it 'does not break' do + arr = ::Array.new(1000) { SecureRandom.hex } + arr.each { |n| subject << n } + ret = arr.all? { |n| subject.contains? n } + + expect(ret).to be true + end + end + + context 'in a multi-threaded environment' do + it 'correctly check that the set contains the item' do + to_insert = %w(one two three four five six) + to_insert.each { |item| subject << item } + + threads = ::Array.new(16) do + in_thread do + 100.times { subject << SecureRandom.hex } + + to_insert.each do |item| + expect(subject.contains? item).to be true + end + end + end + + threads.each(&:join) + + to_insert.each do |item| + expect(subject.contains? item).to be true + end + end + end + end + end + + describe '#remove' do + context 'when item is inside of set' do + before { subject << 'one' << 'two' << 'three' } + + it 'the item is no longer visible to the user' do + subject.remove 'three' + + expect(subject.contains? 'three').to be false + end + + it 'allows for the item to be added despite being physically present' do + subject.remove 'three' + + expect(subject.add 'three').to be true + end + end + + context 'in a multi-threaded environment' do + + it 'adds the items to the set' do + to_insert = %w(one two three four five six) + to_insert.each { |item| subject << item } + + threads = ::Array.new(8) do + [in_thread { subject.remove 'one' }, + in_thread { subject.remove 'two' }, + in_thread { subject.remove 'three' }] + end + + threads.flatten.each(&:join) + + expect(subject.contains? 'one').to be false + expect(subject.contains? 'two').to be false + expect(subject.contains? 'three').to be false + expect(subject.contains? 'four').to be true + expect(subject.contains? 'five').to be true + expect(subject.contains? 'six').to be true + end + + it 'does not recognize the existence of the item when removed' do + to_insert = %w(one two three four five six) + to_insert.each { |item| subject << item } + + ::Array.new(16) do + in_thread do + 100.times { subject << SecureRandom.hex } + + to_insert.each do |item| + subject.remove item + expect(subject.contains? item).to be false + end + end + end + end + end + + context 'when item is not inside of set' do + before { subject << 'one' << 'two' << 'three' } + + it 'does not remove to value' do + expect(subject.remove 'four').to be false + end + + it 'the set remains intact' do + expect(subject).to receive :remove + subject.remove 'four' + + present = %w(one two three).map { |n| subject.contains? n }.all? + expect(present).to be true + end + + context 'when the set is empty' do + subject { described_class.new } + + it 'remove does not break' do + expect(subject.remove 'test').to be false + end + end + + context 'when the set is large' do + subject { described_class.new(1000) { SecureRandom.hex } } + + it 'remove successfully removes the node' do + subject << 'Testing' + expect(subject.remove 'Testing').to be true + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/exchanger_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/exchanger_spec.rb new file mode 100644 index 0000000000..8cf39177ba --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/exchanger_spec.rb @@ -0,0 +1,263 @@ +require 'concurrent/exchanger' +require 'concurrent/atomic/atomic_fixnum' + +RSpec.shared_examples 'exchanger method with indefinite timeout' do + + before(:each) do + subject # ensure proper initialization + end + + it 'blocks indefinitely' do + latch_1 = Concurrent::CountDownLatch.new + latch_2 = Concurrent::CountDownLatch.new + + t = in_thread do + latch_1.count_down + subject.send(method_name, 0.1) + latch_2.count_down + end + + is_sleeping t + expect(latch_1.count).to eq 0 + expect(latch_2.count).to eq 1 + end + + it 'receives the other value' do + first_value = nil + second_value = nil + latch = Concurrent::CountDownLatch.new(2) + + in_thread { first_value = subject.send(method_name, 2); latch.count_down } + in_thread { second_value = subject.send(method_name, 4); latch.count_down } + + latch.wait(1) + + expect(get_value(first_value)).to eq 4 + expect(get_value(second_value)).to eq 2 + end + + it 'can be reused' do + first_value = nil + second_value = nil + latch_1 = Concurrent::CountDownLatch.new(2) + latch_2 = Concurrent::CountDownLatch.new(2) + + in_thread { first_value = subject.send(method_name, 1); latch_1.count_down } + in_thread { second_value = subject.send(method_name, 0); latch_1.count_down } + + latch_1.wait(1) + + in_thread { first_value = subject.send(method_name, 10); latch_2.count_down } + in_thread { second_value = subject.send(method_name, 12); latch_2.count_down } + + latch_2.wait(1) + expect(get_value(first_value)).to eq 12 + expect(get_value(second_value)).to eq 10 + end +end + +RSpec.shared_examples 'exchanger method with finite timeout' do + + it 'blocks until timeout' do + duration = Concurrent::TestHelpers.monotonic_interval do + begin + subject.send(method_name, 2, 0.1) + rescue Concurrent::TimeoutError + # do nothing + end + end + expect(duration).to be_within(0.05).of(0.1) + end + + it 'receives the other value' do + first_value = nil + second_value = nil + latch = Concurrent::CountDownLatch.new(2) + + in_thread { first_value = subject.send(method_name, 2, 1); latch.count_down } + in_thread { second_value = subject.send(method_name, 4, 1); latch.count_down } + + latch.wait(1) + + expect(get_value(first_value)).to eq 4 + expect(get_value(second_value)).to eq 2 + end + + it 'can be reused' do + first_value = nil + second_value = nil + latch_1 = Concurrent::CountDownLatch.new(2) + latch_2 = Concurrent::CountDownLatch.new(2) + + in_thread { first_value = subject.send(method_name, 1, 1); latch_1.count_down } + in_thread { second_value = subject.send(method_name, 0, 1); latch_1.count_down } + + latch_1.wait(1) + + in_thread { first_value = subject.send(method_name, 10, 1); latch_2.count_down } + in_thread { second_value = subject.send(method_name, 12, 1); latch_2.count_down } + + + latch_2.wait(1) + expect(get_value(first_value)).to eq 12 + expect(get_value(second_value)).to eq 10 + end +end + +RSpec.shared_examples 'exchanger method cross-thread interactions' do + + it 'when first, waits for a second' do + first_value = nil + second_value = nil + latch = Concurrent::CountDownLatch.new(1) + + t1 = in_thread do + first_value = subject.send(method_name, :foo, 1) + latch.count_down + end + t1.join(0.1) + + second_value = subject.send(method_name, :bar, 0) + latch.wait(1) + + expect(get_value(first_value)).to eq :bar + expect(get_value(second_value)).to eq :foo + end + + it 'allows multiple firsts to cancel if necessary' do + first_value = nil + second_value = nil + cancels = 3 + cancel_latch = Concurrent::CountDownLatch.new(cancels) + success_latch = Concurrent::CountDownLatch.new(1) + + threads = cancels.times.collect do + in_thread do + begin + first_value = subject.send(method_name, :foo, 0.1) + rescue Concurrent::TimeoutError + # suppress + ensure + cancel_latch.count_down + end + end + end + + threads.each { |t| t.join(1) } + cancel_latch.wait(1) + + t1 = in_thread do + first_value = subject.send(method_name, :bar, 1) + success_latch.count_down + end + t1.join(0.1) + + second_value = subject.send(method_name, :baz, 0) + success_latch.wait(1) + + expect(get_value(first_value)).to eq :baz + expect(get_value(second_value)).to eq :bar + end +end + +RSpec.shared_examples :exchanger do + + context '#exchange' do + let!(:method_name) { :exchange } + + def get_value(result) + result + end + + it_behaves_like 'exchanger method with indefinite timeout' + it_behaves_like 'exchanger method with finite timeout' + it_behaves_like 'exchanger method cross-thread interactions' + end + + context '#exchange!' do + let!(:method_name) { :exchange! } + + def get_value(result) + result + end + + it_behaves_like 'exchanger method with indefinite timeout' + it_behaves_like 'exchanger method with finite timeout' + it_behaves_like 'exchanger method cross-thread interactions' + end + + context '#try_exchange' do + let!(:method_name) { :try_exchange } + + def get_value(result) + result.value + end + + it_behaves_like 'exchanger method with indefinite timeout' + it_behaves_like 'exchanger method with finite timeout' + it_behaves_like 'exchanger method cross-thread interactions' + end +end + +module Concurrent + + RSpec.describe RubyExchanger do + + it_behaves_like :exchanger + + if Concurrent.on_cruby? + + specify 'stress test' do + thread_count = 100 + exchange_count = 100 + latch = Concurrent::CountDownLatch.new(thread_count) + + good = Concurrent::AtomicFixnum.new(0) + bad = Concurrent::AtomicFixnum.new(0) + ugly = Concurrent::AtomicFixnum.new(0) + + thread_count.times.collect do |i| + in_thread do + exchange_count.times do |j| + begin + result = subject.exchange!(i, 1) + result == i ? ugly.up : good.up + rescue Concurrent::TimeoutError + bad.up + end + end + latch.count_down + end + end + + latch.wait + + puts "Good: #{good.value}, Bad (timeout): #{bad.value}, Ugly: #{ugly.value}" + expect(good.value + bad.value + ugly.value).to eq thread_count * exchange_count + expect(ugly.value).to eq 0 + end + end + end + + if Concurrent.on_jruby? + RSpec.describe JavaExchanger do + it_behaves_like :exchanger + end + end + + RSpec.describe Exchanger do + + context 'class hierarchy' do + + if Concurrent.on_jruby? + it 'inherits from JavaExchanger' do + expect(Exchanger.ancestors).to include(JavaExchanger) + end + else + it 'inherits from RubyExchanger' do + expect(Exchanger.ancestors).to include(RubyExchanger) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/cached_thread_pool_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/cached_thread_pool_spec.rb new file mode 100644 index 0000000000..513873b924 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/cached_thread_pool_spec.rb @@ -0,0 +1,244 @@ +require 'concurrent/executor/cached_thread_pool' +require_relative 'thread_pool_shared' + +module Concurrent + + RSpec.describe CachedThreadPool do + + subject do + described_class.new(fallback_policy: :discard) + end + + after(:each) do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it_should_behave_like :thread_pool + + let(:latch) { Concurrent::CountDownLatch.new } + + context '#initialize' do + + subject { described_class.new } + + it 'sets :max_length to DEFAULT_MAX_POOL_SIZE' do + expect(subject.max_length).to eq described_class::DEFAULT_MAX_POOL_SIZE + end + + it 'sets :min_length to DEFAULT_MIN_POOL_SIZE' do + expect(subject.min_length).to eq described_class::DEFAULT_MIN_POOL_SIZE + end + + it 'sets :idletime to DEFAULT_THREAD_IDLETIMEOUT' do + expect(subject.idletime).to eq described_class::DEFAULT_THREAD_IDLETIMEOUT + end + + it 'sets :max_queue to DEFAULT_MAX_QUEUE_SIZE' do + expect(subject.max_queue).to eq described_class::DEFAULT_MAX_QUEUE_SIZE + end + end + + context '#min_length' do + + it 'returns zero on creation' do + expect(subject.min_length).to eq 0 + end + + it 'returns zero while running' do + 10.times { subject.post { nil } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.min_length).to eq 0 + end + + it 'returns zero once shutdown' do + 10.times { subject.post { nil } } + subject.post { latch.count_down } + latch.wait(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.min_length).to eq 0 + end + end + + context '#max_length' do + + it 'returns :max_length on creation' do + expect(subject.max_length).to eq described_class::DEFAULT_MAX_POOL_SIZE + end + + it 'returns :max_length while running' do + 10.times { subject.post { nil } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.max_length).to eq described_class::DEFAULT_MAX_POOL_SIZE + end + + it 'returns :max_length once shutdown' do + 10.times { subject.post { nil } } + subject.post { latch.count_down } + latch.wait(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.max_length).to eq described_class::DEFAULT_MAX_POOL_SIZE + end + end + + context '#largest_length' do + + it 'returns zero on creation' do + expect(subject.largest_length).to eq 0 + end + + it 'returns a non-zero number once tasks have been received' do + 10.times { subject.post { sleep(0.1) } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.largest_length).to be > 0 + end + + it 'returns a non-zero number after shutdown if tasks have been received' do + 10.times { subject.post { sleep(0.1) } } + subject.post { latch.count_down } + latch.wait(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.largest_length).to be > 0 + end + end + + context '#idletime' do + + subject { described_class.new(idletime: 42) } + + it 'returns the thread idletime' do + expect(subject.idletime).to eq 42 + end + end + + context 'runtime-specific implementation' do + + if Concurrent.on_jruby? + + context '#initialize' do + + it 'sets :fallback_policy correctly' do + clazz = java.util.concurrent.ThreadPoolExecutor::DiscardPolicy + policy = clazz.new + expect(clazz).to receive(:new).at_least(:once).with(any_args).and_return(policy) + + subject = CachedThreadPool.new(fallback_policy: :discard) + expect(subject.fallback_policy).to eq :discard + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it 'defaults :fallback_policy to :abort' do + subject = CachedThreadPool.new + expect(subject.fallback_policy).to eq :abort + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it 'raises an exception if given an invalid :fallback_policy' do + expect { + CachedThreadPool.new(fallback_policy: :bogus) + }.to raise_error(ArgumentError) + end + end + + else + + context 'garbage collection' do + + subject { described_class.new(idletime: 0.1, max_threads: 2, gc_interval: 0) } + + it 'removes from pool any thread that has been idle too long' do + latch = Concurrent::CountDownLatch.new(4) + 4.times { subject.post { sleep 0.1; latch.count_down } } + expect(latch.wait(1)).to be true + sleep 0.2 + subject.post {} + sleep 0.2 + expect(subject.length).to be < 4 + end + + it 'deals with dead threads' do + expect(subject).to receive(:ns_worker_died).exactly(5).times.and_call_original + + dead_threads_queue = Queue.new + 5.times { subject.post { sleep 0.1; dead_threads_queue.push Thread.current; raise Exception } } + sleep(0.2) + latch = Concurrent::CountDownLatch.new(5) + 5.times { subject.post { sleep 0.1; latch.count_down } } + expect(latch.wait(1)).to be true + + dead_threads = [] + dead_threads << dead_threads_queue.pop until dead_threads_queue.empty? + expect(dead_threads.all? { |t| !t.alive? }).to be true + end + end + + context 'worker creation and caching' do + + subject { described_class.new(idletime: 1, max_threads: 5) } + + it 'creates new workers when there are none available' do + expect(subject.length).to eq 0 + 5.times { sleep(0.1); subject << proc { sleep(1) } } + sleep(1) + expect(subject.length).to eq 5 + end + + it 'uses existing idle threads' do + 5.times { subject << proc { sleep(0.1) } } + sleep(1) + expect(subject.length).to be >= 5 + 3.times { subject << proc { sleep(1) } } + sleep(0.1) + expect(subject.length).to be >= 5 + end + end + end + + context 'stress' do + configurations = [ + { min_threads: 2, + max_threads: ThreadPoolExecutor::DEFAULT_MAX_POOL_SIZE, + idletime: 0.1, # 1 minute + max_queue: 0, # unlimited + fallback_policy: :caller_runs, # shouldn't matter -- 0 max queue + gc_interval: 0.1 }, + { min_threads: 2, + max_threads: 4, + idletime: 0.1, # 1 minute + max_queue: 0, # unlimited + fallback_policy: :caller_runs, # shouldn't matter -- 0 max queue + gc_interval: 0.1 } + ] + + configurations.each do |config| + specify do + pool = RubyThreadPoolExecutor.new(config) + + 10.times do + count = Concurrent::CountDownLatch.new(100) + 100.times do + pool.post { count.count_down } + end + count.wait + sleep 0.01 # let the tasks end after count_down + expect(pool.length).to be <= [200, config[:max_threads]].min + if pool.length > [110, config[:max_threads]].min + puts "ERRORSIZE #{pool.length} max #{config[:max_threads]}" + end + end + + pool.shutdown + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/executor_quits.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/executor_quits.rb new file mode 100644 index 0000000000..ada42c5d15 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/executor_quits.rb @@ -0,0 +1,14 @@ + +lib = File.expand_path '../../../lib/concurrent-ruby/' +$LOAD_PATH.push lib unless $LOAD_PATH.include? lib + +require 'concurrent' + +executors = [Concurrent::CachedThreadPool.new, Concurrent::SingleThreadExecutor.new, Concurrent::FixedThreadPool.new(1)] +executors.each do |executor| + executor.post do + sleep # sleep indefinitely + end +end + +# the process main thread should quit out which should kill the daemon CachedThreadPool diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/executor_service_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/executor_service_shared.rb new file mode 100644 index 0000000000..e5ffa367c3 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/executor_service_shared.rb @@ -0,0 +1,220 @@ +require_relative 'global_thread_pool_shared' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/atomic/atomic_fixnum' +require 'timeout' + +RSpec.shared_examples :executor_service do + + after(:each) do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it_should_behave_like :global_thread_pool + + context '#post' do + + it 'rejects the block while shutting down' do + latch = Concurrent::CountDownLatch.new(1) + subject.post{ sleep(1) } + subject.shutdown + begin + subject.post{ latch.count_down } + rescue Concurrent::RejectedExecutionError + end + expect(latch.wait(0.1)).to be_falsey + end + + it 'rejects the block once shutdown' do + subject.shutdown + latch = Concurrent::CountDownLatch.new(1) + begin + subject.post{ latch.count_down } + rescue Concurrent::RejectedExecutionError + end + expect(latch.wait(0.1)).to be_falsey + end + end + + context 'auto terminate' do + + # https://github.com/ruby-concurrency/concurrent-ruby/issues/817 + # https://github.com/ruby-concurrency/concurrent-ruby/issues/839 + it 'does not stop shutdown ' do + Timeout.timeout(10) do + begin + test_file = File.join File.dirname(__FILE__), 'executor_quits.rb' + pid = spawn RbConfig.ruby, test_file + Process.waitpid pid + expect($?.success?).to eq true + rescue Errno::ECHILD + # child already gone + rescue Timeout::Error => e + Process.kill :KILL, pid + raise e + end + end + end + + end + + context '#running?' do + + it 'returns true when the thread pool is running' do + expect(subject).to be_running + end + + it 'returns false when the thread pool is shutting down' do + subject.post{ sleep(0.5) } + subject.shutdown + expect(subject).not_to be_running + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it 'returns false when the thread pool is shutdown' do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject).not_to be_running + end + + it 'returns false when the thread pool is killed' do + subject.kill + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject).not_to be_running + end + end + + context '#shutdown' do + + it 'stops accepting new tasks' do + latch1 = Concurrent::CountDownLatch.new(1) + latch2 = Concurrent::CountDownLatch.new(1) + subject.post{ sleep(0.1); latch1.count_down } + latch1.wait(1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + begin + subject.post{ latch2.count_down } + rescue Concurrent::RejectedExecutionError + end + expect(latch2.wait(0.2)).to be_falsey + end + + it 'allows in-progress tasks to complete' do + latch = Concurrent::CountDownLatch.new(1) + subject.post{ sleep(0.1); latch.count_down } + subject.shutdown + expect(latch.wait(1)).to be_truthy + end + + it 'allows pending tasks to complete' do + latch = Concurrent::CountDownLatch.new(2) + subject.post{ sleep(0.2); latch.count_down } + subject.post{ sleep(0.2); latch.count_down } + subject.shutdown + expect(latch.wait(1)).to be_truthy + end + end + + context '#shutdown followed by #wait_for_termination' do + + it 'allows in-progress tasks to complete' do + latch = Concurrent::CountDownLatch.new(1) + subject.post{ sleep(0.1); latch.count_down } + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(latch.wait(1)).to be_truthy + end + + it 'allows pending tasks to complete' do + q = Queue.new + 5.times do |i| + subject.post { sleep 0.1; q << i } + end + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(q.length).to eq 5 + end + + it 'stops accepting/running new tasks' do + expected = Concurrent::AtomicFixnum.new(0) + subject.post{ sleep(0.1); expected.increment } + subject.post{ sleep(0.1); expected.increment } + subject.shutdown + begin + subject.post{ expected.increment } + rescue Concurrent::RejectedExecutionError + end + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(expected.value).to eq(2) + end + end + + context '#kill' do + + it 'stops accepting new tasks' do + expected = Concurrent::AtomicBoolean.new(false) + latch = Concurrent::CountDownLatch.new(1) + subject.post{ sleep(0.1); latch.count_down } + latch.wait(1) + subject.kill + begin + subject.post{ expected.make_true } + rescue Concurrent::RejectedExecutionError + end + sleep(0.1) + expect(expected.value).to be_falsey + end + + it 'rejects all pending tasks' do + subject.post{ sleep(1) } + sleep(0.1) + subject.kill + sleep(0.1) + begin + expect(subject.post{ nil }).to be_falsey + rescue Concurrent::RejectedExecutionError + end + end + end + + context '#wait_for_termination' do + + it 'immediately returns true when no operations are pending' do + subject.shutdown + expect(subject.wait_for_termination(0)).to be_truthy + end + + it 'returns true after shutdown has complete' do + 10.times { subject << proc{ nil } } + sleep(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to be_truthy + end + + it 'returns true when shutdown successfully completes before timeout' do + subject.post{ sleep(0.5) } + sleep(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to be_truthy + end + + it 'returns false when shutdown fails to complete before timeout' do + unless subject.serialized? + latch = Concurrent::CountDownLatch.new 1 + 100.times{ subject.post{ latch.wait } } + sleep(0.1) + subject.shutdown + expect(subject.wait_for_termination(0.01)).to be_falsey + latch.count_down + end + end + + it 'waits forever when no timeout value is given' do + subject.post{ sleep(0.5) } + sleep(0.1) + subject.shutdown + expect(subject.wait_for_termination).to be_truthy + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/fixed_thread_pool_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/fixed_thread_pool_spec.rb new file mode 100644 index 0000000000..c7f7db3bec --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/fixed_thread_pool_spec.rb @@ -0,0 +1,317 @@ +require 'concurrent/executor/fixed_thread_pool' +require_relative 'thread_pool_shared' + +module Concurrent + + RSpec.describe FixedThreadPool do + + let!(:num_threads){ 5 } + subject { described_class.new(num_threads) } + + after(:each) do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it_should_behave_like :thread_pool + + let(:latch) { Concurrent::CountDownLatch.new } + + context '#initialize default values' do + + subject { described_class.new(5) } + + it 'defaults :min_length correctly' do + expect(subject.min_length).to eq 5 + end + + it 'defaults :max_length correctly' do + expect(subject.max_length).to eq 5 + end + + it 'defaults :fallback_policy to :abort' do + expect(subject.fallback_policy).to eq :abort + end + + + it 'defaults :idletime correctly' do + expect(subject.idletime).to eq subject.class.const_get(:DEFAULT_THREAD_IDLETIMEOUT) + end + + it 'defaults default :max_queue to zero' do + expect(subject.max_queue).to eq 0 + end + + end + + context '#initialize explicit values' do + + it 'raises an exception when the pool length is less than one' do + expect { + described_class.new(0) + }.to raise_error(ArgumentError) + end + + + it 'sets explicit :max_queue correctly' do + subject = described_class.new(5, :max_queue => 10) + expect(subject.max_queue).to eq 10 + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it 'correctly sets valid :fallback_policy' do + subject = described_class.new(5, :fallback_policy => :caller_runs) + expect(subject.fallback_policy).to eq :caller_runs + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it "correctly sets valid :idletime" do + subject = described_class.new(5, :idletime => 10) + expect(subject.idletime).to eq 10 + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it 'raises an exception if given an invalid :fallback_policy' do + expect { + described_class.new(5, fallback_policy: :bogus) + }.to raise_error(ArgumentError) + end + end + + context '#min_length' do + + it 'returns :num_threads on creation' do + expect(subject.min_length).to eq num_threads + end + + it 'returns :num_threads while running' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.min_length).to eq num_threads + end + + it 'returns :num_threads once shutdown' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.min_length).to eq num_threads + end + end + + context '#max_length' do + + it 'returns :num_threads on creation' do + expect(subject.max_length).to eq num_threads + end + + it 'returns :num_threads while running' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.max_length).to eq num_threads + end + + it 'returns :num_threads once shutdown' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.max_length).to eq num_threads + end + end + + context '#length' do + + it 'returns :num_threads while running' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.length).to eq num_threads + end + end + + context '#largest_length' do + + it 'returns zero on creation' do + expect(subject.largest_length).to eq 0 + end + + it 'returns :num_threads while running' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.largest_length).to eq num_threads + end + + it 'returns :num_threads once shutdown' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.largest_length).to eq num_threads + end + end + + context '#kill' do + + it 'attempts to kill all in-progress tasks' do + thread_count = [subject.length, 5].max + @expected = false + thread_count.times do + # kill tries to shutdown first with 1sec timeout, so wait 2sec here + subject.post { sleep(2) } + end + subject.post{ @expected = true } + sleep(0.1) + subject.kill + sleep(0.1) + expect(@expected).to be_falsey + end + end + + context 'worker creation and caching' do + + it 'never creates more than :num_threads threads' do + pool = described_class.new(5) + latch = Concurrent::CountDownLatch.new 1 + 100.times{ pool << proc{ latch.wait } } + sleep(0.1) + expect(pool.length).to eq 5 + latch.count_down + pool.shutdown + expect(pool.wait_for_termination(pool_termination_timeout)).to eq true + end + end + + context 'fallback policy' do + + before(:each) do + @queue = Queue.new + end + + after(:each) do + subject.shutdown + end + + # On abort, it should raise an error + it "raises an error when overflow on abort" do + latch = Concurrent::CountDownLatch.new(5) + mutex = Mutex.new + + subject = described_class.new(2, :max_queue => 2, :fallback_policy => :abort) + expect { + 5.times do |i| + subject.post do + sleep 0.1 + mutex.synchronize{ @queue << i } + latch.count_down + end + end + latch.wait(1) + }.to raise_error(RejectedExecutionError) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + # On discard, we'd expect no error, but also not all five results + it 'discards when fallback_policy is :discard' do + latch = Concurrent::CountDownLatch.new(5) + mutex = Mutex.new + + subject = described_class.new(2, :max_queue => 2, :fallback_policy => :discard) + 5.times do |i| + subject.post do + sleep 0.1 + mutex.synchronize{ @queue << i } + latch.count_down + end + end + latch.wait(1) + + expect(@queue.length).to be < 5 + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + # To check for caller_runs, we'll check how many unique threads + # actually ran the block + + it 'uses the calling thread for overflow under caller_runs' do + latch = Concurrent::CountDownLatch.new(5) + mutex = Mutex.new + + subject = described_class.new(2, :max_queue => 2, :fallback_policy => :caller_runs) + + 5.times do |i| + subject.post do + sleep 0.1 + mutex.synchronize{ @queue << Thread.current } + latch.count_down + end + end + latch.wait(1) + + # Turn the queue into an array + a = [] + a << @queue.shift until @queue.empty? + + #NOTE: This test is very, very difficult to setup properly. Hence the 'be_within' matcher + expect(a.size).to be_within(1).of(5) # one for each run of the block + expect(a.uniq.size).to be_within(1).of(3) # one for each of the two threads, plus the caller + + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + end + + context 'runtime-specific implementation' do + + if Concurrent.on_jruby? + + it 'sets :fallback_policy correctly' do + clazz = java.util.concurrent.ThreadPoolExecutor::DiscardPolicy + policy = clazz.new + expect(clazz).to receive(:new).at_least(:once).with(any_args).and_return(policy) + + subject = FixedThreadPool.new(5, fallback_policy: :discard) + expect(subject.fallback_policy).to eq :discard + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + else + + context 'exception handling' do + + it 'restarts threads that experience exception' do + count = subject.length + count.times{ subject << proc{ raise StandardError } } + sleep(1) + expect(subject.length).to eq count + end + end + + context 'worker creation and caching' do + + it 'creates new workers when there are none available' do + pool = described_class.new(5) + expect(pool.length).to eq 0 + 5.times{ pool << proc{ sleep(1) } } + sleep(0.1) + expect(pool.length).to eq 5 + pool.shutdown + expect(pool.wait_for_termination(pool_termination_timeout)).to eq true + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/global_thread_pool_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/global_thread_pool_shared.rb new file mode 100644 index 0000000000..71be156df5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/global_thread_pool_shared.rb @@ -0,0 +1,34 @@ +require 'concurrent/atomic/count_down_latch' + +RSpec.shared_examples :global_thread_pool do + + context '#post' do + + it 'raises an exception if no block is given' do + expect { + subject.post + }.to raise_error(ArgumentError) + end + + it 'returns true when the block is added to the queue' do + expect(subject.post{ nil }).to be_truthy + end + + it 'calls the block with the given arguments' do + latch = Concurrent::CountDownLatch.new(1) + expected = nil + subject.post(1, 2, 3) do |a, b, c| + expected = [a, b, c] + latch.count_down + end + latch.wait(0.2) + expect(expected).to eq [1, 2, 3] + end + + it 'aliases #<<' do + latch = Concurrent::CountDownLatch.new(1) + subject << proc { latch.count_down } + expect(latch.wait(0.2)).to eq true + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/immediate_executor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/immediate_executor_spec.rb new file mode 100644 index 0000000000..c53efd72ed --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/immediate_executor_spec.rb @@ -0,0 +1,12 @@ +require 'concurrent/executor/immediate_executor' +require_relative 'executor_service_shared' + +module Concurrent + + RSpec.describe ImmediateExecutor do + + subject { ImmediateExecutor.new } + + it_should_behave_like :executor_service + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/indirect_immediate_executor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/indirect_immediate_executor_spec.rb new file mode 100644 index 0000000000..f40364f286 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/indirect_immediate_executor_spec.rb @@ -0,0 +1,26 @@ +require 'concurrent/executor/indirect_immediate_executor' +require_relative 'executor_service_shared' + +module Concurrent + + RSpec.describe IndirectImmediateExecutor do + + subject { IndirectImmediateExecutor.new } + + it_should_behave_like :executor_service + + it "runs its tasks synchronously" do + start = Time.now + subject.post { sleep 0.1 } + + expect(Time.now - start).to be >= 0.1 + end + + it "runs the task on a separate thread" do + used_thread = nil + subject.post { used_thread = Thread.current } + + expect(used_thread).not_to be(Thread.current) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/java_single_thread_executor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/java_single_thread_executor_spec.rb new file mode 100644 index 0000000000..2aca29f8d6 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/java_single_thread_executor_spec.rb @@ -0,0 +1,20 @@ +require 'concurrent/utility/engine' + +if Concurrent.on_jruby? + require_relative 'executor_service_shared' + + module Concurrent + + RSpec.describe JavaSingleThreadExecutor, :type=>:jruby do + + after(:each) do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + subject { JavaSingleThreadExecutor.new } + + it_should_behave_like :executor_service + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/java_thread_pool_executor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/java_thread_pool_executor_spec.rb new file mode 100644 index 0000000000..64d4454419 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/java_thread_pool_executor_spec.rb @@ -0,0 +1,79 @@ +require 'concurrent/utility/engine' + +if Concurrent.on_jruby? + require_relative 'thread_pool_executor_shared' + + module Concurrent + + RSpec.describe JavaThreadPoolExecutor, :type => :jruby do + + after(:each) do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + subject do + JavaThreadPoolExecutor.new( + min_threads: 2, + max_threads: 5, + idletime: 60, + max_queue: 10, + fallback_policy: :discard + ) + end + + it_should_behave_like :thread_pool + + it_should_behave_like :thread_pool_executor + + context :prune do + it "is a no-op, pruning is handled by the JVM" do + executor = JavaThreadPoolExecutor.new + executor.prune_pool + end + end + + context '#overload_policy' do + + specify ':abort maps to AbortPolicy' do + clazz = java.util.concurrent.ThreadPoolExecutor::AbortPolicy + policy = clazz.new + expect(clazz).to receive(:new).at_least(:once).with(any_args).and_return(policy) + JavaThreadPoolExecutor.new( + min_threads: 2, + max_threads: 5, + idletime: 60, + max_queue: 10, + fallback_policy: :abort + ) + end + + specify ':discard maps to DiscardPolicy' do + clazz = java.util.concurrent.ThreadPoolExecutor::DiscardPolicy + policy = clazz.new + expect(clazz).to receive(:new).at_least(:once).with(any_args).and_return(policy) + JavaThreadPoolExecutor.new( + min_threads: 2, + max_threads: 5, + idletime: 60, + max_queue: 10, + fallback_policy: :discard + ) + end + + specify ':caller_runs maps to CallerRunsPolicy' do + clazz = java.util.concurrent.ThreadPoolExecutor::CallerRunsPolicy + policy = clazz.new + expect(clazz).to receive(:new).at_least(:once).with(any_args).and_return(policy) + JavaThreadPoolExecutor.new( + min_threads: 2, + max_threads: 5, + idletime: 60, + max_queue: 10, + fallback_policy: :caller_runs + ) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/ruby_single_thread_executor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/ruby_single_thread_executor_spec.rb new file mode 100644 index 0000000000..e1717546aa --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/ruby_single_thread_executor_spec.rb @@ -0,0 +1,17 @@ +require 'concurrent/executor/ruby_single_thread_executor' +require_relative 'executor_service_shared' + +module Concurrent + + RSpec.describe RubySingleThreadExecutor, :type=>:mri do + + after(:each) do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + subject { RubySingleThreadExecutor.new } + + it_should_behave_like :executor_service + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/ruby_thread_pool_executor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/ruby_thread_pool_executor_spec.rb new file mode 100644 index 0000000000..bf328a7c62 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/ruby_thread_pool_executor_spec.rb @@ -0,0 +1,196 @@ +require_relative 'thread_pool_executor_shared' +require 'concurrent/executor/thread_pool_executor' + +module Concurrent + + RSpec.describe RubyThreadPoolExecutor, :type=>:mri do + + after(:each) do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + subject do + RubyThreadPoolExecutor.new( + min_threads: 2, + max_threads: 5, + idletime: 60, + max_queue: 10, + fallback_policy: :discard + ) + end + + it_should_behave_like :thread_pool + + it_should_behave_like :thread_pool_executor + + context :prune, if: !Concurrent.on_jruby? do # pruning is flaky on JRuby + subject do + RubyThreadPoolExecutor.new(idletime: 5, min_threads: 2, max_threads: 10) + end + + Group = Struct.new :waiting_threads, :threads, :mutex, :cond + + def prepare_thread_group(size) + cond = ConditionVariable.new + mutex = Mutex.new + threads = [] + size.times do + subject.post do + mutex.synchronize do + threads << Thread.current + cond.wait(mutex) + threads.delete(Thread.current) + end + end + end + eventually(mutex: mutex) { expect(threads).to have_attributes(size: size) } + Group.new(threads, threads.dup, mutex, cond) + end + + def wakeup_thread_group(group) + group.cond.broadcast + eventually(mutex: group.mutex) do + expect(group.waiting_threads).to have_attributes(size: 0) + end + end + + before(:each) do + @now = Concurrent.monotonic_time + allow(Concurrent).to receive(:monotonic_time) { @now } + + @group1 = prepare_thread_group(5) + @group2 = prepare_thread_group(5) + end + + def eventually(mutex: nil, timeout: 5, &block) + start = Time.now + while Time.now - start < timeout + begin + if mutex + mutex.synchronize do + return yield + end + else + return yield + end + rescue Exception => last_failure + end + Thread.pass + end + raise last_failure + end + + it "triggers pruning when posting work if the last prune happened more than gc_interval ago" do + wakeup_thread_group(@group1) + @now += 6 + wakeup_thread_group(@group2) + subject.post { } + + eventually { expect(@group1.threads).to all(have_attributes(status: false)) } + eventually { expect(@group2.threads).to all(have_attributes(status: 'sleep')) } + end + + it "does not trigger pruning when posting work if the last prune happened less than gc_interval ago" do + wakeup_thread_group(@group1) + @now += 3 + subject.prune_pool + @now += 3 + wakeup_thread_group(@group2) + subject.post { } + + eventually { expect(@group1.threads).to all(have_attributes(status: false)) } + eventually { expect(@group2.threads).to all(have_attributes(status: 'sleep')) } + end + + it "reclaims threads that have been idle for more than idletime seconds" do + wakeup_thread_group(@group1) + @now += 6 + wakeup_thread_group(@group2) + subject.prune_pool + + eventually { expect(@group1.threads).to all(have_attributes(status: false)) } + eventually { expect(@group2.threads).to all(have_attributes(status: 'sleep')) } + end + + it "keeps at least min_length workers" do + wakeup_thread_group(@group1) + wakeup_thread_group(@group2) + @now += 12 + subject.prune_pool + all_threads = @group1.threads + @group2.threads + eventually do + finished_threads = all_threads.find_all { |t| !t.status } + expect(finished_threads).to have_attributes(size: 8) + end + end + end + + context '#remaining_capacity' do + + let!(:expected_max){ 100 } + let(:latch) { Concurrent::CountDownLatch.new } + + subject do + RubyThreadPoolExecutor.new( + min_threads: 10, + max_threads: 20, + idletime: 60, + max_queue: expected_max, + fallback_policy: :discard + ) + end + + it 'returns :max_length when no tasks are enqueued' do + 5.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.remaining_capacity).to eq expected_max + end + + it 'returns the remaining capacity when tasks are enqueued' do + block = Concurrent::CountDownLatch.new + 100.times{ subject.post{ block.wait } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.remaining_capacity).to be < expected_max + block.count_down + end + end + + context 'threads naming' do + subject do + opts = { min_threads: 2 } + opts[:name] = pool_name if pool_name + described_class.new(opts) + end + + let(:names) do + require 'concurrent/set' + Concurrent::Set.new + end + + before do + subject.post(names) { |names| names << Thread.current.name } + subject.post(names) { |names| names << Thread.current.name } + subject.shutdown + subject.wait_for_termination(pool_termination_timeout) + expect(names.size).to eq 2 + end + + context 'without pool name' do + let(:pool_name) { } + it 'sets counted name' do + expect(names.all? { |name| name =~ /^worker-\d+$/ }).to be true + end + end + + context 'with pool name' do + let(:pool_name) { 'MyExecutor' } + it 'sets counted name' do + expect(names.all? { |name| name =~ /^MyExecutor-worker-\d+$/ }).to be true + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/safe_task_executor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/safe_task_executor_spec.rb new file mode 100644 index 0000000000..5b4edf6a42 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/safe_task_executor_spec.rb @@ -0,0 +1,133 @@ +require 'concurrent/executor/safe_task_executor' + +module Concurrent + + RSpec.describe SafeTaskExecutor do + + describe '#execute' do + + context 'happy execution' do + + let(:task) { Proc.new { 42 } } + subject { SafeTaskExecutor.new(task) } + + it 'should return success' do + success, _value, _reason = subject.execute + expect(success).to be_truthy + end + + it 'should return task value' do + _success, value, _reason = subject.execute + expect(value).to eq 42 + end + + it 'should return a nil reason' do + _success, _value, reason = subject.execute + expect(reason).to be_nil + end + + it 'passes all arguments to #execute to the task' do + expected = nil + task = proc {|*args| expected = args } + SafeTaskExecutor.new(task).execute(1, 2, 3) + expect(expected).to eq [1, 2, 3] + end + + it 'protectes #execute with a mutex' do + expect(subject).to receive(:synchronize).with(no_args) + subject.execute + end + end + + context 'failing execution' do + + let(:task) { Proc.new { raise StandardError.new('an error') } } + subject { SafeTaskExecutor.new(task) } + + it 'should return false success' do + success, _value, _reason = subject.execute + expect(success).to be_falsey + end + + it 'should return a nil value' do + _success, value, _reason = subject.execute + expect(value).to be_nil + end + + it 'should return the reason' do + _success, _value, reason = subject.execute + expect(reason).to be_a(StandardError) + expect(reason.message).to eq 'an error' + end + + it 'rescues Exception when :rescue_exception is true' do + task = proc { raise Exception } + subject = SafeTaskExecutor.new(task, rescue_exception: true) + expect { + subject.execute + }.to_not raise_error + end + + it 'rescues StandardError when :rescue_exception is false' do + task = proc { raise StandardError } + subject = SafeTaskExecutor.new(task, rescue_exception: false) + expect { + subject.execute + }.to_not raise_error + + task = proc { raise Exception } + subject = SafeTaskExecutor.new(task, rescue_exception: false) + expect { + subject.execute + }.to raise_error(Exception) + end + + it 'rescues StandardError by default' do + task = proc { raise StandardError } + subject = SafeTaskExecutor.new(task) + expect { + subject.execute + }.to_not raise_error + + task = proc { raise Exception } + subject = SafeTaskExecutor.new(task) + expect { + subject.execute + }.to raise_error(Exception) + end + end + + # These tests only make sense on CRuby as they test a workaround for CRuby bugs: https://github.com/ruby-concurrency/concurrent-ruby/issues/931 + if Concurrent.on_cruby? and Concurrent.ruby_version(:<, 3, 2, 0) + context 'local jump error' do + def execute + Thread.new do + executor = SafeTaskExecutor.new(-> { yield 42 }) + @result = executor.execute + end.join + end + + subject do + to_enum(:execute).first + @result + end + + it 'should return success' do + success, _value, _reason = subject + expect(success).to be_truthy + end + + it 'should return a nil value' do + _success, value, _reason = subject + expect(value).to be_nil + end + + it 'should return a nil reason' do + _success, _value, reason = subject + expect(reason).to be_nil + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/serialized_execution_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/serialized_execution_spec.rb new file mode 100644 index 0000000000..5f0cb41720 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/serialized_execution_spec.rb @@ -0,0 +1,13 @@ +require 'concurrent/executor/serialized_execution_delegator' +require 'concurrent/executor/immediate_executor' +require_relative 'executor_service_shared' + +module Concurrent + + RSpec.describe SerializedExecutionDelegator do + + subject { SerializedExecutionDelegator.new(ImmediateExecutor.new) } + + it_should_behave_like :executor_service + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/simple_executor_service_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/simple_executor_service_spec.rb new file mode 100644 index 0000000000..26a22f7e98 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/simple_executor_service_spec.rb @@ -0,0 +1,101 @@ +require 'concurrent/executor/simple_executor_service' +require 'concurrent/configuration' +require_relative 'executor_service_shared' + +module Concurrent + + RSpec.describe SimpleExecutorService do + + subject { SimpleExecutorService.new } + + it_should_behave_like :executor_service + + context '#post' do + + subject { SimpleExecutorService.new } + + it 'creates a new thread for a call without arguments' do + thread = in_thread{ nil } + expect(Thread).to receive(:new).with(no_args()).and_return(thread) + expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args()) + subject.post{ nil } + end + + it 'executes a call without arguments' do + latch = CountDownLatch.new(1) + subject.post{ latch.count_down } + expect(latch.wait(1)).to be_truthy + end + + it 'creates a new thread for a call with arguments' do + thread = in_thread{ nil } + expect(Thread).to receive(:new).with(1,2,3).and_return(thread) + expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args()) + subject.post(1,2,3){ nil } + end + + it 'executes a call with one argument' do + latch = CountDownLatch.new(3) + subject.post(3){|count| count.times{ latch.count_down } } + expect(latch.wait(1)).to be_truthy + end + + it 'executes a call with multiple arguments' do + latch = CountDownLatch.new(10) + subject.post(1,2,3,4){|*count| count.reduce(:+).times{ latch.count_down } } + expect(latch.wait(1)).to be_truthy + end + + it 'aliases #<<' do + thread = in_thread{ nil } + expect(Thread).to receive(:new).with(no_args()).and_return(thread) + expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args()) + subject << proc{ nil } + end + end + + context 'SimpleExecutorService.post' do + + subject { SimpleExecutorService } + + it 'creates a new thread for a call without arguments' do + thread = in_thread{ nil } + expect(Thread).to receive(:new).with(no_args()).and_return(thread) + expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args()) + subject.post{ nil } + end + + it 'executes a call without arguments' do + latch = CountDownLatch.new(1) + subject.post{ latch.count_down } + expect(latch.wait(1)).to be_truthy + end + + it 'creates a new thread for a call with arguments' do + thread = in_thread{ nil } + expect(Thread).to receive(:new).with(1,2,3).and_return(thread) + expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args()) + subject.post(1,2,3){ nil } + end + + it 'executes a call with one argument' do + latch = CountDownLatch.new(3) + subject.post(3){|count| count.times{ latch.count_down } } + expect(latch.wait(1)).to be_truthy + end + + it 'executes a call with multiple arguments' do + latch = CountDownLatch.new(10) + subject.post(1,2,3,4){|*count| count.reduce(:+).times{ latch.count_down } } + expect(latch.wait(1)).to be_truthy + end + + it 'aliases #<<' do + thread = in_thread{ nil } + expect(Thread).to receive(:new).with(no_args()).and_return(thread) + expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args()) + subject << proc{ nil } + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_class_cast_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_class_cast_spec.rb new file mode 100644 index 0000000000..33ba1f8724 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_class_cast_spec.rb @@ -0,0 +1,28 @@ +require 'concurrent/executor/single_thread_executor' +require 'concurrent/executor/thread_pool_executor' + +module Concurrent + RSpec.describe SingleThreadExecutor do + if Concurrent.on_jruby? + it 'inherits from JavaSingleThreadExecutor' do + expect(SingleThreadExecutor.ancestors).to include(JavaSingleThreadExecutor) + end + else + it 'inherits from RubySingleThreadExecutor' do + expect(SingleThreadExecutor.ancestors).to include(RubySingleThreadExecutor) + end + end + end + + RSpec.describe ThreadPoolExecutor do + if Concurrent.on_jruby? + it 'inherits from JavaThreadPoolExecutor' do + expect(ThreadPoolExecutor.ancestors).to include(JavaThreadPoolExecutor) + end + else + it 'inherits from RubyThreadPoolExecutor' do + expect(ThreadPoolExecutor.ancestors).to include(RubyThreadPoolExecutor) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_executor_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_executor_shared.rb new file mode 100644 index 0000000000..bb91b3d4b7 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_executor_shared.rb @@ -0,0 +1,662 @@ +require_relative 'thread_pool_shared' +require 'concurrent/atomic/atomic_fixnum' + +RSpec.shared_examples :thread_pool_executor do + + after(:each) do + subject.shutdown + subject.wait_for_termination(pool_termination_timeout) + end + + context '#initialize defaults' do + + subject { described_class.new } + + it 'defaults :min_length to DEFAULT_MIN_POOL_SIZE' do + expect(subject.min_length).to eq described_class::DEFAULT_MIN_POOL_SIZE + end + + + it 'defaults :max_length to DEFAULT_MAX_POOL_SIZE' do + expect(subject.max_length).to eq described_class::DEFAULT_MAX_POOL_SIZE + end + + it 'defaults :idletime to DEFAULT_THREAD_IDLETIMEOUT' do + expect(subject.idletime).to eq described_class::DEFAULT_THREAD_IDLETIMEOUT + end + + it 'defaults :max_queue to DEFAULT_MAX_QUEUE_SIZE' do + expect(subject.max_queue).to eq described_class::DEFAULT_MAX_QUEUE_SIZE + end + + it 'defaults :fallback_policy to :abort' do + expect(subject.fallback_policy).to eq :abort + end + + it 'defaults :name to nil' do + expect(subject.name).to be_nil + end + end + + context "#initialize explicit values" do + + it "sets :min_threads" do + pool = described_class.new(min_threads: 2) + expect(pool.min_length).to eq 2 + pool.shutdown + expect(pool.wait_for_termination(pool_termination_timeout)).to eq true + end + + it "sets :max_threads" do + pool = described_class.new(max_threads: 2) + expect(pool.max_length).to eq 2 + pool.shutdown + expect(pool.wait_for_termination(pool_termination_timeout)).to eq true + end + + it "sets :idletime" do + pool = described_class.new(idletime: 2) + expect(pool.idletime).to eq 2 + pool.shutdown + expect(pool.wait_for_termination(pool_termination_timeout)).to eq true + end + + it "doesn't allow max_threads < min_threads" do + expect { + described_class.new(min_threads: 2, max_threads: 1) + }.to raise_error(ArgumentError) + end + + it 'accepts all valid fallback policies' do + Concurrent::RubyThreadPoolExecutor::FALLBACK_POLICIES.each do |policy| + subject = described_class.new(fallback_policy: policy) + expect(subject.fallback_policy).to eq policy + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + end + + it 'raises an exception if :max_threads is less than zero' do + expect { + described_class.new(max_threads: -1) + }.to raise_error(ArgumentError) + end + + it 'raises an exception if :min_threads is less than zero' do + expect { + described_class.new(min_threads: -1) + }.to raise_error(ArgumentError) + end + + it 'raises an exception if :max_threads greater than the max allowable' do + expect { + described_class.new(max_threads: described_class::DEFAULT_MAX_POOL_SIZE+1) + }.to raise_error(ArgumentError) + end + + it 'raises an exception if :max_threads is less than :min_threads' do + expect { + described_class.new(max_threads: 1, min_threads: 100) + }.to raise_error(ArgumentError) + end + + it 'raises an exception if given an invalid :fallback_policy' do + expect { + described_class.new(fallback_policy: :bogus) + }.to raise_error(ArgumentError) + end + + it 'sets :name' do + pool = described_class.new(name: 'MyPool') + expect(pool.name).to eq 'MyPool' + pool.shutdown + pool.wait_for_termination(pool_termination_timeout) + end + end + + context '#max_queue' do + + let!(:expected_max){ 100 } + subject{ described_class.new(max_queue: expected_max) } + + it 'returns the set value on creation' do + expect(subject.max_queue).to eq expected_max + end + + it 'returns the set value when running', :truffle_bug => true do # only actually fails for RubyThreadPoolExecutor + trigger = Concurrent::Event.new + 5.times{ subject.post{ trigger.wait } } + expect(subject.max_queue).to eq expected_max + trigger.set + end + + it 'returns the set value after stopping' do + 5.times{ subject.post{ nil } } + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.max_queue).to eq expected_max + end + end + + context '#synchronous' do + + subject do + described_class.new( + min_threads: 1, + max_threads: 2, + max_queue: 0, + synchronous: true, + fallback_policy: :abort + ) + end + + it 'cannot be set unless `max_queue` is zero' do + expect { + described_class.new( + min_threads: 2, + max_threads: 5, + max_queue: 1, + fallback_policy: :discard, + synchronous: true + ) + }.to raise_error(ArgumentError) + end + + it 'executes fallback policy once max_threads has been reached' do + latch = Concurrent::CountDownLatch.new(1) + (subject.max_length).times do + subject.post { + latch.wait + } + end + + expect(subject.queue_length).to eq 0 + + # verify nothing happening + 20.times { + expect { + subject.post { + sleep + } + }.to raise_error(Concurrent::RejectedExecutionError) + } + + # release + latch.count_down + end + + end + + context '#queue_length', :truffle_bug => true do # only actually fails for RubyThreadPoolExecutor + + let!(:expected_max){ 10 } + subject do + described_class.new( + min_threads: 2, + max_threads: 5, + max_queue: expected_max, + fallback_policy: :discard + ) + end + + it 'returns zero on creation' do + expect(subject.queue_length).to eq 0 + end + + it 'returns zero when there are no enqueued tasks' do + latch = Concurrent::CountDownLatch.new(5) + 5.times{ subject.post{ latch.count_down } } + latch.wait(0.1) + expect(subject.queue_length).to eq 0 + end + + it 'returns the size of the queue when tasks are enqueued' do + trigger = Concurrent::Event.new + 20.times{ subject.post{ trigger.wait } } + expect(subject.queue_length).to be > 0 + trigger.set + end + + it 'returns zero when stopped' do + trigger = Concurrent::Event.new + 20.times{ subject.post{ trigger.wait } } + subject.shutdown + trigger.set + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.queue_length).to eq 0 + end + + it 'can never be greater than :max_queue' do + trigger = Concurrent::Event.new + 20.times{ subject.post{ trigger.wait } } + expect(subject.queue_length).to be <= expected_max + trigger.set + end + end + + context '#remaining_capacity' do + + let!(:expected_max){ 100 } + subject{ described_class.new(max_queue: expected_max) } + + it 'returns -1 when :max_queue is set to zero' do + executor = described_class.new(max_queue: 0) + expect(executor.remaining_capacity).to eq(-1) + executor.shutdown + expect(executor.wait_for_termination(pool_termination_timeout)).to eq true + end + + it 'returns :max_length on creation' do + expect(subject.remaining_capacity).to eq expected_max + end + + it 'returns :max_length when stopped' do + 100.times{ subject.post{ nil } } + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.remaining_capacity).to eq expected_max + end + end + + context '#fallback_policy' do + + let!(:min_threads){ 1 } + let!(:max_threads){ 1 } + let!(:idletime){ 60 } + let!(:max_queue){ 1 } + + context ':abort' do + + subject do + described_class.new( + min_threads: min_threads, + max_threads: max_threads, + idletime: idletime, + max_queue: max_queue, + fallback_policy: :abort + ) + end + + specify '#post raises an error when the queue is at capacity' do + trigger = Concurrent::Event.new + expect { + 20.times{ subject.post{ trigger.wait } } + }.to raise_error(Concurrent::RejectedExecutionError) + trigger.set + end + + specify '#<< raises an error when the queue is at capacity' do + trigger = Concurrent::Event.new + expect { + 20.times{ subject << proc { trigger.wait } } + }.to raise_error(Concurrent::RejectedExecutionError) + trigger.set + end + + specify '#post raises an error when the executor is shutting down' do + trigger = Concurrent::Event.new + expect { + subject.shutdown; subject.post{ trigger.wait } + }.to raise_error(Concurrent::RejectedExecutionError) + trigger.set + end + + specify '#<< raises an error when the executor is shutting down' do + trigger = Concurrent::Event.new + expect { + subject.shutdown; subject << proc { trigger.wait } + }.to raise_error(Concurrent::RejectedExecutionError) + trigger.set + end + + specify 'a #post task is never executed when the queue is at capacity' do + all_tasks_posted = Concurrent::Event.new + + latch = Concurrent::CountDownLatch.new(max_threads) + + initial_executed = Concurrent::AtomicFixnum.new(0) + subsequent_executed = Concurrent::AtomicFixnum.new(0) + + # Fill up all the threads (with a task that won't complete until + # all tasks are posted) + max_threads.times do + subject.post{ latch.count_down; all_tasks_posted.wait ; initial_executed.increment;} + end + + # Wait for all those tasks to be taken off the queue onto a + # worker thread and start executing + latch.wait + + # Fill up the queue (with a task that won't complete until + # all tasks are posted) + max_queue.times do + subject.post{ all_tasks_posted.wait; initial_executed.increment; } + end + + # Inject 20 more tasks, which should throw an exception + 20.times do + expect { + subject.post { subsequent_executed.increment; } + }.to raise_error(Concurrent::RejectedExecutionError) + end + + # Trigger the event, so that the tasks in the threads and on + # the queue can run to completion + all_tasks_posted.set + + # Wait for all tasks to finish + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + + # The tasks should have run until all the threads and the + # queue filled up... + expect(initial_executed.value).to be (max_threads + max_queue) + + # ..but been dropped after that + expect(subsequent_executed.value).to be 0 + end + + specify 'a #<< task is never executed when the queue is at capacity' do + all_tasks_posted = Concurrent::Event.new + + latch = Concurrent::CountDownLatch.new(max_threads) + + initial_executed = Concurrent::AtomicFixnum.new(0) + subsequent_executed = Concurrent::AtomicFixnum.new(0) + + # Fill up all the threads (with a task that won't complete until + # all tasks are posted) + max_threads.times do + subject << proc { latch.count_down; all_tasks_posted.wait ; initial_executed.increment;} + end + + # Wait for all those tasks to be taken off the queue onto a + # worker thread and start executing + latch.wait + + # Fill up the queue (with a task that won't complete until + # all tasks are posted) + max_queue.times do + subject << proc { all_tasks_posted.wait; initial_executed.increment; } + end + + # Inject 20 more tasks, which should throw an exeption + 20.times do + expect { + subject << proc { subsequent_executed.increment; } + }.to raise_error(Concurrent::RejectedExecutionError) + end + + # Trigger the event, so that the tasks in the threads and on + # the queue can run to completion + all_tasks_posted.set + + # Wait for all tasks to finish + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + + # The tasks should have run until all the threads and the + # queue filled up... + expect(initial_executed.value).to be (max_threads + max_queue) + + # ..but been rejected after that + expect(subsequent_executed.value).to be 0 + end + end + + context ':discard' do + + subject do + described_class.new( + min_threads: min_threads, + max_threads: max_threads, + idletime: idletime, + max_queue: max_queue, + fallback_policy: :discard + ) + end + + specify 'a #post task is never executed when the queue is at capacity' do + all_tasks_posted = Concurrent::Event.new + + latch = Concurrent::CountDownLatch.new(max_threads) + + initial_executed = Concurrent::AtomicFixnum.new(0) + subsequent_executed = Concurrent::AtomicFixnum.new(0) + + # Fill up all the threads (with a task that won't complete until + # all tasks are posted) + max_threads.times do + subject.post{ latch.count_down; all_tasks_posted.wait ; initial_executed.increment;} + end + + # Wait for all those tasks to be taken off the queue onto a + # worker thread and start executing + latch.wait + + # Fill up the queue (with a task that won't complete until + # all tasks are posted) + max_queue.times do + subject.post{ all_tasks_posted.wait; initial_executed.increment; } + end + + # Inject 20 more tasks, which should be dropped without an exception + 20.times do + subject.post{ subsequent_executed.increment; } + end + + # Trigger the event, so that the tasks in the threads and on + # the queue can run to completion + all_tasks_posted.set + + # Wait for all tasks to finish + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + + # The tasks should have run until all the threads and the + # queue filled up... + expect(initial_executed.value).to be (max_threads + max_queue) + + # ..but been dropped after that + expect(subsequent_executed.value).to be 0 + end + + specify 'a #<< task is never executed when the queue is at capacity' do + all_tasks_posted = Concurrent::Event.new + + latch = Concurrent::CountDownLatch.new(max_threads) + + initial_executed = Concurrent::AtomicFixnum.new(0) + subsequent_executed = Concurrent::AtomicFixnum.new(0) + + # Fill up all the threads (with a task that won't complete until + # all tasks are posted) + max_threads.times do + subject << proc { latch.count_down; all_tasks_posted.wait ; initial_executed.increment;} + end + + # Wait for all those tasks to be taken off the queue onto a + # worker thread and start executing + latch.wait + + # Fill up the queue (with a task that won't complete until + # all tasks are posted) + max_queue.times do + subject << proc { all_tasks_posted.wait; initial_executed.increment; } + end + + # Inject 20 more tasks, which should be dropped without an exception + 20.times do + subject << proc { subsequent_executed.increment; } + end + + # Trigger the event, so that the tasks in the threads and on + # the queue can run to completion + all_tasks_posted.set + + # Wait for all tasks to finish + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + + # The tasks should have run until all the threads and the + # queue filled up... + expect(initial_executed.value).to be (max_threads + max_queue) + + # ..but been dropped after that + expect(subsequent_executed.value).to be 0 + end + + specify 'a #post task is never executed when the executor is shutting down' do + executed = Concurrent::AtomicFixnum.new(0) + + subject.shutdown + subject.post{ executed.increment } + + # Wait for all tasks to finish + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + + expect(executed.value).to be 0 + end + + specify 'a #<< task is never executed when the executor is shutting down' do + executed = Concurrent::AtomicFixnum.new(0) + + subject.shutdown + subject << proc { executed.increment } + + # Wait for all tasks to finish + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + + expect(executed.value).to be 0 + end + + specify '#post returns false when the executor is shutting down' do + subject.shutdown + ret = subject.post{ nil } + expect(ret).to be false + end + end + + context ':caller_runs' do + + let(:executor) do + described_class.new( + min_threads: 1, + max_threads: 1, + idletime: idletime, + max_queue: 1, + fallback_policy: :caller_runs + ) + end + + after(:each) do + # need to replicate this w/i scope otherwise rspec may complain + executor.shutdown + expect(executor.wait_for_termination(pool_termination_timeout)).to eq true + end + + specify '#post does not create any new threads when the queue is at capacity' do + trigger = Concurrent::Event.new + initial = Thread.list.length + + executor = described_class.new( + min_threads: 1, + max_threads: 1, + idletime: idletime, + max_queue: 1, + fallback_policy: :caller_runs + ) + + # Post several tasks to the executor. Has to be a new thread, + # because it will start blocking once the queue fills up. + in_thread do + 5.times{ executor.post{ trigger.wait } } + end + + expect(Thread.list.length).to be < initial + 1 + 5 + + # Let the executor tasks complete. + trigger.set + + executor.shutdown + expect(executor.wait_for_termination(pool_termination_timeout)).to eq true + end + + specify '#<< executes the task on the current thread when the queue is at capacity' do + trigger = Concurrent::CountDownLatch.new(1) + latch = Concurrent::CountDownLatch.new(1) + executor.post{ trigger.wait } + main = Thread.current + executor.post do + expect(Thread.current).to eq main + latch.count_down + end + latch.wait(0.1) + trigger.count_down + end + + specify '#post executes the task on the current thread when the queue is at capacity' do + trigger = Concurrent::Event.new + latch = Concurrent::CountDownLatch.new(5) + executor.post{ trigger.wait } + 5.times{|i| executor.post{ latch.count_down } } + latch.wait(0.1) + trigger.set + end + + specify '#post executes the task on the current thread when the executor is shutting down' do + latch = Concurrent::CountDownLatch.new(1) + executor.shutdown + executor.post{ latch.count_down } + latch.wait(0.1) + end + + specify '#<< executes the task on the current thread when the executor is shutting down' do + latch = Concurrent::CountDownLatch.new(1) + executor.shutdown + executor << proc { latch.count_down } + latch.wait(0.1) + end + + specify '#post does not block other jobs running on the worker threads' do + log = Queue.new + + # Using a custom instance + executor = described_class.new( + min_threads: 1, + max_threads: 1, + max_queue: 1, + fallback_policy: :caller_runs) + + worker_unblocker = Concurrent::CountDownLatch.new(1) + executor_unblocker = Concurrent::CountDownLatch.new(1) + queue_done = Concurrent::CountDownLatch.new(1) + + # Block the worker thread + executor << proc { worker_unblocker.wait } + + # Fill the queue + executor << proc { log.push :queued; queue_done.count_down } + + # Block in a caller_runs job + caller_runs_thread = Thread.new { + executor << proc { executor_unblocker.wait; log.push :unblocked } + } + + # Wait until the caller_runs job is blocked + Thread.pass until caller_runs_thread.status == 'sleep' + + # Now unblock the worker thread + worker_unblocker.count_down + queue_done.wait + executor_unblocker.count_down + + # Tidy up + caller_runs_thread.join + + # We will see the queued jobs run before the caller_runs job unblocks + expect([log.pop, log.pop]).to eq [:queued, :unblocked] + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_shared.rb new file mode 100644 index 0000000000..a8ab3e2468 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/thread_pool_shared.rb @@ -0,0 +1,113 @@ +require_relative 'executor_service_shared' + +RSpec.shared_examples :thread_pool do + + after(:each) do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it_should_behave_like :executor_service + + let(:latch) { Concurrent::CountDownLatch.new } + + context '#auto_terminate?' do + + it 'returns true by default' do + expect(subject.auto_terminate?).to be true + end + + it 'returns true when :enable_at_exit_handler is true' do + if described_class.to_s =~ /FixedThreadPool$/ + subject = described_class.new(1, auto_terminate: true) + else + subject = described_class.new(auto_terminate: true) + end + expect(subject.auto_terminate?).to be true + + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + + it 'returns false when :enable_at_exit_handler is false' do + if described_class.to_s =~ /FixedThreadPool$/ + subject = described_class.new(1, auto_terminate: false) + else + subject = described_class.new(auto_terminate: false) + end + expect(subject.auto_terminate?).to be false + + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + end + end + + context '#length' do + + it 'returns zero on creation' do + expect(subject.length).to eq 0 + end + + it 'returns zero once shut down' do + 5.times{ subject.post{ sleep(0.1) } } + subject.post { latch.count_down } + latch.wait(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.length).to eq 0 + end + end + + context '#scheduled_task_count' do + + it 'returns zero on creation' do + expect(subject.scheduled_task_count).to eq 0 + end + + it 'returns the approximate number of tasks that have been post thus far' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + expect(subject.scheduled_task_count).to be > 0 + end + + it 'returns the approximate number of tasks that were post' do + 10.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(0.1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject.scheduled_task_count).to be > 0 + end + end + + context '#completed_task_count' do + + it 'returns zero on creation' do + expect(subject.completed_task_count).to eq 0 + end + + unless Concurrent.on_jruby? + + it 'returns the approximate number of tasks that have been completed thus far' do + 5.times{ subject.post{ raise StandardError } } + 5.times{ subject.post{ nil } } + subject.post { latch.count_down } + latch.wait(1) + expect(subject.completed_task_count).to be > 0 + end + end + end + + context '#shutdown' do + + it 'allows threads to exit normally' do + 10.times{ subject << proc{ nil } } + expect(subject.length).to be > 0 + sleep(0.1) + subject.shutdown + sleep(1) + expect(subject.length).to eq(0) + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/timer_set_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/timer_set_spec.rb new file mode 100644 index 0000000000..372b9af5b8 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/timer_set_spec.rb @@ -0,0 +1,396 @@ +require 'concurrent/executor/timer_set' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/atomic_boolean' +require 'timecop' + +module Concurrent + + RSpec.describe TimerSet do + + let(:executor){ Concurrent::ImmediateExecutor.new } + subject{ TimerSet.new(executor: executor) } + + after(:each){ subject.shutdown } + + context 'construction' do + it 'uses the executor given at construction' do + subject = TimerSet.new(executor: Concurrent.global_immediate_executor) + expect(subject.instance_variable_get(:@task_executor)).to eq Concurrent.global_immediate_executor + end + + it 'uses the global io executor be default' do + subject = TimerSet.new + expect(subject.instance_variable_get(:@task_executor)).to eq Concurrent.global_io_executor + end + end + + context '#post' do + + it 'raises an exception when given a task with a delay less than zero' do + expect { + subject.post(-10){ nil } + }.to raise_error(ArgumentError) + end + + it 'raises an exception when no block given' do + expect { + subject.post(10) + }.to raise_error(ArgumentError) + end + + it 'immediately posts a task when the delay is zero' do + timer = subject.instance_variable_get(:@timer_executor) + expect(timer).not_to receive(:post).with(any_args) + subject.post(0){ true } + end + end + + context 'execution' do + + it 'executes a given task when given an interval in seconds' do + latch = CountDownLatch.new(1) + subject.post(0.1){ latch.count_down } + expect(latch.wait(0.2)).to be_truthy + end + + it 'returns an IVar when posting a task' do + expect(subject.post(0.1) { nil }).to be_a Concurrent::IVar + end + + it 'executes a given task when given an interval in seconds, even if longer tasks have been scheduled' do + latch = CountDownLatch.new(1) + subject.post(0.5){ nil } + subject.post(0.1){ latch.count_down } + expect(latch.wait(0.2)).to be_truthy + end + + it 'passes all arguments to the task on execution' do + expected = AtomicReference.new + latch = CountDownLatch.new(1) + subject.post(0.1, 1, 2, 3) do |*args| + expected.value = args + latch.count_down + end + expect(latch.wait(0.2)).to be_truthy + expect(expected.value).to eq [1, 2, 3] + end + + it 'does not execute tasks early' do + latch = Concurrent::CountDownLatch.new(1) + start = Time.now.to_f + subject.post(0.2){ latch.count_down } + expect(latch.wait(1)).to be true + expect(Time.now.to_f - start).to be >= 0.19 + end + + it 'executes all tasks scheduled for the same time' do + latch = CountDownLatch.new(5) + 5.times{ subject.post(0.1){ latch.count_down } } + expect(latch.wait(1)).to be_truthy + end + + it 'executes tasks with different times in schedule order' do + latch = CountDownLatch.new(3) + expected = [] + 3.times{|i| subject.post(i/10){ expected << i; latch.count_down } } + latch.wait(1) + expect(expected).to eq [0, 1, 2] + end + + it 'executes tasks with different times in schedule time' do + tests = 3 + interval = 0.1 + latch = CountDownLatch.new(tests) + expected = Queue.new + start = Time.now + + (1..tests).each do |i| + subject.post(interval * i) { expected << Time.now - start; latch.count_down } + end + + expect(latch.wait((tests * interval) + 1)).to be true + + (1..tests).each do |i| + delta = expected.pop + expect(delta).to be_within(0.1).of((i * interval) + 0.05) + end + end + + it 'continues to execute new tasks even after the queue is emptied' do + 3.times do |i| + task = subject.post(0.1){ i } + expect(task.value).to eq i + end + end + end + + context 'resolution' do + + it 'sets the IVar value on success when delay is zero' do + job = subject.post(0){ 42 } + expect(job.value).to eq 42 + expect(job.reason).to be_nil + expect(job).to be_fulfilled + end + + it 'sets the IVar value on success when given a delay' do + job = subject.post(0.1){ 42 } + expect(job.value).to eq 42 + expect(job.reason).to be_nil + expect(job).to be_fulfilled + end + + it 'sets the IVar reason on failure when delay is zero' do + error = ArgumentError.new('expected error') + job = subject.post(0){ raise error } + expect(job.value).to be_nil + expect(job.reason).to eq error + expect(job).to be_rejected + end + + it 'sets the IVar reason on failure when given a delay' do + error = ArgumentError.new('expected error') + job = subject.post(0.1){ raise error } + expect(job.value).to be_nil + expect(job.reason).to eq error + expect(job).to be_rejected + end + end + + context 'task cancellation' do + + after(:each) do + Timecop.return + end + + it 'fails to cancel the task once processing has begun' do + start_latch = Concurrent::CountDownLatch.new + continue_latch = Concurrent::CountDownLatch.new + job = subject.post(0.1) do + start_latch.count_down + continue_latch.wait(2) + 42 + end + + start_latch.wait(2) + success = job.cancel + continue_latch.count_down + + expect(success).to be false + expect(job.value).to eq 42 + expect(job.reason).to be_nil + end + + it 'fails to cancel the task once processing is complete' do + job = subject.post(0.1){ 42 } + + job.wait(2) + success = job.cancel + + expect(success).to be false + expect(job.value).to eq 42 + expect(job.reason).to be_nil + end + + it 'cancels a pending task' do + actual = AtomicBoolean.new(false) + Timecop.freeze + job = subject.post(0.1){ actual.make_true } + success = job.cancel + Timecop.travel(1) + expect(success).to be true + expect(job.value(0)).to be_nil + expect(job.reason).to be_a CancelledOperationError + end + + it 'returns false when not running' do + task = subject.post(10){ nil } + subject.shutdown + expect(expect(subject.wait_for_termination(pool_termination_timeout)).to eq true).to eq true + expect(task.cancel).to be false + end + end + + context 'task rescheduling' do + + let(:queue) { subject.instance_variable_get(:@queue) } + + it 'raises an exception when given an invalid time' do + task = subject.post(10){ nil } + expect{ task.reschedule(-1) }.to raise_error(ArgumentError) + end + + it 'does not change the current schedule when given an invalid time' do + task = subject.post(10){ nil } + expected = task.schedule_time + begin + task.reschedule(-1) + rescue + end + expect(task.schedule_time).to eq expected + end + + it 'reschdules a pending and unpost task when given a valid time' do + initial_delay = 10 + rescheduled_delay = 20 + task = subject.post(initial_delay){ nil } + original_schedule = task.schedule_time + success = task.reschedule(rescheduled_delay) + expect(success).to be true + expect(task.initial_delay).to be_within(0.01).of(rescheduled_delay) + expect(task.schedule_time).to be > original_schedule + end + + it 'returns false once the task has been post to the executor' do + start_latch = Concurrent::CountDownLatch.new + continue_latch = Concurrent::CountDownLatch.new + + task = subject.post(0.1) do + start_latch.count_down + continue_latch.wait(2) + end + start_latch.wait(2) + + expected = task.schedule_time + success = task.reschedule(10) + continue_latch.count_down + expect(success).to be false + expect(task.schedule_time).to eq expected + end + + it 'returns false once the task is processing' do + start_latch = Concurrent::CountDownLatch.new + continue_latch = Concurrent::CountDownLatch.new + task = subject.post(0.1) do + start_latch.count_down + continue_latch.wait(2) + end + start_latch.wait(2) + + expected = task.schedule_time + success = task.reschedule(10) + continue_latch.count_down + expect(success).to be false + expect(task.schedule_time).to eq expected + end + + it 'returns false once the task has is complete' do + task = subject.post(0.1){ nil } + task.value(2) + expected = task.schedule_time + success = task.reschedule(10) + expect(success).to be false + expect(task.schedule_time).to eq expected + end + + it 'returns false when not running' do + task = subject.post(10){ nil } + subject.shutdown + expect(expect(subject.wait_for_termination(pool_termination_timeout)).to eq true).to eq true + expected = task.schedule_time + success = task.reschedule(10) + expect(success).to be false + expect(task.schedule_time).to be_within(0.01).of(expected) + end + end + + context 'task resetting' do + + it 'calls #reschedule with the original delay' do + initial_delay = 10 + task = subject.post(initial_delay){ nil } + expect(task).to receive(:ns_reschedule).with(initial_delay) + task.reset + end + end + + context 'termination' do + + it 'cancels all pending tasks on #shutdown' do + queue = subject.instance_variable_get(:@queue) + expect(queue).to receive(:clear).with(no_args).at_least(:once) + subject.shutdown + end + + it 'cancels all pending tasks on #kill' do + queue = subject.instance_variable_get(:@queue) + expect(queue).to receive(:clear).with(no_args).at_least(:once) + subject.kill + end + + it 'stops the monitor thread on #shutdown' do + timer_executor = subject.instance_variable_get(:@timer_executor) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(timer_executor).not_to be_running + end + + it 'kills the monitor thread on #kill' do + timer_executor = subject.instance_variable_get(:@timer_executor) + subject.kill + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(timer_executor).not_to be_running + end + + it 'rejects tasks once shutdown' do + queue = subject.instance_variable_get(:@queue) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + subject.post(1) { nil } + expect(queue).to be_empty + end + + it 'rejects tasks once killed' do + queue = subject.instance_variable_get(:@queue) + subject.kill + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + subject.post(1) { nil } + expect(queue).to be_empty + end + + specify '#wait_for_termination returns true if shutdown completes before timeout' do + latch = Concurrent::CountDownLatch.new(1) + subject.post(0){ latch.count_down } + latch.wait(1) + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to be_truthy + end + + specify '#wait_for_termination returns false on timeout' do + latch = Concurrent::CountDownLatch.new(1) + subject.post(0){ latch.count_down } + latch.wait(0.1) + # do not call shutdown -- force timeout + expect(subject.wait_for_termination(0.1)).to be_falsey + end + end + + context 'state' do + + it 'is running? when first created' do + expect(subject).to be_running + expect(subject).not_to be_shutdown + end + + it 'is running? after tasks have been post' do + subject.post(0.1){ nil } + expect(subject).to be_running + expect(subject).not_to be_shutdown + end + + it 'is shutdown? after shutdown completes' do + subject.shutdown + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject).not_to be_running + expect(subject).to be_shutdown + end + + it 'is shutdown? after being killed' do + subject.kill + expect(subject.wait_for_termination(pool_termination_timeout)).to eq true + expect(subject).not_to be_running + expect(subject).to be_shutdown + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/wrapping_executor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/wrapping_executor_spec.rb new file mode 100644 index 0000000000..ec1a8d85a9 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/executor/wrapping_executor_spec.rb @@ -0,0 +1,51 @@ +require 'concurrent/executor/wrapping_executor' +require 'concurrent/configuration' + +module Concurrent + RSpec.describe WrappingExecutor do + + let(:wrapping_executor) { WrappingExecutor.new(executor, &wrapper) } + let(:executor) { Concurrent.global_fast_executor } + let(:wrapper) { nil } + let(:args) { { foo: 'bar', baz: 42 } } + let(:task) { -> (*args) { return nil } } + + subject { wrapping_executor } + + it { is_expected.to be_kind_of(WrappingExecutor) } + it { is_expected.to respond_to(:post) } + it { is_expected.to respond_to(:can_overflow?) } + it { is_expected.to respond_to(:serialized?) } + + describe '#post' do + context 'with passthrough wrapper' do + let(:wrapper) { -> (*args, &task) { return *args, task } } + + it { + expect(executor).to receive(:post).with(args) { |&block| expect(block).to be(task) } + wrapping_executor.post(args, &task) + } + end + + context 'with wrapper modifying args' do + let(:wrapper) { -> (*args, &task) { return *args, { xyz: 'abc' }, task } } + + it { + expect(executor).to receive(:post).with(args, { xyz: 'abc' }) { |&block| expect(block).to be(task) } + wrapping_executor.post(args, &task) + } + end + + context 'with wrapper modifying task' do + let(:wrapper) { -> (*args, &task) { return *args, another_task } } + let(:another_task) { -> (*args) { return true } } + + it { + expect(executor).to receive(:post).with(args) { |&block| expect(block).to be(another_task) } + wrapping_executor.post(args, &task) + } + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/future_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/future_spec.rb new file mode 100644 index 0000000000..69e31b2ef4 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/future_spec.rb @@ -0,0 +1,417 @@ +require 'concurrent/future' +require 'concurrent/executor/simple_executor_service' +require 'concurrent/executor/single_thread_executor' +require 'concurrent/atomic/count_down_latch' + +require_relative 'ivar_shared' +require_relative 'thread_arguments_shared' + +module Concurrent + + RSpec.describe Future do + + let!(:value) { 10 } + let(:executor) { SimpleExecutorService.new } + + subject do + Future.new(executor: executor){ + value + }.execute.tap{ sleep(0.1) } + end + + let!(:fulfilled_value) { 10 } + let!(:rejected_reason) { StandardError.new('mojo jojo') } + + let(:pending_subject) do + executor = Concurrent::SingleThreadExecutor.new + executor.post{ sleep(5) } + Future.execute(executor: executor){ fulfilled_value } + end + + let(:fulfilled_subject) do + Future.new(executor: :immediate){ fulfilled_value }.execute + end + + let(:rejected_subject) do + Future.new(executor: :immediate){ raise rejected_reason }.execute + end + + it_should_behave_like :ivar do + subject { Future.new(executor: :immediate){ value } } + + def dereferenceable_subject(value, opts = {}) + opts = opts.merge(executor: executor) + Future.new(opts){ value }.execute.tap{ sleep(0.1) } + end + + def dereferenceable_observable(opts = {}) + opts = opts.merge(executor: executor) + Future.new(opts){ 'value' } + end + + def execute_dereferenceable(subject) + subject.execute + sleep(0.1) + end + + def trigger_observable(observable) + observable.execute + sleep(0.1) + end + end + + it_should_behave_like :thread_arguments do + + def get_ivar_from_no_args + Concurrent::Future.execute{|*args| args } + end + + def get_ivar_from_args(opts) + Concurrent::Future.execute(opts){|*args| args } + end + end + + context '#initialize' do + + let(:executor) { ImmediateExecutor.new } + + it 'sets the state to :unscheduled' do + expect(Future.new(executor: executor){ nil }).to be_unscheduled + end + + it 'raises an exception when no block given' do + expect { + Future.new.execute + }.to raise_error(ArgumentError) + end + + it 'uses the executor given with the :executor option' do + expect(executor).to receive(:post) + Future.execute(executor: executor){ nil } + end + + it 'uses the global io executor by default' do + allow(Concurrent).to receive(:global_io_executor).and_return(executor) + expect(executor).to receive(:post).and_call_original + Future.execute{ nil } + end + end + + context 'instance #execute' do + + it 'does nothing unless the state is :unscheduled' do + executor = ImmediateExecutor.new + expect(executor).not_to receive(:post).with(any_args) + future = Future.new(executor: executor){ nil } + future.instance_variable_set(:@state, :pending) + future.execute + future.instance_variable_set(:@state, :rejected) + future.execute + future.instance_variable_set(:@state, :fulfilled) + future.execute + end + + it 'posts the block given on construction' do + expect(executor).to receive(:post).with(any_args) + future = Future.new(executor: executor){ nil } + future.execute + end + + it 'sets the state to :pending' do + latch = Concurrent::CountDownLatch.new + executor = Concurrent::SingleThreadExecutor.new + executor.post{ latch.wait(2) } + + future = Future.new(executor: executor){ 42 } + future.execute + expect(future).to be_pending + latch.count_down + end + + it 'returns self' do + future = Future.new(executor: executor){ nil } + expect(future.execute).to be future + end + end + + context 'class #execute' do + + let(:executor) { ImmediateExecutor.new } + + it 'creates a new Future' do + future = Future.execute(executor: executor){ nil } + expect(future).to be_a(Future) + end + + it 'passes the block to the new Future' do + @expected = false + Future.execute(executor: executor){ @expected = true } + expect(@expected).to be_truthy + end + + it 'calls #execute on the new Future' do + future = double('future') + allow(Future).to receive(:new).with(any_args).and_return(future) + expect(future).to receive(:execute).with(no_args) + Future.execute{ nil } + end + end + + context 'fulfillment' do + + let(:executor) { ImmediateExecutor.new } + + it 'sets the state to :processing while the task is executing' do + start_latch = Concurrent::CountDownLatch.new + continue_latch = Concurrent::CountDownLatch.new + executor = Concurrent::SingleThreadExecutor.new + + future = Future.execute(executor: executor) do + start_latch.count_down + continue_latch.wait(2) + 42 + end + + start_latch.wait(2) + state = future.state + continue_latch.count_down + future.value + + expect(state).to eq :processing + end + + it 'passes all arguments to handler' do + expected = false + Future.new(executor: executor){ expected = true }.execute + expect(expected).to be_truthy + end + + it 'sets the value to the result of the handler' do + future = Future.new(executor: executor){ 42 }.execute + expect(future.value).to eq 42 + end + + it 'sets the state to :fulfilled when the block completes' do + future = Future.new(executor: executor){ 42 }.execute + expect(future).to be_fulfilled + end + + it 'sets the value to nil when the handler raises an exception' do + future = Future.new(executor: executor){ raise StandardError }.execute + expect(future.value).to be_nil + end + + it 'sets the value to nil when the handler raises Exception' do + future = Future.new(executor: executor){ raise Exception }.execute + expect(future.value).to be_nil + end + + it 'sets the reason to the Exception instance when the handler raises Exception' do + future = Future.new(executor: executor){ raise Exception }.execute + expect(future.reason).to be_a(Exception) + end + + it 'sets the state to :rejected when the handler raises an exception' do + future = Future.new(executor: executor){ raise StandardError }.execute + expect(future).to be_rejected + end + + context 'aliases' do + + it 'aliases #realized? for #fulfilled?' do + expect(subject).to be_realized + end + + it 'aliases #deref for #value' do + expect(subject.deref).to eq value + end + end + end + + context 'cancellation' do + + context '#cancel' do + + it 'fails to cancel the task once processing has begun' do + start_latch = Concurrent::CountDownLatch.new + continue_latch = Concurrent::CountDownLatch.new + f = Future.execute do + start_latch.count_down + continue_latch.wait(2) + 42 + end + + start_latch.wait(2) + cancelled = f.cancel + continue_latch.count_down + + expect(cancelled).to be false + expect(f.value).to eq 42 + expect(f).to be_fulfilled + end + + it 'fails to cancel the task once processing is complete' do + f = Future.execute{ 42 } + f.wait + cancelled = f.cancel + + expect(cancelled).to be false + expect(f.value).to eq 42 + expect(f).to be_fulfilled + end + + it 'cancels a pending task' do + executor = Concurrent::SingleThreadExecutor.new + latch = Concurrent::CountDownLatch.new + executor.post{ latch.wait(2) } + + f = Future.execute(executor: executor){ 42 } + cancelled = f.cancel + latch.count_down + + expect(cancelled).to be true + expect(f.value).to be_nil + expect(f).to be_rejected + expect(f.reason).to be_a Concurrent::CancelledOperationError + end + end + + context '#wait_or_cancel' do + + it 'returns true if the operation completes before timeout' do + f = Future.execute{ 42 } + success = f.wait_or_cancel(1) + + expect(success).to be true + expect(f.value).to eq 42 + expect(f).to be_fulfilled + end + + it 'cancels the task on timeout' do + latch = Concurrent::CountDownLatch.new + executor = Concurrent::SingleThreadExecutor.new + executor.post{ latch.wait(2) } + + f = Future.execute(executor: executor){ 42 } + success = f.wait_or_cancel(0.1) + latch.count_down + + expect(success).to be false + expect(f.value).to be_nil + expect(f).to be_rejected + expect(f.reason).to be_a Concurrent::CancelledOperationError + end + end + end + + context 'observation' do + + let(:executor) { ImmediateExecutor.new } + + let(:clazz) do + Class.new do + attr_reader :value + attr_reader :reason + attr_reader :count + define_method(:update) do |time, value, reason| + @count ||= 0 + @count = @count.to_i + 1 + @value = value + @reason = reason + end + end + end + + let(:observer) { clazz.new } + + it 'notifies all observers on fulfillment' do + future = Future.new(executor: executor){ 42 } + future.add_observer(observer) + + future.execute + + expect(observer.value).to eq(42) + expect(observer.reason).to be_nil + end + + it 'notifies all observers on rejection' do + future = Future.new(executor: executor){ raise StandardError } + future.add_observer(observer) + + future.execute + + expect(observer.value).to be_nil + expect(observer.reason).to be_a(StandardError) + end + + it 'notifies an observer added after fulfillment' do + future = Future.new(executor: executor){ 42 }.execute + future.add_observer(observer) + expect(observer.value).to eq(42) + end + + it 'notifies an observer added after rejection' do + future = Future.new(executor: executor){ raise StandardError }.execute + future.add_observer(observer) + expect(observer.reason).to be_a(StandardError) + end + + it 'does not notify existing observers when a new observer added after fulfillment' do + future = Future.new(executor: executor){ 42 }.execute + future.add_observer(observer) + + expect(observer.count).to eq(1) + + o2 = clazz.new + future.add_observer(o2) + + expect(observer.count).to eq(1) + expect(o2.value).to eq(42) + end + + it 'does not notify existing observers when a new observer added after rejection' do + future = Future.new(executor: executor){ raise StandardError }.execute + future.add_observer(observer) + + expect(observer.count).to eq(1) + + o2 = clazz.new + future.add_observer(o2) + + expect(observer.count).to eq(1) + expect(o2.reason).to be_a(StandardError) + end + + context 'deadlock avoidance' do + + def reentrant_observer(future) + obs = ::Object.new + obs.define_singleton_method(:update) do |time, value, reason| + @value = future.value + end + obs.define_singleton_method(:value) { @value } + obs + end + + it 'should notify observers outside mutex lock' do + future = Future.new(executor: executor){ 42 } + obs = reentrant_observer(future) + + future.add_observer(obs) + future.execute + + expect(obs.value).to eq 42 + end + + it 'should notify a new observer added after fulfillment outside lock' do + future = Future.new(executor: executor){ 42 }.execute + obs = reentrant_observer(future) + + future.add_observer(obs) + + expect(obs.value).to eq 42 + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/hash_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/hash_spec.rb new file mode 100644 index 0000000000..82a5700589 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/hash_spec.rb @@ -0,0 +1,106 @@ +require 'concurrent/hash' + +module Concurrent + RSpec.describe Hash do + let!(:hsh) { described_class.new } + + describe '.[]' do + describe 'when initializing with no arguments' do + it do + expect(described_class[]).to be_empty + end + end + + describe 'when initializing with an even number of arguments' do + it 'creates a hash using the odd position arguments as keys and even position arguments as values' do + expect(described_class[:hello, 'hello', :world, 'world']).to eq(hello: 'hello', world: 'world') + end + end + + describe 'when initializing with an array of pairs' do + let(:array_of_pairs) { [[:hello, 'hello'], [:world, 'world']] } + + it 'creates a hash using each pair as a (key, value) pair' do + expect(described_class[array_of_pairs]).to eq(hello: 'hello', world: 'world') + end + end + + describe 'when initializing with another hash as an argument' do + let(:other_hash) { {hello: 'hello', world: 'world'} } + let(:fake_other_hash) { double('Fake hash', to_hash: other_hash) } + + it 'creates a new hash' do + expect(described_class[other_hash]).to_not be other_hash + end + + it 'creates a hash with the same contents as the other hash' do + expect(described_class[other_hash]).to eq(hello: 'hello', world: 'world') + end + + it 'creates a hash with the results of calling #to_hash on the other array' do + expect(described_class[fake_other_hash]).to eq(hello: 'hello', world: 'world') + end + end + end + + describe '.new' do + describe 'when initializing with no arguments' do + it do + expect(described_class.new).to be_empty + end + end + + describe 'when initialized with a default object' do + let(:default_object) { :ruby } + + it 'uses the default object for non-existing keys' do + hash = described_class.new(default_object) + + expect(hash[:hello]).to be :ruby + expect(hash[:world]).to be :ruby + end + end + + describe 'when initialized with a block' do + it 'calls the block for non-existing keys' do + block_calls = [] + + hash = described_class.new do |hash_instance, key| + block_calls << [hash_instance, key] + end + + hash[:hello] + hash[:world] + + expect(block_calls).to eq [[hash, :hello], [hash, :world]] + end + + it 'returns the results of calling the block for non-existing key' do + block_results = ['hello', 'world'] + + hash = described_class.new do + block_results.shift + end + + expect(hash[:hello]).to eq 'hello' + expect(hash[:world]).to eq 'world' + end + end + end + + context 'concurrency' do + it do + (1..Concurrent::ThreadSafe::Test::THREADS).map do |i| + in_thread do + 1000.times do |j| + hsh[i * 1000 + j] = i + expect(hsh[i * 1000 + j]).to eq(i) + expect(hsh.delete(i * 1000 + j)).to eq(i) + end + end + end.map(&:join) + expect(hsh).to be_empty + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/immutable_struct_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/immutable_struct_spec.rb new file mode 100644 index 0000000000..25839c544e --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/immutable_struct_spec.rb @@ -0,0 +1,9 @@ +require_relative 'struct_shared' +require 'concurrent/immutable_struct' + +module Concurrent + RSpec.describe ImmutableStruct do + it_should_behave_like :struct + it_should_behave_like :mergeable_struct + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/ivar_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/ivar_shared.rb new file mode 100644 index 0000000000..53f56d6ef0 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/ivar_shared.rb @@ -0,0 +1,158 @@ +require_relative 'concern/dereferenceable_shared' +require_relative 'concern/obligation_shared' +require_relative 'concern/observable_shared' + +RSpec.shared_examples :ivar do + + it_should_behave_like :obligation + it_should_behave_like :dereferenceable + it_should_behave_like :observable + + context 'initialization' do + + it 'sets the state to incomplete' do + expect(subject).to be_incomplete + end + end + + context '#set' do + + it 'sets the state to be fulfilled' do + subject.set(14) + expect(subject).to be_fulfilled + end + + it 'sets the value' do + subject.set(14) + expect(subject.value).to eq 14 + end + + it 'raises an exception if set more than once' do + subject.set(14) + expect {subject.set(2)}.to raise_error(Concurrent::MultipleAssignmentError) + expect(subject.value).to eq 14 + end + + it 'returns self' do + expect(subject.set(42)).to eq subject + end + it 'fulfils when given a block which executes successfully' do + subject.set{ 42 } + expect(subject.value).to eq 42 + end + + it 'rejects when given a block which raises an exception' do + expected = ArgumentError.new + subject.set{ raise expected } + expect(subject.reason).to eq expected + end + + it 'raises an exception when given a value and a block' do + expect { + subject.set(42){ :guide } + }.to raise_error(ArgumentError) + end + + it 'raises an exception when given neither a value nor a block' do + expect { + subject.set + }.to raise_error(ArgumentError) + end + end + + context '#fail' do + + it 'sets the state to be rejected' do + subject.fail + expect(subject).to be_rejected + end + + it 'sets the value to be nil' do + subject.fail + expect(subject.value).to be_nil + end + + it 'sets the reason to the given exception' do + expected = ArgumentError.new + subject.fail(expected) + expect(subject.reason).to eq expected + end + + it 'raises an exception if set more than once' do + subject.fail + expect {subject.fail}.to raise_error(Concurrent::MultipleAssignmentError) + expect(subject.value).to be_nil + end + + it 'defaults the reason to a StandardError' do + subject.fail + expect(subject.reason).to be_a StandardError + end + + it 'returns self' do + expect(subject.fail).to eq subject + end + end + + describe '#try_set' do + + context 'when unset' do + + it 'assigns the value' do + subject.try_set(32) + expect(subject.value).to eq 32 + end + + it 'assigns the block result' do + subject.try_set{ 32 } + expect(subject.value).to eq 32 + end + + it 'returns true' do + expect(subject.try_set('hi')).to eq true + end + end + + context 'when fulfilled' do + + before(:each) { subject.set(27) } + + it 'does not assign the value' do + subject.try_set(88) + expect(subject.value).to eq 27 + end + + it 'does not assign the block result' do + subject.try_set{ 88 } + expect(subject.value).to eq 27 + end + + it 'returns false' do + expect(subject.try_set('hello')).to eq false + end + end + + context 'when rejected' do + + before(:each) { subject.fail } + + it 'does not assign the value' do + subject.try_set(88) + expect(subject).to be_rejected + end + + it 'does not assign the block result' do + subject.try_set{ 88 } + expect(subject).to be_rejected + end + + it 'has a nil value' do + expect(subject.value).to be_nil + end + + it 'returns false' do + expect(subject.try_set('hello')).to eq false + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/ivar_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/ivar_spec.rb new file mode 100644 index 0000000000..66d43d70a5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/ivar_spec.rb @@ -0,0 +1,144 @@ +require_relative 'ivar_shared' +require 'concurrent/ivar' + +module Concurrent + + RSpec.describe IVar do + + let!(:value) { 10 } + + let!(:fulfilled_value) { 10 } + let(:rejected_reason) { StandardError.new('Boom!') } + + subject { IVar.new(value) } + + let(:pending_subject) do + ivar = IVar.new + in_thread do + sleep(0.1) + ivar.set(fulfilled_value) + end + ivar + end + + let(:fulfilled_subject) do + IVar.new.set(fulfilled_value) + end + + let(:rejected_subject) do + IVar.new.fail(rejected_reason) + end + + it_should_behave_like :ivar do + subject{ IVar.new } + + def dereferenceable_subject(value, opts = {}) + IVar.new(value, opts) + end + + def dereferenceable_observable(opts = {}) + IVar.new(NULL, opts) + end + + def execute_dereferenceable(subject) + subject.set('value') + end + + def trigger_observable(observable) + observable.set('value') + end + end + + context '#initialize' do + + it 'does not have to set an initial value' do + i = IVar.new + expect(i).to be_incomplete + end + + it 'does not set an initial value if you pass NULL' do + i = IVar.new(NULL) + expect(i).to be_incomplete + end + + it 'can set an initial value' do + i = IVar.new(14) + expect(i).to be_complete + expect(i.value).to eq 14 + end + + it 'can set an initial value with a block' do + i = IVar.new{ 42 } + expect(i).to be_complete + expect(i.value).to eq 42 + end + + it 'raises an exception if given both a value and a block' do + expect { + IVar.new(42){ 42 } + }.to raise_error(ArgumentError) + end + end + + context 'observation' do + + let(:clazz) do + Class.new do + attr_reader :value + attr_reader :reason + attr_reader :count + define_method(:update) do |time, value, reason| + @count ||= 0 + @count = @count.to_i + 1 + @value = value + @reason = reason + end + end + end + + let(:observer) { clazz.new } + + it 'notifies all observers on #set' do + i = IVar.new + i.add_observer(observer) + + i.set(42) + + expect(observer.value).to eq(42) + expect(observer.reason).to be_nil + end + + context 'deadlock avoidance' do + + def reentrant_observer(i) + obs = ::Object.new + obs.define_singleton_method(:update) do |time, value, reason| + @value = i.value + end + obs.define_singleton_method(:value) { @value } + obs + end + + it 'should notify observers outside mutex lock' do + i = IVar.new + obs = reentrant_observer(i) + + i.add_observer(obs) + i.set(42) + + expect(obs.value).to eq 42 + end + + it 'should notify a new observer added after fulfillment outside lock' do + i = IVar.new + i.set(42) + obs = reentrant_observer(i) + + i.add_observer(obs) + + expect(obs.value).to eq 42 + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/lazy_register_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/lazy_register_spec.rb new file mode 100644 index 0000000000..9ee171c03b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/lazy_register_spec.rb @@ -0,0 +1,9 @@ +require 'concurrent/lazy_register' + +module Concurrent + RSpec.describe LazyRegister do + + pending + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/map_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/map_spec.rb new file mode 100644 index 0000000000..d42f886076 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/map_spec.rb @@ -0,0 +1,938 @@ +require_relative 'collection_each_shared' +require 'concurrent/map' +require 'concurrent/atomic/count_down_latch' + +Thread.abort_on_exception = true + +module Concurrent + RSpec.describe Map do + before(:each) do + @cache = described_class.new + end + + it 'default_proc is called with the Concurrent::Map and the key' do + map = Concurrent::Map.new do |h, k| + [h, k] + end + expect(map[:foo][0]).to be(map) + expect(map[:foo][1]).to eq(:foo) + end + + it 'default_proc is called with the Concurrent::Map and the key after #dup' do + map = Concurrent::Map.new do |h, k| + [h, k] + end + copy = map.dup + expect(copy[:foo][0]).to be(copy) + expect(copy[:foo][1]).to eq(:foo) + end + + it 'concurrency' do + (1..Concurrent::ThreadSafe::Test::THREADS).map do |i| + in_thread do + 1000.times do |j| + key = i * 1000 + j + @cache[key] = i + @cache[key] + @cache.delete(key) + end + end + end.map(&:join) + end + + it 'retrieval' do + expect_size_change(1) do + expect(nil).to eq @cache[:a] + expect(nil).to eq @cache.get(:a) + @cache[:a] = 1 + expect(1).to eq @cache[:a] + expect(1).to eq @cache.get(:a) + end + end + + it '#put_if_absent' do + with_or_without_default_proc do + expect_size_change(1) do + expect(nil).to eq @cache.put_if_absent(:a, 1) + expect(1).to eq @cache.put_if_absent(:a, 1) + expect(1).to eq @cache.put_if_absent(:a, 2) + expect(1).to eq @cache[:a] + end + end + end + + describe '#compute_if_absent' do + it 'works in default_proc' do + map = Concurrent::Map.new do |map, key| + map.compute_if_absent(key) { key * 2 } + end + expect(map[3]).to eq 6 + expect(map.keys).to eq [3] + end + + it 'common' do + with_or_without_default_proc do + expect_size_change(3) do + expect(1).to eq @cache.compute_if_absent(:a) { 1 } + expect(1).to eq @cache.compute_if_absent(:a) { 2 } + expect(1).to eq @cache[:a] + + @cache[:b] = nil + expect(nil).to eq @cache.compute_if_absent(:b) { 1 } + expect(nil).to eq @cache.compute_if_absent(:c) {} + expect(nil).to eq @cache[:c] + expect(true).to eq @cache.key?(:c) + end + end + end + + it 'with return' do + with_or_without_default_proc do + expect_handles_return_lambda(:compute_if_absent, :a) + end + end + + it 'exception' do + with_or_without_default_proc do + expect_handles_exception(:compute_if_absent, :a) + end + end + + it 'atomicity' do + late_compute_threads_count = 10 + late_put_if_absent_threads_count = 10 + getter_threads_count = 5 + compute_started = Concurrent::CountDownLatch.new(1) + compute_proceed = Concurrent::CountDownLatch.new( + late_compute_threads_count + + late_put_if_absent_threads_count + + getter_threads_count + ) + block_until_compute_started = lambda do |name| + # what does it mean? + if (v = @cache[:a]) != nil + expect(nil).to v + end + compute_proceed.count_down + compute_started.wait + end + + expect_size_change 1 do + late_compute_threads = ::Array.new(late_compute_threads_count) do + in_thread do + block_until_compute_started.call('compute_if_absent') + expect(1).to eq @cache.compute_if_absent(:a) { fail } + end + end + + late_put_if_absent_threads = ::Array.new(late_put_if_absent_threads_count) do + in_thread do + block_until_compute_started.call('put_if_absent') + expect(1).to eq @cache.put_if_absent(:a, 2) + end + end + + getter_threads = ::Array.new(getter_threads_count) do + in_thread do + block_until_compute_started.call('getter') + repeat_until_success { expect(1).to eq @cache[:a] } + end + end + + in_thread do + @cache.compute_if_absent(:a) do + compute_started.count_down + compute_proceed.wait + sleep(0.2) + 1 + end + end.join + (late_compute_threads + + late_put_if_absent_threads + + getter_threads).each(&:join) + end + end + end + + describe '#compute_if_present' do + it 'common' do + with_or_without_default_proc do + expect_no_size_change do + expect(nil).to eq @cache.compute_if_present(:a) {} + expect(nil).to eq @cache.compute_if_present(:a) { 1 } + expect(nil).to eq @cache.compute_if_present(:a) { fail } + expect(false).to eq @cache.key?(:a) + end + + @cache[:a] = 1 + expect_no_size_change do + expect(1).to eq @cache.compute_if_present(:a) { 1 } + expect(1).to eq @cache[:a] + expect(2).to eq @cache.compute_if_present(:a) { 2 } + expect(2).to eq @cache[:a] + expect(false).to eq @cache.compute_if_present(:a) { false } + expect(false).to eq @cache[:a] + + @cache[:a] = 1 + yielded = false + @cache.compute_if_present(:a) do |old_value| + yielded = true + expect(1).to eq old_value + 2 + end + expect(true).to eq yielded + end + + expect_size_change(-1) do + expect(nil).to eq @cache.compute_if_present(:a) {} + expect(false).to eq @cache.key?(:a) + expect(nil).to eq @cache.compute_if_present(:a) { 1 } + expect(false).to eq @cache.key?(:a) + end + end + end + + it 'with return' do + with_or_without_default_proc do + @cache[:a] = 1 + expect_handles_return_lambda(:compute_if_present, :a) + end + end + + it 'exception' do + with_or_without_default_proc do + @cache[:a] = 1 + expect_handles_exception(:compute_if_present, :a) + end + end + end + + describe '#compute' do + it 'common' do + with_or_without_default_proc do + expect_no_size_change do + expect_compute(:a, nil, nil) {} + end + + expect_size_change(1) do + expect_compute(:a, nil, 1) { 1 } + expect_compute(:a, 1, 2) { 2 } + expect_compute(:a, 2, false) { false } + expect(false).to eq @cache[:a] + end + + expect_size_change(-1) do + expect_compute(:a, false, nil) {} + end + end + end + + it 'with return' do + with_or_without_default_proc do + expect_handles_return_lambda(:compute, :a) + @cache[:a] = 1 + expect_handles_return_lambda(:compute, :a) + end + end + + it 'exception' do + with_or_without_default_proc do + expect_handles_exception(:compute, :a) + @cache[:a] = 2 + expect_handles_exception(:compute, :a) + end + end + end + + describe '#merge_pair' do + it 'common' do + with_or_without_default_proc do + expect_size_change(1) do + expect(nil).to eq @cache.merge_pair(:a, nil) { fail } + expect(true).to eq @cache.key?(:a) + expect(nil).to eq @cache[:a] + end + + expect_no_size_change do + expect_merge_pair(:a, nil, nil, false) { false } + expect_merge_pair(:a, nil, false, 1) { 1 } + expect_merge_pair(:a, nil, 1, 2) { 2 } + end + + expect_size_change(-1) do + expect_merge_pair(:a, nil, 2, nil) {} + expect(false).to eq @cache.key?(:a) + end + end + end + + it 'with return' do + with_or_without_default_proc do + @cache[:a] = 1 + expect_handles_return_lambda(:merge_pair, :a, 2) + end + end + + it 'exception' do + with_or_without_default_proc do + @cache[:a] = 1 + expect_handles_exception(:merge_pair, :a, 2) + end + end + end + + it 'updates dont block reads' do + if defined?(Concurrent::Collection::SynchronizedMapBackend) && described_class <= Concurrent::Collection::SynchronizedMapBackend + skip("Test does not apply to #{Concurrent::Collection::SynchronizedMapBackend}") + end + + getters_count = 20 + key_klass = Concurrent::ThreadSafe::Test::HashCollisionKey + keys = [key_klass.new(1, 100), + key_klass.new(2, 100), + key_klass.new(3, 100)] # hash colliding keys + inserted_keys = [] + + keys.each do |key, i| + compute_started = Concurrent::CountDownLatch.new(1) + compute_finished = Concurrent::CountDownLatch.new(1) + getters_started = Concurrent::CountDownLatch.new(getters_count) + getters_finished = Concurrent::CountDownLatch.new(getters_count) + + computer_thread = in_thread do + getters_started.wait + @cache.compute_if_absent(key) do + compute_started.count_down + getters_finished.wait + 1 + end + compute_finished.count_down + end + + getter_threads = getters_count.times.map do + in_thread do + getters_started.count_down + inserted_keys.each do |inserted_key| + expect(@cache.key?(inserted_key)).to eq true + expect(@cache[inserted_key]).to eq 1 + end + expect(false).to eq @cache.key?(key) + + compute_started.wait + + inserted_keys.each do |inserted_key| + expect(@cache.key?(inserted_key)).to eq true + expect(@cache[inserted_key]).to eq 1 + end + expect(@cache.key?(key)).to eq false + expect(@cache[key]).to eq nil + getters_finished.count_down + + compute_finished.wait + + expect(@cache.key?(key)).to eq true + expect(@cache[key]).to eq 1 + end + end + + join_with getter_threads + [computer_thread] + inserted_keys << key + end + end + + specify 'collision resistance' do + expect_collision_resistance( + (0..1000).map { |i| Concurrent::ThreadSafe::Test::HashCollisionKey(i, 1) } + ) + end + + specify 'collision resistance with arrays' do + special_array_class = Class.new(::Array) do + def key # assert_collision_resistance expects to be able to call .key to get the "real" key + first.key + end + end + # Test collision resistance with a keys that say they responds_to <=>, but then raise exceptions + # when actually called (ie: an Array filled with non-comparable keys). + # See https://github.com/headius/thread_safe/issues/19 for more info. + expect_collision_resistance( + (0..100).map do |i| + special_array_class.new( + [Concurrent::ThreadSafe::Test::HashCollisionKeyNonComparable.new(i, 1)] + ) + end + ) + end + + it '#replace_pair' do + with_or_without_default_proc do + expect_no_size_change do + expect(false).to eq @cache.replace_pair(:a, 1, 2) + expect(false).to eq @cache.replace_pair(:a, nil, nil) + expect(false).to eq @cache.key?(:a) + end + end + + @cache[:a] = 1 + expect_no_size_change do + expect(true).to eq @cache.replace_pair(:a, 1, 2) + expect(false).to eq @cache.replace_pair(:a, 1, 2) + expect(2).to eq @cache[:a] + expect(true).to eq @cache.replace_pair(:a, 2, 2) + expect(2).to eq @cache[:a] + expect(true).to eq @cache.replace_pair(:a, 2, nil) + expect(false).to eq @cache.replace_pair(:a, 2, nil) + expect(nil).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + expect(true).to eq @cache.replace_pair(:a, nil, nil) + expect(true).to eq @cache.key?(:a) + expect(true).to eq @cache.replace_pair(:a, nil, 1) + expect(1).to eq @cache[:a] + end + end + + it '#replace_if_exists' do + with_or_without_default_proc do + expect_no_size_change do + expect(nil).to eq @cache.replace_if_exists(:a, 1) + expect(false).to eq @cache.key?(:a) + end + + @cache[:a] = 1 + expect_no_size_change do + expect(1).to eq @cache.replace_if_exists(:a, 2) + expect(2).to eq @cache[:a] + expect(2).to eq @cache.replace_if_exists(:a, nil) + expect(nil).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + expect(nil).to eq @cache.replace_if_exists(:a, 1) + expect(1).to eq @cache[:a] + end + end + end + + it '#get_and_set' do + with_or_without_default_proc do + expect(nil).to eq @cache.get_and_set(:a, 1) + expect(true).to eq @cache.key?(:a) + expect(1).to eq @cache[:a] + expect(1).to eq @cache.get_and_set(:a, 2) + expect(2).to eq @cache.get_and_set(:a, nil) + expect(nil).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + expect(nil).to eq @cache.get_and_set(:a, 1) + expect(1).to eq @cache[:a] + end + end + + it '#key' do + with_or_without_default_proc do + expect(nil).to eq @cache.key(1) + @cache[:a] = 1 + expect(:a).to eq @cache.key(1) + expect(nil).to eq @cache.key(0) + end + end + + it '#key?' do + with_or_without_default_proc do + expect(false).to eq @cache.key?(:a) + @cache[:a] = 1 + expect(true).to eq @cache.key?(:a) + end + end + + it '#value?' do + with_or_without_default_proc do + expect(false).to eq @cache.value?(1) + @cache[:a] = 1 + expect(true).to eq @cache.value?(1) + end + end + + it '#delete' do + with_or_without_default_proc do |default_proc_set| + expect_no_size_change do + expect(nil).to eq @cache.delete(:a) + end + @cache[:a] = 1 + expect_size_change(-1) do + expect(1).to eq @cache.delete(:a) + end + expect_no_size_change do + expect(nil).to eq @cache[:a] unless default_proc_set + + expect(false).to eq @cache.key?(:a) + expect(nil).to eq @cache.delete(:a) + end + end + end + + it '#delete_pair' do + with_or_without_default_proc do + expect_no_size_change do + expect(false).to eq @cache.delete_pair(:a, 2) + expect(false).to eq @cache.delete_pair(:a, nil) + end + @cache[:a] = 1 + expect_no_size_change do + expect(false).to eq @cache.delete_pair(:a, 2) + end + expect_size_change(-1) do + expect(1).to eq @cache[:a] + expect(true).to eq @cache.delete_pair(:a, 1) + expect(false).to eq @cache.delete_pair(:a, 1) + expect(false).to eq @cache.key?(:a) + end + end + end + + specify 'default proc' do + @cache = cache_with_default_proc(1) + expect_no_size_change do + expect(false).to eq @cache.key?(:a) + end + expect_size_change(1) do + expect(1).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + end + end + + specify 'falsy default proc' do + @cache = cache_with_default_proc(nil) + expect_no_size_change do + expect(false).to eq @cache.key?(:a) + end + expect_size_change(1) do + expect(nil).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + end + end + + describe '#fetch' do + it 'common' do + with_or_without_default_proc do |default_proc_set| + expect_no_size_change do + expect(1).to eq @cache.fetch(:a, 1) + expect(1).to eq @cache.fetch(:a) { 1 } + expect(false).to eq @cache.key?(:a) + + expect(nil).to eq @cache[:a] unless default_proc_set + end + + @cache[:a] = 1 + expect_no_size_change do + expect(1).to eq @cache.fetch(:a) { fail } + end + + expect { @cache.fetch(:b) }.to raise_error(KeyError) + + expect_no_size_change do + expect(1).to eq @cache.fetch(:b, :c) {1} # assert block supersedes default value argument + expect(false).to eq @cache.key?(:b) + end + end + end + + it 'falsy' do + with_or_without_default_proc do + expect(false).to eq @cache.key?(:a) + + expect_no_size_change do + expect(nil).to eq @cache.fetch(:a, nil) + expect(false).to eq @cache.fetch(:a, false) + expect(nil).to eq @cache.fetch(:a) {} + expect(false).to eq @cache.fetch(:a) { false } + end + + @cache[:a] = nil + expect_no_size_change do + expect(true).to eq @cache.key?(:a) + expect(nil).to eq @cache.fetch(:a) { fail } + end + end + end + + it 'with return' do + with_or_without_default_proc do + r = -> do + @cache.fetch(:a) { return 10 } + end.call + + expect_no_size_change do + expect(10).to eq r + expect(false).to eq @cache.key?(:a) + end + end + end + end + + describe '#fetch_or_store' do + it 'common' do + with_or_without_default_proc do |default_proc_set| + expect_size_change(1) do + expect(1).to eq @cache.fetch_or_store(:a, 1) + expect(1).to eq @cache[:a] + end + + @cache.delete(:a) + + expect_size_change 1 do + expect(1).to eq @cache.fetch_or_store(:a) { 1 } + expect(1).to eq @cache[:a] + end + + expect_no_size_change do + expect(1).to eq @cache.fetch_or_store(:a) { fail } + end + + expect { @cache.fetch_or_store(:b) }. + to raise_error(KeyError) + + expect_size_change(1) do + expect(1).to eq @cache.fetch_or_store(:b, :c) { 1 } # assert block supersedes default value argument + expect(1).to eq @cache[:b] + end + end + end + + it 'falsy' do + with_or_without_default_proc do + expect(false).to eq @cache.key?(:a) + + expect_size_change(1) do + expect(nil).to eq @cache.fetch_or_store(:a, nil) + expect(nil).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + end + @cache.delete(:a) + + expect_size_change(1) do + expect(false).to eq @cache.fetch_or_store(:a, false) + expect(false).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + end + @cache.delete(:a) + + expect_size_change(1) do + expect(nil).to eq @cache.fetch_or_store(:a) {} + expect(nil).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + end + @cache.delete(:a) + + expect_size_change(1) do + expect(false).to eq @cache.fetch_or_store(:a) { false } + expect(false).to eq @cache[:a] + expect(true).to eq @cache.key?(:a) + end + + @cache[:a] = nil + expect_no_size_change do + expect(nil).to eq @cache.fetch_or_store(:a) { fail } + end + end + end + + it 'with return' do + with_or_without_default_proc do + r = -> do + @cache.fetch_or_store(:a) { return 10 } + end.call + + expect_no_size_change do + expect(10).to eq r + expect(false).to eq @cache.key?(:a) + end + end + end + end + + it '#clear' do + @cache[:a] = 1 + expect_size_change(-1) do + expect(@cache).to eq @cache.clear + expect(false).to eq @cache.key?(:a) + expect(nil).to eq @cache[:a] + end + end + + describe '#each_pair' do + it_should_behave_like :collection_each do + let(:method) { :each_pair } + end + end + + describe '#each' do + it_should_behave_like :collection_each do + let(:method) { :each } + end + end + + it '#keys' do + expect([]).to eq @cache.keys + + @cache[1] = 1 + expect([1]).to eq @cache.keys + + @cache[2] = 2 + expect([1, 2]).to eq @cache.keys.sort + end + + it '#values' do + expect([]).to eq @cache.values + + @cache[1] = 1 + expect([1]).to eq @cache.values + + @cache[2] = 2 + expect([1, 2]).to eq @cache.values.sort + end + + it '#each_key' do + expect(@cache).to eq @cache.each_key { fail } + + @cache[1] = 1 + arr = [] + @cache.each_key { |k| arr << k } + expect([1]).to eq arr + + @cache[2] = 2 + arr = [] + @cache.each_key { |k| arr << k } + expect([1, 2]).to eq arr.sort + end + + it '#each_value' do + expect(@cache).to eq @cache.each_value { fail } + + @cache[1] = 1 + arr = [] + @cache.each_value { |k| arr << k } + expect([1]).to eq arr + + @cache[2] = 2 + arr = [] + @cache.each_value { |k| arr << k } + expect([1, 2]).to eq arr.sort + end + + it '#empty' do + expect(true).to eq @cache.empty? + @cache[:a] = 1 + expect(false).to eq @cache.empty? + end + + it 'options validation' do + expect_valid_options(nil) + expect_valid_options({}) + expect_valid_options(foo: :bar) + end + + it 'initial capacity options validation' do + expect_valid_option(:initial_capacity, nil) + expect_valid_option(:initial_capacity, 1) + expect_invalid_option(:initial_capacity, '') + expect_invalid_option(:initial_capacity, 1.0) + expect_invalid_option(:initial_capacity, -1) + end + + it 'load factor options validation' do + expect_valid_option(:load_factor, nil) + expect_valid_option(:load_factor, 0.01) + expect_valid_option(:load_factor, 0.75) + expect_valid_option(:load_factor, 1) + expect_invalid_option(:load_factor, '') + expect_invalid_option(:load_factor, 0) + expect_invalid_option(:load_factor, 1.1) + expect_invalid_option(:load_factor, 2) + expect_invalid_option(:load_factor, -1) + end + + it '#size' do + expect(0).to eq @cache.size + @cache[:a] = 1 + expect(1).to eq @cache.size + @cache[:b] = 1 + expect(2).to eq @cache.size + @cache.delete(:a) + expect(1).to eq @cache.size + @cache.delete(:b) + expect(0).to eq @cache.size + end + + it '#get_or_default' do + with_or_without_default_proc do + expect(1).to eq @cache.get_or_default(:a, 1) + expect(nil).to eq @cache.get_or_default(:a, nil) + expect(false).to eq @cache.get_or_default(:a, false) + expect(false).to eq @cache.key?(:a) + + @cache[:a] = 1 + expect(1).to eq @cache.get_or_default(:a, 2) + end + end + + it '#dup,#clone' do + [:dup, :clone].each do |meth| + cache = cache_with_default_proc(:default_value) + cache[:a] = 1 + dupped = cache.send(meth) + expect(1).to eq dupped[:a] + expect(1).to eq dupped.size + expect_size_change(1, cache) do + expect_no_size_change(dupped) do + cache[:b] = 1 + end + end + expect(false).to eq dupped.key?(:b) + expect_no_size_change(cache) do + expect_size_change(-1, dupped) do + dupped.delete(:a) + end + end + expect(false).to eq dupped.key?(:a) + expect(true).to eq cache.key?(:a) + # test default proc + expect_size_change(1, cache) do + expect_no_size_change dupped do + expect(:default_value).to eq cache[:c] + expect(false).to eq dupped.key?(:c) + end + end + expect_no_size_change cache do + expect_size_change 1, dupped do + expect(dupped[:d]).to eq :default_value + expect(cache.key?(:d)).to eq false + end + end + end + end + + it 'is unfreezable' do + expect { @cache.freeze }.to raise_error(NoMethodError) + end + + it 'marshal dump load' do + new_cache = Marshal.load(Marshal.dump(@cache)) + expect(new_cache).to be_an_instance_of Concurrent::Map + expect(0).to eq new_cache.size + @cache[:a] = 1 + new_cache = Marshal.load(Marshal.dump(@cache)) + expect(1).to eq @cache[:a] + expect(1).to eq new_cache.size + end + + it 'marshal dump does not work with default proc' do + expect { Marshal.dump(Concurrent::Map.new {}) }.to raise_error(TypeError) + end + + it '#inspect' do + regexp = /\A#\Z/i + expect(Concurrent::Map.new.inspect).to match(regexp) + expect((Concurrent::Map.new {}).inspect).to match(regexp) + map = Concurrent::Map.new + map[:foo] = :bar + expect(map.inspect).to match(regexp) + end + + private + + def with_or_without_default_proc(&block) + block.call(false) + @cache = Concurrent::Map.new { |h, k| h[k] = :default_value } + block.call(true) + end + + def cache_with_default_proc(default_value = 1) + Concurrent::Map.new { |cache, k| cache[k] = default_value } + end + + def expect_size_change(change, cache = @cache, &block) + start = cache.size + block.call + expect(change).to eq cache.size - start + end + + def expect_valid_option(option_name, value) + expect_valid_options(option_name => value) + end + + def expect_valid_options(options) + c = Concurrent::Map.new(options) + expect(c).to be_an_instance_of Concurrent::Map + end + + def expect_invalid_option(option_name, value) + expect_invalid_options(option_name => value) + end + + def expect_invalid_options(options) + expect { Concurrent::Map.new(options) }.to raise_error(ArgumentError) + end + + def expect_no_size_change(cache = @cache, &block) + expect_size_change(0, cache, &block) + end + + def expect_handles_return_lambda(method, key, *args) + before_had_key = @cache.key?(key) + before_had_value = before_had_key ? @cache[key] : nil + + returning_lambda = lambda do + @cache.send(method, key, *args) { return :direct_return } + end + + expect_no_size_change do + expect(:direct_return).to eq returning_lambda.call + expect(before_had_key).to eq @cache.key?(key) + expect(before_had_value).to eq @cache[key] if before_had_value + end + end + + class TestException < Exception; end + def expect_handles_exception(method, key, *args) + before_had_key = @cache.key?(key) + before_had_value = before_had_key ? @cache[key] : nil + + expect_no_size_change do + expect { @cache.send(method, key, *args) { raise TestException, '' } }. + to raise_error(TestException) + + expect(before_had_key).to eq @cache.key?(key) + expect(before_had_value).to eq @cache[key] if before_had_value + end + end + + def expect_compute(key, expected_old_value, expected_result, &block) + result = @cache.compute(:a) do |old_value| + expect(expected_old_value).to eq old_value + block.call + end + expect(expected_result).to eq result + end + + def expect_merge_pair(key, value, expected_old_value, expected_result, &block) + result = @cache.merge_pair(key, value) do |old_value| + expect(expected_old_value).to eq old_value + block.call + end + expect(expected_result).to eq result + end + + def expect_collision_resistance(keys) + keys.each { |k| @cache[k] = k.key } + 10.times do |i| + size = keys.size + while i < size + k = keys[i] + expect(k.key == @cache.delete(k) && + !@cache.key?(k) && + (@cache[k] = k.key; @cache[k] == k.key)).to be_truthy + i += 10 + end + end + expect(keys.all? { |k| @cache[k] == k.key }).to be_truthy + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/maybe_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/maybe_spec.rb new file mode 100644 index 0000000000..4813791f11 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/maybe_spec.rb @@ -0,0 +1,231 @@ +require 'concurrent/maybe' + +module Concurrent + + RSpec.describe Maybe do + + context 'construction' do + + it 'hides Maybe.new' do + expect { + Maybe.new + }.to raise_error(NoMethodError) + end + + context 'Maybe.from' do + + it 'raises an exception when no block is given' do + expect { + Maybe.from + }.to raise_error(ArgumentError) + end + + it 'passes all arguments to the block' do + expected = [1, 2, 3] + actual = nil + Maybe.from(*expected) do |*args| + actual = args + end + expect(actual).to eq expected + end + + it 'creates a Just Maybe on success' do + maybe = Maybe.from{ 42 } + expect(maybe).to be_just + end + + it 'sets the value to the block result on success' do + maybe = Maybe.from{ 42 } + expect(maybe.just).to eq 42 + end + + it 'creates a Nothing Maybe on exception' do + maybe = Maybe.from{ raise StandardError.new } + expect(maybe).to be_nothing + end + + it 'sets the reason to the error object on exception' do + ex = StandardError.new + maybe = Maybe.from{ raise ex } + expect(maybe.nothing).to eq ex + end + end + + context 'Maybe.just' do + + let!(:value) { 42 } + subject { Maybe.just(value) } + + it 'creates a new Just Maybe' do + expect(subject).to be_a Maybe + expect(subject).to be_just + end + end + + context 'Maybe.nothing' do + + let!(:error) { StandardError.new } + subject { Maybe.nothing(error) } + + it 'creates a new Nothing Maybe' do + expect(subject).to be_a Maybe + expect(subject).to be_nothing + end + + it 'uses the given Error object' do + error = NoMethodError.new + maybe = Maybe.nothing(error) + expect(maybe.nothing).to be error + end + + it 'creates a new error object with the given string' do + message = 'What do you get when you multiply six by nine?' + maybe = Maybe.nothing(message) + expect(maybe.nothing).to be_a StandardError + expect(maybe.nothing.message).to eq message + end + + it 'creates a new error object when given nothing' do + maybe = Maybe.nothing + expect(maybe.nothing).to be_a StandardError + expect(maybe.nothing.message).to be_empty + end + end + end + + context 'when just' do + + let!(:value) { 42 } + subject { Maybe.just(value) } + + specify '#just? returns true' do + expect(subject).to be_just + end + + specify '#fulfilled? returns true' do + expect(subject).to be_fulfilled + end + + specify '#nothing? returns false' do + expect(subject).to_not be_nothing + end + + specify '#rejected? returns false' do + expect(subject).to_not be_rejected + end + + specify '#just returns the value' do + expect(subject.just).to eq value + end + + specify '#value returns the value' do + expect(subject.value).to eq value + end + + specify '#nothing returns NONE' do + expect(subject.nothing).to be Maybe::NONE + end + + specify '#reason returns NONE' do + expect(subject.reason).to be Maybe::NONE + end + end + + context 'when nothing' do + + let!(:error) { StandardError.new } + subject { Maybe.nothing(error) } + + specify '#just? returns false' do + expect(subject).to_not be_just + end + + specify '#fulfilled? returns false' do + expect(subject).to_not be_fulfilled + end + + specify '#nothing? returns true' do + expect(subject).to be_nothing + end + + specify '#rejected? returns true' do + expect(subject).to be_rejected + end + + specify '#just returns NONE' do + expect(subject.just).to eq Maybe::NONE + end + + specify '#value returns NONE' do + expect(subject.value).to eq Maybe::NONE + end + + specify '#nothing returns the raised error' do + expect(subject.nothing).to be error + end + + specify '#reason returns the raised error' do + expect(subject.reason).to be error + end + end + + context 'comparison' do + + let!(:something_big) { Maybe.just(42) } + let!(:something_small) { Maybe.just(1) } + let!(:nothing_big) { Maybe.nothing(StandardError.new) } + let!(:nothing_small) { Maybe.nothing(ArgumentError.new) } + + specify 'something is not equal to nothing' do + expect(something_big).to_not eq nothing_big + end + + specify 'nothing is equal to nothing' do + expect(nothing_big).to eq nothing_small + end + + specify 'something is equal to the same value' do + first = Maybe.just(42) + second = Maybe.just(42) + expect(first).to eq second + end + + specify 'something is not equal to a different value' do + expect(something_big).to_not eq something_small + end + + specify 'something is greater than a smaller value' do + expect(something_big).to be > something_small + expect(something_big).to be >= something_small + end + + specify 'something is less than a bigger value' do + expect(something_small).to be < something_big + expect(something_small).to be <= something_big + end + + specify 'nothing is not less than nothing' do + expect(nothing_small).to_not be < nothing_big + end + + specify 'nothing is not greater than nothing' do + expect(nothing_big).to_not be > nothing_small + end + end + + context '#or' do + + it 'returns the value when something' do + maybe = Maybe.just(42) + actual = maybe.or(100) + expect(actual).to eq 42 + end + + it 'returns the other when nothing' do + maybe = Maybe.nothing + actual = maybe.or(100) + expect(actual).to eq 100 + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/monotonic_time_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/monotonic_time_spec.rb new file mode 100644 index 0000000000..d4f18a0950 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/monotonic_time_spec.rb @@ -0,0 +1,32 @@ +require 'concurrent/utility/monotonic_time' + +module Concurrent + + RSpec.describe :monotonic_time do + context 'behavior' do + + it 'returns seconds as float' do + expect(Concurrent.monotonic_time).to be_a(Float) + end + + [:float_second, :float_millisecond, :float_microsecond].each do |unit| + it "returns a Float when unit = #{unit.inspect}" do + expect(Concurrent.monotonic_time(unit)).to be_a(Float) + end + end + + [:second, :millisecond, :microsecond, :nanosecond].each do |unit| + it "returns an Integer when unit = #{unit.inspect}" do + expect(Concurrent.monotonic_time(unit)).to be_an(Integer) + end + end + + it 'raises ArgumentError on unknown units' do + expect { + Concurrent.monotonic_time(:foo) + }.to raise_error(ArgumentError) + end + + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/mutable_struct_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/mutable_struct_spec.rb new file mode 100644 index 0000000000..72543d575c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/mutable_struct_spec.rb @@ -0,0 +1,174 @@ +require_relative 'struct_shared' +require 'concurrent/mutable_struct' + +module Concurrent + RSpec.describe MutableStruct do + + it_should_behave_like :struct + it_should_behave_like :mergeable_struct + + let(:anonymous) { described_class.new(:name, :address, :zip) } + let!(:customer) { described_class.new('Customer', :name, :address, :zip) } + let(:joe) { customer.new('Joe Smith', '123 Maple, Anytown NC', 12345) } + + subject { anonymous.new('Joe Smith', '123 Maple, Anytown NC', 12345) } + + context 'definition' do + + it 'defines a setter for each member' do + members = [:Foo, :bar, 'baz'] + structs = [ + described_class.new(*members).new, + described_class.new('ClassForCheckingSetterDefinition', *members).new + ] + + structs.each do |struct| + members.each do |member| + func = "#{member}=" + expect(struct).to respond_to func + method = struct.method(func) + expect(method.arity).to eq 1 + end + end + end + end + + context '#[member]=' do + + it 'sets the value when given a valid symbol member' do + expect(joe[:name] = 'Jane').to eq 'Jane' + expect(subject[:name] = 'Jane').to eq 'Jane' + end + + it 'sets the value when given a valid string member' do + expect(joe['name'] = 'Jane').to eq 'Jane' + expect(subject['name'] = 'Jane').to eq 'Jane' + end + + it 'raises an exception when given a non-existent symbol member' do + expect{joe[:fooarbaz] = 'Jane'}.to raise_error(NameError) + expect{subject[:fooarbaz] = 'Jane'}.to raise_error(NameError) + end + + it 'raises an exception when given a non-existent string member' do + expect{joe['fooarbaz'] = 'Jane'}.to raise_error(NameError) + expect{subject['fooarbaz'] = 'Jane'}.to raise_error(NameError) + end + end + + context '#[index]=' do + + it 'sets the value when given a valid index' do + expect(joe[0] = 'Jane').to eq 'Jane' + expect(subject[0] = 'Jane').to eq 'Jane' + end + + it 'raises an exception when given an out-of-bound index' do + expect{joe[100] = 'Jane'}.to raise_error(IndexError) + expect{subject[100] = 'Jane'}.to raise_error(IndexError) + end + end + + context 'synchronization' do + + it 'protects #values' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.values + end + + it 'protects #values_at' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.values_at(0) + end + + it 'protects #[index]' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject[0] + end + + it 'protects #[member]' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject[:name] + end + + it 'protects getter methods' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.name + end + + it 'protects #[index]=' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject[0] = :foo + end + + it 'protects #[member]=' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject[:name] = :foo + end + + it 'protects getter methods' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.name = :foo + end + + it 'protects #to_s' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.to_s + end + + it 'protects #inspect' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.inspect + end + + it 'protects #merge' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.merge(subject.members.first => subject.values.first) + end + + it 'protects #to_h' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.to_h + end + + it 'protects #==' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject == joe + end + + it 'protects #each' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.each{|value| nil } + end + + it 'protects #each_pair' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.each_pair{|member, value| nil } + end + + it 'protects #select' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.select{|value| false } + end + + it 'protects #initialize_copy' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.clone + end + end + + context 'copy' do + context '#dup' do + it 'mutates only the copy' do + expect(subject.dup.name = 'Jane').not_to eq(subject.name) + end + end + + context '#clone' do + it 'mutates only the copy' do + expect(subject.clone.name = 'Jane').not_to eq(subject.name) + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/mvar_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/mvar_spec.rb new file mode 100644 index 0000000000..9725f45ed5 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/mvar_spec.rb @@ -0,0 +1,409 @@ +require_relative 'concern/dereferenceable_shared' +require 'concurrent/mvar' + +module Concurrent + + RSpec.describe MVar do + + context 'behavior' do + + # dereferenceable + + def dereferenceable_subject(value, opts = {}) + MVar.new(value, opts) + end + + it_should_behave_like :dereferenceable + end + + describe '#initialize' do + + it 'accepts no initial value' do + m = MVar.new + expect(m).to be_empty + end + + it 'accepts an empty initial value' do + m = MVar.new(MVar::EMPTY) + expect(m).to be_empty + end + + it 'accepts an initial value' do + m = MVar.new(14) + expect(m).not_to be_empty + end + + it 'accepts a nil initial value' do + m = MVar.new(nil) + expect(m).not_to be_empty + end + + end + + describe '#take' do + + it 'sets the MVar to empty' do + m = MVar.new(14) + m.take + expect(m).to be_empty + end + + it 'returns the value on a full MVar' do + m = MVar.new(14) + expect(m.take).to eq 14 + end + + it 'waits for another thread to #put' do + m = MVar.new + + in_thread { + sleep(0.1) + m.put 14 + } + + expect(m.take).to eq 14 + end + + it 'returns TIMEOUT on timeout on an empty MVar' do + m = MVar.new + expect(m.take(0.1)).to eq MVar::TIMEOUT + end + + end + + describe '#borrow' do + + it 'yields current value to the block and puts back value' do + m = MVar.new(14) + expect { |b| m.borrow(&b) }.to yield_with_args(14) + expect(m.take).to eq(14) + end + + it 'puts back value even if an exception is raised' do + m = MVar.new(14) + expect { m.borrow { fail 'boom!' } }.to raise_error('boom!') + expect(m.take).to eq(14) + end + + it 'returns the returned value of the block' do + m = MVar.new(14) + expect(m.borrow { 2 }).to eq(2) + expect(m.take).to eq(14) + end + + it 'returns TIMEOUT on timeout on an empty MVar' do + m = MVar.new + expect(m.borrow(0.1) { }).to eq MVar::TIMEOUT + end + + end + + + describe '#put' do + + it 'sets the MVar to be empty' do + m = MVar.new(14) + m.take + expect(m).to be_empty + end + + it 'sets a new value on an empty MVar' do + m = MVar.new + m.put 14 + expect(m.take).to eq 14 + end + + it 'waits for another thread to #take' do + m = MVar.new(14) + + in_thread { + sleep(0.1) + m.take + } + + expect(m.put(14)).to eq 14 + end + + it 'returns TIMEOUT on timeout on a full MVar' do + m = MVar.new(14) + expect(m.put(14, 0.1)).to eq MVar::TIMEOUT + end + + it 'returns the value' do + m = MVar.new + expect(m.put(14)).to eq 14 + end + + end + + describe '#empty?' do + + it 'returns true on an empty MVar' do + m = MVar.new + expect(m).to be_empty + end + + it 'returns false on a full MVar' do + m = MVar.new(14) + expect(m).not_to be_empty + end + + end + + describe '#full?' do + + it 'returns false on an empty MVar' do + m = MVar.new + expect(m).not_to be_full + end + + it 'returns true on a full MVar' do + m = MVar.new(14) + expect(m).to be_full + end + + end + + describe '#modify' do + + it 'raises an exception when no block given' do + m = MVar.new(14) + expect { m.modify }.to raise_error(ArgumentError) + end + + it 'modifies a full MVar' do + m = MVar.new(14) + m.modify { |v| v + 2 } + expect(m.take).to eq 16 + end + + it 'returns the unmodified value' do + m = MVar.new(14) + expect(m.modify { |v| v + 2 }).to eq 14 + end + + it 'waits for another thread to #put' do + m = MVar.new + + in_thread { + sleep(0.1) + m.put 14 + } + + expect(m.modify { |v| v + 2 }).to eq 14 + end + + it 'is atomic' do + m = MVar.new(0) + + # #modify conceptually does #take and #put - but it should be atomic. + # Check that another #put can't sneak it during the #modify. + + in_thread { + m.modify do |v| + sleep(0.5) + 1 + end + } + + sleep(0.1) + expect(m.put(2, 0.5)).to eq MVar::TIMEOUT + expect(m.take).to eq 1 + end + + it 'returns TIMEOUT on timeout on an empty MVar' do + m = MVar.new + expect(m.modify(0.1) { |v| v + 2 }).to eq MVar::TIMEOUT + end + + end + + describe '#try_put!' do + + it 'returns true an empty MVar' do + m = MVar.new + expect(m.try_put!(14)).to eq true + end + + it 'returns false on a full MVar' do + m = MVar.new(14) + expect(m.try_put!(14)).to eq false + end + + it 'sets an empty MVar to be full' do + m = MVar.new + m.try_put! 14 + expect(m).to be_full + end + + end + + describe '#try_take!' do + + it 'returns EMPTY an empty MVar' do + m = MVar.new + expect(m.try_take!).to eq MVar::EMPTY + end + + it 'returns the value on a full MVar' do + m = MVar.new(14) + expect(m.try_take!).to eq 14 + end + + it 'sets a full MVar to be empty' do + m = MVar.new(14) + m.try_take! + expect(m).to be_empty + end + + end + + describe '#set!' do + + it 'sets an empty MVar to be full' do + m = MVar.new + m.set! 14 + expect(m).to be_full + end + + it 'sets a full MVar to be full' do + m = MVar.new(2) + m.set! 14 + expect(m).to be_full + expect(m.take).to eq 14 + end + + it 'returns EMPTY on an empty MVar' do + m = MVar.new + expect(m.set!(2)).to eq MVar::EMPTY + end + + it 'returns the original value on a full MVar' do + m = MVar.new(14) + expect(m.set!(2)).to eq 14 + end + + end + + describe '#modify!' do + + it 'raises an exception when no block given' do + m = MVar.new(14) + expect { m.modify! }.to raise_error(ArgumentError) + end + + it 'modifies a full MVar' do + m = MVar.new(14) + m.modify! { |v| v + 2 } + expect(m.take).to eq 16 + end + + it 'modifies an empty MVar' do + m = MVar.new + m.modify! { |v| 14 } + expect(m.take).to eq 14 + end + + it 'can be used to set a full MVar to empty' do + m = MVar.new(14) + m.modify! { |v| MVar::EMPTY } + expect(m).to be_empty + end + + it 'can be used to set an empty MVar to empty' do + m = MVar.new + m.modify! { |v| MVar::EMPTY } + expect(m).to be_empty + end + + it 'returns the unmodified value' do + m = MVar.new(14) + expect(m.modify! { |v| v + 2 }).to eq 14 + end + + end + + context 'spurious wake ups' do + + let(:m) { MVar.new } + + before(:each) do + def m.simulate_spurious_wake_up + @mutex.synchronize do + @full_condition.broadcast + @empty_condition.broadcast + end + end + end + + describe '#take' do + it 'waits for another thread to #put' do + in_thread { sleep(0.5); m.put 14 } + in_thread { sleep(0.1); m.simulate_spurious_wake_up } + + expect(m.take).to eq 14 + end + + it 'returns TIMEOUT on timeout on an empty MVar' do + result = nil + in_thread { result = m.take(0.3) } + sleep(0.1) + in_thread { m.simulate_spurious_wake_up } + sleep(0.1) + expect(result).to be_nil + sleep(0.2) + expect(result).to eq MVar::TIMEOUT + end + end + + describe '#modify' do + + it 'waits for another thread to #put' do + in_thread { sleep(0.5); m.put 14 } + in_thread { sleep(0.1); m.simulate_spurious_wake_up } + + expect(m.modify { |v| v + 2 }).to eq 14 + end + + it 'returns TIMEOUT on timeout on an empty MVar' do + result = nil + in_thread { result = m.modify(0.3) { |v| v + 2 } } + sleep(0.1) + in_thread { m.simulate_spurious_wake_up } + sleep(0.1) + expect(result).to be_nil + sleep(0.2) + expect(result).to eq MVar::TIMEOUT + end + end + + describe '#put' do + + before(:each) { m.put(42) } + + it 'waits for another thread to #take' do + in_thread { sleep(0.5); m.take } + in_thread { sleep(0.1); m.simulate_spurious_wake_up } + + expect(m.put(14)).to eq 14 + end + + it 'returns TIMEOUT on timeout on a full MVar' do + # TODO (pitr-ch 15-Oct-2016): fails on jruby + result = nil + in_thread { result = m.put(14, 0.3) } + sleep(0.1) + in_thread { m.simulate_spurious_wake_up } + sleep(0.1) + expect(result).to be_nil + sleep(0.2) + expect(result).to eq MVar::TIMEOUT + end + end + + + end + + end + +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/no_concurrent_files_loaded_before_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/no_concurrent_files_loaded_before_spec.rb new file mode 100644 index 0000000000..dcbe6a3a8b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/no_concurrent_files_loaded_before_spec.rb @@ -0,0 +1,7 @@ +files_loaded_before = $LOADED_FEATURES.grep(/\/concurrent\//).grep_v(/\/version\.rb$/) + +RSpec.describe 'The test harness', if: ENV['ISOLATED'] do + it 'does not load concurrent-ruby files to ensure there are no missing requires' do + expect(files_loaded_before).to eq [] + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/options_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/options_spec.rb new file mode 100644 index 0000000000..3b0f8b206b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/options_spec.rb @@ -0,0 +1,39 @@ +require 'concurrent/options' + +module Concurrent + + RSpec.describe Options do + + context '.executor_from_options' do + + let(:executor) { ImmediateExecutor.new } + let(:io_executor) { ImmediateExecutor.new } + let(:fast_executor) { ImmediateExecutor.new } + + it 'returns the given :executor' do + expect(Options.executor_from_options(executor: executor)).to eq executor + end + + it 'returns the global io executor when :executor is :io' do + executor = Options.executor_from_options(executor: :io) + expect(executor).to eq Concurrent.global_io_executor + end + + it 'returns the global fast executor when :executor is :fast' do + executor = Options.executor_from_options(executor: :fast) + expect(executor).to eq Concurrent.global_fast_executor + end + + it 'returns an immediate executor when :executor is :immediate' do + executor = Options.executor_from_options(executor: :immediate) + expect(executor).to be_a Concurrent::ImmediateExecutor + end + + it 'raises an exception when :executor is an unrecognized symbol' do + expect { + Options.executor_from_options(executor: :bogus) + }.to raise_error(ArgumentError) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/processing_actor_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/processing_actor_spec.rb new file mode 100644 index 0000000000..36e3ac0e83 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/processing_actor_spec.rb @@ -0,0 +1,74 @@ +require 'concurrent/edge/processing_actor' + +RSpec.describe 'Concurrent::ProcessingActor' do + specify do + actor = Concurrent::ProcessingActor.act do |the_actor| + the_actor.receive.then do |message| + # the actor ends with message + message + end + end # + + actor.tell! :a_message + expect(actor.termination.value!).to eq :a_message + + def count(actor, count) + # the block passed to receive is called when the actor receives the message + actor.receive.then do |number_or_command, answer| + # number_or_command, answer = p a + # p number_or_command, answer + + # code which is evaluated after the number is received + case number_or_command + when :done + # this will become the result (final value) of the actor + count + when :count + # reply the current count + answer.fulfill count + # continue running + count(actor, count) + when Integer + # this will call count again to set up what to do on next message, based on new state `count + numer` + count(actor, count + number_or_command) + end + end + # evaluation of count ends immediately + # code which is evaluated before the number is received, should be empty + end + + counter = Concurrent::ProcessingActor.act { |a| count a, 0 } + answer = counter.tell!(2).ask_op { |a| [:count, a] }.value! + expect(counter.tell!(3).tell!(:done).termination.value!).to eq 5 + expect(answer.value!).to eq 2 + + add_once_actor = Concurrent::ProcessingActor.act do |the_actor| + the_actor.receive.then do |a, b, reply| + result = a + b + reply.fulfill result + # terminate with result value + result + end + end + + expect(add_once_actor.ask_op { |a| [1, 2, a] }.value!.value!).to eq 3 + # expect(add_once_actor.ask_operation(%w(ab cd)).reason).to be_a_kind_of RuntimeError + expect(add_once_actor.termination.value!).to eq 3 + + def pair_adder(actor) + (actor.receive & actor.receive).then do |(value1, answer1), (value2, answer2)| + result = value1 + value2 + answer1.fulfill result if answer1 + answer2.fulfill result if answer2 + pair_adder actor + end + end + + pair_adder = Concurrent::ProcessingActor.act { |a| pair_adder a } + pair_adder.ask_op { |a| [2, a] } + answer = pair_adder.ask_op { |a| [3, a] }.value! + expect(answer.value!).to eq 5 + expect((pair_adder.ask_op { |a| ['a', a] }.value! & pair_adder.ask_op { |a| ['b', a] }.value!).value!).to eq %w[ab ab] + expect((pair_adder.ask_op { |a| ['a', a] }.value! | pair_adder.ask_op { |a| ['b', a] }.value!).value!).to eq 'ab' + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/promise_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/promise_spec.rb new file mode 100644 index 0000000000..af21a1874b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/promise_spec.rb @@ -0,0 +1,771 @@ +require 'concurrent/promise' +require 'concurrent/executor/simple_executor_service' +require 'concurrent/executor/single_thread_executor' +require 'concurrent/atomic/count_down_latch' + +require_relative 'ivar_shared' +require_relative 'thread_arguments_shared' + +module Concurrent + + RSpec.describe Promise do + + let!(:value) { 10 } + let(:executor) { SimpleExecutorService.new } + + let(:empty_root) { Promise.new(executor: executor){ nil } } + let!(:fulfilled_value) { 10 } + let!(:rejected_reason) { StandardError.new('mojo jojo') } + + let(:pending_subject) do + executor = Concurrent::SingleThreadExecutor.new + executor.post{ sleep(5) } + Promise.execute(executor: executor){ fulfilled_value } + end + + let(:fulfilled_subject) do + Promise.new(executor: :immediate){ fulfilled_value }.execute + end + + let(:rejected_subject) do + Promise.new(executor: :immediate){ raise rejected_reason }.execute + end + + it_should_behave_like :ivar do + subject{ Promise.new(executor: :immediate){ value } } + + def dereferenceable_subject(value, opts = {}) + opts = opts.merge(executor: executor) + Promise.new(opts){ value }.execute.tap{ sleep(0.1) } + end + + def dereferenceable_observable(opts = {}) + opts = opts.merge(executor: executor) + Promise.new(opts){ 'value' } + end + + def execute_dereferenceable(subject) + subject.execute + sleep(0.1) + end + + def trigger_observable(observable) + observable.execute + sleep(0.1) + end + end + + it_should_behave_like :thread_arguments do + + def get_ivar_from_no_args + Concurrent::Promise.execute{|*args| args } + end + + def get_ivar_from_args(opts) + Concurrent::Promise.execute(opts){|*args| args } + end + end + + context 'initializers' do + describe '.fulfill' do + + subject { Promise.fulfill(10) } + + it 'should return a Promise' do + expect(subject).to be_a Promise + end + + it 'should return a fulfilled Promise' do + expect(subject).to be_fulfilled + end + + it 'should return a Promise with set value' do + expect(subject.value).to eq 10 + end + end + + describe '.reject' do + + let(:reason) { ArgumentError.new } + subject { Promise.reject(reason) } + + it 'should return a Promise' do + expect(subject).to be_a Promise + end + + it 'should return a rejected Promise' do + expect(subject).to be_rejected + end + + it 'should return a Promise with set reason' do + expect(subject.reason).to be reason + end + end + + describe '.new' do + it 'should return an unscheduled Promise' do + p = Promise.new(executor: :immediate){ nil } + expect(p).to be_unscheduled + end + end + + describe '.execute' do + it 'creates a new Promise' do + p = Promise.execute(executor: :immediate){ nil } + expect(p).to be_a(Promise) + end + + it 'passes the block to the new Promise' do + p = Promise.execute(executor: :immediate){ 20 } + expect(p.value).to eq 20 + end + + it 'calls #execute on the new Promise' do + p = double('promise') + allow(Promise).to receive(:new).with(any_args).and_return(p) + expect(p).to receive(:execute).with(no_args) + Promise.execute(executor: :immediate){ nil } + end + end + end + + context '#execute' do + + context 'unscheduled' do + + it 'sets the promise to :pending' do + start_latch = CountDownLatch.new + end_latch = CountDownLatch.new + p = Promise.new(executor: executor) do + start_latch.count_down + end_latch.wait(1) + end + start_latch.wait(1) + p.execute + expect(p).to be_pending + end_latch.count_down + end + + it 'posts the block given in construction' do + executor = ImmediateExecutor.new + expect(executor).to receive(:post).with(any_args).and_call_original + Promise.new(executor: executor){ nil }.execute + end + end + + context 'pending' do + + it 'sets the promise to :pending' do + latch = CountDownLatch.new + p = Promise.new{ latch.wait(1) }.execute + expect(p).to be_pending + latch.count_down + end + + it 'does not post again' do + executor = SimpleExecutorService.new + expect(executor).to receive(:post).with(any_args).once + + latch = CountDownLatch.new + p = Promise.new(executor: executor){ latch.wait(1) }.execute + + 10.times { p.execute } + latch.count_down + end + end + + describe 'with children' do + + let(:start_latch) { CountDownLatch.new(4) } + let(:end_latch) { CountDownLatch.new(1) } + let(:root) { Promise.new(executor: executor){ start_latch.count_down; end_latch.wait(5) } } + let(:c1) { root.then { start_latch.count_down; end_latch.wait(5) } } + let(:c2) { root.then { start_latch.count_down; end_latch.wait(5) } } + let(:c2_1) { c2.then { start_latch.count_down; end_latch.wait(5) } } + + context 'when called on the root' do + it 'should set all promises to :pending' do + root.execute + start_latch.wait(1) + [root, c1, c2, c2_1].each { |p| expect(p).to be_pending } + end_latch.count_down + end + end + + context 'when called on a child' do + it 'should set all promises to :pending' do + c2_1.execute + start_latch.wait(1) + [root, c1, c2, c2_1].each { |p| expect(p).to be_pending } + end_latch.count_down + end + end + + context "when called on child after parent completes" do + let(:parent_promise) { Concurrent::Promise.execute { 1 + 1 }.tap { |p| p.wait } } + it 'sets state to :pending immediately' do + latch = CountDownLatch.new + child_promise = parent_promise.then { |_| latch.wait }.execute + expect(child_promise.state).to eq(:pending) + latch.count_down + end + end + end + end + + describe '#then' do + + it 'returns a new promise when a block is passed' do + child = empty_root.then { nil } + expect(child).to be_a Promise + expect(child).not_to be empty_root + end + + it 'returns a new promise when a rescuer is passed' do + child = empty_root.then(Proc.new{}) + expect(child).to be_a Promise + expect(child).not_to be empty_root + end + + it 'returns a new promise when a block and rescuer are passed' do + child = empty_root.then(Proc.new{}) { nil } + expect(child).to be_a Promise + expect(child).not_to be empty_root + end + + it 'returns a new promise when a block, rescuer and executor are passed' do + new_executor = Concurrent::SingleThreadExecutor.new + child = empty_root.then(Proc.new{}, new_executor) { nil } + expect(child).to be_a Promise + expect(child).not_to be empty_root + expect(child.instance_variable_get(:@executor)).to be(new_executor) + end + + it 'supports setting the executor using a named parameter' do + new_executor = Concurrent::SingleThreadExecutor.new + child = empty_root.then(executor: new_executor) { nil } + expect(child.instance_variable_get(:@executor)).to be(new_executor) + end + + it 'should have block or rescuers' do + expect { empty_root.then }.to raise_error(ArgumentError) + end + + context 'unscheduled' do + + let(:p1) { Promise.new(executor: executor){nil} } + let(:child) { p1.then{} } + + it 'returns a new promise' do + expect(child).to be_a Promise + expect(p1).not_to be child + end + + it 'returns an unscheduled promise' do + expect(child).to be_unscheduled + end + end + + context 'pending' do + + let(:child) { pending_subject.then{} } + + it 'returns a new promise' do + expect(child).to be_a Promise + expect(pending_subject).not_to be child + end + + it 'returns a pending promise' do + expect(child).to be_pending + end + end + + context 'fulfilled' do + it 'returns a new Promise' do + p1 = fulfilled_subject + p2 = p1.then{} + expect(p2).to be_a(Promise) + expect(p1).not_to eq p2 + end + + it 'notifies fulfillment to new child' do + child = fulfilled_subject.then(Proc.new{ 7 }) { |v| v + 5 } + expect(child.value).to eq fulfilled_value + 5 + end + end + + context 'rejected' do + it 'returns a new Promise when :rejected' do + p1 = rejected_subject + p2 = p1.then{} + expect(p2).to be_a(Promise) + expect(p1).not_to eq p2 + end + + it 'notifies rejection to new child' do + child = rejected_subject.then(Proc.new{ 7 }) { |v| v + 5 } + expect(child.value).to eq 7 + end + end + + it 'can be called more than once' do + p = pending_subject + p1 = p.then{} + p2 = p.then{} + expect(p1).not_to be p2 + end + end + + describe 'on_success' do + it 'should have a block' do + expect { empty_root.on_success }.to raise_error(ArgumentError) + end + + it 'returns a new promise' do + child = empty_root.on_success { nil } + expect(child).to be_a Promise + expect(child).not_to be empty_root + end + end + + context '#rescue' do + + it 'returns a new promise' do + child = empty_root.rescue { nil } + expect(child).to be_a Promise + expect(child).not_to be empty_root + end + end + + describe '#flat_map' do + + it 'returns a promise' do + child = empty_root.flat_map { nil } + expect(child).to be_a Promise + expect(child).not_to be empty_root + end + + it 'succeeds if both promises succeed' do + child = Promise.new(executor: :immediate) { 1 }. + flat_map { |v| Promise.new(executor: :immediate) { v + 10 } }.execute.wait + + expect(child.value!).to eq(11) + end + + it 'fails if the left promise fails' do + child = Promise.new(executor: :immediate) { fail }. + flat_map { |v| Promise.new(executor: :immediate) { v + 10 } }.execute.wait + + expect(child).to be_rejected + end + + it 'fails if the right promise fails' do + child = Promise.new(executor: :immediate) { 1 }. + flat_map { |v| Promise.new(executor: :immediate) { fail } }.execute.wait + + expect(child).to be_rejected + end + + it 'fails if the generating block fails' do + child = Promise.new(executor: :immediate) { }.flat_map { fail }.execute.wait + + expect(child).to be_rejected + end + + end + + describe '#zip' do + let(:promise1) { Promise.new(executor: :immediate) { 1 } } + let(:promise2) { Promise.new(executor: :immediate) { 2 } } + let(:promise3) { Promise.new(executor: :immediate) { [3] } } + + it 'executes the returned Promise by default' do + composite = promise1.zip(promise2, promise3) + + expect(composite).to be_fulfilled + end + + it 'executes the returned Promise when execute is true' do + composite = promise1.zip(promise2, promise3, execute: true) + + expect(composite).to be_fulfilled + end + + it 'does not execute the returned Promise when execute is false' do + composite = promise1.zip(promise2, promise3, execute: false) + + expect(composite).to be_unscheduled + end + + it 'allows setting executor for Promise chain' do + new_executor = Concurrent::SingleThreadExecutor.new + promise = promise1.zip(promise2, promise3, executor: new_executor) + + promise = promise.instance_variable_get(:@parent) until promise.send(:root?) + expect(promise.instance_variable_get(:@executor)).to be(new_executor) + end + + it 'yields the results as an array' do + composite = promise1.zip(promise2, promise3).execute.wait + + expect(composite.value).to eq([1, 2, [3]]) + end + + it 'fails if one component fails' do + composite = promise1.zip(promise2, rejected_subject, promise3).execute.wait + + expect(composite).to be_rejected + end + + it 'preserves ordering of the executed promises' do + 10.times do + latch1 = CountDownLatch.new + latch2 = CountDownLatch.new + executor = SimpleExecutorService.new + + p1 = Concurrent::Promise.execute(executor: executor) { latch1.wait; 'one' } + p2 = Concurrent::Promise.execute(executor: executor) { latch2.wait; 'two' } + p3 = Concurrent::Promise.execute(executor: executor) { 'three' } + + latch1.count_down + latch2.count_down + + result = Concurrent::Promise.zip(p1, p2, p3).value! + expect(result).to eq(['one', 'two', 'three']) + end + end + end + + describe '.zip' do + let(:promise1) { Promise.new(executor: :immediate) { 1 } } + let(:promise2) { Promise.new(executor: :immediate) { 2 } } + let(:promise3) { Promise.new(executor: :immediate) { [3] } } + + it 'executes the returned Promise by default' do + composite = Promise.zip(promise1, promise2, promise3) + + expect(composite).to be_fulfilled + end + + it 'executes the returned Promise when execute is true' do + composite = Promise.zip(promise1, promise2, promise3, execute: true) + + expect(composite).to be_fulfilled + end + + it 'does not execute the returned Promise when execute is false' do + composite = Promise.zip(promise1, promise2, promise3, execute: false) + + expect(composite).to be_unscheduled + end + + it 'allows setting executor for Promise chain' do + new_executor = Concurrent::SingleThreadExecutor.new + promise = Promise.zip(promise1, promise2, promise3, executor: new_executor) + + promise = promise.instance_variable_get(:@parent) until promise.send(:root?) + expect(promise.instance_variable_get(:@executor)).to be(new_executor) + end + + it 'yields the results as an array' do + composite = Promise.zip(promise1, promise2, promise3).execute.wait + + expect(composite.value).to eq([1, 2, [3]]) + end + + it 'fails if one component fails' do + composite = Promise.zip(promise1, promise2, rejected_subject, promise3).execute.wait + + expect(composite).to be_rejected + end + + it 'preserves ordering of the executed promises' do + promise1 = Promise.execute do + # resolves after the second promise + sleep 0.2 + 'one' + end + + promise2 = Promise.execute do + sleep 0.1 + 'two' + end + + promise3 = Promise.execute do + 'three' + end + + result = Promise.zip(promise1, promise2, promise3).value + + expect(result).to eql(['one', 'two', 'three']) + end + end + + describe 'aggregators' do + + let(:promise1) { Promise.new(executor: :immediate) { 1 } } + let(:promise2) { Promise.new(executor: :immediate) { 2 } } + let(:promise3) { Promise.new(executor: :immediate) { [3] } } + + describe '.all?' do + + it 'returns a new Promise' do + composite = Promise.all?(promise1, promise2, promise3).execute + expect(composite).to be_a Concurrent::Promise + end + + it 'does not execute the returned Promise' do + composite = Promise.all?(promise1, promise2, promise3) + expect(composite).to be_unscheduled + end + + it 'executes the #then condition when all components succeed' do + counter = Concurrent::AtomicFixnum.new(0) + latch = Concurrent::CountDownLatch.new(1) + + Promise.all?(promise1, promise2, promise3). + then { counter.up; latch.count_down }. + rescue { counter.down; latch.count_down }. + execute + + latch.wait(1) + + expect(counter.value).to eq 1 + end + + it 'executes the #then condition when no promises are given' do + counter = Concurrent::AtomicFixnum.new(0) + latch = Concurrent::CountDownLatch.new(1) + + Promise.all?. + then { counter.up; latch.count_down }. + rescue { counter.down; latch.count_down }. + execute + + latch.wait(1) + + expect(counter.value).to eq 1 + end + + it 'executes the #rescue handler if even one component fails' do + counter = Concurrent::AtomicFixnum.new(0) + latch = Concurrent::CountDownLatch.new(1) + + Promise.all?(promise1, promise2, rejected_subject, promise3). + then { counter.up; latch.count_down }. + rescue { counter.down; latch.count_down }. + execute + + latch.wait(1) + + expect(counter.value).to eq(-1) + end + end + + describe '.any?' do + + it 'returns a new Promise' do + composite = Promise.any?(promise1, promise2, promise3).execute + expect(composite).to be_a Concurrent::Promise + end + + it 'does not execute the returned Promise' do + composite = Promise.any?(promise1, promise2, promise3) + expect(composite).to be_unscheduled + end + + it 'executes the #then condition when any components succeed' do + counter = Concurrent::AtomicFixnum.new(0) + latch = Concurrent::CountDownLatch.new(1) + + Promise.any?(promise1, promise2, rejected_subject, promise3). + then { counter.up; latch.count_down }. + rescue { counter.down; latch.count_down }. + execute + + latch.wait(1) + + expect(counter.value).to eq 1 + end + + it 'executes the #then condition when no promises are given' do + counter = Concurrent::AtomicFixnum.new(0) + latch = Concurrent::CountDownLatch.new(1) + + Promise.any?. + then { counter.up; latch.count_down }. + rescue { counter.down; latch.count_down }. + execute + + latch.wait(1) + + expect(counter.value).to eq 1 + end + + it 'executes the #rescue handler if all componenst fail' do + counter = Concurrent::AtomicFixnum.new(0) + latch = Concurrent::CountDownLatch.new(1) + + Promise.any?(rejected_subject, rejected_subject, rejected_subject, rejected_subject). + then { counter.up; latch.count_down }. + rescue { counter.down; latch.count_down }. + execute + + latch.wait(1) + + expect(counter.value).to eq(-1) + end + end + end + + context 'fulfillment' do + + context '#set' do + + it '#can only be called on the root promise' do + root = Promise.new{ :foo } + child = root.then{ :bar } + + expect { child.set('foo') }.to raise_error PromiseExecutionError + expect { root.set('foo') }.not_to raise_error + end + + it 'triggers children' do + expected = nil + root = Promise.new(executor: :immediate){ nil } + root.then{ |result| expected = result } + root.set(20) + expect(expected).to eq 20 + end + + it 'can be called with a block' do + p = Promise.new(executor: :immediate) + ch = p.then(&:to_s) + p.set { :value } + + expect(p.value).to eq :value + expect(p.state).to eq :fulfilled + + expect(ch.value).to eq 'value' + expect(ch.state).to eq :fulfilled + end + end + + context '#fail' do + + it 'can only be called on the root promise' do + root = Promise.new{ :foo } + child = root.then{ :bar } + + expect { child.fail }.to raise_error PromiseExecutionError + expect { root.fail }.not_to raise_error + end + + it 'rejects children' do + expected = nil + root = Promise.new(executor: :immediate) + root.then(Proc.new{ |reason| expected = reason }) + root.fail(ArgumentError.new('simulated error')) + expect(expected).to be_a ArgumentError + end + end + + it 'passes the result of each block to all its children' do + expected = nil + Promise.new(executor: :immediate){ 20 }.then{ |result| expected = result }.execute + expect(expected).to eq 20 + end + + it 'sets the promise value to the result if its block' do + root = Promise.new(executor: :immediate){ 20 } + p = root.then{ |result| result * 2}.execute + expect(root.value).to eq 20 + expect(p.value).to eq 40 + end + + it 'sets the promise state to :fulfilled if the block completes' do + p = Promise.new(executor: :immediate){ 10 * 2 }.then{|result| result * 2}.execute + expect(p).to be_fulfilled + end + + it 'passes the last result through when a promise has no block' do + expected = nil + Promise.new(executor: :immediate){ 20 }.then(Proc.new{}).then{|result| expected = result}.execute + expect(expected).to eq 20 + end + + it 'uses result as fulfillment value when a promise has no block' do + p = Promise.new(executor: :immediate){ 20 }.then(Proc.new{}).execute + expect(p.value).to eq 20 + end + + it 'can manage long chain' do + root = Promise.new(executor: :immediate){ 20 } + p1 = root.then { |b| b * 3 } + p2 = root.then { |c| c + 2 } + p3 = p1.then { |d| d + 7 } + + root.execute + + expect(root.value).to eq 20 + expect(p1.value).to eq 60 + expect(p2.value).to eq 22 + expect(p3.value).to eq 67 + end + end + + context 'rejection' do + + it 'passes the reason to all its children' do + expected = nil + handler = proc { |reason| expected = reason } + Promise.new(executor: :immediate){ raise ArgumentError }.then(handler).execute + expect(expected).to be_a ArgumentError + end + + it 'sets the promise value to the result if its block' do + root = Promise.new(executor: :immediate){ raise ArgumentError } + p = root.then(Proc.new{ |reason| 42 }).execute + expect(p.value).to eq 42 + end + + it 'sets the promise state to :rejected if the block completes' do + p = Promise.new(executor: :immediate){ raise ArgumentError }.execute + expect(p).to be_rejected + end + + it 'uses reason as rejection reason when a promise has no rescue callable' do + p = Promise.new(executor: :immediate){ raise ArgumentError }.then{ |val| val }.execute + expect(p).to be_rejected + expect(p.reason).to be_a ArgumentError + end + + it 'rejects on Exception' do + p = Promise.new(executor: :immediate){ raise Exception }.execute + expect(p).to be_rejected + end + + end + + context 'aliases' do + + it 'aliases #realized? for #fulfilled?' do + expect(fulfilled_subject).to be_realized + end + + it 'aliases #deref for #value' do + expect(fulfilled_subject.deref).to eq fulfilled_value + end + + it 'aliases #catch for #rescue' do + child = rejected_subject.catch { 7 } + expect(child.value).to eq 7 + end + + it 'aliases #on_error for #rescue' do + child = rejected_subject.on_error { 7 } + expect(child.value).to eq 7 + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/promises_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/promises_spec.rb new file mode 100644 index 0000000000..5e9d735d5c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/promises_spec.rb @@ -0,0 +1,757 @@ +require 'concurrent/edge/promises' +require 'concurrent/atomic/count_down_latch' + +RSpec.describe 'Concurrent::Promises' do + + include Concurrent::Promises::FactoryMethods + + describe 'chain_resolvable' do + it 'event' do + b = resolvable_event + a = resolvable_event.chain_resolvable(b) + a.resolve + expect(b).to be_resolved + end + + it 'future' do + b = resolvable_future + a = resolvable_future.chain_resolvable(b) + a.fulfill :val + expect(b).to be_resolved + expect(b.value).to eq :val + end + end + + describe '.future' do + it 'executes' do + future = future { 1 + 1 } + expect(future.value!).to eq 2 + + future = fulfilled_future(1).then { |v| v + 1 } + expect(future.value!).to eq 2 + + future = future(1, 2, &-> v { v }) + expect { future.value! }.to raise_error ArgumentError, /wrong number of arguments/ + + future = fulfilled_future(1).then(2, &-> v { v }) + expect { future.value! }.to raise_error ArgumentError, /wrong number of arguments/ + end + + it 'executes with args' do + future = future(1, 2, &:+) + expect(future.value!).to eq 3 + + future = fulfilled_future(1).then(1) { |v, a| v + 1 } + expect(future.value!).to eq 2 + end + end + + describe '.delay' do + + def behaves_as_delay(delay, value) + expect(delay.resolved?).to eq false + expect(delay.value!).to eq value + end + + specify do + behaves_as_delay delay { 1 + 1 }, 2 + behaves_as_delay fulfilled_future(1).delay.then { |v| v + 1 }, 2 + behaves_as_delay delay(1) { |a| a + 1 }, 2 + behaves_as_delay fulfilled_future(1).delay.then { |v| v + 1 }, 2 + end + end + + describe '.schedule' do + it 'scheduled execution' do + start = Time.now.to_f + queue = Queue.new + future = schedule(0.1) { 1 + 1 }.then { |v| queue.push(v); queue.push(Time.now.to_f - start); queue } + + expect(future.value!).to eq queue + expect(queue.pop).to eq 2 + expect(queue.pop).to be >= 0.09 + + start = Time.now.to_f + queue = Queue.new + future = resolved_event. + schedule(0.1). + then { 1 }. + then { |v| queue.push(v); queue.push(Time.now.to_f - start); queue } + + expect(future.value!).to eq queue + expect(queue.pop).to eq 1 + expect(queue.pop).to be >= 0.09 + end + + it 'scheduled execution in graph' do + start = Time.now.to_f + queue = Queue.new + future = future { sleep 0.1; 1 }. + schedule(0.1). + then { |v| v + 1 }. + then { |v| queue.push(v); queue.push(Time.now.to_f - start); queue } + + expect(future.value!).to eq queue + expect(queue.pop).to eq 2 + expect(queue.pop).to be >= 0.09 + + scheduled = resolved_event.schedule(0.1) + expect(scheduled.resolved?).to be_falsey + scheduled.wait + expect(scheduled.resolved?).to be_truthy + end + + end + + describe '.event' do + specify do + resolvable_event = resolvable_event() + one = resolvable_event.chain(1) { |arg| arg } + join = zip(resolvable_event).chain { 1 } + expect(one.resolved?).to be false + resolvable_event.resolve + expect(one.value!).to eq 1 + expect(join.wait.resolved?).to be true + end + end + + describe '.future without block' do + specify do + resolvable_future = resolvable_future() + one = resolvable_future.then(&:succ) + join = zip_futures(resolvable_future).then { |v| v } + expect(one.resolved?).to be false + resolvable_future.fulfill 0 + expect(one.value!).to eq 1 + expect(join.wait!.resolved?).to be true + expect(join.value!).to eq 0 + end + end + + describe '.any_resolved' do + it 'continues on first result' do + f1 = resolvable_future + f2 = resolvable_future + f3 = resolvable_future + + any1 = any_resolved_future(f1, f2) + any2 = f2 | f3 + + f1.fulfill 1 + f2.reject StandardError.new + + expect(any1.value!).to eq 1 + expect(any2.reason).to be_a_kind_of StandardError + end + end + + describe '.any_fulfilled' do + it 'continues on first result' do + f1 = resolvable_future + f2 = resolvable_future + + any = any_fulfilled_future(f1, f2) + + f1.reject StandardError.new + f2.fulfill :value + + expect(any.value!).to eq :value + end + + it 'treats a resolved Event as a fulfilled Future' do + any = any_fulfilled_future( + resolved_event, + fulfilled_future(:value), + ) + + expect(any.value!).to eq nil + end + + it 'treats a pending Event as a pending Future' do + any = any_fulfilled_future( + resolvable_event, + fulfilled_future(:value), + ) + + expect(any.value!).to eq :value + end + end + + describe '.zip' do + it 'waits for all results' do + a = future { 1 } + b = future { 2 } + c = future { 3 } + + z1 = a & b + z2 = zip a, b, c + z3 = zip a + z4 = zip + + expect(z1.value!).to eq [1, 2] + expect(z2.value!).to eq [1, 2, 3] + expect(z3.value!).to eq [1] + expect(z4.value!).to eq [] + + q = Queue.new + z1.then { |*args| q << args } + # first is an array because it is zipping so 2 arguments + expect(q.pop).to eq [1, 2] + + z1.then { |*args| args }.then { |*args| q << args } + # after then it is again just one argument + expect(q.pop).to eq [[1, 2]] + + fulfilled_future([1, 2]).then { |*args| q << args } + expect(q.pop).to eq [[1, 2]] + + z1.then { |a1, b1, c1| q << [a1, b1, c1] } + expect(q.pop).to eq [1, 2, nil] + + z2.then { |a1, b1, c1| q << [a1, b1, c1] } + expect(q.pop).to eq [1, 2, 3] + + z3.then { |a1| q << a1 } + expect(q.pop).to eq 1 + + z3.then { |*as| q << as } + expect(q.pop).to eq [1] + + z4.then { |a1| q << a1 } + expect(q.pop).to eq nil + + z4.then { |*as| q << as } + expect(q.pop).to eq [] + + expect(z1.then { |a1, b1| a1 + b1 }.value!).to eq 3 + expect(z1.then { |a1, b1| a1 + b1 }.value!).to eq 3 + expect(z1.then(&:+).value!).to eq 3 + expect(z2.then { |a1, b1, c1| a1 + b1 + c1 }.value!).to eq 6 + + expect(future { 1 }.delay).to be_a_kind_of Concurrent::Promises::Future + expect(future { 1 }.delay.wait!).to be_resolved + expect(resolvable_event.resolve.delay).to be_a_kind_of Concurrent::Promises::Event + expect(resolvable_event.resolve.delay.wait).to be_resolved + + a = future { 1 } + b = future { raise 'b' } + c = future { raise 'c' } + + zip(a, b, c).chain { |*args| q << args } + expect(q.pop.flatten.map(&:class)).to eq [FalseClass, 0.class, NilClass, NilClass, NilClass, RuntimeError, RuntimeError] + zip(a, b, c).rescue { |*args| q << args } + expect(q.pop.map(&:class)).to eq [NilClass, RuntimeError, RuntimeError] + + expect(zip.wait(0.1)).to eq true + end + + context 'when a future raises an error' do + + let(:a_future) { future { raise 'error' } } + + it 'raises a concurrent error' do + expect { zip(a_future).value! }.to raise_error(StandardError, 'error') + end + + context 'when deeply nested' do + it 'raises the original error' do + expect { zip(zip(a_future)).value! }.to raise_error(StandardError, 'error') + end + end + end + end + + describe '.zip_events' do + it 'waits for all and returns event' do + a = fulfilled_future 1 + b = rejected_future :any + c = resolvable_event.resolve + + z2 = zip_events a, b, c + z3 = zip_events a + z4 = zip_events + + expect(z2.resolved?).to be_truthy + expect(z3.resolved?).to be_truthy + expect(z4.resolved?).to be_truthy + end + end + + describe '.rejected_future' do + it 'raises the correct error when passed an unraised error' do + f = rejected_future(StandardError.new('boom')) + expect { f.value! }.to raise_error(StandardError, 'boom') + end + end + + describe 'Future' do + it 'has sync and async callbacks' do + callbacks_tester = ->(event_or_future) do + queue = Queue.new + push_args = -> *args { queue.push args } + + event_or_future.on_resolution!(&push_args) + event_or_future.on_resolution!(1, &push_args) + if event_or_future.is_a? Concurrent::Promises::Future + event_or_future.on_fulfillment!(&push_args) + event_or_future.on_fulfillment!(2, &push_args) + event_or_future.on_rejection!(&push_args) + event_or_future.on_rejection!(3, &push_args) + end + + event_or_future.on_resolution(&push_args) + event_or_future.on_resolution(4, &push_args) + if event_or_future.is_a? Concurrent::Promises::Future + event_or_future.on_fulfillment(&push_args) + event_or_future.on_fulfillment(5, &push_args) + event_or_future.on_rejection(&push_args) + event_or_future.on_rejection(6, &push_args) + end + event_or_future.on_resolution_using(:io, &push_args) + event_or_future.on_resolution_using(:io, 7, &push_args) + if event_or_future.is_a? Concurrent::Promises::Future + event_or_future.on_fulfillment_using(:io, &push_args) + event_or_future.on_fulfillment_using(:io, 8, &push_args) + event_or_future.on_rejection_using(:io, &push_args) + event_or_future.on_rejection_using(:io, 9, &push_args) + end + + event_or_future.wait + ::Array.new(event_or_future.is_a?(Concurrent::Promises::Future) ? 12 : 6) { queue.pop } + end + + callback_results = callbacks_tester.call(fulfilled_future(:v)) + expect(callback_results).to contain_exactly([true, :v, nil], + [true, :v, nil, 1], + [:v], + [:v, 2], + [true, :v, nil], + [true, :v, nil, 4], + [:v], + [:v, 5], + [true, :v, nil], + [true, :v, nil, 7], + [:v], + [:v, 8]) + + err = StandardError.new 'boo' + callback_results = callbacks_tester.call(rejected_future(err)) + expect(callback_results).to contain_exactly([false, nil, err], + [false, nil, err, 1], + [err], + [err, 3], + [false, nil, err], + [false, nil, err, 4], + [err], + [err, 6], + [false, nil, err], + [false, nil, err, 7], + [err], + [err, 9]) + + callback_results = callbacks_tester.call(resolved_event) + expect(callback_results).to contain_exactly([], [1], [], [4], [], [7]) + end + + methods_with_timeout = { wait: false, + wait!: false, + value: nil, + value!: nil, + reason: nil, + result: nil } + methods_with_timeout.each do |method_with_timeout, timeout_value| + it "#{ method_with_timeout } supports setting timeout" do + start_latch = Concurrent::CountDownLatch.new + end_latch = Concurrent::CountDownLatch.new + + future = future do + start_latch.count_down + end_latch.wait(0.2) + end + + expect(start_latch.wait(0.1)).to eq true + expect(future).not_to be_resolved + expect(future.send(method_with_timeout, 0.01)).to eq timeout_value + expect(future).not_to be_resolved + + end_latch.count_down + expect(future.value!).to eq true + end + end + + it 'chains' do + future0 = future { 1 }.then { |v| v + 2 } # both executed on default FAST_EXECUTOR + future1 = future0.then_on(:fast) { raise 'boo' } # executed on IO_EXECUTOR + future2 = future1.then { |v| v + 1 } # will reject with 'boo' error, executed on default FAST_EXECUTOR + future3 = future1.rescue { |err| err.message } # executed on default FAST_EXECUTOR + future4 = future0.chain { |success, value, reason| success } # executed on default FAST_EXECUTOR + future5 = future3.with_default_executor(:fast) # connects new future with different executor, the new future is resolved when future3 is + future6 = future5.then(&:capitalize) # executes on IO_EXECUTOR because default was set to :io on future5 + future7 = future0 & future3 + future8 = future0.rescue { raise 'never happens' } # future0 fulfills so future8'll have same value as future 0 + + futures = [future0, future1, future2, future3, future4, future5, future6, future7, future8] + futures.each(&:wait) + + table = futures.each_with_index.map do |f, i| + '%5i %7s %10s %6s %4s %6s' % [i, f.fulfilled?, f.value, f.reason, + (f.promise.executor if f.promise.respond_to?(:executor)), + f.default_executor] + end.unshift('index success value reason pool d.pool') + + expect(table.join("\n")).to eq <<-TABLE.gsub(/^\s+\|/, '').strip + |index success value reason pool d.pool + | 0 true 3 io io + | 1 false boo fast io + | 2 false boo io io + | 3 true boo io io + | 4 true true io io + | 5 true boo fast + | 6 true Boo fast fast + | 7 true [3, "boo"] io + | 8 true 3 io io + TABLE + end + + it 'chains with correct arguments' do + heads = [future { 1 }, + future { [2, 3] }, + fulfilled_future(4), + fulfilled_future([5, 6])] + results = [1, + [2, 3], + 4, + [5, 6]] + heads.each_with_index do |head, i| + expect(head.then { |a| a }.value!).to eq results[i] + expect(head.then { |a, b| [a, b].compact }.value!).to eq (results[i].is_a?(Array) ? results[i] : [results[i]]) + expect(head.then { |*a| a }.value!).to eq [results[i]] + end + end + + it 'constructs promise like tree' do + # if head of the tree is not constructed with #future but with #delay it does not start execute, + # it's triggered later by calling wait or value on any of the dependent futures or the delay itself + three = (head = delay { 1 }).then { |v| v.succ }.then(&:succ) + four = three.delay.then(&:succ) + + # meaningful to_s and inspect defined for Future and Promise + expect(head.to_s).to match(/#/) + expect(head.inspect).to( + match(/#/)) + + # evaluates only up to three, four is left unevaluated + expect(three.value!).to eq 3 + expect(four).not_to be_resolved + + expect(four.value!).to eq 4 + + # futures hidden behind two delays trigger evaluation of both + double_delay = delay { 1 }.delay.then(&:succ) + expect(double_delay.value!).to eq 2 + end + + it 'allows graphs' do + head = future { 1 } + branch1 = head.then(&:succ) + branch2 = head.then(&:succ).delay.then(&:succ) + results = [ + zip(branch1, branch2).then { |b1, b2| b1 + b2 }, + branch1.zip(branch2).then { |b1, b2| b1 + b2 }, + (branch1 & branch2).then { |b1, b2| b1 + b2 }] + + Thread.pass until branch1.resolved? + expect(branch1).to be_resolved + expect(branch2).not_to be_resolved + + expect(results.map(&:value)).to eq [5, 5, 5] + expect(zip(branch1, branch2).value!).to eq [2, 3] + end + + describe '#flat' do + it 'returns value of inner future' do + f = future { future { 1 } }.flat.then(&:succ) + expect(f.value!).to eq 2 + end + + it 'propagates rejection of inner future' do + err = StandardError.new('boo') + f = future { rejected_future(err) }.flat + expect(f.reason).to eq err + end + + it 'it propagates rejection of the future which was suppose to provide inner future' do + f = future { raise 'boo' }.flat + expect(f.reason.message).to eq 'boo' + end + + it 'rejects if inner value is not a future' do + f = future { 'boo' }.flat + expect(f.reason).to be_an_instance_of TypeError + end + + it 'accepts inner event' do + f = future { resolved_event }.flat + expect(f.result).to eq [true, nil, nil] + end + + it 'propagates requests for values to delayed futures' do + expect(future { delay { 1 } }.flat.value!(0.1)).to eq 1 + expect(::Array.new(3) { |i| Concurrent::Promises.delay { i } }. + inject { |a, b| a.then { b }.flat }.value!(0.2)).to eq 2 + end + + it 'has shortcuts' do + expect(fulfilled_future(1).then_flat { |v| future(v) { v + 1 } }.value!).to eq 2 + expect(fulfilled_future(1).then_flat_event { |v| resolved_event }.wait.resolved?).to eq true + expect(fulfilled_future(1).then_flat_on(:fast) { |v| future(v) { v + 1 } }.value!).to eq 2 + end + end + + it 'resolves future when Exception raised' do + message = 'reject by an Exception' + future = future { raise Exception, message } + expect(future.wait(0.1)).to eq true + future.wait + expect(future).to be_resolved + expect(future).to be_rejected + + expect(future.reason).to be_instance_of Exception + expect(future.result).to be_instance_of Array + expect(future.value).to be_nil + expect { future.value! }.to raise_error(Exception, message) + end + + it 'runs' do + body = lambda do |v| + v += 1 + v < 5 ? future(v, &body) : v + end + expect(future(0, &body).run.value!).to eq 5 + + body = lambda do |v| + v += 1 + v < 5 ? future(v, &body) : raise(v.to_s) + end + expect(future(0, &body).run.reason.message).to eq '5' + + body = lambda do |v| + v += 1 + v < 5 ? [future(v, &body)] : v + end + expect(future(0, &body).run(-> v { v.first if v.is_a? Array }).value!).to eq 5 + end + + it 'can be risen when rejected' do + strip_methods = -> backtrace do + backtrace.map do |line| + /^.*:\d+:in/.match(line)[0] rescue line + end + end + + future = rejected_future TypeError.new + backtrace = caller; exception = (raise future rescue $!) + expect(exception).to be_a TypeError + expect(strip_methods[backtrace] - strip_methods[exception.backtrace]).to be_empty + + exception = TypeError.new + exception.set_backtrace(first_backtrace = %W[/a /b /c]) + future = rejected_future exception + backtrace = caller; exception = (raise future rescue $!) + expect(exception).to be_a TypeError + expect(strip_methods[first_backtrace + backtrace] - strip_methods[exception.backtrace]).to be_empty + + future = rejected_future(TypeError.new) & rejected_future(TypeError.new) + backtrace = caller; exception = (raise future rescue $!) + expect(exception).to be_a Concurrent::MultipleErrors + expect(strip_methods[backtrace] - strip_methods[exception.backtrace]).to be_empty + end + end + + describe 'ResolvableEvent' do + specify "#wait" do + event = resolvable_event + expect(event.wait(0, false)).to be_falsey + expect(event.wait(0, true)).to be_falsey + expect(event.wait).to eq event + expect(event.wait(0, false)).to be_truthy + expect(event.wait(0, true)).to be_truthy + end + + specify "#resolve(raise_on_reassign = true)" do + event = resolvable_event + event.resolve + expect { event.resolve }.to raise_error(Concurrent::MultipleAssignmentError) + end + + specify "#resolve(raise_on_reassign = false)" do + event = resolvable_event + event.resolve + expect(event.resolve(false)).to be_falsey + end + + specify "reservation" do + event = resolvable_event + expect(event.reserve).to be_truthy + expect(event.pending?).to be_truthy + expect(event.state).to eq :pending + expect(event.resolve false).to be_falsey + expect(event.resolve true, true).to be_truthy + end + end + + describe 'ResolvableFuture' do + specify "#wait" do + future = resolvable_future + expect(future.wait(0)).to be_falsey + expect(future.wait(0, [true, :v, nil])).to be_falsey + expect(future.wait).to eq future + expect(future.wait(0, nil)).to be_truthy + expect(future.wait(0, [true, :v, nil])).to be_truthy + end + + specify "#wait!" do + future = resolvable_future + expect(future.wait!(0)).to be_falsey + expect(future.wait!(0, [true, :v, nil])).to be_falsey + expect(future.wait!).to eq future + expect(future.wait!(0, nil)).to be_truthy + expect(future.wait!(0, [true, :v, nil])).to be_truthy + + future = resolvable_future + expect(future.wait!(0)).to be_falsey + expect(future.wait!(0, [false, nil, RuntimeError.new])).to be_falsey + expect { future.wait! }.to raise_error RuntimeError + end + + specify "#value" do + future = resolvable_future + expect(future.value(0)).to eq nil + expect(future.value(0, :timeout, [true, :v, nil])).to eq :timeout + expect(future.value).to eq :v + expect(future.value(0)).to eq :v + expect(future.value(0, :timeout, [true, :v, nil])).to eq :v + end + + specify "#value!" do + future = resolvable_future + expect(future.value!(0)).to eq nil + expect(future.value!(0, :timeout, [true, :v, nil])).to eq :timeout + expect(future.value!).to eq :v + expect(future.value!(0, :timeout, nil)).to eq :v + expect(future.value!(0, :timeout, [true, :v, nil])).to eq :v + + future = resolvable_future + expect(future.wait!(0)).to be_falsey + expect(future.wait!(0, [false, nil, RuntimeError.new])).to be_falsey + expect { future.wait! }.to raise_error RuntimeError + end + + specify "#reason" do + future = resolvable_future + expect(future.reason(0)).to eq nil + expect(future.reason(0, :timeout, [false, nil, :err])).to eq :timeout + expect(future.reason).to eq :err + expect(future.reason(0)).to eq :err + expect(future.reason(0, :timeout, [false, nil, :err])).to eq :err + end + + specify "result" do + future = resolvable_future + expect(future.result(0)).to eq nil + expect(future.result(0, [true, :v, nil])).to be_falsey + expect(future.result).to eq [true, :v, nil] + expect(future.result(0)).to eq [true, :v, nil] + expect(future.result(0, [true, :v, nil])).to eq [true, :v, nil] + end + + specify "reservation" do + future = resolvable_future + expect(future.reserve).to be_truthy + expect(future.pending?).to be_truthy + expect(future.state).to eq :pending + expect(future.resolve true, :value, nil, false).to be_falsey + expect(future.fulfill :value, false).to be_falsey + expect(future.reject :err, false).to be_falsey + expect { future.resolve true, :value, nil }.to raise_error(Concurrent::MultipleAssignmentError) + expect(future.resolve true, :value, nil, false, true).to be_truthy + + future = resolvable_future + expect(future.reserve).to be_truthy + expect(future.fulfill :value, false, true).to be_truthy + + future = resolvable_future + expect(future.reserve).to be_truthy + expect(future.reject :err, false, true).to be_truthy + end + + specify "atomic_resolution" do + future1 = resolvable_future + future2 = resolvable_future + + expect(Concurrent::Promises::Resolvable. + atomic_resolution(future1 => [true, :v, nil], + future2 => [false, nil, :err])).to eq true + expect(future1.fulfilled?).to be_truthy + expect(future2.rejected?).to be_truthy + + future1 = resolvable_future + future2 = resolvable_future.fulfill :val + + expect(Concurrent::Promises::Resolvable. + atomic_resolution(future1 => [true, :v, nil], + future2 => [false, nil, :err])).to eq false + + expect(future1.pending?).to be_truthy + expect(future2.fulfilled?).to be_truthy + + expect(future1.reserve).to be_truthy + expect(future2.reserve).to be_falsey + end + end + + describe 'interoperability' do + it 'with processing actor', if: !Concurrent.on_jruby? do + require 'concurrent/actor' + actor = Concurrent::Actor::Utils::AdHoc.spawn :doubler do + -> v { v * 2 } + end + + expect(future { 2 }. + then_ask(actor). + then { |v| v + 2 }. + value!).to eq 6 + end + + it 'with erlang actor' do + require 'concurrent/edge/erlang_actor' + actor = Concurrent::ErlangActor.spawn type: :on_thread do + reply receive * 2 + end + + expect(future { 2 }. + then_ask(actor). + then { |v| v + 2 }. + value!).to eq 6 + end + + it 'with channel' do + require 'concurrent/edge/channel' + ch1 = Concurrent::Promises::Channel.new + ch2 = Concurrent::Promises::Channel.new + + result = Concurrent::Promises::Channel.select_op([ch1, ch2]) + ch1.push 1 + expect(result.value!).to eq [ch1, 1] + + future { 1 + 1 }.then_channel_push(ch1) + result = (Concurrent::Promises.future { '%02d' } & ch1.select_op(ch2)). + then { |format, (_channel, value)| format format, value } + expect(result.value!).to eq '02' + end + end + + specify 'zip_futures_over' do + expect(zip_futures_over([1, 2]) { |v| v.succ }.value!).to eq [2, 3] + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/require_all_files_separately.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/require_all_files_separately.rb new file mode 100644 index 0000000000..e283c6019f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/require_all_files_separately.rb @@ -0,0 +1,29 @@ +RSpec.describe 'Every file', if: ENV['ISOLATED'] do + it 'can be required on its own' do + root = File.expand_path('../..', __dir__) + require_paths = [] + dirs = ["#{root}/lib/concurrent-ruby", "#{root}/lib/concurrent-ruby-edge"] + dirs.each do |dir| + Dir.glob("#{dir}/**/*.rb") do |file| + require_path = file[dir.size + 1...-3] + private_file = %w[ruby_ java_ jruby_ truffleruby_].any? { |prefix| + File.basename(require_path).start_with?(prefix) + } + unless private_file + require_paths << require_path + end + end + end + + require_paths.each do |require_path| + # An easy way to see the output and backtrace without RSpec formatting it + # raise require_path unless system RbConfig.ruby, '-w', '-e', 'require ARGV.first', require_path + + # puts require_path + out = IO.popen([RbConfig.ruby, '-w', '-e', 'require ARGV.first', require_path], err: [:child, :out], &:read) + status = $? + expect(out).to eq "" + expect(status).to be_success + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/scheduled_task_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/scheduled_task_spec.rb new file mode 100644 index 0000000000..fc5af91430 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/scheduled_task_spec.rb @@ -0,0 +1,282 @@ +require 'concurrent/scheduled_task' +require 'concurrent/atomic/count_down_latch' +require 'timecop' +require_relative 'concern/dereferenceable_shared' +require_relative 'concern/obligation_shared' +require_relative 'concern/observable_shared' + +module Concurrent + + RSpec.describe ScheduledTask do + + context 'behavior' do + + let!(:fulfilled_value) { 10 } + let!(:rejected_reason) { StandardError.new('mojo jojo') } + + let(:pending_subject) do + ScheduledTask.new(1){ fulfilled_value }.execute + end + + let(:fulfilled_subject) do + ScheduledTask.new(0, executor: :immediate){ fulfilled_value }.execute + end + + let(:rejected_subject) do + ScheduledTask.new(0, executor: :immediate){ raise rejected_reason }.execute + end + + def dereferenceable_subject(value, opts = {}) + task = ScheduledTask.execute(0, opts){ value }.execute + task.value + task + end + + def dereferenceable_observable(opts = {}) + ScheduledTask.new(0, opts){ 'value' } + end + + def execute_dereferenceable(subject) + subject.execute + subject.value + end + + def trigger_observable(observable) + observable.execute + sleep(0.2) + end + + subject{ ScheduledTask.new(0.1){ nil } } + + it_should_behave_like :obligation + it_should_behave_like :dereferenceable + it_should_behave_like :observable + end + + context '#initialize' do + + it 'accepts a number of seconds (from now) as the schedule time' do + expected = 60 + Timecop.freeze do + task = ScheduledTask.new(expected){ nil }.execute + expect(task.initial_delay).to be_within(0.1).of(expected) + end + end + + it 'raises an exception when seconds is less than zero' do + expect { + ScheduledTask.new(-1){ nil } + }.to raise_error(ArgumentError) + end + + it 'raises an exception when no block given' do + expect { + ScheduledTask.new(1) + }.to raise_error(ArgumentError) + end + + it 'sets the initial state to :unscheduled' do + task = ScheduledTask.new(1){ nil } + expect(task).to be_unscheduled + end + end + + context 'instance #execute' do + + it 'does nothing unless the state is :unscheduled' do + expect(Concurrent).not_to receive(:timer).with(any_args) + task = ScheduledTask.new(1){ nil } + task.instance_variable_set(:@state, :pending) + task.execute + task.instance_variable_set(:@state, :rejected) + task.execute + task.instance_variable_set(:@state, :fulfilled) + task.execute + end + + it 'sets the sate to :pending' do + task = ScheduledTask.new(1){ nil } + task.execute + expect(task).to be_pending + end + + it 'returns self' do + task = ScheduledTask.new(1){ nil } + expect(task.execute).to eq task + end + end + + context 'class #execute' do + + it 'creates a new ScheduledTask' do + task = ScheduledTask.execute(1){ nil } + expect(task).to be_a(ScheduledTask) + end + + it 'passes the block to the new ScheduledTask' do + @expected = false + task = ScheduledTask.execute(0.1){ @expected = true } + task.value(1) + expect(@expected).to be_truthy + end + + it 'calls #execute on the new ScheduledTask' do + task = ScheduledTask.new(0.1){ nil } + allow(ScheduledTask).to receive(:new).with(any_args).and_return(task) + expect(task).to receive(:execute).with(no_args) + ScheduledTask.execute(0.1){ nil } + end + end + + context 'execution' do + + it 'passes :args from the options to the block' do + expected = [1, 2, 3] + actual = nil + latch = Concurrent::CountDownLatch.new + ScheduledTask.execute(0, args: expected) do |*args| + actual = args + latch.count_down + end + latch.wait(2) + expect(actual).to eq expected + end + + it 'uses the :executor from the options' do + latch = Concurrent::CountDownLatch.new + executor = Concurrent::ImmediateExecutor.new + expect(executor).to receive(:post).once.with(any_args).and_call_original + ScheduledTask.execute(0, executor: executor) do + latch.count_down + end + latch.wait(2) + end + + it 'uses the :timer_set from the options' do + timer = Concurrent::TimerSet.new + queue = timer.instance_variable_get(:@queue) + task = ScheduledTask.execute(1, timer_set: timer){ nil } + expect(queue.size).to eq 1 + task.cancel + end + + it 'sets the state to :processing when the task is running' do + start_latch = Concurrent::CountDownLatch.new(1) + continue_latch = Concurrent::CountDownLatch.new(1) + task = ScheduledTask.new(0.1) { + start_latch.count_down + continue_latch.wait(2) + }.execute + start_latch.wait(2) + state = task.state + continue_latch.count_down + expect(state).to eq :processing + end + end + + context '#cancel' do + + it 'returns false if the task has already been performed' do + task = ScheduledTask.new(0.1){ 42 }.execute + task.value(1) + expect(task.cancel).to be_falsey + end + + it 'returns false if the task is already in progress' do + latch = Concurrent::CountDownLatch.new(1) + task = ScheduledTask.new(0.1) { + latch.count_down + sleep(1) + }.execute + latch.wait(1) + expect(task.cancel).to be_falsey + end + + it 'cancels the task if it has not yet scheduled' do + latch = Concurrent::CountDownLatch.new(1) + task = ScheduledTask.new(0.1){ latch.count_down } + task.cancel + task.execute + expect(latch.wait(0.3)).to be_falsey + end + + it 'cancels the task if it has not yet started' do + latch = Concurrent::CountDownLatch.new(1) + task = ScheduledTask.new(0.3){ latch.count_down }.execute + sleep(0.1) + task.cancel + expect(latch.wait(0.5)).to be_falsey + end + + it 'returns true on success' do + task = ScheduledTask.new(10){ nil }.execute + sleep(0.1) + expect(task.cancel).to be_truthy + end + + it 'sets the reason to CancelledOperationError when cancelled' do + task = ScheduledTask.new(10){ 42 }.execute + sleep(0.1) + task.cancel + expect(task).to be_rejected + expect(task.reason).to be_a CancelledOperationError + end + end + + context 'observation' do + + let(:clazz) do + Class.new do + attr_reader :value + attr_reader :reason + attr_reader :count + attr_reader :latch + def initialize + @latch = Concurrent::CountDownLatch.new(1) + @count = 0 + end + def update(time, value, reason) + @count = @count.to_i + 1 + @value = value + @reason = reason + @latch.count_down + end + end + end + + let(:observer) { clazz.new } + + it 'returns true for an observer added while :unscheduled' do + task = ScheduledTask.new(0.1){ 42 } + expect(task.add_observer(observer)).to be_truthy + end + + it 'returns true for an observer added while :pending' do + task = ScheduledTask.new(0.1){ 42 }.execute + expect(task.add_observer(observer)).to be_truthy + end + + it 'returns true for an observer added while :processing' do + task = ScheduledTask.new(0.1){ sleep(1); 42 }.execute + sleep(0.2) + expect(task.add_observer(observer)).to be_truthy + end + + it 'notifies all observers on fulfillment' do + task = ScheduledTask.new(0.1){ 42 }.execute + task.add_observer(observer) + observer.latch.wait(1) + expect(observer.value).to eq(42) + expect(observer.reason).to be_nil + end + + it 'notifies all observers on rejection' do + task = ScheduledTask.new(0.1){ raise StandardError }.execute + task.add_observer(observer) + observer.latch.wait(1) + expect(observer.value).to be_nil + expect(observer.reason).to be_a(StandardError) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/set_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/set_spec.rb new file mode 100644 index 0000000000..a7c940a28c --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/set_spec.rb @@ -0,0 +1,119 @@ +require 'concurrent/set' +require 'concurrent/atomic/cyclic_barrier' + +module Concurrent + RSpec.describe Set do + let!(:set) { described_class.new } + + describe '.[]' do + describe 'when initializing with no arguments' do + it do + expect(described_class[]).to be_empty + end + end + + describe 'when initializing with arguments' do + it 'creates a set with the given objects' do + expect(described_class[:hello, :world]).to eq ::Set.new([:hello, :world]) + end + end + end + + describe '.new' do + describe 'when initializing with no arguments' do + it do + expect(described_class.new).to be_empty + end + end + + describe 'when initializing with an enumerable object' do + let(:enumerable_object) { [:hello, :world] } + + it 'creates a set with the contents of the enumerable object' do + expect(described_class.new(enumerable_object)).to eq ::Set.new([:hello, :world]) + end + + describe 'when initializing with a block argument' do + let(:block_argument) { proc { |value| :"#{value}_ruby" } } + + it 'creates a set with the contents of the enumerable object' do + expect(described_class.new(enumerable_object, &block_argument)).to eq ::Set.new([:hello_ruby, :world_ruby]) + end + end + end + end + + context 'concurrency' do + it '#add and #delete' do + (1..Concurrent::ThreadSafe::Test::THREADS).map do |i| + in_thread do + 1000.times do + v = i + set << v + expect(set).not_to be_empty + set.delete(v) + end + end + end.map(&:join) + expect(set).to be_empty + end + + it 'force context switch' do + barrier = Concurrent::CyclicBarrier.new(2) + + # methods like include? or delete? are implemented for CRuby in Ruby itself + # @see https://github.com/ruby/ruby/blob/master/lib/set.rb + set.clear + + # add a single element + set.add(1) + + # This thread should start and `Set#reject!` in CRuby should cache a value of `0` for size + thread_reject = in_thread do + # we expect this to return nil since nothing should have changed. + expect(set.reject! do |v| + barrier.wait + v == 1 # only delete the 1 value + end).to eq set + end + + thread_add = in_thread do + barrier.wait + expect(set.add?(1)).to eq set + end + + join_with [thread_reject, thread_add] + end + + it '#each' do + threads = [] + ("a".."z").inject(set, &:<<) # setup a non-empty set + + threads << in_thread do + 2000.times do + size = nil + set.each do |member| + if size.nil? + size = set.length + else + expect(set.length).to eq(size) + end + end + end + end + + threads += (1..19).map do |i| + in_thread do + v = i * 1000 + 10.times do + 200.times { |j| set << (v+j) } + 200.times { |j| set.delete(v+j) } + end + end + end + + threads.map(&:join) + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/settable_struct_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/settable_struct_spec.rb new file mode 100644 index 0000000000..eaae34427f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/settable_struct_spec.rb @@ -0,0 +1,202 @@ +require_relative 'struct_shared' +require 'concurrent/settable_struct' + +module Concurrent + RSpec.describe SettableStruct do + + it_should_behave_like :struct + it_should_behave_like :mergeable_struct + + let(:anonymous) { described_class.new(:name, :address, :zip) } + let!(:customer) { described_class.new('Customer', :name, :address, :zip) } + let(:joe) { customer.new('Joe Smith', '123 Maple, Anytown NC', 12345) } + let(:empty) { customer.new } + + subject { anonymous.new } + + context 'definition' do + + it 'defines a setter for each member' do + members = [:Foo, :bar, 'baz'] + structs = [ + described_class.new(*members).new, + described_class.new('ClassForCheckingSetterDefinition', *members).new + ] + + structs.each do |struct| + members.each do |member| + func = "#{member}=" + expect(struct).to respond_to func + method = struct.method(func) + expect(method.arity).to eq 1 + end + end + end + end + + context '#[member]=' do + + it 'sets the value when given a valid symbol member' do + expect(empty[:name] = 'Jane').to eq 'Jane' + expect(subject[:name] = 'Jane').to eq 'Jane' + end + + it 'sets the value when given a valid string member' do + expect(empty['name'] = 'Jane').to eq 'Jane' + expect(subject['name'] = 'Jane').to eq 'Jane' + end + + it 'raises an exception when given a non-existent symbol member' do + expect{empty[:fooarbaz] = 'Jane'}.to raise_error(NameError) + expect{subject[:fooarbaz] = 'Jane'}.to raise_error(NameError) + end + + it 'raises an exception when given a non-existent string member' do + expect{empty['fooarbaz'] = 'Jane'}.to raise_error(NameError) + expect{subject['fooarbaz'] = 'Jane'}.to raise_error(NameError) + end + + it 'raises an exception when given a symbol member that has already been set' do + empty[:name] = 'John' + subject[:name] = 'John' + expect{empty[:name] = 'Jane'}.to raise_error(ImmutabilityError) + expect{subject[:name] = 'Jane'}.to raise_error(ImmutabilityError) + end + + it 'raises an exception when given a string member that has already been set' do + empty['name'] = 'John' + subject['name'] = 'John' + expect{empty['name'] = 'Jane'}.to raise_error(ImmutabilityError) + expect{subject['name'] = 'Jane'}.to raise_error(ImmutabilityError) + end + end + + context '#[index]=' do + + it 'sets the value when given a valid index' do + expect(empty[0] = 'Jane').to eq 'Jane' + expect(subject[0] = 'Jane').to eq 'Jane' + end + + it 'raises an exception when given an out-of-bound index' do + expect{empty[100] = 'Jane'}.to raise_error(IndexError) + expect{subject[100] = 'Jane'}.to raise_error(IndexError) + end + + it 'raises an exception when given an index that has already been set' do + empty[0] = 'John' + subject[0] = 'John' + expect{empty[0] = 'Jane'}.to raise_error(ImmutabilityError) + expect{subject[0] = 'Jane'}.to raise_error(ImmutabilityError) + end + end + + context 'synchronization' do + + it 'protects #values' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.values + end + + it 'protects #values_at' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.values_at(0) + end + + it 'protects #[index]' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject[0] + end + + it 'protects #[member]' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject[:name] + end + + it 'protects getter methods' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.name + end + + it 'protects #[index]=' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject[0] = :foo + end + + it 'protects #[member]=' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject[:name] = :foo + end + + it 'protects getter methods' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.name = :foo + end + + it 'protects #to_s' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.to_s + end + + it 'protects #inspect' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.inspect + end + + it 'protects #to_h' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.to_h + end + + it 'protects #merge' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.merge(subject.members.first => subject.values.first) + end + + it 'protects #==' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject == joe + end + + it 'protects #each' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.each{|value| nil } + end + + it 'protects #each_pair' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.each_pair{|member, value| nil } + end + + it 'protects #select' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.select{|value| false } + end + + it 'protects #initialize_copy' do + expect(subject).to receive(:synchronize).at_least(:once).with(no_args).and_call_original + subject.clone + end + end + + context 'copy' do + context '#dup' do + it 'retains settability of members' do + subject['name'] = 'John' + expect { subject.dup['name'] = 'Jane' } + .to raise_error(ImmutabilityError) + expect(subject['address'] = 'Earth').to eq('Earth') + end + end + + context '#clone' do + it 'retains settability of members' do + subject['name'] = 'John' + expect { subject.clone['name'] = 'Jane' } + .to raise_error(ImmutabilityError) + expect(subject['address'] = 'Earth').to eq('Earth') + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/struct_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/struct_shared.rb new file mode 100644 index 0000000000..ae812f223f --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/struct_shared.rb @@ -0,0 +1,535 @@ +RSpec.shared_examples :struct do + + context 'definition' do + + it 'registers the class when given a class name' do + class_name = 'ValidClassName' + clazz = described_class.new(class_name) + expect{ described_class.const_get(class_name) }.to_not raise_error + expect(clazz).to be_a Class + expect(clazz.ancestors).to include described_class + end + + it 'registers the class when given a class name which is defined in the ancestors' do + class_name = 'ValidClassName2' + Object.const_set(class_name, class_name) + clazz = described_class.new(class_name) + expect{ described_class.const_get(class_name) }.to_not raise_error + expect(clazz).to be_a Class + expect(clazz.ancestors).to include described_class + end + + it 'creates an anonymous class when given at least one member' do + clazz = described_class.new(:foo) + expect{ described_class.const_get(clazz.to_s) }.to raise_error(NameError) + expect(clazz).to be_a Class + expect(clazz.ancestors).to include described_class + end + + it 'raises an exception when given an invalid class name' do + expect{ described_class.new('lowercase') }.to raise_error(NameError) + expect{ described_class.new('_') }.to raise_error(NameError) + expect{ described_class.new('1') }.to raise_error(NameError) + end + + it 'defines a getter for each member' do + members = [:Foo, :bar, 'baz'] + structs = [ + described_class.new(*members).new, + described_class.new('ClassForCheckingGetterDefinition', *members).new + ] + + structs.each do |struct| + members.each do |member| + expect(struct).to respond_to member + method = struct.method(member) + expect(method.arity).to eq 0 + end + end + end + + it 'raises an exception when given no members' do + expect{ described_class.new() }.to raise_error(ArgumentError) + end + + it 'raise an exception when given an invalid member' do + expect{ described_class.new('ClassForCheckingValidFieldNames1', 1) }.to raise_error(TypeError) + end + + it 'evalues a given block against the new class' do + clazz1 = described_class.new('ClassForCheckingBlockProcessing', :foo, :bar) do + def baz(foo, bar) foo + bar; end + end + clazz2 = described_class.new(:foo, :bar) do + def baz(foo, bar) foo + bar; end + end + + [clazz1, clazz2].each do |clazz| + struct = clazz.new + expect(struct).to respond_to :baz + expect(struct.method(:baz).arity).to eq 2 + expect(struct.baz(40, 2)).to eq 42 + end + end + end + + context 'construction' do + + let!(:members){ [:Foo, :bar, 'baz'] } + let!(:values){ [42, '42', :fortytwo] } + let!(:classes) do + [ + described_class.new(*members), + described_class.new('StructConstructionTester', *members) + ] + end + + it 'sets all absent members to nil' do + classes.each do |clazz| + struct = clazz.new + members.each do |member| + expect(struct.send(member)).to be_nil + end + end + end + + it 'sets all given members in order' do + classes.each do |clazz| + struct = clazz.new(*values) + members.each_with_index do |member, index| + expect(struct.send(member)).to eq values[index] + end + end + end + + it 'raises an exception when extra members are given' do + classes.each do |clazz| + extra_values = values << 'forty two' + expect{ clazz.new(*extra_values) }.to raise_error(ArgumentError) + end + end + end + + context 'properties' do + + let!(:anon_struct_members) { [:name, :address, :zip] } + let(:anon_struct) { described_class.new(*anon_struct_members) } + + let!(:named_struct_members) { [:left, :right] } + let(:named_struct) do + described_class.new("Test#{described_class}Properties".gsub(/::/, ''), + *named_struct_members) + end + + context '#length' do + + it 'returns the number of struct members' do + expect(anon_struct.new.length).to eq anon_struct_members.length + expect(named_struct.new.length).to eq named_struct_members.length + end + end + + context '#members' do + + it 'returns the struct members as an array of symbols' do + expect(anon_struct.new.members).to eq anon_struct_members + expect(named_struct.new.members).to eq named_struct_members + end + + it 'returns a different object than the array passed at definition' do + expect(anon_struct.new.members.object_id).to_not eq anon_struct_members.object_id + expect(named_struct.new.members.object_id).to_not eq named_struct_members.object_id + end + end + + context '#size' do + + it 'returns the number of struct members' do + expect(anon_struct.new.size).to eq anon_struct_members.size + expect(named_struct.new.size).to eq named_struct_members.size + end + end + + context '#values' do + + it 'returns the values of the struct as an array in order' do + expect(anon_struct.new().values).to eq [nil, nil, nil] + expect(named_struct.new().values).to eq [nil, nil] + + expect(anon_struct.new(:foo, :bar, :baz).values).to eq [:foo, :bar, :baz] + expect(named_struct.new(:yes, :no).values).to eq [:yes, :no] + end + end + + context '#values_at' do + + let(:anon_struct) do + described_class.new(:zero, :one, :two, :three, :four, :five, :six, :seven, :eight, :nine). + new(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + end + + let!(:named_struct) do + described_class.new("Test#{described_class}ValuesAtAccessor".gsub(/::/, ''), + :zero, :one, :two, :three, :four, :five, :six, :seven, :eight, :nine). + new(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + end + + it 'returns the value at the given offset' do + expect(anon_struct.values_at(3)).to eq [3] + expect(named_struct.values_at(7)).to eq [7] + end + + it 'returns the values at multiple given offsets' do + expect(anon_struct.values_at(4, 1, 7)).to eq [4, 1, 7] + expect(named_struct.values_at(2, 4, 6)).to eq [2, 4, 6] + end + + it 'returns values at offsets in a given range' do + expect(anon_struct.values_at(4..7)).to eq [4, 5, 6, 7] + expect(named_struct.values_at(1..3)).to eq [1, 2, 3] + end + + it 'returns values for multiple ranges' do + expect(anon_struct.values_at(1..3, 4..7)).to eq [1, 2, 3, 4, 5, 6, 7] + expect(named_struct.values_at(1..3, 4..7)).to eq [1, 2, 3, 4, 5, 6, 7] + end + + it 'returns values for ranges and offsets' do + expect(anon_struct.values_at(1, 2, 3, 4..7)).to eq [1, 2, 3, 4, 5, 6, 7] + expect(named_struct.values_at(1, 2, 3, 4..7)).to eq [1, 2, 3, 4, 5, 6, 7] + end + end + end + + context 'accessors' do + + let!(:anon_struct_members) { [:name, :address, :zip] } + let(:anon_struct) { described_class.new(*anon_struct_members) } + + let!(:named_struct_members) { [:left, :right] } + let(:named_struct) do + described_class.new("Test#{described_class}Properties".gsub(/::/, ''), + *named_struct_members) + end + + let(:anon_instance){ anon_struct.new('Douglass Adams', 'Earth', 42) } + let(:named_instance){ named_struct.new('up', 'down') } + + context '#[member]' do + + it 'retrieves the value when given a valid symbol member' do + expect(anon_instance[:address]).to eq 'Earth' + expect(named_instance[:right]).to eq 'down' + end + + it 'retrieves the value when given a valid string member' do + expect(anon_instance['address']).to eq 'Earth' + expect(named_instance['right']).to eq 'down' + end + + it 'raises an exception when given a non-existent symbol member' do + expect{anon_instance[:foo]}.to raise_error(NameError) + expect{named_instance[:bar]}.to raise_error(NameError) + end + + it 'raises an exception when given a non-existent string member' do + expect{anon_instance['foo']}.to raise_error(NameError) + expect{named_instance['bar']}.to raise_error(NameError) + end + end + + context '#[index]' do + + it 'retrieves the value when given a valid index' do + expect(anon_instance[1]).to eq 'Earth' + expect(named_instance[1]).to eq 'down' + end + + it 'raises an exception when given an out-of-bound index' do + expect{anon_instance[100]}.to raise_error(IndexError) + expect{named_instance[100]}.to raise_error(IndexError) + end + end + end + + context 'comparison' do + + let(:customer) { described_class.new(:name, :address, :zip) } + let(:employer) { described_class.new(:name, :address, :zip) } + + let!(:joe) { customer.new('Joe Smith', '123 Maple, Anytown NC', 12345) } + let!(:joejr) { customer.new('Joe Smith', '123 Maple, Anytown NC', 12345) } + let!(:jane) { customer.new('Jane Doe', '456 Elm, Anytown NC', 12345) } + let!(:janejr){ employer.new('Jane Doe', '456 Elm, Anytown NC', 12345) } + + context '#==' do + + it 'returns true if other has same struct subclass and equal values' do + expect(joe == joejr).to be true + end + + it 'returns false if other has different struct subclass' do + expect(jane == janejr).to be false + end + + it 'returns false if other has different values' do + expect(jane == joe).to be false + end + end + + context '#!=' do + + it 'returns false if other has same struct subclass and equal values' do + expect(joe != joejr).to be false + end + + it 'returns true if other has different struct subclass' do + expect(jane != janejr).to be true + end + + it 'returns true if other has different values' do + expect(jane != joe).to be true + end + end + end + + context 'enumeration' do + + let(:members) { [:name, :address, :zip] } + let(:values) { ['Joe Smith', '123 Maple, Anytown NC', 12345] } + + let(:customer) { described_class.new(*members) } + let!(:joe) { customer.new(*values) } + + context '#each' do + + it 'yields the value of each struct member in order' do + index = 0 + joe.each do |value| + expect(joe[index]).to eq value + index += 1 + end + expect(index).to eq 3 + end + + it 'returns an enumerator when no block is given' do + expect(joe.each).to be_a Enumerator + end + end + + context '#each_pair' do + + it 'yields the name and value of each struct member in order' do + index = 0 + joe.each_pair do |name, value| + expect(joe.members[index]).to eq name + expect(joe[index]).to eq value + index += 1 + end + expect(index).to eq 3 + end + + it 'returns an enumerator when no block is given' do + expect(joe.each_pair).to be_a Enumerator + end + end + + context '#select' do + + it 'yields each value' do + index = 0 + joe.select do |value| + expect(joe[index]).to eq value + index += 1 + end + expect(index).to eq 3 + end + + it 'returns an Array with the values from for which the block returns true' do + result = joe.select{|value| value.is_a?(String) } + expect(result).to eq ['Joe Smith', '123 Maple, Anytown NC'] + end + + it 'returns an enumerator when no block is given' do + expect(joe.select).to be_a Enumerator + end + end + end + + context 'conversion' do + + let!(:anon_struct_members) { [:name, :address, :zip] } + let(:anon_struct) { described_class.new(*anon_struct_members) } + + let!(:named_struct_members) { [:left, :right] } + let(:named_struct) do + described_class.new("Test#{described_class}Properties".gsub(/::/, ''), + *named_struct_members) + end + + context '#to_s' do + + it 'includes the name of the class when registered' do + expect(named_struct.new.to_s).to match(/#{named_struct}/) + end + + it 'includes the names of all members' do + string = anon_struct.new.to_s + anon_struct_members.each do |member| + expect(string).to match(/#{member}/) + end + + string = named_struct.new.to_s + named_struct_members.each do |member| + expect(string).to match(/#{member}/) + end + end + + it 'includes all values' do + values = [:foo, 'bar', 42] + string = anon_struct.new(*values).to_s + values.each do |value| + expect(string).to match(/#{value}/) + end + + values = ['bar', 42] + string = named_struct.new(*values).to_s + values.each do |value| + expect(string).to match(/#{value}/) + end + end + + it 'returns the same string as #inspect' do + values = [:foo, 'bar', 42] + struct = anon_struct.new(*values) + expect(struct.to_s).to eq struct.inspect + + values = ['bar', 42] + struct = named_struct.new(*values) + expect(struct.to_s).to eq struct.inspect + end + end + + context '#to_a' do + it 'returns the to_a for this struct as an array' do + expect(anon_struct.new().to_a).to eq [nil, nil, nil] + expect(named_struct.new().to_a).to eq [nil, nil] + + expect(anon_struct.new(:foo, :bar, :baz).to_a).to eq [:foo, :bar, :baz] + expect(named_struct.new(:yes, :no).to_a).to eq [:yes, :no] + end + end + + context '#to_h' do + + it 'returns a Hash containing the names and values in order' do + expected = {name: nil, address: nil, zip: nil} + expect(anon_struct.new().to_h).to eq expected + + expected = {left: nil, right: nil} + expect(named_struct.new().to_h).to eq expected + + expected = {name: :foo, address: :bar, zip: :baz} + expect(anon_struct.new(:foo, :bar, :baz).to_h).to eq expected + + expected = {left: :yes, right: :no} + expect(named_struct.new(:yes, :no).to_h).to eq expected + end + end + end + + context 'copy' do + let(:this) do + described_class.new(:foo, :bar, :baz).new('foo'.freeze, ['bar'], 42) + end + + context '#dup' do + it 'shallowly duplicates all members along with the struct' do + copy = this.dup + expect(copy.foo).not_to be this.foo + expect(copy.bar).not_to be this.bar + expect(copy.bar.first).to be this.bar.first + expect(copy.baz).to be this.baz + end + + it 'discards frozen state of the struct' do + expect(this.freeze.dup).not_to be_frozen + end + + it 'retains frozen state of members' do + expect(this.dup.foo).to be_frozen + end + + it 'discards singleton class' do + this.define_singleton_method(:qux) { 'qux' } + expect(this.qux).to eq('qux') + expect{this.dup.qux}.to raise_error(NoMethodError) + end + + it 'copies the singleton class of members' do + this.bar.define_singleton_method(:qux) { 'qux' } + expect(this.bar.qux).to eq('qux') + expect(this.dup.bar.qux).to eq('qux') + end + end + + context '#clone' do + it 'shallowly clones all members along with the struct' do + copy = this.clone + expect(copy.foo).not_to be this.foo + expect(copy.bar).not_to be this.bar + expect(copy.bar.first).to be this.bar.first + expect(copy.baz).to be this.baz + end + + it 'retains frozen state' do + expect(this.freeze.clone).to be_frozen + expect(this.clone.foo).to be_frozen + end + + it 'copies the singleton class' do + this.define_singleton_method(:qux) { 'qux' } + expect(this.qux).to eq('qux') + expect(this.clone.qux).to eq('qux') + end + + it 'copies the singleton class of members' do + this.bar.define_singleton_method(:qux) { 'qux' } + expect(this.bar.qux).to eq('qux') + expect(this.clone.bar.qux).to eq('qux') + end + end + end +end + +RSpec.shared_examples :mergeable_struct do + + let(:this){ described_class.new(:foo, :bar, :baz).new('foo', nil, nil)} + let(:other){ {baz: 42} } + + context '#merge' do + it 'updates all members with the new values from a given hash' do + expect(this.merge(other).baz).to eq 42 + end + + it 'calls the given block for each key in `other`' do + actual = 0 + this = described_class.new(:foo, :bar, :baz).new('foo', :bar, 42) + this.merge(bar: :yes, baz: :no){|member, thisval, otherval| actual += 1 } + expect(actual).to eq 2 + end + + it 'retains the value for all members not without values in the given hash' do + expect(this.merge(other).foo).to eq 'foo' + end + + it 'raises an exception when given a hash with members not in the struct' do + expect{this.merge(bogus: true)}.to raise_exception(ArgumentError) + end + + it 'returns a new object' do + expect(this.merge(other).object_id).to_not eq this.object_id + expect(this.merge(other).object_id).to_not eq other.object_id + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/synchronization_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/synchronization_spec.rb new file mode 100644 index 0000000000..3ddccf8e86 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/synchronization_spec.rb @@ -0,0 +1,245 @@ +require 'timeout' +require 'concurrent/synchronization' + +module Concurrent + + RSpec.describe Synchronization do + + RSpec.shared_examples :attr_volatile do + + specify 'older writes are always visible' do + store = store() + store.not_volatile = 0 + store.volatile = 0 + @stop = false + + in_thread do + Thread.abort_on_exception = true + 1000000000.times do |i| + store.not_volatile = i + store.volatile = i + break if @stop # on JRuby this is not kill-able loop + end + end + + t2 = in_thread do + Thread.abort_on_exception = true + 10.times.map do + Thread.pass + volatile = store.volatile + not_volatile = store.not_volatile + not_volatile >= volatile + end + end + + expect(t2.value.all?).to eq true + @stop = true + end + end + + describe Synchronization::Object do + class AAClass < Synchronization::Object + end + + class ABClass < AAClass + safe_initialization! + end + + class ACClass < ABClass + end + + class ADClass < ACClass + safe_initialization! + end + + it 'does not ensure visibility when not needed' do + expect(Concurrent::Synchronization).not_to receive(:full_memory_barrier) + AAClass.new + end + + it "does ensure visibility when specified" do + expect(Concurrent::Synchronization).to receive(:full_memory_barrier).exactly(:once) + ABClass.new + end + + it "does ensure visibility when specified in a parent" do + expect(Concurrent::Synchronization).to receive(:full_memory_barrier).exactly(:once) + ACClass.new + end + + it "does ensure visibility once when specified in child again" do + expect(Concurrent::Synchronization).to receive(:full_memory_barrier).exactly(:once) + ADClass.new + end + + # TODO (pitr 12-Sep-2015): give a whole gem a pass to find classes with final fields without using the convention and migrate + Synchronization::Object.ensure_safe_initialization_when_final_fields_are_present + + class VolatileFieldClass < Synchronization::Object + attr_volatile :volatile + attr_accessor :not_volatile + end + + let(:store) { VolatileFieldClass.new } + it_should_behave_like :attr_volatile + end + + describe Synchronization::LockableObject do + + class BClass < Synchronization::LockableObject + safe_initialization! + + attr_volatile :volatile + attr_accessor :not_volatile + + def initialize(value = nil) + super() + @Final = value + ns_initialize + end + + def final + @Final + end + + def count + synchronize { @count += 1 } + end + + def wait(timeout = nil) + synchronize { ns_wait(timeout) } + end + + public :synchronize + + private + + def ns_initialize + @count = 0 + end + end + + subject { BClass.new } + + describe '#wait' do + + it 'puts the current thread to sleep' do + t1 = in_thread do + Thread.abort_on_exception = true + subject.wait + end + t2 = in_thread { Thread.pass until t1.status == 'sleep' } + join_with t2 + end + + it 'allows the sleeping thread to be killed' do + t = in_thread do + Thread.abort_on_exception = true + subject.wait rescue nil + end + sleep 0.1 + t.kill + sleep 0.1 + expect(t.join).not_to eq nil + expect(t.alive?).to eq false + end + + it 'releases the lock on the current object' do + t1 = in_thread do + # #wait should release lock, even if it was already held on entry + t2 = in_thread { subject.wait } + Thread.pass until t2.status == 'sleep' + subject.synchronize {} # it will deadlock here if #wait doesn't release lock + t2 + end + join_with t1 + repeat_until_success { expect(t1.value.status).to eq 'sleep' } + end + + it 'can be called from within a #synchronize block' do + t1 = in_thread do + t2 = in_thread { subject.synchronize { subject.wait } } + Thread.pass until t2.status == 'sleep' + subject.synchronize {} # it will deadlock here if #wait doesn't release lock + t2 + end + join_with t1 + repeat_until_success { expect(t1.value.status).to eq 'sleep' } + end + end + + describe '#synchronize' do + it 'allows only one thread to execute count' do + threads = 10.times.map { in_thread(subject) { 100.times { subject.count } } } + threads.each(&:join) + expect(subject.count).to eq 1001 + end + end + + describe 'signaling' do + pending 'for now pending, tested pretty well by Event' + end + + specify 'final field always visible' do + require 'concurrent/atomic/count_down_latch' + store = BClass.new 'asd' + done = CountDownLatch.new + in_thread do + 1000000000.times do |i| + store = BClass.new i.to_s + break if done.count == 0 + end + end + in_thread do + 10.times do + expect(store.final).not_to be_nil + Thread.pass + end + done.count_down + end + end + + let(:store) { BClass.new } + it_should_behave_like :attr_volatile + end + + describe 'Concurrent::Synchronization::Volatile module' do + class BareClass + include Synchronization::Volatile + + attr_volatile :volatile + attr_accessor :not_volatile + end + + let(:store) { BareClass.new } + it_should_behave_like :attr_volatile + end + + describe 'attr_atomic' do + specify do + a = Class.new(Synchronization::Object) do + attr_atomic :a + + def initialize(*rest) + super + self.a = :a + end + end + + b = Class.new(a) do + attr_atomic :b + + def initialize + super + self.b = :b + end + end + + instance = b.new + expect(instance.a).to be == :a + expect(instance.b).to be == :b + end + end + + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_arguments_shared.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_arguments_shared.rb new file mode 100644 index 0000000000..8f3f386ba2 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_arguments_shared.rb @@ -0,0 +1,46 @@ + +RSpec.shared_examples :thread_arguments do + + it 'passes an empty array when opts is not given' do + future = get_ivar_from_no_args + expect(future.value).to eq [] + end + + it 'passes an empty array when opts is an empty hash' do + future = get_ivar_from_args({}) + expect(future.value).to eq [] + end + + it 'passes an empty array when there is no :args key' do + future = get_ivar_from_args(foo: 'bar') + expect(future.value).to eq [] + end + + it 'passes an empty array when the :args key has a nil value' do + future = get_ivar_from_args(args: nil) + expect(future.value).to eq [] + end + + it 'passes a one-element array when the :args key has a non-array value' do + future = get_ivar_from_args(args: 'foo') + expect(future.value).to eq ['foo'] + end + + it 'passes an array when when the :args key has an array value' do + expected = [1, 2, 3, 4] + future = get_ivar_from_args(args: expected) + expect(future.value).to eq expected + end + + it 'passes the given array when the :args key has a complex array value' do + expected = [(1..10).to_a, (20..30).to_a, (100..110).to_a] + future = get_ivar_from_args(args: expected) + expect(future.value).to eq expected + end + + it 'allows the given arguments array to be dereferenced' do + expected = [1, 2, 3, 4] + future = get_ivar_from_args(args: expected) + expect(future.value).to eq expected + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/map_loops_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/map_loops_spec.rb new file mode 100644 index 0000000000..cf36f27971 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/map_loops_spec.rb @@ -0,0 +1,508 @@ +Thread.abort_on_exception = true + +module Concurrent + + RSpec.describe 'MapTorture', stress: true do + THREAD_COUNT = 40 + KEY_COUNT = (((2**13) - 2) * 0.75).to_i # get close to the doubling cliff + LOW_KEY_COUNT = (((2**8 ) - 2) * 0.75).to_i # get close to the doubling cliff + + INITIAL_VALUE_CACHE_SETUP = lambda do |options, keys| + cache = Concurrent::Map.new + initial_value = options[:initial_value] || 0 + keys.each { |key| cache[key] = initial_value } + cache + end + ZERO_VALUE_CACHE_SETUP = lambda do |options, keys| + INITIAL_VALUE_CACHE_SETUP.call(options.merge(:initial_value => 0), keys) + end + + DEFAULTS = { + key_count: KEY_COUNT, + thread_count: THREAD_COUNT, + loop_count: 1, + prelude: '', + cache_setup: lambda { |options, keys| Concurrent::Map.new } + } + + LOW_KEY_COUNT_OPTIONS = {loop_count: 150, key_count: LOW_KEY_COUNT} + SINGLE_KEY_COUNT_OPTIONS = {loop_count: 100_000, key_count: 1} + + it 'concurrency' do + code = <<-RUBY_EVAL + cache[key] + cache[key] = key + cache[key] + cache.delete(key) + RUBY_EVAL + do_thread_loop(:concurrency, code) + end + + it '#put_if_absent' do + do_thread_loop( + :put_if_absent, + 'acc += 1 unless cache.put_if_absent(key, key)', + key_count: 100_000 + ) do |result, cache, options, keys| + expect_standard_accumulator_test_result(result, cache, options, keys) + end + end + + it '#compute_put_if_absent' do + code = <<-RUBY_EVAL + if key.even? + cache.compute_if_absent(key) { acc += 1; key } + else + acc += 1 unless cache.put_if_absent(key, key) + end + RUBY_EVAL + do_thread_loop(:compute_if_absent, code) do |result, cache, options, keys| + expect_standard_accumulator_test_result(result, cache, options, keys) + end + end + + it '#compute_if_absent_and_present' do + compute_if_absent_and_present + compute_if_absent_and_present(LOW_KEY_COUNT_OPTIONS) + compute_if_absent_and_present(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'add_remove_to_zero' do + add_remove_to_zero + add_remove_to_zero(LOW_KEY_COUNT_OPTIONS) + add_remove_to_zero(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'add_remove_to_zero_via_merge_pair' do + add_remove_to_zero_via_merge_pair + add_remove_to_zero_via_merge_pair(LOW_KEY_COUNT_OPTIONS) + add_remove_to_zero_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'add_remove' do + add_remove + add_remove(LOW_KEY_COUNT_OPTIONS) + add_remove(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'add_remove_via_compute' do + add_remove_via_compute + add_remove_via_compute(LOW_KEY_COUNT_OPTIONS) + add_remove_via_compute(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'add_remove_via_compute_if_absent_present' do + add_remove_via_compute_if_absent_present + add_remove_via_compute_if_absent_present(LOW_KEY_COUNT_OPTIONS) + add_remove_via_compute_if_absent_present(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'add_remove_indiscriminate' do + add_remove_indiscriminate + add_remove_indiscriminate(LOW_KEY_COUNT_OPTIONS) + add_remove_indiscriminate(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'count_up' do + count_up + count_up(LOW_KEY_COUNT_OPTIONS) + count_up(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'count_up_via_compute' do + count_up_via_compute + count_up_via_compute(LOW_KEY_COUNT_OPTIONS) + count_up_via_compute(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'count_up_via_merge_pair' do + count_up_via_merge_pair + count_up_via_merge_pair(LOW_KEY_COUNT_OPTIONS) + count_up_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS) + end + + it 'count_race' do + prelude = 'change = (rand(2) == 1) ? 1 : -1' + code = <<-RUBY_EVAL + v = cache[key] + acc += change if cache.replace_pair(key, v, v + change) + RUBY_EVAL + do_thread_loop( + :count_race, + code, + loop_count: 5, + prelude: prelude, + cache_setup: ZERO_VALUE_CACHE_SETUP + ) do |result, cache, options, keys| + result_sum = sum(result) + expect(sum(keys.map { |key| cache[key] })).to eq result_sum + expect(sum(cache.values)).to eq result_sum + expect(options[:key_count]).to eq cache.size + end + end + + it 'get_and_set_new' do + code = 'acc += 1 unless cache.get_and_set(key, key)' + do_thread_loop(:get_and_set_new, code) do |result, cache, options, keys| + expect_standard_accumulator_test_result(result, cache, options, keys) + end + end + + it 'get_and_set_existing' do + code = 'acc += 1 if cache.get_and_set(key, key) == -1' + do_thread_loop( + :get_and_set_existing, + code, + cache_setup: INITIAL_VALUE_CACHE_SETUP, + initial_value: -1 + ) do |result, cache, options, keys| + expect_standard_accumulator_test_result(result, cache, options, keys) + end + end + + private + + def compute_if_absent_and_present(opts = {}) + prelude = 'on_present = rand(2) == 1' + code = <<-RUBY_EVAL + if on_present + cache.compute_if_present(key) { |old_value| acc += 1; old_value + 1 } + else + cache.compute_if_absent(key) { acc += 1; 1 } + end + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5, prelude: prelude}.merge(opts) + ) do |result, cache, options, keys| + stored_sum = 0 + stored_key_count = 0 + keys.each do |k| + if value = cache[k] + stored_sum += value + stored_key_count += 1 + end + end + expect(stored_sum).to eq sum(result) + expect(stored_key_count).to eq cache.size + end + end + + def add_remove(opts = {}) + prelude = 'do_add = rand(2) == 1' + code = <<-RUBY_EVAL + if do_add + acc += 1 unless cache.put_if_absent(key, key) + else + acc -= 1 if cache.delete_pair(key, key) + end + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5, prelude: prelude}.merge(opts) + ) do |result, cache, options, keys| + expect_all_key_mappings_exist(cache, keys, false) + expect(cache.size).to eq sum(result) + end + end + + def add_remove_via_compute(opts = {}) + prelude = 'do_add = rand(2) == 1' + code = <<-RUBY_EVAL + cache.compute(key) do |old_value| + if do_add + acc += 1 unless old_value + key + else + acc -= 1 if old_value + nil + end + end + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5, prelude: prelude}.merge(opts) + ) do |result, cache, options, keys| + expect_all_key_mappings_exist(cache, keys, false) + expect(cache.size).to eq sum(result) + end + end + + def add_remove_via_compute_if_absent_present(opts = {}) + prelude = 'do_add = rand(2) == 1' + code = <<-RUBY_EVAL + if do_add + cache.compute_if_absent(key) { acc += 1; key } + else + cache.compute_if_present(key) { acc -= 1; nil } + end + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5, prelude: prelude}.merge(opts) + ) do |result, cache, options, keys| + expect_all_key_mappings_exist(cache, keys, false) + expect(cache.size).to eq sum(result) + end + end + + def add_remove_indiscriminate(opts = {}) + prelude = 'do_add = rand(2) == 1' + code = <<-RUBY_EVAL + if do_add + acc += 1 unless cache.put_if_absent(key, key) + else + acc -= 1 if cache.delete(key) + end + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5, prelude: prelude}.merge(opts) + ) do |result, cache, options, keys| + expect_all_key_mappings_exist(cache, keys, false) + expect(cache.size).to eq sum(result) + end + end + + def count_up(opts = {}) + code = <<-RUBY_EVAL + v = cache[key] + acc += 1 if cache.replace_pair(key, v, v + 1) + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5, cache_setup: ZERO_VALUE_CACHE_SETUP}.merge(opts) + ) do |result, cache, options, keys| + expect_count_up(result, cache, options, keys) + end + end + + def count_up_via_compute(opts = {}) + code = <<-RUBY_EVAL + cache.compute(key) do |old_value| + acc += 1 + old_value ? old_value + 1 : 1 + end + RUBY_EVAL + do_thread_loop( + __method__, + code, {loop_count: 5}.merge(opts) + ) do |result, cache, options, keys| + expect_count_up(result, cache, options, keys) + result.inject(nil) do |previous_value, next_value| # since compute guarantees atomicity all count ups should be equal + expect(previous_value).to eq next_value if previous_value + next_value + end + end + end + + def count_up_via_merge_pair(opts = {}) + code = <<-RUBY_EVAL + cache.merge_pair(key, 1) { |old_value| old_value + 1 } + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5}.merge(opts) + ) do |result, cache, options, keys| + all_match = true + expected_value = options[:loop_count] * options[:thread_count] + keys.each do |key| + value = cache[key] + if expected_value != value + all_match = false + break + end + end + expect(all_match).to be_truthy + end + end + + def add_remove_to_zero(opts = {}) + code = <<-RUBY_EVAL + acc += 1 unless cache.put_if_absent(key, key) + acc -= 1 if cache.delete_pair(key, key) + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5}.merge(opts) + ) do |result, cache, options, keys| + expect_all_key_mappings_exist(cache, keys, false) + expect(cache.size).to eq sum(result) + end + end + + def add_remove_to_zero_via_merge_pair(opts = {}) + code = <<-RUBY_EVAL + acc += (cache.merge_pair(key, key) {}) ? 1 : -1 + RUBY_EVAL + do_thread_loop( + __method__, + code, + {loop_count: 5}.merge(opts) + ) do |result, cache, options, keys| + expect_all_key_mappings_exist(cache, keys, false) + expect(cache.size).to eq sum(result) + end + end + + def do_thread_loop(name, code, options = {}, &block) + options = DEFAULTS.merge(options) + meth = define_loop(name, code, options[:prelude]) + keys = to_keys_array(options[:key_count]) + run_thread_loop(meth, keys, options, &block) + + if options[:key_count] > 1 + options[:key_count] = (options[:key_count] / 40).to_i + keys = to_hash_collision_keys_array(options[:key_count]) + run_thread_loop( + meth, + keys, + options.merge(loop_count: options[:loop_count] * 5), + &block + ) + end + end + + def run_thread_loop(meth, keys, options, &block) + cache = options[:cache_setup].call(options, keys) + barrier = Concurrent::ThreadSafe::Test::Barrier.new(options[:thread_count]) + result = (1..options[:thread_count]).map do + in_thread do + setup_sync_and_start_loop( + meth, + cache, + keys, + barrier, + options[:loop_count] + ) + end + end.map(&:value) + block.call(result, cache, options, keys) if block_given? + end + + def setup_sync_and_start_loop(meth, cache, keys, barrier, loop_count) + my_keys = keys.shuffle + barrier.await + if my_keys.size == 1 + key = my_keys.first + send("#{meth}_single_key", cache, key, loop_count) + else + send("#{meth}_multiple_keys", cache, my_keys, loop_count) + end + end + + def define_loop(name, body, prelude) + inner_meth_name = :"_#{name}_loop_inner" + outer_meth_name = :"_#{name}_loop_outer" + # looping is splitted into the "loop methods" to trigger the JIT + self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{inner_meth_name}_multiple_keys(cache, keys, i, length, acc) + #{prelude} + target = i + length + while i < target + key = keys[i] + #{body} + i += 1 + end + acc + end unless method_defined?(:#{inner_meth_name}_multiple_keys) + RUBY_EVAL + + self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{inner_meth_name}_single_key(cache, key, i, length, acc) + #{prelude} + target = i + length + while i < target + #{body} + i += 1 + end + acc + end unless method_defined?(:#{inner_meth_name}_single_key) + RUBY_EVAL + + self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{outer_meth_name}_multiple_keys(cache, keys, loop_count) + total_length = keys.size + acc = 0 + inc = 100 + loop_count.times do + i = 0 + pre_loop_inc = total_length % inc + acc = #{inner_meth_name}_multiple_keys(cache, keys, i, pre_loop_inc, acc) + i += pre_loop_inc + while i < total_length + acc = #{inner_meth_name}_multiple_keys(cache, keys, i, inc, acc) + i += inc + end + end + acc + end unless method_defined?(:#{outer_meth_name}_multiple_keys) + RUBY_EVAL + + self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{outer_meth_name}_single_key(cache, key, loop_count) + acc = 0 + i = 0 + inc = 100 + + pre_loop_inc = loop_count % inc + acc = #{inner_meth_name}_single_key(cache, key, i, pre_loop_inc, acc) + i += pre_loop_inc + + while i < loop_count + acc = #{inner_meth_name}_single_key(cache, key, i, inc, acc) + i += inc + end + acc + end unless method_defined?(:#{outer_meth_name}_single_key) + RUBY_EVAL + outer_meth_name + end + + def to_keys_array(key_count) + arr = [] + key_count.times {|i| arr << i} + arr + end + + def to_hash_collision_keys_array(key_count) + to_keys_array(key_count).map { |key| Concurrent::ThreadSafe::Test::HashCollisionKey(key) } + end + + def sum(result) + result.inject(0) { |acc, i| acc + i } + end + + def expect_standard_accumulator_test_result(result, cache, options, keys) + expect_all_key_mappings_exist(cache, keys) + expect(options[:key_count]).to eq sum(result) + expect(options[:key_count]).to eq cache.size + end + + def expect_all_key_mappings_exist(cache, keys, all_must_exist = true) + keys.each do |key| + value = cache[key] + if value || all_must_exist + expect(key).to eq value unless key == value # don't do a bazzilion assertions unless necessary + end + end + end + + def expect_count_up(result, cache, options, keys) + keys.each do |key| + value = cache[key] + expect(value).to be_truthy unless value + end + expect(sum(cache.values)).to eq sum(result) + expect(options[:key_count]).to eq cache.size + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/no_unsafe_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/no_unsafe_spec.rb new file mode 100644 index 0000000000..029c7a610d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/no_unsafe_spec.rb @@ -0,0 +1,29 @@ +require 'concurrent/utility/engine' + +if Concurrent.on_jruby? && ENV['TEST_NO_UNSAFE'] + # to be used like this: rake test TEST_NO_UNSAFE=true + load 'test/package.jar' + java_import 'thread_safe.SecurityManager' + manager = SecurityManager.new + + # Prevent accessing internal classes + manager.deny(java.lang.RuntimePermission.new('accessClassInPackage.sun.misc')) + java.lang.System.setSecurityManager(manager) + + module Concurrent + describe 'no_unsafe' do + it 'security_manager_is_used' do + begin + java_import 'sun.misc.Unsafe' + fail + rescue SecurityError + end + end + + it 'no_unsafe_version_of_chmv8_is_used' do + require 'concurrent/thread_safe/jruby_cache_backend' # make sure the jar has been loaded + expect(!Java::OrgJrubyExtThread_safe::JRubyMapBackendLibrary::JRubyMapBackend::CAN_USE_UNSAFE_CHM).to be_truthy + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/synchronized_delegator_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/synchronized_delegator_spec.rb new file mode 100644 index 0000000000..8d6f120eac --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/thread_safe/synchronized_delegator_spec.rb @@ -0,0 +1,85 @@ +require 'concurrent/thread_safe/synchronized_delegator' + +module Concurrent + RSpec.describe SynchronizedDelegator do + it 'wraps array' do + array = ::Array.new + sync_array = described_class.new(array) + + array << 1 + expect(1).to eq sync_array[0] + + sync_array << 2 + expect(2).to eq array[1] + end + + it 'synchronizes access' do + t1_continue, t2_continue = false, false + + hash = ::Hash.new do |the_hash, key| + t2_continue = true + unless the_hash.find { |e| e[1] == key.to_s } # just to do something + the_hash[key] = key.to_s + Thread.pass until t1_continue + end + end + sync_hash = described_class.new(hash) + sync_hash[1] = 'egy' + + t1 = in_thread do + sync_hash[2] = 'dva' + sync_hash[3] # triggers t2_continue + end + + t2 = in_thread do + Thread.pass until t2_continue + sync_hash[4] = '42' + end + + sleep(0.05) # sleep some to allow threads to boot + + until t2.status == 'sleep' do + Thread.pass + end + + expect(3).to eq hash.keys.size + + t1_continue = true + t1.join; t2.join + + expect(4).to eq sync_hash.size + end + + it 'synchronizes access with block' do + t1_continue, t2_continue = false, false + + array = ::Array.new + sync_array = described_class.new(array) + + t1 = in_thread do + sync_array << 1 + sync_array.each do + t2_continue = true + Thread.pass until t1_continue + end + end + + t2 = in_thread do + # sleep(0.01) + Thread.pass until t2_continue + sync_array << 2 + end + + until t2.status == 'sleep' || t2.status == false + Thread.pass + end + + expect(1).to eq array.size + + t1_continue = true + t1.join; t2.join + + expect([1, 2]).to eq array + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/throttle_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/throttle_spec.rb new file mode 100644 index 0000000000..17bbc0f8e6 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/throttle_spec.rb @@ -0,0 +1,96 @@ +require 'thread' +require 'concurrent/edge/throttle' + +RSpec.describe 'Concurrent' do + describe 'Throttle' do + specify 'acquiring' do + skip('flaky on truffleruby') if Concurrent.on_truffleruby? + + throttle = Concurrent::Throttle.new 2 + expect(throttle.max_capacity).to eq 2 + expect(throttle.available_capacity).to eq 2 + + expect(throttle.try_acquire).to be_truthy + expect(throttle.max_capacity).to eq 2 + expect(throttle.available_capacity).to eq 1 + + thread = in_thread { throttle.acquire; throttle.release; :ok } + expect(thread.value).to eq :ok + + expect(throttle.try_acquire).to be_truthy + expect(throttle.max_capacity).to eq 2 + expect(throttle.available_capacity).to eq 0 + + thread1 = in_thread { throttle.acquire(2); throttle.release; :ok } + thread2 = in_thread { throttle.acquire(0.01) { :ok } } + thread3 = in_thread { throttle.acquire(2) { :ok } } + is_sleeping thread1 + expect(thread2.value).to be_falsey + is_sleeping thread3 + + expect(throttle.try_acquire).to be_falsey + expect(throttle.max_capacity).to eq 2 + expect(throttle.available_capacity).to eq(0) + expect(throttle.send(:capacity)).to eq(-3) + + throttle.release + + expect(throttle.max_capacity).to eq 2 + expect(throttle.available_capacity).to eq 0 + expect(thread1.value).to eq :ok + expect(thread3.value).to eq :ok + end + + specify '#to_s' do + throttle = Concurrent::Throttle.new 2 + expect(throttle.to_s).to match(/Throttle.*available 2 of 2/) + end + + specify '#on' do + throttle = Concurrent::Throttle.new 2 + io_proxy = throttle.on :io + expect(throttle.on(:io)).to eq io_proxy + + expect(io_proxy.can_overflow?).to eq Concurrent.executor(:io).can_overflow? + expect(io_proxy.serialized?).to eq Concurrent.executor(:io).serialized? + + # cache only one proxy + fast_proxy = throttle.on :fast + expect(throttle.on(:io)).to eq io_proxy + expect(throttle.on(:fast)).not_to eq fast_proxy + end + + specify 'capacity limited' do + limit = 4 + throttle = Concurrent::Throttle.new limit + counter = Concurrent::AtomicFixnum.new + testing = -> i do + counter.increment + sleep rand * 0.02 + 0.02 + # returns less then 3 since it's throttled + v = counter.value + counter.decrement + v + end + + result = Concurrent::Promises.zip( + *20.times.map { |i| throttle.future(i, &testing) } + ).value! + expect(result.all? { |v| v <= limit }).to be_truthy, result.to_s + + result = Array.new(20) do |i1| + Thread.new(i1) { |i2| throttle.acquire { testing.call i2 } } + end.map(&:value) + expect(result.all? { |v| v <= limit }).to be_truthy, result.to_s + + throttled_futures = 20.times.map do |i| + Concurrent::Promises. + fulfilled_future(i). + then_on(throttle.on(:io), &testing) + end + + result = Concurrent::Promises.zip(*throttled_futures).value! + expect(result.all? { |v| v <= limit }).to be_truthy, result.to_s + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/timer_task_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/timer_task_spec.rb new file mode 100644 index 0000000000..44cc3e22e7 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/timer_task_spec.rb @@ -0,0 +1,225 @@ +require_relative 'concern/dereferenceable_shared' +require_relative 'concern/observable_shared' +require 'concurrent/timer_task' + +module Concurrent + + RSpec.describe TimerTask do + + context :dereferenceable do + + def kill_subject + @subject.kill if defined?(@subject) && @subject + rescue Exception + # prevent exceptions with mocks in tests + end + + after(:each) do + kill_subject + end + + def dereferenceable_subject(value, opts = {}) + kill_subject + opts = opts.merge(execution_interval: 0.1, run_now: true) + @subject = TimerTask.new(opts) { value }.execute.tap { sleep(0.1) } + end + + def dereferenceable_observable(opts = {}) + opts = opts.merge(execution_interval: 0.1, run_now: true) + @subject = TimerTask.new(opts) { 'value' } + end + + def execute_dereferenceable(subject) + subject.execute + sleep(0.1) + end + + it_should_behave_like :dereferenceable + end + + context :observable do + + subject { TimerTask.new(execution_interval: 0.1) { nil } } + + after(:each) { subject.kill } + + def trigger_observable(observable) + observable.execute + sleep(0.2) + end + + it_should_behave_like :observable + end + + context 'created with #new' do + + context '#initialize' do + + it 'raises an exception if no block given' do + expect { + Concurrent::TimerTask.new + }.to raise_error(ArgumentError) + end + + it 'raises an exception if :execution_interval is not greater than zero' do + expect { + Concurrent::TimerTask.new(execution_interval: 0) { nil } + }.to raise_error(ArgumentError) + end + + it 'raises an exception if :execution_interval is not an integer' do + expect { + Concurrent::TimerTask.new(execution_interval: 'one') { nil } + }.to raise_error(ArgumentError) + end + + it 'uses the default execution interval when no interval is given' do + subject = TimerTask.new { nil } + expect(subject.execution_interval).to eq TimerTask::EXECUTION_INTERVAL + end + + it 'uses the given execution interval' do + subject = TimerTask.new(execution_interval: 5) { nil } + expect(subject.execution_interval).to eq 5 + end + end + + context '#kill' do + + it 'returns true on success' do + task = TimerTask.execute(run_now: false) { nil } + sleep(0.1) + expect(task.kill).to be_truthy + end + end + + context '#shutdown' do + + it 'returns true on success' do + task = TimerTask.execute(run_now: false) { nil } + sleep(0.1) + expect(task.shutdown).to be_truthy + end + end + end + + context 'arguments' do + + it 'raises an exception if no block given' do + expect { + Concurrent::TimerTask.execute + }.to raise_error(ArgumentError) + end + + specify '#execution_interval is writeable' do + + latch = CountDownLatch.new(1) + subject = TimerTask.new(timeout_interval: 1, + execution_interval: 1, + run_now: true) do |task| + task.execution_interval = 3 + latch.count_down + end + + expect(subject.execution_interval).to eq(1) + subject.execution_interval = 0.1 + expect(subject.execution_interval).to eq(0.1) + + subject.execute + latch.wait(0.2) + + expect(subject.execution_interval).to eq(3) + subject.kill + end + + specify '#timeout_interval being written produces a warning' do + subject = TimerTask.new(timeout_interval: 1, + execution_interval: 0.1, + run_now: true) do |task| + expect { task.timeout_interval = 3 }.to output("TimerTask timeouts are now ignored as these were not able to be implemented correctly\n").to_stderr + end + + expect { subject.timeout_interval = 2 }.to output("TimerTask timeouts are now ignored as these were not able to be implemented correctly\n").to_stderr + end + end + + context 'execution' do + + it 'runs the block immediately when the :run_now option is true' do + latch = CountDownLatch.new(1) + subject = TimerTask.execute(execution: 500, now: true) { latch.count_down } + expect(latch.wait(1)).to be_truthy + subject.kill + end + + it 'waits for :execution_interval seconds when the :run_now option is false' do + latch = CountDownLatch.new(1) + subject = TimerTask.execute(execution: 0.1, now: false) { latch.count_down } + expect(latch.count).to eq 1 + expect(latch.wait(1)).to be_truthy + subject.kill + end + + it 'waits for :execution_interval seconds when the :run_now option is not given' do + latch = CountDownLatch.new(1) + subject = TimerTask.execute(execution: 0.1, now: false) { latch.count_down } + expect(latch.count).to eq 1 + expect(latch.wait(1)).to be_truthy + subject.kill + end + + it 'passes a "self" reference to the block as the sole argument' do + expected = nil + latch = CountDownLatch.new(1) + subject = TimerTask.new(execution_interval: 1, run_now: true) do |task| + expected = task + latch.count_down + end + subject.execute + latch.wait(1) + expect(expected).to eq subject + expect(latch.count).to eq(0) + subject.kill + end + end + + context 'observation' do + + let(:observer) do + Class.new do + attr_reader :time + attr_reader :value + attr_reader :ex + attr_reader :latch + define_method(:initialize) { @latch = CountDownLatch.new(1) } + define_method(:update) do |time, value, ex| + @time = time + @value = value + @ex = ex + @latch.count_down + end + end.new + end + + it 'notifies all observers on success' do + subject = TimerTask.new(execution: 0.1) { 42 } + subject.add_observer(observer) + subject.execute + observer.latch.wait(1) + expect(observer.value).to eq(42) + expect(observer.ex).to be_nil + subject.kill + end + + it 'notifies all observers on error' do + subject = TimerTask.new(execution: 0.1) { raise ArgumentError } + subject.add_observer(observer) + subject.execute + observer.latch.wait(1) + expect(observer.value).to be_nil + expect(observer.ex).to be_a(ArgumentError) + subject.kill + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/tvar_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/tvar_spec.rb new file mode 100644 index 0000000000..437ff5788b --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/tvar_spec.rb @@ -0,0 +1,176 @@ +require 'concurrent/tvar' + +module Concurrent + + RSpec.describe TVar do + + context '#initialize' do + + it 'accepts an initial value' do + t = TVar.new(14) + expect(t.value).to eq 14 + end + + end + + context '#value' do + + it 'gets the value' do + t = TVar.new(14) + expect(t.value).to eq 14 + end + + end + + context '#value=' do + + it 'sets the value' do + t = TVar.new(14) + t.value = 2 + expect(t.value).to eq 2 + end + + end + + end + + RSpec.describe '#atomically' do + + it 'raises an exception when no block given' do + expect { Concurrent::atomically }.to raise_error(ArgumentError) + end + + it 'raises the same exception that was raised in Concurrent::atomically' do + expect { + Concurrent::atomically do + raise StandardError, 'This is an error!' + end + }.to raise_error(StandardError, 'This is an error!') + end + + it 'retries on abort' do + count = 0 + + Concurrent::atomically do + if count == 0 + count = 1 + Concurrent::abort_transaction + else + count = 2 + end + end + + expect(count).to eq 2 + end + + it 'commits writes if the transaction succeeds' do + t = TVar.new(0) + + Concurrent::atomically do + t.value = 1 + end + + expect(t.value).to eq 1 + end + + it 'undoes writes if the transaction is aborted' do + t = TVar.new(0) + + count = 0 + + Concurrent::atomically do + if count == 0 + t.value = 1 + count = 1 + Concurrent::abort_transaction + end + end + + expect(t.value).to eq 0 + end + + it 'provides atomicity' do + t1 = TVar.new(0) + t2 = TVar.new(0) + + count = 0 + + Concurrent::atomically do + if count == 0 + count = 1 + t1.value = 1 + Concurrent::abort_transaction + t2.value = 2 + end + end + + expect(t1.value).to eq 0 + expect(t2.value).to eq 0 + end + + it 'nests' do + t = TVar.new(0) + + Concurrent::atomically do + expect(t.value).to eq 0 + t.value = 1 + Concurrent::atomically do + expect(t.value).to eq 1 + t.value = 2 + Concurrent::atomically do + expect(t.value).to eq 2 + t.value = 3 + end + expect(t.value).to eq 3 + t.value = 4 + end + expect(t.value).to eq 4 + t.value = 5 + end + + expect(t.value).to eq 5 + end + + it 'reflects transactional writes from within the same transaction' do + t = TVar.new(0) + + Concurrent::atomically do + expect(t.value).to eq 0 + t.value = 14 + expect(t.value).to eq 14 + t.value = 2 + expect(t.value).to eq 2 + end + end + + end + + RSpec.describe '#abort_transaction' do + + it 'raises an exception outside an #atomically block' do + expect { Concurrent::abort_transaction }.to raise_error(Concurrent::Transaction::AbortError) + end + + end + + RSpec.describe '#leave_transaction' do + + it 'raises an exception outside an #atomically block' do + expect { Concurrent::leave_transaction }.to raise_error(Concurrent::Transaction::LeaveError) + end + + it 'neither commits nor aborts a transaction' do + t = TVar.new(0) + + Concurrent::atomically do + expect(t.value).to eq 0 + t.value = 14 + Concurrent::leave_transaction + end + + expect(t.value).to eq 0 + end + + end + +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/utility/processor_count_spec.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/utility/processor_count_spec.rb new file mode 100644 index 0000000000..229125feb7 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/concurrent/utility/processor_count_spec.rb @@ -0,0 +1,20 @@ +require 'concurrent/utility/processor_counter' + +module Concurrent + + RSpec.describe '#processor_count' do + + it 'retuns a positive integer' do + expect(Concurrent::processor_count).to be_a Integer + expect(Concurrent::processor_count).to be >= 1 + end + end + + RSpec.describe '#physical_processor_count' do + + it 'retuns a positive integer' do + expect(Concurrent::physical_processor_count).to be_a Integer + expect(Concurrent::physical_processor_count).to be >= 1 + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/spec_helper.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/spec_helper.rb new file mode 100644 index 0000000000..2a8fbc54a8 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/spec_helper.rb @@ -0,0 +1,68 @@ +if ENV['COVERAGE'] + require 'simplecov' + require 'coveralls' + + SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter + + SimpleCov.start do + project_name 'concurrent-ruby' + add_filter '/examples/' + add_filter '/spec/' + end +end + +if ENV['NO_PATH'] + # Patch rspec not to add lib to $LOAD_PATH, allows to test installed gems + $LOAD_PATH.delete File.expand_path(File.join(__dir__, '..', 'lib')) + class RSpec::Core::Configuration + remove_method :requires= + + def requires=(paths) + directories = [default_path].select { |p| File.directory? p } + RSpec::Core::RubyProject.add_to_load_path(*directories) + paths.each { |path| require path } + @requires += paths + end + end +end + +require_relative 'support/example_group_extensions' +require_relative 'support/threadsafe_test' + +RSpec.configure do |config| + #config.raise_errors_for_deprecations! + config.filter_run_excluding stress: true + config.order = 'random' + config.disable_monkey_patching! + config.example_status_persistence_file_path = 'spec/examples.txt' + + config.include Concurrent::TestHelpers + config.extend Concurrent::TestHelpers + + config.before :all do + # Only configure logging if it has been required, to make sure the necessary require's are in place + if Concurrent.respond_to? :use_simple_logger + Concurrent.use_simple_logger Logger::FATAL + end + end + + config.before :each do + expect(!defined?(@created_threads) || @created_threads.nil? || @created_threads.empty?).to be_truthy + end + + config.after :each do + while defined?(@created_threads) && @created_threads && (thread = (@created_threads.pop(true) rescue nil)) + thread.kill + thread_join = thread.join(1.0) + expect(thread_join).not_to be_nil, thread.inspect + end + end +end + +# Remove verbose and confusing "Pending: (Failures listed here ...)" section at the end. +# From https://github.com/rspec/rspec-core/issues/2377#issuecomment-275131981 +module RSpecNoPendingOutput + def dump_pending(_) + end +end +RSpec::Core::Formatters::DocumentationFormatter.prepend RSpecNoPendingOutput diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/.gitignore b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/example_group_extensions.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/example_group_extensions.rb new file mode 100644 index 0000000000..25d9739e90 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/example_group_extensions.rb @@ -0,0 +1,67 @@ +require 'rbconfig' + +module Concurrent + module TestHelpers + extend self + + def pool_termination_timeout + 5 + end + + def delta(v1, v2) + if block_given? + v1 = yield(v1) + v2 = yield(v2) + end + return (v1 - v2).abs + end + + def monotonic_interval + raise ArgumentError.new('no block given') unless block_given? + start_time = Concurrent.monotonic_time + yield + Concurrent.monotonic_time - start_time + end + + def in_fiber(&block) + Fiber.new(&block) + end + + def in_thread(*arguments, &block) + @created_threads ||= Queue.new + new_thread = Thread.new(*arguments) do |*args, &b| + Thread.abort_on_exception = true + block.call(*args, &b) + end + @created_threads << new_thread + new_thread + end + + def is_sleeping(thread) + expect(in_thread { Thread.pass until thread.status == 'sleep' }.join(1)).not_to eq nil + end + + def repeat_until_success(timeout = 5, &test) + start_time = Concurrent.monotonic_time + last_exception = nil + while Concurrent.monotonic_time - start_time < timeout + begin + test.call + return true + rescue Exception => e + last_exception = e + Thread.pass + end + end + raise last_exception + end + + def join_with(threads, timeout = 5) + threads = Array(threads) + threads.each do |t| + joined_thread = t.join(timeout * threads.size) + expect(joined_thread).not_to eq nil + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/threadsafe_test.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/threadsafe_test.rb new file mode 100644 index 0000000000..4184f389f1 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/spec/support/threadsafe_test.rb @@ -0,0 +1,72 @@ +module Concurrent + module ThreadSafe + module Test + + THREADS = (RUBY_ENGINE == 'ruby' ? 100 : 10) + + class Barrier + def initialize(count = 1) + @count = count + @mutex = Mutex.new + @cond = ConditionVariable.new + end + + def release + @mutex.synchronize do + @count -= 1 if @count > 0 + @cond.broadcast if @count.zero? + end + end + + def await + @mutex.synchronize do + if @count.zero? # fall through + elsif @count > 0 + @count -= 1 + @count.zero? ? @cond.broadcast : @cond.wait(@mutex) + end + end + end + end + + class HashCollisionKey + attr_reader :hash, :key + def initialize(key, hash = key.hash % 3) + @key = key + @hash = hash + end + + def eql?(other) + other.kind_of?(self.class) && @key.eql?(other.key) + end + + def even? + @key.even? + end + + def <=>(other) + @key <=> other.key + end + end + + # having 4 separate HCK classes helps for a more thorough CHMV8 testing + class HashCollisionKey2 < HashCollisionKey; end + class HashCollisionKeyNoCompare < HashCollisionKey + def <=>(other) + 0 + end + end + class HashCollisionKey4 < HashCollisionKeyNoCompare; end + + HASH_COLLISION_CLASSES = [HashCollisionKey, HashCollisionKey2, HashCollisionKeyNoCompare, HashCollisionKey4] + + def self.HashCollisionKey(key, hash = key.hash % 3) + HASH_COLLISION_CLASSES[rand(4)].new(key, hash) + end + + class HashCollisionKeyNonComparable < HashCollisionKey + undef <=> + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/support/generate_docs.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/support/generate_docs.rb new file mode 100644 index 0000000000..f585f651bc --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/support/generate_docs.rb @@ -0,0 +1,46 @@ +# Using Ruby 2.6 to run this as the docs of 1.1.5 need it + +root = File.dirname(__dir__) +versions_file = "#{root}/docs-source/signpost.md" +# Note: 1.0.5 and 1.1.4 are too old, and use different rake task names +versions = File.read(versions_file).scan(/\[(\d+\.\d+\.\d+) with/).map(&:first) +versions.reverse! + +def sh(*args) + command = "$ #{args.join(' ')}" + puts command + unless system(*args, exception: true) + raise "Failed: #{command}" + end +end + +sh "rm", "-rf", "site" +sh "mkdir", "site" + +versions.each do |version| + puts + puts version + sh "git", "checkout", "v#{version}" + has_docs = Dir.exist?('docs') + + sh "rm", "-f", "Gemfile.lock" + sh "bundle", "install" + sh "bundle", "exec", "rake", "yard:#{version}" + + sh "cp", "-R", "docs/#{version}", "site" + sh "rm", "-rf", "docs/#{version}" + sh "git", "restore", "docs" if has_docs + sh "git", "restore", "docs-source" +end + +sh "git", "checkout", "master" + +sh "rm", "-f", "Gemfile.lock" +sh "bundle", "install" +sh "bundle", "exec", "rake", "yard" + +versions.each do |version| + sh "cp", "-R", "site/#{version}", "docs/#{version}" +end + +sh "rm", "-rf", "site" diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/support/yard_full_types.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/support/yard_full_types.rb new file mode 100644 index 0000000000..23d3e5e413 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/support/yard_full_types.rb @@ -0,0 +1,54 @@ +module YARD + + VERSION[0..2] == '0.9' or raise 'incompatible YARD' + + module Templates::Helpers + + # make sure the signatures are complete not simplified with + # '...' and '?' instead of nil + module HtmlHelper + def signature_types(meth, link = true) + meth = convert_method_to_overload(meth) + if meth.respond_to?(:object) && !meth.has_tag?(:return) + meth = meth.object + end + + type = options.default_return || "" + if meth.tag(:return) && meth.tag(:return).types + types = meth.tags(:return).map {|t| t.types ? t.types : [] }.flatten.uniq + first = link ? h(types.first) : format_types([types.first], false) + # if types.size == 2 && types.last == 'nil' + # type = first + '?' + if types.size == 2 && types.last =~ /^(Array)?<#{Regexp.quote types.first}>$/ + type = first + '+' + # elsif types.size > 2 + # type = [first, '...'].join(', ') + elsif types == ['void'] && options.hide_void_return + type = "" + else + type = link ? h(types.join(", ")) : format_types(types, false) + end + elsif !type.empty? + type = link ? h(type) : format_types([type], false) + end + type = "#{type} " unless type.empty? + type + end + + # enables :strikethrough extension + def html_markup_markdown(text) + # TODO: other libraries might be more complex + provider = markup_class(:markdown) + if provider.to_s == 'RDiscount' + provider.new(text, :autolink).to_html + elsif provider.to_s == 'RedcarpetCompat' + provider.new(text, :no_intraemphasis, :gh_blockcode, + :fenced_code, :autolink, :tables, + :lax_spacing, :strikethrough).to_html + else + provider.new(text).to_html + end + end + end + end +end diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/fulldoc/html/css/common.css b/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/fulldoc/html/css/common.css new file mode 100644 index 0000000000..f7f7f98b8d --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/fulldoc/html/css/common.css @@ -0,0 +1,135 @@ +/* Override this file with custom rules */ + +body { + line-height: 18px; +} + +.docstring h1:before { + content: '# '; + color: silver; +} + +.docstring h2:before { + content: '## '; + color: silver; +} + +.docstring code, .docstring .object_link a, #filecontents code { + padding: 0px 3px 1px 3px; + border: 1px solid #eef; + background: #f5f5ff; +} + +#filecontents pre code, .docstring pre code { + border: none; + background: none; + padding: 0; +} + +#filecontents pre.code, .docstring pre.code, .tags pre.example, .docstring code, .docstring .object_link a, +#filecontents code { + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} + +/* syntax highlighting */ +.source_code { + display: none; + padding: 3px 8px; + border-left: 8px solid #ddd; + margin-top: 5px; +} + +#filecontents pre.code, .docstring pre.code, .source_code pre { + font-family: monospace; +} + +#filecontents pre.code, .docstring pre.code { + display: block; +} + +.source_code .lines { + padding-right: 12px; + color: #555; + text-align: right; +} + +#filecontents pre.code, .docstring pre.code, +.tags pre.example { + padding: 5px 12px; + margin-top: 4px; + border: 1px solid #eef; + background: #f5f5ff; +} + +pre.code { + color: #000; +} + +pre.code .info.file { + color: #555; +} + +pre.code .val { + color: #036A07; +} + +pre.code .tstring_content, +pre.code .heredoc_beg, pre.code .heredoc_end, +pre.code .qwords_beg, pre.code .qwords_end, +pre.code .tstring, pre.code .dstring { + color: #036A07; +} + +pre.code .fid, +pre.code .rubyid_new, +pre.code .rubyid_to_s, +pre.code .rubyid_to_sym, +pre.code .rubyid_to_f, +pre.code .rubyid_to_i, +pre.code .rubyid_each { + color: inherit; +} + +pre.code .comment { + color: #777; + font-style: italic; +} + +pre.code .const, pre.code .constant { + color: inherit; + font-weight: bold; + font-style: italic; +} + +pre.code .label, +pre.code .symbol { + color: #C5060B; +} + +pre.code .kw, +pre.code .rubyid_require, +pre.code .rubyid_extend, +pre.code .rubyid_include, +pre.code .int { + color: #0000FF; +} + +pre.code .ivar { + color: #660E7A; +} + +pre.code .gvar, +pre.code .rubyid_backref, +pre.code .rubyid_nth_ref { + color: #6D79DE; +} + +pre.code .regexp, .dregexp { + color: #036A07; +} + +pre.code a { + border-bottom: 1px dotted #bbf; +} + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/layout/html/footer.erb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/layout/html/footer.erb new file mode 100644 index 0000000000..719023ab16 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/layout/html/footer.erb @@ -0,0 +1,14 @@ + + + diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/layout/html/objects.erb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/layout/html/objects.erb new file mode 100755 index 0000000000..1acfdf96fc --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/layout/html/objects.erb @@ -0,0 +1,34 @@ +

Namespace Listing A-Z

+ +<% if Registry.root.meths(:included => false).size > 0 %> +
  • <%= linkify(Registry.root) %>
+<% end %> + +<% i = 0 %> + + + + +
+ <% @objects_by_letter.sort_by {|l,o| l.to_s }.each do |letter, objects| %> + <% if (i += 1) % 8 == 0 %> + + <% i = 0 %> + <% end %> +
    +
  • <%= letter %>
  • +
      + <%# better sorting on docs/master/_index.html %> + <%# objects.each do |obj| %> + <% objects.sort_by { |o| [o.name, o.namespace.path] }.each do |obj| %> +
    • + <%= linkify obj, obj.name %> + <% if !obj.namespace.root? %> + (<%= obj.namespace.path %>) + <% end %> +
    • + <% end %> +
    +
+ <% end %> +
diff --git a/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/module/setup.rb b/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/module/setup.rb new file mode 100644 index 0000000000..23a28bfb27 --- /dev/null +++ b/app/server/ruby/vendor/concurrent-ruby-1.2.2/yard-template/default/module/setup.rb @@ -0,0 +1,21 @@ +def sort_listing(list) + list.sort_by do |o| + [o.scope.to_s, + object == o.namespace ? 0 : 1, # sort owned methods first + # o.namespace.to_s.downcase, # sort by included module + o.name.to_s.downcase] + end +end + +# TODO (pitr-ch 01-Jan-2019): enable inherited methods including, and do review of the documentation + +# def method_listing(include_specials = true) +# return @smeths ||= method_listing.reject {|o| special_method?(o) } unless include_specials +# return @meths if defined?(@meths) && @meths +# @meths = object.meths(:inherited => true, :included => !options.embed_mixins.empty?) +# unless options.embed_mixins.empty? +# @meths = @meths.reject {|m| options.embed_mixins_match?(m.namespace) == false } +# end +# @meths = sort_listing(prune_method_listing(@meths)) +# @meths +# end diff --git a/app/server/ruby/vendor/i18n-1.14.1/.github/ISSUE_TEMPLATE/bug_report.md b/app/server/ruby/vendor/i18n-1.14.1/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..086bc38780 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a bug report +title: "[BUG]" +labels: '' +assignees: '' + +--- + +## What I tried to do + +* Fill this out! + +## What I expected to happen + +* Fill this out! + +## What actually happened + +* Fill this out! + +## Versions of i18n, rails, and anything else you think is necessary + +* Fill this out! + +---- + +Bonus points for providing an application or a small code example which reproduces the issue. + +Thanks! :heart: diff --git a/app/server/ruby/vendor/i18n-1.14.1/.github/funding.yml b/app/server/ruby/vendor/i18n-1.14.1/.github/funding.yml new file mode 100644 index 0000000000..2dda82af1e --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/.github/funding.yml @@ -0,0 +1 @@ +github: [radar] diff --git a/app/server/ruby/vendor/i18n-1.14.1/.github/workflows/ruby.yml b/app/server/ruby/vendor/i18n-1.14.1/.github/workflows/ruby.yml new file mode 100644 index 0000000000..3e33190efc --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/.github/workflows/ruby.yml @@ -0,0 +1,71 @@ +name: Ruby + +on: + # Trigger the workflow on push or pull request, + # but only for the master branch + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + env: + BUNDLE_GEMFILE: ${{ matrix.gemfile }} + strategy: + fail-fast: false + matrix: + ruby_version: [3.2, 3.1, "3.0", 2.7, 2.6, jruby] + gemfile: + - Gemfile + - gemfiles/Gemfile.rails-5.2.x + - gemfiles/Gemfile.rails-6.0.x + - gemfiles/Gemfile.rails-6.1.x + - gemfiles/Gemfile.rails-7.0.x + - gemfiles/Gemfile.rails-main + exclude: + # Ruby 3.2 is not supported by Rails 5.2.x + - ruby_version: 3.2 + gemfile: gemfiles/Gemfile.rails-5.2.x + + # Ruby 3.1 is not supported by Rails 5.2.x + - ruby_version: 3.1 + gemfile: gemfiles/Gemfile.rails-5.2.x + + # Ruby 3.x is not supported by Rails 5.2.x + - ruby_version: "3.0" + gemfile: gemfiles/Gemfile.rails-5.2.x + + # Ruby 2.6.x is not supported by Rails main + - ruby_version: 2.6 + gemfile: gemfiles/Gemfile.rails-main + + # Ruby 2.6.x is not supported by Rails 7.0.x + - ruby_version: 2.6 + gemfile: gemfiles/Gemfile.rails-7.0.x + + # JRuby 9.4.2.0 (3.1.0) is not supported by Rails 5.2.x + - ruby_version: jruby + gemfile: gemfiles/Gemfile.rails-5.2.x + + # JRuby is not supported by Rails 7.0.x + - ruby_version: jruby + gemfile: gemfiles/Gemfile.rails-7.0.x + + # JRuby is not supported by Rails main + - ruby_version: jruby + gemfile: gemfiles/Gemfile.rails-main + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby_version }} + bundler-cache: true # 'bundle install' and cache + - name: Build and test with Rake + run: bundle exec rake diff --git a/app/server/ruby/vendor/i18n/.gitignore b/app/server/ruby/vendor/i18n-1.14.1/.gitignore similarity index 52% rename from app/server/ruby/vendor/i18n/.gitignore rename to app/server/ruby/vendor/i18n-1.14.1/.gitignore index b066d1ea22..98e0c4c2d6 100644 --- a/app/server/ruby/vendor/i18n/.gitignore +++ b/app/server/ruby/vendor/i18n-1.14.1/.gitignore @@ -6,3 +6,8 @@ vendor/**/* pkg .bundle .rvmrc +.ruby-version +.ruby-gemset +.tool-versions +Gemfile.lock +gemfiles/*.lock diff --git a/app/server/ruby/vendor/i18n-1.14.1/CHANGELOG.md b/app/server/ruby/vendor/i18n-1.14.1/CHANGELOG.md new file mode 100644 index 0000000000..f0a53a3d1f --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog has moved + +For changes, please see our [Releases page](https://github.com/svenfuchs/i18n/releases). diff --git a/app/server/ruby/vendor/i18n-1.14.1/Gemfile b/app/server/ruby/vendor/i18n-1.14.1/Gemfile new file mode 100644 index 0000000000..460f92d0eb --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/Gemfile @@ -0,0 +1,9 @@ +source 'https://rubygems.org' + +gemspec + +gem 'mocha', '~> 1.7.0' +gem 'test_declarative', '0.0.6' +gem 'rake', '~> 13' +gem 'minitest', '~> 5.14' +gem 'json' diff --git a/app/server/ruby/vendor/i18n/MIT-LICENSE b/app/server/ruby/vendor/i18n-1.14.1/MIT-LICENSE similarity index 100% rename from app/server/ruby/vendor/i18n/MIT-LICENSE rename to app/server/ruby/vendor/i18n-1.14.1/MIT-LICENSE diff --git a/app/server/ruby/vendor/i18n-1.14.1/README.md b/app/server/ruby/vendor/i18n-1.14.1/README.md new file mode 100644 index 0000000000..1d174ddc5e --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/README.md @@ -0,0 +1,123 @@ +# Ruby I18n + +[![Gem Version](https://badge.fury.io/rb/i18n.svg)](https://badge.fury.io/rb/i18n) +[![Build Status](https://github.com/ruby-i18n/i18n/workflows/Ruby/badge.svg)](https://github.com/ruby-i18n/i18n/actions?query=workflow%3ARuby) + +Ruby internationalization and localization (i18n) solution. + +Currently maintained by @radar. + +## Usage + +### Rails + +You will most commonly use this library within a Rails app. + +[See the Rails Guide](https://guides.rubyonrails.org/i18n.html) for an example of its usage. + +### Ruby (without Rails) + +If you want to use this library without Rails, you can simply add `i18n` to your `Gemfile`: + +```ruby +gem 'i18n' +``` + +Then configure I18n with some translations, and a default locale: + +```ruby +I18n.load_path += Dir[File.expand_path("config/locales") + "/*.yml"] +I18n.default_locale = :en # (note that `en` is already the default!) +``` + +A simple translation file in your project might live at `config/locales/en.yml` and look like: + +```yml +en: + test: "This is a test" +``` + +You can then access this translation by doing: + +```ruby +I18n.t(:test) +``` + +You can switch locales in your project by setting `I18n.locale` to a different value: + +```ruby +I18n.locale = :de +I18n.t(:test) # => "Dies ist ein Test" +``` + +## Features + +* Translation and localization +* Interpolation of values to translations +* Pluralization (CLDR compatible) +* Customizable transliteration to ASCII +* Flexible defaults +* Bulk lookup +* Lambdas as translation data +* Custom key/scope separator +* Custom exception handlers +* Extensible architecture with a swappable backend + +## Pluggable Features + +* Cache +* Pluralization: lambda pluralizers stored as translation data +* Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation) +* [Gettext support](https://github.com/ruby-i18n/i18n/wiki/Gettext) +* Translation metadata + +## Alternative Backend + +* Chain +* ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs) +* KeyValue (uses active_support/json and cannot store procs) + +For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/ruby-i18n/i18n/wiki/Resources). + +## Tests + +You can run tests both with + +* `rake test` or just `rake` +* run any test file directly, e.g. `ruby -Ilib:test test/api/simple_test.rb` + +You can run all tests against all Gemfiles with + +* `ruby test/run_all.rb` + +The structure of the test suite is a bit unusual as it uses modules to reuse +particular tests in different test cases. + +The reason for this is that we need to enforce the I18n API across various +combinations of extensions. E.g. the Simple backend alone needs to support +the same API as any combination of feature and/or optimization modules included +to the Simple backend. We test this by reusing the same API definition (implemented +as test methods) in test cases with different setups. + +You can find the test cases that enforce the API in test/api. And you can find +the API definition test methods in test/api/tests. + +All other test cases (e.g. as defined in test/backend, test/core_ext) etc. +follow the usual test setup and should be easy to grok. + +## More Documentation + +Additional documentation can be found here: https://github.com/ruby-i18n/i18n/wiki + +## Contributors + +* @radar +* @carlosantoniodasilva +* @josevalim +* @knapo +* @tigrish +* [and many more](https://github.com/ruby-i18n/i18n/graphs/contributors) + +## License + +MIT License. See the included MIT-LICENSE file. diff --git a/app/server/ruby/vendor/i18n/Rakefile b/app/server/ruby/vendor/i18n-1.14.1/Rakefile similarity index 81% rename from app/server/ruby/vendor/i18n/Rakefile rename to app/server/ruby/vendor/i18n-1.14.1/Rakefile index 5e6e1dbf00..bdc6683c06 100644 --- a/app/server/ruby/vendor/i18n/Rakefile +++ b/app/server/ruby/vendor/i18n-1.14.1/Rakefile @@ -6,7 +6,7 @@ task :default => [:test] Rake::TestTask.new(:test) do |t| t.libs << 'lib' t.libs << 'test' - t.pattern = "#{File.dirname(__FILE__)}/test/all.rb" + t.pattern = "test/**/*_test.rb" t.verbose = true t.warning = true end diff --git a/app/server/ruby/vendor/i18n/benchmark/example.yml b/app/server/ruby/vendor/i18n-1.14.1/benchmark/example.yml similarity index 100% rename from app/server/ruby/vendor/i18n/benchmark/example.yml rename to app/server/ruby/vendor/i18n-1.14.1/benchmark/example.yml diff --git a/app/server/ruby/vendor/i18n/benchmark/run.rb b/app/server/ruby/vendor/i18n-1.14.1/benchmark/run.rb similarity index 79% rename from app/server/ruby/vendor/i18n/benchmark/run.rb rename to app/server/ruby/vendor/i18n-1.14.1/benchmark/run.rb index 0a59eeb16b..b37c3b7f59 100644 --- a/app/server/ruby/vendor/i18n/benchmark/run.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/benchmark/run.rb @@ -1,11 +1,11 @@ -#! /usr/bin/ruby +#!/usr/bin/ruby $:.unshift File.expand_path('../../lib', __FILE__) +require 'bundler/setup' require 'i18n' require 'benchmark' require 'yaml' -DATA_STORES = ARGV.delete("-ds") N = (ARGV.shift || 1000).to_i YAML_HASH = YAML.load_file(File.expand_path("example.yml", File.dirname(__FILE__))) @@ -16,20 +16,16 @@ module Backends include I18n::Backend::InterpolationCompiler end.new - if DATA_STORES - require 'rubygems' - require File.expand_path('../../test/test_setup/active_record', __FILE__) - require File.expand_path('../../test/test_setup/rufus_tokyo', __FILE__) - - Test.setup_active_record - ActiveRecord = I18n::Backend::ActiveRecord.new if defined?(::ActiveRecord) - - Test.setup_rufus_tokyo - TokyoCabinet = I18n::Backend::KeyValue.new(Rufus::Tokyo::Cabinet.new("*"), true) if defined?(::Rufus::Tokyo) + begin + require 'active_support' + KeyValue = I18n::Backend::KeyValue.new({}, true) + puts "Running KeyValue with ActiveSupport #{ActiveSupport::VERSION::STRING}" + rescue LoadError + puts 'Skipping KeyValue since ActiveSupport could not be loaded.' end end -ORDER = %w(Simple Interpolation ActiveRecord TokyoCabinet) +ORDER = %w(Simple Interpolation KeyValue) ORDER.map!(&:to_sym) if RUBY_VERSION > '1.9' module Benchmark @@ -62,7 +58,7 @@ def self.measure_objects(n, &blk) puts "=> #{backend_name}\n\n" Benchmark.rt "store", 1 do - I18n.backend.store_translations *(YAML_HASH.to_a.first) + I18n.backend.store_translations(*YAML_HASH.to_a.first) end I18n.backend.translate :en, :first @@ -114,4 +110,4 @@ def self.measure_objects(n, &blk) end puts "Running memoized benchmarks with N = #{N}\n\n" -(ORDER & Backends.constants).each(&benchmarker) \ No newline at end of file +(ORDER & Backends.constants).each(&benchmarker) diff --git a/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.0.x b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.0.x new file mode 100644 index 0000000000..07dc2e4c8d --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.0.x @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gemspec :path => '..' + +gem 'activesupport', '~> 5.0.0' +gem 'mocha', '~> 1.7.0' +gem 'test_declarative', '0.0.6' +gem 'rake', '~> 13' +gem 'minitest', '~> 5.14' + +platforms :mri do + gem 'oj' +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.1.x b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.1.x new file mode 100644 index 0000000000..96350ede49 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.1.x @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gemspec :path => '..' + +gem 'activesupport', '~> 5.1.0' +gem 'mocha', '~> 1.7.0' +gem 'test_declarative', '0.0.6' +gem 'rake' +gem 'minitest', '~> 5.14' + +platforms :mri do + gem 'oj' +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.2.x b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.2.x new file mode 100644 index 0000000000..760abe84e5 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-5.2.x @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gemspec :path => '..' + +gem 'activesupport', '~> 5.2.0' +gem 'mocha', '~> 1.7.0' +gem 'test_declarative', '0.0.6' +gem 'rake' +gem 'minitest', '~> 5.14' + +platforms :mri do + gem 'oj' +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-6.0.x b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-6.0.x new file mode 100644 index 0000000000..c6d1b27940 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-6.0.x @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gemspec :path => '..' + +gem 'activesupport', '~> 6.0.0' +gem 'mocha', '~> 1.7.0' +gem 'test_declarative', '0.0.6' +gem 'rake' +gem 'minitest', '~> 5.14' + +platforms :mri do + gem 'oj' +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-6.1.x b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-6.1.x new file mode 100644 index 0000000000..cea7afe323 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-6.1.x @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gemspec :path => '..' + +gem 'activesupport', '~> 6.1' +gem 'mocha', '~> 1.7.0' +gem 'test_declarative', '0.0.6' +gem 'rake' +gem 'minitest', '~> 5.14' + +platforms :mri do + gem 'oj' +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-7.0.x b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-7.0.x new file mode 100644 index 0000000000..90ffe74f20 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-7.0.x @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gemspec :path => '..' + +gem 'activesupport', '~> 7.0' +gem 'mocha', '~> 1.7.0' +gem 'test_declarative', '0.0.6' +gem 'rake' +gem 'minitest', '~> 5.14' + +platforms :mri do + gem 'oj' +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-main b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-main new file mode 100644 index 0000000000..d04f26ee99 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/gemfiles/Gemfile.rails-main @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gemspec :path => '..' + +gem 'activesupport', github: 'rails/rails', branch: 'main' +gem 'mocha', '~> 1.7.0' +gem 'test_declarative', '0.0.6' +gem 'rake' +gem 'minitest', '~> 5.1' + +platforms :mri do + gem 'oj' +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/i18n.gemspec b/app/server/ruby/vendor/i18n-1.14.1/i18n.gemspec new file mode 100644 index 0000000000..2bfad5be2f --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/i18n.gemspec @@ -0,0 +1,31 @@ +# encoding: utf-8 + +$: << File.expand_path('../lib', __FILE__) +require 'i18n/version' + +Gem::Specification.new do |s| + s.name = "i18n" + s.version = I18n::VERSION + s.authors = ["Sven Fuchs", "Joshua Harvey", "Matt Aimonetti", "Stephan Soller", "Saimon Moore", "Ryan Bigg"] + s.email = "rails-i18n@googlegroups.com" + s.homepage = "https://github.com/ruby-i18n/i18n" + s.summary = "New wave Internationalization support for Ruby" + s.description = "New wave Internationalization support for Ruby." + s.license = "MIT" + + s.metadata = { + 'bug_tracker_uri' => 'https://github.com/ruby-i18n/i18n/issues', + 'changelog_uri' => 'https://github.com/ruby-i18n/i18n/releases', + 'documentation_uri' => 'https://guides.rubyonrails.org/i18n.html', + 'source_code_uri' => 'https://github.com/ruby-i18n/i18n', + } + + s.files = Dir.glob("lib/**/*") + %w(README.md MIT-LICENSE) + s.platform = Gem::Platform::RUBY + s.require_path = 'lib' + s.required_rubygems_version = '>= 1.3.5' + s.required_ruby_version = '>= 2.3.0' + + s.add_dependency 'concurrent-ruby', '~> 1.0' + +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n.rb similarity index 64% rename from app/server/ruby/vendor/i18n/lib/i18n.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n.rb index 98b65bf3eb..d3369704ab 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n.rb @@ -1,4 +1,10 @@ +# frozen_string_literal: true + +require 'concurrent/map' +require 'concurrent/hash' + require 'i18n/version' +require 'i18n/utils' require 'i18n/exceptions' require 'i18n/interpolate/ruby' @@ -8,11 +14,44 @@ module I18n autoload :Gettext, 'i18n/gettext' autoload :Locale, 'i18n/locale' autoload :Tests, 'i18n/tests' + autoload :Middleware, 'i18n/middleware' + + RESERVED_KEYS = %i[ + cascade + deep_interpolation + default + exception_handler + fallback + fallback_in_progress + fallback_original_locale + format + object + raise + resolve + scope + separator + throw + ] + EMPTY_HASH = {}.freeze - RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :format, :cascade, :throw, :raise, :rescue_format] - RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/ + def self.new_double_nested_cache # :nodoc: + Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new } + end - extend(Module.new { + # Marks a key as reserved. Reserved keys are used internally, + # and can't also be used for interpolation. If you are using any + # extra keys as I18n options, you should call I18n.reserve_key + # before any I18n.translate (etc) calls are made. + def self.reserve_key(key) + RESERVED_KEYS << key.to_sym + @reserved_keys_pattern = nil + end + + def self.reserved_keys_pattern # :nodoc: + @reserved_keys_pattern ||= /%\{(#{RESERVED_KEYS.join("|")})\}/ + end + + module Base # Gets I18n configuration object. def config Thread.current[:i18n_config] ||= I18n::Config.new @@ -41,9 +80,17 @@ def #{method}=(value) # Rails development environment. Backends can implement whatever strategy # is useful. def reload! + config.clear_available_locales_set config.backend.reload! end + # Tells the backend to load translations now. Used in situations like the + # Rails production environment. Backends can implement whatever strategy + # is useful. + def eager_load! + config.backend.eager_load! + end + # Translates, pluralizes and interpolates a given key using a given locale, # scope, and default, as well as interpolation values. # @@ -83,7 +130,7 @@ def reload! # *PLURALIZATION* # # Translation data can contain pluralized translations. Pluralized translations - # are arrays of singluar/plural versions of translations like ['Foo', 'Foos']. + # are arrays of singular/plural versions of translations like ['Foo', 'Foos']. # # Note that I18n::Backend::Simple only supports an algorithm for English # pluralization rules. Other algorithms can be supported by custom backends. @@ -131,47 +178,65 @@ def reload! # called and passed the key and options. # # E.g. assuming the key :salutation resolves to: - # lambda { |key, options| options[:gender] == 'm' ? "Mr. %{options[:name]}" : "Mrs. %{options[:name]}" } + # lambda { |key, options| options[:gender] == 'm' ? "Mr. #{options[:name]}" : "Mrs. #{options[:name]}" } # # Then I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith". # + # Note that the string returned by lambda will go through string interpolation too, + # so the following lambda would give the same result: + # lambda { |key, options| options[:gender] == 'm' ? "Mr. %{name}" : "Mrs. %{name}" } + # # It is recommended to use/implement lambdas in an "idempotent" way. E.g. when # a cache layer is put in front of I18n.translate it will generate a cache key - # from the argument values passed to #translate. Therefor your lambdas should + # from the argument values passed to #translate. Therefore your lambdas should # always return the same translations/values per unique combination of argument # values. - def translate(*args) - options = args.last.is_a?(Hash) ? args.pop.dup : {} - key = args.shift - backend = config.backend - locale = options.delete(:locale) || config.locale - handling = options.delete(:throw) && :throw || options.delete(:raise) && :raise # TODO deprecate :raise - + # + # *Ruby 2.7+ keyword arguments warning* + # + # This method uses keyword arguments. + # There is a breaking change in ruby that produces warning with ruby 2.7 and won't work as expected with ruby 3.0 + # The "hash" parameter must be passed as keyword argument. + # + # Good: + # I18n.t(:salutation, :gender => 'w', :name => 'Smith') + # I18n.t(:salutation, **{ :gender => 'w', :name => 'Smith' }) + # I18n.t(:salutation, **any_hash) + # + # Bad: + # I18n.t(:salutation, { :gender => 'w', :name => 'Smith' }) + # I18n.t(:salutation, any_hash) + # + def translate(key = nil, throw: false, raise: false, locale: nil, **options) # TODO deprecate :raise + locale ||= config.locale + raise Disabled.new('t') if locale == false enforce_available_locales!(locale) - raise I18n::ArgumentError if key.is_a?(String) && key.empty? - result = catch(:exception) do - if key.is_a?(Array) - key.map { |k| backend.translate(locale, k, options) } - else - backend.translate(locale, key, options) + backend = config.backend + + if key.is_a?(Array) + key.map do |k| + translate_key(k, throw, raise, locale, backend, options) end + else + translate_key(key, throw, raise, locale, backend, options) end - result.is_a?(MissingTranslation) ? handle_exception(handling, result, locale, key, options) : result end alias :t :translate # Wrapper for translate that adds :raise => true. With # this option, if no translation is found, it will raise I18n::MissingTranslationData - def translate!(key, options={}) - translate(key, options.merge(:raise => true)) + def translate!(key, **options) + translate(key, **options, raise: true) end alias :t! :translate! # Returns true if a translation exists for a given key, otherwise returns false. - def exists?(key, locale = config.locale) + def exists?(key, _locale = nil, locale: _locale, **options) + locale ||= config.locale + raise Disabled.new('exists?') if locale == false raise I18n::ArgumentError if key.is_a?(String) && key.empty? - config.backend.exists?(locale, key) + config.backend.exists?(locale, key, options) end # Transliterates UTF-8 characters to ASCII. By default this method will @@ -203,14 +268,14 @@ def exists?(key, locale = config.locale) # # Setting a Hash using Ruby: # - # store_translations(:de, :i18n => { - # :transliterate => { - # :rule => { - # "ü" => "ue", - # "ö" => "oe" - # } - # } - # ) + # store_translations(:de, i18n: { + # transliterate: { + # rule: { + # 'ü' => 'ue', + # 'ö' => 'oe' + # } + # } + # }) # # Setting a Proc: # @@ -225,37 +290,40 @@ def exists?(key, locale = config.locale) # I18n.transliterate("Jürgen") # => "Juergen" # I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen" # I18n.transliterate("Jürgen", :locale => :de) # => "Juergen" - def transliterate(*args) - options = args.pop.dup if args.last.is_a?(Hash) - key = args.shift - locale = options && options.delete(:locale) || config.locale - handling = options && (options.delete(:throw) && :throw || options.delete(:raise) && :raise) - replacement = options && options.delete(:replacement) + def transliterate(key, throw: false, raise: false, locale: nil, replacement: nil, **options) + locale ||= config.locale + raise Disabled.new('transliterate') if locale == false enforce_available_locales!(locale) + config.backend.transliterate(locale, key, replacement) rescue I18n::ArgumentError => exception - handle_exception(handling, exception, locale, key, options || {}) + handle_exception((throw && :throw || raise && :raise), exception, locale, key, options) end # Localizes certain objects, such as dates and numbers to local formatting. - def localize(object, options = nil) - options = options ? options.dup : {} - locale = options.delete(:locale) || config.locale - format = options.delete(:format) || :default + def localize(object, locale: nil, format: nil, **options) + locale ||= config.locale + raise Disabled.new('l') if locale == false enforce_available_locales!(locale) + + format ||= :default config.backend.localize(locale, object, format, options) end alias :l :localize # Executes block with given I18n.locale set. def with_locale(tmp_locale = nil) - if tmp_locale + if tmp_locale == nil + yield + else current_locale = self.locale - self.locale = tmp_locale + self.locale = tmp_locale + begin + yield + ensure + self.locale = current_locale + end end - yield - ensure - self.locale = current_locale if tmp_locale end # Merges the given locale, key and scope into a single array of keys. @@ -264,11 +332,11 @@ def with_locale(tmp_locale = nil) def normalize_keys(locale, key, scope, separator = nil) separator ||= I18n.default_separator - keys = [] - keys.concat normalize_key(locale, separator) - keys.concat normalize_key(scope, separator) - keys.concat normalize_key(key, separator) - keys + [ + *normalize_key(locale, separator), + *normalize_key(scope, separator), + *normalize_key(key, separator) + ] end # Returns true when the passed locale, which can be either a String or a @@ -279,17 +347,29 @@ def locale_available?(locale) # Raises an InvalidLocale exception when the passed locale is not available. def enforce_available_locales!(locale) - handle_enforce_available_locales_deprecation - - if config.enforce_available_locales + if locale != false && config.enforce_available_locales raise I18n::InvalidLocale.new(locale) if !locale_available?(locale) end end - # making these private until Ruby 1.9.2 can send to protected methods again - # see http://redmine.ruby-lang.org/repositories/revision/ruby-19?rev=24280 + def available_locales_initialized? + config.available_locales_initialized? + end + private + def translate_key(key, throw, raise, locale, backend, options) + result = catch(:exception) do + backend.translate(locale, key, options) + end + + if result.is_a?(MissingTranslation) + handle_exception((throw && :throw || raise && :raise), result, locale, key, options) + else + result + end + end + # Any exceptions thrown in translate will be sent to the @@exception_handler # which can be a Symbol, a Proc or any other Object unless they're forced to # be raised or thrown (MissingTranslation). @@ -300,18 +380,18 @@ def enforce_available_locales!(locale) # # Examples: # - # I18n.exception_handler = :default_exception_handler # this is the default - # I18n.default_exception_handler(exception, locale, key, options) # will be called like this + # I18n.exception_handler = :custom_exception_handler # this is the default + # I18n.custom_exception_handler(exception, locale, key, options) # will be called like this # # I18n.exception_handler = lambda { |*args| ... } # a lambda # I18n.exception_handler.call(exception, locale, key, options) # will be called like this # - # I18n.exception_handler = I18nExceptionHandler.new # an object - # I18n.exception_handler.call(exception, locale, key, options) # will be called like this + # I18n.exception_handler = I18nExceptionHandler.new # an object + # I18n.exception_handler.call(exception, locale, key, options) # will be called like this def handle_exception(handling, exception, locale, key, options) case handling when :raise - raise(exception.respond_to?(:to_exception) ? exception.to_exception : exception) + raise exception.respond_to?(:to_exception) ? exception.to_exception : exception when :throw throw :exception, exception else @@ -324,41 +404,32 @@ def handle_exception(handling, exception, locale, key, options) end end + @@normalized_key_cache = I18n.new_double_nested_cache + def normalize_key(key, separator) - normalized_key_cache[separator][key] ||= + @@normalized_key_cache[separator][key] ||= case key when Array - key.map { |k| normalize_key(k, separator) }.flatten + key.flat_map { |k| normalize_key(k, separator) } else keys = key.to_s.split(separator) keys.delete('') - keys.map! { |k| k.to_sym } + keys.map! do |k| + case k + when /\A[-+]?([1-9]\d*|0)\z/ # integer + k.to_i + when 'true' + true + when 'false' + false + else + k.to_sym + end + end keys end end + end - def normalized_key_cache - @normalized_key_cache ||= Hash.new { |h,k| h[k] = {} } - end - - # DEPRECATED. Use I18n.normalize_keys instead. - def normalize_translation_keys(locale, key, scope, separator = nil) - puts "I18n.normalize_translation_keys is deprecated. Please use the class I18n.normalize_keys instead." - normalize_keys(locale, key, scope, separator) - end - - # DEPRECATED. Please use the I18n::ExceptionHandler class instead. - def default_exception_handler(exception, locale, key, options) - puts "I18n.default_exception_handler is deprecated. Please use the class I18n::ExceptionHandler instead " + - "(an instance of which is set to I18n.exception_handler by default)." - exception.is_a?(MissingTranslation) ? exception.message : raise(exception) - end - - def handle_enforce_available_locales_deprecation - if config.enforce_available_locales.nil? && !defined?(@unenforced_available_locales_deprecation) - $stderr.puts "[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message." - @unenforced_available_locales_deprecation = true - end - end - }) + extend Base end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend.rb similarity index 84% rename from app/server/ruby/vendor/i18n/lib/i18n/backend.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend.rb index 46ef054bfc..863d618782 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend.rb @@ -1,14 +1,18 @@ +# frozen_string_literal: true + module I18n module Backend autoload :Base, 'i18n/backend/base' - autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler' autoload :Cache, 'i18n/backend/cache' + autoload :CacheFile, 'i18n/backend/cache_file' autoload :Cascade, 'i18n/backend/cascade' autoload :Chain, 'i18n/backend/chain' autoload :Fallbacks, 'i18n/backend/fallbacks' autoload :Flatten, 'i18n/backend/flatten' autoload :Gettext, 'i18n/backend/gettext' + autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler' autoload :KeyValue, 'i18n/backend/key_value' + autoload :LazyLoadable, 'i18n/backend/lazy_loadable' autoload :Memoize, 'i18n/backend/memoize' autoload :Metadata, 'i18n/backend/metadata' autoload :Pluralization, 'i18n/backend/pluralization' diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/base.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/base.rb new file mode 100644 index 0000000000..57756758ca --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/base.rb @@ -0,0 +1,304 @@ +# frozen_string_literal: true + +require 'yaml' +require 'json' + +module I18n + module Backend + module Base + include I18n::Backend::Transliterator + + # Accepts a list of paths to translation files. Loads translations from + # plain Ruby (*.rb), YAML files (*.yml), or JSON files (*.json). See #load_rb, #load_yml, and #load_json + # for details. + def load_translations(*filenames) + filenames = I18n.load_path if filenames.empty? + filenames.flatten.each do |filename| + loaded_translations = load_file(filename) + yield filename, loaded_translations if block_given? + end + end + + # This method receives a locale, a data hash and options for storing translations. + # Should be implemented + def store_translations(locale, data, options = EMPTY_HASH) + raise NotImplementedError + end + + def translate(locale, key, options = EMPTY_HASH) + raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty? + raise InvalidLocale.new(locale) unless locale + return nil if key.nil? && !options.key?(:default) + + entry = lookup(locale, key, options[:scope], options) unless key.nil? + + if entry.nil? && options.key?(:default) + entry = default(locale, key, options[:default], options) + else + entry = resolve_entry(locale, key, entry, options) + end + + count = options[:count] + + if entry.nil? && (subtrees? || !count) + if (options.key?(:default) && !options[:default].nil?) || !options.key?(:default) + throw(:exception, I18n::MissingTranslation.new(locale, key, options)) + end + end + + entry = entry.dup if entry.is_a?(String) + entry = pluralize(locale, entry, count) if count + + if entry.nil? && !subtrees? + throw(:exception, I18n::MissingTranslation.new(locale, key, options)) + end + + deep_interpolation = options[:deep_interpolation] + values = Utils.except(options, *RESERVED_KEYS) unless options.empty? + if values + entry = if deep_interpolation + deep_interpolate(locale, entry, values) + else + interpolate(locale, entry, values) + end + end + entry + end + + def exists?(locale, key, options = EMPTY_HASH) + lookup(locale, key, options[:scope]) != nil + end + + # Acts the same as +strftime+, but uses a localized version of the + # format string. Takes a key from the date/time formats translations as + # a format argument (e.g., :short in :'date.formats'). + def localize(locale, object, format = :default, options = EMPTY_HASH) + if object.nil? && options.include?(:default) + return options[:default] + end + raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime) + + if Symbol === format + key = format + type = object.respond_to?(:sec) ? 'time' : 'date' + options = options.merge(:raise => true, :object => object, :locale => locale) + format = I18n.t(:"#{type}.formats.#{key}", **options) + end + + format = translate_localization_format(locale, object, format, options) + object.strftime(format) + end + + # Returns an array of locales for which translations are available + # ignoring the reserved translation meta data key :i18n. + def available_locales + raise NotImplementedError + end + + def reload! + eager_load! if eager_loaded? + end + + def eager_load! + @eager_loaded = true + end + + protected + + def eager_loaded? + @eager_loaded ||= false + end + + # The method which actually looks up for the translation in the store. + def lookup(locale, key, scope = [], options = EMPTY_HASH) + raise NotImplementedError + end + + def subtrees? + true + end + + # Evaluates defaults. + # If given subject is an Array, it walks the array and returns the + # first translation that can be resolved. Otherwise it tries to resolve + # the translation directly. + def default(locale, object, subject, options = EMPTY_HASH) + if options.size == 1 && options.has_key?(:default) + options = {} + else + options = Utils.except(options, :default) + end + + case subject + when Array + subject.each do |item| + result = resolve(locale, object, item, options) + return result unless result.nil? + end and nil + else + resolve(locale, object, subject, options) + end + end + + # Resolves a translation. + # If the given subject is a Symbol, it will be translated with the + # given options. If it is a Proc then it will be evaluated. All other + # subjects will be returned directly. + def resolve(locale, object, subject, options = EMPTY_HASH) + return subject if options[:resolve] == false + result = catch(:exception) do + case subject + when Symbol + I18n.translate(subject, **options.merge(:locale => locale, :throw => true)) + when Proc + date_or_time = options.delete(:object) || object + resolve(locale, object, subject.call(date_or_time, **options)) + else + subject + end + end + result unless result.is_a?(MissingTranslation) + end + alias_method :resolve_entry, :resolve + + # Picks a translation from a pluralized mnemonic subkey according to English + # pluralization rules : + # - It will pick the :one subkey if count is equal to 1. + # - It will pick the :other subkey otherwise. + # - It will pick the :zero subkey in the special case where count is + # equal to 0 and there is a :zero subkey present. This behaviour is + # not standard with regards to the CLDR pluralization rules. + # Other backends can implement more flexible or complex pluralization rules. + def pluralize(locale, entry, count) + entry = entry.reject { |k, _v| k == :attributes } if entry.is_a?(Hash) + return entry unless entry.is_a?(Hash) && count + + key = pluralization_key(entry, count) + raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key) + entry[key] + end + + # Interpolates values into a given subject. + # + # if the given subject is a string then: + # method interpolates "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X' + # # => "file test.txt opened by %{user}" + # + # if the given subject is an array then: + # each element of the array is recursively interpolated (until it finds a string) + # method interpolates ["yes, %{user}", ["maybe no, %{user}, "no, %{user}"]], :user => "bartuz" + # # => "["yes, bartuz",["maybe no, bartuz", "no, bartuz"]]" + def interpolate(locale, subject, values = EMPTY_HASH) + return subject if values.empty? + + case subject + when ::String then I18n.interpolate(subject, values) + when ::Array then subject.map { |element| interpolate(locale, element, values) } + else + subject + end + end + + # Deep interpolation + # + # deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } }, + # ann: 'good', john: 'big' + # #=> { people: { ann: "Ann is good", john: "John is big" } } + def deep_interpolate(locale, data, values = EMPTY_HASH) + return data if values.empty? + + case data + when ::String + I18n.interpolate(data, values) + when ::Hash + data.each_with_object({}) do |(k, v), result| + result[k] = deep_interpolate(locale, v, values) + end + when ::Array + data.map do |v| + deep_interpolate(locale, v, values) + end + else + data + end + end + + # Loads a single translations file by delegating to #load_rb or + # #load_yml depending on the file extension and directly merges the + # data to the existing translations. Raises I18n::UnknownFileType + # for all other file extensions. + def load_file(filename) + type = File.extname(filename).tr('.', '').downcase + raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true) + data, keys_symbolized = send(:"load_#{type}", filename) + unless data.is_a?(Hash) + raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not') + end + data.each { |locale, d| store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized) } + + data + end + + # Loads a plain Ruby translations file. eval'ing the file must yield + # a Hash containing translation data with locales as toplevel keys. + def load_rb(filename) + translations = eval(IO.read(filename), binding, filename) + [translations, false] + end + + # Loads a YAML translations file. The data must have locales as + # toplevel keys. + def load_yml(filename) + begin + if YAML.respond_to?(:unsafe_load_file) # Psych 4.0 way + [YAML.unsafe_load_file(filename, symbolize_names: true, freeze: true), true] + else + [YAML.load_file(filename), false] + end + rescue TypeError, ScriptError, StandardError => e + raise InvalidLocaleData.new(filename, e.inspect) + end + end + alias_method :load_yaml, :load_yml + + # Loads a JSON translations file. The data must have locales as + # toplevel keys. + def load_json(filename) + begin + # Use #load_file as a proxy for a version of JSON where symbolize_names and freeze are supported. + if ::JSON.respond_to?(:load_file) + [::JSON.load_file(filename, symbolize_names: true, freeze: true), true] + else + [::JSON.parse(File.read(filename)), false] + end + rescue TypeError, StandardError => e + raise InvalidLocaleData.new(filename, e.inspect) + end + end + + def translate_localization_format(locale, object, format, options) + format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match| + case match + when '%a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday] + when '%^a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday].upcase + when '%A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday] + when '%^A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday].upcase + when '%b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon] + when '%^b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon].upcase + when '%B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon] + when '%^B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon].upcase + when '%p' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).upcase + when '%P' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).downcase + end + end + rescue MissingTranslationData => e + e.message + end + + def pluralization_key(entry, count) + key = :zero if count == 0 && entry.has_key?(:zero) + key ||= count == 1 ? :one : :other + end + end + end +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/cache.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cache.rb similarity index 72% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/cache.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cache.rb index 3c456ff9e2..40c18d6575 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/cache.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This module allows you to easily cache all responses from the backend - thus # speeding up the I18n aspects of your application quite a bit. # @@ -13,9 +15,13 @@ # You can use any cache implementation you want that provides the same API as # ActiveSupport::Cache (only the methods #fetch and #write are being used). # -# The cache_key implementation assumes that you only pass values to -# I18n.translate that return a valid key from #hash (see -# http://www.ruby-doc.org/core/classes/Object.html#M000337). +# The cache_key implementation by default assumes you pass values that return +# a valid key from #hash (see +# https://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can +# configure your own digest method via which responds to #hexdigest (see +# https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/Digest.html): +# +# I18n.cache_key_digest = OpenSSL::Digest::SHA256.new # # If you use a lambda as a default value in your translation like this: # @@ -37,6 +43,7 @@ module I18n class << self @@cache_store = nil @@cache_namespace = nil + @@cache_key_digest = nil def cache_store @@cache_store @@ -54,6 +61,14 @@ def cache_namespace=(namespace) @@cache_namespace = namespace end + def cache_key_digest + @@cache_key_digest + end + + def cache_key_digest=(key_digest) + @@cache_key_digest = key_digest + end + def perform_caching? !cache_store.nil? end @@ -62,7 +77,7 @@ def perform_caching? module Backend # TODO Should the cache be cleared if new translations are stored? module Cache - def translate(locale, key, options = {}) + def translate(locale, key, options = EMPTY_HASH) I18n.perform_caching? ? fetch(cache_key(locale, key, options)) { super } : super end @@ -76,7 +91,8 @@ def fetch(cache_key, &block) end def _fetch(cache_key, &block) - result = I18n.cache_store.read(cache_key) and return result + result = I18n.cache_store.read(cache_key) + return result unless result.nil? result = catch(:exception, &block) I18n.cache_store.write(cache_key, result) unless result.is_a?(Proc) result @@ -84,13 +100,14 @@ def _fetch(cache_key, &block) def cache_key(locale, key, options) # This assumes that only simple, native Ruby values are passed to I18n.translate. - "i18n/#{I18n.cache_namespace}/#{locale}/#{key.hash}/#{USE_INSPECT_HASH ? options.inspect.hash : options.hash}" + "i18n/#{I18n.cache_namespace}/#{locale}/#{digest_item(key)}/#{digest_item(options)}" end private - # In Ruby < 1.9 the following is true: { :foo => 1, :bar => 2 }.hash == { :foo => 2, :bar => 1 }.hash - # Therefore we must use the hash of the inspect string instead to avoid cache key colisions. - USE_INSPECT_HASH = RUBY_VERSION <= "1.9" + + def digest_item(key) + I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.to_s.hash + end end end end diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cache_file.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cache_file.rb new file mode 100644 index 0000000000..0c5e192210 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cache_file.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'openssl' + +module I18n + module Backend + # Overwrites the Base load_file method to cache loaded file contents. + module CacheFile + # Optionally provide path_roots array to normalize filename paths, + # to make the cached i18n data portable across environments. + attr_accessor :path_roots + + protected + + # Track loaded translation files in the `i18n.load_file` scope, + # and skip loading the file if its contents are still up-to-date. + def load_file(filename) + initialized = !respond_to?(:initialized?) || initialized? + key = I18n::Backend::Flatten.escape_default_separator(normalized_path(filename)) + old_mtime, old_digest = initialized && lookup(:i18n, key, :load_file) + return if (mtime = File.mtime(filename).to_i) == old_mtime || + (digest = OpenSSL::Digest::SHA256.file(filename).hexdigest) == old_digest + super + store_translations(:i18n, load_file: { key => [mtime, digest] }) + end + + # Translate absolute filename to relative path for i18n key. + def normalized_path(file) + return file unless path_roots + path = path_roots.find(&file.method(:start_with?)) || + raise(InvalidLocaleData.new(file, 'outside expected path roots')) + file.sub(path, path_roots.index(path).to_s) + end + end + end +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/cascade.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cascade.rb similarity index 95% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/cascade.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cascade.rb index d8fb1cf48b..782b07b594 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/cascade.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/cascade.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # The Cascade module adds the ability to do cascading lookups to backends that # are compatible to the Simple backend. # @@ -31,7 +33,7 @@ module I18n module Backend module Cascade - def lookup(locale, key, scope = [], options = {}) + def lookup(locale, key, scope = [], options = EMPTY_HASH) return super unless cascade = options[:cascade] cascade = { :step => 1 } unless cascade.is_a?(Hash) diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/chain.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/chain.rb similarity index 54% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/chain.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/chain.rb index cb6ad43b33..e081a91c2a 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/chain.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/chain.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module I18n module Backend # Backend that chains multiple other backends and checks each of them when @@ -14,6 +16,8 @@ module Backend # # The implementation assumes that all backends added to the Chain implement # a lookup method with the same API as Simple backend does. + # + # Fallback translations using the :default option are only used by the last backend of a chain. class Chain module Implementation include Base @@ -24,11 +28,24 @@ def initialize(*backends) self.backends = backends end + def initialized? + backends.all? do |backend| + backend.instance_eval do + return false unless initialized? + end + end + true + end + def reload! backends.each { |backend| backend.reload! } end - def store_translations(locale, data, options = {}) + def eager_load! + backends.each { |backend| backend.eager_load! } + end + + def store_translations(locale, data, options = EMPTY_HASH) backends.first.store_translations(locale, data, options) end @@ -36,17 +53,17 @@ def available_locales backends.map { |backend| backend.available_locales }.flatten.uniq end - def translate(locale, key, default_options = {}) + def translate(locale, key, default_options = EMPTY_HASH) namespace = nil - options = default_options.except(:default) + options = Utils.except(default_options, :default) backends.each do |backend| catch(:exception) do options = default_options if backend == backends.last translation = backend.translate(locale, key, options) if namespace_lookup?(translation, options) - namespace = translation.merge(namespace || {}) - elsif !translation.nil? + namespace = _deep_merge(translation, namespace || {}) + elsif !translation.nil? || (options.key?(:default) && options[:default].nil?) return translation end end @@ -56,13 +73,13 @@ def translate(locale, key, default_options = {}) throw(:exception, I18n::MissingTranslation.new(locale, key, options)) end - def exists?(locale, key) + def exists?(locale, key, options = EMPTY_HASH) backends.any? do |backend| - backend.exists?(locale, key) + backend.exists?(locale, key, options) end end - def localize(locale, object, format = :default, options = {}) + def localize(locale, object, format = :default, options = EMPTY_HASH) backends.each do |backend| catch(:exception) do result = backend.localize(locale, object, format, options) and return result @@ -72,9 +89,39 @@ def localize(locale, object, format = :default, options = {}) end protected + def init_translations + backends.each do |backend| + backend.send(:init_translations) + end + end + + def translations + backends.reverse.each_with_object({}) do |backend, memo| + partial_translations = backend.instance_eval do + init_translations unless initialized? + translations + end + Utils.deep_merge!(memo, partial_translations) { |_, a, b| b || a } + end + end + def namespace_lookup?(result, options) result.is_a?(Hash) && !options.has_key?(:count) end + + private + # This is approximately what gets used in ActiveSupport. + # However since we are not guaranteed to run in an ActiveSupport context + # it is wise to have our own copy. We underscore it + # to not pollute the namespace of the including class. + def _deep_merge(hash, other_hash) + copy = hash.dup + other_hash.each_pair do |k,v| + value_from_other = hash[k] + copy[k] = value_from_other.is_a?(Hash) && v.is_a?(Hash) ? _deep_merge(value_from_other, v) : v + end + copy + end end include Implementation diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/fallbacks.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/fallbacks.rb similarity index 53% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/fallbacks.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/fallbacks.rb index d74b800ccf..6d4d6e138d 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/fallbacks.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/fallbacks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # I18n locale fallbacks are useful when you want your application to use # translations from other locales when translations for the current locale are # missing. E.g. you might want to use :en translations when translations in @@ -14,11 +16,13 @@ class << self # Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+. def fallbacks @@fallbacks ||= I18n::Locale::Fallbacks.new + Thread.current[:i18n_fallbacks] || @@fallbacks end # Sets the current fallbacks implementation. Use this to set a different fallbacks implementation. def fallbacks=(fallbacks) - @@fallbacks = fallbacks + @@fallbacks = fallbacks.is_a?(Array) ? I18n::Locale::Fallbacks.new(fallbacks) : fallbacks + Thread.current[:i18n_fallbacks] = @@fallbacks end end @@ -34,27 +38,50 @@ module Fallbacks # The default option takes precedence over fallback locales only when # it's a Symbol. When the default contains a String, Proc or Hash # it is evaluated last after all the fallback locales have been tried. - def translate(locale, key, options = {}) - return super if options[:fallback] + def translate(locale, key, options = EMPTY_HASH) + return super unless options.fetch(:fallback, true) + return super if options[:fallback_in_progress] default = extract_non_symbol_default!(options) if options[:default] - options[:fallback] = true + fallback_options = options.merge(:fallback_in_progress => true, fallback_original_locale: locale) I18n.fallbacks[locale].each do |fallback| begin catch(:exception) do - result = super(fallback, key, options) - return result unless result.nil? + result = super(fallback, key, fallback_options) + unless result.nil? + on_fallback(locale, fallback, key, options) if locale.to_s != fallback.to_s + return result + end end rescue I18n::InvalidLocale # we do nothing when the locale is invalid, as this is a fallback anyways. end end - options.delete(:fallback) + + return if options.key?(:default) && options[:default].nil? return super(locale, nil, options.merge(:default => default)) if default throw(:exception, I18n::MissingTranslation.new(locale, key, options)) end + def resolve_entry(locale, object, subject, options = EMPTY_HASH) + return subject if options[:resolve] == false + result = catch(:exception) do + options.delete(:fallback_in_progress) if options.key?(:fallback_in_progress) + + case subject + when Symbol + I18n.translate(subject, **options.merge(:locale => options[:fallback_original_locale], :throw => true)) + when Proc + date_or_time = options.delete(:object) || object + resolve_entry(options[:fallback_original_locale], object, subject.call(date_or_time, **options)) + else + subject + end + end + result unless result.is_a?(MissingTranslation) + end + def extract_non_symbol_default!(options) defaults = [options[:default]].flatten first_non_symbol_default = defaults.detect{|default| !default.is_a?(Symbol)} @@ -64,6 +91,25 @@ def extract_non_symbol_default!(options) return first_non_symbol_default end + def exists?(locale, key, options = EMPTY_HASH) + return super unless options.fetch(:fallback, true) + I18n.fallbacks[locale].each do |fallback| + begin + return true if super(fallback, key) + rescue I18n::InvalidLocale + # we do nothing when the locale is invalid, as this is a fallback anyways. + end + end + + false + end + + private + + # Overwrite on_fallback to add specified logic when the fallback succeeds. + def on_fallback(_original_locale, _fallback_locale, _key, _options) + nil + end end end end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/flatten.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/flatten.rb similarity index 90% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/flatten.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/flatten.rb index c23f7c1319..e9bd9d531d 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/flatten.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/flatten.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module I18n module Backend # This module contains several helpers to assist flattening translations. @@ -16,14 +18,17 @@ module Flatten # and creates way less objects than the one at I18n.normalize_keys. # It also handles escaping the translation keys. def self.normalize_flat_keys(locale, key, scope, separator) - keys = [scope, key].flatten.compact + keys = [scope, key] + keys.flatten! + keys.compact! + separator ||= I18n.default_separator if separator != FLATTEN_SEPARATOR - keys.map! do |k| - k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}", - "#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}") - end + from_str = "#{FLATTEN_SEPARATOR}#{separator}" + to_str = "#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}" + + keys.map! { |k| k.to_s.tr from_str, to_str } end keys.join(".") @@ -43,7 +48,7 @@ def normalize_flat_keys(locale, key, scope, separator) # Store flattened links. def links - @links ||= Hash.new { |h,k| h[k] = {} } + @links ||= I18n.new_double_nested_cache end # Flatten keys for nested Hashes by chaining up keys: @@ -99,7 +104,7 @@ def resolve_link(locale, key) end def find_link(locale, key) #:nodoc: - links[locale].each do |from, to| + links[locale].each_pair do |from, to| return [from, to] if key[0, from.length] == from end && nil end @@ -110,4 +115,4 @@ def escape_default_separator(key) #:nodoc: end end -end \ No newline at end of file +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/gettext.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/gettext.rb similarity index 60% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/gettext.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/gettext.rb index c357a6d4d2..076964638e 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/gettext.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/gettext.rb @@ -1,24 +1,35 @@ +# frozen_string_literal: true + require 'i18n/gettext' require 'i18n/gettext/po_parser' -# Experimental support for using Gettext po files to store translations. -# -# To use this you can simply include the module to the Simple backend - or -# whatever other backend you are using. -# -# I18n::Backend::Simple.include(I18n::Backend::Gettext) -# -# Now you should be able to include your Gettext translation (*.po) files to -# the I18n.load_path so they're loaded to the backend and you can use them as -# usual: -# -# I18n.load_path += Dir["path/to/locales/*.po"] -# -# Following the Gettext convention this implementation expects that your -# translation files are named by their locales. E.g. the file en.po would -# contain the translations for the English locale. module I18n module Backend + # Experimental support for using Gettext po files to store translations. + # + # To use this you can simply include the module to the Simple backend - or + # whatever other backend you are using. + # + # I18n::Backend::Simple.include(I18n::Backend::Gettext) + # + # Now you should be able to include your Gettext translation (*.po) files to + # the +I18n.load_path+ so they're loaded to the backend and you can use them as + # usual: + # + # I18n.load_path += Dir["path/to/locales/*.po"] + # + # Following the Gettext convention this implementation expects that your + # translation files are named by their locales. E.g. the file en.po would + # contain the translations for the English locale. + # + # To translate text you must use one of the translate methods provided by + # I18n::Gettext::Helpers. + # + # include I18n::Gettext::Helpers + # puts _("some string") + # + # Without it strings containing periods (".") will not be translated. + module Gettext class PoData < Hash def set_comment(msgid_or_sym, comment) @@ -30,7 +41,7 @@ def set_comment(msgid_or_sym, comment) def load_po(filename) locale = ::File.basename(filename, '.po').to_sym data = normalize(locale, parse(filename)) - { locale => data } + [{ locale => data }, false] end def parse(filename) @@ -48,7 +59,7 @@ def normalize(locale, data) { part => _normalized.empty? ? value : _normalized } end - result.deep_merge!(normalized) + Utils.deep_merge!(result, normalized) end result end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/interpolation_compiler.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/interpolation_compiler.rb similarity index 96% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/interpolation_compiler.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/interpolation_compiler.rb index c544070444..8b52e7b3e9 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/interpolation_compiler.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/interpolation_compiler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # The InterpolationCompiler module contains optimizations that can tremendously # speed up the interpolation process on the Simple backend. # @@ -12,7 +14,7 @@ # # Note that InterpolationCompiler does not yield meaningful results and consequently # should not be used with Ruby 1.9 (YARV) but improves performance everywhere else -# (jRuby, Rubinius and 1.8.7). +# (jRuby, Rubinius). module I18n module Backend module InterpolationCompiler @@ -104,7 +106,7 @@ def interpolate(locale, string, values) end end - def store_translations(locale, data, options = {}) + def store_translations(locale, data, options = EMPTY_HASH) compile_all_strings_in(data) super end diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/key_value.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/key_value.rb new file mode 100644 index 0000000000..b937e253a8 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/key_value.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true + +require 'i18n/backend/base' + +module I18n + + begin + require 'oj' + class JSON + class << self + def encode(value) + Oj::Rails.encode(value) + end + def decode(value) + Oj.load(value) + end + end + end + rescue LoadError + require 'active_support/json' + JSON = ActiveSupport::JSON + end + + module Backend + # This is a basic backend for key value stores. It receives on + # initialization the store, which should respond to three methods: + # + # * store#[](key) - Used to get a value + # * store#[]=(key, value) - Used to set a value + # * store#keys - Used to get all keys + # + # Since these stores only supports string, all values are converted + # to JSON before being stored, allowing it to also store booleans, + # hashes and arrays. However, this store does not support Procs. + # + # As the ActiveRecord backend, Symbols are just supported when loading + # translations from the filesystem or through explicit store translations. + # + # Also, avoid calling I18n.available_locales since it's a somehow + # expensive operation in most stores. + # + # == Example + # + # To setup I18n to use TokyoCabinet in memory is quite straightforward: + # + # require 'rufus/tokyo/cabinet' # gem install rufus-tokyo + # I18n.backend = I18n::Backend::KeyValue.new(Rufus::Tokyo::Cabinet.new('*')) + # + # == Performance + # + # You may make this backend even faster by including the Memoize module. + # However, notice that you should properly clear the cache if you change + # values directly in the key-store. + # + # == Subtrees + # + # In most backends, you are allowed to retrieve part of a translation tree: + # + # I18n.backend.store_translations :en, :foo => { :bar => :baz } + # I18n.t "foo" #=> { :bar => :baz } + # + # This backend supports this feature by default, but it slows down the storage + # of new data considerably and makes hard to delete entries. That said, you are + # allowed to disable the storage of subtrees on initialization: + # + # I18n::Backend::KeyValue.new(@store, false) + # + # This is useful if you are using a KeyValue backend chained to a Simple backend. + class KeyValue + module Implementation + attr_accessor :store + + include Base, Flatten + + def initialize(store, subtrees=true) + @store, @subtrees = store, subtrees + end + + def initialized? + !@store.nil? + end + + def store_translations(locale, data, options = EMPTY_HASH) + escape = options.fetch(:escape, true) + flatten_translations(locale, data, escape, @subtrees).each do |key, value| + key = "#{locale}.#{key}" + + case value + when Hash + if @subtrees && (old_value = @store[key]) + old_value = JSON.decode(old_value) + value = Utils.deep_merge!(Utils.deep_symbolize_keys(old_value), value) if old_value.is_a?(Hash) + end + when Proc + raise "Key-value stores cannot handle procs" + end + + @store[key] = JSON.encode(value) unless value.is_a?(Symbol) + end + end + + def available_locales + locales = @store.keys.map { |k| k =~ /\./; $` } + locales.uniq! + locales.compact! + locales.map! { |k| k.to_sym } + locales + end + + protected + + # Queries the translations from the key-value store and converts + # them into a hash such as the one returned from loading the + # haml files + def translations + @translations = Utils.deep_symbolize_keys(@store.keys.clone.map do |main_key| + main_value = JSON.decode(@store[main_key]) + main_key.to_s.split(".").reverse.inject(main_value) do |value, key| + {key.to_sym => value} + end + end.inject{|hash, elem| Utils.deep_merge!(hash, elem)}) + end + + def init_translations + # NO OP + # This call made also inside Simple Backend and accessed by + # other plugins like I18n-js and babilu and + # to use it along with the Chain backend we need to + # provide a uniform API even for protected methods :S + end + + def subtrees? + @subtrees + end + + def lookup(locale, key, scope = [], options = EMPTY_HASH) + key = normalize_flat_keys(locale, key, scope, options[:separator]) + value = @store["#{locale}.#{key}"] + value = JSON.decode(value) if value + + if value.is_a?(Hash) + Utils.deep_symbolize_keys(value) + elsif !value.nil? + value + elsif !@subtrees + SubtreeProxy.new("#{locale}.#{key}", @store) + end + end + + def pluralize(locale, entry, count) + if subtrees? + super + else + return entry unless entry.is_a?(Hash) + key = pluralization_key(entry, count) + entry[key] + end + end + end + + class SubtreeProxy + def initialize(master_key, store) + @master_key = master_key + @store = store + @subtree = nil + end + + def has_key?(key) + @subtree && @subtree.has_key?(key) || self[key] + end + + def [](key) + unless @subtree && value = @subtree[key] + value = @store["#{@master_key}.#{key}"] + if value + value = JSON.decode(value) + (@subtree ||= {})[key] = value + end + end + value + end + + def is_a?(klass) + Hash == klass || super + end + alias :kind_of? :is_a? + + def instance_of?(klass) + Hash == klass || super + end + + def nil? + @subtree.nil? + end + + def inspect + @subtree.inspect + end + end + + include Implementation + end + end +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/lazy_loadable.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/lazy_loadable.rb new file mode 100644 index 0000000000..575b32bfa5 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/lazy_loadable.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +module I18n + module Backend + # Backend that lazy loads translations based on the current locale. This + # implementation avoids loading all translations up front. Instead, it only + # loads the translations that belong to the current locale. This offers a + # performance incentive in local development and test environments for + # applications with many translations for many different locales. It's + # particularly useful when the application only refers to a single locales' + # translations at a time (ex. A Rails workload). The implementation + # identifies which translation files from the load path belong to the + # current locale by pattern matching against their path name. + # + # Specifically, a translation file is considered to belong to a locale if: + # a) the filename is in the I18n load path + # b) the filename ends in a supported extension (ie. .yml, .json, .po, .rb) + # c) the filename starts with the locale identifier + # d) the locale identifier and optional proceeding text is separated by an underscore, ie. "_". + # + # Examples: + # Valid files that will be selected by this backend: + # + # "files/locales/en_translation.yml" (Selected for locale "en") + # "files/locales/fr.po" (Selected for locale "fr") + # + # Invalid files that won't be selected by this backend: + # + # "files/locales/translation-file" + # "files/locales/en-translation.unsupported" + # "files/locales/french/translation.yml" + # "files/locales/fr/translation.yml" + # + # The implementation uses this assumption to defer the loading of + # translation files until the current locale actually requires them. + # + # The backend has two working modes: lazy_load and eager_load. + # + # Note: This backend should only be enabled in test environments! + # When the mode is set to false, the backend behaves exactly like the + # Simple backend, with an additional check that the paths being loaded + # abide by the format. If paths can't be matched to the format, an error is raised. + # + # You can configure lazy loaded backends through the initializer or backends + # accessor: + # + # # In test environments + # + # I18n.backend = I18n::Backend::LazyLoadable.new(lazy_load: true) + # + # # In other environments, such as production and CI + # + # I18n.backend = I18n::Backend::LazyLoadable.new(lazy_load: false) # default + # + class LocaleExtractor + class << self + def locale_from_path(path) + name = File.basename(path, ".*") + locale = name.split("_").first + locale.to_sym unless locale.nil? + end + end + end + + class LazyLoadable < Simple + def initialize(lazy_load: false) + @lazy_load = lazy_load + end + + # Returns whether the current locale is initialized. + def initialized? + if lazy_load? + initialized_locales[I18n.locale] + else + super + end + end + + # Clean up translations and uninitialize all locales. + def reload! + if lazy_load? + @initialized_locales = nil + @translations = nil + else + super + end + end + + # Eager loading is not supported in the lazy context. + def eager_load! + if lazy_load? + raise UnsupportedMethod.new(__method__, self.class, "Cannot eager load translations because backend was configured with lazy_load: true.") + else + super + end + end + + # Parse the load path and extract all locales. + def available_locales + if lazy_load? + I18n.load_path.map { |path| LocaleExtractor.locale_from_path(path) }.uniq + else + super + end + end + + def lookup(locale, key, scope = [], options = EMPTY_HASH) + if lazy_load? + I18n.with_locale(locale) do + super + end + else + super + end + end + + protected + + + # Load translations from files that belong to the current locale. + def init_translations + file_errors = if lazy_load? + initialized_locales[I18n.locale] = true + load_translations_and_collect_file_errors(filenames_for_current_locale) + else + @initialized = true + load_translations_and_collect_file_errors(I18n.load_path) + end + + raise InvalidFilenames.new(file_errors) unless file_errors.empty? + end + + def initialized_locales + @initialized_locales ||= Hash.new(false) + end + + private + + def lazy_load? + @lazy_load + end + + class FilenameIncorrect < StandardError + def initialize(file, expected_locale, unexpected_locales) + super "#{file} can only load translations for \"#{expected_locale}\". Found translations for: #{unexpected_locales}." + end + end + + # Loads each file supplied and asserts that the file only loads + # translations as expected by the name. The method returns a list of + # errors corresponding to offending files. + def load_translations_and_collect_file_errors(files) + errors = [] + + load_translations(files) do |file, loaded_translations| + assert_file_named_correctly!(file, loaded_translations) + rescue FilenameIncorrect => e + errors << e + end + + errors + end + + # Select all files from I18n load path that belong to current locale. + # These files must start with the locale identifier (ie. "en", "pt-BR"), + # followed by an "_" demarcation to separate proceeding text. + def filenames_for_current_locale + I18n.load_path.flatten.select do |path| + LocaleExtractor.locale_from_path(path) == I18n.locale + end + end + + # Checks if a filename is named in correspondence to the translations it loaded. + # The locale extracted from the path must be the single locale loaded in the translations. + def assert_file_named_correctly!(file, translations) + loaded_locales = translations.keys.map(&:to_sym) + expected_locale = LocaleExtractor.locale_from_path(file) + unexpected_locales = loaded_locales.reject { |locale| locale == expected_locale } + + raise FilenameIncorrect.new(file, expected_locale, unexpected_locales) unless unexpected_locales.empty? + end + end + end +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/memoize.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/memoize.rb similarity index 78% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/memoize.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/memoize.rb index ae9801fca3..3293d2b427 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/memoize.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/memoize.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Memoize module simply memoizes the values returned by lookup using # a flat hash and can tremendously speed up the lookup process in a backend. # @@ -14,7 +16,7 @@ def available_locales @memoized_locales ||= super end - def store_translations(locale, data, options = {}) + def store_translations(locale, data, options = EMPTY_HASH) reset_memoizations!(locale) super end @@ -24,9 +26,15 @@ def reload! super end + def eager_load! + memoized_lookup + available_locales + super + end + protected - def lookup(locale, key, scope = nil, options = {}) + def lookup(locale, key, scope = nil, options = EMPTY_HASH) flat_key = I18n::Backend::Flatten.normalize_flat_keys(locale, key, scope, options[:separator]).to_sym flat_hash = memoized_lookup[locale.to_sym] @@ -34,7 +42,7 @@ def lookup(locale, key, scope = nil, options = {}) end def memoized_lookup - @memoized_lookup ||= Hash.new { |h, k| h[k] = {} } + @memoized_lookup ||= I18n.new_double_nested_cache end def reset_memoizations!(locale=nil) diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/metadata.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/metadata.rb similarity index 79% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/metadata.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/metadata.rb index 52c0a295b9..51ea7a2a88 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/metadata.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/metadata.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # I18n translation metadata is useful when you want to access information # about how a translation was looked up, pluralized or interpolated in # your application. @@ -21,29 +23,33 @@ class << self def included(base) Object.class_eval do def translation_metadata - @translation_metadata ||= {} + unless self.frozen? + @translation_metadata ||= {} + else + {} + end end def translation_metadata=(translation_metadata) - @translation_metadata = translation_metadata + @translation_metadata = translation_metadata unless self.frozen? end end unless Object.method_defined?(:translation_metadata) end end - def translate(locale, key, options = {}) + def translate(locale, key, options = EMPTY_HASH) metadata = { :locale => locale, :key => key, :scope => options[:scope], :default => options[:default], :separator => options[:separator], - :values => options.reject { |name, value| RESERVED_KEYS.include?(name) } + :values => options.reject { |name, _value| RESERVED_KEYS.include?(name) } } with_metadata(metadata) { super } end - def interpolate(locale, entry, values = {}) + def interpolate(locale, entry, values = EMPTY_HASH) metadata = entry.translation_metadata.merge(:original => entry) with_metadata(metadata) { super } end diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/pluralization.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/pluralization.rb new file mode 100644 index 0000000000..1d3277b886 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/pluralization.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +# I18n Pluralization are useful when you want your application to +# customize pluralization rules. +# +# To enable locale specific pluralizations you can simply include the +# Pluralization module to the Simple backend - or whatever other backend you +# are using. +# +# I18n::Backend::Simple.include(I18n::Backend::Pluralization) +# +# You also need to make sure to provide pluralization algorithms to the +# backend, i.e. include them to your I18n.load_path accordingly. +module I18n + module Backend + module Pluralization + # Overwrites the Base backend translate method so that it will check the + # translation meta data space (:i18n) for a locale specific pluralization + # rule and use it to pluralize the given entry. I.e., the library expects + # pluralization rules to be stored at I18n.t(:'i18n.plural.rule') + # + # Pluralization rules are expected to respond to #call(count) and + # return a pluralization key. Valid keys depend on the pluralization + # rules for the locale, as defined in the CLDR. + # As of v41, 6 locale-specific plural categories are defined: + # :few, :many, :one, :other, :two, :zero + # + # n.b., The :one plural category does not imply the number 1. + # Instead, :one is a category for any number that behaves like 1 in + # that locale. For example, in some locales, :one is used for numbers + # that end in "1" (like 1, 21, 151) but that don't end in + # 11 (like 11, 111, 10311). + # Similar notes apply to the :two, and :zero plural categories. + # + # If you want to have different strings for the categories of count == 0 + # (e.g. "I don't have any cars") or count == 1 (e.g. "I have a single car") + # use the explicit `"0"` and `"1"` keys. + # https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules + def pluralize(locale, entry, count) + return entry unless entry.is_a?(Hash) && count + + pluralizer = pluralizer(locale) + if pluralizer.respond_to?(:call) + # Deprecation: The use of the `zero` key in this way is incorrect. + # Users that want a different string for the case of `count == 0` should use the explicit "0" key instead. + # We keep this incorrect behaviour for now for backwards compatibility until we can remove it. + # Ref: https://github.com/ruby-i18n/i18n/issues/629 + return entry[:zero] if count == 0 && entry.has_key?(:zero) + + # "0" and "1" are special cases + # https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules + if count == 0 || count == 1 + value = entry[symbolic_count(count)] + return value if value + end + + # Lateral Inheritance of "count" attribute (http://www.unicode.org/reports/tr35/#Lateral_Inheritance): + # > If there is no value for a path, and that path has a [@count="x"] attribute and value, then: + # > 1. If "x" is numeric, the path falls back to the path with [@count=«the plural rules category for x for that locale»], within that the same locale. + # > 2. If "x" is anything but "other", it falls back to a path [@count="other"], within that the same locale. + # > 3. If "x" is "other", it falls back to the path that is completely missing the count item, within that the same locale. + # Note: We don't yet implement #3 above, since we haven't decided how lateral inheritance attributes should be represented. + plural_rule_category = pluralizer.call(count) + + value = if entry.has_key?(plural_rule_category) || entry.has_key?(:other) + entry[plural_rule_category] || entry[:other] + else + raise InvalidPluralizationData.new(entry, count, plural_rule_category) + end + else + super + end + end + + protected + + def pluralizers + @pluralizers ||= {} + end + + def pluralizer(locale) + pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false) + end + + private + + # Normalizes categories of 0.0 and 1.0 + # and returns the symbolic version + def symbolic_count(count) + count = 0 if count == 0 + count = 1 if count == 1 + count.to_s.to_sym + end + end + end +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/backend/simple.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/simple.rb similarity index 56% rename from app/server/ruby/vendor/i18n/lib/i18n/backend/simple.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/simple.rb index 95ffb6ab6f..7caa7dd1ad 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/backend/simple.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/simple.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +require 'i18n/backend/base' + module I18n module Backend # A simple backend that reads translations from YAML files and stores them in @@ -15,10 +19,11 @@ module Backend # # I18n::Backend::Simple.include(I18n::Backend::Pluralization) class Simple - (class << self; self; end).class_eval { public :include } - module Implementation include Base + + # Mutex to ensure that concurrent translations loading will be thread-safe + MUTEX = Mutex.new def initialized? @initialized ||= false @@ -28,18 +33,23 @@ def initialized? # This uses a deep merge for the translations hash, so existing # translations will be overwritten by new ones only at the deepest # level of the hash. - def store_translations(locale, data, options = {}) + def store_translations(locale, data, options = EMPTY_HASH) + if I18n.enforce_available_locales && + I18n.available_locales_initialized? && + !I18n.locale_available?(locale) + return data + end locale = locale.to_sym - translations[locale] ||= {} - data = data.deep_symbolize_keys - translations[locale].deep_merge!(data) + translations[locale] ||= Concurrent::Hash.new + data = Utils.deep_symbolize_keys(data) unless options.fetch(:skip_symbolize_keys, false) + Utils.deep_merge!(translations[locale], data) end # Get available locales from the translations hash def available_locales init_translations unless initialized? translations.inject([]) do |locales, (locale, data)| - locales << locale unless (data.keys - [:i18n]).empty? + locales << locale unless data.size <= 1 && (data.empty? || data.has_key?(:i18n)) locales end end @@ -51,6 +61,23 @@ def reload! super end + def eager_load! + init_translations unless initialized? + super + end + + def translations(do_init: false) + # To avoid returning empty translations, + # call `init_translations` + init_translations if do_init && !initialized? + + @translations ||= Concurrent::Hash.new do |h, k| + MUTEX.synchronize do + h[k] = Concurrent::Hash.new + end + end + end + protected def init_translations @@ -58,24 +85,23 @@ def init_translations @initialized = true end - def translations - @translations ||= {} - end - # Looks up a translation from the translations hash. Returns nil if - # eiher key is nil, or locale, scope or key do not exist as a key in the + # either key is nil, or locale, scope or key do not exist as a key in the # nested translations hash. Splits keys or scopes containing dots # into multiple keys, i.e. currency.format is regarded the same as # %w(currency format). - def lookup(locale, key, scope = [], options = {}) + def lookup(locale, key, scope = [], options = EMPTY_HASH) init_translations unless initialized? keys = I18n.normalize_keys(locale, key, scope, options[:separator]) keys.inject(translations) do |result, _key| - _key = _key.to_sym - return nil unless result.is_a?(Hash) && result.has_key?(_key) + return nil unless result.is_a?(Hash) + unless result.has_key?(_key) + _key = _key.to_s.to_sym + return nil unless result.has_key?(_key) + end result = result[_key] - result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol) + result = resolve_entry(locale, _key, result, Utils.except(options.merge(:scope => nil), :count)) if result.is_a?(Symbol) result end end diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/transliterator.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/transliterator.rb new file mode 100644 index 0000000000..70c0df3dc8 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/backend/transliterator.rb @@ -0,0 +1,108 @@ +# encoding: utf-8 +# frozen_string_literal: true + +module I18n + module Backend + module Transliterator + DEFAULT_REPLACEMENT_CHAR = "?" + + # Given a locale and a UTF-8 string, return the locale's ASCII + # approximation for the string. + def transliterate(locale, string, replacement = nil) + @transliterators ||= {} + @transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule', + :locale => locale, :resolve => false, :default => {}) + @transliterators[locale].transliterate(string, replacement) + end + + # Get a transliterator instance. + def self.get(rule = nil) + if !rule || rule.kind_of?(Hash) + HashTransliterator.new(rule) + elsif rule.kind_of? Proc + ProcTransliterator.new(rule) + else + raise I18n::ArgumentError, "Transliteration rule must be a proc or a hash." + end + end + + # A transliterator which accepts a Proc as its transliteration rule. + class ProcTransliterator + def initialize(rule) + @rule = rule + end + + def transliterate(string, replacement = nil) + @rule.call(string) + end + end + + # A transliterator which accepts a Hash of characters as its translation + # rule. + class HashTransliterator + DEFAULT_APPROXIMATIONS = { + "À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE", + "Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I", + "Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O", + "Õ"=>"O", "Ö"=>"O", "×"=>"x", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U", + "Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "ẞ"=>"SS", "à"=>"a", + "á"=>"a", "â"=>"a", "ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", + "è"=>"e", "é"=>"e", "ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", + "ï"=>"i", "ð"=>"d", "ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", + "ö"=>"o", "ø"=>"o", "ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", + "þ"=>"th", "ÿ"=>"y", "Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", + "ą"=>"a", "Ć"=>"C", "ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", + "Č"=>"C", "č"=>"c", "Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", + "ē"=>"e", "Ĕ"=>"E", "ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", + "Ě"=>"E", "ě"=>"e", "Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", + "ġ"=>"g", "Ģ"=>"G", "ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", + "Ĩ"=>"I", "ĩ"=>"i", "Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", + "į"=>"i", "İ"=>"I", "ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", + "Ķ"=>"K", "ķ"=>"k", "ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", + "Ľ"=>"L", "ľ"=>"l", "Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", + "ń"=>"n", "Ņ"=>"N", "ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG", + "ŋ"=>"ng", "Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", + "Œ"=>"OE", "œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", + "ř"=>"r", "Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", + "Š"=>"S", "š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", + "ŧ"=>"t", "Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", + "Ů"=>"U", "ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", + "ŵ"=>"w", "Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", + "ż"=>"z", "Ž"=>"Z", "ž"=>"z" + }.freeze + + def initialize(rule = nil) + @rule = rule + add_default_approximations + add rule if rule + end + + def transliterate(string, replacement = nil) + replacement ||= DEFAULT_REPLACEMENT_CHAR + string.gsub(/[^\x00-\x7f]/u) do |char| + approximations[char] || replacement + end + end + + private + + def approximations + @approximations ||= {} + end + + def add_default_approximations + DEFAULT_APPROXIMATIONS.each do |key, value| + approximations[key] = value + end + end + + # Add transliteration rules to the approximations hash. + def add(hash) + hash.each do |key, value| + approximations[key.to_s] = value.to_s + end + end + end + end + end +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/config.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/config.rb similarity index 71% rename from app/server/ruby/vendor/i18n/lib/i18n/config.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/config.rb index b5750bd496..9878e02e70 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/config.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/config.rb @@ -1,15 +1,19 @@ +# frozen_string_literal: true + +require 'set' + module I18n class Config # The only configuration value that is not global and scoped to thread is :locale. # It defaults to the default_locale. def locale - @locale ||= default_locale + defined?(@locale) && @locale != nil ? @locale : default_locale end # Sets the current locale pseudo-globally, i.e. in the Thread.current hash. def locale=(locale) I18n.enforce_available_locales!(locale) - @locale = locale.to_sym rescue nil + @locale = locale && locale.to_sym end # Returns the current backend. Defaults to +Backend::Simple+. @@ -30,11 +34,11 @@ def default_locale # Sets the current default locale. Used to set a custom default locale. def default_locale=(locale) I18n.enforce_available_locales!(locale) - @@default_locale = locale.to_sym rescue nil + @@default_locale = locale && locale.to_sym end # Returns an array of locales for which translations are available. - # Unless you explicitely set these through I18n.available_locales= + # Unless you explicitly set these through I18n.available_locales= # the call will be delegated to the backend. def available_locales @@available_locales ||= nil @@ -56,6 +60,17 @@ def available_locales=(locales) @@available_locales_set = nil end + # Returns true if the available_locales have been initialized + def available_locales_initialized? + ( !!defined?(@@available_locales) && !!@@available_locales ) + end + + # Clears the available locales set so it can be recomputed again after I18n + # gets reloaded. + def clear_available_locales_set #:nodoc: + @@available_locales_set = nil + end + # Returns the current default scope separator. Defaults to '.' def default_separator @@default_separator ||= '.' @@ -66,7 +81,8 @@ def default_separator=(separator) @@default_separator = separator end - # Return the current exception handler. Defaults to :default_exception_handler. + # Returns the current exception handler. Defaults to an instance of + # I18n::ExceptionHandler. def exception_handler @@exception_handler ||= ExceptionHandler.new end @@ -90,7 +106,7 @@ def missing_interpolation_argument_handler # if you don't care about arity. # # == Example: - # You can supress raising an exception and return string instead: + # You can suppress raising an exception and return string instead: # # I18n.config.missing_interpolation_argument_handler = Proc.new do |key| # "#{key} is missing" @@ -115,16 +131,35 @@ def load_path # behave like a Ruby Array. def load_path=(load_path) @@load_path = load_path + @@available_locales_set = nil + backend.reload! end - # [Deprecated] this will default to true in the future - # Defaults to nil so that it triggers the deprecation warning + # Whether or not to verify if locales are in the list of available locales. + # Defaults to true. + @@enforce_available_locales = true def enforce_available_locales - defined?(@@enforce_available_locales) ? @@enforce_available_locales : nil + @@enforce_available_locales end def enforce_available_locales=(enforce_available_locales) @@enforce_available_locales = enforce_available_locales end + + # Returns the current interpolation patterns. Defaults to + # I18n::DEFAULT_INTERPOLATION_PATTERNS. + def interpolation_patterns + @@interpolation_patterns ||= I18n::DEFAULT_INTERPOLATION_PATTERNS.dup + end + + # Sets the current interpolation patterns. Used to set a interpolation + # patterns. + # + # E.g. using {{}} as a placeholder like "{{hello}}, world!": + # + # I18n.config.interpolation_patterns << /\{\{(\w+)\}\}/ + def interpolation_patterns=(interpolation_patterns) + @@interpolation_patterns = interpolation_patterns + end end end diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/exceptions.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/exceptions.rb new file mode 100644 index 0000000000..23ca46ecbd --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/exceptions.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require 'cgi' + +module I18n + class ExceptionHandler + def call(exception, _locale, _key, _options) + if exception.is_a?(MissingTranslation) + exception.message + else + raise exception + end + end + end + + class ArgumentError < ::ArgumentError; end + + class Disabled < ArgumentError + def initialize(method) + super(<<~MESSAGE) + I18n.#{method} is currently disabled, likely because your application is still in its loading phase. + + This method is meant to display text in the user locale, so calling it before the user locale has + been set is likely to display text from the wrong locale to some users. + + If you have a legitimate reason to access i18n data outside of the user flow, you can do so by passing + the desired locale explicitly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)` + MESSAGE + end + end + + class InvalidLocale < ArgumentError + attr_reader :locale + def initialize(locale) + @locale = locale + super "#{locale.inspect} is not a valid locale" + end + end + + class InvalidLocaleData < ArgumentError + attr_reader :filename + def initialize(filename, exception_message) + @filename, @exception_message = filename, exception_message + super "can not load translations from #{filename}: #{exception_message}" + end + end + + class MissingTranslation < ArgumentError + module Base + PERMITTED_KEYS = [:scope, :default].freeze + + attr_reader :locale, :key, :options + + def initialize(locale, key, options = EMPTY_HASH) + @key, @locale, @options = key, locale, options.slice(*PERMITTED_KEYS) + options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) } + end + + def keys + @keys ||= I18n.normalize_keys(locale, key, options[:scope]).tap do |keys| + keys << 'no key' if keys.size < 2 + end + end + + def message + if (default = options[:default]).is_a?(Array) && default.any? + other_options = ([key, *default]).map { |k| normalized_option(k).prepend('- ') }.join("\n") + "Translation missing. Options considered were:\n#{other_options}" + else + "Translation missing: #{keys.join('.')}" + end + end + + def normalized_option(key) + I18n.normalize_keys(locale, key, options[:scope]).join('.') + end + + alias :to_s :message + + def to_exception + MissingTranslationData.new(locale, key, options) + end + end + + include Base + end + + class MissingTranslationData < ArgumentError + include MissingTranslation::Base + end + + class InvalidPluralizationData < ArgumentError + attr_reader :entry, :count, :key + def initialize(entry, count, key) + @entry, @count, @key = entry, count, key + super "translation data #{entry.inspect} can not be used with :count => #{count}. key '#{key}' is missing." + end + end + + class MissingInterpolationArgument < ArgumentError + attr_reader :key, :values, :string + def initialize(key, values, string) + @key, @values, @string = key, values, string + super "missing interpolation argument #{key.inspect} in #{string.inspect} (#{values.inspect} given)" + end + end + + class ReservedInterpolationKey < ArgumentError + attr_reader :key, :string + def initialize(key, string) + @key, @string = key, string + super "reserved key #{key.inspect} used in #{string.inspect}" + end + end + + class UnknownFileType < ArgumentError + attr_reader :type, :filename + def initialize(type, filename) + @type, @filename = type, filename + super "can not load translations from #{filename}, the file type #{type} is not known" + end + end + + class UnsupportedMethod < ArgumentError + attr_reader :method, :backend_klass, :msg + def initialize(method, backend_klass, msg) + @method = method + @backend_klass = backend_klass + @msg = msg + super "#{backend_klass} does not support the ##{method} method. #{msg}" + end + end + + class InvalidFilenames < ArgumentError + NUMBER_OF_ERRORS_SHOWN = 20 + def initialize(file_errors) + super <<~MSG + Found #{file_errors.count} error(s). + The first #{[file_errors.count, NUMBER_OF_ERRORS_SHOWN].min} error(s): + #{file_errors.map(&:message).first(NUMBER_OF_ERRORS_SHOWN).join("\n")} + + To use the LazyLoadable backend: + 1. Filenames must start with the locale. + 2. An underscore must separate the locale with any optional text that follows. + 3. The file must only contain translation data for the single locale. + + Example: + "/config/locales/fr.yml" which contains: + ```yml + fr: + dog: + chien + ``` + MSG + end + end +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/gettext.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext.rb similarity index 56% rename from app/server/ruby/vendor/i18n/lib/i18n/gettext.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext.rb index 26a5d4829d..858daff44a 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/gettext.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module I18n module Gettext PLURAL_SEPARATOR = "\001" @@ -8,11 +10,12 @@ module Gettext @@plural_keys = { :en => [:one, :other] } class << self - # returns an array of plural keys for the given locale so that we can - # convert from gettext's integer-index based style + # returns an array of plural keys for the given locale or the whole hash + # of locale mappings to plural keys so that we can convert from gettext's + # integer-index based style # TODO move this information to the pluralization module - def plural_keys(locale) - @@plural_keys[locale] || @@plural_keys[:en] + def plural_keys(*args) + args.empty? ? @@plural_keys : @@plural_keys[args.first] || @@plural_keys[:en] end def extract_scope(msgid, separator) diff --git a/app/server/ruby/vendor/i18n/lib/i18n/gettext/helpers.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext/helpers.rb similarity index 78% rename from app/server/ruby/vendor/i18n/lib/i18n/gettext/helpers.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext/helpers.rb index ea07d0526e..d077619ff2 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/gettext/helpers.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'i18n/gettext' module I18n @@ -7,8 +9,17 @@ module Gettext # # include I18n::Gettext::Helpers module Helpers - def gettext(msgid, options = {}) - I18n.t(msgid, { :default => msgid, :separator => '|' }.merge(options)) + # Makes dynamic translation messages readable for the gettext parser. + # _(fruit) cannot be understood by the gettext parser. To help the parser find all your translations, + # you can add fruit = N_("Apple") which does not translate, but tells the parser: "Apple" needs translation. + # * msgid: the message id. + # * Returns: msgid. + def N_(msgsid) + msgsid + end + + def gettext(msgid, options = EMPTY_HASH) + I18n.t(msgid, **{:default => msgid, :separator => '|'}.merge(options)) end alias _ gettext diff --git a/app/server/ruby/vendor/i18n/lib/i18n/gettext/po_parser.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext/po_parser.rb similarity index 98% rename from app/server/ruby/vendor/i18n/lib/i18n/gettext/po_parser.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext/po_parser.rb index 547df6a593..a07fdc5874 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/gettext/po_parser.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/gettext/po_parser.rb @@ -28,7 +28,7 @@ def unescape(orig) ret.gsub!(/\\"/, "\"") ret end - + def parse(str, data, ignore_fuzzy = true) @comments = [] @data = data @@ -64,7 +64,7 @@ def parse(str, data, ignore_fuzzy = true) str = $' when /\A\#(.*)/ @q.push [:COMMENT, $&] - str = $' + str = $' when /\A\"(.*)\"/ @q.push [:STRING, $1] str = $' @@ -73,7 +73,7 @@ def parse(str, data, ignore_fuzzy = true) #@q.push [:STRING, c] str = str[1..-1] end - end + end @q.push [false, '$end'] if $DEBUG @q.each do |a,b| @@ -88,7 +88,7 @@ def parse(str, data, ignore_fuzzy = true) end @data end - + def next_token @q.shift end @@ -101,11 +101,11 @@ def on_message(msgid, msgstr) @comments.clear @msgctxt = "" end - + def on_comment(comment) @fuzzy = true if (/fuzzy/ =~ comment) @comments << comment - end + end ..end src/poparser.ry modeval..id7a99570e05 @@ -245,7 +245,7 @@ def _reduce_5( val, _values, result ) module_eval <<'.,.,', 'src/poparser.ry', 48 def _reduce_8( val, _values, result ) - if @fuzzy and $ignore_fuzzy + if @fuzzy and $ignore_fuzzy if val[1] != "" $stderr.print _("Warning: fuzzy message was ignored.\n") $stderr.print " msgid '#{val[1]}'\n" diff --git a/app/server/ruby/vendor/i18n/lib/i18n/interpolate/ruby.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/interpolate/ruby.rb similarity index 55% rename from app/server/ruby/vendor/i18n/lib/i18n/interpolate/ruby.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/interpolate/ruby.rb index 442677f29d..5b50593fec 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/interpolate/ruby.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/interpolate/ruby.rb @@ -1,28 +1,42 @@ +# frozen_string_literal: true + # heavily based on Masao Mutoh's gettext String interpolation extension # http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb module I18n - INTERPOLATION_PATTERN = Regexp.union( + DEFAULT_INTERPOLATION_PATTERNS = [ /%%/, - /%\{(\w+)\}/, # matches placeholders like "%{foo}" - /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%.d" - ) + /%\{([\w|]+)\}/, # matches placeholders like "%{foo} or %{foo|word}" + /%<(\w+)>([^\d]*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%.d" + ].freeze + INTERPOLATION_PATTERN = Regexp.union(DEFAULT_INTERPOLATION_PATTERNS) + deprecate_constant :INTERPOLATION_PATTERN + + INTERPOLATION_PATTERNS_CACHE = Hash.new do |hash, patterns| + hash[patterns] = Regexp.union(patterns) + end + private_constant :INTERPOLATION_PATTERNS_CACHE class << self # Return String or raises MissingInterpolationArgument exception. # Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler. def interpolate(string, values) - raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ RESERVED_KEYS_PATTERN + raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ I18n.reserved_keys_pattern raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash) interpolate_hash(string, values) end def interpolate_hash(string, values) - string.gsub(INTERPOLATION_PATTERN) do |match| + pattern = INTERPOLATION_PATTERNS_CACHE[config.interpolation_patterns] + interpolated = false + + interpolated_string = string.gsub(pattern) do |match| + interpolated = true + if match == '%%' '%' else - key = ($1 || $2).to_sym + key = ($1 || $2 || match.tr("%{}", "")).to_sym value = if values.key?(key) values[key] else @@ -32,6 +46,8 @@ def interpolate_hash(string, values) $3 ? sprintf("%#{$3}", value) : value end end + + interpolated ? interpolated_string : string end end end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/locale.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale.rb similarity index 80% rename from app/server/ruby/vendor/i18n/lib/i18n/locale.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale.rb index 4f9d0266d5..c4078e614b 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/locale.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module I18n module Locale autoload :Fallbacks, 'i18n/locale/fallbacks' diff --git a/app/server/ruby/vendor/i18n/lib/i18n/locale/fallbacks.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/fallbacks.rb similarity index 73% rename from app/server/ruby/vendor/i18n/lib/i18n/locale/fallbacks.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/fallbacks.rb index 08bf6f5508..e30acc4340 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/locale/fallbacks.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/fallbacks.rb @@ -15,19 +15,12 @@ # * all parent locales of a given locale (e.g. :es for :"es-MX") first, # * the current default locales and all of their parents second # -# The default locales are set to [I18n.default_locale] by default but can be -# set to something else. +# The default locales are set to [] by default but can be set to something else. # # One can additionally add any number of additional fallback locales manually. # These will be added before the default locales to the fallback chain. For # example: # -# # using the default locale as default fallback locale -# -# I18n.default_locale = :"en-US" -# I18n.fallbacks = I18n::Locale::Fallbacks.new(:"de-AT" => :"de-DE") -# I18n.fallbacks[:"de-AT"] # => [:"de-AT", :"de-DE", :de, :"en-US", :en] -# # # using a custom locale as default fallback locale # # I18n.fallbacks = I18n::Locale::Fallbacks.new(:"en-GB", :"de-AT" => :de, :"de-CH" => :de) @@ -46,7 +39,7 @@ # fallbacks[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en] # fallbacks[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en] # -# # people speaking Sami as spoken in Finnland also speak Swedish and Finnish as spoken in Finnland +# # people speaking Sami as spoken in Finland also speak Swedish and Finnish as spoken in Finland # fallbacks.map(:sms => [:"se-FI", :"fi-FI"]) # fallbacks[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en] @@ -56,40 +49,48 @@ class Fallbacks < Hash def initialize(*mappings) @map = {} map(mappings.pop) if mappings.last.is_a?(Hash) - self.defaults = mappings.empty? ? [I18n.default_locale.to_sym] : mappings + self.defaults = mappings.empty? ? [] : mappings end def defaults=(defaults) - @defaults = defaults.map { |default| compute(default, false) }.flatten + @defaults = defaults.flat_map { |default| compute(default, false) } end attr_reader :defaults def [](locale) raise InvalidLocale.new(locale) if locale.nil? + raise Disabled.new('fallback#[]') if locale == false locale = locale.to_sym super || store(locale, compute(locale)) end - def map(mappings) - mappings.each do |from, to| - from, to = from.to_sym, Array(to) - to.each do |_to| - @map[from] ||= [] - @map[from] << _to.to_sym + def map(*args, &block) + if args.count == 1 && !block_given? + mappings = args.first + mappings.each do |from, to| + from, to = from.to_sym, Array(to) + to.each do |_to| + @map[from] ||= [] + @map[from] << _to.to_sym + end end + else + @map.map(*args, &block) end end protected def compute(tags, include_defaults = true, exclude = []) - result = Array(tags).collect do |tag| + result = Array(tags).flat_map do |tag| tags = I18n::Locale::Tag.tag(tag).self_and_parents.map! { |t| t.to_sym } - exclude tags.each { |_tag| tags += compute(@map[_tag], false, exclude + tags) if @map[_tag] } tags - end.flatten + end result.push(*defaults) if include_defaults - result.uniq.compact + result.uniq! + result.compact! + result end end end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/locale/tag.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag.rb similarity index 100% rename from app/server/ruby/vendor/i18n/lib/i18n/locale/tag.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag.rb diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/parents.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/parents.rb new file mode 100644 index 0000000000..6283e667ff --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/parents.rb @@ -0,0 +1,24 @@ +module I18n + module Locale + module Tag + module Parents + def parent + @parent ||= + begin + segs = to_a + segs.compact! + segs.length > 1 ? self.class.tag(*segs[0..(segs.length - 2)].join('-')) : nil + end + end + + def self_and_parents + @self_and_parents ||= [self].concat parents + end + + def parents + @parents ||= parent ? [parent].concat(parent.parents) : [] + end + end + end + end +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/locale/tag/rfc4646.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/rfc4646.rb similarity index 100% rename from app/server/ruby/vendor/i18n/lib/i18n/locale/tag/rfc4646.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/rfc4646.rb diff --git a/app/server/ruby/vendor/i18n/lib/i18n/locale/tag/simple.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/simple.rb similarity index 84% rename from app/server/ruby/vendor/i18n/lib/i18n/locale/tag/simple.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/simple.rb index 68642a123f..18d55c2861 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/locale/tag/simple.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/locale/tag/simple.rb @@ -1,5 +1,5 @@ # Simple Locale tag implementation that computes subtags by simply splitting -# the locale tag at '-' occurences. +# the locale tag at '-' occurrences. module I18n module Locale module Tag @@ -19,7 +19,7 @@ def initialize(*tag) end def subtags - @subtags = tag.to_s.split('-').map { |subtag| subtag.to_s } + @subtags = tag.to_s.split('-').map!(&:to_s) end def to_sym diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/middleware.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/middleware.rb new file mode 100644 index 0000000000..59b377e280 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/middleware.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module I18n + class Middleware + + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + ensure + Thread.current[:i18n_config] = I18n::Config.new + end + + end +end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests.rb similarity index 93% rename from app/server/ruby/vendor/i18n/lib/i18n/tests.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests.rb index 554cdefea0..30d0ed53bf 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module I18n module Tests autoload :Basics, 'i18n/tests/basics' diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/basics.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/basics.rb similarity index 74% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/basics.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/basics.rb index dc0596a322..833762be48 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/basics.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/basics.rb @@ -5,12 +5,11 @@ def teardown I18n.available_locales = nil end - test "available_locales returns the locales stored to the backend by default" do + test "available_locales returns the available_locales produced by the backend, by default" do I18n.backend.store_translations('de', :foo => 'bar') I18n.backend.store_translations('en', :foo => 'foo') - assert I18n.available_locales.include?(:de) - assert I18n.available_locales.include?(:en) + assert_equal I18n.available_locales, I18n.backend.available_locales end test "available_locales can be set to something else independently from the actual locale data" do @@ -24,11 +23,10 @@ def teardown assert_equal [:foo, :bar], I18n.available_locales I18n.available_locales = nil - assert I18n.available_locales.include?(:de) - assert I18n.available_locales.include?(:en) + assert_equal I18n.available_locales, I18n.backend.available_locales end - test "available_locales memoizes when set explicitely" do + test "available_locales memoizes when set explicitly" do I18n.backend.expects(:available_locales).never I18n.available_locales = [:foo] I18n.backend.store_translations('de', :bar => 'baz') @@ -36,9 +34,10 @@ def teardown assert_equal [:foo], I18n.available_locales end - test "available_locales delegates to the backend when not set explicitely" do - I18n.backend.expects(:available_locales).twice - assert_equal I18n.available_locales, I18n.available_locales + test "available_locales delegates to the backend when not set explicitly" do + original_available_locales_value = I18n.backend.available_locales + I18n.backend.expects(:available_locales).returns(original_available_locales_value).twice + assert_equal I18n.backend.available_locales, I18n.available_locales end test "exists? is implemented by the backend" do diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/defaults.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/defaults.rb similarity index 75% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/defaults.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/defaults.rb index 081dcbd14e..31fdb469c7 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/defaults.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/defaults.rb @@ -24,8 +24,20 @@ def setup assert_equal 'bar', I18n.t(:does_not_exist, :default => [:does_not_exist_2, :'foo.bar']) end + test "defaults: given an array as a default with false it returns false" do + assert_equal false, I18n.t(:does_not_exist, :default => [false]) + end + + test "defaults: given false it returns false" do + assert_equal false, I18n.t(:does_not_exist, :default => false) + end + + test "defaults: given nil it returns nil" do + assert_nil I18n.t(:does_not_exist, :default => nil) + end + test "defaults: given an array of missing keys it raises a MissingTranslationData exception" do - assert_raise I18n::MissingTranslationData do + assert_raises I18n::MissingTranslationData do I18n.t(:does_not_exist, :default => [:does_not_exist_2, :does_not_exist_3], :raise => true) end end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/interpolation.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/interpolation.rb similarity index 70% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/interpolation.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/interpolation.rb index add8b90e0e..6bfe2e0366 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/interpolation.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/interpolation.rb @@ -24,6 +24,10 @@ module Interpolation assert_equal 'Hi David!', interpolate(:default => 'Hi %{name}!', :name => 'David') end + test "interpolation: works with a pipe" do + assert_equal 'Hi david!', interpolate(:default => 'Hi %{name|lowercase}!', :'name|lowercase' => 'david') + end + test "interpolation: given a nil value it still interpolates it into the string" do assert_equal 'Hi !', interpolate(:default => 'Hi %{name}!', :name => nil) end @@ -37,13 +41,13 @@ module Interpolation end test "interpolation: given values but missing a key it raises I18n::MissingInterpolationArgument" do - assert_raise(I18n::MissingInterpolationArgument) do + assert_raises(I18n::MissingInterpolationArgument) do interpolate(:default => '%{foo}', :bar => 'bar') end end test "interpolation: it does not raise I18n::MissingInterpolationArgument for escaped variables" do - assert_nothing_raised(I18n::MissingInterpolationArgument) do + assert_nothing_raised do assert_equal 'Barr %{foo}', interpolate(:default => '%{bar} %%{foo}', :bar => 'Barr') end end @@ -54,6 +58,11 @@ module Interpolation assert_equal 'Hi Yehuda!', interpolate(:interpolate, :name => 'Yehuda') end + test "interpolation: given an array interpolates each element" do + I18n.backend.store_translations(:en, :array_interpolate => ['Hi', 'Mr. %{name}', 'or sir %{name}']) + assert_equal ['Hi', 'Mr. Bartuz', 'or sir Bartuz'], interpolate(:array_interpolate, :name => 'Bartuz') + end + test "interpolation: given the translation is in utf-8 it still works" do assert_equal 'Häi David!', interpolate(:default => 'Häi %{name}!', :name => 'David') end @@ -68,13 +77,13 @@ module Interpolation if Object.const_defined?(:Encoding) test "interpolation: given a euc-jp translation and a utf-8 value it raises Encoding::CompatibilityError" do - assert_raise(Encoding::CompatibilityError) do + assert_raises(Encoding::CompatibilityError) do interpolate(:default => euc_jp('こんにちは、%{name}さん!'), :name => 'ゆきひろ') end end test "interpolation: given a utf-8 translation and a euc-jp value it raises Encoding::CompatibilityError" do - assert_raise(Encoding::CompatibilityError) do + assert_raises(Encoding::CompatibilityError) do interpolate(:default => 'こんにちは、%{name}さん!', :name => euc_jp('ゆきひろ')) end end @@ -99,9 +108,30 @@ module Interpolation end test "interpolation: given a translations containing a reserved key it raises I18n::ReservedInterpolationKey" do - assert_raise(I18n::ReservedInterpolationKey) { interpolate(:default => '%{default}', :foo => :bar) } - assert_raise(I18n::ReservedInterpolationKey) { interpolate(:default => '%{scope}', :foo => :bar) } - assert_raise(I18n::ReservedInterpolationKey) { interpolate(:default => '%{separator}', :foo => :bar) } + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{exception_handler}') } + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{default}') } + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{separator}') } + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{scope}') } + end + + test "interpolation: deep interpolation for default string" do + assert_equal 'Hi %{name}!', interpolate(:default => 'Hi %{name}!', :deep_interpolation => true) + end + + test "interpolation: deep interpolation for interpolated string" do + assert_equal 'Hi Ann!', interpolate(:default => 'Hi %{name}!', :name => 'Ann', :deep_interpolation => true) + end + + test "interpolation: deep interpolation for Hash" do + people = { :people => { :ann => 'Ann is %{ann}', :john => 'John is %{john}' } } + interpolated_people = { :people => { :ann => 'Ann is good', :john => 'John is big' } } + assert_equal interpolated_people, interpolate(:default => people, :ann => 'good', :john => 'big', :deep_interpolation => true) + end + + test "interpolation: deep interpolation for Array" do + people = { :people => ['Ann is %{ann}', 'John is %{john}'] } + interpolated_people = { :people => ['Ann is good', 'John is big'] } + assert_equal interpolated_people, interpolate(:default => people, :ann => 'good', :john => 'big', :deep_interpolation => true) end protected diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/link.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/link.rb similarity index 83% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/link.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/link.rb index da84a2c843..d2f20e8072 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/link.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/link.rb @@ -26,7 +26,7 @@ module Link } assert_equal('linked', I18n.backend.translate('en', :'foo.link')) end - + test "linked lookup: if a dot-separated key resolves to a dot-separated symbol it looks up the symbol" do I18n.backend.store_translations 'en', { :foo => { :link => :"bar.linked" }, @@ -51,6 +51,16 @@ module Link assert_equal "can't be blank", I18n.t(:"activerecord.errors.messages.blank") assert_equal "can't be blank", I18n.t(:"activerecord.errors.messages.blank") end + + test "linked lookup: a link can resolve with option :count" do + I18n.backend.store_translations 'en', { + :counter => :counted, + :counted => { :foo => { :one => "one", :other => "other" }, :bar => "bar" } + } + assert_equal "one", I18n.t(:'counter.foo', count: 1) + assert_equal "other", I18n.t(:'counter.foo', count: 2) + assert_equal "bar", I18n.t(:'counter.bar', count: 3) + end end end end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization.rb similarity index 100% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/localization.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization.rb diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization/date.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/date.rb similarity index 54% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/localization/date.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/date.rb index a7234752cf..c21fbbf317 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization/date.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/date.rb @@ -11,8 +11,7 @@ def setup end test "localize Date: given the short format it uses it" do - # TODO should be Mrz, shouldn't it? - assert_equal '01. Mar', I18n.l(@date, :format => :short, :locale => :de) + assert_equal '01. Mär', I18n.l(@date, :format => :short, :locale => :de) end test "localize Date: given the long format it uses it" do @@ -27,17 +26,45 @@ def setup assert_equal 'Samstag', I18n.l(@date, :format => '%A', :locale => :de) end + test "localize Date: given a uppercased day name format it returns the correct day name in upcase" do + assert_equal 'samstag'.upcase, I18n.l(@date, :format => '%^A', :locale => :de) + end + test "localize Date: given an abbreviated day name format it returns the correct abbreviated day name" do assert_equal 'Sa', I18n.l(@date, :format => '%a', :locale => :de) end + test "localize Date: given an meridian indicator format it returns the correct meridian indicator" do + assert_equal 'AM', I18n.l(@date, :format => '%p', :locale => :de) + assert_equal 'am', I18n.l(@date, :format => '%P', :locale => :de) + end + + test "localize Date: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do + assert_equal 'sa'.upcase, I18n.l(@date, :format => '%^a', :locale => :de) + end + test "localize Date: given a month name format it returns the correct month name" do assert_equal 'März', I18n.l(@date, :format => '%B', :locale => :de) end + test "localize Date: given a uppercased month name format it returns the correct month name in upcase" do + assert_equal 'märz'.upcase, I18n.l(@date, :format => '%^B', :locale => :de) + end + test "localize Date: given an abbreviated month name format it returns the correct abbreviated month name" do - # TODO should be Mrz, shouldn't it? - assert_equal 'Mar', I18n.l(@date, :format => '%b', :locale => :de) + assert_equal 'Mär', I18n.l(@date, :format => '%b', :locale => :de) + end + + test "localize Date: given an abbreviated and uppercased month name format it returns the correct abbreviated month name in upcase" do + assert_equal 'mär'.upcase, I18n.l(@date, :format => '%^b', :locale => :de) + end + + test "localize Date: given a date format with the month name upcased it returns the correct value" do + assert_equal '1. FEBRUAR 2008', I18n.l(::Date.new(2008, 2, 1), :format => "%-d. %^B %Y", :locale => :de) + end + + test "localize Date: given missing translations it returns the correct error message" do + assert_equal 'Translation missing: fr.date.abbr_month_names', I18n.l(@date, :format => '%b', :locale => :fr) end test "localize Date: given an unknown format it does not fail" do @@ -46,21 +73,25 @@ def setup test "localize Date: does not modify the options hash" do options = { :format => '%b', :locale => :de } - assert_equal 'Mar', I18n.l(@date, options) + assert_equal 'Mär', I18n.l(@date, **options) assert_equal({ :format => '%b', :locale => :de }, options) - assert_nothing_raised { I18n.l(@date, options.freeze) } + assert_nothing_raised { I18n.l(@date, **options.freeze) } + end + + test "localize Date: given nil with default value it returns default" do + assert_equal 'default', I18n.l(nil, :default => 'default') end test "localize Date: given nil it raises I18n::ArgumentError" do - assert_raise(I18n::ArgumentError) { I18n.l(nil) } + assert_raises(I18n::ArgumentError) { I18n.l(nil) } end test "localize Date: given a plain Object it raises I18n::ArgumentError" do - assert_raise(I18n::ArgumentError) { I18n.l(Object.new) } + assert_raises(I18n::ArgumentError) { I18n.l(Object.new) } end test "localize Date: given a format is missing it raises I18n::MissingTranslationData" do - assert_raise(I18n::MissingTranslationData) { I18n.l(@date, :format => :missing) } + assert_raises(I18n::MissingTranslationData) { I18n.l(@date, :format => :missing) } end test "localize Date: it does not alter the format string" do @@ -81,7 +112,7 @@ def setup_date_translations :day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag), :abbr_day_names => %w(So Mo Di Mi Do Fr Sa), :month_names => %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember).unshift(nil), - :abbr_month_names => %w(Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil) + :abbr_month_names => %w(Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil) } } end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization/date_time.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/date_time.rb similarity index 60% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/localization/date_time.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/date_time.rb index 7a30bff491..b5d3527dc3 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization/date_time.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/date_time.rb @@ -12,8 +12,7 @@ def setup end test "localize DateTime: given the short format it uses it" do - # TODO should be Mrz, shouldn't it? - assert_equal '01. Mar 06:00', I18n.l(@datetime, :format => :short, :locale => :de) + assert_equal '01. Mär 06:00', I18n.l(@datetime, :format => :short, :locale => :de) end test "localize DateTime: given the long format it uses it" do @@ -21,25 +20,47 @@ def setup end test "localize DateTime: given the default format it uses it" do - # TODO should be Mrz, shouldn't it? - assert_equal 'Sa, 01. Mar 2008 06:00:00 +0000', I18n.l(@datetime, :format => :default, :locale => :de) + assert_equal 'Sa, 01. Mär 2008 06:00:00 +0000', I18n.l(@datetime, :format => :default, :locale => :de) end test "localize DateTime: given a day name format it returns the correct day name" do assert_equal 'Samstag', I18n.l(@datetime, :format => '%A', :locale => :de) end + test "localize DateTime: given a uppercased day name format it returns the correct day name in upcase" do + assert_equal 'samstag'.upcase, I18n.l(@datetime, :format => '%^A', :locale => :de) + end + test "localize DateTime: given an abbreviated day name format it returns the correct abbreviated day name" do assert_equal 'Sa', I18n.l(@datetime, :format => '%a', :locale => :de) end + test "localize DateTime: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do + assert_equal 'sa'.upcase, I18n.l(@datetime, :format => '%^a', :locale => :de) + end + test "localize DateTime: given a month name format it returns the correct month name" do assert_equal 'März', I18n.l(@datetime, :format => '%B', :locale => :de) end + test "localize DateTime: given a uppercased month name format it returns the correct month name in upcase" do + assert_equal 'märz'.upcase, I18n.l(@datetime, :format => '%^B', :locale => :de) + end + test "localize DateTime: given an abbreviated month name format it returns the correct abbreviated month name" do - # TODO should be Mrz, shouldn't it? - assert_equal 'Mar', I18n.l(@datetime, :format => '%b', :locale => :de) + assert_equal 'Mär', I18n.l(@datetime, :format => '%b', :locale => :de) + end + + test "localize DateTime: given an abbreviated and uppercased month name format it returns the correct abbreviated month name in upcase" do + assert_equal 'mär'.upcase, I18n.l(@datetime, :format => '%^b', :locale => :de) + end + + test "localize DateTime: given a date format with the month name upcased it returns the correct value" do + assert_equal '1. FEBRUAR 2008', I18n.l(::DateTime.new(2008, 2, 1, 6), :format => "%-d. %^B %Y", :locale => :de) + end + + test "localize DateTime: given missing translations it returns the correct error message" do + assert_equal 'Translation missing: fr.date.abbr_month_names', I18n.l(@datetime, :format => '%b', :locale => :fr) end test "localize DateTime: given a meridian indicator format it returns the correct meridian indicator" do @@ -57,7 +78,7 @@ def setup end test "localize DateTime: given a format is missing it raises I18n::MissingTranslationData" do - assert_raise(I18n::MissingTranslationData) { I18n.l(@datetime, :format => :missing) } + assert_raises(I18n::MissingTranslationData) { I18n.l(@datetime, :format => :missing) } end protected diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization/procs.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/procs.rb similarity index 87% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/localization/procs.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/procs.rb index 7b7813e226..7db45d1ddb 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization/procs.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/procs.rb @@ -52,19 +52,20 @@ module Procs test "localize Time: given a format that resolves to a Proc it calls the Proc with the object" do setup_time_proc_translations time = ::Time.utc(2008, 3, 1, 6, 0) - assert_equal inspect_args([time, {}]), I18n.l(time, :format => :proc, :locale => :ru) + assert_equal I18n::Tests::Localization::Procs.inspect_args([time], {}), I18n.l(time, :format => :proc, :locale => :ru) end test "localize Time: given a format that resolves to a Proc it calls the Proc with the object and extra options" do setup_time_proc_translations time = ::Time.utc(2008, 3, 1, 6, 0) options = { :foo => 'foo' } - assert_equal inspect_args([time, options]), I18n.l(time, options.merge(:format => :proc, :locale => :ru)) + assert_equal I18n::Tests::Localization::Procs.inspect_args([time], options), I18n.l(time, **options.merge(:format => :proc, :locale => :ru)) end protected - def inspect_args(args) + def self.inspect_args(args, kwargs) + args << kwargs args = args.map do |arg| case arg when ::Time, ::DateTime @@ -72,7 +73,8 @@ def inspect_args(args) when ::Date arg.strftime('%a, %d %b %Y') when Hash - arg.delete(:fallback) + arg.delete(:fallback_in_progress) + arg.delete(:fallback_original_locale) arg.inspect else arg.inspect @@ -85,12 +87,12 @@ def setup_time_proc_translations I18n.backend.store_translations :ru, { :time => { :formats => { - :proc => lambda { |*args| inspect_args(args) } + :proc => lambda { |*args, **kwargs| I18n::Tests::Localization::Procs.inspect_args(args, kwargs) } } }, :date => { :formats => { - :proc => lambda { |*args| inspect_args(args) } + :proc => lambda { |*args, **kwargs| I18n::Tests::Localization::Procs.inspect_args(args, kwargs) } }, :'day_names' => lambda { |key, options| (options[:format] =~ /^%A/) ? diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization/time.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/time.rb similarity index 63% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/localization/time.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/time.rb index 8bbba43c18..456a7602c9 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/localization/time.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/localization/time.rb @@ -12,8 +12,7 @@ def setup end test "localize Time: given the short format it uses it" do - # TODO should be Mrz, shouldn't it? - assert_equal '01. Mar 06:00', I18n.l(@time, :format => :short, :locale => :de) + assert_equal '01. Mär 06:00', I18n.l(@time, :format => :short, :locale => :de) end test "localize Time: given the long format it uses it" do @@ -29,17 +28,40 @@ def setup assert_equal 'Samstag', I18n.l(@time, :format => '%A', :locale => :de) end + test "localize Time: given a uppercased day name format it returns the correct day name in upcase" do + assert_equal 'samstag'.upcase, I18n.l(@time, :format => '%^A', :locale => :de) + end + test "localize Time: given an abbreviated day name format it returns the correct abbreviated day name" do assert_equal 'Sa', I18n.l(@time, :format => '%a', :locale => :de) end + test "localize Time: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do + assert_equal 'sa'.upcase, I18n.l(@time, :format => '%^a', :locale => :de) + end + test "localize Time: given a month name format it returns the correct month name" do assert_equal 'März', I18n.l(@time, :format => '%B', :locale => :de) end + test "localize Time: given a uppercased month name format it returns the correct month name in upcase" do + assert_equal 'märz'.upcase, I18n.l(@time, :format => '%^B', :locale => :de) + end + test "localize Time: given an abbreviated month name format it returns the correct abbreviated month name" do - # TODO should be Mrz, shouldn't it? - assert_equal 'Mar', I18n.l(@time, :format => '%b', :locale => :de) + assert_equal 'Mär', I18n.l(@time, :format => '%b', :locale => :de) + end + + test "localize Time: given an abbreviated and uppercased month name format it returns the correct abbreviated month name in upcase" do + assert_equal 'mär'.upcase, I18n.l(@time, :format => '%^b', :locale => :de) + end + + test "localize Time: given a date format with the month name upcased it returns the correct value" do + assert_equal '1. FEBRUAR 2008', I18n.l(::Time.utc(2008, 2, 1, 6, 0), :format => "%-d. %^B %Y", :locale => :de) + end + + test "localize Time: given missing translations it returns the correct error message" do + assert_equal 'Translation missing: fr.date.abbr_month_names', I18n.l(@time, :format => '%b', :locale => :fr) end test "localize Time: given a meridian indicator format it returns the correct meridian indicator" do @@ -57,7 +79,7 @@ def setup end test "localize Time: given a format is missing it raises I18n::MissingTranslationData" do - assert_raise(I18n::MissingTranslationData) { I18n.l(@time, :format => :missing) } + assert_raises(I18n::MissingTranslationData) { I18n.l(@time, :format => :missing) } end protected diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/lookup.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/lookup.rb similarity index 86% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/lookup.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/lookup.rb index 62c069d168..bbd775f0d2 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/lookup.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/lookup.rb @@ -17,7 +17,7 @@ def setup assert_equal({ :a => "b" }, I18n.t(:hash)) end - test "lookup: it returns a array" do + test "lookup: it returns an array" do assert_equal(%w(a b c), I18n.t(:array)) end @@ -30,11 +30,11 @@ def setup end test "lookup: given a missing key, no default and no raise option it returns an error message" do - assert_equal "translation missing: en.missing", I18n.t(:missing) + assert_equal "Translation missing: en.missing", I18n.t(:missing) end test "lookup: given a missing key, no default and the raise option it raises MissingTranslationData" do - assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } + assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } end test "lookup: does not raise an exception if no translation data is present for the given locale" do @@ -43,9 +43,9 @@ def setup test "lookup: does not modify the options hash" do options = {} - assert_equal "a", I18n.t(:string, options) + assert_equal "a", I18n.t(:string, **options) assert_equal({}, options) - assert_nothing_raised { I18n.t(:string, options.freeze) } + assert_nothing_raised { I18n.t(:string, **options.freeze) } end test "lookup: given an array of keys it translates all of them" do @@ -61,7 +61,7 @@ def setup # In fact it probably *should* fail but Rails currently relies on using the default locale instead. # So we'll stick to this for now until we get it fixed in Rails. test "lookup: given nil as a locale it does not raise but use the default locale" do - # assert_raise(I18n::InvalidLocale) { I18n.t(:bar, :locale => nil) } + # assert_raises(I18n::InvalidLocale) { I18n.t(:bar, :locale => nil) } assert_nothing_raised { I18n.t(:bar, :locale => nil) } end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/pluralization.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/pluralization.rb similarity index 91% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/pluralization.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/pluralization.rb index d3319dcdad..19e73f3746 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/pluralization.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/pluralization.rb @@ -28,7 +28,7 @@ module Pluralization end test "pluralization: given incomplete pluralization data it raises I18n::InvalidPluralizationData" do - assert_raise(I18n::InvalidPluralizationData) { I18n.t(:default => { :one => 'bar' }, :count => 2) } + assert_raises(I18n::InvalidPluralizationData) { I18n.t(:default => { :one => 'bar' }, :count => 2) } end end end diff --git a/app/server/ruby/vendor/i18n/lib/i18n/tests/procs.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/procs.rb similarity index 68% rename from app/server/ruby/vendor/i18n/lib/i18n/tests/procs.rb rename to app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/procs.rb index 55ff9529c6..6abd861200 100644 --- a/app/server/ruby/vendor/i18n/lib/i18n/tests/procs.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/tests/procs.rb @@ -4,28 +4,34 @@ module I18n module Tests module Procs test "lookup: given a translation is a proc it calls the proc with the key and interpolation values" do - I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| filter_args(*args) }) + I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| I18n::Tests::Procs.filter_args(*args) }) + assert_equal '[:a_lambda, {:foo=>"foo"}]', I18n.t(:a_lambda, :foo => 'foo') + end + + test "lookup: given a translation is a proc it passes the interpolation values as keyword arguments" do + I18n.backend.store_translations(:en, :a_lambda => lambda { |key, foo:, **| I18n::Tests::Procs.filter_args(key, foo: foo) }) assert_equal '[:a_lambda, {:foo=>"foo"}]', I18n.t(:a_lambda, :foo => 'foo') end test "defaults: given a default is a Proc it calls it with the key and interpolation values" do - proc = lambda { |*args| filter_args(*args) } + proc = lambda { |*args| I18n::Tests::Procs.filter_args(*args) } assert_equal '[nil, {:foo=>"foo"}]', I18n.t(nil, :default => proc, :foo => 'foo') end test "defaults: given a default is a key that resolves to a Proc it calls it with the key and interpolation values" do - I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| filter_args(*args) }) + the_lambda = lambda { |*args| I18n::Tests::Procs.filter_args(*args) } + I18n.backend.store_translations(:en, :a_lambda => the_lambda) assert_equal '[:a_lambda, {:foo=>"foo"}]', I18n.t(nil, :default => :a_lambda, :foo => 'foo') assert_equal '[:a_lambda, {:foo=>"foo"}]', I18n.t(nil, :default => [nil, :a_lambda], :foo => 'foo') end test "interpolation: given an interpolation value is a lambda it calls it with key and values before interpolating it" do - proc = lambda { |*args| filter_args(*args) } + proc = lambda { |*args| I18n::Tests::Procs.filter_args(*args) } assert_match %r(\[\{:foo=>#\}\]), I18n.t(nil, :default => '%{foo}', :foo => proc) end test "interpolation: given a key resolves to a Proc that returns a string then interpolation still works" do - proc = lambda { |*args| "%{foo}: " + filter_args(*args) } + proc = lambda { |*args| "%{foo}: " + I18n::Tests::Procs.filter_args(*args) } assert_equal 'foo: [nil, {:foo=>"foo"}]', I18n.t(nil, :default => proc, :foo => 'foo') end @@ -37,18 +43,23 @@ module Procs end test "lookup: given the option :resolve => false was passed it does not resolve proc translations" do - I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| filter_args(*args) }) + I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| I18n::Tests::Procs.filter_args(*args) }) assert_equal Proc, I18n.t(:a_lambda, :resolve => false).class end test "lookup: given the option :resolve => false was passed it does not resolve proc default" do - assert_equal Proc, I18n.t(nil, :default => lambda { |*args| filter_args(*args) }, :resolve => false).class + assert_equal Proc, I18n.t(nil, :default => lambda { |*args| I18n::Tests::Procs.filter_args(*args) }, :resolve => false).class end - protected - def filter_args(*args) - args.map {|arg| arg.delete(:fallback) if arg.is_a?(Hash) ; arg }.inspect + def self.filter_args(*args) + args.map do |arg| + if arg.is_a?(Hash) + arg.delete(:fallback_in_progress) + arg.delete(:fallback_original_locale) + end + arg + end.inspect end end end diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/utils.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/utils.rb new file mode 100644 index 0000000000..88415615f2 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/utils.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module I18n + module Utils + class << self + if Hash.method_defined?(:except) + def except(hash, *keys) + hash.except(*keys) + end + else + def except(hash, *keys) + hash = hash.dup + keys.each { |k| hash.delete(k) } + hash + end + end + + def deep_merge(hash, other_hash, &block) + deep_merge!(hash.dup, other_hash, &block) + end + + def deep_merge!(hash, other_hash, &block) + hash.merge!(other_hash) do |key, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + deep_merge(this_val, other_val, &block) + elsif block_given? + yield key, this_val, other_val + else + other_val + end + end + end + + def deep_symbolize_keys(hash) + hash.each_with_object({}) do |(key, value), result| + result[key.respond_to?(:to_sym) ? key.to_sym : key] = deep_symbolize_keys_in_object(value) + result + end + end + + private + + def deep_symbolize_keys_in_object(value) + case value + when Hash + deep_symbolize_keys(value) + when Array + value.map { |e| deep_symbolize_keys_in_object(e) } + else + value + end + end + end + end +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/version.rb b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/version.rb new file mode 100644 index 0000000000..965f5dd8d6 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/lib/i18n/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module I18n + VERSION = "1.14.1" +end diff --git a/app/server/ruby/vendor/i18n/test/api/all_features_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/all_features_test.rb similarity index 100% rename from app/server/ruby/vendor/i18n/test/api/all_features_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/all_features_test.rb diff --git a/app/server/ruby/vendor/i18n/test/api/cascade_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/cascade_test.rb similarity index 100% rename from app/server/ruby/vendor/i18n/test/api/cascade_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/cascade_test.rb diff --git a/app/server/ruby/vendor/i18n/test/api/chain_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/chain_test.rb similarity index 100% rename from app/server/ruby/vendor/i18n/test/api/chain_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/chain_test.rb diff --git a/app/server/ruby/vendor/i18n/test/api/fallbacks_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/fallbacks_test.rb similarity index 100% rename from app/server/ruby/vendor/i18n/test/api/fallbacks_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/fallbacks_test.rb diff --git a/app/server/ruby/vendor/i18n/test/api/key_value_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/key_value_test.rb similarity index 78% rename from app/server/ruby/vendor/i18n/test/api/key_value_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/key_value_test.rb index 6a8d405ffa..4d6cdd8a42 100644 --- a/app/server/ruby/vendor/i18n/test/api/key_value_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/api/key_value_test.rb @@ -1,7 +1,5 @@ require 'test_helper' -I18n::TestCase.setup_rufus_tokyo - class I18nKeyValueApiTest < I18n::TestCase include I18n::Tests::Basics include I18n::Tests::Defaults @@ -15,14 +13,12 @@ class I18nKeyValueApiTest < I18n::TestCase include I18n::Tests::Localization::Time # include Tests::Api::Localization::Procs - STORE = Rufus::Tokyo::Cabinet.new('*') - def setup - I18n.backend = I18n::Backend::KeyValue.new(STORE) + I18n.backend = I18n::Backend::KeyValue.new({}) super end test "make sure we use the KeyValue backend" do assert_equal I18n::Backend::KeyValue, I18n.backend.class end -end if defined?(Rufus::Tokyo::Cabinet) +end if I18n::TestCase.key_value? diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/api/lazy_loadable_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/lazy_loadable_test.rb new file mode 100644 index 0000000000..a47dac478b --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/api/lazy_loadable_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' + +class I18nLazyLoadableBackendApiTest < I18n::TestCase + def setup + I18n.backend = I18n::Backend::LazyLoadable.new + super + end + + include I18n::Tests::Basics + include I18n::Tests::Defaults + include I18n::Tests::Interpolation + include I18n::Tests::Link + include I18n::Tests::Lookup + include I18n::Tests::Pluralization + include I18n::Tests::Procs + include I18n::Tests::Localization::Date + include I18n::Tests::Localization::DateTime + include I18n::Tests::Localization::Time + include I18n::Tests::Localization::Procs + + test "make sure we use the LazyLoadable backend" do + assert_equal I18n::Backend::LazyLoadable, I18n.backend.class + end +end diff --git a/app/server/ruby/vendor/i18n/test/api/memoize_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/memoize_test.rb similarity index 90% rename from app/server/ruby/vendor/i18n/test/api/memoize_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/memoize_test.rb index 0304580a52..ed938665c7 100644 --- a/app/server/ruby/vendor/i18n/test/api/memoize_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/api/memoize_test.rb @@ -27,8 +27,6 @@ def setup end end -I18n::TestCase.setup_rufus_tokyo - class I18nMemoizeBackendWithKeyValueApiTest < I18n::TestCase include I18n::Tests::Basics include I18n::Tests::Defaults @@ -47,14 +45,12 @@ class MemoizeBackend < I18n::Backend::KeyValue include I18n::Backend::Memoize end - STORE = Rufus::Tokyo::Cabinet.new('*') - def setup - I18n.backend = MemoizeBackend.new(STORE) + I18n.backend = MemoizeBackend.new({}) super end test "make sure we use the MemoizeBackend backend" do assert_equal MemoizeBackend, I18n.backend.class end -end if defined?(Rufus::Tokyo::Cabinet) +end if I18n::TestCase.key_value? diff --git a/app/server/ruby/vendor/i18n/test/api/override_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/override_test.rb similarity index 81% rename from app/server/ruby/vendor/i18n/test/api/override_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/override_test.rb index 67e1f068c4..41b14d2d4d 100644 --- a/app/server/ruby/vendor/i18n/test/api/override_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/api/override_test.rb @@ -2,8 +2,8 @@ class I18nOverrideTest < I18n::TestCase module OverrideInverse - def translate(*args) - super(*args).reverse + def translate(key, **options) + super(key, **options).reverse end alias :t :translate end @@ -16,9 +16,9 @@ def translate(*args) end def setup + super @I18n = I18n.dup @I18n.backend = I18n::Backend::Simple.new - super end test "make sure modules can overwrite I18n methods" do @@ -26,15 +26,14 @@ def setup @I18n.backend.store_translations('en', :foo => 'bar') assert_equal 'rab', @I18n.translate(:foo, :locale => 'en') - # FIXME: this fails under 1.8.7 - # assert_equal 'rab', @I18n.t(:foo, :locale => 'en') + assert_equal 'rab', @I18n.t(:foo, :locale => 'en') assert_equal 'rab', @I18n.translate!(:foo, :locale => 'en') assert_equal 'rab', @I18n.t!(:foo, :locale => 'en') end test "make sure modules can overwrite I18n signature" do exception = catch(:exception) do - @I18n.t('Hello', 'Welcome message on home page', :tokenize => true, :throw => true) + @I18n.t('Hello', :tokenize => true, :throw => true) end assert exception.message @I18n.extend OverrideSignature diff --git a/app/server/ruby/vendor/i18n/test/api/pluralization_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/pluralization_test.rb similarity index 100% rename from app/server/ruby/vendor/i18n/test/api/pluralization_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/pluralization_test.rb diff --git a/app/server/ruby/vendor/i18n/test/api/simple_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/api/simple_test.rb similarity index 100% rename from app/server/ruby/vendor/i18n/test/api/simple_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/api/simple_test.rb diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/cache_file_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/cache_file_test.rb new file mode 100644 index 0000000000..b7c75a073f --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/cache_file_test.rb @@ -0,0 +1,94 @@ +require 'test_helper' +require 'fileutils' +require 'tempfile' + +module CountWrites + attr_reader :writes + + def initialize(*args) + super + @writes = [] + end + + def store_translations(*args) + super.tap { @writes << args } + end +end + +module CacheFileTest + test 'load_translations caches loaded file contents' do + setup_backend! + I18n.load_path = [locales_dir + '/en.yml'] + assert_equal 0, @backend.writes.count + + @backend.load_translations unless @backend.is_a?(I18n::Backend::Simple) + assert_equal('baz', I18n.t('foo.bar')) + assert_equal 2, @backend.writes.count + + @backend.load_translations + assert_equal('baz', I18n.t('foo.bar')) + assert_equal 2, @backend.writes.count + end + + test 'setting path_roots normalizes write key' do + setup_backend! + I18n.load_path = [locales_dir + '/en.yml'] + @backend.path_roots = [locales_dir] + @backend.load_translations + refute_nil I18n.t("0/en\x01yml", scope: :load_file, locale: :i18n, default: nil) + end + + test 'load_translations caches file through updated modification time' do + setup_backend! + Tempfile.open(['test', '.yml']) do |file| + I18n.load_path = [file.path] + + File.write(file, { :en => { :foo => { :bar => 'baz' } } }.to_yaml) + assert_equal 0, @backend.writes.count + + @backend.load_translations unless @backend.is_a?(I18n::Backend::Simple) + assert_equal('baz', I18n.t('foo.bar')) + assert_equal 2, @backend.writes.count + + FileUtils.touch(file, :mtime => Time.now + 1) + @backend.load_translations + assert_equal('baz', I18n.t('foo.bar')) + assert_equal 2, @backend.writes.count + + File.write(file, { :en => { :foo => { :bar => 'baa' } } }.to_yaml) + FileUtils.touch(file, :mtime => Time.now + 1) + @backend.load_translations + assert_equal('baa', I18n.t('foo.bar')) + assert_equal 4, @backend.writes.count + end + end +end + +class SimpleCacheFileTest < I18n::TestCase + include CacheFileTest + + class Backend < I18n::Backend::Simple + include CountWrites + include I18n::Backend::CacheFile + end + + def setup_backend! + @backend = I18n.backend = Backend.new + end +end + +class KeyValueCacheFileTest < I18n::TestCase + include CacheFileTest + + class Backend < I18n::Backend::KeyValue + include CountWrites + include I18n::Backend::CacheFile + def initialize + super({}) + end + end + + def setup_backend! + @backend = I18n.backend = Backend.new + end +end if I18n::TestCase.key_value? diff --git a/app/server/ruby/vendor/i18n/test/backend/cache_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/cache_test.rb similarity index 55% rename from app/server/ruby/vendor/i18n/test/backend/cache_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/backend/cache_test.rb index 0f0e02857e..3ca4f19b71 100644 --- a/app/server/ruby/vendor/i18n/test/backend/cache_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/cache_test.rb @@ -1,4 +1,5 @@ require 'test_helper' +require 'openssl' begin require 'active_support' @@ -15,9 +16,13 @@ def setup I18n.backend = Backend.new super I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store) + I18n.cache_store.clear + I18n.cache_key_digest = nil end def teardown + super + I18n.cache_store.clear I18n.cache_store = nil end @@ -36,15 +41,30 @@ def teardown assert_equal 'Bar', I18n.t(:bar) end + test "translate returns a cached false response" do + I18n.backend.expects(:lookup).never + I18n.cache_store.expects(:read).returns(false) + assert_equal false, I18n.t(:foo) + end + test "still raises MissingTranslationData but also caches it" do - assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } - assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } + assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } + assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } assert_equal 1, I18n.cache_store.instance_variable_get(:@data).size # I18n.backend.expects(:lookup).returns(nil) - # assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } + # assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } # I18n.backend.expects(:lookup).never - # assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } + # assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } + end + + test "MissingTranslationData does not cache custom options" do + I18n.t(:missing, :scope => :foo, :extra => true) + assert_equal 1, I18n.cache_store.instance_variable_get(:@data).size + + value = I18n.cache_store.read(I18n.cache_store.instance_variable_get(:@data).keys.first) + + assert_equal({ scope: :foo }, value.options) end test "uses 'i18n' as a cache key namespace by default" do @@ -58,9 +78,17 @@ def teardown end test "adds locale and hash of key and hash of options" do - options = { :bar=>1 } - options_hash = I18n::Backend::Cache::USE_INSPECT_HASH ? options.inspect.hash : options.hash - assert_equal "i18n//en/#{:foo.hash}/#{options_hash}", I18n.backend.send(:cache_key, :en, :foo, options) + options = { :bar => 1 } + assert_equal "i18n//en/#{:foo.to_s.hash}/#{options.to_s.hash}", I18n.backend.send(:cache_key, :en, :foo, options) + end + + test "cache_key uses configured digest method" do + digest = OpenSSL::Digest::SHA256.new + options = { :bar => 1 } + options_hash = options.inspect + with_cache_key_digest(digest) do + assert_equal "i18n//en/#{digest.hexdigest(:foo.to_s)}/#{digest.hexdigest(options_hash)}", I18n.backend.send(:cache_key, :en, :foo, options) + end end test "keys should not be equal" do @@ -80,6 +108,12 @@ def with_cache_namespace(namespace) yield I18n.cache_namespace = nil end + + def with_cache_key_digest(digest) + I18n.cache_key_digest = digest + yield + I18n.cache_key_digest = nil + end end end # AS cache check diff --git a/app/server/ruby/vendor/i18n/test/backend/cascade_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/cascade_test.rb similarity index 90% rename from app/server/ruby/vendor/i18n/test/backend/cascade_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/backend/cascade_test.rb index e0bda10b59..39a3dd5e12 100644 --- a/app/server/ruby/vendor/i18n/test/backend/cascade_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/cascade_test.rb @@ -6,13 +6,14 @@ class Backend < I18n::Backend::Simple end def setup + super I18n.backend = Backend.new store_translations(:en, :foo => 'foo', :bar => { :baz => 'baz' }) @cascade_options = { :step => 1, :offset => 1, :skip_root => false } end def lookup(key, options = {}) - I18n.t(key, options.merge(:cascade => @cascade_options)) + I18n.t(key, **options.merge(:cascade => @cascade_options)) end test "still returns an existing translation as usual" do @@ -28,9 +29,9 @@ def lookup(key, options = {}) end test "raises I18n::MissingTranslationData exception when no translation was found" do - assert_raise(I18n::MissingTranslationData) { lookup(:'foo.missing', :raise => true) } - assert_raise(I18n::MissingTranslationData) { lookup(:'bar.baz.missing', :raise => true) } - assert_raise(I18n::MissingTranslationData) { lookup(:'missing.bar.baz', :raise => true) } + assert_raises(I18n::MissingTranslationData) { lookup(:'foo.missing', :raise => true) } + assert_raises(I18n::MissingTranslationData) { lookup(:'bar.baz.missing', :raise => true) } + assert_raises(I18n::MissingTranslationData) { lookup(:'missing.bar.baz', :raise => true) } end test "cascades before evaluating the default" do diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/chain_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/chain_test.rb new file mode 100644 index 0000000000..acb50efe43 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/chain_test.rb @@ -0,0 +1,175 @@ +require 'test_helper' + +class I18nBackendChainTest < I18n::TestCase + def setup + super + @first = backend(:en => { + :foo => 'Foo', :formats => { + :short => 'short', + :subformats => {:short => 'short'}, + }, + :plural_1 => { :one => '%{count}' }, + :dates => {:a => "A"}, + :fallback_bar => nil, + }) + @second = backend(:en => { + :bar => 'Bar', :formats => { + :long => 'long', + :subformats => {:long => 'long'}, + }, + :plural_2 => { :one => 'one' }, + :dates => {:a => "B", :b => "B"}, + :fallback_bar => 'Bar', + }) + @chain = I18n.backend = I18n::Backend::Chain.new(@first, @second) + end + + test "looks up translations from the first chained backend" do + assert_equal 'Foo', @first.send(:translations)[:en][:foo] + assert_equal 'Foo', I18n.t(:foo) + end + + test "looks up translations from the second chained backend" do + assert_equal 'Bar', @second.send(:translations)[:en][:bar] + assert_equal 'Bar', I18n.t(:bar) + end + + test "defaults only apply to lookups on the last backend in the chain" do + assert_equal 'Foo', I18n.t(:foo, :default => 'Bah') + assert_equal 'Bar', I18n.t(:bar, :default => 'Bah') + assert_equal 'Bah', I18n.t(:bah, :default => 'Bah') # default kicks in only here + end + + test "default" do + assert_equal 'Fuh', I18n.t(:default => 'Fuh') + assert_equal 'Zero', I18n.t(:default => { :zero => 'Zero' }, :count => 0) + assert_equal({ :zero => 'Zero' }, I18n.t(:default => { :zero => 'Zero' })) + assert_equal 'Foo', I18n.t(:default => :foo) + end + + test 'default is returned if translation is missing' do + assert_equal({}, I18n.t(:'i18n.transliterate.rule', :locale => 'en', :default => {})) + end + + test "namespace lookup collects results from all backends and merges deep hashes" do + assert_equal({:long=>"long", :subformats=>{:long=>"long", :short=>"short"}, :short=>"short"}, I18n.t(:formats)) + end + + test "namespace lookup collects results from all backends and lets leftmost backend take priority" do + assert_equal({ :a => "A", :b => "B" }, I18n.t(:dates)) + end + + test "namespace lookup with only the first backend returning a result" do + assert_equal({ :one => '%{count}' }, I18n.t(:plural_1)) + end + + test "pluralization still works" do + assert_equal '1', I18n.t(:plural_1, :count => 1) + assert_equal 'one', I18n.t(:plural_2, :count => 1) + end + + test "bulk lookup collects results from all backends" do + assert_equal ['Foo', 'Bar'], I18n.t([:foo, :bar]) + assert_equal ['Foo', 'Bar', 'Bah'], I18n.t([:foo, :bar, :bah], :default => 'Bah') + assert_equal [{ + :long=>"long", + :subformats=>{:long=>"long", :short=>"short"}, + :short=>"short"}, {:one=>"one"}, + "Bah"], I18n.t([:formats, :plural_2, :bah], :default => 'Bah') + end + + test "store_translations options are not dropped while transferring to backend" do + @first.expects(:store_translations).with(:foo, {:bar => :baz}, {:option => 'persists'}) + I18n.backend.store_translations :foo, {:bar => :baz}, {:option => 'persists'} + end + + test 'store should call initialize on all backends and return true if all initialized' do + @first.send :init_translations + @second.send :init_translations + assert I18n.backend.initialized? + end + + test 'store should call initialize on all backends and return false if one not initialized' do + @first.reload! + @second.send :init_translations + assert !I18n.backend.initialized? + end + + test 'should reload all backends' do + @first.send :init_translations + @second.send :init_translations + I18n.backend.reload! + assert !@first.initialized? + assert !@second.initialized? + end + + test 'should eager load all backends' do + I18n.backend.eager_load! + assert @first.initialized? + assert @second.initialized? + end + + test "falls back to other backends for nil values" do + assert_nil @first.send(:translations)[:en][:fallback_bar] + assert_equal 'Bar', @second.send(:translations)[:en][:fallback_bar] + assert_equal 'Bar', I18n.t(:fallback_bar) + end + + test 'should be able to get all translations of all backends merged together' do + expected = { + en: { + foo: 'Foo', + bar: 'Bar', + formats: { + short: 'short', + long: 'long', + subformats: { short: 'short', long: 'long' } + }, + plural_1: { one: "%{count}" }, + plural_2: { one: 'one' }, + dates: { a: 'A', b: 'B' }, + fallback_bar: 'Bar' + } + } + assert_equal expected, I18n.backend.send(:translations) + end + + protected + + def backend(translations) + backend = I18n::Backend::Simple.new + translations.each { |locale, data| backend.store_translations(locale, data) } + backend + end +end + +class I18nBackendChainWithKeyValueTest < I18n::TestCase + def setup_backend!(subtrees = true) + first = I18n::Backend::KeyValue.new({}, subtrees) + first.store_translations(:en, :plural_1 => { :one => '%{count}' }) + + second = I18n::Backend::Simple.new + second.store_translations(:en, :plural_2 => { :one => 'one' }) + I18n.backend = I18n::Backend::Chain.new(first, second) + end + + test "subtrees enabled: looks up pluralization translations from the first chained backend" do + setup_backend! + assert_equal '1', I18n.t(:plural_1, count: 1) + end + + test "subtrees disabled: looks up pluralization translations from the first chained backend" do + setup_backend!(false) + assert_equal '1', I18n.t(:plural_1, count: 1) + end + + test "subtrees enabled: looks up translations from the second chained backend" do + setup_backend! + assert_equal 'one', I18n.t(:plural_2, count: 1) + end + + test "subtrees disabled: looks up translations from the second chained backend" do + setup_backend!(false) + assert_equal 'one', I18n.t(:plural_2, count: 1) + end +end if I18n::TestCase.key_value? diff --git a/app/server/ruby/vendor/i18n/test/backend/exceptions_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/exceptions_test.rb similarity index 85% rename from app/server/ruby/vendor/i18n/test/backend/exceptions_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/backend/exceptions_test.rb index 5b5aef9391..e19c8121e3 100644 --- a/app/server/ruby/vendor/i18n/test/backend/exceptions_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/exceptions_test.rb @@ -2,6 +2,7 @@ class I18nBackendExceptionsTest < I18n::TestCase def setup + super I18n.backend = I18n::Backend::Simple.new end @@ -9,7 +10,7 @@ def setup exception = catch(:exception) do I18n.t(:'baz.missing', :scope => :'foo.bar', :throw => true) end - assert_equal "translation missing: en.foo.bar.baz.missing", exception.message + assert_equal "Translation missing: en.foo.bar.baz.missing", exception.message end test "exceptions: MissingTranslationData message from #translate includes the given scope and full key" do @@ -17,7 +18,7 @@ def setup I18n.t(:'baz.missing', :scope => :'foo.bar', :raise => true) rescue I18n::MissingTranslationData => exception end - assert_equal "translation missing: en.foo.bar.baz.missing", exception.message + assert_equal "Translation missing: en.foo.bar.baz.missing", exception.message end test "exceptions: MissingTranslationData message from #localize includes the given scope and full key" do @@ -25,7 +26,7 @@ def setup I18n.l(Time.now, :format => :foo) rescue I18n::MissingTranslationData => exception end - assert_equal "translation missing: en.time.formats.foo", exception.message + assert_equal "Translation missing: en.time.formats.foo", exception.message end test "exceptions: MissingInterpolationArgument message includes missing key, provided keys and full string" do diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/fallbacks_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/fallbacks_test.rb new file mode 100644 index 0000000000..8c20a04b2a --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/fallbacks_test.rb @@ -0,0 +1,430 @@ +require 'test_helper' + +class I18nBackendFallbacksTranslateTest < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.backend = Backend.new + store_translations(:en, :foo => 'Foo in :en', :bar => 'Bar in :en', :buz => 'Buz in :en', :interpolate => 'Interpolate %{value}', :interpolate_count => 'Interpolate %{value} %{count}') + store_translations(:de, :bar => 'Bar in :de', :baz => 'Baz in :de') + store_translations(:'de-DE', :baz => 'Baz in :de-DE') + store_translations(:'pt-BR', :baz => 'Baz in :pt-BR') + end + + test "still returns an existing translation as usual" do + assert_equal 'Foo in :en', I18n.t(:foo, :locale => :en) + assert_equal 'Bar in :de', I18n.t(:bar, :locale => :de) + assert_equal 'Baz in :de-DE', I18n.t(:baz, :locale => :'de-DE') + end + + test "returns interpolated value if no key provided" do + assert_equal 'Interpolate %{value}', I18n.t(:interpolate) + end + + test "returns the :de translation for a missing :'de-DE' translation" do + assert_equal 'Bar in :de', I18n.t(:bar, :locale => :'de-DE') + end + + test "keeps the count option when defaulting to a different key" do + assert_equal 'Interpolate 5 10', I18n.t(:non_existent, default: :interpolate_count, count: 10, value: 5) + end + + test "returns the :de translation for a missing :'de-DE' when :default is a String" do + assert_equal 'Bar in :de', I18n.t(:bar, :locale => :'de-DE', :default => "Default Bar") + assert_equal "Default Bar", I18n.t(:missing_bar, :locale => :'de-DE', :default => "Default Bar") + end + + test "returns the :de translation for a missing :'de-DE' when defaults is a Symbol (which exists in :en)" do + assert_equal "Bar in :de", I18n.t(:bar, :locale => :'de-DE', :default => [:buz]) + end + + test "returns the :'de-DE' default :baz translation for a missing :'de-DE' (which exists in :de)" do + assert_equal "Baz in :de-DE", I18n.t(:bar, :locale => :'de-DE', :default => [:baz]) + end + + test "returns the :de translation for a missing :'de-DE' when :default is a Proc" do + assert_equal 'Bar in :de', I18n.t(:bar, :locale => :'de-DE', :default => Proc.new { "Default Bar" }) + assert_equal "Default Bar", I18n.t(:missing_bar, :locale => :'de-DE', :default => Proc.new { "Default Bar" }) + end + + test "returns the :de translation for a missing :'de-DE' when :default is a Hash" do + assert_equal 'Bar in :de', I18n.t(:bar, :locale => :'de-DE', :default => {}) + assert_equal({}, I18n.t(:missing_bar, :locale => :'de-DE', :default => {})) + end + + test "returns the :de translation for a missing :'de-DE' when :default is nil" do + assert_equal 'Bar in :de', I18n.t(:bar, :locale => :'de-DE', :default => nil) + assert_nil I18n.t(:missing_bar, :locale => :'de-DE', :default => nil) + end + + test "returns the Translation missing: message if the default is also missing" do + translation_missing_message = <<~MSG + Translation missing. Options considered were: + - de-DE.missing_bar + - de-DE.missing_baz + MSG + + assert_equal translation_missing_message.chomp, I18n.t(:missing_bar, :locale => :'de-DE', :default => [:missing_baz]) + end + + test "returns the simple Translation missing: message when default is an empty Array" do + assert_equal "Translation missing: de-DE.missing_bar", I18n.t(:missing_bar, :locale => :'de-DE', :default => []) + end + + test "returns the :'de-DE' default :baz translation for a missing :'de-DE' when defaults contains Symbol" do + assert_equal 'Baz in :de-DE', I18n.t(:missing_foo, :locale => :'de-DE', :default => [:baz, "Default Bar"]) + end + + test "returns the defaults translation for a missing :'de-DE' when defaults contains a String or Proc before Symbol" do + assert_equal "Default Bar", I18n.t(:missing_foo, :locale => :'de-DE', :default => [:missing_bar, "Default Bar", :baz]) + assert_equal "Default Bar", I18n.t(:missing_foo, :locale => :'de-DE', :default => [:missing_bar, Proc.new { "Default Bar" }, :baz]) + end + + test "returns the default translation for a missing :'de-DE' and existing :de when default is a Hash" do + assert_equal 'Default 6 Bars', I18n.t(:missing_foo, :locale => :'de-DE', :default => [:missing_bar, {:other => "Default %{count} Bars"}, "Default Bar"], :count => 6) + end + + test "returns the default translation for a missing :de translation even when default is a String when fallback is disabled" do + assert_equal 'Default String', I18n.t(:foo, :locale => :de, :default => 'Default String', :fallback => false) + end + + test "raises I18n::MissingTranslationData exception when fallback is disabled even when fallback translation exists" do + assert_raises(I18n::MissingTranslationData) { I18n.t(:foo, :locale => :de, :fallback => false, :raise => true) } + end + + test "raises I18n::MissingTranslationData exception when no translation was found" do + assert_raises(I18n::MissingTranslationData) { I18n.t(:faa, :locale => :en, :raise => true) } + assert_raises(I18n::MissingTranslationData) { I18n.t(:faa, :locale => :de, :raise => true) } + end + + test "should ensure that default is not splitted on new line char" do + assert_equal "Default \n Bar", I18n.t(:missing_bar, :default => "Default \n Bar") + end + + test "should not raise error when enforce_available_locales is true, :'pt' is missing and default is a Symbol" do + I18n.enforce_available_locales = true + begin + assert_equal 'Foo', I18n.t(:'model.attrs.foo', :locale => :'pt-BR', :default => [:'attrs.foo', "Foo"]) + ensure + I18n.enforce_available_locales = false + end + end + + test "returns fallback default given missing pluralization data" do + assert_equal 'default', I18n.t(:missing_bar, count: 1, default: 'default') + assert_equal 'default', I18n.t(:missing_bar, count: 0, default: 'default') + end + + test "multi-threaded fallbacks" do + I18n.fallbacks = [:en] + + thread = Thread.new do + I18n.fallbacks = [:de] + end + + begin + thread.join + assert_equal 'Bar in :en', I18n.t(:bar, :locale => :'pt-BR') + ensure + thread.exit + I18n.fallbacks = I18n::Locale::Fallbacks.new + end + end +end + +# See Issue #534 +class I18nBackendFallbacksLocalizeTestWithDefaultLocale < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.backend = Backend.new + I18n.enforce_available_locales = false + I18n.fallbacks = [I18n.default_locale] + store_translations(:en, time: { formats: { fallback: 'en fallback' } }) + end + + test "falls back to default locale - Issue #534" do + assert_equal 'en fallback', I18n.l(Time.now, format: :fallback, locale: "un-supported") + end +end + +# See Issue #536 +class I18nBackendFallbacksWithCustomClass < I18n::TestCase + class BackendWithFallbacks < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + # Quacks like a fallback class + class MyDefaultFallback + def [](key) + [:my_language] + end + end + + def setup + super + I18n.backend = BackendWithFallbacks.new + I18n.enforce_available_locales = false + I18n.fallbacks = MyDefaultFallback.new + store_translations(:my_language, foo: 'customer foo') + store_translations(:en, foo: 'english foo') + end + + test "can use a default fallback object that doesn't inherit from I18n::Locale::Fallbacks" do + assert_equal 'customer foo', I18n.t(:foo, locale: :en) + assert_equal 'customer foo', I18n.t(:foo, locale: :nothing) + end +end + +# See Issue #546 +class I18nBackendFallbacksLocalizeTestWithMultipleThreads < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.backend = Backend.new + I18n.enforce_available_locales = false + I18n.fallbacks = [I18n.default_locale] + store_translations(:en, time: { formats: { fallback: 'en fallback' } }) + end + + test "falls back to default locale - Issue #546" do + Thread.new { assert_equal 'en fallback', I18n.l(Time.now, format: :fallback, locale: "un-supported") }.join + end +end + +# See Issue #590 +class I18nBackendFallbacksSymbolResolveRestartsLookupAtOriginalLocale < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.backend = Backend.new + I18n.enforce_available_locales = false + I18n.fallbacks = [:root] + store_translations(:ak, + 'calendars' => { + 'gregorian' => { + 'months' => { + 'format' => { + 'abbreviated' => { + 1 => 'S-Ɔ' + # Other months omitted for brevity + } + } + } + } + }) + store_translations(:root, + 'calendars' => { + 'gregorian' => { + 'months' => { + 'format' => { + 'abbreviated' => :"calendars.gregorian.months.format.wide", + 'wide' => { + 1 => 'M01' + # Other months omitted for brevity + } + }, + 'stand-alone' => { + 'abbreviated' => :"calendars.gregorian.months.format.abbreviated" + } + } + } + }) + end + + test 'falls back to original locale when symbol resolved at fallback locale' do + assert_equal({ 1 => 'S-Ɔ' }, I18n.t('calendars.gregorian.months.stand-alone.abbreviated', locale: :"ak-GH")) + end +end + +# See Issue #617 +class RegressionTestFor617 < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.backend = Backend.new + I18n.enforce_available_locales = false + I18n.fallbacks = {:en=>[:en], :"en-US"=>[:"en-US", :en]} + I18n.locale = :'en-US' + store_translations(:"en-US", {}) + store_translations(:en, :activerecord=>{:models=>{:product=>{:one=>"Product", :other=>"Products"}, :"product/ticket"=>{:one=>"Ticket", :other=>"Tickets"}}}) + end + + test 'model scope resolution' do + defaults = [:product, "Ticket"] + options = {:scope=>[:activerecord, :models], :count=>1, :default=> defaults} + assert_equal("Ticket", I18n.t(:"product/ticket", **options)) + end +end + +class I18nBackendFallbacksLocalizeTest < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.backend = Backend.new + store_translations(:en, :date => { :formats => { :en => 'en' }, :day_names => %w(Sunday) }) + store_translations(:de, :date => { :formats => { :de => 'de' }, :day_names => %w(Sunday) }) + end + + test "still uses an existing format as usual" do + assert_equal 'en', I18n.l(Date.today, :format => :en, :locale => :en) + end + test "looks up and uses a fallback locale's format for a key missing in the given locale" do + assert_equal 'de', I18n.l(Date.today, :format => :de, :locale => :'de-DE') + end + + test "still uses an existing day name translation as usual" do + assert_equal 'Sunday', I18n.l(Date.new(2010, 1, 3), :format => '%A', :locale => :en) + end + + test "uses a fallback locale's translation for a key missing in the given locale" do + assert_equal 'Sunday', I18n.l(Date.new(2010, 1, 3), :format => '%A', :locale => :'de-DE') + end +end + +class I18nBackendFallbacksWithChainTest < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + class Chain < I18n::Backend::Chain + include I18n::Backend::Fallbacks + end + + def setup + super + backend = Backend.new + backend.store_translations(:de, :foo => 'FOO') + backend.store_translations(:'pt-BR', :foo => 'Baz in :pt-BR') + I18n.backend = Chain.new(I18n::Backend::Simple.new, backend) + end + + test "falls back from de-DE to de when there is no translation for de-DE available" do + assert_equal 'FOO', I18n.t(:foo, :locale => :'de-DE') + end + + test "exists? falls back from de-DE to de given a key missing from the given locale" do + assert_equal true, I18n.exists?(:foo, :locale => :'de-DE') + end + + test "exists? should return false when fallback disabled given a key missing from the given locale" do + assert_equal false, I18n.exists?(:foo, :locale => :'de-DE', fallback: false) + end + + test "falls back from de-DE to de when there is no translation for de-DE available when using arrays, too" do + assert_equal ['FOO', 'FOO'], I18n.t([:foo, :foo], :locale => :'de-DE') + end + + test "should not raise error when enforce_available_locales is true, :'pt' is missing and default is a Symbol" do + I18n.enforce_available_locales = true + begin + assert_equal 'Foo', I18n.t(:'model.attrs.foo', :locale => :'pt-BR', :default => [:'attrs.foo', "Foo"]) + ensure + I18n.enforce_available_locales = false + end + end +end + +class I18nBackendFallbacksExistsTest < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.backend = Backend.new + store_translations(:en, :foo => 'Foo in :en', :bar => 'Bar in :en') + store_translations(:de, :bar => 'Bar in :de') + store_translations(:'de-DE', :baz => 'Baz in :de-DE') + end + + test "exists? given an existing key will return true" do + assert_equal true, I18n.exists?(:foo) + end + + test "exists? given a non-existing key will return false" do + assert_equal false, I18n.exists?(:bogus) + end + + test "exists? given an existing key and an existing locale will return true" do + assert_equal true, I18n.exists?(:foo, :en) + assert_equal true, I18n.exists?(:bar, :de) + end + + test "exists? given a non-existing key and an existing locale will return false" do + assert_equal false, I18n.exists?(:bogus, :en) + assert_equal false, I18n.exists?(:bogus, :de) + end + + test "exists? should return true given a key which is missing from the given locale and exists in a fallback locale" do + assert_equal true, I18n.exists?(:bar, :de) + assert_equal true, I18n.exists?(:bar, :'de-DE') + end + + test "exists? should return false given a key which is missing from the given locale and all its fallback locales" do + assert_equal false, I18n.exists?(:baz, :de) + assert_equal false, I18n.exists?(:bogus, :'de-DE') + end + + test "exists? should return false when fallback is disabled given a key which is missing from the given locale" do + assert_equal true, I18n.exists?(:bar, :'de-DE') + assert_equal false, I18n.exists?(:bar, :'de-DE', fallback: false) + assert_equal false, I18n.exists?(:bar, :'de-DE-XX', fallback: false) + end +end + +class I18nBackendOnFallbackHookTest < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Fallbacks + + attr :fallback_collector + + private + + def on_fallback(*args) + @fallback_collector ||= [] + @fallback_collector << args + end + end + + def setup + super + I18n.backend = Backend.new + I18n.fallbacks = I18n::Locale::Fallbacks.new(de: :en) + store_translations(:en, :foo => 'Foo in :en', :bar => 'Bar in :en') + store_translations(:de, :bar => 'Bar in :de') + store_translations(:"de-DE", :baz => 'Baz in :"de-DE"') + end + + test "on_fallback should be called when fallback happens" do + assert_equal [:"de-DE", :de, :en], I18n.fallbacks[:"de-DE"] + assert_equal 'Baz in :"de-DE"', I18n.t(:baz, locale: :'de-DE') + assert_equal 'Bar in :de', I18n.t(:bar, locale: :'de-DE') + assert_equal 'Foo in :en', I18n.t(:foo, locale: :'de-DE') + assert_equal [:'de-DE', :de, :bar, {}], I18n.backend.fallback_collector[0] + assert_equal [:'de-DE', :en, :foo, {}], I18n.backend.fallback_collector[1] + end + + test "on_fallback should not be called when use a String locale" do + assert_equal 'Bar in :de', I18n.t("bar", locale: "de") + assert I18n.backend.fallback_collector.nil? + end +end diff --git a/app/server/ruby/vendor/i18n/test/backend/interpolation_compiler_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/interpolation_compiler_test.rb similarity index 98% rename from app/server/ruby/vendor/i18n/test/backend/interpolation_compiler_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/backend/interpolation_compiler_test.rb index cffe28f9ed..35acce3843 100644 --- a/app/server/ruby/vendor/i18n/test/backend/interpolation_compiler_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/interpolation_compiler_test.rb @@ -78,7 +78,7 @@ def test_handles_weird_strings end def test_raises_exception_when_argument_is_missing - assert_raise(I18n::MissingInterpolationArgument) do + assert_raises(I18n::MissingInterpolationArgument) do compile_and_interpolate('%{first} %{last}', :first => 'first') end end diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/key_value_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/key_value_test.rb new file mode 100644 index 0000000000..34bce7b028 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/key_value_test.rb @@ -0,0 +1,106 @@ +require 'test_helper' + +class I18nBackendKeyValueTest < I18n::TestCase + def setup_backend!(subtree=true) + I18n.backend = I18n::Backend::KeyValue.new({}, subtree) + store_translations(:en, :foo => { :bar => 'bar', :baz => 'baz' }) + end + + def assert_flattens(expected, nested, escape=true, subtree=true) + assert_equal expected, I18n.backend.flatten_translations("en", nested, escape, subtree) + end + + test "hash flattening works" do + setup_backend! + assert_flattens( + {:a=>'a', :b=>{:c=>'c', :d=>'d', :f=>{:x=>'x'}}, :"b.f" => {:x=>"x"}, :"b.c"=>"c", :"b.f.x"=>"x", :"b.d"=>"d"}, + {:a=>'a', :b=>{:c=>'c', :d=>'d', :f=>{:x=>'x'}}} + ) + assert_flattens({:a=>{:b =>['a', 'b']}, :"a.b"=>['a', 'b']}, {:a=>{:b =>['a', 'b']}}) + assert_flattens({:"a\001b" => "c"}, {:"a.b" => "c"}) + assert_flattens({:"a.b"=>['a', 'b']}, {:a=>{:b =>['a', 'b']}}, true, false) + assert_flattens({:"a.b" => "c"}, {:"a.b" => "c"}, false) + end + + test "store_translations supports numeric keys" do + setup_backend! + store_translations(:en, 1 => 'foo') + assert_equal 'foo', I18n.t('1') + assert_equal 'foo', I18n.t(1) + assert_equal 'foo', I18n.t(:'1') + end + + test "store_translations handle subtrees by default" do + setup_backend! + assert_equal({ :bar => 'bar', :baz => 'baz' }, I18n.t("foo")) + end + + test "store_translations merge subtrees accordingly" do + setup_backend! + store_translations(:en, :foo => { :baz => "BAZ"}) + assert_equal('BAZ', I18n.t("foo.baz")) + assert_equal({ :bar => 'bar', :baz => 'BAZ' }, I18n.t("foo")) + end + + test "store_translations does not handle subtrees if desired" do + setup_backend!(false) + assert_raises I18n::MissingTranslationData do + I18n.t("foo", :raise => true) + end + end + + test 'initialized? checks that a store is available' do + setup_backend! + I18n.backend.reload! + assert_equal I18n.backend.initialized?, true + end + + test 'translations gets the translations from the store' do + setup_backend! + I18n.backend.send(:translations) + expected = { :en => {:foo => { :bar => 'bar', :baz => 'baz' }} } + assert_equal expected, translations + end + + test "subtrees enabled: given incomplete pluralization data it raises I18n::InvalidPluralizationData" do + setup_backend! + store_translations(:en, :bar => { :one => "One" }) + assert_raises(I18n::InvalidPluralizationData) { I18n.t(:bar, :count => 2) } + end + + test "subtrees disabled: given incomplete pluralization data it returns an error message" do + setup_backend!(false) + store_translations(:en, :bar => { :one => "One" }) + assert_equal "Translation missing: en.bar", I18n.t(:bar, :count => 2) + end + + test "translate handles subtrees for pluralization" do + setup_backend!(false) + store_translations(:en, :bar => { :one => "One" }) + assert_equal("One", I18n.t("bar", :count => 1)) + end + + test "subtrees enabled: returns localized string given missing pluralization data" do + setup_backend!(true) + assert_equal 'bar', I18n.t("foo.bar", count: 1) + end + + test "subtrees disabled: returns localized string given missing pluralization data" do + setup_backend!(false) + assert_equal 'bar', I18n.t("foo.bar", count: 1) + end + + test "subtrees enabled: Returns fallback default given missing pluralization data" do + setup_backend!(true) + I18n.backend.extend I18n::Backend::Fallbacks + assert_equal 'default', I18n.t(:missing_bar, count: 1, default: 'default') + assert_equal 'default', I18n.t(:missing_bar, count: 0, default: 'default') + end + + test "subtrees disabled: Returns fallback default given missing pluralization data" do + setup_backend!(false) + I18n.backend.extend I18n::Backend::Fallbacks + assert_equal 'default', I18n.t(:missing_bar, count: 1, default: 'default') + assert_equal 'default', I18n.t(:missing_bar, count: 0, default: 'default') + end +end if I18n::TestCase.key_value? diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/lazy_loadable_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/lazy_loadable_test.rb new file mode 100644 index 0000000000..01e2efcc3d --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/lazy_loadable_test.rb @@ -0,0 +1,223 @@ +require 'test_helper' + +class I18nBackendLazyLoadableTest < I18n::TestCase + def setup + super + + @lazy_mode_backend = I18n::Backend::LazyLoadable.new(lazy_load: true) + @eager_mode_backend = I18n::Backend::LazyLoadable.new(lazy_load: false) + + I18n.load_path = [File.join(locales_dir, '/en.yml'), File.join(locales_dir, '/en.yaml'), File.join(locales_dir, '/fr.yml')] + end + + test "lazy mode: only loads translations for current locale" do + with_lazy_mode do + @backend.reload! + + assert_nil translations + + I18n.with_locale(:en) { I18n.t("foo.bar") } + assert_equal({ en: { foo: { bar: "baz" }}}, translations) + end + end + + test "lazy mode: merges translations for current locale with translations already existing in memory" do + with_lazy_mode do + @backend.reload! + + I18n.with_locale(:en) { I18n.t("foo.bar") } + assert_equal({ en: { foo: { bar: "baz" }}}, translations) + + I18n.with_locale(:fr) { I18n.t("animal.dog") } + assert_equal({ en: { foo: { bar: "baz" } }, fr: { animal: { dog: "chien" } } }, translations) + end + end + + test "lazy mode: #initialized? responds based on whether current locale is initialized" do + with_lazy_mode do + @backend.reload! + + I18n.with_locale(:en) do + refute_predicate @backend, :initialized? + I18n.t("foo.bar") + assert_predicate @backend, :initialized? + end + + I18n.with_locale(:fr) do + refute_predicate @backend, :initialized? + end + end + end + + test "lazy mode: reload! uninitializes all locales" do + with_lazy_mode do + I18n.with_locale(:en) { I18n.t("foo.bar") } + I18n.with_locale(:fr) { I18n.t("animal.dog") } + + @backend.reload! + + I18n.with_locale(:en) do + refute_predicate @backend, :initialized? + end + + I18n.with_locale(:fr) do + refute_predicate @backend, :initialized? + end + end + end + + test "lazy mode: eager_load! raises UnsupportedMethod exception" do + with_lazy_mode do + exception = assert_raises(I18n::UnsupportedMethod) { @backend.eager_load! } + expected_msg = "I18n::Backend::LazyLoadable does not support the #eager_load! method. Cannot eager load translations because backend was configured with lazy_load: true." + assert_equal expected_msg, exception.message + end + end + + test "lazy mode: loads translations from files that start with current locale identifier" do + with_lazy_mode do + file_contents = { en: { alice: "bob" } }.to_yaml + + invalid_files = [ + { filename: ['translation', '.yml'] }, # No locale identifier + { filename: ['translation', '.unsupported'] }, # No locale identifier and unsupported extension + ] + + invalid_files.each do |file| + with_translation_file_in_load_path(file[:filename], file[:dir], file_contents) do + I18n.with_locale(:en) { I18n.t("foo.bar") } + assert_equal({ en: { foo: { bar: "baz" }}}, translations) + end + end + + valid_files = [ + { filename: ['en_translation', '.yml'] }, # Contains locale identifier with correct demarcation, and supported extension + { filename: ['en_', '.yml'] }, # Path component matches locale identifier exactly + ] + + valid_files.each do |file| + with_translation_file_in_load_path(file[:filename], file[:dir], file_contents) do + I18n.with_locale(:en) { I18n.t("foo.bar") } + assert_equal({ en: { foo: { bar: "baz" }, alice: "bob" }}, translations) + end + end + end + end + + test "lazy mode: files with unsupported extensions raise UnknownFileType error" do + with_lazy_mode do + file_contents = { en: { alice: "bob" } }.to_yaml + filename = ['en_translation', '.unsupported'] # Correct locale identifier, but unsupported extension + + with_translation_file_in_load_path(filename, nil, file_contents) do + assert_raises(I18n::UnknownFileType) { I18n.t("foo.bar") } + end + end + end + + test "lazy mode: #available_locales returns all locales available from load path irrespective of current locale" do + with_lazy_mode do + I18n.with_locale(:en) { assert_equal [:en, :fr], @backend.available_locales } + I18n.with_locale(:fr) { assert_equal [:en, :fr], @backend.available_locales } + end + end + + test "lazy mode: raises error if translations loaded don't correspond to locale extracted from filename" do + filename = ["en_", ".yml"] + file_contents = { fr: { dog: "chien" } }.to_yaml + + with_lazy_mode do + with_translation_file_in_load_path(filename, nil, file_contents) do |file_path| + exception = assert_raises(I18n::InvalidFilenames) { I18n.t("foo.bar") } + + expected_message = /#{Regexp.escape(file_path)} can only load translations for "en"\. Found translations for: \[\:fr\]/ + assert_match expected_message, exception.message + end + end + end + + test "lazy mode: raises error if translations for more than one locale are loaded from a single file" do + filename = ["en_", ".yml"] + file_contents = { en: { alice: "bob" }, fr: { dog: "chien" }, de: { cat: 'katze' } }.to_yaml + + with_lazy_mode do + with_translation_file_in_load_path(filename, nil, file_contents) do |file_path| + exception = assert_raises(I18n::InvalidFilenames) { I18n.t("foo.bar") } + + expected_message = /#{Regexp.escape(file_path)} can only load translations for "en"\. Found translations for: \[\:fr\, \:de\]/ + assert_match expected_message, exception.message + end + end + end + + test "lazy mode: #lookup lazy loads translations for supplied locale" do + with_lazy_mode do + @backend.reload! + assert_nil translations + + I18n.with_locale(:en) do + assert_equal "chien", @backend.lookup(:fr, "animal.dog") + end + + assert_equal({ fr: { animal: { dog: "chien" } } }, translations) + end + end + + test "eager mode: load all translations, irrespective of locale" do + with_eager_mode do + @backend.reload! + + assert_nil translations + + I18n.with_locale(:en) { I18n.t("foo.bar") } + assert_equal({ en: { foo: { bar: "baz" } }, fr: { animal: { dog: "chien" } } }, translations) + end + end + + test "eager mode: raises error if locales loaded cannot be extracted from load path names" do + with_eager_mode do + @backend.reload! + + contents = { de: { cat: 'katze' } }.to_yaml + + with_translation_file_in_load_path(['fr_translation', '.yml'], nil, contents) do |file_path| + exception = assert_raises(I18n::InvalidFilenames) { I18n.t("foo.bar") } + + expected_message = /#{Regexp.escape(file_path)} can only load translations for "fr"\. Found translations for: \[\:de\]/ + assert_match expected_message, exception.message + end + end + end + + private + + def with_lazy_mode + @backend = I18n.backend = @lazy_mode_backend + + yield + end + + def with_eager_mode + @backend = I18n.backend = @eager_mode_backend + + yield + end + + + def with_translation_file_in_load_path(name, tmpdir, file_contents) + @backend.reload! + + path_to_dir = FileUtils.mkdir_p(File.join(Dir.tmpdir, tmpdir)).first if tmpdir + locale_file = Tempfile.new(name, path_to_dir) + + locale_file.write(file_contents) + locale_file.rewind + + I18n.load_path << locale_file.path + + yield(locale_file.path) + + I18n.load_path.delete(locale_file.path) + end +end + diff --git a/app/server/ruby/vendor/i18n/test/backend/memoize_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/memoize_test.rb similarity index 50% rename from app/server/ruby/vendor/i18n/test/backend/memoize_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/backend/memoize_test.rb index a8a2e11ecf..d8503c147b 100644 --- a/app/server/ruby/vendor/i18n/test/backend/memoize_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/memoize_test.rb @@ -1,5 +1,4 @@ require 'test_helper' - require 'backend/simple_test' class I18nBackendMemoizeTest < I18nBackendSimpleTest @@ -44,4 +43,42 @@ def test_resets_available_locales_on_store_translations assert I18n.available_locales.include?(:copa) assert_equal 1, I18n.backend.spy_calls end + + def test_eager_load + I18n.eager_load! + I18n.backend.spy_calls = 0 + assert_equal I18n.available_locales, I18n.available_locales + assert_equal 0, I18n.backend.spy_calls + end + + module TestLookup + def lookup(locale, key, scope = [], options = {}) + keys = I18n.normalize_keys(locale, key, scope, options[:separator]) + keys.inspect + end + end + + def test_lookup_concurrent_consistency + backend_impl = Class.new(I18n::Backend::Simple) do + include TestLookup + include I18n::Backend::Memoize + end + backend = backend_impl.new + + memoized_lookup = backend.send(:memoized_lookup) + + assert_equal "[:foo, :scoped, :sample]", backend.translate('foo', scope = [:scoped, :sample]) + + 30.times.inject([]) do |memo, i| + memo << Thread.new do + backend.translate('bar', scope); backend.translate(:baz, scope) + end + end.each(&:join) + + memoized_lookup = backend.send(:memoized_lookup) + puts memoized_lookup.inspect if $VERBOSE + assert_equal 3, memoized_lookup.size, "NON-THREAD-SAFE lookup memoization backend: #{memoized_lookup.class}" + # if a plain Hash is used might eventually end up in a weird (inconsistent) state + end + end diff --git a/app/server/ruby/vendor/i18n/test/backend/metadata_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/metadata_test.rb similarity index 99% rename from app/server/ruby/vendor/i18n/test/backend/metadata_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/backend/metadata_test.rb index 5ccb6462ce..44612cf080 100644 --- a/app/server/ruby/vendor/i18n/test/backend/metadata_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/metadata_test.rb @@ -6,6 +6,7 @@ class Backend < I18n::Backend::Simple end def setup + super I18n.backend = Backend.new store_translations(:en, :foo => 'Hi %{name}') end diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_fallback_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_fallback_test.rb new file mode 100644 index 0000000000..351aa6a353 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_fallback_test.rb @@ -0,0 +1,69 @@ +require 'test_helper' + +class I18nBackendPluralizationFallbackTest < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Pluralization + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.default_locale = :'en' + I18n.backend = Backend.new + + store_translations('en', cat: { zero: 'cat', one: 'cat', other: 'cats' }) + store_translations('en-US', cat: { zero: 'no cat', one: nil, other: 'lots of cats' }) + + store_translations('ru', cat: { one: 'кот', few: 'кошек', many: 'кошка', other: 'кошек' }) + # probably not a real locale but just to demonstrate + store_translations('ru-US', cat: { one: nil, few: nil, many: nil, other: nil }) + store_translations('ru', i18n: { plural: { rule: russian_rule }}) + end + + test "fallbacks: nils are ignored and fallback is applied" do + assert_equal "no cat", I18n.t("cat", count: 0, locale: "en-US") + assert_equal "cat", I18n.t("cat", count: 0, locale: "en") + + assert_equal "cat", I18n.t("cat", count: 1, locale: "en-US") + assert_equal "cat", I18n.t("cat", count: 1, locale: "en") + + assert_equal "lots of cats", I18n.t("cat", count: 2, locale: "en-US") + assert_equal "cats", I18n.t("cat", count: 2, locale: "en") + end + + test "fallbacks: nils are ignored and fallback is applied, with custom rule" do + # more specs: https://github.com/svenfuchs/rails-i18n/blob/master/spec/unit/pluralization/east_slavic.rb + assert_equal "кошка", I18n.t("cat", count: 0, locale: "ru") + assert_equal "кошка", I18n.t("cat", count: 0, locale: "ru-US") + + assert_equal "кот", I18n.t("cat", count: 1, locale: "ru") + assert_equal "кот", I18n.t("cat", count: 1, locale: "ru-US") + + assert_equal "кошек", I18n.t("cat", count: 2, locale: "ru") + assert_equal "кошек", I18n.t("cat", count: 2, locale: "ru-US") + + assert_equal "кошек", I18n.t("cat", count: 1.5, locale: "ru") + assert_equal "кошек", I18n.t("cat", count: 1.5, locale: "ru-US") + end + + private + + # copied from https://github.com/svenfuchs/rails-i18n/blob/master/lib/rails_i18n/common_pluralizations/east_slavic.rb + def russian_rule + lambda do |n| + n ||= 0 + mod10 = n % 10 + mod100 = n % 100 + + if mod10 == 1 && mod100 != 11 + :one + elsif (2..4).include?(mod10) && !(12..14).include?(mod100) + :few + elsif mod10 == 0 || (5..9).include?(mod10) || (11..14).include?(mod100) + :many + else + :other + end + end + end +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_scope_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_scope_test.rb new file mode 100644 index 0000000000..e5208739d6 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_scope_test.rb @@ -0,0 +1,55 @@ +require 'test_helper' + +class I18nBackendPluralizationScopeTest < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Pluralization + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.default_locale = :'en' + I18n.backend = Backend.new + + translations = { + i18n: { + plural: { + keys: [:one, :other], + rule: lambda { |n| n == 1 ? :one : :other }, + } + }, + activerecord: { + models: { + my_model: { + one: 'one model', + other: 'more models', + some_other_key: { + key: 'value' + } + } + } + } + } + + store_translations('en', translations) + end + + test "pluralization picks :other for 2" do + args = { + scope: [:activerecord, :models], + count: 2, + default: ["My model"] + } + assert_equal 'more models', I18n.translate(:my_model, **args) + end + + test "pluralization picks :one for 1" do + args = { + scope: [:activerecord, :models], + count: 1, + default: ["My model"] + } + assert_equal 'one model', I18n.translate(:my_model, **args) + end + +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_test.rb new file mode 100644 index 0000000000..0a7321a551 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/pluralization_test.rb @@ -0,0 +1,95 @@ +require 'test_helper' + +class I18nBackendPluralizationTest < I18n::TestCase + class Backend < I18n::Backend::Simple + include I18n::Backend::Pluralization + include I18n::Backend::Fallbacks + end + + def setup + super + I18n.backend = Backend.new + @rule = lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : n == 0 || (2..10).include?(n % 100) ? :few : (11..19).include?(n % 100) ? :many : :other } + store_translations(:xx, :i18n => { :plural => { :rule => @rule } }) + @entry = { :"0" => 'none', :"1" => 'single', :one => 'one', :few => 'few', :many => 'many', :other => 'other' } + @entry_with_zero = @entry.merge( { :zero => 'zero' } ) + end + + test "pluralization picks a pluralizer from :'i18n.pluralize'" do + assert_equal @rule, I18n.backend.send(:pluralizer, :xx) + end + + test "pluralization picks the explicit 1 rule for count == 1, the explicit rule takes priority over the matching :one rule" do + assert_equal 'single', I18n.t(:count => 1, :default => @entry, :locale => :xx) + assert_equal 'single', I18n.t(:count => 1.0, :default => @entry, :locale => :xx) + end + + test "pluralization picks :one for 1, since in this case that is the matching rule for 1 (when there is no explicit 1 rule)" do + @entry.delete(:"1") + assert_equal 'one', I18n.t(:count => 1, :default => @entry, :locale => :xx) + end + + test "pluralization picks :few for 2" do + assert_equal 'few', I18n.t(:count => 2, :default => @entry, :locale => :xx) + end + + test "pluralization picks :many for 11" do + assert_equal 'many', I18n.t(:count => 11, :default => @entry, :locale => :xx) + end + + test "pluralization picks zero for 0 if the key is contained in the data" do + assert_equal 'zero', I18n.t(:count => 0, :default => @entry_with_zero, :locale => :xx) + end + + test "pluralization picks explicit 0 rule for count == 0, since the explicit rule takes priority over the matching :few rule" do + assert_equal 'none', I18n.t(:count => 0, :default => @entry, :locale => :xx) + assert_equal 'none', I18n.t(:count => 0.0, :default => @entry, :locale => :xx) + assert_equal 'none', I18n.t(:count => -0, :default => @entry, :locale => :xx) + end + + test "pluralization picks :few for 0 (when there is no explicit 0 rule)" do + @entry.delete(:"0") + assert_equal 'few', I18n.t(:count => 0, :default => @entry, :locale => :xx) + end + + test "pluralization does Lateral Inheritance to :other to cover missing data" do + @entry.delete(:many) + assert_equal 'other', I18n.t(:count => 11, :default => @entry, :locale => :xx) + end + + test "pluralization picks one for 1 if the entry has attributes hash on unknown locale" do + @entry[:attributes] = { :field => 'field', :second => 'second' } + assert_equal 'one', I18n.t(:count => 1, :default => @entry, :locale => :pirate) + end + + test "Nested keys within pluralization context" do + store_translations(:xx, + :stars => { + one: "%{count} star", + other: "%{count} stars", + special: { + one: "%{count} special star", + other: "%{count} special stars", + } + } + ) + assert_equal "1 star", I18n.t('stars', count: 1, :locale => :xx) + assert_equal "20 stars", I18n.t('stars', count: 20, :locale => :xx) + assert_equal "1 special star", I18n.t('stars.special', count: 1, :locale => :xx) + assert_equal "20 special stars", I18n.t('stars.special', count: 20, :locale => :xx) + end + + test "Fallbacks can pick up rules from fallback locales, too" do + assert_equal @rule, I18n.backend.send(:pluralizer, :'xx-XX') + end + + test "linked lookup works with pluralization backend" do + I18n.backend.store_translations(:xx, { + :automobiles => :autos, + :autos => :cars, + :cars => { :porsche => { :one => "I have %{count} Porsche 🚗", :other => "I have %{count} Porsches 🚗" } } + }) + assert_equal "I have 1 Porsche 🚗", I18n.t(:'automobiles.porsche', count: 1, :locale => :xx) + assert_equal "I have 20 Porsches 🚗", I18n.t(:'automobiles.porsche', count: 20, :locale => :xx) + end +end diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/backend/simple_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/simple_test.rb new file mode 100644 index 0000000000..d568961d15 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/simple_test.rb @@ -0,0 +1,232 @@ +require 'test_helper' + +class I18nBackendSimpleTest < I18n::TestCase + def setup + super + I18n.backend = I18n::Backend::Simple.new + I18n.load_path = [locales_dir + '/en.yml'] + end + + # useful because this way we can use the backend with no key for interpolation/pluralization + test "simple backend translate: given nil as a key it still interpolations the default value" do + assert_equal "Hi David", I18n.t(nil, :default => "Hi %{name}", :name => "David") + end + + test "simple backend translate: given true as a key" do + store_translations :en, available: { true => "Yes", false => "No" } + assert_equal "Yes", I18n.t(:available)[true] + assert_equal "No", I18n.t(:available)[false] + end + + test "simple backend translate: given integer as a key" do + store_translations :en, available: { -1 => "Possibly", 0 => "Maybe", 1 => "Yes", 2 => "No", 3 => "Never" } + assert_equal "Possibly", I18n.t(:available)[-1] + assert_equal "Possibly", I18n.t('available.-1') + assert_equal "Maybe", I18n.t(:available)[0] + assert_equal "Maybe", I18n.t('available.0') + assert_equal "Yes", I18n.t(:available)[1] + assert_equal "Yes", I18n.t('available.1') + assert_equal "No", I18n.t(:available)[2] + assert_equal "No", I18n.t('available.2') + assert_equal "Never", I18n.t(:available)[3] + assert_equal "Never", I18n.t('available.+3') + end + + test 'simple backend translate: given integer with a leading positive/negative sign' do + store_translations :en, available: { -1 => "No", 0 => "Maybe", 1 => "Yes" } + assert_equal 'No', I18n.t(:available)[-1] + assert_equal 'No', I18n.t('available.-1') + assert_equal 'Maybe', I18n.t(:available)[+0] + assert_equal 'Maybe', I18n.t(:available)[-0] + assert_equal 'Maybe', I18n.t('available.-0') + assert_equal 'Maybe', I18n.t('available.+0') + assert_equal 'Yes', I18n.t(:available)[+1] + assert_equal 'Yes', I18n.t('available.+1') + end + + test 'simple backend translate: given integer with a lead zero as a key' do + store_translations :en, available: { '01' => 'foo' } + assert_equal 'foo', I18n.t(:available)[:'01'] + assert_equal 'foo', I18n.t('available.01') + end + + test "simple backend translate: symbolize keys in hash" do + store_translations :en, nested_hashes_in_array: { hello: "world" } + assert_equal "world", I18n.t('nested_hashes_in_array.hello') + assert_equal "world", I18n.t('nested_hashes_in_array')[:hello] + end + + test "simple backend translate: symbolize keys in array" do + store_translations :en, nested_hashes_in_array: [ { hello: "world" } ] + I18n.t('nested_hashes_in_array').each do |val| + assert_equal "world", val[:hello] + end + end + + # loading translations + test "simple load_translations: given an unknown file type it raises I18n::UnknownFileType" do + assert_raises(I18n::UnknownFileType) { I18n.backend.load_translations("#{locales_dir}/en.xml") } + end + + test "simple load_translations: given a YAML file name with yaml extension does not raise anything" do + assert_nothing_raised { I18n.backend.load_translations("#{locales_dir}/en.yaml") } + end + + test "simple load_translations: given a JSON file name with yaml extension does not raise anything" do + assert_nothing_raised { I18n.backend.load_translations("#{locales_dir}/en.json") } + end + + test "simple load_translations: given a Ruby file name it does not raise anything" do + assert_nothing_raised { I18n.backend.load_translations("#{locales_dir}/en.rb") } + end + + test "simple load_translations: given no argument, it uses I18n.load_path" do + I18n.backend.load_translations + assert_equal({ :en => { :foo => { :bar => 'baz' } } }, I18n.backend.send(:translations)) + end + + test "simple load_rb: loads data from a Ruby file" do + data, _ = I18n.backend.send(:load_rb, "#{locales_dir}/en.rb") + assert_equal({ :en => { :fuh => { :bah => 'bas' } } }, data) + end + + test "simple load_yml: loads data from a YAML file" do + data, _ = I18n.backend.send(:load_yml, "#{locales_dir}/en.yml") + if ::YAML.respond_to?(:unsafe_load_file) + assert_equal({ :en => { :foo => { :bar => 'baz' } } }, data) + assert_predicate data.dig(:en, :foo, :bar), :frozen? + else + assert_equal({ 'en' => { 'foo' => { 'bar' => 'baz' } } }, data) + end + end + + test "simple load_json: loads data from a JSON file" do + data, _ = I18n.backend.send(:load_json, "#{locales_dir}/en.json") + + if JSON.respond_to?(:load_file) + assert_equal({ :en => { :foo => { :bar => 'baz' } } }, data) + assert_predicate data.dig(:en, :foo, :bar), :frozen? + else + assert_equal({ 'en' => { 'foo' => { 'bar' => 'baz' } } }, data) + end + end + + test "simple load_translations: loads data from known file formats" do + I18n.backend = I18n::Backend::Simple.new + I18n.backend.load_translations("#{locales_dir}/en.rb", "#{locales_dir}/en.yml") + expected = { :en => { :fuh => { :bah => "bas" }, :foo => { :bar => "baz" } } } + assert_equal expected, translations + end + + test "simple load_translations: given file names as array it does not raise anything" do + assert_nothing_raised { I18n.backend.load_translations(["#{locales_dir}/en.rb", "#{locales_dir}/en.yml"]) } + end + + # storing translations + + test "simple store_translations: stores translations, ... no, really :-)" do + store_translations :'en', :foo => 'bar' + assert_equal Hash[:'en', {:foo => 'bar'}], translations + end + + test "simple store_translations: deep_merges with existing translations" do + store_translations :'en', :foo => {:bar => 'bar'} + store_translations :'en', :foo => {:baz => 'baz'} + assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], translations + end + + test "simple store_translations: converts the given locale to a Symbol" do + store_translations 'en', :foo => 'bar' + assert_equal Hash[:'en', {:foo => 'bar'}], translations + end + + test "simple store_translations: converts keys to Symbols" do + store_translations 'en', 'foo' => {'bar' => 'bar', 'baz' => 'baz'} + assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], translations + end + + test "simple store_translations: do not store translations unavailable locales if enforce_available_locales is true" do + begin + I18n.enforce_available_locales = true + I18n.available_locales = [:en, :es] + store_translations(:fr, :foo => {:bar => 'barfr', :baz => 'bazfr'}) + store_translations(:es, :foo => {:bar => 'bares', :baz => 'bazes'}) + assert_equal translations[:fr], {} + assert_equal Hash[:foo, {:bar => 'bares', :baz => 'bazes'}], translations[:es] + ensure + I18n.config.enforce_available_locales = false + end + end + + test "simple store_translations: store translations for unavailable locales if enforce_available_locales is false" do + I18n.available_locales = [:en, :es] + store_translations(:fr, :foo => {:bar => 'barfr', :baz => 'bazfr'}) + assert_equal Hash[:foo, {:bar => 'barfr', :baz => 'bazfr'}], translations[:fr] + end + + test "simple store_translations: supports numeric keys" do + store_translations(:en, 1 => 'foo') + assert_equal 'foo', I18n.t('1') + assert_equal 'foo', I18n.t(1) + assert_equal 'foo', I18n.t(:'1') + end + + test "simple store_translations: store translations doesn't deep symbolize keys if skip_symbolize_keys is true" do + data = { :foo => {'bar' => 'barfr', 'baz' => 'bazfr'} } + + # symbolized by default + store_translations(:fr, data) + assert_equal Hash[:foo, {:bar => 'barfr', :baz => 'bazfr'}], translations[:fr] + + I18n.backend.reload! + + # not deep symbolized when configured + store_translations(:fr, data, skip_symbolize_keys: true) + assert_equal Hash[:foo, {'bar' => 'barfr', 'baz' => 'bazfr'}], translations[:fr] + end + + # reloading translations + + test "simple reload_translations: unloads translations" do + I18n.backend.reload! + assert_nil translations + end + + test "simple reload_translations: uninitializes the backend" do + I18n.backend.reload! + assert_equal false, I18n.backend.initialized? + end + + test "simple eager_load!: loads the translations" do + assert_equal false, I18n.backend.initialized? + I18n.backend.eager_load! + assert_equal true, I18n.backend.initialized? + end + + test "simple reload!: reinitialize the backend if it was previously eager loaded" do + I18n.backend.eager_load! + I18n.backend.reload! + assert_equal true, I18n.backend.initialized? + end + + test "Nested keys within pluralization context" do + store_translations(:en, + :stars => { + one: "%{count} star", + other: "%{count} stars", + special: { + one: "%{count} special star", + other: "%{count} special stars", + } + } + ) + assert_equal "1 star", I18n.t('stars', count: 1, :locale => :en) + assert_equal "20 stars", I18n.t('stars', count: 20, :locale => :en) + assert_equal "1 special star", I18n.t('stars.special', count: 1, :locale => :en) + assert_equal "20 special stars", I18n.t('stars.special', count: 20, :locale => :en) + end + + test "returns localized string given missing pluralization data" do + assert_equal 'baz', I18n.t('foo.bar', count: 1) + end +end diff --git a/app/server/ruby/vendor/i18n/test/backend/transliterator_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/backend/transliterator_test.rb similarity index 90% rename from app/server/ruby/vendor/i18n/test/backend/transliterator_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/backend/transliterator_test.rb index 4012dce228..4c2bc226c7 100644 --- a/app/server/ruby/vendor/i18n/test/backend/transliterator_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/backend/transliterator_test.rb @@ -3,6 +3,7 @@ class I18nBackendTransliterator < I18n::TestCase def setup + super I18n.backend = I18n::Backend::Simple.new @proc = lambda { |n| n.upcase } @hash = { "ü" => "ue", "ö" => "oe", "a" => "a" } @@ -21,7 +22,7 @@ def setup test "transliteration rule must be a proc or hash" do store_translations(:xx, :i18n => {:transliterate => {:rule => ""}}) - assert_raise I18n::ArgumentError do + assert_raises I18n::ArgumentError do I18n.backend.transliterate(:xx, "ü") end end @@ -41,7 +42,7 @@ def setup # create string with range of Unicode's western characters with # diacritics, excluding the division and multiplication signs which for # some reason or other are floating in the middle of all the letters. - string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include? c}.pack("U*") + string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include? c}.append(0x1E9E).pack("U*") string.split(//) do |char| assert_match %r{^[a-zA-Z']*$}, @transliterator.transliterate(string) end @@ -55,11 +56,9 @@ def setup assert_equal "abc#", @transliterator.transliterate("abcſ", "#") end - if RUBY_VERSION >= "1.9" - test "default transliterator raises errors for invalid UTF-8" do - assert_raise ArgumentError do - @transliterator.transliterate("a\x92b") - end + test "default transliterator raises errors for invalid UTF-8" do + assert_raises ArgumentError do + @transliterator.transliterate("a\x92b") end end @@ -75,7 +74,7 @@ def setup test "default transliterator fails with custom rules with uncomposed input" do char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS transliterator = I18n::Backend::Transliterator.get(@hash) - assert_not_equal "ue", transliterator.transliterate(char) + refute_equal "ue", transliterator.transliterate(char) end test "DEFAULT_APPROXIMATIONS is frozen to prevent concurrency issues" do diff --git a/app/server/ruby/vendor/i18n/test/gettext/api_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/gettext/api_test.rb similarity index 98% rename from app/server/ruby/vendor/i18n/test/gettext/api_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/gettext/api_test.rb index ac4edaf0ff..2cd157c0d8 100644 --- a/app/server/ruby/vendor/i18n/test/gettext/api_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/gettext/api_test.rb @@ -6,6 +6,7 @@ class I18nGettextApiTest < I18n::TestCase def setup + super I18n.locale = :en I18n.backend.store_translations :de, { 'Hi Gettext!' => 'Hallo Gettext!', @@ -17,6 +18,13 @@ def setup }, :separator => '|' end + # N_ + def test_N_returns_original_msg + assert_equal 'foo|bar', N_('foo|bar') + I18n.locale = :de + assert_equal 'Hi Gettext!', N_('Hi Gettext!') + end + # gettext def test_gettext_uses_msg_as_default assert_equal 'Hi Gettext!', _('Hi Gettext!') diff --git a/app/server/ruby/vendor/i18n-1.14.1/test/gettext/backend_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/gettext/backend_test.rb new file mode 100644 index 0000000000..30254e6be0 --- /dev/null +++ b/app/server/ruby/vendor/i18n-1.14.1/test/gettext/backend_test.rb @@ -0,0 +1,92 @@ +# encoding: utf-8 + +require 'test_helper' + +class I18nGettextBackendTest < I18n::TestCase + include I18n::Gettext::Helpers + + class Backend < I18n::Backend::Simple + include I18n::Backend::Gettext + end + + def setup + super + I18n.backend = Backend.new + I18n.locale = :en + I18n.load_path = ["#{locales_dir}/de.po"] + I18n.default_separator = '|' + end + + def test_backend_loads_po_file + I18n.backend.send(:init_translations) + assert I18n.backend.send(:translations)[:de][:"Axis"] + end + + def test_looks_up_a_translation + I18n.locale = :de + assert_equal 'Auto', gettext('car') + end + + def test_uses_default_translation + assert_equal 'car', gettext('car') + end + + def test_looks_up_a_namespaced_translation + I18n.locale = :de + assert_equal 'Räderzahl', sgettext('Car|Wheels count') + assert_equal 'Räderzahl', pgettext('Car', 'Wheels count') + assert_equal 'Räderzahl!', pgettext('New car', 'Wheels count') + end + + def test_uses_namespaced_default_translation + assert_equal 'Wheels count', sgettext('Car|Wheels count') + assert_equal 'Wheels count', pgettext('Car', 'Wheels count') + assert_equal 'Wheels count', pgettext('New car', 'Wheels count') + end + + def test_pluralizes_entry + I18n.locale = :de + assert_equal 'Achse', ngettext('Axis', 'Axis', 1) + assert_equal 'Achsen', ngettext('Axis', 'Axis', 2) + end + + def test_pluralizes_default_entry + assert_equal 'Axis', ngettext('Axis', 'Axis', 1) + assert_equal 'Axis', ngettext('Axis', 'Axis', 2) + end + + def test_pluralizes_namespaced_entry + I18n.locale = :de + assert_equal 'Rad', nsgettext('Car|wheel', 'wheels', 1) + assert_equal 'Räder', nsgettext('Car|wheel', 'wheels', 2) + assert_equal 'Rad', npgettext('Car', 'wheel', 'wheels', 1) + assert_equal 'Räder', npgettext('Car', 'wheel', 'wheels', 2) + assert_equal 'Rad!', npgettext('New car', 'wheel', 'wheels', 1) + assert_equal 'Räder!', npgettext('New car', 'wheel', 'wheels', 2) + end + + def test_pluralizes_namespaced_default_entry + assert_equal 'wheel', nsgettext('Car|wheel', 'wheels', 1) + assert_equal 'wheels', nsgettext('Car|wheel', 'wheels', 2) + assert_equal 'wheel', npgettext('Car', 'wheel', 'wheels', 1) + assert_equal 'wheels', npgettext('Car', 'wheel', 'wheels', 2) + assert_equal 'wheel', npgettext('New car', 'wheel', 'wheels', 1) + assert_equal 'wheels', npgettext('New car', 'wheel', 'wheels', 2) + end + + def test_pluralizes_namespaced_entry_with_alternative_syntax + I18n.locale = :de + assert_equal 'Rad', nsgettext(['Car|wheel', 'wheels'], 1) + assert_equal 'Räder', nsgettext(['Car|wheel', 'wheels'], 2) + assert_equal 'Rad', npgettext('Car', ['wheel', 'wheels'], 1) + assert_equal 'Räder', npgettext('Car', ['wheel', 'wheels'], 2) + assert_equal 'Rad!', npgettext('New car', ['wheel', 'wheels'], 1) + assert_equal 'Räder!', npgettext('New car', ['wheel', 'wheels'], 2) + end + + def test_ngettextpluralizes_entry_with_dots + I18n.locale = :de + assert_equal 'Auf 1 Achse.', n_("On %{count} wheel.", "On %{count} wheels.", 1) + assert_equal 'Auf 2 Achsen.', n_("On %{count} wheel.", "On %{count} wheels.", 2) + end +end diff --git a/app/server/ruby/vendor/i18n/test/i18n/exceptions_test.rb b/app/server/ruby/vendor/i18n-1.14.1/test/i18n/exceptions_test.rb similarity index 67% rename from app/server/ruby/vendor/i18n/test/i18n/exceptions_test.rb rename to app/server/ruby/vendor/i18n-1.14.1/test/i18n/exceptions_test.rb index e633d7d9f6..771308d0b2 100644 --- a/app/server/ruby/vendor/i18n/test/i18n/exceptions_test.rb +++ b/app/server/ruby/vendor/i18n-1.14.1/test/i18n/exceptions_test.rb @@ -13,6 +13,11 @@ def test_invalid_locale_stores_locale end end + test "MissingTranslation can be initialized without options" do + exception = I18n::MissingTranslation.new(:en, 'foo') + assert_equal({}, exception.options) + end + test "MissingTranslationData exception stores locale, key and options" do force_missing_translation_data do |exception| assert_equal 'de', exception.locale @@ -27,36 +32,24 @@ def test_invalid_locale_stores_locale end end - test "MissingTranslationData html_message is a span with the titlelized last key token" do - exception = I18n::MissingTranslationData.new(:de, :foo, :scope => :bar) - assert_equal 'Foo', exception.html_message - end - - test "MissingTranslationData html_message html escapes key names" do - exception = I18n::MissingTranslationData.new(:de, '', :scope => '