summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2021-06-21 14:29:34 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2021-06-21 14:29:34 +0000
commit89fed0e42389e6b3291e9f2e8b1165858b6c4b4f (patch)
tree0f4d08540c8e5fa16bcd0c99cd413fe815560e4b
parentb4233fd961752b80d42ae68e8b42acfb3e99be57 (diff)
parent33a56a4450406ed97f7289fe0b6a0a20482f0a01 (diff)
downloadlibchrome-android12-mainline-documentsui-release.tar.gz
Snap for 7478028 from 33a56a4450406ed97f7289fe0b6a0a20482f0a01 to mainline-documentsui-releaseandroid-mainline-12.0.0_r26android-mainline-12.0.0_r2aml_doc_310851020android12-mainline-documentsui-release
Change-Id: Ie15f13b179f0ad841e4dfd95fcfdce5f01a17f29
-rw-r--r--Android.bp620
-rw-r--r--BUILD.IGNORE10
-rw-r--r--BUILD.gn382
-rw-r--r--METADATA3
-rw-r--r--base/android/android_hardware_buffer_abi.h90
-rw-r--r--base/android/android_hardware_buffer_compat.cc129
-rw-r--r--base/android/android_hardware_buffer_compat.h51
-rw-r--r--base/android/android_image_reader_abi.h97
-rw-r--r--base/android/android_image_reader_compat.cc142
-rw-r--r--base/android/android_image_reader_compat.h79
-rw-r--r--base/android/android_image_reader_compat_unittest.cc43
-rw-r--r--base/android/animation_frame_time_histogram.cc26
-rw-r--r--base/android/apk_assets.cc47
-rw-r--r--base/android/apk_assets.h39
-rw-r--r--base/android/application_status_listener.cc78
-rw-r--r--base/android/application_status_listener.h88
-rw-r--r--base/android/application_status_listener_unittest.cc131
-rw-r--r--base/android/base_jni_onload.cc24
-rw-r--r--base/android/callback_android.cc44
-rw-r--r--base/android/callback_android.h38
-rw-r--r--base/android/child_process_binding_types.h25
-rw-r--r--base/android/child_process_service.cc79
-rw-r--r--base/android/command_line_android.cc89
-rw-r--r--base/android/content_uri_utils.cc45
-rw-r--r--base/android/content_uri_utils.h29
-rw-r--r--base/android/content_uri_utils_unittest.cc40
-rw-r--r--base/android/cpu_features.cc22
-rw-r--r--base/android/early_trace_event_binding.cc67
-rw-r--r--base/android/event_log.cc16
-rw-r--r--base/android/event_log.h20
-rw-r--r--base/android/field_trial_list.cc47
-rw-r--r--base/android/important_file_writer_android.cc37
-rw-r--r--base/android/java/src/org/chromium/base/ActivityState.java48
-rw-r--r--base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java145
-rw-r--r--base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java705
-rw-r--r--base/android/java/src/org/chromium/base/ApkAssets.java58
-rw-r--r--base/android/java/src/org/chromium/base/ApplicationStatus.java620
-rw-r--r--base/android/java/src/org/chromium/base/BaseSwitches.java32
-rw-r--r--base/android/java/src/org/chromium/base/Callback.java43
-rw-r--r--base/android/java/src/org/chromium/base/CollectionUtil.java99
-rw-r--r--base/android/java/src/org/chromium/base/CommandLine.java389
-rw-r--r--base/android/java/src/org/chromium/base/CommandLineInitUtil.java103
-rw-r--r--base/android/java/src/org/chromium/base/ContentUriUtils.java251
-rw-r--r--base/android/java/src/org/chromium/base/CpuFeatures.java42
-rw-r--r--base/android/java/src/org/chromium/base/EarlyTraceEvent.java299
-rw-r--r--base/android/java/src/org/chromium/base/EventLog.java20
-rw-r--r--base/android/java/src/org/chromium/base/FieldTrialList.java46
-rw-r--r--base/android/java/src/org/chromium/base/FileUtils.java149
-rw-r--r--base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java31
-rw-r--r--base/android/java/src/org/chromium/base/JNIUtils.java46
-rw-r--r--base/android/java/src/org/chromium/base/JavaHandlerThread.java119
-rw-r--r--base/android/java/src/org/chromium/base/LocaleUtils.java207
-rw-r--r--base/android/java/src/org/chromium/base/MemoryPressureListener.java130
-rw-r--r--base/android/java/src/org/chromium/base/NonThreadSafe.java41
-rw-r--r--base/android/java/src/org/chromium/base/ObserverList.java249
-rw-r--r--base/android/java/src/org/chromium/base/PathService.java26
-rw-r--r--base/android/java/src/org/chromium/base/PathUtils.java263
-rw-r--r--base/android/java/src/org/chromium/base/PowerMonitor.java80
-rw-r--r--base/android/java/src/org/chromium/base/Promise.java294
-rw-r--r--base/android/java/src/org/chromium/base/SecureRandomInitializer.java35
-rw-r--r--base/android/java/src/org/chromium/base/StreamUtil.java28
-rw-r--r--base/android/java/src/org/chromium/base/SysUtils.java199
-rw-r--r--base/android/java/src/org/chromium/base/ThrowUncaughtException.java21
-rw-r--r--base/android/java/src/org/chromium/base/TimeUtils.java18
-rw-r--r--base/android/java/src/org/chromium/base/TraceEvent.java387
-rw-r--r--base/android/java/src/org/chromium/base/UnguessableToken.java91
-rw-r--r--base/android/java/src/org/chromium/base/annotations/DoNotInline.java20
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java829
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/Linker.java1160
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java16
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java20
-rw-r--r--base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java35
-rw-r--r--base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java15
-rw-r--r--base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java301
-rw-r--r--base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java113
-rw-r--r--base/android/java/src/org/chromium/base/metrics/CachedMetrics.java307
-rw-r--r--base/android/java/src/org/chromium/base/metrics/RecordHistogram.java331
-rw-r--r--base/android/java/src/org/chromium/base/metrics/RecordUserAction.java85
-rw-r--r--base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java27
-rw-r--r--base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java78
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java305
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java766
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java27
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java278
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java346
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java76
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl7
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java68
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl (renamed from mojo/public/mojom/base/logfont_win.mojom)10
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl26
-rw-r--r--base/android/java/templates/BuildConfig.template70
-rw-r--r--base/android/java/templates/NativeLibraries.template109
-rw-r--r--base/android/java_exception_reporter.cc7
-rw-r--r--base/android/java_handler_thread.cc132
-rw-r--r--base/android/java_handler_thread.h95
-rw-r--r--base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java77
-rw-r--r--base/android/javatests/src/org/chromium/base/ApiCompatibilityUtilsTest.java88
-rw-r--r--base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java35
-rw-r--r--base/android/javatests/src/org/chromium/base/CommandLineTest.java141
-rw-r--r--base/android/javatests/src/org/chromium/base/EarlyTraceEventTest.java272
-rw-r--r--base/android/javatests/src/org/chromium/base/LocaleUtilsTest.java262
-rw-r--r--base/android/javatests/src/org/chromium/base/ObserverListTest.java340
-rw-r--r--base/android/javatests/src/org/chromium/base/StrictModeContextTest.java118
-rw-r--r--base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java201
-rw-r--r--base/android/jni_array_unittest.cc389
-rw-r--r--base/android/jni_generator/PRESUBMIT.py37
-rw-r--r--base/android/jni_generator/README.md118
-rw-r--r--base/android/jni_generator/SampleForTests_jni.golden494
-rw-r--r--base/android/jni_generator/testNativeExportsOnlyOption.golden214
-rw-r--r--base/android/jni_registrar.cc30
-rw-r--r--base/android/jni_registrar.h28
-rw-r--r--base/android/jni_utils.cc24
-rw-r--r--base/android/jni_utils.h28
-rw-r--r--base/android/jni_weak_ref.cc79
-rw-r--r--base/android/jni_weak_ref.h51
-rw-r--r--base/android/junit/src/org/chromium/base/ApplicationStatusTest.java70
-rw-r--r--base/android/junit/src/org/chromium/base/LogTest.java87
-rw-r--r--base/android/junit/src/org/chromium/base/NonThreadSafeTest.java63
-rw-r--r--base/android/junit/src/org/chromium/base/PromiseTest.java316
-rw-r--r--base/android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java356
-rw-r--r--base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java57
-rw-r--r--base/android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java332
-rw-r--r--base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java425
-rw-r--r--base/android/library_loader/library_load_from_apk_status_codes.h49
-rw-r--r--base/android/library_loader/library_loader_hooks.cc254
-rw-r--r--base/android/library_loader/library_loader_hooks.h73
-rw-r--r--base/android/library_loader/library_prefetcher.cc333
-rw-r--r--base/android/library_loader/library_prefetcher.h66
-rw-r--r--base/android/library_loader/library_prefetcher_unittest.cc46
-rw-r--r--base/android/linker/config.gni13
-rw-r--r--base/android/linker/linker_jni.cc696
-rw-r--r--base/android/locale_utils.cc27
-rw-r--r--base/android/locale_utils.h25
-rw-r--r--base/android/memory_pressure_listener_android.cc30
-rw-r--r--base/android/memory_pressure_listener_android.h29
-rw-r--r--base/android/orderfile/BUILD.gn34
-rw-r--r--base/android/path_service_android.cc23
-rw-r--r--base/android/path_utils.cc82
-rw-r--r--base/android/path_utils.h55
-rw-r--r--base/android/path_utils_unittest.cc65
-rw-r--r--base/android/proguard/chromium_apk.flags65
-rw-r--r--base/android/proguard/chromium_code.flags75
-rw-r--r--base/android/record_histogram.cc343
-rw-r--r--base/android/record_user_action.cc59
-rw-r--r--base/android/statistics_recorder_android.cc29
-rw-r--r--base/android/sys_utils.cc51
-rw-r--r--base/android/sys_utils.h24
-rw-r--r--base/android/sys_utils_unittest.cc24
-rw-r--r--base/android/throw_uncaught_exception.cc19
-rw-r--r--base/android/throw_uncaught_exception.h20
-rw-r--r--base/android/time_utils.cc20
-rw-r--r--base/android/trace_event_binding.cc155
-rw-r--r--base/android/unguessable_token_android.cc41
-rw-r--r--base/android/unguessable_token_android.h43
-rw-r--r--base/android/unguessable_token_android_unittest.cc42
-rw-r--r--base/barrier_closure_unittest.cc81
-rw-r--r--base/base_paths_android.cc66
-rw-r--r--base/base_paths_android.h25
-rw-r--r--base/bind_unittest.nc322
-rw-r--r--base/bit_cast_unittest.cc31
-rw-r--r--base/build_time.cc4
-rw-r--r--base/callback_list_unittest.nc56
-rw-r--r--base/callback_unittest.nc53
-rw-r--r--base/check_example.cc37
-rw-r--r--base/containers/adapters_unittest.cc52
-rw-r--r--base/containers/flat_map_unittest.cc369
-rw-r--r--base/containers/flat_set_unittest.cc121
-rw-r--r--base/containers/flat_tree_unittest.cc1385
-rw-r--r--base/containers/hash_tables_unittest.cc67
-rw-r--r--base/containers/id_map.h290
-rw-r--r--base/containers/id_map_unittest.cc399
-rw-r--r--base/containers/linked_list_unittest.cc349
-rw-r--r--base/containers/mru_cache_unittest.cc394
-rw-r--r--base/containers/small_map_unittest.cc603
-rw-r--r--base/containers/stack_container_unittest.cc145
-rw-r--r--base/containers/unique_ptr_adapters.h78
-rw-r--r--base/containers/unique_ptr_adapters_unittest.cc134
-rw-r--r--base/debug/activity_analyzer.cc412
-rw-r--r--base/debug/activity_analyzer.h262
-rw-r--r--base/debug/activity_analyzer_unittest.cc546
-rw-r--r--base/debug/asan_invalid_access.cc101
-rw-r--r--base/debug/asan_invalid_access.h46
-rw-r--r--base/debug/crash_logging_unittest.cc17
-rw-r--r--base/debug/proc_maps_linux_unittest.cc328
-rw-r--r--base/debug/stack_trace_unittest.cc320
-rw-r--r--base/debug/thread_heap_usage_tracker.cc340
-rw-r--r--base/debug/thread_heap_usage_tracker_unittest.cc607
-rw-r--r--base/deferred_sequenced_task_runner.cc129
-rw-r--r--base/deferred_sequenced_task_runner.h97
-rw-r--r--base/deferred_sequenced_task_runner_unittest.cc214
-rw-r--r--base/file_descriptor_store.cc73
-rw-r--r--base/file_descriptor_store.h73
-rw-r--r--base/files/file_locking_unittest.cc232
-rw-r--r--base/files/file_path_watcher_fsevents.cc282
-rw-r--r--base/files/file_path_watcher_fsevents.h99
-rw-r--r--base/files/file_path_watcher_kqueue.cc372
-rw-r--r--base/files/file_path_watcher_kqueue.h125
-rw-r--r--base/files/file_path_watcher_stub.cc41
-rw-r--r--base/files/file_proxy.cc358
-rw-r--r--base/files/file_proxy.h142
-rw-r--r--base/files/file_proxy_unittest.cc401
-rw-r--r--base/files/file_util_android.cc16
-rw-r--r--base/files/file_util_posix.cc3
-rw-r--r--base/files/file_util_unittest.cc3749
-rw-r--r--base/files/memory_mapped_file_unittest.cc243
-rw-r--r--base/hash_unittest.cc82
-rw-r--r--base/i18n/base_i18n_switches.cc21
-rw-r--r--base/i18n/base_i18n_switches.h21
-rw-r--r--base/i18n/bidi_line_iterator.cc119
-rw-r--r--base/i18n/bidi_line_iterator.h60
-rw-r--r--base/i18n/bidi_line_iterator_unittest.cc209
-rw-r--r--base/i18n/break_iterator.cc191
-rw-r--r--base/i18n/break_iterator.h182
-rw-r--r--base/i18n/break_iterator_unittest.cc584
-rw-r--r--base/i18n/build_utf8_validator_tables.cc470
-rw-r--r--base/i18n/case_conversion.cc90
-rw-r--r--base/i18n/case_conversion.h48
-rw-r--r--base/i18n/case_conversion_unittest.cc119
-rw-r--r--base/i18n/char_iterator.cc80
-rw-r--r--base/i18n/char_iterator.h134
-rw-r--r--base/i18n/char_iterator_unittest.cc101
-rw-r--r--base/i18n/character_encoding.cc42
-rw-r--r--base/i18n/character_encoding.h20
-rw-r--r--base/i18n/character_encoding_unittest.cc23
-rw-r--r--base/i18n/encoding_detection.cc40
-rw-r--r--base/i18n/encoding_detection.h21
-rw-r--r--base/i18n/file_util_icu.cc179
-rw-r--r--base/i18n/file_util_icu.h58
-rw-r--r--base/i18n/file_util_icu_unittest.cc140
-rw-r--r--base/i18n/i18n_constants.cc13
-rw-r--r--base/i18n/i18n_constants.h21
-rw-r--r--base/i18n/icu_string_conversions.cc223
-rw-r--r--base/i18n/icu_string_conversions.h57
-rw-r--r--base/i18n/icu_string_conversions_unittest.cc235
-rw-r--r--base/i18n/icu_util.cc333
-rw-r--r--base/i18n/icu_util.h67
-rw-r--r--base/i18n/message_formatter.cc142
-rw-r--r--base/i18n/message_formatter.h128
-rw-r--r--base/i18n/message_formatter_unittest.cc185
-rw-r--r--base/i18n/number_formatting.cc97
-rw-r--r--base/i18n/number_formatting.h38
-rw-r--r--base/i18n/number_formatting_unittest.cc142
-rw-r--r--base/i18n/rtl.cc496
-rw-r--r--base/i18n/rtl_unittest.cc556
-rw-r--r--base/i18n/streaming_utf8_validator.cc59
-rw-r--r--base/i18n/streaming_utf8_validator.h66
-rw-r--r--base/i18n/streaming_utf8_validator_perftest.cc240
-rw-r--r--base/i18n/streaming_utf8_validator_unittest.cc412
-rw-r--r--base/i18n/string_compare.cc29
-rw-r--r--base/i18n/string_compare.h28
-rw-r--r--base/i18n/string_search.cc81
-rw-r--r--base/i18n/string_search.h55
-rw-r--r--base/i18n/string_search_unittest.cc228
-rw-r--r--base/i18n/time_formatting.cc301
-rw-r--r--base/i18n/time_formatting.h142
-rw-r--r--base/i18n/time_formatting_unittest.cc433
-rw-r--r--base/i18n/timezone.cc34
-rw-r--r--base/i18n/timezone.h24
-rw-r--r--base/i18n/timezone_unittest.cc27
-rw-r--r--base/i18n/unicodestring.h32
-rw-r--r--base/i18n/utf8_validator_tables.cc55
-rw-r--r--base/i18n/utf8_validator_tables.h32
-rw-r--r--base/json/json_perftest.cc84
-rw-r--r--base/linux_util.cc226
-rw-r--r--base/linux_util.h44
-rw-r--r--base/logging.cc8
-rw-r--r--base/memory/discardable_memory.cc13
-rw-r--r--base/memory/discardable_memory.h78
-rw-r--r--base/memory/discardable_memory_allocator.cc29
-rw-r--r--base/memory/discardable_memory_allocator.h38
-rw-r--r--base/memory/discardable_shared_memory.cc514
-rw-r--r--base/memory/discardable_shared_memory.h187
-rw-r--r--base/memory/discardable_shared_memory_unittest.cc456
-rw-r--r--base/memory/memory_coordinator_client.cc27
-rw-r--r--base/memory/memory_coordinator_client.h79
-rw-r--r--base/memory/memory_coordinator_client_registry.cc41
-rw-r--r--base/memory/memory_coordinator_client_registry.h56
-rw-r--r--base/memory/memory_coordinator_client_registry_unittest.cc58
-rw-r--r--base/memory/memory_coordinator_proxy.cc37
-rw-r--r--base/memory/memory_coordinator_proxy.h49
-rw-r--r--base/memory/memory_pressure_listener.cc129
-rw-r--r--base/memory/memory_pressure_listener.h102
-rw-r--r--base/memory/memory_pressure_listener_unittest.cc79
-rw-r--r--base/memory/memory_pressure_monitor.cc71
-rw-r--r--base/memory/memory_pressure_monitor.h53
-rw-r--r--base/memory/memory_pressure_monitor_chromeos.cc288
-rw-r--r--base/memory/memory_pressure_monitor_chromeos.h128
-rw-r--r--base/memory/memory_pressure_monitor_chromeos_unittest.cc172
-rw-r--r--base/memory/memory_pressure_monitor_unittest.cc33
-rw-r--r--base/memory/platform_shared_memory_region_android.cc203
-rw-r--r--base/memory/platform_shared_memory_region_mac.cc233
-rw-r--r--base/memory/ptr_util_unittest.cc40
-rw-r--r--base/memory/ref_counted_unittest.nc28
-rw-r--r--base/memory/shared_memory_handle.h4
-rw-r--r--base/memory/shared_memory_posix.cc38
-rw-r--r--base/memory/shared_memory_tracker.cc147
-rw-r--r--base/memory/shared_memory_tracker.h90
-rw-r--r--base/message_loop/message_loop.cc10
-rw-r--r--base/message_loop/message_loop.h7
-rw-r--r--base/message_loop/message_loop_current.cc4
-rw-r--r--base/message_loop/message_loop_current.h4
-rw-r--r--base/message_loop/message_loop_unittest.cc6
-rw-r--r--base/message_loop/message_pump_android.cc313
-rw-r--r--base/message_loop/message_pump_android.h102
-rw-r--r--base/message_loop/message_pump_for_ui.h4
-rw-r--r--base/message_loop/message_pump_libevent_unittest.cc263
-rw-r--r--base/message_loop/message_pump_perftest.cc243
-rw-r--r--base/metrics/field_trial_params_unittest.cc458
-rw-r--r--base/metrics/histogram_functions_unittest.cc127
-rw-r--r--base/metrics/histogram_unittest.nc90
-rw-r--r--base/native_library_unittest.cc166
-rw-r--r--base/nix/mime_util_xdg.cc33
-rw-r--r--base/nix/mime_util_xdg.h36
-rw-r--r--base/nix/xdg_util.cc152
-rw-r--r--base/nix/xdg_util.h77
-rw-r--r--base/nix/xdg_util_unittest.cc181
-rw-r--r--base/numerics/BUILD.gn28
-rw-r--r--base/os_compat_android.cc178
-rw-r--r--base/os_compat_android.h21
-rw-r--r--base/os_compat_android_unittest.cc41
-rw-r--r--base/path_service_unittest.cc278
-rw-r--r--base/posix/safe_strerror.cc13
-rw-r--r--base/power_monitor/power_monitor.cc66
-rw-r--r--base/power_monitor/power_monitor_device_source.cc33
-rw-r--r--base/power_monitor/power_monitor_device_source_android.cc39
-rw-r--r--base/power_monitor/power_monitor_device_source_chromeos.cc40
-rw-r--r--base/power_monitor/power_monitor_device_source_stub.cc14
-rw-r--r--base/power_monitor/power_monitor_source.cc69
-rw-r--r--base/power_monitor/power_monitor_unittest.cc83
-rw-r--r--base/process/memory_unittest.cc533
-rw-r--r--base/process/process_linux.cc201
-rw-r--r--base/process/process_metrics_unittest.cc4
-rw-r--r--base/process/process_unittest.cc330
-rw-r--r--base/process/process_util_unittest.cc1357
-rw-r--r--base/profiler/native_stack_sampler.cc34
-rw-r--r--base/profiler/native_stack_sampler.h97
-rw-r--r--base/profiler/native_stack_sampler_posix.cc19
-rw-r--r--base/profiler/stack_sampling_profiler.cc808
-rw-r--r--base/profiler/stack_sampling_profiler.h354
-rw-r--r--base/profiler/stack_sampling_profiler_unittest.cc1522
-rw-r--r--base/profiler/test_support_library.cc30
-rw-r--r--base/profiler/win32_stack_frame_unwinder.cc186
-rw-r--r--base/profiler/win32_stack_frame_unwinder.h102
-rw-r--r--base/profiler/win32_stack_frame_unwinder_unittest.cc223
-rw-r--r--base/run_loop.h2
-rw-r--r--base/run_loop_unittest.cc636
-rw-r--r--base/safe_numerics_unittest.cc1640
-rw-r--r--base/scoped_native_library_unittest.cc48
-rw-r--r--base/sequenced_task_runner_unittest.cc104
-rw-r--r--base/strings/latin1_string_conversions.cc19
-rw-r--r--base/strings/latin1_string_conversions.h34
-rw-r--r--base/strings/utf_offset_string_conversions.cc268
-rw-r--r--base/strings/utf_offset_string_conversions.h114
-rw-r--r--base/strings/utf_offset_string_conversions_unittest.cc300
-rw-r--r--base/supports_user_data.cc51
-rw-r--r--base/supports_user_data.h87
-rw-r--r--base/supports_user_data_unittest.cc40
-rw-r--r--base/synchronization/waitable_event_watcher_unittest.cc429
-rw-r--r--base/sys_byteorder_unittest.cc142
-rw-r--r--base/sys_info_android.cc241
-rw-r--r--base/syslog_logging.cc119
-rw-r--r--base/syslog_logging.h50
-rw-r--r--base/system_monitor/system_monitor.cc51
-rw-r--r--base/system_monitor/system_monitor.h75
-rw-r--r--base/system_monitor/system_monitor_unittest.cc55
-rw-r--r--base/task_scheduler/delayed_task_manager_unittest.cc209
-rw-r--r--base/task_scheduler/initialization_util.cc22
-rw-r--r--base/task_scheduler/initialization_util.h21
-rw-r--r--base/task_scheduler/priority_queue_unittest.cc170
-rw-r--r--base/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc662
-rw-r--r--base/task_scheduler/scheduler_worker_pool_impl_unittest.cc1707
-rw-r--r--base/task_scheduler/scheduler_worker_stack_unittest.cc254
-rw-r--r--base/task_scheduler/scheduler_worker_unittest.cc897
-rw-r--r--base/task_scheduler/task_scheduler_impl_unittest.cc841
-rw-r--r--base/task_scheduler/task_tracker_posix_unittest.cc101
-rw-r--r--base/task_scheduler/task_tracker_unittest.cc1364
-rw-r--r--base/task_scheduler/task_unittest.cc60
-rw-r--r--base/task_scheduler/test_task_factory.cc106
-rw-r--r--base/task_scheduler/test_task_factory.h99
-rw-r--r--base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java46
-rw-r--r--base/test/android/java/src/org/chromium/base/ITestCallback.aidl23
-rw-r--r--base/test/android/java/src/org/chromium/base/ITestController.aidl25
-rw-r--r--base/test/android/java/src/org/chromium/base/JavaHandlerThreadHelpers.java65
-rw-r--r--base/test/android/java/src/org/chromium/base/MainReturnCodeResult.java40
-rw-r--r--base/test/android/java/src/org/chromium/base/MultiprocessTestClientLauncher.java383
-rw-r--r--base/test/android/java/src/org/chromium/base/MultiprocessTestClientService.java14
-rw-r--r--base/test/android/java/src/org/chromium/base/MultiprocessTestClientService0.java10
-rw-r--r--base/test/android/java/src/org/chromium/base/MultiprocessTestClientService1.java10
-rw-r--r--base/test/android/java/src/org/chromium/base/MultiprocessTestClientService2.java10
-rw-r--r--base/test/android/java/src/org/chromium/base/MultiprocessTestClientService3.java10
-rw-r--r--base/test/android/java/src/org/chromium/base/MultiprocessTestClientService4.java10
-rw-r--r--base/test/android/java/src/org/chromium/base/MultiprocessTestClientServiceDelegate.java94
-rw-r--r--base/test/android/java/src/org/chromium/base/TestUiThread.java51
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java291
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java162
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java277
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java137
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/ScreenshotOnFailureStatement.java83
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/SetUpStatement.java35
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/SetUpTestRule.java35
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/TestChildProcessConnection.java87
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/TestListInstrumentationRunListener.java142
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/TestTraceEvent.java168
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/BaseJUnit4RunnerDelegate.java42
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/BlockJUnit4RunnerDelegate.java42
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/MethodParamAnnotationRule.java62
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/MethodParamRule.java35
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/ParameterAnnotations.java78
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/ParameterSet.java129
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedFrameworkMethod.java94
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java221
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegate.java36
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegateCommon.java69
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegateFactory.java115
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java118
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java252
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java188
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java49
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java84
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java22
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java24
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/Feature.java29
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/FlakyTest.java22
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java238
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java32
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java26
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/Manual.java21
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/Matchers.java44
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java43
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java19
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java43
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java37
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java78
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/RetryOnFailure.java25
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java29
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java49
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java85
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java143
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java22
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java84
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java51
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/parameter/CommandLineParameter.java32
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/parameter/SkipCommandLineParameterization.java23
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java49
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/SetUpStatementTest.java64
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/TestListInstrumentationRunListenerTest.java119
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/asynctask/BackgroundShadowAsyncTask.java72
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/asynctask/CustomShadowAsyncTask.java32
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/params/ExampleParameterizedTest.java105
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerDelegateCommonTest.java77
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerDelegateFactoryTest.java133
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerTest.java108
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/params/ParameterizedTestNameTest.java201
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/util/DisableIfTest.java193
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheckTest.java95
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/util/RestrictionSkipCheckTest.java129
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/util/SkipCheckTest.java130
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/util/TestRunnerTestRule.java132
-rw-r--r--base/test/fuzzed_data_provider.cc98
-rw-r--r--base/test/fuzzed_data_provider.h80
-rw-r--r--base/test/gtest_xml_unittest_result_printer.cc162
-rw-r--r--base/test/gtest_xml_unittest_result_printer.h55
-rw-r--r--base/test/gtest_xml_util.cc234
-rw-r--r--base/test/gtest_xml_util.h27
-rw-r--r--base/test/icu_test_util.cc39
-rw-r--r--base/test/icu_test_util.h35
-rw-r--r--base/test/launcher/test_launcher.cc1345
-rw-r--r--base/test/launcher/test_launcher.h268
-rw-r--r--base/test/launcher/test_launcher_tracer.cc53
-rw-r--r--base/test/launcher/test_launcher_tracer.h55
-rw-r--r--base/test/launcher/test_result.cc96
-rw-r--r--base/test/launcher/test_result.h104
-rw-r--r--base/test/launcher/test_results_tracker.cc541
-rw-r--r--base/test/launcher/test_results_tracker.h149
-rw-r--r--base/test/launcher/unit_test_launcher.cc750
-rw-r--r--base/test/launcher/unit_test_launcher.h134
-rw-r--r--base/test/malloc_wrapper.cc11
-rw-r--r--base/test/malloc_wrapper.h21
-rw-r--r--base/test/metrics/histogram_tester_unittest.cc157
-rw-r--r--base/test/metrics/user_action_tester.cc38
-rw-r--r--base/test/metrics/user_action_tester.h50
-rw-r--r--base/test/metrics/user_action_tester_unittest.cc86
-rw-r--r--base/test/mock_callback.h366
-rw-r--r--base/test/mock_callback.h.pump85
-rw-r--r--base/test/mock_callback_unittest.cc59
-rw-r--r--base/test/mock_devices_changed_observer.cc13
-rw-r--r--base/test/mock_devices_changed_observer.h31
-rw-r--r--base/test/mock_log.cc68
-rw-r--r--base/test/mock_log.h100
-rw-r--r--base/test/multiprocess_test.cc4
-rw-r--r--base/test/native_library_test_utils.cc19
-rw-r--r--base/test/native_library_test_utils.h26
-rw-r--r--base/test/null_task_runner.cc29
-rw-r--r--base/test/null_task_runner.h39
-rw-r--r--base/test/perf_log.cc45
-rw-r--r--base/test/perf_log.h24
-rw-r--r--base/test/perf_test_suite.cc50
-rw-r--r--base/test/perf_test_suite.h22
-rw-r--r--base/test/perf_time_logger.cc27
-rw-r--r--base/test/perf_time_logger.h37
-rw-r--r--base/test/power_monitor_test_base.cc68
-rw-r--r--base/test/power_monitor_test_base.h54
-rw-r--r--base/test/run_all_base_unittests.cc15
-rw-r--r--base/test/run_all_perftests.cc9
-rw-r--r--base/test/run_all_unittests.cc15
-rw-r--r--base/test/scoped_command_line.cc22
-rw-r--r--base/test/scoped_command_line.h34
-rw-r--r--base/test/scoped_mock_time_message_loop_task_runner.cc38
-rw-r--r--base/test/scoped_mock_time_message_loop_task_runner.h45
-rw-r--r--base/test/scoped_mock_time_message_loop_task_runner_unittest.cc120
-rw-r--r--base/test/scoped_path_override.cc40
-rw-r--r--base/test/scoped_path_override.h43
-rw-r--r--base/test/sequenced_task_runner_test_template.cc269
-rw-r--r--base/test/sequenced_task_runner_test_template.h350
-rw-r--r--base/test/task_runner_test_template.cc47
-rw-r--r--base/test/task_runner_test_template.h230
-rw-r--r--base/test/test_discardable_memory_allocator.cc61
-rw-r--r--base/test/test_discardable_memory_allocator.h32
-rw-r--r--base/test/test_file_util_android.cc26
-rw-r--r--base/test/test_message_loop.cc18
-rw-r--r--base/test/test_message_loop.h34
-rw-r--r--base/test/test_pending_task_unittest.cc57
-rw-r--r--base/test/test_shared_library.cc30
-rw-r--r--base/test/test_suite.cc486
-rw-r--r--base/test/test_suite.h103
-rw-r--r--base/test/test_support_android.cc225
-rw-r--r--base/test/test_support_android.h25
-rw-r--r--base/test/test_ui_thread_android.cc14
-rw-r--r--base/test/test_ui_thread_android.h20
-rw-r--r--base/test/thread_test_helper.cc41
-rw-r--r--base/test/thread_test_helper.h50
-rw-r--r--base/test/trace_event_analyzer.cc1069
-rw-r--r--base/test/trace_event_analyzer.h842
-rw-r--r--base/test/trace_event_analyzer_unittest.cc961
-rw-r--r--base/test/trace_to_file.cc106
-rw-r--r--base/test/trace_to_file.h35
-rw-r--r--base/test/values_test_util.cc76
-rw-r--r--base/test/values_test_util.h53
-rw-r--r--base/third_party/dynamic_annotations/LICENSE28
-rw-r--r--base/third_party/dynamic_annotations/README.chromium23
-rw-r--r--base/third_party/valgrind/LICENSE39
-rw-r--r--base/third_party/valgrind/README.chromium11
-rw-r--r--base/threading/platform_thread_android.cc96
-rw-r--r--base/threading/post_task_and_reply_impl_unittest.cc198
-rw-r--r--base/threading/sequenced_task_runner_handle_unittest.cc90
-rw-r--r--base/threading/thread_perftest.cc321
-rw-r--r--base/threading/thread_task_runner_handle_unittest.cc122
-rw-r--r--base/threading/watchdog.cc183
-rw-r--r--base/threading/watchdog.h96
-rw-r--r--base/threading/watchdog_unittest.cc141
-rw-r--r--base/time/time.h6
-rw-r--r--base/tools_sanity_unittest.cc423
-rw-r--r--base/trace_event/auto_open_close_event.cc52
-rw-r--r--base/trace_event/auto_open_close_event.h57
-rw-r--r--base/trace_event/blame_context.cc111
-rw-r--r--base/trace_event/blame_context.h138
-rw-r--r--base/trace_event/blame_context_unittest.cc175
-rw-r--r--base/trace_event/category_registry.cc156
-rw-r--r--base/trace_event/category_registry.h93
-rw-r--r--base/trace_event/cfi_backtrace_android.cc314
-rw-r--r--base/trace_event/cfi_backtrace_android.h157
-rw-r--r--base/trace_event/cfi_backtrace_android_unittest.cc197
-rw-r--r--base/trace_event/event_name_filter.cc26
-rw-r--r--base/trace_event/event_name_filter.h46
-rw-r--r--base/trace_event/event_name_filter_unittest.cc42
-rw-r--r--base/trace_event/heap_profiler_allocation_context.cc88
-rw-r--r--base/trace_event/heap_profiler_allocation_context.h132
-rw-r--r--base/trace_event/heap_profiler_allocation_context_tracker.cc274
-rw-r--r--base/trace_event/heap_profiler_allocation_context_tracker.h140
-rw-r--r--base/trace_event/heap_profiler_allocation_context_tracker_unittest.cc350
-rw-r--r--base/trace_event/heap_profiler_event_filter.cc70
-rw-r--r--base/trace_event/heap_profiler_event_filter.h40
-rw-r--r--base/trace_event/java_heap_dump_provider_android.cc47
-rw-r--r--base/trace_event/java_heap_dump_provider_android.h36
-rw-r--r--base/trace_event/java_heap_dump_provider_android_unittest.cc22
-rw-r--r--base/trace_event/malloc_dump_provider.cc189
-rw-r--r--base/trace_event/malloc_dump_provider.h56
-rw-r--r--base/trace_event/memory_allocator_dump.cc148
-rw-r--r--base/trace_event/memory_allocator_dump.h153
-rw-r--r--base/trace_event/memory_allocator_dump_guid.cc40
-rw-r--r--base/trace_event/memory_allocator_dump_guid.h55
-rw-r--r--base/trace_event/memory_allocator_dump_unittest.cc177
-rw-r--r--base/trace_event/memory_dump_manager.cc545
-rw-r--r--base/trace_event/memory_dump_manager.h267
-rw-r--r--base/trace_event/memory_dump_manager_test_utils.h38
-rw-r--r--base/trace_event/memory_dump_manager_unittest.cc840
-rw-r--r--base/trace_event/memory_dump_provider.h52
-rw-r--r--base/trace_event/memory_dump_provider_info.cc43
-rw-r--r--base/trace_event/memory_dump_provider_info.h108
-rw-r--r--base/trace_event/memory_dump_request_args.cc64
-rw-r--r--base/trace_event/memory_dump_request_args.h101
-rw-r--r--base/trace_event/memory_dump_scheduler.cc118
-rw-r--r--base/trace_event/memory_dump_scheduler.h76
-rw-r--r--base/trace_event/memory_dump_scheduler_unittest.cc200
-rw-r--r--base/trace_event/memory_infra_background_whitelist.cc392
-rw-r--r--base/trace_event/memory_infra_background_whitelist.h33
-rw-r--r--base/trace_event/memory_infra_background_whitelist_unittest.cc37
-rw-r--r--base/trace_event/memory_usage_estimator.cc14
-rw-r--r--base/trace_event/memory_usage_estimator.h654
-rw-r--r--base/trace_event/memory_usage_estimator_unittest.cc265
-rw-r--r--base/trace_event/process_memory_dump.cc511
-rw-r--r--base/trace_event/process_memory_dump.h284
-rw-r--r--base/trace_event/process_memory_dump_unittest.cc565
-rw-r--r--base/trace_event/trace_buffer.cc347
-rw-r--r--base/trace_event/trace_buffer.h130
-rw-r--r--base/trace_event/trace_category.h109
-rw-r--r--base/trace_event/trace_category_unittest.cc155
-rw-r--r--base/trace_event/trace_config.cc618
-rw-r--r--base/trace_event/trace_config.h321
-rw-r--r--base/trace_event/trace_config_category_filter.cc235
-rw-r--r--base/trace_event/trace_config_category_filter.h81
-rw-r--r--base/trace_event/trace_config_memory_test_util.h152
-rw-r--r--base/trace_event/trace_config_unittest.cc663
-rw-r--r--base/trace_event/trace_event.h6
-rw-r--r--base/trace_event/trace_event_android.cc216
-rw-r--r--base/trace_event/trace_event_android_unittest.cc22
-rw-r--r--base/trace_event/trace_event_argument.cc576
-rw-r--r--base/trace_event/trace_event_argument.h92
-rw-r--r--base/trace_event/trace_event_argument_unittest.cc165
-rw-r--r--base/trace_event/trace_event_filter.cc17
-rw-r--r--base/trace_event/trace_event_filter.h51
-rw-r--r--base/trace_event/trace_event_filter_test_utils.cc61
-rw-r--r--base/trace_event/trace_event_filter_test_utils.h53
-rw-r--r--base/trace_event/trace_event_impl.cc489
-rw-r--r--base/trace_event/trace_event_impl.h191
-rw-r--r--base/trace_event/trace_event_memory_overhead.cc179
-rw-r--r--base/trace_event/trace_event_memory_overhead.h95
-rw-r--r--base/trace_event/trace_event_system_stats_monitor.cc132
-rw-r--r--base/trace_event/trace_event_system_stats_monitor.h76
-rw-r--r--base/trace_event/trace_event_system_stats_monitor_unittest.cc68
-rw-r--r--base/trace_event/trace_event_unittest.cc3169
-rw-r--r--base/trace_event/trace_log.cc1787
-rw-r--r--base/trace_event/trace_log.h528
-rw-r--r--base/trace_event/trace_log_constants.cc26
-rw-r--r--base/trace_event/tracing_agent.cc24
-rw-r--r--base/trace_event/tracing_agent.h96
-rw-r--r--base/unguessable_token_unittest.cc155
-rw-r--r--base/value_conversions.cc99
-rw-r--r--base/value_conversions.h35
-rwxr-xr-xbuild/android/gyp/util/build_utils_test.py44
-rw-r--r--build/android/gyp/util/jar_info_utils.py52
-rwxr-xr-xbuild/android/gyp/util/md5_check_test.py144
-rw-r--r--build/android/gyp/util/proguard_util.py212
-rw-r--r--build/android/gyp/util/resource_utils.py511
-rwxr-xr-xbuild/android/pylib/constants/host_paths_unittest.py50
-rw-r--r--build/android/pylib/content_settings.py80
-rw-r--r--build/android/pylib/device_settings.py199
-rw-r--r--build/android/pylib/pexpect.py21
-rwxr-xr-xbuild/android/pylib/restart_adbd.sh20
-rw-r--r--build/android/pylib/valgrind_tools.py130
-rwxr-xr-xbuild/apply_locales.py45
-rw-r--r--build/build_config.h13
-rwxr-xr-xbuild/check_gn_headers.py304
-rwxr-xr-xbuild/check_gn_headers_unittest.py101
-rwxr-xr-xbuild/check_return_value.py17
-rwxr-xr-xbuild/clobber.py132
-rwxr-xr-xbuild/cp.py23
-rwxr-xr-xbuild/detect_host_arch.py51
-rwxr-xr-xbuild/dir_exists.py23
-rwxr-xr-xbuild/download_translation_unit_tool.py54
-rwxr-xr-xbuild/env_dump.py56
-rwxr-xr-xbuild/extract_from_cab.py63
-rwxr-xr-xbuild/find_depot_tools.py73
-rwxr-xr-xbuild/find_isolated_tests.py78
-rwxr-xr-xbuild/fix_gn_headers.py218
-rwxr-xr-xbuild/get_landmines.py82
-rwxr-xr-xbuild/get_syzygy_binaries.py529
-rw-r--r--build/gn_helpers_unittest.py117
-rw-r--r--build/gn_run_binary.py31
-rw-r--r--build/gyp_chromium.py68
-rw-r--r--build/gyp_environment.py30
-rw-r--r--build/gyp_helper.py68
-rw-r--r--build/gypi_to_gn.py192
-rw-r--r--build/landmine_utils.py33
-rwxr-xr-xbuild/landmines.py145
-rw-r--r--build/precompile.h53
-rwxr-xr-xbuild/print_python_deps.py113
-rwxr-xr-xbuild/protoc_java.py85
-rw-r--r--build/redirect_stdout.py19
-rwxr-xr-xbuild/rm.py38
-rwxr-xr-xbuild/run_swarming_xcode_install.py81
-rwxr-xr-xbuild/swarming_xcode_install.py64
-rwxr-xr-xbuild/symlink.py60
-rwxr-xr-xbuild/vs_toolchain.py471
-rwxr-xr-xbuild/write_build_date_header.py118
-rwxr-xr-xbuild/write_buildflag_header.py95
-rw-r--r--components/json_schema/README6
-rw-r--r--components/json_schema/json_schema_validator_unittest.cc177
-rw-r--r--components/json_schema/json_schema_validator_unittest_base.cc710
-rw-r--r--components/json_schema/json_schema_validator_unittest_base.h56
-rw-r--r--components/policy/core/common/async_policy_loader.cc137
-rw-r--r--components/policy/core/common/async_policy_loader.h126
-rw-r--r--components/policy/core/common/async_policy_provider.cc131
-rw-r--r--components/policy/core/common/async_policy_provider.h80
-rw-r--r--components/policy/core/common/async_policy_provider_unittest.cc227
-rw-r--r--components/policy/core/common/config_dir_policy_loader.cc240
-rw-r--r--components/policy/core/common/config_dir_policy_loader.h72
-rw-r--r--components/policy/core/common/config_dir_policy_loader_unittest.cc244
-rw-r--r--components/policy/core/common/configuration_policy_provider.cc75
-rw-r--r--components/policy/core/common/configuration_policy_provider.h105
-rw-r--r--components/policy/core/common/configuration_policy_provider_test.cc420
-rw-r--r--components/policy/core/common/configuration_policy_provider_test.h158
-rw-r--r--components/policy/core/common/external_data_fetcher.cc45
-rw-r--r--components/policy/core/common/external_data_fetcher.h53
-rw-r--r--components/policy/core/common/external_data_manager.h36
-rw-r--r--components/policy/core/common/fake_async_policy_loader.cc37
-rw-r--r--components/policy/core/common/fake_async_policy_loader.h54
-rw-r--r--components/policy/core/common/generate_policy_source_unittest.cc227
-rw-r--r--components/policy/core/common/mock_configuration_policy_provider.cc48
-rw-r--r--components/policy/core/common/mock_configuration_policy_provider.h66
-rw-r--r--components/policy/core/common/mock_policy_service.cc21
-rw-r--r--components/policy/core/common/mock_policy_service.h39
-rw-r--r--components/policy/core/common/plist_writer.cc91
-rw-r--r--components/policy/core/common/plist_writer.h25
-rw-r--r--components/policy/core/common/plist_writer_unittest.cc135
-rw-r--r--components/policy/core/common/policy_bundle.cc104
-rw-r--r--components/policy/core/common/policy_bundle.h74
-rw-r--r--components/policy/core/common/policy_bundle_unittest.cc263
-rw-r--r--components/policy/core/common/policy_details.h48
-rw-r--r--components/policy/core/common/policy_map.cc234
-rw-r--r--components/policy/core/common/policy_map.h157
-rw-r--r--components/policy/core/common/policy_map_unittest.cc354
-rw-r--r--components/policy/core/common/policy_namespace.cc43
-rw-r--r--components/policy/core/common/policy_namespace.h66
-rw-r--r--components/policy/core/common/policy_pref_names.cc30
-rw-r--r--components/policy/core/common/policy_pref_names.h21
-rw-r--r--components/policy/core/common/policy_proto_decoders.cc189
-rw-r--r--components/policy/core/common/policy_proto_decoders.h34
-rw-r--r--components/policy/core/common/policy_scheduler.cc73
-rw-r--r--components/policy/core/common/policy_scheduler.h98
-rw-r--r--components/policy/core/common/policy_scheduler_unittest.cc133
-rw-r--r--components/policy/core/common/policy_service.cc44
-rw-r--r--components/policy/core/common/policy_service.h116
-rw-r--r--components/policy/core/common/policy_service_impl.cc278
-rw-r--r--components/policy/core/common/policy_service_impl.h103
-rw-r--r--components/policy/core/common/policy_service_impl_unittest.cc706
-rw-r--r--components/policy/core/common/policy_service_stub.cc34
-rw-r--r--components/policy/core/common/policy_service_stub.h42
-rw-r--r--components/policy/core/common/policy_statistics_collector.cc94
-rw-r--r--components/policy/core/common/policy_statistics_collector.h69
-rw-r--r--components/policy/core/common/policy_statistics_collector_unittest.cc189
-rw-r--r--components/policy/core/common/policy_switches.cc22
-rw-r--r--components/policy/core/common/policy_switches.h22
-rw-r--r--components/policy/core/common/policy_test_utils.cc209
-rw-r--r--components/policy/core/common/policy_test_utils.h69
-rw-r--r--components/policy/core/common/preg_parser.cc410
-rw-r--r--components/policy/core/common/preg_parser.h56
-rw-r--r--components/policy/core/common/preg_parser_fuzzer.cc45
-rw-r--r--components/policy/core/common/preg_parser_unittest.cc194
-rw-r--r--components/policy/core/common/proxy_policy_provider.cc65
-rw-r--r--components/policy/core/common/proxy_policy_provider.h64
-rw-r--r--components/policy/core/common/proxy_policy_provider_unittest.cc102
-rw-r--r--components/policy/core/common/schema_map.cc118
-rw-r--r--components/policy/core/common/schema_map.h68
-rw-r--r--components/policy/core/common/schema_map_unittest.cc308
-rw-r--r--components/policy/core/common/schema_registry.cc278
-rw-r--r--components/policy/core/common/schema_registry.h170
-rw-r--r--components/policy/core/common/schema_registry_tracking_policy_provider.cc99
-rw-r--r--components/policy/core/common/schema_registry_tracking_policy_provider.h92
-rw-r--r--components/policy/core/common/schema_registry_tracking_policy_provider_unittest.cc244
-rw-r--r--components/policy/core/common/schema_registry_unittest.cc329
-rw-r--r--components/policy/core/common/schema_unittest.cc1286
-rw-r--r--components/timers/alarm_timer_unittest.cc372
-rw-r--r--dbus/bus_unittest.cc420
-rw-r--r--dbus/dbus_statistics_unittest.cc178
-rw-r--r--dbus/end_to_end_async_unittest.cc645
-rw-r--r--dbus/end_to_end_sync_unittest.cc126
-rw-r--r--dbus/message_unittest.cc727
-rw-r--r--dbus/mock_unittest.cc215
-rw-r--r--dbus/object_manager_unittest.cc419
-rw-r--r--dbus/object_proxy_unittest.cc147
-rw-r--r--dbus/property_unittest.cc545
-rw-r--r--dbus/signal_sender_verification_unittest.cc394
-rw-r--r--dbus/string_util_unittest.cc31
-rw-r--r--dbus/util_unittest.cc16
-rw-r--r--dbus/values_util_unittest.cc687
-rw-r--r--ipc/constants.mojom8
-rw-r--r--ipc/ipc_channel_nacl.cc396
-rw-r--r--ipc/ipc_channel_nacl.h114
-rw-r--r--ipc/ipc_test.mojom54
-rwxr-xr-xlibchrome_tools/files_not_built16
-rwxr-xr-x[-rw-r--r--]libchrome_tools/mojom_generate_type_mappings.py17
-rw-r--r--libchrome_tools/patches/0001-Fix-pending_broker_clients-handling.patch44
-rw-r--r--libchrome_tools/patches/Add-base-NoDestructor-T.patch129
-rw-r--r--libchrome_tools/patches/Add-header-files-base-check_op-notreached-h.patch48
-rw-r--r--libchrome_tools/patches/Connect-to-NameOwnerChanged-signal-when-setting-call.patch163
-rw-r--r--libchrome_tools/patches/Fix-TimeDelta.patch164
-rw-r--r--libchrome_tools/patches/Fix-Wdefaulted-function-deleted-warning-in-MessageLo.patch38
-rw-r--r--libchrome_tools/patches/Mojo-Check-if-dispatcher-is-null-in-Core-UnwrapPlatf.patch38
-rw-r--r--libchrome_tools/patches/Refactor-AlarmTimer-to-report-error-to-the-caller.patch180
-rw-r--r--libchrome_tools/patches/WaitForServiceToBeAvailable.patch45
-rw-r--r--libchrome_tools/patches/components-timers-fix-fd-leak-in-AlarmTimer.patch102
-rw-r--r--libchrome_tools/patches/dbus-Add-TryRegisterFallback.patch137
-rw-r--r--libchrome_tools/patches/dbus-Make-Bus-is_connected-mockable.patch93
-rw-r--r--libchrome_tools/patches/dbus-Remove-LOG-ERROR-in-ObjectProxy.patch58
-rw-r--r--libchrome_tools/patches/dbus-Support-UnexportMethod-from-an-exported-object.patch194
-rw-r--r--libchrome_tools/patches/enable-location-source.patch26
-rw-r--r--libchrome_tools/patches/fix-fd-watcher-leak.patch188
-rw-r--r--libchrome_tools/patches/libchrome-Add-EmptyResponseCallback-for-backward-com.patch42
-rw-r--r--libchrome_tools/patches/libchrome-Introduce-stub-ConvertableToTraceFormat.patch55
-rw-r--r--libchrome_tools/patches/libchrome-Remove-glib-dependency.patch31
-rw-r--r--libchrome_tools/patches/libchrome-Unpatch-sys.path-update.patch41
-rw-r--r--libchrome_tools/patches/libchrome-Update-crypto.patch182
-rw-r--r--libchrome_tools/patches/libchrome-fix-integer-overflow-if-microseconds-is-IN.patch27
-rw-r--r--libchrome_tools/patches/patches75
-rw-r--r--libchrome_tools/patches/r680000-forward-compatibility-patch-part-1.patch222
-rw-r--r--libchrome_tools/patches/r680000-forward-compatibility-patch-part-2.patch94
-rwxr-xr-xlibchrome_tools/uprev/copy_new_files.py74
-rwxr-xr-xlibchrome_tools/uprev/dirty_uprev.py69
-rw-r--r--libchrome_tools/uprev/filtered_utils.py108
-rw-r--r--libchrome_tools/uprev/filters.py129
-rwxr-xr-xlibchrome_tools/uprev/generate_filtered_tree.py257
-rw-r--r--libchrome_tools/uprev/lazytree.py169
-rwxr-xr-xlibchrome_tools/uprev/reconnect_history.py333
-rw-r--r--libchrome_tools/uprev/utils.py261
-rw-r--r--mojo/BUILD.gn69
-rw-r--r--mojo/PRESUBMIT.py44
-rw-r--r--mojo/core/BUILD.gn325
-rw-r--r--mojo/core/broker_win.cc162
-rw-r--r--mojo/core/channel_fuchsia.cc466
-rw-r--r--mojo/core/channel_win.cc377
-rw-r--r--mojo/core/embedder/BUILD.gn29
-rw-r--r--mojo/core/node_controller.cc7
-rw-r--r--mojo/core/ports/BUILD.gn59
-rw-r--r--mojo/core/test/BUILD.gn88
-rw-r--r--mojo/core/test/test_utils_win.cc49
-rw-r--r--mojo/public/BUILD.gn33
-rw-r--r--mojo/public/c/system/BUILD.gn38
-rw-r--r--mojo/public/c/system/tests/BUILD.gn38
-rw-r--r--mojo/public/c/test_support/BUILD.gn15
-rw-r--r--mojo/public/cpp/base/BUILD.gn85
-rw-r--r--mojo/public/cpp/base/logfont_win.typemap18
-rw-r--r--mojo/public/cpp/base/logfont_win_mojom_traits.cc39
-rw-r--r--mojo/public/cpp/base/logfont_win_mojom_traits.h30
-rw-r--r--mojo/public/cpp/bindings/BUILD.gn228
-rw-r--r--mojo/public/cpp/bindings/tests/BUILD.gn156
-rw-r--r--mojo/public/cpp/platform/BUILD.gn50
-rw-r--r--mojo/public/cpp/platform/named_platform_channel_fuchsia.cc26
-rw-r--r--mojo/public/cpp/platform/named_platform_channel_win.cc110
-rw-r--r--mojo/public/cpp/platform/tests/BUILD.gn19
-rw-r--r--mojo/public/cpp/system/BUILD.gn75
-rw-r--r--mojo/public/cpp/system/tests/BUILD.gn35
-rw-r--r--mojo/public/cpp/test_support/BUILD.gn20
-rw-r--r--mojo/public/interfaces/BUILD.gn9
-rw-r--r--mojo/public/interfaces/bindings/BUILD.gn24
-rw-r--r--mojo/public/interfaces/bindings/tests/BUILD.gn266
-rw-r--r--mojo/public/java/BUILD.gn79
-rw-r--r--mojo/public/java/system/BUILD.gn177
-rw-r--r--mojo/public/js/BUILD.gn72
-rw-r--r--mojo/public/mojom/base/BUILD.gn45
-rw-r--r--mojo/public/tools/bindings/BUILD.gn86
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl4
-rw-r--r--mojo/public/tools/fuzzers/BUILD.gn67
-rw-r--r--soong/Android.bp9
-rw-r--r--testing/empty_main.cc8
-rw-r--r--testing/gmock_mutant.h130
-rw-r--r--third_party/ashmem/ashmem.h15
-rw-r--r--ui/gfx/geometry/angle_conversions.h29
-rw-r--r--ui/gfx/geometry/axis_transform2d.cc16
-rw-r--r--ui/gfx/geometry/axis_transform2d.h138
-rw-r--r--ui/gfx/geometry/axis_transform2d_unittest.cc91
-rw-r--r--ui/gfx/geometry/box_f.cc70
-rw-r--r--ui/gfx/geometry/box_f.h160
-rw-r--r--ui/gfx/geometry/box_unittest.cc175
-rw-r--r--ui/gfx/geometry/cubic_bezier.cc213
-rw-r--r--ui/gfx/geometry/cubic_bezier.h96
-rw-r--r--ui/gfx/geometry/cubic_bezier_unittest.cc230
-rw-r--r--ui/gfx/geometry/dip_util.cc88
-rw-r--r--ui/gfx/geometry/dip_util.h41
-rw-r--r--ui/gfx/geometry/insets_unittest.cc139
-rw-r--r--ui/gfx/geometry/matrix3_f.cc291
-rw-r--r--ui/gfx/geometry/matrix3_f.h139
-rw-r--r--ui/gfx/geometry/matrix3_unittest.cc182
-rw-r--r--ui/gfx/geometry/mojo/BUILD.gn51
-rw-r--r--ui/gfx/geometry/point3_f.cc40
-rw-r--r--ui/gfx/geometry/point3_f.h128
-rw-r--r--ui/gfx/geometry/point3_unittest.cc71
-rw-r--r--ui/gfx/geometry/point_unittest.cc236
-rw-r--r--ui/gfx/geometry/quad_f.cc134
-rw-r--r--ui/gfx/geometry/quad_f.h130
-rw-r--r--ui/gfx/geometry/quad_unittest.cc361
-rw-r--r--ui/gfx/geometry/quaternion.cc106
-rw-r--r--ui/gfx/geometry/quaternion.h93
-rw-r--r--ui/gfx/geometry/quaternion_unittest.cc169
-rw-r--r--ui/gfx/geometry/rect_conversions.cc82
-rw-r--r--ui/gfx/geometry/rect_conversions.h36
-rw-r--r--ui/gfx/geometry/rect_unittest.cc1141
-rw-r--r--ui/gfx/geometry/safe_integer_conversions_unittest.cc80
-rw-r--r--ui/gfx/geometry/scroll_offset_unittest.cc124
-rw-r--r--ui/gfx/geometry/size_unittest.cc251
-rw-r--r--ui/gfx/geometry/test/rect_test_util.cc23
-rw-r--r--ui/gfx/geometry/test/rect_test_util.h21
-rw-r--r--ui/gfx/geometry/vector2d_conversions.cc30
-rw-r--r--ui/gfx/geometry/vector2d_conversions.h24
-rw-r--r--ui/gfx/geometry/vector2d_unittest.cc293
-rw-r--r--ui/gfx/geometry/vector3d_f.cc108
-rw-r--r--ui/gfx/geometry/vector3d_f.h151
-rw-r--r--ui/gfx/geometry/vector3d_unittest.cc347
-rw-r--r--ui/gfx/range/BUILD.gn35
-rw-r--r--ui/gfx/range/mojo/BUILD.gn49
900 files changed, 139329 insertions, 4746 deletions
diff --git a/Android.bp b/Android.bp
index 8104e8829b..6bd807c029 100644
--- a/Android.bp
+++ b/Android.bp
@@ -20,19 +20,485 @@
// for details.
// Note: gensrcs does not support exclude_srcs, so filegroup rule is
// introduced.
+package {
+ default_applicable_licenses: ["external_libchrome_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+ name: "external_libchrome_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-BSD",
+ "SPDX-license-identifier-GPL-2.0",
+ "SPDX-license-identifier-LGPL-2.1",
+ "SPDX-license-identifier-MIT",
+ "SPDX-license-identifier-MPL",
+ "SPDX-license-identifier-Unicode-DFS",
+ "legacy_unencumbered",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
filegroup {
name: "libchrome-include-sources",
srcs: [
- "base/**/*.h",
- "build/**/*.h",
- "components/**/*.h",
- "device/**/*.h",
- "testing/**/*.h",
- "third_party/**/*.h",
- "ui/**/*.h",
- ],
- exclude_srcs: [
- "base/android/**/*",
+ "base/allocator/allocator_extension.h",
+ "base/allocator/allocator_shim.h",
+ "base/allocator/allocator_shim_internals.h",
+ "base/allocator/allocator_shim_override_cpp_symbols.h",
+ "base/allocator/allocator_shim_override_libc_symbols.h",
+ "base/allocator/allocator_shim_override_linker_wrapped_symbols.h",
+ "base/allocator/buildflags.h",
+ "base/android/android_hardware_buffer_compat.h",
+ "base/android/build_info.h",
+ "base/android/content_uri_utils.h",
+ "base/android/java_exception_reporter.h",
+ "base/android/jni_android.h",
+ "base/android/jni_array.h",
+ "base/android/jni_string.h",
+ "base/android/path_utils.h",
+ "base/android/scoped_hardware_buffer_handle.h",
+ "base/android/scoped_java_ref.h",
+ "base/android/sys_utils.h",
+ "base/at_exit.h",
+ "base/atomic_ref_count.h",
+ "base/atomic_sequence_num.h",
+ "base/atomicops.h",
+ "base/atomicops_internals_atomicword_compat.h",
+ "base/atomicops_internals_portable.h",
+ "base/atomicops_internals_x86_msvc.h",
+ "base/auto_reset.h",
+ "base/barrier_closure.h",
+ "base/base64.h",
+ "base/base64url.h",
+ "base/base_export.h",
+ "base/base_paths.h",
+ "base/base_paths_android.h",
+ "base/base_paths_posix.h",
+ "base/base_switches.h",
+ "base/big_endian.h",
+ "base/bind.h",
+ "base/bind_helpers.h",
+ "base/bind_internal.h",
+ "base/bit_cast.h",
+ "base/bits.h",
+ "base/build_time.h",
+ "base/callback.h",
+ "base/callback_forward.h",
+ "base/callback_helpers.h",
+ "base/callback_internal.h",
+ "base/callback_list.h",
+ "base/cancelable_callback.h",
+ "base/cfi_buildflags.h",
+ "base/command_line.h",
+ "base/compiler_specific.h",
+ "base/component_export.h",
+ "base/containers/adapters.h",
+ "base/containers/circular_deque.h",
+ "base/containers/flat_map.h",
+ "base/containers/flat_set.h",
+ "base/containers/flat_tree.h",
+ "base/containers/hash_tables.h",
+ "base/containers/linked_list.h",
+ "base/containers/mru_cache.h",
+ "base/containers/queue.h",
+ "base/containers/ring_buffer.h",
+ "base/containers/small_map.h",
+ "base/containers/span.h",
+ "base/containers/stack.h",
+ "base/containers/stack_container.h",
+ "base/containers/vector_buffer.h",
+ "base/cpu.h",
+ "base/critical_closure.h",
+ "base/debug/activity_tracker.h",
+ "base/debug/alias.h",
+ "base/debug/crash_logging.h",
+ "base/debug/debugger.h",
+ "base/debug/debugging_buildflags.h",
+ "base/debug/dump_without_crashing.h",
+ "base/debug/elf_reader_linux.h",
+ "base/debug/leak_annotations.h",
+ "base/debug/leak_tracker.h",
+ "base/debug/proc_maps_linux.h",
+ "base/debug/profiler.h",
+ "base/debug/stack_trace.h",
+ "base/debug/task_annotator.h",
+ "base/debug/thread_heap_usage_tracker.h",
+ "base/environment.h",
+ "base/export_template.h",
+ "base/feature_list.h",
+ "base/file_descriptor_posix.h",
+ "base/file_version_info.h",
+ "base/files/dir_reader_fallback.h",
+ "base/files/dir_reader_linux.h",
+ "base/files/dir_reader_posix.h",
+ "base/files/file.h",
+ "base/files/file_descriptor_watcher_posix.h",
+ "base/files/file_enumerator.h",
+ "base/files/file_path.h",
+ "base/files/file_path_watcher.h",
+ "base/files/file_tracing.h",
+ "base/files/file_util.h",
+ "base/files/important_file_writer.h",
+ "base/files/memory_mapped_file.h",
+ "base/files/platform_file.h",
+ "base/files/scoped_file.h",
+ "base/files/scoped_temp_dir.h",
+ "base/format_macros.h",
+ "base/gtest_prod_util.h",
+ "base/guid.h",
+ "base/hash.h",
+ "base/i18n/base_i18n_export.h",
+ "base/i18n/rtl.h",
+ "base/json/json_file_value_serializer.h",
+ "base/json/json_parser.h",
+ "base/json/json_reader.h",
+ "base/json/json_string_value_serializer.h",
+ "base/json/json_value_converter.h",
+ "base/json/json_writer.h",
+ "base/json/string_escape.h",
+ "base/lazy_instance.h",
+ "base/lazy_instance_helpers.h",
+ "base/location.h",
+ "base/logging.h",
+ "base/macros.h",
+ "base/md5.h",
+ "base/memory/aligned_memory.h",
+ "base/memory/free_deleter.h",
+ "base/memory/linked_ptr.h",
+ "base/memory/platform_shared_memory_region.h",
+ "base/memory/protected_memory.h",
+ "base/memory/protected_memory_buildflags.h",
+ "base/memory/protected_memory_cfi.h",
+ "base/memory/ptr_util.h",
+ "base/memory/raw_scoped_refptr_mismatch_checker.h",
+ "base/memory/read_only_shared_memory_region.h",
+ "base/memory/ref_counted.h",
+ "base/memory/ref_counted_delete_on_sequence.h",
+ "base/memory/ref_counted_memory.h",
+ "base/memory/scoped_policy.h",
+ "base/memory/scoped_refptr.h",
+ "base/memory/shared_memory.h",
+ "base/memory/shared_memory_handle.h",
+ "base/memory/shared_memory_helper.h",
+ "base/memory/shared_memory_mapping.h",
+ "base/memory/singleton.h",
+ "base/memory/unsafe_shared_memory_region.h",
+ "base/memory/weak_ptr.h",
+ "base/memory/writable_shared_memory_region.h",
+ "base/message_loop/incoming_task_queue.h",
+ "base/message_loop/message_loop.h",
+ "base/message_loop/message_loop_current.h",
+ "base/message_loop/message_loop_task_runner.h",
+ "base/message_loop/message_pump.h",
+ "base/message_loop/message_pump_default.h",
+ "base/message_loop/message_pump_for_io.h",
+ "base/message_loop/message_pump_for_ui.h",
+ "base/message_loop/message_pump_glib.h",
+ "base/message_loop/message_pump_libevent.h",
+ "base/message_loop/timer_slack.h",
+ "base/message_loop/watchable_io_message_pump_posix.h",
+ "base/metrics/bucket_ranges.h",
+ "base/metrics/dummy_histogram.h",
+ "base/metrics/field_trial.h",
+ "base/metrics/field_trial_param_associator.h",
+ "base/metrics/field_trial_params.h",
+ "base/metrics/histogram.h",
+ "base/metrics/histogram_base.h",
+ "base/metrics/histogram_delta_serialization.h",
+ "base/metrics/histogram_flattener.h",
+ "base/metrics/histogram_functions.h",
+ "base/metrics/histogram_macros.h",
+ "base/metrics/histogram_macros_internal.h",
+ "base/metrics/histogram_macros_local.h",
+ "base/metrics/histogram_samples.h",
+ "base/metrics/histogram_snapshot_manager.h",
+ "base/metrics/metrics_hashes.h",
+ "base/metrics/persistent_histogram_allocator.h",
+ "base/metrics/persistent_histogram_storage.h",
+ "base/metrics/persistent_memory_allocator.h",
+ "base/metrics/persistent_sample_map.h",
+ "base/metrics/record_histogram_checker.h",
+ "base/metrics/sample_map.h",
+ "base/metrics/sample_vector.h",
+ "base/metrics/single_sample_metrics.h",
+ "base/metrics/sparse_histogram.h",
+ "base/metrics/statistics_recorder.h",
+ "base/metrics/user_metrics.h",
+ "base/metrics/user_metrics_action.h",
+ "base/native_library.h",
+ "base/no_destructor.h",
+ "base/numerics/checked_math.h",
+ "base/numerics/checked_math_impl.h",
+ "base/numerics/clamped_math.h",
+ "base/numerics/clamped_math_impl.h",
+ "base/numerics/math_constants.h",
+ "base/numerics/ranges.h",
+ "base/numerics/safe_conversions.h",
+ "base/numerics/safe_conversions_arm_impl.h",
+ "base/numerics/safe_conversions_impl.h",
+ "base/numerics/safe_math.h",
+ "base/numerics/safe_math_arm_impl.h",
+ "base/numerics/safe_math_clang_gcc_impl.h",
+ "base/numerics/safe_math_shared_impl.h",
+ "base/observer_list.h",
+ "base/observer_list_threadsafe.h",
+ "base/optional.h",
+ "base/os_compat_android.h",
+ "base/path_service.h",
+ "base/pending_task.h",
+ "base/pickle.h",
+ "base/posix/eintr_wrapper.h",
+ "base/posix/file_descriptor_shuffle.h",
+ "base/posix/global_descriptors.h",
+ "base/posix/safe_strerror.h",
+ "base/posix/unix_domain_socket.h",
+ "base/post_task_and_reply_with_result_internal.h",
+ "base/power_monitor/power_monitor.h",
+ "base/power_monitor/power_monitor_device_source.h",
+ "base/power_monitor/power_monitor_source.h",
+ "base/power_monitor/power_observer.h",
+ "base/process/internal_aix.h",
+ "base/process/internal_linux.h",
+ "base/process/kill.h",
+ "base/process/launch.h",
+ "base/process/memory.h",
+ "base/process/process.h",
+ "base/process/process_handle.h",
+ "base/process/process_info.h",
+ "base/process/process_iterator.h",
+ "base/process/process_metrics.h",
+ "base/process/process_metrics_iocounters.h",
+ "base/rand_util.h",
+ "base/run_loop.h",
+ "base/sampling_heap_profiler/lock_free_address_hash_set.h",
+ "base/sampling_heap_profiler/sampling_heap_profiler.h",
+ "base/scoped_clear_errno.h",
+ "base/scoped_generic.h",
+ "base/scoped_native_library.h",
+ "base/scoped_observer.h",
+ "base/sequence_checker.h",
+ "base/sequence_checker_impl.h",
+ "base/sequence_token.h",
+ "base/sequenced_task_runner.h",
+ "base/sequenced_task_runner_helpers.h",
+ "base/sha1.h",
+ "base/single_thread_task_runner.h",
+ "base/stl_util.h",
+ "base/strings/char_traits.h",
+ "base/strings/nullable_string16.h",
+ "base/strings/old_utf_string_conversions.h",
+ "base/strings/pattern.h",
+ "base/strings/safe_sprintf.h",
+ "base/strings/strcat.h",
+ "base/strings/string16.h",
+ "base/strings/string_number_conversions.h",
+ "base/strings/string_piece.h",
+ "base/strings/string_piece_forward.h",
+ "base/strings/string_split.h",
+ "base/strings/string_tokenizer.h",
+ "base/strings/string_util.h",
+ "base/strings/string_util_posix.h",
+ "base/strings/stringize_macros.h",
+ "base/strings/stringprintf.h",
+ "base/strings/sys_string_conversions.h",
+ "base/strings/utf_string_conversion_utils.h",
+ "base/strings/utf_string_conversions.h",
+ "base/sync_socket.h",
+ "base/synchronization/atomic_flag.h",
+ "base/synchronization/cancellation_flag.h",
+ "base/synchronization/condition_variable.h",
+ "base/synchronization/lock.h",
+ "base/synchronization/lock_impl.h",
+ "base/synchronization/spin_wait.h",
+ "base/synchronization/synchronization_buildflags.h",
+ "base/synchronization/waitable_event.h",
+ "base/synchronization/waitable_event_watcher.h",
+ "base/sys_byteorder.h",
+ "base/sys_info.h",
+ "base/sys_info_internal.h",
+ "base/task/cancelable_task_tracker.h",
+ "base/task/sequence_manager/enqueue_order.h",
+ "base/task/sequence_manager/graceful_queue_shutdown_helper.h",
+ "base/task/sequence_manager/intrusive_heap.h",
+ "base/task/sequence_manager/lazily_deallocated_deque.h",
+ "base/task/sequence_manager/lazy_now.h",
+ "base/task/sequence_manager/moveable_auto_lock.h",
+ "base/task/sequence_manager/real_time_domain.h",
+ "base/task/sequence_manager/sequence_manager.h",
+ "base/task/sequence_manager/sequence_manager_impl.h",
+ "base/task/sequence_manager/sequenced_task_source.h",
+ "base/task/sequence_manager/task_queue.h",
+ "base/task/sequence_manager/task_queue_impl.h",
+ "base/task/sequence_manager/task_queue_selector.h",
+ "base/task/sequence_manager/task_queue_selector_logic.h",
+ "base/task/sequence_manager/task_time_observer.h",
+ "base/task/sequence_manager/test/fake_task.h",
+ "base/task/sequence_manager/test/lazy_thread_controller_for_test.h",
+ "base/task/sequence_manager/test/mock_time_domain.h",
+ "base/task/sequence_manager/test/sequence_manager_for_test.h",
+ "base/task/sequence_manager/test/test_task_queue.h",
+ "base/task/sequence_manager/test/test_task_time_observer.h",
+ "base/task/sequence_manager/thread_controller.h",
+ "base/task/sequence_manager/thread_controller_impl.h",
+ "base/task/sequence_manager/thread_controller_with_message_pump_impl.h",
+ "base/task/sequence_manager/time_domain.h",
+ "base/task/sequence_manager/work_queue.h",
+ "base/task/sequence_manager/work_queue_sets.h",
+ "base/task_runner.h",
+ "base/task_runner_util.h",
+ "base/task_scheduler/can_schedule_sequence_observer.h",
+ "base/task_scheduler/delayed_task_manager.h",
+ "base/task_scheduler/environment_config.h",
+ "base/task_scheduler/lazy_task_runner.h",
+ "base/task_scheduler/post_task.h",
+ "base/task_scheduler/priority_queue.h",
+ "base/task_scheduler/scheduler_lock.h",
+ "base/task_scheduler/scheduler_lock_impl.h",
+ "base/task_scheduler/scheduler_single_thread_task_runner_manager.h",
+ "base/task_scheduler/scheduler_worker.h",
+ "base/task_scheduler/scheduler_worker_observer.h",
+ "base/task_scheduler/scheduler_worker_params.h",
+ "base/task_scheduler/scheduler_worker_pool.h",
+ "base/task_scheduler/scheduler_worker_pool_impl.h",
+ "base/task_scheduler/scheduler_worker_pool_params.h",
+ "base/task_scheduler/scheduler_worker_stack.h",
+ "base/task_scheduler/scoped_set_task_priority_for_current_thread.h",
+ "base/task_scheduler/sequence.h",
+ "base/task_scheduler/sequence_sort_key.h",
+ "base/task_scheduler/service_thread.h",
+ "base/task_scheduler/single_thread_task_runner_thread_mode.h",
+ "base/task_scheduler/task.h",
+ "base/task_scheduler/task_scheduler.h",
+ "base/task_scheduler/task_scheduler_impl.h",
+ "base/task_scheduler/task_tracker.h",
+ "base/task_scheduler/task_tracker_posix.h",
+ "base/task_scheduler/task_traits.h",
+ "base/task_scheduler/task_traits_details.h",
+ "base/task_scheduler/test_utils.h",
+ "base/task_scheduler/tracked_ref.h",
+ "base/template_util.h",
+ "base/test/bind_test_util.h",
+ "base/test/copy_only_int.h",
+ "base/test/fontconfig_util_linux.h",
+ "base/test/gtest_util.h",
+ "base/test/metrics/histogram_enum_reader.h",
+ "base/test/metrics/histogram_tester.h",
+ "base/test/mock_entropy_provider.h",
+ "base/test/move_only_int.h",
+ "base/test/multiprocess_test.h",
+ "base/test/scoped_environment_variable_override.h",
+ "base/test/scoped_feature_list.h",
+ "base/test/scoped_locale.h",
+ "base/test/scoped_task_environment.h",
+ "base/test/simple_test_clock.h",
+ "base/test/simple_test_tick_clock.h",
+ "base/test/test_file_util.h",
+ "base/test/test_io_thread.h",
+ "base/test/test_mock_time_task_runner.h",
+ "base/test/test_pending_task.h",
+ "base/test/test_shared_memory_util.h",
+ "base/test/test_simple_task_runner.h",
+ "base/test/test_switches.h",
+ "base/test/test_timeouts.h",
+ "base/third_party/dynamic_annotations/dynamic_annotations.h",
+ "base/third_party/icu/icu_utf.h",
+ "base/third_party/libevent/event.h",
+ "base/third_party/nspr/prtime.h",
+ "base/third_party/symbolize/symbolize.h",
+ "base/third_party/valgrind/memcheck.h",
+ "base/third_party/valgrind/valgrind.h",
+ "base/thread_annotations.h",
+ "base/threading/platform_thread.h",
+ "base/threading/platform_thread_internal_posix.h",
+ "base/threading/post_task_and_reply_impl.h",
+ "base/threading/scoped_blocking_call.h",
+ "base/threading/sequence_local_storage_map.h",
+ "base/threading/sequence_local_storage_slot.h",
+ "base/threading/sequenced_task_runner_handle.h",
+ "base/threading/simple_thread.h",
+ "base/threading/thread.h",
+ "base/threading/thread_checker.h",
+ "base/threading/thread_checker_impl.h",
+ "base/threading/thread_collision_warner.h",
+ "base/threading/thread_id_name_manager.h",
+ "base/threading/thread_local.h",
+ "base/threading/thread_local_storage.h",
+ "base/threading/thread_restrictions.h",
+ "base/threading/thread_task_runner_handle.h",
+ "base/time/clock.h",
+ "base/time/default_clock.h",
+ "base/time/default_tick_clock.h",
+ "base/time/tick_clock.h",
+ "base/time/time.h",
+ "base/time/time_override.h",
+ "base/time/time_to_iso8601.h",
+ "base/timer/elapsed_timer.h",
+ "base/timer/hi_res_timer_manager.h",
+ "base/timer/mock_timer.h",
+ "base/timer/timer.h",
+ "base/trace_event/common/trace_event_common.h",
+ "base/trace_event/heap_profiler.h",
+ "base/trace_event/trace_event.h",
+ "base/tuple.h",
+ "base/unguessable_token.h",
+ "base/value_iterators.h",
+ "base/values.h",
+ "base/version.h",
+ "base/vlog.h",
+ "build/build_config.h",
+ "build/buildflag.h",
+ "device/bluetooth/bluetooth_advertisement.h",
+ "device/bluetooth/bluetooth_common.h",
+ "device/bluetooth/bluetooth_export.h",
+ "device/bluetooth/bluetooth_uuid.h",
+ "device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h",
+ "testing/gmock/include/gmock/gmock.h",
+ "testing/gtest/include/gtest/gtest.h",
+ "testing/gtest/include/gtest/gtest_prod.h",
+ "testing/multiprocess_func_list.h",
+ "testing/platform_test.h",
+ "third_party/ashmem/ashmem.h",
+ "third_party/modp_b64/modp_b64.h",
+ "third_party/protobuf/src/google/protobuf/message_lite.h",
+ "third_party/re2/src/re2/re2.h",
+ "ui/gfx/geometry/insets.h",
+ "ui/gfx/geometry/insets_f.h",
+ "ui/gfx/geometry/mojo/geometry_struct_traits.h",
+ "ui/gfx/geometry/point.h",
+ "ui/gfx/geometry/point_conversions.h",
+ "ui/gfx/geometry/point_f.h",
+ "ui/gfx/geometry/rect.h",
+ "ui/gfx/geometry/rect_f.h",
+ "ui/gfx/geometry/safe_integer_conversions.h",
+ "ui/gfx/geometry/scroll_offset.h",
+ "ui/gfx/geometry/size.h",
+ "ui/gfx/geometry/size_conversions.h",
+ "ui/gfx/geometry/size_f.h",
+ "ui/gfx/geometry/vector2d.h",
+ "ui/gfx/geometry/vector2d_f.h",
+ "ui/gfx/gfx_export.h",
+ "ui/gfx/range/gfx_range_export.h",
+ "ui/gfx/range/mojo/range_struct_traits.h",
+ "ui/gfx/range/range.h",
+ "ui/gfx/range/range_f.h",
],
}
@@ -67,6 +533,8 @@ cc_defaults {
// so the raw header files are used for them.
generated_headers: ["libchrome-include"],
export_generated_headers: ["libchrome-include"],
+ header_libs: ["jni_headers"],
+ export_header_lib_headers: ["jni_headers"],
target: {
host: {
cflags: [
@@ -146,12 +614,10 @@ libchromeCommonSrc = [
"base/md5.cc",
"base/memory/aligned_memory.cc",
"base/memory/platform_shared_memory_region.cc",
- "base/memory/platform_shared_memory_region_posix.cc",
"base/memory/read_only_shared_memory_region.cc",
"base/memory/ref_counted.cc",
"base/memory/ref_counted_memory.cc",
"base/memory/shared_memory_handle.cc",
- "base/memory/shared_memory_handle_posix.cc",
"base/memory/shared_memory_helper.cc",
"base/memory/shared_memory_mapping.cc",
"base/memory/unsafe_shared_memory_region.cc",
@@ -306,19 +772,42 @@ libchromeLinuxSrc = [
"base/sys_info_linux.cc",
"base/threading/platform_thread_internal_posix.cc",
"base/threading/platform_thread_linux.cc",
- "components/timers/alarm_timer_chromeos.cc",
]
libchromeLinuxGlibcSrc = [
"base/allocator/allocator_shim.cc",
"base/allocator/allocator_shim_default_dispatch_to_glibc.cc",
"base/debug/stack_trace_posix.cc",
+ "base/memory/platform_shared_memory_region_posix.cc",
+ "base/memory/shared_memory_handle_posix.cc",
+]
+
+libchromeLinuxBionicSrc = [
+ "base/debug/stack_trace_android.cc",
+ "base/memory/platform_shared_memory_region_posix.cc",
+ "base/memory/shared_memory_handle_posix.cc",
]
libchromeAndroidSrc = [
+ "base/android/android_hardware_buffer_compat.cc",
+ "base/android/build_info.cc",
+ "base/android/content_uri_utils.cc",
+ "base/android/java_exception_reporter.cc",
+ "base/android/jni_android.cc",
+ "base/android/jni_array.cc",
+ "base/android/jni_string.cc",
+ "base/android/path_utils.cc",
+ "base/android/scoped_java_ref.cc",
+ "base/android/scoped_hardware_buffer_handle.cc",
+ "base/android/sys_utils.cc",
+ "base/base_paths_android.cc",
"base/debug/stack_trace_android.cc",
+ "base/memory/platform_shared_memory_region_android.cc",
"base/memory/shared_memory_android.cc",
- "base/sys_info_chromeos.cc",
+ "base/memory/shared_memory_handle_android.cc",
+ "base/os_compat_android.cc",
+ "base/sys_info_android.cc",
+ "base/time/time_android.cc",
]
// libchrome static+shared for host and device
@@ -332,19 +821,34 @@ cc_library {
srcs: libchromeCommonSrc,
export_shared_lib_headers: ["libbase"],
- export_static_lib_headers: ["libgtest_prod"],
+ export_header_lib_headers: ["libgtest_prod_headers"],
shared_libs: [
"libbase",
"libevent",
],
+ header_libs: [
+ "libgtest_prod_headers",
+ ],
static_libs: [
- "libgtest_prod",
"libmodpb64",
],
+ generated_headers: [
+ "libmojo_jni_headers",
+ "libmojo_jni_registration_headers",
+ ],
+ export_generated_headers: [
+ "libmojo_jni_registration_headers",
+ ],
target: {
linux: {
srcs: libchromeLinuxSrc,
},
+ linux_bionic: {
+ srcs: libchromeLinuxBionicSrc,
+ shared_libs: [
+ "liblog",
+ ],
+ },
linux_glibc: {
srcs: libchromeLinuxGlibcSrc,
},
@@ -835,7 +1339,10 @@ genrule {
srcs: [
"base/android/java/src/org/chromium/base/BuildInfo.java",
+ "base/android/java/src/org/chromium/base/ContentUriUtils.java",
"base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
+ "base/android/java/src/org/chromium/base/PathUtils.java",
+ "base/android/java/src/org/chromium/base/SysUtils.java",
"base/android/java/src/org/chromium/base/ThreadUtils.java",
"mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java",
"mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java",
@@ -845,8 +1352,11 @@ genrule {
out: [
"jni/BaseRunLoop_jni.h",
"jni/BuildInfo_jni.h",
+ "jni/ContentUriUtils_jni.h",
"jni/CoreImpl_jni.h",
"jni/JavaExceptionReporter_jni.h",
+ "jni/PathUtils_jni.h",
+ "jni/SysUtils_jni.h",
"jni/WatcherImpl_jni.h",
],
}
@@ -959,10 +1469,80 @@ java_library {
srcs: [
":libmojo_mojom_java_srcs",
- "base/android/java/src/**/*.java",
- "mojo/android/system/src/**/*.java",
- "mojo/public/java/bindings/src/**/*.java",
- "mojo/public/java/system/src/**/*.java",
+ "base/android/java/src/org/chromium/base/BuildConfig.java",
+ "base/android/java/src/org/chromium/base/BuildInfo.java",
+ "base/android/java/src/org/chromium/base/ContextUtils.java",
+ "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
+ "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
+ "base/android/java/src/org/chromium/base/Log.java",
+ "base/android/java/src/org/chromium/base/PackageUtils.java",
+ "base/android/java/src/org/chromium/base/StrictModeContext.java",
+ "base/android/java/src/org/chromium/base/Supplier.java",
+ "base/android/java/src/org/chromium/base/ThreadUtils.java",
+ "base/android/java/src/org/chromium/base/TimezoneUtils.java",
+ "base/android/java/src/org/chromium/base/VisibleForTesting.java",
+ "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
+ "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
+ "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
+ "base/android/java/src/org/chromium/base/annotations/MainDex.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeCall.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
+ "base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java",
+ "base/android/java/src/org/chromium/base/annotations/UsedByReflection.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java",
+ "mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/Core.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/Flags.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/Handle.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/Pair.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/HandleBase.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java",
],
static_libs: [
diff --git a/BUILD.IGNORE b/BUILD.IGNORE
new file mode 100644
index 0000000000..d84edf6d59
--- /dev/null
+++ b/BUILD.IGNORE
@@ -0,0 +1,10 @@
+android
+components/policy
+fuzzer
+perftest
+power_monitor
+test_runner
+tests
+trace_event
+ui/gfx
+unittest
diff --git a/BUILD.gn b/BUILD.gn
index b956970b24..6cb4e68bde 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1,8 +1,12 @@
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+#
+# BUILD.gn doesn't compile all files in the directory to reduce build size.
+# Missing files can be added if needed.
import("//common-mk/mojom_bindings_generator.gni")
+import("//common-mk/mojom_type_mappings_generator.gni")
import("//common-mk/pkg_config.gni")
group("all") {
@@ -32,9 +36,11 @@ config("libchrome_config") {
cflags = [
"-Wno-deprecated-register",
"-Wno-narrowing",
+ "-Wno-unreachable-code-return",
"-Wno-unused-local-typedefs",
"-Xclang-only=-Wno-char-subscripts",
]
+
# Address sanitizer + coverage builds do not support -z,defs.
if (!(use.asan || use.coverage)) {
ldflags = [ "-Wl,-z,defs" ]
@@ -45,6 +51,7 @@ config("base_core_config") {
cflags = [
# Suppressing warning in base/strings/stringprintf.cc.
"-Wno-format-nonliteral",
+
# This is for _exit(1) in base/debug/debugger_posix.cc.
"-Wno-unreachable-code",
]
@@ -54,8 +61,15 @@ libbase_sublibs = [
{
name = "base-core"
output_name = name + "-${libbase_ver}"
- libs = [ "pthread", "rt", "modp_b64" ]
- pkg_deps = [ "glib-2.0", "libevent" ]
+ libs = [
+ "pthread",
+ "rt",
+ "modp_b64",
+ ]
+ pkg_deps = [
+ "glib-2.0",
+ "libevent",
+ ]
configs = [ ":base_core_config" ]
sources = [
"base/allocator/allocator_extension.cc",
@@ -125,8 +139,8 @@ libbase_sublibs = [
"base/memory/shared_memory_handle.cc",
"base/memory/shared_memory_handle_posix.cc",
"base/memory/shared_memory_helper.cc",
- "base/memory/shared_memory_posix.cc",
"base/memory/shared_memory_mapping.cc",
+ "base/memory/shared_memory_posix.cc",
"base/memory/unsafe_shared_memory_region.cc",
"base/memory/weak_ptr.cc",
"base/memory/writable_shared_memory_region.cc",
@@ -157,8 +171,8 @@ libbase_sublibs = [
"base/metrics/sample_vector.cc",
"base/metrics/sparse_histogram.cc",
"base/metrics/statistics_recorder.cc",
- "base/path_service.cc",
"base/observer_list_threadsafe.cc",
+ "base/path_service.cc",
"base/pending_task.cc",
"base/pickle.cc",
"base/posix/file_descriptor_shuffle.cc",
@@ -186,8 +200,8 @@ libbase_sublibs = [
"base/rand_util_posix.cc",
"base/run_loop.cc",
"base/sequence_checker_impl.cc",
- "base/sequenced_task_runner.cc",
"base/sequence_token.cc",
+ "base/sequenced_task_runner.cc",
"base/sha1.cc",
"base/strings/nullable_string16.cc",
"base/strings/pattern.cc",
@@ -214,10 +228,11 @@ libbase_sublibs = [
"base/sys_info_chromeos.cc",
"base/sys_info_linux.cc",
"base/sys_info_posix.cc",
- "base/task_runner.cc",
"base/task/cancelable_task_tracker.cc",
+ "base/task_runner.cc",
"base/task_scheduler/delayed_task_manager.cc",
"base/task_scheduler/environment_config.cc",
+ "base/task_scheduler/lazy_task_runner.cc",
"base/task_scheduler/post_task.cc",
"base/task_scheduler/priority_queue.cc",
"base/task_scheduler/scheduler_lock_impl.cc",
@@ -234,9 +249,9 @@ libbase_sublibs = [
"base/task_scheduler/task.cc",
"base/task_scheduler/task_scheduler.cc",
"base/task_scheduler/task_scheduler_impl.cc",
- "base/task_scheduler/task_traits.cc",
"base/task_scheduler/task_tracker.cc",
"base/task_scheduler/task_tracker_posix.cc",
+ "base/task_scheduler/task_traits.cc",
"base/third_party/dynamic_annotations/dynamic_annotations.c",
"base/third_party/icu/icu_utf.cc",
"base/third_party/nspr/prtime.cc",
@@ -257,8 +272,6 @@ libbase_sublibs = [
"base/threading/thread_local_storage_posix.cc",
"base/threading/thread_restrictions.cc",
"base/threading/thread_task_runner_handle.cc",
- "base/timer/elapsed_timer.cc",
- "base/timer/timer.cc",
"base/time/clock.cc",
"base/time/default_clock.cc",
"base/time/default_tick_clock.cc",
@@ -268,6 +281,8 @@ libbase_sublibs = [
"base/time/time_exploded_posix.cc",
"base/time/time_now_posix.cc",
"base/time/time_override.cc",
+ "base/timer/elapsed_timer.cc",
+ "base/timer/timer.cc",
"base/unguessable_token.cc",
"base/value_iterators.cc",
"base/values.cc",
@@ -282,8 +297,8 @@ libbase_sublibs = [
deps = [ ":base-core" ]
libs = [ "dl" ]
sources = [
- "base/native_library_posix.cc",
"base/native_library.cc",
+ "base/native_library_posix.cc",
"base/scoped_native_library.cc",
]
},
@@ -307,6 +322,7 @@ libbase_sublibs = [
output_name = name + "-${libbase_ver}"
testonly = true
sources = [
+ "base/test/scoped_task_environment.cc",
"base/test/simple_test_clock.cc",
"base/test/simple_test_tick_clock.cc",
"base/test/test_file_util.cc",
@@ -317,7 +333,7 @@ libbase_sublibs = [
"base/test/test_switches.cc",
"base/test/test_timeouts.cc",
]
- }
+ },
]
if (use.crypto) {
@@ -325,8 +341,14 @@ if (use.crypto) {
{
name = "base-crypto"
output_name = name + "-${libbase_ver}"
- deps = [ ":base-core", ":base-dl" ]
- pkg_deps = [ "nss", "openssl" ]
+ deps = [
+ ":base-core",
+ ":base-dl",
+ ]
+ pkg_deps = [
+ "nss",
+ "openssl",
+ ]
sources = [
"crypto/hmac.cc",
"crypto/hmac_nss.cc",
@@ -347,10 +369,11 @@ if (use.crypto) {
"crypto/symmetric_key_nss.cc",
"crypto/third_party/nss/rsawrapr.c",
"crypto/third_party/nss/sha512.cc",
+
# Added to libchrome only (not upstream) to support OpenSSL 1.1 API
"crypto/libcrypto-compat.c",
]
- }
+ },
]
}
@@ -362,9 +385,9 @@ if (use.dbus) {
deps = [ ":base-core" ]
pkg_deps = [ "dbus-1" ]
if (use.fuzzer) {
- pkg_deps += [ "protobuf" ]
+ pkg_deps += [ "protobuf" ]
} else {
- pkg_deps += [ "protobuf-lite" ]
+ pkg_deps += [ "protobuf-lite" ]
}
sources = [
"dbus/bus.cc",
@@ -388,9 +411,9 @@ if (use.dbus) {
testonly = true
pkg_deps = [ "dbus-1" ]
if (use.fuzzer) {
- pkg_deps += [ "protobuf" ]
+ pkg_deps += [ "protobuf" ]
} else {
- pkg_deps += [ "protobuf-lite" ]
+ pkg_deps += [ "protobuf-lite" ]
}
sources = [
"dbus/mock_bus.cc",
@@ -450,7 +473,7 @@ foreach(attr, libbase_sublibs) {
}
configs += [
":libchrome_config",
- "//common-mk:visibility_default"
+ "//common-mk:visibility_default",
]
if (buildtype == "static_library") {
configs -= [ "//common-mk:use_thin_archive" ]
@@ -473,20 +496,26 @@ action("base") {
script = "//common-mk/write_args.py"
outputs = [ "${root_out_dir}/lib/lib${target_name}-${libbase_ver}.so" ]
args = [ "--output" ] + outputs + [ "--" ] + [
- "GROUP", "(", "AS_NEEDED", "(",
- ]
+ "GROUP",
+ "(",
+ "AS_NEEDED",
+ "(",
+ ]
foreach(attr, libbase_sublibs) {
if (!defined(attr.testonly) || !attr.testonly) {
args += [ "-l" + attr.output_name ]
}
}
- args += [ ")", ")" ]
+ args += [
+ ")",
+ ")",
+ ]
}
libchrome_exported_cflags = [
- "-I/usr/include/base-${libbase_ver}",
- "-Wno-unused-local-typedefs",
- "-DBASE_VER=${libbase_ver}",
+ "-I/usr/include/base-${libbase_ver}",
+ "-Wno-unused-local-typedefs",
+ "-DBASE_VER=${libbase_ver}",
]
if (use.asan) {
@@ -500,8 +529,7 @@ generate_pkg_config("libchrome") {
version = "${libbase_ver}"
requires_private = []
foreach(attr, libbase_sublibs) {
- if ((!defined(attr.testonly) || !attr.testonly)
- && defined(attr.pkg_deps)) {
+ if ((!defined(attr.testonly) || !attr.testonly) && defined(attr.pkg_deps)) {
requires_private += attr.pkg_deps
}
}
@@ -531,14 +559,20 @@ action("base-test") {
script = "//common-mk/write_args.py"
outputs = [ "${root_out_dir}/lib${target_name}-${libbase_ver}.a" ]
args = [ "--output" ] + outputs + [ "--" ] + [
- "GROUP", "(", "AS_NEEDED", "(",
- ]
+ "GROUP",
+ "(",
+ "AS_NEEDED",
+ "(",
+ ]
foreach(attr, libbase_sublibs) {
if (defined(attr.testonly) && attr.testonly) {
args += [ "-l" + attr.output_name ]
}
}
- args += [ ")", ")" ]
+ args += [
+ ")",
+ ")",
+ ]
}
generate_pkg_config("libchrome-test") {
@@ -566,8 +600,33 @@ generate_pkg_config("libchrome-test") {
}
if (use.mojo) {
+ generate_mojom_type_mappings("mojom_type_mappings") {
+ mojom_type_mappings_generator =
+ "libchrome_tools/mojom_generate_type_mappings.py"
+ sources = [
+ "mojo/public/cpp/base/big_buffer.typemap",
+ "mojo/public/cpp/base/big_string.typemap",
+ "mojo/public/cpp/base/file.typemap",
+ "mojo/public/cpp/base/file_error.typemap",
+ "mojo/public/cpp/base/file_info.typemap",
+ "mojo/public/cpp/base/file_path.typemap",
+ "mojo/public/cpp/base/memory_allocator_dump_cross_process_uid.typemap",
+ "mojo/public/cpp/base/process_id.typemap",
+ "mojo/public/cpp/base/read_only_buffer.typemap",
+ "mojo/public/cpp/base/ref_counted_memory.typemap",
+ "mojo/public/cpp/base/shared_memory.typemap",
+ "mojo/public/cpp/base/string16.typemap",
+ "mojo/public/cpp/base/text_direction.typemap",
+ "mojo/public/cpp/base/thread_priority.typemap",
+ "mojo/public/cpp/base/time.typemap",
+ "mojo/public/cpp/base/unguessable_token.typemap",
+ "mojo/public/cpp/base/values.typemap",
+ ]
+ }
generate_mojom_bindings_gen("mojom_bindings_gen") {
- mojom_bindings_generator = "mojo/public/tools/bindings/mojom_bindings_generator.py"
+ mojom_bindings_generator =
+ "mojo/public/tools/bindings/mojom_bindings_generator.py"
+ typemaps = get_target_outputs(":mojom_type_mappings")
sources = [
"ipc/ipc.mojom",
"mojo/public/mojom/base/big_buffer.mojom",
@@ -576,6 +635,7 @@ if (use.mojo) {
"mojo/public/mojom/base/file_error.mojom",
"mojo/public/mojom/base/file_info.mojom",
"mojo/public/mojom/base/file_path.mojom",
+ "mojo/public/mojom/base/memory_allocator_dump_cross_process_uid.mojom",
"mojo/public/mojom/base/process_id.mojom",
"mojo/public/mojom/base/read_only_buffer.mojom",
"mojo/public/mojom/base/ref_counted_memory.mojom",
@@ -592,7 +652,8 @@ if (use.mojo) {
}
generate_mojom_bindings_gen("mojom_bindings_native_gen") {
- mojom_bindings_generator = "mojo/public/tools/bindings/mojom_bindings_generator.py"
+ mojom_bindings_generator =
+ "mojo/public/tools/bindings/mojom_bindings_generator.py"
sources = [
"mojo/public/interfaces/bindings/interface_control_messages.mojom",
"mojo/public/interfaces/bindings/native_struct.mojom",
@@ -606,7 +667,14 @@ if (use.mojo) {
# crbug.com/924035.
static_library("mojo") {
output_name = "mojo-${libbase_ver}"
- deps = [ ":base-core", ":base-crypto", ":mojom_bindings_gen", ":mojom_bindings_native_gen" ]
+ deps = [
+ ":base-core",
+ ":base-crypto",
+ ":mojom_bindings_gen",
+ ":mojom_bindings_native_gen",
+ ":mojom_type_mappings",
+ ]
+
# TODO(hidehiko): Consolidate with build_config.h.
configs -= [
"//common-mk:use_thin_archive",
@@ -618,129 +686,131 @@ if (use.mojo) {
"//common-mk:nouse_thin_archive",
"//common-mk:pic",
]
- sources = [
- "ipc/ipc_message.cc",
- "ipc/ipc_message_attachment.cc",
- "ipc/ipc_message_attachment_set.cc",
- "ipc/ipc_message_utils.cc",
- "ipc/ipc_mojo_handle_attachment.cc",
- "ipc/ipc_mojo_message_helper.cc",
- "ipc/ipc_mojo_param_traits.cc",
- "ipc/ipc_platform_file_attachment_posix.cc",
- "mojo/core/invitation_dispatcher.cc",
- "mojo/core/connection_params.cc",
- "mojo/core/channel_posix.cc",
- "mojo/core/platform_handle_dispatcher.cc",
- "mojo/core/mojo_core.cc",
- "mojo/core/channel.cc",
- "mojo/core/entrypoints.cc",
- "mojo/core/broker_posix.cc",
- "mojo/core/data_pipe_producer_dispatcher.cc",
- "mojo/core/broker_host.cc",
- "mojo/core/watcher_dispatcher.cc",
- "mojo/core/request_context.cc",
- "mojo/core/configuration.cc",
- "mojo/core/node_channel.cc",
- "mojo/core/shared_buffer_dispatcher.cc",
- "mojo/core/watch.cc",
- "mojo/core/embedder/scoped_ipc_support.cc",
- "mojo/core/embedder/embedder.cc",
- "mojo/core/message_pipe_dispatcher.cc",
- "mojo/core/handle_table.cc",
- "mojo/core/core.cc",
- "mojo/core/ports/event.cc",
- "mojo/core/ports/name.cc",
- "mojo/core/ports/port.cc",
- "mojo/core/ports/message_queue.cc",
- "mojo/core/ports/port_locker.cc",
- "mojo/core/ports/node.cc",
- "mojo/core/ports/user_message.cc",
- "mojo/core/ports/port_ref.cc",
- "mojo/core/data_pipe_consumer_dispatcher.cc",
- "mojo/core/scoped_process_handle.cc",
- "mojo/core/node_controller.cc",
- "mojo/core/watcher_set.cc",
- "mojo/core/dispatcher.cc",
- "mojo/core/platform_handle_in_transit.cc",
- "mojo/core/platform_handle_utils.cc",
- "mojo/core/platform_shared_memory_mapping.cc",
- "mojo/core/user_message_impl.cc",
- "mojo/core/data_pipe_control_message.cc",
- "mojo/public/c/system/thunks.cc",
- "mojo/public/cpp/base/big_string_mojom_traits.cc",
- "mojo/public/cpp/base/shared_memory_mojom_traits.cc",
- "mojo/public/cpp/base/unguessable_token_mojom_traits.cc",
- "mojo/public/cpp/base/ref_counted_memory_mojom_traits.cc",
- "mojo/public/cpp/base/big_buffer.cc",
- "mojo/public/cpp/base/read_only_buffer_mojom_traits.cc",
- "mojo/public/cpp/base/string16_mojom_traits.cc",
- "mojo/public/cpp/base/big_buffer_mojom_traits.cc",
- "mojo/public/cpp/base/file_info_mojom_traits.cc",
- "mojo/public/cpp/base/time_mojom_traits.cc",
- "mojo/public/cpp/base/thread_priority_mojom_traits.cc",
- "mojo/public/cpp/base/text_direction_mojom_traits.cc",
- "mojo/public/cpp/base/values_mojom_traits.cc",
- "mojo/public/cpp/base/file_path_mojom_traits.cc",
- "mojo/public/cpp/base/process_id_mojom_traits.cc",
- "mojo/public/cpp/base/file_mojom_traits.cc",
- "mojo/public/cpp/bindings/lib/serialization_context.cc",
- "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc",
- "mojo/public/cpp/bindings/lib/array_internal.cc",
- "mojo/public/cpp/bindings/lib/interface_ptr_state.cc",
- "mojo/public/cpp/bindings/lib/buffer.cc",
- "mojo/public/cpp/bindings/lib/sync_call_restrictions.cc",
- "mojo/public/cpp/bindings/lib/multiplex_router.cc",
- "mojo/public/cpp/bindings/lib/sync_handle_watcher.cc",
- "mojo/public/cpp/bindings/lib/validation_errors.cc",
- "mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc",
- "mojo/public/cpp/bindings/lib/message_dumper.cc",
- "mojo/public/cpp/bindings/lib/sync_event_watcher.cc",
- "mojo/public/cpp/bindings/lib/task_runner_helper.cc",
- "mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc",
- "mojo/public/cpp/bindings/lib/validation_context.cc",
- "mojo/public/cpp/bindings/lib/associated_group.cc",
- "mojo/public/cpp/bindings/lib/native_struct_serialization.cc",
- "mojo/public/cpp/bindings/lib/validation_util.cc",
- "mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc",
- "mojo/public/cpp/bindings/lib/filter_chain.cc",
- "mojo/public/cpp/bindings/lib/message.cc",
- "mojo/public/cpp/bindings/lib/unserialized_message_context.cc",
- "mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc",
- "mojo/public/cpp/bindings/lib/control_message_proxy.cc",
- "mojo/public/cpp/bindings/lib/control_message_handler.cc",
- "mojo/public/cpp/bindings/lib/connector.cc",
- "mojo/public/cpp/bindings/lib/interface_endpoint_client.cc",
- "mojo/public/cpp/bindings/lib/sync_handle_registry.cc",
- "mojo/public/cpp/bindings/lib/associated_binding.cc",
- "mojo/public/cpp/bindings/lib/message_header_validator.cc",
- "mojo/public/cpp/bindings/lib/associated_group_controller.cc",
- "mojo/public/cpp/bindings/lib/associated_interface_ptr.cc",
- "mojo/public/cpp/bindings/lib/fixed_buffer.cc",
- "mojo/public/cpp/bindings/lib/message_internal.cc",
- "mojo/public/cpp/bindings/lib/binding_state.cc",
- "mojo/public/cpp/platform/platform_channel_endpoint.cc",
- "mojo/public/cpp/platform/platform_handle.cc",
- "mojo/public/cpp/platform/named_platform_channel.cc",
- "mojo/public/cpp/platform/platform_channel.cc",
- "mojo/public/cpp/platform/platform_channel_server_endpoint.cc",
- "mojo/public/cpp/platform/socket_utils_posix.cc",
- "mojo/public/cpp/platform/named_platform_channel_posix.cc",
- "mojo/public/cpp/system/buffer.cc",
- "mojo/public/cpp/system/platform_handle.cc",
- "mojo/public/cpp/system/wait.cc",
- "mojo/public/cpp/system/wait_set.cc",
- "mojo/public/cpp/system/data_pipe_utils.cc",
- "mojo/public/cpp/system/scope_to_message_pipe.cc",
- "mojo/public/cpp/system/handle_signal_tracker.cc",
- "mojo/public/cpp/system/trap.cc",
- "mojo/public/cpp/system/isolated_connection.cc",
- "mojo/public/cpp/system/string_data_pipe_producer.cc",
- "mojo/public/cpp/system/data_pipe_drainer.cc",
- "mojo/public/cpp/system/invitation.cc",
- "mojo/public/cpp/system/simple_watcher.cc",
- "mojo/public/cpp/system/file_data_pipe_producer.cc",
- "mojo/public/cpp/system/message_pipe.cc",
- ] + get_target_outputs(":mojom_bindings_gen") + get_target_outputs(":mojom_bindings_native_gen")
+ sources =
+ [
+ "ipc/ipc_message.cc",
+ "ipc/ipc_message_attachment.cc",
+ "ipc/ipc_message_attachment_set.cc",
+ "ipc/ipc_message_utils.cc",
+ "ipc/ipc_mojo_handle_attachment.cc",
+ "ipc/ipc_mojo_message_helper.cc",
+ "ipc/ipc_mojo_param_traits.cc",
+ "ipc/ipc_platform_file_attachment_posix.cc",
+ "mojo/core/invitation_dispatcher.cc",
+ "mojo/core/connection_params.cc",
+ "mojo/core/channel_posix.cc",
+ "mojo/core/platform_handle_dispatcher.cc",
+ "mojo/core/mojo_core.cc",
+ "mojo/core/channel.cc",
+ "mojo/core/entrypoints.cc",
+ "mojo/core/broker_posix.cc",
+ "mojo/core/data_pipe_producer_dispatcher.cc",
+ "mojo/core/broker_host.cc",
+ "mojo/core/watcher_dispatcher.cc",
+ "mojo/core/request_context.cc",
+ "mojo/core/configuration.cc",
+ "mojo/core/node_channel.cc",
+ "mojo/core/shared_buffer_dispatcher.cc",
+ "mojo/core/watch.cc",
+ "mojo/core/embedder/scoped_ipc_support.cc",
+ "mojo/core/embedder/embedder.cc",
+ "mojo/core/message_pipe_dispatcher.cc",
+ "mojo/core/handle_table.cc",
+ "mojo/core/core.cc",
+ "mojo/core/ports/event.cc",
+ "mojo/core/ports/name.cc",
+ "mojo/core/ports/port.cc",
+ "mojo/core/ports/message_queue.cc",
+ "mojo/core/ports/port_locker.cc",
+ "mojo/core/ports/node.cc",
+ "mojo/core/ports/user_message.cc",
+ "mojo/core/ports/port_ref.cc",
+ "mojo/core/data_pipe_consumer_dispatcher.cc",
+ "mojo/core/scoped_process_handle.cc",
+ "mojo/core/node_controller.cc",
+ "mojo/core/watcher_set.cc",
+ "mojo/core/dispatcher.cc",
+ "mojo/core/platform_handle_in_transit.cc",
+ "mojo/core/platform_handle_utils.cc",
+ "mojo/core/platform_shared_memory_mapping.cc",
+ "mojo/core/user_message_impl.cc",
+ "mojo/core/data_pipe_control_message.cc",
+ "mojo/public/c/system/thunks.cc",
+ "mojo/public/cpp/base/big_string_mojom_traits.cc",
+ "mojo/public/cpp/base/shared_memory_mojom_traits.cc",
+ "mojo/public/cpp/base/unguessable_token_mojom_traits.cc",
+ "mojo/public/cpp/base/ref_counted_memory_mojom_traits.cc",
+ "mojo/public/cpp/base/big_buffer.cc",
+ "mojo/public/cpp/base/read_only_buffer_mojom_traits.cc",
+ "mojo/public/cpp/base/string16_mojom_traits.cc",
+ "mojo/public/cpp/base/big_buffer_mojom_traits.cc",
+ "mojo/public/cpp/base/file_info_mojom_traits.cc",
+ "mojo/public/cpp/base/time_mojom_traits.cc",
+ "mojo/public/cpp/base/thread_priority_mojom_traits.cc",
+ "mojo/public/cpp/base/text_direction_mojom_traits.cc",
+ "mojo/public/cpp/base/values_mojom_traits.cc",
+ "mojo/public/cpp/base/file_path_mojom_traits.cc",
+ "mojo/public/cpp/base/process_id_mojom_traits.cc",
+ "mojo/public/cpp/base/file_mojom_traits.cc",
+ "mojo/public/cpp/bindings/lib/serialization_context.cc",
+ "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc",
+ "mojo/public/cpp/bindings/lib/array_internal.cc",
+ "mojo/public/cpp/bindings/lib/interface_ptr_state.cc",
+ "mojo/public/cpp/bindings/lib/buffer.cc",
+ "mojo/public/cpp/bindings/lib/sync_call_restrictions.cc",
+ "mojo/public/cpp/bindings/lib/multiplex_router.cc",
+ "mojo/public/cpp/bindings/lib/sync_handle_watcher.cc",
+ "mojo/public/cpp/bindings/lib/validation_errors.cc",
+ "mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc",
+ "mojo/public/cpp/bindings/lib/message_dumper.cc",
+ "mojo/public/cpp/bindings/lib/sync_event_watcher.cc",
+ "mojo/public/cpp/bindings/lib/task_runner_helper.cc",
+ "mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc",
+ "mojo/public/cpp/bindings/lib/validation_context.cc",
+ "mojo/public/cpp/bindings/lib/associated_group.cc",
+ "mojo/public/cpp/bindings/lib/native_struct_serialization.cc",
+ "mojo/public/cpp/bindings/lib/validation_util.cc",
+ "mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc",
+ "mojo/public/cpp/bindings/lib/filter_chain.cc",
+ "mojo/public/cpp/bindings/lib/message.cc",
+ "mojo/public/cpp/bindings/lib/unserialized_message_context.cc",
+ "mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc",
+ "mojo/public/cpp/bindings/lib/control_message_proxy.cc",
+ "mojo/public/cpp/bindings/lib/control_message_handler.cc",
+ "mojo/public/cpp/bindings/lib/connector.cc",
+ "mojo/public/cpp/bindings/lib/interface_endpoint_client.cc",
+ "mojo/public/cpp/bindings/lib/sync_handle_registry.cc",
+ "mojo/public/cpp/bindings/lib/associated_binding.cc",
+ "mojo/public/cpp/bindings/lib/message_header_validator.cc",
+ "mojo/public/cpp/bindings/lib/associated_group_controller.cc",
+ "mojo/public/cpp/bindings/lib/associated_interface_ptr.cc",
+ "mojo/public/cpp/bindings/lib/fixed_buffer.cc",
+ "mojo/public/cpp/bindings/lib/message_internal.cc",
+ "mojo/public/cpp/bindings/lib/binding_state.cc",
+ "mojo/public/cpp/platform/platform_channel_endpoint.cc",
+ "mojo/public/cpp/platform/platform_handle.cc",
+ "mojo/public/cpp/platform/named_platform_channel.cc",
+ "mojo/public/cpp/platform/platform_channel.cc",
+ "mojo/public/cpp/platform/platform_channel_server_endpoint.cc",
+ "mojo/public/cpp/platform/socket_utils_posix.cc",
+ "mojo/public/cpp/platform/named_platform_channel_posix.cc",
+ "mojo/public/cpp/system/buffer.cc",
+ "mojo/public/cpp/system/platform_handle.cc",
+ "mojo/public/cpp/system/wait.cc",
+ "mojo/public/cpp/system/wait_set.cc",
+ "mojo/public/cpp/system/data_pipe_utils.cc",
+ "mojo/public/cpp/system/scope_to_message_pipe.cc",
+ "mojo/public/cpp/system/handle_signal_tracker.cc",
+ "mojo/public/cpp/system/trap.cc",
+ "mojo/public/cpp/system/isolated_connection.cc",
+ "mojo/public/cpp/system/string_data_pipe_producer.cc",
+ "mojo/public/cpp/system/data_pipe_drainer.cc",
+ "mojo/public/cpp/system/invitation.cc",
+ "mojo/public/cpp/system/simple_watcher.cc",
+ "mojo/public/cpp/system/file_data_pipe_producer.cc",
+ "mojo/public/cpp/system/message_pipe.cc",
+ ] + get_target_outputs(":mojom_bindings_gen") +
+ get_target_outputs(":mojom_bindings_native_gen")
}
generate_pkg_config("libmojo") {
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000000..6d8601bb68
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: RESTRICTED
+}
diff --git a/base/android/android_hardware_buffer_abi.h b/base/android/android_hardware_buffer_abi.h
new file mode 100644
index 0000000000..7012532d18
--- /dev/null
+++ b/base/android/android_hardware_buffer_abi.h
@@ -0,0 +1,90 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_ANDROID_HARDWARE_BUFFER_ABI_H_
+#define BASE_ANDROID_ANDROID_HARDWARE_BUFFER_ABI_H_
+
+// Minimal binary interface definitions for AHardwareBuffer based on
+// include/android/hardware_buffer.h from the Android NDK for platform level
+// 26+. This is only intended for use from the AndroidHardwareBufferCompat
+// wrapper for building without NDK platform level support, it is not a
+// general-use header and is not complete.
+//
+// TODO(crbug.com/771171): Delete this file when third_party/android_ndk/
+// is updated to a version that contains the android/hardware_buffer.h file.
+//
+// Please refer to the API documentation for details:
+// https://developer.android.com/ndk/reference/hardware__buffer_8h.html
+
+#include <stdint.h>
+
+// Use "C" linkage to match the original header file. This isn't strictly
+// required since the file is not declaring global functions, but the types
+// should remain in the global namespace for compatibility, and it's a reminder
+// that forward declarations elsewhere should use "extern "C" to avoid
+// namespace issues.
+extern "C" {
+
+typedef struct AHardwareBuffer AHardwareBuffer;
+typedef struct ARect ARect;
+
+enum {
+ AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM = 1,
+ AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM = 2,
+ AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM = 3,
+ AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM = 4,
+ AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT = 0x16,
+ AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM = 0x2b,
+ AHARDWAREBUFFER_FORMAT_BLOB = 0x21,
+};
+
+enum {
+ AHARDWAREBUFFER_USAGE_CPU_READ_NEVER = 0UL,
+ AHARDWAREBUFFER_USAGE_CPU_READ_RARELY = 2UL,
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN = 3UL,
+ AHARDWAREBUFFER_USAGE_CPU_READ_MASK = 0xFUL,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER = 0UL << 4,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY = 2UL << 4,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN = 3UL << 4,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK = 0xFUL << 4,
+ AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE = 1UL << 8,
+ AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT = 1UL << 9,
+ AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT = 1UL << 14,
+ AHARDWAREBUFFER_USAGE_VIDEO_ENCODE = 1UL << 16,
+ AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA = 1UL << 23,
+ AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER = 1UL << 24,
+};
+
+typedef struct AHardwareBuffer_Desc {
+ uint32_t width;
+ uint32_t height;
+ uint32_t layers;
+ uint32_t format;
+ uint64_t usage;
+ uint32_t stride;
+ uint32_t rfu0;
+ uint64_t rfu1;
+} AHardwareBuffer_Desc;
+
+using PFAHardwareBuffer_allocate = void (*)(const AHardwareBuffer_Desc* desc,
+ AHardwareBuffer** outBuffer);
+using PFAHardwareBuffer_acquire = void (*)(AHardwareBuffer* buffer);
+using PFAHardwareBuffer_describe = void (*)(const AHardwareBuffer* buffer,
+ AHardwareBuffer_Desc* outDesc);
+using PFAHardwareBuffer_lock = int (*)(AHardwareBuffer* buffer,
+ uint64_t usage,
+ int32_t fence,
+ const ARect* rect,
+ void** outVirtualAddress);
+using PFAHardwareBuffer_recvHandleFromUnixSocket =
+ int (*)(int socketFd, AHardwareBuffer** outBuffer);
+using PFAHardwareBuffer_release = void (*)(AHardwareBuffer* buffer);
+using PFAHardwareBuffer_sendHandleToUnixSocket =
+ int (*)(const AHardwareBuffer* buffer, int socketFd);
+using PFAHardwareBuffer_unlock = int (*)(AHardwareBuffer* buffer,
+ int32_t* fence);
+
+} // extern "C"
+
+#endif // BASE_ANDROID_ANDROID_HARDWARE_BUFFER_ABI_H_
diff --git a/base/android/android_hardware_buffer_compat.cc b/base/android/android_hardware_buffer_compat.cc
new file mode 100644
index 0000000000..70f058947b
--- /dev/null
+++ b/base/android/android_hardware_buffer_compat.cc
@@ -0,0 +1,129 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/android_hardware_buffer_compat.h"
+
+#include "base/android/build_info.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+
+#include <dlfcn.h>
+
+namespace base {
+
+namespace {
+
+static base::LazyInstance<AndroidHardwareBufferCompat>::Leaky g_compat =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+AndroidHardwareBufferCompat::AndroidHardwareBufferCompat() {
+ DCHECK(IsSupportAvailable());
+
+ // TODO(klausw): If the Chromium build requires __ANDROID_API__ >= 26 at some
+ // point in the future, we could directly use the global functions instead of
+ // dynamic loading. However, since this would be incompatible with pre-Oreo
+ // devices, this is unlikely to happen in the foreseeable future, so just
+ // unconditionally use dynamic loading.
+
+ // cf. base/android/linker/modern_linker_jni.cc
+ void* main_dl_handle = dlopen(nullptr, RTLD_NOW);
+
+ *reinterpret_cast<void**>(&allocate_) =
+ dlsym(main_dl_handle, "AHardwareBuffer_allocate");
+ DCHECK(allocate_);
+
+ *reinterpret_cast<void**>(&acquire_) =
+ dlsym(main_dl_handle, "AHardwareBuffer_acquire");
+ DCHECK(acquire_);
+
+ *reinterpret_cast<void**>(&describe_) =
+ dlsym(main_dl_handle, "AHardwareBuffer_describe");
+ DCHECK(describe_);
+
+ *reinterpret_cast<void**>(&lock_) =
+ dlsym(main_dl_handle, "AHardwareBuffer_lock");
+ DCHECK(lock_);
+
+ *reinterpret_cast<void**>(&recv_handle_) =
+ dlsym(main_dl_handle, "AHardwareBuffer_recvHandleFromUnixSocket");
+ DCHECK(recv_handle_);
+
+ *reinterpret_cast<void**>(&release_) =
+ dlsym(main_dl_handle, "AHardwareBuffer_release");
+ DCHECK(release_);
+
+ *reinterpret_cast<void**>(&send_handle_) =
+ dlsym(main_dl_handle, "AHardwareBuffer_sendHandleToUnixSocket");
+ DCHECK(send_handle_);
+
+ *reinterpret_cast<void**>(&unlock_) =
+ dlsym(main_dl_handle, "AHardwareBuffer_unlock");
+ DCHECK(unlock_);
+}
+
+// static
+bool AndroidHardwareBufferCompat::IsSupportAvailable() {
+ return base::android::BuildInfo::GetInstance()->sdk_int() >=
+ base::android::SDK_VERSION_OREO;
+}
+
+// static
+AndroidHardwareBufferCompat AndroidHardwareBufferCompat::GetInstance() {
+ return g_compat.Get();
+}
+
+void AndroidHardwareBufferCompat::Allocate(const AHardwareBuffer_Desc* desc,
+ AHardwareBuffer** out_buffer) {
+ DCHECK(IsSupportAvailable());
+ allocate_(desc, out_buffer);
+}
+
+void AndroidHardwareBufferCompat::Acquire(AHardwareBuffer* buffer) {
+ DCHECK(IsSupportAvailable());
+ acquire_(buffer);
+}
+
+void AndroidHardwareBufferCompat::Describe(const AHardwareBuffer* buffer,
+ AHardwareBuffer_Desc* out_desc) {
+ DCHECK(IsSupportAvailable());
+ describe_(buffer, out_desc);
+}
+
+int AndroidHardwareBufferCompat::Lock(AHardwareBuffer* buffer,
+ uint64_t usage,
+ int32_t fence,
+ const ARect* rect,
+ void** out_virtual_address) {
+ DCHECK(IsSupportAvailable());
+ return lock_(buffer, usage, fence, rect, out_virtual_address);
+}
+
+int AndroidHardwareBufferCompat::RecvHandleFromUnixSocket(
+ int socket_fd,
+ AHardwareBuffer** out_buffer) {
+ DCHECK(IsSupportAvailable());
+ return recv_handle_(socket_fd, out_buffer);
+}
+
+void AndroidHardwareBufferCompat::Release(AHardwareBuffer* buffer) {
+ DCHECK(IsSupportAvailable());
+ release_(buffer);
+}
+
+int AndroidHardwareBufferCompat::SendHandleToUnixSocket(
+ const AHardwareBuffer* buffer,
+ int socket_fd) {
+ DCHECK(IsSupportAvailable());
+ return send_handle_(buffer, socket_fd);
+}
+
+int AndroidHardwareBufferCompat::Unlock(AHardwareBuffer* buffer,
+ int32_t* fence) {
+ DCHECK(IsSupportAvailable());
+ return unlock_(buffer, fence);
+}
+
+} // namespace base
diff --git a/base/android/android_hardware_buffer_compat.h b/base/android/android_hardware_buffer_compat.h
new file mode 100644
index 0000000000..14be3d5b9c
--- /dev/null
+++ b/base/android/android_hardware_buffer_compat.h
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_ANDROID_HARDWARE_BUFFER_COMPAT_H_
+#define BASE_ANDROID_ANDROID_HARDWARE_BUFFER_COMPAT_H_
+
+#include "base/android/android_hardware_buffer_abi.h"
+#include "base/base_export.h"
+#include "base/lazy_instance.h"
+
+namespace base {
+
+// This class provides runtime support for working with AHardwareBuffer objects
+// on Android O systems without requiring building for the Android O NDK level.
+// Don't call GetInstance() unless IsSupportAvailable() returns true.
+class BASE_EXPORT AndroidHardwareBufferCompat {
+ public:
+ static bool IsSupportAvailable();
+ static AndroidHardwareBufferCompat GetInstance();
+
+ void Allocate(const AHardwareBuffer_Desc* desc, AHardwareBuffer** outBuffer);
+ void Acquire(AHardwareBuffer* buffer);
+ void Describe(const AHardwareBuffer* buffer, AHardwareBuffer_Desc* outDesc);
+ int Lock(AHardwareBuffer* buffer,
+ uint64_t usage,
+ int32_t fence,
+ const ARect* rect,
+ void** out_virtual_address);
+ int RecvHandleFromUnixSocket(int socketFd, AHardwareBuffer** outBuffer);
+ void Release(AHardwareBuffer* buffer);
+ int SendHandleToUnixSocket(const AHardwareBuffer* buffer, int socketFd);
+ int Unlock(AHardwareBuffer* buffer, int32_t* fence);
+
+ private:
+ friend struct base::LazyInstanceTraitsBase<AndroidHardwareBufferCompat>;
+ AndroidHardwareBufferCompat();
+
+ PFAHardwareBuffer_allocate allocate_;
+ PFAHardwareBuffer_acquire acquire_;
+ PFAHardwareBuffer_describe describe_;
+ PFAHardwareBuffer_lock lock_;
+ PFAHardwareBuffer_recvHandleFromUnixSocket recv_handle_;
+ PFAHardwareBuffer_release release_;
+ PFAHardwareBuffer_sendHandleToUnixSocket send_handle_;
+ PFAHardwareBuffer_unlock unlock_;
+};
+
+} // namespace base
+
+#endif // BASE_ANDROID_ANDROID_HARDWARE_BUFFER_COMPAT_H_
diff --git a/base/android/android_image_reader_abi.h b/base/android/android_image_reader_abi.h
new file mode 100644
index 0000000000..c81087fe59
--- /dev/null
+++ b/base/android/android_image_reader_abi.h
@@ -0,0 +1,97 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_ANDROID_IMAGE_READER_ABI_H_
+#define BASE_ANDROID_ANDROID_IMAGE_READER_ABI_H_
+
+// Minimal binary interface definitions for AImage,AImageReader
+// and ANativeWindow based on include/media/NdkImage.h,
+// include/media/NdkImageReader.h and include/android/native_window_jni.h
+// from the Android NDK for platform level 26+. This is only
+// intended for use from the AndroidImageReader wrapper for building
+// without NDK platform level support, it is not a general-use header
+// and is not complete. Only the functions/data types which
+// are currently needed by media/gpu/android/image_reader_gl_owner.h are
+// included in this ABI
+//
+// Please refer to the API documentation for details:
+// https://developer.android.com/ndk/reference/group/media (AIMage and
+// AImageReader)
+// https://developer.android.com/ndk/reference/group/native-activity
+// (ANativeWindow)
+
+#include <android/native_window.h>
+#include <media/NdkMediaError.h>
+
+#include <jni.h>
+#include <stdint.h>
+
+// Use "C" linkage to match the original header file. This isn't strictly
+// required since the file is not declaring global functions, but the types
+// should remain in the global namespace for compatibility, and it's a reminder
+// that forward declarations elsewhere should use "extern "C" to avoid
+// namespace issues.
+extern "C" {
+
+// For AImage
+typedef struct AHardwareBuffer AHardwareBuffer;
+
+typedef struct AImage AImage;
+
+enum AIMAGE_FORMATS {
+ AIMAGE_FORMAT_YUV_420_888 = 0x23,
+ IMAGE_FORMAT_PRIVATE = 0x22
+};
+
+using pAImage_delete = void (*)(AImage* image);
+
+using pAImage_deleteAsync = void (*)(AImage* image, int releaseFenceFd);
+
+using pAImage_getHardwareBuffer = media_status_t (*)(const AImage* image,
+ AHardwareBuffer** buffer);
+
+using pAImage_getWidth = media_status_t (*)(const AImage* image,
+ int32_t* width);
+
+using pAImage_getHeight = media_status_t (*)(const AImage* image,
+ int32_t* height);
+
+// For AImageReader
+
+typedef struct AImageReader AImageReader;
+
+typedef void (*AImageReader_ImageCallback)(void* context, AImageReader* reader);
+
+typedef struct AImageReader_ImageListener {
+ void* context;
+ AImageReader_ImageCallback onImageAvailable;
+} AImageReader_ImageListener;
+
+using pAImageReader_new = media_status_t (*)(int32_t width,
+ int32_t height,
+ int32_t format,
+ int32_t maxImages,
+ AImageReader** reader);
+
+using pAImageReader_setImageListener =
+ media_status_t (*)(AImageReader* reader,
+ AImageReader_ImageListener* listener);
+
+using pAImageReader_delete = void (*)(AImageReader* reader);
+
+using pAImageReader_getWindow = media_status_t (*)(AImageReader* reader,
+ ANativeWindow** window);
+
+using pAImageReader_acquireLatestImageAsync =
+ media_status_t (*)(AImageReader* reader,
+ AImage** image,
+ int* acquireFenceFd);
+
+// For ANativeWindow
+using pANativeWindow_toSurface = jobject (*)(JNIEnv* env,
+ ANativeWindow* window);
+
+} // extern "C"
+
+#endif // BASE_ANDROID_ANDROID_IMAGE_READER_ABI_H_
diff --git a/base/android/android_image_reader_compat.cc b/base/android/android_image_reader_compat.cc
new file mode 100644
index 0000000000..0b08c179d8
--- /dev/null
+++ b/base/android/android_image_reader_compat.cc
@@ -0,0 +1,142 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/android_image_reader_compat.h"
+
+#include <dlfcn.h>
+
+#include "base/android/build_info.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+
+#define LOAD_FUNCTION(lib, func) \
+ do { \
+ func##_ = reinterpret_cast<p##func>(dlsym(lib, #func)); \
+ if (!func##_) { \
+ DLOG(ERROR) << "Unable to load function " << #func; \
+ return false; \
+ } \
+ } while (0)
+
+namespace base {
+namespace android {
+
+AndroidImageReader& AndroidImageReader::GetInstance() {
+ // C++11 static local variable initialization is
+ // thread-safe.
+ static base::NoDestructor<AndroidImageReader> instance;
+ return *instance;
+}
+
+bool AndroidImageReader::IsSupported() {
+ return is_supported_;
+}
+
+AndroidImageReader::AndroidImageReader() {
+ is_supported_ = LoadFunctions();
+}
+
+bool AndroidImageReader::LoadFunctions() {
+ // If the Chromium build requires __ANDROID_API__ >= 26 at some
+ // point in the future, we could directly use the global functions instead of
+ // dynamic loading. However, since this would be incompatible with pre-Oreo
+ // devices, this is unlikely to happen in the foreseeable future, so we use
+ // dynamic loading.
+
+ // Functions are not present for android version older than OREO
+ if (base::android::BuildInfo::GetInstance()->sdk_int() <
+ base::android::SDK_VERSION_OREO) {
+ return false;
+ }
+
+ void* libmediandk = dlopen("libmediandk.so", RTLD_NOW);
+ if (libmediandk == nullptr) {
+ LOG(ERROR) << "Couldnt open libmediandk.so";
+ return false;
+ }
+
+ LOAD_FUNCTION(libmediandk, AImage_delete);
+ LOAD_FUNCTION(libmediandk, AImage_deleteAsync);
+ LOAD_FUNCTION(libmediandk, AImage_getHardwareBuffer);
+ LOAD_FUNCTION(libmediandk, AImage_getWidth);
+ LOAD_FUNCTION(libmediandk, AImage_getHeight);
+ LOAD_FUNCTION(libmediandk, AImageReader_new);
+ LOAD_FUNCTION(libmediandk, AImageReader_setImageListener);
+ LOAD_FUNCTION(libmediandk, AImageReader_delete);
+ LOAD_FUNCTION(libmediandk, AImageReader_getWindow);
+ LOAD_FUNCTION(libmediandk, AImageReader_acquireLatestImageAsync);
+
+ void* libandroid = dlopen("libandroid.so", RTLD_NOW);
+ if (libandroid == nullptr) {
+ LOG(ERROR) << "Couldnt open libandroid.so";
+ return false;
+ }
+
+ LOAD_FUNCTION(libandroid, ANativeWindow_toSurface);
+
+ return true;
+}
+
+void AndroidImageReader::AImage_delete(AImage* image) {
+ AImage_delete_(image);
+}
+
+void AndroidImageReader::AImage_deleteAsync(AImage* image, int releaseFenceFd) {
+ AImage_deleteAsync_(image, releaseFenceFd);
+}
+
+media_status_t AndroidImageReader::AImage_getHardwareBuffer(
+ const AImage* image,
+ AHardwareBuffer** buffer) {
+ return AImage_getHardwareBuffer_(image, buffer);
+}
+
+media_status_t AndroidImageReader::AImage_getWidth(const AImage* image,
+ int32_t* width) {
+ return AImage_getWidth_(image, width);
+}
+
+media_status_t AndroidImageReader::AImage_getHeight(const AImage* image,
+ int32_t* height) {
+ return AImage_getHeight_(image, height);
+}
+
+media_status_t AndroidImageReader::AImageReader_new(int32_t width,
+ int32_t height,
+ int32_t format,
+ int32_t maxImages,
+ AImageReader** reader) {
+ return AImageReader_new_(width, height, format, maxImages, reader);
+}
+
+media_status_t AndroidImageReader::AImageReader_setImageListener(
+ AImageReader* reader,
+ AImageReader_ImageListener* listener) {
+ return AImageReader_setImageListener_(reader, listener);
+}
+
+void AndroidImageReader::AImageReader_delete(AImageReader* reader) {
+ AImageReader_delete_(reader);
+}
+
+media_status_t AndroidImageReader::AImageReader_getWindow(
+ AImageReader* reader,
+ ANativeWindow** window) {
+ return AImageReader_getWindow_(reader, window);
+}
+
+media_status_t AndroidImageReader::AImageReader_acquireLatestImageAsync(
+ AImageReader* reader,
+ AImage** image,
+ int* acquireFenceFd) {
+ return AImageReader_acquireLatestImageAsync_(reader, image, acquireFenceFd);
+}
+
+jobject AndroidImageReader::ANativeWindow_toSurface(JNIEnv* env,
+ ANativeWindow* window) {
+ return ANativeWindow_toSurface_(env, window);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/android_image_reader_compat.h b/base/android/android_image_reader_compat.h
new file mode 100644
index 0000000000..5d5d881f47
--- /dev/null
+++ b/base/android/android_image_reader_compat.h
@@ -0,0 +1,79 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_ANDROID_IMAGE_READER_COMPAT_H_
+#define BASE_ANDROID_ANDROID_IMAGE_READER_COMPAT_H_
+
+#include "base/android/android_image_reader_abi.h"
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/no_destructor.h"
+
+namespace base {
+namespace android {
+
+// This class provides runtime support for working with AImage, AImageReader and
+// ANativeWindow objects on Android O systems without requiring building for the
+// Android O NDK level. Don't call GetInstance() unless IsSupported() returns
+// true.
+class BASE_EXPORT AndroidImageReader {
+ public:
+ // Thread safe GetInstance.
+ static AndroidImageReader& GetInstance();
+
+ // Check if the image reader usage is supported. This function returns TRUE
+ // if android version is >=OREO, the media flag is enabled and all the
+ // required functions are loaded.
+ bool IsSupported();
+
+ // Naming convention of all the below functions are chosen to exactly match
+ // the function names in the NDK.
+ void AImage_delete(AImage* image);
+ void AImage_deleteAsync(AImage* image, int releaseFenceFd);
+ media_status_t AImage_getHardwareBuffer(const AImage* image,
+ AHardwareBuffer** buffer);
+ media_status_t AImage_getWidth(const AImage* image, int32_t* width);
+ media_status_t AImage_getHeight(const AImage* image, int32_t* height);
+ media_status_t AImageReader_new(int32_t width,
+ int32_t height,
+ int32_t format,
+ int32_t maxImages,
+ AImageReader** reader);
+ media_status_t AImageReader_setImageListener(
+ AImageReader* reader,
+ AImageReader_ImageListener* listener);
+ void AImageReader_delete(AImageReader* reader);
+ media_status_t AImageReader_getWindow(AImageReader* reader,
+ ANativeWindow** window);
+ media_status_t AImageReader_acquireLatestImageAsync(AImageReader* reader,
+ AImage** image,
+ int* acquireFenceFd);
+ jobject ANativeWindow_toSurface(JNIEnv* env, ANativeWindow* window);
+
+ private:
+ friend class base::NoDestructor<AndroidImageReader>;
+
+ AndroidImageReader();
+ bool LoadFunctions();
+
+ bool is_supported_;
+ pAImage_delete AImage_delete_;
+ pAImage_deleteAsync AImage_deleteAsync_;
+ pAImage_getHardwareBuffer AImage_getHardwareBuffer_;
+ pAImage_getWidth AImage_getWidth_;
+ pAImage_getHeight AImage_getHeight_;
+ pAImageReader_new AImageReader_new_;
+ pAImageReader_setImageListener AImageReader_setImageListener_;
+ pAImageReader_delete AImageReader_delete_;
+ pAImageReader_getWindow AImageReader_getWindow_;
+ pAImageReader_acquireLatestImageAsync AImageReader_acquireLatestImageAsync_;
+ pANativeWindow_toSurface ANativeWindow_toSurface_;
+
+ DISALLOW_COPY_AND_ASSIGN(AndroidImageReader);
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_ANDROID_IMAGE_READER_COMPAT_H_
diff --git a/base/android/android_image_reader_compat_unittest.cc b/base/android/android_image_reader_compat_unittest.cc
new file mode 100644
index 0000000000..756ec9f140
--- /dev/null
+++ b/base/android/android_image_reader_compat_unittest.cc
@@ -0,0 +1,43 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/android_image_reader_compat.h"
+
+#include <stdint.h>
+#include <memory>
+
+#include "base/android/build_info.h"
+#include "base/test/scoped_feature_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+class AndroidImageReaderTest : public testing::Test {
+ public:
+ AndroidImageReaderTest() = default;
+ ~AndroidImageReaderTest() override = default;
+};
+
+// Getting instance of AndroidImageReader will invoke AndroidImageReader
+// constructor which will dlopen the mediandk and androidndk .so files and do
+// all the required symbol lookups.
+TEST_F(AndroidImageReaderTest, GetImageReaderInstance) {
+ // It is expected that image reader support will be available from android
+ // version OREO.
+ EXPECT_EQ(AndroidImageReader::GetInstance().IsSupported(),
+ base::android::BuildInfo::GetInstance()->sdk_int() >=
+ base::android::SDK_VERSION_OREO);
+}
+
+// There should be only 1 instance of AndroidImageReader im memory. Hence 2
+// instances should have same memory address.
+TEST_F(AndroidImageReaderTest, CompareImageReaderInstance) {
+ AndroidImageReader& a1 = AndroidImageReader::GetInstance();
+ AndroidImageReader& a2 = AndroidImageReader::GetInstance();
+ ASSERT_EQ(&a1, &a2);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/animation_frame_time_histogram.cc b/base/android/animation_frame_time_histogram.cc
new file mode 100644
index 0000000000..23dffd84a0
--- /dev/null
+++ b/base/android/animation_frame_time_histogram.cc
@@ -0,0 +1,26 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_string.h"
+#include "base/metrics/histogram_macros.h"
+#include "jni/AnimationFrameTimeHistogram_jni.h"
+
+using base::android::JavaParamRef;
+
+// static
+void JNI_AnimationFrameTimeHistogram_SaveHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ const JavaParamRef<jstring>& j_histogram_name,
+ const JavaParamRef<jlongArray>& j_frame_times_ms,
+ jint j_count) {
+ jlong *frame_times_ms = env->GetLongArrayElements(j_frame_times_ms, NULL);
+ std::string histogram_name = base::android::ConvertJavaStringToUTF8(
+ env, j_histogram_name);
+
+ for (int i = 0; i < j_count; ++i) {
+ UMA_HISTOGRAM_TIMES(histogram_name.c_str(),
+ base::TimeDelta::FromMilliseconds(frame_times_ms[i]));
+ }
+}
diff --git a/base/android/apk_assets.cc b/base/android/apk_assets.cc
new file mode 100644
index 0000000000..de468b497f
--- /dev/null
+++ b/base/android/apk_assets.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <jni.h>
+
+#include "base/android/apk_assets.h"
+
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/file_descriptor_store.h"
+#include "jni/ApkAssets_jni.h"
+
+namespace base {
+namespace android {
+
+int OpenApkAsset(const std::string& file_path,
+ base::MemoryMappedFile::Region* region) {
+ // The AssetManager API of the NDK does not expose a method for accessing raw
+ // resources :(
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jlongArray> jarr = Java_ApkAssets_open(
+ env, base::android::ConvertUTF8ToJavaString(env, file_path));
+ std::vector<jlong> results;
+ base::android::JavaLongArrayToLongVector(env, jarr.obj(), &results);
+ CHECK_EQ(3U, results.size());
+ int fd = static_cast<int>(results[0]);
+ region->offset = results[1];
+ region->size = results[2];
+ return fd;
+}
+
+bool RegisterApkAssetWithFileDescriptorStore(const std::string& key,
+ const base::FilePath& file_path) {
+ base::MemoryMappedFile::Region region =
+ base::MemoryMappedFile::Region::kWholeFile;
+ int asset_fd = OpenApkAsset(file_path.value(), &region);
+ if (asset_fd == -1)
+ return false;
+ base::FileDescriptorStore::GetInstance().Set(key, base::ScopedFD(asset_fd),
+ region);
+ return true;
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/apk_assets.h b/base/android/apk_assets.h
new file mode 100644
index 0000000000..cdac0000af
--- /dev/null
+++ b/base/android/apk_assets.h
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_APK_ASSETS_H_
+#define BASE_ANDROID_APK_ASSETS_H_
+
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+
+namespace base {
+namespace android {
+
+// Opens an asset (e.g. a .pak file) from the apk.
+// Can be used from renderer process.
+// Fails if the asset is not stored uncompressed within the .apk.
+// Returns: The File Descriptor of the asset, or -1 upon failure.
+// Input arguments:
+// - |file_path|: Path to file within .apk. e.g.: assets/foo.pak
+// Output arguments:
+// - |region|: size & offset (in bytes) within the .apk of the asset.
+BASE_EXPORT int OpenApkAsset(
+ const std::string& file_path,
+ base::MemoryMappedFile::Region* region);
+
+// Registers an uncompressed asset from within the apk in the
+// FileDescriptorStore.
+// Returns: true in case of success, false otherwise.
+BASE_EXPORT bool RegisterApkAssetWithFileDescriptorStore(
+ const std::string& key,
+ const base::FilePath& file_path);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_APK_ASSETS_H_
diff --git a/base/android/application_status_listener.cc b/base/android/application_status_listener.cc
new file mode 100644
index 0000000000..c8c2cc68e9
--- /dev/null
+++ b/base/android/application_status_listener.cc
@@ -0,0 +1,78 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/application_status_listener.h"
+
+#include <jni.h>
+
+#include "base/lazy_instance.h"
+#include "base/observer_list_threadsafe.h"
+#include "jni/ApplicationStatus_jni.h"
+
+namespace base {
+namespace android {
+
+namespace {
+
+struct LeakyLazyObserverListTraits :
+ base::internal::LeakyLazyInstanceTraits<
+ ObserverListThreadSafe<ApplicationStatusListener> > {
+ static ObserverListThreadSafe<ApplicationStatusListener>*
+ New(void* instance) {
+ ObserverListThreadSafe<ApplicationStatusListener>* ret =
+ base::internal::LeakyLazyInstanceTraits<ObserverListThreadSafe<
+ ApplicationStatusListener>>::New(instance);
+ // Leaky.
+ ret->AddRef();
+ return ret;
+ }
+};
+
+LazyInstance<ObserverListThreadSafe<ApplicationStatusListener>,
+ LeakyLazyObserverListTraits> g_observers =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+ApplicationStatusListener::ApplicationStatusListener(
+ const ApplicationStatusListener::ApplicationStateChangeCallback& callback)
+ : callback_(callback) {
+ DCHECK(!callback_.is_null());
+ g_observers.Get().AddObserver(this);
+
+ Java_ApplicationStatus_registerThreadSafeNativeApplicationStateListener(
+ AttachCurrentThread());
+}
+
+ApplicationStatusListener::~ApplicationStatusListener() {
+ g_observers.Get().RemoveObserver(this);
+}
+
+void ApplicationStatusListener::Notify(ApplicationState state) {
+ callback_.Run(state);
+}
+
+// static
+void ApplicationStatusListener::NotifyApplicationStateChange(
+ ApplicationState state) {
+ g_observers.Get().Notify(FROM_HERE, &ApplicationStatusListener::Notify,
+ state);
+}
+
+// static
+ApplicationState ApplicationStatusListener::GetState() {
+ return static_cast<ApplicationState>(
+ Java_ApplicationStatus_getStateForApplication(AttachCurrentThread()));
+}
+
+static void JNI_ApplicationStatus_OnApplicationStateChange(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ jint new_state) {
+ ApplicationState application_state = static_cast<ApplicationState>(new_state);
+ ApplicationStatusListener::NotifyApplicationStateChange(application_state);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/application_status_listener.h b/base/android/application_status_listener.h
new file mode 100644
index 0000000000..fcc26a2946
--- /dev/null
+++ b/base/android/application_status_listener.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_APPLICATION_STATUS_LISTENER_H_
+#define BASE_ANDROID_APPLICATION_STATUS_LISTENER_H_
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list_threadsafe.h"
+
+namespace base {
+namespace android {
+
+// Define application state values like APPLICATION_STATE_VISIBLE in a
+// way that ensures they're always the same than their Java counterpart.
+//
+// Note that these states represent the most visible Activity state.
+// If there are activities with states paused and stopped, only
+// HAS_PAUSED_ACTIVITIES should be returned.
+//
+// A Java counterpart will be generated for this enum.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base
+enum ApplicationState {
+ APPLICATION_STATE_UNKNOWN = 0,
+ APPLICATION_STATE_HAS_RUNNING_ACTIVITIES = 1,
+ APPLICATION_STATE_HAS_PAUSED_ACTIVITIES = 2,
+ APPLICATION_STATE_HAS_STOPPED_ACTIVITIES = 3,
+ APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES = 4
+};
+
+// A native helper class to listen to state changes of the Android
+// Application. This mirrors org.chromium.base.ApplicationStatus.
+// any thread.
+//
+// To start listening, create a new instance, passing a callback to a
+// function that takes an ApplicationState parameter. To stop listening,
+// simply delete the listener object. The implementation guarantees
+// that the callback will always be called on the thread that created
+// the listener.
+//
+// Example:
+//
+// void OnApplicationStateChange(ApplicationState state) {
+// ...
+// }
+//
+// // Start listening.
+// ApplicationStatusListener* my_listener =
+// new ApplicationStatusListener(
+// base::Bind(&OnApplicationStateChange));
+//
+// ...
+//
+// // Stop listening.
+// delete my_listener
+//
+class BASE_EXPORT ApplicationStatusListener {
+ public:
+ typedef base::Callback<void(ApplicationState)> ApplicationStateChangeCallback;
+
+ explicit ApplicationStatusListener(
+ const ApplicationStateChangeCallback& callback);
+ ~ApplicationStatusListener();
+
+ // Internal use only: must be public to be called from JNI and unit tests.
+ static void NotifyApplicationStateChange(ApplicationState state);
+
+ // Expose jni call for ApplicationStatus.getStateForApplication.
+ static ApplicationState GetState();
+
+ private:
+ void Notify(ApplicationState state);
+
+ ApplicationStateChangeCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApplicationStatusListener);
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_APPLICATION_STATUS_LISTENER_H_
diff --git a/base/android/application_status_listener_unittest.cc b/base/android/application_status_listener_unittest.cc
new file mode 100644
index 0000000000..803dedb128
--- /dev/null
+++ b/base/android/application_status_listener_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/application_status_listener.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+namespace {
+
+using base::android::ScopedJavaLocalRef;
+
+// An invalid ApplicationState value.
+const ApplicationState kInvalidApplicationState =
+ static_cast<ApplicationState>(100);
+
+// Used to generate a callback that stores the new state at a given location.
+void StoreStateTo(ApplicationState* target, ApplicationState state) {
+ *target = state;
+}
+
+void RunTasksUntilIdle() {
+ RunLoop run_loop;
+ run_loop.RunUntilIdle();
+}
+
+// Shared state for the multi-threaded test.
+// This uses a thread to register for events and listen to them, while state
+// changes are forced on the main thread.
+class MultiThreadedTest {
+ public:
+ MultiThreadedTest()
+ : state_(kInvalidApplicationState),
+ event_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ thread_("ApplicationStatusTest thread"),
+ main_() {}
+
+ void Run() {
+ // Start the thread and tell it to register for events.
+ thread_.Start();
+ thread_.task_runner()->PostTask(
+ FROM_HERE, base::Bind(&MultiThreadedTest::RegisterThreadForEvents,
+ base::Unretained(this)));
+
+ // Wait for its completion.
+ event_.Wait();
+
+ // Change state, then wait for the thread to modify state.
+ ApplicationStatusListener::NotifyApplicationStateChange(
+ APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+ event_.Wait();
+ EXPECT_EQ(APPLICATION_STATE_HAS_RUNNING_ACTIVITIES, state_);
+
+ // Again
+ ApplicationStatusListener::NotifyApplicationStateChange(
+ APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES);
+ event_.Wait();
+ EXPECT_EQ(APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES, state_);
+ }
+
+ private:
+ void ExpectOnThread() {
+ EXPECT_EQ(thread_.message_loop(), base::MessageLoop::current());
+ }
+
+ void RegisterThreadForEvents() {
+ ExpectOnThread();
+ listener_.reset(new ApplicationStatusListener(base::Bind(
+ &MultiThreadedTest::StoreStateAndSignal, base::Unretained(this))));
+ EXPECT_TRUE(listener_.get());
+ event_.Signal();
+ }
+
+ void StoreStateAndSignal(ApplicationState state) {
+ ExpectOnThread();
+ state_ = state;
+ event_.Signal();
+ }
+
+ ApplicationState state_;
+ base::WaitableEvent event_;
+ base::Thread thread_;
+ base::MessageLoop main_;
+ std::unique_ptr<ApplicationStatusListener> listener_;
+};
+
+} // namespace
+
+TEST(ApplicationStatusListenerTest, SingleThread) {
+ MessageLoop message_loop;
+
+ ApplicationState result = kInvalidApplicationState;
+
+ // Create a new listener that stores the new state into |result| on every
+ // state change.
+ ApplicationStatusListener listener(
+ base::Bind(&StoreStateTo, base::Unretained(&result)));
+
+ EXPECT_EQ(kInvalidApplicationState, result);
+
+ ApplicationStatusListener::NotifyApplicationStateChange(
+ APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+ RunTasksUntilIdle();
+ EXPECT_EQ(APPLICATION_STATE_HAS_RUNNING_ACTIVITIES, result);
+
+ ApplicationStatusListener::NotifyApplicationStateChange(
+ APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES);
+ RunTasksUntilIdle();
+ EXPECT_EQ(APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES, result);
+}
+
+TEST(ApplicationStatusListenerTest, TwoThreads) {
+ MultiThreadedTest test;
+ test.Run();
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/base_jni_onload.cc b/base/android/base_jni_onload.cc
new file mode 100644
index 0000000000..170dd8402f
--- /dev/null
+++ b/base/android/base_jni_onload.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/base_jni_onload.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_utils.h"
+#include "base/android/library_loader/library_loader_hooks.h"
+#include "base/bind.h"
+
+namespace base {
+namespace android {
+
+bool OnJNIOnLoadInit() {
+ InitAtExitManager();
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::InitReplacementClassLoader(env,
+ base::android::GetClassLoader(env));
+ return true;
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/callback_android.cc b/base/android/callback_android.cc
new file mode 100644
index 0000000000..7143664155
--- /dev/null
+++ b/base/android/callback_android.cc
@@ -0,0 +1,44 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/callback_android.h"
+
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "jni/Callback_jni.h"
+
+namespace base {
+namespace android {
+
+void RunObjectCallbackAndroid(const JavaRef<jobject>& callback,
+ const JavaRef<jobject>& arg) {
+ Java_Helper_onObjectResultFromNative(AttachCurrentThread(), callback, arg);
+}
+
+void RunBooleanCallbackAndroid(const JavaRef<jobject>& callback, bool arg) {
+ Java_Helper_onBooleanResultFromNative(AttachCurrentThread(), callback,
+ static_cast<jboolean>(arg));
+}
+
+void RunIntCallbackAndroid(const JavaRef<jobject>& callback, int arg) {
+ Java_Helper_onIntResultFromNative(AttachCurrentThread(), callback, arg);
+}
+
+void RunStringCallbackAndroid(const JavaRef<jobject>& callback,
+ const std::string& arg) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> java_string = ConvertUTF8ToJavaString(env, arg);
+ Java_Helper_onObjectResultFromNative(env, callback, java_string);
+}
+
+void RunByteArrayCallbackAndroid(const JavaRef<jobject>& callback,
+ const std::vector<uint8_t>& arg) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jbyteArray> j_bytes = ToJavaByteArray(env, arg);
+ Java_Helper_onObjectResultFromNative(env, callback, j_bytes);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/callback_android.h b/base/android/callback_android.h
new file mode 100644
index 0000000000..8a14c1fa39
--- /dev/null
+++ b/base/android/callback_android.h
@@ -0,0 +1,38 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_CALLBACK_ANDROID_H_
+#define BASE_ANDROID_CALLBACK_ANDROID_H_
+
+#include <jni.h>
+#include <string>
+#include <vector>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/base_export.h"
+
+// Provides helper utility methods that run the given callback with the
+// specified argument.
+namespace base {
+namespace android {
+
+void BASE_EXPORT RunObjectCallbackAndroid(const JavaRef<jobject>& callback,
+ const JavaRef<jobject>& arg);
+
+void BASE_EXPORT RunBooleanCallbackAndroid(const JavaRef<jobject>& callback,
+ bool arg);
+
+void BASE_EXPORT RunIntCallbackAndroid(const JavaRef<jobject>& callback,
+ int arg);
+
+void BASE_EXPORT RunStringCallbackAndroid(const JavaRef<jobject>& callback,
+ const std::string& arg);
+
+void BASE_EXPORT RunByteArrayCallbackAndroid(const JavaRef<jobject>& callback,
+ const std::vector<uint8_t>& arg);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_CALLBACK_ANDROID_H_
diff --git a/base/android/child_process_binding_types.h b/base/android/child_process_binding_types.h
new file mode 100644
index 0000000000..a3900d5e81
--- /dev/null
+++ b/base/android/child_process_binding_types.h
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_CHILD_PROCESS_BINDING_TYPES_H_
+#define BASE_ANDROID_CHILD_PROCESS_BINDING_TYPES_H_
+
+namespace base {
+namespace android {
+
+// Defines the state of bindgings with child process. See ChildProcessConnection
+// to see what the bindings are. Note these values are used as array indices.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base
+enum class ChildBindingState {
+ UNBOUND,
+ WAIVED,
+ MODERATE,
+ STRONG,
+ MAX_VALUE = STRONG
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_CHILD_PROCESS_BINDING_TYPES_H_
diff --git a/base/android/child_process_service.cc b/base/android/child_process_service.cc
new file mode 100644
index 0000000000..013a70b564
--- /dev/null
+++ b/base/android/child_process_service.cc
@@ -0,0 +1,79 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/library_loader/library_loader_hooks.h"
+#include "base/file_descriptor_store.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/posix/global_descriptors.h"
+#include "jni/ChildProcessService_jni.h"
+
+using base::android::JavaIntArrayToIntVector;
+using base::android::JavaParamRef;
+
+namespace base {
+namespace android {
+
+void JNI_ChildProcessService_RegisterFileDescriptors(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jobjectArray>& j_keys,
+ const JavaParamRef<jintArray>& j_ids,
+ const JavaParamRef<jintArray>& j_fds,
+ const JavaParamRef<jlongArray>& j_offsets,
+ const JavaParamRef<jlongArray>& j_sizes) {
+ std::vector<base::Optional<std::string>> keys;
+ jsize keys_size = env->GetArrayLength(j_keys);
+ keys.reserve(keys_size);
+ for (jsize i = 0; i < keys_size; i++) {
+ base::android::ScopedJavaLocalRef<jstring> str(
+ env, static_cast<jstring>(env->GetObjectArrayElement(j_keys, i)));
+ base::Optional<std::string> key;
+ if (!str.is_null()) {
+ key = base::android::ConvertJavaStringToUTF8(env, str);
+ }
+ keys.push_back(std::move(key));
+ }
+
+ std::vector<int> ids;
+ base::android::JavaIntArrayToIntVector(env, j_ids, &ids);
+ std::vector<int> fds;
+ base::android::JavaIntArrayToIntVector(env, j_fds, &fds);
+ std::vector<int64_t> offsets;
+ base::android::JavaLongArrayToInt64Vector(env, j_offsets, &offsets);
+ std::vector<int64_t> sizes;
+ base::android::JavaLongArrayToInt64Vector(env, j_sizes, &sizes);
+
+ DCHECK_EQ(keys.size(), ids.size());
+ DCHECK_EQ(ids.size(), fds.size());
+ DCHECK_EQ(fds.size(), offsets.size());
+ DCHECK_EQ(offsets.size(), sizes.size());
+
+ for (size_t i = 0; i < ids.size(); i++) {
+ base::MemoryMappedFile::Region region = {offsets.at(i), sizes.at(i)};
+ const base::Optional<std::string>& key = keys.at(i);
+ int id = ids.at(i);
+ int fd = fds.at(i);
+ if (key) {
+ base::FileDescriptorStore::GetInstance().Set(*key, base::ScopedFD(fd),
+ region);
+ } else {
+ base::GlobalDescriptors::GetInstance()->Set(id, fd, region);
+ }
+ }
+}
+
+void JNI_ChildProcessService_ExitChildProcess(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ VLOG(0) << "ChildProcessService: Exiting child process.";
+ base::android::LibraryLoaderExitHook();
+ _exit(0);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/command_line_android.cc b/base/android/command_line_android.cc
new file mode 100644
index 0000000000..c9b545f526
--- /dev/null
+++ b/base/android/command_line_android.cc
@@ -0,0 +1,89 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "jni/CommandLine_jni.h"
+
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
+using base::CommandLine;
+
+namespace {
+
+void JNI_CommandLine_AppendJavaStringArrayToCommandLine(
+ JNIEnv* env,
+ const JavaParamRef<jobjectArray>& array,
+ bool includes_program) {
+ std::vector<std::string> vec;
+ if (array)
+ base::android::AppendJavaStringArrayToStringVector(env, array, &vec);
+ if (!includes_program)
+ vec.insert(vec.begin(), std::string());
+ CommandLine extra_command_line(vec);
+ CommandLine::ForCurrentProcess()->AppendArguments(extra_command_line,
+ includes_program);
+}
+
+} // namespace
+
+static jboolean JNI_CommandLine_HasSwitch(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jswitch) {
+ std::string switch_string(ConvertJavaStringToUTF8(env, jswitch));
+ return CommandLine::ForCurrentProcess()->HasSwitch(switch_string);
+}
+
+static ScopedJavaLocalRef<jstring> JNI_CommandLine_GetSwitchValue(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jswitch) {
+ std::string switch_string(ConvertJavaStringToUTF8(env, jswitch));
+ std::string value(CommandLine::ForCurrentProcess()->GetSwitchValueNative(
+ switch_string));
+ if (value.empty())
+ return ScopedJavaLocalRef<jstring>();
+ return ConvertUTF8ToJavaString(env, value);
+}
+
+static void JNI_CommandLine_AppendSwitch(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jswitch) {
+ std::string switch_string(ConvertJavaStringToUTF8(env, jswitch));
+ CommandLine::ForCurrentProcess()->AppendSwitch(switch_string);
+}
+
+static void JNI_CommandLine_AppendSwitchWithValue(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jswitch,
+ const JavaParamRef<jstring>& jvalue) {
+ std::string switch_string(ConvertJavaStringToUTF8(env, jswitch));
+ std::string value_string (ConvertJavaStringToUTF8(env, jvalue));
+ CommandLine::ForCurrentProcess()->AppendSwitchASCII(switch_string,
+ value_string);
+}
+
+static void JNI_CommandLine_AppendSwitchesAndArguments(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jobjectArray>& array) {
+ JNI_CommandLine_AppendJavaStringArrayToCommandLine(env, array, false);
+}
+
+static void JNI_CommandLine_Init(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& jclazz,
+ const JavaParamRef<jobjectArray>& init_command_line) {
+ // TODO(port): Make an overload of Init() that takes StringVector rather than
+ // have to round-trip via AppendArguments.
+ CommandLine::Init(0, nullptr);
+ JNI_CommandLine_AppendJavaStringArrayToCommandLine(env, init_command_line,
+ true);
+}
diff --git a/base/android/content_uri_utils.cc b/base/android/content_uri_utils.cc
new file mode 100644
index 0000000000..a7955dca6b
--- /dev/null
+++ b/base/android/content_uri_utils.cc
@@ -0,0 +1,45 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/content_uri_utils.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "jni/ContentUriUtils_jni.h"
+
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ScopedJavaLocalRef;
+
+namespace base {
+
+bool ContentUriExists(const FilePath& content_uri) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> j_uri =
+ ConvertUTF8ToJavaString(env, content_uri.value());
+ return Java_ContentUriUtils_contentUriExists(env, j_uri);
+}
+
+File OpenContentUriForRead(const FilePath& content_uri) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> j_uri =
+ ConvertUTF8ToJavaString(env, content_uri.value());
+ jint fd = Java_ContentUriUtils_openContentUriForRead(env, j_uri);
+ if (fd < 0)
+ return File();
+ return File(fd);
+}
+
+std::string GetContentUriMimeType(const FilePath& content_uri) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> j_uri =
+ ConvertUTF8ToJavaString(env, content_uri.value());
+ ScopedJavaLocalRef<jstring> j_mime =
+ Java_ContentUriUtils_getMimeType(env, j_uri);
+ if (j_mime.is_null())
+ return std::string();
+
+ return base::android::ConvertJavaStringToUTF8(env, j_mime.obj());
+}
+
+} // namespace base
diff --git a/base/android/content_uri_utils.h b/base/android/content_uri_utils.h
new file mode 100644
index 0000000000..6d817c051e
--- /dev/null
+++ b/base/android/content_uri_utils.h
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_CONTENT_URI_UTILS_H_
+#define BASE_ANDROID_CONTENT_URI_UTILS_H_
+
+#include <jni.h>
+
+#include "base/base_export.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+
+namespace base {
+
+// Opens a content URI for read and returns the file descriptor to the caller.
+// Returns -1 if the URI is invalid.
+BASE_EXPORT File OpenContentUriForRead(const FilePath& content_uri);
+
+// Check whether a content URI exists.
+BASE_EXPORT bool ContentUriExists(const FilePath& content_uri);
+
+// Gets MIME type from a content URI. Returns an empty string if the URI is
+// invalid.
+BASE_EXPORT std::string GetContentUriMimeType(const FilePath& content_uri);
+
+} // namespace base
+
+#endif // BASE_ANDROID_CONTENT_URI_UTILS_H_
diff --git a/base/android/content_uri_utils_unittest.cc b/base/android/content_uri_utils_unittest.cc
new file mode 100644
index 0000000000..0e5cf0cbbb
--- /dev/null
+++ b/base/android/content_uri_utils_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/content_uri_utils.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/test/test_file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+// Disable on Android ASAN bot due to consistent failures: crbug.com/807080.
+#if !defined(ADDRESS_SANITIZER)
+TEST(ContentUriUtilsTest, ContentUriMimeTest) {
+ // Get the test image path.
+ FilePath data_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir));
+ data_dir = data_dir.AppendASCII("file_util");
+ ASSERT_TRUE(PathExists(data_dir));
+ FilePath image_file = data_dir.Append(FILE_PATH_LITERAL("red.png"));
+
+ // Insert the image into MediaStore. MediaStore will do some conversions, and
+ // return the content URI.
+ FilePath path = base::InsertImageIntoMediaStore(image_file);
+ EXPECT_TRUE(path.IsContentUri());
+ EXPECT_TRUE(PathExists(path));
+
+ std::string mime = GetContentUriMimeType(path);
+ EXPECT_EQ(mime, std::string("image/png"));
+
+ FilePath invalid_path("content://foo.bar");
+ mime = GetContentUriMimeType(invalid_path);
+ EXPECT_TRUE(mime.empty());
+}
+#endif
+
+} // namespace android
+} // namespace base
diff --git a/base/android/cpu_features.cc b/base/android/cpu_features.cc
new file mode 100644
index 0000000000..7c1dbe314c
--- /dev/null
+++ b/base/android/cpu_features.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cpu-features.h>
+
+#include "base/android/jni_android.h"
+#include "jni/CpuFeatures_jni.h"
+
+namespace base {
+namespace android {
+
+jint JNI_CpuFeatures_GetCoreCount(JNIEnv*, const JavaParamRef<jclass>&) {
+ return android_getCpuCount();
+}
+
+jlong JNI_CpuFeatures_GetCpuFeatures(JNIEnv*, const JavaParamRef<jclass>&) {
+ return android_getCpuFeatures();
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/early_trace_event_binding.cc b/base/android/early_trace_event_binding.cc
new file mode 100644
index 0000000000..bf6b910675
--- /dev/null
+++ b/base/android/early_trace_event_binding.cc
@@ -0,0 +1,67 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "base/android/jni_string.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "jni/EarlyTraceEvent_jni.h"
+
+namespace base {
+namespace android {
+
+const char kEarlyJavaCategory[] = "EarlyJava";
+
+static void JNI_EarlyTraceEvent_RecordEarlyEvent(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jname,
+ jlong begin_time_ns,
+ jlong end_time_ns,
+ jint thread_id,
+ jlong thread_duration_ms) {
+ std::string name = ConvertJavaStringToUTF8(env, jname);
+ int64_t begin_us = begin_time_ns / 1000;
+ int64_t end_us = end_time_ns / 1000;
+ int64_t thread_duration_us = thread_duration_ms * 1000;
+
+ INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMPS(
+ kEarlyJavaCategory, name.c_str(), trace_event_internal::kNoId, thread_id,
+ TimeTicks::FromInternalValue(begin_us),
+ TimeTicks::FromInternalValue(end_us),
+ ThreadTicks::Now() + TimeDelta::FromMicroseconds(thread_duration_us),
+ TRACE_EVENT_FLAG_COPY);
+}
+
+static void JNI_EarlyTraceEvent_RecordEarlyStartAsyncEvent(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jname,
+ jlong id,
+ jlong timestamp_ns) {
+ std::string name = ConvertJavaStringToUTF8(env, jname);
+ int64_t timestamp_us = timestamp_ns / 1000;
+
+ TRACE_EVENT_COPY_ASYNC_BEGIN_WITH_TIMESTAMP0(
+ kEarlyJavaCategory, name.c_str(), id,
+ base::TimeTicks() + base::TimeDelta::FromMicroseconds(timestamp_us));
+}
+
+static void JNI_EarlyTraceEvent_RecordEarlyFinishAsyncEvent(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jname,
+ jlong id,
+ jlong timestamp_ns) {
+ std::string name = ConvertJavaStringToUTF8(env, jname);
+ int64_t timestamp_us = timestamp_ns / 1000;
+
+ TRACE_EVENT_COPY_ASYNC_END_WITH_TIMESTAMP0(
+ kEarlyJavaCategory, name.c_str(), id,
+ base::TimeTicks() + base::TimeDelta::FromMicroseconds(timestamp_us));
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/event_log.cc b/base/android/event_log.cc
new file mode 100644
index 0000000000..3eb5926b1f
--- /dev/null
+++ b/base/android/event_log.cc
@@ -0,0 +1,16 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/event_log.h"
+#include "jni/EventLog_jni.h"
+
+namespace base {
+namespace android {
+
+void EventLogWriteInt(int tag, int value) {
+ Java_EventLog_writeEvent(AttachCurrentThread(), tag, value);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/event_log.h b/base/android/event_log.h
new file mode 100644
index 0000000000..ebd5919a5b
--- /dev/null
+++ b/base/android/event_log.h
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_EVENT_LOG_H_
+#define BASE_ANDROID_EVENT_LOG_H_
+
+#include <jni.h>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace android {
+
+void BASE_EXPORT EventLogWriteInt(int tag, int value);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_EVENT_LOG_H_
diff --git a/base/android/field_trial_list.cc b/base/android/field_trial_list.cc
new file mode 100644
index 0000000000..1dec5b537a
--- /dev/null
+++ b/base/android/field_trial_list.cc
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <jni.h>
+
+#include <map>
+#include <string>
+
+#include "base/android/jni_string.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_params.h"
+#include "jni/FieldTrialList_jni.h"
+
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
+
+static ScopedJavaLocalRef<jstring> JNI_FieldTrialList_FindFullName(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jtrial_name) {
+ std::string trial_name(ConvertJavaStringToUTF8(env, jtrial_name));
+ return ConvertUTF8ToJavaString(
+ env, base::FieldTrialList::FindFullName(trial_name));
+}
+
+static jboolean JNI_FieldTrialList_TrialExists(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jtrial_name) {
+ std::string trial_name(ConvertJavaStringToUTF8(env, jtrial_name));
+ return base::FieldTrialList::TrialExists(trial_name);
+}
+
+static ScopedJavaLocalRef<jstring> JNI_FieldTrialList_GetVariationParameter(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jtrial_name,
+ const JavaParamRef<jstring>& jparameter_key) {
+ std::map<std::string, std::string> parameters;
+ base::GetFieldTrialParams(ConvertJavaStringToUTF8(env, jtrial_name),
+ &parameters);
+ return ConvertUTF8ToJavaString(
+ env, parameters[ConvertJavaStringToUTF8(env, jparameter_key)]);
+}
diff --git a/base/android/important_file_writer_android.cc b/base/android/important_file_writer_android.cc
new file mode 100644
index 0000000000..fcaa3b1ea0
--- /dev/null
+++ b/base/android/important_file_writer_android.cc
@@ -0,0 +1,37 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/android/jni_string.h"
+#include "base/files/important_file_writer.h"
+#include "base/threading/thread_restrictions.h"
+#include "jni/ImportantFileWriterAndroid_jni.h"
+
+namespace base {
+namespace android {
+
+static jboolean JNI_ImportantFileWriterAndroid_WriteFileAtomically(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& /* clazz */,
+ const JavaParamRef<jstring>& file_name,
+ const JavaParamRef<jbyteArray>& data) {
+ // This is called on the UI thread during shutdown to save tab data, so
+ // needs to enable IO.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ std::string native_file_name;
+ base::android::ConvertJavaStringToUTF8(env, file_name, &native_file_name);
+ base::FilePath path(native_file_name);
+ int data_length = env->GetArrayLength(data);
+ jbyte* native_data = env->GetByteArrayElements(data, NULL);
+ std::string native_data_string(reinterpret_cast<char *>(native_data),
+ data_length);
+ bool result = base::ImportantFileWriter::WriteFileAtomically(
+ path, native_data_string);
+ env->ReleaseByteArrayElements(data, native_data, JNI_ABORT);
+ return result;
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/java/src/org/chromium/base/ActivityState.java b/base/android/java/src/org/chromium/base/ActivityState.java
new file mode 100644
index 0000000000..b14814c1c0
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ActivityState.java
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A set of states that represent the last state change of an Activity.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@IntDef({ActivityState.CREATED, ActivityState.STARTED, ActivityState.RESUMED, ActivityState.PAUSED,
+ ActivityState.STOPPED, ActivityState.DESTROYED})
+public @interface ActivityState {
+ /**
+ * Represents Activity#onCreate().
+ */
+ int CREATED = 1;
+
+ /**
+ * Represents Activity#onStart().
+ */
+ int STARTED = 2;
+
+ /**
+ * Represents Activity#onResume().
+ */
+ int RESUMED = 3;
+
+ /**
+ * Represents Activity#onPause().
+ */
+ int PAUSED = 4;
+
+ /**
+ * Represents Activity#onStop().
+ */
+ int STOPPED = 5;
+
+ /**
+ * Represents Activity#onDestroy(). This is also used when the state of an Activity is unknown.
+ */
+ int DESTROYED = 6;
+}
diff --git a/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java b/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java
new file mode 100644
index 0000000000..ad5cdd815b
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java
@@ -0,0 +1,145 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
+import android.util.Log;
+
+/**
+ * Record Android animation frame rate and save it to UMA histogram. This is mainly for monitoring
+ * any jankiness of short Chrome Android animations. It is limited to few seconds of recording.
+ */
+public class AnimationFrameTimeHistogram {
+ private static final String TAG = "AnimationFrameTimeHistogram";
+ private static final int MAX_FRAME_TIME_NUM = 600; // 10 sec on 60 fps.
+
+ private final Recorder mRecorder = new Recorder();
+ private final String mHistogramName;
+
+ /**
+ * @param histogramName The histogram name that the recorded frame times will be saved.
+ * This must be also defined in histograms.xml
+ * @return An AnimatorListener instance that records frame time histogram on start and end
+ * automatically.
+ */
+ public static AnimatorListener getAnimatorRecorder(final String histogramName) {
+ return new AnimatorListenerAdapter() {
+ private final AnimationFrameTimeHistogram mAnimationFrameTimeHistogram =
+ new AnimationFrameTimeHistogram(histogramName);
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mAnimationFrameTimeHistogram.startRecording();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimationFrameTimeHistogram.endRecording();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mAnimationFrameTimeHistogram.endRecording();
+ }
+ };
+ }
+
+ /**
+ * @param histogramName The histogram name that the recorded frame times will be saved.
+ * This must be also defined in histograms.xml
+ */
+ public AnimationFrameTimeHistogram(String histogramName) {
+ mHistogramName = histogramName;
+ }
+
+ /**
+ * Start recording frame times. The recording can fail if it exceeds a few seconds.
+ */
+ public void startRecording() {
+ mRecorder.startRecording();
+ }
+
+ /**
+ * End recording and save it to histogram. It won't save histogram if the recording wasn't
+ * successful.
+ */
+ public void endRecording() {
+ if (mRecorder.endRecording()) {
+ nativeSaveHistogram(mHistogramName,
+ mRecorder.getFrameTimesMs(), mRecorder.getFrameTimesCount());
+ }
+ mRecorder.cleanUp();
+ }
+
+ /**
+ * Record Android animation frame rate and return the result.
+ */
+ private static class Recorder implements TimeListener {
+ // TODO(kkimlabs): If we can use in the future, migrate to Choreographer for minimal
+ // workload.
+ private final TimeAnimator mAnimator = new TimeAnimator();
+ private long[] mFrameTimesMs;
+ private int mFrameTimesCount;
+
+ private Recorder() {
+ mAnimator.setTimeListener(this);
+ }
+
+ private void startRecording() {
+ assert !mAnimator.isRunning();
+ mFrameTimesCount = 0;
+ mFrameTimesMs = new long[MAX_FRAME_TIME_NUM];
+ mAnimator.start();
+ }
+
+ /**
+ * @return Whether the recording was successful. If successful, the result is available via
+ * getFrameTimesNs and getFrameTimesCount.
+ */
+ private boolean endRecording() {
+ boolean succeeded = mAnimator.isStarted();
+ mAnimator.end();
+ return succeeded;
+ }
+
+ private long[] getFrameTimesMs() {
+ return mFrameTimesMs;
+ }
+
+ private int getFrameTimesCount() {
+ return mFrameTimesCount;
+ }
+
+ /**
+ * Deallocates the temporary buffer to record frame times. Must be called after ending
+ * the recording and getting the result.
+ */
+ private void cleanUp() {
+ mFrameTimesMs = null;
+ }
+
+ @Override
+ public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+ if (mFrameTimesCount == mFrameTimesMs.length) {
+ mAnimator.end();
+ cleanUp();
+ Log.w(TAG, "Animation frame time recording reached the maximum number. It's either"
+ + "the animation took too long or recording end is not called.");
+ return;
+ }
+
+ // deltaTime is 0 for the first frame.
+ if (deltaTime > 0) {
+ mFrameTimesMs[mFrameTimesCount++] = deltaTime;
+ }
+ }
+ }
+
+ private native void nativeSaveHistogram(String histogramName, long[] frameTimesMs, int count);
+}
diff --git a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
new file mode 100644
index 0000000000..d1c4693c4a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
@@ -0,0 +1,705 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.StatFs;
+import android.os.StrictMode;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.text.Html;
+import android.text.Spanned;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.textclassifier.TextClassifier;
+import android.widget.TextView;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Utility class to use new APIs that were added after ICS (API level 14).
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class ApiCompatibilityUtils {
+ private ApiCompatibilityUtils() {
+ }
+
+ /**
+ * Compares two long values numerically. The value returned is identical to what would be
+ * returned by {@link Long#compare(long, long)} which is available since API level 19.
+ */
+ public static int compareLong(long lhs, long rhs) {
+ return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
+ }
+
+ /**
+ * Compares two boolean values. The value returned is identical to what would be returned by
+ * {@link Boolean#compare(boolean, boolean)} which is available since API level 19.
+ */
+ public static int compareBoolean(boolean lhs, boolean rhs) {
+ return lhs == rhs ? 0 : lhs ? 1 : -1;
+ }
+
+ /**
+ * Checks that the object reference is not null and throws NullPointerException if it is.
+ * See {@link Objects#requireNonNull} which is available since API level 19.
+ * @param obj The object to check
+ */
+ @NonNull
+ public static <T> T requireNonNull(T obj) {
+ if (obj == null) throw new NullPointerException();
+ return obj;
+ }
+
+ /**
+ * Checks that the object reference is not null and throws NullPointerException if it is.
+ * See {@link Objects#requireNonNull} which is available since API level 19.
+ * @param obj The object to check
+ * @param message The message to put into NullPointerException
+ */
+ @NonNull
+ public static <T> T requireNonNull(T obj, String message) {
+ if (obj == null) throw new NullPointerException(message);
+ return obj;
+ }
+
+ /**
+ * {@link String#getBytes()} but specifying UTF-8 as the encoding and capturing the resulting
+ * UnsupportedEncodingException.
+ */
+ public static byte[] getBytesUtf8(String str) {
+ try {
+ return str.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("UTF-8 encoding not available.", e);
+ }
+ }
+
+ /**
+ * Returns true if view's layout direction is right-to-left.
+ *
+ * @param view the View whose layout is being considered
+ */
+ public static boolean isLayoutRtl(View view) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ } else {
+ // All layouts are LTR before JB MR1.
+ return false;
+ }
+ }
+
+ /**
+ * @see Configuration#getLayoutDirection()
+ */
+ public static int getLayoutDirection(Configuration configuration) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return configuration.getLayoutDirection();
+ } else {
+ // All layouts are LTR before JB MR1.
+ return View.LAYOUT_DIRECTION_LTR;
+ }
+ }
+
+ /**
+ * @return True if the running version of the Android supports printing.
+ */
+ public static boolean isPrintingSupported() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ }
+
+ /**
+ * @return True if the running version of the Android supports elevation. Elevation of a view
+ * determines the visual appearance of its shadow.
+ */
+ public static boolean isElevationSupported() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+ }
+
+ /**
+ * @see android.view.View#setLayoutDirection(int)
+ */
+ public static void setLayoutDirection(View view, int layoutDirection) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ view.setLayoutDirection(layoutDirection);
+ } else {
+ // Do nothing. RTL layouts aren't supported before JB MR1.
+ }
+ }
+
+ /**
+ * @see android.view.View#setTextAlignment(int)
+ */
+ public static void setTextAlignment(View view, int textAlignment) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ view.setTextAlignment(textAlignment);
+ } else {
+ // Do nothing. RTL text isn't supported before JB MR1.
+ }
+ }
+
+ /**
+ * @see android.view.View#setTextDirection(int)
+ */
+ public static void setTextDirection(View view, int textDirection) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ view.setTextDirection(textDirection);
+ } else {
+ // Do nothing. RTL text isn't supported before JB MR1.
+ }
+ }
+
+ /**
+ * See {@link android.view.View#setLabelFor(int)}.
+ */
+ public static void setLabelFor(View labelView, int id) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ labelView.setLabelFor(id);
+ } else {
+ // Do nothing. #setLabelFor() isn't supported before JB MR1.
+ }
+ }
+
+ /**
+ * @see android.widget.TextView#getCompoundDrawablesRelative()
+ */
+ public static Drawable[] getCompoundDrawablesRelative(TextView textView) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return textView.getCompoundDrawablesRelative();
+ } else {
+ return textView.getCompoundDrawables();
+ }
+ }
+
+ /**
+ * @see android.widget.TextView#setCompoundDrawablesRelative(Drawable, Drawable, Drawable,
+ * Drawable)
+ */
+ public static void setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top,
+ Drawable end, Drawable bottom) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // On JB MR1, due to a platform bug, setCompoundDrawablesRelative() is a no-op if the
+ // view has ever been measured. As a workaround, use setCompoundDrawables() directly.
+ // See: http://crbug.com/368196 and http://crbug.com/361709
+ boolean isRtl = isLayoutRtl(textView);
+ textView.setCompoundDrawables(isRtl ? end : start, top, isRtl ? start : end, bottom);
+ } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ textView.setCompoundDrawablesRelative(start, top, end, bottom);
+ } else {
+ textView.setCompoundDrawables(start, top, end, bottom);
+ }
+ }
+
+ /**
+ * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable,
+ * Drawable, Drawable, Drawable)
+ */
+ public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView,
+ Drawable start, Drawable top, Drawable end, Drawable bottom) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Work around the platform bug described in setCompoundDrawablesRelative() above.
+ boolean isRtl = isLayoutRtl(textView);
+ textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top,
+ isRtl ? start : end, bottom);
+ } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
+ } else {
+ textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
+ }
+ }
+
+ /**
+ * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int,
+ * int)
+ */
+ public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView,
+ int start, int top, int end, int bottom) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Work around the platform bug described in setCompoundDrawablesRelative() above.
+ boolean isRtl = isLayoutRtl(textView);
+ textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top,
+ isRtl ? start : end, bottom);
+ } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
+ } else {
+ textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
+ }
+ }
+
+ /**
+ * @see android.text.Html#toHtml(Spanned, int)
+ * @param option is ignored on below N
+ */
+ @SuppressWarnings("deprecation")
+ public static String toHtml(Spanned spanned, int option) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return Html.toHtml(spanned, option);
+ } else {
+ return Html.toHtml(spanned);
+ }
+ }
+
+ // These methods have a new name, and the old name is deprecated.
+
+ /**
+ * @see android.app.PendingIntent#getCreatorPackage()
+ */
+ @SuppressWarnings("deprecation")
+ public static String getCreatorPackage(PendingIntent intent) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return intent.getCreatorPackage();
+ } else {
+ return intent.getTargetPackage();
+ }
+ }
+
+ /**
+ * @see android.provider.Settings.Global#DEVICE_PROVISIONED
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public static boolean isDeviceProvisioned(Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return true;
+ if (context == null) return true;
+ if (context.getContentResolver() == null) return true;
+ return Settings.Global.getInt(
+ context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+
+ /**
+ * @see android.app.Activity#finishAndRemoveTask()
+ */
+ public static void finishAndRemoveTask(Activity activity) {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
+ activity.finishAndRemoveTask();
+ } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
+ // crbug.com/395772 : Fallback for Activity.finishAndRemoveTask() failing.
+ new FinishAndRemoveTaskWithRetry(activity).run();
+ } else {
+ activity.finish();
+ }
+ }
+
+ /**
+ * Set elevation if supported.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static boolean setElevation(View view, float elevationValue) {
+ if (!isElevationSupported()) return false;
+
+ view.setElevation(elevationValue);
+ return true;
+ }
+
+ /**
+ * Gets an intent to start the Android system notification settings activity for an app.
+ *
+ * @param context Context of the app whose settings intent should be returned.
+ */
+ public static Intent getNotificationSettingsIntent(Context context) {
+ Intent intent = new Intent();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
+ } else {
+ intent.setAction("android.settings.ACTION_APP_NOTIFICATION_SETTINGS");
+ intent.putExtra("app_package", context.getPackageName());
+ intent.putExtra("app_uid", context.getApplicationInfo().uid);
+ }
+ return intent;
+ }
+
+ private static class FinishAndRemoveTaskWithRetry implements Runnable {
+ private static final long RETRY_DELAY_MS = 500;
+ private static final long MAX_TRY_COUNT = 3;
+ private final Activity mActivity;
+ private int mTryCount;
+
+ FinishAndRemoveTaskWithRetry(Activity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void run() {
+ mActivity.finishAndRemoveTask();
+ mTryCount++;
+ if (!mActivity.isFinishing()) {
+ if (mTryCount < MAX_TRY_COUNT) {
+ ThreadUtils.postOnUiThreadDelayed(this, RETRY_DELAY_MS);
+ } else {
+ mActivity.finish();
+ }
+ }
+ }
+ }
+
+ /**
+ * @return Whether the screen of the device is interactive.
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean isInteractive(Context context) {
+ PowerManager manager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
+ return manager.isInteractive();
+ } else {
+ return manager.isScreenOn();
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public static int getActivityNewDocumentFlag() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+ } else {
+ return Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;
+ }
+ }
+
+ /**
+ * @see android.provider.Settings.Secure#SKIP_FIRST_USE_HINTS
+ */
+ public static boolean shouldSkipFirstUseHints(ContentResolver contentResolver) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return Settings.Secure.getInt(
+ contentResolver, Settings.Secure.SKIP_FIRST_USE_HINTS, 0) != 0;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @param activity Activity that should get the task description update.
+ * @param title Title of the activity.
+ * @param icon Icon of the activity.
+ * @param color Color of the activity. It must be a fully opaque color.
+ */
+ public static void setTaskDescription(Activity activity, String title, Bitmap icon, int color) {
+ // TaskDescription requires an opaque color.
+ assert Color.alpha(color) == 255;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ActivityManager.TaskDescription description =
+ new ActivityManager.TaskDescription(title, icon, color);
+ activity.setTaskDescription(description);
+ }
+ }
+
+ /**
+ * @see android.view.Window#setStatusBarColor(int color).
+ */
+ public static void setStatusBarColor(Window window, int statusBarColor) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
+
+ // If both system bars are black, we can remove these from our layout,
+ // removing or shrinking the SurfaceFlinger overlay required for our views.
+ // This benefits battery usage on L and M. However, this no longer provides a battery
+ // benefit as of N and starts to cause flicker bugs on O, so don't bother on O and up.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && statusBarColor == Color.BLACK
+ && window.getNavigationBarColor() == Color.BLACK) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ }
+ window.setStatusBarColor(statusBarColor);
+ }
+
+ /**
+ * Sets the status bar icons to dark or light. Note that this is only valid for
+ * Android M+.
+ *
+ * @param rootView The root view used to request updates to the system UI theming.
+ * @param useDarkIcons Whether the status bar icons should be dark.
+ */
+ public static void setStatusBarIconColor(View rootView, boolean useDarkIcons) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
+
+ int systemUiVisibility = rootView.getSystemUiVisibility();
+ if (useDarkIcons) {
+ systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ } else {
+ systemUiVisibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ }
+ rootView.setSystemUiVisibility(systemUiVisibility);
+ }
+
+ /**
+ * @see android.content.res.Resources#getDrawable(int id).
+ * TODO(ltian): use {@link AppCompatResources} to parse drawable to prevent fail on
+ * {@link VectorDrawable}. (http://crbug.com/792129)
+ */
+ @SuppressWarnings("deprecation")
+ public static Drawable getDrawable(Resources res, int id) throws NotFoundException {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return res.getDrawable(id, null);
+ } else {
+ return res.getDrawable(id);
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ /**
+ * @see android.content.res.Resources#getDrawableForDensity(int id, int density).
+ */
+ @SuppressWarnings("deprecation")
+ public static Drawable getDrawableForDensity(Resources res, int id, int density) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return res.getDrawableForDensity(id, density, null);
+ } else {
+ return res.getDrawableForDensity(id, density);
+ }
+ }
+
+ /**
+ * @see android.app.Activity#finishAfterTransition().
+ */
+ public static void finishAfterTransition(Activity activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ activity.finishAfterTransition();
+ } else {
+ activity.finish();
+ }
+ }
+
+ /**
+ * @see android.content.pm.PackageManager#getUserBadgedIcon(Drawable, android.os.UserHandle).
+ */
+ public static Drawable getUserBadgedIcon(Context context, int id) {
+ Drawable drawable = getDrawable(context.getResources(), id);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ PackageManager packageManager = context.getPackageManager();
+ drawable = packageManager.getUserBadgedIcon(drawable, Process.myUserHandle());
+ }
+ return drawable;
+ }
+
+ /**
+ * @see android.content.pm.PackageManager#getUserBadgedDrawableForDensity(Drawable drawable,
+ * UserHandle user, Rect badgeLocation, int badgeDensity).
+ */
+ public static Drawable getUserBadgedDrawableForDensity(
+ Context context, Drawable drawable, Rect badgeLocation, int density) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ PackageManager packageManager = context.getPackageManager();
+ return packageManager.getUserBadgedDrawableForDensity(
+ drawable, Process.myUserHandle(), badgeLocation, density);
+ }
+ return drawable;
+ }
+
+ /**
+ * @see android.content.res.Resources#getColor(int id).
+ */
+ @SuppressWarnings("deprecation")
+ public static int getColor(Resources res, int id) throws NotFoundException {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return res.getColor(id, null);
+ } else {
+ return res.getColor(id);
+ }
+ }
+
+ /**
+ * @see android.graphics.drawable.Drawable#getColorFilter().
+ */
+ @SuppressWarnings("NewApi")
+ public static ColorFilter getColorFilter(Drawable drawable) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return drawable.getColorFilter();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @see android.widget.TextView#setTextAppearance(int id).
+ */
+ @SuppressWarnings("deprecation")
+ public static void setTextAppearance(TextView view, int id) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ view.setTextAppearance(id);
+ } else {
+ view.setTextAppearance(view.getContext(), id);
+ }
+ }
+
+ /**
+ * See {@link android.os.StatFs#getAvailableBlocksLong}.
+ */
+ @SuppressWarnings("deprecation")
+ public static long getAvailableBlocks(StatFs statFs) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ return statFs.getAvailableBlocksLong();
+ } else {
+ return statFs.getAvailableBlocks();
+ }
+ }
+
+ /**
+ * See {@link android.os.StatFs#getBlockCount}.
+ */
+ @SuppressWarnings("deprecation")
+ public static long getBlockCount(StatFs statFs) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ return statFs.getBlockCountLong();
+ } else {
+ return statFs.getBlockCount();
+ }
+ }
+
+ /**
+ * See {@link android.os.StatFs#getBlockSize}.
+ */
+ @SuppressWarnings("deprecation")
+ public static long getBlockSize(StatFs statFs) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ return statFs.getBlockSizeLong();
+ } else {
+ return statFs.getBlockSize();
+ }
+ }
+
+ /**
+ * @param context The Android context, used to retrieve the UserManager system service.
+ * @return Whether the device is running in demo mode.
+ */
+ @SuppressWarnings("NewApi")
+ public static boolean isDemoUser(Context context) {
+ // UserManager#isDemoUser() is only available in Android NMR1+.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return false;
+
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ return userManager.isDemoUser();
+ }
+
+ /**
+ * @see Context#checkPermission(String, int, int)
+ */
+ public static int checkPermission(Context context, String permission, int pid, int uid) {
+ try {
+ return context.checkPermission(permission, pid, uid);
+ } catch (RuntimeException e) {
+ // Some older versions of Android throw odd errors when checking for permissions, so
+ // just swallow the exception and treat it as the permission is denied.
+ // crbug.com/639099
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * @see android.view.inputmethod.InputMethodSubType#getLocate()
+ */
+ @SuppressWarnings("deprecation")
+ public static String getLocale(InputMethodSubtype inputMethodSubType) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return inputMethodSubType.getLanguageTag();
+ } else {
+ return inputMethodSubType.getLocale();
+ }
+ }
+
+ /**
+ * Get a URI for |file| which has the image capture. This function assumes that path of |file|
+ * is based on the result of UiUtils.getDirectoryForImageCapture().
+ *
+ * @param file image capture file.
+ * @return URI for |file|.
+ */
+ public static Uri getUriForImageCaptureFile(File file) {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
+ ? ContentUriUtils.getContentUriFromFile(file)
+ : Uri.fromFile(file);
+ }
+
+ /**
+ * Get the URI for a downloaded file.
+ *
+ * @param file A downloaded file.
+ * @return URI for |file|.
+ */
+ public static Uri getUriForDownloadedFile(File file) {
+ return Build.VERSION.SDK_INT > Build.VERSION_CODES.M
+ ? FileUtils.getUriForFile(file)
+ : Uri.fromFile(file);
+ }
+
+ /**
+ * @see android.view.Window#FEATURE_INDETERMINATE_PROGRESS
+ */
+ public static void setWindowIndeterminateProgress(Window window) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ @SuppressWarnings("deprecation")
+ int featureNumber = Window.FEATURE_INDETERMINATE_PROGRESS;
+
+ @SuppressWarnings("deprecation")
+ int featureValue = Window.PROGRESS_VISIBILITY_OFF;
+
+ window.setFeatureInt(featureNumber, featureValue);
+ }
+ }
+
+ /**
+ * @param activity The {@link Activity} to check.
+ * @return Whether or not {@code activity} is currently in Android N+ multi-window mode.
+ */
+ public static boolean isInMultiWindowMode(Activity activity) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ return false;
+ }
+ return activity.isInMultiWindowMode();
+ }
+
+ /**
+ * Disables the Smart Select {@link TextClassifier} for the given {@link TextView} instance.
+ * @param textView The {@link TextView} that should have its classifier disabled.
+ */
+ @TargetApi(Build.VERSION_CODES.O)
+ public static void disableSmartSelectionTextClassifier(TextView textView) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
+
+ textView.setTextClassifier(TextClassifier.NO_OP);
+ }
+
+ /**
+ * Creates an ActivityOptions Bundle with basic options and the LaunchDisplayId set.
+ * @param displayId The id of the display to launch into.
+ * @return The created bundle, or null if unsupported.
+ */
+ public static Bundle createLaunchDisplayIdActivityOptions(int displayId) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null;
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(displayId);
+ return options.toBundle();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/ApkAssets.java b/base/android/java/src/org/chromium/base/ApkAssets.java
new file mode 100644
index 0000000000..19108e5957
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ApkAssets.java
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.util.Log;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+import java.io.IOException;
+
+/**
+ * A utility class to retrieve references to uncompressed assets insides the apk. A reference is
+ * defined as tuple (file descriptor, offset, size) enabling direct mapping without deflation.
+ * This can be used even within the renderer process, since it just dup's the apk's fd.
+ */
+@JNINamespace("base::android")
+public class ApkAssets {
+ private static final String LOGTAG = "ApkAssets";
+
+ @CalledByNative
+ public static long[] open(String fileName) {
+ AssetFileDescriptor afd = null;
+ try {
+ AssetManager manager = ContextUtils.getApplicationContext().getAssets();
+ afd = manager.openNonAssetFd(fileName);
+ return new long[] {afd.getParcelFileDescriptor().detachFd(), afd.getStartOffset(),
+ afd.getLength()};
+ } catch (IOException e) {
+ // As a general rule there's no point logging here because the caller should handle
+ // receiving an fd of -1 sensibly, and the log message is either mirrored later, or
+ // unwanted (in the case where a missing file is expected), or wanted but will be
+ // ignored, as most non-fatal logs are.
+ // It makes sense to log here when the file exists, but is unable to be opened as an fd
+ // because (for example) it is unexpectedly compressed in an apk. In that case, the log
+ // message might save someone some time working out what has gone wrong.
+ // For that reason, we only suppress the message when the exception message doesn't look
+ // informative (Android framework passes the filename as the message on actual file not
+ // found, and the empty string also wouldn't give any useful information for debugging).
+ if (!e.getMessage().equals("") && !e.getMessage().equals(fileName)) {
+ Log.e(LOGTAG, "Error while loading asset " + fileName + ": " + e);
+ }
+ return new long[] {-1, -1, -1};
+ } finally {
+ try {
+ if (afd != null) {
+ afd.close();
+ }
+ } catch (IOException e2) {
+ Log.e(LOGTAG, "Unable to close AssetFileDescriptor", e2);
+ }
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/ApplicationStatus.java b/base/android/java/src/org/chromium/base/ApplicationStatus.java
new file mode 100644
index 0000000000..9496da8c1e
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ApplicationStatus.java
@@ -0,0 +1,620 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Application;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.Window;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Provides information about the current activity's status, and a way
+ * to register / unregister listeners for state changes.
+ */
+@JNINamespace("base::android")
+public class ApplicationStatus {
+ private static final String TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS =
+ "android.support.v7.internal.app.ToolbarActionBar$ToolbarCallbackWrapper";
+ // In builds using the --use_unpublished_apis flag, the ToolbarActionBar class name does not
+ // include the "internal" package.
+ private static final String TOOLBAR_CALLBACK_WRAPPER_CLASS =
+ "android.support.v7.app.ToolbarActionBar$ToolbarCallbackWrapper";
+ private static final String WINDOW_PROFILER_CALLBACK =
+ "com.android.tools.profiler.support.event.WindowProfilerCallback";
+
+ private static class ActivityInfo {
+ private int mStatus = ActivityState.DESTROYED;
+ private ObserverList<ActivityStateListener> mListeners = new ObserverList<>();
+
+ /**
+ * @return The current {@link ActivityState} of the activity.
+ */
+ @ActivityState
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * @param status The new {@link ActivityState} of the activity.
+ */
+ public void setStatus(@ActivityState int status) {
+ mStatus = status;
+ }
+
+ /**
+ * @return A list of {@link ActivityStateListener}s listening to this activity.
+ */
+ public ObserverList<ActivityStateListener> getListeners() {
+ return mListeners;
+ }
+ }
+
+ static {
+ // Chrome initializes this only for the main process. This assert aims to try and catch
+ // usages from GPU / renderers, while still allowing tests.
+ assert ContextUtils.isMainProcess()
+ || ContextUtils.getProcessName().contains(":test")
+ : "Cannot use ApplicationState from process: "
+ + ContextUtils.getProcessName();
+ }
+
+ private static final Object sCurrentApplicationStateLock = new Object();
+
+ @SuppressLint("SupportAnnotationUsage")
+ @ApplicationState
+ // The getStateForApplication() historically returned ApplicationState.HAS_DESTROYED_ACTIVITIES
+ // when no activity has been observed.
+ private static Integer sCurrentApplicationState = ApplicationState.HAS_DESTROYED_ACTIVITIES;
+
+ /** Last activity that was shown (or null if none or it was destroyed). */
+ @SuppressLint("StaticFieldLeak")
+ private static Activity sActivity;
+
+ /** A lazily initialized listener that forwards application state changes to native. */
+ private static ApplicationStateListener sNativeApplicationStateListener;
+
+ private static boolean sIsInitialized;
+
+ /**
+ * A map of which observers listen to state changes from which {@link Activity}.
+ */
+ private static final Map<Activity, ActivityInfo> sActivityInfo = new ConcurrentHashMap<>();
+
+ /**
+ * A list of observers to be notified when any {@link Activity} has a state change.
+ */
+ private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners =
+ new ObserverList<>();
+
+ /**
+ * A list of observers to be notified when the visibility state of this {@link Application}
+ * changes. See {@link #getStateForApplication()}.
+ */
+ private static final ObserverList<ApplicationStateListener> sApplicationStateListeners =
+ new ObserverList<>();
+
+ /**
+ * A list of observers to be notified when the window focus changes.
+ * See {@link #registerWindowFocusChangedListener}.
+ */
+ private static final ObserverList<WindowFocusChangedListener> sWindowFocusListeners =
+ new ObserverList<>();
+
+ /**
+ * Interface to be implemented by listeners.
+ */
+ public interface ApplicationStateListener {
+ /**
+ * Called when the application's state changes.
+ * @param newState The application state.
+ */
+ void onApplicationStateChange(@ApplicationState int newState);
+ }
+
+ /**
+ * Interface to be implemented by listeners.
+ */
+ public interface ActivityStateListener {
+ /**
+ * Called when the activity's state changes.
+ * @param activity The activity that had a state change.
+ * @param newState New activity state.
+ */
+ void onActivityStateChange(Activity activity, @ActivityState int newState);
+ }
+
+ /**
+ * Interface to be implemented by listeners for window focus events.
+ */
+ public interface WindowFocusChangedListener {
+ /**
+ * Called when the window focus changes for {@code activity}.
+ * @param activity The {@link Activity} that has a window focus changed event.
+ * @param hasFocus Whether or not {@code activity} gained or lost focus.
+ */
+ public void onWindowFocusChanged(Activity activity, boolean hasFocus);
+ }
+
+ private ApplicationStatus() {}
+
+ /**
+ * Registers a listener to receive window focus updates on activities in this application.
+ * @param listener Listener to receive window focus events.
+ */
+ public static void registerWindowFocusChangedListener(WindowFocusChangedListener listener) {
+ sWindowFocusListeners.addObserver(listener);
+ }
+
+ /**
+ * Unregisters a listener from receiving window focus updates on activities in this application.
+ * @param listener Listener that doesn't want to receive window focus events.
+ */
+ public static void unregisterWindowFocusChangedListener(WindowFocusChangedListener listener) {
+ sWindowFocusListeners.removeObserver(listener);
+ }
+
+ /**
+ * Intercepts calls to an existing Window.Callback. Most invocations are passed on directly
+ * to the composed Window.Callback but enables intercepting/manipulating others.
+ *
+ * This is used to relay window focus changes throughout the app and remedy a bug in the
+ * appcompat library.
+ */
+ private static class WindowCallbackProxy implements InvocationHandler {
+ private final Window.Callback mCallback;
+ private final Activity mActivity;
+
+ public WindowCallbackProxy(Activity activity, Window.Callback callback) {
+ mCallback = callback;
+ mActivity = activity;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getName().equals("onWindowFocusChanged") && args.length == 1
+ && args[0] instanceof Boolean) {
+ onWindowFocusChanged((boolean) args[0]);
+ return null;
+ } else {
+ try {
+ return method.invoke(mCallback, args);
+ } catch (InvocationTargetException e) {
+ // Special-case for when a method is not defined on the underlying
+ // Window.Callback object. Because we're using a Proxy to forward all method
+ // calls, this breaks the Android framework's handling for apps built against
+ // an older SDK. The framework expects an AbstractMethodError but due to
+ // reflection it becomes wrapped inside an InvocationTargetException. Undo the
+ // wrapping to signal the framework accordingly.
+ if (e.getCause() instanceof AbstractMethodError) {
+ throw e.getCause();
+ }
+ throw e;
+ }
+ }
+ }
+
+ public void onWindowFocusChanged(boolean hasFocus) {
+ mCallback.onWindowFocusChanged(hasFocus);
+
+ for (WindowFocusChangedListener listener : sWindowFocusListeners) {
+ listener.onWindowFocusChanged(mActivity, hasFocus);
+ }
+ }
+ }
+
+ /**
+ * Initializes the activity status for a specified application.
+ *
+ * @param application The application whose status you wish to monitor.
+ */
+ public static void initialize(Application application) {
+ if (sIsInitialized) return;
+ sIsInitialized = true;
+
+ registerWindowFocusChangedListener(new WindowFocusChangedListener() {
+ @Override
+ public void onWindowFocusChanged(Activity activity, boolean hasFocus) {
+ if (!hasFocus || activity == sActivity) return;
+
+ int state = getStateForActivity(activity);
+
+ if (state != ActivityState.DESTROYED && state != ActivityState.STOPPED) {
+ sActivity = activity;
+ }
+
+ // TODO(dtrainor): Notify of active activity change?
+ }
+ });
+
+ application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
+ @Override
+ public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
+ onStateChange(activity, ActivityState.CREATED);
+ Window.Callback callback = activity.getWindow().getCallback();
+ activity.getWindow().setCallback((Window.Callback) Proxy.newProxyInstance(
+ Window.Callback.class.getClassLoader(), new Class[] {Window.Callback.class},
+ new ApplicationStatus.WindowCallbackProxy(activity, callback)));
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ onStateChange(activity, ActivityState.DESTROYED);
+ checkCallback(activity);
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ onStateChange(activity, ActivityState.PAUSED);
+ checkCallback(activity);
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ onStateChange(activity, ActivityState.RESUMED);
+ checkCallback(activity);
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+ checkCallback(activity);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ onStateChange(activity, ActivityState.STARTED);
+ checkCallback(activity);
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ onStateChange(activity, ActivityState.STOPPED);
+ checkCallback(activity);
+ }
+
+ private void checkCallback(Activity activity) {
+ if (BuildConfig.DCHECK_IS_ON) {
+ Class<? extends Window.Callback> callback =
+ activity.getWindow().getCallback().getClass();
+ assert(Proxy.isProxyClass(callback)
+ || callback.getName().equals(TOOLBAR_CALLBACK_WRAPPER_CLASS)
+ || callback.getName().equals(TOOLBAR_CALLBACK_INTERNAL_WRAPPER_CLASS)
+ || callback.getName().equals(WINDOW_PROFILER_CALLBACK));
+ }
+ }
+ });
+ }
+
+ /**
+ * Asserts that initialize method has been called.
+ */
+ private static void assertInitialized() {
+ if (!sIsInitialized) {
+ throw new IllegalStateException("ApplicationStatus has not been initialized yet.");
+ }
+ }
+
+ /**
+ * Must be called by the main activity when it changes state.
+ *
+ * @param activity Current activity.
+ * @param newState New state value.
+ */
+ private static void onStateChange(Activity activity, @ActivityState int newState) {
+ if (activity == null) throw new IllegalArgumentException("null activity is not supported");
+
+ if (sActivity == null
+ || newState == ActivityState.CREATED
+ || newState == ActivityState.RESUMED
+ || newState == ActivityState.STARTED) {
+ sActivity = activity;
+ }
+
+ int oldApplicationState = getStateForApplication();
+ ActivityInfo info;
+
+ synchronized (sCurrentApplicationStateLock) {
+ if (newState == ActivityState.CREATED) {
+ assert !sActivityInfo.containsKey(activity);
+ sActivityInfo.put(activity, new ActivityInfo());
+ }
+
+ info = sActivityInfo.get(activity);
+ info.setStatus(newState);
+
+ // Remove before calling listeners so that isEveryActivityDestroyed() returns false when
+ // this was the last activity.
+ if (newState == ActivityState.DESTROYED) {
+ sActivityInfo.remove(activity);
+ if (activity == sActivity) sActivity = null;
+ }
+
+ sCurrentApplicationState = determineApplicationState();
+ }
+
+ // Notify all state observers that are specifically listening to this activity.
+ for (ActivityStateListener listener : info.getListeners()) {
+ listener.onActivityStateChange(activity, newState);
+ }
+
+ // Notify all state observers that are listening globally for all activity state
+ // changes.
+ for (ActivityStateListener listener : sGeneralActivityStateListeners) {
+ listener.onActivityStateChange(activity, newState);
+ }
+
+ int applicationState = getStateForApplication();
+ if (applicationState != oldApplicationState) {
+ for (ApplicationStateListener listener : sApplicationStateListeners) {
+ listener.onApplicationStateChange(applicationState);
+ }
+ }
+ }
+
+ /**
+ * Testing method to update the state of the specified activity.
+ */
+ @VisibleForTesting
+ public static void onStateChangeForTesting(Activity activity, int newState) {
+ onStateChange(activity, newState);
+ }
+
+ /**
+ * @return The most recent focused {@link Activity} tracked by this class. Being focused means
+ * out of all the activities tracked here, it has most recently gained window focus.
+ */
+ public static Activity getLastTrackedFocusedActivity() {
+ return sActivity;
+ }
+
+ /**
+ * @return A {@link List} of all non-destroyed {@link Activity}s.
+ */
+ public static List<WeakReference<Activity>> getRunningActivities() {
+ assertInitialized();
+ List<WeakReference<Activity>> activities = new ArrayList<>();
+ for (Activity activity : sActivityInfo.keySet()) {
+ activities.add(new WeakReference<>(activity));
+ }
+ return activities;
+ }
+
+ /**
+ * Query the state for a given activity. If the activity is not being tracked, this will
+ * return {@link ActivityState#DESTROYED}.
+ *
+ * <p>
+ * Please note that Chrome can have multiple activities running simultaneously. Please also
+ * look at {@link #getStateForApplication()} for more details.
+ *
+ * <p>
+ * When relying on this method, be familiar with the expected life cycle state
+ * transitions:
+ * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle">
+ * Activity Lifecycle
+ * </a>
+ *
+ * <p>
+ * During activity transitions (activity B launching in front of activity A), A will completely
+ * paused before the creation of activity B begins.
+ *
+ * <p>
+ * A basic flow for activity A starting, followed by activity B being opened and then closed:
+ * <ul>
+ * <li> -- Starting Activity A --
+ * <li> Activity A - ActivityState.CREATED
+ * <li> Activity A - ActivityState.STARTED
+ * <li> Activity A - ActivityState.RESUMED
+ * <li> -- Starting Activity B --
+ * <li> Activity A - ActivityState.PAUSED
+ * <li> Activity B - ActivityState.CREATED
+ * <li> Activity B - ActivityState.STARTED
+ * <li> Activity B - ActivityState.RESUMED
+ * <li> Activity A - ActivityState.STOPPED
+ * <li> -- Closing Activity B, Activity A regaining focus --
+ * <li> Activity B - ActivityState.PAUSED
+ * <li> Activity A - ActivityState.STARTED
+ * <li> Activity A - ActivityState.RESUMED
+ * <li> Activity B - ActivityState.STOPPED
+ * <li> Activity B - ActivityState.DESTROYED
+ * </ul>
+ *
+ * @param activity The activity whose state is to be returned.
+ * @return The state of the specified activity (see {@link ActivityState}).
+ */
+ @ActivityState
+ public static int getStateForActivity(@Nullable Activity activity) {
+ ApplicationStatus.assertInitialized();
+ if (activity == null) return ActivityState.DESTROYED;
+ ActivityInfo info = sActivityInfo.get(activity);
+ return info != null ? info.getStatus() : ActivityState.DESTROYED;
+ }
+
+ /**
+ * @return The state of the application (see {@link ApplicationState}).
+ */
+ @ApplicationState
+ @CalledByNative
+ public static int getStateForApplication() {
+ synchronized (sCurrentApplicationStateLock) {
+ return sCurrentApplicationState;
+ }
+ }
+
+ /**
+ * Checks whether or not any Activity in this Application is visible to the user. Note that
+ * this includes the PAUSED state, which can happen when the Activity is temporarily covered
+ * by another Activity's Fragment (e.g.).
+ * @return Whether any Activity under this Application is visible.
+ */
+ public static boolean hasVisibleActivities() {
+ int state = getStateForApplication();
+ return state == ApplicationState.HAS_RUNNING_ACTIVITIES
+ || state == ApplicationState.HAS_PAUSED_ACTIVITIES;
+ }
+
+ /**
+ * Checks to see if there are any active Activity instances being watched by ApplicationStatus.
+ * @return True if all Activities have been destroyed.
+ */
+ public static boolean isEveryActivityDestroyed() {
+ return sActivityInfo.isEmpty();
+ }
+
+ /**
+ * Registers the given listener to receive state changes for all activities.
+ * @param listener Listener to receive state changes.
+ */
+ public static void registerStateListenerForAllActivities(ActivityStateListener listener) {
+ sGeneralActivityStateListeners.addObserver(listener);
+ }
+
+ /**
+ * Registers the given listener to receive state changes for {@code activity}. After a call to
+ * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with
+ * {@link ActivityState#DESTROYED} all listeners associated with that particular
+ * {@link Activity} are removed.
+ * @param listener Listener to receive state changes.
+ * @param activity Activity to track or {@code null} to track all activities.
+ */
+ @SuppressLint("NewApi")
+ public static void registerStateListenerForActivity(ActivityStateListener listener,
+ Activity activity) {
+ if (activity == null) {
+ throw new IllegalStateException("Attempting to register listener on a null activity.");
+ }
+ ApplicationStatus.assertInitialized();
+
+ ActivityInfo info = sActivityInfo.get(activity);
+ if (info == null) {
+ throw new IllegalStateException(
+ "Attempting to register listener on an untracked activity.");
+ }
+ assert info.getStatus() != ActivityState.DESTROYED;
+ info.getListeners().addObserver(listener);
+ }
+
+ /**
+ * Unregisters the given listener from receiving activity state changes.
+ * @param listener Listener that doesn't want to receive state changes.
+ */
+ public static void unregisterActivityStateListener(ActivityStateListener listener) {
+ sGeneralActivityStateListeners.removeObserver(listener);
+
+ // Loop through all observer lists for all activities and remove the listener.
+ for (ActivityInfo info : sActivityInfo.values()) {
+ info.getListeners().removeObserver(listener);
+ }
+ }
+
+ /**
+ * Registers the given listener to receive state changes for the application.
+ * @param listener Listener to receive state state changes.
+ */
+ public static void registerApplicationStateListener(ApplicationStateListener listener) {
+ sApplicationStateListeners.addObserver(listener);
+ }
+
+ /**
+ * Unregisters the given listener from receiving state changes.
+ * @param listener Listener that doesn't want to receive state changes.
+ */
+ public static void unregisterApplicationStateListener(ApplicationStateListener listener) {
+ sApplicationStateListeners.removeObserver(listener);
+ }
+
+ /**
+ * Robolectric JUnit tests create a new application between each test, while all the context
+ * in static classes isn't reset. This function allows to reset the application status to avoid
+ * being in a dirty state.
+ */
+ public static void destroyForJUnitTests() {
+ sApplicationStateListeners.clear();
+ sGeneralActivityStateListeners.clear();
+ sActivityInfo.clear();
+ sWindowFocusListeners.clear();
+ sIsInitialized = false;
+ synchronized (sCurrentApplicationStateLock) {
+ sCurrentApplicationState = determineApplicationState();
+ }
+ sActivity = null;
+ sNativeApplicationStateListener = null;
+ }
+
+ /**
+ * Registers the single thread-safe native activity status listener.
+ * This handles the case where the caller is not on the main thread.
+ * Note that this is used by a leaky singleton object from the native
+ * side, hence lifecycle management is greatly simplified.
+ */
+ @CalledByNative
+ private static void registerThreadSafeNativeApplicationStateListener() {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (sNativeApplicationStateListener != null) return;
+
+ sNativeApplicationStateListener = new ApplicationStateListener() {
+ @Override
+ public void onApplicationStateChange(int newState) {
+ nativeOnApplicationStateChange(newState);
+ }
+ };
+ registerApplicationStateListener(sNativeApplicationStateListener);
+ }
+ });
+ }
+
+ /**
+ * Determines the current application state as defined by {@link ApplicationState}. This will
+ * loop over all the activities and check their state to determine what the general application
+ * state should be.
+ * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed.
+ * HAS_PAUSED_ACTIVITIES if none are running and one is paused.
+ * HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped.
+ * HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped.
+ */
+ @ApplicationState
+ private static int determineApplicationState() {
+ boolean hasPausedActivity = false;
+ boolean hasStoppedActivity = false;
+
+ for (ActivityInfo info : sActivityInfo.values()) {
+ int state = info.getStatus();
+ if (state != ActivityState.PAUSED
+ && state != ActivityState.STOPPED
+ && state != ActivityState.DESTROYED) {
+ return ApplicationState.HAS_RUNNING_ACTIVITIES;
+ } else if (state == ActivityState.PAUSED) {
+ hasPausedActivity = true;
+ } else if (state == ActivityState.STOPPED) {
+ hasStoppedActivity = true;
+ }
+ }
+
+ if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES;
+ if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES;
+ return ApplicationState.HAS_DESTROYED_ACTIVITIES;
+ }
+
+ // Called to notify the native side of state changes.
+ // IMPORTANT: This is always called on the main thread!
+ private static native void nativeOnApplicationStateChange(@ApplicationState int newState);
+}
diff --git a/base/android/java/src/org/chromium/base/BaseSwitches.java b/base/android/java/src/org/chromium/base/BaseSwitches.java
new file mode 100644
index 0000000000..fe47cdda1d
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/BaseSwitches.java
@@ -0,0 +1,32 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * Contains all of the command line switches that are specific to the base/
+ * portion of Chromium on Android.
+ */
+public abstract class BaseSwitches {
+ // Block ChildProcessMain thread of render process service until a Java debugger is attached.
+ // To pause even earlier: am set-debug-app org.chromium.chrome:sandbox_process0
+ // However, this flag is convenient when you don't know the process number, or want
+ // all renderers to pause (set-debug-app applies to only one process at a time).
+ public static final String RENDERER_WAIT_FOR_JAVA_DEBUGGER = "renderer-wait-for-java-debugger";
+
+ // Force low-end device mode when set.
+ public static final String ENABLE_LOW_END_DEVICE_MODE = "enable-low-end-device-mode";
+
+ // Force disabling of low-end device mode when set.
+ public static final String DISABLE_LOW_END_DEVICE_MODE = "disable-low-end-device-mode";
+
+ // Adds additional thread idle time information into the trace event output.
+ public static final String ENABLE_IDLE_TRACING = "enable-idle-tracing";
+
+ // Default country code to be used for search engine localization.
+ public static final String DEFAULT_COUNTRY_CODE_AT_INSTALL = "default-country-code";
+
+ // Prevent instantiation.
+ private BaseSwitches() {}
+}
diff --git a/base/android/java/src/org/chromium/base/Callback.java b/base/android/java/src/org/chromium/base/Callback.java
new file mode 100644
index 0000000000..f5f20b9c75
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/Callback.java
@@ -0,0 +1,43 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.CalledByNative;
+
+/**
+ * A simple single-argument callback to handle the result of a computation.
+ *
+ * @param <T> The type of the computation's result.
+ */
+public interface Callback<T> {
+ /**
+ * Invoked with the result of a computation.
+ */
+ void onResult(T result);
+
+ /**
+ * JNI Generator does not know how to target static methods on interfaces
+ * (which is new in Java 8, and requires desugaring).
+ */
+ abstract class Helper {
+ @SuppressWarnings("unchecked")
+ @CalledByNative("Helper")
+ static void onObjectResultFromNative(Callback callback, Object result) {
+ callback.onResult(result);
+ }
+
+ @SuppressWarnings("unchecked")
+ @CalledByNative("Helper")
+ static void onBooleanResultFromNative(Callback callback, boolean result) {
+ callback.onResult(Boolean.valueOf(result));
+ }
+
+ @SuppressWarnings("unchecked")
+ @CalledByNative("Helper")
+ static void onIntResultFromNative(Callback callback, int result) {
+ callback.onResult(Integer.valueOf(result));
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/CollectionUtil.java b/base/android/java/src/org/chromium/base/CollectionUtil.java
new file mode 100644
index 0000000000..60933807b8
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/CollectionUtil.java
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.support.annotation.NonNull;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Functions used for easier initialization of Java collections. Inspired by
+ * functionality in com.google.common.collect in Guava but cherry-picked to
+ * bare-minimum functionality to avoid bloat. (http://crbug.com/272790 provides
+ * further details)
+ */
+public final class CollectionUtil {
+ private CollectionUtil() {}
+
+ @SafeVarargs
+ public static <E> HashSet<E> newHashSet(E... elements) {
+ HashSet<E> set = new HashSet<E>(elements.length);
+ Collections.addAll(set, elements);
+ return set;
+ }
+
+ @SafeVarargs
+ public static <E> ArrayList<E> newArrayList(E... elements) {
+ ArrayList<E> list = new ArrayList<E>(elements.length);
+ Collections.addAll(list, elements);
+ return list;
+ }
+
+ @VisibleForTesting
+ public static <E> ArrayList<E> newArrayList(Iterable<E> iterable) {
+ ArrayList<E> list = new ArrayList<E>();
+ for (E element : iterable) {
+ list.add(element);
+ }
+ return list;
+ }
+
+ @SafeVarargs
+ public static <K, V> HashMap<K, V> newHashMap(Pair<? extends K, ? extends V>... entries) {
+ HashMap<K, V> map = new HashMap<>();
+ for (Pair<? extends K, ? extends V> entry : entries) {
+ map.put(entry.first, entry.second);
+ }
+ return map;
+ }
+
+ public static boolean[] booleanListToBooleanArray(@NonNull List<Boolean> list) {
+ boolean[] array = new boolean[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ array[i] = list.get(i);
+ }
+ return array;
+ }
+
+ public static int[] integerListToIntArray(@NonNull List<Integer> list) {
+ int[] array = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ array[i] = list.get(i);
+ }
+ return array;
+ }
+
+ public static long[] longListToLongArray(@NonNull List<Long> list) {
+ long[] array = new long[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ array[i] = list.get(i);
+ }
+ return array;
+ }
+
+ // This is a utility helper method that adds functionality available in API 24 (see
+ // Collection.forEach).
+ public static <T> void forEach(Collection<? extends T> collection, Callback<T> worker) {
+ for (T entry : collection) worker.onResult(entry);
+ }
+
+ // This is a utility helper method that adds functionality available in API 24 (see
+ // Collection.forEach).
+ @SuppressWarnings("unchecked")
+ public static <K, V> void forEach(
+ Map<? extends K, ? extends V> map, Callback<Entry<K, V>> worker) {
+ for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
+ worker.onResult((Map.Entry<K, V>) entry);
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/CommandLine.java b/base/android/java/src/org/chromium/base/CommandLine.java
new file mode 100644
index 0000000000..963b1464af
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/CommandLine.java
@@ -0,0 +1,389 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.chromium.base.annotations.MainDex;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Java mirror of base/command_line.h.
+ * Android applications don't have command line arguments. Instead, they're "simulated" by reading a
+ * file at a specific location early during startup. Applications each define their own files, e.g.,
+ * ContentShellApplication.COMMAND_LINE_FILE.
+**/
+@MainDex
+public abstract class CommandLine {
+ // Public abstract interface, implemented in derived classes.
+ // All these methods reflect their native-side counterparts.
+ /**
+ * Returns true if this command line contains the given switch.
+ * (Switch names ARE case-sensitive).
+ */
+ @VisibleForTesting
+ public abstract boolean hasSwitch(String switchString);
+
+ /**
+ * Return the value associated with the given switch, or null.
+ * @param switchString The switch key to lookup. It should NOT start with '--' !
+ * @return switch value, or null if the switch is not set or set to empty.
+ */
+ public abstract String getSwitchValue(String switchString);
+
+ /**
+ * Return the value associated with the given switch, or {@code defaultValue} if the switch
+ * was not specified.
+ * @param switchString The switch key to lookup. It should NOT start with '--' !
+ * @param defaultValue The default value to return if the switch isn't set.
+ * @return Switch value, or {@code defaultValue} if the switch is not set or set to empty.
+ */
+ public String getSwitchValue(String switchString, String defaultValue) {
+ String value = getSwitchValue(switchString);
+ return TextUtils.isEmpty(value) ? defaultValue : value;
+ }
+
+ /**
+ * Append a switch to the command line. There is no guarantee
+ * this action happens before the switch is needed.
+ * @param switchString the switch to add. It should NOT start with '--' !
+ */
+ @VisibleForTesting
+ public abstract void appendSwitch(String switchString);
+
+ /**
+ * Append a switch and value to the command line. There is no
+ * guarantee this action happens before the switch is needed.
+ * @param switchString the switch to add. It should NOT start with '--' !
+ * @param value the value for this switch.
+ * For example, --foo=bar becomes 'foo', 'bar'.
+ */
+ public abstract void appendSwitchWithValue(String switchString, String value);
+
+ /**
+ * Append switch/value items in "command line" format (excluding argv[0] program name).
+ * E.g. { '--gofast', '--username=fred' }
+ * @param array an array of switch or switch/value items in command line format.
+ * Unlike the other append routines, these switches SHOULD start with '--' .
+ * Unlike init(), this does not include the program name in array[0].
+ */
+ public abstract void appendSwitchesAndArguments(String[] array);
+
+ /**
+ * Determine if the command line is bound to the native (JNI) implementation.
+ * @return true if the underlying implementation is delegating to the native command line.
+ */
+ public boolean isNativeImplementation() {
+ return false;
+ }
+
+ /**
+ * Returns the switches and arguments passed into the program, with switches and their
+ * values coming before all of the arguments.
+ */
+ protected abstract String[] getCommandLineArguments();
+
+ /**
+ * Destroy the command line. Called when a different instance is set.
+ * @see #setInstance
+ */
+ protected void destroy() {}
+
+ private static final AtomicReference<CommandLine> sCommandLine =
+ new AtomicReference<CommandLine>();
+
+ /**
+ * @return true if the command line has already been initialized.
+ */
+ public static boolean isInitialized() {
+ return sCommandLine.get() != null;
+ }
+
+ // Equivalent to CommandLine::ForCurrentProcess in C++.
+ @VisibleForTesting
+ public static CommandLine getInstance() {
+ CommandLine commandLine = sCommandLine.get();
+ assert commandLine != null;
+ return commandLine;
+ }
+
+ /**
+ * Initialize the singleton instance, must be called exactly once (either directly or
+ * via one of the convenience wrappers below) before using the static singleton instance.
+ * @param args command line flags in 'argv' format: args[0] is the program name.
+ */
+ public static void init(@Nullable String[] args) {
+ setInstance(new JavaCommandLine(args));
+ }
+
+ /**
+ * Initialize the command line from the command-line file.
+ *
+ * @param file The fully qualified command line file.
+ */
+ public static void initFromFile(String file) {
+ char[] buffer = readFileAsUtf8(file);
+ init(buffer == null ? null : tokenizeQuotedArguments(buffer));
+ }
+
+ /**
+ * Resets both the java proxy and the native command lines. This allows the entire
+ * command line initialization to be re-run including the call to onJniLoaded.
+ */
+ @VisibleForTesting
+ public static void reset() {
+ setInstance(null);
+ }
+
+ /**
+ * Parse command line flags from a flat buffer, supporting double-quote enclosed strings
+ * containing whitespace. argv elements are derived by splitting the buffer on whitepace;
+ * double quote characters may enclose tokens containing whitespace; a double-quote literal
+ * may be escaped with back-slash. (Otherwise backslash is taken as a literal).
+ * @param buffer A command line in command line file format as described above.
+ * @return the tokenized arguments, suitable for passing to init().
+ */
+ @VisibleForTesting
+ static String[] tokenizeQuotedArguments(char[] buffer) {
+ // Just field trials can take up to 10K of command line.
+ if (buffer.length > 64 * 1024) {
+ // Check that our test runners are setting a reasonable number of flags.
+ throw new RuntimeException("Flags file too big: " + buffer.length);
+ }
+
+ ArrayList<String> args = new ArrayList<String>();
+ StringBuilder arg = null;
+ final char noQuote = '\0';
+ final char singleQuote = '\'';
+ final char doubleQuote = '"';
+ char currentQuote = noQuote;
+ for (char c : buffer) {
+ // Detect start or end of quote block.
+ if ((currentQuote == noQuote && (c == singleQuote || c == doubleQuote))
+ || c == currentQuote) {
+ if (arg != null && arg.length() > 0 && arg.charAt(arg.length() - 1) == '\\') {
+ // Last char was a backslash; pop it, and treat c as a literal.
+ arg.setCharAt(arg.length() - 1, c);
+ } else {
+ currentQuote = currentQuote == noQuote ? c : noQuote;
+ }
+ } else if (currentQuote == noQuote && Character.isWhitespace(c)) {
+ if (arg != null) {
+ args.add(arg.toString());
+ arg = null;
+ }
+ } else {
+ if (arg == null) arg = new StringBuilder();
+ arg.append(c);
+ }
+ }
+ if (arg != null) {
+ if (currentQuote != noQuote) {
+ Log.w(TAG, "Unterminated quoted string: " + arg);
+ }
+ args.add(arg.toString());
+ }
+ return args.toArray(new String[args.size()]);
+ }
+
+ private static final String TAG = "CommandLine";
+ private static final String SWITCH_PREFIX = "--";
+ private static final String SWITCH_TERMINATOR = SWITCH_PREFIX;
+ private static final String SWITCH_VALUE_SEPARATOR = "=";
+
+ public static void enableNativeProxy() {
+ // Make a best-effort to ensure we make a clean (atomic) switch over from the old to
+ // the new command line implementation. If another thread is modifying the command line
+ // when this happens, all bets are off. (As per the native CommandLine).
+ sCommandLine.set(new NativeCommandLine(getJavaSwitchesOrNull()));
+ }
+
+ @Nullable
+ public static String[] getJavaSwitchesOrNull() {
+ CommandLine commandLine = sCommandLine.get();
+ if (commandLine != null) {
+ return commandLine.getCommandLineArguments();
+ }
+ return null;
+ }
+
+ private static void setInstance(CommandLine commandLine) {
+ CommandLine oldCommandLine = sCommandLine.getAndSet(commandLine);
+ if (oldCommandLine != null) {
+ oldCommandLine.destroy();
+ }
+ }
+
+ /**
+ * @param fileName the file to read in.
+ * @return Array of chars read from the file, or null if the file cannot be read.
+ */
+ private static char[] readFileAsUtf8(String fileName) {
+ File f = new File(fileName);
+ try (FileReader reader = new FileReader(f)) {
+ char[] buffer = new char[(int) f.length()];
+ int charsRead = reader.read(buffer);
+ // charsRead < f.length() in the case of multibyte characters.
+ return Arrays.copyOfRange(buffer, 0, charsRead);
+ } catch (IOException e) {
+ return null; // Most likely file not found.
+ }
+ }
+
+ private CommandLine() {}
+
+ private static class JavaCommandLine extends CommandLine {
+ private HashMap<String, String> mSwitches = new HashMap<String, String>();
+ private ArrayList<String> mArgs = new ArrayList<String>();
+
+ // The arguments begin at index 1, since index 0 contains the executable name.
+ private int mArgsBegin = 1;
+
+ JavaCommandLine(@Nullable String[] args) {
+ if (args == null || args.length == 0 || args[0] == null) {
+ mArgs.add("");
+ } else {
+ mArgs.add(args[0]);
+ appendSwitchesInternal(args, 1);
+ }
+ // Invariant: we always have the argv[0] program name element.
+ assert mArgs.size() > 0;
+ }
+
+ @Override
+ protected String[] getCommandLineArguments() {
+ return mArgs.toArray(new String[mArgs.size()]);
+ }
+
+ @Override
+ public boolean hasSwitch(String switchString) {
+ return mSwitches.containsKey(switchString);
+ }
+
+ @Override
+ public String getSwitchValue(String switchString) {
+ // This is slightly round about, but needed for consistency with the NativeCommandLine
+ // version which does not distinguish empty values from key not present.
+ String value = mSwitches.get(switchString);
+ return value == null || value.isEmpty() ? null : value;
+ }
+
+ @Override
+ public void appendSwitch(String switchString) {
+ appendSwitchWithValue(switchString, null);
+ }
+
+ /**
+ * Appends a switch to the current list.
+ * @param switchString the switch to add. It should NOT start with '--' !
+ * @param value the value for this switch.
+ */
+ @Override
+ public void appendSwitchWithValue(String switchString, String value) {
+ mSwitches.put(switchString, value == null ? "" : value);
+
+ // Append the switch and update the switches/arguments divider mArgsBegin.
+ String combinedSwitchString = SWITCH_PREFIX + switchString;
+ if (value != null && !value.isEmpty()) {
+ combinedSwitchString += SWITCH_VALUE_SEPARATOR + value;
+ }
+
+ mArgs.add(mArgsBegin++, combinedSwitchString);
+ }
+
+ @Override
+ public void appendSwitchesAndArguments(String[] array) {
+ appendSwitchesInternal(array, 0);
+ }
+
+ // Add the specified arguments, but skipping the first |skipCount| elements.
+ private void appendSwitchesInternal(String[] array, int skipCount) {
+ boolean parseSwitches = true;
+ for (String arg : array) {
+ if (skipCount > 0) {
+ --skipCount;
+ continue;
+ }
+
+ if (arg.equals(SWITCH_TERMINATOR)) {
+ parseSwitches = false;
+ }
+
+ if (parseSwitches && arg.startsWith(SWITCH_PREFIX)) {
+ String[] parts = arg.split(SWITCH_VALUE_SEPARATOR, 2);
+ String value = parts.length > 1 ? parts[1] : null;
+ appendSwitchWithValue(parts[0].substring(SWITCH_PREFIX.length()), value);
+ } else {
+ mArgs.add(arg);
+ }
+ }
+ }
+ }
+
+ private static class NativeCommandLine extends CommandLine {
+ public NativeCommandLine(@Nullable String[] args) {
+ nativeInit(args);
+ }
+
+ @Override
+ public boolean hasSwitch(String switchString) {
+ return nativeHasSwitch(switchString);
+ }
+
+ @Override
+ public String getSwitchValue(String switchString) {
+ return nativeGetSwitchValue(switchString);
+ }
+
+ @Override
+ public void appendSwitch(String switchString) {
+ nativeAppendSwitch(switchString);
+ }
+
+ @Override
+ public void appendSwitchWithValue(String switchString, String value) {
+ nativeAppendSwitchWithValue(switchString, value);
+ }
+
+ @Override
+ public void appendSwitchesAndArguments(String[] array) {
+ nativeAppendSwitchesAndArguments(array);
+ }
+
+ @Override
+ public boolean isNativeImplementation() {
+ return true;
+ }
+
+ @Override
+ protected String[] getCommandLineArguments() {
+ assert false;
+ return null;
+ }
+
+ @Override
+ protected void destroy() {
+ // TODO(https://crbug.com/771205): Downgrade this to an assert once we have eliminated
+ // tests that do this.
+ throw new IllegalStateException("Can't destroy native command line after startup");
+ }
+ }
+
+ private static native void nativeInit(String[] args);
+ private static native boolean nativeHasSwitch(String switchString);
+ private static native String nativeGetSwitchValue(String switchString);
+ private static native void nativeAppendSwitch(String switchString);
+ private static native void nativeAppendSwitchWithValue(String switchString, String value);
+ private static native void nativeAppendSwitchesAndArguments(String[] array);
+}
diff --git a/base/android/java/src/org/chromium/base/CommandLineInitUtil.java b/base/android/java/src/org/chromium/base/CommandLineInitUtil.java
new file mode 100644
index 0000000000..e51b95d6b5
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/CommandLineInitUtil.java
@@ -0,0 +1,103 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.provider.Settings;
+import android.support.annotation.Nullable;
+
+import java.io.File;
+
+/**
+ * Provides implementation of command line initialization for Android.
+ */
+public final class CommandLineInitUtil {
+ /**
+ * The location of the command line file needs to be in a protected
+ * directory so requires root access to be tweaked, i.e., no other app in a
+ * regular (non-rooted) device can change this file's contents.
+ * See below for debugging on a regular (non-rooted) device.
+ */
+ private static final String COMMAND_LINE_FILE_PATH = "/data/local";
+
+ /**
+ * This path (writable by the shell in regular non-rooted "user" builds) is used when:
+ * 1) The "debug app" is set to the application calling this.
+ * and
+ * 2) ADB is enabled.
+ * 3) Force enabled by the embedder.
+ */
+ private static final String COMMAND_LINE_FILE_PATH_DEBUG_APP = "/data/local/tmp";
+
+ private CommandLineInitUtil() {
+ }
+
+ /**
+ * Initializes the CommandLine class, pulling command line arguments from {@code fileName}.
+ * @param fileName The name of the command line file to pull arguments from.
+ */
+ public static void initCommandLine(String fileName) {
+ initCommandLine(fileName, null);
+ }
+
+ /**
+ * Initializes the CommandLine class, pulling command line arguments from {@code fileName}.
+ * @param fileName The name of the command line file to pull arguments from.
+ * @param shouldUseDebugFlags If non-null, returns whether debug flags are allowed to be used.
+ */
+ public static void initCommandLine(
+ String fileName, @Nullable Supplier<Boolean> shouldUseDebugFlags) {
+ assert !CommandLine.isInitialized();
+ File commandLineFile = new File(COMMAND_LINE_FILE_PATH_DEBUG_APP, fileName);
+ // shouldUseDebugCommandLine() uses IPC, so don't bother calling it if no flags file exists.
+ boolean debugFlagsExist = commandLineFile.exists();
+ if (!debugFlagsExist || !shouldUseDebugCommandLine(shouldUseDebugFlags)) {
+ commandLineFile = new File(COMMAND_LINE_FILE_PATH, fileName);
+ }
+ CommandLine.initFromFile(commandLineFile.getPath());
+ }
+
+ /**
+ * Use an alternative path if:
+ * - The current build is "eng" or "userdebug", OR
+ * - adb is enabled and this is the debug app, OR
+ * - Force enabled by the embedder.
+ * @param shouldUseDebugFlags If non-null, returns whether debug flags are allowed to be used.
+ */
+ private static boolean shouldUseDebugCommandLine(
+ @Nullable Supplier<Boolean> shouldUseDebugFlags) {
+ if (shouldUseDebugFlags != null && shouldUseDebugFlags.get()) return true;
+ Context context = ContextUtils.getApplicationContext();
+ String debugApp = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1
+ ? getDebugAppPreJBMR1(context)
+ : getDebugAppJBMR1(context);
+ // Check isDebugAndroid() last to get full code coverage when using userdebug devices.
+ return context.getPackageName().equals(debugApp) || BuildInfo.isDebugAndroid();
+ }
+
+ @SuppressLint("NewApi")
+ private static String getDebugAppJBMR1(Context context) {
+ boolean adbEnabled = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ADB_ENABLED, 0) == 1;
+ if (adbEnabled) {
+ return Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.DEBUG_APP);
+ }
+ return null;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static String getDebugAppPreJBMR1(Context context) {
+ boolean adbEnabled = Settings.System.getInt(context.getContentResolver(),
+ Settings.System.ADB_ENABLED, 0) == 1;
+ if (adbEnabled) {
+ return Settings.System.getString(context.getContentResolver(),
+ Settings.System.DEBUG_APP);
+ }
+ return null;
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/ContentUriUtils.java b/base/android/java/src/org/chromium/base/ContentUriUtils.java
new file mode 100644
index 0000000000..ba92a56c4f
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ContentUriUtils.java
@@ -0,0 +1,251 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import org.chromium.base.annotations.CalledByNative;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * This class provides methods to access content URI schemes.
+ */
+public abstract class ContentUriUtils {
+ private static final String TAG = "ContentUriUtils";
+ private static FileProviderUtil sFileProviderUtil;
+
+ // Guards access to sFileProviderUtil.
+ private static final Object sLock = new Object();
+
+ /**
+ * Provides functionality to translate a file into a content URI for use
+ * with a content provider.
+ */
+ public interface FileProviderUtil {
+ /**
+ * Generate a content URI from the given file.
+ *
+ * @param file The file to be translated.
+ */
+ Uri getContentUriFromFile(File file);
+ }
+
+ // Prevent instantiation.
+ private ContentUriUtils() {}
+
+ public static void setFileProviderUtil(FileProviderUtil util) {
+ synchronized (sLock) {
+ sFileProviderUtil = util;
+ }
+ }
+
+ public static Uri getContentUriFromFile(File file) {
+ synchronized (sLock) {
+ if (sFileProviderUtil != null) {
+ return sFileProviderUtil.getContentUriFromFile(file);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Opens the content URI for reading, and returns the file descriptor to
+ * the caller. The caller is responsible for closing the file descriptor.
+ *
+ * @param uriString the content URI to open
+ * @return file descriptor upon success, or -1 otherwise.
+ */
+ @CalledByNative
+ public static int openContentUriForRead(String uriString) {
+ AssetFileDescriptor afd = getAssetFileDescriptor(uriString);
+ if (afd != null) {
+ return afd.getParcelFileDescriptor().detachFd();
+ }
+ return -1;
+ }
+
+ /**
+ * Check whether a content URI exists.
+ *
+ * @param uriString the content URI to query.
+ * @return true if the URI exists, or false otherwise.
+ */
+ @CalledByNative
+ public static boolean contentUriExists(String uriString) {
+ AssetFileDescriptor asf = null;
+ try {
+ asf = getAssetFileDescriptor(uriString);
+ return asf != null;
+ } finally {
+ // Do not use StreamUtil.closeQuietly here, as AssetFileDescriptor
+ // does not implement Closeable until KitKat.
+ if (asf != null) {
+ try {
+ asf.close();
+ } catch (IOException e) {
+ // Closing quietly.
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieve the MIME type for the content URI.
+ *
+ * @param uriString the content URI to look up.
+ * @return MIME type or null if the input params are empty or invalid.
+ */
+ @CalledByNative
+ public static String getMimeType(String uriString) {
+ ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver();
+ Uri uri = Uri.parse(uriString);
+ if (isVirtualDocument(uri)) {
+ String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
+ return (streamTypes != null && streamTypes.length > 0) ? streamTypes[0] : null;
+ }
+ return resolver.getType(uri);
+ }
+
+ /**
+ * Helper method to open a content URI and returns the ParcelFileDescriptor.
+ *
+ * @param uriString the content URI to open.
+ * @return AssetFileDescriptor of the content URI, or NULL if the file does not exist.
+ */
+ private static AssetFileDescriptor getAssetFileDescriptor(String uriString) {
+ ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver();
+ Uri uri = Uri.parse(uriString);
+
+ try {
+ if (isVirtualDocument(uri)) {
+ String[] streamTypes = resolver.getStreamTypes(uri, "*/*");
+ if (streamTypes != null && streamTypes.length > 0) {
+ AssetFileDescriptor afd =
+ resolver.openTypedAssetFileDescriptor(uri, streamTypes[0], null);
+ if (afd != null && afd.getStartOffset() != 0) {
+ // Do not use StreamUtil.closeQuietly here, as AssetFileDescriptor
+ // does not implement Closeable until KitKat.
+ try {
+ afd.close();
+ } catch (IOException e) {
+ // Closing quietly.
+ }
+ throw new SecurityException("Cannot open files with non-zero offset type.");
+ }
+ return afd;
+ }
+ } else {
+ ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
+ if (pfd != null) {
+ return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Cannot find content uri: " + uriString, e);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Cannot open content uri: " + uriString, e);
+ } catch (Exception e) {
+ Log.w(TAG, "Unknown content uri: " + uriString, e);
+ }
+ return null;
+ }
+
+ /**
+ * Method to resolve the display name of a content URI.
+ *
+ * @param uri the content URI to be resolved.
+ * @param context {@link Context} in interest.
+ * @param columnField the column field to query.
+ * @return the display name of the @code uri if present in the database
+ * or an empty string otherwise.
+ */
+ public static String getDisplayName(Uri uri, Context context, String columnField) {
+ if (uri == null) return "";
+ ContentResolver contentResolver = context.getContentResolver();
+ try (Cursor cursor = contentResolver.query(uri, null, null, null, null)) {
+ if (cursor != null && cursor.getCount() >= 1) {
+ cursor.moveToFirst();
+ int displayNameIndex = cursor.getColumnIndex(columnField);
+ if (displayNameIndex == -1) {
+ return "";
+ }
+ String displayName = cursor.getString(displayNameIndex);
+ // For Virtual documents, try to modify the file extension so it's compatible
+ // with the alternative MIME type.
+ if (hasVirtualFlag(cursor)) {
+ String[] mimeTypes = contentResolver.getStreamTypes(uri, "*/*");
+ if (mimeTypes != null && mimeTypes.length > 0) {
+ String ext =
+ MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeTypes[0]);
+ if (ext != null) {
+ // Just append, it's simpler and more secure than altering an
+ // existing extension.
+ displayName += "." + ext;
+ }
+ }
+ }
+ return displayName;
+ }
+ } catch (NullPointerException e) {
+ // Some android models don't handle the provider call correctly.
+ // see crbug.com/345393
+ return "";
+ }
+ return "";
+ }
+
+ /**
+ * Checks whether the passed Uri represents a virtual document.
+ *
+ * @param uri the content URI to be resolved.
+ * @return True for virtual file, false for any other file.
+ */
+ private static boolean isVirtualDocument(Uri uri) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return false;
+ if (uri == null) return false;
+ if (!DocumentsContract.isDocumentUri(ContextUtils.getApplicationContext(), uri)) {
+ return false;
+ }
+ ContentResolver contentResolver = ContextUtils.getApplicationContext().getContentResolver();
+ try (Cursor cursor = contentResolver.query(uri, null, null, null, null)) {
+ if (cursor != null && cursor.getCount() >= 1) {
+ cursor.moveToFirst();
+ return hasVirtualFlag(cursor);
+ }
+ } catch (NullPointerException e) {
+ // Some android models don't handle the provider call correctly.
+ // see crbug.com/345393
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the passed cursor for a document has a virtual document flag.
+ *
+ * The called must close the passed cursor.
+ *
+ * @param cursor Cursor with COLUMN_FLAGS.
+ * @return True for virtual file, false for any other file.
+ */
+ private static boolean hasVirtualFlag(Cursor cursor) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return false;
+ int index = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
+ return index > -1
+ && (cursor.getLong(index) & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/CpuFeatures.java b/base/android/java/src/org/chromium/base/CpuFeatures.java
new file mode 100644
index 0000000000..ae4969c99e
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/CpuFeatures.java
@@ -0,0 +1,42 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.JNINamespace;
+
+// The only purpose of this class is to allow sending CPU properties
+// from the browser process to sandboxed renderer processes. This is
+// needed because sandboxed processes cannot, on ARM, query the kernel
+// about the CPU's properties by parsing /proc, so this operation must
+// be performed in the browser process, and the result passed to
+// renderer ones.
+//
+// For more context, see http://crbug.com/164154
+//
+// Technically, this is a wrapper around the native NDK cpufeatures
+// library. The exact CPU features bits are never used in Java so
+// there is no point in duplicating their definitions here.
+//
+@JNINamespace("base::android")
+public abstract class CpuFeatures {
+ /**
+ * Return the number of CPU Cores on the device.
+ */
+ public static int getCount() {
+ return nativeGetCoreCount();
+ }
+
+ /**
+ * Return the CPU feature mask.
+ * This is a 64-bit integer that corresponds to the CPU's features.
+ * The value comes directly from android_getCpuFeatures().
+ */
+ public static long getMask() {
+ return nativeGetCpuFeatures();
+ }
+
+ private static native int nativeGetCoreCount();
+ private static native long nativeGetCpuFeatures();
+}
diff --git a/base/android/java/src/org/chromium/base/EarlyTraceEvent.java b/base/android/java/src/org/chromium/base/EarlyTraceEvent.java
new file mode 100644
index 0000000000..0f64fc2329
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/EarlyTraceEvent.java
@@ -0,0 +1,299 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.os.Process;
+import android.os.StrictMode;
+import android.os.SystemClock;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Support for early tracing, before the native library is loaded.
+ *
+ * This is limited, as:
+ * - Arguments are not supported
+ * - Thread time is not reported
+ * - Two events with the same name cannot be in progress at the same time.
+ *
+ * Events recorded here are buffered in Java until the native library is available. Then it waits
+ * for the completion of pending events, and sends the events to the native side.
+ *
+ * Locking: This class is threadsafe. It is enabled when general tracing is, and then disabled when
+ * tracing is enabled from the native side. Event completions are still processed as long
+ * as some are pending, then early tracing is permanently disabled after dumping the
+ * events. This means that if any early event is still pending when tracing is disabled,
+ * all early events are dropped.
+ */
+@JNINamespace("base::android")
+@MainDex
+public class EarlyTraceEvent {
+ // Must be kept in sync with the native kAndroidTraceConfigFile.
+ private static final String TRACE_CONFIG_FILENAME = "/data/local/chrome-trace-config.json";
+
+ /** Single trace event. */
+ @VisibleForTesting
+ static final class Event {
+ final String mName;
+ final int mThreadId;
+ final long mBeginTimeNanos;
+ final long mBeginThreadTimeMillis;
+ long mEndTimeNanos;
+ long mEndThreadTimeMillis;
+
+ Event(String name) {
+ mName = name;
+ mThreadId = Process.myTid();
+ mBeginTimeNanos = elapsedRealtimeNanos();
+ mBeginThreadTimeMillis = SystemClock.currentThreadTimeMillis();
+ }
+
+ void end() {
+ assert mEndTimeNanos == 0;
+ assert mEndThreadTimeMillis == 0;
+ mEndTimeNanos = elapsedRealtimeNanos();
+ mEndThreadTimeMillis = SystemClock.currentThreadTimeMillis();
+ }
+
+ @VisibleForTesting
+ @SuppressLint("NewApi")
+ static long elapsedRealtimeNanos() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return SystemClock.elapsedRealtimeNanos();
+ } else {
+ return SystemClock.elapsedRealtime() * 1000000;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static final class AsyncEvent {
+ final boolean mIsStart;
+ final String mName;
+ final long mId;
+ final long mTimestampNanos;
+
+ AsyncEvent(String name, long id, boolean isStart) {
+ mName = name;
+ mId = id;
+ mIsStart = isStart;
+ mTimestampNanos = Event.elapsedRealtimeNanos();
+ }
+ }
+
+ // State transitions are:
+ // - enable(): DISABLED -> ENABLED
+ // - disable(): ENABLED -> FINISHING
+ // - Once there are no pending events: FINISHING -> FINISHED.
+ @VisibleForTesting static final int STATE_DISABLED = 0;
+ @VisibleForTesting static final int STATE_ENABLED = 1;
+ @VisibleForTesting static final int STATE_FINISHING = 2;
+ @VisibleForTesting static final int STATE_FINISHED = 3;
+
+ // Locks the fields below.
+ private static final Object sLock = new Object();
+
+ @VisibleForTesting static volatile int sState = STATE_DISABLED;
+ // Not final as these object are not likely to be used at all.
+ @VisibleForTesting static List<Event> sCompletedEvents;
+ @VisibleForTesting
+ static Map<String, Event> sPendingEventByKey;
+ @VisibleForTesting static List<AsyncEvent> sAsyncEvents;
+ @VisibleForTesting static List<String> sPendingAsyncEvents;
+
+ /** @see TraceEvent#MaybeEnableEarlyTracing().
+ */
+ static void maybeEnable() {
+ ThreadUtils.assertOnUiThread();
+ boolean shouldEnable = false;
+ // Checking for the trace config filename touches the disk.
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ if (CommandLine.getInstance().hasSwitch("trace-startup")) {
+ shouldEnable = true;
+ } else {
+ try {
+ shouldEnable = (new File(TRACE_CONFIG_FILENAME)).exists();
+ } catch (SecurityException e) {
+ // Access denied, not enabled.
+ }
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ if (shouldEnable) enable();
+ }
+
+ @VisibleForTesting
+ static void enable() {
+ synchronized (sLock) {
+ if (sState != STATE_DISABLED) return;
+ sCompletedEvents = new ArrayList<Event>();
+ sPendingEventByKey = new HashMap<String, Event>();
+ sAsyncEvents = new ArrayList<AsyncEvent>();
+ sPendingAsyncEvents = new ArrayList<String>();
+ sState = STATE_ENABLED;
+ }
+ }
+
+ /**
+ * Disables Early tracing.
+ *
+ * Once this is called, no new event will be registered. However, end() calls are still recorded
+ * as long as there are pending events. Once there are none left, pass the events to the native
+ * side.
+ */
+ static void disable() {
+ synchronized (sLock) {
+ if (!enabled()) return;
+ sState = STATE_FINISHING;
+ maybeFinishLocked();
+ }
+ }
+
+ /**
+ * Returns whether early tracing is currently active.
+ *
+ * Active means that Early Tracing is either enabled or waiting to complete pending events.
+ */
+ static boolean isActive() {
+ int state = sState;
+ return (state == STATE_ENABLED || state == STATE_FINISHING);
+ }
+
+ static boolean enabled() {
+ return sState == STATE_ENABLED;
+ }
+
+ /** @see {@link TraceEvent#begin()}. */
+ public static void begin(String name) {
+ // begin() and end() are going to be called once per TraceEvent, this avoids entering a
+ // synchronized block at each and every call.
+ if (!enabled()) return;
+ Event event = new Event(name);
+ Event conflictingEvent;
+ synchronized (sLock) {
+ if (!enabled()) return;
+ conflictingEvent = sPendingEventByKey.put(makeEventKeyForCurrentThread(name), event);
+ }
+ if (conflictingEvent != null) {
+ throw new IllegalArgumentException(
+ "Multiple pending trace events can't have the same name");
+ }
+ }
+
+ /** @see {@link TraceEvent#end()}. */
+ public static void end(String name) {
+ if (!isActive()) return;
+ synchronized (sLock) {
+ if (!isActive()) return;
+ Event event = sPendingEventByKey.remove(makeEventKeyForCurrentThread(name));
+ if (event == null) return;
+ event.end();
+ sCompletedEvents.add(event);
+ if (sState == STATE_FINISHING) maybeFinishLocked();
+ }
+ }
+
+ /** @see {@link TraceEvent#startAsync()}. */
+ public static void startAsync(String name, long id) {
+ if (!enabled()) return;
+ AsyncEvent event = new AsyncEvent(name, id, true /*isStart*/);
+ synchronized (sLock) {
+ if (!enabled()) return;
+ sAsyncEvents.add(event);
+ sPendingAsyncEvents.add(name);
+ }
+ }
+
+ /** @see {@link TraceEvent#finishAsync()}. */
+ public static void finishAsync(String name, long id) {
+ if (!isActive()) return;
+ AsyncEvent event = new AsyncEvent(name, id, false /*isStart*/);
+ synchronized (sLock) {
+ if (!isActive()) return;
+ if (!sPendingAsyncEvents.remove(name)) return;
+ sAsyncEvents.add(event);
+ if (sState == STATE_FINISHING) maybeFinishLocked();
+ }
+ }
+
+ @VisibleForTesting
+ static void resetForTesting() {
+ sState = EarlyTraceEvent.STATE_DISABLED;
+ sCompletedEvents = null;
+ sPendingEventByKey = null;
+ sAsyncEvents = null;
+ sPendingAsyncEvents = null;
+ }
+
+ private static void maybeFinishLocked() {
+ if (!sCompletedEvents.isEmpty()) {
+ dumpEvents(sCompletedEvents);
+ sCompletedEvents.clear();
+ }
+ if (!sAsyncEvents.isEmpty()) {
+ dumpAsyncEvents(sAsyncEvents);
+ sAsyncEvents.clear();
+ }
+ if (sPendingEventByKey.isEmpty() && sPendingAsyncEvents.isEmpty()) {
+ sState = STATE_FINISHED;
+ sPendingEventByKey = null;
+ sCompletedEvents = null;
+ sPendingAsyncEvents = null;
+ sAsyncEvents = null;
+ }
+ }
+
+ private static void dumpEvents(List<Event> events) {
+ long offsetNanos = getOffsetNanos();
+ for (Event e : events) {
+ nativeRecordEarlyEvent(e.mName, e.mBeginTimeNanos + offsetNanos,
+ e.mEndTimeNanos + offsetNanos, e.mThreadId,
+ e.mEndThreadTimeMillis - e.mBeginThreadTimeMillis);
+ }
+ }
+ private static void dumpAsyncEvents(List<AsyncEvent> events) {
+ long offsetNanos = getOffsetNanos();
+ for (AsyncEvent e : events) {
+ if (e.mIsStart) {
+ nativeRecordEarlyStartAsyncEvent(e.mName, e.mId, e.mTimestampNanos + offsetNanos);
+ } else {
+ nativeRecordEarlyFinishAsyncEvent(e.mName, e.mId, e.mTimestampNanos + offsetNanos);
+ }
+ }
+ }
+
+ private static long getOffsetNanos() {
+ long nativeNowNanos = TimeUtils.nativeGetTimeTicksNowUs() * 1000;
+ long javaNowNanos = Event.elapsedRealtimeNanos();
+ return nativeNowNanos - javaNowNanos;
+ }
+
+ /**
+ * Returns a key which consists of |name| and the ID of the current thread.
+ * The key is used with pending events making them thread-specific, thus avoiding
+ * an exception when similarly named events are started from multiple threads.
+ */
+ @VisibleForTesting
+ static String makeEventKeyForCurrentThread(String name) {
+ return name + "@" + Process.myTid();
+ }
+
+ private static native void nativeRecordEarlyEvent(String name, long beginTimNanos,
+ long endTimeNanos, int threadId, long threadDurationMillis);
+ private static native void nativeRecordEarlyStartAsyncEvent(
+ String name, long id, long timestamp);
+ private static native void nativeRecordEarlyFinishAsyncEvent(
+ String name, long id, long timestamp);
+}
diff --git a/base/android/java/src/org/chromium/base/EventLog.java b/base/android/java/src/org/chromium/base/EventLog.java
new file mode 100644
index 0000000000..f889175b7a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/EventLog.java
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * A simple interface to Android's EventLog to be used by native code.
+ */
+@JNINamespace("base::android")
+public class EventLog {
+
+ @CalledByNative
+ public static void writeEvent(int tag, int value) {
+ android.util.EventLog.writeEvent(tag, value);
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/FieldTrialList.java b/base/android/java/src/org/chromium/base/FieldTrialList.java
new file mode 100644
index 0000000000..c3468a4af0
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/FieldTrialList.java
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.MainDex;
+
+/**
+ * Helper to get field trial information.
+ */
+@MainDex
+public class FieldTrialList {
+
+ private FieldTrialList() {}
+
+ /**
+ * @param trialName The name of the trial to get the group for.
+ * @return The group name chosen for the named trial, or the empty string if the trial does
+ * not exist.
+ */
+ public static String findFullName(String trialName) {
+ return nativeFindFullName(trialName);
+ }
+
+ /**
+ * @param trialName The name of the trial to get the group for.
+ * @return Whether the trial exists or not.
+ */
+ public static boolean trialExists(String trialName) {
+ return nativeTrialExists(trialName);
+ }
+
+ /**
+ * @param trialName The name of the trial with the parameter.
+ * @param parameterKey The key of the parameter.
+ * @return The value of the parameter or an empty string if not found.
+ */
+ public static String getVariationParameter(String trialName, String parameterKey) {
+ return nativeGetVariationParameter(trialName, parameterKey);
+ }
+
+ private static native String nativeFindFullName(String trialName);
+ private static native boolean nativeTrialExists(String trialName);
+ private static native String nativeGetVariationParameter(String trialName, String parameterKey);
+}
diff --git a/base/android/java/src/org/chromium/base/FileUtils.java b/base/android/java/src/org/chromium/base/FileUtils.java
new file mode 100644
index 0000000000..e44cd928ae
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/FileUtils.java
@@ -0,0 +1,149 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Helper methods for dealing with Files.
+ */
+public class FileUtils {
+ private static final String TAG = "FileUtils";
+
+ /**
+ * Delete the given File and (if it's a directory) everything within it.
+ */
+ public static void recursivelyDeleteFile(File currentFile) {
+ ThreadUtils.assertOnBackgroundThread();
+ if (currentFile.isDirectory()) {
+ File[] files = currentFile.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ recursivelyDeleteFile(file);
+ }
+ }
+ }
+
+ if (!currentFile.delete()) Log.e(TAG, "Failed to delete: " + currentFile);
+ }
+
+ /**
+ * Delete the given files or directories by calling {@link #recursivelyDeleteFile(File)}.
+ * @param files The files to delete.
+ */
+ public static void batchDeleteFiles(List<File> files) {
+ ThreadUtils.assertOnBackgroundThread();
+
+ for (File file : files) {
+ if (file.exists()) recursivelyDeleteFile(file);
+ }
+ }
+
+ /**
+ * Extracts an asset from the app's APK to a file.
+ * @param context
+ * @param assetName Name of the asset to extract.
+ * @param dest File to extract the asset to.
+ * @return true on success.
+ */
+ public static boolean extractAsset(Context context, String assetName, File dest) {
+ InputStream inputStream = null;
+ OutputStream outputStream = null;
+ try {
+ inputStream = context.getAssets().open(assetName);
+ outputStream = new BufferedOutputStream(new FileOutputStream(dest));
+ byte[] buffer = new byte[8192];
+ int c;
+ while ((c = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, c);
+ }
+ inputStream.close();
+ outputStream.close();
+ return true;
+ } catch (IOException e) {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ex) {
+ }
+ }
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Atomically copies the data from an input stream into an output file.
+ * @param is Input file stream to read data from.
+ * @param outFile Output file path.
+ * @param buffer Caller-provided buffer. Provided to avoid allocating the same
+ * buffer on each call when copying several files in sequence.
+ * @throws IOException in case of I/O error.
+ */
+ public static void copyFileStreamAtomicWithBuffer(InputStream is, File outFile, byte[] buffer)
+ throws IOException {
+ File tmpOutputFile = new File(outFile.getPath() + ".tmp");
+ try (OutputStream os = new FileOutputStream(tmpOutputFile)) {
+ Log.i(TAG, "Writing to %s", outFile);
+
+ int count = 0;
+ while ((count = is.read(buffer, 0, buffer.length)) != -1) {
+ os.write(buffer, 0, count);
+ }
+ }
+ if (!tmpOutputFile.renameTo(outFile)) {
+ throw new IOException();
+ }
+ }
+
+ /**
+ * Returns a URI that points at the file.
+ * @param file File to get a URI for.
+ * @return URI that points at that file, either as a content:// URI or a file:// URI.
+ */
+ public static Uri getUriForFile(File file) {
+ // TODO(crbug/709584): Uncomment this when http://crbug.com/709584 has been fixed.
+ // assert !ThreadUtils.runningOnUiThread();
+ Uri uri = null;
+
+ try {
+ // Try to obtain a content:// URI, which is preferred to a file:// URI so that
+ // receiving apps don't attempt to determine the file's mime type (which often fails).
+ uri = ContentUriUtils.getContentUriFromFile(file);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Could not create content uri: " + e);
+ }
+
+ if (uri == null) uri = Uri.fromFile(file);
+
+ return uri;
+ }
+
+ /**
+ * Returns the file extension, or an empty string if none.
+ * @param file Name of the file, with or without the full path.
+ * @return empty string if no extension, extension otherwise.
+ */
+ public static String getExtension(String file) {
+ int index = file.lastIndexOf('.');
+ if (index == -1) return "";
+ return file.substring(index + 1).toLowerCase(Locale.US);
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java b/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java
new file mode 100644
index 0000000000..cbaf7f76a1
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java
@@ -0,0 +1,31 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * This class provides an interface to the native class for writing
+ * important data files without risking data loss.
+ */
+@JNINamespace("base::android")
+public class ImportantFileWriterAndroid {
+
+ /**
+ * Write a binary file atomically.
+ *
+ * This either writes all the data or leaves the file unchanged.
+ *
+ * @param fileName The complete path of the file to be written
+ * @param data The data to be written to the file
+ * @return true if the data was written to the file, false if not.
+ */
+ public static boolean writeFileAtomically(String fileName, byte[] data) {
+ return nativeWriteFileAtomically(fileName, data);
+ }
+
+ private static native boolean nativeWriteFileAtomically(
+ String fileName, byte[] data);
+}
diff --git a/base/android/java/src/org/chromium/base/JNIUtils.java b/base/android/java/src/org/chromium/base/JNIUtils.java
new file mode 100644
index 0000000000..3fcec91316
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/JNIUtils.java
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+
+/**
+ * This class provides JNI-related methods to the native library.
+ */
+@MainDex
+public class JNIUtils {
+ private static Boolean sSelectiveJniRegistrationEnabled;
+
+ /**
+ * This returns a ClassLoader that is capable of loading Chromium Java code. Such a ClassLoader
+ * is needed for the few cases where the JNI mechanism is unable to automatically determine the
+ * appropriate ClassLoader instance.
+ */
+ @CalledByNative
+ public static Object getClassLoader() {
+ return JNIUtils.class.getClassLoader();
+ }
+
+ /**
+ * @return whether or not the current process supports selective JNI registration.
+ */
+ @CalledByNative
+ public static boolean isSelectiveJniRegistrationEnabled() {
+ if (sSelectiveJniRegistrationEnabled == null) {
+ sSelectiveJniRegistrationEnabled = false;
+ }
+ return sSelectiveJniRegistrationEnabled;
+ }
+
+ /**
+ * Allow this process to selectively perform JNI registration. This must be called before
+ * loading native libraries or it will have no effect.
+ */
+ public static void enableSelectiveJniRegistration() {
+ assert sSelectiveJniRegistrationEnabled == null;
+ sSelectiveJniRegistrationEnabled = true;
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/JavaHandlerThread.java b/base/android/java/src/org/chromium/base/JavaHandlerThread.java
new file mode 100644
index 0000000000..9a1c924398
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/JavaHandlerThread.java
@@ -0,0 +1,119 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+/**
+ * Thread in Java with an Android Handler. This class is not thread safe.
+ */
+@JNINamespace("base::android")
+@MainDex
+public class JavaHandlerThread {
+ private final HandlerThread mThread;
+
+ private Throwable mUnhandledException;
+
+ /**
+ * Construct a java-only instance. Can be connected with native side later.
+ * Useful for cases where a java thread is needed before native library is loaded.
+ */
+ public JavaHandlerThread(String name, int priority) {
+ mThread = new HandlerThread(name, priority);
+ }
+
+ @CalledByNative
+ private static JavaHandlerThread create(String name, int priority) {
+ return new JavaHandlerThread(name, priority);
+ }
+
+ public Looper getLooper() {
+ assert hasStarted();
+ return mThread.getLooper();
+ }
+
+ public void maybeStart() {
+ if (hasStarted()) return;
+ mThread.start();
+ }
+
+ @CalledByNative
+ private void startAndInitialize(final long nativeThread, final long nativeEvent) {
+ maybeStart();
+ new Handler(mThread.getLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ nativeInitializeThread(nativeThread, nativeEvent);
+ }
+ });
+ }
+
+ @CalledByNative
+ private void quitThreadSafely(final long nativeThread) {
+ // Allow pending java tasks to run, but don't run any delayed or newly queued up tasks.
+ new Handler(mThread.getLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ mThread.quit();
+ nativeOnLooperStopped(nativeThread);
+ }
+ });
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ // When we can, signal that new tasks queued up won't be run.
+ mThread.getLooper().quitSafely();
+ }
+ }
+
+ @CalledByNative
+ private void joinThread() {
+ boolean joined = false;
+ while (!joined) {
+ try {
+ mThread.join();
+ joined = true;
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ private boolean hasStarted() {
+ return mThread.getState() != Thread.State.NEW;
+ }
+
+ @CalledByNative
+ private boolean isAlive() {
+ return mThread.isAlive();
+ }
+
+ // This should *only* be used for tests. In production we always need to call the original
+ // uncaught exception handler (the framework's) after any uncaught exception handling we do, as
+ // it generates crash dumps and kills the process.
+ @CalledByNative
+ private void listenForUncaughtExceptionsForTesting() {
+ mThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ mUnhandledException = e;
+ }
+ });
+ }
+
+ @CalledByNative
+ private Throwable getUncaughtExceptionIfAny() {
+ return mUnhandledException;
+ }
+
+ private native void nativeInitializeThread(long nativeJavaHandlerThread, long nativeEvent);
+ private native void nativeOnLooperStopped(long nativeJavaHandlerThread);
+}
diff --git a/base/android/java/src/org/chromium/base/LocaleUtils.java b/base/android/java/src/org/chromium/base/LocaleUtils.java
new file mode 100644
index 0000000000..05d39029a5
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/LocaleUtils.java
@@ -0,0 +1,207 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.LocaleList;
+import android.text.TextUtils;
+
+import org.chromium.base.annotations.CalledByNative;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * This class provides the locale related methods.
+ */
+public class LocaleUtils {
+ /**
+ * Guards this class from being instantiated.
+ */
+ private LocaleUtils() {
+ }
+
+ /**
+ * Java keeps deprecated language codes for Hebrew, Yiddish and Indonesian but Chromium uses
+ * updated ones. Similarly, Android uses "tl" while Chromium uses "fil" for Tagalog/Filipino.
+ * So apply a mapping here.
+ * See http://developer.android.com/reference/java/util/Locale.html
+ * @return a updated language code for Chromium with given language string.
+ */
+ public static String getUpdatedLanguageForChromium(String language) {
+ // IMPORTANT: Keep in sync with the mapping found in:
+ // build/android/gyp/util/resource_utils.py
+ switch (language) {
+ case "iw":
+ return "he"; // Hebrew
+ case "ji":
+ return "yi"; // Yiddish
+ case "in":
+ return "id"; // Indonesian
+ case "tl":
+ return "fil"; // Filipino
+ default:
+ return language;
+ }
+ }
+
+ /**
+ * @return a locale with updated language codes for Chromium, with translated modern language
+ * codes used by Chromium.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @VisibleForTesting
+ public static Locale getUpdatedLocaleForChromium(Locale locale) {
+ String language = locale.getLanguage();
+ String languageForChrome = getUpdatedLanguageForChromium(language);
+ if (languageForChrome.equals(language)) {
+ return locale;
+ }
+ return new Locale.Builder().setLocale(locale).setLanguage(languageForChrome).build();
+ }
+
+ /**
+ * Android uses "tl" while Chromium uses "fil" for Tagalog/Filipino.
+ * So apply a mapping here.
+ * See http://developer.android.com/reference/java/util/Locale.html
+ * @return a updated language code for Android with given language string.
+ */
+ public static String getUpdatedLanguageForAndroid(String language) {
+ // IMPORTANT: Keep in sync with the mapping found in:
+ // build/android/gyp/util/resource_utils.py
+ switch (language) {
+ case "und":
+ return ""; // Undefined
+ case "fil":
+ return "tl"; // Filipino
+ default:
+ return language;
+ }
+ }
+
+ /**
+ * @return a locale with updated language codes for Android, from translated modern language
+ * codes used by Chromium.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @VisibleForTesting
+ public static Locale getUpdatedLocaleForAndroid(Locale locale) {
+ String language = locale.getLanguage();
+ String languageForAndroid = getUpdatedLanguageForAndroid(language);
+ if (languageForAndroid.equals(language)) {
+ return locale;
+ }
+ return new Locale.Builder().setLocale(locale).setLanguage(languageForAndroid).build();
+ }
+
+ /**
+ * This function creates a Locale object from xx-XX style string where xx is language code
+ * and XX is a country code. This works for API level lower than 21.
+ * @return the locale that best represents the language tag.
+ */
+ public static Locale forLanguageTagCompat(String languageTag) {
+ String[] tag = languageTag.split("-");
+ if (tag.length == 0) {
+ return new Locale("");
+ }
+ String language = getUpdatedLanguageForAndroid(tag[0]);
+ if ((language.length() != 2 && language.length() != 3)) {
+ return new Locale("");
+ }
+ if (tag.length == 1) {
+ return new Locale(language);
+ }
+ String country = tag[1];
+ if (country.length() != 2 && country.length() != 3) {
+ return new Locale(language);
+ }
+ return new Locale(language, country);
+ }
+
+ /**
+ * This function creates a Locale object from xx-XX style string where xx is language code
+ * and XX is a country code.
+ * @return the locale that best represents the language tag.
+ */
+ public static Locale forLanguageTag(String languageTag) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ Locale locale = Locale.forLanguageTag(languageTag);
+ return getUpdatedLocaleForAndroid(locale);
+ }
+ return forLanguageTagCompat(languageTag);
+ }
+
+ /**
+ * Converts Locale object to the BCP 47 compliant string format.
+ * This works for API level lower than 24.
+ *
+ * Note that for Android M or before, we cannot use Locale.getLanguage() and
+ * Locale.toLanguageTag() for this purpose. Since Locale.getLanguage() returns deprecated
+ * language code even if the Locale object is constructed with updated language code. As for
+ * Locale.toLanguageTag(), it does a special conversion from deprecated language code to updated
+ * one, but it is only usable for Android N or after.
+ * @return a well-formed IETF BCP 47 language tag with language and country code that
+ * represents this locale.
+ */
+ public static String toLanguageTag(Locale locale) {
+ String language = getUpdatedLanguageForChromium(locale.getLanguage());
+ String country = locale.getCountry();
+ if (language.equals("no") && country.equals("NO") && locale.getVariant().equals("NY")) {
+ return "nn-NO";
+ }
+ return country.isEmpty() ? language : language + "-" + country;
+ }
+
+ /**
+ * Converts LocaleList object to the comma separated BCP 47 compliant string format.
+ *
+ * @return a well-formed IETF BCP 47 language tag with language and country code that
+ * represents this locale list.
+ */
+ @TargetApi(Build.VERSION_CODES.N)
+ public static String toLanguageTags(LocaleList localeList) {
+ ArrayList<String> newLocaleList = new ArrayList<>();
+ for (int i = 0; i < localeList.size(); i++) {
+ Locale locale = getUpdatedLocaleForChromium(localeList.get(i));
+ newLocaleList.add(toLanguageTag(locale));
+ }
+ return TextUtils.join(",", newLocaleList);
+ }
+
+ /**
+ * @return a comma separated language tags string that represents a default locale.
+ * Each language tag is well-formed IETF BCP 47 language tag with language and country
+ * code.
+ */
+ @CalledByNative
+ public static String getDefaultLocaleString() {
+ return toLanguageTag(Locale.getDefault());
+ }
+
+ /**
+ * @return a comma separated language tags string that represents a default locale or locales.
+ * Each language tag is well-formed IETF BCP 47 language tag with language and country
+ * code.
+ */
+ public static String getDefaultLocaleListString() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return toLanguageTags(LocaleList.getDefault());
+ }
+ return getDefaultLocaleString();
+ }
+
+ /**
+ * @return The default country code set during install.
+ */
+ @CalledByNative
+ private static String getDefaultCountryCode() {
+ CommandLine commandLine = CommandLine.getInstance();
+ return commandLine.hasSwitch(BaseSwitches.DEFAULT_COUNTRY_CODE_AT_INSTALL)
+ ? commandLine.getSwitchValue(BaseSwitches.DEFAULT_COUNTRY_CODE_AT_INSTALL)
+ : Locale.getDefault().getCountry();
+ }
+
+}
diff --git a/base/android/java/src/org/chromium/base/MemoryPressureListener.java b/base/android/java/src/org/chromium/base/MemoryPressureListener.java
new file mode 100644
index 0000000000..6c80970f48
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/MemoryPressureListener.java
@@ -0,0 +1,130 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.app.Activity;
+import android.content.ComponentCallbacks2;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.memory.MemoryPressureCallback;
+
+/**
+ * This class is Java equivalent of base::MemoryPressureListener: it distributes pressure
+ * signals to callbacks.
+ *
+ * The class also serves as an entry point to the native side - once native code is ready,
+ * it adds native callback.
+ *
+ * notifyMemoryPressure() is called exclusively by MemoryPressureMonitor, which
+ * monitors and throttles pressure signals.
+ *
+ * NOTE: this class should only be used on UiThread as defined by ThreadUtils (which is
+ * Android main thread for Chrome, but can be some other thread for WebView).
+ */
+@MainDex
+public class MemoryPressureListener {
+ /**
+ * Sending an intent with this action to Chrome will cause it to issue a call to onLowMemory
+ * thus simulating a low memory situations.
+ */
+ private static final String ACTION_LOW_MEMORY = "org.chromium.base.ACTION_LOW_MEMORY";
+
+ /**
+ * Sending an intent with this action to Chrome will cause it to issue a call to onTrimMemory
+ * thus simulating a low memory situations.
+ */
+ private static final String ACTION_TRIM_MEMORY = "org.chromium.base.ACTION_TRIM_MEMORY";
+
+ /**
+ * Sending an intent with this action to Chrome will cause it to issue a call to onTrimMemory
+ * with notification level TRIM_MEMORY_RUNNING_CRITICAL thus simulating a low memory situation
+ */
+ private static final String ACTION_TRIM_MEMORY_RUNNING_CRITICAL =
+ "org.chromium.base.ACTION_TRIM_MEMORY_RUNNING_CRITICAL";
+
+ /**
+ * Sending an intent with this action to Chrome will cause it to issue a call to onTrimMemory
+ * with notification level TRIM_MEMORY_MODERATE thus simulating a low memory situation
+ */
+ private static final String ACTION_TRIM_MEMORY_MODERATE =
+ "org.chromium.base.ACTION_TRIM_MEMORY_MODERATE";
+
+ private static final ObserverList<MemoryPressureCallback> sCallbacks = new ObserverList<>();
+
+ /**
+ * Called by the native side to add native callback.
+ */
+ @CalledByNative
+ private static void addNativeCallback() {
+ addCallback(MemoryPressureListener::nativeOnMemoryPressure);
+ }
+
+ /**
+ * Adds a memory pressure callback.
+ * Callback is only added once, regardless of the number of addCallback() calls.
+ * This method should be called only on ThreadUtils.UiThread.
+ */
+ public static void addCallback(MemoryPressureCallback callback) {
+ sCallbacks.addObserver(callback);
+ }
+
+ /**
+ * Removes previously added memory pressure callback.
+ * This method should be called only on ThreadUtils.UiThread.
+ */
+ public static void removeCallback(MemoryPressureCallback callback) {
+ sCallbacks.removeObserver(callback);
+ }
+
+ /**
+ * Distributes |pressure| to all callbacks.
+ * This method should be called only on ThreadUtils.UiThread.
+ */
+ public static void notifyMemoryPressure(@MemoryPressureLevel int pressure) {
+ for (MemoryPressureCallback callback : sCallbacks) {
+ callback.onPressure(pressure);
+ }
+ }
+
+ /**
+ * Used by applications to simulate a memory pressure signal. By throwing certain intent
+ * actions.
+ */
+ public static boolean handleDebugIntent(Activity activity, String action) {
+ if (ACTION_LOW_MEMORY.equals(action)) {
+ simulateLowMemoryPressureSignal(activity);
+ } else if (ACTION_TRIM_MEMORY.equals(action)) {
+ simulateTrimMemoryPressureSignal(activity, ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ } else if (ACTION_TRIM_MEMORY_RUNNING_CRITICAL.equals(action)) {
+ simulateTrimMemoryPressureSignal(activity,
+ ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL);
+ } else if (ACTION_TRIM_MEMORY_MODERATE.equals(action)) {
+ simulateTrimMemoryPressureSignal(activity, ComponentCallbacks2.TRIM_MEMORY_MODERATE);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static void simulateLowMemoryPressureSignal(Activity activity) {
+ // The Application and the Activity each have a list of callbacks they notify when this
+ // method is called. Notifying these will simulate the event at the App/Activity level
+ // as well as trigger the listener bound from native in this process.
+ activity.getApplication().onLowMemory();
+ activity.onLowMemory();
+ }
+
+ private static void simulateTrimMemoryPressureSignal(Activity activity, int level) {
+ // The Application and the Activity each have a list of callbacks they notify when this
+ // method is called. Notifying these will simulate the event at the App/Activity level
+ // as well as trigger the listener bound from native in this process.
+ activity.getApplication().onTrimMemory(level);
+ activity.onTrimMemory(level);
+ }
+
+ private static native void nativeOnMemoryPressure(@MemoryPressureLevel int pressure);
+}
diff --git a/base/android/java/src/org/chromium/base/NonThreadSafe.java b/base/android/java/src/org/chromium/base/NonThreadSafe.java
new file mode 100644
index 0000000000..53f38d2c81
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/NonThreadSafe.java
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * NonThreadSafe is a helper class used to help verify that methods of a
+ * class are called from the same thread.
+ */
+public class NonThreadSafe {
+ private Long mThreadId;
+
+ public NonThreadSafe() {
+ ensureThreadIdAssigned();
+ }
+
+ /**
+ * Changes the thread that is checked for in CalledOnValidThread. This may
+ * be useful when an object may be created on one thread and then used
+ * exclusively on another thread.
+ */
+ @VisibleForTesting
+ public synchronized void detachFromThread() {
+ mThreadId = null;
+ }
+
+ /**
+ * Checks if the method is called on the valid thread.
+ * Assigns the current thread if no thread was assigned.
+ */
+ @SuppressWarnings("NoSynchronizedMethodCheck")
+ public synchronized boolean calledOnValidThread() {
+ ensureThreadIdAssigned();
+ return mThreadId.equals(Thread.currentThread().getId());
+ }
+
+ private void ensureThreadIdAssigned() {
+ if (mThreadId == null) mThreadId = Thread.currentThread().getId();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/ObserverList.java b/base/android/java/src/org/chromium/base/ObserverList.java
new file mode 100644
index 0000000000..59276c6ea8
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ObserverList.java
@@ -0,0 +1,249 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * A container for a list of observers.
+ * <p/>
+ * This container can be modified during iteration without invalidating the iterator.
+ * So, it safely handles the case of an observer removing itself or other observers from the list
+ * while observers are being notified.
+ * <p/>
+ * The implementation (and the interface) is heavily influenced by the C++ ObserverList.
+ * Notable differences:
+ * - The iterator implements NOTIFY_EXISTING_ONLY.
+ * - The range-based for loop is left to the clients to implement in terms of iterator().
+ * <p/>
+ * This class is not threadsafe. Observers MUST be added, removed and will be notified on the same
+ * thread this is created.
+ *
+ * @param <E> The type of observers that this list should hold.
+ */
+@NotThreadSafe
+public class ObserverList<E> implements Iterable<E> {
+ /**
+ * Extended iterator interface that provides rewind functionality.
+ */
+ public interface RewindableIterator<E> extends Iterator<E> {
+ /**
+ * Rewind the iterator back to the beginning.
+ *
+ * If we need to iterate multiple times, we can avoid iterator object reallocation by using
+ * this method.
+ */
+ public void rewind();
+ }
+
+ public final List<E> mObservers = new ArrayList<E>();
+ private int mIterationDepth;
+ private int mCount;
+ private boolean mNeedsCompact;
+
+ public ObserverList() {}
+
+ /**
+ * Add an observer to the list.
+ * <p/>
+ * An observer should not be added to the same list more than once. If an iteration is already
+ * in progress, this observer will be not be visible during that iteration.
+ *
+ * @return true if the observer list changed as a result of the call.
+ */
+ public boolean addObserver(E obs) {
+ // Avoid adding null elements to the list as they may be removed on a compaction.
+ if (obs == null || mObservers.contains(obs)) {
+ return false;
+ }
+
+ // Structurally modifying the underlying list here. This means we
+ // cannot use the underlying list's iterator to iterate over the list.
+ boolean result = mObservers.add(obs);
+ assert result;
+
+ ++mCount;
+ return true;
+ }
+
+ /**
+ * Remove an observer from the list if it is in the list.
+ *
+ * @return true if an element was removed as a result of this call.
+ */
+ public boolean removeObserver(E obs) {
+ if (obs == null) {
+ return false;
+ }
+
+ int index = mObservers.indexOf(obs);
+ if (index == -1) {
+ return false;
+ }
+
+ if (mIterationDepth == 0) {
+ // No one is iterating over the list.
+ mObservers.remove(index);
+ } else {
+ mNeedsCompact = true;
+ mObservers.set(index, null);
+ }
+ --mCount;
+ assert mCount >= 0;
+
+ return true;
+ }
+
+ public boolean hasObserver(E obs) {
+ return mObservers.contains(obs);
+ }
+
+ public void clear() {
+ mCount = 0;
+
+ if (mIterationDepth == 0) {
+ mObservers.clear();
+ return;
+ }
+
+ int size = mObservers.size();
+ mNeedsCompact |= size != 0;
+ for (int i = 0; i < size; i++) {
+ mObservers.set(i, null);
+ }
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return new ObserverListIterator();
+ }
+
+ /**
+ * It's the same as {@link ObserverList#iterator()} but the return type is
+ * {@link RewindableIterator}. Use this iterator type if you need to use
+ * {@link RewindableIterator#rewind()}.
+ */
+ public RewindableIterator<E> rewindableIterator() {
+ return new ObserverListIterator();
+ }
+
+ /**
+ * Returns the number of observers currently registered in the ObserverList.
+ * This is equivalent to the number of non-empty spaces in |mObservers|.
+ */
+ public int size() {
+ return mCount;
+ }
+
+ /**
+ * Returns true if the ObserverList contains no observers.
+ */
+ public boolean isEmpty() {
+ return mCount == 0;
+ }
+
+ /**
+ * Compact the underlying list be removing null elements.
+ * <p/>
+ * Should only be called when mIterationDepth is zero.
+ */
+ private void compact() {
+ assert mIterationDepth == 0;
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ if (mObservers.get(i) == null) {
+ mObservers.remove(i);
+ }
+ }
+ }
+
+ private void incrementIterationDepth() {
+ mIterationDepth++;
+ }
+
+ private void decrementIterationDepthAndCompactIfNeeded() {
+ mIterationDepth--;
+ assert mIterationDepth >= 0;
+ if (mIterationDepth > 0) return;
+ if (!mNeedsCompact) return;
+ mNeedsCompact = false;
+ compact();
+ }
+
+ /**
+ * Returns the size of the underlying storage of the ObserverList.
+ * It will take into account the empty spaces inside |mObservers|.
+ */
+ private int capacity() {
+ return mObservers.size();
+ }
+
+ private E getObserverAt(int index) {
+ return mObservers.get(index);
+ }
+
+ private class ObserverListIterator implements RewindableIterator<E> {
+ private int mListEndMarker;
+ private int mIndex;
+ private boolean mIsExhausted;
+
+ private ObserverListIterator() {
+ ObserverList.this.incrementIterationDepth();
+ mListEndMarker = ObserverList.this.capacity();
+ }
+
+ @Override
+ public void rewind() {
+ compactListIfNeeded();
+ ObserverList.this.incrementIterationDepth();
+ mListEndMarker = ObserverList.this.capacity();
+ mIsExhausted = false;
+ mIndex = 0;
+ }
+
+ @Override
+ public boolean hasNext() {
+ int lookupIndex = mIndex;
+ while (lookupIndex < mListEndMarker
+ && ObserverList.this.getObserverAt(lookupIndex) == null) {
+ lookupIndex++;
+ }
+ if (lookupIndex < mListEndMarker) return true;
+
+ // We have reached the end of the list, allow for compaction.
+ compactListIfNeeded();
+ return false;
+ }
+
+ @Override
+ public E next() {
+ // Advance if the current element is null.
+ while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) {
+ mIndex++;
+ }
+ if (mIndex < mListEndMarker) return ObserverList.this.getObserverAt(mIndex++);
+
+ // We have reached the end of the list, allow for compaction.
+ compactListIfNeeded();
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private void compactListIfNeeded() {
+ if (!mIsExhausted) {
+ mIsExhausted = true;
+ ObserverList.this.decrementIterationDepthAndCompactIfNeeded();
+ }
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/PathService.java b/base/android/java/src/org/chromium/base/PathService.java
new file mode 100644
index 0000000000..9807c2e82a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/PathService.java
@@ -0,0 +1,26 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * This class provides java side access to the native PathService.
+ */
+@JNINamespace("base::android")
+public abstract class PathService {
+
+ // Must match the value of DIR_MODULE in base/base_paths.h!
+ public static final int DIR_MODULE = 3;
+
+ // Prevent instantiation.
+ private PathService() {}
+
+ public static void override(int what, String path) {
+ nativeOverride(what, path);
+ }
+
+ private static native void nativeOverride(int what, String path);
+}
diff --git a/base/android/java/src/org/chromium/base/PathUtils.java b/base/android/java/src/org/chromium/base/PathUtils.java
new file mode 100644
index 0000000000..e6fc8029b8
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/PathUtils.java
@@ -0,0 +1,263 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.system.Os;
+import android.text.TextUtils;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.metrics.RecordHistogram;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class provides the path related methods for the native library.
+ */
+@MainDex
+public abstract class PathUtils {
+ private static final String TAG = "PathUtils";
+ private static final String THUMBNAIL_DIRECTORY_NAME = "textures";
+
+ private static final int DATA_DIRECTORY = 0;
+ private static final int THUMBNAIL_DIRECTORY = 1;
+ private static final int CACHE_DIRECTORY = 2;
+ private static final int NUM_DIRECTORIES = 3;
+ private static final AtomicBoolean sInitializationStarted = new AtomicBoolean();
+ private static AsyncTask<Void, Void, String[]> sDirPathFetchTask;
+
+ // If the AsyncTask started in setPrivateDataDirectorySuffix() fails to complete by the time we
+ // need the values, we will need the suffix so that we can restart the task synchronously on
+ // the UI thread.
+ private static String sDataDirectorySuffix;
+ private static String sCacheSubDirectory;
+
+ // Prevent instantiation.
+ private PathUtils() {}
+
+ /**
+ * Initialization-on-demand holder. This exists for thread-safe lazy initialization. It will
+ * cause getOrComputeDirectoryPaths() to be called (safely) the first time DIRECTORY_PATHS is
+ * accessed.
+ *
+ * <p>See https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom.
+ */
+ private static class Holder {
+ private static final String[] DIRECTORY_PATHS = getOrComputeDirectoryPaths();
+ }
+
+ /**
+ * Get the directory paths from sDirPathFetchTask if available, or compute it synchronously
+ * on the UI thread otherwise. This should only be called as part of Holder's initialization
+ * above to guarantee thread-safety as part of the initialization-on-demand holder idiom.
+ */
+ private static String[] getOrComputeDirectoryPaths() {
+ try {
+ // We need to call sDirPathFetchTask.cancel() here to prevent races. If it returns
+ // true, that means that the task got canceled successfully (and thus, it did not
+ // finish running its task). Otherwise, it failed to cancel, meaning that it was
+ // already finished.
+ if (sDirPathFetchTask.cancel(false)) {
+ // Allow disk access here because we have no other choice.
+ try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
+ // sDirPathFetchTask did not complete. We have to run the code it was supposed
+ // to be responsible for synchronously on the UI thread.
+ return PathUtils.setPrivateDataDirectorySuffixInternal();
+ }
+ } else {
+ // sDirPathFetchTask succeeded, and the values we need should be ready to access
+ // synchronously in its internal future.
+ return sDirPathFetchTask.get();
+ }
+ } catch (InterruptedException e) {
+ } catch (ExecutionException e) {
+ }
+
+ return null;
+ }
+
+ @SuppressLint("NewApi")
+ private static void chmod(String path, int mode) {
+ // Both Os.chmod and ErrnoException require SDK >= 21. But while Dalvik on < 21 tolerates
+ // Os.chmod, it throws VerifyError for ErrnoException, so catch Exception instead.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
+ try {
+ Os.chmod(path, mode);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to set permissions for path \"" + path + "\"");
+ }
+ }
+
+ /**
+ * Fetch the path of the directory where private data is to be stored by the application. This
+ * is meant to be called in an AsyncTask in setPrivateDataDirectorySuffix(), but if we need the
+ * result before the AsyncTask has had a chance to finish, then it's best to cancel the task
+ * and run it on the UI thread instead, inside getOrComputeDirectoryPaths().
+ *
+ * @see Context#getDir(String, int)
+ */
+ private static String[] setPrivateDataDirectorySuffixInternal() {
+ String[] paths = new String[NUM_DIRECTORIES];
+ Context appContext = ContextUtils.getApplicationContext();
+ paths[DATA_DIRECTORY] = appContext.getDir(
+ sDataDirectorySuffix, Context.MODE_PRIVATE).getPath();
+ // MODE_PRIVATE results in rwxrwx--x, but we want rwx------, as a defence-in-depth measure.
+ chmod(paths[DATA_DIRECTORY], 0700);
+ paths[THUMBNAIL_DIRECTORY] = appContext.getDir(
+ THUMBNAIL_DIRECTORY_NAME, Context.MODE_PRIVATE).getPath();
+ if (appContext.getCacheDir() != null) {
+ if (sCacheSubDirectory == null) {
+ paths[CACHE_DIRECTORY] = appContext.getCacheDir().getPath();
+ } else {
+ paths[CACHE_DIRECTORY] =
+ new File(appContext.getCacheDir(), sCacheSubDirectory).getPath();
+ }
+ }
+ return paths;
+ }
+
+ /**
+ * Starts an asynchronous task to fetch the path of the directory where private data is to be
+ * stored by the application.
+ *
+ * <p>This task can run long (or more likely be delayed in a large task queue), in which case we
+ * want to cancel it and run on the UI thread instead. Unfortunately, this means keeping a bit
+ * of extra static state - we need to store the suffix and the application context in case we
+ * need to try to re-execute later.
+ *
+ * @param suffix The private data directory suffix.
+ * @param cacheSubDir The subdirectory in the cache directory to use, if non-null.
+ * @see Context#getDir(String, int)
+ */
+ public static void setPrivateDataDirectorySuffix(String suffix, String cacheSubDir) {
+ // This method should only be called once, but many tests end up calling it multiple times,
+ // so adding a guard here.
+ if (!sInitializationStarted.getAndSet(true)) {
+ assert ContextUtils.getApplicationContext() != null;
+ sDataDirectorySuffix = suffix;
+ sCacheSubDirectory = cacheSubDir;
+ sDirPathFetchTask = new AsyncTask<Void, Void, String[]>() {
+ @Override
+ protected String[] doInBackground(Void... unused) {
+ return PathUtils.setPrivateDataDirectorySuffixInternal();
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ public static void setPrivateDataDirectorySuffix(String suffix) {
+ setPrivateDataDirectorySuffix(suffix, null);
+ }
+
+ /**
+ * @param index The index of the cached directory path.
+ * @return The directory path requested.
+ */
+ private static String getDirectoryPath(int index) {
+ return Holder.DIRECTORY_PATHS[index];
+ }
+
+ /**
+ * @return the private directory that is used to store application data.
+ */
+ @CalledByNative
+ public static String getDataDirectory() {
+ assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
+ return getDirectoryPath(DATA_DIRECTORY);
+ }
+
+ /**
+ * @return the cache directory.
+ */
+ @CalledByNative
+ public static String getCacheDirectory() {
+ assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
+ return getDirectoryPath(CACHE_DIRECTORY);
+ }
+
+ @CalledByNative
+ public static String getThumbnailCacheDirectory() {
+ assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
+ return getDirectoryPath(THUMBNAIL_DIRECTORY);
+ }
+
+ /**
+ * @return the public downloads directory.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private static String getDownloadsDirectory() {
+ // Temporarily allowing disk access while fixing. TODO: http://crbug.com/508615
+ try (StrictModeContext unused = StrictModeContext.allowDiskReads()) {
+ long time = SystemClock.elapsedRealtime();
+ String downloadsPath =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ .getPath();
+ RecordHistogram.recordTimesHistogram("Android.StrictMode.DownloadsDir",
+ SystemClock.elapsedRealtime() - time, TimeUnit.MILLISECONDS);
+ return downloadsPath;
+ }
+ }
+
+ /**
+ * @return Download directories including the default storage directory on SD card, and a
+ * private directory on external SD card.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ public static String[] getAllPrivateDownloadsDirectories() {
+ File[] files;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
+ files = ContextUtils.getApplicationContext().getExternalFilesDirs(
+ Environment.DIRECTORY_DOWNLOADS);
+ }
+ } else {
+ files = new File[] {Environment.getExternalStorageDirectory()};
+ }
+
+ ArrayList<String> absolutePaths = new ArrayList<String>();
+ for (int i = 0; i < files.length; ++i) {
+ if (files[i] == null || TextUtils.isEmpty(files[i].getAbsolutePath())) continue;
+ absolutePaths.add(files[i].getAbsolutePath());
+ }
+
+ return absolutePaths.toArray(new String[absolutePaths.size()]);
+ }
+
+ /**
+ * @return the path to native libraries.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private static String getNativeLibraryDirectory() {
+ ApplicationInfo ai = ContextUtils.getApplicationContext().getApplicationInfo();
+ if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+ || (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return ai.nativeLibraryDir;
+ }
+
+ return "/system/lib/";
+ }
+
+ /**
+ * @return the external storage directory.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ public static String getExternalStorageDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/PowerMonitor.java b/base/android/java/src/org/chromium/base/PowerMonitor.java
new file mode 100644
index 0000000000..ae36a75d00
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/PowerMonitor.java
@@ -0,0 +1,80 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * Integrates native PowerMonitor with the java side.
+ */
+@JNINamespace("base::android")
+public class PowerMonitor {
+ private static PowerMonitor sInstance;
+
+ private boolean mIsBatteryPower;
+
+ public static void createForTests() {
+ // Applications will create this once the JNI side has been fully wired up both sides. For
+ // tests, we just need native -> java, that is, we don't need to notify java -> native on
+ // creation.
+ sInstance = new PowerMonitor();
+ }
+
+ /**
+ * Create a PowerMonitor instance if none exists.
+ */
+ public static void create() {
+ ThreadUtils.assertOnUiThread();
+
+ if (sInstance != null) return;
+
+ Context context = ContextUtils.getApplicationContext();
+ sInstance = new PowerMonitor();
+ IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ Intent batteryStatusIntent = context.registerReceiver(null, ifilter);
+ if (batteryStatusIntent != null) onBatteryChargingChanged(batteryStatusIntent);
+
+ IntentFilter powerConnectedFilter = new IntentFilter();
+ powerConnectedFilter.addAction(Intent.ACTION_POWER_CONNECTED);
+ powerConnectedFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ PowerMonitor.onBatteryChargingChanged(intent);
+ }
+ }, powerConnectedFilter);
+ }
+
+ private PowerMonitor() {
+ }
+
+ private static void onBatteryChargingChanged(Intent intent) {
+ assert sInstance != null;
+ int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+ // If we're not plugged, assume we're running on battery power.
+ sInstance.mIsBatteryPower = chargePlug != BatteryManager.BATTERY_PLUGGED_USB
+ && chargePlug != BatteryManager.BATTERY_PLUGGED_AC;
+ nativeOnBatteryChargingChanged();
+ }
+
+ @CalledByNative
+ private static boolean isBatteryPower() {
+ // Creation of the PowerMonitor can be deferred based on the browser startup path. If the
+ // battery power is requested prior to the browser triggering the creation, force it to be
+ // created now.
+ if (sInstance == null) create();
+
+ return sInstance.mIsBatteryPower;
+ }
+
+ private static native void nativeOnBatteryChargingChanged();
+}
diff --git a/base/android/java/src/org/chromium/base/Promise.java b/base/android/java/src/org/chromium/base/Promise.java
new file mode 100644
index 0000000000..4319148d9c
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/Promise.java
@@ -0,0 +1,294 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Handler;
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A Promise class to be used as a placeholder for a result that will be provided asynchronously.
+ * It must only be accessed from a single thread.
+ * @param <T> The type the Promise will be fulfilled with.
+ */
+public class Promise<T> {
+ // TODO(peconn): Implement rejection handlers that can recover from rejection.
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({UNFULFILLED, FULFILLED, REJECTED})
+ private @interface PromiseState {}
+
+ private static final int UNFULFILLED = 0;
+ private static final int FULFILLED = 1;
+ private static final int REJECTED = 2;
+
+ @PromiseState
+ private int mState = UNFULFILLED;
+
+ private T mResult;
+ private final List<Callback<T>> mFulfillCallbacks = new LinkedList<>();
+
+ private Exception mRejectReason;
+ private final List<Callback<Exception>> mRejectCallbacks = new LinkedList<>();
+
+ private final Thread mThread = Thread.currentThread();
+ private final Handler mHandler = new Handler();
+
+ private boolean mThrowingRejectionHandler;
+
+ /**
+ * A function class for use when chaining Promises with {@link Promise#then(Function)}.
+ * @param <A> The type of the function input.
+ * @param <R> The type of the function output.
+ */
+ public interface Function<A, R> {
+ R apply(A argument);
+ }
+
+ /**
+ * A function class for use when chaining Promises with {@link Promise#then(AsyncFunction)}.
+ * @param <A> The type of the function input.
+ * @param <R> The type of the function output.
+ */
+ public interface AsyncFunction<A, R> {
+ Promise<R> apply(A argument);
+ }
+
+ /**
+ * An exception class for when a rejected Promise is not handled and cannot pass the rejection
+ * to a subsequent Promise.
+ */
+ public static class UnhandledRejectionException extends RuntimeException {
+ public UnhandledRejectionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * Convenience method that calls {@link #then(Callback, Callback)} providing a rejection
+ * {@link Callback} that throws a {@link UnhandledRejectionException}. Only use this on
+ * Promises that do not have rejection handlers or dependant Promises.
+ */
+ public void then(Callback<T> onFulfill) {
+ checkThread();
+
+ // Allow multiple single argument then(Callback)'s, but don't bother adding duplicate
+ // throwing rejection handlers.
+ if (mThrowingRejectionHandler) {
+ thenInner(onFulfill);
+ return;
+ }
+
+ assert mRejectCallbacks.size() == 0 : "Do not call the single argument "
+ + "Promise.then(Callback) on a Promise that already has a rejection handler.";
+
+ Callback<Exception> onReject = reason -> {
+ throw new UnhandledRejectionException(
+ "Promise was rejected without a rejection handler.", reason);
+ };
+
+ then(onFulfill, onReject);
+ mThrowingRejectionHandler = true;
+ }
+
+ /**
+ * Queues {@link Callback}s to be run when the Promise is either fulfilled or rejected. If the
+ * Promise is already fulfilled or rejected, the appropriate callback will be run on the next
+ * iteration of the message loop.
+ *
+ * @param onFulfill The Callback to be called on fulfillment.
+ * @param onReject The Callback to be called on rejection. The argument to onReject will
+ * may be null if the Promise was rejected manually.
+ */
+ public void then(Callback<T> onFulfill, Callback<Exception> onReject) {
+ checkThread();
+ thenInner(onFulfill);
+ exceptInner(onReject);
+ }
+
+ /**
+ * Adds a rejection handler to the Promise. This handler will be called if this Promise or any
+ * Promises this Promise depends on is rejected or fails. The {@link Callback} will be given
+ * the exception that caused the rejection, or null if the rejection was manual (caused by a
+ * call to {@link #reject()}.
+ */
+ public void except(Callback<Exception> onReject) {
+ checkThread();
+ exceptInner(onReject);
+ }
+
+ private void thenInner(Callback<T> onFulfill) {
+ if (mState == FULFILLED) {
+ postCallbackToLooper(onFulfill, mResult);
+ } else if (mState == UNFULFILLED) {
+ mFulfillCallbacks.add(onFulfill);
+ }
+ }
+
+ private void exceptInner(Callback<Exception> onReject) {
+ assert !mThrowingRejectionHandler : "Do not add an exception handler to a Promise you have "
+ + "called the single argument Promise.then(Callback) on.";
+
+ if (mState == REJECTED) {
+ postCallbackToLooper(onReject, mRejectReason);
+ } else if (mState == UNFULFILLED) {
+ mRejectCallbacks.add(onReject);
+ }
+ }
+
+ /**
+ * Queues a {@link Promise.Function} to be run when the Promise is fulfilled. When this Promise
+ * is fulfilled, the function will be run and its result will be place in the returned Promise.
+ */
+ public <R> Promise<R> then(final Function<T, R> function) {
+ checkThread();
+
+ // Create a new Promise to store the result of the function.
+ final Promise<R> promise = new Promise<>();
+
+ // Once this Promise is fulfilled:
+ // - Apply the given function to the result.
+ // - Fulfill the new Promise.
+ thenInner(result -> {
+ try {
+ promise.fulfill(function.apply(result));
+ } catch (Exception e) {
+ // If function application fails, reject the next Promise.
+ promise.reject(e);
+ }
+ });
+
+ // If this Promise is rejected, reject the next Promise.
+ exceptInner(promise::reject);
+
+ return promise;
+ }
+
+ /**
+ * Queues a {@link Promise.AsyncFunction} to be run when the Promise is fulfilled. When this
+ * Promise is fulfilled, the AsyncFunction will be run. When the result of the AsyncFunction is
+ * available, it will be placed in the returned Promise.
+ */
+ public <R> Promise<R> then(final AsyncFunction<T, R> function) {
+ checkThread();
+
+ // Create a new Promise to be returned.
+ final Promise<R> promise = new Promise<>();
+
+ // Once this Promise is fulfilled:
+ // - Apply the given function to the result (giving us an inner Promise).
+ // - On fulfillment of this inner Promise, fulfill our return Promise.
+ thenInner(result -> {
+ try {
+ // When the inner Promise is fulfilled, fulfill the return Promise.
+ // Alternatively, if the inner Promise is rejected, reject the return Promise.
+ function.apply(result).then(promise::fulfill, promise::reject);
+ } catch (Exception e) {
+ // If creating the inner Promise failed, reject the next Promise.
+ promise.reject(e);
+ }
+ });
+
+ // If this Promise is rejected, reject the next Promise.
+ exceptInner(promise::reject);
+
+ return promise;
+ }
+
+ /**
+ * Fulfills the Promise with the result and passes it to any {@link Callback}s previously queued
+ * on the next iteration of the message loop.
+ */
+ public void fulfill(final T result) {
+ checkThread();
+ assert mState == UNFULFILLED;
+
+ mState = FULFILLED;
+ mResult = result;
+
+ for (final Callback<T> callback : mFulfillCallbacks) {
+ postCallbackToLooper(callback, result);
+ }
+
+ mFulfillCallbacks.clear();
+ }
+
+ /**
+ * Rejects the Promise, rejecting all those Promises that rely on it.
+ *
+ * This may throw an exception if a dependent Promise fails to handle the rejection, so it is
+ * important to make it explicit when a Promise may be rejected, so that users of that Promise
+ * know to provide rejection handling.
+ */
+ public void reject(final Exception reason) {
+ checkThread();
+ assert mState == UNFULFILLED;
+
+ mState = REJECTED;
+ mRejectReason = reason;
+
+ for (final Callback<Exception> callback : mRejectCallbacks) {
+ postCallbackToLooper(callback, reason);
+ }
+ mRejectCallbacks.clear();
+ }
+
+ /**
+ * Rejects a Promise, see {@link #reject(Exception)}.
+ */
+ public void reject() {
+ reject(null);
+ }
+
+ /**
+ * Returns whether the promise is fulfilled.
+ */
+ public boolean isFulfilled() {
+ checkThread();
+ return mState == FULFILLED;
+ }
+
+ /**
+ * Returns whether the promise is rejected.
+ */
+ public boolean isRejected() {
+ checkThread();
+ return mState == REJECTED;
+ }
+
+ /**
+ * Must be called after the promise has been fulfilled.
+ *
+ * @return The promised result.
+ */
+ public T getResult() {
+ assert isFulfilled();
+ return mResult;
+ }
+
+ /**
+ * Convenience method to return a Promise fulfilled with the given result.
+ */
+ public static <T> Promise<T> fulfilled(T result) {
+ Promise<T> promise = new Promise<>();
+ promise.fulfill(result);
+ return promise;
+ }
+
+ private void checkThread() {
+ assert mThread == Thread.currentThread() : "Promise must only be used on a single Thread.";
+ }
+
+ // We use a different template parameter here so this can be used for both T and Throwables.
+ private <S> void postCallbackToLooper(final Callback<S> callback, final S result) {
+ // Post the callbacks to the Thread looper so we don't get a long chain of callbacks
+ // holding up the thread.
+ mHandler.post(() -> callback.onResult(result));
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/SecureRandomInitializer.java b/base/android/java/src/org/chromium/base/SecureRandomInitializer.java
new file mode 100644
index 0000000000..bfd7b4943a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/SecureRandomInitializer.java
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.SuppressLint;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+
+/**
+ * This class contains code to initialize a SecureRandom generator securely on Android platforms
+ * <= 4.3. See
+ * {@link http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html}.
+ */
+// TODO(crbug.com/635567): Fix this properly.
+@SuppressLint("SecureRandom")
+public class SecureRandomInitializer {
+ private static final int NUM_RANDOM_BYTES = 16;
+
+ /**
+ * Safely initializes the random number generator, by seeding it with data from /dev/urandom.
+ */
+ public static void initialize(SecureRandom generator) throws IOException {
+ try (FileInputStream fis = new FileInputStream("/dev/urandom")) {
+ byte[] seedBytes = new byte[NUM_RANDOM_BYTES];
+ if (fis.read(seedBytes) != seedBytes.length) {
+ throw new IOException("Failed to get enough random data.");
+ }
+ generator.setSeed(seedBytes);
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/StreamUtil.java b/base/android/java/src/org/chromium/base/StreamUtil.java
new file mode 100644
index 0000000000..f8cbfeeb9e
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/StreamUtil.java
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Helper methods to deal with stream related tasks.
+ */
+public class StreamUtil {
+ /**
+ * Handle closing a {@link java.io.Closeable} via {@link java.io.Closeable#close()} and catch
+ * the potentially thrown {@link java.io.IOException}.
+ * @param closeable The Closeable to be closed.
+ */
+ public static void closeQuietly(Closeable closeable) {
+ if (closeable == null) return;
+
+ try {
+ closeable.close();
+ } catch (IOException ex) {
+ // Ignore the exception on close.
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/SysUtils.java b/base/android/java/src/org/chromium/base/SysUtils.java
new file mode 100644
index 0000000000..d4eb30de5b
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/SysUtils.java
@@ -0,0 +1,199 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.StrictMode;
+import android.util.Log;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.metrics.CachedMetrics;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Exposes system related information about the current device.
+ */
+@JNINamespace("base::android")
+public class SysUtils {
+ // A device reporting strictly more total memory in megabytes cannot be considered 'low-end'.
+ private static final int ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB = 512;
+ private static final int ANDROID_O_LOW_MEMORY_DEVICE_THRESHOLD_MB = 1024;
+
+ private static final String TAG = "SysUtils";
+
+ private static Boolean sLowEndDevice;
+ private static Integer sAmountOfPhysicalMemoryKB;
+
+ private static CachedMetrics.BooleanHistogramSample sLowEndMatches =
+ new CachedMetrics.BooleanHistogramSample("Android.SysUtilsLowEndMatches");
+
+ private SysUtils() { }
+
+ /**
+ * Return the amount of physical memory on this device in kilobytes.
+ * @return Amount of physical memory in kilobytes, or 0 if there was
+ * an error trying to access the information.
+ */
+ private static int detectAmountOfPhysicalMemoryKB() {
+ // Extract total memory RAM size by parsing /proc/meminfo, note that
+ // this is exactly what the implementation of sysconf(_SC_PHYS_PAGES)
+ // does. However, it can't be called because this method must be
+ // usable before any native code is loaded.
+
+ // An alternative is to use ActivityManager.getMemoryInfo(), but this
+ // requires a valid ActivityManager handle, which can only come from
+ // a valid Context object, which itself cannot be retrieved
+ // during early startup, where this method is called. And making it
+ // an explicit parameter here makes all call paths _much_ more
+ // complicated.
+
+ Pattern pattern = Pattern.compile("^MemTotal:\\s+([0-9]+) kB$");
+ // Synchronously reading files in /proc in the UI thread is safe.
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ FileReader fileReader = new FileReader("/proc/meminfo");
+ try {
+ BufferedReader reader = new BufferedReader(fileReader);
+ try {
+ String line;
+ for (;;) {
+ line = reader.readLine();
+ if (line == null) {
+ Log.w(TAG, "/proc/meminfo lacks a MemTotal entry?");
+ break;
+ }
+ Matcher m = pattern.matcher(line);
+ if (!m.find()) continue;
+
+ int totalMemoryKB = Integer.parseInt(m.group(1));
+ // Sanity check.
+ if (totalMemoryKB <= 1024) {
+ Log.w(TAG, "Invalid /proc/meminfo total size in kB: " + m.group(1));
+ break;
+ }
+
+ return totalMemoryKB;
+ }
+
+ } finally {
+ reader.close();
+ }
+ } finally {
+ fileReader.close();
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Cannot get total physical size from /proc/meminfo", e);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
+ return 0;
+ }
+
+ /**
+ * @return Whether or not this device should be considered a low end device.
+ */
+ @CalledByNative
+ public static boolean isLowEndDevice() {
+ if (sLowEndDevice == null) {
+ sLowEndDevice = detectLowEndDevice();
+ }
+ return sLowEndDevice.booleanValue();
+ }
+
+ /**
+ * @return Whether or not this device should be considered a low end device.
+ */
+ public static int amountOfPhysicalMemoryKB() {
+ if (sAmountOfPhysicalMemoryKB == null) {
+ sAmountOfPhysicalMemoryKB = detectAmountOfPhysicalMemoryKB();
+ }
+ return sAmountOfPhysicalMemoryKB.intValue();
+ }
+
+ /**
+ * @return Whether or not the system has low available memory.
+ */
+ @CalledByNative
+ public static boolean isCurrentlyLowMemory() {
+ ActivityManager am =
+ (ActivityManager) ContextUtils.getApplicationContext().getSystemService(
+ Context.ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
+ am.getMemoryInfo(info);
+ return info.lowMemory;
+ }
+
+ /**
+ * Resets the cached value, if any.
+ */
+ @VisibleForTesting
+ public static void resetForTesting() {
+ sLowEndDevice = null;
+ sAmountOfPhysicalMemoryKB = null;
+ }
+
+ public static boolean hasCamera(final Context context) {
+ final PackageManager pm = context.getPackageManager();
+ // JellyBean support.
+ boolean hasCamera = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ hasCamera |= pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+ }
+ return hasCamera;
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private static boolean detectLowEndDevice() {
+ assert CommandLine.isInitialized();
+ if (CommandLine.getInstance().hasSwitch(BaseSwitches.ENABLE_LOW_END_DEVICE_MODE)) {
+ return true;
+ }
+ if (CommandLine.getInstance().hasSwitch(BaseSwitches.DISABLE_LOW_END_DEVICE_MODE)) {
+ return false;
+ }
+
+ sAmountOfPhysicalMemoryKB = detectAmountOfPhysicalMemoryKB();
+ boolean isLowEnd = true;
+ if (sAmountOfPhysicalMemoryKB <= 0) {
+ isLowEnd = false;
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ isLowEnd = sAmountOfPhysicalMemoryKB / 1024 <= ANDROID_O_LOW_MEMORY_DEVICE_THRESHOLD_MB;
+ } else {
+ isLowEnd = sAmountOfPhysicalMemoryKB / 1024 <= ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB;
+ }
+
+ // For evaluation purposes check whether our computation agrees with Android API value.
+ Context appContext = ContextUtils.getApplicationContext();
+ boolean isLowRam = false;
+ if (appContext != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ isLowRam = ((ActivityManager) ContextUtils.getApplicationContext().getSystemService(
+ Context.ACTIVITY_SERVICE))
+ .isLowRamDevice();
+ }
+ sLowEndMatches.record(isLowEnd == isLowRam);
+
+ return isLowEnd;
+ }
+
+ /**
+ * Creates a new trace event to log the number of minor / major page faults, if tracing is
+ * enabled.
+ */
+ public static void logPageFaultCountToTracing() {
+ nativeLogPageFaultCountToTracing();
+ }
+
+ private static native void nativeLogPageFaultCountToTracing();
+}
diff --git a/base/android/java/src/org/chromium/base/ThrowUncaughtException.java b/base/android/java/src/org/chromium/base/ThrowUncaughtException.java
new file mode 100644
index 0000000000..d5f18a278d
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ThrowUncaughtException.java
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+
+@MainDex
+abstract class ThrowUncaughtException {
+ @CalledByNative
+ private static void post() {
+ ThreadUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ throw new RuntimeException("Intentional exception not caught by JNI");
+ }
+ });
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/TimeUtils.java b/base/android/java/src/org/chromium/base/TimeUtils.java
new file mode 100644
index 0000000000..dcacabf205
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/TimeUtils.java
@@ -0,0 +1,18 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+
+/** Time-related utilities. */
+@JNINamespace("base::android")
+@MainDex
+public class TimeUtils {
+ private TimeUtils() {}
+
+ /** Returns TimeTicks::Now() in microseconds. */
+ public static native long nativeGetTimeTicksNowUs();
+}
diff --git a/base/android/java/src/org/chromium/base/TraceEvent.java b/base/android/java/src/org/chromium/base/TraceEvent.java
new file mode 100644
index 0000000000..96590900e0
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/TraceEvent.java
@@ -0,0 +1,387 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Printer;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+/**
+ * Java mirror of Chrome trace event API. See base/trace_event/trace_event.h.
+ *
+ * To get scoped trace events, use the "try with resource" construct, for instance:
+ * <pre>{@code
+ * try (TraceEvent e = TraceEvent.scoped("MyTraceEvent")) {
+ * // code.
+ * }
+ * }</pre>
+ *
+ * It is OK to use tracing before the native library has loaded, in a slightly restricted fashion.
+ * @see EarlyTraceEvent for details.
+ */
+@JNINamespace("base::android")
+@MainDex
+public class TraceEvent implements AutoCloseable {
+ private static volatile boolean sEnabled;
+ private static volatile boolean sATraceEnabled; // True when taking an Android systrace.
+
+ private static class BasicLooperMonitor implements Printer {
+ private static final String EARLY_TOPLEVEL_TASK_NAME = "Looper.dispatchMessage: ";
+
+ @Override
+ public void println(final String line) {
+ if (line.startsWith(">")) {
+ beginHandling(line);
+ } else {
+ assert line.startsWith("<");
+ endHandling(line);
+ }
+ }
+
+ void beginHandling(final String line) {
+ // May return an out-of-date value. this is not an issue as EarlyTraceEvent#begin()
+ // will filter the event in this case.
+ boolean earlyTracingActive = EarlyTraceEvent.isActive();
+ if (sEnabled || earlyTracingActive) {
+ String target = getTarget(line);
+ if (sEnabled) {
+ nativeBeginToplevel(target);
+ } else if (earlyTracingActive) {
+ // Synthesize a task name instead of using a parameter, as early tracing doesn't
+ // support parameters.
+ EarlyTraceEvent.begin(EARLY_TOPLEVEL_TASK_NAME + target);
+ }
+ }
+ }
+
+ void endHandling(final String line) {
+ if (EarlyTraceEvent.isActive()) {
+ EarlyTraceEvent.end(EARLY_TOPLEVEL_TASK_NAME + getTarget(line));
+ }
+ if (sEnabled) nativeEndToplevel();
+ }
+
+ /**
+ * Android Looper formats |line| as ">>>>> Dispatching to (TARGET) [...]" since at least
+ * 2009 (Donut). Extracts the TARGET part of the message.
+ */
+ private static String getTarget(String logLine) {
+ int start = logLine.indexOf('(', 21); // strlen(">>>>> Dispatching to ")
+ int end = start == -1 ? -1 : logLine.indexOf(')', start);
+ return end != -1 ? logLine.substring(start + 1, end) : "";
+ }
+ }
+
+ /**
+ * A class that records, traces and logs statistics about the UI thead's Looper.
+ * The output of this class can be used in a number of interesting ways:
+ * <p>
+ * <ol><li>
+ * When using chrometrace, there will be a near-continuous line of
+ * measurements showing both event dispatches as well as idles;
+ * </li><li>
+ * Logging messages are output for events that run too long on the
+ * event dispatcher, making it easy to identify problematic areas;
+ * </li><li>
+ * Statistics are output whenever there is an idle after a non-trivial
+ * amount of activity, allowing information to be gathered about task
+ * density and execution cadence on the Looper;
+ * </li></ol>
+ * <p>
+ * The class attaches itself as an idle handler to the main Looper, and
+ * monitors the execution of events and idle notifications. Task counters
+ * accumulate between idle notifications and get reset when a new idle
+ * notification is received.
+ */
+ private static final class IdleTracingLooperMonitor extends BasicLooperMonitor
+ implements MessageQueue.IdleHandler {
+ // Tags for dumping to logcat or TraceEvent
+ private static final String TAG = "TraceEvent.LooperMonitor";
+ private static final String IDLE_EVENT_NAME = "Looper.queueIdle";
+
+ // Calculation constants
+ private static final long FRAME_DURATION_MILLIS = 1000L / 60L; // 60 FPS
+ // A reasonable threshold for defining a Looper event as "long running"
+ private static final long MIN_INTERESTING_DURATION_MILLIS =
+ FRAME_DURATION_MILLIS;
+ // A reasonable threshold for a "burst" of tasks on the Looper
+ private static final long MIN_INTERESTING_BURST_DURATION_MILLIS =
+ MIN_INTERESTING_DURATION_MILLIS * 3;
+
+ // Stats tracking
+ private long mLastIdleStartedAt;
+ private long mLastWorkStartedAt;
+ private int mNumTasksSeen;
+ private int mNumIdlesSeen;
+ private int mNumTasksSinceLastIdle;
+
+ // State
+ private boolean mIdleMonitorAttached;
+
+ // Called from within the begin/end methods only.
+ // This method can only execute on the looper thread, because that is
+ // the only thread that is permitted to call Looper.myqueue().
+ private final void syncIdleMonitoring() {
+ if (sEnabled && !mIdleMonitorAttached) {
+ // approximate start time for computational purposes
+ mLastIdleStartedAt = SystemClock.elapsedRealtime();
+ Looper.myQueue().addIdleHandler(this);
+ mIdleMonitorAttached = true;
+ Log.v(TAG, "attached idle handler");
+ } else if (mIdleMonitorAttached && !sEnabled) {
+ Looper.myQueue().removeIdleHandler(this);
+ mIdleMonitorAttached = false;
+ Log.v(TAG, "detached idle handler");
+ }
+ }
+
+ @Override
+ final void beginHandling(final String line) {
+ // Close-out any prior 'idle' period before starting new task.
+ if (mNumTasksSinceLastIdle == 0) {
+ TraceEvent.end(IDLE_EVENT_NAME);
+ }
+ mLastWorkStartedAt = SystemClock.elapsedRealtime();
+ syncIdleMonitoring();
+ super.beginHandling(line);
+ }
+
+ @Override
+ final void endHandling(final String line) {
+ final long elapsed = SystemClock.elapsedRealtime()
+ - mLastWorkStartedAt;
+ if (elapsed > MIN_INTERESTING_DURATION_MILLIS) {
+ traceAndLog(Log.WARN, "observed a task that took "
+ + elapsed + "ms: " + line);
+ }
+ super.endHandling(line);
+ syncIdleMonitoring();
+ mNumTasksSeen++;
+ mNumTasksSinceLastIdle++;
+ }
+
+ private static void traceAndLog(int level, String message) {
+ TraceEvent.instant("TraceEvent.LooperMonitor:IdleStats", message);
+ Log.println(level, TAG, message);
+ }
+
+ @Override
+ public final boolean queueIdle() {
+ final long now = SystemClock.elapsedRealtime();
+ if (mLastIdleStartedAt == 0) mLastIdleStartedAt = now;
+ final long elapsed = now - mLastIdleStartedAt;
+ mNumIdlesSeen++;
+ TraceEvent.begin(IDLE_EVENT_NAME, mNumTasksSinceLastIdle + " tasks since last idle.");
+ if (elapsed > MIN_INTERESTING_BURST_DURATION_MILLIS) {
+ // Dump stats
+ String statsString = mNumTasksSeen + " tasks and "
+ + mNumIdlesSeen + " idles processed so far, "
+ + mNumTasksSinceLastIdle + " tasks bursted and "
+ + elapsed + "ms elapsed since last idle";
+ traceAndLog(Log.DEBUG, statsString);
+ }
+ mLastIdleStartedAt = now;
+ mNumTasksSinceLastIdle = 0;
+ return true; // stay installed
+ }
+ }
+
+ // Holder for monitor avoids unnecessary construction on non-debug runs
+ private static final class LooperMonitorHolder {
+ private static final BasicLooperMonitor sInstance =
+ CommandLine.getInstance().hasSwitch(BaseSwitches.ENABLE_IDLE_TRACING)
+ ? new IdleTracingLooperMonitor() : new BasicLooperMonitor();
+ }
+
+ private final String mName;
+
+ /**
+ * Constructor used to support the "try with resource" construct.
+ */
+ private TraceEvent(String name, String arg) {
+ mName = name;
+ begin(name, arg);
+ }
+
+ @Override
+ public void close() {
+ end(mName);
+ }
+
+ /**
+ * Factory used to support the "try with resource" construct.
+ *
+ * Note that if tracing is not enabled, this will not result in allocating an object.
+ *
+ * @param name Trace event name.
+ * @param name The arguments of the event.
+ * @return a TraceEvent, or null if tracing is not enabled.
+ */
+ public static TraceEvent scoped(String name, String arg) {
+ if (!(EarlyTraceEvent.enabled() || enabled())) return null;
+ return new TraceEvent(name, arg);
+ }
+
+ /**
+ * Similar to {@link #scoped(String, String arg)}, but uses null for |arg|.
+ */
+ public static TraceEvent scoped(String name) {
+ return scoped(name, null);
+ }
+
+ /**
+ * Register an enabled observer, such that java traces are always enabled with native.
+ */
+ public static void registerNativeEnabledObserver() {
+ nativeRegisterEnabledObserver();
+ }
+
+ /**
+ * Notification from native that tracing is enabled/disabled.
+ */
+ @CalledByNative
+ public static void setEnabled(boolean enabled) {
+ if (enabled) EarlyTraceEvent.disable();
+ // Only disable logging if Chromium enabled it originally, so as to not disrupt logging done
+ // by other applications
+ if (sEnabled != enabled) {
+ sEnabled = enabled;
+ // Android M+ systrace logs this on its own. Only log it if not writing to Android
+ // systrace.
+ if (sATraceEnabled) return;
+ ThreadUtils.getUiThreadLooper().setMessageLogging(
+ enabled ? LooperMonitorHolder.sInstance : null);
+ }
+ }
+
+ /**
+ * May enable early tracing depending on the environment.
+ *
+ * Must be called after the command-line has been read.
+ */
+ public static void maybeEnableEarlyTracing() {
+ EarlyTraceEvent.maybeEnable();
+ if (EarlyTraceEvent.isActive()) {
+ ThreadUtils.getUiThreadLooper().setMessageLogging(LooperMonitorHolder.sInstance);
+ }
+ }
+
+ /**
+ * Enables or disabled Android systrace path of Chrome tracing. If enabled, all Chrome
+ * traces will be also output to Android systrace. Because of the overhead of Android
+ * systrace, this is for WebView only.
+ */
+ public static void setATraceEnabled(boolean enabled) {
+ if (sATraceEnabled == enabled) return;
+ sATraceEnabled = enabled;
+ if (enabled) {
+ // Calls TraceEvent.setEnabled(true) via
+ // TraceLog::EnabledStateObserver::OnTraceLogEnabled
+ nativeStartATrace();
+ } else {
+ // Calls TraceEvent.setEnabled(false) via
+ // TraceLog::EnabledStateObserver::OnTraceLogDisabled
+ nativeStopATrace();
+ }
+ }
+
+ /**
+ * @return True if tracing is enabled, false otherwise.
+ * It is safe to call trace methods without checking if TraceEvent
+ * is enabled.
+ */
+ public static boolean enabled() {
+ return sEnabled;
+ }
+
+ /**
+ * Triggers the 'instant' native trace event with no arguments.
+ * @param name The name of the event.
+ */
+ public static void instant(String name) {
+ if (sEnabled) nativeInstant(name, null);
+ }
+
+ /**
+ * Triggers the 'instant' native trace event.
+ * @param name The name of the event.
+ * @param arg The arguments of the event.
+ */
+ public static void instant(String name, String arg) {
+ if (sEnabled) nativeInstant(name, arg);
+ }
+
+ /**
+ * Triggers the 'start' native trace event with no arguments.
+ * @param name The name of the event.
+ * @param id The id of the asynchronous event.
+ */
+ public static void startAsync(String name, long id) {
+ EarlyTraceEvent.startAsync(name, id);
+ if (sEnabled) nativeStartAsync(name, id);
+ }
+
+ /**
+ * Triggers the 'finish' native trace event with no arguments.
+ * @param name The name of the event.
+ * @param id The id of the asynchronous event.
+ */
+ public static void finishAsync(String name, long id) {
+ EarlyTraceEvent.finishAsync(name, id);
+ if (sEnabled) nativeFinishAsync(name, id);
+ }
+
+ /**
+ * Triggers the 'begin' native trace event with no arguments.
+ * @param name The name of the event.
+ */
+ public static void begin(String name) {
+ begin(name, null);
+ }
+
+ /**
+ * Triggers the 'begin' native trace event.
+ * @param name The name of the event.
+ * @param arg The arguments of the event.
+ */
+ public static void begin(String name, String arg) {
+ EarlyTraceEvent.begin(name);
+ if (sEnabled) nativeBegin(name, arg);
+ }
+
+ /**
+ * Triggers the 'end' native trace event with no arguments.
+ * @param name The name of the event.
+ */
+ public static void end(String name) {
+ end(name, null);
+ }
+
+ /**
+ * Triggers the 'end' native trace event.
+ * @param name The name of the event.
+ * @param arg The arguments of the event.
+ */
+ public static void end(String name, String arg) {
+ EarlyTraceEvent.end(name);
+ if (sEnabled) nativeEnd(name, arg);
+ }
+
+ private static native void nativeRegisterEnabledObserver();
+ private static native void nativeStartATrace();
+ private static native void nativeStopATrace();
+ private static native void nativeInstant(String name, String arg);
+ private static native void nativeBegin(String name, String arg);
+ private static native void nativeEnd(String name, String arg);
+ private static native void nativeBeginToplevel(String target);
+ private static native void nativeEndToplevel();
+ private static native void nativeStartAsync(String name, long id);
+ private static native void nativeFinishAsync(String name, long id);
+}
diff --git a/base/android/java/src/org/chromium/base/UnguessableToken.java b/base/android/java/src/org/chromium/base/UnguessableToken.java
new file mode 100644
index 0000000000..4b1619dae8
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/UnguessableToken.java
@@ -0,0 +1,91 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.chromium.base.annotations.CalledByNative;
+
+/**
+ * This class mirrors unguessable_token.h . Since tokens are passed by value,
+ * we don't bother to maintain a native token. This implements Parcelable so
+ * that it may be sent via binder.
+ *
+ * To get one of these from native, one must start with a
+ * base::UnguessableToken, then create a Java object from it. See
+ * jni_unguessable_token.h for information.
+ */
+public class UnguessableToken implements Parcelable {
+ private final long mHigh;
+ private final long mLow;
+
+ private UnguessableToken(long high, long low) {
+ mHigh = high;
+ mLow = low;
+ }
+
+ @CalledByNative
+ private static UnguessableToken create(long high, long low) {
+ return new UnguessableToken(high, low);
+ }
+
+ @CalledByNative
+ public long getHighForSerialization() {
+ return mHigh;
+ }
+
+ @CalledByNative
+ public long getLowForSerialization() {
+ return mLow;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mHigh);
+ dest.writeLong(mLow);
+ }
+
+ public static final Parcelable.Creator<UnguessableToken> CREATOR =
+ new Parcelable.Creator<UnguessableToken>() {
+ @Override
+ public UnguessableToken createFromParcel(Parcel source) {
+ long high = source.readLong();
+ long low = source.readLong();
+ if (high == 0 || low == 0) {
+ // Refuse to create an empty UnguessableToken.
+ return null;
+ }
+ return new UnguessableToken(high, low);
+ }
+
+ @Override
+ public UnguessableToken[] newArray(int size) {
+ return new UnguessableToken[size];
+ }
+ };
+
+ // To avoid unwieldy calls in JNI for tests, parcel and unparcel.
+ // TODO(liberato): It would be nice if we could include this only with a
+ // java driver that's linked only with unit tests, but i don't see a way
+ // to do that.
+ @CalledByNative
+ private UnguessableToken parcelAndUnparcelForTesting() {
+ Parcel parcel = Parcel.obtain();
+ writeToParcel(parcel, 0);
+
+ // Rewind the parcel and un-parcel.
+ parcel.setDataPosition(0);
+ UnguessableToken token = CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return token;
+ }
+};
diff --git a/base/android/java/src/org/chromium/base/annotations/DoNotInline.java b/base/android/java/src/org/chromium/base/annotations/DoNotInline.java
new file mode 100644
index 0000000000..9252f3a79b
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/annotations/DoNotInline.java
@@ -0,0 +1,20 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The annotated method or class should never be inlined.
+ *
+ * The annotated method (or methods on the annotated class) are guaranteed not to be inlined by
+ * Proguard. Other optimizations may still apply.
+ */
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface DoNotInline {}
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
new file mode 100644
index 0000000000..5bc62042d4
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -0,0 +1,829 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.library_loader;
+
+import static org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Process;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.system.Os;
+
+import org.chromium.base.AsyncTask;
+import org.chromium.base.BuildConfig;
+import org.chromium.base.BuildInfo;
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.FileUtils;
+import org.chromium.base.Log;
+import org.chromium.base.SysUtils;
+import org.chromium.base.TraceEvent;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.metrics.RecordHistogram;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.ZipFile;
+
+import javax.annotation.Nullable;
+
+/**
+ * This class provides functionality to load and register the native libraries.
+ * Callers are allowed to separate loading the libraries from initializing them.
+ * This may be an advantage for Android Webview, where the libraries can be loaded
+ * by the zygote process, but then needs per process initialization after the
+ * application processes are forked from the zygote process.
+ *
+ * The libraries may be loaded and initialized from any thread. Synchronization
+ * primitives are used to ensure that overlapping requests from different
+ * threads are handled sequentially.
+ *
+ * See also base/android/library_loader/library_loader_hooks.cc, which contains
+ * the native counterpart to this class.
+ */
+@MainDex
+@JNINamespace("base::android")
+public class LibraryLoader {
+ private static final String TAG = "LibraryLoader";
+
+ // Set to true to enable debug logs.
+ private static final boolean DEBUG = false;
+
+ // Experience shows that on some devices, the PackageManager fails to properly extract
+ // native shared libraries to the /data partition at installation or upgrade time,
+ // which creates all kind of chaos (https://crbug.com/806998).
+ //
+ // We implement a fallback when we detect the issue by manually extracting the library
+ // into Chromium's own data directory, then retrying to load the new library from here.
+ //
+ // This will work for any device running K-. Starting with Android L, render processes
+ // cannot access the file system anymore, and extraction will always fail for them.
+ // However, the issue doesn't seem to appear in the field for Android L.
+ //
+ // Also, starting with M, the issue doesn't exist if shared libraries are stored
+ // uncompressed in the APK (as Chromium does), because the system linker can access them
+ // directly, and the PackageManager will thus never extract them in the first place.
+ static public final boolean PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION =
+ Build.VERSION.SDK_INT <= VERSION_CODES.KITKAT;
+
+ // Location of extracted native libraries.
+ private static final String LIBRARY_DIR = "native_libraries";
+
+ // SharedPreferences key for "don't prefetch libraries" flag
+ private static final String DONT_PREFETCH_LIBRARIES_KEY = "dont_prefetch_libraries";
+
+ private static final EnumeratedHistogramSample sRelinkerCountHistogram =
+ new EnumeratedHistogramSample("ChromiumAndroidLinker.RelinkerFallbackCount", 2);
+
+ // The singleton instance of LibraryLoader. Never null (not final for tests).
+ private static LibraryLoader sInstance = new LibraryLoader();
+
+ // One-way switch becomes true when the libraries are initialized (
+ // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in
+ // library_loader_hooks.cc).
+ // Note that this member should remain a one-way switch, since it accessed from multiple
+ // threads without a lock.
+ private volatile boolean mInitialized;
+
+ // One-way switch that becomes true once
+ // {@link asyncPrefetchLibrariesToMemory} has been called.
+ private final AtomicBoolean mPrefetchLibraryHasBeenCalled = new AtomicBoolean();
+
+ // Guards all fields below.
+ private final Object mLock = new Object();
+
+ private NativeLibraryPreloader mLibraryPreloader;
+ private boolean mLibraryPreloaderCalled;
+
+ // One-way switch becomes true when the libraries are loaded.
+ private boolean mLoaded;
+
+ // One-way switch becomes true when the Java command line is switched to
+ // native.
+ private boolean mCommandLineSwitched;
+
+ // One-way switches recording attempts to use Relro sharing in the browser.
+ // The flags are used to report UMA stats later.
+ private boolean mIsUsingBrowserSharedRelros;
+ private boolean mLoadAtFixedAddressFailed;
+
+ // One-way switch becomes true if the Chromium library was loaded from the
+ // APK file directly.
+ private boolean mLibraryWasLoadedFromApk;
+
+ // The type of process the shared library is loaded in.
+ private @LibraryProcessType int mLibraryProcessType;
+
+ // The number of milliseconds it took to load all the native libraries, which
+ // will be reported via UMA. Set once when the libraries are done loading.
+ private long mLibraryLoadTimeMs;
+
+ // The return value of NativeLibraryPreloader.loadLibrary(), which will be reported
+ // via UMA, it is initialized to the invalid value which shouldn't showup in UMA
+ // report.
+ private int mLibraryPreloaderStatus = -1;
+
+ /**
+ * Call this method to determine if this chromium project must
+ * use this linker. If not, System.loadLibrary() should be used to load
+ * libraries instead.
+ */
+ public static boolean useCrazyLinker() {
+ // TODO(digit): Remove this early return GVR is loadable.
+ // A non-monochrome APK (such as ChromePublic.apk or ChromeModernPublic.apk) on N+ cannot
+ // use the Linker because the latter is incompatible with the GVR library. Fall back
+ // to using System.loadLibrary() or System.load() at the cost of no RELRO sharing.
+ //
+ // A non-monochrome APK (such as ChromePublic.apk) can be installed on N+ in these
+ // circumstances:
+ // * installing APK manually
+ // * after OTA from M to N
+ // * side-installing Chrome (possibly from another release channel)
+ // * Play Store bugs leading to incorrect APK flavor being installed
+ //
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.N) return false;
+
+ // The auto-generated NativeLibraries.sUseLinker variable will be true if the
+ // build has not explicitly disabled Linker features.
+ return NativeLibraries.sUseLinker;
+ }
+
+ /**
+ * Call this method to determine if the chromium project must load the library
+ * directly from a zip file.
+ */
+ private static boolean isInZipFile() {
+ // The auto-generated NativeLibraries.sUseLibraryInZipFile variable will be true
+ // iff the library remains embedded in the APK zip file on the target.
+ return NativeLibraries.sUseLibraryInZipFile;
+ }
+
+ /**
+ * Set native library preloader, if set, the NativeLibraryPreloader.loadLibrary will be invoked
+ * before calling System.loadLibrary, this only applies when not using the chromium linker.
+ *
+ * @param loader the NativeLibraryPreloader, it shall only be set once and before the
+ * native library loaded.
+ */
+ public void setNativeLibraryPreloader(NativeLibraryPreloader loader) {
+ synchronized (mLock) {
+ assert mLibraryPreloader == null && !mLoaded;
+ mLibraryPreloader = loader;
+ }
+ }
+
+ public static LibraryLoader getInstance() {
+ return sInstance;
+ }
+
+ private LibraryLoader() {}
+
+ /**
+ * This method blocks until the library is fully loaded and initialized.
+ *
+ * @param processType the process the shared library is loaded in.
+ */
+ public void ensureInitialized(@LibraryProcessType int processType) throws ProcessInitException {
+ synchronized (mLock) {
+ if (mInitialized) {
+ // Already initialized, nothing to do.
+ return;
+ }
+ loadAlreadyLocked(ContextUtils.getApplicationContext());
+ initializeAlreadyLocked(processType);
+ }
+ }
+
+ /**
+ * Calls native library preloader (see {@link #setNativeLibraryPreloader}) with the app
+ * context. If there is no preloader set, this function does nothing.
+ * Preloader is called only once, so calling it explicitly via this method means
+ * that it won't be (implicitly) called during library loading.
+ */
+ public void preloadNow() {
+ preloadNowOverrideApplicationContext(ContextUtils.getApplicationContext());
+ }
+
+ /**
+ * Similar to {@link #preloadNow}, but allows specifying app context to use.
+ */
+ public void preloadNowOverrideApplicationContext(Context appContext) {
+ synchronized (mLock) {
+ if (!useCrazyLinker()) {
+ preloadAlreadyLocked(appContext);
+ }
+ }
+ }
+
+ private void preloadAlreadyLocked(Context appContext) {
+ try (TraceEvent te = TraceEvent.scoped("LibraryLoader.preloadAlreadyLocked")) {
+ // Preloader uses system linker, we shouldn't preload if Chromium linker is used.
+ assert !useCrazyLinker();
+ if (mLibraryPreloader != null && !mLibraryPreloaderCalled) {
+ mLibraryPreloaderStatus = mLibraryPreloader.loadLibrary(appContext);
+ mLibraryPreloaderCalled = true;
+ }
+ }
+ }
+
+ /**
+ * Checks if library is fully loaded and initialized.
+ */
+ public boolean isInitialized() {
+ return mInitialized;
+ }
+
+ /**
+ * Loads the library and blocks until the load completes. The caller is responsible
+ * for subsequently calling ensureInitialized().
+ * May be called on any thread, but should only be called once. Note the thread
+ * this is called on will be the thread that runs the native code's static initializers.
+ * See the comment in doInBackground() for more considerations on this.
+ *
+ * @throws ProcessInitException if the native library failed to load.
+ */
+ public void loadNow() throws ProcessInitException {
+ loadNowOverrideApplicationContext(ContextUtils.getApplicationContext());
+ }
+
+ /**
+ * Override kept for callers that need to load from a different app context. Do not use unless
+ * specifically required to load from another context that is not the current process's app
+ * context.
+ *
+ * @param appContext The overriding app context to be used to load libraries.
+ * @throws ProcessInitException if the native library failed to load with this context.
+ */
+ public void loadNowOverrideApplicationContext(Context appContext) throws ProcessInitException {
+ synchronized (mLock) {
+ if (mLoaded && appContext != ContextUtils.getApplicationContext()) {
+ throw new IllegalStateException("Attempt to load again from alternate context.");
+ }
+ loadAlreadyLocked(appContext);
+ }
+ }
+
+ /**
+ * Initializes the library here and now: must be called on the thread that the
+ * native will call its "main" thread. The library must have previously been
+ * loaded with loadNow.
+ *
+ * @param processType the process the shared library is loaded in.
+ */
+ public void initialize(@LibraryProcessType int processType) throws ProcessInitException {
+ synchronized (mLock) {
+ initializeAlreadyLocked(processType);
+ }
+ }
+
+ /**
+ * Disables prefetching for subsequent runs. The value comes from "DontPrefetchLibraries"
+ * finch experiment, and is pushed on every run. I.e. the effect of the finch experiment
+ * lags by one run, which is the best we can do considering that prefetching happens way
+ * before finch is initialized. Note that since LibraryLoader is in //base, it can't depend
+ * on ChromeFeatureList, and has to rely on external code pushing the value.
+ *
+ * @param dontPrefetch whether not to prefetch libraries
+ */
+ public static void setDontPrefetchLibrariesOnNextRuns(boolean dontPrefetch) {
+ ContextUtils.getAppSharedPreferences()
+ .edit()
+ .putBoolean(DONT_PREFETCH_LIBRARIES_KEY, dontPrefetch)
+ .apply();
+ }
+
+ /**
+ * @return whether not to prefetch libraries (see setDontPrefetchLibrariesOnNextRun()).
+ */
+ private static boolean isNotPrefetchingLibraries() {
+ // This might be the first time getAppSharedPreferences() is used, so relax strict mode
+ // to allow disk reads.
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ return ContextUtils.getAppSharedPreferences().getBoolean(
+ DONT_PREFETCH_LIBRARIES_KEY, false);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ /** Prefetches the native libraries in a background thread.
+ *
+ * Launches an AsyncTask that, through a short-lived forked process, reads a
+ * part of each page of the native library. This is done to warm up the
+ * page cache, turning hard page faults into soft ones.
+ *
+ * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is
+ * detrimental to the startup time.
+ */
+ public void asyncPrefetchLibrariesToMemory() {
+ SysUtils.logPageFaultCountToTracing();
+ if (isNotPrefetchingLibraries()) return;
+
+ final boolean coldStart = mPrefetchLibraryHasBeenCalled.compareAndSet(false, true);
+
+ // Collection should start close to the native library load, but doesn't have
+ // to be simultaneous with it. Also, don't prefetch in this case, as this would
+ // skew the results.
+ if (coldStart && CommandLine.getInstance().hasSwitch("log-native-library-residency")) {
+ // nativePeriodicallyCollectResidency() sleeps, run it on another thread,
+ // and not on the AsyncTask thread pool.
+ new Thread(LibraryLoader::nativePeriodicallyCollectResidency).start();
+ return;
+ }
+
+ new LibraryPrefetchTask(coldStart).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ private static class LibraryPrefetchTask extends AsyncTask<Void, Void, Void> {
+ private final boolean mColdStart;
+
+ public LibraryPrefetchTask(boolean coldStart) {
+ mColdStart = coldStart;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try (TraceEvent e = TraceEvent.scoped("LibraryLoader.asyncPrefetchLibrariesToMemory")) {
+ int percentage = nativePercentageOfResidentNativeLibraryCode();
+ // Arbitrary percentage threshold. If most of the native library is already
+ // resident (likely with monochrome), don't bother creating a prefetch process.
+ boolean prefetch = mColdStart && percentage < 90;
+ if (prefetch) {
+ nativeForkAndPrefetchNativeLibrary();
+ }
+ if (percentage != -1) {
+ String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch"
+ + (mColdStart ? ".ColdStartup" : ".WarmStartup");
+ RecordHistogram.recordPercentageHistogram(histogram, percentage);
+ }
+ }
+ return null;
+ }
+ }
+
+ // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker.
+ // Sets UMA flags depending on the results of loading.
+ private void loadLibraryWithCustomLinkerAlreadyLocked(
+ Linker linker, @Nullable String zipFilePath, String libFilePath) {
+ assert Thread.holdsLock(mLock);
+ if (linker.isUsingBrowserSharedRelros()) {
+ // If the browser is set to attempt shared RELROs then we try first with shared
+ // RELROs enabled, and if that fails then retry without.
+ mIsUsingBrowserSharedRelros = true;
+ try {
+ linker.loadLibrary(libFilePath);
+ } catch (UnsatisfiedLinkError e) {
+ Log.w(TAG, "Failed to load native library with shared RELRO, retrying without");
+ mLoadAtFixedAddressFailed = true;
+ linker.loadLibraryNoFixedAddress(libFilePath);
+ }
+ } else {
+ // No attempt to use shared RELROs in the browser, so load as normal.
+ linker.loadLibrary(libFilePath);
+ }
+
+ // Loaded successfully, so record if we loaded directly from an APK.
+ if (zipFilePath != null) {
+ mLibraryWasLoadedFromApk = true;
+ }
+ }
+
+ static void incrementRelinkerCountHitHistogram() {
+ sRelinkerCountHistogram.record(1);
+ }
+
+ static void incrementRelinkerCountNotHitHistogram() {
+ sRelinkerCountHistogram.record(0);
+ }
+
+ // Experience shows that on some devices, the system sometimes fails to extract native libraries
+ // at installation or update time from the APK. This function will extract the library and
+ // return the extracted file path.
+ static String getExtractedLibraryPath(Context appContext, String libName) {
+ assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION;
+ Log.w(TAG, "Failed to load libName %s, attempting fallback extraction then trying again",
+ libName);
+ String libraryEntry = LibraryLoader.makeLibraryPathInZipFile(libName, false, false);
+ return extractFileIfStale(appContext, libraryEntry, makeLibraryDirAndSetPermission());
+ }
+
+ // Invoke either Linker.loadLibrary(...), System.loadLibrary(...) or System.load(...),
+ // triggering JNI_OnLoad in native code.
+ // TODO(crbug.com/635567): Fix this properly.
+ @SuppressLint({"DefaultLocale", "NewApi", "UnsafeDynamicallyLoadedCode"})
+ private void loadAlreadyLocked(Context appContext) throws ProcessInitException {
+ try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadAlreadyLocked")) {
+ if (!mLoaded) {
+ assert !mInitialized;
+
+ long startTime = SystemClock.uptimeMillis();
+
+ if (useCrazyLinker()) {
+ // Load libraries using the Chromium linker.
+ Linker linker = Linker.getInstance();
+
+ String apkFilePath =
+ isInZipFile() ? appContext.getApplicationInfo().sourceDir : null;
+ linker.prepareLibraryLoad(apkFilePath);
+
+ for (String library : NativeLibraries.LIBRARIES) {
+ // Don't self-load the linker. This is because the build system is
+ // not clever enough to understand that all the libraries packaged
+ // in the final .apk don't need to be explicitly loaded.
+ if (linker.isChromiumLinkerLibrary(library)) {
+ if (DEBUG) Log.i(TAG, "ignoring self-linker load");
+ continue;
+ }
+
+ // Determine where the library should be loaded from.
+ String libFilePath = System.mapLibraryName(library);
+ if (apkFilePath != null) {
+ Log.i(TAG, " Loading " + library + " from within " + apkFilePath);
+ } else {
+ Log.i(TAG, "Loading " + library);
+ }
+
+ try {
+ // Load the library using this Linker. May throw UnsatisfiedLinkError.
+ loadLibraryWithCustomLinkerAlreadyLocked(
+ linker, apkFilePath, libFilePath);
+ incrementRelinkerCountNotHitHistogram();
+ } catch (UnsatisfiedLinkError e) {
+ if (!isInZipFile()
+ && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) {
+ loadLibraryWithCustomLinkerAlreadyLocked(
+ linker, null, getExtractedLibraryPath(appContext, library));
+ incrementRelinkerCountHitHistogram();
+ } else {
+ Log.e(TAG, "Unable to load library: " + library);
+ throw(e);
+ }
+ }
+ }
+
+ linker.finishLibraryLoad();
+ } else {
+ setEnvForNative();
+ preloadAlreadyLocked(appContext);
+
+ // If the libraries are located in the zip file, assert that the device API
+ // level is M or higher. On devices lower than M, the libraries should
+ // always be loaded by Linker.
+ assert !isInZipFile() || Build.VERSION.SDK_INT >= VERSION_CODES.M;
+
+ // Load libraries using the system linker.
+ for (String library : NativeLibraries.LIBRARIES) {
+ try {
+ if (!isInZipFile()) {
+ // The extract and retry logic isn't needed because this path is
+ // used only for local development.
+ System.loadLibrary(library);
+ } else {
+ // Load directly from the APK.
+ boolean is64Bit = Process.is64Bit();
+ String zipFilePath = appContext.getApplicationInfo().sourceDir;
+ // In API level 23 and above, it’s possible to open a .so file
+ // directly from the APK of the path form
+ // "my_zip_file.zip!/libs/libstuff.so". See:
+ // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#opening-shared-libraries-directly-from-an-apk
+ String libraryName = zipFilePath + "!/"
+ + makeLibraryPathInZipFile(library, true, is64Bit);
+ Log.i(TAG, "libraryName: " + libraryName);
+ System.load(libraryName);
+ }
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "Unable to load library: " + library);
+ throw(e);
+ }
+ }
+ }
+
+ long stopTime = SystemClock.uptimeMillis();
+ mLibraryLoadTimeMs = stopTime - startTime;
+ Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)",
+ mLibraryLoadTimeMs,
+ startTime % 10000,
+ stopTime % 10000));
+
+ mLoaded = true;
+ }
+ } catch (UnsatisfiedLinkError e) {
+ throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e);
+ }
+ }
+
+ /**
+ * @param library The library name that is looking for.
+ * @param crazyPrefix true iff adding crazy linker prefix to the file name.
+ * @param is64Bit true if the caller think it's run on a 64 bit device.
+ * @return the library path name in the zip file.
+ */
+ @NonNull
+ public static String makeLibraryPathInZipFile(
+ String library, boolean crazyPrefix, boolean is64Bit) {
+ // Determine the ABI string that Android uses to find native libraries. Values are described
+ // in: https://developer.android.com/ndk/guides/abis.html
+ // The 'armeabi' is omitted here because it is not supported in Chrome/WebView, while Cronet
+ // and Cast load the native library via other paths.
+ String cpuAbi;
+ switch (NativeLibraries.sCpuFamily) {
+ case NativeLibraries.CPU_FAMILY_ARM:
+ cpuAbi = is64Bit ? "arm64-v8a" : "armeabi-v7a";
+ break;
+ case NativeLibraries.CPU_FAMILY_X86:
+ cpuAbi = is64Bit ? "x86_64" : "x86";
+ break;
+ case NativeLibraries.CPU_FAMILY_MIPS:
+ cpuAbi = is64Bit ? "mips64" : "mips";
+ break;
+ default:
+ throw new RuntimeException("Unknown CPU ABI for native libraries");
+ }
+
+ // When both the Chromium linker and zip-uncompressed native libraries are used,
+ // the build system renames the native shared libraries with a 'crazy.' prefix
+ // (e.g. "/lib/armeabi-v7a/libfoo.so" -> "/lib/armeabi-v7a/crazy.libfoo.so").
+ //
+ // This prevents the package manager from extracting them at installation/update time
+ // to the /data directory. The libraries can still be accessed directly by the Chromium
+ // linker from the APK.
+ String crazyPart = crazyPrefix ? "crazy." : "";
+ return String.format("lib/%s/%s%s", cpuAbi, crazyPart, System.mapLibraryName(library));
+ }
+
+ // The WebView requires the Command Line to be switched over before
+ // initialization is done. This is okay in the WebView's case since the
+ // JNI is already loaded by this point.
+ public void switchCommandLineForWebView() {
+ synchronized (mLock) {
+ ensureCommandLineSwitchedAlreadyLocked();
+ }
+ }
+
+ // Switch the CommandLine over from Java to native if it hasn't already been done.
+ // This must happen after the code is loaded and after JNI is ready (since after the
+ // switch the Java CommandLine will delegate all calls the native CommandLine).
+ private void ensureCommandLineSwitchedAlreadyLocked() {
+ assert mLoaded;
+ if (mCommandLineSwitched) {
+ return;
+ }
+ CommandLine.enableNativeProxy();
+ mCommandLineSwitched = true;
+ }
+
+ // Invoke base::android::LibraryLoaded in library_loader_hooks.cc
+ private void initializeAlreadyLocked(@LibraryProcessType int processType)
+ throws ProcessInitException {
+ if (mInitialized) {
+ if (mLibraryProcessType != processType) {
+ throw new ProcessInitException(
+ LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED);
+ }
+ return;
+ }
+ mLibraryProcessType = processType;
+
+ ensureCommandLineSwitchedAlreadyLocked();
+
+ if (!nativeLibraryLoaded(mLibraryProcessType)) {
+ Log.e(TAG, "error calling nativeLibraryLoaded");
+ throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI);
+ }
+
+ // Check that the version of the library we have loaded matches the version we expect
+ Log.i(TAG, String.format("Expected native library version number \"%s\", "
+ + "actual native library version number \"%s\"",
+ NativeLibraries.sVersionNumber, nativeGetVersionNumber()));
+ if (!NativeLibraries.sVersionNumber.equals(nativeGetVersionNumber())) {
+ throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION);
+ }
+
+ // From now on, keep tracing in sync with native.
+ TraceEvent.registerNativeEnabledObserver();
+
+ if (processType == LibraryProcessType.PROCESS_BROWSER
+ && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) {
+ // Perform the detection and deletion of obsolete native libraries on a background
+ // background thread.
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ final String suffix = BuildInfo.getInstance().extractedFileSuffix;
+ final File[] files = getLibraryDir().listFiles();
+ if (files == null) return;
+
+ for (File file : files) {
+ // NOTE: Do not simply look for <suffix> at the end of the file.
+ //
+ // Extracted library files have names like 'libfoo.so<suffix>', but
+ // extractFileIfStale() will use FileUtils.copyFileStreamAtomicWithBuffer()
+ // to create them, and this method actually uses a transient temporary file
+ // named like 'libfoo.so<suffix>.tmp' to do that. These temporary files, if
+ // detected here, should be preserved; hence the reason why contains() is
+ // used below.
+ if (!file.getName().contains(suffix)) {
+ String fileName = file.getName();
+ if (!file.delete()) {
+ Log.w(TAG, "Unable to remove %s", fileName);
+ } else {
+ Log.i(TAG, "Removed obsolete file %s", fileName);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ // From this point on, native code is ready to use and checkIsReady()
+ // shouldn't complain from now on (and in fact, it's used by the
+ // following calls).
+ // Note that this flag can be accessed asynchronously, so any initialization
+ // must be performed before.
+ mInitialized = true;
+ }
+
+ // Called after all native initializations are complete.
+ public void onNativeInitializationComplete() {
+ synchronized (mLock) {
+ recordBrowserProcessHistogramAlreadyLocked();
+ }
+ }
+
+ // Record Chromium linker histogram state for the main browser process. Called from
+ // onNativeInitializationComplete().
+ private void recordBrowserProcessHistogramAlreadyLocked() {
+ assert Thread.holdsLock(mLock);
+ if (useCrazyLinker()) {
+ nativeRecordChromiumAndroidLinkerBrowserHistogram(mIsUsingBrowserSharedRelros,
+ mLoadAtFixedAddressFailed,
+ mLibraryWasLoadedFromApk ? LibraryLoadFromApkStatusCodes.SUCCESSFUL
+ : LibraryLoadFromApkStatusCodes.UNKNOWN,
+ mLibraryLoadTimeMs);
+ }
+ if (mLibraryPreloader != null) {
+ nativeRecordLibraryPreloaderBrowserHistogram(mLibraryPreloaderStatus);
+ }
+ }
+
+ // Register pending Chromium linker histogram state for renderer processes. This cannot be
+ // recorded as a histogram immediately because histograms and IPC are not ready at the
+ // time it are captured. This function stores a pending value, so that a later call to
+ // RecordChromiumAndroidLinkerRendererHistogram() will record it correctly.
+ public void registerRendererProcessHistogram(boolean requestedSharedRelro,
+ boolean loadAtFixedAddressFailed) {
+ synchronized (mLock) {
+ if (useCrazyLinker()) {
+ nativeRegisterChromiumAndroidLinkerRendererHistogram(
+ requestedSharedRelro, loadAtFixedAddressFailed, mLibraryLoadTimeMs);
+ }
+ if (mLibraryPreloader != null) {
+ nativeRegisterLibraryPreloaderRendererHistogram(mLibraryPreloaderStatus);
+ }
+ }
+ }
+
+ /**
+ * Override the library loader (normally with a mock) for testing.
+ * @param loader the mock library loader.
+ */
+ @VisibleForTesting
+ public static void setLibraryLoaderForTesting(LibraryLoader loader) {
+ sInstance = loader;
+ }
+
+ /**
+ * Configure ubsan using $UBSAN_OPTIONS. This function needs to be called before any native
+ * libraries are loaded because ubsan reads its configuration from $UBSAN_OPTIONS when the
+ * native library is loaded.
+ */
+ public static void setEnvForNative() {
+ // The setenv API was added in L. On older versions of Android, we should still see ubsan
+ // reports, but they will not have stack traces.
+ if (BuildConfig.IS_UBSAN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ // This value is duplicated in build/android/pylib/constants/__init__.py.
+ Os.setenv("UBSAN_OPTIONS",
+ "print_stacktrace=1 stack_trace_format='#%n pc %o %m' "
+ + "handle_segv=0 handle_sigbus=0 handle_sigfpe=0",
+ true);
+ } catch (Exception e) {
+ Log.w(TAG, "failed to set UBSAN_OPTIONS", e);
+ }
+ }
+ }
+
+ // Android system sometimes fails to extract libraries from APK (https://crbug.com/806998).
+ // This function manually extract libraries as a fallback.
+ @SuppressLint({"SetWorldReadable"})
+ private static String extractFileIfStale(
+ Context appContext, String pathWithinApk, File destDir) {
+ assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION;
+
+ String apkPath = appContext.getApplicationInfo().sourceDir;
+ String fileName =
+ (new File(pathWithinApk)).getName() + BuildInfo.getInstance().extractedFileSuffix;
+ File libraryFile = new File(destDir, fileName);
+
+ if (!libraryFile.exists()) {
+ try (ZipFile zipFile = new ZipFile(apkPath);
+ InputStream inputStream =
+ zipFile.getInputStream(zipFile.getEntry(pathWithinApk))) {
+ if (zipFile.getEntry(pathWithinApk) == null)
+ throw new RuntimeException("Cannot find ZipEntry" + pathWithinApk);
+
+ FileUtils.copyFileStreamAtomicWithBuffer(
+ inputStream, libraryFile, new byte[16 * 1024]);
+ libraryFile.setReadable(true, false);
+ libraryFile.setExecutable(true, false);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return libraryFile.getAbsolutePath();
+ }
+
+ // Ensure the extracted native libraries is created with the right permissions.
+ private static File makeLibraryDirAndSetPermission() {
+ if (!ContextUtils.isIsolatedProcess()) {
+ File cacheDir = ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext());
+ File libDir = new File(cacheDir, LIBRARY_DIR);
+ cacheDir.mkdir();
+ cacheDir.setExecutable(true, false);
+ libDir.mkdir();
+ libDir.setExecutable(true, false);
+ }
+ return getLibraryDir();
+ }
+
+ // Return File object for the directory containing extracted native libraries.
+ private static File getLibraryDir() {
+ return new File(
+ ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext()), LIBRARY_DIR);
+ }
+
+ // Only methods needed before or during normal JNI registration are during System.OnLoad.
+ // nativeLibraryLoaded is then called to register everything else. This process is called
+ // "initialization". This method will be mapped (by generated code) to the LibraryLoaded
+ // definition in base/android/library_loader/library_loader_hooks.cc.
+ //
+ // Return true on success and false on failure.
+ private native boolean nativeLibraryLoaded(@LibraryProcessType int processType);
+
+ // Method called to record statistics about the Chromium linker operation for the main
+ // browser process. Indicates whether the linker attempted relro sharing for the browser,
+ // and if it did, whether the library failed to load at a fixed address. Also records
+ // support for loading a library directly from the APK file, and the number of milliseconds
+ // it took to load the libraries.
+ private native void nativeRecordChromiumAndroidLinkerBrowserHistogram(
+ boolean isUsingBrowserSharedRelros,
+ boolean loadAtFixedAddressFailed,
+ int libraryLoadFromApkStatus,
+ long libraryLoadTime);
+
+ // Method called to record the return value of NativeLibraryPreloader.loadLibrary for the main
+ // browser process.
+ private native void nativeRecordLibraryPreloaderBrowserHistogram(int status);
+
+ // Method called to register (for later recording) statistics about the Chromium linker
+ // operation for a renderer process. Indicates whether the linker attempted relro sharing,
+ // and if it did, whether the library failed to load at a fixed address. Also records the
+ // number of milliseconds it took to load the libraries.
+ private native void nativeRegisterChromiumAndroidLinkerRendererHistogram(
+ boolean requestedSharedRelro,
+ boolean loadAtFixedAddressFailed,
+ long libraryLoadTime);
+
+ // Method called to register (for later recording) the return value of
+ // NativeLibraryPreloader.loadLibrary for a renderer process.
+ private native void nativeRegisterLibraryPreloaderRendererHistogram(int status);
+
+ // Get the version of the native library. This is needed so that we can check we
+ // have the right version before initializing the (rest of the) JNI.
+ private native String nativeGetVersionNumber();
+
+ // Finds the ranges corresponding to the native library pages, forks a new
+ // process to prefetch these pages and waits for it. The new process then
+ // terminates. This is blocking.
+ private static native void nativeForkAndPrefetchNativeLibrary();
+
+ // Returns the percentage of the native library code page that are currently reseident in
+ // memory.
+ private static native int nativePercentageOfResidentNativeLibraryCode();
+
+ // Periodically logs native library residency from this thread.
+ private static native void nativePeriodicallyCollectResidency();
+}
diff --git a/base/android/java/src/org/chromium/base/library_loader/Linker.java b/base/android/java/src/org/chromium/base/library_loader/Linker.java
new file mode 100644
index 0000000000..5e30cfa496
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/library_loader/Linker.java
@@ -0,0 +1,1160 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.library_loader;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.StreamUtil;
+import org.chromium.base.SysUtils;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.AccessedByNative;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/*
+ * Technical note:
+ *
+ * The point of this class is to provide an alternative to System.loadLibrary()
+ * to load native shared libraries. One specific feature that it supports is the
+ * ability to save RAM by sharing the ELF RELRO sections between renderer
+ * processes.
+ *
+ * When two processes load the same native library at the _same_ memory address,
+ * the content of their RELRO section (which includes C++ vtables or any
+ * constants that contain pointers) will be largely identical [1].
+ *
+ * By default, the RELRO section is backed by private RAM in each process,
+ * which is still significant on mobile (e.g. 1.28 MB / process on Chrome 30 for
+ * Android).
+ *
+ * However, it is possible to save RAM by creating a shared memory region,
+ * copy the RELRO content into it, then have each process swap its private,
+ * regular RELRO, with a shared, read-only, mapping of the shared one.
+ *
+ * This trick saves 98% of the RELRO section size per extra process, after the
+ * first one. On the other hand, this requires careful communication between
+ * the process where the shared RELRO is created and the one(s) where it is used.
+ *
+ * Note that swapping the regular RELRO with the shared one is not an atomic
+ * operation. Care must be taken that no other thread tries to run native code
+ * that accesses it during it. In practice, this means the swap must happen
+ * before library native code is executed.
+ *
+ * [1] The exceptions are pointers to external, randomized, symbols, like
+ * those from some system libraries, but these are very few in practice.
+ */
+
+/*
+ * Security considerations:
+ *
+ * - Whether the browser process loads its native libraries at the same
+ * addresses as the service ones (to save RAM by sharing the RELRO too)
+ * depends on the configuration variable BROWSER_SHARED_RELRO_CONFIG.
+ *
+ * Not using fixed library addresses in the browser process is preferred
+ * for regular devices since it maintains the efficacy of ASLR as an
+ * exploit mitigation across the render <-> browser privilege boundary.
+ *
+ * - The shared RELRO memory region is always forced read-only after creation,
+ * which means it is impossible for a compromised service process to map
+ * it read-write (e.g. by calling mmap() or mprotect()) and modify its
+ * content, altering values seen in other service processes.
+ *
+ * - Once the RELRO ashmem region or file is mapped into a service process's
+ * address space, the corresponding file descriptor is immediately closed. The
+ * file descriptor is kept opened in the browser process, because a copy needs
+ * to be sent to each new potential service process.
+ *
+ * - The common library load addresses are randomized for each instance of
+ * the program on the device. See getRandomBaseLoadAddress() for more
+ * details on how this is obtained.
+ *
+ * - When loading several libraries in service processes, a simple incremental
+ * approach from the original random base load address is used. This is
+ * sufficient to deal correctly with component builds (which can use dozens
+ * of shared libraries), while regular builds always embed a single shared
+ * library per APK.
+ */
+
+/**
+ * Here's an explanation of how this class is supposed to be used:
+ *
+ * - Native shared libraries should be loaded with Linker.loadLibrary(),
+ * instead of System.loadLibrary(). The two functions should behave the same
+ * (at a high level).
+ *
+ * - Before loading any library, prepareLibraryLoad() should be called.
+ *
+ * - After loading all libraries, finishLibraryLoad() should be called, before
+ * running any native code from any of the libraries (except their static
+ * constructors, which can't be avoided).
+ *
+ * - A service process shall call either initServiceProcess() or
+ * disableSharedRelros() early (i.e. before any loadLibrary() call).
+ * Otherwise, the linker considers that it is running inside the browser
+ * process. This is because various Chromium projects have vastly
+ * different initialization paths.
+ *
+ * disableSharedRelros() completely disables shared RELROs, and loadLibrary()
+ * will behave exactly like System.loadLibrary().
+ *
+ * initServiceProcess(baseLoadAddress) indicates that shared RELROs are to be
+ * used in this process.
+ *
+ * - The browser is in charge of deciding where in memory each library should
+ * be loaded. This address must be passed to each service process (see
+ * ChromiumLinkerParams.java in content for a helper class to do so).
+ *
+ * - The browser will also generate shared RELROs for each library it loads.
+ * More specifically, by default when in the browser process, the linker
+ * will:
+ *
+ * - Load libraries randomly (just like System.loadLibrary()).
+ * - Compute the fixed address to be used to load the same library
+ * in service processes.
+ * - Create a shared memory region populated with the RELRO region
+ * content pre-relocated for the specific fixed address above.
+ *
+ * Note that these shared RELRO regions cannot be used inside the browser
+ * process. They are also never mapped into it.
+ *
+ * This behaviour is altered by the BROWSER_SHARED_RELRO_CONFIG configuration
+ * variable below, which may force the browser to load the libraries at
+ * fixed addresses too.
+ *
+ * - Once all libraries are loaded in the browser process, one can call
+ * getSharedRelros() which returns a Bundle instance containing a map that
+ * links each loaded library to its shared RELRO region.
+ *
+ * This Bundle must be passed to each service process, for example through
+ * a Binder call (note that the Bundle includes file descriptors and cannot
+ * be added as an Intent extra).
+ *
+ * - In a service process, finishLibraryLoad() and/or loadLibrary() may
+ * block until the RELRO section Bundle is received. This is typically
+ * done by calling useSharedRelros() from another thread.
+ *
+ * This method also ensures the process uses the shared RELROs.
+ */
+public class Linker {
+ // Log tag for this class.
+ private static final String TAG = "LibraryLoader";
+
+ // Name of the library that contains our JNI code.
+ private static final String LINKER_JNI_LIBRARY = "chromium_android_linker";
+
+ // Constants used to control the behaviour of the browser process with
+ // regards to the shared RELRO section.
+ // NEVER -> The browser never uses it itself.
+ // LOW_RAM_ONLY -> It is only used on devices with low RAM.
+ // ALWAYS -> It is always used.
+ // NOTE: These names are known and expected by the Linker test scripts.
+ public static final int BROWSER_SHARED_RELRO_CONFIG_NEVER = 0;
+ public static final int BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY = 1;
+ public static final int BROWSER_SHARED_RELRO_CONFIG_ALWAYS = 2;
+
+ // Configuration variable used to control how the browser process uses the
+ // shared RELRO. Only change this while debugging linker-related issues.
+ // NOTE: This variable's name is known and expected by the Linker test scripts.
+ public static final int BROWSER_SHARED_RELRO_CONFIG =
+ BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY;
+
+ // Constants used to control the memory device config. Can be set explicitly
+ // by setMemoryDeviceConfigForTesting().
+ // INIT -> Value is undetermined (will check at runtime).
+ // LOW -> This is a low-memory device.
+ // NORMAL -> This is not a low-memory device.
+ public static final int MEMORY_DEVICE_CONFIG_INIT = 0;
+ public static final int MEMORY_DEVICE_CONFIG_LOW = 1;
+ public static final int MEMORY_DEVICE_CONFIG_NORMAL = 2;
+
+ // Indicates if this is a low-memory device or not. The default is to
+ // determine this by probing the system at runtime, but this can be forced
+ // for testing by calling setMemoryDeviceConfigForTesting().
+ private int mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_INIT;
+
+ // Set to true to enable debug logs.
+ protected static final boolean DEBUG = false;
+
+ // Used to pass the shared RELRO Bundle through Binder.
+ public static final String EXTRA_LINKER_SHARED_RELROS =
+ "org.chromium.base.android.linker.shared_relros";
+
+ // Guards all access to the linker.
+ protected final Object mLock = new Object();
+
+ // The name of a class that implements TestRunner.
+ private String mTestRunnerClassName;
+
+ // Size of reserved Breakpad guard region. Should match the value of
+ // kBreakpadGuardRegionBytes on the JNI side. Used when computing the load
+ // addresses of multiple loaded libraries. Set to 0 to disable the guard.
+ private static final int BREAKPAD_GUARD_REGION_BYTES = 16 * 1024 * 1024;
+
+ // Size of the area requested when using ASLR to obtain a random load address.
+ // Should match the value of kAddressSpaceReservationSize on the JNI side.
+ // Used when computing the load addresses of multiple loaded libraries to
+ // ensure that we don't try to load outside the area originally requested.
+ private static final int ADDRESS_SPACE_RESERVATION = 192 * 1024 * 1024;
+
+ // Becomes true after linker initialization.
+ private boolean mInitialized;
+
+ // Set to true if this runs in the browser process. Disabled by initServiceProcess().
+ private boolean mInBrowserProcess = true;
+
+ // Becomes true to indicate this process needs to wait for a shared RELRO in
+ // finishLibraryLoad().
+ private boolean mWaitForSharedRelros;
+
+ // Becomes true when initialization determines that the browser process can use the
+ // shared RELRO.
+ private boolean mBrowserUsesSharedRelro;
+
+ // The map of all RELRO sections either created or used in this process.
+ private Bundle mSharedRelros;
+
+ // Current common random base load address. A value of -1 indicates not yet initialized.
+ private long mBaseLoadAddress = -1;
+
+ // Current fixed-location load address for the next library called by loadLibrary().
+ // A value of -1 indicates not yet initialized.
+ private long mCurrentLoadAddress = -1;
+
+ // Becomes true once prepareLibraryLoad() has been called.
+ private boolean mPrepareLibraryLoadCalled;
+
+ // The map of libraries that are currently loaded in this process.
+ private HashMap<String, LibInfo> mLoadedLibraries;
+
+ // Singleton.
+ private static final Linker sSingleton = new Linker();
+
+ // Private singleton constructor.
+ private Linker() {
+ // Ensure this class is not referenced unless it's used.
+ assert LibraryLoader.useCrazyLinker();
+ }
+
+ /**
+ * Get singleton instance. Returns a Linker.
+ *
+ * On N+ Monochrome is selected by Play Store. With Monochrome this code is not used, instead
+ * Chrome asks the WebView to provide the library (and the shared RELRO). If the WebView fails
+ * to provide the library, the system linker is used as a fallback.
+ *
+ * Linker runs on all Android releases, but is incompatible with GVR library on N+.
+ * Linker is preferred on M- because it does not write the shared RELRO to disk at
+ * almost every cold startup.
+ *
+ * @return the Linker implementation instance.
+ */
+ public static Linker getInstance() {
+ return sSingleton;
+ }
+
+ /**
+ * Check that native library linker tests are enabled.
+ * If not enabled, calls to testing functions will fail with an assertion
+ * error.
+ *
+ * @return true if native library linker tests are enabled.
+ */
+ public static boolean areTestsEnabled() {
+ return NativeLibraries.sEnableLinkerTests;
+ }
+
+ /**
+ * Assert NativeLibraries.sEnableLinkerTests is true.
+ * Hard assertion that we are in a testing context. Cannot be disabled. The
+ * test methods in this module permit injection of runnable code by class
+ * name. To protect against both malicious and accidental use of these
+ * methods, we ensure that NativeLibraries.sEnableLinkerTests is true when
+ * any is called.
+ */
+ private static void assertLinkerTestsAreEnabled() {
+ assert NativeLibraries.sEnableLinkerTests : "Testing method called in non-testing context";
+ }
+
+ /**
+ * A public interface used to run runtime linker tests after loading
+ * libraries. Should only be used to implement the linker unit tests,
+ * which is controlled by the value of NativeLibraries.sEnableLinkerTests
+ * configured at build time.
+ */
+ public interface TestRunner {
+ /**
+ * Run runtime checks and return true if they all pass.
+ *
+ * @param memoryDeviceConfig The current memory device configuration.
+ * @param inBrowserProcess true iff this is the browser process.
+ * @return true if all checks pass.
+ */
+ public boolean runChecks(int memoryDeviceConfig, boolean inBrowserProcess);
+ }
+
+ /**
+ * Call this to retrieve the name of the current TestRunner class name
+ * if any. This can be useful to pass it from the browser process to
+ * child ones.
+ *
+ * @return null or a String holding the name of the class implementing
+ * the TestRunner set by calling setTestRunnerClassNameForTesting() previously.
+ */
+ public final String getTestRunnerClassNameForTesting() {
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+
+ synchronized (mLock) {
+ return mTestRunnerClassName;
+ }
+ }
+
+ /**
+ * Sets the test class name.
+ *
+ * On the first call, instantiates a Linker and sets its test runner class name. On subsequent
+ * calls, checks that the singleton produced by the first call matches the test runner class
+ * name.
+ */
+ public static final void setupForTesting(String testRunnerClassName) {
+ if (DEBUG) {
+ Log.i(TAG, "setupForTesting(" + testRunnerClassName + ") called");
+ }
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+
+ synchronized (sSingleton) {
+ sSingleton.mTestRunnerClassName = testRunnerClassName;
+ }
+ }
+
+ /**
+ * Instantiate and run the current TestRunner, if any. The TestRunner implementation
+ * must be instantiated _after_ all libraries are loaded to ensure that its
+ * native methods are properly registered.
+ *
+ * @param memoryDeviceConfig Linker memory config, or 0 if unused
+ * @param inBrowserProcess true if in the browser process
+ */
+ private final void runTestRunnerClassForTesting(
+ int memoryDeviceConfig, boolean inBrowserProcess) {
+ if (DEBUG) {
+ Log.i(TAG, "runTestRunnerClassForTesting called");
+ }
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+
+ synchronized (mLock) {
+ if (mTestRunnerClassName == null) {
+ Log.wtf(TAG, "Linker runtime tests not set up for this process");
+ assert false;
+ }
+ if (DEBUG) {
+ Log.i(TAG, "Instantiating " + mTestRunnerClassName);
+ }
+ TestRunner testRunner = null;
+ try {
+ testRunner = (TestRunner) Class.forName(mTestRunnerClassName)
+ .getDeclaredConstructor()
+ .newInstance();
+ } catch (Exception e) {
+ Log.wtf(TAG, "Could not instantiate test runner class by name", e);
+ assert false;
+ }
+
+ if (!testRunner.runChecks(memoryDeviceConfig, inBrowserProcess)) {
+ Log.wtf(TAG, "Linker runtime tests failed in this process");
+ assert false;
+ }
+
+ Log.i(TAG, "All linker tests passed");
+ }
+ }
+
+ /**
+ * Call this method before any other Linker method to force a specific
+ * memory device configuration. Should only be used for testing.
+ *
+ * @param memoryDeviceConfig MEMORY_DEVICE_CONFIG_LOW or MEMORY_DEVICE_CONFIG_NORMAL.
+ */
+ public final void setMemoryDeviceConfigForTesting(int memoryDeviceConfig) {
+ if (DEBUG) {
+ Log.i(TAG, "setMemoryDeviceConfigForTesting(" + memoryDeviceConfig + ") called");
+ }
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+ assert memoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW
+ || memoryDeviceConfig == MEMORY_DEVICE_CONFIG_NORMAL;
+
+ synchronized (mLock) {
+ assert mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT;
+
+ mMemoryDeviceConfig = memoryDeviceConfig;
+ if (DEBUG) {
+ if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) {
+ Log.i(TAG, "Simulating a low-memory device");
+ } else {
+ Log.i(TAG, "Simulating a regular-memory device");
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine whether a library is the linker library.
+ *
+ * @param library the name of the library.
+ * @return true is the library is the Linker's own JNI library.
+ */
+ boolean isChromiumLinkerLibrary(String library) {
+ return library.equals(LINKER_JNI_LIBRARY);
+ }
+
+ /**
+ * Load the Linker JNI library. Throws UnsatisfiedLinkError on error.
+ */
+ @SuppressLint({"UnsafeDynamicallyLoadedCode"})
+ private static void loadLinkerJniLibrary() {
+ LibraryLoader.setEnvForNative();
+ if (DEBUG) {
+ String libName = "lib" + LINKER_JNI_LIBRARY + ".so";
+ Log.i(TAG, "Loading " + libName);
+ }
+ try {
+ System.loadLibrary(LINKER_JNI_LIBRARY);
+ LibraryLoader.incrementRelinkerCountNotHitHistogram();
+ } catch (UnsatisfiedLinkError e) {
+ if (LibraryLoader.PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) {
+ System.load(LibraryLoader.getExtractedLibraryPath(
+ ContextUtils.getApplicationContext(), LINKER_JNI_LIBRARY));
+ LibraryLoader.incrementRelinkerCountHitHistogram();
+ }
+ }
+ }
+
+ /**
+ * Obtain a random base load address at which to place loaded libraries.
+ *
+ * @return new base load address
+ */
+ private long getRandomBaseLoadAddress() {
+ // nativeGetRandomBaseLoadAddress() returns an address at which it has previously
+ // successfully mapped an area larger than the largest library we expect to load,
+ // on the basis that we will be able, with high probability, to map our library
+ // into it.
+ //
+ // One issue with this is that we do not yet know the size of the library that
+ // we will load is. If it is smaller than the size we used to obtain a random
+ // address the library mapping may still succeed. The other issue is that
+ // although highly unlikely, there is no guarantee that something else does not
+ // map into the area we are going to use between here and when we try to map into it.
+ //
+ // The above notes mean that all of this is probablistic. It is however okay to do
+ // because if, worst case and unlikely, we get unlucky in our choice of address,
+ // the back-out and retry without the shared RELRO in the ChildProcessService will
+ // keep things running.
+ final long address = nativeGetRandomBaseLoadAddress();
+ if (DEBUG) {
+ Log.i(TAG, String.format(Locale.US, "Random native base load address: 0x%x", address));
+ }
+ return address;
+ }
+
+ /**
+ * Load a native shared library with the Chromium linker. Note the crazy linker treats
+ * libraries and files as equivalent, so you can only open one library in a given zip
+ * file. The library must not be the Chromium linker library.
+ *
+ * @param libFilePath The path of the library (possibly in the zip file).
+ */
+ void loadLibrary(String libFilePath) {
+ if (DEBUG) {
+ Log.i(TAG, "loadLibrary: " + libFilePath);
+ }
+ final boolean isFixedAddressPermitted = true;
+ loadLibraryImpl(libFilePath, isFixedAddressPermitted);
+ }
+
+ /**
+ * Load a native shared library with the Chromium linker, ignoring any
+ * requested fixed address for RELRO sharing. Note the crazy linker treats libraries and
+ * files as equivalent, so you can only open one library in a given zip file. The
+ * library must not be the Chromium linker library.
+ *
+ * @param libFilePath The path of the library (possibly in the zip file).
+ */
+ void loadLibraryNoFixedAddress(String libFilePath) {
+ if (DEBUG) {
+ Log.i(TAG, "loadLibraryAtAnyAddress: " + libFilePath);
+ }
+ final boolean isFixedAddressPermitted = false;
+ loadLibraryImpl(libFilePath, isFixedAddressPermitted);
+ }
+
+ // Used internally to initialize the linker's data. Assumes lock is held.
+ // Loads JNI, and sets mMemoryDeviceConfig and mBrowserUsesSharedRelro.
+ private void ensureInitializedLocked() {
+ assert Thread.holdsLock(mLock);
+
+ if (mInitialized) {
+ return;
+ }
+
+ // On first call, load libchromium_android_linker.so. Cannot be done in the
+ // constructor because instantiation occurs on the UI thread.
+ loadLinkerJniLibrary();
+
+ if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT) {
+ if (SysUtils.isLowEndDevice()) {
+ mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_LOW;
+ } else {
+ mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_NORMAL;
+ }
+ }
+
+ // Cannot run in the constructor because SysUtils.isLowEndDevice() relies
+ // on CommandLine, which may not be available at instantiation.
+ switch (BROWSER_SHARED_RELRO_CONFIG) {
+ case BROWSER_SHARED_RELRO_CONFIG_NEVER:
+ mBrowserUsesSharedRelro = false;
+ break;
+ case BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY:
+ if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) {
+ mBrowserUsesSharedRelro = true;
+ Log.w(TAG, "Low-memory device: shared RELROs used in all processes");
+ } else {
+ mBrowserUsesSharedRelro = false;
+ }
+ break;
+ case BROWSER_SHARED_RELRO_CONFIG_ALWAYS:
+ Log.w(TAG, "Beware: shared RELROs used in all processes!");
+ mBrowserUsesSharedRelro = true;
+ break;
+ default:
+ Log.wtf(TAG, "FATAL: illegal shared RELRO config");
+ throw new AssertionError();
+ }
+
+ mInitialized = true;
+ }
+
+ /**
+ * Call this method to determine if the linker will try to use shared RELROs
+ * for the browser process.
+ */
+ public boolean isUsingBrowserSharedRelros() {
+ synchronized (mLock) {
+ ensureInitializedLocked();
+ return mInBrowserProcess && mBrowserUsesSharedRelro;
+ }
+ }
+
+ /**
+ * Call this method just before loading any native shared libraries in this process.
+ *
+ * @param apkFilePath Optional current APK file path. If provided, the linker
+ * will try to load libraries directly from it.
+ */
+ public void prepareLibraryLoad(@Nullable String apkFilePath) {
+ if (DEBUG) {
+ Log.i(TAG, "prepareLibraryLoad() called");
+ }
+ synchronized (mLock) {
+ ensureInitializedLocked();
+ if (apkFilePath != null) {
+ nativeAddZipArchivePath(apkFilePath);
+ }
+ mPrepareLibraryLoadCalled = true;
+
+ if (mInBrowserProcess) {
+ // Force generation of random base load address, as well
+ // as creation of shared RELRO sections in this process.
+ setupBaseLoadAddressLocked();
+ }
+ }
+ }
+
+ /**
+ * Call this method just after loading all native shared libraries in this process.
+ * Note that when in a service process, this will block until the RELRO bundle is
+ * received, i.e. when another thread calls useSharedRelros().
+ */
+ void finishLibraryLoad() {
+ if (DEBUG) {
+ Log.i(TAG, "finishLibraryLoad() called");
+ }
+ synchronized (mLock) {
+ ensureInitializedLocked();
+ if (DEBUG) {
+ Log.i(TAG,
+ String.format(Locale.US,
+ "mInBrowserProcess=%b mBrowserUsesSharedRelro=%b mWaitForSharedRelros=%b",
+ mInBrowserProcess, mBrowserUsesSharedRelro, mWaitForSharedRelros));
+ }
+
+ if (mLoadedLibraries == null) {
+ if (DEBUG) {
+ Log.i(TAG, "No libraries loaded");
+ }
+ } else {
+ if (mInBrowserProcess) {
+ // Create new Bundle containing RELRO section information
+ // for all loaded libraries. Make it available to getSharedRelros().
+ mSharedRelros = createBundleFromLibInfoMap(mLoadedLibraries);
+ if (DEBUG) {
+ Log.i(TAG, "Shared RELRO created");
+ dumpBundle(mSharedRelros);
+ }
+
+ if (mBrowserUsesSharedRelro) {
+ useSharedRelrosLocked(mSharedRelros);
+ }
+ }
+
+ if (mWaitForSharedRelros) {
+ assert !mInBrowserProcess;
+
+ // Wait until the shared relro bundle is received from useSharedRelros().
+ while (mSharedRelros == null) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException ie) {
+ // Restore the thread's interrupt status.
+ Thread.currentThread().interrupt();
+ }
+ }
+ useSharedRelrosLocked(mSharedRelros);
+ // Clear the Bundle to ensure its file descriptor references can't be reused.
+ mSharedRelros.clear();
+ mSharedRelros = null;
+ }
+ }
+
+ // If testing, run tests now that all libraries are loaded and initialized.
+ if (NativeLibraries.sEnableLinkerTests) {
+ runTestRunnerClassForTesting(mMemoryDeviceConfig, mInBrowserProcess);
+ }
+ }
+ if (DEBUG) {
+ Log.i(TAG, "finishLibraryLoad() exiting");
+ }
+ }
+
+ /**
+ * Call this to send a Bundle containing the shared RELRO sections to be
+ * used in this process. If initServiceProcess() was previously called,
+ * finishLibraryLoad() will not exit until this method is called in another
+ * thread with a non-null value.
+ *
+ * @param bundle The Bundle instance containing a map of shared RELRO sections
+ * to use in this process.
+ */
+ public void useSharedRelros(Bundle bundle) {
+ // Ensure the bundle uses the application's class loader, not the framework
+ // one which doesn't know anything about LibInfo.
+ // Also, hold a fresh copy of it so the caller can't recycle it.
+ Bundle clonedBundle = null;
+ if (bundle != null) {
+ bundle.setClassLoader(LibInfo.class.getClassLoader());
+ clonedBundle = new Bundle(LibInfo.class.getClassLoader());
+ Parcel parcel = Parcel.obtain();
+ bundle.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ clonedBundle.readFromParcel(parcel);
+ parcel.recycle();
+ }
+ if (DEBUG) {
+ Log.i(TAG, "useSharedRelros() called with " + bundle + ", cloned " + clonedBundle);
+ }
+ synchronized (mLock) {
+ // Note that in certain cases, this can be called before
+ // initServiceProcess() in service processes.
+ mSharedRelros = clonedBundle;
+ // Tell any listener blocked in finishLibraryLoad() about it.
+ mLock.notifyAll();
+ }
+ }
+
+ /**
+ * Call this to retrieve the shared RELRO sections created in this process,
+ * after loading all libraries.
+ *
+ * @return a new Bundle instance, or null if RELRO sharing is disabled on
+ * this system, or if initServiceProcess() was called previously.
+ */
+ public Bundle getSharedRelros() {
+ if (DEBUG) {
+ Log.i(TAG, "getSharedRelros() called");
+ }
+ synchronized (mLock) {
+ if (!mInBrowserProcess) {
+ if (DEBUG) {
+ Log.i(TAG, "... returning null Bundle");
+ }
+ return null;
+ }
+
+ // Return the Bundle created in finishLibraryLoad().
+ if (DEBUG) {
+ Log.i(TAG, "... returning " + mSharedRelros);
+ }
+ return mSharedRelros;
+ }
+ }
+
+ /**
+ * Call this method before loading any libraries to indicate that this
+ * process shall neither create or reuse shared RELRO sections.
+ */
+ public void disableSharedRelros() {
+ if (DEBUG) {
+ Log.i(TAG, "disableSharedRelros() called");
+ }
+ synchronized (mLock) {
+ ensureInitializedLocked();
+ mInBrowserProcess = false;
+ mWaitForSharedRelros = false;
+ mBrowserUsesSharedRelro = false;
+ }
+ }
+
+ /**
+ * Call this method before loading any libraries to indicate that this
+ * process is ready to reuse shared RELRO sections from another one.
+ * Typically used when starting service processes.
+ *
+ * @param baseLoadAddress the base library load address to use.
+ */
+ public void initServiceProcess(long baseLoadAddress) {
+ if (DEBUG) {
+ Log.i(TAG,
+ String.format(Locale.US, "initServiceProcess(0x%x) called", baseLoadAddress));
+ }
+ synchronized (mLock) {
+ ensureInitializedLocked();
+ mInBrowserProcess = false;
+ mBrowserUsesSharedRelro = false;
+ mWaitForSharedRelros = true;
+ mBaseLoadAddress = baseLoadAddress;
+ mCurrentLoadAddress = baseLoadAddress;
+ }
+ }
+
+ /**
+ * Retrieve the base load address of all shared RELRO sections.
+ * This also enforces the creation of shared RELRO sections in
+ * prepareLibraryLoad(), which can later be retrieved with getSharedRelros().
+ *
+ * @return a common, random base load address, or 0 if RELRO sharing is
+ * disabled.
+ */
+ public long getBaseLoadAddress() {
+ synchronized (mLock) {
+ ensureInitializedLocked();
+ if (!mInBrowserProcess) {
+ Log.w(TAG, "Shared RELRO sections are disabled in this process!");
+ return 0;
+ }
+
+ setupBaseLoadAddressLocked();
+ if (DEBUG) {
+ Log.i(TAG,
+ String.format(
+ Locale.US, "getBaseLoadAddress() returns 0x%x", mBaseLoadAddress));
+ }
+ return mBaseLoadAddress;
+ }
+ }
+
+ // Used internally to lazily setup the common random base load address.
+ private void setupBaseLoadAddressLocked() {
+ assert Thread.holdsLock(mLock);
+ if (mBaseLoadAddress == -1) {
+ mBaseLoadAddress = getRandomBaseLoadAddress();
+ mCurrentLoadAddress = mBaseLoadAddress;
+ if (mBaseLoadAddress == 0) {
+ // If the random address is 0 there are issues with finding enough
+ // free address space, so disable RELRO shared / fixed load addresses.
+ Log.w(TAG, "Disabling shared RELROs due address space pressure");
+ mBrowserUsesSharedRelro = false;
+ mWaitForSharedRelros = false;
+ }
+ }
+ }
+
+ // Used for debugging only.
+ private void dumpBundle(Bundle bundle) {
+ if (DEBUG) {
+ Log.i(TAG, "Bundle has " + bundle.size() + " items: " + bundle);
+ }
+ }
+
+ /**
+ * Use the shared RELRO section from a Bundle received form another process.
+ * Call this after calling setBaseLoadAddress() then loading all libraries
+ * with loadLibrary().
+ *
+ * @param bundle Bundle instance generated with createSharedRelroBundle() in
+ * another process.
+ */
+ private void useSharedRelrosLocked(Bundle bundle) {
+ assert Thread.holdsLock(mLock);
+
+ if (DEBUG) {
+ Log.i(TAG, "Linker.useSharedRelrosLocked() called");
+ }
+
+ if (bundle == null) {
+ if (DEBUG) {
+ Log.i(TAG, "null bundle!");
+ }
+ return;
+ }
+
+ if (mLoadedLibraries == null) {
+ if (DEBUG) {
+ Log.i(TAG, "No libraries loaded!");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ dumpBundle(bundle);
+ }
+ HashMap<String, LibInfo> relroMap = createLibInfoMapFromBundle(bundle);
+
+ // Apply the RELRO section to all libraries that were already loaded.
+ for (Map.Entry<String, LibInfo> entry : relroMap.entrySet()) {
+ String libName = entry.getKey();
+ LibInfo libInfo = entry.getValue();
+ if (!nativeUseSharedRelro(libName, libInfo)) {
+ Log.w(TAG, "Could not use shared RELRO section for " + libName);
+ } else {
+ if (DEBUG) {
+ Log.i(TAG, "Using shared RELRO section for " + libName);
+ }
+ }
+ }
+
+ // In service processes, close all file descriptors from the map now.
+ if (!mInBrowserProcess) {
+ closeLibInfoMap(relroMap);
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "Linker.useSharedRelrosLocked() exiting");
+ }
+ }
+
+ /**
+ * Implements loading a native shared library with the Chromium linker.
+ *
+ * Load a native shared library with the Chromium linker. If the zip file
+ * is not null, the shared library must be uncompressed and page aligned
+ * inside the zipfile. Note the crazy linker treats libraries and files as
+ * equivalent, so you can only open one library in a given zip file. The
+ * library must not be the Chromium linker library.
+ *
+ * @param libFilePath The path of the library (possibly in the zip file).
+ * @param isFixedAddressPermitted If true, uses a fixed load address if one was
+ * supplied, otherwise ignores the fixed address and loads wherever available.
+ */
+ void loadLibraryImpl(String libFilePath, boolean isFixedAddressPermitted) {
+ if (DEBUG) {
+ Log.i(TAG, "loadLibraryImpl: " + libFilePath + ", " + isFixedAddressPermitted);
+ }
+ synchronized (mLock) {
+ ensureInitializedLocked();
+
+ // Security: Ensure prepareLibraryLoad() was called before.
+ // In theory, this can be done lazily here, but it's more consistent
+ // to use a pair of functions (i.e. prepareLibraryLoad() + finishLibraryLoad())
+ // that wrap all calls to loadLibrary() in the library loader.
+ assert mPrepareLibraryLoadCalled;
+
+ if (mLoadedLibraries == null) {
+ mLoadedLibraries = new HashMap<String, LibInfo>();
+ }
+
+ if (mLoadedLibraries.containsKey(libFilePath)) {
+ if (DEBUG) {
+ Log.i(TAG, "Not loading " + libFilePath + " twice");
+ }
+ return;
+ }
+
+ LibInfo libInfo = new LibInfo();
+ long loadAddress = 0;
+ if (isFixedAddressPermitted) {
+ if ((mInBrowserProcess && mBrowserUsesSharedRelro) || mWaitForSharedRelros) {
+ // Load the library at a fixed address.
+ loadAddress = mCurrentLoadAddress;
+
+ // For multiple libraries, ensure we stay within reservation range.
+ if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) {
+ String errorMessage =
+ "Load address outside reservation, for: " + libFilePath;
+ Log.e(TAG, errorMessage);
+ throw new UnsatisfiedLinkError(errorMessage);
+ }
+ }
+ }
+
+ final String sharedRelRoName = libFilePath;
+ if (!nativeLoadLibrary(libFilePath, loadAddress, libInfo)) {
+ String errorMessage = "Unable to load library: " + libFilePath;
+ Log.e(TAG, errorMessage);
+ throw new UnsatisfiedLinkError(errorMessage);
+ }
+
+ // Print the load address to the logcat when testing the linker. The format
+ // of the string is expected by the Python test_runner script as one of:
+ // BROWSER_LIBRARY_ADDRESS: <library-name> <address>
+ // RENDERER_LIBRARY_ADDRESS: <library-name> <address>
+ // Where <library-name> is the library name, and <address> is the hexadecimal load
+ // address.
+ if (NativeLibraries.sEnableLinkerTests) {
+ String tag =
+ mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS" : "RENDERER_LIBRARY_ADDRESS";
+ Log.i(TAG,
+ String.format(
+ Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress));
+ }
+
+ if (mInBrowserProcess) {
+ // Create a new shared RELRO section at the 'current' fixed load address.
+ if (!nativeCreateSharedRelro(sharedRelRoName, mCurrentLoadAddress, libInfo)) {
+ Log.w(TAG,
+ String.format(Locale.US, "Could not create shared RELRO for %s at %x",
+ libFilePath, mCurrentLoadAddress));
+ } else {
+ if (DEBUG) {
+ Log.i(TAG,
+ String.format(Locale.US, "Created shared RELRO for %s at %x: %s",
+ sharedRelRoName, mCurrentLoadAddress, libInfo.toString()));
+ }
+ }
+ }
+
+ if (loadAddress != 0 && mCurrentLoadAddress != 0) {
+ // Compute the next current load address. If mCurrentLoadAddress
+ // is not 0, this is an explicit library load address. Otherwise,
+ // this is an explicit load address for relocated RELRO sections
+ // only.
+ mCurrentLoadAddress =
+ libInfo.mLoadAddress + libInfo.mLoadSize + BREAKPAD_GUARD_REGION_BYTES;
+ }
+
+ mLoadedLibraries.put(sharedRelRoName, libInfo);
+ if (DEBUG) {
+ Log.i(TAG, "Library details " + libInfo.toString());
+ }
+ }
+ }
+
+ /**
+ * Record information for a given library.
+ * IMPORTANT: Native code knows about this class's fields, so
+ * don't change them without modifying the corresponding C++ sources.
+ * Also, the LibInfo instance owns the shared RELRO file descriptor.
+ */
+ private static class LibInfo implements Parcelable {
+ LibInfo() {}
+
+ // from Parcelable
+ LibInfo(Parcel in) {
+ mLoadAddress = in.readLong();
+ mLoadSize = in.readLong();
+ mRelroStart = in.readLong();
+ mRelroSize = in.readLong();
+ ParcelFileDescriptor fd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ // If CreateSharedRelro fails, the OS file descriptor will be -1 and |fd| will be null.
+ if (fd != null) {
+ mRelroFd = fd.detachFd();
+ }
+ }
+
+ public void close() {
+ if (mRelroFd >= 0) {
+ StreamUtil.closeQuietly(ParcelFileDescriptor.adoptFd(mRelroFd));
+ mRelroFd = -1;
+ }
+ }
+
+ // from Parcelable
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (mRelroFd >= 0) {
+ out.writeLong(mLoadAddress);
+ out.writeLong(mLoadSize);
+ out.writeLong(mRelroStart);
+ out.writeLong(mRelroSize);
+ try {
+ ParcelFileDescriptor fd = ParcelFileDescriptor.fromFd(mRelroFd);
+ fd.writeToParcel(out, 0);
+ fd.close();
+ } catch (java.io.IOException e) {
+ Log.e(TAG, "Can't write LibInfo file descriptor to parcel", e);
+ }
+ }
+ }
+
+ // from Parcelable
+ @Override
+ public int describeContents() {
+ return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ // from Parcelable
+ public static final Parcelable.Creator<LibInfo> CREATOR =
+ new Parcelable.Creator<LibInfo>() {
+ @Override
+ public LibInfo createFromParcel(Parcel in) {
+ return new LibInfo(in);
+ }
+
+ @Override
+ public LibInfo[] newArray(int size) {
+ return new LibInfo[size];
+ }
+ };
+
+ // IMPORTANT: Don't change these fields without modifying the
+ // native code that accesses them directly!
+ @AccessedByNative
+ public long mLoadAddress; // page-aligned library load address.
+ @AccessedByNative
+ public long mLoadSize; // page-aligned library load size.
+ @AccessedByNative
+ public long mRelroStart; // page-aligned address in memory, or 0 if none.
+ @AccessedByNative
+ public long mRelroSize; // page-aligned size in memory, or 0.
+ @AccessedByNative
+ public int mRelroFd = -1; // shared RELRO file descriptor, or -1
+ }
+
+ // Create a Bundle from a map of LibInfo objects.
+ private Bundle createBundleFromLibInfoMap(HashMap<String, LibInfo> map) {
+ Bundle bundle = new Bundle(map.size());
+ for (Map.Entry<String, LibInfo> entry : map.entrySet()) {
+ bundle.putParcelable(entry.getKey(), entry.getValue());
+ }
+ return bundle;
+ }
+
+ // Create a new LibInfo map from a Bundle.
+ private HashMap<String, LibInfo> createLibInfoMapFromBundle(Bundle bundle) {
+ HashMap<String, LibInfo> map = new HashMap<String, LibInfo>();
+ for (String library : bundle.keySet()) {
+ LibInfo libInfo = bundle.getParcelable(library);
+ map.put(library, libInfo);
+ }
+ return map;
+ }
+
+ // Call the close() method on all values of a LibInfo map.
+ private void closeLibInfoMap(HashMap<String, LibInfo> map) {
+ for (Map.Entry<String, LibInfo> entry : map.entrySet()) {
+ entry.getValue().close();
+ }
+ }
+
+ /**
+ * Move activity from the native thread to the main UI thread.
+ * Called from native code on its own thread. Posts a callback from
+ * the UI thread back to native code.
+ *
+ * @param opaque Opaque argument.
+ */
+ @CalledByNative
+ @MainDex
+ private static void postCallbackOnMainThread(final long opaque) {
+ ThreadUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ nativeRunCallbackOnUiThread(opaque);
+ }
+ });
+ }
+
+ /**
+ * Native method to run callbacks on the main UI thread.
+ * Supplied by the crazy linker and called by postCallbackOnMainThread.
+ *
+ * @param opaque Opaque crazy linker arguments.
+ */
+ private static native void nativeRunCallbackOnUiThread(long opaque);
+
+ /**
+ * Native method used to load a library.
+ *
+ * @param library Platform specific library name (e.g. libfoo.so)
+ * @param loadAddress Explicit load address, or 0 for randomized one.
+ * @param libInfo If not null, the mLoadAddress and mLoadSize fields
+ * of this LibInfo instance will set on success.
+ * @return true for success, false otherwise.
+ */
+ private static native boolean nativeLoadLibrary(
+ String library, long loadAddress, LibInfo libInfo);
+
+ /**
+ * Native method used to add a zip archive or APK to the search path
+ * for native libraries. Allows loading directly from it.
+ *
+ * @param zipfilePath Path of the zip file containing the libraries.
+ * @return true for success, false otherwise.
+ */
+ private static native boolean nativeAddZipArchivePath(String zipFilePath);
+
+ /**
+ * Native method used to create a shared RELRO section.
+ * If the library was already loaded at the same address using
+ * nativeLoadLibrary(), this creates the RELRO for it. Otherwise,
+ * this loads a new temporary library at the specified address,
+ * creates and extracts the RELRO section from it, then unloads it.
+ *
+ * @param library Library name.
+ * @param loadAddress load address, which can be different from the one
+ * used to load the library in the current process!
+ * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize
+ * and mRelroFd will be set.
+ * @return true on success, false otherwise.
+ */
+ private static native boolean nativeCreateSharedRelro(
+ String library, long loadAddress, LibInfo libInfo);
+
+ /**
+ * Native method used to use a shared RELRO section.
+ *
+ * @param library Library name.
+ * @param libInfo A LibInfo instance containing valid RELRO information
+ * @return true on success.
+ */
+ private static native boolean nativeUseSharedRelro(String library, LibInfo libInfo);
+
+ /**
+ * Return a random address that should be free to be mapped with the given size.
+ * Maps an area large enough for the largest library we might attempt to load,
+ * and if successful then unmaps it and returns the address of the area allocated
+ * by the system (with ASLR). The idea is that this area should remain free of
+ * other mappings until we map our library into it.
+ *
+ * @return address to pass to future mmap, or 0 on error.
+ */
+ private static native long nativeGetRandomBaseLoadAddress();
+}
diff --git a/base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java b/base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java
new file mode 100644
index 0000000000..2b94370bd8
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.library_loader;
+
+/**
+ * These are the possible failures from the LibraryLoader
+ */
+public class LoaderErrors {
+ public static final int LOADER_ERROR_NORMAL_COMPLETION = 0;
+ public static final int LOADER_ERROR_FAILED_TO_REGISTER_JNI = 1;
+ public static final int LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED = 2;
+ public static final int LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION = 3;
+ public static final int LOADER_ERROR_NATIVE_STARTUP_FAILED = 4;
+}
diff --git a/base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java b/base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java
new file mode 100644
index 0000000000..6f8008d645
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java
@@ -0,0 +1,20 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.library_loader;
+
+import android.content.Context;
+
+/**
+ * This is interface to preload the native library before calling System.loadLibrary.
+ *
+ * Preloading shouldn't call System.loadLibrary() or otherwise cause any Chromium
+ * code to be run, because it can be called before Chromium command line is known.
+ * It can however open the library via dlopen() or android_dlopen_ext() so that
+ * dlopen() later called by System.loadLibrary() becomes a noop. This is what the
+ * only subclass (MonochromeLibraryPreloader) is doing.
+ */
+public abstract class NativeLibraryPreloader {
+ public abstract int loadLibrary(Context context);
+}
diff --git a/base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java b/base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java
new file mode 100644
index 0000000000..106667536d
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.library_loader;
+
+/**
+ * The exception that is thrown when the intialization of a process was failed.
+ */
+public class ProcessInitException extends Exception {
+ private int mErrorCode = LoaderErrors.LOADER_ERROR_NORMAL_COMPLETION;
+
+ /**
+ * @param errorCode This will be one of the LoaderErrors error codes.
+ */
+ public ProcessInitException(int errorCode) {
+ mErrorCode = errorCode;
+ }
+
+ /**
+ * @param errorCode This will be one of the LoaderErrors error codes.
+ * @param throwable The wrapped throwable obj.
+ */
+ public ProcessInitException(int errorCode, Throwable throwable) {
+ super(null, throwable);
+ mErrorCode = errorCode;
+ }
+
+ /**
+ * Return the error code.
+ */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java b/base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java
new file mode 100644
index 0000000000..258aa0bbdf
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java
@@ -0,0 +1,15 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.memory;
+
+import org.chromium.base.MemoryPressureLevel;
+
+/**
+ * Memory pressure callback interface.
+ */
+@FunctionalInterface
+public interface MemoryPressureCallback {
+ public void onPressure(@MemoryPressureLevel int pressure);
+}
diff --git a/base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java b/base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java
new file mode 100644
index 0000000000..c8af484682
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java
@@ -0,0 +1,301 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.memory;
+
+import android.app.ActivityManager;
+import android.content.ComponentCallbacks2;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.SystemClock;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.MemoryPressureLevel;
+import org.chromium.base.MemoryPressureListener;
+import org.chromium.base.Supplier;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.metrics.CachedMetrics;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class monitors memory pressure and reports it to the native side.
+ * Even though there can be other callbacks besides MemoryPressureListener (which reports
+ * pressure to the native side, and is added implicitly), the class is designed to suite
+ * needs of native MemoryPressureListeners.
+ *
+ * There are two groups of MemoryPressureListeners:
+ *
+ * 1. Stateless, i.e. ones that simply free memory (caches, etc.) in response to memory
+ * pressure. These listeners need to be called periodically (to have effect), but not
+ * too frequently (to avoid regressing performance too much).
+ *
+ * 2. Stateful, i.e. ones that change their behavior based on the last received memory
+ * pressure (in addition to freeing memory). These listeners need to know when the
+ * pressure subsides, i.e. they need to be notified about CRITICAL->MODERATE changes.
+ *
+ * Android notifies about memory pressure through onTrimMemory() / onLowMemory() callbacks
+ * from ComponentCallbacks2, but these are unreliable (e.g. called too early, called just
+ * once, not called when memory pressure subsides, etc., see https://crbug.com/813909 for
+ * more examples).
+ *
+ * There is also ActivityManager.getMyMemoryState() API which returns current pressure for
+ * the calling process. It has its caveats, for example it can't be called from isolated
+ * processes (renderers). Plus we don't want to poll getMyMemoryState() unnecessarily, for
+ * example there is no reason to poll it when Chrome is in the background.
+ *
+ * This class implements the following principles:
+ *
+ * 1. Throttle pressure signals sent to callbacks.
+ * Callbacks are called at most once during throttling interval. If same pressure is
+ * reported several times during the interval, all reports except the first one are
+ * ignored.
+ *
+ * 2. Always report changes in pressure.
+ * If pressure changes during the interval, the change is not ignored, but delayed
+ * until the end of the interval.
+ *
+ * 3. Poll on CRITICAL memory pressure.
+ * Once CRITICAL pressure is reported, getMyMemoryState API is used to periodically
+ * query pressure until it subsides (becomes non-CRITICAL).
+ *
+ * Zooming out, the class is used as follows:
+ *
+ * 1. Only the browser process / WebView process poll, and it only polls when it makes
+ * sense to do so (when Chrome is in the foreground / there are WebView instances
+ * around).
+ *
+ * 2. Services (GPU, renderers) don't poll, instead they get additional pressure signals
+ * from the main process.
+ *
+ * NOTE: This class should only be used on UiThread as defined by ThreadUtils (which is
+ * Android main thread for Chrome, but can be some other thread for WebView).
+ */
+@MainDex
+public class MemoryPressureMonitor {
+ private static final int DEFAULT_THROTTLING_INTERVAL_MS = 60 * 1000;
+
+ private final int mThrottlingIntervalMs;
+
+ // Pressure reported to callbacks in the current throttling interval.
+ private @MemoryPressureLevel int mLastReportedPressure = MemoryPressureLevel.NONE;
+
+ // Pressure received (but not reported) during the current throttling interval,
+ // or null if no pressure was received.
+ private @MemoryPressureLevel Integer mThrottledPressure;
+
+ // Whether we need to throttle pressure signals.
+ private boolean mIsInsideThrottlingInterval;
+
+ private boolean mPollingEnabled;
+
+ // Changed by tests.
+ private Supplier<Integer> mCurrentPressureSupplier =
+ MemoryPressureMonitor::getCurrentMemoryPressure;
+
+ // Changed by tests.
+ private MemoryPressureCallback mReportingCallback =
+ MemoryPressureListener::notifyMemoryPressure;
+
+ private final Runnable mThrottlingIntervalTask = this ::onThrottlingIntervalFinished;
+
+ // ActivityManager.getMyMemoryState() time histograms, recorded by getCurrentMemoryPressure().
+ // Using Count1MHistogramSample because TimesHistogramSample doesn't support microsecond
+ // precision.
+ private static final CachedMetrics.Count1MHistogramSample sGetMyMemoryStateSucceededTime =
+ new CachedMetrics.Count1MHistogramSample(
+ "Android.MemoryPressureMonitor.GetMyMemoryState.Succeeded.Time");
+ private static final CachedMetrics.Count1MHistogramSample sGetMyMemoryStateFailedTime =
+ new CachedMetrics.Count1MHistogramSample(
+ "Android.MemoryPressureMonitor.GetMyMemoryState.Failed.Time");
+
+ // The only instance.
+ public static final MemoryPressureMonitor INSTANCE =
+ new MemoryPressureMonitor(DEFAULT_THROTTLING_INTERVAL_MS);
+
+ @VisibleForTesting
+ protected MemoryPressureMonitor(int throttlingIntervalMs) {
+ mThrottlingIntervalMs = throttlingIntervalMs;
+ }
+
+ /**
+ * Starts listening to ComponentCallbacks2.
+ */
+ public void registerComponentCallbacks() {
+ ThreadUtils.assertOnUiThread();
+
+ ContextUtils.getApplicationContext().registerComponentCallbacks(new ComponentCallbacks2() {
+ @Override
+ public void onTrimMemory(int level) {
+ Integer pressure = memoryPressureFromTrimLevel(level);
+ if (pressure != null) {
+ notifyPressure(pressure);
+ }
+ }
+
+ @Override
+ public void onLowMemory() {
+ notifyPressure(MemoryPressureLevel.CRITICAL);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {}
+ });
+ }
+
+ /**
+ * Enables memory pressure polling.
+ * See class comment for specifics. This method also does a single pressure check to get
+ * the current pressure.
+ */
+ public void enablePolling() {
+ ThreadUtils.assertOnUiThread();
+ if (mPollingEnabled) return;
+
+ mPollingEnabled = true;
+ if (!mIsInsideThrottlingInterval) {
+ reportCurrentPressure();
+ }
+ }
+
+ /**
+ * Disables memory pressure polling.
+ */
+ public void disablePolling() {
+ ThreadUtils.assertOnUiThread();
+ if (!mPollingEnabled) return;
+
+ mPollingEnabled = false;
+ }
+
+ /**
+ * Notifies the class about change in memory pressure.
+ * Note that |pressure| might get throttled or delayed, i.e. calling this method doesn't
+ * necessarily call the callbacks. See the class comment.
+ */
+ public void notifyPressure(@MemoryPressureLevel int pressure) {
+ ThreadUtils.assertOnUiThread();
+
+ if (mIsInsideThrottlingInterval) {
+ // We've already reported during this interval. Save |pressure| and act on
+ // it later, when the interval finishes.
+ mThrottledPressure = pressure;
+ return;
+ }
+
+ reportPressure(pressure);
+ }
+
+ /**
+ * Last pressure that was reported to MemoryPressureListener.
+ * Returns MemoryPressureLevel.NONE if nothing was reported yet.
+ */
+ public @MemoryPressureLevel int getLastReportedPressure() {
+ ThreadUtils.assertOnUiThread();
+ return mLastReportedPressure;
+ }
+
+ private void reportPressure(@MemoryPressureLevel int pressure) {
+ assert !mIsInsideThrottlingInterval : "Can't report pressure when throttling.";
+
+ startThrottlingInterval();
+
+ mLastReportedPressure = pressure;
+ mReportingCallback.onPressure(pressure);
+ }
+
+ private void onThrottlingIntervalFinished() {
+ mIsInsideThrottlingInterval = false;
+
+ // If there was a pressure change during the interval, report it.
+ if (mThrottledPressure != null && mLastReportedPressure != mThrottledPressure) {
+ int throttledPressure = mThrottledPressure;
+ mThrottledPressure = null;
+ reportPressure(throttledPressure);
+ return;
+ }
+
+ // The pressure didn't change during the interval. Report current pressure
+ // (starting a new interval) if we need to.
+ if (mPollingEnabled && mLastReportedPressure == MemoryPressureLevel.CRITICAL) {
+ reportCurrentPressure();
+ }
+ }
+
+ private void reportCurrentPressure() {
+ Integer pressure = mCurrentPressureSupplier.get();
+ if (pressure != null) {
+ reportPressure(pressure);
+ }
+ }
+
+ private void startThrottlingInterval() {
+ ThreadUtils.postOnUiThreadDelayed(mThrottlingIntervalTask, mThrottlingIntervalMs);
+ mIsInsideThrottlingInterval = true;
+ }
+
+ @VisibleForTesting
+ public void setCurrentPressureSupplierForTesting(Supplier<Integer> supplier) {
+ mCurrentPressureSupplier = supplier;
+ }
+
+ @VisibleForTesting
+ public void setReportingCallbackForTesting(MemoryPressureCallback callback) {
+ mReportingCallback = callback;
+ }
+
+ /**
+ * Queries current memory pressure.
+ * Returns null if the pressure couldn't be determined.
+ */
+ private static @MemoryPressureLevel Integer getCurrentMemoryPressure() {
+ long startNanos = elapsedRealtimeNanos();
+ try {
+ ActivityManager.RunningAppProcessInfo processInfo =
+ new ActivityManager.RunningAppProcessInfo();
+ ActivityManager.getMyMemoryState(processInfo);
+ recordRealtimeNanosDuration(sGetMyMemoryStateSucceededTime, startNanos);
+ return memoryPressureFromTrimLevel(processInfo.lastTrimLevel);
+ } catch (Exception e) {
+ // Defensively catch all exceptions, just in case.
+ recordRealtimeNanosDuration(sGetMyMemoryStateFailedTime, startNanos);
+ return null;
+ }
+ }
+
+ private static void recordRealtimeNanosDuration(
+ CachedMetrics.Count1MHistogramSample histogram, long startNanos) {
+ // We're using Count1MHistogram, so we need to calculate duration in microseconds
+ long durationUs = TimeUnit.NANOSECONDS.toMicros(elapsedRealtimeNanos() - startNanos);
+ // record() takes int, so we need to clamp.
+ histogram.record((int) Math.min(durationUs, Integer.MAX_VALUE));
+ }
+
+ private static long elapsedRealtimeNanos() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return SystemClock.elapsedRealtimeNanos();
+ } else {
+ return SystemClock.elapsedRealtime() * 1000000;
+ }
+ }
+
+ /**
+ * Maps ComponentCallbacks2.TRIM_* value to MemoryPressureLevel.
+ * Returns null if |level| couldn't be mapped and should be ignored.
+ */
+ @VisibleForTesting
+ public static @MemoryPressureLevel Integer memoryPressureFromTrimLevel(int level) {
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE
+ || level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
+ return MemoryPressureLevel.CRITICAL;
+ } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
+ // Don't notify on TRIM_MEMORY_UI_HIDDEN, since this class only
+ // dispatches actionable memory pressure signals to native.
+ return MemoryPressureLevel.MODERATE;
+ }
+ return null;
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java b/base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java
new file mode 100644
index 0000000000..dc90f5706e
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java
@@ -0,0 +1,113 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.memory;
+
+import android.content.ComponentCallbacks2;
+import android.content.res.Configuration;
+import android.support.annotation.IntDef;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.metrics.RecordHistogram;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Centralizes UMA data collection for Android-specific memory conditions.
+ */
+public class MemoryPressureUma implements ComponentCallbacks2 {
+ @IntDef({
+ Notification.UNKNOWN_TRIM_LEVEL, Notification.TRIM_MEMORY_COMPLETE,
+ Notification.TRIM_MEMORY_MODERATE, Notification.TRIM_MEMORY_BACKGROUND,
+ Notification.TRIM_MEMORY_UI_HIDDEN, Notification.TRIM_MEMORY_RUNNING_CRITICAL,
+ Notification.TRIM_MEMORY_RUNNING_LOW, Notification.TRIM_MEMORY_RUNNING_MODERATE,
+ Notification.ON_LOW_MEMORY, Notification.NOTIFICATION_MAX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Notification {
+ // WARNING: These values are persisted to logs. Entries should not be
+ // renumbered and numeric values should never be reused.
+ // Keep in sync with "Android.MemoryPressureNotification" UMA enum.
+ int UNKNOWN_TRIM_LEVEL = 0;
+ int TRIM_MEMORY_COMPLETE = 1;
+ int TRIM_MEMORY_MODERATE = 2;
+ int TRIM_MEMORY_BACKGROUND = 3;
+ int TRIM_MEMORY_UI_HIDDEN = 4;
+ int TRIM_MEMORY_RUNNING_CRITICAL = 5;
+ int TRIM_MEMORY_RUNNING_LOW = 6;
+ int TRIM_MEMORY_RUNNING_MODERATE = 7;
+ int ON_LOW_MEMORY = 8;
+
+ // Must be the last one.
+ int NOTIFICATION_MAX = 9;
+ }
+
+ private final String mHistogramName;
+
+ private static MemoryPressureUma sInstance;
+
+ public static void initializeForBrowser() {
+ initializeInstance("Browser");
+ }
+
+ public static void initializeForChildService() {
+ initializeInstance("ChildService");
+ }
+
+ private static void initializeInstance(String processType) {
+ ThreadUtils.assertOnUiThread();
+ assert sInstance == null;
+ sInstance = new MemoryPressureUma(processType);
+ ContextUtils.getApplicationContext().registerComponentCallbacks(sInstance);
+ }
+
+ private MemoryPressureUma(String processType) {
+ mHistogramName = "Android.MemoryPressureNotification." + processType;
+ }
+
+ @Override
+ public void onLowMemory() {
+ record(Notification.ON_LOW_MEMORY);
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ switch (level) {
+ case TRIM_MEMORY_COMPLETE:
+ record(Notification.TRIM_MEMORY_COMPLETE);
+ break;
+ case TRIM_MEMORY_MODERATE:
+ record(Notification.TRIM_MEMORY_MODERATE);
+ break;
+ case TRIM_MEMORY_BACKGROUND:
+ record(Notification.TRIM_MEMORY_BACKGROUND);
+ break;
+ case TRIM_MEMORY_UI_HIDDEN:
+ record(Notification.TRIM_MEMORY_UI_HIDDEN);
+ break;
+ case TRIM_MEMORY_RUNNING_CRITICAL:
+ record(Notification.TRIM_MEMORY_RUNNING_CRITICAL);
+ break;
+ case TRIM_MEMORY_RUNNING_LOW:
+ record(Notification.TRIM_MEMORY_RUNNING_LOW);
+ break;
+ case TRIM_MEMORY_RUNNING_MODERATE:
+ record(Notification.TRIM_MEMORY_RUNNING_MODERATE);
+ break;
+ default:
+ record(Notification.UNKNOWN_TRIM_LEVEL);
+ break;
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {}
+
+ private void record(@Notification int notification) {
+ RecordHistogram.recordEnumeratedHistogram(
+ mHistogramName, notification, Notification.NOTIFICATION_MAX);
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/metrics/CachedMetrics.java b/base/android/java/src/org/chromium/base/metrics/CachedMetrics.java
new file mode 100644
index 0000000000..ba03e51275
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/metrics/CachedMetrics.java
@@ -0,0 +1,307 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.metrics;
+
+import org.chromium.base.library_loader.LibraryLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility classes for recording UMA metrics before the native library
+ * may have been loaded. Metrics are cached until the library is known
+ * to be loaded, then committed to the MetricsService all at once.
+ */
+public class CachedMetrics {
+ /**
+ * Base class for cached metric objects. Subclasses are expected to call
+ * addToCache() when some metric state gets recorded that requires a later
+ * commit operation when the native library is loaded.
+ */
+ private abstract static class CachedMetric {
+ private static final List<CachedMetric> sMetrics = new ArrayList<CachedMetric>();
+
+ protected final String mName;
+ protected boolean mCached;
+
+ /**
+ * @param name Name of the metric to record.
+ */
+ protected CachedMetric(String name) {
+ mName = name;
+ }
+
+ /**
+ * Adds this object to the sMetrics cache, if it hasn't been added already.
+ * Must be called while holding the synchronized(sMetrics) lock.
+ * Note: The synchronization is not done inside this function because subclasses
+ * need to increment their held values under lock to ensure thread-safety.
+ */
+ protected final void addToCache() {
+ assert Thread.holdsLock(sMetrics);
+
+ if (mCached) return;
+ sMetrics.add(this);
+ mCached = true;
+ }
+
+ /**
+ * Commits the metric. Expects the native library to be loaded.
+ * Must be called while holding the synchronized(sMetrics) lock.
+ */
+ protected abstract void commitAndClear();
+ }
+
+ /**
+ * Caches an action that will be recorded after native side is loaded.
+ */
+ public static class ActionEvent extends CachedMetric {
+ private int mCount;
+
+ public ActionEvent(String actionName) {
+ super(actionName);
+ }
+
+ public void record() {
+ synchronized (CachedMetric.sMetrics) {
+ if (LibraryLoader.getInstance().isInitialized()) {
+ recordWithNative();
+ } else {
+ mCount++;
+ addToCache();
+ }
+ }
+ }
+
+ private void recordWithNative() {
+ RecordUserAction.record(mName);
+ }
+
+ @Override
+ protected void commitAndClear() {
+ while (mCount > 0) {
+ recordWithNative();
+ mCount--;
+ }
+ }
+ }
+
+ /** Caches a set of integer histogram samples. */
+ public static class SparseHistogramSample extends CachedMetric {
+ private final List<Integer> mSamples = new ArrayList<Integer>();
+
+ public SparseHistogramSample(String histogramName) {
+ super(histogramName);
+ }
+
+ public void record(int sample) {
+ synchronized (CachedMetric.sMetrics) {
+ if (LibraryLoader.getInstance().isInitialized()) {
+ recordWithNative(sample);
+ } else {
+ mSamples.add(sample);
+ addToCache();
+ }
+ }
+ }
+
+ private void recordWithNative(int sample) {
+ RecordHistogram.recordSparseSlowlyHistogram(mName, sample);
+ }
+
+ @Override
+ protected void commitAndClear() {
+ for (Integer sample : mSamples) {
+ recordWithNative(sample);
+ }
+ mSamples.clear();
+ }
+ }
+
+ /** Caches a set of enumerated histogram samples. */
+ public static class EnumeratedHistogramSample extends CachedMetric {
+ private final List<Integer> mSamples = new ArrayList<Integer>();
+ private final int mMaxValue;
+
+ public EnumeratedHistogramSample(String histogramName, int maxValue) {
+ super(histogramName);
+ mMaxValue = maxValue;
+ }
+
+ public void record(int sample) {
+ synchronized (CachedMetric.sMetrics) {
+ if (LibraryLoader.getInstance().isInitialized()) {
+ recordWithNative(sample);
+ } else {
+ mSamples.add(sample);
+ addToCache();
+ }
+ }
+ }
+
+ private void recordWithNative(int sample) {
+ RecordHistogram.recordEnumeratedHistogram(mName, sample, mMaxValue);
+ }
+
+ @Override
+ protected void commitAndClear() {
+ for (Integer sample : mSamples) {
+ recordWithNative(sample);
+ }
+ mSamples.clear();
+ }
+ }
+
+ /** Caches a set of times histogram samples. */
+ public static class TimesHistogramSample extends CachedMetric {
+ private final List<Long> mSamples = new ArrayList<Long>();
+ private final TimeUnit mTimeUnit;
+
+ public TimesHistogramSample(String histogramName, TimeUnit timeUnit) {
+ super(histogramName);
+ RecordHistogram.assertTimesHistogramSupportsUnit(timeUnit);
+ mTimeUnit = timeUnit;
+ }
+
+ public void record(long sample) {
+ synchronized (CachedMetric.sMetrics) {
+ if (LibraryLoader.getInstance().isInitialized()) {
+ recordWithNative(sample);
+ } else {
+ mSamples.add(sample);
+ addToCache();
+ }
+ }
+ }
+
+ private void recordWithNative(long sample) {
+ RecordHistogram.recordTimesHistogram(mName, sample, mTimeUnit);
+ }
+
+ @Override
+ protected void commitAndClear() {
+ for (Long sample : mSamples) {
+ recordWithNative(sample);
+ }
+ mSamples.clear();
+ }
+ }
+
+ /** Caches a set of boolean histogram samples. */
+ public static class BooleanHistogramSample extends CachedMetric {
+ private final List<Boolean> mSamples = new ArrayList<Boolean>();
+
+ public BooleanHistogramSample(String histogramName) {
+ super(histogramName);
+ }
+
+ public void record(boolean sample) {
+ synchronized (CachedMetric.sMetrics) {
+ if (LibraryLoader.getInstance().isInitialized()) {
+ recordWithNative(sample);
+ } else {
+ mSamples.add(sample);
+ addToCache();
+ }
+ }
+ }
+
+ private void recordWithNative(boolean sample) {
+ RecordHistogram.recordBooleanHistogram(mName, sample);
+ }
+
+ @Override
+ protected void commitAndClear() {
+ for (Boolean sample : mSamples) {
+ recordWithNative(sample);
+ }
+ mSamples.clear();
+ }
+ }
+
+ /**
+ * Caches a set of custom count histogram samples.
+ * Corresponds to UMA_HISTOGRAM_CUSTOM_COUNTS C++ macro.
+ */
+ public static class CustomCountHistogramSample extends CachedMetric {
+ private final List<Integer> mSamples = new ArrayList<Integer>();
+ private final int mMin;
+ private final int mMax;
+ private final int mNumBuckets;
+
+ public CustomCountHistogramSample(String histogramName, int min, int max, int numBuckets) {
+ super(histogramName);
+ mMin = min;
+ mMax = max;
+ mNumBuckets = numBuckets;
+ }
+
+ public void record(int sample) {
+ synchronized (CachedMetric.sMetrics) {
+ if (LibraryLoader.getInstance().isInitialized()) {
+ recordWithNative(sample);
+ } else {
+ mSamples.add(sample);
+ addToCache();
+ }
+ }
+ }
+
+ private void recordWithNative(int sample) {
+ RecordHistogram.recordCustomCountHistogram(mName, sample, mMin, mMax, mNumBuckets);
+ }
+
+ @Override
+ protected void commitAndClear() {
+ for (Integer sample : mSamples) {
+ recordWithNative(sample);
+ }
+ mSamples.clear();
+ }
+ }
+
+ /**
+ * Caches a set of count histogram samples in range [1, 100).
+ * Corresponds to UMA_HISTOGRAM_COUNTS_100 C++ macro.
+ */
+ public static class Count100HistogramSample extends CustomCountHistogramSample {
+ public Count100HistogramSample(String histogramName) {
+ super(histogramName, 1, 100, 50);
+ }
+ }
+
+ /**
+ * Caches a set of count histogram samples in range [1, 1000).
+ * Corresponds to UMA_HISTOGRAM_COUNTS_1000 C++ macro.
+ */
+ public static class Count1000HistogramSample extends CustomCountHistogramSample {
+ public Count1000HistogramSample(String histogramName) {
+ super(histogramName, 1, 1000, 50);
+ }
+ }
+
+ /**
+ * Caches a set of count histogram samples in range [1, 1000000).
+ * Corresponds to UMA_HISTOGRAM_COUNTS_1M C++ macro.
+ */
+ public static class Count1MHistogramSample extends CustomCountHistogramSample {
+ public Count1MHistogramSample(String histogramName) {
+ super(histogramName, 1, 1000000, 50);
+ }
+ }
+
+ /**
+ * Calls out to native code to commit any cached histograms and events.
+ * Should be called once the native library has been loaded.
+ */
+ public static void commitCachedMetrics() {
+ synchronized (CachedMetric.sMetrics) {
+ for (CachedMetric metric : CachedMetric.sMetrics) {
+ metric.commitAndClear();
+ }
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/metrics/RecordHistogram.java b/base/android/java/src/org/chromium/base/metrics/RecordHistogram.java
new file mode 100644
index 0000000000..898f0094ab
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/metrics/RecordHistogram.java
@@ -0,0 +1,331 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.metrics;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Java API for recording UMA histograms.
+ *
+ * Internally, histograms objects are cached on the Java side by their pointer
+ * values (converted to long). This is safe to do because C++ Histogram objects
+ * are never freed. Caching them on the Java side prevents needing to do costly
+ * Java String to C++ string conversions on the C++ side during lookup.
+ *
+ * Note: the JNI calls are relatively costly - avoid calling these methods in performance-critical
+ * code.
+ */
+@JNINamespace("base::android")
+@MainDex
+public class RecordHistogram {
+ private static Throwable sDisabledBy;
+ private static Map<String, Long> sCache =
+ Collections.synchronizedMap(new HashMap<String, Long>());
+
+ /**
+ * Tests may not have native initialized, so they may need to disable metrics. The value should
+ * be reset after the test done, to avoid carrying over state to unrelated tests.
+ *
+ * In JUnit tests this can be done automatically using
+ * {@link org.chromium.chrome.browser.DisableHistogramsRule}
+ */
+ @VisibleForTesting
+ public static void setDisabledForTests(boolean disabled) {
+ if (disabled && sDisabledBy != null) {
+ throw new IllegalStateException("Histograms are already disabled.", sDisabledBy);
+ }
+ sDisabledBy = disabled ? new Throwable() : null;
+ }
+
+ private static long getCachedHistogramKey(String name) {
+ Long key = sCache.get(name);
+ // Note: If key is null, we don't have it cached. In that case, pass 0
+ // to the native code, which gets converted to a null histogram pointer
+ // which will cause the native code to look up the object on the native
+ // side.
+ return (key == null ? 0 : key);
+ }
+
+ /**
+ * Records a sample in a boolean UMA histogram of the given name. Boolean histogram has two
+ * buckets, corresponding to success (true) and failure (false). This is the Java equivalent of
+ * the UMA_HISTOGRAM_BOOLEAN C++ macro.
+ * @param name name of the histogram
+ * @param sample sample to be recorded, either true or false
+ */
+ public static void recordBooleanHistogram(String name, boolean sample) {
+ if (sDisabledBy != null) return;
+ long key = getCachedHistogramKey(name);
+ long result = nativeRecordBooleanHistogram(name, key, sample);
+ if (result != key) sCache.put(name, result);
+ }
+
+ /**
+ * Records a sample in an enumerated histogram of the given name and boundary. Note that
+ * |boundary| identifies the histogram - it should be the same at every invocation. This is the
+ * Java equivalent of the UMA_HISTOGRAM_ENUMERATION C++ macro.
+ * @param name name of the histogram
+ * @param sample sample to be recorded, at least 0 and at most |boundary| - 1
+ * @param boundary upper bound for legal sample values - all sample values have to be strictly
+ * lower than |boundary|
+ */
+ public static void recordEnumeratedHistogram(String name, int sample, int boundary) {
+ if (sDisabledBy != null) return;
+ long key = getCachedHistogramKey(name);
+ long result = nativeRecordEnumeratedHistogram(name, key, sample, boundary);
+ if (result != key) sCache.put(name, result);
+ }
+
+ /**
+ * Records a sample in a count histogram. This is the Java equivalent of the
+ * UMA_HISTOGRAM_COUNTS C++ macro.
+ * @param name name of the histogram
+ * @param sample sample to be recorded, at least 1 and at most 999999
+ */
+ public static void recordCountHistogram(String name, int sample) {
+ recordCustomCountHistogram(name, sample, 1, 1000000, 50);
+ }
+
+ /**
+ * Records a sample in a count histogram. This is the Java equivalent of the
+ * UMA_HISTOGRAM_COUNTS_100 C++ macro.
+ * @param name name of the histogram
+ * @param sample sample to be recorded, at least 1 and at most 99
+ */
+ public static void recordCount100Histogram(String name, int sample) {
+ recordCustomCountHistogram(name, sample, 1, 100, 50);
+ }
+
+ /**
+ * Records a sample in a count histogram. This is the Java equivalent of the
+ * UMA_HISTOGRAM_COUNTS_1000 C++ macro.
+ * @param name name of the histogram
+ * @param sample sample to be recorded, at least 1 and at most 999
+ */
+ public static void recordCount1000Histogram(String name, int sample) {
+ recordCustomCountHistogram(name, sample, 1, 1000, 50);
+ }
+
+ /**
+ * Records a sample in a count histogram. This is the Java equivalent of the
+ * UMA_HISTOGRAM_CUSTOM_COUNTS C++ macro.
+ * @param name name of the histogram
+ * @param sample sample to be recorded, at least |min| and at most |max| - 1
+ * @param min lower bound for expected sample values. It must be >= 1
+ * @param max upper bounds for expected sample values
+ * @param numBuckets the number of buckets
+ */
+ public static void recordCustomCountHistogram(
+ String name, int sample, int min, int max, int numBuckets) {
+ if (sDisabledBy != null) return;
+ long key = getCachedHistogramKey(name);
+ long result = nativeRecordCustomCountHistogram(name, key, sample, min, max, numBuckets);
+ if (result != key) sCache.put(name, result);
+ }
+
+ /**
+ * Records a sample in a linear histogram. This is the Java equivalent for using
+ * base::LinearHistogram.
+ * @param name name of the histogram
+ * @param sample sample to be recorded, at least |min| and at most |max| - 1.
+ * @param min lower bound for expected sample values, should be at least 1.
+ * @param max upper bounds for expected sample values
+ * @param numBuckets the number of buckets
+ */
+ public static void recordLinearCountHistogram(
+ String name, int sample, int min, int max, int numBuckets) {
+ if (sDisabledBy != null) return;
+ long key = getCachedHistogramKey(name);
+ long result = nativeRecordLinearCountHistogram(name, key, sample, min, max, numBuckets);
+ if (result != key) sCache.put(name, result);
+ }
+
+ /**
+ * Records a sample in a percentage histogram. This is the Java equivalent of the
+ * UMA_HISTOGRAM_PERCENTAGE C++ macro.
+ * @param name name of the histogram
+ * @param sample sample to be recorded, at least 0 and at most 100.
+ */
+ public static void recordPercentageHistogram(String name, int sample) {
+ if (sDisabledBy != null) return;
+ long key = getCachedHistogramKey(name);
+ long result = nativeRecordEnumeratedHistogram(name, key, sample, 101);
+ if (result != key) sCache.put(name, result);
+ }
+
+ /**
+ * Records a sparse histogram. This is the Java equivalent of UmaHistogramSparse.
+ * @param name name of the histogram
+ * @param sample sample to be recorded. All values of |sample| are valid, including negative
+ * values.
+ */
+ public static void recordSparseSlowlyHistogram(String name, int sample) {
+ if (sDisabledBy != null) return;
+ long key = getCachedHistogramKey(name);
+ long result = nativeRecordSparseHistogram(name, key, sample);
+ if (result != key) sCache.put(name, result);
+ }
+
+ /**
+ * Records a sample in a histogram of times. Useful for recording short durations. This is the
+ * Java equivalent of the UMA_HISTOGRAM_TIMES C++ macro.
+ * Note that histogram samples will always be converted to milliseconds when logged.
+ * @param name name of the histogram
+ * @param duration duration to be recorded
+ * @param timeUnit the unit of the duration argument (must be >= MILLISECONDS)
+ */
+ public static void recordTimesHistogram(String name, long duration, TimeUnit timeUnit) {
+ assertTimesHistogramSupportsUnit(timeUnit);
+ recordCustomTimesHistogramMilliseconds(
+ name, timeUnit.toMillis(duration), 1, TimeUnit.SECONDS.toMillis(10), 50);
+ }
+
+ /**
+ * Records a sample in a histogram of times. Useful for recording medium durations. This is the
+ * Java equivalent of the UMA_HISTOGRAM_MEDIUM_TIMES C++ macro.
+ * Note that histogram samples will always be converted to milliseconds when logged.
+ * @param name name of the histogram
+ * @param duration duration to be recorded
+ * @param timeUnit the unit of the duration argument (must be >= MILLISECONDS)
+ */
+ public static void recordMediumTimesHistogram(String name, long duration, TimeUnit timeUnit) {
+ assertTimesHistogramSupportsUnit(timeUnit);
+ recordCustomTimesHistogramMilliseconds(
+ name, timeUnit.toMillis(duration), 10, TimeUnit.MINUTES.toMillis(3), 50);
+ }
+
+ /**
+ * Records a sample in a histogram of times. Useful for recording long durations. This is the
+ * Java equivalent of the UMA_HISTOGRAM_LONG_TIMES C++ macro.
+ * Note that histogram samples will always be converted to milliseconds when logged.
+ * @param name name of the histogram
+ * @param duration duration to be recorded
+ * @param timeUnit the unit of the duration argument (must be >= MILLISECONDS)
+ */
+ public static void recordLongTimesHistogram(String name, long duration, TimeUnit timeUnit) {
+ assertTimesHistogramSupportsUnit(timeUnit);
+ recordCustomTimesHistogramMilliseconds(
+ name, timeUnit.toMillis(duration), 1, TimeUnit.HOURS.toMillis(1), 50);
+ }
+
+ /**
+ * Records a sample in a histogram of times. Useful for recording long durations. This is the
+ * Java equivalent of the UMA_HISTOGRAM_LONG_TIMES_100 C++ macro.
+ * Note that histogram samples will always be converted to milliseconds when logged.
+ * @param name name of the histogram
+ * @param duration duration to be recorded
+ * @param timeUnit the unit of the duration argument (must be >= MILLISECONDS)
+ */
+ public static void recordLongTimesHistogram100(String name, long duration, TimeUnit timeUnit) {
+ assertTimesHistogramSupportsUnit(timeUnit);
+ recordCustomTimesHistogramMilliseconds(
+ name, timeUnit.toMillis(duration), 1, TimeUnit.HOURS.toMillis(1), 100);
+ }
+
+ /**
+ * Records a sample in a histogram of times with custom buckets. This is the Java equivalent of
+ * the UMA_HISTOGRAM_CUSTOM_TIMES C++ macro.
+ * Note that histogram samples will always be converted to milliseconds when logged.
+ * @param name name of the histogram
+ * @param duration duration to be recorded
+ * @param min the minimum bucket value
+ * @param max the maximum bucket value
+ * @param timeUnit the unit of the duration, min, and max arguments (must be >= MILLISECONDS)
+ * @param numBuckets the number of buckets
+ */
+ public static void recordCustomTimesHistogram(
+ String name, long duration, long min, long max, TimeUnit timeUnit, int numBuckets) {
+ assertTimesHistogramSupportsUnit(timeUnit);
+ recordCustomTimesHistogramMilliseconds(name, timeUnit.toMillis(duration),
+ timeUnit.toMillis(min), timeUnit.toMillis(max), numBuckets);
+ }
+
+ /**
+ * Records a sample in a histogram of sizes in KB. This is the Java equivalent of the
+ * UMA_HISTOGRAM_MEMORY_KB C++ macro.
+ *
+ * Good for sizes up to about 500MB.
+ *
+ * @param name name of the histogram.
+ * @param sizeInkB Sample to record in KB.
+ */
+ public static void recordMemoryKBHistogram(String name, int sizeInKB) {
+ recordCustomCountHistogram(name, sizeInKB, 1000, 500000, 50);
+ }
+
+ /**
+ * Asserts that the time unit is supported by TimesHistogram.
+ * @param timeUnit the unit, must be >= MILLISECONDS
+ */
+ /* package */ static void assertTimesHistogramSupportsUnit(TimeUnit timeUnit) {
+ // Use extra variable, or else 'git cl format' produces weird results.
+ boolean supported = timeUnit != TimeUnit.NANOSECONDS && timeUnit != TimeUnit.MICROSECONDS;
+ assert supported : "TimesHistogram doesn't support MICROSECOND and NANOSECONDS time units. "
+ + "Consider using CountHistogram instead.";
+ }
+
+ private static int clampToInt(long value) {
+ if (value > Integer.MAX_VALUE) return Integer.MAX_VALUE;
+ // Note: Clamping to MIN_VALUE rather than 0, to let base/ histograms code
+ // do its own handling of negative values in the future.
+ if (value < Integer.MIN_VALUE) return Integer.MIN_VALUE;
+ return (int) value;
+ }
+
+ private static void recordCustomTimesHistogramMilliseconds(
+ String name, long duration, long min, long max, int numBuckets) {
+ if (sDisabledBy != null) return;
+ long key = getCachedHistogramKey(name);
+ // Note: Duration, min and max are clamped to int here because that's what's expected by
+ // the native histograms API. Callers of these functions still pass longs because that's
+ // the types returned by TimeUnit and System.currentTimeMillis() APIs, from which these
+ // values come.
+ assert max == clampToInt(max);
+ long result = nativeRecordCustomTimesHistogramMilliseconds(
+ name, key, clampToInt(duration), clampToInt(min), clampToInt(max), numBuckets);
+ if (result != key) sCache.put(name, result);
+ }
+
+ /**
+ * Returns the number of samples recorded in the given bucket of the given histogram.
+ * @param name name of the histogram to look up
+ * @param sample the bucket containing this sample value will be looked up
+ */
+ @VisibleForTesting
+ public static int getHistogramValueCountForTesting(String name, int sample) {
+ return nativeGetHistogramValueCountForTesting(name, sample);
+ }
+
+ /**
+ * Returns the number of samples recorded for the given histogram.
+ * @param name name of the histogram to look up.
+ */
+ @VisibleForTesting
+ public static int getHistogramTotalCountForTesting(String name) {
+ return nativeGetHistogramTotalCountForTesting(name);
+ }
+
+ private static native long nativeRecordCustomTimesHistogramMilliseconds(
+ String name, long key, int duration, int min, int max, int numBuckets);
+
+ private static native long nativeRecordBooleanHistogram(String name, long key, boolean sample);
+ private static native long nativeRecordEnumeratedHistogram(
+ String name, long key, int sample, int boundary);
+ private static native long nativeRecordCustomCountHistogram(
+ String name, long key, int sample, int min, int max, int numBuckets);
+ private static native long nativeRecordLinearCountHistogram(
+ String name, long key, int sample, int min, int max, int numBuckets);
+ private static native long nativeRecordSparseHistogram(String name, long key, int sample);
+
+ private static native int nativeGetHistogramValueCountForTesting(String name, int sample);
+ private static native int nativeGetHistogramTotalCountForTesting(String name);
+}
diff --git a/base/android/java/src/org/chromium/base/metrics/RecordUserAction.java b/base/android/java/src/org/chromium/base/metrics/RecordUserAction.java
new file mode 100644
index 0000000000..0d2ba548d2
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/metrics/RecordUserAction.java
@@ -0,0 +1,85 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.metrics;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * Java API for recording UMA actions.
+ *
+ * WARNINGS:
+ * JNI calls are relatively costly - avoid using in performance-critical code.
+ *
+ * We use a script (extract_actions.py) to scan the source code and extract actions. A string
+ * literal (not a variable) must be passed to record().
+ */
+@JNINamespace("base::android")
+public class RecordUserAction {
+ private static Throwable sDisabledBy;
+
+ /**
+ * Tests may not have native initialized, so they may need to disable metrics. The value should
+ * be reset after the test done, to avoid carrying over state to unrelated tests.
+ */
+ @VisibleForTesting
+ public static void setDisabledForTests(boolean disabled) {
+ if (disabled && sDisabledBy != null) {
+ throw new IllegalStateException("UserActions are already disabled.", sDisabledBy);
+ }
+ sDisabledBy = disabled ? new Throwable() : null;
+ }
+
+ public static void record(final String action) {
+ if (sDisabledBy != null) return;
+
+ if (ThreadUtils.runningOnUiThread()) {
+ nativeRecordUserAction(action);
+ return;
+ }
+
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ nativeRecordUserAction(action);
+ }
+ });
+ }
+
+ /**
+ * Interface to a class that receives a callback for each UserAction that is recorded.
+ */
+ public interface UserActionCallback {
+ @CalledByNative("UserActionCallback")
+ void onActionRecorded(String action);
+ }
+
+ private static long sNativeActionCallback;
+
+ /**
+ * Register a callback that is executed for each recorded UserAction.
+ * Only one callback can be registered at a time.
+ * The callback has to be unregistered using removeActionCallbackForTesting().
+ */
+ public static void setActionCallbackForTesting(UserActionCallback callback) {
+ assert sNativeActionCallback == 0;
+ sNativeActionCallback = nativeAddActionCallbackForTesting(callback);
+ }
+
+ /**
+ * Unregister the UserActionCallback.
+ */
+ public static void removeActionCallbackForTesting() {
+ assert sNativeActionCallback != 0;
+ nativeRemoveActionCallbackForTesting(sNativeActionCallback);
+ sNativeActionCallback = 0;
+ }
+
+ private static native void nativeRecordUserAction(String action);
+ private static native long nativeAddActionCallbackForTesting(UserActionCallback callback);
+ private static native void nativeRemoveActionCallbackForTesting(long callbackId);
+}
diff --git a/base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java b/base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java
new file mode 100644
index 0000000000..bff3fae763
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java
@@ -0,0 +1,27 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.metrics;
+
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * Java API which exposes the registered histograms on the native side as
+ * JSON test.
+ */
+@JNINamespace("base::android")
+public final class StatisticsRecorderAndroid {
+ private StatisticsRecorderAndroid() {}
+
+ /**
+ * @param verbosityLevel controls the information that should be included when dumping each of
+ * the histogram.
+ * @return All the registered histograms as JSON text.
+ */
+ public static String toJson(@JSONVerbosityLevel int verbosityLevel) {
+ return nativeToJson(verbosityLevel);
+ }
+
+ private static native String nativeToJson(@JSONVerbosityLevel int verbosityLevel);
+} \ No newline at end of file
diff --git a/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
new file mode 100644
index 0000000000..5588ec5bdf
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java
@@ -0,0 +1,78 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.multidex;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.support.multidex.MultiDex;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.MainDex;
+
+/**
+ * Performs multidex installation for non-isolated processes.
+ */
+@MainDex
+public class ChromiumMultiDexInstaller {
+ private static final String TAG = "base_multidex";
+
+ /**
+ * Suffix for the meta-data tag in the AndroidManifext.xml that determines whether loading
+ * secondary dexes should be skipped for a given process name.
+ */
+ private static final String IGNORE_MULTIDEX_KEY = ".ignore_multidex";
+
+ /**
+ * Installs secondary dexes if possible/necessary.
+ *
+ * Isolated processes (e.g. renderer processes) can't load secondary dex files on
+ * K and below, so we don't even try in that case.
+ *
+ * In release builds of app apks (as opposed to test apks), this is a no-op because:
+ * - multidex isn't necessary in release builds because we run proguard there and
+ * thus aren't threatening to hit the dex limit; and
+ * - calling MultiDex.install, even in the absence of secondary dexes, causes a
+ * significant regression in start-up time (crbug.com/525695).
+ *
+ * @param context The application context.
+ */
+ @VisibleForTesting
+ public static void install(Context context) {
+ // TODO(jbudorick): Back out this version check once support for K & below works.
+ // http://crbug.com/512357
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
+ && !shouldInstallMultiDex(context)) {
+ Log.i(TAG, "Skipping multidex installation: not needed for process.");
+ } else {
+ MultiDex.install(context);
+ Log.i(TAG, "Completed multidex installation.");
+ }
+ }
+
+ // Determines whether MultiDex should be installed for the current process. Isolated
+ // Processes should skip MultiDex as they can not actually access the files on disk.
+ // Privileged processes need ot have all of their dependencies in the MainDex for
+ // performance reasons.
+ private static boolean shouldInstallMultiDex(Context context) {
+ if (ContextUtils.isIsolatedProcess()) {
+ return false;
+ }
+ String currentProcessName = ContextUtils.getProcessName();
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(context.getPackageName(),
+ PackageManager.GET_META_DATA);
+ if (appInfo == null || appInfo.metaData == null) return true;
+ return !appInfo.metaData.getBoolean(currentProcessName + IGNORE_MULTIDEX_KEY, false);
+ } catch (PackageManager.NameNotFoundException e) {
+ return true;
+ }
+ }
+
+}
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java b/base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java
new file mode 100644
index 0000000000..43ae2591d4
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java
@@ -0,0 +1,305 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import org.chromium.base.Log;
+import org.chromium.base.VisibleForTesting;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Queue;
+
+/**
+ * This class is responsible for allocating and managing connections to child
+ * process services. These connections are in a pool (the services are defined
+ * in the AndroidManifest.xml).
+ */
+public class ChildConnectionAllocator {
+ private static final String TAG = "ChildConnAllocator";
+
+ /** Factory interface. Used by tests to specialize created connections. */
+ @VisibleForTesting
+ public interface ConnectionFactory {
+ ChildProcessConnection createConnection(Context context, ComponentName serviceName,
+ boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle);
+ }
+
+ /** Default implementation of the ConnectionFactory that creates actual connections. */
+ private static class ConnectionFactoryImpl implements ConnectionFactory {
+ @Override
+ public ChildProcessConnection createConnection(Context context, ComponentName serviceName,
+ boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle) {
+ return new ChildProcessConnection(
+ context, serviceName, bindToCaller, bindAsExternalService, serviceBundle);
+ }
+ }
+
+ // Delay between the call to freeConnection and the connection actually beeing freed.
+ private static final long FREE_CONNECTION_DELAY_MILLIS = 1;
+
+ // The handler of the thread on which all interations should happen.
+ private final Handler mLauncherHandler;
+
+ // Connections to services. Indices of the array correspond to the service numbers.
+ private final ChildProcessConnection[] mChildProcessConnections;
+
+ // Runnable which will be called when allocator wants to allocate a new connection, but does
+ // not have any more free slots. May be null.
+ private final Runnable mFreeSlotCallback;
+ private final String mPackageName;
+ private final String mServiceClassName;
+ private final boolean mBindToCaller;
+ private final boolean mBindAsExternalService;
+ private final boolean mUseStrongBinding;
+
+ // The list of free (not bound) service indices.
+ private final ArrayList<Integer> mFreeConnectionIndices;
+
+ private final Queue<Runnable> mPendingAllocations = new ArrayDeque<>();
+
+ private ConnectionFactory mConnectionFactory = new ConnectionFactoryImpl();
+
+ /**
+ * Factory method that retrieves the service name and number of service from the
+ * AndroidManifest.xml.
+ */
+ public static ChildConnectionAllocator create(Context context, Handler launcherHandler,
+ Runnable freeSlotCallback, String packageName, String serviceClassName,
+ String numChildServicesManifestKey, boolean bindToCaller, boolean bindAsExternalService,
+ boolean useStrongBinding) {
+ int numServices = -1;
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ ApplicationInfo appInfo =
+ packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+ if (appInfo.metaData != null) {
+ numServices = appInfo.metaData.getInt(numChildServicesManifestKey, -1);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Could not get application info.");
+ }
+
+ if (numServices < 0) {
+ throw new RuntimeException("Illegal meta data value for number of child services");
+ }
+
+ // Check that the service exists.
+ try {
+ // PackageManager#getServiceInfo() throws an exception if the service does not exist.
+ packageManager.getServiceInfo(
+ new ComponentName(packageName, serviceClassName + "0"), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Illegal meta data value: the child service doesn't exist");
+ }
+
+ return new ChildConnectionAllocator(launcherHandler, freeSlotCallback, packageName,
+ serviceClassName, bindToCaller, bindAsExternalService, useStrongBinding,
+ numServices);
+ }
+
+ /**
+ * Factory method used with some tests to create an allocator with values passed in directly
+ * instead of being retrieved from the AndroidManifest.xml.
+ */
+ @VisibleForTesting
+ public static ChildConnectionAllocator createForTest(Runnable freeSlotCallback,
+ String packageName, String serviceClassName, int serviceCount, boolean bindToCaller,
+ boolean bindAsExternalService, boolean useStrongBinding) {
+ return new ChildConnectionAllocator(new Handler(), freeSlotCallback, packageName,
+ serviceClassName, bindToCaller, bindAsExternalService, useStrongBinding,
+ serviceCount);
+ }
+
+ private ChildConnectionAllocator(Handler launcherHandler, Runnable freeSlotCallback,
+ String packageName, String serviceClassName, boolean bindToCaller,
+ boolean bindAsExternalService, boolean useStrongBinding, int numChildServices) {
+ mFreeSlotCallback = freeSlotCallback;
+ mLauncherHandler = launcherHandler;
+ assert isRunningOnLauncherThread();
+ mPackageName = packageName;
+ mServiceClassName = serviceClassName;
+ mBindToCaller = bindToCaller;
+ mBindAsExternalService = bindAsExternalService;
+ mUseStrongBinding = useStrongBinding;
+ mChildProcessConnections = new ChildProcessConnection[numChildServices];
+ mFreeConnectionIndices = new ArrayList<Integer>(numChildServices);
+ for (int i = 0; i < numChildServices; i++) {
+ mFreeConnectionIndices.add(i);
+ }
+ }
+
+ /** @return a bound connection, or null if there are no free slots. */
+ public ChildProcessConnection allocate(Context context, Bundle serviceBundle,
+ final ChildProcessConnection.ServiceCallback serviceCallback) {
+ assert isRunningOnLauncherThread();
+ if (mFreeConnectionIndices.isEmpty()) {
+ Log.d(TAG, "Ran out of services to allocate.");
+ return null;
+ }
+ int slot = mFreeConnectionIndices.remove(0);
+ assert mChildProcessConnections[slot] == null;
+ ComponentName serviceName = new ComponentName(mPackageName, mServiceClassName + slot);
+
+ // Wrap the service callbacks so that:
+ // - we can intercept onChildProcessDied and clean-up connections
+ // - the callbacks are actually posted so that this method will return before the callbacks
+ // are called (so that the caller may set any reference to the returned connection before
+ // any callback logic potentially tries to access that connection).
+ ChildProcessConnection.ServiceCallback serviceCallbackWrapper =
+ new ChildProcessConnection.ServiceCallback() {
+ @Override
+ public void onChildStarted() {
+ assert isRunningOnLauncherThread();
+ if (serviceCallback != null) {
+ mLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ serviceCallback.onChildStarted();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onChildStartFailed(final ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+ if (serviceCallback != null) {
+ mLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ serviceCallback.onChildStartFailed(connection);
+ }
+ });
+ }
+ freeConnectionWithDelay(connection);
+ }
+
+ @Override
+ public void onChildProcessDied(final ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+ if (serviceCallback != null) {
+ mLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ serviceCallback.onChildProcessDied(connection);
+ }
+ });
+ }
+ freeConnectionWithDelay(connection);
+ }
+
+ private void freeConnectionWithDelay(final ChildProcessConnection connection) {
+ // Freeing a service should be delayed. This is so that we avoid immediately
+ // reusing the freed service (see http://crbug.com/164069): the framework
+ // might keep a service process alive when it's been unbound for a short
+ // time. If a new connection to the same service is bound at that point, the
+ // process is reused and bad things happen (mostly static variables are set
+ // when we don't expect them to).
+ mLauncherHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ free(connection);
+ }
+ }, FREE_CONNECTION_DELAY_MILLIS);
+ }
+ };
+
+ ChildProcessConnection connection = mConnectionFactory.createConnection(
+ context, serviceName, mBindToCaller, mBindAsExternalService, serviceBundle);
+ mChildProcessConnections[slot] = connection;
+
+ connection.start(mUseStrongBinding, serviceCallbackWrapper);
+ Log.d(TAG, "Allocator allocated and bound a connection, name: %s, slot: %d",
+ mServiceClassName, slot);
+ return connection;
+ }
+
+ /** Frees a connection and notifies listeners. */
+ private void free(ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+
+ // mChildProcessConnections is relatively short (20 items at max at this point).
+ // We are better of iterating than caching in a map.
+ int slot = Arrays.asList(mChildProcessConnections).indexOf(connection);
+ if (slot == -1) {
+ Log.e(TAG, "Unable to find connection to free.");
+ assert false;
+ } else {
+ mChildProcessConnections[slot] = null;
+ assert !mFreeConnectionIndices.contains(slot);
+ mFreeConnectionIndices.add(slot);
+ Log.d(TAG, "Allocator freed a connection, name: %s, slot: %d", mServiceClassName, slot);
+ }
+
+ if (mPendingAllocations.isEmpty()) return;
+ mPendingAllocations.remove().run();
+ assert mFreeConnectionIndices.isEmpty();
+ if (!mPendingAllocations.isEmpty() && mFreeSlotCallback != null) mFreeSlotCallback.run();
+ }
+
+ // Can only be called once all slots are full, ie when allocate returns null.
+ // The callback will be called when a slot becomes free, and should synchronous call
+ // allocate to take the slot.
+ public void queueAllocation(Runnable runnable) {
+ assert mFreeConnectionIndices.isEmpty();
+ boolean wasEmpty = mPendingAllocations.isEmpty();
+ mPendingAllocations.add(runnable);
+ if (wasEmpty && mFreeSlotCallback != null) mFreeSlotCallback.run();
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public boolean anyConnectionAllocated() {
+ return mFreeConnectionIndices.size() < mChildProcessConnections.length;
+ }
+
+ public boolean isFreeConnectionAvailable() {
+ assert isRunningOnLauncherThread();
+ return !mFreeConnectionIndices.isEmpty();
+ }
+
+ public int getNumberOfServices() {
+ return mChildProcessConnections.length;
+ }
+
+ public boolean isConnectionFromAllocator(ChildProcessConnection connection) {
+ for (ChildProcessConnection existingConnection : mChildProcessConnections) {
+ if (existingConnection == connection) return true;
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ public void setConnectionFactoryForTesting(ConnectionFactory connectionFactory) {
+ mConnectionFactory = connectionFactory;
+ }
+
+ /** @return the count of connections managed by the allocator */
+ @VisibleForTesting
+ public int allocatedConnectionsCountForTesting() {
+ assert isRunningOnLauncherThread();
+ return mChildProcessConnections.length - mFreeConnectionIndices.size();
+ }
+
+ @VisibleForTesting
+ public ChildProcessConnection getChildProcessConnectionAtSlotForTesting(int slotNumber) {
+ return mChildProcessConnections[slotNumber];
+ }
+
+ private boolean isRunningOnLauncherThread() {
+ return mLauncherHandler.getLooper() == Looper.myLooper();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
new file mode 100644
index 0000000000..bfa5d5cc6a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -0,0 +1,766 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+
+import org.chromium.base.ChildBindingState;
+import org.chromium.base.Log;
+import org.chromium.base.MemoryPressureLevel;
+import org.chromium.base.MemoryPressureListener;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.TraceEvent;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.memory.MemoryPressureCallback;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Manages a connection between the browser activity and a child service.
+ */
+public class ChildProcessConnection {
+ private static final String TAG = "ChildProcessConn";
+ private static final int NUM_BINDING_STATES = ChildBindingState.MAX_VALUE + 1;
+
+ /**
+ * Used to notify the consumer about the process start. These callbacks will be invoked before
+ * the ConnectionCallbacks.
+ */
+ public interface ServiceCallback {
+ /**
+ * Called when the child process has successfully started and is ready for connection
+ * setup.
+ */
+ void onChildStarted();
+
+ /**
+ * Called when the child process failed to start. This can happen if the process is already
+ * in use by another client. The client will not receive any other callbacks after this one.
+ */
+ void onChildStartFailed(ChildProcessConnection connection);
+
+ /**
+ * Called when the service has been disconnected. whether it was stopped by the client or
+ * if it stopped unexpectedly (process crash).
+ * This is the last callback from this interface that a client will receive for a specific
+ * connection.
+ */
+ void onChildProcessDied(ChildProcessConnection connection);
+ }
+
+ /**
+ * Used to notify the consumer about the connection being established.
+ */
+ public interface ConnectionCallback {
+ /**
+ * Called when the connection to the service is established.
+ * @param connection the connection object to the child process
+ */
+ void onConnected(ChildProcessConnection connection);
+ }
+
+ /**
+ * Delegate that ChildServiceConnection should call when the service connects/disconnects.
+ * These callbacks are expected to happen on a background thread.
+ */
+ @VisibleForTesting
+ protected interface ChildServiceConnectionDelegate {
+ void onServiceConnected(IBinder service);
+ void onServiceDisconnected();
+ }
+
+ @VisibleForTesting
+ protected interface ChildServiceConnectionFactory {
+ ChildServiceConnection createConnection(
+ Intent bindIntent, int bindFlags, ChildServiceConnectionDelegate delegate);
+ }
+
+ /** Interface representing a connection to the Android service. Can be mocked in unit-tests. */
+ @VisibleForTesting
+ protected interface ChildServiceConnection {
+ boolean bind();
+ void unbind();
+ boolean isBound();
+ }
+
+ /** Implementation of ChildServiceConnection that does connect to a service. */
+ private static class ChildServiceConnectionImpl
+ implements ChildServiceConnection, ServiceConnection {
+ private final Context mContext;
+ private final Intent mBindIntent;
+ private final int mBindFlags;
+ private final ChildServiceConnectionDelegate mDelegate;
+ private boolean mBound;
+
+ private ChildServiceConnectionImpl(Context context, Intent bindIntent, int bindFlags,
+ ChildServiceConnectionDelegate delegate) {
+ mContext = context;
+ mBindIntent = bindIntent;
+ mBindFlags = bindFlags;
+ mDelegate = delegate;
+ }
+
+ @Override
+ public boolean bind() {
+ if (!mBound) {
+ try {
+ TraceEvent.begin("ChildProcessConnection.ChildServiceConnectionImpl.bind");
+ mBound = mContext.bindService(mBindIntent, this, mBindFlags);
+ } finally {
+ TraceEvent.end("ChildProcessConnection.ChildServiceConnectionImpl.bind");
+ }
+ }
+ return mBound;
+ }
+
+ @Override
+ public void unbind() {
+ if (mBound) {
+ mContext.unbindService(this);
+ mBound = false;
+ }
+ }
+
+ @Override
+ public boolean isBound() {
+ return mBound;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName className, final IBinder service) {
+ mDelegate.onServiceConnected(service);
+ }
+
+ // Called on the main thread to notify that the child service did not disconnect gracefully.
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ mDelegate.onServiceDisconnected();
+ }
+ }
+
+ // Synchronize on this for access.
+ @GuardedBy("sAllBindingStateCounts")
+ private static final int[] sAllBindingStateCounts = new int[NUM_BINDING_STATES];
+
+ @VisibleForTesting
+ static void resetBindingStateCountsForTesting() {
+ synchronized (sAllBindingStateCounts) {
+ for (int i = 0; i < NUM_BINDING_STATES; ++i) {
+ sAllBindingStateCounts[i] = 0;
+ }
+ }
+ }
+
+ private final Handler mLauncherHandler;
+ private final ComponentName mServiceName;
+
+ // Parameters passed to the child process through the service binding intent.
+ // If the service gets recreated by the framework the intent will be reused, so these parameters
+ // should be common to all processes of that type.
+ private final Bundle mServiceBundle;
+
+ // Whether bindToCaller should be called on the service after setup to check that only one
+ // process is bound to the service.
+ private final boolean mBindToCaller;
+
+ private static class ConnectionParams {
+ final Bundle mConnectionBundle;
+ final List<IBinder> mClientInterfaces;
+
+ ConnectionParams(Bundle connectionBundle, List<IBinder> clientInterfaces) {
+ mConnectionBundle = connectionBundle;
+ mClientInterfaces = clientInterfaces;
+ }
+ }
+
+ // This is set in start() and is used in onServiceConnected().
+ private ServiceCallback mServiceCallback;
+
+ // This is set in setupConnection() and is later used in doConnectionSetup(), after which the
+ // variable is cleared. Therefore this is only valid while the connection is being set up.
+ private ConnectionParams mConnectionParams;
+
+ // Callback provided in setupConnection() that will communicate the result to the caller. This
+ // has to be called exactly once after setupConnection(), even if setup fails, so that the
+ // caller can free up resources associated with the setup attempt. This is set to null after the
+ // call.
+ private ConnectionCallback mConnectionCallback;
+
+ private IChildProcessService mService;
+
+ // Set to true when the service connection callback runs. This differs from
+ // mServiceConnectComplete, which tracks that the connection completed successfully.
+ private boolean mDidOnServiceConnected;
+
+ // Set to true when the service connected successfully.
+ private boolean mServiceConnectComplete;
+
+ // Set to true when the service disconnects, as opposed to being properly closed. This happens
+ // when the process crashes or gets killed by the system out-of-memory killer.
+ private boolean mServiceDisconnected;
+
+ // Process ID of the corresponding child process.
+ private int mPid;
+
+ // Strong binding will make the service priority equal to the priority of the activity.
+ private final ChildServiceConnection mStrongBinding;
+
+ // Moderate binding will make the service priority equal to the priority of a visible process
+ // while the app is in the foreground.
+ // This is also used as the initial binding before any priorities are set.
+ private final ChildServiceConnection mModerateBinding;
+
+ // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
+ // to start() and stop().
+ private final ChildServiceConnection mWaivedBinding;
+
+ // Refcount of bindings.
+ private int mStrongBindingCount;
+ private int mModerateBindingCount;
+
+ // Set to true once unbind() was called.
+ private boolean mUnbound;
+
+ // Binding state of this connection.
+ private @ChildBindingState int mBindingState;
+
+ // Protects access to instance variables that are also accessed on the client thread.
+ private final Object mClientThreadLock = new Object();
+
+ // Same as above except it no longer updates after |unbind()|.
+ @GuardedBy("mClientThreadLock")
+ private @ChildBindingState int mBindingStateCurrentOrWhenDied;
+
+ // Indicate |kill()| was called to intentionally kill this process.
+ @GuardedBy("mClientThreadLock")
+ private boolean mKilledByUs;
+
+ // Copy of |sAllBindingStateCounts| at the time this is unbound.
+ @GuardedBy("mClientThreadLock")
+ private int[] mAllBindingStateCountsWhenDied;
+
+ private MemoryPressureCallback mMemoryPressureCallback;
+
+ public ChildProcessConnection(Context context, ComponentName serviceName, boolean bindToCaller,
+ boolean bindAsExternalService, Bundle serviceBundle) {
+ this(context, serviceName, bindToCaller, bindAsExternalService, serviceBundle,
+ null /* connectionFactory */);
+ }
+
+ @VisibleForTesting
+ public ChildProcessConnection(final Context context, ComponentName serviceName,
+ boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle,
+ ChildServiceConnectionFactory connectionFactory) {
+ mLauncherHandler = new Handler();
+ assert isRunningOnLauncherThread();
+ mServiceName = serviceName;
+ mServiceBundle = serviceBundle != null ? serviceBundle : new Bundle();
+ mServiceBundle.putBoolean(ChildProcessConstants.EXTRA_BIND_TO_CALLER, bindToCaller);
+ mBindToCaller = bindToCaller;
+
+ if (connectionFactory == null) {
+ connectionFactory = new ChildServiceConnectionFactory() {
+ @Override
+ public ChildServiceConnection createConnection(
+ Intent bindIntent, int bindFlags, ChildServiceConnectionDelegate delegate) {
+ return new ChildServiceConnectionImpl(context, bindIntent, bindFlags, delegate);
+ }
+ };
+ }
+
+ ChildServiceConnectionDelegate delegate = new ChildServiceConnectionDelegate() {
+ @Override
+ public void onServiceConnected(final IBinder service) {
+ mLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onServiceConnectedOnLauncherThread(service);
+ }
+ });
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ mLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onServiceDisconnectedOnLauncherThread();
+ }
+ });
+ }
+ };
+
+ Intent intent = new Intent();
+ intent.setComponent(serviceName);
+ if (serviceBundle != null) {
+ intent.putExtras(serviceBundle);
+ }
+
+ int defaultFlags = Context.BIND_AUTO_CREATE
+ | (bindAsExternalService ? Context.BIND_EXTERNAL_SERVICE : 0);
+
+ mModerateBinding = connectionFactory.createConnection(intent, defaultFlags, delegate);
+ mStrongBinding = connectionFactory.createConnection(
+ intent, defaultFlags | Context.BIND_IMPORTANT, delegate);
+ mWaivedBinding = connectionFactory.createConnection(
+ intent, defaultFlags | Context.BIND_WAIVE_PRIORITY, delegate);
+ }
+
+ public final IChildProcessService getService() {
+ assert isRunningOnLauncherThread();
+ return mService;
+ }
+
+ public final ComponentName getServiceName() {
+ assert isRunningOnLauncherThread();
+ return mServiceName;
+ }
+
+ public boolean isConnected() {
+ return mService != null;
+ }
+
+ /**
+ * @return the connection pid, or 0 if not yet connected
+ */
+ public int getPid() {
+ assert isRunningOnLauncherThread();
+ return mPid;
+ }
+
+ /**
+ * Starts a connection to an IChildProcessService. This must be followed by a call to
+ * setupConnection() to setup the connection parameters. start() and setupConnection() are
+ * separate to allow to pass whatever parameters are available in start(), and complete the
+ * remainder addStrongBinding while reducing the connection setup latency.
+ * @param useStrongBinding whether a strong binding should be bound by default. If false, an
+ * initial moderate binding is used.
+ * @param serviceCallback (optional) callbacks invoked when the child process starts or fails to
+ * start and when the service stops.
+ */
+ public void start(boolean useStrongBinding, ServiceCallback serviceCallback) {
+ try {
+ TraceEvent.begin("ChildProcessConnection.start");
+ assert isRunningOnLauncherThread();
+ assert mConnectionParams
+ == null : "setupConnection() called before start() in ChildProcessConnection.";
+
+ mServiceCallback = serviceCallback;
+
+ if (!bind(useStrongBinding)) {
+ Log.e(TAG, "Failed to establish the service connection.");
+ // We have to notify the caller so that they can free-up associated resources.
+ // TODO(ppi): Can we hard-fail here?
+ notifyChildProcessDied();
+ }
+ } finally {
+ TraceEvent.end("ChildProcessConnection.start");
+ }
+ }
+
+ /**
+ * Sets-up the connection after it was started with start().
+ * @param connectionBundle a bundle passed to the service that can be used to pass various
+ * parameters to the service
+ * @param clientInterfaces optional client specified interfaces that the child can use to
+ * communicate with the parent process
+ * @param connectionCallback will be called exactly once after the connection is set up or the
+ * setup fails
+ */
+ public void setupConnection(Bundle connectionBundle, @Nullable List<IBinder> clientInterfaces,
+ ConnectionCallback connectionCallback) {
+ assert isRunningOnLauncherThread();
+ assert mConnectionParams == null;
+ if (mServiceDisconnected) {
+ Log.w(TAG, "Tried to setup a connection that already disconnected.");
+ connectionCallback.onConnected(null);
+ return;
+ }
+ try {
+ TraceEvent.begin("ChildProcessConnection.setupConnection");
+ mConnectionCallback = connectionCallback;
+ mConnectionParams = new ConnectionParams(connectionBundle, clientInterfaces);
+ // Run the setup if the service is already connected. If not, doConnectionSetup() will
+ // be called from onServiceConnected().
+ if (mServiceConnectComplete) {
+ doConnectionSetup();
+ }
+ } finally {
+ TraceEvent.end("ChildProcessConnection.setupConnection");
+ }
+ }
+
+ /**
+ * Terminates the connection to IChildProcessService, closing all bindings. It is safe to call
+ * this multiple times.
+ */
+ public void stop() {
+ assert isRunningOnLauncherThread();
+ unbind();
+ notifyChildProcessDied();
+ }
+
+ public void kill() {
+ assert isRunningOnLauncherThread();
+ IChildProcessService service = mService;
+ unbind();
+ try {
+ if (service != null) service.forceKill();
+ } catch (RemoteException e) {
+ // Intentionally ignore since we are killing it anyway.
+ }
+ synchronized (mClientThreadLock) {
+ mKilledByUs = true;
+ }
+ notifyChildProcessDied();
+ }
+
+ private void onServiceConnectedOnLauncherThread(IBinder service) {
+ assert isRunningOnLauncherThread();
+ // A flag from the parent class ensures we run the post-connection logic only once
+ // (instead of once per each ChildServiceConnection).
+ if (mDidOnServiceConnected) {
+ return;
+ }
+ try {
+ TraceEvent.begin("ChildProcessConnection.ChildServiceConnection.onServiceConnected");
+ mDidOnServiceConnected = true;
+ mService = IChildProcessService.Stub.asInterface(service);
+
+ if (mBindToCaller) {
+ try {
+ if (!mService.bindToCaller()) {
+ if (mServiceCallback != null) {
+ mServiceCallback.onChildStartFailed(this);
+ }
+ unbind();
+ return;
+ }
+ } catch (RemoteException ex) {
+ // Do not trigger the StartCallback here, since the service is already
+ // dead and the onChildStopped callback will run from onServiceDisconnected().
+ Log.e(TAG, "Failed to bind service to connection.", ex);
+ return;
+ }
+ }
+
+ if (mServiceCallback != null) {
+ mServiceCallback.onChildStarted();
+ }
+
+ mServiceConnectComplete = true;
+
+ if (mMemoryPressureCallback == null) {
+ final MemoryPressureCallback callback = this ::onMemoryPressure;
+ ThreadUtils.postOnUiThread(() -> MemoryPressureListener.addCallback(callback));
+ mMemoryPressureCallback = callback;
+ }
+
+ // Run the setup if the connection parameters have already been provided. If
+ // not, doConnectionSetup() will be called from setupConnection().
+ if (mConnectionParams != null) {
+ doConnectionSetup();
+ }
+ } finally {
+ TraceEvent.end("ChildProcessConnection.ChildServiceConnection.onServiceConnected");
+ }
+ }
+
+ private void onServiceDisconnectedOnLauncherThread() {
+ assert isRunningOnLauncherThread();
+ // Ensure that the disconnection logic runs only once (instead of once per each
+ // ChildServiceConnection).
+ if (mServiceDisconnected) {
+ return;
+ }
+ mServiceDisconnected = true;
+ Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=%d", mPid);
+ stop(); // We don't want to auto-restart on crash. Let the browser do that.
+
+ // If we have a pending connection callback, we need to communicate the failure to
+ // the caller.
+ if (mConnectionCallback != null) {
+ mConnectionCallback.onConnected(null);
+ mConnectionCallback = null;
+ }
+ }
+
+ private void onSetupConnectionResult(int pid) {
+ mPid = pid;
+ assert mPid != 0 : "Child service claims to be run by a process of pid=0.";
+
+ if (mConnectionCallback != null) {
+ mConnectionCallback.onConnected(this);
+ }
+ mConnectionCallback = null;
+ }
+
+ /**
+ * Called after the connection parameters have been set (in setupConnection()) *and* a
+ * connection has been established (as signaled by onServiceConnected()). These two events can
+ * happen in any order.
+ */
+ private void doConnectionSetup() {
+ try {
+ TraceEvent.begin("ChildProcessConnection.doConnectionSetup");
+ assert mServiceConnectComplete && mService != null;
+ assert mConnectionParams != null;
+
+ ICallbackInt pidCallback = new ICallbackInt.Stub() {
+ @Override
+ public void call(final int pid) {
+ mLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onSetupConnectionResult(pid);
+ }
+ });
+ }
+ };
+ try {
+ mService.setupConnection(mConnectionParams.mConnectionBundle, pidCallback,
+ mConnectionParams.mClientInterfaces);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Failed to setup connection.", re);
+ }
+ mConnectionParams = null;
+ } finally {
+ TraceEvent.end("ChildProcessConnection.doConnectionSetup");
+ }
+ }
+
+ private boolean bind(boolean useStrongBinding) {
+ assert isRunningOnLauncherThread();
+ assert !mUnbound;
+
+ boolean success;
+ if (useStrongBinding) {
+ success = mStrongBinding.bind();
+ } else {
+ mModerateBindingCount++;
+ success = mModerateBinding.bind();
+ }
+ if (!success) return false;
+
+ mWaivedBinding.bind();
+ updateBindingState();
+ return true;
+ }
+
+ @VisibleForTesting
+ protected void unbind() {
+ assert isRunningOnLauncherThread();
+ mService = null;
+ mConnectionParams = null;
+ mUnbound = true;
+ mStrongBinding.unbind();
+ mWaivedBinding.unbind();
+ mModerateBinding.unbind();
+ updateBindingState();
+
+ int[] bindingStateCounts;
+ synchronized (sAllBindingStateCounts) {
+ bindingStateCounts = Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES);
+ }
+ synchronized (mClientThreadLock) {
+ mAllBindingStateCountsWhenDied = bindingStateCounts;
+ }
+
+ if (mMemoryPressureCallback != null) {
+ final MemoryPressureCallback callback = mMemoryPressureCallback;
+ ThreadUtils.postOnUiThread(() -> MemoryPressureListener.removeCallback(callback));
+ mMemoryPressureCallback = null;
+ }
+ }
+
+ public boolean isStrongBindingBound() {
+ assert isRunningOnLauncherThread();
+ return mStrongBinding.isBound();
+ }
+
+ public void addStrongBinding() {
+ assert isRunningOnLauncherThread();
+ if (!isConnected()) {
+ Log.w(TAG, "The connection is not bound for %d", getPid());
+ return;
+ }
+ if (mStrongBindingCount == 0) {
+ mStrongBinding.bind();
+ updateBindingState();
+ }
+ mStrongBindingCount++;
+ }
+
+ public void removeStrongBinding() {
+ assert isRunningOnLauncherThread();
+ if (!isConnected()) {
+ Log.w(TAG, "The connection is not bound for %d", getPid());
+ return;
+ }
+ assert mStrongBindingCount > 0;
+ mStrongBindingCount--;
+ if (mStrongBindingCount == 0) {
+ mStrongBinding.unbind();
+ updateBindingState();
+ }
+ }
+
+ public boolean isModerateBindingBound() {
+ assert isRunningOnLauncherThread();
+ return mModerateBinding.isBound();
+ }
+
+ public void addModerateBinding() {
+ assert isRunningOnLauncherThread();
+ if (!isConnected()) {
+ Log.w(TAG, "The connection is not bound for %d", getPid());
+ return;
+ }
+ if (mModerateBindingCount == 0) {
+ mModerateBinding.bind();
+ updateBindingState();
+ }
+ mModerateBindingCount++;
+ }
+
+ public void removeModerateBinding() {
+ assert isRunningOnLauncherThread();
+ if (!isConnected()) {
+ Log.w(TAG, "The connection is not bound for %d", getPid());
+ return;
+ }
+ assert mModerateBindingCount > 0;
+ mModerateBindingCount--;
+ if (mModerateBindingCount == 0) {
+ mModerateBinding.unbind();
+ updateBindingState();
+ }
+ }
+
+ /**
+ * @return true if the connection is bound and only bound with the waived binding or if the
+ * connection is unbound and was only bound with the waived binding when it disconnected.
+ */
+ public @ChildBindingState int bindingStateCurrentOrWhenDied() {
+ // WARNING: this method can be called from a thread other than the launcher thread.
+ // Note that it returns the current waived bound only state and is racy. This not really
+ // preventable without changing the caller's API, short of blocking.
+ synchronized (mClientThreadLock) {
+ return mBindingStateCurrentOrWhenDied;
+ }
+ }
+
+ /**
+ * @return true if the connection is intentionally killed by calling kill().
+ */
+ public boolean isKilledByUs() {
+ // WARNING: this method can be called from a thread other than the launcher thread.
+ // Note that it returns the current waived bound only state and is racy. This not really
+ // preventable without changing the caller's API, short of blocking.
+ synchronized (mClientThreadLock) {
+ return mKilledByUs;
+ }
+ }
+
+ public int[] bindingStateCountsCurrentOrWhenDied() {
+ // WARNING: this method can be called from a thread other than the launcher thread.
+ // Note that it returns the current waived bound only state and is racy. This not really
+ // preventable without changing the caller's API, short of blocking.
+ synchronized (mClientThreadLock) {
+ if (mAllBindingStateCountsWhenDied != null) {
+ return Arrays.copyOf(mAllBindingStateCountsWhenDied, NUM_BINDING_STATES);
+ }
+ }
+ synchronized (sAllBindingStateCounts) {
+ return Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES);
+ }
+ }
+
+ // Should be called any binding is bound or unbound.
+ private void updateBindingState() {
+ int oldBindingState = mBindingState;
+ if (mUnbound) {
+ mBindingState = ChildBindingState.UNBOUND;
+ } else if (mStrongBinding.isBound()) {
+ mBindingState = ChildBindingState.STRONG;
+ } else if (mModerateBinding.isBound()) {
+ mBindingState = ChildBindingState.MODERATE;
+ } else {
+ assert mWaivedBinding.isBound();
+ mBindingState = ChildBindingState.WAIVED;
+ }
+
+ if (mBindingState != oldBindingState) {
+ synchronized (sAllBindingStateCounts) {
+ if (oldBindingState != ChildBindingState.UNBOUND) {
+ assert sAllBindingStateCounts[oldBindingState] > 0;
+ sAllBindingStateCounts[oldBindingState]--;
+ }
+ if (mBindingState != ChildBindingState.UNBOUND) {
+ sAllBindingStateCounts[mBindingState]++;
+ }
+ }
+ }
+
+ if (!mUnbound) {
+ synchronized (mClientThreadLock) {
+ mBindingStateCurrentOrWhenDied = mBindingState;
+ }
+ }
+ }
+
+ private void notifyChildProcessDied() {
+ if (mServiceCallback != null) {
+ // Guard against nested calls to this method.
+ ServiceCallback serviceCallback = mServiceCallback;
+ mServiceCallback = null;
+ serviceCallback.onChildProcessDied(this);
+ }
+ }
+
+ private boolean isRunningOnLauncherThread() {
+ return mLauncherHandler.getLooper() == Looper.myLooper();
+ }
+
+ @VisibleForTesting
+ public void crashServiceForTesting() throws RemoteException {
+ mService.forceKill();
+ }
+
+ @VisibleForTesting
+ public boolean didOnServiceConnectedForTesting() {
+ return mDidOnServiceConnected;
+ }
+
+ @VisibleForTesting
+ protected Handler getLauncherHandler() {
+ return mLauncherHandler;
+ }
+
+ private void onMemoryPressure(@MemoryPressureLevel int pressure) {
+ mLauncherHandler.post(() -> onMemoryPressureOnLauncherThread(pressure));
+ }
+
+ private void onMemoryPressureOnLauncherThread(@MemoryPressureLevel int pressure) {
+ if (mService == null) return;
+ try {
+ mService.onMemoryPressure(pressure);
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java
new file mode 100644
index 0000000000..ec232d7c16
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+/**
+ * Constants to be used by child processes.
+ */
+public interface ChildProcessConstants {
+ // Below are the names for the items placed in the bind or start command intent.
+ // Note that because that intent maybe reused if a service is restarted, none should be process
+ // specific.
+
+ public static final String EXTRA_BIND_TO_CALLER =
+ "org.chromium.base.process_launcher.extra.bind_to_caller";
+
+ // Below are the names for the items placed in the Bundle passed in the
+ // IChildProcessService.setupConnection call, once the connection has been established.
+
+ // Key for the command line.
+ public static final String EXTRA_COMMAND_LINE =
+ "org.chromium.base.process_launcher.extra.command_line";
+
+ // Key for the file descriptors that should be mapped in the child process.
+ public static final String EXTRA_FILES = "org.chromium.base.process_launcher.extra.extraFiles";
+}
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java
new file mode 100644
index 0000000000..7cdc8528bd
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java
@@ -0,0 +1,278 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.TraceEvent;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This class is used to start a child process by connecting to a ChildProcessService.
+ */
+public class ChildProcessLauncher {
+ private static final String TAG = "ChildProcLauncher";
+
+ /** Delegate that client should use to customize the process launching. */
+ public abstract static class Delegate {
+ /**
+ * Called when the launcher is about to start. Gives the embedder a chance to provide an
+ * already bound connection if it has one. (allowing for warm-up connections: connections
+ * that are already bound in advance to speed up child process start-up time).
+ * Note that onBeforeConnectionAllocated will not be called if this method returns a
+ * connection.
+ * @param connectionAllocator the allocator the returned connection should have been
+ * allocated of.
+ * @param serviceCallback the service callback that the connection should use.
+ * @return a bound connection to use to connect to the child process service, or null if a
+ * connection should be allocated and bound by the launcher.
+ */
+ public ChildProcessConnection getBoundConnection(
+ ChildConnectionAllocator connectionAllocator,
+ ChildProcessConnection.ServiceCallback serviceCallback) {
+ return null;
+ }
+
+ /**
+ * Called before a connection is allocated.
+ * Note that this is only called if the ChildProcessLauncher is created with
+ * {@link #createWithConnectionAllocator}.
+ * @param serviceBundle the bundle passed in the service intent. Clients can add their own
+ * extras to the bundle.
+ */
+ public void onBeforeConnectionAllocated(Bundle serviceBundle) {}
+
+ /**
+ * Called before setup is called on the connection.
+ * @param connectionBundle the bundle passed to the {@link ChildProcessService} in the
+ * setup call. Clients can add their own extras to the bundle.
+ */
+ public void onBeforeConnectionSetup(Bundle connectionBundle) {}
+
+ /**
+ * Called when the connection was successfully established, meaning the setup call on the
+ * service was successful.
+ * @param connection the connection over which the setup call was made.
+ */
+ public void onConnectionEstablished(ChildProcessConnection connection) {}
+
+ /**
+ * Called when a connection has been disconnected. Only invoked if onConnectionEstablished
+ * was called, meaning the connection was already established.
+ * @param connection the connection that got disconnected.
+ */
+ public void onConnectionLost(ChildProcessConnection connection) {}
+ }
+
+ // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
+ private static final int NULL_PROCESS_HANDLE = 0;
+
+ // The handle for the thread we were created on and on which all methods should be called.
+ private final Handler mLauncherHandler;
+
+ private final Delegate mDelegate;
+
+ private final String[] mCommandLine;
+ private final FileDescriptorInfo[] mFilesToBeMapped;
+
+ // The allocator used to create the connection.
+ private final ChildConnectionAllocator mConnectionAllocator;
+
+ // The IBinder interfaces provided to the created service.
+ private final List<IBinder> mClientInterfaces;
+
+ // The actual service connection. Set once we have connected to the service.
+ private ChildProcessConnection mConnection;
+
+ /**
+ * Constructor.
+ *
+ * @param launcherHandler the handler for the thread where all operations should happen.
+ * @param delegate the delagate that gets notified of the launch progress.
+ * @param commandLine the command line that should be passed to the started process.
+ * @param filesToBeMapped the files that should be passed to the started process.
+ * @param connectionAllocator the allocator used to create connections to the service.
+ * @param clientInterfaces the interfaces that should be passed to the started process so it can
+ * communicate with the parent process.
+ */
+ public ChildProcessLauncher(Handler launcherHandler, Delegate delegate, String[] commandLine,
+ FileDescriptorInfo[] filesToBeMapped, ChildConnectionAllocator connectionAllocator,
+ List<IBinder> clientInterfaces) {
+ assert connectionAllocator != null;
+ mLauncherHandler = launcherHandler;
+ isRunningOnLauncherThread();
+ mCommandLine = commandLine;
+ mConnectionAllocator = connectionAllocator;
+ mDelegate = delegate;
+ mFilesToBeMapped = filesToBeMapped;
+ mClientInterfaces = clientInterfaces;
+ }
+
+ /**
+ * Starts the child process and calls setup on it if {@param setupConnection} is true.
+ * @param setupConnection whether the setup should be performed on the connection once
+ * established
+ * @param queueIfNoFreeConnection whether to queue that request if no service connection is
+ * available. If the launcher was created with a connection provider, this parameter has no
+ * effect.
+ * @return true if the connection was started or was queued.
+ */
+ public boolean start(final boolean setupConnection, final boolean queueIfNoFreeConnection) {
+ assert isRunningOnLauncherThread();
+ try {
+ TraceEvent.begin("ChildProcessLauncher.start");
+ ChildProcessConnection.ServiceCallback serviceCallback =
+ new ChildProcessConnection.ServiceCallback() {
+ @Override
+ public void onChildStarted() {}
+
+ @Override
+ public void onChildStartFailed(ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+ assert mConnection == connection;
+ Log.e(TAG, "ChildProcessConnection.start failed, trying again");
+ mLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // The child process may already be bound to another client
+ // (this can happen if multi-process WebView is used in more
+ // than one process), so try starting the process again.
+ // This connection that failed to start has not been freed,
+ // so a new bound connection will be allocated.
+ mConnection = null;
+ start(setupConnection, queueIfNoFreeConnection);
+ }
+ });
+ }
+
+ @Override
+ public void onChildProcessDied(ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+ assert mConnection == connection;
+ ChildProcessLauncher.this.onChildProcessDied();
+ }
+ };
+ mConnection = mDelegate.getBoundConnection(mConnectionAllocator, serviceCallback);
+ if (mConnection != null) {
+ assert mConnectionAllocator.isConnectionFromAllocator(mConnection);
+ setupConnection();
+ return true;
+ }
+ if (!allocateAndSetupConnection(
+ serviceCallback, setupConnection, queueIfNoFreeConnection)
+ && !queueIfNoFreeConnection) {
+ return false;
+ }
+ return true;
+ } finally {
+ TraceEvent.end("ChildProcessLauncher.start");
+ }
+ }
+
+ public ChildProcessConnection getConnection() {
+ return mConnection;
+ }
+
+ public ChildConnectionAllocator getConnectionAllocator() {
+ return mConnectionAllocator;
+ }
+
+ private boolean allocateAndSetupConnection(
+ final ChildProcessConnection.ServiceCallback serviceCallback,
+ final boolean setupConnection, final boolean queueIfNoFreeConnection) {
+ assert mConnection == null;
+ Bundle serviceBundle = new Bundle();
+ mDelegate.onBeforeConnectionAllocated(serviceBundle);
+
+ mConnection = mConnectionAllocator.allocate(
+ ContextUtils.getApplicationContext(), serviceBundle, serviceCallback);
+ if (mConnection == null) {
+ if (!queueIfNoFreeConnection) {
+ Log.d(TAG, "Failed to allocate a child connection (no queuing).");
+ return false;
+ }
+ mConnectionAllocator.queueAllocation(
+ () -> allocateAndSetupConnection(
+ serviceCallback, setupConnection, queueIfNoFreeConnection));
+ return false;
+ }
+
+ if (setupConnection) {
+ setupConnection();
+ }
+ return true;
+ }
+
+ private void setupConnection() {
+ ChildProcessConnection.ConnectionCallback connectionCallback =
+ new ChildProcessConnection.ConnectionCallback() {
+ @Override
+ public void onConnected(ChildProcessConnection connection) {
+ assert mConnection == connection;
+ onServiceConnected();
+ }
+ };
+ Bundle connectionBundle = createConnectionBundle();
+ mDelegate.onBeforeConnectionSetup(connectionBundle);
+ mConnection.setupConnection(connectionBundle, getClientInterfaces(), connectionCallback);
+ }
+
+ private void onServiceConnected() {
+ assert isRunningOnLauncherThread();
+
+ Log.d(TAG, "on connect callback, pid=%d", mConnection.getPid());
+
+ mDelegate.onConnectionEstablished(mConnection);
+
+ // Proactively close the FDs rather than waiting for the GC to do it.
+ try {
+ for (FileDescriptorInfo fileInfo : mFilesToBeMapped) {
+ fileInfo.fd.close();
+ }
+ } catch (IOException ioe) {
+ Log.w(TAG, "Failed to close FD.", ioe);
+ }
+ }
+
+ public int getPid() {
+ assert isRunningOnLauncherThread();
+ return mConnection == null ? NULL_PROCESS_HANDLE : mConnection.getPid();
+ }
+
+ public List<IBinder> getClientInterfaces() {
+ return mClientInterfaces;
+ }
+
+ private boolean isRunningOnLauncherThread() {
+ return mLauncherHandler.getLooper() == Looper.myLooper();
+ }
+
+ private Bundle createConnectionBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putStringArray(ChildProcessConstants.EXTRA_COMMAND_LINE, mCommandLine);
+ bundle.putParcelableArray(ChildProcessConstants.EXTRA_FILES, mFilesToBeMapped);
+ return bundle;
+ }
+
+ private void onChildProcessDied() {
+ assert isRunningOnLauncherThread();
+ if (getPid() != 0) {
+ mDelegate.onConnectionLost(mConnection);
+ }
+ }
+
+ public void stop() {
+ assert isRunningOnLauncherThread();
+ Log.d(TAG, "stopping child connection: pid=%d", mConnection.getPid());
+ mConnection.stop();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java
new file mode 100644
index 0000000000..876ebbb175
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java
@@ -0,0 +1,346 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import org.chromium.base.BaseSwitches;
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.MemoryPressureLevel;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.memory.MemoryPressureMonitor;
+
+import java.util.List;
+import java.util.concurrent.Semaphore;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * This is the base class for child services; the embedding application should contain
+ * ProcessService0, 1.. etc subclasses that provide the concrete service entry points, so it can
+ * connect to more than one distinct process (i.e. one process per service number, up to limit of
+ * N).
+ * The embedding application must declare these service instances in the application section
+ * of its AndroidManifest.xml, first with some meta-data describing the services:
+ * <meta-data android:name="org.chromium.test_app.SERVICES_NAME"
+ * android:value="org.chromium.test_app.ProcessService"/>
+ * and then N entries of the form:
+ * <service android:name="org.chromium.test_app.ProcessServiceX"
+ * android:process=":processX" />
+ *
+ * Subclasses must also provide a delegate in this class constructor. That delegate is responsible
+ * for loading native libraries and running the main entry point of the service.
+ */
+@JNINamespace("base::android")
+@MainDex
+public abstract class ChildProcessService extends Service {
+ private static final String MAIN_THREAD_NAME = "ChildProcessMain";
+ private static final String TAG = "ChildProcessService";
+
+ // Only for a check that create is only called once.
+ private static boolean sCreateCalled;
+
+ private final ChildProcessServiceDelegate mDelegate;
+
+ private final Object mBinderLock = new Object();
+ private final Object mLibraryInitializedLock = new Object();
+
+ // True if we should enforce that bindToCaller() is called before setupConnection().
+ // Only set once in bind(), does not require synchronization.
+ private boolean mBindToCallerCheck;
+
+ // PID of the client of this service, set in bindToCaller(), if mBindToCallerCheck is true.
+ @GuardedBy("mBinderLock")
+ private int mBoundCallingPid;
+
+ // This is the native "Main" thread for the renderer / utility process.
+ private Thread mMainThread;
+
+ // Parameters received via IPC, only accessed while holding the mMainThread monitor.
+ private String[] mCommandLineParams;
+
+ // File descriptors that should be registered natively.
+ private FileDescriptorInfo[] mFdInfos;
+
+ @GuardedBy("mLibraryInitializedLock")
+ private boolean mLibraryInitialized;
+
+ // Called once the service is bound and all service related member variables have been set.
+ // Only set once in bind(), does not require synchronization.
+ private boolean mServiceBound;
+
+ private final Semaphore mActivitySemaphore = new Semaphore(1);
+
+ public ChildProcessService(ChildProcessServiceDelegate delegate) {
+ mDelegate = delegate;
+ }
+
+ // Binder object used by clients for this service.
+ private final IChildProcessService.Stub mBinder = new IChildProcessService.Stub() {
+ // NOTE: Implement any IChildProcessService methods here.
+ @Override
+ public boolean bindToCaller() {
+ assert mBindToCallerCheck;
+ assert mServiceBound;
+ synchronized (mBinderLock) {
+ int callingPid = Binder.getCallingPid();
+ if (mBoundCallingPid == 0) {
+ mBoundCallingPid = callingPid;
+ } else if (mBoundCallingPid != callingPid) {
+ Log.e(TAG, "Service is already bound by pid %d, cannot bind for pid %d",
+ mBoundCallingPid, callingPid);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void setupConnection(Bundle args, ICallbackInt pidCallback, List<IBinder> callbacks)
+ throws RemoteException {
+ assert mServiceBound;
+ synchronized (mBinderLock) {
+ if (mBindToCallerCheck && mBoundCallingPid == 0) {
+ Log.e(TAG, "Service has not been bound with bindToCaller()");
+ pidCallback.call(-1);
+ return;
+ }
+ }
+
+ pidCallback.call(Process.myPid());
+ processConnectionBundle(args, callbacks);
+ }
+
+ @Override
+ public void forceKill() {
+ assert mServiceBound;
+ Process.killProcess(Process.myPid());
+ }
+
+ @Override
+ public void onMemoryPressure(@MemoryPressureLevel int pressure) {
+ // This method is called by the host process when the host process reports pressure
+ // to its native side. The key difference between the host process and its services is
+ // that the host process polls memory pressure when it gets CRITICAL, and periodically
+ // invokes pressure listeners until pressure subsides. (See MemoryPressureMonitor for
+ // more info.)
+ //
+ // Services don't poll, so this side-channel is used to notify services about memory
+ // pressure from the host process's POV.
+ //
+ // However, since both host process and services listen to ComponentCallbacks2, we
+ // can't be sure that the host process won't get better signals than their services.
+ // I.e. we need to watch out for a situation where a service gets CRITICAL, but the
+ // host process gets MODERATE - in this case we need to ignore MODERATE.
+ //
+ // So we're ignoring pressure from the host process if it's better than the last
+ // reported pressure. I.e. the host process can drive pressure up, but it'll go
+ // down only when we the service get a signal through ComponentCallbacks2.
+ ThreadUtils.postOnUiThread(() -> {
+ if (pressure >= MemoryPressureMonitor.INSTANCE.getLastReportedPressure()) {
+ MemoryPressureMonitor.INSTANCE.notifyPressure(pressure);
+ }
+ });
+ }
+ };
+
+ /**
+ * Loads Chrome's native libraries and initializes a ChildProcessService.
+ */
+ // For sCreateCalled check.
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i(TAG, "Creating new ChildProcessService pid=%d", Process.myPid());
+ if (sCreateCalled) {
+ throw new RuntimeException("Illegal child process reuse.");
+ }
+ sCreateCalled = true;
+
+ // Initialize the context for the application that owns this ChildProcessService object.
+ ContextUtils.initApplicationContext(getApplicationContext());
+
+ mDelegate.onServiceCreated();
+
+ mMainThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // CommandLine must be initialized before everything else.
+ synchronized (mMainThread) {
+ while (mCommandLineParams == null) {
+ mMainThread.wait();
+ }
+ }
+ assert mServiceBound;
+ CommandLine.init(mCommandLineParams);
+
+ if (CommandLine.getInstance().hasSwitch(
+ BaseSwitches.RENDERER_WAIT_FOR_JAVA_DEBUGGER)) {
+ android.os.Debug.waitForDebugger();
+ }
+
+ boolean nativeLibraryLoaded = false;
+ try {
+ nativeLibraryLoaded = mDelegate.loadNativeLibrary(getApplicationContext());
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to load native library.", e);
+ }
+ if (!nativeLibraryLoaded) {
+ System.exit(-1);
+ }
+
+ synchronized (mLibraryInitializedLock) {
+ mLibraryInitialized = true;
+ mLibraryInitializedLock.notifyAll();
+ }
+ synchronized (mMainThread) {
+ mMainThread.notifyAll();
+ while (mFdInfos == null) {
+ mMainThread.wait();
+ }
+ }
+
+ SparseArray<String> idsToKeys = mDelegate.getFileDescriptorsIdsToKeys();
+
+ int[] fileIds = new int[mFdInfos.length];
+ String[] keys = new String[mFdInfos.length];
+ int[] fds = new int[mFdInfos.length];
+ long[] regionOffsets = new long[mFdInfos.length];
+ long[] regionSizes = new long[mFdInfos.length];
+ for (int i = 0; i < mFdInfos.length; i++) {
+ FileDescriptorInfo fdInfo = mFdInfos[i];
+ String key = idsToKeys != null ? idsToKeys.get(fdInfo.id) : null;
+ if (key != null) {
+ keys[i] = key;
+ } else {
+ fileIds[i] = fdInfo.id;
+ }
+ fds[i] = fdInfo.fd.detachFd();
+ regionOffsets[i] = fdInfo.offset;
+ regionSizes[i] = fdInfo.size;
+ }
+ nativeRegisterFileDescriptors(keys, fileIds, fds, regionOffsets, regionSizes);
+
+ mDelegate.onBeforeMain();
+ if (mActivitySemaphore.tryAcquire()) {
+ mDelegate.runMain();
+ nativeExitChildProcess();
+ }
+ } catch (InterruptedException e) {
+ Log.w(TAG, "%s startup failed: %s", MAIN_THREAD_NAME, e);
+ }
+ }
+ }, MAIN_THREAD_NAME);
+ mMainThread.start();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.i(TAG, "Destroying ChildProcessService pid=%d", Process.myPid());
+ if (mActivitySemaphore.tryAcquire()) {
+ // TODO(crbug.com/457406): This is a bit hacky, but there is no known better solution
+ // as this service will get reused (at least if not sandboxed).
+ // In fact, we might really want to always exit() from onDestroy(), not just from
+ // the early return here.
+ System.exit(0);
+ return;
+ }
+ synchronized (mLibraryInitializedLock) {
+ try {
+ while (!mLibraryInitialized) {
+ // Avoid a potential race in calling through to native code before the library
+ // has loaded.
+ mLibraryInitializedLock.wait();
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ mDelegate.onDestroy();
+ }
+
+ /*
+ * Returns the communication channel to the service. Note that even if multiple clients were to
+ * connect, we should only get one call to this method. So there is no need to synchronize
+ * member variables that are only set in this method and accessed from binder methods, as binder
+ * methods can't be called until this method returns.
+ * @param intent The intent that was used to bind to the service.
+ * @return the binder used by the client to setup the connection.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ assert !mServiceBound;
+
+ // We call stopSelf() to request that this service be stopped as soon as the client unbinds.
+ // Otherwise the system may keep it around and available for a reconnect. The child
+ // processes do not currently support reconnect; they must be initialized from scratch every
+ // time.
+ stopSelf();
+
+ mBindToCallerCheck =
+ intent.getBooleanExtra(ChildProcessConstants.EXTRA_BIND_TO_CALLER, false);
+ mServiceBound = true;
+ mDelegate.onServiceBound(intent);
+ // Don't block bind() with any extra work, post it to the application thread instead.
+ new Handler(Looper.getMainLooper())
+ .post(() -> mDelegate.preloadNativeLibrary(getApplicationContext()));
+ return mBinder;
+ }
+
+ private void processConnectionBundle(Bundle bundle, List<IBinder> clientInterfaces) {
+ // Required to unparcel FileDescriptorInfo.
+ ClassLoader classLoader = getApplicationContext().getClassLoader();
+ bundle.setClassLoader(classLoader);
+ synchronized (mMainThread) {
+ if (mCommandLineParams == null) {
+ mCommandLineParams =
+ bundle.getStringArray(ChildProcessConstants.EXTRA_COMMAND_LINE);
+ mMainThread.notifyAll();
+ }
+ // We must have received the command line by now
+ assert mCommandLineParams != null;
+ Parcelable[] fdInfosAsParcelable =
+ bundle.getParcelableArray(ChildProcessConstants.EXTRA_FILES);
+ if (fdInfosAsParcelable != null) {
+ // For why this arraycopy is necessary:
+ // http://stackoverflow.com/questions/8745893/i-dont-get-why-this-classcastexception-occurs
+ mFdInfos = new FileDescriptorInfo[fdInfosAsParcelable.length];
+ System.arraycopy(fdInfosAsParcelable, 0, mFdInfos, 0, fdInfosAsParcelable.length);
+ }
+ mDelegate.onConnectionSetup(bundle, clientInterfaces);
+ mMainThread.notifyAll();
+ }
+ }
+
+ /**
+ * Helper for registering FileDescriptorInfo objects with GlobalFileDescriptors or
+ * FileDescriptorStore.
+ * This includes the IPC channel, the crash dump signals and resource related
+ * files.
+ */
+ private static native void nativeRegisterFileDescriptors(
+ String[] keys, int[] id, int[] fd, long[] offset, long[] size);
+
+ /**
+ * Force the child process to exit.
+ */
+ private static native void nativeExitChildProcess();
+}
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java
new file mode 100644
index 0000000000..7beffeffa2
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java
@@ -0,0 +1,76 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.SparseArray;
+
+import java.util.List;
+
+/**
+ * The interface that embedders should implement to specialize child service creation.
+ */
+public interface ChildProcessServiceDelegate {
+ /** Invoked when the service was created. This is the first method invoked on the delegate. */
+ void onServiceCreated();
+
+ /**
+ * Called when the service is bound. Invoked on a background thread.
+ * @param intent the intent that started the service.
+ */
+ void onServiceBound(Intent intent);
+
+ /**
+ * Called once the connection has been setup. Invoked on a background thread.
+ * @param connectionBundle the bundle pass to the setupConnection call
+ * @param clientInterfaces the IBinders interfaces provided by the client
+ */
+ void onConnectionSetup(Bundle connectionBundle, List<IBinder> clientInterfaces);
+
+ /**
+ * Called when the service gets destroyed.
+ * Note that the system might kill the process hosting the service without this method being
+ * called.
+ */
+ void onDestroy();
+
+ /**
+ * Called when the delegate should load the native library.
+ * @param hostContext The host context the library should be loaded with (i.e. Chrome).
+ * @return true if the library was loaded successfully, false otherwise in which case the
+ * service stops.
+ */
+ boolean loadNativeLibrary(Context hostContext);
+
+ /**
+ * Called when the delegate should preload the native library.
+ * Preloading is automatically done during library loading, but can also be called explicitly
+ * to speed up the loading. See {@link LibraryLoader.preloadNow}.
+ * @param hostContext The host context the library should be preloaded with (i.e. Chrome).
+ */
+ void preloadNativeLibrary(Context hostContext);
+
+ /**
+ * Should return a map that associatesfile descriptors' IDs to keys.
+ * This is needed as at the moment we use 2 different stores for the FDs in native code:
+ * base::FileDescriptorStore which associates FDs with string identifiers (the key), and
+ * base::GlobalDescriptors which associates FDs with int ids.
+ * FDs for which the returned map contains a mapping are added to base::FileDescriptorStore with
+ * the associated key, all others are added to base::GlobalDescriptors.
+ */
+ SparseArray<String> getFileDescriptorsIdsToKeys();
+
+ /** Called before the main method is invoked. */
+ void onBeforeMain();
+
+ /**
+ * The main entry point for the service. This method should block as long as the service should
+ * be running.
+ */
+ void runMain();
+}
diff --git a/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl b/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl
new file mode 100644
index 0000000000..e37d8c748d
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.aidl
@@ -0,0 +1,7 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+parcelable FileDescriptorInfo;
diff --git a/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java b/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java
new file mode 100644
index 0000000000..3dc366389a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java
@@ -0,0 +1,68 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.annotations.UsedByReflection;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Parcelable class that contains file descriptor and file region information to
+ * be passed to child processes.
+ */
+@Immutable
+@MainDex
+@UsedByReflection("child_process_launcher_helper_android.cc")
+public final class FileDescriptorInfo implements Parcelable {
+ public final int id;
+ public final ParcelFileDescriptor fd;
+ public final long offset;
+ public final long size;
+
+ public FileDescriptorInfo(int id, ParcelFileDescriptor fd, long offset, long size) {
+ this.id = id;
+ this.fd = fd;
+ this.offset = offset;
+ this.size = size;
+ }
+
+ FileDescriptorInfo(Parcel in) {
+ id = in.readInt();
+ fd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+ offset = in.readLong();
+ size = in.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ dest.writeParcelable(fd, CONTENTS_FILE_DESCRIPTOR);
+ dest.writeLong(offset);
+ dest.writeLong(size);
+ }
+
+ public static final Parcelable.Creator<FileDescriptorInfo> CREATOR =
+ new Parcelable.Creator<FileDescriptorInfo>() {
+ @Override
+ public FileDescriptorInfo createFromParcel(Parcel in) {
+ return new FileDescriptorInfo(in);
+ }
+
+ @Override
+ public FileDescriptorInfo[] newArray(int size) {
+ return new FileDescriptorInfo[size];
+ }
+ };
+}
diff --git a/mojo/public/mojom/base/logfont_win.mojom b/base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl
index 27922b8478..db93cb0dbd 100644
--- a/mojo/public/mojom/base/logfont_win.mojom
+++ b/base/android/java/src/org/chromium/base/process_launcher/ICallbackInt.aidl
@@ -2,10 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-module mojo_base.mojom;
+package org.chromium.base.process_launcher;
-// Native Windows struct. Its typemap only exists for windows builds.
-[EnableIf=is_win]
-struct LOGFONT {
- array<uint8> bytes;
-};
+oneway interface ICallbackInt {
+ void call(int value);
+}
diff --git a/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl b/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl
new file mode 100644
index 0000000000..298e0bf49a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl
@@ -0,0 +1,26 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import android.os.Bundle;
+
+import org.chromium.base.process_launcher.ICallbackInt;
+
+interface IChildProcessService {
+ // On the first call to this method, the service will record the calling PID
+ // and return true. Subsequent calls will only return true if the calling PID
+ // is the same as the recorded one.
+ boolean bindToCaller();
+
+ // Sets up the initial IPC channel.
+ oneway void setupConnection(in Bundle args, ICallbackInt pidCallback,
+ in List<IBinder> clientInterfaces);
+
+ // Forcefully kills the child process.
+ oneway void forceKill();
+
+ // Notifies about memory pressure. The argument is MemoryPressureLevel enum.
+ oneway void onMemoryPressure(int pressure);
+}
diff --git a/base/android/java/templates/BuildConfig.template b/base/android/java/templates/BuildConfig.template
new file mode 100644
index 0000000000..1006d12ee1
--- /dev/null
+++ b/base/android/java/templates/BuildConfig.template
@@ -0,0 +1,70 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+#define Q(x) #x
+#define QUOTE(x) Q(x)
+
+#if defined(USE_FINAL)
+#define MAYBE_FINAL final
+#else
+#define MAYBE_FINAL
+#endif
+
+/**
+ * Build configuration. Generated on a per-target basis.
+ */
+public class BuildConfig {
+
+#if defined(ENABLE_MULTIDEX)
+ public static MAYBE_FINAL boolean IS_MULTIDEX_ENABLED = true;
+#else
+ public static MAYBE_FINAL boolean IS_MULTIDEX_ENABLED = false;
+#endif
+
+#if defined(_FIREBASE_APP_ID)
+ public static MAYBE_FINAL String FIREBASE_APP_ID = QUOTE(_FIREBASE_APP_ID);
+#else
+ public static MAYBE_FINAL String FIREBASE_APP_ID = "";
+#endif
+
+#if defined(_DCHECK_IS_ON)
+ public static MAYBE_FINAL boolean DCHECK_IS_ON = true;
+#else
+ public static MAYBE_FINAL boolean DCHECK_IS_ON = false;
+#endif
+
+#if defined(_IS_UBSAN)
+ public static MAYBE_FINAL boolean IS_UBSAN = true;
+#else
+ public static MAYBE_FINAL boolean IS_UBSAN = false;
+#endif
+
+ // Sorted list of locales that have a compressed .pak within assets.
+ // Stored as an array because AssetManager.list() is slow.
+#if defined(COMPRESSED_LOCALE_LIST)
+ public static MAYBE_FINAL String[] COMPRESSED_LOCALES = COMPRESSED_LOCALE_LIST;
+#else
+ public static MAYBE_FINAL String[] COMPRESSED_LOCALES = {};
+#endif
+
+ // Sorted list of locales that have an uncompressed .pak within assets.
+ // Stored as an array because AssetManager.list() is slow.
+#if defined(UNCOMPRESSED_LOCALE_LIST)
+ public static MAYBE_FINAL String[] UNCOMPRESSED_LOCALES = UNCOMPRESSED_LOCALE_LIST;
+#else
+ public static MAYBE_FINAL String[] UNCOMPRESSED_LOCALES = {};
+#endif
+
+ // The ID of the android string resource that stores the product version.
+ // This layer of indirection is necessary to make the resource dependency
+ // optional for android_apk targets/base_java (ex. for cronet).
+#if defined(_RESOURCES_VERSION_VARIABLE)
+ public static MAYBE_FINAL int R_STRING_PRODUCT_VERSION = _RESOURCES_VERSION_VARIABLE;
+#else
+ // Default value, do not use.
+ public static MAYBE_FINAL int R_STRING_PRODUCT_VERSION = 0;
+#endif
+}
diff --git a/base/android/java/templates/NativeLibraries.template b/base/android/java/templates/NativeLibraries.template
new file mode 100644
index 0000000000..68277df753
--- /dev/null
+++ b/base/android/java/templates/NativeLibraries.template
@@ -0,0 +1,109 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.library_loader;
+
+public class NativeLibraries {
+ /**
+ * IMPORTANT NOTE: The variables defined here must _not_ be 'final'.
+ *
+ * The reason for this is very subtle:
+ *
+ * - This template is used to generate several distinct, but similar
+ * files used in different contexts:
+ *
+ * o .../gen/templates/org/chromium/base/library_loader/NativeLibraries.java
+ *
+ * This file is used to build base.jar, which is the library
+ * jar used by chromium projects. However, the
+ * corresponding NativeLibraries.class file will _not_ be part
+ * of the final base.jar.
+ *
+ * o .../$PROJECT/native_libraries_java/NativeLibraries.java
+ *
+ * This file is used to build an APK (e.g. $PROJECT
+ * could be 'content_shell_apk'). Its content will depend on
+ * this target's specific build configuration, and differ from
+ * the source file above.
+ *
+ * - During the final link, all .jar files are linked together into
+ * a single .dex file, and the second version of NativeLibraries.class
+ * will be put into the final output file, and used at runtime.
+ *
+ * - If the variables were defined as 'final', their value would be
+ * optimized out inside of 'base.jar', and could not be specialized
+ * for every chromium program. This, however, doesn't apply to arrays of
+ * strings, which can be defined as final.
+ *
+ * This exotic scheme is used to avoid injecting project-specific, or
+ * even build-specific, values into the base layer. E.g. this is
+ * how the component build is supported on Android without modifying
+ * the sources of each and every Chromium-based target.
+ */
+
+ public static final int CPU_FAMILY_UNKNOWN = 0;
+ public static final int CPU_FAMILY_ARM = 1;
+ public static final int CPU_FAMILY_MIPS = 2;
+ public static final int CPU_FAMILY_X86 = 3;
+
+#if defined(ENABLE_CHROMIUM_LINKER_LIBRARY_IN_ZIP_FILE) && \
+ !defined(ENABLE_CHROMIUM_LINKER)
+#error "Must have ENABLE_CHROMIUM_LINKER to enable library in zip file"
+#endif
+
+ // Set to true to enable the use of the Chromium Linker.
+#if defined(ENABLE_CHROMIUM_LINKER)
+ public static boolean sUseLinker = true;
+#else
+ public static boolean sUseLinker = false;
+#endif
+
+#if defined(ENABLE_CHROMIUM_LINKER_LIBRARY_IN_ZIP_FILE)
+ public static boolean sUseLibraryInZipFile = true;
+#else
+ public static boolean sUseLibraryInZipFile = false;
+#endif
+
+#if defined(ENABLE_CHROMIUM_LINKER_TESTS)
+ public static boolean sEnableLinkerTests = true;
+#else
+ public static boolean sEnableLinkerTests = false;
+#endif
+
+ // This is the list of native libraries to be loaded (in the correct order)
+ // by LibraryLoader.java. The base java library is compiled with no
+ // array defined, and then the build system creates a version of the file
+ // with the real list of libraries required (which changes based upon which
+ // .apk is being built).
+ // TODO(cjhopman): This is public since it is referenced by NativeTestActivity.java
+ // directly. The two ways of library loading should be refactored into one.
+ public static final String[] LIBRARIES =
+#if defined(NATIVE_LIBRARIES_LIST)
+ NATIVE_LIBRARIES_LIST;
+#else
+ {};
+#endif
+
+ // This is the expected version of the 'main' native library, which is the one that
+ // implements the initial set of base JNI functions including
+ // base::android::nativeGetVersionName()
+ static String sVersionNumber =
+#if defined(NATIVE_LIBRARIES_VERSION_NUMBER)
+ NATIVE_LIBRARIES_VERSION_NUMBER;
+#else
+ "";
+#endif
+
+ public static int sCpuFamily =
+#if defined(ANDROID_APP_CPU_FAMILY_ARM)
+ CPU_FAMILY_ARM;
+#elif defined(ANDROID_APP_CPU_FAMILY_X86)
+ CPU_FAMILY_X86;
+#elif defined(ANDROID_APP_CPU_FAMILY_MIPS)
+ CPU_FAMILY_MIPS;
+#else
+ CPU_FAMILY_UNKNOWN;
+#endif
+
+}
diff --git a/base/android/java_exception_reporter.cc b/base/android/java_exception_reporter.cc
index 96eb38e581..be3e7039ac 100644
--- a/base/android/java_exception_reporter.cc
+++ b/base/android/java_exception_reporter.cc
@@ -38,8 +38,11 @@ void SetJavaExceptionCallback(void (*callback)(const char*)) {
}
void SetJavaException(const char* exception) {
- DCHECK(g_java_exception_callback);
- g_java_exception_callback(exception);
+ // No need to print exception because they are already logged via
+ // env->ExceptionDescribe() within jni_android.cc.
+ if (g_java_exception_callback) {
+ g_java_exception_callback(exception);
+ }
}
void JNI_JavaExceptionReporter_ReportJavaException(
diff --git a/base/android/java_handler_thread.cc b/base/android/java_handler_thread.cc
new file mode 100644
index 0000000000..ea87ba76d9
--- /dev/null
+++ b/base/android/java_handler_thread.cc
@@ -0,0 +1,132 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/java_handler_thread.h"
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread_internal_posix.h"
+#include "base/threading/thread_restrictions.h"
+#include "jni/JavaHandlerThread_jni.h"
+
+using base::android::AttachCurrentThread;
+
+namespace base {
+
+namespace android {
+
+JavaHandlerThread::JavaHandlerThread(const char* name,
+ base::ThreadPriority priority)
+ : JavaHandlerThread(Java_JavaHandlerThread_create(
+ AttachCurrentThread(),
+ ConvertUTF8ToJavaString(AttachCurrentThread(), name),
+ base::internal::ThreadPriorityToNiceValue(priority))) {}
+
+JavaHandlerThread::JavaHandlerThread(
+ const base::android::ScopedJavaLocalRef<jobject>& obj)
+ : java_thread_(obj) {}
+
+JavaHandlerThread::~JavaHandlerThread() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ DCHECK(!Java_JavaHandlerThread_isAlive(env, java_thread_));
+ DCHECK(!message_loop_ || message_loop_->IsAborted());
+ // TODO(mthiesse): We shouldn't leak the MessageLoop as this could affect
+ // future tests.
+ if (message_loop_ && message_loop_->IsAborted()) {
+ // When the message loop has been aborted due to a crash, we intentionally
+ // leak the message loop because the message loop hasn't been shut down
+ // properly and would trigger DCHECKS. This should only happen in tests,
+ // where we handle the exception instead of letting it take down the
+ // process.
+ message_loop_.release();
+ }
+}
+
+void JavaHandlerThread::Start() {
+ // Check the thread has not already been started.
+ DCHECK(!message_loop_);
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::WaitableEvent initialize_event(
+ WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ Java_JavaHandlerThread_startAndInitialize(
+ env, java_thread_, reinterpret_cast<intptr_t>(this),
+ reinterpret_cast<intptr_t>(&initialize_event));
+ // Wait for thread to be initialized so it is ready to be used when Start
+ // returns.
+ base::ThreadRestrictions::ScopedAllowWait wait_allowed;
+ initialize_event.Wait();
+}
+
+void JavaHandlerThread::Stop() {
+ DCHECK(!task_runner()->BelongsToCurrentThread());
+ task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&JavaHandlerThread::StopOnThread, base::Unretained(this)));
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_JavaHandlerThread_joinThread(env, java_thread_);
+}
+
+void JavaHandlerThread::InitializeThread(JNIEnv* env,
+ const JavaParamRef<jobject>& obj,
+ jlong event) {
+ // TYPE_JAVA to get the Android java style message loop.
+ message_loop_ =
+ std::make_unique<MessageLoopForUI>(base::MessageLoop::TYPE_JAVA);
+ Init();
+ reinterpret_cast<base::WaitableEvent*>(event)->Signal();
+}
+
+void JavaHandlerThread::OnLooperStopped(JNIEnv* env,
+ const JavaParamRef<jobject>& obj) {
+ DCHECK(task_runner()->BelongsToCurrentThread());
+ message_loop_.reset();
+ CleanUp();
+}
+
+void JavaHandlerThread::StopMessageLoopForTesting() {
+ DCHECK(task_runner()->BelongsToCurrentThread());
+ StopOnThread();
+}
+
+void JavaHandlerThread::JoinForTesting() {
+ DCHECK(!task_runner()->BelongsToCurrentThread());
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_JavaHandlerThread_joinThread(env, java_thread_);
+}
+
+void JavaHandlerThread::ListenForUncaughtExceptionsForTesting() {
+ DCHECK(!task_runner()->BelongsToCurrentThread());
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_JavaHandlerThread_listenForUncaughtExceptionsForTesting(env,
+ java_thread_);
+}
+
+ScopedJavaLocalRef<jthrowable> JavaHandlerThread::GetUncaughtExceptionIfAny() {
+ DCHECK(!task_runner()->BelongsToCurrentThread());
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return Java_JavaHandlerThread_getUncaughtExceptionIfAny(env, java_thread_);
+}
+
+void JavaHandlerThread::StopOnThread() {
+ DCHECK(task_runner()->BelongsToCurrentThread());
+ message_loop_->QuitWhenIdle(base::BindOnce(
+ &JavaHandlerThread::QuitThreadSafely, base::Unretained(this)));
+}
+
+void JavaHandlerThread::QuitThreadSafely() {
+ DCHECK(task_runner()->BelongsToCurrentThread());
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_JavaHandlerThread_quitThreadSafely(env, java_thread_,
+ reinterpret_cast<intptr_t>(this));
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/java_handler_thread.h b/base/android/java_handler_thread.h
new file mode 100644
index 0000000000..d65dfe2987
--- /dev/null
+++ b/base/android/java_handler_thread.h
@@ -0,0 +1,95 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_JAVA_HANDLER_THREAD_H_
+#define BASE_ANDROID_JAVA_HANDLER_THREAD_H_
+
+#include <jni.h>
+
+#include <memory>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+
+namespace base {
+
+class MessageLoop;
+
+namespace android {
+
+// A Java Thread with a native message loop. To run tasks, post them
+// to the message loop and they will be scheduled along with Java tasks
+// on the thread.
+// This is useful for callbacks where the receiver expects a thread
+// with a prepared Looper.
+class BASE_EXPORT JavaHandlerThread {
+ public:
+ // Create new thread.
+ explicit JavaHandlerThread(
+ const char* name,
+ base::ThreadPriority priority = base::ThreadPriority::NORMAL);
+ // Wrap and connect to an existing JavaHandlerThread.
+ // |obj| is an instance of JavaHandlerThread.
+ explicit JavaHandlerThread(
+ const base::android::ScopedJavaLocalRef<jobject>& obj);
+ virtual ~JavaHandlerThread();
+
+ // Called from any thread.
+ base::MessageLoop* message_loop() const { return message_loop_.get(); }
+
+ // Gets the TaskRunner associated with the message loop.
+ // Called from any thread.
+ scoped_refptr<SingleThreadTaskRunner> task_runner() const {
+ return message_loop_ ? message_loop_->task_runner() : nullptr;
+ }
+
+ // Called from the parent thread.
+ void Start();
+ void Stop();
+
+ // Called from java on the newly created thread.
+ // Start() will not return before this methods has finished.
+ void InitializeThread(JNIEnv* env,
+ const JavaParamRef<jobject>& obj,
+ jlong event);
+ // Called from java on this thread.
+ void OnLooperStopped(JNIEnv* env, const JavaParamRef<jobject>& obj);
+
+ // Called from this thread.
+ void StopMessageLoopForTesting();
+ // Called from this thread.
+ void JoinForTesting();
+
+ // Called from this thread.
+ // See comment in JavaHandlerThread.java regarding use of this function.
+ void ListenForUncaughtExceptionsForTesting();
+ // Called from this thread.
+ ScopedJavaLocalRef<jthrowable> GetUncaughtExceptionIfAny();
+
+ protected:
+ // Semantically the same as base::Thread#Init(), but unlike base::Thread the
+ // Android Looper will already be running. This Init() call will still run
+ // before other tasks are posted to the thread.
+ virtual void Init() {}
+
+ // Semantically the same as base::Thread#CleanUp(), called after the message
+ // loop ends. The Android Looper will also have been quit by this point.
+ virtual void CleanUp() {}
+
+ std::unique_ptr<base::MessageLoopForUI> message_loop_;
+
+ private:
+ void StartMessageLoop();
+
+ void StopOnThread();
+ void QuitThreadSafely();
+
+ ScopedJavaGlobalRef<jobject> java_thread_;
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_JAVA_HANDLER_THREAD_H_
diff --git a/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java b/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java
new file mode 100644
index 0000000000..20c626d194
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java
@@ -0,0 +1,77 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.app.Application;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacks2;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.AdvancedMockContext;
+
+/**
+ * Tests for {@link org.chromium.base.test.util.AdvancedMockContext}.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class AdvancedMockContextTest {
+ private static class Callback1 implements ComponentCallbacks {
+ protected Configuration mConfiguration;
+ protected boolean mOnLowMemoryCalled;
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ mConfiguration = configuration;
+ }
+
+ @Override
+ public void onLowMemory() {
+ mOnLowMemoryCalled = true;
+ }
+ }
+
+ private static class Callback2 extends Callback1 implements ComponentCallbacks2 {
+ private int mLevel;
+
+ @Override
+ public void onTrimMemory(int level) {
+ mLevel = level;
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testComponentCallbacksForTargetContext() {
+ Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Application targetApplication = (Application) targetContext.getApplicationContext();
+ AdvancedMockContext context = new AdvancedMockContext(targetContext);
+ Callback1 callback1 = new Callback1();
+ Callback2 callback2 = new Callback2();
+ context.registerComponentCallbacks(callback1);
+ context.registerComponentCallbacks(callback2);
+
+ targetApplication.onLowMemory();
+ Assert.assertTrue("onLowMemory should have been called.", callback1.mOnLowMemoryCalled);
+ Assert.assertTrue("onLowMemory should have been called.", callback2.mOnLowMemoryCalled);
+
+ Configuration configuration = new Configuration();
+ targetApplication.onConfigurationChanged(configuration);
+ Assert.assertEquals("onConfigurationChanged should have been called.", configuration,
+ callback1.mConfiguration);
+ Assert.assertEquals("onConfigurationChanged should have been called.", configuration,
+ callback2.mConfiguration);
+
+ targetApplication.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE);
+ Assert.assertEquals("onTrimMemory should have been called.",
+ ComponentCallbacks2.TRIM_MEMORY_MODERATE, callback2.mLevel);
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/ApiCompatibilityUtilsTest.java b/base/android/javatests/src/org/chromium/base/ApiCompatibilityUtilsTest.java
new file mode 100644
index 0000000000..de0858a58a
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/ApiCompatibilityUtilsTest.java
@@ -0,0 +1,88 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.os.Build;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+
+/**
+ * Test of ApiCompatibilityUtils
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ApiCompatibilityUtilsTest {
+ private static final long WAIT_TIMEOUT_IN_MS = 5000;
+ private static final long SLEEP_INTERVAL_IN_MS = 50;
+
+ static class MockActivity extends Activity {
+ int mFinishAndRemoveTaskCallbackCount;
+ int mFinishCallbackCount;
+ boolean mIsFinishing;
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void finishAndRemoveTask() {
+ mFinishAndRemoveTaskCallbackCount++;
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) mIsFinishing = true;
+ }
+
+ @Override
+ public void finish() {
+ mFinishCallbackCount++;
+ mIsFinishing = true;
+ }
+
+ @Override
+ public boolean isFinishing() {
+ return mIsFinishing;
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testFinishAndRemoveTask() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ MockActivity activity = new MockActivity();
+ ApiCompatibilityUtils.finishAndRemoveTask(activity);
+
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
+ Assert.assertEquals(1, activity.mFinishAndRemoveTaskCallbackCount);
+ Assert.assertEquals(0, activity.mFinishCallbackCount);
+ } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
+ long startTime = SystemClock.uptimeMillis();
+ while (activity.mFinishCallbackCount == 0
+ && SystemClock.uptimeMillis() - startTime < WAIT_TIMEOUT_IN_MS) {
+ try {
+ Thread.sleep(SLEEP_INTERVAL_IN_MS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted thread sleep", e);
+ }
+ }
+
+ // MockActivity#finishAndRemoveTask() never sets isFinishing() to true for
+ // LOLLIPOP to simulate an exceptional case. In that case, MockActivity#finish()
+ // should be called after 3 tries.
+ Assert.assertEquals(3, activity.mFinishAndRemoveTaskCallbackCount);
+ Assert.assertEquals(1, activity.mFinishCallbackCount);
+ } else {
+ Assert.assertEquals(0, activity.mFinishAndRemoveTaskCallbackCount);
+ Assert.assertEquals(1, activity.mFinishCallbackCount);
+ }
+ Assert.assertTrue(activity.mIsFinishing);
+ }
+ });
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java b/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java
new file mode 100644
index 0000000000..a3be5dd155
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java
@@ -0,0 +1,35 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Feature;
+
+/**
+ * Test class for {@link CommandLineInitUtil}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CommandLineInitUtilTest {
+ /**
+ * Verifies that the default command line flags get set for Chrome Public tests.
+ */
+ @Test
+ @SmallTest
+ @Feature({"CommandLine"})
+ public void testDefaultCommandLineFlagsSet() {
+ CommandLineInitUtil.initCommandLine(CommandLineFlags.getTestCmdLineFile());
+ Assert.assertTrue("CommandLine not initialized.", CommandLine.isInitialized());
+
+ final CommandLine commandLine = CommandLine.getInstance();
+ Assert.assertTrue(commandLine.hasSwitch("enable-test-intents"));
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/CommandLineTest.java b/base/android/javatests/src/org/chromium/base/CommandLineTest.java
new file mode 100644
index 0000000000..787f85d999
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/CommandLineTest.java
@@ -0,0 +1,141 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.Feature;
+
+/**
+ * Tests for {@link CommandLine}.
+ * TODO(bauerb): Convert to local JUnit test
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class CommandLineTest {
+ // A reference command line. Note that switch2 is [brea\d], switch3 is [and "butter"],
+ // and switch4 is [a "quoted" 'food'!]
+ static final String INIT_SWITCHES[] = { "init_command", "--SWITCH", "Arg",
+ "--switch2=brea\\d", "--switch3=and \"butter\"",
+ "--switch4=a \"quoted\" 'food'!",
+ "--", "--actually_an_arg" };
+
+ // The same command line, but in quoted string format.
+ static final char INIT_SWITCHES_BUFFER[] =
+ ("init_command --SWITCH Arg --switch2=brea\\d --switch3=\"and \\\"butt\"er\\\" "
+ + "--switch4='a \"quoted\" \\'food\\'!' "
+ + "-- --actually_an_arg").toCharArray();
+
+ static final String CL_ADDED_SWITCH = "zappo-dappo-doggy-trainer";
+ static final String CL_ADDED_SWITCH_2 = "username";
+ static final String CL_ADDED_VALUE_2 = "bozo";
+
+ @Before
+ public void setUp() throws Exception {
+ CommandLine.reset();
+ }
+
+ void checkInitSwitches() {
+ CommandLine cl = CommandLine.getInstance();
+ Assert.assertFalse(cl.hasSwitch("init_command"));
+ Assert.assertFalse(cl.hasSwitch("switch"));
+ Assert.assertTrue(cl.hasSwitch("SWITCH"));
+ Assert.assertFalse(cl.hasSwitch("--SWITCH"));
+ Assert.assertFalse(cl.hasSwitch("Arg"));
+ Assert.assertFalse(cl.hasSwitch("actually_an_arg"));
+ Assert.assertEquals("brea\\d", cl.getSwitchValue("switch2"));
+ Assert.assertEquals("and \"butter\"", cl.getSwitchValue("switch3"));
+ Assert.assertEquals("a \"quoted\" 'food'!", cl.getSwitchValue("switch4"));
+ Assert.assertNull(cl.getSwitchValue("SWITCH"));
+ Assert.assertNull(cl.getSwitchValue("non-existant"));
+ }
+
+ void checkSettingThenGetting() {
+ CommandLine cl = CommandLine.getInstance();
+
+ // Add a plain switch.
+ Assert.assertFalse(cl.hasSwitch(CL_ADDED_SWITCH));
+ cl.appendSwitch(CL_ADDED_SWITCH);
+ Assert.assertTrue(cl.hasSwitch(CL_ADDED_SWITCH));
+
+ // Add a switch paired with a value.
+ Assert.assertFalse(cl.hasSwitch(CL_ADDED_SWITCH_2));
+ Assert.assertNull(cl.getSwitchValue(CL_ADDED_SWITCH_2));
+ cl.appendSwitchWithValue(CL_ADDED_SWITCH_2, CL_ADDED_VALUE_2);
+ Assert.assertTrue(CL_ADDED_VALUE_2.equals(cl.getSwitchValue(CL_ADDED_SWITCH_2)));
+
+ // Append a few new things.
+ final String switchesAndArgs[] = { "dummy", "--superfast", "--speed=turbo" };
+ Assert.assertFalse(cl.hasSwitch("dummy"));
+ Assert.assertFalse(cl.hasSwitch("superfast"));
+ Assert.assertNull(cl.getSwitchValue("speed"));
+ cl.appendSwitchesAndArguments(switchesAndArgs);
+ Assert.assertFalse(cl.hasSwitch("dummy"));
+ Assert.assertFalse(cl.hasSwitch("command"));
+ Assert.assertTrue(cl.hasSwitch("superfast"));
+ Assert.assertTrue("turbo".equals(cl.getSwitchValue("speed")));
+ }
+
+ void checkTokenizer(String[] expected, String toParse) {
+ String[] actual = CommandLine.tokenizeQuotedArguments(toParse.toCharArray());
+ Assert.assertEquals(expected.length, actual.length);
+ for (int i = 0; i < expected.length; ++i) {
+ Assert.assertEquals("comparing element " + i, expected[i], actual[i]);
+ }
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testJavaInitialization() {
+ CommandLine.init(INIT_SWITCHES);
+ checkInitSwitches();
+ checkSettingThenGetting();
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testBufferInitialization() {
+ CommandLine.init(CommandLine.tokenizeQuotedArguments(INIT_SWITCHES_BUFFER));
+ checkInitSwitches();
+ checkSettingThenGetting();
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testArgumentTokenizer() {
+ String toParse = " a\"\\bc de\\\"f g\"\\h ij k\" \"lm";
+ String[] expected = { "a\\bc de\"f g\\h",
+ "ij",
+ "k lm" };
+ checkTokenizer(expected, toParse);
+
+ toParse = "";
+ expected = new String[0];
+ checkTokenizer(expected, toParse);
+
+ toParse = " \t\n";
+ checkTokenizer(expected, toParse);
+
+ toParse = " \"a'b\" 'c\"d' \"e\\\"f\" 'g\\'h' \"i\\'j\" 'k\\\"l'"
+ + " m\"n\\'o\"p q'r\\\"s't";
+ expected = new String[] { "a'b",
+ "c\"d",
+ "e\"f",
+ "g'h",
+ "i\\'j",
+ "k\\\"l",
+ "mn\\'op",
+ "qr\\\"st"};
+ checkTokenizer(expected, toParse);
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/EarlyTraceEventTest.java b/base/android/javatests/src/org/chromium/base/EarlyTraceEventTest.java
new file mode 100644
index 0000000000..59a478ac36
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/EarlyTraceEventTest.java
@@ -0,0 +1,272 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import static org.chromium.base.EarlyTraceEvent.AsyncEvent;
+import static org.chromium.base.EarlyTraceEvent.Event;
+
+import android.os.Process;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.LibraryProcessType;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.Feature;
+
+/**
+ * Tests for {@link EarlyTraceEvent}.
+ *
+ * TODO(lizeb): Move to roboelectric tests.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class EarlyTraceEventTest {
+ private static final String EVENT_NAME = "MyEvent";
+ private static final String EVENT_NAME2 = "MyOtherEvent";
+ private static final long EVENT_ID = 1;
+ private static final long EVENT_ID2 = 2;
+
+ @Before
+ public void setUp() throws Exception {
+ LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
+ EarlyTraceEvent.resetForTesting();
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testCanRecordEvent() {
+ EarlyTraceEvent.enable();
+ long myThreadId = Process.myTid();
+ long beforeNanos = Event.elapsedRealtimeNanos();
+ EarlyTraceEvent.begin(EVENT_NAME);
+ EarlyTraceEvent.end(EVENT_NAME);
+ long afterNanos = Event.elapsedRealtimeNanos();
+
+ Assert.assertEquals(1, EarlyTraceEvent.sCompletedEvents.size());
+ Assert.assertTrue(EarlyTraceEvent.sPendingEventByKey.isEmpty());
+ Event event = EarlyTraceEvent.sCompletedEvents.get(0);
+ Assert.assertEquals(EVENT_NAME, event.mName);
+ Assert.assertEquals(myThreadId, event.mThreadId);
+ Assert.assertTrue(
+ beforeNanos <= event.mBeginTimeNanos && event.mBeginTimeNanos <= afterNanos);
+ Assert.assertTrue(event.mBeginTimeNanos <= event.mEndTimeNanos);
+ Assert.assertTrue(beforeNanos <= event.mEndTimeNanos && event.mEndTimeNanos <= afterNanos);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testCanRecordAsyncEvent() {
+ EarlyTraceEvent.enable();
+ long beforeNanos = Event.elapsedRealtimeNanos();
+ EarlyTraceEvent.startAsync(EVENT_NAME, EVENT_ID);
+ EarlyTraceEvent.finishAsync(EVENT_NAME, EVENT_ID);
+ long afterNanos = Event.elapsedRealtimeNanos();
+
+ Assert.assertEquals(2, EarlyTraceEvent.sAsyncEvents.size());
+ Assert.assertTrue(EarlyTraceEvent.sPendingEventByKey.isEmpty());
+ AsyncEvent eventStart = EarlyTraceEvent.sAsyncEvents.get(0);
+ AsyncEvent eventEnd = EarlyTraceEvent.sAsyncEvents.get(1);
+ Assert.assertEquals(EVENT_NAME, eventStart.mName);
+ Assert.assertEquals(EVENT_ID, eventStart.mId);
+ Assert.assertEquals(EVENT_NAME, eventEnd.mName);
+ Assert.assertEquals(EVENT_ID, eventEnd.mId);
+ Assert.assertTrue(beforeNanos <= eventStart.mTimestampNanos
+ && eventEnd.mTimestampNanos <= afterNanos);
+ Assert.assertTrue(eventStart.mTimestampNanos <= eventEnd.mTimestampNanos);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testRecordAsyncFinishEventWhenFinishing() {
+ EarlyTraceEvent.enable();
+ EarlyTraceEvent.startAsync(EVENT_NAME, EVENT_ID);
+ EarlyTraceEvent.disable();
+
+ Assert.assertEquals(EarlyTraceEvent.STATE_FINISHING, EarlyTraceEvent.sState);
+ Assert.assertTrue(EarlyTraceEvent.sAsyncEvents.isEmpty());
+ Assert.assertEquals(1, EarlyTraceEvent.sPendingAsyncEvents.size());
+ EarlyTraceEvent.finishAsync(EVENT_NAME, EVENT_ID);
+ Assert.assertEquals(EarlyTraceEvent.STATE_FINISHED, EarlyTraceEvent.sState);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testCanRecordEventUsingTryWith() {
+ EarlyTraceEvent.enable();
+ long myThreadId = Process.myTid();
+ long beforeNanos = Event.elapsedRealtimeNanos();
+ try (TraceEvent e = TraceEvent.scoped(EVENT_NAME)) {
+ // Required comment to pass presubmit checks.
+ }
+ long afterNanos = Event.elapsedRealtimeNanos();
+
+ Assert.assertEquals(1, EarlyTraceEvent.sCompletedEvents.size());
+ Assert.assertTrue(EarlyTraceEvent.sPendingEventByKey.isEmpty());
+ Event event = EarlyTraceEvent.sCompletedEvents.get(0);
+ Assert.assertEquals(EVENT_NAME, event.mName);
+ Assert.assertEquals(myThreadId, event.mThreadId);
+ Assert.assertTrue(
+ beforeNanos <= event.mBeginTimeNanos && event.mBeginTimeNanos <= afterNanos);
+ Assert.assertTrue(event.mBeginTimeNanos <= event.mEndTimeNanos);
+ Assert.assertTrue(beforeNanos <= event.mEndTimeNanos && event.mEndTimeNanos <= afterNanos);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testIncompleteEvent() {
+ EarlyTraceEvent.enable();
+ EarlyTraceEvent.begin(EVENT_NAME);
+
+ Assert.assertTrue(EarlyTraceEvent.sCompletedEvents.isEmpty());
+ Assert.assertEquals(1, EarlyTraceEvent.sPendingEventByKey.size());
+ EarlyTraceEvent.Event event = EarlyTraceEvent.sPendingEventByKey.get(
+ EarlyTraceEvent.makeEventKeyForCurrentThread(EVENT_NAME));
+ Assert.assertEquals(EVENT_NAME, event.mName);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testNoDuplicatePendingEventsFromSameThread() {
+ EarlyTraceEvent.enable();
+ EarlyTraceEvent.begin(EVENT_NAME);
+ try {
+ EarlyTraceEvent.begin(EVENT_NAME);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ return;
+ }
+ Assert.fail();
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testDuplicatePendingEventsFromDifferentThreads() throws Exception {
+ EarlyTraceEvent.enable();
+
+ Thread otherThread = new Thread(() -> { EarlyTraceEvent.begin(EVENT_NAME); });
+ otherThread.start();
+ otherThread.join();
+
+ // At this point we have a pending event with EVENT_NAME name. But events are per
+ // thread, so we should be able to start EVENT_NAME event in a different thread.
+ EarlyTraceEvent.begin(EVENT_NAME);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testIgnoreEventsWhenDisabled() {
+ EarlyTraceEvent.begin(EVENT_NAME);
+ EarlyTraceEvent.end(EVENT_NAME);
+ try (TraceEvent e = TraceEvent.scoped(EVENT_NAME2)) {
+ // Required comment to pass presubmit checks.
+ }
+ Assert.assertNull(EarlyTraceEvent.sCompletedEvents);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testIgnoreAsyncEventsWhenDisabled() {
+ EarlyTraceEvent.startAsync(EVENT_NAME, EVENT_ID);
+ EarlyTraceEvent.finishAsync(EVENT_NAME, EVENT_ID);
+ Assert.assertNull(EarlyTraceEvent.sAsyncEvents);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testIgnoreNewEventsWhenFinishing() {
+ EarlyTraceEvent.enable();
+ EarlyTraceEvent.begin(EVENT_NAME);
+ EarlyTraceEvent.disable();
+
+ Assert.assertEquals(EarlyTraceEvent.STATE_FINISHING, EarlyTraceEvent.sState);
+ EarlyTraceEvent.begin(EVENT_NAME2);
+ EarlyTraceEvent.end(EVENT_NAME2);
+
+ Assert.assertEquals(1, EarlyTraceEvent.sPendingEventByKey.size());
+ Assert.assertTrue(EarlyTraceEvent.sCompletedEvents.isEmpty());
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testIgnoreNewAsyncEventsWhenFinishing() {
+ EarlyTraceEvent.enable();
+ EarlyTraceEvent.startAsync(EVENT_NAME, EVENT_ID);
+ EarlyTraceEvent.disable();
+
+ Assert.assertEquals(EarlyTraceEvent.STATE_FINISHING, EarlyTraceEvent.sState);
+ EarlyTraceEvent.startAsync(EVENT_NAME2, EVENT_ID2);
+
+ Assert.assertEquals(1, EarlyTraceEvent.sPendingAsyncEvents.size());
+ Assert.assertTrue(EarlyTraceEvent.sAsyncEvents.isEmpty());
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testFinishingToFinished() {
+ EarlyTraceEvent.enable();
+ EarlyTraceEvent.begin(EVENT_NAME);
+ EarlyTraceEvent.disable();
+
+ Assert.assertEquals(EarlyTraceEvent.STATE_FINISHING, EarlyTraceEvent.sState);
+ EarlyTraceEvent.begin(EVENT_NAME2);
+ EarlyTraceEvent.end(EVENT_NAME2);
+ EarlyTraceEvent.end(EVENT_NAME);
+
+ Assert.assertEquals(EarlyTraceEvent.STATE_FINISHED, EarlyTraceEvent.sState);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testCannotBeReenabledOnceFinished() {
+ EarlyTraceEvent.enable();
+ EarlyTraceEvent.begin(EVENT_NAME);
+ EarlyTraceEvent.end(EVENT_NAME);
+ EarlyTraceEvent.disable();
+ Assert.assertEquals(EarlyTraceEvent.STATE_FINISHED, EarlyTraceEvent.sState);
+
+ EarlyTraceEvent.enable();
+ Assert.assertEquals(EarlyTraceEvent.STATE_FINISHED, EarlyTraceEvent.sState);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testThreadIdIsRecorded() throws Exception {
+ EarlyTraceEvent.enable();
+ final long[] threadId = {0};
+
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ TraceEvent.begin(EVENT_NAME);
+ threadId[0] = Process.myTid();
+ TraceEvent.end(EVENT_NAME);
+ }
+ };
+ thread.start();
+ thread.join();
+
+ Assert.assertEquals(1, EarlyTraceEvent.sCompletedEvents.size());
+ EarlyTraceEvent.Event event = EarlyTraceEvent.sCompletedEvents.get(0);
+ Assert.assertEquals(threadId[0], event.mThreadId);
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/LocaleUtilsTest.java b/base/android/javatests/src/org/chromium/base/LocaleUtilsTest.java
new file mode 100644
index 0000000000..fc37a1f565
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/LocaleUtilsTest.java
@@ -0,0 +1,262 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.os.LocaleList;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+
+import java.util.Locale;
+
+/**
+ * Tests for the LocaleUtils class.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class LocaleUtilsTest {
+ // This is also a part of test for toLanguageTag when API level is lower than 24
+ @Test
+ @SmallTest
+ public void testGetUpdatedLanguageForChromium() {
+ String language = "en";
+ String updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language);
+ Assert.assertEquals(language, updatedLanguage);
+
+ language = "iw";
+ updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language);
+ Assert.assertEquals("he", updatedLanguage);
+
+ language = "ji";
+ updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language);
+ Assert.assertEquals("yi", updatedLanguage);
+
+ language = "in";
+ updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language);
+ Assert.assertEquals("id", updatedLanguage);
+
+ language = "tl";
+ updatedLanguage = LocaleUtils.getUpdatedLanguageForChromium(language);
+ Assert.assertEquals("fil", updatedLanguage);
+ }
+
+ // This is also a part of test for toLanguageTags when API level is 24 or higher
+ @Test
+ @SmallTest
+ @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
+ public void testGetUpdatedLocaleForChromium() {
+ Locale locale = new Locale("jp");
+ Locale updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale);
+ Assert.assertEquals(locale, updatedLocale);
+
+ locale = new Locale("iw");
+ updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale);
+ Assert.assertEquals(new Locale("he"), updatedLocale);
+
+ locale = new Locale("ji");
+ updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale);
+ Assert.assertEquals(new Locale("yi"), updatedLocale);
+
+ locale = new Locale("in");
+ updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale);
+ Assert.assertEquals(new Locale("id"), updatedLocale);
+
+ locale = new Locale("tl");
+ updatedLocale = LocaleUtils.getUpdatedLocaleForChromium(locale);
+ Assert.assertEquals(new Locale("fil"), updatedLocale);
+ }
+
+ // This is also a part of test for forLanguageTag when API level is lower than 21
+ @Test
+ @SmallTest
+ public void testGetUpdatedLanguageForAndroid() {
+ String language = "en";
+ String updatedLanguage = LocaleUtils.getUpdatedLanguageForAndroid(language);
+ Assert.assertEquals(language, updatedLanguage);
+
+ language = "und";
+ updatedLanguage = LocaleUtils.getUpdatedLanguageForAndroid(language);
+ Assert.assertEquals("", updatedLanguage);
+
+ language = "fil";
+ updatedLanguage = LocaleUtils.getUpdatedLanguageForAndroid(language);
+ Assert.assertEquals("tl", updatedLanguage);
+ }
+
+ // This is also a part of test for forLanguageTag when API level is 21 or higher
+ @Test
+ @SmallTest
+ @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
+ public void testGetUpdatedLocaleForAndroid() {
+ Locale locale = new Locale("jp");
+ Locale updatedLocale = LocaleUtils.getUpdatedLocaleForAndroid(locale);
+ Assert.assertEquals(locale, updatedLocale);
+
+ locale = new Locale("und");
+ updatedLocale = LocaleUtils.getUpdatedLocaleForAndroid(locale);
+ Assert.assertEquals(new Locale(""), updatedLocale);
+
+ locale = new Locale("fil");
+ updatedLocale = LocaleUtils.getUpdatedLocaleForAndroid(locale);
+ Assert.assertEquals(new Locale("tl"), updatedLocale);
+ }
+
+ // Test for toLanguageTag when API level is lower than 24
+ @Test
+ @SmallTest
+ public void testToLanguageTag() {
+ Locale locale = new Locale("en", "US");
+ String localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("en-US", localeString);
+
+ locale = new Locale("jp");
+ localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("jp", localeString);
+
+ locale = new Locale("mas");
+ localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("mas", localeString);
+
+ locale = new Locale("es", "005");
+ localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("es-005", localeString);
+
+ locale = new Locale("iw");
+ localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("he", localeString);
+
+ locale = new Locale("ji");
+ localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("yi", localeString);
+
+ locale = new Locale("in", "ID");
+ localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("id-ID", localeString);
+
+ locale = new Locale("tl", "PH");
+ localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("fil-PH", localeString);
+
+ locale = new Locale("no", "NO", "NY");
+ localeString = LocaleUtils.toLanguageTag(locale);
+ Assert.assertEquals("nn-NO", localeString);
+ }
+
+ // Test for toLanguageTags when API level is 24 or higher
+ @Test
+ @SmallTest
+ @MinAndroidSdkLevel(Build.VERSION_CODES.N)
+ @SuppressLint("NewApi")
+ public void testToLanguageTags() {
+ Locale locale1 = new Locale("en", "US");
+ Locale locale2 = new Locale("es", "005");
+ LocaleList localeList = new LocaleList(locale1, locale2);
+ String localeString = LocaleUtils.toLanguageTags(localeList);
+ Assert.assertEquals("en-US,es-005", localeString);
+
+ locale1 = new Locale("jp");
+ locale2 = new Locale("mas");
+ localeList = new LocaleList(locale1, locale2);
+ localeString = LocaleUtils.toLanguageTags(localeList);
+ Assert.assertEquals("jp,mas", localeString);
+
+ locale1 = new Locale("iw");
+ locale2 = new Locale("ji");
+ localeList = new LocaleList(locale1, locale2);
+ localeString = LocaleUtils.toLanguageTags(localeList);
+ Assert.assertEquals("he,yi", localeString);
+
+ locale1 = new Locale("in", "ID");
+ locale2 = new Locale("tl", "PH");
+ localeList = new LocaleList(locale1, locale2);
+ localeString = LocaleUtils.toLanguageTags(localeList);
+ Assert.assertEquals("id-ID,fil-PH", localeString);
+
+ locale1 = new Locale("no", "NO", "NY");
+ localeList = new LocaleList(locale1);
+ localeString = LocaleUtils.toLanguageTags(localeList);
+ Assert.assertEquals("nn-NO", localeString);
+ }
+
+ // Test for forLanguageTag when API level is lower than 21
+ @Test
+ @SmallTest
+ public void testForLanguageTagCompat() {
+ String languageTag = "";
+ Locale locale = new Locale("");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "und";
+ locale = new Locale("");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "en";
+ locale = new Locale("en");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "mas";
+ locale = new Locale("mas");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "en-GB";
+ locale = new Locale("en", "GB");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "es-419";
+ locale = new Locale("es", "419");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ // Tests if updated Chromium language code and deprecated language code
+ // are pointing to the same Locale Object.
+ languageTag = "he";
+ locale = new Locale("iw");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "iw";
+ locale = new Locale("he");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "ji";
+ locale = new Locale("yi");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "yi";
+ locale = new Locale("ji");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "in";
+ locale = new Locale("id");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "id";
+ locale = new Locale("in");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ // Tests for Tagalog/Filipino if updated Chromium language code and
+ // language code are pointing to the same Locale Object.
+ languageTag = "tl";
+ locale = new Locale("tl");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "fil";
+ locale = new Locale("tl");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ // Test with invalid inputs.
+ languageTag = "notValidLanguage";
+ locale = new Locale("");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+
+ languageTag = "en-notValidCountry";
+ locale = new Locale("en");
+ Assert.assertEquals(locale, LocaleUtils.forLanguageTagCompat(languageTag));
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/ObserverListTest.java b/base/android/javatests/src/org/chromium/base/ObserverListTest.java
new file mode 100644
index 0000000000..f3b3e939bc
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/ObserverListTest.java
@@ -0,0 +1,340 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.Feature;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Tests for (@link ObserverList}.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ObserverListTest {
+ interface Observer {
+ void observe(int x);
+ }
+
+ private static class Foo implements Observer {
+ private final int mScalar;
+ private int mTotal = 0;
+
+ Foo(int scalar) {
+ mScalar = scalar;
+ }
+
+ @Override
+ public void observe(int x) {
+ mTotal += x * mScalar;
+ }
+ }
+
+ /**
+ * An observer which add a given Observer object to the list when observe is called.
+ */
+ private static class FooAdder implements Observer {
+ private final ObserverList<Observer> mList;
+ private final Observer mLucky;
+
+ FooAdder(ObserverList<Observer> list, Observer oblivious) {
+ mList = list;
+ mLucky = oblivious;
+ }
+
+ @Override
+ public void observe(int x) {
+ mList.addObserver(mLucky);
+ }
+ }
+
+ /**
+ * An observer which removes a given Observer object from the list when observe is called.
+ */
+ private static class FooRemover implements Observer {
+ private final ObserverList<Observer> mList;
+ private final Observer mDoomed;
+
+ FooRemover(ObserverList<Observer> list, Observer innocent) {
+ mList = list;
+ mDoomed = innocent;
+ }
+
+ @Override
+ public void observe(int x) {
+ mList.removeObserver(mDoomed);
+ }
+ }
+
+ private static <T> int getSizeOfIterable(Iterable<T> iterable) {
+ if (iterable instanceof Collection<?>) return ((Collection<?>) iterable).size();
+ int num = 0;
+ for (T el : iterable) num++;
+ return num;
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testRemoveWhileIteration() {
+ ObserverList<Observer> observerList = new ObserverList<Observer>();
+ Foo a = new Foo(1);
+ Foo b = new Foo(-1);
+ Foo c = new Foo(1);
+ Foo d = new Foo(-1);
+ Foo e = new Foo(-1);
+ FooRemover evil = new FooRemover(observerList, c);
+
+ observerList.addObserver(a);
+ observerList.addObserver(b);
+
+ for (Observer obs : observerList) obs.observe(10);
+
+ // Removing an observer not in the list should do nothing.
+ observerList.removeObserver(e);
+
+ observerList.addObserver(evil);
+ observerList.addObserver(c);
+ observerList.addObserver(d);
+
+ for (Observer obs : observerList) obs.observe(10);
+
+ // observe should be called twice on a.
+ Assert.assertEquals(20, a.mTotal);
+ // observe should be called twice on b.
+ Assert.assertEquals(-20, b.mTotal);
+ // evil removed c from the observerList before it got any callbacks.
+ Assert.assertEquals(0, c.mTotal);
+ // observe should be called once on d.
+ Assert.assertEquals(-10, d.mTotal);
+ // e was never added to the list, observe should not be called.
+ Assert.assertEquals(0, e.mTotal);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testAddWhileIteration() {
+ ObserverList<Observer> observerList = new ObserverList<Observer>();
+ Foo a = new Foo(1);
+ Foo b = new Foo(-1);
+ Foo c = new Foo(1);
+ FooAdder evil = new FooAdder(observerList, c);
+
+ observerList.addObserver(evil);
+ observerList.addObserver(a);
+ observerList.addObserver(b);
+
+ for (Observer obs : observerList) obs.observe(10);
+
+ Assert.assertTrue(observerList.hasObserver(c));
+ Assert.assertEquals(10, a.mTotal);
+ Assert.assertEquals(-10, b.mTotal);
+ Assert.assertEquals(0, c.mTotal);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testIterator() {
+ ObserverList<Integer> observerList = new ObserverList<Integer>();
+ observerList.addObserver(5);
+ observerList.addObserver(10);
+ observerList.addObserver(15);
+ Assert.assertEquals(3, getSizeOfIterable(observerList));
+
+ observerList.removeObserver(10);
+ Assert.assertEquals(2, getSizeOfIterable(observerList));
+
+ Iterator<Integer> it = observerList.iterator();
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(5 == it.next());
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(15 == it.next());
+ Assert.assertFalse(it.hasNext());
+
+ boolean removeExceptionThrown = false;
+ try {
+ it.remove();
+ Assert.fail("Expecting UnsupportedOperationException to be thrown here.");
+ } catch (UnsupportedOperationException e) {
+ removeExceptionThrown = true;
+ }
+ Assert.assertTrue(removeExceptionThrown);
+ Assert.assertEquals(2, getSizeOfIterable(observerList));
+
+ boolean noElementExceptionThrown = false;
+ try {
+ it.next();
+ Assert.fail("Expecting NoSuchElementException to be thrown here.");
+ } catch (NoSuchElementException e) {
+ noElementExceptionThrown = true;
+ }
+ Assert.assertTrue(noElementExceptionThrown);
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testRewindableIterator() {
+ ObserverList<Integer> observerList = new ObserverList<Integer>();
+ observerList.addObserver(5);
+ observerList.addObserver(10);
+ observerList.addObserver(15);
+ Assert.assertEquals(3, getSizeOfIterable(observerList));
+
+ ObserverList.RewindableIterator<Integer> it = observerList.rewindableIterator();
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(5 == it.next());
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(10 == it.next());
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(15 == it.next());
+ Assert.assertFalse(it.hasNext());
+
+ it.rewind();
+
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(5 == it.next());
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(10 == it.next());
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(15 == it.next());
+ Assert.assertEquals(5, (int) observerList.mObservers.get(0));
+ observerList.removeObserver(5);
+ Assert.assertEquals(null, observerList.mObservers.get(0));
+
+ it.rewind();
+
+ Assert.assertEquals(10, (int) observerList.mObservers.get(0));
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(10 == it.next());
+ Assert.assertTrue(it.hasNext());
+ Assert.assertTrue(15 == it.next());
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testAddObserverReturnValue() {
+ ObserverList<Object> observerList = new ObserverList<Object>();
+
+ Object a = new Object();
+ Assert.assertTrue(observerList.addObserver(a));
+ Assert.assertFalse(observerList.addObserver(a));
+
+ Object b = new Object();
+ Assert.assertTrue(observerList.addObserver(b));
+ Assert.assertFalse(observerList.addObserver(null));
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testRemoveObserverReturnValue() {
+ ObserverList<Object> observerList = new ObserverList<Object>();
+
+ Object a = new Object();
+ Object b = new Object();
+ observerList.addObserver(a);
+ observerList.addObserver(b);
+
+ Assert.assertTrue(observerList.removeObserver(a));
+ Assert.assertFalse(observerList.removeObserver(a));
+ Assert.assertFalse(observerList.removeObserver(new Object()));
+ Assert.assertTrue(observerList.removeObserver(b));
+ Assert.assertFalse(observerList.removeObserver(null));
+
+ // If we remove an object while iterating, it will be replaced by 'null'.
+ observerList.addObserver(a);
+ Assert.assertTrue(observerList.removeObserver(a));
+ Assert.assertFalse(observerList.removeObserver(null));
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testSize() {
+ ObserverList<Object> observerList = new ObserverList<Object>();
+
+ Assert.assertEquals(0, observerList.size());
+ Assert.assertTrue(observerList.isEmpty());
+
+ observerList.addObserver(null);
+ Assert.assertEquals(0, observerList.size());
+ Assert.assertTrue(observerList.isEmpty());
+
+ Object a = new Object();
+ observerList.addObserver(a);
+ Assert.assertEquals(1, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ observerList.addObserver(a);
+ Assert.assertEquals(1, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ observerList.addObserver(null);
+ Assert.assertEquals(1, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ Object b = new Object();
+ observerList.addObserver(b);
+ Assert.assertEquals(2, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ observerList.removeObserver(null);
+ Assert.assertEquals(2, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ observerList.removeObserver(new Object());
+ Assert.assertEquals(2, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ observerList.removeObserver(b);
+ Assert.assertEquals(1, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ observerList.removeObserver(b);
+ Assert.assertEquals(1, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ observerList.removeObserver(a);
+ Assert.assertEquals(0, observerList.size());
+ Assert.assertTrue(observerList.isEmpty());
+
+ observerList.removeObserver(a);
+ observerList.removeObserver(b);
+ observerList.removeObserver(null);
+ observerList.removeObserver(new Object());
+ Assert.assertEquals(0, observerList.size());
+ Assert.assertTrue(observerList.isEmpty());
+
+ observerList.addObserver(new Object());
+ observerList.addObserver(new Object());
+ observerList.addObserver(new Object());
+ observerList.addObserver(a);
+ Assert.assertEquals(4, observerList.size());
+ Assert.assertFalse(observerList.isEmpty());
+
+ observerList.clear();
+ Assert.assertEquals(0, observerList.size());
+ Assert.assertTrue(observerList.isEmpty());
+
+ observerList.removeObserver(a);
+ observerList.removeObserver(b);
+ observerList.removeObserver(null);
+ observerList.removeObserver(new Object());
+ Assert.assertEquals(0, observerList.size());
+ Assert.assertTrue(observerList.isEmpty());
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/StrictModeContextTest.java b/base/android/javatests/src/org/chromium/base/StrictModeContextTest.java
new file mode 100644
index 0000000000..59f38edbf8
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/StrictModeContextTest.java
@@ -0,0 +1,118 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.StrictMode;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Tests for the StrictModeContext class.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class StrictModeContextTest {
+ private StrictMode.ThreadPolicy mOldThreadPolicy;
+ private StrictMode.VmPolicy mOldVmPolicy;
+ private FileOutputStream mFosForWriting;
+ private FileInputStream mFisForReading;
+
+ @Before
+ public void setUp() throws Exception {
+ mFosForWriting = new FileOutputStream(File.createTempFile("foo", "bar"));
+ mFisForReading = new FileInputStream(File.createTempFile("foo", "baz"));
+ enableStrictMode();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ disableStrictMode();
+ mFosForWriting.close();
+ mFisForReading.close();
+ }
+
+ private void enableStrictMode() {
+ mOldThreadPolicy = StrictMode.getThreadPolicy();
+ mOldVmPolicy = StrictMode.getVmPolicy();
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyLog()
+ .penaltyDeath()
+ .build());
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build());
+ }
+
+ private void disableStrictMode() {
+ StrictMode.setThreadPolicy(mOldThreadPolicy);
+ StrictMode.setVmPolicy(mOldVmPolicy);
+ }
+
+ private void writeToDisk() {
+ try {
+ mFosForWriting.write(ApiCompatibilityUtils.getBytesUtf8("Foo"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void assertWriteToDiskThrows() {
+ boolean didThrow = false;
+ try {
+ writeToDisk();
+ } catch (Exception e) {
+ didThrow = true;
+ }
+ Assert.assertTrue("Expected disk write to throw.", didThrow);
+ }
+
+ private void readFromDisk() {
+ try {
+ mFisForReading.read();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void assertReadFromDiskThrows() {
+ boolean didThrow = false;
+ try {
+ readFromDisk();
+ } catch (Exception e) {
+ didThrow = true;
+ }
+ Assert.assertTrue("Expected disk read to throw.", didThrow);
+ }
+
+ @Test
+ @SmallTest
+ public void testAllowDiskWrites() {
+ try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
+ writeToDisk();
+ }
+ assertWriteToDiskThrows();
+ }
+
+ @Test
+ @SmallTest
+ public void testAllowDiskReads() {
+ try (StrictModeContext unused = StrictModeContext.allowDiskReads()) {
+ readFromDisk();
+ assertWriteToDiskThrows();
+ }
+ assertReadFromDiskThrows();
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java b/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java
new file mode 100644
index 0000000000..3ecfb3a561
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java
@@ -0,0 +1,201 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.metrics;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.LibraryProcessType;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.MetricsUtils.HistogramDelta;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for the Java API for recording UMA histograms.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class RecordHistogramTest {
+ @Before
+ public void setUp() throws Exception {
+ LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
+ }
+
+ /**
+ * Tests recording of boolean histograms.
+ */
+ @Test
+ @SmallTest
+ public void testRecordBooleanHistogram() {
+ String histogram = "HelloWorld.BooleanMetric";
+ HistogramDelta falseCount = new HistogramDelta(histogram, 0);
+ HistogramDelta trueCount = new HistogramDelta(histogram, 1);
+ Assert.assertEquals(0, trueCount.getDelta());
+ Assert.assertEquals(0, falseCount.getDelta());
+
+ RecordHistogram.recordBooleanHistogram(histogram, true);
+ Assert.assertEquals(1, trueCount.getDelta());
+ Assert.assertEquals(0, falseCount.getDelta());
+
+ RecordHistogram.recordBooleanHistogram(histogram, true);
+ Assert.assertEquals(2, trueCount.getDelta());
+ Assert.assertEquals(0, falseCount.getDelta());
+
+ RecordHistogram.recordBooleanHistogram(histogram, false);
+ Assert.assertEquals(2, trueCount.getDelta());
+ Assert.assertEquals(1, falseCount.getDelta());
+ }
+
+ /**
+ * Tests recording of enumerated histograms.
+ */
+ @Test
+ @SmallTest
+ public void testRecordEnumeratedHistogram() {
+ String histogram = "HelloWorld.EnumeratedMetric";
+ HistogramDelta zeroCount = new HistogramDelta(histogram, 0);
+ HistogramDelta oneCount = new HistogramDelta(histogram, 1);
+ HistogramDelta twoCount = new HistogramDelta(histogram, 2);
+ final int boundary = 3;
+
+ Assert.assertEquals(0, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordEnumeratedHistogram(histogram, 0, boundary);
+ Assert.assertEquals(1, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordEnumeratedHistogram(histogram, 0, boundary);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordEnumeratedHistogram(histogram, 2, boundary);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(1, twoCount.getDelta());
+ }
+
+ /**
+ * Tests recording of count histograms.
+ */
+ @Test
+ @SmallTest
+ public void testRecordCountHistogram() {
+ String histogram = "HelloWorld.CountMetric";
+ HistogramDelta zeroCount = new HistogramDelta(histogram, 0);
+ HistogramDelta oneCount = new HistogramDelta(histogram, 1);
+ HistogramDelta twoCount = new HistogramDelta(histogram, 2);
+ HistogramDelta eightThousandCount = new HistogramDelta(histogram, 8000);
+
+ Assert.assertEquals(0, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+ Assert.assertEquals(0, eightThousandCount.getDelta());
+
+ RecordHistogram.recordCountHistogram(histogram, 0);
+ Assert.assertEquals(1, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+ Assert.assertEquals(0, eightThousandCount.getDelta());
+
+ RecordHistogram.recordCountHistogram(histogram, 0);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+ Assert.assertEquals(0, eightThousandCount.getDelta());
+
+ RecordHistogram.recordCountHistogram(histogram, 2);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(1, twoCount.getDelta());
+ Assert.assertEquals(0, eightThousandCount.getDelta());
+
+ RecordHistogram.recordCountHistogram(histogram, 8000);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(1, twoCount.getDelta());
+ Assert.assertEquals(1, eightThousandCount.getDelta());
+ }
+
+ /**
+ * Tests recording of custom times histograms.
+ */
+ @Test
+ @SmallTest
+ public void testRecordCustomTimesHistogram() {
+ String histogram = "HelloWorld.CustomTimesMetric";
+ HistogramDelta zeroCount = new HistogramDelta(histogram, 0);
+ HistogramDelta oneCount = new HistogramDelta(histogram, 1);
+ HistogramDelta twoCount = new HistogramDelta(histogram, 100);
+
+ Assert.assertEquals(0, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ TimeUnit milli = TimeUnit.MILLISECONDS;
+
+ RecordHistogram.recordCustomTimesHistogram(histogram, 0, 1, 100, milli, 3);
+ Assert.assertEquals(1, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordCustomTimesHistogram(histogram, 0, 1, 100, milli, 3);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordCustomTimesHistogram(histogram, 95, 1, 100, milli, 3);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(1, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordCustomTimesHistogram(histogram, 200, 1, 100, milli, 3);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(1, oneCount.getDelta());
+ Assert.assertEquals(1, twoCount.getDelta());
+ }
+
+ /**
+ * Tests recording of linear count histograms.
+ */
+ @Test
+ @SmallTest
+ public void testRecordLinearCountHistogram() {
+ String histogram = "HelloWorld.LinearCountMetric";
+ HistogramDelta zeroCount = new HistogramDelta(histogram, 0);
+ HistogramDelta oneCount = new HistogramDelta(histogram, 1);
+ HistogramDelta twoCount = new HistogramDelta(histogram, 2);
+ final int min = 1;
+ final int max = 3;
+ final int numBuckets = 4;
+
+ Assert.assertEquals(0, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordLinearCountHistogram(histogram, 0, min, max, numBuckets);
+ Assert.assertEquals(1, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordLinearCountHistogram(histogram, 0, min, max, numBuckets);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(0, twoCount.getDelta());
+
+ RecordHistogram.recordLinearCountHistogram(histogram, 2, min, max, numBuckets);
+ Assert.assertEquals(2, zeroCount.getDelta());
+ Assert.assertEquals(0, oneCount.getDelta());
+ Assert.assertEquals(1, twoCount.getDelta());
+ }
+}
diff --git a/base/android/jni_array_unittest.cc b/base/android/jni_array_unittest.cc
new file mode 100644
index 0000000000..245cd5082a
--- /dev/null
+++ b/base/android/jni_array_unittest.cc
@@ -0,0 +1,389 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_array.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+TEST(JniArray, BasicConversions) {
+ const uint8_t kBytes[] = {0, 1, 2, 3};
+ const size_t kLen = arraysize(kBytes);
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jbyteArray> bytes = ToJavaByteArray(env, kBytes, kLen);
+ ASSERT_TRUE(bytes.obj());
+
+ std::vector<uint8_t> inputVector(kBytes, kBytes + kLen);
+ ScopedJavaLocalRef<jbyteArray> bytesFromVector =
+ ToJavaByteArray(env, inputVector);
+ ASSERT_TRUE(bytesFromVector.obj());
+
+ std::vector<uint8_t> vectorFromBytes(5);
+ std::vector<uint8_t> vectorFromVector(5);
+ JavaByteArrayToByteVector(env, bytes.obj(), &vectorFromBytes);
+ JavaByteArrayToByteVector(env, bytesFromVector.obj(), &vectorFromVector);
+ EXPECT_EQ(4U, vectorFromBytes.size());
+ EXPECT_EQ(4U, vectorFromVector.size());
+ std::vector<uint8_t> expected_vec(kBytes, kBytes + kLen);
+ EXPECT_EQ(expected_vec, vectorFromBytes);
+ EXPECT_EQ(expected_vec, vectorFromVector);
+
+ AppendJavaByteArrayToByteVector(env, bytes.obj(), &vectorFromBytes);
+ EXPECT_EQ(8U, vectorFromBytes.size());
+ expected_vec.insert(expected_vec.end(), kBytes, kBytes + kLen);
+ EXPECT_EQ(expected_vec, vectorFromBytes);
+}
+
+void CheckBoolConversion(JNIEnv* env,
+ const bool* bool_array,
+ const size_t len,
+ const ScopedJavaLocalRef<jbooleanArray>& booleans) {
+ ASSERT_TRUE(booleans.obj());
+
+ jsize java_array_len = env->GetArrayLength(booleans.obj());
+ ASSERT_EQ(static_cast<jsize>(len), java_array_len);
+
+ jboolean value;
+ for (size_t i = 0; i < len; ++i) {
+ env->GetBooleanArrayRegion(booleans.obj(), i, 1, &value);
+ ASSERT_EQ(bool_array[i], value);
+ }
+}
+
+TEST(JniArray, BoolConversions) {
+ const bool kBools[] = {false, true, false};
+ const size_t kLen = arraysize(kBools);
+
+ JNIEnv* env = AttachCurrentThread();
+ CheckBoolConversion(env, kBools, kLen, ToJavaBooleanArray(env, kBools, kLen));
+}
+
+void CheckIntConversion(
+ JNIEnv* env,
+ const int* int_array,
+ const size_t len,
+ const ScopedJavaLocalRef<jintArray>& ints) {
+ ASSERT_TRUE(ints.obj());
+
+ jsize java_array_len = env->GetArrayLength(ints.obj());
+ ASSERT_EQ(static_cast<jsize>(len), java_array_len);
+
+ jint value;
+ for (size_t i = 0; i < len; ++i) {
+ env->GetIntArrayRegion(ints.obj(), i, 1, &value);
+ ASSERT_EQ(int_array[i], value);
+ }
+}
+
+TEST(JniArray, IntConversions) {
+ const int kInts[] = {0, 1, -1, std::numeric_limits<int32_t>::min(),
+ std::numeric_limits<int32_t>::max()};
+ const size_t kLen = arraysize(kInts);
+
+ JNIEnv* env = AttachCurrentThread();
+ CheckIntConversion(env, kInts, kLen, ToJavaIntArray(env, kInts, kLen));
+
+ const std::vector<int> vec(kInts, kInts + kLen);
+ CheckIntConversion(env, kInts, kLen, ToJavaIntArray(env, vec));
+}
+
+void CheckLongConversion(JNIEnv* env,
+ const int64_t* long_array,
+ const size_t len,
+ const ScopedJavaLocalRef<jlongArray>& longs) {
+ ASSERT_TRUE(longs.obj());
+
+ jsize java_array_len = env->GetArrayLength(longs.obj());
+ ASSERT_EQ(static_cast<jsize>(len), java_array_len);
+
+ jlong value;
+ for (size_t i = 0; i < len; ++i) {
+ env->GetLongArrayRegion(longs.obj(), i, 1, &value);
+ ASSERT_EQ(long_array[i], value);
+ }
+}
+
+TEST(JniArray, LongConversions) {
+ const int64_t kLongs[] = {0, 1, -1, std::numeric_limits<int64_t>::min(),
+ std::numeric_limits<int64_t>::max()};
+ const size_t kLen = arraysize(kLongs);
+
+ JNIEnv* env = AttachCurrentThread();
+ CheckLongConversion(env, kLongs, kLen, ToJavaLongArray(env, kLongs, kLen));
+
+ const std::vector<int64_t> vec(kLongs, kLongs + kLen);
+ CheckLongConversion(env, kLongs, kLen, ToJavaLongArray(env, vec));
+}
+
+void CheckIntArrayConversion(JNIEnv* env,
+ ScopedJavaLocalRef<jintArray> jints,
+ std::vector<int> int_vector,
+ const size_t len) {
+ jint value;
+ for (size_t i = 0; i < len; ++i) {
+ env->GetIntArrayRegion(jints.obj(), i, 1, &value);
+ ASSERT_EQ(int_vector[i], value);
+ }
+}
+
+void CheckBoolArrayConversion(JNIEnv* env,
+ ScopedJavaLocalRef<jbooleanArray> jbooleans,
+ std::vector<bool> bool_vector,
+ const size_t len) {
+ jboolean value;
+ for (size_t i = 0; i < len; ++i) {
+ env->GetBooleanArrayRegion(jbooleans.obj(), i, 1, &value);
+ ASSERT_EQ(bool_vector[i], value);
+ }
+}
+
+void CheckFloatConversion(
+ JNIEnv* env,
+ const float* float_array,
+ const size_t len,
+ const ScopedJavaLocalRef<jfloatArray>& floats) {
+ ASSERT_TRUE(floats.obj());
+
+ jsize java_array_len = env->GetArrayLength(floats.obj());
+ ASSERT_EQ(static_cast<jsize>(len), java_array_len);
+
+ jfloat value;
+ for (size_t i = 0; i < len; ++i) {
+ env->GetFloatArrayRegion(floats.obj(), i, 1, &value);
+ ASSERT_EQ(float_array[i], value);
+ }
+}
+
+TEST(JniArray, FloatConversions) {
+ const float kFloats[] = { 0.0f, 1.0f, -10.0f};
+ const size_t kLen = arraysize(kFloats);
+
+ JNIEnv* env = AttachCurrentThread();
+ CheckFloatConversion(env, kFloats, kLen,
+ ToJavaFloatArray(env, kFloats, kLen));
+
+ const std::vector<float> vec(kFloats, kFloats + kLen);
+ CheckFloatConversion(env, kFloats, kLen, ToJavaFloatArray(env, vec));
+}
+
+TEST(JniArray, JavaBooleanArrayToBoolVector) {
+ const bool kBools[] = {false, true, false};
+ const size_t kLen = arraysize(kBools);
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jbooleanArray> jbooleans(env, env->NewBooleanArray(kLen));
+ ASSERT_TRUE(jbooleans.obj());
+
+ for (size_t i = 0; i < kLen; ++i) {
+ jboolean j = static_cast<jboolean>(kBools[i]);
+ env->SetBooleanArrayRegion(jbooleans.obj(), i, 1, &j);
+ ASSERT_FALSE(HasException(env));
+ }
+
+ std::vector<bool> bools;
+ JavaBooleanArrayToBoolVector(env, jbooleans.obj(), &bools);
+
+ ASSERT_EQ(static_cast<jsize>(bools.size()),
+ env->GetArrayLength(jbooleans.obj()));
+
+ CheckBoolArrayConversion(env, jbooleans, bools, kLen);
+}
+
+TEST(JniArray, JavaIntArrayToIntVector) {
+ const int kInts[] = {0, 1, -1};
+ const size_t kLen = arraysize(kInts);
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jintArray> jints(env, env->NewIntArray(kLen));
+ ASSERT_TRUE(jints.obj());
+
+ for (size_t i = 0; i < kLen; ++i) {
+ jint j = static_cast<jint>(kInts[i]);
+ env->SetIntArrayRegion(jints.obj(), i, 1, &j);
+ ASSERT_FALSE(HasException(env));
+ }
+
+ std::vector<int> ints;
+ JavaIntArrayToIntVector(env, jints.obj(), &ints);
+
+ ASSERT_EQ(static_cast<jsize>(ints.size()), env->GetArrayLength(jints.obj()));
+
+ CheckIntArrayConversion(env, jints, ints, kLen);
+}
+
+TEST(JniArray, JavaLongArrayToInt64Vector) {
+ const int64_t kInt64s[] = {0LL, 1LL, -1LL};
+ const size_t kLen = arraysize(kInt64s);
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jlongArray> jlongs(env, env->NewLongArray(kLen));
+ ASSERT_TRUE(jlongs.obj());
+
+ for (size_t i = 0; i < kLen; ++i) {
+ jlong j = static_cast<jlong>(kInt64s[i]);
+ env->SetLongArrayRegion(jlongs.obj(), i, 1, &j);
+ ASSERT_FALSE(HasException(env));
+ }
+
+ std::vector<int64_t> int64s;
+ JavaLongArrayToInt64Vector(env, jlongs.obj(), &int64s);
+
+ ASSERT_EQ(static_cast<jsize>(int64s.size()),
+ env->GetArrayLength(jlongs.obj()));
+
+ jlong value;
+ for (size_t i = 0; i < kLen; ++i) {
+ env->GetLongArrayRegion(jlongs.obj(), i, 1, &value);
+ ASSERT_EQ(int64s[i], value);
+ ASSERT_EQ(kInt64s[i], int64s[i]);
+ }
+}
+
+TEST(JniArray, JavaLongArrayToLongVector) {
+ const int64_t kInt64s[] = {0LL, 1LL, -1LL};
+ const size_t kLen = arraysize(kInt64s);
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jlongArray> jlongs(env, env->NewLongArray(kLen));
+ ASSERT_TRUE(jlongs.obj());
+
+ for (size_t i = 0; i < kLen; ++i) {
+ jlong j = static_cast<jlong>(kInt64s[i]);
+ env->SetLongArrayRegion(jlongs.obj(), i, 1, &j);
+ ASSERT_FALSE(HasException(env));
+ }
+
+ std::vector<jlong> jlongs_vector;
+ JavaLongArrayToLongVector(env, jlongs.obj(), &jlongs_vector);
+
+ ASSERT_EQ(static_cast<jsize>(jlongs_vector.size()),
+ env->GetArrayLength(jlongs.obj()));
+
+ jlong value;
+ for (size_t i = 0; i < kLen; ++i) {
+ env->GetLongArrayRegion(jlongs.obj(), i, 1, &value);
+ ASSERT_EQ(jlongs_vector[i], value);
+ }
+}
+
+TEST(JniArray, JavaFloatArrayToFloatVector) {
+ const float kFloats[] = {0.0, 0.5, -0.5};
+ const size_t kLen = arraysize(kFloats);
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jfloatArray> jfloats(env, env->NewFloatArray(kLen));
+ ASSERT_TRUE(jfloats.obj());
+
+ for (size_t i = 0; i < kLen; ++i) {
+ jfloat j = static_cast<jfloat>(kFloats[i]);
+ env->SetFloatArrayRegion(jfloats.obj(), i, 1, &j);
+ ASSERT_FALSE(HasException(env));
+ }
+
+ std::vector<float> floats;
+ JavaFloatArrayToFloatVector(env, jfloats.obj(), &floats);
+
+ ASSERT_EQ(static_cast<jsize>(floats.size()),
+ env->GetArrayLength(jfloats.obj()));
+
+ jfloat value;
+ for (size_t i = 0; i < kLen; ++i) {
+ env->GetFloatArrayRegion(jfloats.obj(), i, 1, &value);
+ ASSERT_EQ(floats[i], value);
+ }
+}
+
+TEST(JniArray, JavaArrayOfByteArrayToStringVector) {
+ const int kMaxItems = 50;
+ JNIEnv* env = AttachCurrentThread();
+
+ // Create a byte[][] object.
+ ScopedJavaLocalRef<jclass> byte_array_clazz(env, env->FindClass("[B"));
+ ASSERT_TRUE(byte_array_clazz.obj());
+
+ ScopedJavaLocalRef<jobjectArray> array(
+ env, env->NewObjectArray(kMaxItems, byte_array_clazz.obj(), NULL));
+ ASSERT_TRUE(array.obj());
+
+ // Create kMaxItems byte buffers.
+ char text[16];
+ for (int i = 0; i < kMaxItems; ++i) {
+ snprintf(text, sizeof text, "%d", i);
+ ScopedJavaLocalRef<jbyteArray> byte_array =
+ ToJavaByteArray(env, reinterpret_cast<uint8_t*>(text),
+ static_cast<size_t>(strlen(text)));
+ ASSERT_TRUE(byte_array.obj());
+
+ env->SetObjectArrayElement(array.obj(), i, byte_array.obj());
+ ASSERT_FALSE(HasException(env));
+ }
+
+ // Convert to std::vector<std::string>, check the content.
+ std::vector<std::string> vec;
+ JavaArrayOfByteArrayToStringVector(env, array.obj(), &vec);
+
+ EXPECT_EQ(static_cast<size_t>(kMaxItems), vec.size());
+ for (int i = 0; i < kMaxItems; ++i) {
+ snprintf(text, sizeof text, "%d", i);
+ EXPECT_STREQ(text, vec[i].c_str());
+ }
+}
+
+TEST(JniArray, JavaArrayOfIntArrayToIntVector) {
+ const size_t kNumItems = 4;
+ JNIEnv* env = AttachCurrentThread();
+
+ // Create an int[][] object.
+ ScopedJavaLocalRef<jclass> int_array_clazz(env, env->FindClass("[I"));
+ ASSERT_TRUE(int_array_clazz.obj());
+
+ ScopedJavaLocalRef<jobjectArray> array(
+ env, env->NewObjectArray(kNumItems, int_array_clazz.obj(), nullptr));
+ ASSERT_TRUE(array.obj());
+
+ // Populate int[][] object.
+ const int kInts0[] = {0, 1, -1, std::numeric_limits<int32_t>::min(),
+ std::numeric_limits<int32_t>::max()};
+ const size_t kLen0 = arraysize(kInts0);
+ ScopedJavaLocalRef<jintArray> int_array0 = ToJavaIntArray(env, kInts0, kLen0);
+ env->SetObjectArrayElement(array.obj(), 0, int_array0.obj());
+
+ const int kInts1[] = {3, 4, 5};
+ const size_t kLen1 = arraysize(kInts1);
+ ScopedJavaLocalRef<jintArray> int_array1 = ToJavaIntArray(env, kInts1, kLen1);
+ env->SetObjectArrayElement(array.obj(), 1, int_array1.obj());
+
+ const int kInts2[] = {};
+ const size_t kLen2 = 0;
+ ScopedJavaLocalRef<jintArray> int_array2 = ToJavaIntArray(env, kInts2, kLen2);
+ env->SetObjectArrayElement(array.obj(), 2, int_array2.obj());
+
+ const int kInts3[] = {16};
+ const size_t kLen3 = arraysize(kInts3);
+ ScopedJavaLocalRef<jintArray> int_array3 = ToJavaIntArray(env, kInts3, kLen3);
+ env->SetObjectArrayElement(array.obj(), 3, int_array3.obj());
+
+ // Convert to std::vector<std::vector<int>>, check the content.
+ std::vector<std::vector<int>> out;
+ JavaArrayOfIntArrayToIntVector(env, array.obj(), &out);
+
+ EXPECT_EQ(kNumItems, out.size());
+ CheckIntArrayConversion(env, int_array0, out[0], kLen0);
+ CheckIntArrayConversion(env, int_array1, out[1], kLen1);
+ CheckIntArrayConversion(env, int_array2, out[2], kLen2);
+ CheckIntArrayConversion(env, int_array3, out[3], kLen3);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/jni_generator/PRESUBMIT.py b/base/android/jni_generator/PRESUBMIT.py
deleted file mode 100644
index bc76d5bb97..0000000000
--- a/base/android/jni_generator/PRESUBMIT.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Presubmit script for android buildbot.
-
-See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
-details on the presubmit API built into depot_tools.
-"""
-
-
-def CommonChecks(input_api, output_api):
- base_android_jni_generator_dir = input_api.PresubmitLocalPath()
-
- env = dict(input_api.environ)
- env.update({
- 'PYTHONPATH': base_android_jni_generator_dir,
- 'PYTHONDONTWRITEBYTECODE': '1',
- })
-
- return input_api.canned_checks.RunUnitTests(
- input_api,
- output_api,
- unit_tests=[
- input_api.os_path.join(
- base_android_jni_generator_dir, 'jni_generator_tests.py')
- ],
- env=env,
- )
-
-
-def CheckChangeOnUpload(input_api, output_api):
- return CommonChecks(input_api, output_api)
-
-
-def CheckChangeOnCommit(input_api, output_api):
- return CommonChecks(input_api, output_api)
diff --git a/base/android/jni_generator/README.md b/base/android/jni_generator/README.md
new file mode 100644
index 0000000000..17ad150c32
--- /dev/null
+++ b/base/android/jni_generator/README.md
@@ -0,0 +1,118 @@
+# Overview
+JNI (Java Native Interface) is the mechanism that enables Java code to call
+native functions, and native code to call Java functions.
+
+ * Native code calls into Java using apis from `<jni.h>`, which basically mirror
+ Java's reflection APIs.
+ * Java code calls native functions by declaring body-less functions with the
+ `native` keyword, and then calling them as normal Java functions.
+
+`jni_generator` generates boiler-plate code with the goal of making our code:
+ 1. easier to write, and
+ 2. typesafe.
+
+`jni_generator` uses regular expressions to parse .Java files, so don't do
+anything too fancy. E.g.:
+ * Classes must be either explicitly imported, or are assumed to be in
+the same package. To use `java.lang` classes, add an explicit import.
+ * Inner classes need to be referenced through the outer class. E.g.:
+ `void call(Outer.Inner inner)`
+
+The presense of any JNI within a class will result in ProGuard obfuscation for
+the class to be disabled.
+
+### Exposing Native Methods
+
+**Without Crazy Linker:**
+ * Java->Native calls are exported from the shared library and lazily resolved
+ by the runtime (via `dlsym()`).
+
+**With Crazy Linker:**
+ * Java->Native calls are explicitly registered with JNI on the native side.
+ Explicit registration is necessary because crazy linker provides its own
+ `dlsym()`, but JNI is hardcoded to use the system's `dlsym()`.
+ * The logic to explicitly register stubs is generated by
+ `jni_registration_generator.py`.
+ * This script finds all native methods by scanning all source `.java` files
+ of an APK. Inefficient, but very convenient.
+ * Since `dlsym()` is not used in this case, we use a linker script to avoid
+ the cost of exporting symbols from the shared library (refer to
+ `//build/config/android:hide_all_but_jni_onload`).
+ * `jni_registration_generator.py` exposes two registrations methods:
+ * `RegisterNonMainDexNatives` - Registers native functions needed by multiple
+ process types (e.g. Rendereres, GPU process).
+ * `RegisterMainDexNatives` - Registers native functions needed only by the
+ browser process.
+
+### Exposing Java Methods
+
+Java methods just need to be annotated with `@CalledByNative`. The generated
+functions can be put into a namespace using `@JNINamespace("your_namespace")`.
+
+## Usage
+
+Because the generator does not generate any source files, generated headers must
+not be `#included` by multiple sources. If there are Java functions that need to
+be called by multiple sources, one source should be chosen to expose the
+functions to the others via additional wrapper functions.
+
+### Calling Java -> Native
+
+ * Methods marked as `native` will have stubs generated for them that forward
+ calls to C++ function (that you must write).
+ * If the first parameter is a C++ object (e.g. `long mNativePointer`), then the
+ bindings will automatically generate the appropriate cast and call into C++
+ code (JNI itself is only C).
+
+### Calling Native -> Java
+
+ * Methods annotated with `@CalledByNative` will have stubs generated for them.
+ * Just call the generated stubs defined in generated `.h` files.
+
+### Java Objects and Garbage Collection
+
+All pointers to Java objects must be registered with JNI in order to prevent
+garbage collection from invalidating them.
+
+For Strings & Arrays - it's common practice to use the `//base/android/jni_*`
+helpers to convert them to `std::vectors` and `std::strings` as soon as
+possible.
+
+For other objects - use smart pointers to store them:
+ * `ScopedJavaLocalRef<>` - When lifetime is the current function's scope.
+ * `ScopedJavaGlobalRef<>` - When lifetime is longer than the current function's
+ scope.
+ * `JavaObjectWeakGlobalRef<>` - Weak reference (do not prevent garbage
+ collection).
+ * `JavaParamRef<>` - Use to accept any of the above as a parameter to a
+ function without creating a redundant registration.
+
+### Additional Guidelines / Advice
+
+Minimize the surface API between the two sides. Rather than calling multiple
+functions across boundaries, call only one (and then on the other side, call as
+many little functions as required).
+
+If a Java object "owns" a native one, store the pointer via
+`"long mNativeClassName"`. Ensure to eventually call a native method to delete
+the object. For example, have a `close()` that deletes the native object.
+
+The best way to pass "compound" types across in either direction is to
+create an inner class with PODs and a factory function. If possible, make mark
+all the fields as "final".
+
+## Build Rules
+
+ * `generate_jni` - Generates a header file with stubs for given `.java` files
+ * `generate_jar_jni` - Generates a header file with stubs for a given `.jar`
+ file
+ * `generate_jni_registration` - Generates a header file with functions to
+ register native-side JNI methods (required only when using crazy linker).
+
+Refer to [//build/config/android/rules.gni](https://cs.chromium.org/chromium/src/build/config/android/rules.gni)
+for more about the GN templates.
+
+## Changing `jni_generator`
+
+ * Python unit tests live in `jni_generator_tests.py`
+ * A working demo app exists as `//base/android/jni_generator:sample_jni_apk`
diff --git a/base/android/jni_generator/SampleForTests_jni.golden b/base/android/jni_generator/SampleForTests_jni.golden
new file mode 100644
index 0000000000..c3fe96851d
--- /dev/null
+++ b/base/android/jni_generator/SampleForTests_jni.golden
@@ -0,0 +1,494 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+// This file is autogenerated by
+// base/android/jni_generator/jni_generator.py
+// For
+// org/chromium/example/jni_generator/SampleForTests
+
+#ifndef org_chromium_example_jni_generator_SampleForTests_JNI
+#define org_chromium_example_jni_generator_SampleForTests_JNI
+
+#include <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+
+
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA[] =
+ "org/chromium/example/jni_generator/SampleForTests$InnerStructA";
+
+JNI_REGISTRATION_EXPORT extern const char
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass[] =
+ "org/chromium/example/jni_generator/SampleForTests$InnerClass";
+
+JNI_REGISTRATION_EXPORT extern const char
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests[] =
+ "org/chromium/example/jni_generator/SampleForTests";
+
+JNI_REGISTRATION_EXPORT extern const char
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB[] =
+ "org/chromium/example/jni_generator/SampleForTests$InnerStructB";
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz = 0;
+#ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(JNIEnv*
+ env) {
+ return base::android::LazyGetClass(env,
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA,
+ &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz);
+}
+#endif
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz = 0;
+#ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz(JNIEnv* env)
+ {
+ return base::android::LazyGetClass(env,
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass,
+ &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz);
+}
+#endif
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_clazz = 0;
+#ifndef org_chromium_example_jni_1generator_SampleForTests_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env,
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests,
+ &g_org_chromium_example_jni_1generator_SampleForTests_clazz);
+}
+#endif
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz = 0;
+#ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(JNIEnv*
+ env) {
+ return base::android::LazyGetClass(env,
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB,
+ &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz);
+}
+#endif
+
+
+// Step 2: Constants (optional).
+
+
+// Step 3: Method stubs.
+namespace base {
+namespace android {
+
+static jlong JNI_SampleForTests_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
+ jcaller,
+ const base::android::JavaParamRef<jstring>& param);
+
+JNI_GENERATOR_EXPORT jlong Java_org_chromium_example_jni_1generator_SampleForTests_nativeInit(
+ JNIEnv* env,
+ jobject jcaller,
+ jstring param) {
+ return JNI_SampleForTests_Init(env, base::android::JavaParamRef<jobject>(env, jcaller),
+ base::android::JavaParamRef<jstring>(env, param));
+}
+
+JNI_GENERATOR_EXPORT void Java_org_chromium_example_jni_1generator_SampleForTests_nativeDestroy(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeCPPClass) {
+ CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+ CHECK_NATIVE_PTR(env, jcaller, native, "Destroy");
+ return native->Destroy(env, base::android::JavaParamRef<jobject>(env, jcaller));
+}
+
+static jdouble JNI_SampleForTests_GetDoubleFunction(JNIEnv* env, const
+ base::android::JavaParamRef<jobject>& jcaller);
+
+JNI_GENERATOR_EXPORT jdouble
+ Java_org_chromium_example_jni_1generator_SampleForTests_nativeGetDoubleFunction(
+ JNIEnv* env,
+ jobject jcaller) {
+ return JNI_SampleForTests_GetDoubleFunction(env, base::android::JavaParamRef<jobject>(env,
+ jcaller));
+}
+
+static jfloat JNI_SampleForTests_GetFloatFunction(JNIEnv* env, const
+ base::android::JavaParamRef<jclass>& jcaller);
+
+JNI_GENERATOR_EXPORT jfloat
+ Java_org_chromium_example_jni_1generator_SampleForTests_nativeGetFloatFunction(
+ JNIEnv* env,
+ jclass jcaller) {
+ return JNI_SampleForTests_GetFloatFunction(env, base::android::JavaParamRef<jclass>(env,
+ jcaller));
+}
+
+static void JNI_SampleForTests_SetNonPODDatatype(JNIEnv* env, const
+ base::android::JavaParamRef<jobject>& jcaller,
+ const base::android::JavaParamRef<jobject>& rect);
+
+JNI_GENERATOR_EXPORT void
+ Java_org_chromium_example_jni_1generator_SampleForTests_nativeSetNonPODDatatype(
+ JNIEnv* env,
+ jobject jcaller,
+ jobject rect) {
+ return JNI_SampleForTests_SetNonPODDatatype(env, base::android::JavaParamRef<jobject>(env,
+ jcaller), base::android::JavaParamRef<jobject>(env, rect));
+}
+
+static base::android::ScopedJavaLocalRef<jobject> JNI_SampleForTests_GetNonPODDatatype(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jcaller);
+
+JNI_GENERATOR_EXPORT jobject
+ Java_org_chromium_example_jni_1generator_SampleForTests_nativeGetNonPODDatatype(
+ JNIEnv* env,
+ jobject jcaller) {
+ return JNI_SampleForTests_GetNonPODDatatype(env, base::android::JavaParamRef<jobject>(env,
+ jcaller)).Release();
+}
+
+JNI_GENERATOR_EXPORT jint Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethod(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeCPPClass) {
+ CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+ CHECK_NATIVE_PTR(env, jcaller, native, "Method", 0);
+ return native->Method(env, base::android::JavaParamRef<jobject>(env, jcaller));
+}
+
+JNI_GENERATOR_EXPORT jdouble
+ Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethodOtherP0(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativePtr) {
+ CPPClass::InnerClass* native = reinterpret_cast<CPPClass::InnerClass*>(nativePtr);
+ CHECK_NATIVE_PTR(env, jcaller, native, "MethodOtherP0", 0);
+ return native->MethodOtherP0(env, base::android::JavaParamRef<jobject>(env, jcaller));
+}
+
+JNI_GENERATOR_EXPORT void Java_org_chromium_example_jni_1generator_SampleForTests_nativeAddStructB(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeCPPClass,
+ jobject b) {
+ CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+ CHECK_NATIVE_PTR(env, jcaller, native, "AddStructB");
+ return native->AddStructB(env, base::android::JavaParamRef<jobject>(env, jcaller),
+ base::android::JavaParamRef<jobject>(env, b));
+}
+
+JNI_GENERATOR_EXPORT void
+ Java_org_chromium_example_jni_1generator_SampleForTests_nativeIterateAndDoSomethingWithStructB(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeCPPClass) {
+ CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+ CHECK_NATIVE_PTR(env, jcaller, native, "IterateAndDoSomethingWithStructB");
+ return native->IterateAndDoSomethingWithStructB(env, base::android::JavaParamRef<jobject>(env,
+ jcaller));
+}
+
+JNI_GENERATOR_EXPORT jstring
+ Java_org_chromium_example_jni_1generator_SampleForTests_nativeReturnAString(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeCPPClass) {
+ CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass);
+ CHECK_NATIVE_PTR(env, jcaller, native, "ReturnAString", NULL);
+ return native->ReturnAString(env, base::android::JavaParamRef<jobject>(env, jcaller)).Release();
+}
+
+static jint JNI_InnerClass_GetInnerIntFunction(JNIEnv* env, const
+ base::android::JavaParamRef<jclass>& jcaller);
+
+JNI_GENERATOR_EXPORT jint
+ Java_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_nativeGetInnerIntFunction(
+ JNIEnv* env,
+ jclass jcaller) {
+ return JNI_InnerClass_GetInnerIntFunction(env, base::android::JavaParamRef<jclass>(env, jcaller));
+}
+
+
+static base::subtle::AtomicWord g_org_chromium_example_jni_1generator_SampleForTests_javaMethod = 0;
+static jint Java_SampleForTests_javaMethod(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper foo,
+ JniIntWrapper bar) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "javaMethod",
+ "(II)I",
+ &g_org_chromium_example_jni_1generator_SampleForTests_javaMethod);
+
+ jint ret =
+ env->CallIntMethod(obj.obj(),
+ method_id, as_jint(foo), as_jint(bar));
+ jni_generator::CheckException(env);
+ return ret;
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_staticJavaMethod = 0;
+static jboolean Java_SampleForTests_staticJavaMethod(JNIEnv* env) {
+ CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), false);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_STATIC>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "staticJavaMethod",
+ "()Z",
+ &g_org_chromium_example_jni_1generator_SampleForTests_staticJavaMethod);
+
+ jboolean ret =
+ env->CallStaticBooleanMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ method_id);
+ jni_generator::CheckException(env);
+ return ret;
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_packagePrivateJavaMethod = 0;
+static void Java_SampleForTests_packagePrivateJavaMethod(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "packagePrivateJavaMethod",
+ "()V",
+ &g_org_chromium_example_jni_1generator_SampleForTests_packagePrivateJavaMethod);
+
+ env->CallVoidMethod(obj.obj(),
+ method_id);
+ jni_generator::CheckException(env);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_methodWithGenericParams = 0;
+static void Java_SampleForTests_methodWithGenericParams(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj, const base::android::JavaRef<jobject>& foo,
+ const base::android::JavaRef<jobject>& bar) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "methodWithGenericParams",
+ "(Ljava/util/Map;Ljava/util/LinkedList;)V",
+ &g_org_chromium_example_jni_1generator_SampleForTests_methodWithGenericParams);
+
+ env->CallVoidMethod(obj.obj(),
+ method_id, foo.obj(), bar.obj());
+ jni_generator::CheckException(env);
+}
+
+static base::subtle::AtomicWord g_org_chromium_example_jni_1generator_SampleForTests_Constructor =
+ 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_Constructor(JNIEnv* env,
+ JniIntWrapper foo,
+ JniIntWrapper bar) {
+ CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "<init>",
+ "(II)V",
+ &g_org_chromium_example_jni_1generator_SampleForTests_Constructor);
+
+ jobject ret =
+ env->NewObject(org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ method_id, as_jint(foo), as_jint(bar));
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_methodThatThrowsException = 0;
+static void Java_SampleForTests_methodThatThrowsException(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "methodThatThrowsException",
+ "()V",
+ &g_org_chromium_example_jni_1generator_SampleForTests_methodThatThrowsException);
+
+ env->CallVoidMethod(obj.obj(),
+ method_id);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_javaMethodWithAnnotatedParam = 0;
+static void Java_SampleForTests_javaMethodWithAnnotatedParam(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj, JniIntWrapper foo) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "javaMethodWithAnnotatedParam",
+ "(I)V",
+ &g_org_chromium_example_jni_1generator_SampleForTests_javaMethodWithAnnotatedParam);
+
+ env->CallVoidMethod(obj.obj(),
+ method_id, as_jint(foo));
+ jni_generator::CheckException(env);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_create = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_InnerStructA_create(JNIEnv* env, jlong l,
+ JniIntWrapper i,
+ const base::android::JavaRef<jstring>& s) {
+ CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env),
+ org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_STATIC>(
+ env, org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env),
+ "create",
+ "(JILjava/lang/String;)Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;",
+ &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_create);
+
+ jobject ret =
+env->CallStaticObjectMethod(org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(env),
+ method_id, l, as_jint(i), s.obj());
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+static base::subtle::AtomicWord g_org_chromium_example_jni_1generator_SampleForTests_addStructA = 0;
+static void Java_SampleForTests_addStructA(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jobject>& a) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "addStructA",
+ "(Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;)V",
+ &g_org_chromium_example_jni_1generator_SampleForTests_addStructA);
+
+ env->CallVoidMethod(obj.obj(),
+ method_id, a.obj());
+ jni_generator::CheckException(env);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_iterateAndDoSomething = 0;
+static void Java_SampleForTests_iterateAndDoSomething(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "iterateAndDoSomething",
+ "()V",
+ &g_org_chromium_example_jni_1generator_SampleForTests_iterateAndDoSomething);
+
+ env->CallVoidMethod(obj.obj(),
+ method_id);
+ jni_generator::CheckException(env);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getKey = 0;
+static jlong Java_InnerStructB_getKey(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), 0);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env),
+ "getKey",
+ "()J",
+ &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getKey);
+
+ jlong ret =
+ env->CallLongMethod(obj.obj(),
+ method_id);
+ jni_generator::CheckException(env);
+ return ret;
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getValue = 0;
+static base::android::ScopedJavaLocalRef<jstring> Java_InnerStructB_getValue(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env),
+ "getValue",
+ "()Ljava/lang/String;",
+ &g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getValue);
+
+ jstring ret =
+ static_cast<jstring>(env->CallObjectMethod(obj.obj(),
+ method_id));
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jstring>(env, ret);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_getInnerInterface = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_getInnerInterface(JNIEnv* env)
+ {
+ CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_STATIC>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "getInnerInterface",
+ "()Lorg/chromium/example/jni_generator/SampleForTests$InnerInterface;",
+ &g_org_chromium_example_jni_1generator_SampleForTests_getInnerInterface);
+
+ jobject ret =
+ env->CallStaticObjectMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ method_id);
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+static base::subtle::AtomicWord g_org_chromium_example_jni_1generator_SampleForTests_getInnerEnum =
+ 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_getInnerEnum(JNIEnv* env) {
+ CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_STATIC>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "getInnerEnum",
+ "()Lorg/chromium/example/jni_generator/SampleForTests$InnerEnum;",
+ &g_org_chromium_example_jni_1generator_SampleForTests_getInnerEnum);
+
+ jobject ret =
+ env->CallStaticObjectMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ method_id);
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+} // namespace android
+} // namespace base
+
+#endif // org_chromium_example_jni_generator_SampleForTests_JNI
diff --git a/base/android/jni_generator/testNativeExportsOnlyOption.golden b/base/android/jni_generator/testNativeExportsOnlyOption.golden
new file mode 100644
index 0000000000..bdc8d29e94
--- /dev/null
+++ b/base/android/jni_generator/testNativeExportsOnlyOption.golden
@@ -0,0 +1,214 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+// This file is autogenerated by
+// base/android/jni_generator/jni_generator.py
+// For
+// org/chromium/example/jni_generator/SampleForTests
+
+#ifndef org_chromium_example_jni_generator_SampleForTests_JNI
+#define org_chromium_example_jni_generator_SampleForTests_JNI
+
+#include <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+
+
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass[] =
+ "org/chromium/example/jni_generator/SampleForTests$MyOtherInnerClass";
+
+JNI_REGISTRATION_EXPORT extern const char
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass[] =
+ "org/chromium/example/jni_generator/SampleForTests$MyInnerClass";
+
+JNI_REGISTRATION_EXPORT extern const char
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests[];
+const char kClassPath_org_chromium_example_jni_1generator_SampleForTests[] =
+ "org/chromium/example/jni_generator/SampleForTests";
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz = 0;
+#ifndef org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz_defined
+inline jclass
+ org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env,
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass,
+ &g_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz);
+}
+#endif
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz = 0;
+#ifndef org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz(JNIEnv*
+ env) {
+ return base::android::LazyGetClass(env,
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass,
+ &g_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz);
+}
+#endif
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_clazz = 0;
+#ifndef org_chromium_example_jni_1generator_SampleForTests_clazz_defined
+#define org_chromium_example_jni_1generator_SampleForTests_clazz_defined
+inline jclass org_chromium_example_jni_1generator_SampleForTests_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env,
+ kClassPath_org_chromium_example_jni_1generator_SampleForTests,
+ &g_org_chromium_example_jni_1generator_SampleForTests_clazz);
+}
+#endif
+
+
+// Step 2: Constants (optional).
+
+
+// Step 3: Method stubs.
+JNI_GENERATOR_EXPORT jint
+ Java_org_chromium_example_jni_1generator_SampleForTests_nativeStaticMethod(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeTest,
+ jint arg1) {
+ Test* native = reinterpret_cast<Test*>(nativeTest);
+ CHECK_NATIVE_PTR(env, jcaller, native, "StaticMethod", 0);
+ return native->StaticMethod(env, base::android::JavaParamRef<jobject>(env, jcaller), arg1);
+}
+
+JNI_GENERATOR_EXPORT jint Java_org_chromium_example_jni_1generator_SampleForTests_nativeMethod(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeTest,
+ jint arg1) {
+ Test* native = reinterpret_cast<Test*>(nativeTest);
+ CHECK_NATIVE_PTR(env, jcaller, native, "Method", 0);
+ return native->Method(env, base::android::JavaParamRef<jobject>(env, jcaller), arg1);
+}
+
+static jint JNI_MyInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller);
+
+JNI_GENERATOR_EXPORT jint
+ Java_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_nativeInit(
+ JNIEnv* env,
+ jobject jcaller) {
+ return JNI_MyInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
+}
+
+static jint JNI_MyOtherInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
+ jcaller);
+
+JNI_GENERATOR_EXPORT jint
+ Java_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_nativeInit(
+ JNIEnv* env,
+ jobject jcaller) {
+ return JNI_MyOtherInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
+}
+
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParam = 0;
+static void Java_SampleForTests_testMethodWithParam(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj, JniIntWrapper iParam) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "testMethodWithParam",
+ "(I)V",
+ &g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParam);
+
+ env->CallVoidMethod(obj.obj(),
+ method_id, as_jint(iParam));
+ jni_generator::CheckException(env);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParamAndReturn = 0;
+static base::android::ScopedJavaLocalRef<jstring>
+ Java_SampleForTests_testMethodWithParamAndReturn(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj, JniIntWrapper iParam) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "testMethodWithParamAndReturn",
+ "(I)Ljava/lang/String;",
+ &g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParamAndReturn);
+
+ jstring ret =
+ static_cast<jstring>(env->CallObjectMethod(obj.obj(),
+ method_id, as_jint(iParam)));
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jstring>(env, ret);
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithParam = 0;
+static jint Java_SampleForTests_testStaticMethodWithParam(JNIEnv* env, JniIntWrapper iParam) {
+ CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_STATIC>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "testStaticMethodWithParam",
+ "(I)I",
+ &g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithParam);
+
+ jint ret =
+ env->CallStaticIntMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ method_id, as_jint(iParam));
+ jni_generator::CheckException(env);
+ return ret;
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithNoParam = 0;
+static jdouble Java_SampleForTests_testMethodWithNoParam(JNIEnv* env) {
+ CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_STATIC>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "testMethodWithNoParam",
+ "()D",
+ &g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithNoParam);
+
+ jdouble ret =
+ env->CallStaticDoubleMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ method_id);
+ jni_generator::CheckException(env);
+ return ret;
+}
+
+static base::subtle::AtomicWord
+ g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithNoParam = 0;
+static base::android::ScopedJavaLocalRef<jstring>
+ Java_SampleForTests_testStaticMethodWithNoParam(JNIEnv* env) {
+ CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_STATIC>(
+ env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ "testStaticMethodWithNoParam",
+ "()Ljava/lang/String;",
+ &g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithNoParam);
+
+ jstring ret =
+static_cast<jstring>(env->CallStaticObjectMethod(org_chromium_example_jni_1generator_SampleForTests_clazz(env),
+ method_id));
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jstring>(env, ret);
+}
+
+#endif // org_chromium_example_jni_generator_SampleForTests_JNI
diff --git a/base/android/jni_registrar.cc b/base/android/jni_registrar.cc
new file mode 100644
index 0000000000..8e13e60097
--- /dev/null
+++ b/base/android/jni_registrar.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_registrar.h"
+
+#include "base/logging.h"
+#include "base/android/jni_android.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace android {
+
+bool RegisterNativeMethods(JNIEnv* env,
+ const RegistrationMethod* method,
+ size_t count) {
+ TRACE_EVENT0("startup", "base_android::RegisterNativeMethods")
+ const RegistrationMethod* end = method + count;
+ while (method != end) {
+ if (!method->func(env)) {
+ DLOG(ERROR) << method->name << " failed registration!";
+ return false;
+ }
+ method++;
+ }
+ return true;
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/jni_registrar.h b/base/android/jni_registrar.h
new file mode 100644
index 0000000000..31a4750385
--- /dev/null
+++ b/base/android/jni_registrar.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_JNI_REGISTRAR_H_
+#define BASE_ANDROID_JNI_REGISTRAR_H_
+
+#include <jni.h>
+#include <stddef.h>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace android {
+
+struct RegistrationMethod;
+
+// Registers the JNI bindings for the specified |method| definition containing
+// |count| elements. Returns whether the registration of the given methods
+// succeeded.
+BASE_EXPORT bool RegisterNativeMethods(JNIEnv* env,
+ const RegistrationMethod* method,
+ size_t count);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_JNI_REGISTRAR_H_
diff --git a/base/android/jni_utils.cc b/base/android/jni_utils.cc
new file mode 100644
index 0000000000..c5e370c9f0
--- /dev/null
+++ b/base/android/jni_utils.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_utils.h"
+
+#include "base/android/scoped_java_ref.h"
+
+#include "jni/JNIUtils_jni.h"
+
+namespace base {
+namespace android {
+
+ScopedJavaLocalRef<jobject> GetClassLoader(JNIEnv* env) {
+ return Java_JNIUtils_getClassLoader(env);
+}
+
+bool IsSelectiveJniRegistrationEnabled(JNIEnv* env) {
+ return Java_JNIUtils_isSelectiveJniRegistrationEnabled(env);
+}
+
+} // namespace android
+} // namespace base
+
diff --git a/base/android/jni_utils.h b/base/android/jni_utils.h
new file mode 100644
index 0000000000..c626ba4602
--- /dev/null
+++ b/base/android/jni_utils.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_JNI_UTILS_H_
+#define BASE_ANDROID_JNI_UTILS_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+
+namespace base {
+
+namespace android {
+
+// Gets a ClassLoader instance capable of loading Chromium java classes.
+// This should be called either from JNI_OnLoad or from within a method called
+// via JNI from Java.
+BASE_EXPORT ScopedJavaLocalRef<jobject> GetClassLoader(JNIEnv* env);
+
+// Returns true if the current process permits selective JNI registration.
+BASE_EXPORT bool IsSelectiveJniRegistrationEnabled(JNIEnv* env);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_JNI_UTILS_H_
+
diff --git a/base/android/jni_weak_ref.cc b/base/android/jni_weak_ref.cc
new file mode 100644
index 0000000000..88efa723e5
--- /dev/null
+++ b/base/android/jni_weak_ref.cc
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_weak_ref.h"
+
+#include <utility>
+
+#include "base/android/jni_android.h"
+#include "base/logging.h"
+
+using base::android::AttachCurrentThread;
+
+JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef() : obj_(nullptr) {}
+
+JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(
+ const JavaObjectWeakGlobalRef& orig)
+ : obj_(nullptr) {
+ Assign(orig);
+}
+
+JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(
+ JavaObjectWeakGlobalRef&& orig) noexcept
+ : obj_(orig.obj_) {
+ orig.obj_ = nullptr;
+}
+
+JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj)
+ : obj_(env->NewWeakGlobalRef(obj)) {
+}
+
+JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(
+ JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj)
+ : obj_(env->NewWeakGlobalRef(obj.obj())) {
+}
+
+JavaObjectWeakGlobalRef::~JavaObjectWeakGlobalRef() {
+ reset();
+}
+
+void JavaObjectWeakGlobalRef::operator=(const JavaObjectWeakGlobalRef& rhs) {
+ Assign(rhs);
+}
+
+void JavaObjectWeakGlobalRef::operator=(JavaObjectWeakGlobalRef&& rhs) {
+ std::swap(obj_, rhs.obj_);
+}
+
+void JavaObjectWeakGlobalRef::reset() {
+ if (obj_) {
+ AttachCurrentThread()->DeleteWeakGlobalRef(obj_);
+ obj_ = nullptr;
+ }
+}
+
+base::android::ScopedJavaLocalRef<jobject>
+ JavaObjectWeakGlobalRef::get(JNIEnv* env) const {
+ return GetRealObject(env, obj_);
+}
+
+base::android::ScopedJavaLocalRef<jobject> GetRealObject(
+ JNIEnv* env, jweak obj) {
+ jobject real = nullptr;
+ if (obj)
+ real = env->NewLocalRef(obj);
+ return base::android::ScopedJavaLocalRef<jobject>(env, real);
+}
+
+void JavaObjectWeakGlobalRef::Assign(const JavaObjectWeakGlobalRef& other) {
+ if (&other == this)
+ return;
+
+ JNIEnv* env = AttachCurrentThread();
+ if (obj_)
+ env->DeleteWeakGlobalRef(obj_);
+
+ obj_ = other.obj_ ? env->NewWeakGlobalRef(other.obj_) : nullptr;
+}
diff --git a/base/android/jni_weak_ref.h b/base/android/jni_weak_ref.h
new file mode 100644
index 0000000000..43a26b5fe5
--- /dev/null
+++ b/base/android/jni_weak_ref.h
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_JNI_WEAK_REF_H_
+#define BASE_ANDROID_JNI_WEAK_REF_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/base_export.h"
+
+// Manages WeakGlobalRef lifecycle.
+// This class is not thread-safe w.r.t. get() and reset(). Multiple threads may
+// safely use get() concurrently, but if the user calls reset() (or of course,
+// calls the destructor) they'll need to provide their own synchronization.
+class BASE_EXPORT JavaObjectWeakGlobalRef {
+ public:
+ JavaObjectWeakGlobalRef();
+ JavaObjectWeakGlobalRef(const JavaObjectWeakGlobalRef& orig);
+ JavaObjectWeakGlobalRef(JavaObjectWeakGlobalRef&& orig) noexcept;
+ JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj);
+ JavaObjectWeakGlobalRef(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj);
+ virtual ~JavaObjectWeakGlobalRef();
+
+ void operator=(const JavaObjectWeakGlobalRef& rhs);
+ void operator=(JavaObjectWeakGlobalRef&& rhs);
+
+ base::android::ScopedJavaLocalRef<jobject> get(JNIEnv* env) const;
+
+ // Returns true if the weak reference has not been initialized to point at
+ // an object (or ḣas had reset() called).
+ // Do not call this to test if the object referred to still exists! The weak
+ // reference remains initialized even if the target object has been collected.
+ bool is_uninitialized() const { return obj_ == nullptr; }
+
+ void reset();
+
+ private:
+ void Assign(const JavaObjectWeakGlobalRef& rhs);
+
+ jweak obj_;
+};
+
+// Get the real object stored in the weak reference returned as a
+// ScopedJavaLocalRef.
+BASE_EXPORT base::android::ScopedJavaLocalRef<jobject> GetRealObject(
+ JNIEnv* env, jweak obj);
+
+#endif // BASE_ANDROID_JNI_WEAK_REF_H_
diff --git a/base/android/junit/src/org/chromium/base/ApplicationStatusTest.java b/base/android/junit/src/org/chromium/base/ApplicationStatusTest.java
new file mode 100644
index 0000000000..eb1829539d
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/ApplicationStatusTest.java
@@ -0,0 +1,70 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.view.KeyEvent;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowActivity;
+import org.robolectric.shadows.multidex.ShadowMultiDex;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for {@link ApplicationStatus}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE,
+ shadows = {ApplicationStatusTest.TrackingShadowActivity.class, ShadowMultiDex.class})
+public class ApplicationStatusTest {
+ /** Shadow that tracks calls to onWindowFocusChanged and dispatchKeyEvent. */
+ @Implements(Activity.class)
+ public static class TrackingShadowActivity extends ShadowActivity {
+ private int mWindowFocusCalls;
+ private int mDispatchKeyEventCalls;
+ private boolean mReturnValueForKeyDispatch;
+
+ @Implementation
+ public void onWindowFocusChanged(@SuppressWarnings("unused") boolean hasFocus) {
+ mWindowFocusCalls++;
+ }
+
+ @Implementation
+ public boolean dispatchKeyEvent(@SuppressWarnings("unused") KeyEvent event) {
+ mDispatchKeyEventCalls++;
+ return mReturnValueForKeyDispatch;
+ }
+ }
+
+ @Test
+ public void testWindowsFocusChanged() throws Exception {
+ ApplicationStatus.initialize(RuntimeEnvironment.application);
+
+ ApplicationStatus.WindowFocusChangedListener mock =
+ mock(ApplicationStatus.WindowFocusChangedListener.class);
+ ApplicationStatus.registerWindowFocusChangedListener(mock);
+
+ ActivityController<Activity> controller =
+ Robolectric.buildActivity(Activity.class).create().start().visible();
+ TrackingShadowActivity shadow = (TrackingShadowActivity) Shadows.shadowOf(controller.get());
+
+ controller.get().getWindow().getCallback().onWindowFocusChanged(true);
+ // Assert that listeners were notified.
+ verify(mock).onWindowFocusChanged(controller.get(), true);
+ // Also ensure that the original activity is forwarded the notification.
+ Assert.assertEquals(1, shadow.mWindowFocusCalls);
+ }
+}
diff --git a/base/android/junit/src/org/chromium/base/LogTest.java b/base/android/junit/src/org/chromium/base/LogTest.java
new file mode 100644
index 0000000000..a3832a0aad
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/LogTest.java
@@ -0,0 +1,87 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+import java.util.List;
+
+/** Unit tests for {@link Log}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class LogTest {
+ /** Tests that the computed call origin is the correct one. */
+ @Test
+ public void callOriginTest() {
+ Log.d("Foo", "Bar");
+
+ List<ShadowLog.LogItem> logs = ShadowLog.getLogs();
+ assertEquals("Only one log should be written", 1, logs.size());
+
+ assertTrue("The origin of the log message (" + logs.get(0).msg + ") looks wrong.",
+ logs.get(0).msg.matches("\\[LogTest.java:\\d+\\].*"));
+ }
+
+ @Test
+ public void normalizeTagTest() {
+ assertEquals("cr_foo", Log.normalizeTag("cr.foo"));
+ assertEquals("cr_foo", Log.normalizeTag("cr_foo"));
+ assertEquals("cr_foo", Log.normalizeTag("foo"));
+ assertEquals("cr_ab_foo", Log.normalizeTag("ab_foo"));
+ }
+
+ /** Tests that exceptions provided to the log functions are properly recognized and printed. */
+ @Test
+ public void exceptionLoggingTest() {
+ Throwable t = new Throwable() {
+ @Override
+ public String toString() {
+ return "MyThrowable";
+ }
+ };
+
+ Throwable t2 = new Throwable() {
+ @Override
+ public String toString() {
+ return "MyOtherThrowable";
+ }
+ };
+
+ List<ShadowLog.LogItem> logs;
+
+ // The throwable gets printed out
+ Log.i("Foo", "Bar", t);
+ logs = ShadowLog.getLogs();
+ assertEquals(t, logs.get(logs.size() - 1).throwable);
+ assertEquals("Bar", logs.get(logs.size() - 1).msg);
+
+ // The throwable can be both added to the message itself and printed out
+ Log.i("Foo", "Bar %s", t);
+ logs = ShadowLog.getLogs();
+ assertEquals(t, logs.get(logs.size() - 1).throwable);
+ assertEquals("Bar MyThrowable", logs.get(logs.size() - 1).msg);
+
+ // Non throwable are properly identified
+ Log.i("Foo", "Bar %s", t, "Baz");
+ logs = ShadowLog.getLogs();
+ assertNull(logs.get(logs.size() - 1).throwable);
+ assertEquals("Bar MyThrowable", logs.get(logs.size() - 1).msg);
+
+ // The last throwable is the one used that is going to be printed out
+ Log.i("Foo", "Bar %s %s", t, t2);
+ logs = ShadowLog.getLogs();
+ assertEquals(t2, logs.get(logs.size() - 1).throwable);
+ assertEquals("Bar MyThrowable MyOtherThrowable", logs.get(logs.size() - 1).msg);
+ }
+}
diff --git a/base/android/junit/src/org/chromium/base/NonThreadSafeTest.java b/base/android/junit/src/org/chromium/base/NonThreadSafeTest.java
new file mode 100644
index 0000000000..9c57199c9e
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/NonThreadSafeTest.java
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import org.chromium.base.test.util.Feature;
+
+/**
+ * Tests for NonThreadSafe.
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class NonThreadSafeTest {
+ /**
+ * Test for creating and using on the same thread
+ */
+ @Test
+ @Feature({"Android-AppBase"})
+ public void testCreateAndUseOnSameThread() {
+ NonThreadSafe t = new NonThreadSafe();
+ Assert.assertTrue(t.calledOnValidThread());
+ }
+
+ /**
+ * Test if calledOnValidThread returns false if used on another thread.
+ */
+ @Test
+ @Feature({"Android-AppBase"})
+ public void testCreateAndUseOnDifferentThread() {
+ final NonThreadSafe t = new NonThreadSafe();
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Assert.assertFalse(t.calledOnValidThread());
+ }
+ }).start();
+ }
+
+ /**
+ * Test if detachFromThread reassigns the thread.
+ */
+ @Test
+ @Feature({"Android-AppBase"})
+ public void testDetachFromThread() {
+ final NonThreadSafe t = new NonThreadSafe();
+ Assert.assertTrue(t.calledOnValidThread());
+ t.detachFromThread();
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Assert.assertTrue(t.calledOnValidThread());
+ Assert.assertTrue(t.calledOnValidThread());
+ }
+ }).start();
+ }
+}
diff --git a/base/android/junit/src/org/chromium/base/PromiseTest.java b/base/android/junit/src/org/chromium/base/PromiseTest.java
new file mode 100644
index 0000000000..58d39564f5
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/PromiseTest.java
@@ -0,0 +1,316 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+import org.chromium.base.Promise.UnhandledRejectionException;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for {@link Promise}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class PromiseTest {
+ // We need a simple mutable reference type for testing.
+ private static class Value {
+ private int mValue;
+
+ public int get() {
+ return mValue;
+ }
+
+ public void set(int value) {
+ mValue = value;
+ }
+ }
+
+ /** Tests that the callback is called on fulfillment. */
+ @Test
+ public void callback() {
+ final Value value = new Value();
+
+ Promise<Integer> promise = new Promise<Integer>();
+ promise.then(PromiseTest.<Integer>setValue(value, 1));
+
+ assertEquals(value.get(), 0);
+
+ promise.fulfill(new Integer(1));
+ assertEquals(value.get(), 1);
+ }
+
+ /** Tests that multiple callbacks are called. */
+ @Test
+ public void multipleCallbacks() {
+ final Value value = new Value();
+
+ Promise<Integer> promise = new Promise<Integer>();
+ Callback<Integer> callback = new Callback<Integer>() {
+ @Override
+ public void onResult(Integer result) {
+ value.set(value.get() + 1);
+ }
+ };
+ promise.then(callback);
+ promise.then(callback);
+
+ assertEquals(value.get(), 0);
+
+ promise.fulfill(new Integer(0));
+ assertEquals(value.get(), 2);
+ }
+
+ /** Tests that a callback is called immediately when given to a fulfilled Promise. */
+ @Test
+ public void callbackOnFulfilled() {
+ final Value value = new Value();
+
+ Promise<Integer> promise = Promise.fulfilled(new Integer(0));
+ assertEquals(value.get(), 0);
+
+ promise.then(PromiseTest.<Integer>setValue(value, 1));
+
+ assertEquals(value.get(), 1);
+ }
+
+ /** Tests that promises can chain synchronous functions correctly. */
+ @Test
+ public void promiseChaining() {
+ Promise<Integer> promise = new Promise<Integer>();
+ final Value value = new Value();
+
+ promise.then(new Promise.Function<Integer, String>(){
+ @Override
+ public String apply(Integer arg) {
+ return arg.toString();
+ }
+ }).then(new Promise.Function<String, String>(){
+ @Override
+ public String apply(String arg) {
+ return arg + arg;
+ }
+ }).then(new Callback<String>() {
+ @Override
+ public void onResult(String result) {
+ value.set(result.length());
+ }
+ });
+
+ promise.fulfill(new Integer(123));
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertEquals(6, value.get());
+ }
+
+ /** Tests that promises can chain asynchronous functions correctly. */
+ @Test
+ public void promiseChainingAsyncFunctions() {
+ Promise<Integer> promise = new Promise<Integer>();
+ final Value value = new Value();
+
+ final Promise<String> innerPromise = new Promise<String>();
+
+ promise.then(new Promise.AsyncFunction<Integer, String>() {
+ @Override
+ public Promise<String> apply(Integer arg) {
+ return innerPromise;
+ }
+ }).then(new Callback<String>(){
+ @Override
+ public void onResult(String result) {
+ value.set(result.length());
+ }
+ });
+
+ assertEquals(0, value.get());
+
+ promise.fulfill(5);
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertEquals(0, value.get());
+
+ innerPromise.fulfill("abc");
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertEquals(3, value.get());
+ }
+
+ /** Tests that a Promise that does not use its result does not throw on rejection. */
+ @Test
+ public void rejectPromiseNoCallbacks() {
+ Promise<Integer> promise = new Promise<Integer>();
+
+ boolean caught = false;
+ try {
+ promise.reject();
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ } catch (UnhandledRejectionException e) {
+ caught = true;
+ }
+ assertFalse(caught);
+ }
+
+ /** Tests that a Promise that uses its result throws on rejection if it has no handler. */
+ @Test
+ public void rejectPromiseNoHandler() {
+ Promise<Integer> promise = new Promise<Integer>();
+ promise.then(PromiseTest.<Integer>identity()).then(PromiseTest.<Integer>pass());
+
+ boolean caught = false;
+ try {
+ promise.reject();
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ } catch (UnhandledRejectionException e) {
+ caught = true;
+ }
+ assertTrue(caught);
+ }
+
+ /** Tests that a Promise that handles rejection does not throw on rejection. */
+ @Test
+ public void rejectPromiseHandled() {
+ Promise<Integer> promise = new Promise<Integer>();
+ promise.then(PromiseTest.<Integer>identity())
+ .then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>pass());
+
+ boolean caught = false;
+ try {
+ promise.reject();
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ } catch (UnhandledRejectionException e) {
+ caught = true;
+ }
+ assertFalse(caught);
+ }
+
+ /** Tests that rejections carry the exception information. */
+ @Test
+ public void rejectionInformation() {
+ Promise<Integer> promise = new Promise<Integer>();
+ promise.then(PromiseTest.<Integer>pass());
+
+ String message = "Promise Test";
+ try {
+ promise.reject(new NegativeArraySizeException(message));
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ fail();
+ } catch (UnhandledRejectionException e) {
+ assertTrue(e.getCause() instanceof NegativeArraySizeException);
+ assertEquals(e.getCause().getMessage(), message);
+ }
+ }
+
+ /** Tests that rejections propagate. */
+ @Test
+ public void rejectionChaining() {
+ final Value value = new Value();
+ Promise<Integer> promise = new Promise<Integer>();
+
+ Promise<Integer> result =
+ promise.then(PromiseTest.<Integer>identity()).then(PromiseTest.<Integer>identity());
+
+ result.then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5));
+
+ promise.reject(new Exception());
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+ assertEquals(value.get(), 5);
+ assertTrue(result.isRejected());
+ }
+
+ /** Tests that Promises get rejected if a Function throws. */
+ @Test
+ public void rejectOnThrow() {
+ Value value = new Value();
+ Promise<Integer> promise = new Promise<Integer>();
+ promise.then(new Promise.Function<Integer, Integer>() {
+ @Override
+ public Integer apply(Integer argument) {
+ throw new IllegalArgumentException();
+ }
+ }).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5));
+
+ promise.fulfill(0);
+
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertEquals(value.get(), 5);
+ }
+
+ /** Tests that Promises get rejected if an AsyncFunction throws. */
+ @Test
+ public void rejectOnAsyncThrow() {
+ Value value = new Value();
+ Promise<Integer> promise = new Promise<Integer>();
+
+ promise.then(new Promise.AsyncFunction<Integer, Integer>() {
+ @Override
+ public Promise<Integer> apply(Integer argument) {
+ throw new IllegalArgumentException();
+ }
+ }).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5));
+
+ promise.fulfill(0);
+
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertEquals(value.get(), 5);
+ }
+
+ /** Tests that Promises get rejected if an AsyncFunction rejects. */
+ @Test
+ public void rejectOnAsyncReject() {
+ Value value = new Value();
+ Promise<Integer> promise = new Promise<Integer>();
+ final Promise<Integer> inner = new Promise<Integer>();
+
+ promise.then(new Promise.AsyncFunction<Integer, Integer>() {
+ @Override
+ public Promise<Integer> apply(Integer argument) {
+ return inner;
+ }
+ }).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5));
+
+ promise.fulfill(0);
+
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertEquals(value.get(), 0);
+
+ inner.reject();
+
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertEquals(value.get(), 5);
+ }
+
+ /** Convenience method that returns a Callback that does nothing with its result. */
+ private static <T> Callback<T> pass() {
+ return new Callback<T>() {
+ @Override
+ public void onResult(T result) {}
+ };
+ }
+
+ /** Convenience method that returns a Function that just passes through its argument. */
+ private static <T> Promise.Function<T, T> identity() {
+ return new Promise.Function<T, T>() {
+ @Override
+ public T apply(T argument) {
+ return argument;
+ }
+ };
+ }
+
+ /** Convenience method that returns a Callback that sets the given Value on execution. */
+ private static <T> Callback<T> setValue(final Value toSet, final int value) {
+ return new Callback<T>() {
+ @Override
+ public void onResult(T result) {
+ toSet.set(value);
+ }
+ };
+ }
+}
diff --git a/base/android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java b/base/android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java
new file mode 100644
index 0000000000..1249a66acd
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java
@@ -0,0 +1,356 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.memory;
+
+import android.content.ComponentCallbacks2;
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+import org.chromium.base.MemoryPressureLevel;
+import org.chromium.base.Supplier;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for MemoryPressureMonitor.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class MemoryPressureMonitorTest {
+ private MemoryPressureMonitor mMonitor;
+
+ private static class TestPressureCallback implements MemoryPressureCallback {
+ private Integer mReportedPressure;
+
+ public void assertCalledWith(@MemoryPressureLevel int expectedPressure) {
+ Assert.assertNotNull("Callback was not called", mReportedPressure);
+ Assert.assertEquals(expectedPressure, (int) mReportedPressure);
+ }
+
+ public void assertNotCalled() {
+ Assert.assertNull(mReportedPressure);
+ }
+
+ public void reset() {
+ mReportedPressure = null;
+ }
+
+ @Override
+ public void onPressure(@MemoryPressureLevel int pressure) {
+ assertNotCalled();
+ mReportedPressure = pressure;
+ }
+ }
+
+ private static class TestPressureSupplier implements Supplier<Integer> {
+ private @MemoryPressureLevel Integer mPressure;
+ private boolean mIsCalled;
+
+ public TestPressureSupplier(@MemoryPressureLevel Integer pressure) {
+ mPressure = pressure;
+ }
+
+ @Override
+ public @MemoryPressureLevel Integer get() {
+ assertNotCalled();
+ mIsCalled = true;
+ return mPressure;
+ }
+
+ public void assertCalled() {
+ Assert.assertTrue(mIsCalled);
+ }
+
+ public void assertNotCalled() {
+ Assert.assertFalse(mIsCalled);
+ }
+ }
+
+ private static final int THROTTLING_INTERVAL_MS = 1000;
+
+ @Before
+ public void setUp() {
+ // Explicitly set main thread as UiThread. Other places rely on that.
+ ThreadUtils.setUiThread(Looper.getMainLooper());
+
+ // Pause main thread to get control over when tasks are run (see runUiThreadFor()).
+ ShadowLooper.pauseMainLooper();
+
+ mMonitor = new MemoryPressureMonitor(THROTTLING_INTERVAL_MS);
+ mMonitor.setCurrentPressureSupplierForTesting(null);
+ }
+
+ /**
+ * Runs all UiThread tasks posted |delayMs| in the future.
+ * @param delayMs
+ */
+ private void runUiThreadFor(long delayMs) {
+ ShadowLooper.idleMainLooper(delayMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ @SmallTest
+ public void testTrimLevelTranslation() {
+ Integer[][] trimLevelToPressureMap = {//
+ // Levels >= TRIM_MEMORY_COMPLETE map to CRITICAL.
+ {ComponentCallbacks2.TRIM_MEMORY_COMPLETE + 1, MemoryPressureLevel.CRITICAL},
+ {ComponentCallbacks2.TRIM_MEMORY_COMPLETE, MemoryPressureLevel.CRITICAL},
+
+ // TRIM_MEMORY_RUNNING_CRITICAL maps to CRITICAL.
+ {ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL, MemoryPressureLevel.CRITICAL},
+
+ // Levels < TRIM_MEMORY_COMPLETE && >= TRIM_MEMORY_BACKGROUND map to MODERATE.
+ {ComponentCallbacks2.TRIM_MEMORY_COMPLETE - 1, MemoryPressureLevel.MODERATE},
+ {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND + 1, MemoryPressureLevel.MODERATE},
+ {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND, MemoryPressureLevel.MODERATE},
+
+ // Other levels are not mapped.
+ {ComponentCallbacks2.TRIM_MEMORY_BACKGROUND - 1, null},
+ {ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, null},
+ {ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE, null},
+ {ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN, null}};
+ for (Integer[] trimLevelAndPressure : trimLevelToPressureMap) {
+ int trimLevel = trimLevelAndPressure[0];
+ Integer expectedPressure = trimLevelAndPressure[1];
+ Integer actualPressure = MemoryPressureMonitor.memoryPressureFromTrimLevel(trimLevel);
+ Assert.assertEquals(expectedPressure, actualPressure);
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testThrottleInterval() {
+ TestPressureCallback callback = new TestPressureCallback();
+ mMonitor.setReportingCallbackForTesting(callback);
+
+ // First notification should go through.
+ mMonitor.notifyPressure(MemoryPressureLevel.NONE);
+ callback.assertCalledWith(MemoryPressureLevel.NONE);
+
+ callback.reset();
+
+ // This one should be throttled.
+ mMonitor.notifyPressure(MemoryPressureLevel.NONE);
+ callback.assertNotCalled();
+
+ runUiThreadFor(THROTTLING_INTERVAL_MS - 1);
+
+ // We're still within the throttling interval, so this notification should
+ // still be throttled.
+ mMonitor.notifyPressure(MemoryPressureLevel.NONE);
+ callback.assertNotCalled();
+
+ runUiThreadFor(1);
+
+ // We're past the throttling interval at this point, so this notification
+ // should go through.
+ mMonitor.notifyPressure(MemoryPressureLevel.NONE);
+ callback.assertCalledWith(MemoryPressureLevel.NONE);
+ }
+
+ @Test
+ @SmallTest
+ public void testChangeNotIgnored() {
+ TestPressureCallback callback = new TestPressureCallback();
+ mMonitor.setReportingCallbackForTesting(callback);
+
+ mMonitor.notifyPressure(MemoryPressureLevel.NONE);
+ callback.assertCalledWith(MemoryPressureLevel.NONE);
+
+ callback.reset();
+
+ // Second notification is throttled, but should be reported after the
+ // throttling interval ends.
+ mMonitor.notifyPressure(MemoryPressureLevel.MODERATE);
+ callback.assertNotCalled();
+
+ runUiThreadFor(THROTTLING_INTERVAL_MS - 1);
+
+ // Shouldn't be reported at this point.
+ callback.assertNotCalled();
+
+ runUiThreadFor(1);
+
+ callback.assertCalledWith(MemoryPressureLevel.MODERATE);
+ }
+
+ @Test
+ @SmallTest
+ public void testNoopChangeIgnored() {
+ TestPressureCallback callback = new TestPressureCallback();
+ mMonitor.setReportingCallbackForTesting(callback);
+
+ mMonitor.notifyPressure(MemoryPressureLevel.NONE);
+ callback.assertCalledWith(MemoryPressureLevel.NONE);
+
+ callback.reset();
+
+ // Report MODERATE and then NONE, so that the throttling interval finishes with the
+ // same pressure that started it (i.e. NONE). In this case MODERATE should be ignored.
+ mMonitor.notifyPressure(MemoryPressureLevel.MODERATE);
+ mMonitor.notifyPressure(MemoryPressureLevel.NONE);
+
+ runUiThreadFor(THROTTLING_INTERVAL_MS);
+
+ callback.assertNotCalled();
+ }
+
+ @Test
+ @SmallTest
+ public void testPollingInitiallyDisabled() {
+ TestPressureSupplier pressureSupplier =
+ new TestPressureSupplier(MemoryPressureLevel.MODERATE);
+ mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier);
+
+ mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL);
+ runUiThreadFor(THROTTLING_INTERVAL_MS);
+
+ // We finished the interval with CRITICAL, but since polling is disabled, we shouldn't
+ // poll the current pressure.
+ pressureSupplier.assertNotCalled();
+ }
+
+ @Test
+ @SmallTest
+ public void testEnablePollingPolls() {
+ TestPressureCallback callback = new TestPressureCallback();
+ mMonitor.setReportingCallbackForTesting(callback);
+
+ TestPressureSupplier pressureSupplier =
+ new TestPressureSupplier(MemoryPressureLevel.MODERATE);
+ mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier);
+
+ mMonitor.enablePolling();
+
+ // When polling is enabled, current pressure should be retrieved and reported.
+ pressureSupplier.assertCalled();
+ callback.assertCalledWith(MemoryPressureLevel.MODERATE);
+ }
+
+ @Test
+ @SmallTest
+ public void testNullSupplierResultIgnored() {
+ TestPressureCallback callback = new TestPressureCallback();
+ mMonitor.setReportingCallbackForTesting(callback);
+
+ TestPressureSupplier pressureSupplier = new TestPressureSupplier(null);
+ mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier);
+
+ mMonitor.enablePolling();
+
+ // The pressure supplier should be called, but its null result should be ignored.
+ pressureSupplier.assertCalled();
+ callback.assertNotCalled();
+ }
+
+ @Test
+ @SmallTest
+ public void testEnablePollingRespectsThrottling() {
+ TestPressureSupplier pressureSupplier =
+ new TestPressureSupplier(MemoryPressureLevel.MODERATE);
+ mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier);
+
+ mMonitor.notifyPressure(MemoryPressureLevel.NONE);
+
+ // The notification above started a throttling interval, so we shouldn't ask for the
+ // current pressure when polling is enabled.
+ mMonitor.enablePolling();
+
+ pressureSupplier.assertNotCalled();
+ }
+
+ @Test
+ @SmallTest
+ public void testPollingIfCRITICAL() {
+ TestPressureCallback callback = new TestPressureCallback();
+ mMonitor.setReportingCallbackForTesting(callback);
+
+ TestPressureSupplier pressureSupplier =
+ new TestPressureSupplier(MemoryPressureLevel.MODERATE);
+ mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier);
+
+ mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL);
+ callback.reset();
+
+ mMonitor.enablePolling();
+
+ runUiThreadFor(THROTTLING_INTERVAL_MS - 1);
+
+ // Pressure should be polled after the interval ends, not before.
+ pressureSupplier.assertNotCalled();
+
+ runUiThreadFor(1);
+
+ // We started and finished the throttling interval with CRITICAL pressure, so
+ // we should poll the current pressure at the end of the interval.
+ pressureSupplier.assertCalled();
+ callback.assertCalledWith(MemoryPressureLevel.MODERATE);
+ }
+
+ @Test
+ @SmallTest
+ public void testNoPollingIfNotCRITICAL() {
+ TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE);
+ mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier);
+
+ mMonitor.notifyPressure(MemoryPressureLevel.MODERATE);
+
+ mMonitor.enablePolling();
+
+ runUiThreadFor(THROTTLING_INTERVAL_MS);
+
+ // We started and finished the throttling interval with non-CRITICAL pressure,
+ // so no polling should take place.
+ pressureSupplier.assertNotCalled();
+ }
+
+ @Test
+ @SmallTest
+ public void testNoPollingIfChangedToCRITICAL() {
+ TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE);
+ mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier);
+
+ mMonitor.notifyPressure(MemoryPressureLevel.MODERATE);
+ mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL);
+
+ mMonitor.enablePolling();
+
+ runUiThreadFor(THROTTLING_INTERVAL_MS);
+
+ // We finished the throttling interval with CRITITCAL, but started with MODERATE,
+ // so no polling should take place.
+ pressureSupplier.assertNotCalled();
+ }
+
+ @Test
+ @SmallTest
+ public void testDisablePolling() {
+ TestPressureSupplier pressureSupplier = new TestPressureSupplier(MemoryPressureLevel.NONE);
+ mMonitor.setCurrentPressureSupplierForTesting(pressureSupplier);
+
+ mMonitor.notifyPressure(MemoryPressureLevel.CRITICAL);
+
+ mMonitor.enablePolling();
+
+ runUiThreadFor(THROTTLING_INTERVAL_MS - 1);
+
+ // Whether polling is enabled or not should be taken into account only after the interval
+ // finishes, so disabling it here should have the same affect as if it was never enabled.
+ mMonitor.disablePolling();
+
+ runUiThreadFor(1);
+
+ pressureSupplier.assertNotCalled();
+ }
+}
diff --git a/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java b/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java
new file mode 100644
index 0000000000..18fe6ce2ac
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java
@@ -0,0 +1,57 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.metrics.test;
+
+import android.util.Pair;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import org.chromium.base.metrics.RecordHistogram;
+
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of RecordHistogram which does not rely on native and still enables testing of
+ * histogram counts.
+ */
+@Implements(RecordHistogram.class)
+public class ShadowRecordHistogram {
+ private static HashMap<Pair<String, Integer>, Integer> sSamples =
+ new HashMap<Pair<String, Integer>, Integer>();
+
+ @Resetter
+ public static void reset() {
+ sSamples.clear();
+ }
+
+ @Implementation
+ public static void recordCountHistogram(String name, int sample) {
+ Pair<String, Integer> key = Pair.create(name, sample);
+ incrementSampleCount(key);
+ }
+
+ @Implementation
+ public static void recordLongTimesHistogram100(String name, long duration, TimeUnit timeUnit) {
+ Pair<String, Integer> key = Pair.create(name, (int) timeUnit.toMillis(duration));
+ incrementSampleCount(key);
+ }
+
+ @Implementation
+ public static int getHistogramValueCountForTesting(String name, int sample) {
+ Integer i = sSamples.get(Pair.create(name, sample));
+ return (i != null) ? i : 0;
+ }
+
+ private static void incrementSampleCount(Pair<String, Integer> key) {
+ Integer i = sSamples.get(key);
+ if (i == null) {
+ i = 0;
+ }
+ sSamples.put(key, i + 1);
+ }
+}
diff --git a/base/android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java b/base/android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java
new file mode 100644
index 0000000000..5c5bca9121
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java
@@ -0,0 +1,332 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Feature;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/** Unit tests for the ChildConnectionAllocator class. */
+@Config(manifest = Config.NONE)
+@RunWith(BaseRobolectricTestRunner.class)
+public class ChildConnectionAllocatorTest {
+ private static final String TEST_PACKAGE_NAME = "org.chromium.allocator_test";
+
+ private static final int MAX_CONNECTION_NUMBER = 2;
+
+ private static final int FREE_CONNECTION_TEST_CALLBACK_START_FAILED = 1;
+ private static final int FREE_CONNECTION_TEST_CALLBACK_PROCESS_DIED = 2;
+
+ @Mock
+ private ChildProcessConnection.ServiceCallback mServiceCallback;
+
+ static class TestConnectionFactory implements ChildConnectionAllocator.ConnectionFactory {
+ private ComponentName mLastServiceName;
+
+ private ChildProcessConnection mConnection;
+
+ private ChildProcessConnection.ServiceCallback mConnectionServiceCallback;
+
+ @Override
+ public ChildProcessConnection createConnection(Context context, ComponentName serviceName,
+ boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle) {
+ mLastServiceName = serviceName;
+ if (mConnection == null) {
+ mConnection = mock(ChildProcessConnection.class);
+ // Retrieve the ServiceCallback so we can simulate the service process dying.
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ mConnectionServiceCallback =
+ (ChildProcessConnection.ServiceCallback) invocation.getArgument(1);
+ return null;
+ }
+ })
+ .when(mConnection)
+ .start(anyBoolean(), any(ChildProcessConnection.ServiceCallback.class));
+ }
+ return mConnection;
+ }
+
+ public ComponentName getAndResetLastServiceName() {
+ ComponentName serviceName = mLastServiceName;
+ mLastServiceName = null;
+ return serviceName;
+ }
+
+ // Use this method to have a callback invoked when the connection is started on the next
+ // created connection.
+ public void invokeCallbackOnConnectionStart(final boolean onChildStarted,
+ final boolean onStartFailed, final boolean onChildProcessDied) {
+ final ChildProcessConnection connection = mock(ChildProcessConnection.class);
+ mConnection = connection;
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ ChildProcessConnection.ServiceCallback serviceCallback =
+ (ChildProcessConnection.ServiceCallback) invocation.getArgument(1);
+ if (onChildStarted) {
+ serviceCallback.onChildStarted();
+ }
+ if (onStartFailed) {
+ serviceCallback.onChildStartFailed(connection);
+ }
+ if (onChildProcessDied) {
+ serviceCallback.onChildProcessDied(connection);
+ }
+ return null;
+ }
+ })
+ .when(mConnection)
+ .start(anyBoolean(), any(ChildProcessConnection.ServiceCallback.class));
+ }
+
+ public void simulateServiceStartFailed() {
+ mConnectionServiceCallback.onChildStartFailed(mConnection);
+ }
+
+ public void simulateServiceProcessDying() {
+ mConnectionServiceCallback.onChildProcessDied(mConnection);
+ }
+ }
+
+ private final TestConnectionFactory mTestConnectionFactory = new TestConnectionFactory();
+
+ private ChildConnectionAllocator mAllocator;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mAllocator = ChildConnectionAllocator.createForTest(null, TEST_PACKAGE_NAME,
+ "AllocatorTest", MAX_CONNECTION_NUMBER, true /* bindToCaller */,
+ false /* bindAsExternalService */, false /* useStrongBinding */);
+ mAllocator.setConnectionFactoryForTesting(mTestConnectionFactory);
+ }
+
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testPlainAllocate() {
+ assertFalse(mAllocator.anyConnectionAllocated());
+ assertEquals(MAX_CONNECTION_NUMBER, mAllocator.getNumberOfServices());
+
+ ChildProcessConnection connection =
+ mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback);
+ assertNotNull(connection);
+
+ verify(connection, times(1))
+ .start(eq(false) /* useStrongBinding */,
+ any(ChildProcessConnection.ServiceCallback.class));
+ assertTrue(mAllocator.anyConnectionAllocated());
+ }
+
+ /** Tests that different services are created until we reach the max number specified. */
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testAllocateMaxNumber() {
+ assertTrue(mAllocator.isFreeConnectionAvailable());
+ Set<ComponentName> serviceNames = new HashSet<>();
+ for (int i = 0; i < MAX_CONNECTION_NUMBER; i++) {
+ ChildProcessConnection connection = mAllocator.allocate(
+ null /* context */, null /* serviceBundle */, mServiceCallback);
+ assertNotNull(connection);
+ ComponentName serviceName = mTestConnectionFactory.getAndResetLastServiceName();
+ assertFalse(serviceNames.contains(serviceName));
+ serviceNames.add(serviceName);
+ }
+ assertFalse(mAllocator.isFreeConnectionAvailable());
+ assertNull(mAllocator.allocate(
+ null /* context */, null /* serviceBundle */, mServiceCallback));
+ }
+
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testQueueAllocation() {
+ Runnable freeConnectionCallback = mock(Runnable.class);
+ mAllocator = ChildConnectionAllocator.createForTest(freeConnectionCallback,
+ TEST_PACKAGE_NAME, "AllocatorTest", 1, true /* bindToCaller */,
+ false /* bindAsExternalService */, false /* useStrongBinding */);
+ mAllocator.setConnectionFactoryForTesting(mTestConnectionFactory);
+ // Occupy all slots.
+ ChildProcessConnection connection =
+ mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback);
+ assertNotNull(connection);
+ assertFalse(mAllocator.isFreeConnectionAvailable());
+
+ final ChildProcessConnection newConnection[] = new ChildProcessConnection[2];
+ Runnable allocate1 = () -> {
+ newConnection[0] = mAllocator.allocate(
+ null /* context */, null /* serviceBundle */, mServiceCallback);
+ };
+ Runnable allocate2 = () -> {
+ newConnection[1] = mAllocator.allocate(
+ null /* context */, null /* serviceBundle */, mServiceCallback);
+ };
+ mAllocator.queueAllocation(allocate1);
+ mAllocator.queueAllocation(allocate2);
+ verify(freeConnectionCallback, times(1)).run();
+ assertNull(newConnection[0]);
+
+ mTestConnectionFactory.simulateServiceProcessDying();
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertNotNull(newConnection[0]);
+ assertNull(newConnection[1]);
+
+ mTestConnectionFactory.simulateServiceProcessDying();
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertNotNull(newConnection[1]);
+ }
+
+ /**
+ * Tests that the connection is created with the useStrongBinding parameter specified in the
+ * allocator.
+ */
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testStrongBindingParam() {
+ for (boolean useStrongBinding : new boolean[] {true, false}) {
+ ChildConnectionAllocator allocator = ChildConnectionAllocator.createForTest(null,
+ TEST_PACKAGE_NAME, "AllocatorTest", MAX_CONNECTION_NUMBER,
+ true /* bindToCaller */, false /* bindAsExternalService */, useStrongBinding);
+ allocator.setConnectionFactoryForTesting(mTestConnectionFactory);
+ ChildProcessConnection connection = allocator.allocate(
+ null /* context */, null /* serviceBundle */, mServiceCallback);
+ verify(connection, times(0)).start(useStrongBinding, mServiceCallback);
+ }
+ }
+
+ /**
+ * Tests that the various ServiceCallbacks are propagated and posted, so they happen after the
+ * ChildProcessAllocator,allocate() method has returned.
+ */
+ public void runTestWithConnectionCallbacks(
+ boolean onChildStarted, boolean onChildStartFailed, boolean onChildProcessDied) {
+ // We have to pause the Roboletric looper or it'll execute the posted tasks synchronoulsy.
+ ShadowLooper.pauseMainLooper();
+ mTestConnectionFactory.invokeCallbackOnConnectionStart(
+ onChildStarted, onChildStartFailed, onChildProcessDied);
+ ChildProcessConnection connection =
+ mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback);
+ assertNotNull(connection);
+
+ // Callbacks are posted.
+ verify(mServiceCallback, never()).onChildStarted();
+ verify(mServiceCallback, never()).onChildStartFailed(any());
+ verify(mServiceCallback, never()).onChildProcessDied(any());
+ ShadowLooper.unPauseMainLooper();
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ verify(mServiceCallback, times(onChildStarted ? 1 : 0)).onChildStarted();
+ verify(mServiceCallback, times(onChildStartFailed ? 1 : 0)).onChildStartFailed(any());
+ verify(mServiceCallback, times(onChildProcessDied ? 1 : 0)).onChildProcessDied(any());
+ }
+
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testOnChildStartedCallback() {
+ runTestWithConnectionCallbacks(true /* onChildStarted */, false /* onChildStartFailed */,
+ false /* onChildProcessDied */);
+ }
+
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testOnChildStartFailedCallback() {
+ runTestWithConnectionCallbacks(false /* onChildStarted */, true /* onChildStartFailed */,
+ false /* onChildProcessDied */);
+ }
+
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testOnChildProcessDiedCallback() {
+ runTestWithConnectionCallbacks(false /* onChildStarted */, false /* onChildStartFailed */,
+ true /* onChildProcessDied */);
+ }
+
+ /**
+ * Tests that the allocator clears the connection when it fails to bind/process dies.
+ */
+ private void testFreeConnection(int callbackType) {
+ ChildProcessConnection connection =
+ mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback);
+
+ assertNotNull(connection);
+ ComponentName serviceName = mTestConnectionFactory.getAndResetLastServiceName();
+ verify(connection, times(1))
+ .start(eq(false) /* useStrongBinding */,
+ any(ChildProcessConnection.ServiceCallback.class));
+ assertTrue(mAllocator.anyConnectionAllocated());
+ int onChildStartFailedExpectedCount = 0;
+ int onChildProcessDiedExpectedCount = 0;
+ switch (callbackType) {
+ case FREE_CONNECTION_TEST_CALLBACK_START_FAILED:
+ mTestConnectionFactory.simulateServiceStartFailed();
+ onChildStartFailedExpectedCount = 1;
+ break;
+ case FREE_CONNECTION_TEST_CALLBACK_PROCESS_DIED:
+ mTestConnectionFactory.simulateServiceProcessDying();
+ onChildProcessDiedExpectedCount = 1;
+ break;
+ default:
+ fail();
+ break;
+ }
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ assertFalse(mAllocator.anyConnectionAllocated());
+ verify(mServiceCallback, never()).onChildStarted();
+ verify(mServiceCallback, times(onChildStartFailedExpectedCount))
+ .onChildStartFailed(connection);
+ verify(mServiceCallback, times(onChildProcessDiedExpectedCount))
+ .onChildProcessDied(connection);
+
+ // Allocate a new connection to make sure we are not getting the same connection.
+ connection =
+ mAllocator.allocate(null /* context */, null /* serviceBundle */, mServiceCallback);
+ assertNotNull(connection);
+ assertNotEquals(mTestConnectionFactory.getAndResetLastServiceName(), serviceName);
+ }
+
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testFreeConnectionOnChildStartFailed() {
+ testFreeConnection(FREE_CONNECTION_TEST_CALLBACK_START_FAILED);
+ }
+
+ @Test
+ @Feature({"ProcessManagement"})
+ public void testFreeConnectionOnChildProcessDied() {
+ testFreeConnection(FREE_CONNECTION_TEST_CALLBACK_PROCESS_DIED);
+ }
+}
diff --git a/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java b/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java
new file mode 100644
index 0000000000..95474ea1e9
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java
@@ -0,0 +1,425 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.or;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+import org.chromium.base.ChildBindingState;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for ChildProcessConnection. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ChildProcessConnectionTest {
+ private static class ChildServiceConnectionMock
+ implements ChildProcessConnection.ChildServiceConnection {
+ private final Intent mBindIntent;
+ private final ChildProcessConnection.ChildServiceConnectionDelegate mDelegate;
+ private boolean mBound;
+
+ public ChildServiceConnectionMock(
+ Intent bindIntent, ChildProcessConnection.ChildServiceConnectionDelegate delegate) {
+ mBindIntent = bindIntent;
+ mDelegate = delegate;
+ }
+
+ @Override
+ public boolean bind() {
+ mBound = true;
+ return true;
+ }
+
+ @Override
+ public void unbind() {
+ mBound = false;
+ }
+
+ @Override
+ public boolean isBound() {
+ return mBound;
+ }
+
+ public void notifyServiceConnected(IBinder service) {
+ mDelegate.onServiceConnected(service);
+ }
+
+ public void notifyServiceDisconnected() {
+ mDelegate.onServiceDisconnected();
+ }
+
+ public Intent getBindIntent() {
+ return mBindIntent;
+ }
+ };
+
+ private final ChildProcessConnection.ChildServiceConnectionFactory mServiceConnectionFactory =
+ new ChildProcessConnection.ChildServiceConnectionFactory() {
+ @Override
+ public ChildProcessConnection.ChildServiceConnection createConnection(
+ Intent bindIntent, int bindFlags,
+ ChildProcessConnection.ChildServiceConnectionDelegate delegate) {
+ ChildServiceConnectionMock connection =
+ spy(new ChildServiceConnectionMock(bindIntent, delegate));
+ if (mFirstServiceConnection == null) {
+ mFirstServiceConnection = connection;
+ }
+ return connection;
+ }
+ };
+
+ @Mock
+ private ChildProcessConnection.ServiceCallback mServiceCallback;
+
+ @Mock
+ private ChildProcessConnection.ConnectionCallback mConnectionCallback;
+
+ private IChildProcessService mIChildProcessService;
+
+ private Binder mChildProcessServiceBinder;
+
+ private ChildServiceConnectionMock mFirstServiceConnection;
+
+ // Parameters captured from the IChildProcessService.setupConnection() call
+ private Bundle mConnectionBundle;
+ private ICallbackInt mConnectionPidCallback;
+ private IBinder mConnectionIBinderCallback;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+
+ mIChildProcessService = mock(IChildProcessService.class);
+ // Capture the parameters passed to the IChildProcessService.setupConnection() call.
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ mConnectionBundle = (Bundle) invocation.getArgument(0);
+ mConnectionPidCallback = (ICallbackInt) invocation.getArgument(1);
+ mConnectionIBinderCallback = (IBinder) invocation.getArgument(2);
+ return null;
+ }
+ })
+ .when(mIChildProcessService)
+ .setupConnection(
+ or(isNull(), any(Bundle.class)), or(isNull(), any()), or(isNull(), any()));
+
+ mChildProcessServiceBinder = new Binder();
+ mChildProcessServiceBinder.attachInterface(
+ mIChildProcessService, IChildProcessService.class.getName());
+ }
+
+ private ChildProcessConnection createDefaultTestConnection() {
+ return createTestConnection(false /* bindToCaller */, false /* bindAsExternalService */,
+ null /* serviceBundle */);
+ }
+
+ private ChildProcessConnection createTestConnection(
+ boolean bindToCaller, boolean bindAsExternalService, Bundle serviceBundle) {
+ String packageName = "org.chromium.test";
+ String serviceName = "TestService";
+ return new ChildProcessConnection(null /* context */,
+ new ComponentName(packageName, serviceName), bindToCaller, bindAsExternalService,
+ serviceBundle, mServiceConnectionFactory);
+ }
+
+ @Test
+ public void testStrongBinding() {
+ ChildProcessConnection connection = createDefaultTestConnection();
+ connection.start(true /* useStrongBinding */, null /* serviceCallback */);
+ assertTrue(connection.isStrongBindingBound());
+
+ connection = createDefaultTestConnection();
+ connection.start(false /* useStrongBinding */, null /* serviceCallback */);
+ assertFalse(connection.isStrongBindingBound());
+ }
+
+ @Test
+ public void testServiceBundle() {
+ Bundle serviceBundle = new Bundle();
+ final String intKey = "org.chromium.myInt";
+ final int intValue = 34;
+ final int defaultValue = -1;
+ serviceBundle.putInt(intKey, intValue);
+ String stringKey = "org.chromium.myString";
+ String stringValue = "thirty four";
+ serviceBundle.putString(stringKey, stringValue);
+
+ ChildProcessConnection connection = createTestConnection(
+ false /* bindToCaller */, false /* bindAsExternalService */, serviceBundle);
+ // Start the connection without the ChildServiceConnection connecting.
+ connection.start(false /* useStrongBinding */, null /* serviceCallback */);
+ assertNotNull(mFirstServiceConnection);
+ Intent bindIntent = mFirstServiceConnection.getBindIntent();
+ assertNotNull(bindIntent);
+ assertEquals(intValue, bindIntent.getIntExtra(intKey, defaultValue));
+ assertEquals(stringValue, bindIntent.getStringExtra(stringKey));
+ }
+
+ @Test
+ public void testServiceStartsSuccessfully() {
+ ChildProcessConnection connection = createDefaultTestConnection();
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, mServiceCallback);
+ Assert.assertTrue(connection.isModerateBindingBound());
+ Assert.assertFalse(connection.didOnServiceConnectedForTesting());
+ verify(mServiceCallback, never()).onChildStarted();
+ verify(mServiceCallback, never()).onChildStartFailed(any());
+ verify(mServiceCallback, never()).onChildProcessDied(any());
+
+ // The service connects.
+ mFirstServiceConnection.notifyServiceConnected(null /* iBinder */);
+ Assert.assertTrue(connection.didOnServiceConnectedForTesting());
+ verify(mServiceCallback, times(1)).onChildStarted();
+ verify(mServiceCallback, never()).onChildStartFailed(any());
+ verify(mServiceCallback, never()).onChildProcessDied(any());
+ }
+
+ @Test
+ public void testServiceStartsAndFailsToBind() {
+ ChildProcessConnection connection = createDefaultTestConnection();
+ assertNotNull(mFirstServiceConnection);
+ // Note we use doReturn so the actual bind() method is not called (it would with
+ // when(mFirstServiceConnection.bind()).thenReturn(false).
+ doReturn(false).when(mFirstServiceConnection).bind();
+ connection.start(false /* useStrongBinding */, mServiceCallback);
+
+ Assert.assertFalse(connection.isModerateBindingBound());
+ Assert.assertFalse(connection.didOnServiceConnectedForTesting());
+ verify(mServiceCallback, never()).onChildStarted();
+ verify(mServiceCallback, never()).onChildStartFailed(any());
+ verify(mServiceCallback, times(1)).onChildProcessDied(connection);
+ }
+
+ @Test
+ public void testServiceStops() {
+ ChildProcessConnection connection = createDefaultTestConnection();
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, mServiceCallback);
+ mFirstServiceConnection.notifyServiceConnected(null /* iBinder */);
+ connection.stop();
+ verify(mServiceCallback, times(1)).onChildStarted();
+ verify(mServiceCallback, never()).onChildStartFailed(any());
+ verify(mServiceCallback, times(1)).onChildProcessDied(connection);
+ }
+
+ @Test
+ public void testServiceDisconnects() {
+ ChildProcessConnection connection = createDefaultTestConnection();
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, mServiceCallback);
+ mFirstServiceConnection.notifyServiceConnected(null /* iBinder */);
+ mFirstServiceConnection.notifyServiceDisconnected();
+ verify(mServiceCallback, times(1)).onChildStarted();
+ verify(mServiceCallback, never()).onChildStartFailed(any());
+ verify(mServiceCallback, times(1)).onChildProcessDied(connection);
+ }
+
+ @Test
+ public void testNotBoundToCaller() throws RemoteException {
+ ChildProcessConnection connection = createTestConnection(false /* bindToCaller */,
+ false /* bindAsExternalService */, null /* serviceBundle */);
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, mServiceCallback);
+ mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
+ // Service is started and bindToCallback is not called.
+ verify(mServiceCallback, times(1)).onChildStarted();
+ verify(mServiceCallback, never()).onChildStartFailed(any());
+ verify(mServiceCallback, never()).onChildProcessDied(connection);
+ verify(mIChildProcessService, never()).bindToCaller();
+ }
+
+ @Test
+ public void testBoundToCallerSuccess() throws RemoteException {
+ ChildProcessConnection connection = createTestConnection(true /* bindToCaller */,
+ false /* bindAsExternalService */, null /* serviceBundle */);
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, mServiceCallback);
+ when(mIChildProcessService.bindToCaller()).thenReturn(true);
+ mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
+ // Service is started and bindToCallback is called.
+ verify(mServiceCallback, times(1)).onChildStarted();
+ verify(mServiceCallback, never()).onChildStartFailed(any());
+ verify(mServiceCallback, never()).onChildProcessDied(connection);
+ verify(mIChildProcessService, times(1)).bindToCaller();
+ }
+
+ @Test
+ public void testBoundToCallerFailure() throws RemoteException {
+ ChildProcessConnection connection = createTestConnection(true /* bindToCaller */,
+ false /* bindAsExternalService */, null /* serviceBundle */);
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, mServiceCallback);
+ // Pretend bindToCaller returns false, i.e. the service is already bound to a different
+ // service.
+ when(mIChildProcessService.bindToCaller()).thenReturn(false);
+ mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
+ // Service fails to start.
+ verify(mServiceCallback, never()).onChildStarted();
+ verify(mServiceCallback, times(1)).onChildStartFailed(any());
+ verify(mServiceCallback, never()).onChildProcessDied(connection);
+ verify(mIChildProcessService, times(1)).bindToCaller();
+ }
+
+ @Test
+ public void testSetupConnectionBeforeServiceConnected() throws RemoteException {
+ ChildProcessConnection connection = createDefaultTestConnection();
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, null /* serviceCallback */);
+ connection.setupConnection(
+ null /* connectionBundle */, null /* callback */, mConnectionCallback);
+ verify(mConnectionCallback, never()).onConnected(any());
+ mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
+ ShadowLooper.runUiThreadTasks();
+ assertNotNull(mConnectionPidCallback);
+ mConnectionPidCallback.call(34 /* pid */);
+ verify(mConnectionCallback, times(1)).onConnected(connection);
+ }
+
+ @Test
+ public void testSetupConnectionAfterServiceConnected() throws RemoteException {
+ ChildProcessConnection connection = createDefaultTestConnection();
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, null /* serviceCallback */);
+ mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
+ connection.setupConnection(
+ null /* connectionBundle */, null /* callback */, mConnectionCallback);
+ verify(mConnectionCallback, never()).onConnected(any());
+ ShadowLooper.runUiThreadTasks();
+ assertNotNull(mConnectionPidCallback);
+ mConnectionPidCallback.call(34 /* pid */);
+ verify(mConnectionCallback, times(1)).onConnected(connection);
+ }
+
+ @Test
+ public void testKill() throws RemoteException {
+ ChildProcessConnection connection = createDefaultTestConnection();
+ assertNotNull(mFirstServiceConnection);
+ connection.start(false /* useStrongBinding */, null /* serviceCallback */);
+ mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
+ connection.setupConnection(
+ null /* connectionBundle */, null /* callback */, mConnectionCallback);
+ verify(mConnectionCallback, never()).onConnected(any());
+ ShadowLooper.runUiThreadTasks();
+ assertNotNull(mConnectionPidCallback);
+ mConnectionPidCallback.call(34 /* pid */);
+ verify(mConnectionCallback, times(1)).onConnected(connection);
+
+ // Add strong binding so that connection is oom protected.
+ connection.removeModerateBinding();
+ assertEquals(ChildBindingState.WAIVED, connection.bindingStateCurrentOrWhenDied());
+ connection.addModerateBinding();
+ assertEquals(ChildBindingState.MODERATE, connection.bindingStateCurrentOrWhenDied());
+ connection.addStrongBinding();
+ assertEquals(ChildBindingState.STRONG, connection.bindingStateCurrentOrWhenDied());
+
+ // Kill and verify state.
+ connection.kill();
+ verify(mIChildProcessService).forceKill();
+ assertEquals(ChildBindingState.STRONG, connection.bindingStateCurrentOrWhenDied());
+ Assert.assertTrue(connection.isKilledByUs());
+ }
+
+ @Test
+ public void testBindingStateCounts() throws RemoteException {
+ ChildProcessConnection.resetBindingStateCountsForTesting();
+ ChildProcessConnection connection0 = createDefaultTestConnection();
+ ChildServiceConnectionMock connectionMock0 = mFirstServiceConnection;
+ mFirstServiceConnection = null;
+ ChildProcessConnection connection1 = createDefaultTestConnection();
+ ChildServiceConnectionMock connectionMock1 = mFirstServiceConnection;
+ mFirstServiceConnection = null;
+ ChildProcessConnection connection2 = createDefaultTestConnection();
+ ChildServiceConnectionMock connectionMock2 = mFirstServiceConnection;
+ mFirstServiceConnection = null;
+
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 0, 0});
+
+ connection0.start(false /* useStrongBinding */, null /* serviceCallback */);
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 0});
+
+ connection1.start(true /* useStrongBinding */, null /* serviceCallback */);
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
+
+ connection2.start(false /* useStrongBinding */, null /* serviceCallback */);
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 2, 1});
+
+ Binder binder0 = new Binder();
+ Binder binder1 = new Binder();
+ Binder binder2 = new Binder();
+ binder0.attachInterface(mIChildProcessService, IChildProcessService.class.getName());
+ binder1.attachInterface(mIChildProcessService, IChildProcessService.class.getName());
+ binder2.attachInterface(mIChildProcessService, IChildProcessService.class.getName());
+ connectionMock0.notifyServiceConnected(binder0);
+ connectionMock1.notifyServiceConnected(binder1);
+ connectionMock2.notifyServiceConnected(binder2);
+ ShadowLooper.runUiThreadTasks();
+
+ // Add and remove moderate binding works as expected.
+ connection2.removeModerateBinding();
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 1, 1, 1});
+ connection2.addModerateBinding();
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 2, 1});
+
+ // Add and remove strong binding works as expected.
+ connection0.addStrongBinding();
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 2});
+ connection0.removeStrongBinding();
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 2, 1});
+
+ // Stopped connection should no longe update.
+ connection0.stop();
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
+ assertArrayEquals(
+ connection1.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
+
+ connection2.removeModerateBinding();
+ assertArrayEquals(
+ connection0.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
+ assertArrayEquals(
+ connection1.bindingStateCountsCurrentOrWhenDied(), new int[] {0, 1, 0, 1});
+ }
+}
diff --git a/base/android/library_loader/library_load_from_apk_status_codes.h b/base/android/library_loader/library_load_from_apk_status_codes.h
new file mode 100644
index 0000000000..8910d4800d
--- /dev/null
+++ b/base/android/library_loader/library_load_from_apk_status_codes.h
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOAD_FROM_APK_STATUS_CODES_H_
+#define BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOAD_FROM_APK_STATUS_CODES_H_
+
+namespace base {
+namespace android {
+
+namespace {
+
+// This enum must be kept in sync with the LibraryLoadFromApkStatus enum in
+// tools/metrics/histograms/histograms.xml.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base.library_loader
+enum LibraryLoadFromApkStatusCodes {
+ // The loader was unable to determine whether the functionality is supported.
+ LIBRARY_LOAD_FROM_APK_STATUS_CODES_UNKNOWN = 0,
+
+ // The device does not support loading a library directly from the APK file
+ // (obsolete).
+ LIBRARY_LOAD_FROM_APK_STATUS_CODES_NOT_SUPPORTED_OBSOLETE = 1,
+
+ // The device supports loading a library directly from the APK file.
+ // (obsolete).
+ LIBRARY_LOAD_FROM_APK_STATUS_CODES_SUPPORTED_OBSOLETE = 2,
+
+ // The Chromium library was successfully loaded directly from the APK file.
+ LIBRARY_LOAD_FROM_APK_STATUS_CODES_SUCCESSFUL = 3,
+
+ // The Chromium library was successfully loaded using the unpack library
+ // fallback because it was compressed or not page aligned in the APK file.
+ LIBRARY_LOAD_FROM_APK_STATUS_CODES_USED_UNPACK_LIBRARY_FALLBACK = 4,
+
+ // The Chromium library was successfully loaded using the no map executable
+ // support fallback (obsolete).
+ LIBRARY_LOAD_FROM_APK_STATUS_CODES_USED_NO_MAP_EXEC_SUPPORT_FALLBACK_OBSOLETE
+ = 5,
+
+ // End sentinel.
+ LIBRARY_LOAD_FROM_APK_STATUS_CODES_MAX = 6,
+};
+
+} // namespace
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOAD_FROM_APK_STATUS_CODES_H_
diff --git a/base/android/library_loader/library_loader_hooks.cc b/base/android/library_loader/library_loader_hooks.cc
new file mode 100644
index 0000000000..40bff5e337
--- /dev/null
+++ b/base/android/library_loader/library_loader_hooks.cc
@@ -0,0 +1,254 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/library_loader/library_loader_hooks.h"
+
+#include "base/android/jni_string.h"
+#include "base/android/library_loader/anchor_functions_buildflags.h"
+#include "base/android/library_loader/library_load_from_apk_status_codes.h"
+#include "base/android/library_loader/library_prefetcher.h"
+#include "base/android/orderfile/orderfile_buildflags.h"
+#include "base/at_exit.h"
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "jni/LibraryLoader_jni.h"
+
+#if BUILDFLAG(ORDERFILE_INSTRUMENTATION)
+#include "base/android/orderfile/orderfile_instrumentation.h"
+#endif
+
+namespace base {
+namespace android {
+
+namespace {
+
+base::AtExitManager* g_at_exit_manager = NULL;
+const char* g_library_version_number = "";
+LibraryLoadedHook* g_registration_callback = NULL;
+NativeInitializationHook* g_native_initialization_hook = NULL;
+
+enum RendererHistogramCode {
+ // Renderer load at fixed address success, fail, or not attempted.
+ // Renderers do not attempt to load at at fixed address if on a
+ // low-memory device on which browser load at fixed address has already
+ // failed.
+ LFA_SUCCESS = 0,
+ LFA_BACKOFF_USED = 1,
+ LFA_NOT_ATTEMPTED = 2,
+
+ // End sentinel, also used as nothing-pending indicator.
+ MAX_RENDERER_HISTOGRAM_CODE = 3,
+ NO_PENDING_HISTOGRAM_CODE = MAX_RENDERER_HISTOGRAM_CODE
+};
+
+enum BrowserHistogramCode {
+ // Non-low-memory random address browser loads.
+ NORMAL_LRA_SUCCESS = 0,
+
+ // Low-memory browser loads at fixed address, success or fail.
+ LOW_MEMORY_LFA_SUCCESS = 1,
+ LOW_MEMORY_LFA_BACKOFF_USED = 2,
+
+ MAX_BROWSER_HISTOGRAM_CODE = 3,
+};
+
+RendererHistogramCode g_renderer_histogram_code = NO_PENDING_HISTOGRAM_CODE;
+
+// Indicate whether g_library_preloader_renderer_histogram_code is valid
+bool g_library_preloader_renderer_histogram_code_registered = false;
+
+// The return value of NativeLibraryPreloader.loadLibrary() in child processes,
+// it is initialized to the invalid value which shouldn't showup in UMA report.
+int g_library_preloader_renderer_histogram_code = -1;
+
+// The amount of time, in milliseconds, that it took to load the shared
+// libraries in the renderer. Set in
+// RegisterChromiumAndroidLinkerRendererHistogram.
+long g_renderer_library_load_time_ms = 0;
+
+void RecordChromiumAndroidLinkerRendererHistogram() {
+ if (g_renderer_histogram_code == NO_PENDING_HISTOGRAM_CODE)
+ return;
+ // Record and release the pending histogram value.
+ UMA_HISTOGRAM_ENUMERATION("ChromiumAndroidLinker.RendererStates",
+ g_renderer_histogram_code,
+ MAX_RENDERER_HISTOGRAM_CODE);
+ g_renderer_histogram_code = NO_PENDING_HISTOGRAM_CODE;
+
+ // Record how long it took to load the shared libraries.
+ UMA_HISTOGRAM_TIMES("ChromiumAndroidLinker.RendererLoadTime",
+ base::TimeDelta::FromMilliseconds(g_renderer_library_load_time_ms));
+}
+
+void RecordLibraryPreloaderRendereHistogram() {
+ if (g_library_preloader_renderer_histogram_code_registered) {
+ UmaHistogramSparse("Android.NativeLibraryPreloader.Result.Renderer",
+ g_library_preloader_renderer_histogram_code);
+ }
+}
+
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+bool ShouldDoOrderfileMemoryOptimization() {
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kOrderfileMemoryOptimization);
+}
+#endif
+
+} // namespace
+
+static void JNI_LibraryLoader_RegisterChromiumAndroidLinkerRendererHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jboolean requested_shared_relro,
+ jboolean load_at_fixed_address_failed,
+ jlong library_load_time_ms) {
+ // Note a pending histogram value for later recording.
+ if (requested_shared_relro) {
+ g_renderer_histogram_code = load_at_fixed_address_failed
+ ? LFA_BACKOFF_USED : LFA_SUCCESS;
+ } else {
+ g_renderer_histogram_code = LFA_NOT_ATTEMPTED;
+ }
+
+ g_renderer_library_load_time_ms = library_load_time_ms;
+}
+
+static void JNI_LibraryLoader_RecordChromiumAndroidLinkerBrowserHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jboolean is_using_browser_shared_relros,
+ jboolean load_at_fixed_address_failed,
+ jint library_load_from_apk_status,
+ jlong library_load_time_ms) {
+ // For low-memory devices, record whether or not we successfully loaded the
+ // browser at a fixed address. Otherwise just record a normal invocation.
+ BrowserHistogramCode histogram_code;
+ if (is_using_browser_shared_relros) {
+ histogram_code = load_at_fixed_address_failed
+ ? LOW_MEMORY_LFA_BACKOFF_USED : LOW_MEMORY_LFA_SUCCESS;
+ } else {
+ histogram_code = NORMAL_LRA_SUCCESS;
+ }
+ UMA_HISTOGRAM_ENUMERATION("ChromiumAndroidLinker.BrowserStates",
+ histogram_code,
+ MAX_BROWSER_HISTOGRAM_CODE);
+
+ // Record the device support for loading a library directly from the APK file.
+ UMA_HISTOGRAM_ENUMERATION(
+ "ChromiumAndroidLinker.LibraryLoadFromApkStatus",
+ static_cast<LibraryLoadFromApkStatusCodes>(library_load_from_apk_status),
+ LIBRARY_LOAD_FROM_APK_STATUS_CODES_MAX);
+
+ // Record how long it took to load the shared libraries.
+ UMA_HISTOGRAM_TIMES("ChromiumAndroidLinker.BrowserLoadTime",
+ base::TimeDelta::FromMilliseconds(library_load_time_ms));
+}
+
+static void JNI_LibraryLoader_RecordLibraryPreloaderBrowserHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint status) {
+ UmaHistogramSparse("Android.NativeLibraryPreloader.Result.Browser", status);
+}
+
+static void JNI_LibraryLoader_RegisterLibraryPreloaderRendererHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint status) {
+ g_library_preloader_renderer_histogram_code = status;
+ g_library_preloader_renderer_histogram_code_registered = true;
+}
+
+void SetNativeInitializationHook(
+ NativeInitializationHook native_initialization_hook) {
+ g_native_initialization_hook = native_initialization_hook;
+}
+
+void RecordLibraryLoaderRendererHistograms() {
+ RecordChromiumAndroidLinkerRendererHistogram();
+ RecordLibraryPreloaderRendereHistogram();
+}
+
+void SetLibraryLoadedHook(LibraryLoadedHook* func) {
+ g_registration_callback = func;
+}
+
+static jboolean JNI_LibraryLoader_LibraryLoaded(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint library_process_type) {
+#if BUILDFLAG(ORDERFILE_INSTRUMENTATION)
+ orderfile::StartDelayedDump();
+#endif
+
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+ if (ShouldDoOrderfileMemoryOptimization()) {
+ NativeLibraryPrefetcher::MadviseForOrderfile();
+ }
+#endif
+
+ if (g_native_initialization_hook &&
+ !g_native_initialization_hook(
+ static_cast<LibraryProcessType>(library_process_type)))
+ return false;
+ if (g_registration_callback && !g_registration_callback(env, nullptr))
+ return false;
+ return true;
+}
+
+void LibraryLoaderExitHook() {
+ if (g_at_exit_manager) {
+ delete g_at_exit_manager;
+ g_at_exit_manager = NULL;
+ }
+}
+
+static void JNI_LibraryLoader_ForkAndPrefetchNativeLibrary(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+ return NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary(
+ ShouldDoOrderfileMemoryOptimization());
+#endif
+}
+
+static jint JNI_LibraryLoader_PercentageOfResidentNativeLibraryCode(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+ return NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode();
+#else
+ return -1;
+#endif
+}
+
+static void JNI_LibraryLoader_PeriodicallyCollectResidency(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+ NativeLibraryPrefetcher::PeriodicallyCollectResidency();
+#else
+ LOG(WARNING) << "Collecting residency is not supported.";
+#endif
+}
+
+void SetVersionNumber(const char* version_number) {
+ g_library_version_number = strdup(version_number);
+}
+
+ScopedJavaLocalRef<jstring> JNI_LibraryLoader_GetVersionNumber(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ return ConvertUTF8ToJavaString(env, g_library_version_number);
+}
+
+void InitAtExitManager() {
+ g_at_exit_manager = new base::AtExitManager();
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/library_loader/library_loader_hooks.h b/base/android/library_loader/library_loader_hooks.h
new file mode 100644
index 0000000000..9dff26ac24
--- /dev/null
+++ b/base/android/library_loader/library_loader_hooks.h
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOADER_HOOKS_H_
+#define BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOADER_HOOKS_H_
+
+#include <jni.h>
+
+#include "base/base_export.h"
+#include "base/callback.h"
+
+namespace base {
+namespace android {
+
+// The process the shared library is loaded in.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base.library_loader
+enum LibraryProcessType {
+ // The LibraryLoad has not been initialized.
+ PROCESS_UNINITIALIZED = 0,
+ // Shared library is running in browser process.
+ PROCESS_BROWSER = 1,
+ // Shared library is running in child process.
+ PROCESS_CHILD = 2,
+ // Shared library is running in the app that uses webview.
+ PROCESS_WEBVIEW = 3,
+ // Shared library is running in child process as part of webview.
+ PROCESS_WEBVIEW_CHILD = 4,
+};
+
+typedef bool NativeInitializationHook(LibraryProcessType library_process_type);
+
+BASE_EXPORT void SetNativeInitializationHook(
+ NativeInitializationHook native_initialization_hook);
+
+// Record any pending renderer histogram value as histograms. Pending values
+// are set by RegisterChromiumAndroidLinkerRendererHistogram and
+// RegisterLibraryPreloaderRendererHistogram.
+BASE_EXPORT void RecordLibraryLoaderRendererHistograms();
+
+// Typedef for hook function to be called (indirectly from Java) once the
+// libraries are loaded. The hook function should register the JNI bindings
+// required to start the application. It should return true for success and
+// false for failure.
+// Note: this can't use base::Callback because there is no way of initializing
+// the default callback without using static objects, which we forbid.
+typedef bool LibraryLoadedHook(JNIEnv* env,
+ jclass clazz);
+
+// Set the hook function to be called (from Java) once the libraries are loaded.
+// SetLibraryLoadedHook may only be called from JNI_OnLoad. The hook function
+// should register the JNI bindings required to start the application.
+
+BASE_EXPORT void SetLibraryLoadedHook(LibraryLoadedHook* func);
+
+// Pass the version name to the loader. This used to check that the library
+// version matches the version expected by Java before completing JNI
+// registration.
+// Note: argument must remain valid at least until library loading is complete.
+BASE_EXPORT void SetVersionNumber(const char* version_number);
+
+// Call on exit to delete the AtExitManager which OnLibraryLoadedOnUIThread
+// created.
+BASE_EXPORT void LibraryLoaderExitHook();
+
+// Initialize AtExitManager, this must be done at the begining of loading
+// shared library.
+void InitAtExitManager();
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_LIBRARY_LOADER_LIBRARY_LOADER_HOOKS_H_
diff --git a/base/android/library_loader/library_prefetcher.cc b/base/android/library_loader/library_prefetcher.cc
new file mode 100644
index 0000000000..2ccf6a080a
--- /dev/null
+++ b/base/android/library_loader/library_prefetcher.cc
@@ -0,0 +1,333 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/library_loader/library_prefetcher.h"
+
+#include <stddef.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <atomic>
+#include <cstdlib>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/android/library_loader/anchor_functions.h"
+#include "base/android/orderfile/orderfile_buildflags.h"
+#include "base/bits.h"
+#include "base/files/file.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(ORDERFILE_INSTRUMENTATION)
+#include "base/android/orderfile/orderfile_instrumentation.h"
+#endif
+
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+
+namespace base {
+namespace android {
+
+namespace {
+
+// Android defines the background priority to this value since at least 2009
+// (see Process.java).
+constexpr int kBackgroundPriority = 10;
+// Valid for all Android architectures.
+constexpr size_t kPageSize = 4096;
+
+// Reads a byte per page between |start| and |end| to force it into the page
+// cache.
+// Heap allocations, syscalls and library functions are not allowed in this
+// function.
+// Returns true for success.
+#if defined(ADDRESS_SANITIZER)
+// Disable AddressSanitizer instrumentation for this function. It is touching
+// memory that hasn't been allocated by the app, though the addresses are
+// valid. Furthermore, this takes place in a child process. See crbug.com/653372
+// for the context.
+__attribute__((no_sanitize_address))
+#endif
+void Prefetch(size_t start, size_t end) {
+ unsigned char* start_ptr = reinterpret_cast<unsigned char*>(start);
+ unsigned char* end_ptr = reinterpret_cast<unsigned char*>(end);
+ unsigned char dummy = 0;
+ for (unsigned char* ptr = start_ptr; ptr < end_ptr; ptr += kPageSize) {
+ // Volatile is required to prevent the compiler from eliminating this
+ // loop.
+ dummy ^= *static_cast<volatile unsigned char*>(ptr);
+ }
+}
+
+// Populates the per-page residency between |start| and |end| in |residency|. If
+// successful, |residency| has the size of |end| - |start| in pages.
+// Returns true for success.
+bool Mincore(size_t start, size_t end, std::vector<unsigned char>* residency) {
+ if (start % kPageSize || end % kPageSize)
+ return false;
+ size_t size = end - start;
+ size_t size_in_pages = size / kPageSize;
+ if (residency->size() != size_in_pages)
+ residency->resize(size_in_pages);
+ int err = HANDLE_EINTR(
+ mincore(reinterpret_cast<void*>(start), size, &(*residency)[0]));
+ PLOG_IF(ERROR, err) << "mincore() failed";
+ return !err;
+}
+
+// Returns the start and end of .text, aligned to the lower and upper page
+// boundaries, respectively.
+std::pair<size_t, size_t> GetTextRange() {
+ // |kStartOfText| may not be at the beginning of a page, since .plt can be
+ // before it, yet in the same mapping for instance.
+ size_t start_page = kStartOfText - kStartOfText % kPageSize;
+ // Set the end to the page on which the beginning of the last symbol is. The
+ // actual symbol may spill into the next page by a few bytes, but this is
+ // outside of the executable code range anyway.
+ size_t end_page = base::bits::Align(kEndOfText, kPageSize);
+ return {start_page, end_page};
+}
+
+// Returns the start and end pages of the unordered section of .text, aligned to
+// lower and upper page boundaries, respectively.
+std::pair<size_t, size_t> GetOrderedTextRange() {
+ size_t start_page = kStartOfOrderedText - kStartOfOrderedText % kPageSize;
+ // kEndOfUnorderedText is not considered ordered, but the byte immediately
+ // before is considered ordered and so can not be contained in the start page.
+ size_t end_page = base::bits::Align(kEndOfOrderedText, kPageSize);
+ return {start_page, end_page};
+}
+
+// Calls madvise(advice) on the specified range. Does nothing if the range is
+// empty.
+void MadviseOnRange(const std::pair<size_t, size_t>& range, int advice) {
+ if (range.first >= range.second) {
+ return;
+ }
+ size_t size = range.second - range.first;
+ int err = madvise(reinterpret_cast<void*>(range.first), size, advice);
+ if (err) {
+ PLOG(ERROR) << "madvise() failed";
+ }
+}
+
+// Timestamp in ns since Unix Epoch, and residency, as returned by mincore().
+struct TimestampAndResidency {
+ uint64_t timestamp_nanos;
+ std::vector<unsigned char> residency;
+
+ TimestampAndResidency(uint64_t timestamp_nanos,
+ std::vector<unsigned char>&& residency)
+ : timestamp_nanos(timestamp_nanos), residency(residency) {}
+};
+
+// Returns true for success.
+bool CollectResidency(size_t start,
+ size_t end,
+ std::vector<TimestampAndResidency>* data) {
+ // Not using base::TimeTicks() to not call too many base:: symbol that would
+ // pollute the reached symbols dumps.
+ struct timespec ts;
+ if (HANDLE_EINTR(clock_gettime(CLOCK_MONOTONIC, &ts))) {
+ PLOG(ERROR) << "Cannot get the time.";
+ return false;
+ }
+ uint64_t now =
+ static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec;
+ std::vector<unsigned char> residency;
+ if (!Mincore(start, end, &residency))
+ return false;
+
+ data->emplace_back(now, std::move(residency));
+ return true;
+}
+
+void DumpResidency(size_t start,
+ size_t end,
+ std::unique_ptr<std::vector<TimestampAndResidency>> data) {
+ auto path = base::FilePath(
+ base::StringPrintf("/data/local/tmp/chrome/residency-%d.txt", getpid()));
+ auto file =
+ base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ if (!file.IsValid()) {
+ PLOG(ERROR) << "Cannot open file to dump the residency data "
+ << path.value();
+ return;
+ }
+
+ // First line: start-end of text range.
+ CHECK(IsOrderingSane());
+ CHECK_LT(start, kStartOfText);
+ CHECK_LT(kEndOfText, end);
+ auto start_end = base::StringPrintf("%" PRIuS " %" PRIuS "\n",
+ kStartOfText - start, kEndOfText - start);
+ file.WriteAtCurrentPos(start_end.c_str(), start_end.size());
+
+ for (const auto& data_point : *data) {
+ auto timestamp =
+ base::StringPrintf("%" PRIu64 " ", data_point.timestamp_nanos);
+ file.WriteAtCurrentPos(timestamp.c_str(), timestamp.size());
+
+ std::vector<char> dump;
+ dump.reserve(data_point.residency.size() + 1);
+ for (auto c : data_point.residency)
+ dump.push_back(c ? '1' : '0');
+ dump[dump.size() - 1] = '\n';
+ file.WriteAtCurrentPos(&dump[0], dump.size());
+ }
+}
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+// Used for "LibraryLoader.PrefetchDetailedStatus".
+enum class PrefetchStatus {
+ kSuccess = 0,
+ kWrongOrdering = 1,
+ kForkFailed = 2,
+ kChildProcessCrashed = 3,
+ kChildProcessKilled = 4,
+ kMaxValue = kChildProcessKilled
+};
+
+PrefetchStatus ForkAndPrefetch(bool ordered_only) {
+ if (!IsOrderingSane()) {
+ LOG(WARNING) << "Incorrect code ordering";
+ return PrefetchStatus::kWrongOrdering;
+ }
+
+ // Looking for ranges is done before the fork, to avoid syscalls and/or memory
+ // allocations in the forked process. The child process inherits the lock
+ // state of its parent thread. It cannot rely on being able to acquire any
+ // lock (unless special care is taken in a pre-fork handler), including being
+ // able to call malloc().
+ //
+ // Always prefetch the ordered section first, as it's reached early during
+ // startup, and not necessarily located at the beginning of .text.
+ std::vector<std::pair<size_t, size_t>> ranges = {GetOrderedTextRange()};
+ if (!ordered_only)
+ ranges.push_back(GetTextRange());
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ setpriority(PRIO_PROCESS, 0, kBackgroundPriority);
+ // _exit() doesn't call the atexit() handlers.
+ for (const auto& range : ranges) {
+ Prefetch(range.first, range.second);
+ }
+ _exit(EXIT_SUCCESS);
+ } else {
+ if (pid < 0) {
+ return PrefetchStatus::kForkFailed;
+ }
+ int status;
+ const pid_t result = HANDLE_EINTR(waitpid(pid, &status, 0));
+ if (result == pid) {
+ if (WIFEXITED(status))
+ return PrefetchStatus::kSuccess;
+ if (WIFSIGNALED(status)) {
+ int signal = WTERMSIG(status);
+ switch (signal) {
+ case SIGSEGV:
+ case SIGBUS:
+ return PrefetchStatus::kChildProcessCrashed;
+ break;
+ case SIGKILL:
+ case SIGTERM:
+ default:
+ return PrefetchStatus::kChildProcessKilled;
+ }
+ }
+ }
+ // Should not happen. Per man waitpid(2), errors are:
+ // - EINTR: handled.
+ // - ECHILD if the process doesn't have an unwaited-for child with this PID.
+ // - EINVAL.
+ return PrefetchStatus::kChildProcessKilled;
+ }
+}
+
+} // namespace
+
+// static
+void NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary(bool ordered_only) {
+#if BUILDFLAG(ORDERFILE_INSTRUMENTATION)
+ // Avoid forking with orderfile instrumentation because the child process
+ // would create a dump as well.
+ return;
+#endif
+
+ PrefetchStatus status = ForkAndPrefetch(ordered_only);
+ UMA_HISTOGRAM_BOOLEAN("LibraryLoader.PrefetchStatus",
+ status == PrefetchStatus::kSuccess);
+ UMA_HISTOGRAM_ENUMERATION("LibraryLoader.PrefetchDetailedStatus", status);
+ if (status != PrefetchStatus::kSuccess) {
+ LOG(WARNING) << "Cannot prefetch the library. status = "
+ << static_cast<int>(status);
+ }
+}
+
+// static
+int NativeLibraryPrefetcher::PercentageOfResidentCode(size_t start,
+ size_t end) {
+ size_t total_pages = 0;
+ size_t resident_pages = 0;
+
+ std::vector<unsigned char> residency;
+ bool ok = Mincore(start, end, &residency);
+ if (!ok)
+ return -1;
+ total_pages += residency.size();
+ resident_pages += std::count_if(residency.begin(), residency.end(),
+ [](unsigned char x) { return x & 1; });
+ if (total_pages == 0)
+ return -1;
+ return static_cast<int>((100 * resident_pages) / total_pages);
+}
+
+// static
+int NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode() {
+ if (!IsOrderingSane()) {
+ LOG(WARNING) << "Incorrect code ordering";
+ return -1;
+ }
+ const auto& range = GetTextRange();
+ return PercentageOfResidentCode(range.first, range.second);
+}
+
+// static
+void NativeLibraryPrefetcher::PeriodicallyCollectResidency() {
+ CHECK_EQ(static_cast<long>(kPageSize), sysconf(_SC_PAGESIZE));
+
+ const auto& range = GetTextRange();
+ auto data = std::make_unique<std::vector<TimestampAndResidency>>();
+ for (int i = 0; i < 60; ++i) {
+ if (!CollectResidency(range.first, range.second, data.get()))
+ return;
+ usleep(2e5);
+ }
+ DumpResidency(range.first, range.second, std::move(data));
+}
+
+// static
+void NativeLibraryPrefetcher::MadviseForOrderfile() {
+ CHECK(IsOrderingSane());
+ LOG(WARNING) << "Performing experimental madvise from orderfile information";
+ // First MADV_RANDOM on all of text, then turn the ordered text range back to
+ // normal. The ordered range may be placed anywhere within .text.
+ MadviseOnRange(GetTextRange(), MADV_RANDOM);
+ MadviseOnRange(GetOrderedTextRange(), MADV_NORMAL);
+}
+
+} // namespace android
+} // namespace base
+#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
diff --git a/base/android/library_loader/library_prefetcher.h b/base/android/library_loader/library_prefetcher.h
new file mode 100644
index 0000000000..29f294ef62
--- /dev/null
+++ b/base/android/library_loader/library_prefetcher.h
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_LIBRARY_LOADER_LIBRARY_PREFETCHER_H_
+#define BASE_ANDROID_LIBRARY_LOADER_LIBRARY_PREFETCHER_H_
+
+#include <jni.h>
+
+#include <stdint.h>
+
+#include "base/android/library_loader/anchor_functions_buildflags.h"
+#include "base/base_export.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+
+namespace base {
+namespace android {
+
+// Forks and waits for a process prefetching the native library. This is done in
+// a forked process for the following reasons:
+// - Isolating the main process from mistakes in getting the address range, only
+// crashing the forked process in case of mistake.
+// - Not inflating the memory used by the main process uselessly, which could
+// increase its likelihood to be killed.
+// The forked process has background priority and, since it is not declared to
+// the Android runtime, can be killed at any time, which is not an issue here.
+class BASE_EXPORT NativeLibraryPrefetcher {
+ public:
+ // Finds the executable code range, forks a low priority process pre-fetching
+ // it wait()s for the process to exit or die. If ordered_only is true, only
+ // the ordered section is prefetched. See GetOrdrderedTextRange() in
+ // library_prefetcher.cc.
+ static void ForkAndPrefetchNativeLibrary(bool ordered_only);
+
+ // Returns the percentage of the native library code currently resident in
+ // memory, or -1 in case of error.
+ static int PercentageOfResidentNativeLibraryCode();
+
+ // Collects residency for the native library executable multiple times, then
+ // dumps it to disk.
+ static void PeriodicallyCollectResidency();
+
+ // Calls madvise() on the native library executable, using orderfile
+ // information to decide how to advise each part of the library.
+ static void MadviseForOrderfile();
+
+ private:
+ // Returns the percentage of [start, end] currently resident in
+ // memory, or -1 in case of error.
+ static int PercentageOfResidentCode(size_t start, size_t end);
+
+ FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
+ TestPercentageOfResidentCode);
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(NativeLibraryPrefetcher);
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
+
+#endif // BASE_ANDROID_LIBRARY_LOADER_LIBRARY_PREFETCHER_H_
diff --git a/base/android/library_loader/library_prefetcher_unittest.cc b/base/android/library_loader/library_prefetcher_unittest.cc
new file mode 100644
index 0000000000..ebb0d48faf
--- /dev/null
+++ b/base/android/library_loader/library_prefetcher_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/library_loader/library_prefetcher.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include "base/android/library_loader/anchor_functions_buildflags.h"
+#include "base/memory/shared_memory.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+namespace base {
+namespace android {
+
+// Fails with ASAN, crbug.com/570423.
+#if !defined(ADDRESS_SANITIZER)
+namespace {
+const size_t kPageSize = 4096;
+} // namespace
+
+TEST(NativeLibraryPrefetcherTest, TestPercentageOfResidentCode) {
+ size_t length = 4 * kPageSize;
+ base::SharedMemory shared_mem;
+ ASSERT_TRUE(shared_mem.CreateAndMapAnonymous(length));
+ void* address = shared_mem.memory();
+ size_t start = reinterpret_cast<size_t>(address);
+ size_t end = start + length;
+
+ // Remove everything.
+ ASSERT_EQ(0, madvise(address, length, MADV_DONTNEED));
+ EXPECT_EQ(0, NativeLibraryPrefetcher::PercentageOfResidentCode(start, end));
+
+ // Get everything back.
+ ASSERT_EQ(0, mlock(address, length));
+ EXPECT_EQ(100, NativeLibraryPrefetcher::PercentageOfResidentCode(start, end));
+ munlock(address, length);
+}
+#endif // !defined(ADDRESS_SANITIZER)
+
+} // namespace android
+} // namespace base
+#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
diff --git a/base/android/linker/config.gni b/base/android/linker/config.gni
new file mode 100644
index 0000000000..27793ffe6e
--- /dev/null
+++ b/base/android/linker/config.gni
@@ -0,0 +1,13 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/config.gni")
+import("//build/config/compiler/compiler.gni")
+import("//build/config/sanitizers/sanitizers.gni")
+
+# Chromium linker doesn't reliably support loading multiple libraries;
+# disable for component builds, see crbug.com/657093.
+# Chromium linker causes instrumentation to return incorrect results.
+chromium_linker_supported =
+ !is_component_build && !enable_profiling && !use_order_profiling && !is_asan
diff --git a/base/android/linker/linker_jni.cc b/base/android/linker/linker_jni.cc
new file mode 100644
index 0000000000..ba632db683
--- /dev/null
+++ b/base/android/linker/linker_jni.cc
@@ -0,0 +1,696 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is the Android-specific Chromium linker, a tiny shared library
+// implementing a custom dynamic linker that can be used to load the
+// real Chromium libraries.
+
+// The main point of this linker is to be able to share the RELRO
+// section of libchrome.so (or equivalent) between renderer processes.
+
+// This source code *cannot* depend on anything from base/ or the C++
+// STL, to keep the final library small, and avoid ugly dependency issues.
+
+#include <android/log.h>
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include "build/build_config.h"
+
+#include <crazy_linker.h>
+
+// Set this to 1 to enable debug traces to the Android log.
+// Note that LOG() from "base/logging.h" cannot be used, since it is
+// in base/ which hasn't been loaded yet.
+#define DEBUG 0
+
+#define TAG "cr_ChromiumAndroidLinker"
+
+#if DEBUG
+#define LOG_INFO(FORMAT, ...) \
+ __android_log_print(ANDROID_LOG_INFO, TAG, "%s: " FORMAT, __FUNCTION__, \
+ ##__VA_ARGS__)
+#else
+#define LOG_INFO(FORMAT, ...) ((void)0)
+#endif
+#define LOG_ERROR(FORMAT, ...) \
+ __android_log_print(ANDROID_LOG_ERROR, TAG, "%s: " FORMAT, __FUNCTION__, \
+ ##__VA_ARGS__)
+
+#define UNUSED __attribute__((unused))
+
+// See commentary in crazy_linker_elf_loader.cpp for the effect of setting
+// this. If changing there, change here also.
+//
+// For more, see:
+// https://crbug.com/504410
+#define RESERVE_BREAKPAD_GUARD_REGION 1
+
+#if defined(ARCH_CPU_X86)
+// Dalvik JIT generated code doesn't guarantee 16-byte stack alignment on
+// x86 - use force_align_arg_pointer to realign the stack at the JNI
+// boundary. https://crbug.com/655248
+#define JNI_GENERATOR_EXPORT \
+ extern "C" __attribute__((visibility("default"), force_align_arg_pointer))
+#else
+#define JNI_GENERATOR_EXPORT extern "C" __attribute__((visibility("default")))
+#endif
+
+namespace chromium_android_linker {
+
+namespace {
+
+// Larger than the largest library we might attempt to load.
+constexpr size_t kAddressSpaceReservationSize = 192 * 1024 * 1024;
+
+// Size of any Breakpad guard region. 16MB is comfortably larger than the
+// ~6MB relocation packing of the current 64-bit libchrome.so, the largest we
+// expect to encounter.
+#if RESERVE_BREAKPAD_GUARD_REGION
+constexpr size_t kBreakpadGuardRegionBytes = 16 * 1024 * 1024;
+#endif
+
+// A simple scoped UTF String class that can be initialized from
+// a Java jstring handle. Modeled like std::string, which cannot
+// be used here.
+class String {
+ public:
+ String(JNIEnv* env, jstring str);
+
+ inline ~String() { ::free(ptr_); }
+
+ inline const char* c_str() const { return ptr_ ? ptr_ : ""; }
+ inline size_t size() const { return size_; }
+
+ private:
+ char* ptr_;
+ size_t size_;
+};
+
+// Simple scoped UTF String class constructor.
+String::String(JNIEnv* env, jstring str) {
+ size_ = env->GetStringUTFLength(str);
+ ptr_ = static_cast<char*>(::malloc(size_ + 1));
+
+ // Note: This runs before browser native code is loaded, and so cannot
+ // rely on anything from base/. This means that we must use
+ // GetStringUTFChars() and not base::android::ConvertJavaStringToUTF8().
+ //
+ // GetStringUTFChars() suffices because the only strings used here are
+ // paths to APK files or names of shared libraries, all of which are
+ // plain ASCII, defined and hard-coded by the Chromium Android build.
+ //
+ // For more: see
+ // https://crbug.com/508876
+ //
+ // Note: GetStringUTFChars() returns Java UTF-8 bytes. This is good
+ // enough for the linker though.
+ const char* bytes = env->GetStringUTFChars(str, nullptr);
+ ::memcpy(ptr_, bytes, size_);
+ ptr_[size_] = '\0';
+
+ env->ReleaseStringUTFChars(str, bytes);
+}
+
+// Find the jclass JNI reference corresponding to a given |class_name|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*clazz|.
+bool InitClassReference(JNIEnv* env, const char* class_name, jclass* clazz) {
+ *clazz = env->FindClass(class_name);
+ if (!*clazz) {
+ LOG_ERROR("Could not find class for %s", class_name);
+ return false;
+ }
+ return true;
+}
+
+// Initialize a jfieldID corresponding to the field of a given |clazz|,
+// with name |field_name| and signature |field_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*field_id|.
+bool InitFieldId(JNIEnv* env,
+ jclass clazz,
+ const char* field_name,
+ const char* field_sig,
+ jfieldID* field_id) {
+ *field_id = env->GetFieldID(clazz, field_name, field_sig);
+ if (!*field_id) {
+ LOG_ERROR("Could not find ID for field '%s'", field_name);
+ return false;
+ }
+ LOG_INFO("Found ID %p for field '%s'", *field_id, field_name);
+ return true;
+}
+
+// Initialize a jmethodID corresponding to the static method of a given
+// |clazz|, with name |method_name| and signature |method_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*method_id|.
+bool InitStaticMethodId(JNIEnv* env,
+ jclass clazz,
+ const char* method_name,
+ const char* method_sig,
+ jmethodID* method_id) {
+ *method_id = env->GetStaticMethodID(clazz, method_name, method_sig);
+ if (!*method_id) {
+ LOG_ERROR("Could not find ID for static method '%s'", method_name);
+ return false;
+ }
+ LOG_INFO("Found ID %p for static method '%s'", *method_id, method_name);
+ return true;
+}
+
+// Initialize a jfieldID corresponding to the static field of a given |clazz|,
+// with name |field_name| and signature |field_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*field_id|.
+bool InitStaticFieldId(JNIEnv* env,
+ jclass clazz,
+ const char* field_name,
+ const char* field_sig,
+ jfieldID* field_id) {
+ *field_id = env->GetStaticFieldID(clazz, field_name, field_sig);
+ if (!*field_id) {
+ LOG_ERROR("Could not find ID for static field '%s'", field_name);
+ return false;
+ }
+ LOG_INFO("Found ID %p for static field '%s'", *field_id, field_name);
+ return true;
+}
+
+// Initialize a jint corresponding to the static integer field of a class
+// with class name |class_name| and field name |field_name|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*value|.
+bool InitStaticInt(JNIEnv* env,
+ const char* class_name,
+ const char* field_name,
+ jint* value) {
+ jclass clazz;
+ if (!InitClassReference(env, class_name, &clazz))
+ return false;
+
+ jfieldID field_id;
+ if (!InitStaticFieldId(env, clazz, field_name, "I", &field_id))
+ return false;
+
+ *value = env->GetStaticIntField(clazz, field_id);
+ LOG_INFO("Found value %d for class '%s', static field '%s'",
+ *value, class_name, field_name);
+
+ return true;
+}
+
+// A class used to model the field IDs of the org.chromium.base.Linker
+// LibInfo inner class, used to communicate data with the Java side
+// of the linker.
+struct LibInfo_class {
+ jfieldID load_address_id;
+ jfieldID load_size_id;
+ jfieldID relro_start_id;
+ jfieldID relro_size_id;
+ jfieldID relro_fd_id;
+
+ // Initialize an instance.
+ bool Init(JNIEnv* env) {
+ jclass clazz;
+ if (!InitClassReference(
+ env, "org/chromium/base/library_loader/Linker$LibInfo", &clazz)) {
+ return false;
+ }
+
+ return InitFieldId(env, clazz, "mLoadAddress", "J", &load_address_id) &&
+ InitFieldId(env, clazz, "mLoadSize", "J", &load_size_id) &&
+ InitFieldId(env, clazz, "mRelroStart", "J", &relro_start_id) &&
+ InitFieldId(env, clazz, "mRelroSize", "J", &relro_size_id) &&
+ InitFieldId(env, clazz, "mRelroFd", "I", &relro_fd_id);
+ }
+
+ void SetLoadInfo(JNIEnv* env,
+ jobject library_info_obj,
+ size_t load_address,
+ size_t load_size) {
+ env->SetLongField(library_info_obj, load_address_id, load_address);
+ env->SetLongField(library_info_obj, load_size_id, load_size);
+ }
+
+ void SetRelroInfo(JNIEnv* env,
+ jobject library_info_obj,
+ size_t relro_start,
+ size_t relro_size,
+ int relro_fd) {
+ env->SetLongField(library_info_obj, relro_start_id, relro_start);
+ env->SetLongField(library_info_obj, relro_size_id, relro_size);
+ env->SetIntField(library_info_obj, relro_fd_id, relro_fd);
+ }
+
+ // Use this instance to convert a RelroInfo reference into
+ // a crazy_library_info_t.
+ void GetRelroInfo(JNIEnv* env,
+ jobject library_info_obj,
+ size_t* relro_start,
+ size_t* relro_size,
+ int* relro_fd) {
+ if (relro_start) {
+ *relro_start = static_cast<size_t>(
+ env->GetLongField(library_info_obj, relro_start_id));
+ }
+
+ if (relro_size) {
+ *relro_size = static_cast<size_t>(
+ env->GetLongField(library_info_obj, relro_size_id));
+ }
+
+ if (relro_fd) {
+ *relro_fd = env->GetIntField(library_info_obj, relro_fd_id);
+ }
+ }
+};
+
+// Variable containing LibInfo for the loaded library.
+LibInfo_class s_lib_info_fields;
+
+// Return true iff |address| is a valid address for the target CPU.
+inline bool IsValidAddress(jlong address) {
+ return static_cast<jlong>(static_cast<size_t>(address)) == address;
+}
+
+// The linker uses a single crazy_context_t object created on demand.
+// There is no need to protect this against concurrent access, locking
+// is already handled on the Java side.
+crazy_context_t* GetCrazyContext() {
+ static crazy_context_t* s_crazy_context = nullptr;
+
+ if (!s_crazy_context) {
+ // Create new context.
+ s_crazy_context = crazy_context_create();
+
+ // Ensure libraries located in the same directory as the linker
+ // can be loaded before system ones.
+ crazy_context_add_search_path_for_address(
+ s_crazy_context, reinterpret_cast<void*>(&GetCrazyContext));
+ }
+
+ return s_crazy_context;
+}
+
+// A scoped crazy_library_t that automatically closes the handle
+// on scope exit, unless Release() has been called.
+class ScopedLibrary {
+ public:
+ ScopedLibrary() : lib_(nullptr) {}
+
+ ~ScopedLibrary() {
+ if (lib_)
+ crazy_library_close_with_context(lib_, GetCrazyContext());
+ }
+
+ crazy_library_t* Get() { return lib_; }
+
+ crazy_library_t** GetPtr() { return &lib_; }
+
+ crazy_library_t* Release() {
+ crazy_library_t* ret = lib_;
+ lib_ = nullptr;
+ return ret;
+ }
+
+ private:
+ crazy_library_t* lib_;
+};
+
+// Retrieve the SDK build version and pass it into the crazy linker. This
+// needs to be done early in initialization, before any other crazy linker
+// code is run.
+// |env| is the current JNI environment handle.
+// On success, return true.
+bool InitSDKVersionInfo(JNIEnv* env) {
+ jint value = 0;
+ if (!InitStaticInt(env, "android/os/Build$VERSION", "SDK_INT", &value))
+ return false;
+
+ crazy_set_sdk_build_version(static_cast<int>(value));
+ LOG_INFO("Set SDK build version to %d", static_cast<int>(value));
+
+ return true;
+}
+
+} // namespace
+
+// Use Android ASLR to create a random address into which we expect to be
+// able to load libraries. Note that this is probabilistic; we unmap the
+// address we get from mmap and assume we can re-map into it later. This
+// works the majority of the time. If it doesn't, client code backs out and
+// then loads the library normally at any available address.
+// |env| is the current JNI environment handle, and |clazz| a class.
+// Returns the address selected by ASLR, or 0 on error.
+JNI_GENERATOR_EXPORT jlong
+Java_org_chromium_base_library_1loader_Linker_nativeGetRandomBaseLoadAddress(
+ JNIEnv* env,
+ jclass clazz) {
+ size_t bytes = kAddressSpaceReservationSize;
+
+#if RESERVE_BREAKPAD_GUARD_REGION
+ // Pad the requested address space size for a Breakpad guard region.
+ bytes += kBreakpadGuardRegionBytes;
+#endif
+
+ void* address =
+ mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (address == MAP_FAILED) {
+ LOG_INFO("Random base load address not determinable");
+ return 0;
+ }
+ munmap(address, bytes);
+
+#if RESERVE_BREAKPAD_GUARD_REGION
+ // Allow for a Breakpad guard region ahead of the returned address.
+ address = reinterpret_cast<void*>(
+ reinterpret_cast<uintptr_t>(address) + kBreakpadGuardRegionBytes);
+#endif
+
+ LOG_INFO("Random base load address is %p", address);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(address));
+}
+
+// We identify the abi tag for which the linker is running. This allows
+// us to select the library which matches the abi of the linker.
+
+#if defined(__arm__) && defined(__ARM_ARCH_7A__)
+#define CURRENT_ABI "armeabi-v7a"
+#elif defined(__arm__)
+#define CURRENT_ABI "armeabi"
+#elif defined(__i386__)
+#define CURRENT_ABI "x86"
+#elif defined(__mips__)
+#define CURRENT_ABI "mips"
+#elif defined(__x86_64__)
+#define CURRENT_ABI "x86_64"
+#elif defined(__aarch64__)
+#define CURRENT_ABI "arm64-v8a"
+#else
+#error "Unsupported target abi"
+#endif
+
+// Add a zip archive file path to the context's current search path
+// list. Making it possible to load libraries directly from it.
+JNI_GENERATOR_EXPORT bool
+Java_org_chromium_base_library_1loader_Linker_nativeAddZipArchivePath(
+ JNIEnv* env,
+ jclass clazz,
+ jstring apk_path_obj) {
+ String apk_path(env, apk_path_obj);
+
+ char search_path[512];
+ snprintf(search_path, sizeof(search_path), "%s!lib/" CURRENT_ABI "/",
+ apk_path.c_str());
+
+ crazy_context_t* context = GetCrazyContext();
+ crazy_context_add_search_path(context, search_path);
+ return true;
+}
+
+// Load a library with the chromium linker. This will also call its
+// JNI_OnLoad() method, which shall register its methods. Note that
+// lazy native method resolution will _not_ work after this, because
+// Dalvik uses the system's dlsym() which won't see the new library,
+// so explicit registration is mandatory.
+//
+// |env| is the current JNI environment handle.
+// |clazz| is the static class handle for org.chromium.base.Linker,
+// and is ignored here.
+// |library_name| is the library name (e.g. libfoo.so).
+// |load_address| is an explicit load address.
+// |lib_info_obj| is a LibInfo handle used to communicate information
+// with the Java side.
+// Return true on success.
+JNI_GENERATOR_EXPORT bool
+Java_org_chromium_base_library_1loader_Linker_nativeLoadLibrary(
+ JNIEnv* env,
+ jclass clazz,
+ jstring lib_name_obj,
+ jlong load_address,
+ jobject lib_info_obj) {
+ String library_name(env, lib_name_obj);
+ LOG_INFO("Called for %s, at address 0x%llx", library_name, load_address);
+ crazy_context_t* context = GetCrazyContext();
+
+ if (!IsValidAddress(load_address)) {
+ LOG_ERROR("Invalid address 0x%llx",
+ static_cast<unsigned long long>(load_address));
+ return false;
+ }
+
+ // Set the desired load address (0 means randomize it).
+ crazy_context_set_load_address(context, static_cast<size_t>(load_address));
+
+ ScopedLibrary library;
+ if (!crazy_library_open(library.GetPtr(), library_name.c_str(), context)) {
+ return false;
+ }
+
+ crazy_library_info_t info;
+ if (!crazy_library_get_info(library.Get(), context, &info)) {
+ LOG_ERROR("Could not get library information for %s: %s",
+ library_name.c_str(), crazy_context_get_error(context));
+ return false;
+ }
+
+ // Release library object to keep it alive after the function returns.
+ library.Release();
+
+ s_lib_info_fields.SetLoadInfo(env, lib_info_obj, info.load_address,
+ info.load_size);
+ LOG_INFO("Success loading library %s", library_name.c_str());
+ return true;
+}
+
+// Class holding the Java class and method ID for the Java side Linker
+// postCallbackOnMainThread method.
+struct JavaCallbackBindings_class {
+ jclass clazz;
+ jmethodID method_id;
+
+ // Initialize an instance.
+ bool Init(JNIEnv* env, jclass linker_class) {
+ clazz = reinterpret_cast<jclass>(env->NewGlobalRef(linker_class));
+ return InitStaticMethodId(env, linker_class, "postCallbackOnMainThread",
+ "(J)V", &method_id);
+ }
+};
+
+static JavaCallbackBindings_class s_java_callback_bindings;
+
+// Designated receiver function for callbacks from Java. Its name is known
+// to the Java side.
+// |env| is the current JNI environment handle and is ignored here.
+// |clazz| is the static class handle for org.chromium.base.Linker,
+// and is ignored here.
+// |arg| is a pointer to an allocated crazy_callback_t, deleted after use.
+JNI_GENERATOR_EXPORT void
+Java_org_chromium_base_library_1loader_Linker_nativeRunCallbackOnUiThread(
+ JNIEnv* env,
+ jclass clazz,
+ jlong arg) {
+ crazy_callback_t* callback = reinterpret_cast<crazy_callback_t*>(arg);
+
+ LOG_INFO("Called back from java with handler %p, opaque %p",
+ callback->handler, callback->opaque);
+
+ crazy_callback_run(callback);
+ delete callback;
+}
+
+// Request a callback from Java. The supplied crazy_callback_t is valid only
+// for the duration of this call, so we copy it to a newly allocated
+// crazy_callback_t and then call the Java side's postCallbackOnMainThread.
+// This will call back to to our RunCallbackOnUiThread some time
+// later on the UI thread.
+// |callback_request| is a crazy_callback_t.
+// |poster_opaque| is unused.
+// Returns true if the callback request succeeds.
+static bool PostForLaterExecution(crazy_callback_t* callback_request,
+ void* poster_opaque UNUSED) {
+ crazy_context_t* context = GetCrazyContext();
+
+ JavaVM* vm;
+ int minimum_jni_version;
+ crazy_context_get_java_vm(context, reinterpret_cast<void**>(&vm),
+ &minimum_jni_version);
+
+ // Do not reuse JNIEnv from JNI_OnLoad, but retrieve our own.
+ JNIEnv* env;
+ if (JNI_OK !=
+ vm->GetEnv(reinterpret_cast<void**>(&env), minimum_jni_version)) {
+ LOG_ERROR("Could not create JNIEnv");
+ return false;
+ }
+
+ // Copy the callback; the one passed as an argument may be temporary.
+ crazy_callback_t* callback = new crazy_callback_t();
+ *callback = *callback_request;
+
+ LOG_INFO("Calling back to java with handler %p, opaque %p", callback->handler,
+ callback->opaque);
+
+ jlong arg = static_cast<jlong>(reinterpret_cast<uintptr_t>(callback));
+
+ env->CallStaticVoidMethod(s_java_callback_bindings.clazz,
+ s_java_callback_bindings.method_id, arg);
+
+ // Back out and return false if we encounter a JNI exception.
+ if (env->ExceptionCheck() == JNI_TRUE) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ delete callback;
+ return false;
+ }
+
+ return true;
+}
+
+JNI_GENERATOR_EXPORT jboolean
+Java_org_chromium_base_library_1loader_Linker_nativeCreateSharedRelro(
+ JNIEnv* env,
+ jclass clazz,
+ jstring library_name,
+ jlong load_address,
+ jobject lib_info_obj) {
+ String lib_name(env, library_name);
+
+ LOG_INFO("Called for %s", lib_name.c_str());
+
+ if (!IsValidAddress(load_address)) {
+ LOG_ERROR("Invalid address 0x%llx",
+ static_cast<unsigned long long>(load_address));
+ return false;
+ }
+
+ ScopedLibrary library;
+ if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) {
+ LOG_ERROR("Could not find %s", lib_name.c_str());
+ return false;
+ }
+
+ crazy_context_t* context = GetCrazyContext();
+ size_t relro_start = 0;
+ size_t relro_size = 0;
+ int relro_fd = -1;
+
+ if (!crazy_library_create_shared_relro(
+ library.Get(), context, static_cast<size_t>(load_address),
+ &relro_start, &relro_size, &relro_fd)) {
+ LOG_ERROR("Could not create shared RELRO sharing for %s: %s\n",
+ lib_name.c_str(), crazy_context_get_error(context));
+ return false;
+ }
+
+ s_lib_info_fields.SetRelroInfo(env, lib_info_obj, relro_start, relro_size,
+ relro_fd);
+ return true;
+}
+
+JNI_GENERATOR_EXPORT jboolean
+Java_org_chromium_base_library_1loader_Linker_nativeUseSharedRelro(
+ JNIEnv* env,
+ jclass clazz,
+ jstring library_name,
+ jobject lib_info_obj) {
+ String lib_name(env, library_name);
+
+ LOG_INFO("Called for %s, lib_info_ref=%p", lib_name.c_str(), lib_info_obj);
+
+ ScopedLibrary library;
+ if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) {
+ LOG_ERROR("Could not find %s", lib_name.c_str());
+ return false;
+ }
+
+ crazy_context_t* context = GetCrazyContext();
+ size_t relro_start = 0;
+ size_t relro_size = 0;
+ int relro_fd = -1;
+ s_lib_info_fields.GetRelroInfo(env, lib_info_obj, &relro_start, &relro_size,
+ &relro_fd);
+
+ LOG_INFO("library=%s relro start=%p size=%p fd=%d", lib_name.c_str(),
+ (void*)relro_start, (void*)relro_size, relro_fd);
+
+ if (!crazy_library_use_shared_relro(library.Get(), context, relro_start,
+ relro_size, relro_fd)) {
+ LOG_ERROR("Could not use shared RELRO for %s: %s", lib_name.c_str(),
+ crazy_context_get_error(context));
+ return false;
+ }
+
+ LOG_INFO("Library %s using shared RELRO section!", lib_name.c_str());
+
+ return true;
+}
+
+static bool LinkerJNIInit(JavaVM* vm, JNIEnv* env) {
+ LOG_INFO("Entering");
+
+ // Initialize SDK version info.
+ LOG_INFO("Retrieving SDK version info");
+ if (!InitSDKVersionInfo(env))
+ return false;
+
+ // Find LibInfo field ids.
+ LOG_INFO("Caching field IDs");
+ if (!s_lib_info_fields.Init(env)) {
+ return false;
+ }
+
+ // Register native methods.
+ jclass linker_class;
+ if (!InitClassReference(env, "org/chromium/base/library_loader/Linker",
+ &linker_class))
+ return false;
+
+ // Resolve and save the Java side Linker callback class and method.
+ LOG_INFO("Resolving callback bindings");
+ if (!s_java_callback_bindings.Init(env, linker_class)) {
+ return false;
+ }
+
+ // Save JavaVM* handle into context.
+ crazy_context_t* context = GetCrazyContext();
+ crazy_context_set_java_vm(context, vm, JNI_VERSION_1_4);
+
+ // Register the function that the crazy linker can call to post code
+ // for later execution.
+ crazy_context_set_callback_poster(context, &PostForLaterExecution, nullptr);
+
+ return true;
+}
+
+// JNI_OnLoad() hook called when the linker library is loaded through
+// the regular System.LoadLibrary) API. This shall save the Java VM
+// handle and initialize LibInfo fields.
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ LOG_INFO("Entering");
+ // Get new JNIEnv
+ JNIEnv* env;
+ if (JNI_OK != vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4)) {
+ LOG_ERROR("Could not create JNIEnv");
+ return -1;
+ }
+
+ // Initialize linker base and implementations.
+ if (!LinkerJNIInit(vm, env)) {
+ return -1;
+ }
+
+ LOG_INFO("Done");
+ return JNI_VERSION_1_4;
+}
+
+} // namespace chromium_android_linker
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ return chromium_android_linker::JNI_OnLoad(vm, reserved);
+}
diff --git a/base/android/locale_utils.cc b/base/android/locale_utils.cc
new file mode 100644
index 0000000000..b3a2346366
--- /dev/null
+++ b/base/android/locale_utils.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/locale_utils.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "jni/LocaleUtils_jni.h"
+
+namespace base {
+namespace android {
+
+std::string GetDefaultCountryCode() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return ConvertJavaStringToUTF8(Java_LocaleUtils_getDefaultCountryCode(env));
+}
+
+std::string GetDefaultLocaleString() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> locale =
+ Java_LocaleUtils_getDefaultLocaleString(env);
+ return ConvertJavaStringToUTF8(locale);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/locale_utils.h b/base/android/locale_utils.h
new file mode 100644
index 0000000000..be68890dfe
--- /dev/null
+++ b/base/android/locale_utils.h
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_LOCALE_UTILS_H_
+#define BASE_ANDROID_LOCALE_UTILS_H_
+
+#include <jni.h>
+
+#include <string>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace android {
+
+BASE_EXPORT std::string GetDefaultCountryCode();
+
+// Return the current default locale of the device as string.
+BASE_EXPORT std::string GetDefaultLocaleString();
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_LOCALE_UTILS_H_
diff --git a/base/android/memory_pressure_listener_android.cc b/base/android/memory_pressure_listener_android.cc
new file mode 100644
index 0000000000..cab66e1c65
--- /dev/null
+++ b/base/android/memory_pressure_listener_android.cc
@@ -0,0 +1,30 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/memory_pressure_listener_android.h"
+
+#include "base/memory/memory_pressure_listener.h"
+#include "jni/MemoryPressureListener_jni.h"
+
+using base::android::JavaParamRef;
+
+// Defined and called by JNI.
+static void JNI_MemoryPressureListener_OnMemoryPressure(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ jint memory_pressure_level) {
+ base::MemoryPressureListener::NotifyMemoryPressure(
+ static_cast<base::MemoryPressureListener::MemoryPressureLevel>(
+ memory_pressure_level));
+}
+
+namespace base {
+namespace android {
+
+void MemoryPressureListenerAndroid::Initialize(JNIEnv* env) {
+ Java_MemoryPressureListener_addNativeCallback(env);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/memory_pressure_listener_android.h b/base/android/memory_pressure_listener_android.h
new file mode 100644
index 0000000000..9edfd421ab
--- /dev/null
+++ b/base/android/memory_pressure_listener_android.h
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_
+#define BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_
+
+#include "base/android/jni_android.h"
+#include "base/macros.h"
+
+namespace base {
+namespace android {
+
+// Implements the C++ counter part of MemoryPressureListener.java
+class BASE_EXPORT MemoryPressureListenerAndroid {
+ public:
+ static void Initialize(JNIEnv* env);
+
+ // Called by JNI.
+ static void OnMemoryPressure(int memory_pressure_type);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MemoryPressureListenerAndroid);
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_
diff --git a/base/android/orderfile/BUILD.gn b/base/android/orderfile/BUILD.gn
deleted file mode 100644
index ff0bfff147..0000000000
--- a/base/android/orderfile/BUILD.gn
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/config.gni")
-
-if (use_order_profiling && target_cpu == "arm") {
- static_library("orderfile_instrumentation") {
- sources = [
- "orderfile_instrumentation.cc",
- "orderfile_instrumentation.h",
- ]
- deps = [
- "//base",
- ]
- }
-
- executable("orderfile_instrumentation_perftest") {
- testonly = true
-
- sources = [
- "orderfile_instrumentation_perftest.cc",
- ]
-
- deps = [
- ":orderfile_instrumentation",
- "//base",
- "//testing/gtest",
- "//testing/perf",
- ]
-
- configs -= [ "//build/config/android:default_orderfile_instrumentation" ]
- }
-}
diff --git a/base/android/path_service_android.cc b/base/android/path_service_android.cc
new file mode 100644
index 0000000000..51be530ea3
--- /dev/null
+++ b/base/android/path_service_android.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "jni/PathService_jni.h"
+
+namespace base {
+namespace android {
+
+void JNI_PathService_Override(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ jint what,
+ const JavaParamRef<jstring>& path) {
+ FilePath file_path(ConvertJavaStringToUTF8(env, path));
+ PathService::Override(what, file_path);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/path_utils.cc b/base/android/path_utils.cc
new file mode 100644
index 0000000000..d1f6d43e9d
--- /dev/null
+++ b/base/android/path_utils.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/path_utils.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/files/file_path.h"
+
+#include "jni/PathUtils_jni.h"
+
+namespace base {
+namespace android {
+
+bool GetDataDirectory(FilePath* result) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> path = Java_PathUtils_getDataDirectory(env);
+ FilePath data_path(ConvertJavaStringToUTF8(path));
+ *result = data_path;
+ return true;
+}
+
+bool GetCacheDirectory(FilePath* result) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> path = Java_PathUtils_getCacheDirectory(env);
+ FilePath cache_path(ConvertJavaStringToUTF8(path));
+ *result = cache_path;
+ return true;
+}
+
+bool GetThumbnailCacheDirectory(FilePath* result) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> path =
+ Java_PathUtils_getThumbnailCacheDirectory(env);
+ FilePath thumbnail_cache_path(ConvertJavaStringToUTF8(path));
+ *result = thumbnail_cache_path;
+ return true;
+}
+
+bool GetDownloadsDirectory(FilePath* result) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> path = Java_PathUtils_getDownloadsDirectory(env);
+ FilePath downloads_path(ConvertJavaStringToUTF8(path));
+ *result = downloads_path;
+ return true;
+}
+
+std::vector<FilePath> GetAllPrivateDownloadsDirectories() {
+ std::vector<std::string> dirs;
+ JNIEnv* env = AttachCurrentThread();
+ auto jarray = Java_PathUtils_getAllPrivateDownloadsDirectories(env);
+ base::android::AppendJavaStringArrayToStringVector(env, jarray.obj(), &dirs);
+
+ std::vector<base::FilePath> file_paths;
+ for (const auto& dir : dirs)
+ file_paths.emplace_back(dir);
+ return file_paths;
+}
+
+bool GetNativeLibraryDirectory(FilePath* result) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> path =
+ Java_PathUtils_getNativeLibraryDirectory(env);
+ FilePath library_path(ConvertJavaStringToUTF8(path));
+ *result = library_path;
+ return true;
+}
+
+bool GetExternalStorageDirectory(FilePath* result) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> path =
+ Java_PathUtils_getExternalStorageDirectory(env);
+ FilePath storage_path(ConvertJavaStringToUTF8(path));
+ *result = storage_path;
+ return true;
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/path_utils.h b/base/android/path_utils.h
new file mode 100644
index 0000000000..650999d49a
--- /dev/null
+++ b/base/android/path_utils.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_PATH_UTILS_H_
+#define BASE_ANDROID_PATH_UTILS_H_
+
+#include <jni.h>
+#include <vector>
+
+#include "base/base_export.h"
+
+namespace base {
+
+class FilePath;
+
+namespace android {
+
+// Retrieves the absolute path to the data directory of the current
+// application. The result is placed in the FilePath pointed to by 'result'.
+// This method is dedicated for base_paths_android.c, Using
+// PathService::Get(base::DIR_ANDROID_APP_DATA, ...) gets the data dir.
+BASE_EXPORT bool GetDataDirectory(FilePath* result);
+
+// Retrieves the absolute path to the cache directory. The result is placed in
+// the FilePath pointed to by 'result'. This method is dedicated for
+// base_paths_android.c, Using PathService::Get(base::DIR_CACHE, ...) gets the
+// cache dir.
+BASE_EXPORT bool GetCacheDirectory(FilePath* result);
+
+// Retrieves the path to the thumbnail cache directory. The result is placed
+// in the FilePath pointed to by 'result'.
+BASE_EXPORT bool GetThumbnailCacheDirectory(FilePath* result);
+
+// Retrieves the path to the public downloads directory. The result is placed
+// in the FilePath pointed to by 'result'.
+BASE_EXPORT bool GetDownloadsDirectory(FilePath* result);
+
+// Retrieves the paths to all download directories, including default storage
+// directory, and a private directory on external SD card.
+BASE_EXPORT std::vector<FilePath> GetAllPrivateDownloadsDirectories();
+
+// Retrieves the path to the native JNI libraries via
+// ApplicationInfo.nativeLibraryDir on the Java side. The result is placed in
+// the FilePath pointed to by 'result'.
+BASE_EXPORT bool GetNativeLibraryDirectory(FilePath* result);
+
+// Retrieves the absolute path to the external storage directory. The result
+// is placed in the FilePath pointed to by 'result'.
+BASE_EXPORT bool GetExternalStorageDirectory(FilePath* result);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_PATH_UTILS_H_
diff --git a/base/android/path_utils_unittest.cc b/base/android/path_utils_unittest.cc
new file mode 100644
index 0000000000..dca8ca13ac
--- /dev/null
+++ b/base/android/path_utils_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/path_utils.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+typedef testing::Test PathUtilsTest;
+
+namespace {
+void ExpectEither(const std::string& expected1,
+ const std::string& expected2,
+ const std::string& actual) {
+ EXPECT_TRUE(expected1 == actual || expected2 == actual)
+ << "Value of: " << actual << std::endl
+ << "Expected either: " << expected1 << std::endl
+ << "or: " << expected2;
+}
+} // namespace
+
+TEST_F(PathUtilsTest, TestGetDataDirectory) {
+ // The string comes from the Java side and depends on the APK
+ // we are running in. Assumes that we are packaged in
+ // org.chromium.native_test
+ FilePath path;
+ GetDataDirectory(&path);
+
+ ExpectEither("/data/data/org.chromium.native_test/app_chrome",
+ "/data/user/0/org.chromium.native_test/app_chrome",
+ path.value());
+}
+
+TEST_F(PathUtilsTest, TestGetCacheDirectory) {
+ // The string comes from the Java side and depends on the APK
+ // we are running in. Assumes that we are packaged in
+ // org.chromium.native_test
+ FilePath path;
+ GetCacheDirectory(&path);
+ ExpectEither("/data/data/org.chromium.native_test/cache",
+ "/data/user/0/org.chromium.native_test/cache",
+ path.value());
+}
+
+TEST_F(PathUtilsTest, TestGetNativeLibraryDirectory) {
+ // The string comes from the Java side and depends on the APK
+ // we are running in. Assumes that the directory contains
+ // the base tests shared object.
+ FilePath path;
+ GetNativeLibraryDirectory(&path);
+ EXPECT_TRUE(
+ base::PathExists(path.Append("libbase_unittests.so")) ||
+ base::PathExists(path.Append("libbase_unittests.cr.so")) ||
+ base::PathExists(path.Append("lib_base_unittests__library.so")) ||
+ base::PathExists(path.Append("lib_base_unittests__library.cr.so")));
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/proguard/chromium_apk.flags b/base/android/proguard/chromium_apk.flags
new file mode 100644
index 0000000000..ac3d7f8200
--- /dev/null
+++ b/base/android/proguard/chromium_apk.flags
@@ -0,0 +1,65 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Contains flags that we'd like all Chromium .apks to use.
+
+# Not needed for Android and saves a bit of processing time.
+-dontpreverify
+
+# Keep line number information, useful for stack traces.
+-keepattributes SourceFile,LineNumberTable
+
+# Keep all CREATOR fields within Parcelable that are kept.
+-keepclassmembers class * implements android.os.Parcelable {
+ public static *** CREATOR;
+}
+
+# Don't obfuscate Parcelables as they might be marshalled outside Chrome.
+# If we annotated all Parcelables that get put into Bundles other than
+# for saveInstanceState (e.g. PendingIntents), then we could actually keep the
+# names of just those ones. For now, we'll just keep them all.
+-keepnames class * implements android.os.Parcelable
+
+# Keep all enum values and valueOf methods. See
+# http://proguard.sourceforge.net/index.html#manual/examples.html
+# for the reason for this. Also, see http://crbug.com/248037.
+-keepclassmembers enum * {
+ public static **[] values();
+}
+
+# Keep classes implementing ParameterProvider -- these will be instantiated
+# via reflection.
+-keep class * implements org.chromium.base.test.params.ParameterProvider
+
+# Allows Proguard freedom in removing these log related calls. We ask for debug
+# and verbose logs to be stripped out in base.Log, so we are just ensuring we
+# get rid of all other debug/verbose logs.
+-assumenosideeffects class android.util.Log {
+ static *** d(...);
+ static *** v(...);
+ static *** isLoggable(...);
+}
+
+# The following chart was created on July 20, 2016, to decide on 3 optimization
+# passes for Chrome.
+# optimization passes | time | .dex size | dirty memory per process
+# -----------------------------------------------------------------
+# 1 | 0:48 | 5805676 | 488972
+# 2 | 1:07 | 5777376 | 487092
+# 3 | 1:24 | 5772192 | 486596
+# 4 | 1:42 | 5771124 | 486484
+# 5 | 1:56 | 5770504 | 486432
+-optimizationpasses 3
+
+# Horizontal class merging marginally increases dex size (as of Mar 2018).
+-optimizations !class/merging/horizontal
+
+# Allowing Proguard to change modifiers. This change shrinks the .dex size by
+# ~1%, and reduces the method count by ~4%.
+-allowaccessmodification
+
+# The support library contains references to newer platform versions.
+# Don't warn about those in case this app is linking against an older
+# platform version. We know about them, and they are safe.
+-dontwarn android.support.**
diff --git a/base/android/proguard/chromium_code.flags b/base/android/proguard/chromium_code.flags
new file mode 100644
index 0000000000..8a3ec58b17
--- /dev/null
+++ b/base/android/proguard/chromium_code.flags
@@ -0,0 +1,75 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Contains flags that can be safely shared with Cronet, and thus would be
+# appropriate for third-party apps to include.
+
+# Keep all annotation related attributes that can affect runtime
+-keepattributes RuntimeVisible*Annotations
+-keepattributes AnnotationDefault
+
+# Keep the annotations, because if we don't, the ProGuard rules that use them
+# will not be respected. These classes then show up in our final dex, which we
+# do not want - see crbug.com/628226.
+-keep @interface org.chromium.base.annotations.AccessedByNative
+-keep @interface org.chromium.base.annotations.CalledByNative
+-keep @interface org.chromium.base.annotations.CalledByNativeUnchecked
+-keep @interface org.chromium.base.annotations.DoNotInline
+-keep @interface org.chromium.base.annotations.RemovableInRelease
+-keep @interface org.chromium.base.annotations.UsedByReflection
+
+# Keeps for class level annotations.
+-keep @org.chromium.base.annotations.UsedByReflection class *
+
+# Keeps for method level annotations.
+-keepclasseswithmembers class * {
+ @org.chromium.base.annotations.AccessedByNative <fields>;
+}
+-keepclasseswithmembers,includedescriptorclasses class * {
+ @org.chromium.base.annotations.CalledByNative <methods>;
+}
+-keepclasseswithmembers,includedescriptorclasses class * {
+ @org.chromium.base.annotations.CalledByNativeUnchecked <methods>;
+}
+-keepclasseswithmembers class * {
+ @org.chromium.base.annotations.UsedByReflection <methods>;
+}
+-keepclasseswithmembers class * {
+ @org.chromium.base.annotations.UsedByReflection <fields>;
+}
+-keepclasseswithmembers,includedescriptorclasses class * {
+ native <methods>;
+}
+
+# Remove methods annotated with this if their return value is unused.
+-assumenosideeffects class ** {
+ @org.chromium.base.annotations.RemovableInRelease <methods>;
+}
+
+# Never inline classes or methods with this annotation, but allow shrinking and
+# obfuscation.
+-keepnames,allowobfuscation @org.chromium.base.annotations.DoNotInline class * {
+ *;
+}
+-keepclassmembernames,allowobfuscation class * {
+ @org.chromium.base.annotations.DoNotInline <methods>;
+}
+
+# Keep all CREATOR fields within Parcelable that are kept.
+-keepclassmembers class org.chromium.** implements android.os.Parcelable {
+ public static *** CREATOR;
+}
+
+# Don't obfuscate Parcelables as they might be marshalled outside Chrome.
+# If we annotated all Parcelables that get put into Bundles other than
+# for saveInstanceState (e.g. PendingIntents), then we could actually keep the
+# names of just those ones. For now, we'll just keep them all.
+-keepnames class org.chromium.** implements android.os.Parcelable
+
+# Keep all enum values and valueOf methods. See
+# http://proguard.sourceforge.net/index.html#manual/examples.html
+# for the reason for this. Also, see http://crbug.com/248037.
+-keepclassmembers enum org.chromium.** {
+ public static **[] values();
+}
diff --git a/base/android/record_histogram.cc b/base/android/record_histogram.cc
new file mode 100644
index 0000000000..f41ec99d18
--- /dev/null
+++ b/base/android/record_histogram.cc
@@ -0,0 +1,343 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "jni/RecordHistogram_jni.h"
+
+namespace base {
+namespace android {
+namespace {
+
+// Simple thread-safe wrapper for caching histograms. This avoids
+// relatively expensive JNI string translation for each recording.
+class HistogramCache {
+ public:
+ HistogramCache() {}
+
+ std::string HistogramConstructionParamsToString(HistogramBase* histogram) {
+ std::string params_str = histogram->histogram_name();
+ switch (histogram->GetHistogramType()) {
+ case HISTOGRAM:
+ case LINEAR_HISTOGRAM:
+ case BOOLEAN_HISTOGRAM:
+ case CUSTOM_HISTOGRAM: {
+ Histogram* hist = static_cast<Histogram*>(histogram);
+ params_str += StringPrintf("/%d/%d/%d", hist->declared_min(),
+ hist->declared_max(), hist->bucket_count());
+ break;
+ }
+ case SPARSE_HISTOGRAM:
+ case DUMMY_HISTOGRAM:
+ break;
+ }
+ return params_str;
+ }
+
+ void JNI_RecordHistogram_CheckHistogramArgs(JNIEnv* env,
+ jstring j_histogram_name,
+ int32_t expected_min,
+ int32_t expected_max,
+ uint32_t expected_bucket_count,
+ HistogramBase* histogram) {
+ std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
+ bool valid_arguments = Histogram::InspectConstructionArguments(
+ histogram_name, &expected_min, &expected_max, &expected_bucket_count);
+ DCHECK(valid_arguments);
+ DCHECK(histogram->HasConstructionArguments(expected_min, expected_max,
+ expected_bucket_count))
+ << histogram_name << "/" << expected_min << "/" << expected_max << "/"
+ << expected_bucket_count << " vs. "
+ << HistogramConstructionParamsToString(histogram);
+ }
+
+ HistogramBase* JNI_RecordHistogram_BooleanHistogram(JNIEnv* env,
+ jstring j_histogram_name,
+ jlong j_histogram_key) {
+ DCHECK(j_histogram_name);
+ HistogramBase* histogram = HistogramFromKey(j_histogram_key);
+ if (histogram)
+ return histogram;
+
+ std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
+ histogram = BooleanHistogram::FactoryGet(
+ histogram_name, HistogramBase::kUmaTargetedHistogramFlag);
+ return histogram;
+ }
+
+ HistogramBase* JNI_RecordHistogram_EnumeratedHistogram(
+ JNIEnv* env,
+ jstring j_histogram_name,
+ jlong j_histogram_key,
+ jint j_boundary) {
+ DCHECK(j_histogram_name);
+ HistogramBase* histogram = HistogramFromKey(j_histogram_key);
+ int32_t boundary = static_cast<int32_t>(j_boundary);
+ if (histogram) {
+ JNI_RecordHistogram_CheckHistogramArgs(env, j_histogram_name, 1, boundary,
+ boundary + 1, histogram);
+ return histogram;
+ }
+
+ std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
+ histogram =
+ LinearHistogram::FactoryGet(histogram_name, 1, boundary, boundary + 1,
+ HistogramBase::kUmaTargetedHistogramFlag);
+ return histogram;
+ }
+
+ HistogramBase* JNI_RecordHistogram_CustomCountHistogram(
+ JNIEnv* env,
+ jstring j_histogram_name,
+ jlong j_histogram_key,
+ jint j_min,
+ jint j_max,
+ jint j_num_buckets) {
+ DCHECK(j_histogram_name);
+ int32_t min = static_cast<int32_t>(j_min);
+ int32_t max = static_cast<int32_t>(j_max);
+ int32_t num_buckets = static_cast<int32_t>(j_num_buckets);
+ HistogramBase* histogram = HistogramFromKey(j_histogram_key);
+ if (histogram) {
+ JNI_RecordHistogram_CheckHistogramArgs(env, j_histogram_name, min, max,
+ num_buckets, histogram);
+ return histogram;
+ }
+
+ DCHECK_GE(min, 1) << "The min expected sample must be >= 1";
+
+ std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
+ histogram =
+ Histogram::FactoryGet(histogram_name, min, max, num_buckets,
+ HistogramBase::kUmaTargetedHistogramFlag);
+ return histogram;
+ }
+
+ HistogramBase* JNI_RecordHistogram_LinearCountHistogram(
+ JNIEnv* env,
+ jstring j_histogram_name,
+ jlong j_histogram_key,
+ jint j_min,
+ jint j_max,
+ jint j_num_buckets) {
+ DCHECK(j_histogram_name);
+ int32_t min = static_cast<int32_t>(j_min);
+ int32_t max = static_cast<int32_t>(j_max);
+ int32_t num_buckets = static_cast<int32_t>(j_num_buckets);
+ HistogramBase* histogram = HistogramFromKey(j_histogram_key);
+ if (histogram) {
+ JNI_RecordHistogram_CheckHistogramArgs(env, j_histogram_name, min, max,
+ num_buckets, histogram);
+ return histogram;
+ }
+
+ std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
+ histogram =
+ LinearHistogram::FactoryGet(histogram_name, min, max, num_buckets,
+ HistogramBase::kUmaTargetedHistogramFlag);
+ return histogram;
+ }
+
+ HistogramBase* JNI_RecordHistogram_SparseHistogram(JNIEnv* env,
+ jstring j_histogram_name,
+ jlong j_histogram_key) {
+ DCHECK(j_histogram_name);
+ HistogramBase* histogram = HistogramFromKey(j_histogram_key);
+ if (histogram)
+ return histogram;
+
+ std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
+ histogram = SparseHistogram::FactoryGet(
+ histogram_name, HistogramBase::kUmaTargetedHistogramFlag);
+ return histogram;
+ }
+
+ HistogramBase* JNI_RecordHistogram_CustomTimesHistogram(
+ JNIEnv* env,
+ jstring j_histogram_name,
+ jlong j_histogram_key,
+ jint j_min,
+ jint j_max,
+ jint j_bucket_count) {
+ DCHECK(j_histogram_name);
+ HistogramBase* histogram = HistogramFromKey(j_histogram_key);
+ int32_t min = static_cast<int32_t>(j_min);
+ int32_t max = static_cast<int32_t>(j_max);
+ int32_t bucket_count = static_cast<int32_t>(j_bucket_count);
+ if (histogram) {
+ JNI_RecordHistogram_CheckHistogramArgs(env, j_histogram_name, min, max,
+ bucket_count, histogram);
+ return histogram;
+ }
+
+ std::string histogram_name = ConvertJavaStringToUTF8(env, j_histogram_name);
+ // This intentionally uses FactoryGet and not FactoryTimeGet. FactoryTimeGet
+ // is just a convenience for constructing the underlying Histogram with
+ // TimeDelta arguments.
+ histogram = Histogram::FactoryGet(histogram_name, min, max, bucket_count,
+ HistogramBase::kUmaTargetedHistogramFlag);
+ return histogram;
+ }
+
+ private:
+ // Convert a jlong |histogram_key| from Java to a HistogramBase* via a cast.
+ // The Java side caches these in a map (see RecordHistogram.java), which is
+ // safe to do since C++ Histogram objects are never freed.
+ static HistogramBase* HistogramFromKey(jlong j_histogram_key) {
+ return reinterpret_cast<HistogramBase*>(j_histogram_key);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramCache);
+};
+
+LazyInstance<HistogramCache>::Leaky g_histograms;
+
+} // namespace
+
+jlong JNI_RecordHistogram_RecordBooleanHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& j_histogram_name,
+ jlong j_histogram_key,
+ jboolean j_sample) {
+ bool sample = static_cast<bool>(j_sample);
+ HistogramBase* histogram =
+ g_histograms.Get().JNI_RecordHistogram_BooleanHistogram(
+ env, j_histogram_name, j_histogram_key);
+ histogram->AddBoolean(sample);
+ return reinterpret_cast<jlong>(histogram);
+}
+
+jlong JNI_RecordHistogram_RecordEnumeratedHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& j_histogram_name,
+ jlong j_histogram_key,
+ jint j_sample,
+ jint j_boundary) {
+ int sample = static_cast<int>(j_sample);
+
+ HistogramBase* histogram =
+ g_histograms.Get().JNI_RecordHistogram_EnumeratedHistogram(
+ env, j_histogram_name, j_histogram_key, j_boundary);
+ histogram->Add(sample);
+ return reinterpret_cast<jlong>(histogram);
+}
+
+jlong JNI_RecordHistogram_RecordCustomCountHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& j_histogram_name,
+ jlong j_histogram_key,
+ jint j_sample,
+ jint j_min,
+ jint j_max,
+ jint j_num_buckets) {
+ int sample = static_cast<int>(j_sample);
+
+ HistogramBase* histogram =
+ g_histograms.Get().JNI_RecordHistogram_CustomCountHistogram(
+ env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets);
+ histogram->Add(sample);
+ return reinterpret_cast<jlong>(histogram);
+}
+
+jlong JNI_RecordHistogram_RecordLinearCountHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& j_histogram_name,
+ jlong j_histogram_key,
+ jint j_sample,
+ jint j_min,
+ jint j_max,
+ jint j_num_buckets) {
+ int sample = static_cast<int>(j_sample);
+
+ HistogramBase* histogram =
+ g_histograms.Get().JNI_RecordHistogram_LinearCountHistogram(
+ env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets);
+ histogram->Add(sample);
+ return reinterpret_cast<jlong>(histogram);
+}
+
+jlong JNI_RecordHistogram_RecordSparseHistogram(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& j_histogram_name,
+ jlong j_histogram_key,
+ jint j_sample) {
+ int sample = static_cast<int>(j_sample);
+ HistogramBase* histogram =
+ g_histograms.Get().JNI_RecordHistogram_SparseHistogram(
+ env, j_histogram_name, j_histogram_key);
+ histogram->Add(sample);
+ return reinterpret_cast<jlong>(histogram);
+}
+
+jlong JNI_RecordHistogram_RecordCustomTimesHistogramMilliseconds(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& j_histogram_name,
+ jlong j_histogram_key,
+ jint j_duration,
+ jint j_min,
+ jint j_max,
+ jint j_num_buckets) {
+ HistogramBase* histogram =
+ g_histograms.Get().JNI_RecordHistogram_CustomTimesHistogram(
+ env, j_histogram_name, j_histogram_key, j_min, j_max, j_num_buckets);
+ histogram->AddTime(
+ TimeDelta::FromMilliseconds(static_cast<int64_t>(j_duration)));
+ return reinterpret_cast<jlong>(histogram);
+}
+
+// This backs a Java test util for testing histograms -
+// MetricsUtils.HistogramDelta. It should live in a test-specific file, but we
+// currently can't have test-specific native code packaged in test-specific Java
+// targets - see http://crbug.com/415945.
+jint JNI_RecordHistogram_GetHistogramValueCountForTesting(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& histogram_name,
+ jint sample) {
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(
+ android::ConvertJavaStringToUTF8(env, histogram_name));
+ if (histogram == nullptr) {
+ // No samples have been recorded for this histogram (yet?).
+ return 0;
+ }
+
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ return samples->GetCount(static_cast<int>(sample));
+}
+
+jint JNI_RecordHistogram_GetHistogramTotalCountForTesting(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& histogram_name) {
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(
+ android::ConvertJavaStringToUTF8(env, histogram_name));
+ if (histogram == nullptr) {
+ // No samples have been recorded for this histogram.
+ return 0;
+ }
+
+ return histogram->SnapshotSamples()->TotalCount();
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/record_user_action.cc b/base/android/record_user_action.cc
new file mode 100644
index 0000000000..683add6b9b
--- /dev/null
+++ b/base/android/record_user_action.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/user_metrics.h"
+#include "jni/RecordUserAction_jni.h"
+
+namespace {
+
+struct ActionCallbackWrapper {
+ base::ActionCallback action_callback;
+};
+
+} // namespace
+
+namespace base {
+namespace android {
+
+static void JNI_RecordUserAction_RecordUserAction(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& j_action) {
+ RecordComputedAction(ConvertJavaStringToUTF8(env, j_action));
+}
+
+static void OnActionRecorded(const JavaRef<jobject>& callback,
+ const std::string& action) {
+ JNIEnv* env = AttachCurrentThread();
+ Java_UserActionCallback_onActionRecorded(
+ env, callback, ConvertUTF8ToJavaString(env, action));
+}
+
+static jlong JNI_RecordUserAction_AddActionCallbackForTesting(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jobject>& callback) {
+ // Create a wrapper for the ActionCallback, so it can life on the heap until
+ // RemoveActionCallbackForTesting() is called.
+ auto* wrapper = new ActionCallbackWrapper{base::Bind(
+ &OnActionRecorded, ScopedJavaGlobalRef<jobject>(env, callback))};
+ base::AddActionCallback(wrapper->action_callback);
+ return reinterpret_cast<intptr_t>(wrapper);
+}
+
+static void JNI_RecordUserAction_RemoveActionCallbackForTesting(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ jlong callback_id) {
+ DCHECK(callback_id);
+ auto* wrapper = reinterpret_cast<ActionCallbackWrapper*>(callback_id);
+ base::RemoveActionCallback(wrapper->action_callback);
+ delete wrapper;
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/statistics_recorder_android.cc b/base/android/statistics_recorder_android.cc
new file mode 100644
index 0000000000..346a7c7383
--- /dev/null
+++ b/base/android/statistics_recorder_android.cc
@@ -0,0 +1,29 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/android/jni_string.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/sys_info.h"
+#include "jni/StatisticsRecorderAndroid_jni.h"
+
+using base::android::JavaParamRef;
+using base::android::ConvertUTF8ToJavaString;
+
+namespace base {
+namespace android {
+
+static ScopedJavaLocalRef<jstring> JNI_StatisticsRecorderAndroid_ToJson(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ jint verbosityLevel) {
+ return ConvertUTF8ToJavaString(
+ env, base::StatisticsRecorder::ToJSON(
+ static_cast<JSONVerbosityLevel>(verbosityLevel)));
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/sys_utils.cc b/base/android/sys_utils.cc
new file mode 100644
index 0000000000..7872b2f7d5
--- /dev/null
+++ b/base/android/sys_utils.cc
@@ -0,0 +1,51 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/sys_utils.h"
+
+#include <memory>
+
+#include "base/android/build_info.h"
+#include "base/process/process_metrics.h"
+#include "base/sys_info.h"
+#include "base/trace_event/trace_event.h"
+#include "jni/SysUtils_jni.h"
+
+namespace base {
+namespace android {
+
+bool SysUtils::IsLowEndDeviceFromJni() {
+ JNIEnv* env = AttachCurrentThread();
+ return Java_SysUtils_isLowEndDevice(env);
+}
+
+bool SysUtils::IsCurrentlyLowMemory() {
+ JNIEnv* env = AttachCurrentThread();
+ return Java_SysUtils_isCurrentlyLowMemory(env);
+}
+
+// Logs the number of minor / major page faults to tracing (and also the time to
+// collect) the metrics. Does nothing if tracing is not enabled.
+static void JNI_SysUtils_LogPageFaultCountToTracing(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jclass>& jcaller) {
+ // This is racy, but we are OK losing data, and collecting it is potentially
+ // expensive (reading and parsing a file).
+ bool enabled;
+ TRACE_EVENT_CATEGORY_GROUP_ENABLED("startup", &enabled);
+ if (!enabled)
+ return;
+ TRACE_EVENT_BEGIN2("memory", "CollectPageFaultCount", "minor", 0, "major", 0);
+ std::unique_ptr<base::ProcessMetrics> process_metrics(
+ base::ProcessMetrics::CreateProcessMetrics(
+ base::GetCurrentProcessHandle()));
+ base::PageFaultCounts counts;
+ process_metrics->GetPageFaultCounts(&counts);
+ TRACE_EVENT_END2("memory", "CollectPageFaults", "minor", counts.minor,
+ "major", counts.major);
+}
+
+} // namespace android
+
+} // namespace base
diff --git a/base/android/sys_utils.h b/base/android/sys_utils.h
new file mode 100644
index 0000000000..b1e368bac9
--- /dev/null
+++ b/base/android/sys_utils.h
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_SYS_UTILS_H_
+#define BASE_ANDROID_SYS_UTILS_H_
+
+#include "base/android/jni_android.h"
+
+namespace base {
+namespace android {
+
+class BASE_EXPORT SysUtils {
+ public:
+ // Returns true iff this is a low-end device.
+ static bool IsLowEndDeviceFromJni();
+ // Returns true if system has low available memory.
+ static bool IsCurrentlyLowMemory();
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_SYS_UTILS_H_
diff --git a/base/android/sys_utils_unittest.cc b/base/android/sys_utils_unittest.cc
new file mode 100644
index 0000000000..d16e2368de
--- /dev/null
+++ b/base/android/sys_utils_unittest.cc
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <unistd.h>
+
+#include "base/sys_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+TEST(SysUtils, AmountOfPhysicalMemory) {
+ // Check that the RAM size reported by sysconf() matches the one
+ // computed by base::SysInfo::AmountOfPhysicalMemory().
+ size_t sys_ram_size =
+ static_cast<size_t>(sysconf(_SC_PHYS_PAGES) * PAGE_SIZE);
+ EXPECT_EQ(sys_ram_size,
+ static_cast<size_t>(SysInfo::AmountOfPhysicalMemory()));
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/throw_uncaught_exception.cc b/base/android/throw_uncaught_exception.cc
new file mode 100644
index 0000000000..68627cc20f
--- /dev/null
+++ b/base/android/throw_uncaught_exception.cc
@@ -0,0 +1,19 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/throw_uncaught_exception.h"
+
+#include "base/android/jni_android.h"
+
+#include "jni/ThrowUncaughtException_jni.h"
+
+namespace base {
+namespace android {
+
+void ThrowUncaughtException() {
+ Java_ThrowUncaughtException_post(AttachCurrentThread());
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/throw_uncaught_exception.h b/base/android/throw_uncaught_exception.h
new file mode 100644
index 0000000000..57bd9088ff
--- /dev/null
+++ b/base/android/throw_uncaught_exception.h
@@ -0,0 +1,20 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_THROW_UNCAUGHT_EXCEPTION_H_
+#define BASE_ANDROID_THROW_UNCAUGHT_EXCEPTION_H_
+
+#include "base/base_export.h"
+
+namespace base {
+namespace android {
+
+// Throw that completely unwinds the java stack. In particular, this will not
+// trigger a jni CheckException crash.
+BASE_EXPORT void ThrowUncaughtException();
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_THROW_UNCAUGHT_EXCEPTION_H_
diff --git a/base/android/time_utils.cc b/base/android/time_utils.cc
new file mode 100644
index 0000000000..632dfb7a4e
--- /dev/null
+++ b/base/android/time_utils.cc
@@ -0,0 +1,20 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "base/time/time.h"
+#include "jni/TimeUtils_jni.h"
+
+namespace base {
+namespace android {
+
+static jlong JNI_TimeUtils_GetTimeTicksNowUs(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ return (TimeTicks::Now() - TimeTicks()).InMicroseconds();
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/trace_event_binding.cc b/base/android/trace_event_binding.cc
new file mode 100644
index 0000000000..623ca75296
--- /dev/null
+++ b/base/android/trace_event_binding.cc
@@ -0,0 +1,155 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <jni.h>
+
+#include <set>
+
+#include "base/android/jni_string.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_impl.h"
+#include "jni/TraceEvent_jni.h"
+
+namespace base {
+namespace android {
+
+namespace {
+
+const char kJavaCategory[] = "Java";
+const char kToplevelCategory[] = "toplevel";
+const char kLooperDispatchMessage[] = "Looper.dispatchMessage";
+
+// Boilerplate for safely converting Java data to TRACE_EVENT data.
+class TraceEventDataConverter {
+ public:
+ TraceEventDataConverter(JNIEnv* env, jstring jname, jstring jarg)
+ : name_(ConvertJavaStringToUTF8(env, jname)),
+ has_arg_(jarg != nullptr),
+ arg_(jarg ? ConvertJavaStringToUTF8(env, jarg) : "") {}
+ ~TraceEventDataConverter() {
+ }
+
+ // Return saves values to pass to TRACE_EVENT macros.
+ const char* name() { return name_.c_str(); }
+ const char* arg_name() { return has_arg_ ? "arg" : nullptr; }
+ const char* arg() { return has_arg_ ? arg_.c_str() : nullptr; }
+
+ private:
+ std::string name_;
+ bool has_arg_;
+ std::string arg_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceEventDataConverter);
+};
+
+class TraceEnabledObserver
+ : public trace_event::TraceLog::EnabledStateObserver {
+ public:
+ void OnTraceLogEnabled() override {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::Java_TraceEvent_setEnabled(env, true);
+ }
+ void OnTraceLogDisabled() override {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::Java_TraceEvent_setEnabled(env, false);
+ }
+};
+
+base::LazyInstance<TraceEnabledObserver>::Leaky g_trace_enabled_state_observer_;
+
+} // namespace
+
+static void JNI_TraceEvent_RegisterEnabledObserver(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ bool enabled = trace_event::TraceLog::GetInstance()->IsEnabled();
+ base::android::Java_TraceEvent_setEnabled(env, enabled);
+ trace_event::TraceLog::GetInstance()->AddEnabledStateObserver(
+ g_trace_enabled_state_observer_.Pointer());
+}
+
+static void JNI_TraceEvent_StartATrace(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ base::trace_event::TraceLog::GetInstance()->StartATrace();
+}
+
+static void JNI_TraceEvent_StopATrace(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ base::trace_event::TraceLog::GetInstance()->StopATrace();
+}
+
+static void JNI_TraceEvent_Instant(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jname,
+ const JavaParamRef<jstring>& jarg) {
+ TraceEventDataConverter converter(env, jname, jarg);
+ if (converter.arg()) {
+ TRACE_EVENT_COPY_INSTANT1(kJavaCategory, converter.name(),
+ TRACE_EVENT_SCOPE_THREAD,
+ converter.arg_name(), converter.arg());
+ } else {
+ TRACE_EVENT_COPY_INSTANT0(kJavaCategory, converter.name(),
+ TRACE_EVENT_SCOPE_THREAD);
+ }
+}
+
+static void JNI_TraceEvent_Begin(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jname,
+ const JavaParamRef<jstring>& jarg) {
+ TraceEventDataConverter converter(env, jname, jarg);
+ if (converter.arg()) {
+ TRACE_EVENT_COPY_BEGIN1(kJavaCategory, converter.name(),
+ converter.arg_name(), converter.arg());
+ } else {
+ TRACE_EVENT_COPY_BEGIN0(kJavaCategory, converter.name());
+ }
+}
+
+static void JNI_TraceEvent_End(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jname,
+ const JavaParamRef<jstring>& jarg) {
+ TraceEventDataConverter converter(env, jname, jarg);
+ if (converter.arg()) {
+ TRACE_EVENT_COPY_END1(kJavaCategory, converter.name(),
+ converter.arg_name(), converter.arg());
+ } else {
+ TRACE_EVENT_COPY_END0(kJavaCategory, converter.name());
+ }
+}
+
+static void JNI_TraceEvent_BeginToplevel(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jtarget) {
+ std::string target = ConvertJavaStringToUTF8(env, jtarget);
+ TRACE_EVENT_BEGIN1(kToplevelCategory, kLooperDispatchMessage, "target",
+ target);
+}
+
+static void JNI_TraceEvent_EndToplevel(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ TRACE_EVENT_END0(kToplevelCategory, kLooperDispatchMessage);
+}
+
+static void JNI_TraceEvent_StartAsync(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jname,
+ jlong jid) {
+ TraceEventDataConverter converter(env, jname, nullptr);
+ TRACE_EVENT_COPY_ASYNC_BEGIN0(kJavaCategory, converter.name(), jid);
+}
+
+static void JNI_TraceEvent_FinishAsync(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jname,
+ jlong jid) {
+ TraceEventDataConverter converter(env, jname, nullptr);
+ TRACE_EVENT_COPY_ASYNC_END0(kJavaCategory, converter.name(), jid);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/unguessable_token_android.cc b/base/android/unguessable_token_android.cc
new file mode 100644
index 0000000000..d041557419
--- /dev/null
+++ b/base/android/unguessable_token_android.cc
@@ -0,0 +1,41 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/unguessable_token_android.h"
+
+#include "jni/UnguessableToken_jni.h"
+
+namespace base {
+namespace android {
+
+ScopedJavaLocalRef<jobject> UnguessableTokenAndroid::Create(
+ JNIEnv* env,
+ const base::UnguessableToken& token) {
+ const uint64_t high = token.GetHighForSerialization();
+ const uint64_t low = token.GetLowForSerialization();
+ DCHECK(high);
+ DCHECK(low);
+ return Java_UnguessableToken_create(env, high, low);
+}
+
+base::UnguessableToken UnguessableTokenAndroid::FromJavaUnguessableToken(
+ JNIEnv* env,
+ const JavaRef<jobject>& token) {
+ const uint64_t high =
+ Java_UnguessableToken_getHighForSerialization(env, token);
+ const uint64_t low = Java_UnguessableToken_getLowForSerialization(env, token);
+ DCHECK(high);
+ DCHECK(low);
+ return base::UnguessableToken::Deserialize(high, low);
+}
+
+ScopedJavaLocalRef<jobject>
+UnguessableTokenAndroid::ParcelAndUnparcelForTesting(
+ JNIEnv* env,
+ const JavaRef<jobject>& token) {
+ return Java_UnguessableToken_parcelAndUnparcelForTesting(env, token);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/unguessable_token_android.h b/base/android/unguessable_token_android.h
new file mode 100644
index 0000000000..bb91f0e500
--- /dev/null
+++ b/base/android/unguessable_token_android.h
@@ -0,0 +1,43 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_UNGUESSABLE_TOKEN_ANDROID_H_
+#define BASE_ANDROID_UNGUESSABLE_TOKEN_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/base_export.h"
+#include "base/unguessable_token.h"
+
+namespace base {
+namespace android {
+
+class BASE_EXPORT UnguessableTokenAndroid {
+ public:
+ // Create a Java UnguessableToken with the same value as |token|.
+ static ScopedJavaLocalRef<jobject> Create(
+ JNIEnv* env,
+ const base::UnguessableToken& token);
+
+ // Create a native UnguessableToken from Java UnguessableToken |token|.
+ static base::UnguessableToken FromJavaUnguessableToken(
+ JNIEnv* env,
+ const JavaRef<jobject>& token);
+
+ // Parcel UnguessableToken |token| and unparcel it, and return the result.
+ // While this method is intended for facilitating unit tests, it results only
+ // in a clone of |token|.
+ static ScopedJavaLocalRef<jobject> ParcelAndUnparcelForTesting(
+ JNIEnv* env,
+ const JavaRef<jobject>& token);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UnguessableTokenAndroid);
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_UNGUESSABLE_TOKEN_ANDROID_H_
diff --git a/base/android/unguessable_token_android_unittest.cc b/base/android/unguessable_token_android_unittest.cc
new file mode 100644
index 0000000000..bdad746d55
--- /dev/null
+++ b/base/android/unguessable_token_android_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/unguessable_token_android.h"
+
+#include "base/android/jni_android.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace android {
+
+TEST(UnguessableTokenAndroid, BasicCreateToken) {
+ JNIEnv* env = AttachCurrentThread();
+ uint64_t high = 0x1234567812345678;
+ uint64_t low = 0x0583503029282304;
+ base::UnguessableToken token = base::UnguessableToken::Deserialize(high, low);
+ ScopedJavaLocalRef<jobject> jtoken =
+ UnguessableTokenAndroid::Create(env, token);
+ base::UnguessableToken result =
+ UnguessableTokenAndroid::FromJavaUnguessableToken(env, jtoken);
+
+ EXPECT_EQ(token, result);
+}
+
+TEST(UnguessableTokenAndroid, ParcelAndUnparcel) {
+ JNIEnv* env = AttachCurrentThread();
+ uint64_t high = 0x1234567812345678;
+ uint64_t low = 0x0583503029282304;
+ base::UnguessableToken token = base::UnguessableToken::Deserialize(high, low);
+ ScopedJavaLocalRef<jobject> jtoken =
+ UnguessableTokenAndroid::Create(env, token);
+ ScopedJavaLocalRef<jobject> jtoken_clone =
+ UnguessableTokenAndroid::ParcelAndUnparcelForTesting(env, jtoken);
+ base::UnguessableToken token_clone =
+ UnguessableTokenAndroid::FromJavaUnguessableToken(env, jtoken_clone);
+
+ EXPECT_EQ(token, token_clone);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/barrier_closure_unittest.cc b/base/barrier_closure_unittest.cc
new file mode 100644
index 0000000000..819f6ac2c2
--- /dev/null
+++ b/base/barrier_closure_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/barrier_closure.h"
+
+#include "base/bind.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void Increment(int* count) { (*count)++; }
+
+TEST(BarrierClosureTest, RunImmediatelyForZeroClosures) {
+ int count = 0;
+ base::Closure done_closure(base::Bind(&Increment, base::Unretained(&count)));
+
+ base::Closure barrier_closure = base::BarrierClosure(0, done_closure);
+ EXPECT_EQ(1, count);
+}
+
+TEST(BarrierClosureTest, RunAfterNumClosures) {
+ int count = 0;
+ base::Closure done_closure(base::Bind(&Increment, base::Unretained(&count)));
+
+ base::Closure barrier_closure = base::BarrierClosure(2, done_closure);
+ EXPECT_EQ(0, count);
+
+ barrier_closure.Run();
+ EXPECT_EQ(0, count);
+
+ barrier_closure.Run();
+ EXPECT_EQ(1, count);
+}
+
+class DestructionIndicator {
+ public:
+ // Sets |*destructed| to true in destructor.
+ DestructionIndicator(bool* destructed) : destructed_(destructed) {
+ *destructed_ = false;
+ }
+
+ ~DestructionIndicator() { *destructed_ = true; }
+
+ void DoNothing() {}
+
+ private:
+ bool* destructed_;
+};
+
+TEST(BarrierClosureTest, ReleasesDoneClosureWhenDone) {
+ bool done_destructed = false;
+ base::Closure barrier_closure = base::BarrierClosure(
+ 1,
+ base::BindOnce(&DestructionIndicator::DoNothing,
+ base::Owned(new DestructionIndicator(&done_destructed))));
+ EXPECT_FALSE(done_destructed);
+ barrier_closure.Run();
+ EXPECT_TRUE(done_destructed);
+}
+
+void ResetBarrierClosure(base::Closure* closure) {
+ *closure = base::Closure();
+}
+
+// Tests a case when |done_closure| resets a |barrier_closure|.
+// |barrier_closure| is a Closure holding the |done_closure|. |done_closure|
+// holds a pointer back to the |barrier_closure|. When |barrier_closure| is
+// Run() it calls ResetBarrierClosure() which erases the |barrier_closure| while
+// still inside of its Run(). The Run() implementation (in base::BarrierClosure)
+// must not try use itself after executing ResetBarrierClosure() or this test
+// would crash inside Run().
+TEST(BarrierClosureTest, KeepingClosureAliveUntilDone) {
+ base::Closure barrier_closure;
+ base::Closure done_closure =
+ base::Bind(ResetBarrierClosure, &barrier_closure);
+ barrier_closure = base::BarrierClosure(1, done_closure);
+ barrier_closure.Run();
+}
+
+} // namespace
diff --git a/base/base_paths_android.cc b/base/base_paths_android.cc
new file mode 100644
index 0000000000..078f565a5c
--- /dev/null
+++ b/base/base_paths_android.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines base::PathProviderAndroid which replaces base::PathProviderPosix for
+// Android in base/path_service.cc.
+
+#include <limits.h>
+#include <unistd.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/path_utils.h"
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/process/process_metrics.h"
+
+namespace base {
+
+bool PathProviderAndroid(int key, FilePath* result) {
+ switch (key) {
+ case base::FILE_EXE: {
+ FilePath bin_dir;
+ if (!ReadSymbolicLink(FilePath(kProcSelfExe), &bin_dir)) {
+ NOTREACHED() << "Unable to resolve " << kProcSelfExe << ".";
+ return false;
+ }
+ *result = bin_dir;
+ return true;
+ }
+ case base::FILE_MODULE:
+ // dladdr didn't work in Android as only the file name was returned.
+ NOTIMPLEMENTED();
+ return false;
+ case base::DIR_MODULE:
+ return base::android::GetNativeLibraryDirectory(result);
+ case base::DIR_SOURCE_ROOT:
+ // Used only by tests.
+ // In that context, hooked up via base/test/test_support_android.cc.
+ NOTIMPLEMENTED();
+ return false;
+ case base::DIR_USER_DESKTOP:
+ // Android doesn't support GetUserDesktop.
+ NOTIMPLEMENTED();
+ return false;
+ case base::DIR_CACHE:
+ return base::android::GetCacheDirectory(result);
+ case base::DIR_ASSETS:
+ // On Android assets are normally loaded from the APK using
+ // base::android::OpenApkAsset(). In tests, since the assets are no
+ // packaged, DIR_ASSETS is overridden to point to the build directory.
+ return false;
+ case base::DIR_ANDROID_APP_DATA:
+ return base::android::GetDataDirectory(result);
+ case base::DIR_ANDROID_EXTERNAL_STORAGE:
+ return base::android::GetExternalStorageDirectory(result);
+ default:
+ // Note: the path system expects this function to override the default
+ // behavior. So no need to log an error if we don't support a given
+ // path. The system will just use the default.
+ return false;
+ }
+}
+
+} // namespace base
diff --git a/base/base_paths_android.h b/base/base_paths_android.h
new file mode 100644
index 0000000000..7a9ac4a674
--- /dev/null
+++ b/base/base_paths_android.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_BASE_PATHS_ANDROID_H_
+#define BASE_BASE_PATHS_ANDROID_H_
+
+// This file declares Android-specific path keys for the base module.
+// These can be used with the PathService to access various special
+// directories and files.
+
+namespace base {
+
+enum {
+ PATH_ANDROID_START = 300,
+
+ DIR_ANDROID_APP_DATA, // Directory where to put Android app's data.
+ DIR_ANDROID_EXTERNAL_STORAGE, // Android external storage directory.
+
+ PATH_ANDROID_END
+};
+
+} // namespace base
+
+#endif // BASE_BASE_PATHS_ANDROID_H_
diff --git a/base/bind_unittest.nc b/base/bind_unittest.nc
new file mode 100644
index 0000000000..d549d2e8ae
--- /dev/null
+++ b/base/bind_unittest.nc
@@ -0,0 +1,322 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/bind_test_util.h"
+
+namespace base {
+
+// Do not put everything inside an anonymous namespace. If you do, many of the
+// helper function declarations will generate unused definition warnings.
+
+static const int kParentValue = 1;
+static const int kChildValue = 2;
+
+class NoRef {
+ public:
+ void VoidMethod0() {}
+ void VoidConstMethod0() const {}
+ int IntMethod0() { return 1; }
+};
+
+class HasRef : public NoRef, public base::RefCounted<HasRef> {
+};
+
+class Parent {
+ public:
+ void AddRef() const {}
+ void Release() const {}
+ virtual void VirtualSet() { value = kParentValue; }
+ void NonVirtualSet() { value = kParentValue; }
+ int value;
+};
+
+class Child : public Parent {
+ public:
+ virtual void VirtualSet() { value = kChildValue; }
+ void NonVirtualSet() { value = kChildValue; }
+};
+
+class NoRefParent {
+ public:
+ virtual void VirtualSet() { value = kParentValue; }
+ void NonVirtualSet() { value = kParentValue; }
+ int value;
+};
+
+class NoRefChild : public NoRefParent {
+ virtual void VirtualSet() { value = kChildValue; }
+ void NonVirtualSet() { value = kChildValue; }
+};
+
+template <typename T>
+T PolymorphicIdentity(T t) {
+ return t;
+}
+
+int UnwrapParentRef(Parent& p) {
+ return p.value;
+}
+
+template <typename T>
+void VoidPolymorphic1(T t) {
+}
+
+void TakesMoveOnly(std::unique_ptr<int>) {
+}
+
+struct NonEmptyFunctor {
+ int x;
+ void operator()() const {}
+};
+
+// TODO(hans): Remove .* and update the static_assert expectations once we roll
+// past Clang r313315. https://crbug.com/765692.
+
+#if defined(NCTEST_METHOD_ON_CONST_OBJECT) // [r"fatal error: static_assert failed .*\"Bound argument \|i\| of type \|Arg\| cannot be forwarded as \|Unwrapped\| to the bound functor, which declares it as \|Param\|\.\""]
+
+// Method bound to const-object.
+//
+// Only const methods should be allowed to work with const objects.
+void WontCompile() {
+ HasRef has_ref;
+ const HasRef* const_has_ref_ptr_ = &has_ref;
+ Callback<void()> method_to_const_cb =
+ Bind(&HasRef::VoidMethod0, const_has_ref_ptr_);
+ method_to_const_cb.Run();
+}
+
+#elif defined(NCTEST_METHOD_BIND_NEEDS_REFCOUNTED_OBJECT) // [r"fatal error: static_assert failed \"Receivers may not be raw pointers\."]
+
+
+// Method bound to non-refcounted object.
+//
+// We require refcounts unless you have Unretained().
+void WontCompile() {
+ NoRef no_ref;
+ Callback<void()> no_ref_cb =
+ Bind(&NoRef::VoidMethod0, &no_ref);
+ no_ref_cb.Run();
+}
+
+#elif defined(NCTEST_CONST_METHOD_NEEDS_REFCOUNTED_OBJECT) // [r"fatal error: static_assert failed \"Receivers may not be raw pointers\."]
+
+// Const Method bound to non-refcounted object.
+//
+// We require refcounts unless you have Unretained().
+void WontCompile() {
+ NoRef no_ref;
+ Callback<void()> no_ref_const_cb =
+ Bind(&NoRef::VoidConstMethod0, &no_ref);
+ no_ref_const_cb.Run();
+}
+
+#elif defined(NCTEST_CONST_POINTER) // [r"fatal error: static_assert failed .*\"Bound argument \|i\| of type \|Arg\| cannot be forwarded as \|Unwrapped\| to the bound functor, which declares it as \|Param\|\.\""]
+
+// Const argument used with non-const pointer parameter of same type.
+//
+// This is just a const-correctness check.
+void WontCompile() {
+ const NoRef* const_no_ref_ptr;
+ Callback<NoRef*()> pointer_same_cb =
+ Bind(&PolymorphicIdentity<NoRef*>, const_no_ref_ptr);
+ pointer_same_cb.Run();
+}
+
+#elif defined(NCTEST_CONST_POINTER_SUBTYPE) // [r"fatal error: static_assert failed .*\"Bound argument \|i\| of type \|Arg\| cannot be forwarded as \|Unwrapped\| to the bound functor, which declares it as \|Param\|\.\""]
+
+// Const argument used with non-const pointer parameter of super type.
+//
+// This is just a const-correctness check.
+void WontCompile() {
+ const NoRefChild* const_child_ptr;
+ Callback<NoRefParent*()> pointer_super_cb =
+ Bind(&PolymorphicIdentity<NoRefParent*>, const_child_ptr);
+ pointer_super_cb.Run();
+}
+
+#elif defined(DISABLED_NCTEST_DISALLOW_NON_CONST_REF_PARAM) // [r"fatal error: no member named 'AddRef' in 'base::NoRef'"]
+// TODO(dcheng): I think there's a type safety promotion issue here where we can
+// pass a const ref to a non const-ref function, or vice versa accidentally. Or
+// we make a copy accidentally. Check.
+
+// Functions with reference parameters, unsupported.
+//
+// First, non-const reference parameters are disallowed by the Google
+// style guide. Second, since we are doing argument forwarding it becomes
+// very tricky to avoid copies, maintain const correctness, and not
+// accidentally have the function be modifying a temporary, or a copy.
+void WontCompile() {
+ Parent p;
+ Callback<int(Parent&)> ref_arg_cb = Bind(&UnwrapParentRef);
+ ref_arg_cb.Run(p);
+}
+
+#elif defined(NCTEST_DISALLOW_BIND_TO_NON_CONST_REF_PARAM) // [r"fatal error: static_assert failed .*\"Bound argument \|i\| of type \|Arg\| cannot be forwarded as \|Unwrapped\| to the bound functor, which declares it as \|Param\|\.\""]
+
+// Binding functions with reference parameters, unsupported.
+//
+// See comment in NCTEST_DISALLOW_NON_CONST_REF_PARAM
+void WontCompile() {
+ Parent p;
+ Callback<int()> ref_cb = Bind(&UnwrapParentRef, p);
+ ref_cb.Run();
+}
+
+#elif defined(NCTEST_NO_IMPLICIT_ARRAY_PTR_CONVERSION) // [r"fatal error: static_assert failed .*\"First bound argument to a method cannot be an array\.\""]
+
+// A method should not be bindable with an array of objects.
+//
+// This is likely not wanted behavior. We specifically check for it though
+// because it is possible, depending on how you implement prebinding, to
+// implicitly convert an array type to a pointer type.
+void WontCompile() {
+ HasRef p[10];
+ Callback<void()> method_bound_to_array_cb =
+ Bind(&HasRef::VoidMethod0, p);
+ method_bound_to_array_cb.Run();
+}
+
+#elif defined(NCTEST_NO_RVALUE_RAW_PTR_FOR_REFCOUNTED_TYPES) // [r"fatal error: static_assert failed .*\"A parameter is a refcounted type and needs scoped_refptr\.\""]
+
+// Refcounted types should not be bound as a raw pointer.
+void WontCompile() {
+ HasRef for_raw_ptr;
+ int a;
+ Callback<void()> ref_count_as_raw_ptr_a =
+ Bind(&VoidPolymorphic1<int*>, &a);
+ Callback<void()> ref_count_as_raw_ptr =
+ Bind(&VoidPolymorphic1<HasRef*>, &for_raw_ptr);
+}
+
+#elif defined(NCTEST_NO_LVALUE_RAW_PTR_FOR_REFCOUNTED_TYPES) // [r"fatal error: static_assert failed .*\"A parameter is a refcounted type and needs scoped_refptr\.\""]
+
+// Refcounted types should not be bound as a raw pointer.
+void WontCompile() {
+ HasRef* for_raw_ptr = nullptr;
+ Callback<void()> ref_count_as_raw_ptr =
+ Bind(&VoidPolymorphic1<HasRef*>, for_raw_ptr);
+}
+
+#elif defined(NCTEST_NO_RVALUE_CONST_RAW_PTR_FOR_REFCOUNTED_TYPES) // [r"fatal error: static_assert failed .*\"A parameter is a refcounted type and needs scoped_refptr\.\""]
+
+// Refcounted types should not be bound as a raw pointer.
+void WontCompile() {
+ const HasRef for_raw_ptr;
+ Callback<void()> ref_count_as_raw_ptr =
+ Bind(&VoidPolymorphic1<const HasRef*>, &for_raw_ptr);
+}
+
+#elif defined(NCTEST_NO_LVALUE_CONST_RAW_PTR_FOR_REFCOUNTED_TYPES) // [r"fatal error: static_assert failed .*\"A parameter is a refcounted type and needs scoped_refptr\.\""]
+
+// Refcounted types should not be bound as a raw pointer.
+void WontCompile() {
+ const HasRef* for_raw_ptr = nullptr;
+ Callback<void()> ref_count_as_raw_ptr =
+ Bind(&VoidPolymorphic1<const HasRef*>, for_raw_ptr);
+}
+
+#elif defined(NCTEST_WEAKPTR_BIND_MUST_RETURN_VOID) // [r"fatal error: static_assert failed .*\"weak_ptrs can only bind to methods without return values\""]
+
+// WeakPtrs cannot be bound to methods with return types.
+void WontCompile() {
+ NoRef no_ref;
+ WeakPtrFactory<NoRef> weak_factory(&no_ref);
+ Callback<int()> weak_ptr_with_non_void_return_type =
+ Bind(&NoRef::IntMethod0, weak_factory.GetWeakPtr());
+ weak_ptr_with_non_void_return_type.Run();
+}
+
+#elif defined(NCTEST_DISALLOW_ASSIGN_DIFFERENT_TYPES) // [r"fatal error: no viable conversion from 'Callback<MakeUnboundRunType<void \(\*\)\(int\)>>' to 'Callback<void \(\)>'"]
+
+// Bind result cannot be assigned to Callbacks with a mismatching type.
+void WontCompile() {
+ Closure callback_mismatches_bind_type = Bind(&VoidPolymorphic1<int>);
+}
+
+#elif defined(NCTEST_DISALLOW_CAPTURING_LAMBDA) // [r"fatal error: implicit instantiation of undefined template 'base::internal::FunctorTraits<\(lambda at (\.\./)+base/bind_unittest.nc:[0-9]+:[0-9]+\), void>'"]
+
+void WontCompile() {
+ int i = 0, j = 0;
+ Bind([i,&j]() {j = i;});
+}
+
+#elif defined(NCTEST_DISALLOW_ONCECALLBACK_RUN_ON_LVALUE) // [r"static_assert failed .*\"OnceCallback::Run\(\) may only be invoked on a non-const rvalue, i\.e\. std::move\(callback\)\.Run\(\)\.\""]
+
+void WontCompile() {
+ OnceClosure cb = Bind([] {});
+ cb.Run();
+}
+
+#elif defined(NCTEST_DISALLOW_ONCECALLBACK_RUN_ON_CONST_LVALUE) // [r"static_assert failed .*\"OnceCallback::Run\(\) may only be invoked on a non-const rvalue, i\.e\. std::move\(callback\)\.Run\(\)\.\""]
+
+void WontCompile() {
+ const OnceClosure cb = Bind([] {});
+ cb.Run();
+}
+
+#elif defined(NCTEST_DISALLOW_ONCECALLBACK_RUN_ON_CONST_RVALUE) // [r"static_assert failed .*\"OnceCallback::Run\(\) may only be invoked on a non-const rvalue, i\.e\. std::move\(callback\)\.Run\(\)\.\""]
+
+void WontCompile() {
+ const OnceClosure cb = Bind([] {});
+ std::move(cb).Run();
+}
+
+#elif defined(NCTEST_DISALLOW_BIND_ONCECALLBACK) // [r"fatal error: static_assert failed .*\"BindRepeating cannot bind OnceCallback. Use BindOnce with std::move\(\)\.\""]
+
+void WontCompile() {
+ Bind(BindOnce([](int) {}), 42);
+}
+
+#elif defined(NCTEST_DISALLOW_BINDONCE_LVALUE_ONCECALLBACK) // [r"fatal error: static_assert failed .*\"BindOnce requires non-const rvalue for OnceCallback binding\."]
+void WontCompile() {
+ auto cb = BindOnce([](int) {});
+ BindOnce(cb, 42);
+}
+
+#elif defined(NCTEST_DISALLOW_BINDONCE_RVALUE_CONST_ONCECALLBACK) // [r"fatal error: static_assert failed .*\"BindOnce requires non-const rvalue for OnceCallback binding\."]
+
+void WontCompile() {
+ const auto cb = BindOnce([](int) {});
+ BindOnce(std::move(cb), 42);
+}
+
+#elif defined(NCTEST_BINDONCE_MOVEONLY_TYPE_BY_VALUE) // [r"fatal error: static_assert failed .*\"Bound argument \|i\| is move-only but will be bound by copy\. Ensure \|Arg\| is mutable and bound using std::move\(\)\.\""]
+
+void WontCompile() {
+ std::unique_ptr<int> x;
+ BindOnce(&TakesMoveOnly, x);
+}
+
+#elif defined(NCTEST_BIND_MOVEONLY_TYPE_BY_VALUE) // [r"Bound argument \|i\| is move-only but will be forwarded by copy\. Ensure \|Arg\| is bound using base::Passed\(\), not std::move\(\)."]
+
+void WontCompile() {
+ std::unique_ptr<int> x;
+ Bind(&TakesMoveOnly, x);
+}
+
+#elif defined(NCTEST_BIND_MOVEONLY_TYPE_WITH_STDMOVE) // [r"Bound argument \|i\| is move-only but will be forwarded by copy\. Ensure \|Arg\| is bound using base::Passed\(\), not std::move\(\)."]
+
+void WontCompile() {
+ std::unique_ptr<int> x;
+ Bind(&TakesMoveOnly, std::move(x));
+}
+
+#elif defined(NCTEST_BIND_NON_EMPTY_FUNCTOR) // [r"fatal error: implicit instantiation of undefined template 'base::internal::FunctorTraits<base::NonEmptyFunctor, void>'"]
+
+void WontCompile() {
+ Bind(NonEmptyFunctor());
+}
+
+#endif
+
+} // namespace base
diff --git a/base/bit_cast_unittest.cc b/base/bit_cast_unittest.cc
new file mode 100644
index 0000000000..f36d3fe64c
--- /dev/null
+++ b/base/bit_cast_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+#include "base/bit_cast.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+TEST(BitCastTest, FloatIntFloat) {
+ float f = 3.1415926f;
+ int i = bit_cast<int32_t>(f);
+ float f2 = bit_cast<float>(i);
+ EXPECT_EQ(f, f2);
+}
+
+struct A {
+ int x;
+};
+
+TEST(BitCastTest, StructureInt) {
+ A a = { 1 };
+ int b = bit_cast<int>(a);
+ EXPECT_EQ(1, b);
+}
+
+} // namespace
+} // namespace base
diff --git a/base/build_time.cc b/base/build_time.cc
index 866840d49f..15782e368d 100644
--- a/base/build_time.cc
+++ b/base/build_time.cc
@@ -7,7 +7,7 @@
#include "base/logging.h"
#include "base/time/time.h"
-#ifdef __ANDROID__
+#if defined(OS_ANDROID)
#include <cutils/properties.h>
#endif
@@ -20,7 +20,7 @@ Time GetBuildTime() {
//
// __DATE__ is exactly "Mmm DD YYYY".
// __TIME__ is exactly "hh:mm:ss".
-#if defined(__ANDROID__)
+#if defined(OS_ANDROID)
char kDateTime[PROPERTY_VALUE_MAX];
property_get("ro.build.date", kDateTime, "Sep 02 2008 08:00:00 PST");
#elif defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD)
diff --git a/base/callback_list_unittest.nc b/base/callback_list_unittest.nc
new file mode 100644
index 0000000000..7347f765dc
--- /dev/null
+++ b/base/callback_list_unittest.nc
@@ -0,0 +1,56 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/callback_list.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/macros.h"
+
+namespace base {
+
+class Foo {
+ public:
+ Foo() {}
+ ~Foo() {}
+};
+
+class FooListener {
+ public:
+ FooListener() {}
+
+ void GotAScopedFoo(std::unique_ptr<Foo> f) { foo_ = std::move(f); }
+
+ std::unique_ptr<Foo> foo_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FooListener);
+};
+
+
+#if defined(NCTEST_MOVE_ONLY_TYPE_PARAMETER) // [r"fatal error: call to (implicitly-)?deleted( copy)? constructor"]
+
+// Callbacks run with a move-only typed parameter.
+//
+// CallbackList does not support move-only typed parameters. Notify() is
+// designed to take zero or more parameters, and run each registered callback
+// with them. With move-only types, the parameter will be set to NULL after the
+// first callback has been run.
+void WontCompile() {
+ FooListener f;
+ CallbackList<void(std::unique_ptr<Foo>)> c1;
+ std::unique_ptr<CallbackList<void(std::unique_ptr<Foo>)>::Subscription> sub =
+ c1.Add(Bind(&FooListener::GotAScopedFoo, Unretained(&f)));
+ c1.Notify(std::unique_ptr<Foo>(new Foo()));
+}
+
+#endif
+
+} // namespace base
diff --git a/base/callback_unittest.nc b/base/callback_unittest.nc
new file mode 100644
index 0000000000..3261529341
--- /dev/null
+++ b/base/callback_unittest.nc
@@ -0,0 +1,53 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/callback.h"
+
+namespace base {
+
+class Parent {
+};
+
+class Child : Parent {
+};
+
+#if defined(NCTEST_EQUALS_REQUIRES_SAMETYPE) // [r"fatal error: no viable conversion from 'RepeatingCallback<int \(\)>' to 'const RepeatingCallback<void \(\)>'"]
+
+// Attempting to call comparison function on two callbacks of different type.
+//
+// This should be a compile time failure because each callback type should be
+// considered distinct.
+void WontCompile() {
+ Closure c1;
+ Callback<int()> c2;
+ c1.Equals(c2);
+}
+
+#elif defined(NCTEST_CONSTRUCTION_FROM_SUBTYPE) // [r"fatal error: no viable conversion from 'Callback<base::Parent \(\)>' to 'Callback<base::Child \(\)>'"]
+
+// Construction of Callback<A> from Callback<B> if A is supertype of B.
+//
+// While this is technically safe, most people aren't used to it when coding
+// C++ so if this is happening, it is almost certainly an error.
+void WontCompile() {
+ Callback<Parent()> cb_a;
+ Callback<Child()> cb_b = cb_a;
+}
+
+#elif defined(NCTEST_ASSIGNMENT_FROM_SUBTYPE) // [r"fatal error: no viable overloaded '='"]
+
+// Assignment of Callback<A> from Callback<B> if A is supertype of B.
+// See explanation for NCTEST_CONSTRUCTION_FROM_SUBTYPE
+void WontCompile() {
+ Callback<Parent()> cb_a;
+ Callback<Child()> cb_b;
+ cb_a = cb_b;
+}
+
+#endif
+
+} // namespace base
diff --git a/base/check_example.cc b/base/check_example.cc
new file mode 100644
index 0000000000..7b9d8e6a80
--- /dev/null
+++ b/base/check_example.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is meant for analyzing the code generated by the CHECK
+// macros in a small executable file that's easy to disassemble.
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+
+// An official build shouldn't generate code to print out messages for
+// the CHECK* macros, nor should it have the strings in the
+// executable. It is also important that the CHECK() function collapse to the
+// same implementation as RELEASE_ASSERT(), in particular on Windows x86.
+// Historically, the stream eating caused additional unnecessary instructions.
+// See https://crbug.com/672699.
+
+#define BLINK_RELEASE_ASSERT_EQUIVALENT(assertion) \
+ (UNLIKELY(!(assertion)) ? (IMMEDIATE_CRASH()) : (void)0)
+
+void DoCheck(bool b) {
+ CHECK(b) << "DoCheck " << b;
+}
+
+void DoBlinkReleaseAssert(bool b) {
+ BLINK_RELEASE_ASSERT_EQUIVALENT(b);
+}
+
+void DoCheckEq(int x, int y) {
+ CHECK_EQ(x, y);
+}
+
+int main(int argc, const char* argv[]) {
+ DoCheck(argc > 1);
+ DoCheckEq(argc, 1);
+ DoBlinkReleaseAssert(argc > 1);
+}
diff --git a/base/containers/adapters_unittest.cc b/base/containers/adapters_unittest.cc
new file mode 100644
index 0000000000..92554b7e1e
--- /dev/null
+++ b/base/containers/adapters_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/adapters.h"
+
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(AdaptersTest, Reversed) {
+ std::vector<int> v;
+ v.push_back(3);
+ v.push_back(2);
+ v.push_back(1);
+ int j = 0;
+ for (int& i : base::Reversed(v)) {
+ EXPECT_EQ(++j, i);
+ i += 100;
+ }
+ EXPECT_EQ(103, v[0]);
+ EXPECT_EQ(102, v[1]);
+ EXPECT_EQ(101, v[2]);
+}
+
+TEST(AdaptersTest, ReversedArray) {
+ int v[3] = {3, 2, 1};
+ int j = 0;
+ for (int& i : base::Reversed(v)) {
+ EXPECT_EQ(++j, i);
+ i += 100;
+ }
+ EXPECT_EQ(103, v[0]);
+ EXPECT_EQ(102, v[1]);
+ EXPECT_EQ(101, v[2]);
+}
+
+TEST(AdaptersTest, ReversedConst) {
+ std::vector<int> v;
+ v.push_back(3);
+ v.push_back(2);
+ v.push_back(1);
+ const std::vector<int>& cv = v;
+ int j = 0;
+ for (int i : base::Reversed(cv)) {
+ EXPECT_EQ(++j, i);
+ }
+}
+
+} // namespace
diff --git a/base/containers/flat_map_unittest.cc b/base/containers/flat_map_unittest.cc
new file mode 100644
index 0000000000..87958bde41
--- /dev/null
+++ b/base/containers/flat_map_unittest.cc
@@ -0,0 +1,369 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/flat_map.h"
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/test/move_only_int.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// A flat_map is basically a interface to flat_tree. So several basic
+// operations are tested to make sure things are set up properly, but the bulk
+// of the tests are in flat_tree_unittests.cc.
+
+using ::testing::ElementsAre;
+
+namespace base {
+
+TEST(FlatMap, IncompleteType) {
+ struct A {
+ using Map = flat_map<A, A>;
+ int data;
+ Map set_with_incomplete_type;
+ Map::iterator it;
+ Map::const_iterator cit;
+
+ // We do not declare operator< because clang complains that it's unused.
+ };
+
+ A a;
+}
+
+TEST(FlatMap, RangeConstructor) {
+ flat_map<int, int>::value_type input_vals[] = {
+ {1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}};
+
+ flat_map<int, int> first(std::begin(input_vals), std::end(input_vals));
+ EXPECT_THAT(first, ElementsAre(std::make_pair(1, 1), std::make_pair(2, 1),
+ std::make_pair(3, 1)));
+
+ flat_map<int, int> last(std::begin(input_vals), std::end(input_vals),
+ KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(last, ElementsAre(std::make_pair(1, 3), std::make_pair(2, 3),
+ std::make_pair(3, 3)));
+}
+
+TEST(FlatMap, MoveConstructor) {
+ using pair = std::pair<MoveOnlyInt, MoveOnlyInt>;
+
+ flat_map<MoveOnlyInt, MoveOnlyInt> original;
+ original.insert(pair(MoveOnlyInt(1), MoveOnlyInt(1)));
+ original.insert(pair(MoveOnlyInt(2), MoveOnlyInt(2)));
+ original.insert(pair(MoveOnlyInt(3), MoveOnlyInt(3)));
+ original.insert(pair(MoveOnlyInt(4), MoveOnlyInt(4)));
+
+ flat_map<MoveOnlyInt, MoveOnlyInt> moved(std::move(original));
+
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(1)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(2)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(3)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(4)));
+}
+
+TEST(FlatMap, VectorConstructor) {
+ using IntPair = std::pair<int, int>;
+ using IntMap = flat_map<int, int>;
+ {
+ std::vector<IntPair> vect{{1, 1}, {1, 2}, {2, 1}};
+ IntMap map(std::move(vect));
+ EXPECT_THAT(map, ElementsAre(IntPair(1, 1), IntPair(2, 1)));
+ }
+ {
+ std::vector<IntPair> vect{{1, 1}, {1, 2}, {2, 1}};
+ IntMap map(std::move(vect), KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(map, ElementsAre(IntPair(1, 2), IntPair(2, 1)));
+ }
+}
+
+TEST(FlatMap, InitializerListConstructor) {
+ {
+ flat_map<int, int> cont(
+ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {1, 2}, {10, 10}, {8, 8}});
+ EXPECT_THAT(cont, ElementsAre(std::make_pair(1, 1), std::make_pair(2, 2),
+ std::make_pair(3, 3), std::make_pair(4, 4),
+ std::make_pair(5, 5), std::make_pair(8, 8),
+ std::make_pair(10, 10)));
+ }
+ {
+ flat_map<int, int> cont(
+ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {1, 2}, {10, 10}, {8, 8}},
+ KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(cont, ElementsAre(std::make_pair(1, 2), std::make_pair(2, 2),
+ std::make_pair(3, 3), std::make_pair(4, 4),
+ std::make_pair(5, 5), std::make_pair(8, 8),
+ std::make_pair(10, 10)));
+ }
+}
+
+TEST(FlatMap, InitializerListAssignment) {
+ flat_map<int, int> cont;
+ cont = {{1, 1}, {2, 2}};
+ EXPECT_THAT(cont, ElementsAre(std::make_pair(1, 1), std::make_pair(2, 2)));
+}
+
+TEST(FlatMap, InsertFindSize) {
+ base::flat_map<int, int> s;
+ s.insert(std::make_pair(1, 1));
+ s.insert(std::make_pair(1, 1));
+ s.insert(std::make_pair(2, 2));
+
+ EXPECT_EQ(2u, s.size());
+ EXPECT_EQ(std::make_pair(1, 1), *s.find(1));
+ EXPECT_EQ(std::make_pair(2, 2), *s.find(2));
+ EXPECT_EQ(s.end(), s.find(7));
+}
+
+TEST(FlatMap, CopySwap) {
+ base::flat_map<int, int> original;
+ original.insert({1, 1});
+ original.insert({2, 2});
+ EXPECT_THAT(original,
+ ElementsAre(std::make_pair(1, 1), std::make_pair(2, 2)));
+
+ base::flat_map<int, int> copy(original);
+ EXPECT_THAT(copy, ElementsAre(std::make_pair(1, 1), std::make_pair(2, 2)));
+
+ copy.erase(copy.begin());
+ copy.insert({10, 10});
+ EXPECT_THAT(copy, ElementsAre(std::make_pair(2, 2), std::make_pair(10, 10)));
+
+ original.swap(copy);
+ EXPECT_THAT(original,
+ ElementsAre(std::make_pair(2, 2), std::make_pair(10, 10)));
+ EXPECT_THAT(copy, ElementsAre(std::make_pair(1, 1), std::make_pair(2, 2)));
+}
+
+// operator[](const Key&)
+TEST(FlatMap, SubscriptConstKey) {
+ base::flat_map<std::string, int> m;
+
+ // Default construct elements that don't exist yet.
+ int& s = m["a"];
+ EXPECT_EQ(0, s);
+ EXPECT_EQ(1u, m.size());
+
+ // The returned mapped reference should refer into the map.
+ s = 22;
+ EXPECT_EQ(22, m["a"]);
+
+ // Overwrite existing elements.
+ m["a"] = 44;
+ EXPECT_EQ(44, m["a"]);
+}
+
+// operator[](Key&&)
+TEST(FlatMap, SubscriptMoveOnlyKey) {
+ base::flat_map<MoveOnlyInt, int> m;
+
+ // Default construct elements that don't exist yet.
+ int& s = m[MoveOnlyInt(1)];
+ EXPECT_EQ(0, s);
+ EXPECT_EQ(1u, m.size());
+
+ // The returned mapped reference should refer into the map.
+ s = 22;
+ EXPECT_EQ(22, m[MoveOnlyInt(1)]);
+
+ // Overwrite existing elements.
+ m[MoveOnlyInt(1)] = 44;
+ EXPECT_EQ(44, m[MoveOnlyInt(1)]);
+}
+
+// insert_or_assign(K&&, M&&)
+TEST(FlatMap, InsertOrAssignMoveOnlyKey) {
+ base::flat_map<MoveOnlyInt, MoveOnlyInt> m;
+
+ // Initial insertion should return an iterator to the element and set the
+ // second pair member to |true|. The inserted key and value should be moved
+ // from.
+ MoveOnlyInt key(1);
+ MoveOnlyInt val(22);
+ auto result = m.insert_or_assign(std::move(key), std::move(val));
+ EXPECT_EQ(1, result.first->first.data());
+ EXPECT_EQ(22, result.first->second.data());
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(1u, m.size());
+ EXPECT_EQ(0, key.data()); // moved from
+ EXPECT_EQ(0, val.data()); // moved from
+
+ // Second call with same key should result in an assignment, overwriting the
+ // old value. Assignment should be indicated by setting the second pair member
+ // to |false|. Only the inserted value should be moved from, the key should be
+ // left intact.
+ key = MoveOnlyInt(1);
+ val = MoveOnlyInt(44);
+ result = m.insert_or_assign(std::move(key), std::move(val));
+ EXPECT_EQ(1, result.first->first.data());
+ EXPECT_EQ(44, result.first->second.data());
+ EXPECT_FALSE(result.second);
+ EXPECT_EQ(1u, m.size());
+ EXPECT_EQ(1, key.data()); // not moved from
+ EXPECT_EQ(0, val.data()); // moved from
+
+ // Check that random insertion results in sorted range.
+ base::flat_map<MoveOnlyInt, int> map;
+ for (int i : {3, 1, 5, 6, 8, 7, 0, 9, 4, 2}) {
+ map.insert_or_assign(MoveOnlyInt(i), i);
+ EXPECT_TRUE(std::is_sorted(map.begin(), map.end()));
+ }
+}
+
+// insert_or_assign(const_iterator hint, K&&, M&&)
+TEST(FlatMap, InsertOrAssignMoveOnlyKeyWithHint) {
+ base::flat_map<MoveOnlyInt, MoveOnlyInt> m;
+
+ // Initial insertion should return an iterator to the element. The inserted
+ // key and value should be moved from.
+ MoveOnlyInt key(1);
+ MoveOnlyInt val(22);
+ auto result = m.insert_or_assign(m.end(), std::move(key), std::move(val));
+ EXPECT_EQ(1, result->first.data());
+ EXPECT_EQ(22, result->second.data());
+ EXPECT_EQ(1u, m.size());
+ EXPECT_EQ(0, key.data()); // moved from
+ EXPECT_EQ(0, val.data()); // moved from
+
+ // Second call with same key should result in an assignment, overwriting the
+ // old value. Only the inserted value should be moved from, the key should be
+ // left intact.
+ key = MoveOnlyInt(1);
+ val = MoveOnlyInt(44);
+ result = m.insert_or_assign(m.end(), std::move(key), std::move(val));
+ EXPECT_EQ(1, result->first.data());
+ EXPECT_EQ(44, result->second.data());
+ EXPECT_EQ(1u, m.size());
+ EXPECT_EQ(1, key.data()); // not moved from
+ EXPECT_EQ(0, val.data()); // moved from
+
+ // Check that random insertion results in sorted range.
+ base::flat_map<MoveOnlyInt, int> map;
+ for (int i : {3, 1, 5, 6, 8, 7, 0, 9, 4, 2}) {
+ map.insert_or_assign(map.end(), MoveOnlyInt(i), i);
+ EXPECT_TRUE(std::is_sorted(map.begin(), map.end()));
+ }
+}
+
+// try_emplace(K&&, Args&&...)
+TEST(FlatMap, TryEmplaceMoveOnlyKey) {
+ base::flat_map<MoveOnlyInt, std::pair<MoveOnlyInt, MoveOnlyInt>> m;
+
+ // Trying to emplace into an empty map should succeed. Insertion should return
+ // an iterator to the element and set the second pair member to |true|. The
+ // inserted key and value should be moved from.
+ MoveOnlyInt key(1);
+ MoveOnlyInt val1(22);
+ MoveOnlyInt val2(44);
+ // Test piecewise construction of mapped_type.
+ auto result = m.try_emplace(std::move(key), std::move(val1), std::move(val2));
+ EXPECT_EQ(1, result.first->first.data());
+ EXPECT_EQ(22, result.first->second.first.data());
+ EXPECT_EQ(44, result.first->second.second.data());
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(1u, m.size());
+ EXPECT_EQ(0, key.data()); // moved from
+ EXPECT_EQ(0, val1.data()); // moved from
+ EXPECT_EQ(0, val2.data()); // moved from
+
+ // Second call with same key should result in a no-op, returning an iterator
+ // to the existing element and returning false as the second pair member.
+ // Key and values that were attempted to be inserted should be left intact.
+ key = MoveOnlyInt(1);
+ auto paired_val = std::make_pair(MoveOnlyInt(33), MoveOnlyInt(55));
+ // Test construction of mapped_type from pair.
+ result = m.try_emplace(std::move(key), std::move(paired_val));
+ EXPECT_EQ(1, result.first->first.data());
+ EXPECT_EQ(22, result.first->second.first.data());
+ EXPECT_EQ(44, result.first->second.second.data());
+ EXPECT_FALSE(result.second);
+ EXPECT_EQ(1u, m.size());
+ EXPECT_EQ(1, key.data()); // not moved from
+ EXPECT_EQ(33, paired_val.first.data()); // not moved from
+ EXPECT_EQ(55, paired_val.second.data()); // not moved from
+
+ // Check that random insertion results in sorted range.
+ base::flat_map<MoveOnlyInt, int> map;
+ for (int i : {3, 1, 5, 6, 8, 7, 0, 9, 4, 2}) {
+ map.try_emplace(MoveOnlyInt(i), i);
+ EXPECT_TRUE(std::is_sorted(map.begin(), map.end()));
+ }
+}
+
+// try_emplace(const_iterator hint, K&&, Args&&...)
+TEST(FlatMap, TryEmplaceMoveOnlyKeyWithHint) {
+ base::flat_map<MoveOnlyInt, std::pair<MoveOnlyInt, MoveOnlyInt>> m;
+
+ // Trying to emplace into an empty map should succeed. Insertion should return
+ // an iterator to the element. The inserted key and value should be moved
+ // from.
+ MoveOnlyInt key(1);
+ MoveOnlyInt val1(22);
+ MoveOnlyInt val2(44);
+ // Test piecewise construction of mapped_type.
+ auto result =
+ m.try_emplace(m.end(), std::move(key), std::move(val1), std::move(val2));
+ EXPECT_EQ(1, result->first.data());
+ EXPECT_EQ(22, result->second.first.data());
+ EXPECT_EQ(44, result->second.second.data());
+ EXPECT_EQ(1u, m.size());
+ EXPECT_EQ(0, key.data()); // moved from
+ EXPECT_EQ(0, val1.data()); // moved from
+ EXPECT_EQ(0, val2.data()); // moved from
+
+ // Second call with same key should result in a no-op, returning an iterator
+ // to the existing element. Key and values that were attempted to be inserted
+ // should be left intact.
+ key = MoveOnlyInt(1);
+ val1 = MoveOnlyInt(33);
+ val2 = MoveOnlyInt(55);
+ auto paired_val = std::make_pair(MoveOnlyInt(33), MoveOnlyInt(55));
+ // Test construction of mapped_type from pair.
+ result = m.try_emplace(m.end(), std::move(key), std::move(paired_val));
+ EXPECT_EQ(1, result->first.data());
+ EXPECT_EQ(22, result->second.first.data());
+ EXPECT_EQ(44, result->second.second.data());
+ EXPECT_EQ(1u, m.size());
+ EXPECT_EQ(1, key.data()); // not moved from
+ EXPECT_EQ(33, paired_val.first.data()); // not moved from
+ EXPECT_EQ(55, paired_val.second.data()); // not moved from
+
+ // Check that random insertion results in sorted range.
+ base::flat_map<MoveOnlyInt, int> map;
+ for (int i : {3, 1, 5, 6, 8, 7, 0, 9, 4, 2}) {
+ map.try_emplace(map.end(), MoveOnlyInt(i), i);
+ EXPECT_TRUE(std::is_sorted(map.begin(), map.end()));
+ }
+}
+
+TEST(FlatMap, UsingTransparentCompare) {
+ using ExplicitInt = base::MoveOnlyInt;
+ base::flat_map<ExplicitInt, int> m;
+ const auto& m1 = m;
+ int x = 0;
+
+ // Check if we can use lookup functions without converting to key_type.
+ // Correctness is checked in flat_tree tests.
+ m.count(x);
+ m1.count(x);
+ m.find(x);
+ m1.find(x);
+ m.equal_range(x);
+ m1.equal_range(x);
+ m.lower_bound(x);
+ m1.lower_bound(x);
+ m.upper_bound(x);
+ m1.upper_bound(x);
+ m.erase(x);
+
+ // Check if we broke overload resolution.
+ m.emplace(ExplicitInt(0), 0);
+ m.emplace(ExplicitInt(1), 0);
+ m.erase(m.begin());
+ m.erase(m.cbegin());
+}
+
+} // namespace base
diff --git a/base/containers/flat_set_unittest.cc b/base/containers/flat_set_unittest.cc
new file mode 100644
index 0000000000..4596975498
--- /dev/null
+++ b/base/containers/flat_set_unittest.cc
@@ -0,0 +1,121 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/flat_set.h"
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/test/move_only_int.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// A flat_set is basically a interface to flat_tree. So several basic
+// operations are tested to make sure things are set up properly, but the bulk
+// of the tests are in flat_tree_unittests.cc.
+
+using ::testing::ElementsAre;
+
+namespace base {
+
+TEST(FlatSet, IncompleteType) {
+ struct A {
+ using Set = flat_set<A>;
+ int data;
+ Set set_with_incomplete_type;
+ Set::iterator it;
+ Set::const_iterator cit;
+
+ // We do not declare operator< because clang complains that it's unused.
+ };
+
+ A a;
+}
+
+TEST(FlatSet, RangeConstructor) {
+ flat_set<int>::value_type input_vals[] = {1, 1, 1, 2, 2, 2, 3, 3, 3};
+
+ flat_set<int> cont(std::begin(input_vals), std::end(input_vals),
+ base::KEEP_FIRST_OF_DUPES);
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3));
+}
+
+TEST(FlatSet, MoveConstructor) {
+ int input_range[] = {1, 2, 3, 4};
+
+ flat_set<MoveOnlyInt> original(std::begin(input_range), std::end(input_range),
+ base::KEEP_FIRST_OF_DUPES);
+ flat_set<MoveOnlyInt> moved(std::move(original));
+
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(1)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(2)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(3)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(4)));
+}
+
+TEST(FlatSet, InitializerListConstructor) {
+ flat_set<int> cont({1, 2, 3, 4, 5, 6, 10, 8}, KEEP_FIRST_OF_DUPES);
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 4, 5, 6, 8, 10));
+}
+
+TEST(FlatSet, InsertFindSize) {
+ base::flat_set<int> s;
+ s.insert(1);
+ s.insert(1);
+ s.insert(2);
+
+ EXPECT_EQ(2u, s.size());
+ EXPECT_EQ(1, *s.find(1));
+ EXPECT_EQ(2, *s.find(2));
+ EXPECT_EQ(s.end(), s.find(7));
+}
+
+TEST(FlatSet, CopySwap) {
+ base::flat_set<int> original;
+ original.insert(1);
+ original.insert(2);
+ EXPECT_THAT(original, ElementsAre(1, 2));
+
+ base::flat_set<int> copy(original);
+ EXPECT_THAT(copy, ElementsAre(1, 2));
+
+ copy.erase(copy.begin());
+ copy.insert(10);
+ EXPECT_THAT(copy, ElementsAre(2, 10));
+
+ original.swap(copy);
+ EXPECT_THAT(original, ElementsAre(2, 10));
+ EXPECT_THAT(copy, ElementsAre(1, 2));
+}
+
+TEST(FlatSet, UsingTransparentCompare) {
+ using ExplicitInt = base::MoveOnlyInt;
+ base::flat_set<ExplicitInt> s;
+ const auto& s1 = s;
+ int x = 0;
+
+ // Check if we can use lookup functions without converting to key_type.
+ // Correctness is checked in flat_tree tests.
+ s.count(x);
+ s1.count(x);
+ s.find(x);
+ s1.find(x);
+ s.equal_range(x);
+ s1.equal_range(x);
+ s.lower_bound(x);
+ s1.lower_bound(x);
+ s.upper_bound(x);
+ s1.upper_bound(x);
+ s.erase(x);
+
+ // Check if we broke overload resolution.
+ s.emplace(0);
+ s.emplace(1);
+ s.erase(s.begin());
+ s.erase(s.cbegin());
+}
+
+} // namespace base
diff --git a/base/containers/flat_tree_unittest.cc b/base/containers/flat_tree_unittest.cc
new file mode 100644
index 0000000000..5b788d506b
--- /dev/null
+++ b/base/containers/flat_tree_unittest.cc
@@ -0,0 +1,1385 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/flat_tree.h"
+
+// Following tests are ported and extended tests from libcpp for std::set.
+// They can be found here:
+// https://github.com/llvm-mirror/libcxx/tree/master/test/std/containers/associative/set
+//
+// Not ported tests:
+// * No tests with PrivateConstructor and std::less<> changed to std::less<T>
+// These tests have to do with C++14 std::less<>
+// http://en.cppreference.com/w/cpp/utility/functional/less_void
+// and add support for templated versions of lookup functions.
+// Because we use same implementation, we figured that it's OK just to check
+// compilation and this is what we do in flat_set_unittest/flat_map_unittest.
+// * No tests for max_size()
+// Has to do with allocator support.
+// * No tests with DefaultOnly.
+// Standard containers allocate each element in the separate node on the heap
+// and then manipulate these nodes. Flat containers store their elements in
+// contiguous memory and move them around, type is required to be movable.
+// * No tests for N3644.
+// This proposal suggests that all default constructed iterators compare
+// equal. Currently we use std::vector iterators and they don't implement
+// this.
+// * No tests with min_allocator and no tests counting allocations.
+// Flat sets currently don't support allocators.
+
+#include <forward_list>
+#include <functional>
+#include <iterator>
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/template_util.h"
+#include "base/test/move_only_int.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+template <class It>
+class InputIterator {
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using value_type = typename std::iterator_traits<It>::value_type;
+ using difference_type = typename std::iterator_traits<It>::difference_type;
+ using pointer = It;
+ using reference = typename std::iterator_traits<It>::reference;
+
+ InputIterator() : it_() {}
+ explicit InputIterator(It it) : it_(it) {}
+
+ reference operator*() const { return *it_; }
+ pointer operator->() const { return it_; }
+
+ InputIterator& operator++() {
+ ++it_;
+ return *this;
+ }
+ InputIterator operator++(int) {
+ InputIterator tmp(*this);
+ ++(*this);
+ return tmp;
+ }
+
+ friend bool operator==(const InputIterator& lhs, const InputIterator& rhs) {
+ return lhs.it_ == rhs.it_;
+ }
+ friend bool operator!=(const InputIterator& lhs, const InputIterator& rhs) {
+ return !(lhs == rhs);
+ }
+
+ private:
+ It it_;
+};
+
+template <typename It>
+InputIterator<It> MakeInputIterator(It it) {
+ return InputIterator<It>(it);
+}
+
+class Emplaceable {
+ public:
+ Emplaceable() : Emplaceable(0, 0.0) {}
+ Emplaceable(int i, double d) : int_(i), double_(d) {}
+ Emplaceable(Emplaceable&& other) : int_(other.int_), double_(other.double_) {
+ other.int_ = 0;
+ other.double_ = 0.0;
+ }
+
+ Emplaceable& operator=(Emplaceable&& other) {
+ int_ = other.int_;
+ other.int_ = 0;
+ double_ = other.double_;
+ other.double_ = 0.0;
+ return *this;
+ }
+
+ friend bool operator==(const Emplaceable& lhs, const Emplaceable& rhs) {
+ return std::tie(lhs.int_, lhs.double_) == std::tie(rhs.int_, rhs.double_);
+ }
+
+ friend bool operator<(const Emplaceable& lhs, const Emplaceable& rhs) {
+ return std::tie(lhs.int_, lhs.double_) < std::tie(rhs.int_, rhs.double_);
+ }
+
+ private:
+ int int_;
+ double double_;
+
+ DISALLOW_COPY_AND_ASSIGN(Emplaceable);
+};
+
+struct TemplateConstructor {
+ template <typename T>
+ TemplateConstructor(const T&) {}
+
+ friend bool operator<(const TemplateConstructor&,
+ const TemplateConstructor&) {
+ return false;
+ }
+};
+
+class NonDefaultConstructibleCompare {
+ public:
+ explicit NonDefaultConstructibleCompare(int) {}
+
+ template <typename T>
+ bool operator()(const T& lhs, const T& rhs) const {
+ return std::less<T>()(lhs, rhs);
+ }
+};
+
+template <class PairType>
+struct LessByFirst {
+ bool operator()(const PairType& lhs, const PairType& rhs) const {
+ return lhs.first < rhs.first;
+ }
+};
+
+// Common test trees.
+using IntTree =
+ flat_tree<int, int, GetKeyFromValueIdentity<int>, std::less<int>>;
+using IntPair = std::pair<int, int>;
+using IntPairTree = flat_tree<IntPair,
+ IntPair,
+ GetKeyFromValueIdentity<IntPair>,
+ LessByFirst<IntPair>>;
+using MoveOnlyTree = flat_tree<MoveOnlyInt,
+ MoveOnlyInt,
+ GetKeyFromValueIdentity<MoveOnlyInt>,
+ std::less<MoveOnlyInt>>;
+using EmplaceableTree = flat_tree<Emplaceable,
+ Emplaceable,
+ GetKeyFromValueIdentity<Emplaceable>,
+ std::less<Emplaceable>>;
+using ReversedTree =
+ flat_tree<int, int, GetKeyFromValueIdentity<int>, std::greater<int>>;
+
+using TreeWithStrangeCompare = flat_tree<int,
+ int,
+ GetKeyFromValueIdentity<int>,
+ NonDefaultConstructibleCompare>;
+
+using ::testing::ElementsAre;
+
+} // namespace
+
+TEST(FlatTree, IsMultipass) {
+ static_assert(!is_multipass<std::istream_iterator<int>>(),
+ "InputIterator is not multipass");
+ static_assert(!is_multipass<std::ostream_iterator<int>>(),
+ "OutputIterator is not multipass");
+
+ static_assert(is_multipass<std::forward_list<int>::iterator>(),
+ "ForwardIterator is multipass");
+ static_assert(is_multipass<std::list<int>::iterator>(),
+ "BidirectionalIterator is multipass");
+ static_assert(is_multipass<std::vector<int>::iterator>(),
+ "RandomAccessIterator is multipass");
+}
+
+TEST(FlatTree, LastUnique) {
+ using Pair = std::pair<int, int>;
+ using Vect = std::vector<Pair>;
+
+ auto cmp = [](const Pair& lhs, const Pair& rhs) {
+ return lhs.first == rhs.first;
+ };
+
+ // Empty case.
+ Vect empty;
+ EXPECT_EQ(empty.end(), LastUnique(empty.begin(), empty.end(), cmp));
+
+ // Single element.
+ Vect one;
+ one.push_back(Pair(1, 1));
+ EXPECT_EQ(one.end(), LastUnique(one.begin(), one.end(), cmp));
+ ASSERT_EQ(1u, one.size());
+ EXPECT_THAT(one, ElementsAre(Pair(1, 1)));
+
+ // Two elements, already unique.
+ Vect two_u;
+ two_u.push_back(Pair(1, 1));
+ two_u.push_back(Pair(2, 2));
+ EXPECT_EQ(two_u.end(), LastUnique(two_u.begin(), two_u.end(), cmp));
+ EXPECT_THAT(two_u, ElementsAre(Pair(1, 1), Pair(2, 2)));
+
+ // Two elements, dupes.
+ Vect two_d;
+ two_d.push_back(Pair(1, 1));
+ two_d.push_back(Pair(1, 2));
+ auto last = LastUnique(two_d.begin(), two_d.end(), cmp);
+ EXPECT_EQ(two_d.begin() + 1, last);
+ two_d.erase(last, two_d.end());
+ EXPECT_THAT(two_d, ElementsAre(Pair(1, 2)));
+
+ // Non-dupes, dupes, non-dupes.
+ Vect ndn;
+ ndn.push_back(Pair(1, 1));
+ ndn.push_back(Pair(2, 1));
+ ndn.push_back(Pair(2, 2));
+ ndn.push_back(Pair(2, 3));
+ ndn.push_back(Pair(3, 1));
+ last = LastUnique(ndn.begin(), ndn.end(), cmp);
+ EXPECT_EQ(ndn.begin() + 3, last);
+ ndn.erase(last, ndn.end());
+ EXPECT_THAT(ndn, ElementsAre(Pair(1, 1), Pair(2, 3), Pair(3, 1)));
+
+ // Dupes, non-dupes, dupes.
+ Vect dnd;
+ dnd.push_back(Pair(1, 1));
+ dnd.push_back(Pair(1, 2));
+ dnd.push_back(Pair(1, 3));
+ dnd.push_back(Pair(2, 1));
+ dnd.push_back(Pair(3, 1));
+ dnd.push_back(Pair(3, 2));
+ dnd.push_back(Pair(3, 3));
+ last = LastUnique(dnd.begin(), dnd.end(), cmp);
+ EXPECT_EQ(dnd.begin() + 3, last);
+ dnd.erase(last, dnd.end());
+ EXPECT_THAT(dnd, ElementsAre(Pair(1, 3), Pair(2, 1), Pair(3, 3)));
+}
+
+// ----------------------------------------------------------------------------
+// Class.
+
+// Check that flat_tree and its iterators can be instantiated with an
+// incomplete type.
+
+TEST(FlatTree, IncompleteType) {
+ struct A {
+ using Tree = flat_tree<A, A, GetKeyFromValueIdentity<A>, std::less<A>>;
+ int data;
+ Tree set_with_incomplete_type;
+ Tree::iterator it;
+ Tree::const_iterator cit;
+
+ // We do not declare operator< because clang complains that it's unused.
+ };
+
+ A a;
+}
+
+TEST(FlatTree, Stability) {
+ using Pair = std::pair<int, int>;
+
+ using Tree =
+ flat_tree<Pair, Pair, GetKeyFromValueIdentity<Pair>, LessByFirst<Pair>>;
+
+ // Constructors are stable.
+ Tree cont({{0, 0}, {1, 0}, {0, 1}, {2, 0}, {0, 2}, {1, 1}});
+
+ auto AllOfSecondsAreZero = [&cont] {
+ return std::all_of(cont.begin(), cont.end(),
+ [](const Pair& elem) { return elem.second == 0; });
+ };
+
+ EXPECT_TRUE(AllOfSecondsAreZero()) << "constructor should be stable";
+
+ // Should not replace existing.
+ cont.insert(Pair(0, 2));
+ cont.insert(Pair(1, 2));
+ cont.insert(Pair(2, 2));
+
+ EXPECT_TRUE(AllOfSecondsAreZero()) << "insert should be stable";
+
+ cont.insert(Pair(3, 0));
+ cont.insert(Pair(3, 2));
+
+ EXPECT_TRUE(AllOfSecondsAreZero()) << "insert should be stable";
+}
+
+// ----------------------------------------------------------------------------
+// Types.
+
+// key_type
+// key_compare
+// value_type
+// value_compare
+// pointer
+// const_pointer
+// reference
+// const_reference
+// size_type
+// difference_type
+// iterator
+// const_iterator
+// reverse_iterator
+// const_reverse_iterator
+
+TEST(FlatTree, Types) {
+ // These are guaranteed to be portable.
+ static_assert((std::is_same<int, IntTree::key_type>::value), "");
+ static_assert((std::is_same<int, IntTree::value_type>::value), "");
+ static_assert((std::is_same<std::less<int>, IntTree::key_compare>::value),
+ "");
+ static_assert((std::is_same<int&, IntTree::reference>::value), "");
+ static_assert((std::is_same<const int&, IntTree::const_reference>::value),
+ "");
+ static_assert((std::is_same<int*, IntTree::pointer>::value), "");
+ static_assert((std::is_same<const int*, IntTree::const_pointer>::value), "");
+}
+
+// ----------------------------------------------------------------------------
+// Lifetime.
+
+// flat_tree()
+// flat_tree(const Compare& comp)
+
+TEST(FlatTree, DefaultConstructor) {
+ {
+ IntTree cont;
+ EXPECT_THAT(cont, ElementsAre());
+ }
+
+ {
+ TreeWithStrangeCompare cont(NonDefaultConstructibleCompare(0));
+ EXPECT_THAT(cont, ElementsAre());
+ }
+}
+
+// flat_tree(InputIterator first,
+// InputIterator last,
+// FlatContainerDupes dupe_handling,
+// const Compare& comp = Compare())
+
+TEST(FlatTree, RangeConstructor) {
+ {
+ IntPair input_vals[] = {{1, 1}, {1, 2}, {2, 1}, {2, 2}, {1, 3},
+ {2, 3}, {3, 1}, {3, 2}, {3, 3}};
+
+ IntPairTree first_of(MakeInputIterator(std::begin(input_vals)),
+ MakeInputIterator(std::end(input_vals)));
+ EXPECT_THAT(first_of,
+ ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1)));
+
+ IntPairTree last_of(MakeInputIterator(std::begin(input_vals)),
+ MakeInputIterator(std::end(input_vals)),
+ KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(last_of,
+ ElementsAre(IntPair(1, 3), IntPair(2, 3), IntPair(3, 3)));
+ }
+ {
+ TreeWithStrangeCompare::value_type input_vals[] = {1, 1, 1, 2, 2,
+ 2, 3, 3, 3};
+
+ TreeWithStrangeCompare cont(MakeInputIterator(std::begin(input_vals)),
+ MakeInputIterator(std::end(input_vals)),
+ KEEP_FIRST_OF_DUPES,
+ NonDefaultConstructibleCompare(0));
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3));
+ }
+}
+
+// flat_tree(const flat_tree& x)
+
+TEST(FlatTree, CopyConstructor) {
+ IntTree original({1, 2, 3, 4});
+ IntTree copied(original);
+
+ EXPECT_THAT(copied, ElementsAre(1, 2, 3, 4));
+
+ EXPECT_THAT(copied, ElementsAre(1, 2, 3, 4));
+ EXPECT_THAT(original, ElementsAre(1, 2, 3, 4));
+ EXPECT_EQ(original, copied);
+}
+
+// flat_tree(flat_tree&& x)
+
+TEST(FlatTree, MoveConstructor) {
+ int input_range[] = {1, 2, 3, 4};
+
+ MoveOnlyTree original(std::begin(input_range), std::end(input_range));
+ MoveOnlyTree moved(std::move(original));
+
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(1)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(2)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(3)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(4)));
+}
+
+// flat_tree(std::vector<value_type>, FlatContainerDupes dupe_handling)
+
+TEST(FlatTree, VectorConstructor) {
+ using Pair = std::pair<int, MoveOnlyInt>;
+
+ // Construct an unsorted vector with a duplicate item in it. Sorted by the
+ // first item, the second allows us to test for stability. Using a move
+ // only type to ensure the vector is not copied.
+ std::vector<Pair> storage;
+ storage.push_back(Pair(2, MoveOnlyInt(0)));
+ storage.push_back(Pair(1, MoveOnlyInt(0)));
+ storage.push_back(Pair(2, MoveOnlyInt(1)));
+
+ using Tree =
+ flat_tree<Pair, Pair, GetKeyFromValueIdentity<Pair>, LessByFirst<Pair>>;
+ Tree tree(std::move(storage));
+
+ // The list should be two items long, with only the first "2" saved.
+ ASSERT_EQ(2u, tree.size());
+ const Pair& zeroth = *tree.begin();
+ ASSERT_EQ(1, zeroth.first);
+ ASSERT_EQ(0, zeroth.second.data());
+
+ const Pair& first = *(tree.begin() + 1);
+ ASSERT_EQ(2, first.first);
+ ASSERT_EQ(0, first.second.data());
+
+ // Test KEEP_LAST_OF_DUPES with a simple vector constructor.
+ std::vector<IntPair> int_storage{{1, 1}, {1, 2}, {2, 1}};
+ IntPairTree int_tree(std::move(int_storage), KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(int_tree, ElementsAre(IntPair(1, 2), IntPair(2, 1)));
+}
+
+// flat_tree(std::initializer_list<value_type> ilist,
+// FlatContainerDupes dupe_handling,
+// const Compare& comp = Compare())
+
+TEST(FlatTree, InitializerListConstructor) {
+ {
+ IntTree cont({1, 2, 3, 4, 5, 6, 10, 8});
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 4, 5, 6, 8, 10));
+ }
+ {
+ IntTree cont({1, 2, 3, 4, 5, 6, 10, 8});
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 4, 5, 6, 8, 10));
+ }
+ {
+ TreeWithStrangeCompare cont({1, 2, 3, 4, 5, 6, 10, 8}, KEEP_FIRST_OF_DUPES,
+ NonDefaultConstructibleCompare(0));
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 4, 5, 6, 8, 10));
+ }
+ {
+ IntPairTree first_of({{1, 1}, {2, 1}, {1, 2}});
+ EXPECT_THAT(first_of, ElementsAre(IntPair(1, 1), IntPair(2, 1)));
+ }
+ {
+ IntPairTree last_of({{1, 1}, {2, 1}, {1, 2}}, KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(last_of, ElementsAre(IntPair(1, 2), IntPair(2, 1)));
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Assignments.
+
+// flat_tree& operator=(const flat_tree&)
+
+TEST(FlatTree, CopyAssignable) {
+ IntTree original({1, 2, 3, 4});
+ IntTree copied;
+ copied = original;
+
+ EXPECT_THAT(copied, ElementsAre(1, 2, 3, 4));
+ EXPECT_THAT(original, ElementsAre(1, 2, 3, 4));
+ EXPECT_EQ(original, copied);
+}
+
+// flat_tree& operator=(flat_tree&&)
+
+TEST(FlatTree, MoveAssignable) {
+ int input_range[] = {1, 2, 3, 4};
+
+ MoveOnlyTree original(std::begin(input_range), std::end(input_range));
+ MoveOnlyTree moved;
+ moved = std::move(original);
+
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(1)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(2)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(3)));
+ EXPECT_EQ(1U, moved.count(MoveOnlyInt(4)));
+}
+
+// flat_tree& operator=(std::initializer_list<value_type> ilist)
+
+TEST(FlatTree, InitializerListAssignable) {
+ IntTree cont({0});
+ cont = {1, 2, 3, 4, 5, 6, 10, 8};
+
+ EXPECT_EQ(0U, cont.count(0));
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 4, 5, 6, 8, 10));
+}
+
+// --------------------------------------------------------------------------
+// Memory management.
+
+// void reserve(size_type new_capacity)
+
+TEST(FlatTree, Reserve) {
+ IntTree cont({1, 2, 3});
+
+ cont.reserve(5);
+ EXPECT_LE(5U, cont.capacity());
+}
+
+// size_type capacity() const
+
+TEST(FlatTree, Capacity) {
+ IntTree cont({1, 2, 3});
+
+ EXPECT_LE(cont.size(), cont.capacity());
+ cont.reserve(5);
+ EXPECT_LE(cont.size(), cont.capacity());
+}
+
+// void shrink_to_fit()
+
+TEST(FlatTree, ShrinkToFit) {
+ IntTree cont({1, 2, 3});
+
+ IntTree::size_type capacity_before = cont.capacity();
+ cont.shrink_to_fit();
+ EXPECT_GE(capacity_before, cont.capacity());
+}
+
+// ----------------------------------------------------------------------------
+// Size management.
+
+// void clear()
+
+TEST(FlatTree, Clear) {
+ IntTree cont({1, 2, 3, 4, 5, 6, 7, 8});
+ cont.clear();
+ EXPECT_THAT(cont, ElementsAre());
+}
+
+// size_type size() const
+
+TEST(FlatTree, Size) {
+ IntTree cont;
+
+ EXPECT_EQ(0U, cont.size());
+ cont.insert(2);
+ EXPECT_EQ(1U, cont.size());
+ cont.insert(1);
+ EXPECT_EQ(2U, cont.size());
+ cont.insert(3);
+ EXPECT_EQ(3U, cont.size());
+ cont.erase(cont.begin());
+ EXPECT_EQ(2U, cont.size());
+ cont.erase(cont.begin());
+ EXPECT_EQ(1U, cont.size());
+ cont.erase(cont.begin());
+ EXPECT_EQ(0U, cont.size());
+}
+
+// bool empty() const
+
+TEST(FlatTree, Empty) {
+ IntTree cont;
+
+ EXPECT_TRUE(cont.empty());
+ cont.insert(1);
+ EXPECT_FALSE(cont.empty());
+ cont.clear();
+ EXPECT_TRUE(cont.empty());
+}
+
+// ----------------------------------------------------------------------------
+// Iterators.
+
+// iterator begin()
+// const_iterator begin() const
+// iterator end()
+// const_iterator end() const
+//
+// reverse_iterator rbegin()
+// const_reverse_iterator rbegin() const
+// reverse_iterator rend()
+// const_reverse_iterator rend() const
+//
+// const_iterator cbegin() const
+// const_iterator cend() const
+// const_reverse_iterator crbegin() const
+// const_reverse_iterator crend() const
+
+TEST(FlatTree, Iterators) {
+ IntTree cont({1, 2, 3, 4, 5, 6, 7, 8});
+
+ auto size = static_cast<IntTree::difference_type>(cont.size());
+
+ EXPECT_EQ(size, std::distance(cont.begin(), cont.end()));
+ EXPECT_EQ(size, std::distance(cont.cbegin(), cont.cend()));
+ EXPECT_EQ(size, std::distance(cont.rbegin(), cont.rend()));
+ EXPECT_EQ(size, std::distance(cont.crbegin(), cont.crend()));
+
+ {
+ IntTree::iterator it = cont.begin();
+ IntTree::const_iterator c_it = cont.cbegin();
+ EXPECT_EQ(it, c_it);
+ for (int j = 1; it != cont.end(); ++it, ++c_it, ++j) {
+ EXPECT_EQ(j, *it);
+ EXPECT_EQ(j, *c_it);
+ }
+ }
+ {
+ IntTree::reverse_iterator rit = cont.rbegin();
+ IntTree::const_reverse_iterator c_rit = cont.crbegin();
+ EXPECT_EQ(rit, c_rit);
+ for (int j = static_cast<int>(size); rit != cont.rend();
+ ++rit, ++c_rit, --j) {
+ EXPECT_EQ(j, *rit);
+ EXPECT_EQ(j, *c_rit);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Insert operations.
+
+// pair<iterator, bool> insert(const value_type& val)
+
+TEST(FlatTree, InsertLValue) {
+ IntTree cont;
+
+ int value = 2;
+ std::pair<IntTree::iterator, bool> result = cont.insert(value);
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(cont.begin(), result.first);
+ EXPECT_EQ(1U, cont.size());
+ EXPECT_EQ(2, *result.first);
+
+ value = 1;
+ result = cont.insert(value);
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(cont.begin(), result.first);
+ EXPECT_EQ(2U, cont.size());
+ EXPECT_EQ(1, *result.first);
+
+ value = 3;
+ result = cont.insert(value);
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(std::prev(cont.end()), result.first);
+ EXPECT_EQ(3U, cont.size());
+ EXPECT_EQ(3, *result.first);
+
+ value = 3;
+ result = cont.insert(value);
+ EXPECT_FALSE(result.second);
+ EXPECT_EQ(std::prev(cont.end()), result.first);
+ EXPECT_EQ(3U, cont.size());
+ EXPECT_EQ(3, *result.first);
+}
+
+// pair<iterator, bool> insert(value_type&& val)
+
+TEST(FlatTree, InsertRValue) {
+ MoveOnlyTree cont;
+
+ std::pair<MoveOnlyTree::iterator, bool> result = cont.insert(MoveOnlyInt(2));
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(cont.begin(), result.first);
+ EXPECT_EQ(1U, cont.size());
+ EXPECT_EQ(2, result.first->data());
+
+ result = cont.insert(MoveOnlyInt(1));
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(cont.begin(), result.first);
+ EXPECT_EQ(2U, cont.size());
+ EXPECT_EQ(1, result.first->data());
+
+ result = cont.insert(MoveOnlyInt(3));
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(std::prev(cont.end()), result.first);
+ EXPECT_EQ(3U, cont.size());
+ EXPECT_EQ(3, result.first->data());
+
+ result = cont.insert(MoveOnlyInt(3));
+ EXPECT_FALSE(result.second);
+ EXPECT_EQ(std::prev(cont.end()), result.first);
+ EXPECT_EQ(3U, cont.size());
+ EXPECT_EQ(3, result.first->data());
+}
+
+// iterator insert(const_iterator position_hint, const value_type& val)
+
+TEST(FlatTree, InsertPositionLValue) {
+ IntTree cont;
+
+ IntTree::iterator result = cont.insert(cont.cend(), 2);
+ EXPECT_EQ(cont.begin(), result);
+ EXPECT_EQ(1U, cont.size());
+ EXPECT_EQ(2, *result);
+
+ result = cont.insert(cont.cend(), 1);
+ EXPECT_EQ(cont.begin(), result);
+ EXPECT_EQ(2U, cont.size());
+ EXPECT_EQ(1, *result);
+
+ result = cont.insert(cont.cend(), 3);
+ EXPECT_EQ(std::prev(cont.end()), result);
+ EXPECT_EQ(3U, cont.size());
+ EXPECT_EQ(3, *result);
+
+ result = cont.insert(cont.cend(), 3);
+ EXPECT_EQ(std::prev(cont.end()), result);
+ EXPECT_EQ(3U, cont.size());
+ EXPECT_EQ(3, *result);
+}
+
+// iterator insert(const_iterator position_hint, value_type&& val)
+
+TEST(FlatTree, InsertPositionRValue) {
+ MoveOnlyTree cont;
+
+ MoveOnlyTree::iterator result = cont.insert(cont.cend(), MoveOnlyInt(2));
+ EXPECT_EQ(cont.begin(), result);
+ EXPECT_EQ(1U, cont.size());
+ EXPECT_EQ(2, result->data());
+
+ result = cont.insert(cont.cend(), MoveOnlyInt(1));
+ EXPECT_EQ(cont.begin(), result);
+ EXPECT_EQ(2U, cont.size());
+ EXPECT_EQ(1, result->data());
+
+ result = cont.insert(cont.cend(), MoveOnlyInt(3));
+ EXPECT_EQ(std::prev(cont.end()), result);
+ EXPECT_EQ(3U, cont.size());
+ EXPECT_EQ(3, result->data());
+
+ result = cont.insert(cont.cend(), MoveOnlyInt(3));
+ EXPECT_EQ(std::prev(cont.end()), result);
+ EXPECT_EQ(3U, cont.size());
+ EXPECT_EQ(3, result->data());
+}
+
+// template <class InputIterator>
+// void insert(InputIterator first, InputIterator last);
+
+TEST(FlatTree, InsertIterIter) {
+ struct GetKeyFromIntIntPair {
+ const int& operator()(const std::pair<int, int>& p) const {
+ return p.first;
+ }
+ };
+
+ using IntIntMap =
+ flat_tree<int, IntPair, GetKeyFromIntIntPair, std::less<int>>;
+
+ {
+ IntIntMap cont;
+ IntPair int_pairs[] = {{3, 1}, {1, 1}, {4, 1}, {2, 1}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs));
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+ IntPair(4, 1)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ std::vector<IntPair> int_pairs;
+ cont.insert(std::begin(int_pairs), std::end(int_pairs));
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+ IntPair(4, 1)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ IntPair int_pairs[] = {{1, 1}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs));
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+ IntPair(4, 1)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ IntPair int_pairs[] = {{1, 2}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs), KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 2), IntPair(2, 1), IntPair(3, 1),
+ IntPair(4, 1)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ IntPair int_pairs[] = {{5, 1}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs));
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+ IntPair(4, 1), IntPair(5, 1)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ IntPair int_pairs[] = {{5, 1}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs), KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+ IntPair(4, 1), IntPair(5, 1)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ IntPair int_pairs[] = {{3, 2}, {1, 2}, {4, 2}, {2, 2}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs));
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+ IntPair(4, 1)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ IntPair int_pairs[] = {{3, 2}, {1, 2}, {4, 2}, {2, 2}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs), KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 2), IntPair(2, 2), IntPair(3, 2),
+ IntPair(4, 2)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ IntPair int_pairs[] = {{3, 2}, {1, 2}, {4, 2}, {2, 2}, {7, 2}, {6, 2},
+ {8, 2}, {5, 2}, {5, 3}, {6, 3}, {7, 3}, {8, 3}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs));
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+ IntPair(4, 1), IntPair(5, 2), IntPair(6, 2),
+ IntPair(7, 2), IntPair(8, 2)));
+ }
+
+ {
+ IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+ IntPair int_pairs[] = {{3, 2}, {1, 2}, {4, 2}, {2, 2}, {7, 2}, {6, 2},
+ {8, 2}, {5, 2}, {5, 3}, {6, 3}, {7, 3}, {8, 3}};
+ cont.insert(std::begin(int_pairs), std::end(int_pairs), KEEP_LAST_OF_DUPES);
+ EXPECT_THAT(cont, ElementsAre(IntPair(1, 2), IntPair(2, 2), IntPair(3, 2),
+ IntPair(4, 2), IntPair(5, 3), IntPair(6, 3),
+ IntPair(7, 3), IntPair(8, 3)));
+ }
+}
+
+// template <class... Args>
+// pair<iterator, bool> emplace(Args&&... args)
+
+TEST(FlatTree, Emplace) {
+ {
+ EmplaceableTree cont;
+
+ std::pair<EmplaceableTree::iterator, bool> result = cont.emplace();
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(cont.begin(), result.first);
+ EXPECT_EQ(1U, cont.size());
+ EXPECT_EQ(Emplaceable(), *cont.begin());
+
+ result = cont.emplace(2, 3.5);
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(std::next(cont.begin()), result.first);
+ EXPECT_EQ(2U, cont.size());
+ EXPECT_EQ(Emplaceable(2, 3.5), *result.first);
+
+ result = cont.emplace(2, 3.5);
+ EXPECT_FALSE(result.second);
+ EXPECT_EQ(std::next(cont.begin()), result.first);
+ EXPECT_EQ(2U, cont.size());
+ EXPECT_EQ(Emplaceable(2, 3.5), *result.first);
+ }
+ {
+ IntTree cont;
+
+ std::pair<IntTree::iterator, bool> result = cont.emplace(2);
+ EXPECT_TRUE(result.second);
+ EXPECT_EQ(cont.begin(), result.first);
+ EXPECT_EQ(1U, cont.size());
+ EXPECT_EQ(2, *result.first);
+ }
+}
+
+// template <class... Args>
+// iterator emplace_hint(const_iterator position_hint, Args&&... args)
+
+TEST(FlatTree, EmplacePosition) {
+ {
+ EmplaceableTree cont;
+
+ EmplaceableTree::iterator result = cont.emplace_hint(cont.cend());
+ EXPECT_EQ(cont.begin(), result);
+ EXPECT_EQ(1U, cont.size());
+ EXPECT_EQ(Emplaceable(), *cont.begin());
+
+ result = cont.emplace_hint(cont.cend(), 2, 3.5);
+ EXPECT_EQ(std::next(cont.begin()), result);
+ EXPECT_EQ(2U, cont.size());
+ EXPECT_EQ(Emplaceable(2, 3.5), *result);
+
+ result = cont.emplace_hint(cont.cbegin(), 2, 3.5);
+ EXPECT_EQ(std::next(cont.begin()), result);
+ EXPECT_EQ(2U, cont.size());
+ EXPECT_EQ(Emplaceable(2, 3.5), *result);
+ }
+ {
+ IntTree cont;
+
+ IntTree::iterator result = cont.emplace_hint(cont.cend(), 2);
+ EXPECT_EQ(cont.begin(), result);
+ EXPECT_EQ(1U, cont.size());
+ EXPECT_EQ(2, *result);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Erase operations.
+
+// iterator erase(const_iterator position_hint)
+
+TEST(FlatTree, ErasePosition) {
+ {
+ IntTree cont({1, 2, 3, 4, 5, 6, 7, 8});
+
+ IntTree::iterator it = cont.erase(std::next(cont.cbegin(), 3));
+ EXPECT_EQ(std::next(cont.begin(), 3), it);
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 5, 6, 7, 8));
+
+ it = cont.erase(std::next(cont.cbegin(), 0));
+ EXPECT_EQ(cont.begin(), it);
+ EXPECT_THAT(cont, ElementsAre(2, 3, 5, 6, 7, 8));
+
+ it = cont.erase(std::next(cont.cbegin(), 5));
+ EXPECT_EQ(cont.end(), it);
+ EXPECT_THAT(cont, ElementsAre(2, 3, 5, 6, 7));
+
+ it = cont.erase(std::next(cont.cbegin(), 1));
+ EXPECT_EQ(std::next(cont.begin()), it);
+ EXPECT_THAT(cont, ElementsAre(2, 5, 6, 7));
+
+ it = cont.erase(std::next(cont.cbegin(), 2));
+ EXPECT_EQ(std::next(cont.begin(), 2), it);
+ EXPECT_THAT(cont, ElementsAre(2, 5, 7));
+
+ it = cont.erase(std::next(cont.cbegin(), 2));
+ EXPECT_EQ(std::next(cont.begin(), 2), it);
+ EXPECT_THAT(cont, ElementsAre(2, 5));
+
+ it = cont.erase(std::next(cont.cbegin(), 0));
+ EXPECT_EQ(std::next(cont.begin(), 0), it);
+ EXPECT_THAT(cont, ElementsAre(5));
+
+ it = cont.erase(cont.cbegin());
+ EXPECT_EQ(cont.begin(), it);
+ EXPECT_EQ(cont.end(), it);
+ }
+ // This is LWG #2059.
+ // There is a potential ambiguity between erase with an iterator and erase
+ // with a key, if key has a templated constructor.
+ {
+ using T = TemplateConstructor;
+
+ flat_tree<T, T, GetKeyFromValueIdentity<T>, std::less<>> cont;
+ T v(0);
+
+ auto it = cont.find(v);
+ if (it != cont.end())
+ cont.erase(it);
+ }
+}
+
+// iterator erase(const_iterator first, const_iterator last)
+
+TEST(FlatTree, EraseRange) {
+ IntTree cont({1, 2, 3, 4, 5, 6, 7, 8});
+
+ IntTree::iterator it =
+ cont.erase(std::next(cont.cbegin(), 5), std::next(cont.cbegin(), 5));
+ EXPECT_EQ(std::next(cont.begin(), 5), it);
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 4, 5, 6, 7, 8));
+
+ it = cont.erase(std::next(cont.cbegin(), 3), std::next(cont.cbegin(), 4));
+ EXPECT_EQ(std::next(cont.begin(), 3), it);
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 5, 6, 7, 8));
+
+ it = cont.erase(std::next(cont.cbegin(), 2), std::next(cont.cbegin(), 5));
+ EXPECT_EQ(std::next(cont.begin(), 2), it);
+ EXPECT_THAT(cont, ElementsAre(1, 2, 7, 8));
+
+ it = cont.erase(std::next(cont.cbegin(), 0), std::next(cont.cbegin(), 2));
+ EXPECT_EQ(std::next(cont.begin(), 0), it);
+ EXPECT_THAT(cont, ElementsAre(7, 8));
+
+ it = cont.erase(cont.cbegin(), cont.cend());
+ EXPECT_EQ(cont.begin(), it);
+ EXPECT_EQ(cont.end(), it);
+}
+
+// size_type erase(const key_type& key)
+
+TEST(FlatTree, EraseKey) {
+ IntTree cont({1, 2, 3, 4, 5, 6, 7, 8});
+
+ EXPECT_EQ(0U, cont.erase(9));
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 4, 5, 6, 7, 8));
+
+ EXPECT_EQ(1U, cont.erase(4));
+ EXPECT_THAT(cont, ElementsAre(1, 2, 3, 5, 6, 7, 8));
+
+ EXPECT_EQ(1U, cont.erase(1));
+ EXPECT_THAT(cont, ElementsAre(2, 3, 5, 6, 7, 8));
+
+ EXPECT_EQ(1U, cont.erase(8));
+ EXPECT_THAT(cont, ElementsAre(2, 3, 5, 6, 7));
+
+ EXPECT_EQ(1U, cont.erase(3));
+ EXPECT_THAT(cont, ElementsAre(2, 5, 6, 7));
+
+ EXPECT_EQ(1U, cont.erase(6));
+ EXPECT_THAT(cont, ElementsAre(2, 5, 7));
+
+ EXPECT_EQ(1U, cont.erase(7));
+ EXPECT_THAT(cont, ElementsAre(2, 5));
+
+ EXPECT_EQ(1U, cont.erase(2));
+ EXPECT_THAT(cont, ElementsAre(5));
+
+ EXPECT_EQ(1U, cont.erase(5));
+ EXPECT_THAT(cont, ElementsAre());
+}
+
+// ----------------------------------------------------------------------------
+// Comparators.
+
+// key_compare key_comp() const
+
+TEST(FlatTree, KeyComp) {
+ ReversedTree cont({1, 2, 3, 4, 5});
+
+ EXPECT_TRUE(std::is_sorted(cont.begin(), cont.end(), cont.key_comp()));
+ int new_elements[] = {6, 7, 8, 9, 10};
+ std::copy(std::begin(new_elements), std::end(new_elements),
+ std::inserter(cont, cont.end()));
+ EXPECT_TRUE(std::is_sorted(cont.begin(), cont.end(), cont.key_comp()));
+}
+
+// value_compare value_comp() const
+
+TEST(FlatTree, ValueComp) {
+ ReversedTree cont({1, 2, 3, 4, 5});
+
+ EXPECT_TRUE(std::is_sorted(cont.begin(), cont.end(), cont.value_comp()));
+ int new_elements[] = {6, 7, 8, 9, 10};
+ std::copy(std::begin(new_elements), std::end(new_elements),
+ std::inserter(cont, cont.end()));
+ EXPECT_TRUE(std::is_sorted(cont.begin(), cont.end(), cont.value_comp()));
+}
+
+// ----------------------------------------------------------------------------
+// Search operations.
+
+// size_type count(const key_type& key) const
+
+TEST(FlatTree, Count) {
+ const IntTree cont({5, 6, 7, 8, 9, 10, 11, 12});
+
+ EXPECT_EQ(1U, cont.count(5));
+ EXPECT_EQ(1U, cont.count(6));
+ EXPECT_EQ(1U, cont.count(7));
+ EXPECT_EQ(1U, cont.count(8));
+ EXPECT_EQ(1U, cont.count(9));
+ EXPECT_EQ(1U, cont.count(10));
+ EXPECT_EQ(1U, cont.count(11));
+ EXPECT_EQ(1U, cont.count(12));
+ EXPECT_EQ(0U, cont.count(4));
+}
+
+// iterator find(const key_type& key)
+// const_iterator find(const key_type& key) const
+
+TEST(FlatTree, Find) {
+ {
+ IntTree cont({5, 6, 7, 8, 9, 10, 11, 12});
+
+ EXPECT_EQ(cont.begin(), cont.find(5));
+ EXPECT_EQ(std::next(cont.begin()), cont.find(6));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.find(7));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.find(8));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.find(9));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.find(10));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.find(11));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.find(12));
+ EXPECT_EQ(std::next(cont.begin(), 8), cont.find(4));
+ }
+ {
+ const IntTree cont({5, 6, 7, 8, 9, 10, 11, 12});
+
+ EXPECT_EQ(cont.begin(), cont.find(5));
+ EXPECT_EQ(std::next(cont.begin()), cont.find(6));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.find(7));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.find(8));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.find(9));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.find(10));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.find(11));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.find(12));
+ EXPECT_EQ(std::next(cont.begin(), 8), cont.find(4));
+ }
+}
+
+// pair<iterator, iterator> equal_range(const key_type& key)
+// pair<const_iterator, const_iterator> equal_range(const key_type& key) const
+
+TEST(FlatTree, EqualRange) {
+ {
+ IntTree cont({5, 7, 9, 11, 13, 15, 17, 19});
+
+ std::pair<IntTree::iterator, IntTree::iterator> result =
+ cont.equal_range(5);
+ EXPECT_EQ(std::next(cont.begin(), 0), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 1), result.second);
+ result = cont.equal_range(7);
+ EXPECT_EQ(std::next(cont.begin(), 1), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 2), result.second);
+ result = cont.equal_range(9);
+ EXPECT_EQ(std::next(cont.begin(), 2), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 3), result.second);
+ result = cont.equal_range(11);
+ EXPECT_EQ(std::next(cont.begin(), 3), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 4), result.second);
+ result = cont.equal_range(13);
+ EXPECT_EQ(std::next(cont.begin(), 4), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 5), result.second);
+ result = cont.equal_range(15);
+ EXPECT_EQ(std::next(cont.begin(), 5), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 6), result.second);
+ result = cont.equal_range(17);
+ EXPECT_EQ(std::next(cont.begin(), 6), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 7), result.second);
+ result = cont.equal_range(19);
+ EXPECT_EQ(std::next(cont.begin(), 7), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 8), result.second);
+ result = cont.equal_range(4);
+ EXPECT_EQ(std::next(cont.begin(), 0), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 0), result.second);
+ result = cont.equal_range(6);
+ EXPECT_EQ(std::next(cont.begin(), 1), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 1), result.second);
+ result = cont.equal_range(8);
+ EXPECT_EQ(std::next(cont.begin(), 2), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 2), result.second);
+ result = cont.equal_range(10);
+ EXPECT_EQ(std::next(cont.begin(), 3), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 3), result.second);
+ result = cont.equal_range(12);
+ EXPECT_EQ(std::next(cont.begin(), 4), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 4), result.second);
+ result = cont.equal_range(14);
+ EXPECT_EQ(std::next(cont.begin(), 5), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 5), result.second);
+ result = cont.equal_range(16);
+ EXPECT_EQ(std::next(cont.begin(), 6), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 6), result.second);
+ result = cont.equal_range(18);
+ EXPECT_EQ(std::next(cont.begin(), 7), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 7), result.second);
+ result = cont.equal_range(20);
+ EXPECT_EQ(std::next(cont.begin(), 8), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 8), result.second);
+ }
+ {
+ const IntTree cont({5, 7, 9, 11, 13, 15, 17, 19});
+
+ std::pair<IntTree::const_iterator, IntTree::const_iterator> result =
+ cont.equal_range(5);
+ EXPECT_EQ(std::next(cont.begin(), 0), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 1), result.second);
+ result = cont.equal_range(7);
+ EXPECT_EQ(std::next(cont.begin(), 1), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 2), result.second);
+ result = cont.equal_range(9);
+ EXPECT_EQ(std::next(cont.begin(), 2), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 3), result.second);
+ result = cont.equal_range(11);
+ EXPECT_EQ(std::next(cont.begin(), 3), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 4), result.second);
+ result = cont.equal_range(13);
+ EXPECT_EQ(std::next(cont.begin(), 4), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 5), result.second);
+ result = cont.equal_range(15);
+ EXPECT_EQ(std::next(cont.begin(), 5), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 6), result.second);
+ result = cont.equal_range(17);
+ EXPECT_EQ(std::next(cont.begin(), 6), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 7), result.second);
+ result = cont.equal_range(19);
+ EXPECT_EQ(std::next(cont.begin(), 7), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 8), result.second);
+ result = cont.equal_range(4);
+ EXPECT_EQ(std::next(cont.begin(), 0), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 0), result.second);
+ result = cont.equal_range(6);
+ EXPECT_EQ(std::next(cont.begin(), 1), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 1), result.second);
+ result = cont.equal_range(8);
+ EXPECT_EQ(std::next(cont.begin(), 2), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 2), result.second);
+ result = cont.equal_range(10);
+ EXPECT_EQ(std::next(cont.begin(), 3), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 3), result.second);
+ result = cont.equal_range(12);
+ EXPECT_EQ(std::next(cont.begin(), 4), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 4), result.second);
+ result = cont.equal_range(14);
+ EXPECT_EQ(std::next(cont.begin(), 5), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 5), result.second);
+ result = cont.equal_range(16);
+ EXPECT_EQ(std::next(cont.begin(), 6), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 6), result.second);
+ result = cont.equal_range(18);
+ EXPECT_EQ(std::next(cont.begin(), 7), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 7), result.second);
+ result = cont.equal_range(20);
+ EXPECT_EQ(std::next(cont.begin(), 8), result.first);
+ EXPECT_EQ(std::next(cont.begin(), 8), result.second);
+ }
+}
+
+// iterator lower_bound(const key_type& key);
+// const_iterator lower_bound(const key_type& key) const;
+
+TEST(FlatTree, LowerBound) {
+ {
+ IntTree cont({5, 7, 9, 11, 13, 15, 17, 19});
+
+ EXPECT_EQ(cont.begin(), cont.lower_bound(5));
+ EXPECT_EQ(std::next(cont.begin()), cont.lower_bound(7));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.lower_bound(9));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.lower_bound(11));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.lower_bound(13));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.lower_bound(15));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.lower_bound(17));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.lower_bound(19));
+ EXPECT_EQ(std::next(cont.begin(), 0), cont.lower_bound(4));
+ EXPECT_EQ(std::next(cont.begin(), 1), cont.lower_bound(6));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.lower_bound(8));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.lower_bound(10));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.lower_bound(12));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.lower_bound(14));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.lower_bound(16));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.lower_bound(18));
+ EXPECT_EQ(std::next(cont.begin(), 8), cont.lower_bound(20));
+ }
+ {
+ const IntTree cont({5, 7, 9, 11, 13, 15, 17, 19});
+
+ EXPECT_EQ(cont.begin(), cont.lower_bound(5));
+ EXPECT_EQ(std::next(cont.begin()), cont.lower_bound(7));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.lower_bound(9));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.lower_bound(11));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.lower_bound(13));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.lower_bound(15));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.lower_bound(17));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.lower_bound(19));
+ EXPECT_EQ(std::next(cont.begin(), 0), cont.lower_bound(4));
+ EXPECT_EQ(std::next(cont.begin(), 1), cont.lower_bound(6));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.lower_bound(8));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.lower_bound(10));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.lower_bound(12));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.lower_bound(14));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.lower_bound(16));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.lower_bound(18));
+ EXPECT_EQ(std::next(cont.begin(), 8), cont.lower_bound(20));
+ }
+}
+
+// iterator upper_bound(const key_type& key)
+// const_iterator upper_bound(const key_type& key) const
+
+TEST(FlatTree, UpperBound) {
+ {
+ IntTree cont({5, 7, 9, 11, 13, 15, 17, 19});
+
+ EXPECT_EQ(std::next(cont.begin(), 1), cont.upper_bound(5));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.upper_bound(7));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.upper_bound(9));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.upper_bound(11));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.upper_bound(13));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.upper_bound(15));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.upper_bound(17));
+ EXPECT_EQ(std::next(cont.begin(), 8), cont.upper_bound(19));
+ EXPECT_EQ(std::next(cont.begin(), 0), cont.upper_bound(4));
+ EXPECT_EQ(std::next(cont.begin(), 1), cont.upper_bound(6));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.upper_bound(8));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.upper_bound(10));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.upper_bound(12));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.upper_bound(14));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.upper_bound(16));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.upper_bound(18));
+ EXPECT_EQ(std::next(cont.begin(), 8), cont.upper_bound(20));
+ }
+ {
+ const IntTree cont({5, 7, 9, 11, 13, 15, 17, 19});
+
+ EXPECT_EQ(std::next(cont.begin(), 1), cont.upper_bound(5));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.upper_bound(7));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.upper_bound(9));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.upper_bound(11));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.upper_bound(13));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.upper_bound(15));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.upper_bound(17));
+ EXPECT_EQ(std::next(cont.begin(), 8), cont.upper_bound(19));
+ EXPECT_EQ(std::next(cont.begin(), 0), cont.upper_bound(4));
+ EXPECT_EQ(std::next(cont.begin(), 1), cont.upper_bound(6));
+ EXPECT_EQ(std::next(cont.begin(), 2), cont.upper_bound(8));
+ EXPECT_EQ(std::next(cont.begin(), 3), cont.upper_bound(10));
+ EXPECT_EQ(std::next(cont.begin(), 4), cont.upper_bound(12));
+ EXPECT_EQ(std::next(cont.begin(), 5), cont.upper_bound(14));
+ EXPECT_EQ(std::next(cont.begin(), 6), cont.upper_bound(16));
+ EXPECT_EQ(std::next(cont.begin(), 7), cont.upper_bound(18));
+ EXPECT_EQ(std::next(cont.begin(), 8), cont.upper_bound(20));
+ }
+}
+
+// ----------------------------------------------------------------------------
+// General operations.
+
+// void swap(flat_tree& other)
+// void swap(flat_tree& lhs, flat_tree& rhs)
+
+TEST(FlatTreeOurs, Swap) {
+ IntTree x({1, 2, 3});
+ IntTree y({4});
+ swap(x, y);
+ EXPECT_THAT(x, ElementsAre(4));
+ EXPECT_THAT(y, ElementsAre(1, 2, 3));
+
+ y.swap(x);
+ EXPECT_THAT(x, ElementsAre(1, 2, 3));
+ EXPECT_THAT(y, ElementsAre(4));
+}
+
+// bool operator==(const flat_tree& lhs, const flat_tree& rhs)
+// bool operator!=(const flat_tree& lhs, const flat_tree& rhs)
+// bool operator<(const flat_tree& lhs, const flat_tree& rhs)
+// bool operator>(const flat_tree& lhs, const flat_tree& rhs)
+// bool operator<=(const flat_tree& lhs, const flat_tree& rhs)
+// bool operator>=(const flat_tree& lhs, const flat_tree& rhs)
+
+TEST(FlatTree, Comparison) {
+ // Provided comparator does not participate in comparison.
+ ReversedTree biggest({3});
+ ReversedTree smallest({1});
+ ReversedTree middle({1, 2});
+
+ EXPECT_EQ(biggest, biggest);
+ EXPECT_NE(biggest, smallest);
+ EXPECT_LT(smallest, middle);
+ EXPECT_LE(smallest, middle);
+ EXPECT_LE(middle, middle);
+ EXPECT_GT(biggest, middle);
+ EXPECT_GE(biggest, middle);
+ EXPECT_GE(biggest, biggest);
+}
+
+TEST(FlatSet, EraseIf) {
+ IntTree x;
+ EraseIf(x, [](int) { return false; });
+ EXPECT_THAT(x, ElementsAre());
+
+ x = {1, 2, 3};
+ EraseIf(x, [](int elem) { return !(elem & 1); });
+ EXPECT_THAT(x, ElementsAre(1, 3));
+
+ x = {1, 2, 3, 4};
+ EraseIf(x, [](int elem) { return elem & 1; });
+ EXPECT_THAT(x, ElementsAre(2, 4));
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/containers/hash_tables_unittest.cc b/base/containers/hash_tables_unittest.cc
new file mode 100644
index 0000000000..6072e5dc91
--- /dev/null
+++ b/base/containers/hash_tables_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/hash_tables.h"
+
+#include <stdint.h>
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class HashPairTest : public testing::Test {
+};
+
+#define INSERT_PAIR_TEST(Type, value1, value2) \
+ { \
+ Type pair(value1, value2); \
+ base::hash_map<Type, int> map; \
+ map[pair] = 1; \
+ }
+
+// Verify that a hash_map can be constructed for pairs of integers of various
+// sizes.
+TEST_F(HashPairTest, IntegerPairs) {
+ typedef std::pair<int16_t, int16_t> Int16Int16Pair;
+ typedef std::pair<int16_t, int32_t> Int16Int32Pair;
+ typedef std::pair<int16_t, int64_t> Int16Int64Pair;
+
+ INSERT_PAIR_TEST(Int16Int16Pair, 4, 6);
+ INSERT_PAIR_TEST(Int16Int32Pair, 9, (1 << 29) + 378128932);
+ INSERT_PAIR_TEST(Int16Int64Pair, 10,
+ (INT64_C(1) << 60) + INT64_C(78931732321));
+
+ typedef std::pair<int32_t, int16_t> Int32Int16Pair;
+ typedef std::pair<int32_t, int32_t> Int32Int32Pair;
+ typedef std::pair<int32_t, int64_t> Int32Int64Pair;
+
+ INSERT_PAIR_TEST(Int32Int16Pair, 4, 6);
+ INSERT_PAIR_TEST(Int32Int32Pair, 9, (1 << 29) + 378128932);
+ INSERT_PAIR_TEST(Int32Int64Pair, 10,
+ (INT64_C(1) << 60) + INT64_C(78931732321));
+
+ typedef std::pair<int64_t, int16_t> Int64Int16Pair;
+ typedef std::pair<int64_t, int32_t> Int64Int32Pair;
+ typedef std::pair<int64_t, int64_t> Int64Int64Pair;
+
+ INSERT_PAIR_TEST(Int64Int16Pair, 4, 6);
+ INSERT_PAIR_TEST(Int64Int32Pair, 9, (1 << 29) + 378128932);
+ INSERT_PAIR_TEST(Int64Int64Pair, 10,
+ (INT64_C(1) << 60) + INT64_C(78931732321));
+}
+
+// Verify that base::hash_set<const char*> compares by pointer value, not as C
+// strings.
+TEST(HashTableTest, CharPointers) {
+ std::string str1("hello");
+ std::string str2("hello");
+ base::hash_set<const char*> set;
+
+ set.insert(str1.c_str());
+ EXPECT_EQ(1u, set.count(str1.c_str()));
+ EXPECT_EQ(0u, set.count(str2.c_str()));
+}
+
+} // namespace
diff --git a/base/containers/id_map.h b/base/containers/id_map.h
new file mode 100644
index 0000000000..4c816da376
--- /dev/null
+++ b/base/containers/id_map.h
@@ -0,0 +1,290 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_ID_MAP_H_
+#define BASE_CONTAINERS_ID_MAP_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+
+#include "base/containers/flat_set.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+
+namespace base {
+
+// This object maintains a list of IDs that can be quickly converted to
+// pointers to objects. It is implemented as a hash table, optimized for
+// relatively small data sets (in the common case, there will be exactly one
+// item in the list).
+//
+// Items can be inserted into the container with arbitrary ID, but the caller
+// must ensure they are unique. Inserting IDs and relying on automatically
+// generated ones is not allowed because they can collide.
+
+// The map's value type (the V param) can be any dereferenceable type, such as a
+// raw pointer or smart pointer
+template <typename V, typename K = int32_t>
+class IDMap final {
+ public:
+ using KeyType = K;
+
+ private:
+ using T = typename std::remove_reference<decltype(*V())>::type;
+
+ using HashTable = std::unordered_map<KeyType, V>;
+
+ public:
+ IDMap() : iteration_depth_(0), next_id_(1), check_on_null_data_(false) {
+ // A number of consumers of IDMap create it on one thread but always
+ // access it from a different, but consistent, thread (or sequence)
+ // post-construction. The first call to CalledOnValidSequence() will re-bind
+ // it.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+ }
+
+ ~IDMap() {
+ // Many IDMap's are static, and hence will be destroyed on the main
+ // thread. However, all the accesses may take place on another thread (or
+ // sequence), such as the IO thread. Detaching again to clean this up.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+ }
+
+ // Sets whether Add and Replace should DCHECK if passed in NULL data.
+ // Default is false.
+ void set_check_on_null_data(bool value) { check_on_null_data_ = value; }
+
+ // Adds a view with an automatically generated unique ID. See AddWithID.
+ KeyType Add(V data) { return AddInternal(std::move(data)); }
+
+ // Adds a new data member with the specified ID. The ID must not be in
+ // the list. The caller either must generate all unique IDs itself and use
+ // this function, or allow this object to generate IDs and call Add. These
+ // two methods may not be mixed, or duplicate IDs may be generated.
+ void AddWithID(V data, KeyType id) { AddWithIDInternal(std::move(data), id); }
+
+ void Remove(KeyType id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ typename HashTable::iterator i = data_.find(id);
+ if (i == data_.end() || IsRemoved(id)) {
+ NOTREACHED() << "Attempting to remove an item not in the list";
+ return;
+ }
+
+ if (iteration_depth_ == 0) {
+ data_.erase(i);
+ } else {
+ removed_ids_.insert(id);
+ }
+ }
+
+ // Replaces the value for |id| with |new_data| and returns the existing value.
+ // Should only be called with an already added id.
+ V Replace(KeyType id, V new_data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!check_on_null_data_ || new_data);
+ typename HashTable::iterator i = data_.find(id);
+ DCHECK(i != data_.end());
+ DCHECK(!IsRemoved(id));
+
+ using std::swap;
+ swap(i->second, new_data);
+ return new_data;
+ }
+
+ void Clear() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (iteration_depth_ == 0) {
+ data_.clear();
+ } else {
+ removed_ids_.reserve(data_.size());
+ removed_ids_.insert(KeyIterator(data_.begin()), KeyIterator(data_.end()));
+ }
+ }
+
+ bool IsEmpty() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return size() == 0u;
+ }
+
+ T* Lookup(KeyType id) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ typename HashTable::const_iterator i = data_.find(id);
+ if (i == data_.end() || !i->second || IsRemoved(id))
+ return nullptr;
+ return &*i->second;
+ }
+
+ size_t size() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return data_.size() - removed_ids_.size();
+ }
+
+#if defined(UNIT_TEST)
+ int iteration_depth() const {
+ return iteration_depth_;
+ }
+#endif // defined(UNIT_TEST)
+
+ // It is safe to remove elements from the map during iteration. All iterators
+ // will remain valid.
+ template<class ReturnType>
+ class Iterator {
+ public:
+ Iterator(IDMap<V, K>* map) : map_(map), iter_(map_->data_.begin()) {
+ Init();
+ }
+
+ Iterator(const Iterator& iter)
+ : map_(iter.map_),
+ iter_(iter.iter_) {
+ Init();
+ }
+
+ const Iterator& operator=(const Iterator& iter) {
+ map_ = iter.map;
+ iter_ = iter.iter;
+ Init();
+ return *this;
+ }
+
+ ~Iterator() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
+
+ // We're going to decrement iteration depth. Make sure it's greater than
+ // zero so that it doesn't become negative.
+ DCHECK_LT(0, map_->iteration_depth_);
+
+ if (--map_->iteration_depth_ == 0)
+ map_->Compact();
+ }
+
+ bool IsAtEnd() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
+ return iter_ == map_->data_.end();
+ }
+
+ KeyType GetCurrentKey() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
+ return iter_->first;
+ }
+
+ ReturnType* GetCurrentValue() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
+ if (!iter_->second || map_->IsRemoved(iter_->first))
+ return nullptr;
+ return &*iter_->second;
+ }
+
+ void Advance() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
+ ++iter_;
+ SkipRemovedEntries();
+ }
+
+ private:
+ void Init() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_);
+ ++map_->iteration_depth_;
+ SkipRemovedEntries();
+ }
+
+ void SkipRemovedEntries() {
+ while (iter_ != map_->data_.end() && map_->IsRemoved(iter_->first))
+ ++iter_;
+ }
+
+ IDMap<V, K>* map_;
+ typename HashTable::const_iterator iter_;
+ };
+
+ typedef Iterator<T> iterator;
+ typedef Iterator<const T> const_iterator;
+
+ private:
+ // Transforms a map iterator to an iterator on the keys of the map.
+ // Used by Clear() to populate |removed_ids_| in bulk.
+ struct KeyIterator : std::iterator<std::forward_iterator_tag, KeyType> {
+ using inner_iterator = typename HashTable::iterator;
+ inner_iterator iter_;
+
+ KeyIterator(inner_iterator iter) : iter_(iter) {}
+ KeyType operator*() const { return iter_->first; }
+ KeyIterator& operator++() {
+ ++iter_;
+ return *this;
+ }
+ KeyIterator operator++(int) { return KeyIterator(iter_++); }
+ bool operator==(const KeyIterator& other) const {
+ return iter_ == other.iter_;
+ }
+ bool operator!=(const KeyIterator& other) const {
+ return iter_ != other.iter_;
+ }
+ };
+
+ KeyType AddInternal(V data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!check_on_null_data_ || data);
+ KeyType this_id = next_id_;
+ DCHECK(data_.find(this_id) == data_.end()) << "Inserting duplicate item";
+ data_[this_id] = std::move(data);
+ next_id_++;
+ return this_id;
+ }
+
+ void AddWithIDInternal(V data, KeyType id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!check_on_null_data_ || data);
+ if (IsRemoved(id)) {
+ removed_ids_.erase(id);
+ } else {
+ DCHECK(data_.find(id) == data_.end()) << "Inserting duplicate item";
+ }
+ data_[id] = std::move(data);
+ }
+
+ bool IsRemoved(KeyType key) const {
+ return removed_ids_.find(key) != removed_ids_.end();
+ }
+
+ void Compact() {
+ DCHECK_EQ(0, iteration_depth_);
+ for (const auto& i : removed_ids_)
+ data_.erase(i);
+ removed_ids_.clear();
+ }
+
+ // Keep track of how many iterators are currently iterating on us to safely
+ // handle removing items during iteration.
+ int iteration_depth_;
+
+ // Keep set of IDs that should be removed after the outermost iteration has
+ // finished. This way we manage to not invalidate the iterator when an element
+ // is removed.
+ base::flat_set<KeyType> removed_ids_;
+
+ // The next ID that we will return from Add()
+ KeyType next_id_;
+
+ HashTable data_;
+
+ // See description above setter.
+ bool check_on_null_data_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(IDMap);
+};
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_ID_MAP_H_
diff --git a/base/containers/id_map_unittest.cc b/base/containers/id_map_unittest.cc
new file mode 100644
index 0000000000..346b69f2bb
--- /dev/null
+++ b/base/containers/id_map_unittest.cc
@@ -0,0 +1,399 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/id_map.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class TestObject {
+};
+
+class DestructorCounter {
+ public:
+ explicit DestructorCounter(int* counter) : counter_(counter) {}
+ ~DestructorCounter() { ++(*counter_); }
+
+ private:
+ int* counter_;
+};
+
+} // namespace
+
+TEST(IDMapTest, Basic) {
+ IDMap<TestObject*> map;
+ EXPECT_TRUE(map.IsEmpty());
+ EXPECT_EQ(0U, map.size());
+
+ TestObject obj1;
+ TestObject obj2;
+
+ int32_t id1 = map.Add(&obj1);
+ EXPECT_FALSE(map.IsEmpty());
+ EXPECT_EQ(1U, map.size());
+ EXPECT_EQ(&obj1, map.Lookup(id1));
+
+ int32_t id2 = map.Add(&obj2);
+ EXPECT_FALSE(map.IsEmpty());
+ EXPECT_EQ(2U, map.size());
+
+ EXPECT_EQ(&obj1, map.Lookup(id1));
+ EXPECT_EQ(&obj2, map.Lookup(id2));
+
+ map.Remove(id1);
+ EXPECT_FALSE(map.IsEmpty());
+ EXPECT_EQ(1U, map.size());
+
+ map.Remove(id2);
+ EXPECT_TRUE(map.IsEmpty());
+ EXPECT_EQ(0U, map.size());
+
+ map.AddWithID(&obj1, 1);
+ map.AddWithID(&obj2, 2);
+ EXPECT_EQ(&obj1, map.Lookup(1));
+ EXPECT_EQ(&obj2, map.Lookup(2));
+
+ EXPECT_EQ(&obj2, map.Replace(2, &obj1));
+ EXPECT_EQ(&obj1, map.Lookup(2));
+
+ EXPECT_EQ(0, map.iteration_depth());
+}
+
+TEST(IDMapTest, IteratorRemainsValidWhenRemovingCurrentElement) {
+ IDMap<TestObject*> map;
+
+ TestObject obj1;
+ TestObject obj2;
+ TestObject obj3;
+
+ map.Add(&obj1);
+ map.Add(&obj2);
+ map.Add(&obj3);
+
+ {
+ IDMap<TestObject*>::const_iterator iter(&map);
+
+ EXPECT_EQ(1, map.iteration_depth());
+
+ while (!iter.IsAtEnd()) {
+ map.Remove(iter.GetCurrentKey());
+ iter.Advance();
+ }
+
+ // Test that while an iterator is still in scope, we get the map emptiness
+ // right (http://crbug.com/35571).
+ EXPECT_TRUE(map.IsEmpty());
+ EXPECT_EQ(0U, map.size());
+ }
+
+ EXPECT_TRUE(map.IsEmpty());
+ EXPECT_EQ(0U, map.size());
+
+ EXPECT_EQ(0, map.iteration_depth());
+}
+
+TEST(IDMapTest, IteratorRemainsValidWhenRemovingOtherElements) {
+ IDMap<TestObject*> map;
+
+ const int kCount = 5;
+ TestObject obj[kCount];
+
+ for (int i = 0; i < kCount; i++)
+ map.Add(&obj[i]);
+
+ // IDMap has no predictable iteration order.
+ int32_t ids_in_iteration_order[kCount];
+ const TestObject* objs_in_iteration_order[kCount];
+ int counter = 0;
+ for (IDMap<TestObject*>::const_iterator iter(&map); !iter.IsAtEnd();
+ iter.Advance()) {
+ ids_in_iteration_order[counter] = iter.GetCurrentKey();
+ objs_in_iteration_order[counter] = iter.GetCurrentValue();
+ counter++;
+ }
+
+ counter = 0;
+ for (IDMap<TestObject*>::const_iterator iter(&map); !iter.IsAtEnd();
+ iter.Advance()) {
+ EXPECT_EQ(1, map.iteration_depth());
+
+ switch (counter) {
+ case 0:
+ EXPECT_EQ(ids_in_iteration_order[0], iter.GetCurrentKey());
+ EXPECT_EQ(objs_in_iteration_order[0], iter.GetCurrentValue());
+ map.Remove(ids_in_iteration_order[1]);
+ break;
+ case 1:
+ EXPECT_EQ(ids_in_iteration_order[2], iter.GetCurrentKey());
+ EXPECT_EQ(objs_in_iteration_order[2], iter.GetCurrentValue());
+ map.Remove(ids_in_iteration_order[3]);
+ break;
+ case 2:
+ EXPECT_EQ(ids_in_iteration_order[4], iter.GetCurrentKey());
+ EXPECT_EQ(objs_in_iteration_order[4], iter.GetCurrentValue());
+ map.Remove(ids_in_iteration_order[0]);
+ break;
+ default:
+ FAIL() << "should not have that many elements";
+ break;
+ }
+
+ counter++;
+ }
+
+ EXPECT_EQ(0, map.iteration_depth());
+}
+
+TEST(IDMapTest, CopyIterator) {
+ IDMap<TestObject*> map;
+
+ TestObject obj1;
+ TestObject obj2;
+ TestObject obj3;
+
+ map.Add(&obj1);
+ map.Add(&obj2);
+ map.Add(&obj3);
+
+ EXPECT_EQ(0, map.iteration_depth());
+
+ {
+ IDMap<TestObject*>::const_iterator iter1(&map);
+ EXPECT_EQ(1, map.iteration_depth());
+
+ // Make sure that copying the iterator correctly increments
+ // map's iteration depth.
+ IDMap<TestObject*>::const_iterator iter2(iter1);
+ EXPECT_EQ(2, map.iteration_depth());
+ }
+
+ // Make sure after destroying all iterators the map's iteration depth
+ // returns to initial state.
+ EXPECT_EQ(0, map.iteration_depth());
+}
+
+TEST(IDMapTest, AssignIterator) {
+ IDMap<TestObject*> map;
+
+ TestObject obj1;
+ TestObject obj2;
+ TestObject obj3;
+
+ map.Add(&obj1);
+ map.Add(&obj2);
+ map.Add(&obj3);
+
+ EXPECT_EQ(0, map.iteration_depth());
+
+ {
+ IDMap<TestObject*>::const_iterator iter1(&map);
+ EXPECT_EQ(1, map.iteration_depth());
+
+ IDMap<TestObject*>::const_iterator iter2(&map);
+ EXPECT_EQ(2, map.iteration_depth());
+
+ // Make sure that assigning the iterator correctly updates
+ // map's iteration depth (-1 for destruction, +1 for assignment).
+ EXPECT_EQ(2, map.iteration_depth());
+ }
+
+ // Make sure after destroying all iterators the map's iteration depth
+ // returns to initial state.
+ EXPECT_EQ(0, map.iteration_depth());
+}
+
+TEST(IDMapTest, IteratorRemainsValidWhenClearing) {
+ IDMap<TestObject*> map;
+
+ const int kCount = 5;
+ TestObject obj[kCount];
+
+ for (int i = 0; i < kCount; i++)
+ map.Add(&obj[i]);
+
+ // IDMap has no predictable iteration order.
+ int32_t ids_in_iteration_order[kCount];
+ const TestObject* objs_in_iteration_order[kCount];
+ int counter = 0;
+ for (IDMap<TestObject*>::const_iterator iter(&map); !iter.IsAtEnd();
+ iter.Advance()) {
+ ids_in_iteration_order[counter] = iter.GetCurrentKey();
+ objs_in_iteration_order[counter] = iter.GetCurrentValue();
+ counter++;
+ }
+
+ counter = 0;
+ for (IDMap<TestObject*>::const_iterator iter(&map); !iter.IsAtEnd();
+ iter.Advance()) {
+ switch (counter) {
+ case 0:
+ EXPECT_EQ(ids_in_iteration_order[0], iter.GetCurrentKey());
+ EXPECT_EQ(objs_in_iteration_order[0], iter.GetCurrentValue());
+ break;
+ case 1:
+ EXPECT_EQ(ids_in_iteration_order[1], iter.GetCurrentKey());
+ EXPECT_EQ(objs_in_iteration_order[1], iter.GetCurrentValue());
+ map.Clear();
+ EXPECT_TRUE(map.IsEmpty());
+ EXPECT_EQ(0U, map.size());
+ break;
+ default:
+ FAIL() << "should not have that many elements";
+ break;
+ }
+ counter++;
+ }
+
+ EXPECT_TRUE(map.IsEmpty());
+ EXPECT_EQ(0U, map.size());
+}
+
+TEST(IDMapTest, OwningPointersDeletesThemOnRemove) {
+ const int kCount = 3;
+
+ int external_del_count = 0;
+ DestructorCounter* external_obj[kCount];
+ int map_external_ids[kCount];
+
+ int owned_del_count = 0;
+ int map_owned_ids[kCount];
+
+ IDMap<DestructorCounter*> map_external;
+ IDMap<std::unique_ptr<DestructorCounter>> map_owned;
+
+ for (int i = 0; i < kCount; ++i) {
+ external_obj[i] = new DestructorCounter(&external_del_count);
+ map_external_ids[i] = map_external.Add(external_obj[i]);
+
+ map_owned_ids[i] =
+ map_owned.Add(std::make_unique<DestructorCounter>(&owned_del_count));
+ }
+
+ for (int i = 0; i < kCount; ++i) {
+ EXPECT_EQ(external_del_count, 0);
+ EXPECT_EQ(owned_del_count, i);
+
+ map_external.Remove(map_external_ids[i]);
+ map_owned.Remove(map_owned_ids[i]);
+ }
+
+ for (int i = 0; i < kCount; ++i) {
+ delete external_obj[i];
+ }
+
+ EXPECT_EQ(external_del_count, kCount);
+ EXPECT_EQ(owned_del_count, kCount);
+}
+
+TEST(IDMapTest, OwningPointersDeletesThemOnClear) {
+ const int kCount = 3;
+
+ int external_del_count = 0;
+ DestructorCounter* external_obj[kCount];
+
+ int owned_del_count = 0;
+
+ IDMap<DestructorCounter*> map_external;
+ IDMap<std::unique_ptr<DestructorCounter>> map_owned;
+
+ for (int i = 0; i < kCount; ++i) {
+ external_obj[i] = new DestructorCounter(&external_del_count);
+ map_external.Add(external_obj[i]);
+
+ map_owned.Add(std::make_unique<DestructorCounter>(&owned_del_count));
+ }
+
+ EXPECT_EQ(external_del_count, 0);
+ EXPECT_EQ(owned_del_count, 0);
+
+ map_external.Clear();
+ map_owned.Clear();
+
+ EXPECT_EQ(external_del_count, 0);
+ EXPECT_EQ(owned_del_count, kCount);
+
+ for (int i = 0; i < kCount; ++i) {
+ delete external_obj[i];
+ }
+
+ EXPECT_EQ(external_del_count, kCount);
+ EXPECT_EQ(owned_del_count, kCount);
+}
+
+TEST(IDMapTest, OwningPointersDeletesThemOnDestruct) {
+ const int kCount = 3;
+
+ int external_del_count = 0;
+ DestructorCounter* external_obj[kCount];
+
+ int owned_del_count = 0;
+
+ {
+ IDMap<DestructorCounter*> map_external;
+ IDMap<std::unique_ptr<DestructorCounter>> map_owned;
+
+ for (int i = 0; i < kCount; ++i) {
+ external_obj[i] = new DestructorCounter(&external_del_count);
+ map_external.Add(external_obj[i]);
+
+ map_owned.Add(std::make_unique<DestructorCounter>(&owned_del_count));
+ }
+ }
+
+ EXPECT_EQ(external_del_count, 0);
+
+ for (int i = 0; i < kCount; ++i) {
+ delete external_obj[i];
+ }
+
+ EXPECT_EQ(external_del_count, kCount);
+ EXPECT_EQ(owned_del_count, kCount);
+}
+
+TEST(IDMapTest, Int64KeyType) {
+ IDMap<TestObject*, int64_t> map;
+ TestObject obj1;
+ const int64_t kId1 = 999999999999999999;
+
+ map.AddWithID(&obj1, kId1);
+ EXPECT_EQ(&obj1, map.Lookup(kId1));
+
+ IDMap<TestObject*, int64_t>::const_iterator iter(&map);
+ ASSERT_FALSE(iter.IsAtEnd());
+ EXPECT_EQ(kId1, iter.GetCurrentKey());
+ EXPECT_EQ(&obj1, iter.GetCurrentValue());
+ iter.Advance();
+ ASSERT_TRUE(iter.IsAtEnd());
+
+ map.Remove(kId1);
+ EXPECT_TRUE(map.IsEmpty());
+}
+
+TEST(IDMapTest, RemovedValueHandling) {
+ TestObject obj;
+ IDMap<TestObject*> map;
+ int key = map.Add(&obj);
+
+ IDMap<TestObject*>::iterator itr(&map);
+ map.Clear();
+ EXPECT_DCHECK_DEATH(map.Remove(key));
+ EXPECT_DCHECK_DEATH(map.Replace(key, &obj));
+ EXPECT_FALSE(map.Lookup(key));
+ EXPECT_FALSE(itr.IsAtEnd());
+ EXPECT_FALSE(itr.GetCurrentValue());
+
+ EXPECT_TRUE(map.IsEmpty());
+ map.AddWithID(&obj, key);
+ EXPECT_EQ(1u, map.size());
+}
+
+} // namespace base
diff --git a/base/containers/linked_list_unittest.cc b/base/containers/linked_list_unittest.cc
new file mode 100644
index 0000000000..8e547ba3fe
--- /dev/null
+++ b/base/containers/linked_list_unittest.cc
@@ -0,0 +1,349 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/linked_list.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class Node : public LinkNode<Node> {
+ public:
+ explicit Node(int id) : id_(id) {}
+
+ int id() const { return id_; }
+
+ private:
+ int id_;
+};
+
+class MultipleInheritanceNodeBase {
+ public:
+ MultipleInheritanceNodeBase() : field_taking_up_space_(0) {}
+ int field_taking_up_space_;
+};
+
+class MultipleInheritanceNode : public MultipleInheritanceNodeBase,
+ public LinkNode<MultipleInheritanceNode> {
+ public:
+ MultipleInheritanceNode() = default;
+};
+
+class MovableNode : public LinkNode<MovableNode> {
+ public:
+ explicit MovableNode(int id) : id_(id) {}
+
+ MovableNode(MovableNode&&) = default;
+
+ int id() const { return id_; }
+
+ private:
+ int id_;
+};
+
+// Checks that when iterating |list| (either from head to tail, or from
+// tail to head, as determined by |forward|), we get back |node_ids|,
+// which is an array of size |num_nodes|.
+void ExpectListContentsForDirection(const LinkedList<Node>& list,
+ int num_nodes, const int* node_ids, bool forward) {
+ int i = 0;
+ for (const LinkNode<Node>* node = (forward ? list.head() : list.tail());
+ node != list.end();
+ node = (forward ? node->next() : node->previous())) {
+ ASSERT_LT(i, num_nodes);
+ int index_of_id = forward ? i : num_nodes - i - 1;
+ EXPECT_EQ(node_ids[index_of_id], node->value()->id());
+ ++i;
+ }
+ EXPECT_EQ(num_nodes, i);
+}
+
+void ExpectListContents(const LinkedList<Node>& list,
+ int num_nodes,
+ const int* node_ids) {
+ {
+ SCOPED_TRACE("Iterating forward (from head to tail)");
+ ExpectListContentsForDirection(list, num_nodes, node_ids, true);
+ }
+ {
+ SCOPED_TRACE("Iterating backward (from tail to head)");
+ ExpectListContentsForDirection(list, num_nodes, node_ids, false);
+ }
+}
+
+TEST(LinkedList, Empty) {
+ LinkedList<Node> list;
+ EXPECT_EQ(list.end(), list.head());
+ EXPECT_EQ(list.end(), list.tail());
+ ExpectListContents(list, 0, nullptr);
+}
+
+TEST(LinkedList, Append) {
+ LinkedList<Node> list;
+ ExpectListContents(list, 0, nullptr);
+
+ Node n1(1);
+ list.Append(&n1);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n1, list.tail());
+ {
+ const int expected[] = {1};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ Node n2(2);
+ list.Append(&n2);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n2, list.tail());
+ {
+ const int expected[] = {1, 2};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ Node n3(3);
+ list.Append(&n3);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n3, list.tail());
+ {
+ const int expected[] = {1, 2, 3};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+}
+
+TEST(LinkedList, RemoveFromList) {
+ LinkedList<Node> list;
+
+ Node n1(1);
+ Node n2(2);
+ Node n3(3);
+ Node n4(4);
+ Node n5(5);
+
+ list.Append(&n1);
+ list.Append(&n2);
+ list.Append(&n3);
+ list.Append(&n4);
+ list.Append(&n5);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n5, list.tail());
+ {
+ const int expected[] = {1, 2, 3, 4, 5};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ // Remove from the middle.
+ n3.RemoveFromList();
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n5, list.tail());
+ {
+ const int expected[] = {1, 2, 4, 5};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ // Remove from the tail.
+ n5.RemoveFromList();
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n4, list.tail());
+ {
+ const int expected[] = {1, 2, 4};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ // Remove from the head.
+ n1.RemoveFromList();
+
+ EXPECT_EQ(&n2, list.head());
+ EXPECT_EQ(&n4, list.tail());
+ {
+ const int expected[] = {2, 4};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ // Empty the list.
+ n2.RemoveFromList();
+ n4.RemoveFromList();
+
+ ExpectListContents(list, 0, nullptr);
+ EXPECT_EQ(list.end(), list.head());
+ EXPECT_EQ(list.end(), list.tail());
+
+ // Fill the list once again.
+ list.Append(&n1);
+ list.Append(&n2);
+ list.Append(&n3);
+ list.Append(&n4);
+ list.Append(&n5);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n5, list.tail());
+ {
+ const int expected[] = {1, 2, 3, 4, 5};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+}
+
+TEST(LinkedList, InsertBefore) {
+ LinkedList<Node> list;
+
+ Node n1(1);
+ Node n2(2);
+ Node n3(3);
+ Node n4(4);
+
+ list.Append(&n1);
+ list.Append(&n2);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n2, list.tail());
+ {
+ const int expected[] = {1, 2};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ n3.InsertBefore(&n2);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n2, list.tail());
+ {
+ const int expected[] = {1, 3, 2};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ n4.InsertBefore(&n1);
+
+ EXPECT_EQ(&n4, list.head());
+ EXPECT_EQ(&n2, list.tail());
+ {
+ const int expected[] = {4, 1, 3, 2};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+}
+
+TEST(LinkedList, InsertAfter) {
+ LinkedList<Node> list;
+
+ Node n1(1);
+ Node n2(2);
+ Node n3(3);
+ Node n4(4);
+
+ list.Append(&n1);
+ list.Append(&n2);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n2, list.tail());
+ {
+ const int expected[] = {1, 2};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ n3.InsertAfter(&n2);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n3, list.tail());
+ {
+ const int expected[] = {1, 2, 3};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+
+ n4.InsertAfter(&n1);
+
+ EXPECT_EQ(&n1, list.head());
+ EXPECT_EQ(&n3, list.tail());
+ {
+ const int expected[] = {1, 4, 2, 3};
+ ExpectListContents(list, arraysize(expected), expected);
+ }
+}
+
+TEST(LinkedList, MultipleInheritanceNode) {
+ MultipleInheritanceNode node;
+ EXPECT_EQ(&node, node.value());
+}
+
+TEST(LinkedList, EmptyListIsEmpty) {
+ LinkedList<Node> list;
+ EXPECT_TRUE(list.empty());
+}
+
+TEST(LinkedList, NonEmptyListIsNotEmpty) {
+ LinkedList<Node> list;
+
+ Node n(1);
+ list.Append(&n);
+
+ EXPECT_FALSE(list.empty());
+}
+
+TEST(LinkedList, EmptiedListIsEmptyAgain) {
+ LinkedList<Node> list;
+
+ Node n(1);
+ list.Append(&n);
+ n.RemoveFromList();
+
+ EXPECT_TRUE(list.empty());
+}
+
+TEST(LinkedList, NodesCanBeReused) {
+ LinkedList<Node> list1;
+ LinkedList<Node> list2;
+
+ Node n(1);
+ list1.Append(&n);
+ n.RemoveFromList();
+ list2.Append(&n);
+
+ EXPECT_EQ(list2.head()->value(), &n);
+}
+
+TEST(LinkedList, RemovedNodeHasNullNextPrevious) {
+ LinkedList<Node> list;
+
+ Node n(1);
+ list.Append(&n);
+ n.RemoveFromList();
+
+ EXPECT_EQ(nullptr, n.next());
+ EXPECT_EQ(nullptr, n.previous());
+}
+
+TEST(LinkedList, NodeMoveConstructor) {
+ LinkedList<MovableNode> list;
+
+ MovableNode n1(1);
+ MovableNode n2(2);
+ MovableNode n3(3);
+
+ list.Append(&n1);
+ list.Append(&n2);
+ list.Append(&n3);
+
+ EXPECT_EQ(&n1, n2.previous());
+ EXPECT_EQ(&n2, n1.next());
+ EXPECT_EQ(&n3, n2.next());
+ EXPECT_EQ(&n2, n3.previous());
+ EXPECT_EQ(2, n2.id());
+
+ MovableNode n2_new(std::move(n2));
+
+ EXPECT_EQ(nullptr, n2.next());
+ EXPECT_EQ(nullptr, n2.previous());
+
+ EXPECT_EQ(&n1, n2_new.previous());
+ EXPECT_EQ(&n2_new, n1.next());
+ EXPECT_EQ(&n3, n2_new.next());
+ EXPECT_EQ(&n2_new, n3.previous());
+ EXPECT_EQ(2, n2_new.id());
+}
+
+} // namespace
+} // namespace base
diff --git a/base/containers/mru_cache_unittest.cc b/base/containers/mru_cache_unittest.cc
new file mode 100644
index 0000000000..28e6f0d66b
--- /dev/null
+++ b/base/containers/mru_cache_unittest.cc
@@ -0,0 +1,394 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/mru_cache.h"
+
+#include <cstddef>
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "base/trace_event/memory_usage_estimator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+int cached_item_live_count = 0;
+
+struct CachedItem {
+ CachedItem() : value(0) {
+ cached_item_live_count++;
+ }
+
+ explicit CachedItem(int new_value) : value(new_value) {
+ cached_item_live_count++;
+ }
+
+ explicit CachedItem(const CachedItem& other) : value(other.value) {
+ cached_item_live_count++;
+ }
+
+ ~CachedItem() {
+ cached_item_live_count--;
+ }
+
+ int value;
+};
+
+} // namespace
+
+TEST(MRUCacheTest, Basic) {
+ typedef base::MRUCache<int, CachedItem> Cache;
+ Cache cache(Cache::NO_AUTO_EVICT);
+
+ // Check failure conditions
+ {
+ CachedItem test_item;
+ EXPECT_TRUE(cache.Get(0) == cache.end());
+ EXPECT_TRUE(cache.Peek(0) == cache.end());
+ }
+
+ static const int kItem1Key = 5;
+ CachedItem item1(10);
+ Cache::iterator inserted_item = cache.Put(kItem1Key, item1);
+ EXPECT_EQ(1U, cache.size());
+
+ // Check that item1 was properly inserted.
+ {
+ Cache::iterator found = cache.Get(kItem1Key);
+ EXPECT_TRUE(inserted_item == cache.begin());
+ EXPECT_TRUE(found != cache.end());
+
+ found = cache.Peek(kItem1Key);
+ EXPECT_TRUE(found != cache.end());
+
+ EXPECT_EQ(kItem1Key, found->first);
+ EXPECT_EQ(item1.value, found->second.value);
+ }
+
+ static const int kItem2Key = 7;
+ CachedItem item2(12);
+ cache.Put(kItem2Key, item2);
+ EXPECT_EQ(2U, cache.size());
+
+ // Check that item1 is the oldest since item2 was added afterwards.
+ {
+ Cache::reverse_iterator oldest = cache.rbegin();
+ ASSERT_TRUE(oldest != cache.rend());
+ EXPECT_EQ(kItem1Key, oldest->first);
+ EXPECT_EQ(item1.value, oldest->second.value);
+ }
+
+ // Check that item1 is still accessible by key.
+ {
+ Cache::iterator test_item = cache.Get(kItem1Key);
+ ASSERT_TRUE(test_item != cache.end());
+ EXPECT_EQ(kItem1Key, test_item->first);
+ EXPECT_EQ(item1.value, test_item->second.value);
+ }
+
+ // Check that retrieving item1 pushed item2 to oldest.
+ {
+ Cache::reverse_iterator oldest = cache.rbegin();
+ ASSERT_TRUE(oldest != cache.rend());
+ EXPECT_EQ(kItem2Key, oldest->first);
+ EXPECT_EQ(item2.value, oldest->second.value);
+ }
+
+ // Remove the oldest item and check that item1 is now the only member.
+ {
+ Cache::reverse_iterator next = cache.Erase(cache.rbegin());
+
+ EXPECT_EQ(1U, cache.size());
+
+ EXPECT_TRUE(next == cache.rbegin());
+ EXPECT_EQ(kItem1Key, next->first);
+ EXPECT_EQ(item1.value, next->second.value);
+
+ cache.Erase(cache.begin());
+ EXPECT_EQ(0U, cache.size());
+ }
+
+ // Check that Clear() works properly.
+ cache.Put(kItem1Key, item1);
+ cache.Put(kItem2Key, item2);
+ EXPECT_EQ(2U, cache.size());
+ cache.Clear();
+ EXPECT_EQ(0U, cache.size());
+}
+
+TEST(MRUCacheTest, GetVsPeek) {
+ typedef base::MRUCache<int, CachedItem> Cache;
+ Cache cache(Cache::NO_AUTO_EVICT);
+
+ static const int kItem1Key = 1;
+ CachedItem item1(10);
+ cache.Put(kItem1Key, item1);
+
+ static const int kItem2Key = 2;
+ CachedItem item2(20);
+ cache.Put(kItem2Key, item2);
+
+ // This should do nothing since the size is bigger than the number of items.
+ cache.ShrinkToSize(100);
+
+ // Check that item1 starts out as oldest
+ {
+ Cache::reverse_iterator iter = cache.rbegin();
+ ASSERT_TRUE(iter != cache.rend());
+ EXPECT_EQ(kItem1Key, iter->first);
+ EXPECT_EQ(item1.value, iter->second.value);
+ }
+
+ // Check that Peek doesn't change ordering
+ {
+ Cache::iterator peekiter = cache.Peek(kItem1Key);
+ ASSERT_TRUE(peekiter != cache.end());
+
+ Cache::reverse_iterator iter = cache.rbegin();
+ ASSERT_TRUE(iter != cache.rend());
+ EXPECT_EQ(kItem1Key, iter->first);
+ EXPECT_EQ(item1.value, iter->second.value);
+ }
+}
+
+TEST(MRUCacheTest, KeyReplacement) {
+ typedef base::MRUCache<int, CachedItem> Cache;
+ Cache cache(Cache::NO_AUTO_EVICT);
+
+ static const int kItem1Key = 1;
+ CachedItem item1(10);
+ cache.Put(kItem1Key, item1);
+
+ static const int kItem2Key = 2;
+ CachedItem item2(20);
+ cache.Put(kItem2Key, item2);
+
+ static const int kItem3Key = 3;
+ CachedItem item3(30);
+ cache.Put(kItem3Key, item3);
+
+ static const int kItem4Key = 4;
+ CachedItem item4(40);
+ cache.Put(kItem4Key, item4);
+
+ CachedItem item5(50);
+ cache.Put(kItem3Key, item5);
+
+ EXPECT_EQ(4U, cache.size());
+ for (int i = 0; i < 3; ++i) {
+ Cache::reverse_iterator iter = cache.rbegin();
+ ASSERT_TRUE(iter != cache.rend());
+ }
+
+ // Make it so only the most important element is there.
+ cache.ShrinkToSize(1);
+
+ Cache::iterator iter = cache.begin();
+ EXPECT_EQ(kItem3Key, iter->first);
+ EXPECT_EQ(item5.value, iter->second.value);
+}
+
+// Make sure that the owning version release its pointers properly.
+TEST(MRUCacheTest, Owning) {
+ using Cache = base::MRUCache<int, std::unique_ptr<CachedItem>>;
+ Cache cache(Cache::NO_AUTO_EVICT);
+
+ int initial_count = cached_item_live_count;
+
+ // First insert and item and then overwrite it.
+ static const int kItem1Key = 1;
+ cache.Put(kItem1Key, WrapUnique(new CachedItem(20)));
+ cache.Put(kItem1Key, WrapUnique(new CachedItem(22)));
+
+ // There should still be one item, and one extra live item.
+ Cache::iterator iter = cache.Get(kItem1Key);
+ EXPECT_EQ(1U, cache.size());
+ EXPECT_TRUE(iter != cache.end());
+ EXPECT_EQ(initial_count + 1, cached_item_live_count);
+
+ // Now remove it.
+ cache.Erase(cache.begin());
+ EXPECT_EQ(initial_count, cached_item_live_count);
+
+ // Now try another cache that goes out of scope to make sure its pointers
+ // go away.
+ {
+ Cache cache2(Cache::NO_AUTO_EVICT);
+ cache2.Put(1, WrapUnique(new CachedItem(20)));
+ cache2.Put(2, WrapUnique(new CachedItem(20)));
+ }
+
+ // There should be no objects leaked.
+ EXPECT_EQ(initial_count, cached_item_live_count);
+
+ // Check that Clear() also frees things correctly.
+ {
+ Cache cache2(Cache::NO_AUTO_EVICT);
+ cache2.Put(1, WrapUnique(new CachedItem(20)));
+ cache2.Put(2, WrapUnique(new CachedItem(20)));
+ EXPECT_EQ(initial_count + 2, cached_item_live_count);
+ cache2.Clear();
+ EXPECT_EQ(initial_count, cached_item_live_count);
+ }
+}
+
+TEST(MRUCacheTest, AutoEvict) {
+ using Cache = base::MRUCache<int, std::unique_ptr<CachedItem>>;
+ static const Cache::size_type kMaxSize = 3;
+
+ int initial_count = cached_item_live_count;
+
+ {
+ Cache cache(kMaxSize);
+
+ static const int kItem1Key = 1, kItem2Key = 2, kItem3Key = 3, kItem4Key = 4;
+ cache.Put(kItem1Key, std::make_unique<CachedItem>(20));
+ cache.Put(kItem2Key, std::make_unique<CachedItem>(21));
+ cache.Put(kItem3Key, std::make_unique<CachedItem>(22));
+ cache.Put(kItem4Key, std::make_unique<CachedItem>(23));
+
+ // The cache should only have kMaxSize items in it even though we inserted
+ // more.
+ EXPECT_EQ(kMaxSize, cache.size());
+ }
+
+ // There should be no objects leaked.
+ EXPECT_EQ(initial_count, cached_item_live_count);
+}
+
+TEST(MRUCacheTest, HashingMRUCache) {
+ // Very simple test to make sure that the hashing cache works correctly.
+ typedef base::HashingMRUCache<std::string, CachedItem> Cache;
+ Cache cache(Cache::NO_AUTO_EVICT);
+
+ CachedItem one(1);
+ cache.Put("First", one);
+
+ CachedItem two(2);
+ cache.Put("Second", two);
+
+ EXPECT_EQ(one.value, cache.Get("First")->second.value);
+ EXPECT_EQ(two.value, cache.Get("Second")->second.value);
+ cache.ShrinkToSize(1);
+ EXPECT_EQ(two.value, cache.Get("Second")->second.value);
+ EXPECT_TRUE(cache.Get("First") == cache.end());
+}
+
+TEST(MRUCacheTest, Swap) {
+ typedef base::MRUCache<int, CachedItem> Cache;
+ Cache cache1(Cache::NO_AUTO_EVICT);
+
+ // Insert two items into cache1.
+ static const int kItem1Key = 1;
+ CachedItem item1(2);
+ Cache::iterator inserted_item = cache1.Put(kItem1Key, item1);
+ EXPECT_EQ(1U, cache1.size());
+
+ static const int kItem2Key = 3;
+ CachedItem item2(4);
+ cache1.Put(kItem2Key, item2);
+ EXPECT_EQ(2U, cache1.size());
+
+ // Verify cache1's elements.
+ {
+ Cache::iterator iter = cache1.begin();
+ ASSERT_TRUE(iter != cache1.end());
+ EXPECT_EQ(kItem2Key, iter->first);
+ EXPECT_EQ(item2.value, iter->second.value);
+
+ ++iter;
+ ASSERT_TRUE(iter != cache1.end());
+ EXPECT_EQ(kItem1Key, iter->first);
+ EXPECT_EQ(item1.value, iter->second.value);
+ }
+
+ // Create another cache2.
+ Cache cache2(Cache::NO_AUTO_EVICT);
+
+ // Insert three items into cache2.
+ static const int kItem3Key = 5;
+ CachedItem item3(6);
+ inserted_item = cache2.Put(kItem3Key, item3);
+ EXPECT_EQ(1U, cache2.size());
+
+ static const int kItem4Key = 7;
+ CachedItem item4(8);
+ cache2.Put(kItem4Key, item4);
+ EXPECT_EQ(2U, cache2.size());
+
+ static const int kItem5Key = 9;
+ CachedItem item5(10);
+ cache2.Put(kItem5Key, item5);
+ EXPECT_EQ(3U, cache2.size());
+
+ // Verify cache2's elements.
+ {
+ Cache::iterator iter = cache2.begin();
+ ASSERT_TRUE(iter != cache2.end());
+ EXPECT_EQ(kItem5Key, iter->first);
+ EXPECT_EQ(item5.value, iter->second.value);
+
+ ++iter;
+ ASSERT_TRUE(iter != cache2.end());
+ EXPECT_EQ(kItem4Key, iter->first);
+ EXPECT_EQ(item4.value, iter->second.value);
+
+ ++iter;
+ ASSERT_TRUE(iter != cache2.end());
+ EXPECT_EQ(kItem3Key, iter->first);
+ EXPECT_EQ(item3.value, iter->second.value);
+ }
+
+ // Swap cache1 and cache2 and verify cache2 has cache1's elements and cache1
+ // has cache2's elements.
+ cache2.Swap(cache1);
+
+ EXPECT_EQ(3U, cache1.size());
+ EXPECT_EQ(2U, cache2.size());
+
+ // Verify cache1's elements.
+ {
+ Cache::iterator iter = cache1.begin();
+ ASSERT_TRUE(iter != cache1.end());
+ EXPECT_EQ(kItem5Key, iter->first);
+ EXPECT_EQ(item5.value, iter->second.value);
+
+ ++iter;
+ ASSERT_TRUE(iter != cache1.end());
+ EXPECT_EQ(kItem4Key, iter->first);
+ EXPECT_EQ(item4.value, iter->second.value);
+
+ ++iter;
+ ASSERT_TRUE(iter != cache1.end());
+ EXPECT_EQ(kItem3Key, iter->first);
+ EXPECT_EQ(item3.value, iter->second.value);
+ }
+
+ // Verify cache2's elements.
+ {
+ Cache::iterator iter = cache2.begin();
+ ASSERT_TRUE(iter != cache2.end());
+ EXPECT_EQ(kItem2Key, iter->first);
+ EXPECT_EQ(item2.value, iter->second.value);
+
+ ++iter;
+ ASSERT_TRUE(iter != cache2.end());
+ EXPECT_EQ(kItem1Key, iter->first);
+ EXPECT_EQ(item1.value, iter->second.value);
+ }
+}
+
+TEST(MRUCacheTest, EstimateMemory) {
+ base::MRUCache<std::string, int> cache(10);
+
+ const std::string key(100u, 'a');
+ cache.Put(key, 1);
+
+ EXPECT_GT(trace_event::EstimateMemoryUsage(cache),
+ trace_event::EstimateMemoryUsage(key));
+}
+
+} // namespace base
diff --git a/base/containers/small_map_unittest.cc b/base/containers/small_map_unittest.cc
new file mode 100644
index 0000000000..6561851f9d
--- /dev/null
+++ b/base/containers/small_map_unittest.cc
@@ -0,0 +1,603 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/small_map.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <functional>
+#include <map>
+#include <unordered_map>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(SmallMap, General) {
+ small_map<std::unordered_map<int, int>> m;
+
+ EXPECT_TRUE(m.empty());
+
+ m[0] = 5;
+
+ EXPECT_FALSE(m.empty());
+ EXPECT_EQ(m.size(), 1u);
+
+ m[9] = 2;
+
+ EXPECT_FALSE(m.empty());
+ EXPECT_EQ(m.size(), 2u);
+
+ EXPECT_EQ(m[9], 2);
+ EXPECT_EQ(m[0], 5);
+ EXPECT_FALSE(m.UsingFullMap());
+
+ small_map<std::unordered_map<int, int>>::iterator iter(m.begin());
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 0);
+ EXPECT_EQ(iter->second, 5);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ((*iter).first, 9);
+ EXPECT_EQ((*iter).second, 2);
+ ++iter;
+ EXPECT_TRUE(iter == m.end());
+
+ m[8] = 23;
+ m[1234] = 90;
+ m[-5] = 6;
+
+ EXPECT_EQ(m[ 9], 2);
+ EXPECT_EQ(m[ 0], 5);
+ EXPECT_EQ(m[1234], 90);
+ EXPECT_EQ(m[ 8], 23);
+ EXPECT_EQ(m[ -5], 6);
+ EXPECT_EQ(m.size(), 5u);
+ EXPECT_FALSE(m.empty());
+ EXPECT_TRUE(m.UsingFullMap());
+
+ iter = m.begin();
+ for (int i = 0; i < 5; i++) {
+ EXPECT_TRUE(iter != m.end());
+ ++iter;
+ }
+ EXPECT_TRUE(iter == m.end());
+
+ const small_map<std::unordered_map<int, int>>& ref = m;
+ EXPECT_TRUE(ref.find(1234) != m.end());
+ EXPECT_TRUE(ref.find(5678) == m.end());
+}
+
+TEST(SmallMap, PostFixIteratorIncrement) {
+ small_map<std::unordered_map<int, int>> m;
+ m[0] = 5;
+ m[2] = 3;
+
+ {
+ small_map<std::unordered_map<int, int>>::iterator iter(m.begin());
+ small_map<std::unordered_map<int, int>>::iterator last(iter++);
+ ++last;
+ EXPECT_TRUE(last == iter);
+ }
+
+ {
+ small_map<std::unordered_map<int, int>>::const_iterator iter(m.begin());
+ small_map<std::unordered_map<int, int>>::const_iterator last(iter++);
+ ++last;
+ EXPECT_TRUE(last == iter);
+ }
+}
+
+// Based on the General testcase.
+TEST(SmallMap, CopyConstructor) {
+ small_map<std::unordered_map<int, int>> src;
+
+ {
+ small_map<std::unordered_map<int, int>> m(src);
+ EXPECT_TRUE(m.empty());
+ }
+
+ src[0] = 5;
+
+ {
+ small_map<std::unordered_map<int, int>> m(src);
+ EXPECT_FALSE(m.empty());
+ EXPECT_EQ(m.size(), 1u);
+ }
+
+ src[9] = 2;
+
+ {
+ small_map<std::unordered_map<int, int>> m(src);
+ EXPECT_FALSE(m.empty());
+ EXPECT_EQ(m.size(), 2u);
+
+ EXPECT_EQ(m[9], 2);
+ EXPECT_EQ(m[0], 5);
+ EXPECT_FALSE(m.UsingFullMap());
+ }
+
+ src[8] = 23;
+ src[1234] = 90;
+ src[-5] = 6;
+
+ {
+ small_map<std::unordered_map<int, int>> m(src);
+ EXPECT_EQ(m[ 9], 2);
+ EXPECT_EQ(m[ 0], 5);
+ EXPECT_EQ(m[1234], 90);
+ EXPECT_EQ(m[ 8], 23);
+ EXPECT_EQ(m[ -5], 6);
+ EXPECT_EQ(m.size(), 5u);
+ EXPECT_FALSE(m.empty());
+ EXPECT_TRUE(m.UsingFullMap());
+ }
+}
+
+template <class inner>
+static bool SmallMapIsSubset(small_map<inner> const& a,
+ small_map<inner> const& b) {
+ typename small_map<inner>::const_iterator it;
+ for (it = a.begin(); it != a.end(); ++it) {
+ typename small_map<inner>::const_iterator it_in_b = b.find(it->first);
+ if (it_in_b == b.end() || it_in_b->second != it->second)
+ return false;
+ }
+ return true;
+}
+
+template <class inner>
+static bool SmallMapEqual(small_map<inner> const& a,
+ small_map<inner> const& b) {
+ return SmallMapIsSubset(a, b) && SmallMapIsSubset(b, a);
+}
+
+TEST(SmallMap, AssignmentOperator) {
+ small_map<std::unordered_map<int, int>> src_small;
+ small_map<std::unordered_map<int, int>> src_large;
+
+ src_small[1] = 20;
+ src_small[2] = 21;
+ src_small[3] = 22;
+ EXPECT_FALSE(src_small.UsingFullMap());
+
+ src_large[1] = 20;
+ src_large[2] = 21;
+ src_large[3] = 22;
+ src_large[5] = 23;
+ src_large[6] = 24;
+ src_large[7] = 25;
+ EXPECT_TRUE(src_large.UsingFullMap());
+
+ // Assignments to empty.
+ small_map<std::unordered_map<int, int>> dest_small;
+ dest_small = src_small;
+ EXPECT_TRUE(SmallMapEqual(dest_small, src_small));
+ EXPECT_EQ(dest_small.UsingFullMap(),
+ src_small.UsingFullMap());
+
+ small_map<std::unordered_map<int, int>> dest_large;
+ dest_large = src_large;
+ EXPECT_TRUE(SmallMapEqual(dest_large, src_large));
+ EXPECT_EQ(dest_large.UsingFullMap(),
+ src_large.UsingFullMap());
+
+ // Assignments which assign from full to small, and vice versa.
+ dest_small = src_large;
+ EXPECT_TRUE(SmallMapEqual(dest_small, src_large));
+ EXPECT_EQ(dest_small.UsingFullMap(),
+ src_large.UsingFullMap());
+
+ dest_large = src_small;
+ EXPECT_TRUE(SmallMapEqual(dest_large, src_small));
+ EXPECT_EQ(dest_large.UsingFullMap(),
+ src_small.UsingFullMap());
+
+ // Double check that SmallMapEqual works:
+ dest_large[42] = 666;
+ EXPECT_FALSE(SmallMapEqual(dest_large, src_small));
+}
+
+TEST(SmallMap, Insert) {
+ small_map<std::unordered_map<int, int>> sm;
+
+ // loop through the transition from small map to map.
+ for (int i = 1; i <= 10; ++i) {
+ VLOG(1) << "Iteration " << i;
+ // insert an element
+ std::pair<small_map<std::unordered_map<int, int>>::iterator, bool> ret;
+ ret = sm.insert(std::make_pair(i, 100*i));
+ EXPECT_TRUE(ret.second);
+ EXPECT_TRUE(ret.first == sm.find(i));
+ EXPECT_EQ(ret.first->first, i);
+ EXPECT_EQ(ret.first->second, 100*i);
+
+ // try to insert it again with different value, fails, but we still get an
+ // iterator back with the original value.
+ ret = sm.insert(std::make_pair(i, -i));
+ EXPECT_FALSE(ret.second);
+ EXPECT_TRUE(ret.first == sm.find(i));
+ EXPECT_EQ(ret.first->first, i);
+ EXPECT_EQ(ret.first->second, 100*i);
+
+ // check the state of the map.
+ for (int j = 1; j <= i; ++j) {
+ small_map<std::unordered_map<int, int>>::iterator it = sm.find(j);
+ EXPECT_TRUE(it != sm.end());
+ EXPECT_EQ(it->first, j);
+ EXPECT_EQ(it->second, j * 100);
+ }
+ EXPECT_EQ(sm.size(), static_cast<size_t>(i));
+ EXPECT_FALSE(sm.empty());
+ }
+}
+
+TEST(SmallMap, InsertRange) {
+ // loop through the transition from small map to map.
+ for (int elements = 0; elements <= 10; ++elements) {
+ VLOG(1) << "Elements " << elements;
+ std::unordered_map<int, int> normal_map;
+ for (int i = 1; i <= elements; ++i) {
+ normal_map.insert(std::make_pair(i, 100*i));
+ }
+
+ small_map<std::unordered_map<int, int>> sm;
+ sm.insert(normal_map.begin(), normal_map.end());
+ EXPECT_EQ(normal_map.size(), sm.size());
+ for (int i = 1; i <= elements; ++i) {
+ VLOG(1) << "Iteration " << i;
+ EXPECT_TRUE(sm.find(i) != sm.end());
+ EXPECT_EQ(sm.find(i)->first, i);
+ EXPECT_EQ(sm.find(i)->second, 100*i);
+ }
+ }
+}
+
+TEST(SmallMap, Erase) {
+ small_map<std::unordered_map<std::string, int>> m;
+ small_map<std::unordered_map<std::string, int>>::iterator iter;
+
+ m["monday"] = 1;
+ m["tuesday"] = 2;
+ m["wednesday"] = 3;
+
+ EXPECT_EQ(m["monday" ], 1);
+ EXPECT_EQ(m["tuesday" ], 2);
+ EXPECT_EQ(m["wednesday"], 3);
+ EXPECT_EQ(m.count("tuesday"), 1u);
+ EXPECT_FALSE(m.UsingFullMap());
+
+ iter = m.begin();
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, "monday");
+ EXPECT_EQ(iter->second, 1);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, "tuesday");
+ EXPECT_EQ(iter->second, 2);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, "wednesday");
+ EXPECT_EQ(iter->second, 3);
+ ++iter;
+ EXPECT_TRUE(iter == m.end());
+
+ EXPECT_EQ(m.erase("tuesday"), 1u);
+
+ EXPECT_EQ(m["monday" ], 1);
+ EXPECT_EQ(m["wednesday"], 3);
+ EXPECT_EQ(m.count("tuesday"), 0u);
+ EXPECT_EQ(m.erase("tuesday"), 0u);
+
+ iter = m.begin();
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, "monday");
+ EXPECT_EQ(iter->second, 1);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, "wednesday");
+ EXPECT_EQ(iter->second, 3);
+ ++iter;
+ EXPECT_TRUE(iter == m.end());
+
+ m["thursday"] = 4;
+ m["friday"] = 5;
+ EXPECT_EQ(m.size(), 4u);
+ EXPECT_FALSE(m.empty());
+ EXPECT_FALSE(m.UsingFullMap());
+
+ m["saturday"] = 6;
+ EXPECT_TRUE(m.UsingFullMap());
+
+ EXPECT_EQ(m.count("friday"), 1u);
+ EXPECT_EQ(m.erase("friday"), 1u);
+ EXPECT_TRUE(m.UsingFullMap());
+ EXPECT_EQ(m.count("friday"), 0u);
+ EXPECT_EQ(m.erase("friday"), 0u);
+
+ EXPECT_EQ(m.size(), 4u);
+ EXPECT_FALSE(m.empty());
+ EXPECT_EQ(m.erase("monday"), 1u);
+ EXPECT_EQ(m.size(), 3u);
+ EXPECT_FALSE(m.empty());
+
+ m.clear();
+ EXPECT_FALSE(m.UsingFullMap());
+ EXPECT_EQ(m.size(), 0u);
+ EXPECT_TRUE(m.empty());
+}
+
+TEST(SmallMap, EraseReturnsIteratorFollowingRemovedElement) {
+ small_map<std::unordered_map<std::string, int>> m;
+ small_map<std::unordered_map<std::string, int>>::iterator iter;
+
+ m["a"] = 0;
+ m["b"] = 1;
+ m["c"] = 2;
+
+ // Erase first item.
+ auto following_iter = m.erase(m.begin());
+ EXPECT_EQ(m.begin(), following_iter);
+ EXPECT_EQ(2u, m.size());
+ EXPECT_EQ(m.count("a"), 0u);
+ EXPECT_EQ(m.count("b"), 1u);
+ EXPECT_EQ(m.count("c"), 1u);
+
+ // Iterate to last item and erase it.
+ ++following_iter;
+ following_iter = m.erase(following_iter);
+ ASSERT_EQ(1u, m.size());
+ EXPECT_EQ(m.end(), following_iter);
+ EXPECT_EQ(m.count("b"), 0u);
+ EXPECT_EQ(m.count("c"), 1u);
+
+ // Erase remaining item.
+ following_iter = m.erase(m.begin());
+ EXPECT_TRUE(m.empty());
+ EXPECT_EQ(m.end(), following_iter);
+}
+
+TEST(SmallMap, NonHashMap) {
+ small_map<std::map<int, int>, 4, std::equal_to<int>> m;
+ EXPECT_TRUE(m.empty());
+
+ m[9] = 2;
+ m[0] = 5;
+
+ EXPECT_EQ(m[9], 2);
+ EXPECT_EQ(m[0], 5);
+ EXPECT_EQ(m.size(), 2u);
+ EXPECT_FALSE(m.empty());
+ EXPECT_FALSE(m.UsingFullMap());
+
+ small_map<std::map<int, int>, 4, std::equal_to<int>>::iterator iter(
+ m.begin());
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 9);
+ EXPECT_EQ(iter->second, 2);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 0);
+ EXPECT_EQ(iter->second, 5);
+ ++iter;
+ EXPECT_TRUE(iter == m.end());
+ --iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 0);
+ EXPECT_EQ(iter->second, 5);
+
+ m[8] = 23;
+ m[1234] = 90;
+ m[-5] = 6;
+
+ EXPECT_EQ(m[ 9], 2);
+ EXPECT_EQ(m[ 0], 5);
+ EXPECT_EQ(m[1234], 90);
+ EXPECT_EQ(m[ 8], 23);
+ EXPECT_EQ(m[ -5], 6);
+ EXPECT_EQ(m.size(), 5u);
+ EXPECT_FALSE(m.empty());
+ EXPECT_TRUE(m.UsingFullMap());
+
+ iter = m.begin();
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, -5);
+ EXPECT_EQ(iter->second, 6);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 0);
+ EXPECT_EQ(iter->second, 5);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 8);
+ EXPECT_EQ(iter->second, 23);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 9);
+ EXPECT_EQ(iter->second, 2);
+ ++iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 1234);
+ EXPECT_EQ(iter->second, 90);
+ ++iter;
+ EXPECT_TRUE(iter == m.end());
+ --iter;
+ ASSERT_TRUE(iter != m.end());
+ EXPECT_EQ(iter->first, 1234);
+ EXPECT_EQ(iter->second, 90);
+}
+
+TEST(SmallMap, DefaultEqualKeyWorks) {
+ // If these tests compile, they pass. The EXPECT calls are only there to avoid
+ // unused variable warnings.
+ small_map<std::unordered_map<int, int>> hm;
+ EXPECT_EQ(0u, hm.size());
+ small_map<std::map<int, int>> m;
+ EXPECT_EQ(0u, m.size());
+}
+
+namespace {
+
+class unordered_map_add_item : public std::unordered_map<int, int> {
+ public:
+ unordered_map_add_item() = default;
+ explicit unordered_map_add_item(const std::pair<int, int>& item) {
+ insert(item);
+ }
+};
+
+void InitMap(unordered_map_add_item* map_ctor) {
+ new (map_ctor) unordered_map_add_item(std::make_pair(0, 0));
+}
+
+class unordered_map_add_item_initializer {
+ public:
+ explicit unordered_map_add_item_initializer(int item_to_add)
+ : item_(item_to_add) {}
+ unordered_map_add_item_initializer() : item_(0) {}
+ void operator()(unordered_map_add_item* map_ctor) const {
+ new (map_ctor) unordered_map_add_item(std::make_pair(item_, item_));
+ }
+
+ int item_;
+};
+
+} // anonymous namespace
+
+TEST(SmallMap, SubclassInitializationWithFunctionPointer) {
+ small_map<unordered_map_add_item, 4, std::equal_to<int>,
+ void (&)(unordered_map_add_item*)>
+ m(InitMap);
+
+ EXPECT_TRUE(m.empty());
+
+ m[1] = 1;
+ m[2] = 2;
+ m[3] = 3;
+ m[4] = 4;
+
+ EXPECT_EQ(4u, m.size());
+ EXPECT_EQ(0u, m.count(0));
+
+ m[5] = 5;
+ EXPECT_EQ(6u, m.size());
+ // Our function adds an extra item when we convert to a map.
+ EXPECT_EQ(1u, m.count(0));
+}
+
+TEST(SmallMap, SubclassInitializationWithFunctionObject) {
+ small_map<unordered_map_add_item, 4, std::equal_to<int>,
+ unordered_map_add_item_initializer>
+ m(unordered_map_add_item_initializer(-1));
+
+ EXPECT_TRUE(m.empty());
+
+ m[1] = 1;
+ m[2] = 2;
+ m[3] = 3;
+ m[4] = 4;
+
+ EXPECT_EQ(4u, m.size());
+ EXPECT_EQ(0u, m.count(-1));
+
+ m[5] = 5;
+ EXPECT_EQ(6u, m.size());
+ // Our functor adds an extra item when we convert to a map.
+ EXPECT_EQ(1u, m.count(-1));
+}
+
+namespace {
+
+// This class acts as a basic implementation of a move-only type. The canonical
+// example of such a type is scoped_ptr/unique_ptr.
+template <typename V>
+class MoveOnlyType {
+ public:
+ MoveOnlyType() : value_(0) {}
+ explicit MoveOnlyType(V value) : value_(value) {}
+
+ MoveOnlyType(MoveOnlyType&& other) {
+ *this = std::move(other);
+ }
+
+ MoveOnlyType& operator=(MoveOnlyType&& other) {
+ value_ = other.value_;
+ other.value_ = 0;
+ return *this;
+ }
+
+ MoveOnlyType(const MoveOnlyType&) = delete;
+ MoveOnlyType& operator=(const MoveOnlyType&) = delete;
+
+ V value() const { return value_; }
+
+ private:
+ V value_;
+};
+
+} // namespace
+
+TEST(SmallMap, MoveOnlyValueType) {
+ small_map<std::map<int, MoveOnlyType<int>>, 2> m;
+
+ m[0] = MoveOnlyType<int>(1);
+ m[1] = MoveOnlyType<int>(2);
+ m.erase(m.begin());
+
+ // small_map will move m[1] to an earlier index in the internal array.
+ EXPECT_EQ(m.size(), 1u);
+ EXPECT_EQ(m[1].value(), 2);
+
+ m[0] = MoveOnlyType<int>(1);
+ // small_map must move the values from the array into the internal std::map.
+ m[2] = MoveOnlyType<int>(3);
+
+ EXPECT_EQ(m.size(), 3u);
+ EXPECT_EQ(m[0].value(), 1);
+ EXPECT_EQ(m[1].value(), 2);
+ EXPECT_EQ(m[2].value(), 3);
+
+ m.erase(m.begin());
+
+ // small_map should also let internal std::map erase with a move-only type.
+ EXPECT_EQ(m.size(), 2u);
+ EXPECT_EQ(m[1].value(), 2);
+ EXPECT_EQ(m[2].value(), 3);
+}
+
+TEST(SmallMap, Emplace) {
+ small_map<std::map<size_t, MoveOnlyType<size_t>>> sm;
+
+ // loop through the transition from small map to map.
+ for (size_t i = 1; i <= 10; ++i) {
+ // insert an element
+ auto ret = sm.emplace(i, MoveOnlyType<size_t>(100 * i));
+ EXPECT_TRUE(ret.second);
+ EXPECT_TRUE(ret.first == sm.find(i));
+ EXPECT_EQ(ret.first->first, i);
+ EXPECT_EQ(ret.first->second.value(), 100 * i);
+
+ // try to insert it again with different value, fails, but we still get an
+ // iterator back with the original value.
+ ret = sm.emplace(i, MoveOnlyType<size_t>(i));
+ EXPECT_FALSE(ret.second);
+ EXPECT_TRUE(ret.first == sm.find(i));
+ EXPECT_EQ(ret.first->first, i);
+ EXPECT_EQ(ret.first->second.value(), 100 * i);
+
+ // check the state of the map.
+ for (size_t j = 1; j <= i; ++j) {
+ const auto it = sm.find(j);
+ EXPECT_TRUE(it != sm.end());
+ EXPECT_EQ(it->first, j);
+ EXPECT_EQ(it->second.value(), j * 100);
+ }
+ EXPECT_EQ(sm.size(), i);
+ EXPECT_FALSE(sm.empty());
+ }
+}
+
+} // namespace base
diff --git a/base/containers/stack_container_unittest.cc b/base/containers/stack_container_unittest.cc
new file mode 100644
index 0000000000..b6bb9b6352
--- /dev/null
+++ b/base/containers/stack_container_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/stack_container.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class Dummy : public base::RefCounted<Dummy> {
+ public:
+ explicit Dummy(int* alive) : alive_(alive) {
+ ++*alive_;
+ }
+
+ private:
+ friend class base::RefCounted<Dummy>;
+
+ ~Dummy() {
+ --*alive_;
+ }
+
+ int* const alive_;
+};
+
+} // namespace
+
+TEST(StackContainer, Vector) {
+ const int stack_size = 3;
+ StackVector<int, stack_size> vect;
+ const int* stack_buffer = &vect.stack_data().stack_buffer()[0];
+
+ // The initial |stack_size| elements should appear in the stack buffer.
+ EXPECT_EQ(static_cast<size_t>(stack_size), vect.container().capacity());
+ for (int i = 0; i < stack_size; i++) {
+ vect.container().push_back(i);
+ EXPECT_EQ(stack_buffer, &vect.container()[0]);
+ EXPECT_TRUE(vect.stack_data().used_stack_buffer_);
+ }
+
+ // Adding more elements should push the array onto the heap.
+ for (int i = 0; i < stack_size; i++) {
+ vect.container().push_back(i + stack_size);
+ EXPECT_NE(stack_buffer, &vect.container()[0]);
+ EXPECT_FALSE(vect.stack_data().used_stack_buffer_);
+ }
+
+ // The array should still be in order.
+ for (int i = 0; i < stack_size * 2; i++)
+ EXPECT_EQ(i, vect.container()[i]);
+
+ // Resize to smaller. Our STL implementation won't reallocate in this case,
+ // otherwise it might use our stack buffer. We reserve right after the resize
+ // to guarantee it isn't using the stack buffer, even though it doesn't have
+ // much data.
+ vect.container().resize(stack_size);
+ vect.container().reserve(stack_size * 2);
+ EXPECT_FALSE(vect.stack_data().used_stack_buffer_);
+
+ // Copying the small vector to another should use the same allocator and use
+ // the now-unused stack buffer. GENERALLY CALLERS SHOULD NOT DO THIS since
+ // they have to get the template types just right and it can cause errors.
+ std::vector<int, StackAllocator<int, stack_size> > other(vect.container());
+ EXPECT_EQ(stack_buffer, &other.front());
+ EXPECT_TRUE(vect.stack_data().used_stack_buffer_);
+ for (int i = 0; i < stack_size; i++)
+ EXPECT_EQ(i, other[i]);
+}
+
+TEST(StackContainer, VectorDoubleDelete) {
+ // Regression testing for double-delete.
+ typedef StackVector<scoped_refptr<Dummy>, 2> Vector;
+ typedef Vector::ContainerType Container;
+ Vector vect;
+
+ int alive = 0;
+ scoped_refptr<Dummy> dummy(new Dummy(&alive));
+ EXPECT_EQ(alive, 1);
+
+ vect->push_back(dummy);
+ EXPECT_EQ(alive, 1);
+
+ Dummy* dummy_unref = dummy.get();
+ dummy = nullptr;
+ EXPECT_EQ(alive, 1);
+
+ Container::iterator itr = std::find(vect->begin(), vect->end(), dummy_unref);
+ EXPECT_EQ(itr->get(), dummy_unref);
+ vect->erase(itr);
+ EXPECT_EQ(alive, 0);
+
+ // Shouldn't crash at exit.
+}
+
+namespace {
+
+template <size_t alignment>
+class AlignedData {
+ public:
+ AlignedData() { memset(data_, 0, alignment); }
+ ~AlignedData() = default;
+ alignas(alignment) char data_[alignment];
+};
+
+} // anonymous namespace
+
+#define EXPECT_ALIGNED(ptr, align) \
+ EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & (align - 1))
+
+TEST(StackContainer, BufferAlignment) {
+ StackVector<wchar_t, 16> text;
+ text->push_back(L'A');
+ EXPECT_ALIGNED(&text[0], alignof(wchar_t));
+
+ StackVector<double, 1> doubles;
+ doubles->push_back(0.0);
+ EXPECT_ALIGNED(&doubles[0], alignof(double));
+
+ StackVector<AlignedData<16>, 1> aligned16;
+ aligned16->push_back(AlignedData<16>());
+ EXPECT_ALIGNED(&aligned16[0], 16);
+
+#if !defined(__GNUC__) || defined(ARCH_CPU_X86_FAMILY)
+ // It seems that non-X86 gcc doesn't respect greater than 16 byte alignment.
+ // See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=33721 for details.
+ // TODO(sbc):re-enable this if GCC starts respecting higher alignments.
+ StackVector<AlignedData<256>, 1> aligned256;
+ aligned256->push_back(AlignedData<256>());
+ EXPECT_ALIGNED(&aligned256[0], 256);
+#endif
+}
+
+template class StackVector<int, 2>;
+template class StackVector<scoped_refptr<Dummy>, 2>;
+
+} // namespace base
diff --git a/base/containers/unique_ptr_adapters.h b/base/containers/unique_ptr_adapters.h
new file mode 100644
index 0000000000..42fab190ab
--- /dev/null
+++ b/base/containers/unique_ptr_adapters.h
@@ -0,0 +1,78 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_UNIQUE_PTR_ADAPTERS_H_
+#define BASE_CONTAINERS_UNIQUE_PTR_ADAPTERS_H_
+
+#include <memory>
+
+namespace base {
+
+// This transparent comparator allows to lookup by raw pointer in
+// a container of unique pointers. This functionality is based on C++14
+// extensions to std::set/std::map interface, and can also be used
+// with base::flat_set/base::flat_map.
+//
+// Example usage:
+// Foo* foo = ...
+// std::set<std::unique_ptr<Foo>, base::UniquePtrComparator> set;
+// set.insert(std::unique_ptr<Foo>(foo));
+// ...
+// auto it = set.find(foo);
+// EXPECT_EQ(foo, it->get());
+//
+// You can find more information about transparent comparisons here:
+// http://en.cppreference.com/w/cpp/utility/functional/less_void
+struct UniquePtrComparator {
+ using is_transparent = int;
+
+ template <typename T>
+ bool operator()(const std::unique_ptr<T>& lhs,
+ const std::unique_ptr<T>& rhs) const {
+ return lhs < rhs;
+ }
+
+ template <typename T>
+ bool operator()(const T* lhs, const std::unique_ptr<T>& rhs) const {
+ return lhs < rhs.get();
+ }
+
+ template <typename T>
+ bool operator()(const std::unique_ptr<T>& lhs, const T* rhs) const {
+ return lhs.get() < rhs;
+ }
+};
+
+// UniquePtrMatcher is useful for finding an element in a container of
+// unique_ptrs when you have the raw pointer.
+//
+// Example usage:
+// std::vector<std::unique_ptr<Foo>> vector;
+// Foo* element = ...
+// auto iter = std::find_if(vector.begin(), vector.end(),
+// MatchesUniquePtr(element));
+//
+// Example of erasing from container:
+// EraseIf(v, MatchesUniquePtr(element));
+//
+template <class T, class Deleter = std::default_delete<T>>
+struct UniquePtrMatcher {
+ explicit UniquePtrMatcher(T* t) : t_(t) {}
+
+ bool operator()(const std::unique_ptr<T, Deleter>& o) {
+ return o.get() == t_;
+ }
+
+ private:
+ T* const t_;
+};
+
+template <class T, class Deleter = std::default_delete<T>>
+UniquePtrMatcher<T, Deleter> MatchesUniquePtr(T* t) {
+ return UniquePtrMatcher<T, Deleter>(t);
+}
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_UNIQUE_PTR_ADAPTERS_H_
diff --git a/base/containers/unique_ptr_adapters_unittest.cc b/base/containers/unique_ptr_adapters_unittest.cc
new file mode 100644
index 0000000000..5b8f1fc024
--- /dev/null
+++ b/base/containers/unique_ptr_adapters_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/unique_ptr_adapters.h"
+
+#include <memory>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class Foo {
+ public:
+ Foo() { instance_count++; }
+ ~Foo() { instance_count--; }
+ static int instance_count;
+};
+
+int Foo::instance_count = 0;
+
+TEST(UniquePtrComparatorTest, Basic) {
+ std::set<std::unique_ptr<Foo>, UniquePtrComparator> set;
+ Foo* foo1 = new Foo();
+ Foo* foo2 = new Foo();
+ Foo* foo3 = new Foo();
+ EXPECT_EQ(3, Foo::instance_count);
+
+ set.emplace(foo1);
+ set.emplace(foo2);
+
+ auto it1 = set.find(foo1);
+ EXPECT_TRUE(it1 != set.end());
+ EXPECT_EQ(foo1, it1->get());
+
+ {
+ auto it2 = set.find(foo2);
+ EXPECT_TRUE(it2 != set.end());
+ EXPECT_EQ(foo2, it2->get());
+ }
+
+ EXPECT_TRUE(set.find(foo3) == set.end());
+
+ set.erase(it1);
+ EXPECT_EQ(2, Foo::instance_count);
+
+ EXPECT_TRUE(set.find(foo1) == set.end());
+
+ {
+ auto it2 = set.find(foo2);
+ EXPECT_TRUE(it2 != set.end());
+ EXPECT_EQ(foo2, it2->get());
+ }
+
+ set.clear();
+ EXPECT_EQ(1, Foo::instance_count);
+
+ EXPECT_TRUE(set.find(foo1) == set.end());
+ EXPECT_TRUE(set.find(foo2) == set.end());
+ EXPECT_TRUE(set.find(foo3) == set.end());
+
+ delete foo3;
+ EXPECT_EQ(0, Foo::instance_count);
+}
+
+TEST(UniquePtrMatcherTest, Basic) {
+ std::vector<std::unique_ptr<Foo>> v;
+ auto foo_ptr1 = std::make_unique<Foo>();
+ Foo* foo1 = foo_ptr1.get();
+ v.push_back(std::move(foo_ptr1));
+ auto foo_ptr2 = std::make_unique<Foo>();
+ Foo* foo2 = foo_ptr2.get();
+ v.push_back(std::move(foo_ptr2));
+
+ {
+ auto iter = std::find_if(v.begin(), v.end(), UniquePtrMatcher<Foo>(foo1));
+ ASSERT_TRUE(iter != v.end());
+ EXPECT_EQ(foo1, iter->get());
+ }
+
+ {
+ auto iter = std::find_if(v.begin(), v.end(), UniquePtrMatcher<Foo>(foo2));
+ ASSERT_TRUE(iter != v.end());
+ EXPECT_EQ(foo2, iter->get());
+ }
+
+ {
+ auto iter = std::find_if(v.begin(), v.end(), MatchesUniquePtr(foo2));
+ ASSERT_TRUE(iter != v.end());
+ EXPECT_EQ(foo2, iter->get());
+ }
+}
+
+class TestDeleter {
+ public:
+ void operator()(Foo* foo) { delete foo; }
+};
+
+TEST(UniquePtrMatcherTest, Deleter) {
+ using UniqueFoo = std::unique_ptr<Foo, TestDeleter>;
+ std::vector<UniqueFoo> v;
+ UniqueFoo foo_ptr1(new Foo);
+ Foo* foo1 = foo_ptr1.get();
+ v.push_back(std::move(foo_ptr1));
+ UniqueFoo foo_ptr2(new Foo);
+ Foo* foo2 = foo_ptr2.get();
+ v.push_back(std::move(foo_ptr2));
+
+ {
+ auto iter = std::find_if(v.begin(), v.end(),
+ UniquePtrMatcher<Foo, TestDeleter>(foo1));
+ ASSERT_TRUE(iter != v.end());
+ EXPECT_EQ(foo1, iter->get());
+ }
+
+ {
+ auto iter = std::find_if(v.begin(), v.end(),
+ UniquePtrMatcher<Foo, TestDeleter>(foo2));
+ ASSERT_TRUE(iter != v.end());
+ EXPECT_EQ(foo2, iter->get());
+ }
+
+ {
+ auto iter = std::find_if(v.begin(), v.end(),
+ MatchesUniquePtr<Foo, TestDeleter>(foo2));
+ ASSERT_TRUE(iter != v.end());
+ EXPECT_EQ(foo2, iter->get());
+ }
+}
+
+} // namespace
+} // namespace base
diff --git a/base/debug/activity_analyzer.cc b/base/debug/activity_analyzer.cc
new file mode 100644
index 0000000000..d787829579
--- /dev/null
+++ b/base/debug/activity_analyzer.cc
@@ -0,0 +1,412 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/debug/activity_analyzer.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+
+namespace base {
+namespace debug {
+
+namespace {
+// An empty snapshot that can be returned when there otherwise is none.
+LazyInstance<ActivityUserData::Snapshot>::Leaky g_empty_user_data_snapshot;
+
+// DO NOT CHANGE VALUES. This is logged persistently in a histogram.
+enum AnalyzerCreationError {
+ kInvalidMemoryMappedFile,
+ kPmaBadFile,
+ kPmaUninitialized,
+ kPmaDeleted,
+ kPmaCorrupt,
+ kAnalyzerCreationErrorMax // Keep this last.
+};
+
+void LogAnalyzerCreationError(AnalyzerCreationError error) {
+ UMA_HISTOGRAM_ENUMERATION("ActivityTracker.Collect.AnalyzerCreationError",
+ error, kAnalyzerCreationErrorMax);
+}
+
+} // namespace
+
+ThreadActivityAnalyzer::Snapshot::Snapshot() = default;
+ThreadActivityAnalyzer::Snapshot::~Snapshot() = default;
+
+ThreadActivityAnalyzer::ThreadActivityAnalyzer(
+ const ThreadActivityTracker& tracker)
+ : activity_snapshot_valid_(tracker.CreateSnapshot(&activity_snapshot_)) {}
+
+ThreadActivityAnalyzer::ThreadActivityAnalyzer(void* base, size_t size)
+ : ThreadActivityAnalyzer(ThreadActivityTracker(base, size)) {}
+
+ThreadActivityAnalyzer::ThreadActivityAnalyzer(
+ PersistentMemoryAllocator* allocator,
+ PersistentMemoryAllocator::Reference reference)
+ : ThreadActivityAnalyzer(allocator->GetAsArray<char>(
+ reference,
+ GlobalActivityTracker::kTypeIdActivityTracker,
+ PersistentMemoryAllocator::kSizeAny),
+ allocator->GetAllocSize(reference)) {}
+
+ThreadActivityAnalyzer::~ThreadActivityAnalyzer() = default;
+
+void ThreadActivityAnalyzer::AddGlobalInformation(
+ GlobalActivityAnalyzer* global) {
+ if (!IsValid())
+ return;
+
+ // User-data is held at the global scope even though it's referenced at the
+ // thread scope.
+ activity_snapshot_.user_data_stack.clear();
+ for (auto& activity : activity_snapshot_.activity_stack) {
+ // The global GetUserDataSnapshot will return an empty snapshot if the ref
+ // or id is not valid.
+ activity_snapshot_.user_data_stack.push_back(global->GetUserDataSnapshot(
+ activity_snapshot_.process_id, activity.user_data_ref,
+ activity.user_data_id));
+ }
+}
+
+GlobalActivityAnalyzer::GlobalActivityAnalyzer(
+ std::unique_ptr<PersistentMemoryAllocator> allocator)
+ : allocator_(std::move(allocator)),
+ analysis_stamp_(0LL),
+ allocator_iterator_(allocator_.get()) {
+ DCHECK(allocator_);
+}
+
+GlobalActivityAnalyzer::~GlobalActivityAnalyzer() = default;
+
+// static
+std::unique_ptr<GlobalActivityAnalyzer>
+GlobalActivityAnalyzer::CreateWithAllocator(
+ std::unique_ptr<PersistentMemoryAllocator> allocator) {
+ if (allocator->GetMemoryState() ==
+ PersistentMemoryAllocator::MEMORY_UNINITIALIZED) {
+ LogAnalyzerCreationError(kPmaUninitialized);
+ return nullptr;
+ }
+ if (allocator->GetMemoryState() ==
+ PersistentMemoryAllocator::MEMORY_DELETED) {
+ LogAnalyzerCreationError(kPmaDeleted);
+ return nullptr;
+ }
+ if (allocator->IsCorrupt()) {
+ LogAnalyzerCreationError(kPmaCorrupt);
+ return nullptr;
+ }
+
+ return WrapUnique(new GlobalActivityAnalyzer(std::move(allocator)));
+}
+
+#if !defined(OS_NACL)
+// static
+std::unique_ptr<GlobalActivityAnalyzer> GlobalActivityAnalyzer::CreateWithFile(
+ const FilePath& file_path) {
+ // Map the file read-write so it can guarantee consistency between
+ // the analyzer and any trackers that my still be active.
+ std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
+ mmfile->Initialize(file_path, MemoryMappedFile::READ_WRITE);
+ if (!mmfile->IsValid()) {
+ LogAnalyzerCreationError(kInvalidMemoryMappedFile);
+ return nullptr;
+ }
+
+ if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true)) {
+ LogAnalyzerCreationError(kPmaBadFile);
+ return nullptr;
+ }
+
+ return CreateWithAllocator(std::make_unique<FilePersistentMemoryAllocator>(
+ std::move(mmfile), 0, 0, StringPiece(), /*readonly=*/true));
+}
+#endif // !defined(OS_NACL)
+
+// static
+std::unique_ptr<GlobalActivityAnalyzer>
+GlobalActivityAnalyzer::CreateWithSharedMemory(
+ std::unique_ptr<SharedMemory> shm) {
+ if (shm->mapped_size() == 0 ||
+ !SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(*shm)) {
+ return nullptr;
+ }
+ return CreateWithAllocator(std::make_unique<SharedPersistentMemoryAllocator>(
+ std::move(shm), 0, StringPiece(), /*readonly=*/true));
+}
+
+// static
+std::unique_ptr<GlobalActivityAnalyzer>
+GlobalActivityAnalyzer::CreateWithSharedMemoryHandle(
+ const SharedMemoryHandle& handle,
+ size_t size) {
+ std::unique_ptr<SharedMemory> shm(
+ new SharedMemory(handle, /*readonly=*/true));
+ if (!shm->Map(size))
+ return nullptr;
+ return CreateWithSharedMemory(std::move(shm));
+}
+
+int64_t GlobalActivityAnalyzer::GetFirstProcess() {
+ PrepareAllAnalyzers();
+ return GetNextProcess();
+}
+
+int64_t GlobalActivityAnalyzer::GetNextProcess() {
+ if (process_ids_.empty())
+ return 0;
+ int64_t pid = process_ids_.back();
+ process_ids_.pop_back();
+ return pid;
+}
+
+ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetFirstAnalyzer(int64_t pid) {
+ analyzers_iterator_ = analyzers_.begin();
+ analyzers_iterator_pid_ = pid;
+ if (analyzers_iterator_ == analyzers_.end())
+ return nullptr;
+ int64_t create_stamp;
+ if (analyzers_iterator_->second->GetProcessId(&create_stamp) == pid &&
+ create_stamp <= analysis_stamp_) {
+ return analyzers_iterator_->second.get();
+ }
+ return GetNextAnalyzer();
+}
+
+ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetNextAnalyzer() {
+ DCHECK(analyzers_iterator_ != analyzers_.end());
+ int64_t create_stamp;
+ do {
+ ++analyzers_iterator_;
+ if (analyzers_iterator_ == analyzers_.end())
+ return nullptr;
+ } while (analyzers_iterator_->second->GetProcessId(&create_stamp) !=
+ analyzers_iterator_pid_ ||
+ create_stamp > analysis_stamp_);
+ return analyzers_iterator_->second.get();
+}
+
+ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetAnalyzerForThread(
+ const ThreadKey& key) {
+ auto found = analyzers_.find(key);
+ if (found == analyzers_.end())
+ return nullptr;
+ return found->second.get();
+}
+
+ActivityUserData::Snapshot GlobalActivityAnalyzer::GetUserDataSnapshot(
+ int64_t pid,
+ uint32_t ref,
+ uint32_t id) {
+ ActivityUserData::Snapshot snapshot;
+
+ void* memory = allocator_->GetAsArray<char>(
+ ref, GlobalActivityTracker::kTypeIdUserDataRecord,
+ PersistentMemoryAllocator::kSizeAny);
+ if (memory) {
+ size_t size = allocator_->GetAllocSize(ref);
+ const ActivityUserData user_data(memory, size);
+ user_data.CreateSnapshot(&snapshot);
+ int64_t process_id;
+ int64_t create_stamp;
+ if (!ActivityUserData::GetOwningProcessId(memory, &process_id,
+ &create_stamp) ||
+ process_id != pid || user_data.id() != id) {
+ // This allocation has been overwritten since it was created. Return an
+ // empty snapshot because whatever was captured is incorrect.
+ snapshot.clear();
+ }
+ }
+
+ return snapshot;
+}
+
+const ActivityUserData::Snapshot&
+GlobalActivityAnalyzer::GetProcessDataSnapshot(int64_t pid) {
+ auto iter = process_data_.find(pid);
+ if (iter == process_data_.end())
+ return g_empty_user_data_snapshot.Get();
+ if (iter->second.create_stamp > analysis_stamp_)
+ return g_empty_user_data_snapshot.Get();
+ DCHECK_EQ(pid, iter->second.process_id);
+ return iter->second.data;
+}
+
+std::vector<std::string> GlobalActivityAnalyzer::GetLogMessages() {
+ std::vector<std::string> messages;
+ PersistentMemoryAllocator::Reference ref;
+
+ PersistentMemoryAllocator::Iterator iter(allocator_.get());
+ while ((ref = iter.GetNextOfType(
+ GlobalActivityTracker::kTypeIdGlobalLogMessage)) != 0) {
+ const char* message = allocator_->GetAsArray<char>(
+ ref, GlobalActivityTracker::kTypeIdGlobalLogMessage,
+ PersistentMemoryAllocator::kSizeAny);
+ if (message)
+ messages.push_back(message);
+ }
+
+ return messages;
+}
+
+std::vector<GlobalActivityTracker::ModuleInfo>
+GlobalActivityAnalyzer::GetModules(int64_t pid) {
+ std::vector<GlobalActivityTracker::ModuleInfo> modules;
+
+ PersistentMemoryAllocator::Iterator iter(allocator_.get());
+ const GlobalActivityTracker::ModuleInfoRecord* record;
+ while (
+ (record =
+ iter.GetNextOfObject<GlobalActivityTracker::ModuleInfoRecord>()) !=
+ nullptr) {
+ int64_t process_id;
+ int64_t create_stamp;
+ if (!OwningProcess::GetOwningProcessId(&record->owner, &process_id,
+ &create_stamp) ||
+ pid != process_id || create_stamp > analysis_stamp_) {
+ continue;
+ }
+ GlobalActivityTracker::ModuleInfo info;
+ if (record->DecodeTo(&info, allocator_->GetAllocSize(
+ allocator_->GetAsReference(record)))) {
+ modules.push_back(std::move(info));
+ }
+ }
+
+ return modules;
+}
+
+GlobalActivityAnalyzer::ProgramLocation
+GlobalActivityAnalyzer::GetProgramLocationFromAddress(uint64_t address) {
+ // TODO(bcwhite): Implement this.
+ return { 0, 0 };
+}
+
+bool GlobalActivityAnalyzer::IsDataComplete() const {
+ DCHECK(allocator_);
+ return !allocator_->IsFull();
+}
+
+GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot() = default;
+GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot(
+ const UserDataSnapshot& rhs) = default;
+GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot(
+ UserDataSnapshot&& rhs) = default;
+GlobalActivityAnalyzer::UserDataSnapshot::~UserDataSnapshot() = default;
+
+void GlobalActivityAnalyzer::PrepareAllAnalyzers() {
+ // Record the time when analysis started.
+ analysis_stamp_ = base::Time::Now().ToInternalValue();
+
+ // Fetch all the records. This will retrieve only ones created since the
+ // last run since the PMA iterator will continue from where it left off.
+ uint32_t type;
+ PersistentMemoryAllocator::Reference ref;
+ while ((ref = allocator_iterator_.GetNext(&type)) != 0) {
+ switch (type) {
+ case GlobalActivityTracker::kTypeIdActivityTracker:
+ case GlobalActivityTracker::kTypeIdActivityTrackerFree:
+ case GlobalActivityTracker::kTypeIdProcessDataRecord:
+ case GlobalActivityTracker::kTypeIdProcessDataRecordFree:
+ case PersistentMemoryAllocator::kTypeIdTransitioning:
+ // Active, free, or transitioning: add it to the list of references
+ // for later analysis.
+ memory_references_.insert(ref);
+ break;
+ }
+ }
+
+ // Clear out any old information.
+ analyzers_.clear();
+ process_data_.clear();
+ process_ids_.clear();
+ std::set<int64_t> seen_pids;
+
+ // Go through all the known references and create objects for them with
+ // snapshots of the current state.
+ for (PersistentMemoryAllocator::Reference memory_ref : memory_references_) {
+ // Get the actual data segment for the tracker. Any type will do since it
+ // is checked below.
+ void* const base = allocator_->GetAsArray<char>(
+ memory_ref, PersistentMemoryAllocator::kTypeIdAny,
+ PersistentMemoryAllocator::kSizeAny);
+ const size_t size = allocator_->GetAllocSize(memory_ref);
+ if (!base)
+ continue;
+
+ switch (allocator_->GetType(memory_ref)) {
+ case GlobalActivityTracker::kTypeIdActivityTracker: {
+ // Create the analyzer on the data. This will capture a snapshot of the
+ // tracker state. This can fail if the tracker is somehow corrupted or
+ // is in the process of shutting down.
+ std::unique_ptr<ThreadActivityAnalyzer> analyzer(
+ new ThreadActivityAnalyzer(base, size));
+ if (!analyzer->IsValid())
+ continue;
+ analyzer->AddGlobalInformation(this);
+
+ // Track PIDs.
+ int64_t pid = analyzer->GetProcessId();
+ if (seen_pids.find(pid) == seen_pids.end()) {
+ process_ids_.push_back(pid);
+ seen_pids.insert(pid);
+ }
+
+ // Add this analyzer to the map of known ones, indexed by a unique
+ // thread
+ // identifier.
+ DCHECK(!base::ContainsKey(analyzers_, analyzer->GetThreadKey()));
+ analyzer->allocator_reference_ = ref;
+ analyzers_[analyzer->GetThreadKey()] = std::move(analyzer);
+ } break;
+
+ case GlobalActivityTracker::kTypeIdProcessDataRecord: {
+ // Get the PID associated with this data record.
+ int64_t process_id;
+ int64_t create_stamp;
+ ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp);
+ DCHECK(!base::ContainsKey(process_data_, process_id));
+
+ // Create a snapshot of the data. This can fail if the data is somehow
+ // corrupted or the process shutdown and the memory being released.
+ UserDataSnapshot& snapshot = process_data_[process_id];
+ snapshot.process_id = process_id;
+ snapshot.create_stamp = create_stamp;
+ const ActivityUserData process_data(base, size);
+ if (!process_data.CreateSnapshot(&snapshot.data))
+ break;
+
+ // Check that nothing changed. If it did, forget what was recorded.
+ ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp);
+ if (process_id != snapshot.process_id ||
+ create_stamp != snapshot.create_stamp) {
+ process_data_.erase(process_id);
+ break;
+ }
+
+ // Track PIDs.
+ if (seen_pids.find(process_id) == seen_pids.end()) {
+ process_ids_.push_back(process_id);
+ seen_pids.insert(process_id);
+ }
+ } break;
+ }
+ }
+
+ // Reverse the list of PIDs so that they get popped in the order found.
+ std::reverse(process_ids_.begin(), process_ids_.end());
+}
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/activity_analyzer.h b/base/debug/activity_analyzer.h
new file mode 100644
index 0000000000..9add85a9e0
--- /dev/null
+++ b/base/debug/activity_analyzer.h
@@ -0,0 +1,262 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_DEBUG_ACTIVITY_ANALYZER_H_
+#define BASE_DEBUG_ACTIVITY_ANALYZER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/debug/activity_tracker.h"
+
+namespace base {
+namespace debug {
+
+class GlobalActivityAnalyzer;
+
+// This class provides analysis of data captured from a ThreadActivityTracker.
+// When created, it takes a snapshot of the data held by the tracker and
+// makes that information available to other code.
+class BASE_EXPORT ThreadActivityAnalyzer {
+ public:
+ struct BASE_EXPORT Snapshot : ThreadActivityTracker::Snapshot {
+ Snapshot();
+ ~Snapshot();
+
+ // The user-data snapshot for an activity, matching the |activity_stack|
+ // of ThreadActivityTracker::Snapshot, if any.
+ std::vector<ActivityUserData::Snapshot> user_data_stack;
+ };
+
+ // This class provides keys that uniquely identify a thread, even across
+ // multiple processes.
+ class ThreadKey {
+ public:
+ ThreadKey(int64_t pid, int64_t tid) : pid_(pid), tid_(tid) {}
+
+ bool operator<(const ThreadKey& rhs) const {
+ if (pid_ != rhs.pid_)
+ return pid_ < rhs.pid_;
+ return tid_ < rhs.tid_;
+ }
+
+ bool operator==(const ThreadKey& rhs) const {
+ return (pid_ == rhs.pid_ && tid_ == rhs.tid_);
+ }
+
+ private:
+ int64_t pid_;
+ int64_t tid_;
+ };
+
+ // Creates an analyzer for an existing activity |tracker|. A snapshot is taken
+ // immediately and the tracker is not referenced again.
+ explicit ThreadActivityAnalyzer(const ThreadActivityTracker& tracker);
+
+ // Creates an analyzer for a block of memory currently or previously in-use
+ // by an activity-tracker. A snapshot is taken immediately and the memory
+ // is not referenced again.
+ ThreadActivityAnalyzer(void* base, size_t size);
+
+ // Creates an analyzer for a block of memory held within a persistent-memory
+ // |allocator| at the given |reference|. A snapshot is taken immediately and
+ // the memory is not referenced again.
+ ThreadActivityAnalyzer(PersistentMemoryAllocator* allocator,
+ PersistentMemoryAllocator::Reference reference);
+
+ ~ThreadActivityAnalyzer();
+
+ // Adds information from the global analyzer.
+ void AddGlobalInformation(GlobalActivityAnalyzer* global);
+
+ // Returns true iff the contained data is valid. Results from all other
+ // methods are undefined if this returns false.
+ bool IsValid() { return activity_snapshot_valid_; }
+
+ // Gets the process id and its creation stamp.
+ int64_t GetProcessId(int64_t* out_stamp = nullptr) {
+ if (out_stamp)
+ *out_stamp = activity_snapshot_.create_stamp;
+ return activity_snapshot_.process_id;
+ }
+
+ // Gets the name of the thread.
+ const std::string& GetThreadName() {
+ return activity_snapshot_.thread_name;
+ }
+
+ // Gets the TheadKey for this thread.
+ ThreadKey GetThreadKey() {
+ return ThreadKey(activity_snapshot_.process_id,
+ activity_snapshot_.thread_id);
+ }
+
+ const Snapshot& activity_snapshot() { return activity_snapshot_; }
+
+ private:
+ friend class GlobalActivityAnalyzer;
+
+ // The snapshot of the activity tracker taken at the moment of construction.
+ Snapshot activity_snapshot_;
+
+ // Flag indicating if the snapshot data is valid.
+ bool activity_snapshot_valid_;
+
+ // A reference into a persistent memory allocator, used by the global
+ // analyzer to know where this tracker came from.
+ PersistentMemoryAllocator::Reference allocator_reference_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadActivityAnalyzer);
+};
+
+
+// This class manages analyzers for all known processes and threads as stored
+// in a persistent memory allocator. It supports retrieval of them through
+// iteration and directly using a ThreadKey, which allows for cross-references
+// to be resolved.
+// Note that though atomic snapshots are used and everything has its snapshot
+// taken at the same time, the multi-snapshot itself is not atomic and thus may
+// show small inconsistencies between threads if attempted on a live system.
+class BASE_EXPORT GlobalActivityAnalyzer {
+ public:
+ struct ProgramLocation {
+ int module;
+ uintptr_t offset;
+ };
+
+ using ThreadKey = ThreadActivityAnalyzer::ThreadKey;
+
+ // Creates a global analyzer from a persistent memory allocator.
+ explicit GlobalActivityAnalyzer(
+ std::unique_ptr<PersistentMemoryAllocator> allocator);
+
+ ~GlobalActivityAnalyzer();
+
+ // Creates a global analyzer using a given persistent-memory |allocator|.
+ static std::unique_ptr<GlobalActivityAnalyzer> CreateWithAllocator(
+ std::unique_ptr<PersistentMemoryAllocator> allocator);
+
+#if !defined(OS_NACL)
+ // Creates a global analyzer using the contents of a file given in
+ // |file_path|.
+ static std::unique_ptr<GlobalActivityAnalyzer> CreateWithFile(
+ const FilePath& file_path);
+#endif // !defined(OS_NACL)
+
+ // Like above but accesses an allocator in a mapped shared-memory segment.
+ static std::unique_ptr<GlobalActivityAnalyzer> CreateWithSharedMemory(
+ std::unique_ptr<SharedMemory> shm);
+
+ // Like above but takes a handle to an existing shared memory segment and
+ // maps it before creating the tracker.
+ static std::unique_ptr<GlobalActivityAnalyzer> CreateWithSharedMemoryHandle(
+ const SharedMemoryHandle& handle,
+ size_t size);
+
+ // Iterates over all known valid processes and returns their PIDs or zero
+ // if there are no more. Calls to GetFirstProcess() will perform a global
+ // snapshot in order to provide a relatively consistent state across the
+ // future calls to GetNextProcess() and GetFirst/NextAnalyzer(). PIDs are
+ // returned in the order they're found meaning that a first-launched
+ // controlling process will be found first. Note, however, that space
+ // freed by an exiting process may be re-used by a later process.
+ int64_t GetFirstProcess();
+ int64_t GetNextProcess();
+
+ // Iterates over all known valid analyzers for the a given process or returns
+ // null if there are no more.
+ //
+ // GetFirstProcess() must be called first in order to capture a global
+ // snapshot! Ownership stays with the global analyzer object and all existing
+ // analyzer pointers are invalidated when GetFirstProcess() is called.
+ ThreadActivityAnalyzer* GetFirstAnalyzer(int64_t pid);
+ ThreadActivityAnalyzer* GetNextAnalyzer();
+
+ // Gets the analyzer for a specific thread or null if there is none.
+ // Ownership stays with the global analyzer object.
+ ThreadActivityAnalyzer* GetAnalyzerForThread(const ThreadKey& key);
+
+ // Extract user data based on a reference and its identifier.
+ ActivityUserData::Snapshot GetUserDataSnapshot(int64_t pid,
+ uint32_t ref,
+ uint32_t id);
+
+ // Extract the data for a specific process. An empty snapshot will be
+ // returned if the process is not known.
+ const ActivityUserData::Snapshot& GetProcessDataSnapshot(int64_t pid);
+
+ // Gets all log messages stored within.
+ std::vector<std::string> GetLogMessages();
+
+ // Gets modules corresponding to a pid. This pid must come from a call to
+ // GetFirst/NextProcess. Only modules that were first registered prior to
+ // GetFirstProcess's snapshot are returned.
+ std::vector<GlobalActivityTracker::ModuleInfo> GetModules(int64_t pid);
+
+ // Gets the corresponding "program location" for a given "program counter".
+ // This will return {0,0} if no mapping could be found.
+ ProgramLocation GetProgramLocationFromAddress(uint64_t address);
+
+ // Returns whether the data is complete. Data can be incomplete if the
+ // recording size quota is hit.
+ bool IsDataComplete() const;
+
+ private:
+ using AnalyzerMap =
+ std::map<ThreadKey, std::unique_ptr<ThreadActivityAnalyzer>>;
+
+ struct UserDataSnapshot {
+ // Complex class needs out-of-line ctor/dtor.
+ UserDataSnapshot();
+ UserDataSnapshot(const UserDataSnapshot& rhs);
+ UserDataSnapshot(UserDataSnapshot&& rhs);
+ ~UserDataSnapshot();
+
+ int64_t process_id;
+ int64_t create_stamp;
+ ActivityUserData::Snapshot data;
+ };
+
+ // Finds, creates, and indexes analyzers for all known processes and threads.
+ void PrepareAllAnalyzers();
+
+ // The persistent memory allocator holding all tracking data.
+ std::unique_ptr<PersistentMemoryAllocator> allocator_;
+
+ // The time stamp when analysis began. This is used to prevent looking into
+ // process IDs that get reused when analyzing a live system.
+ int64_t analysis_stamp_;
+
+ // The iterator for finding tracking information in the allocator.
+ PersistentMemoryAllocator::Iterator allocator_iterator_;
+
+ // A set of all interesting memory references found within the allocator.
+ std::set<PersistentMemoryAllocator::Reference> memory_references_;
+
+ // A set of all process-data memory references found within the allocator.
+ std::map<int64_t, UserDataSnapshot> process_data_;
+
+ // A set of all process IDs collected during PrepareAllAnalyzers. These are
+ // popped and returned one-by-one with calls to GetFirst/NextProcess().
+ std::vector<int64_t> process_ids_;
+
+ // A map, keyed by ThreadKey, of all valid activity analyzers.
+ AnalyzerMap analyzers_;
+
+ // The iterator within the analyzers_ map for returning analyzers through
+ // first/next iteration.
+ AnalyzerMap::iterator analyzers_iterator_;
+ int64_t analyzers_iterator_pid_;
+
+ DISALLOW_COPY_AND_ASSIGN(GlobalActivityAnalyzer);
+};
+
+} // namespace debug
+} // namespace base
+
+#endif // BASE_DEBUG_ACTIVITY_ANALYZER_H_
diff --git a/base/debug/activity_analyzer_unittest.cc b/base/debug/activity_analyzer_unittest.cc
new file mode 100644
index 0000000000..e08b43aff3
--- /dev/null
+++ b/base/debug/activity_analyzer_unittest.cc
@@ -0,0 +1,546 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/debug/activity_analyzer.h"
+
+#include <atomic>
+#include <memory>
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/debug/activity_tracker.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ptr_util.h"
+#include "base/pending_task.h"
+#include "base/process/process.h"
+#include "base/stl_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/spin_wait.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace debug {
+
+namespace {
+
+class TestActivityTracker : public ThreadActivityTracker {
+ public:
+ TestActivityTracker(std::unique_ptr<char[]> memory, size_t mem_size)
+ : ThreadActivityTracker(memset(memory.get(), 0, mem_size), mem_size),
+ mem_segment_(std::move(memory)) {}
+
+ ~TestActivityTracker() override = default;
+
+ private:
+ std::unique_ptr<char[]> mem_segment_;
+};
+
+} // namespace
+
+
+class ActivityAnalyzerTest : public testing::Test {
+ public:
+ const int kMemorySize = 1 << 20; // 1MiB
+ const int kStackSize = 1 << 10; // 1KiB
+
+ ActivityAnalyzerTest() = default;
+
+ ~ActivityAnalyzerTest() override {
+ GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
+ if (global_tracker) {
+ global_tracker->ReleaseTrackerForCurrentThreadForTesting();
+ delete global_tracker;
+ }
+ }
+
+ std::unique_ptr<ThreadActivityTracker> CreateActivityTracker() {
+ std::unique_ptr<char[]> memory(new char[kStackSize]);
+ return std::make_unique<TestActivityTracker>(std::move(memory), kStackSize);
+ }
+
+ template <typename Function>
+ void AsOtherProcess(int64_t pid, Function function) {
+ std::unique_ptr<GlobalActivityTracker> old_global =
+ GlobalActivityTracker::ReleaseForTesting();
+ ASSERT_TRUE(old_global);
+
+ PersistentMemoryAllocator* old_allocator = old_global->allocator();
+ std::unique_ptr<PersistentMemoryAllocator> new_allocator(
+ std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(old_allocator->data()), old_allocator->size(), 0,
+ 0, "", false));
+ GlobalActivityTracker::CreateWithAllocator(std::move(new_allocator), 3,
+ pid);
+
+ function();
+
+ GlobalActivityTracker::ReleaseForTesting();
+ GlobalActivityTracker::SetForTesting(std::move(old_global));
+ }
+
+ static void DoNothing() {}
+};
+
+TEST_F(ActivityAnalyzerTest, ThreadAnalyzerConstruction) {
+ std::unique_ptr<ThreadActivityTracker> tracker = CreateActivityTracker();
+ {
+ ThreadActivityAnalyzer analyzer(*tracker);
+ EXPECT_TRUE(analyzer.IsValid());
+ EXPECT_EQ(PlatformThread::GetName(), analyzer.GetThreadName());
+ }
+
+ // TODO(bcwhite): More tests once Analyzer does more.
+}
+
+
+// GlobalActivityAnalyzer tests below.
+
+namespace {
+
+class SimpleActivityThread : public SimpleThread {
+ public:
+ SimpleActivityThread(const std::string& name,
+ const void* source,
+ Activity::Type activity,
+ const ActivityData& data)
+ : SimpleThread(name, Options()),
+ source_(source),
+ activity_(activity),
+ data_(data),
+ ready_(false),
+ exit_(false),
+ exit_condition_(&lock_) {}
+
+ ~SimpleActivityThread() override = default;
+
+ void Run() override {
+ ThreadActivityTracker::ActivityId id =
+ GlobalActivityTracker::Get()
+ ->GetOrCreateTrackerForCurrentThread()
+ ->PushActivity(source_, activity_, data_);
+
+ {
+ AutoLock auto_lock(lock_);
+ ready_.store(true, std::memory_order_release);
+ while (!exit_.load(std::memory_order_relaxed))
+ exit_condition_.Wait();
+ }
+
+ GlobalActivityTracker::Get()->GetTrackerForCurrentThread()->PopActivity(id);
+ }
+
+ void Exit() {
+ AutoLock auto_lock(lock_);
+ exit_.store(true, std::memory_order_relaxed);
+ exit_condition_.Signal();
+ }
+
+ void WaitReady() {
+ SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(ready_.load(std::memory_order_acquire));
+ }
+
+ private:
+ const void* source_;
+ Activity::Type activity_;
+ ActivityData data_;
+
+ std::atomic<bool> ready_;
+ std::atomic<bool> exit_;
+ Lock lock_;
+ ConditionVariable exit_condition_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleActivityThread);
+};
+
+} // namespace
+
+TEST_F(ActivityAnalyzerTest, GlobalAnalyzerConstruction) {
+ GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
+ GlobalActivityTracker::Get()->process_data().SetString("foo", "bar");
+
+ PersistentMemoryAllocator* allocator =
+ GlobalActivityTracker::Get()->allocator();
+ GlobalActivityAnalyzer analyzer(std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "", true));
+
+ // The only thread at this point is the test thread of this process.
+ const int64_t pid = analyzer.GetFirstProcess();
+ ASSERT_NE(0, pid);
+ ThreadActivityAnalyzer* ta1 = analyzer.GetFirstAnalyzer(pid);
+ ASSERT_TRUE(ta1);
+ EXPECT_FALSE(analyzer.GetNextAnalyzer());
+ ThreadActivityAnalyzer::ThreadKey tk1 = ta1->GetThreadKey();
+ EXPECT_EQ(ta1, analyzer.GetAnalyzerForThread(tk1));
+ EXPECT_EQ(0, analyzer.GetNextProcess());
+
+ // Create a second thread that will do something.
+ SimpleActivityThread t2("t2", nullptr, Activity::ACT_TASK,
+ ActivityData::ForTask(11));
+ t2.Start();
+ t2.WaitReady();
+
+ // Now there should be two. Calling GetFirstProcess invalidates any
+ // previously returned analyzer pointers.
+ ASSERT_EQ(pid, analyzer.GetFirstProcess());
+ EXPECT_TRUE(analyzer.GetFirstAnalyzer(pid));
+ EXPECT_TRUE(analyzer.GetNextAnalyzer());
+ EXPECT_FALSE(analyzer.GetNextAnalyzer());
+ EXPECT_EQ(0, analyzer.GetNextProcess());
+
+ // Let thread exit.
+ t2.Exit();
+ t2.Join();
+
+ // Now there should be only one again.
+ ASSERT_EQ(pid, analyzer.GetFirstProcess());
+ ThreadActivityAnalyzer* ta2 = analyzer.GetFirstAnalyzer(pid);
+ ASSERT_TRUE(ta2);
+ EXPECT_FALSE(analyzer.GetNextAnalyzer());
+ ThreadActivityAnalyzer::ThreadKey tk2 = ta2->GetThreadKey();
+ EXPECT_EQ(ta2, analyzer.GetAnalyzerForThread(tk2));
+ EXPECT_EQ(tk1, tk2);
+ EXPECT_EQ(0, analyzer.GetNextProcess());
+
+ // Verify that there is process data.
+ const ActivityUserData::Snapshot& data_snapshot =
+ analyzer.GetProcessDataSnapshot(pid);
+ ASSERT_LE(1U, data_snapshot.size());
+ EXPECT_EQ("bar", data_snapshot.at("foo").GetString());
+}
+
+TEST_F(ActivityAnalyzerTest, GlobalAnalyzerFromSharedMemory) {
+ SharedMemoryHandle handle1;
+ SharedMemoryHandle handle2;
+
+ {
+ std::unique_ptr<SharedMemory> shmem(new SharedMemory());
+ ASSERT_TRUE(shmem->CreateAndMapAnonymous(kMemorySize));
+ handle1 = shmem->handle().Duplicate();
+ ASSERT_TRUE(handle1.IsValid());
+ handle2 = shmem->handle().Duplicate();
+ ASSERT_TRUE(handle2.IsValid());
+ }
+
+ GlobalActivityTracker::CreateWithSharedMemoryHandle(handle1, kMemorySize, 0,
+ "", 3);
+ GlobalActivityTracker::Get()->process_data().SetString("foo", "bar");
+
+ std::unique_ptr<GlobalActivityAnalyzer> analyzer =
+ GlobalActivityAnalyzer::CreateWithSharedMemoryHandle(handle2,
+ kMemorySize);
+
+ const int64_t pid = analyzer->GetFirstProcess();
+ ASSERT_NE(0, pid);
+ const ActivityUserData::Snapshot& data_snapshot =
+ analyzer->GetProcessDataSnapshot(pid);
+ ASSERT_LE(1U, data_snapshot.size());
+ EXPECT_EQ("bar", data_snapshot.at("foo").GetString());
+}
+
+TEST_F(ActivityAnalyzerTest, UserDataSnapshotTest) {
+ GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
+ ThreadActivityAnalyzer::Snapshot tracker_snapshot;
+
+ const char string1a[] = "string1a";
+ const char string1b[] = "string1b";
+ const char string2a[] = "string2a";
+ const char string2b[] = "string2b";
+
+ PersistentMemoryAllocator* allocator =
+ GlobalActivityTracker::Get()->allocator();
+ GlobalActivityAnalyzer global_analyzer(
+ std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "",
+ true));
+
+ ThreadActivityTracker* tracker =
+ GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
+
+ {
+ ScopedActivity activity1(1, 11, 111);
+ ActivityUserData& user_data1 = activity1.user_data();
+ user_data1.Set("raw1", "foo1", 4);
+ user_data1.SetString("string1", "bar1");
+ user_data1.SetChar("char1", '1');
+ user_data1.SetInt("int1", -1111);
+ user_data1.SetUint("uint1", 1111);
+ user_data1.SetBool("bool1", true);
+ user_data1.SetReference("ref1", string1a, sizeof(string1a));
+ user_data1.SetStringReference("sref1", string1b);
+
+ {
+ ScopedActivity activity2(2, 22, 222);
+ ActivityUserData& user_data2 = activity2.user_data();
+ user_data2.Set("raw2", "foo2", 4);
+ user_data2.SetString("string2", "bar2");
+ user_data2.SetChar("char2", '2');
+ user_data2.SetInt("int2", -2222);
+ user_data2.SetUint("uint2", 2222);
+ user_data2.SetBool("bool2", false);
+ user_data2.SetReference("ref2", string2a, sizeof(string2a));
+ user_data2.SetStringReference("sref2", string2b);
+
+ ASSERT_TRUE(tracker->CreateSnapshot(&tracker_snapshot));
+ ASSERT_EQ(2U, tracker_snapshot.activity_stack.size());
+
+ ThreadActivityAnalyzer analyzer(*tracker);
+ analyzer.AddGlobalInformation(&global_analyzer);
+ const ThreadActivityAnalyzer::Snapshot& analyzer_snapshot =
+ analyzer.activity_snapshot();
+ ASSERT_EQ(2U, analyzer_snapshot.user_data_stack.size());
+ const ActivityUserData::Snapshot& user_data =
+ analyzer_snapshot.user_data_stack.at(1);
+ EXPECT_EQ(8U, user_data.size());
+ ASSERT_TRUE(ContainsKey(user_data, "raw2"));
+ EXPECT_EQ("foo2", user_data.at("raw2").Get().as_string());
+ ASSERT_TRUE(ContainsKey(user_data, "string2"));
+ EXPECT_EQ("bar2", user_data.at("string2").GetString().as_string());
+ ASSERT_TRUE(ContainsKey(user_data, "char2"));
+ EXPECT_EQ('2', user_data.at("char2").GetChar());
+ ASSERT_TRUE(ContainsKey(user_data, "int2"));
+ EXPECT_EQ(-2222, user_data.at("int2").GetInt());
+ ASSERT_TRUE(ContainsKey(user_data, "uint2"));
+ EXPECT_EQ(2222U, user_data.at("uint2").GetUint());
+ ASSERT_TRUE(ContainsKey(user_data, "bool2"));
+ EXPECT_FALSE(user_data.at("bool2").GetBool());
+ ASSERT_TRUE(ContainsKey(user_data, "ref2"));
+ EXPECT_EQ(string2a, user_data.at("ref2").GetReference().data());
+ EXPECT_EQ(sizeof(string2a), user_data.at("ref2").GetReference().size());
+ ASSERT_TRUE(ContainsKey(user_data, "sref2"));
+ EXPECT_EQ(string2b, user_data.at("sref2").GetStringReference().data());
+ EXPECT_EQ(strlen(string2b),
+ user_data.at("sref2").GetStringReference().size());
+ }
+
+ ASSERT_TRUE(tracker->CreateSnapshot(&tracker_snapshot));
+ ASSERT_EQ(1U, tracker_snapshot.activity_stack.size());
+
+ ThreadActivityAnalyzer analyzer(*tracker);
+ analyzer.AddGlobalInformation(&global_analyzer);
+ const ThreadActivityAnalyzer::Snapshot& analyzer_snapshot =
+ analyzer.activity_snapshot();
+ ASSERT_EQ(1U, analyzer_snapshot.user_data_stack.size());
+ const ActivityUserData::Snapshot& user_data =
+ analyzer_snapshot.user_data_stack.at(0);
+ EXPECT_EQ(8U, user_data.size());
+ EXPECT_EQ("foo1", user_data.at("raw1").Get().as_string());
+ EXPECT_EQ("bar1", user_data.at("string1").GetString().as_string());
+ EXPECT_EQ('1', user_data.at("char1").GetChar());
+ EXPECT_EQ(-1111, user_data.at("int1").GetInt());
+ EXPECT_EQ(1111U, user_data.at("uint1").GetUint());
+ EXPECT_TRUE(user_data.at("bool1").GetBool());
+ EXPECT_EQ(string1a, user_data.at("ref1").GetReference().data());
+ EXPECT_EQ(sizeof(string1a), user_data.at("ref1").GetReference().size());
+ EXPECT_EQ(string1b, user_data.at("sref1").GetStringReference().data());
+ EXPECT_EQ(strlen(string1b),
+ user_data.at("sref1").GetStringReference().size());
+ }
+
+ ASSERT_TRUE(tracker->CreateSnapshot(&tracker_snapshot));
+ ASSERT_EQ(0U, tracker_snapshot.activity_stack.size());
+}
+
+TEST_F(ActivityAnalyzerTest, GlobalUserDataTest) {
+ const int64_t pid = GetCurrentProcId();
+ GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
+
+ const char string1[] = "foo";
+ const char string2[] = "bar";
+
+ PersistentMemoryAllocator* allocator =
+ GlobalActivityTracker::Get()->allocator();
+ GlobalActivityAnalyzer global_analyzer(
+ std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "",
+ true));
+
+ ActivityUserData& process_data = GlobalActivityTracker::Get()->process_data();
+ ASSERT_NE(0U, process_data.id());
+ process_data.Set("raw", "foo", 3);
+ process_data.SetString("string", "bar");
+ process_data.SetChar("char", '9');
+ process_data.SetInt("int", -9999);
+ process_data.SetUint("uint", 9999);
+ process_data.SetBool("bool", true);
+ process_data.SetReference("ref", string1, sizeof(string1));
+ process_data.SetStringReference("sref", string2);
+
+ int64_t first_pid = global_analyzer.GetFirstProcess();
+ DCHECK_EQ(pid, first_pid);
+ const ActivityUserData::Snapshot& snapshot =
+ global_analyzer.GetProcessDataSnapshot(pid);
+ ASSERT_TRUE(ContainsKey(snapshot, "raw"));
+ EXPECT_EQ("foo", snapshot.at("raw").Get().as_string());
+ ASSERT_TRUE(ContainsKey(snapshot, "string"));
+ EXPECT_EQ("bar", snapshot.at("string").GetString().as_string());
+ ASSERT_TRUE(ContainsKey(snapshot, "char"));
+ EXPECT_EQ('9', snapshot.at("char").GetChar());
+ ASSERT_TRUE(ContainsKey(snapshot, "int"));
+ EXPECT_EQ(-9999, snapshot.at("int").GetInt());
+ ASSERT_TRUE(ContainsKey(snapshot, "uint"));
+ EXPECT_EQ(9999U, snapshot.at("uint").GetUint());
+ ASSERT_TRUE(ContainsKey(snapshot, "bool"));
+ EXPECT_TRUE(snapshot.at("bool").GetBool());
+ ASSERT_TRUE(ContainsKey(snapshot, "ref"));
+ EXPECT_EQ(string1, snapshot.at("ref").GetReference().data());
+ EXPECT_EQ(sizeof(string1), snapshot.at("ref").GetReference().size());
+ ASSERT_TRUE(ContainsKey(snapshot, "sref"));
+ EXPECT_EQ(string2, snapshot.at("sref").GetStringReference().data());
+ EXPECT_EQ(strlen(string2), snapshot.at("sref").GetStringReference().size());
+}
+
+TEST_F(ActivityAnalyzerTest, GlobalModulesTest) {
+ GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
+ GlobalActivityTracker* global = GlobalActivityTracker::Get();
+
+ PersistentMemoryAllocator* allocator = global->allocator();
+ GlobalActivityAnalyzer global_analyzer(
+ std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "",
+ true));
+
+ GlobalActivityTracker::ModuleInfo info1;
+ info1.is_loaded = true;
+ info1.address = 0x12345678;
+ info1.load_time = 1111;
+ info1.size = 0xABCDEF;
+ info1.timestamp = 111;
+ info1.age = 11;
+ info1.identifier[0] = 1;
+ info1.file = "anything";
+ info1.debug_file = "elsewhere";
+
+ global->RecordModuleInfo(info1);
+ std::vector<GlobalActivityTracker::ModuleInfo> modules1;
+ modules1 = global_analyzer.GetModules(global_analyzer.GetFirstProcess());
+ ASSERT_EQ(1U, modules1.size());
+ GlobalActivityTracker::ModuleInfo& stored1a = modules1[0];
+ EXPECT_EQ(info1.is_loaded, stored1a.is_loaded);
+ EXPECT_EQ(info1.address, stored1a.address);
+ EXPECT_NE(info1.load_time, stored1a.load_time);
+ EXPECT_EQ(info1.size, stored1a.size);
+ EXPECT_EQ(info1.timestamp, stored1a.timestamp);
+ EXPECT_EQ(info1.age, stored1a.age);
+ EXPECT_EQ(info1.identifier[0], stored1a.identifier[0]);
+ EXPECT_EQ(info1.file, stored1a.file);
+ EXPECT_EQ(info1.debug_file, stored1a.debug_file);
+
+ info1.is_loaded = false;
+ global->RecordModuleInfo(info1);
+ modules1 = global_analyzer.GetModules(global_analyzer.GetFirstProcess());
+ ASSERT_EQ(1U, modules1.size());
+ GlobalActivityTracker::ModuleInfo& stored1b = modules1[0];
+ EXPECT_EQ(info1.is_loaded, stored1b.is_loaded);
+ EXPECT_EQ(info1.address, stored1b.address);
+ EXPECT_NE(info1.load_time, stored1b.load_time);
+ EXPECT_EQ(info1.size, stored1b.size);
+ EXPECT_EQ(info1.timestamp, stored1b.timestamp);
+ EXPECT_EQ(info1.age, stored1b.age);
+ EXPECT_EQ(info1.identifier[0], stored1b.identifier[0]);
+ EXPECT_EQ(info1.file, stored1b.file);
+ EXPECT_EQ(info1.debug_file, stored1b.debug_file);
+
+ GlobalActivityTracker::ModuleInfo info2;
+ info2.is_loaded = true;
+ info2.address = 0x87654321;
+ info2.load_time = 2222;
+ info2.size = 0xFEDCBA;
+ info2.timestamp = 222;
+ info2.age = 22;
+ info2.identifier[0] = 2;
+ info2.file = "nothing";
+ info2.debug_file = "farewell";
+
+ global->RecordModuleInfo(info2);
+ std::vector<GlobalActivityTracker::ModuleInfo> modules2;
+ modules2 = global_analyzer.GetModules(global_analyzer.GetFirstProcess());
+ ASSERT_EQ(2U, modules2.size());
+ GlobalActivityTracker::ModuleInfo& stored2 = modules2[1];
+ EXPECT_EQ(info2.is_loaded, stored2.is_loaded);
+ EXPECT_EQ(info2.address, stored2.address);
+ EXPECT_NE(info2.load_time, stored2.load_time);
+ EXPECT_EQ(info2.size, stored2.size);
+ EXPECT_EQ(info2.timestamp, stored2.timestamp);
+ EXPECT_EQ(info2.age, stored2.age);
+ EXPECT_EQ(info2.identifier[0], stored2.identifier[0]);
+ EXPECT_EQ(info2.file, stored2.file);
+ EXPECT_EQ(info2.debug_file, stored2.debug_file);
+}
+
+TEST_F(ActivityAnalyzerTest, GlobalLogMessages) {
+ GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
+
+ PersistentMemoryAllocator* allocator =
+ GlobalActivityTracker::Get()->allocator();
+ GlobalActivityAnalyzer analyzer(std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "", true));
+
+ GlobalActivityTracker::Get()->RecordLogMessage("hello world");
+ GlobalActivityTracker::Get()->RecordLogMessage("foo bar");
+
+ std::vector<std::string> messages = analyzer.GetLogMessages();
+ ASSERT_EQ(2U, messages.size());
+ EXPECT_EQ("hello world", messages[0]);
+ EXPECT_EQ("foo bar", messages[1]);
+}
+
+TEST_F(ActivityAnalyzerTest, GlobalMultiProcess) {
+ GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 1001);
+ GlobalActivityTracker* global = GlobalActivityTracker::Get();
+ PersistentMemoryAllocator* allocator = global->allocator();
+ EXPECT_EQ(1001, global->process_id());
+
+ int64_t process_id;
+ int64_t create_stamp;
+ ActivityUserData::GetOwningProcessId(
+ GlobalActivityTracker::Get()->process_data().GetBaseAddress(),
+ &process_id, &create_stamp);
+ ASSERT_EQ(1001, process_id);
+
+ GlobalActivityTracker::Get()->process_data().SetInt("pid",
+ global->process_id());
+
+ GlobalActivityAnalyzer analyzer(std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(allocator->data()), allocator->size(), 0, 0, "", true));
+
+ AsOtherProcess(2002, [&global]() {
+ ASSERT_NE(global, GlobalActivityTracker::Get());
+ EXPECT_EQ(2002, GlobalActivityTracker::Get()->process_id());
+
+ int64_t process_id;
+ int64_t create_stamp;
+ ActivityUserData::GetOwningProcessId(
+ GlobalActivityTracker::Get()->process_data().GetBaseAddress(),
+ &process_id, &create_stamp);
+ ASSERT_EQ(2002, process_id);
+
+ GlobalActivityTracker::Get()->process_data().SetInt(
+ "pid", GlobalActivityTracker::Get()->process_id());
+ });
+ ASSERT_EQ(global, GlobalActivityTracker::Get());
+ EXPECT_EQ(1001, GlobalActivityTracker::Get()->process_id());
+
+ const int64_t pid1 = analyzer.GetFirstProcess();
+ ASSERT_EQ(1001, pid1);
+ const int64_t pid2 = analyzer.GetNextProcess();
+ ASSERT_EQ(2002, pid2);
+ EXPECT_EQ(0, analyzer.GetNextProcess());
+
+ const ActivityUserData::Snapshot& pdata1 =
+ analyzer.GetProcessDataSnapshot(pid1);
+ const ActivityUserData::Snapshot& pdata2 =
+ analyzer.GetProcessDataSnapshot(pid2);
+ EXPECT_EQ(1001, pdata1.at("pid").GetInt());
+ EXPECT_EQ(2002, pdata2.at("pid").GetInt());
+}
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/asan_invalid_access.cc b/base/debug/asan_invalid_access.cc
new file mode 100644
index 0000000000..07c19db9c5
--- /dev/null
+++ b/base/debug/asan_invalid_access.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/debug/asan_invalid_access.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/debug/alias.h"
+#include "base/logging.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace base {
+namespace debug {
+
+namespace {
+
+#if defined(OS_WIN) && defined(ADDRESS_SANITIZER)
+// Corrupt a memory block and make sure that the corruption gets detected either
+// when we free it or when another crash happens (if |induce_crash| is set to
+// true).
+NOINLINE void CorruptMemoryBlock(bool induce_crash) {
+ // NOTE(sebmarchand): We intentionally corrupt a memory block here in order to
+ // trigger an Address Sanitizer (ASAN) error report.
+ static const int kArraySize = 5;
+ LONG* array = new LONG[kArraySize];
+
+ // Explicitly call out to a kernel32 function to perform the memory access.
+ // This way the underflow won't be detected but the corruption will (as the
+ // allocator will still be hooked).
+ auto InterlockedIncrementFn =
+ reinterpret_cast<LONG (*)(LONG volatile * addend)>(
+ GetProcAddress(GetModuleHandle(L"kernel32"), "InterlockedIncrement"));
+ CHECK(InterlockedIncrementFn);
+
+ LONG volatile dummy = InterlockedIncrementFn(array - 1);
+ base::debug::Alias(const_cast<LONG*>(&dummy));
+
+ if (induce_crash)
+ CHECK(false);
+ delete[] array;
+}
+#endif // OS_WIN && ADDRESS_SANITIZER
+
+} // namespace
+
+#if defined(ADDRESS_SANITIZER)
+// NOTE(sebmarchand): We intentionally perform some invalid heap access here in
+// order to trigger an AddressSanitizer (ASan) error report.
+
+static const size_t kArraySize = 5;
+
+void AsanHeapOverflow() {
+ // Declares the array as volatile to make sure it doesn't get optimized away.
+ std::unique_ptr<volatile int[]> array(
+ const_cast<volatile int*>(new int[kArraySize]));
+ int dummy = array[kArraySize];
+ base::debug::Alias(&dummy);
+}
+
+void AsanHeapUnderflow() {
+ // Declares the array as volatile to make sure it doesn't get optimized away.
+ std::unique_ptr<volatile int[]> array(
+ const_cast<volatile int*>(new int[kArraySize]));
+ // We need to store the underflow address in a temporary variable as trying to
+ // access array[-1] will trigger a warning C4245: "conversion from 'int' to
+ // 'size_t', signed/unsigned mismatch".
+ volatile int* underflow_address = &array[0] - 1;
+ int dummy = *underflow_address;
+ base::debug::Alias(&dummy);
+}
+
+void AsanHeapUseAfterFree() {
+ // Declares the array as volatile to make sure it doesn't get optimized away.
+ std::unique_ptr<volatile int[]> array(
+ const_cast<volatile int*>(new int[kArraySize]));
+ volatile int* dangling = array.get();
+ array.reset();
+ int dummy = dangling[kArraySize / 2];
+ base::debug::Alias(&dummy);
+}
+
+#if defined(OS_WIN)
+void AsanCorruptHeapBlock() {
+ CorruptMemoryBlock(false);
+}
+
+void AsanCorruptHeap() {
+ CorruptMemoryBlock(true);
+}
+#endif // OS_WIN
+#endif // ADDRESS_SANITIZER
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/asan_invalid_access.h b/base/debug/asan_invalid_access.h
new file mode 100644
index 0000000000..dc9a7ee647
--- /dev/null
+++ b/base/debug/asan_invalid_access.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Defines some functions that intentionally do an invalid memory access in
+// order to trigger an AddressSanitizer (ASan) error report.
+
+#ifndef BASE_DEBUG_ASAN_INVALID_ACCESS_H_
+#define BASE_DEBUG_ASAN_INVALID_ACCESS_H_
+
+#include "base/base_export.h"
+#include "base/compiler_specific.h"
+#include "build/build_config.h"
+
+namespace base {
+namespace debug {
+
+#if defined(ADDRESS_SANITIZER)
+
+// Generates an heap buffer overflow.
+BASE_EXPORT NOINLINE void AsanHeapOverflow();
+
+// Generates an heap buffer underflow.
+BASE_EXPORT NOINLINE void AsanHeapUnderflow();
+
+// Generates an use after free.
+BASE_EXPORT NOINLINE void AsanHeapUseAfterFree();
+
+// The "corrupt-block" and "corrupt-heap" classes of bugs is specific to
+// Windows.
+#if defined(OS_WIN)
+// Corrupts a memory block and makes sure that the corruption gets detected when
+// we try to free this block.
+BASE_EXPORT NOINLINE void AsanCorruptHeapBlock();
+
+// Corrupts the heap and makes sure that the corruption gets detected when a
+// crash occur.
+BASE_EXPORT NOINLINE void AsanCorruptHeap();
+
+#endif // OS_WIN
+#endif // ADDRESS_SANITIZER
+
+} // namespace debug
+} // namespace base
+
+#endif // BASE_DEBUG_ASAN_INVALID_ACCESS_H_
diff --git a/base/debug/crash_logging_unittest.cc b/base/debug/crash_logging_unittest.cc
new file mode 100644
index 0000000000..c10d36e368
--- /dev/null
+++ b/base/debug/crash_logging_unittest.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/debug/crash_logging.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(CrashLoggingTest, UninitializedCrashKeyStringSupport) {
+ auto* crash_key = base::debug::AllocateCrashKeyString(
+ "test", base::debug::CrashKeySize::Size32);
+ EXPECT_FALSE(crash_key);
+
+ base::debug::SetCrashKeyString(crash_key, "value");
+
+ base::debug::ClearCrashKeyString(crash_key);
+}
diff --git a/base/debug/proc_maps_linux_unittest.cc b/base/debug/proc_maps_linux_unittest.cc
new file mode 100644
index 0000000000..7abf152b0e
--- /dev/null
+++ b/base/debug/proc_maps_linux_unittest.cc
@@ -0,0 +1,328 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/debug/proc_maps_linux.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace debug {
+
+TEST(ProcMapsTest, Empty) {
+ std::vector<MappedMemoryRegion> regions;
+ EXPECT_TRUE(ParseProcMaps("", &regions));
+ EXPECT_EQ(0u, regions.size());
+}
+
+TEST(ProcMapsTest, NoSpaces) {
+ static const char kNoSpaces[] =
+ "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat\n";
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_TRUE(ParseProcMaps(kNoSpaces, &regions));
+ ASSERT_EQ(1u, regions.size());
+
+ EXPECT_EQ(0x00400000u, regions[0].start);
+ EXPECT_EQ(0x0040b000u, regions[0].end);
+ EXPECT_EQ(0x00002200u, regions[0].offset);
+ EXPECT_EQ("/bin/cat", regions[0].path);
+}
+
+TEST(ProcMapsTest, Spaces) {
+ static const char kSpaces[] =
+ "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/space cat\n";
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_TRUE(ParseProcMaps(kSpaces, &regions));
+ ASSERT_EQ(1u, regions.size());
+
+ EXPECT_EQ(0x00400000u, regions[0].start);
+ EXPECT_EQ(0x0040b000u, regions[0].end);
+ EXPECT_EQ(0x00002200u, regions[0].offset);
+ EXPECT_EQ("/bin/space cat", regions[0].path);
+}
+
+TEST(ProcMapsTest, NoNewline) {
+ static const char kNoSpaces[] =
+ "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat";
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_FALSE(ParseProcMaps(kNoSpaces, &regions));
+}
+
+TEST(ProcMapsTest, NoPath) {
+ static const char kNoPath[] =
+ "00400000-0040b000 rw-p 00000000 00:00 0 \n";
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_TRUE(ParseProcMaps(kNoPath, &regions));
+ ASSERT_EQ(1u, regions.size());
+
+ EXPECT_EQ(0x00400000u, regions[0].start);
+ EXPECT_EQ(0x0040b000u, regions[0].end);
+ EXPECT_EQ(0x00000000u, regions[0].offset);
+ EXPECT_EQ("", regions[0].path);
+}
+
+TEST(ProcMapsTest, Heap) {
+ static const char kHeap[] =
+ "022ac000-022cd000 rw-p 00000000 00:00 0 [heap]\n";
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_TRUE(ParseProcMaps(kHeap, &regions));
+ ASSERT_EQ(1u, regions.size());
+
+ EXPECT_EQ(0x022ac000u, regions[0].start);
+ EXPECT_EQ(0x022cd000u, regions[0].end);
+ EXPECT_EQ(0x00000000u, regions[0].offset);
+ EXPECT_EQ("[heap]", regions[0].path);
+}
+
+#if defined(ARCH_CPU_32_BITS)
+TEST(ProcMapsTest, Stack32) {
+ static const char kStack[] =
+ "beb04000-beb25000 rw-p 00000000 00:00 0 [stack]\n";
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_TRUE(ParseProcMaps(kStack, &regions));
+ ASSERT_EQ(1u, regions.size());
+
+ EXPECT_EQ(0xbeb04000u, regions[0].start);
+ EXPECT_EQ(0xbeb25000u, regions[0].end);
+ EXPECT_EQ(0x00000000u, regions[0].offset);
+ EXPECT_EQ("[stack]", regions[0].path);
+}
+#elif defined(ARCH_CPU_64_BITS)
+TEST(ProcMapsTest, Stack64) {
+ static const char kStack[] =
+ "7fff69c5b000-7fff69c7d000 rw-p 00000000 00:00 0 [stack]\n";
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_TRUE(ParseProcMaps(kStack, &regions));
+ ASSERT_EQ(1u, regions.size());
+
+ EXPECT_EQ(0x7fff69c5b000u, regions[0].start);
+ EXPECT_EQ(0x7fff69c7d000u, regions[0].end);
+ EXPECT_EQ(0x00000000u, regions[0].offset);
+ EXPECT_EQ("[stack]", regions[0].path);
+}
+#endif
+
+TEST(ProcMapsTest, Multiple) {
+ static const char kMultiple[] =
+ "00400000-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n"
+ "0060a000-0060b000 r--p 0000a000 fc:00 794418 /bin/cat\n"
+ "0060b000-0060c000 rw-p 0000b000 fc:00 794418 /bin/cat\n";
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_TRUE(ParseProcMaps(kMultiple, &regions));
+ ASSERT_EQ(3u, regions.size());
+
+ EXPECT_EQ(0x00400000u, regions[0].start);
+ EXPECT_EQ(0x0040b000u, regions[0].end);
+ EXPECT_EQ(0x00000000u, regions[0].offset);
+ EXPECT_EQ("/bin/cat", regions[0].path);
+
+ EXPECT_EQ(0x0060a000u, regions[1].start);
+ EXPECT_EQ(0x0060b000u, regions[1].end);
+ EXPECT_EQ(0x0000a000u, regions[1].offset);
+ EXPECT_EQ("/bin/cat", regions[1].path);
+
+ EXPECT_EQ(0x0060b000u, regions[2].start);
+ EXPECT_EQ(0x0060c000u, regions[2].end);
+ EXPECT_EQ(0x0000b000u, regions[2].offset);
+ EXPECT_EQ("/bin/cat", regions[2].path);
+}
+
+TEST(ProcMapsTest, Permissions) {
+ static struct {
+ const char* input;
+ uint8_t permissions;
+ } kTestCases[] = {
+ {"00400000-0040b000 ---s 00000000 fc:00 794418 /bin/cat\n", 0},
+ {"00400000-0040b000 ---S 00000000 fc:00 794418 /bin/cat\n", 0},
+ {"00400000-0040b000 r--s 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::READ},
+ {"00400000-0040b000 -w-s 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::WRITE},
+ {"00400000-0040b000 --xs 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::EXECUTE},
+ {"00400000-0040b000 rwxs 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::READ | MappedMemoryRegion::WRITE |
+ MappedMemoryRegion::EXECUTE},
+ {"00400000-0040b000 ---p 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::PRIVATE},
+ {"00400000-0040b000 r--p 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::READ | MappedMemoryRegion::PRIVATE},
+ {"00400000-0040b000 -w-p 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::WRITE | MappedMemoryRegion::PRIVATE},
+ {"00400000-0040b000 --xp 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::EXECUTE | MappedMemoryRegion::PRIVATE},
+ {"00400000-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n",
+ MappedMemoryRegion::READ | MappedMemoryRegion::WRITE |
+ MappedMemoryRegion::EXECUTE | MappedMemoryRegion::PRIVATE},
+ };
+
+ for (size_t i = 0; i < arraysize(kTestCases); ++i) {
+ SCOPED_TRACE(
+ base::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i].input));
+
+ std::vector<MappedMemoryRegion> regions;
+ EXPECT_TRUE(ParseProcMaps(kTestCases[i].input, &regions));
+ EXPECT_EQ(1u, regions.size());
+ if (regions.empty())
+ continue;
+ EXPECT_EQ(kTestCases[i].permissions, regions[0].permissions);
+ }
+}
+
+#if defined(ADDRESS_SANITIZER)
+// AddressSanitizer may move local variables to a dedicated "fake stack" which
+// is outside the stack region listed in /proc/self/maps. We disable ASan
+// instrumentation for this function to force the variable to be local.
+__attribute__((no_sanitize_address))
+#endif
+void CheckProcMapsRegions(const std::vector<MappedMemoryRegion> &regions) {
+ // We should be able to find both the current executable as well as the stack
+ // mapped into memory. Use the address of |exe_path| as a way of finding the
+ // stack.
+ FilePath exe_path;
+ EXPECT_TRUE(PathService::Get(FILE_EXE, &exe_path));
+ uintptr_t address = reinterpret_cast<uintptr_t>(&exe_path);
+ bool found_exe = false;
+ bool found_stack = false;
+ bool found_address = false;
+
+ for (size_t i = 0; i < regions.size(); ++i) {
+ if (regions[i].path == exe_path.value()) {
+ // It's OK to find the executable mapped multiple times as there'll be
+ // multiple sections (e.g., text, data).
+ found_exe = true;
+ }
+
+ if (regions[i].path == "[stack]") {
+// On Android the test is run on a background thread, since [stack] is for
+// the main thread, we cannot test this.
+#if !defined(OS_ANDROID)
+ EXPECT_GE(address, regions[i].start);
+ EXPECT_LT(address, regions[i].end);
+#endif
+ EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::READ);
+ EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::WRITE);
+ EXPECT_FALSE(regions[i].permissions & MappedMemoryRegion::EXECUTE);
+ EXPECT_TRUE(regions[i].permissions & MappedMemoryRegion::PRIVATE);
+ EXPECT_FALSE(found_stack) << "Found duplicate stacks";
+ found_stack = true;
+ }
+
+ if (address >= regions[i].start && address < regions[i].end) {
+ EXPECT_FALSE(found_address) << "Found same address in multiple regions";
+ found_address = true;
+ }
+ }
+
+ EXPECT_TRUE(found_exe);
+ EXPECT_TRUE(found_stack);
+ EXPECT_TRUE(found_address);
+}
+
+TEST(ProcMapsTest, ReadProcMaps) {
+ std::string proc_maps;
+ ASSERT_TRUE(ReadProcMaps(&proc_maps));
+
+ std::vector<MappedMemoryRegion> regions;
+ ASSERT_TRUE(ParseProcMaps(proc_maps, &regions));
+ ASSERT_FALSE(regions.empty());
+
+ CheckProcMapsRegions(regions);
+}
+
+TEST(ProcMapsTest, ReadProcMapsNonEmptyString) {
+ std::string old_string("I forgot to clear the string");
+ std::string proc_maps(old_string);
+ ASSERT_TRUE(ReadProcMaps(&proc_maps));
+ EXPECT_EQ(std::string::npos, proc_maps.find(old_string));
+}
+
+TEST(ProcMapsTest, MissingFields) {
+ static const char* const kTestCases[] = {
+ "00400000\n", // Missing end + beyond.
+ "00400000-0040b000\n", // Missing perms + beyond.
+ "00400000-0040b000 r-xp\n", // Missing offset + beyond.
+ "00400000-0040b000 r-xp 00000000\n", // Missing device + beyond.
+ "00400000-0040b000 r-xp 00000000 fc:00\n", // Missing inode + beyond.
+ "00400000-0040b000 00000000 fc:00 794418 /bin/cat\n", // Missing perms.
+ "00400000-0040b000 r-xp fc:00 794418 /bin/cat\n", // Missing offset.
+ "00400000-0040b000 r-xp 00000000 fc:00 /bin/cat\n", // Missing inode.
+ "00400000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing end.
+ "-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing start.
+ "00400000-0040b000 r-xp 00000000 794418 /bin/cat\n", // Missing device.
+ };
+
+ for (size_t i = 0; i < arraysize(kTestCases); ++i) {
+ SCOPED_TRACE(base::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i]));
+ std::vector<MappedMemoryRegion> regions;
+ EXPECT_FALSE(ParseProcMaps(kTestCases[i], &regions));
+ }
+}
+
+TEST(ProcMapsTest, InvalidInput) {
+ static const char* const kTestCases[] = {
+ "thisisal-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n",
+ "0040000d-linvalid rwxp 00000000 fc:00 794418 /bin/cat\n",
+ "00400000-0040b000 inpu 00000000 fc:00 794418 /bin/cat\n",
+ "00400000-0040b000 rwxp tforproc fc:00 794418 /bin/cat\n",
+ "00400000-0040b000 rwxp 00000000 ma:ps 794418 /bin/cat\n",
+ "00400000-0040b000 rwxp 00000000 fc:00 parse! /bin/cat\n",
+ };
+
+ for (size_t i = 0; i < arraysize(kTestCases); ++i) {
+ SCOPED_TRACE(base::StringPrintf("kTestCases[%zu] = %s", i, kTestCases[i]));
+ std::vector<MappedMemoryRegion> regions;
+ EXPECT_FALSE(ParseProcMaps(kTestCases[i], &regions));
+ }
+}
+
+TEST(ProcMapsTest, ParseProcMapsEmptyString) {
+ std::vector<MappedMemoryRegion> regions;
+ EXPECT_TRUE(ParseProcMaps("", &regions));
+ EXPECT_EQ(0ULL, regions.size());
+}
+
+// Testing a couple of remotely possible weird things in the input:
+// - Line ending with \r\n or \n\r.
+// - File name contains quotes.
+// - File name has whitespaces.
+TEST(ProcMapsTest, ParseProcMapsWeirdCorrectInput) {
+ std::vector<MappedMemoryRegion> regions;
+ const std::string kContents =
+ "00400000-0040b000 r-xp 00000000 fc:00 2106562 "
+ " /bin/cat\r\n"
+ "7f53b7dad000-7f53b7f62000 r-xp 00000000 fc:00 263011 "
+ " /lib/x86_64-linux-gnu/libc-2.15.so\n\r"
+ "7f53b816d000-7f53b818f000 r-xp 00000000 fc:00 264284 "
+ " /lib/x86_64-linux-gnu/ld-2.15.so\n"
+ "7fff9c7ff000-7fff9c800000 r-xp 00000000 00:00 0 "
+ " \"vd so\"\n"
+ "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 "
+ " [vsys call]\n";
+ EXPECT_TRUE(ParseProcMaps(kContents, &regions));
+ EXPECT_EQ(5ULL, regions.size());
+ EXPECT_EQ("/bin/cat", regions[0].path);
+ EXPECT_EQ("/lib/x86_64-linux-gnu/libc-2.15.so", regions[1].path);
+ EXPECT_EQ("/lib/x86_64-linux-gnu/ld-2.15.so", regions[2].path);
+ EXPECT_EQ("\"vd so\"", regions[3].path);
+ EXPECT_EQ("[vsys call]", regions[4].path);
+}
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/stack_trace_unittest.cc b/base/debug/stack_trace_unittest.cc
new file mode 100644
index 0000000000..02f076a2ae
--- /dev/null
+++ b/base/debug/stack_trace_unittest.cc
@@ -0,0 +1,320 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <limits>
+#include <sstream>
+#include <string>
+
+#include "base/debug/debugging_buildflags.h"
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/process/kill.h"
+#include "base/process/process_handle.h"
+#include "base/test/test_timeouts.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_IOS)
+#include "base/test/multiprocess_test.h"
+#endif
+
+namespace base {
+namespace debug {
+
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_IOS)
+typedef MultiProcessTest StackTraceTest;
+#else
+typedef testing::Test StackTraceTest;
+#endif
+
+// Note: On Linux, this test currently only fully works on Debug builds.
+// See comments in the #ifdef soup if you intend to change this.
+#if defined(OS_WIN)
+// Always fails on Windows: crbug.com/32070
+#define MAYBE_OutputToStream DISABLED_OutputToStream
+#else
+#define MAYBE_OutputToStream OutputToStream
+#endif
+#if !defined(__UCLIBC__) && !defined(_AIX)
+TEST_F(StackTraceTest, MAYBE_OutputToStream) {
+ StackTrace trace;
+
+ // Dump the trace into a string.
+ std::ostringstream os;
+ trace.OutputToStream(&os);
+ std::string backtrace_message = os.str();
+
+ // ToString() should produce the same output.
+ EXPECT_EQ(backtrace_message, trace.ToString());
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG
+ // Stack traces require an extra data table that bloats our binaries,
+ // so they're turned off for release builds. We stop the test here,
+ // at least letting us verify that the calls don't crash.
+ return;
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG
+
+ size_t frames_found = 0;
+ trace.Addresses(&frames_found);
+ ASSERT_GE(frames_found, 5u) <<
+ "No stack frames found. Skipping rest of test.";
+
+ // Check if the output has symbol initialization warning. If it does, fail.
+ ASSERT_EQ(backtrace_message.find("Dumping unresolved backtrace"),
+ std::string::npos) <<
+ "Unable to resolve symbols. Skipping rest of test.";
+
+#if defined(OS_MACOSX)
+#if 0
+ // Disabled due to -fvisibility=hidden in build config.
+
+ // Symbol resolution via the backtrace_symbol function does not work well
+ // in OS X.
+ // See this thread:
+ //
+ // http://lists.apple.com/archives/darwin-dev/2009/Mar/msg00111.html
+ //
+ // Just check instead that we find our way back to the "start" symbol
+ // which should be the first symbol in the trace.
+ //
+ // TODO(port): Find a more reliable way to resolve symbols.
+
+ // Expect to at least find main.
+ EXPECT_TRUE(backtrace_message.find("start") != std::string::npos)
+ << "Expected to find start in backtrace:\n"
+ << backtrace_message;
+
+#endif
+#elif defined(USE_SYMBOLIZE)
+ // This branch is for gcc-compiled code, but not Mac due to the
+ // above #if.
+ // Expect a demangled symbol.
+ EXPECT_TRUE(backtrace_message.find("testing::Test::Run()") !=
+ std::string::npos)
+ << "Expected a demangled symbol in backtrace:\n"
+ << backtrace_message;
+
+#elif 0
+ // This is the fall-through case; it used to cover Windows.
+ // But it's disabled because of varying buildbot configs;
+ // some lack symbols.
+
+ // Expect to at least find main.
+ EXPECT_TRUE(backtrace_message.find("main") != std::string::npos)
+ << "Expected to find main in backtrace:\n"
+ << backtrace_message;
+
+#if defined(OS_WIN)
+// MSVC doesn't allow the use of C99's __func__ within C++, so we fake it with
+// MSVC's __FUNCTION__ macro.
+#define __func__ __FUNCTION__
+#endif
+
+ // Expect to find this function as well.
+ // Note: This will fail if not linked with -rdynamic (aka -export_dynamic)
+ EXPECT_TRUE(backtrace_message.find(__func__) != std::string::npos)
+ << "Expected to find " << __func__ << " in backtrace:\n"
+ << backtrace_message;
+
+#endif // define(OS_MACOSX)
+}
+
+#if !defined(OFFICIAL_BUILD) && !defined(NO_UNWIND_TABLES)
+// Disabled in Official builds, where Link-Time Optimization can result in two
+// or fewer stack frames being available, causing the test to fail.
+TEST_F(StackTraceTest, TruncatedTrace) {
+ StackTrace trace;
+
+ size_t count = 0;
+ trace.Addresses(&count);
+ ASSERT_LT(2u, count);
+
+ StackTrace truncated(2);
+ truncated.Addresses(&count);
+ EXPECT_EQ(2u, count);
+}
+#endif // !defined(OFFICIAL_BUILD)
+
+// The test is used for manual testing, e.g., to see the raw output.
+TEST_F(StackTraceTest, DebugOutputToStream) {
+ StackTrace trace;
+ std::ostringstream os;
+ trace.OutputToStream(&os);
+ VLOG(1) << os.str();
+}
+
+// The test is used for manual testing, e.g., to see the raw output.
+TEST_F(StackTraceTest, DebugPrintBacktrace) {
+ StackTrace().Print();
+}
+#endif // !defined(__UCLIBC__)
+
+#if defined(OS_POSIX) && !defined(OS_ANDROID)
+#if !defined(OS_IOS)
+static char* newArray() {
+ // Clang warns about the mismatched new[]/delete if they occur in the same
+ // function.
+ return new char[10];
+}
+
+MULTIPROCESS_TEST_MAIN(MismatchedMallocChildProcess) {
+ char* pointer = newArray();
+ delete pointer;
+ return 2;
+}
+
+// Regression test for StackDumpingSignalHandler async-signal unsafety.
+// Combined with tcmalloc's debugallocation, that signal handler
+// and e.g. mismatched new[]/delete would cause a hang because
+// of re-entering malloc.
+TEST_F(StackTraceTest, AsyncSignalUnsafeSignalHandlerHang) {
+ Process child = SpawnChild("MismatchedMallocChildProcess");
+ ASSERT_TRUE(child.IsValid());
+ int exit_code;
+ ASSERT_TRUE(
+ child.WaitForExitWithTimeout(TestTimeouts::action_timeout(), &exit_code));
+}
+#endif // !defined(OS_IOS)
+
+namespace {
+
+std::string itoa_r_wrapper(intptr_t i, size_t sz, int base, size_t padding) {
+ char buffer[1024];
+ CHECK_LE(sz, sizeof(buffer));
+
+ char* result = internal::itoa_r(i, buffer, sz, base, padding);
+ EXPECT_TRUE(result);
+ return std::string(buffer);
+}
+
+} // namespace
+
+TEST_F(StackTraceTest, itoa_r) {
+ EXPECT_EQ("0", itoa_r_wrapper(0, 128, 10, 0));
+ EXPECT_EQ("-1", itoa_r_wrapper(-1, 128, 10, 0));
+
+ // Test edge cases.
+ if (sizeof(intptr_t) == 4) {
+ EXPECT_EQ("ffffffff", itoa_r_wrapper(-1, 128, 16, 0));
+ EXPECT_EQ("-2147483648",
+ itoa_r_wrapper(std::numeric_limits<intptr_t>::min(), 128, 10, 0));
+ EXPECT_EQ("2147483647",
+ itoa_r_wrapper(std::numeric_limits<intptr_t>::max(), 128, 10, 0));
+
+ EXPECT_EQ("80000000",
+ itoa_r_wrapper(std::numeric_limits<intptr_t>::min(), 128, 16, 0));
+ EXPECT_EQ("7fffffff",
+ itoa_r_wrapper(std::numeric_limits<intptr_t>::max(), 128, 16, 0));
+ } else if (sizeof(intptr_t) == 8) {
+ EXPECT_EQ("ffffffffffffffff", itoa_r_wrapper(-1, 128, 16, 0));
+ EXPECT_EQ("-9223372036854775808",
+ itoa_r_wrapper(std::numeric_limits<intptr_t>::min(), 128, 10, 0));
+ EXPECT_EQ("9223372036854775807",
+ itoa_r_wrapper(std::numeric_limits<intptr_t>::max(), 128, 10, 0));
+
+ EXPECT_EQ("8000000000000000",
+ itoa_r_wrapper(std::numeric_limits<intptr_t>::min(), 128, 16, 0));
+ EXPECT_EQ("7fffffffffffffff",
+ itoa_r_wrapper(std::numeric_limits<intptr_t>::max(), 128, 16, 0));
+ } else {
+ ADD_FAILURE() << "Missing test case for your size of intptr_t ("
+ << sizeof(intptr_t) << ")";
+ }
+
+ // Test hex output.
+ EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 0));
+ EXPECT_EQ("deadbeef", itoa_r_wrapper(0xdeadbeef, 128, 16, 0));
+
+ // Check that itoa_r respects passed buffer size limit.
+ char buffer[1024];
+ EXPECT_TRUE(internal::itoa_r(0xdeadbeef, buffer, 10, 16, 0));
+ EXPECT_TRUE(internal::itoa_r(0xdeadbeef, buffer, 9, 16, 0));
+ EXPECT_FALSE(internal::itoa_r(0xdeadbeef, buffer, 8, 16, 0));
+ EXPECT_FALSE(internal::itoa_r(0xdeadbeef, buffer, 7, 16, 0));
+ EXPECT_TRUE(internal::itoa_r(0xbeef, buffer, 5, 16, 4));
+ EXPECT_FALSE(internal::itoa_r(0xbeef, buffer, 5, 16, 5));
+ EXPECT_FALSE(internal::itoa_r(0xbeef, buffer, 5, 16, 6));
+
+ // Test padding.
+ EXPECT_EQ("1", itoa_r_wrapper(1, 128, 10, 0));
+ EXPECT_EQ("1", itoa_r_wrapper(1, 128, 10, 1));
+ EXPECT_EQ("01", itoa_r_wrapper(1, 128, 10, 2));
+ EXPECT_EQ("001", itoa_r_wrapper(1, 128, 10, 3));
+ EXPECT_EQ("0001", itoa_r_wrapper(1, 128, 10, 4));
+ EXPECT_EQ("00001", itoa_r_wrapper(1, 128, 10, 5));
+ EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 0));
+ EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 1));
+ EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 2));
+ EXPECT_EQ("688", itoa_r_wrapper(0x688, 128, 16, 3));
+ EXPECT_EQ("0688", itoa_r_wrapper(0x688, 128, 16, 4));
+ EXPECT_EQ("00688", itoa_r_wrapper(0x688, 128, 16, 5));
+}
+#endif // defined(OS_POSIX) && !defined(OS_ANDROID)
+
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
+
+template <size_t Depth>
+void NOINLINE ExpectStackFramePointers(const void** frames,
+ size_t max_depth) {
+ code_start:
+ // Calling __builtin_frame_address() forces compiler to emit
+ // frame pointers, even if they are not enabled.
+ EXPECT_NE(nullptr, __builtin_frame_address(0));
+ ExpectStackFramePointers<Depth - 1>(frames, max_depth);
+
+ constexpr size_t frame_index = Depth - 1;
+ const void* frame = frames[frame_index];
+ EXPECT_GE(frame, &&code_start) << "For frame at index " << frame_index;
+ EXPECT_LE(frame, &&code_end) << "For frame at index " << frame_index;
+ code_end: return;
+}
+
+template <>
+void NOINLINE ExpectStackFramePointers<1>(const void** frames,
+ size_t max_depth) {
+ code_start:
+ // Calling __builtin_frame_address() forces compiler to emit
+ // frame pointers, even if they are not enabled.
+ EXPECT_NE(nullptr, __builtin_frame_address(0));
+ size_t count = TraceStackFramePointers(frames, max_depth, 0);
+ ASSERT_EQ(max_depth, count);
+
+ const void* frame = frames[0];
+ EXPECT_GE(frame, &&code_start) << "For the top frame";
+ EXPECT_LE(frame, &&code_end) << "For the top frame";
+ code_end: return;
+}
+
+#if defined(MEMORY_SANITIZER)
+// The test triggers use-of-uninitialized-value errors on MSan bots.
+// This is expected because we're walking and reading the stack, and
+// sometimes we read fp / pc from the place that previously held
+// uninitialized value.
+#define MAYBE_TraceStackFramePointers DISABLED_TraceStackFramePointers
+#else
+#define MAYBE_TraceStackFramePointers TraceStackFramePointers
+#endif
+TEST_F(StackTraceTest, MAYBE_TraceStackFramePointers) {
+ constexpr size_t kDepth = 5;
+ const void* frames[kDepth];
+ ExpectStackFramePointers<kDepth>(frames, kDepth);
+}
+
+#if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StackEnd StackEnd
+#else
+#define MAYBE_StackEnd DISABLED_StackEnd
+#endif
+
+TEST_F(StackTraceTest, MAYBE_StackEnd) {
+ EXPECT_NE(0u, GetStackEnd());
+}
+
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/thread_heap_usage_tracker.cc b/base/debug/thread_heap_usage_tracker.cc
new file mode 100644
index 0000000000..6d00b1ccbb
--- /dev/null
+++ b/base/debug/thread_heap_usage_tracker.cc
@@ -0,0 +1,340 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/debug/thread_heap_usage_tracker.h"
+
+#include <stdint.h>
+#include <algorithm>
+#include <limits>
+#include <new>
+#include <type_traits>
+
+#include "base/allocator/allocator_shim.h"
+#include "base/allocator/buildflags.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/threading/thread_local_storage.h"
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX) || defined(OS_IOS)
+#include <malloc/malloc.h>
+#else
+#include <malloc.h>
+#endif
+
+namespace base {
+namespace debug {
+
+namespace {
+
+using base::allocator::AllocatorDispatch;
+
+const uintptr_t kSentinelMask = std::numeric_limits<uintptr_t>::max() - 1;
+ThreadHeapUsage* const kInitializationSentinel =
+ reinterpret_cast<ThreadHeapUsage*>(kSentinelMask);
+ThreadHeapUsage* const kTeardownSentinel =
+ reinterpret_cast<ThreadHeapUsage*>(kSentinelMask | 1);
+
+ThreadLocalStorage::Slot& ThreadAllocationUsage() {
+ static NoDestructor<ThreadLocalStorage::Slot> thread_allocator_usage(
+ [](void* thread_heap_usage) {
+ // This destructor will be called twice. Once to destroy the actual
+ // ThreadHeapUsage instance and a second time, immediately after, for
+ // the sentinel. Re-setting the TLS slow (below) does re-initialize the
+ // TLS slot. The ThreadLocalStorage code is designed to deal with this
+ // use case and will re-call the destructor with the kTeardownSentinel
+ // as arg.
+ if (thread_heap_usage == kTeardownSentinel)
+ return;
+ DCHECK_NE(thread_heap_usage, kInitializationSentinel);
+
+ // Deleting the ThreadHeapUsage TLS object will re-enter the shim and
+ // hit RecordFree() (see below). The sentinel prevents RecordFree() from
+ // re-creating another ThreadHeapUsage object.
+ ThreadAllocationUsage().Set(kTeardownSentinel);
+ delete static_cast<ThreadHeapUsage*>(thread_heap_usage);
+ });
+ return *thread_allocator_usage;
+}
+
+bool g_heap_tracking_enabled = false;
+
+// Forward declared as it needs to delegate memory allocation to the next
+// lower shim.
+ThreadHeapUsage* GetOrCreateThreadUsage();
+
+size_t GetAllocSizeEstimate(const AllocatorDispatch* next,
+ void* ptr,
+ void* context) {
+ if (ptr == nullptr)
+ return 0U;
+
+ return next->get_size_estimate_function(next, ptr, context);
+}
+
+void RecordAlloc(const AllocatorDispatch* next,
+ void* ptr,
+ size_t size,
+ void* context) {
+ ThreadHeapUsage* usage = GetOrCreateThreadUsage();
+ if (usage == nullptr)
+ return;
+
+ usage->alloc_ops++;
+ size_t estimate = GetAllocSizeEstimate(next, ptr, context);
+ if (size && estimate) {
+ // Only keep track of the net number of bytes allocated in the scope if the
+ // size estimate function returns sane values, e.g. non-zero.
+ usage->alloc_bytes += estimate;
+ usage->alloc_overhead_bytes += estimate - size;
+
+ // Record the max outstanding number of bytes, but only if the difference
+ // is net positive (e.g. more bytes allocated than freed in the scope).
+ if (usage->alloc_bytes > usage->free_bytes) {
+ uint64_t allocated_bytes = usage->alloc_bytes - usage->free_bytes;
+ if (allocated_bytes > usage->max_allocated_bytes)
+ usage->max_allocated_bytes = allocated_bytes;
+ }
+ } else {
+ usage->alloc_bytes += size;
+ }
+}
+
+void RecordFree(const AllocatorDispatch* next, void* ptr, void* context) {
+ ThreadHeapUsage* usage = GetOrCreateThreadUsage();
+ if (usage == nullptr)
+ return;
+
+ size_t estimate = GetAllocSizeEstimate(next, ptr, context);
+ usage->free_ops++;
+ usage->free_bytes += estimate;
+}
+
+void* AllocFn(const AllocatorDispatch* self, size_t size, void* context) {
+ void* ret = self->next->alloc_function(self->next, size, context);
+ if (ret != nullptr)
+ RecordAlloc(self->next, ret, size, context);
+
+ return ret;
+}
+
+void* AllocZeroInitializedFn(const AllocatorDispatch* self,
+ size_t n,
+ size_t size,
+ void* context) {
+ void* ret =
+ self->next->alloc_zero_initialized_function(self->next, n, size, context);
+ if (ret != nullptr)
+ RecordAlloc(self->next, ret, size, context);
+
+ return ret;
+}
+
+void* AllocAlignedFn(const AllocatorDispatch* self,
+ size_t alignment,
+ size_t size,
+ void* context) {
+ void* ret =
+ self->next->alloc_aligned_function(self->next, alignment, size, context);
+ if (ret != nullptr)
+ RecordAlloc(self->next, ret, size, context);
+
+ return ret;
+}
+
+void* ReallocFn(const AllocatorDispatch* self,
+ void* address,
+ size_t size,
+ void* context) {
+ if (address != nullptr)
+ RecordFree(self->next, address, context);
+
+ void* ret = self->next->realloc_function(self->next, address, size, context);
+ if (ret != nullptr && size != 0)
+ RecordAlloc(self->next, ret, size, context);
+
+ return ret;
+}
+
+void FreeFn(const AllocatorDispatch* self, void* address, void* context) {
+ if (address != nullptr)
+ RecordFree(self->next, address, context);
+ self->next->free_function(self->next, address, context);
+}
+
+size_t GetSizeEstimateFn(const AllocatorDispatch* self,
+ void* address,
+ void* context) {
+ return self->next->get_size_estimate_function(self->next, address, context);
+}
+
+unsigned BatchMallocFn(const AllocatorDispatch* self,
+ size_t size,
+ void** results,
+ unsigned num_requested,
+ void* context) {
+ unsigned count = self->next->batch_malloc_function(self->next, size, results,
+ num_requested, context);
+ for (unsigned i = 0; i < count; ++i) {
+ RecordAlloc(self->next, results[i], size, context);
+ }
+ return count;
+}
+
+void BatchFreeFn(const AllocatorDispatch* self,
+ void** to_be_freed,
+ unsigned num_to_be_freed,
+ void* context) {
+ for (unsigned i = 0; i < num_to_be_freed; ++i) {
+ if (to_be_freed[i] != nullptr) {
+ RecordFree(self->next, to_be_freed[i], context);
+ }
+ }
+ self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed,
+ context);
+}
+
+void FreeDefiniteSizeFn(const AllocatorDispatch* self,
+ void* ptr,
+ size_t size,
+ void* context) {
+ if (ptr != nullptr)
+ RecordFree(self->next, ptr, context);
+ self->next->free_definite_size_function(self->next, ptr, size, context);
+}
+
+// The allocator dispatch used to intercept heap operations.
+AllocatorDispatch allocator_dispatch = {&AllocFn,
+ &AllocZeroInitializedFn,
+ &AllocAlignedFn,
+ &ReallocFn,
+ &FreeFn,
+ &GetSizeEstimateFn,
+ &BatchMallocFn,
+ &BatchFreeFn,
+ &FreeDefiniteSizeFn,
+ nullptr};
+
+ThreadHeapUsage* GetOrCreateThreadUsage() {
+ auto tls_ptr = reinterpret_cast<uintptr_t>(ThreadAllocationUsage().Get());
+ if ((tls_ptr & kSentinelMask) == kSentinelMask)
+ return nullptr; // Re-entrancy case.
+
+ auto* allocator_usage = reinterpret_cast<ThreadHeapUsage*>(tls_ptr);
+ if (allocator_usage == nullptr) {
+ // Prevent reentrancy due to the allocation below.
+ ThreadAllocationUsage().Set(kInitializationSentinel);
+
+ allocator_usage = new ThreadHeapUsage();
+ static_assert(std::is_pod<ThreadHeapUsage>::value,
+ "AllocatorDispatch must be POD");
+ memset(allocator_usage, 0, sizeof(*allocator_usage));
+ ThreadAllocationUsage().Set(allocator_usage);
+ }
+
+ return allocator_usage;
+}
+
+} // namespace
+
+ThreadHeapUsageTracker::ThreadHeapUsageTracker() : thread_usage_(nullptr) {
+ static_assert(std::is_pod<ThreadHeapUsage>::value, "Must be POD.");
+}
+
+ThreadHeapUsageTracker::~ThreadHeapUsageTracker() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (thread_usage_ != nullptr) {
+ // If this tracker wasn't stopped, make it inclusive so that the
+ // usage isn't lost.
+ Stop(false);
+ }
+}
+
+void ThreadHeapUsageTracker::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ thread_usage_ = GetOrCreateThreadUsage();
+ usage_ = *thread_usage_;
+
+ // Reset the stats for our current scope.
+ // The per-thread usage instance now tracks this scope's usage, while this
+ // instance persists the outer scope's usage stats. On destruction, this
+ // instance will restore the outer scope's usage stats with this scope's
+ // usage added.
+ memset(thread_usage_, 0, sizeof(*thread_usage_));
+}
+
+void ThreadHeapUsageTracker::Stop(bool usage_is_exclusive) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(nullptr, thread_usage_);
+
+ ThreadHeapUsage current = *thread_usage_;
+ if (usage_is_exclusive) {
+ // Restore the outer scope.
+ *thread_usage_ = usage_;
+ } else {
+ // Update the outer scope with the accrued inner usage.
+ if (thread_usage_->max_allocated_bytes) {
+ uint64_t outer_net_alloc_bytes = usage_.alloc_bytes - usage_.free_bytes;
+
+ thread_usage_->max_allocated_bytes =
+ std::max(usage_.max_allocated_bytes,
+ outer_net_alloc_bytes + thread_usage_->max_allocated_bytes);
+ }
+
+ thread_usage_->alloc_ops += usage_.alloc_ops;
+ thread_usage_->alloc_bytes += usage_.alloc_bytes;
+ thread_usage_->alloc_overhead_bytes += usage_.alloc_overhead_bytes;
+ thread_usage_->free_ops += usage_.free_ops;
+ thread_usage_->free_bytes += usage_.free_bytes;
+ }
+
+ thread_usage_ = nullptr;
+ usage_ = current;
+}
+
+ThreadHeapUsage ThreadHeapUsageTracker::GetUsageSnapshot() {
+ ThreadHeapUsage* usage = GetOrCreateThreadUsage();
+ DCHECK_NE(nullptr, usage);
+ return *usage;
+}
+
+void ThreadHeapUsageTracker::EnableHeapTracking() {
+ EnsureTLSInitialized();
+
+ CHECK_EQ(false, g_heap_tracking_enabled) << "No double-enabling.";
+ g_heap_tracking_enabled = true;
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::InsertAllocatorDispatch(&allocator_dispatch);
+#else
+ CHECK(false) << "Can't enable heap tracking without the shim.";
+#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
+}
+
+bool ThreadHeapUsageTracker::IsHeapTrackingEnabled() {
+ return g_heap_tracking_enabled;
+}
+
+void ThreadHeapUsageTracker::DisableHeapTrackingForTesting() {
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::RemoveAllocatorDispatchForTesting(&allocator_dispatch);
+#else
+ CHECK(false) << "Can't disable heap tracking without the shim.";
+#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
+ DCHECK_EQ(true, g_heap_tracking_enabled) << "Heap tracking not enabled.";
+ g_heap_tracking_enabled = false;
+}
+
+base::allocator::AllocatorDispatch*
+ThreadHeapUsageTracker::GetDispatchForTesting() {
+ return &allocator_dispatch;
+}
+
+void ThreadHeapUsageTracker::EnsureTLSInitialized() {
+ ignore_result(ThreadAllocationUsage());
+}
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/thread_heap_usage_tracker_unittest.cc b/base/debug/thread_heap_usage_tracker_unittest.cc
new file mode 100644
index 0000000000..b99576cbad
--- /dev/null
+++ b/base/debug/thread_heap_usage_tracker_unittest.cc
@@ -0,0 +1,607 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/debug/thread_heap_usage_tracker.h"
+
+#include <map>
+
+#include "base/allocator/allocator_shim.h"
+#include "base/allocator/buildflags.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_MACOSX)
+#include "base/allocator/allocator_interception_mac.h"
+#endif
+
+namespace base {
+namespace debug {
+
+namespace {
+
+class TestingThreadHeapUsageTracker : public ThreadHeapUsageTracker {
+ public:
+ using ThreadHeapUsageTracker::DisableHeapTrackingForTesting;
+ using ThreadHeapUsageTracker::EnsureTLSInitialized;
+ using ThreadHeapUsageTracker::GetDispatchForTesting;
+};
+
+// A fixture class that allows testing the AllocatorDispatch associated with
+// the ThreadHeapUsageTracker class in isolation against a mocked
+// underlying
+// heap implementation.
+class ThreadHeapUsageTrackerTest : public testing::Test {
+ public:
+ using AllocatorDispatch = base::allocator::AllocatorDispatch;
+
+ static const size_t kAllocationPadding;
+ enum SizeFunctionKind {
+ EXACT_SIZE_FUNCTION,
+ PADDING_SIZE_FUNCTION,
+ ZERO_SIZE_FUNCTION,
+ };
+
+ ThreadHeapUsageTrackerTest() : size_function_kind_(EXACT_SIZE_FUNCTION) {
+ EXPECT_EQ(nullptr, g_self);
+ g_self = this;
+ }
+
+ ~ThreadHeapUsageTrackerTest() override {
+ EXPECT_EQ(this, g_self);
+ g_self = nullptr;
+ }
+
+ void set_size_function_kind(SizeFunctionKind kind) {
+ size_function_kind_ = kind;
+ }
+
+ void SetUp() override {
+ TestingThreadHeapUsageTracker::EnsureTLSInitialized();
+
+ dispatch_under_test_ =
+ TestingThreadHeapUsageTracker::GetDispatchForTesting();
+ ASSERT_EQ(nullptr, dispatch_under_test_->next);
+
+ dispatch_under_test_->next = &g_mock_dispatch;
+ }
+
+ void TearDown() override {
+ ASSERT_EQ(&g_mock_dispatch, dispatch_under_test_->next);
+
+ dispatch_under_test_->next = nullptr;
+ }
+
+ void* MockMalloc(size_t size) {
+ return dispatch_under_test_->alloc_function(dispatch_under_test_, size,
+ nullptr);
+ }
+
+ void* MockCalloc(size_t n, size_t size) {
+ return dispatch_under_test_->alloc_zero_initialized_function(
+ dispatch_under_test_, n, size, nullptr);
+ }
+
+ void* MockAllocAligned(size_t alignment, size_t size) {
+ return dispatch_under_test_->alloc_aligned_function(
+ dispatch_under_test_, alignment, size, nullptr);
+ }
+
+ void* MockRealloc(void* address, size_t size) {
+ return dispatch_under_test_->realloc_function(dispatch_under_test_, address,
+ size, nullptr);
+ }
+
+ void MockFree(void* address) {
+ dispatch_under_test_->free_function(dispatch_under_test_, address, nullptr);
+ }
+
+ size_t MockGetSizeEstimate(void* address) {
+ return dispatch_under_test_->get_size_estimate_function(
+ dispatch_under_test_, address, nullptr);
+ }
+
+ private:
+ void RecordAlloc(void* address, size_t size) {
+ if (address != nullptr)
+ allocation_size_map_[address] = size;
+ }
+
+ void DeleteAlloc(void* address) {
+ if (address != nullptr)
+ EXPECT_EQ(1U, allocation_size_map_.erase(address));
+ }
+
+ size_t GetSizeEstimate(void* address) {
+ auto it = allocation_size_map_.find(address);
+ if (it == allocation_size_map_.end())
+ return 0;
+
+ size_t ret = it->second;
+ switch (size_function_kind_) {
+ case EXACT_SIZE_FUNCTION:
+ break;
+ case PADDING_SIZE_FUNCTION:
+ ret += kAllocationPadding;
+ break;
+ case ZERO_SIZE_FUNCTION:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+ }
+
+ static void* OnAllocFn(const AllocatorDispatch* self,
+ size_t size,
+ void* context) {
+ EXPECT_EQ(&g_mock_dispatch, self);
+
+ void* ret = malloc(size);
+ g_self->RecordAlloc(ret, size);
+ return ret;
+ }
+
+ static void* OnAllocZeroInitializedFn(const AllocatorDispatch* self,
+ size_t n,
+ size_t size,
+ void* context) {
+ EXPECT_EQ(&g_mock_dispatch, self);
+
+ void* ret = calloc(n, size);
+ g_self->RecordAlloc(ret, n * size);
+ return ret;
+ }
+
+ static void* OnAllocAlignedFn(const AllocatorDispatch* self,
+ size_t alignment,
+ size_t size,
+ void* context) {
+ EXPECT_EQ(&g_mock_dispatch, self);
+
+ // This is a cheat as it doesn't return aligned allocations. This has the
+ // advantage of working for all platforms for this test.
+ void* ret = malloc(size);
+ g_self->RecordAlloc(ret, size);
+ return ret;
+ }
+
+ static void* OnReallocFn(const AllocatorDispatch* self,
+ void* address,
+ size_t size,
+ void* context) {
+ EXPECT_EQ(&g_mock_dispatch, self);
+
+ g_self->DeleteAlloc(address);
+ void* ret = realloc(address, size);
+ g_self->RecordAlloc(ret, size);
+ return ret;
+ }
+
+ static void OnFreeFn(const AllocatorDispatch* self,
+ void* address,
+ void* context) {
+ EXPECT_EQ(&g_mock_dispatch, self);
+
+ g_self->DeleteAlloc(address);
+ free(address);
+ }
+
+ static size_t OnGetSizeEstimateFn(const AllocatorDispatch* self,
+ void* address,
+ void* context) {
+ EXPECT_EQ(&g_mock_dispatch, self);
+
+ return g_self->GetSizeEstimate(address);
+ }
+
+ using AllocationSizeMap = std::map<void*, size_t>;
+
+ SizeFunctionKind size_function_kind_;
+ AllocationSizeMap allocation_size_map_;
+ AllocatorDispatch* dispatch_under_test_;
+
+ static base::allocator::AllocatorDispatch g_mock_dispatch;
+ static ThreadHeapUsageTrackerTest* g_self;
+};
+
+const size_t ThreadHeapUsageTrackerTest::kAllocationPadding = 23;
+
+ThreadHeapUsageTrackerTest* ThreadHeapUsageTrackerTest::g_self = nullptr;
+
+base::allocator::AllocatorDispatch ThreadHeapUsageTrackerTest::g_mock_dispatch =
+ {
+ &ThreadHeapUsageTrackerTest::OnAllocFn, // alloc_function
+ &ThreadHeapUsageTrackerTest::
+ OnAllocZeroInitializedFn, // alloc_zero_initialized_function
+ &ThreadHeapUsageTrackerTest::
+ OnAllocAlignedFn, // alloc_aligned_function
+ &ThreadHeapUsageTrackerTest::OnReallocFn, // realloc_function
+ &ThreadHeapUsageTrackerTest::OnFreeFn, // free_function
+ &ThreadHeapUsageTrackerTest::
+ OnGetSizeEstimateFn, // get_size_estimate_function
+ nullptr, // batch_malloc
+ nullptr, // batch_free
+ nullptr, // free_definite_size_function
+ nullptr, // next
+};
+
+} // namespace
+
+TEST_F(ThreadHeapUsageTrackerTest, SimpleUsageWithExactSizeFunction) {
+ set_size_function_kind(EXACT_SIZE_FUNCTION);
+
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ ThreadHeapUsage u1 = ThreadHeapUsageTracker::GetUsageSnapshot();
+
+ EXPECT_EQ(0U, u1.alloc_ops);
+ EXPECT_EQ(0U, u1.alloc_bytes);
+ EXPECT_EQ(0U, u1.alloc_overhead_bytes);
+ EXPECT_EQ(0U, u1.free_ops);
+ EXPECT_EQ(0U, u1.free_bytes);
+ EXPECT_EQ(0U, u1.max_allocated_bytes);
+
+ const size_t kAllocSize = 1029U;
+ void* ptr = MockMalloc(kAllocSize);
+ MockFree(ptr);
+
+ usage_tracker.Stop(false);
+ ThreadHeapUsage u2 = usage_tracker.usage();
+
+ EXPECT_EQ(1U, u2.alloc_ops);
+ EXPECT_EQ(kAllocSize, u2.alloc_bytes);
+ EXPECT_EQ(0U, u2.alloc_overhead_bytes);
+ EXPECT_EQ(1U, u2.free_ops);
+ EXPECT_EQ(kAllocSize, u2.free_bytes);
+ EXPECT_EQ(kAllocSize, u2.max_allocated_bytes);
+}
+
+TEST_F(ThreadHeapUsageTrackerTest, SimpleUsageWithPaddingSizeFunction) {
+ set_size_function_kind(PADDING_SIZE_FUNCTION);
+
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ ThreadHeapUsage u1 = ThreadHeapUsageTracker::GetUsageSnapshot();
+
+ EXPECT_EQ(0U, u1.alloc_ops);
+ EXPECT_EQ(0U, u1.alloc_bytes);
+ EXPECT_EQ(0U, u1.alloc_overhead_bytes);
+ EXPECT_EQ(0U, u1.free_ops);
+ EXPECT_EQ(0U, u1.free_bytes);
+ EXPECT_EQ(0U, u1.max_allocated_bytes);
+
+ const size_t kAllocSize = 1029U;
+ void* ptr = MockMalloc(kAllocSize);
+ MockFree(ptr);
+
+ usage_tracker.Stop(false);
+ ThreadHeapUsage u2 = usage_tracker.usage();
+
+ EXPECT_EQ(1U, u2.alloc_ops);
+ EXPECT_EQ(kAllocSize + kAllocationPadding, u2.alloc_bytes);
+ EXPECT_EQ(kAllocationPadding, u2.alloc_overhead_bytes);
+ EXPECT_EQ(1U, u2.free_ops);
+ EXPECT_EQ(kAllocSize + kAllocationPadding, u2.free_bytes);
+ EXPECT_EQ(kAllocSize + kAllocationPadding, u2.max_allocated_bytes);
+}
+
+TEST_F(ThreadHeapUsageTrackerTest, SimpleUsageWithZeroSizeFunction) {
+ set_size_function_kind(ZERO_SIZE_FUNCTION);
+
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ ThreadHeapUsage u1 = ThreadHeapUsageTracker::GetUsageSnapshot();
+ EXPECT_EQ(0U, u1.alloc_ops);
+ EXPECT_EQ(0U, u1.alloc_bytes);
+ EXPECT_EQ(0U, u1.alloc_overhead_bytes);
+ EXPECT_EQ(0U, u1.free_ops);
+ EXPECT_EQ(0U, u1.free_bytes);
+ EXPECT_EQ(0U, u1.max_allocated_bytes);
+
+ const size_t kAllocSize = 1029U;
+ void* ptr = MockMalloc(kAllocSize);
+ MockFree(ptr);
+
+ usage_tracker.Stop(false);
+ ThreadHeapUsage u2 = usage_tracker.usage();
+
+ // With a get-size function that returns zero, there's no way to get the size
+ // of an allocation that's being freed, hence the shim can't tally freed bytes
+ // nor the high-watermark allocated bytes.
+ EXPECT_EQ(1U, u2.alloc_ops);
+ EXPECT_EQ(kAllocSize, u2.alloc_bytes);
+ EXPECT_EQ(0U, u2.alloc_overhead_bytes);
+ EXPECT_EQ(1U, u2.free_ops);
+ EXPECT_EQ(0U, u2.free_bytes);
+ EXPECT_EQ(0U, u2.max_allocated_bytes);
+}
+
+TEST_F(ThreadHeapUsageTrackerTest, ReallocCorrectlyTallied) {
+ const size_t kAllocSize = 237U;
+
+ {
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ // Reallocating nullptr should count as a single alloc.
+ void* ptr = MockRealloc(nullptr, kAllocSize);
+ ThreadHeapUsage usage = ThreadHeapUsageTracker::GetUsageSnapshot();
+ EXPECT_EQ(1U, usage.alloc_ops);
+ EXPECT_EQ(kAllocSize, usage.alloc_bytes);
+ EXPECT_EQ(0U, usage.alloc_overhead_bytes);
+ EXPECT_EQ(0U, usage.free_ops);
+ EXPECT_EQ(0U, usage.free_bytes);
+ EXPECT_EQ(kAllocSize, usage.max_allocated_bytes);
+
+ // Reallocating a valid pointer to a zero size should count as a single
+ // free.
+ ptr = MockRealloc(ptr, 0U);
+
+ usage_tracker.Stop(false);
+ EXPECT_EQ(1U, usage_tracker.usage().alloc_ops);
+ EXPECT_EQ(kAllocSize, usage_tracker.usage().alloc_bytes);
+ EXPECT_EQ(0U, usage_tracker.usage().alloc_overhead_bytes);
+ EXPECT_EQ(1U, usage_tracker.usage().free_ops);
+ EXPECT_EQ(kAllocSize, usage_tracker.usage().free_bytes);
+ EXPECT_EQ(kAllocSize, usage_tracker.usage().max_allocated_bytes);
+
+ // Realloc to zero size may or may not return a nullptr - make sure to
+ // free the zero-size alloc in the latter case.
+ if (ptr != nullptr)
+ MockFree(ptr);
+ }
+
+ {
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ void* ptr = MockMalloc(kAllocSize);
+ ThreadHeapUsage usage = ThreadHeapUsageTracker::GetUsageSnapshot();
+ EXPECT_EQ(1U, usage.alloc_ops);
+
+ // Now try reallocating a valid pointer to a larger size, this should count
+ // as one free and one alloc.
+ const size_t kLargerAllocSize = kAllocSize + 928U;
+ ptr = MockRealloc(ptr, kLargerAllocSize);
+
+ usage_tracker.Stop(false);
+ EXPECT_EQ(2U, usage_tracker.usage().alloc_ops);
+ EXPECT_EQ(kAllocSize + kLargerAllocSize, usage_tracker.usage().alloc_bytes);
+ EXPECT_EQ(0U, usage_tracker.usage().alloc_overhead_bytes);
+ EXPECT_EQ(1U, usage_tracker.usage().free_ops);
+ EXPECT_EQ(kAllocSize, usage_tracker.usage().free_bytes);
+ EXPECT_EQ(kLargerAllocSize, usage_tracker.usage().max_allocated_bytes);
+
+ MockFree(ptr);
+ }
+}
+
+TEST_F(ThreadHeapUsageTrackerTest, NestedMaxWorks) {
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ const size_t kOuterAllocSize = 1029U;
+ void* ptr = MockMalloc(kOuterAllocSize);
+ MockFree(ptr);
+
+ EXPECT_EQ(kOuterAllocSize,
+ ThreadHeapUsageTracker::GetUsageSnapshot().max_allocated_bytes);
+
+ {
+ ThreadHeapUsageTracker inner_usage_tracker;
+ inner_usage_tracker.Start();
+
+ const size_t kInnerAllocSize = 673U;
+ ptr = MockMalloc(kInnerAllocSize);
+ MockFree(ptr);
+
+ inner_usage_tracker.Stop(false);
+
+ EXPECT_EQ(kInnerAllocSize, inner_usage_tracker.usage().max_allocated_bytes);
+ }
+
+ // The greater, outer allocation size should have been restored.
+ EXPECT_EQ(kOuterAllocSize,
+ ThreadHeapUsageTracker::GetUsageSnapshot().max_allocated_bytes);
+
+ const size_t kLargerInnerAllocSize = kOuterAllocSize + 673U;
+ {
+ ThreadHeapUsageTracker inner_usage_tracker;
+ inner_usage_tracker.Start();
+
+ ptr = MockMalloc(kLargerInnerAllocSize);
+ MockFree(ptr);
+
+ inner_usage_tracker.Stop(false);
+ EXPECT_EQ(kLargerInnerAllocSize,
+ inner_usage_tracker.usage().max_allocated_bytes);
+ }
+
+ // The greater, inner allocation size should have been preserved.
+ EXPECT_EQ(kLargerInnerAllocSize,
+ ThreadHeapUsageTracker::GetUsageSnapshot().max_allocated_bytes);
+
+ // Now try the case with an outstanding net alloc size when entering the
+ // inner scope.
+ void* outer_ptr = MockMalloc(kOuterAllocSize);
+ EXPECT_EQ(kLargerInnerAllocSize,
+ ThreadHeapUsageTracker::GetUsageSnapshot().max_allocated_bytes);
+ {
+ ThreadHeapUsageTracker inner_usage_tracker;
+ inner_usage_tracker.Start();
+
+ ptr = MockMalloc(kLargerInnerAllocSize);
+ MockFree(ptr);
+
+ inner_usage_tracker.Stop(false);
+ EXPECT_EQ(kLargerInnerAllocSize,
+ inner_usage_tracker.usage().max_allocated_bytes);
+ }
+
+ // While the inner scope saw only the inner net outstanding allocation size,
+ // the outer scope saw both outstanding at the same time.
+ EXPECT_EQ(kOuterAllocSize + kLargerInnerAllocSize,
+ ThreadHeapUsageTracker::GetUsageSnapshot().max_allocated_bytes);
+
+ MockFree(outer_ptr);
+
+ // Test a net-negative scope.
+ ptr = MockMalloc(kLargerInnerAllocSize);
+ {
+ ThreadHeapUsageTracker inner_usage_tracker;
+ inner_usage_tracker.Start();
+
+ MockFree(ptr);
+
+ const size_t kInnerAllocSize = 1;
+ ptr = MockMalloc(kInnerAllocSize);
+
+ inner_usage_tracker.Stop(false);
+ // Since the scope is still net-negative, the max is clamped at zero.
+ EXPECT_EQ(0U, inner_usage_tracker.usage().max_allocated_bytes);
+ }
+
+ MockFree(ptr);
+}
+
+TEST_F(ThreadHeapUsageTrackerTest, NoStopImpliesInclusive) {
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ const size_t kOuterAllocSize = 1029U;
+ void* ptr = MockMalloc(kOuterAllocSize);
+ MockFree(ptr);
+
+ ThreadHeapUsage usage = ThreadHeapUsageTracker::GetUsageSnapshot();
+ EXPECT_EQ(kOuterAllocSize, usage.max_allocated_bytes);
+
+ const size_t kInnerLargerAllocSize = kOuterAllocSize + 673U;
+
+ {
+ ThreadHeapUsageTracker inner_usage_tracker;
+ inner_usage_tracker.Start();
+
+ // Make a larger allocation than the outer scope.
+ ptr = MockMalloc(kInnerLargerAllocSize);
+ MockFree(ptr);
+
+ // inner_usage_tracker goes out of scope without a Stop().
+ }
+
+ ThreadHeapUsage current = ThreadHeapUsageTracker::GetUsageSnapshot();
+ EXPECT_EQ(usage.alloc_ops + 1, current.alloc_ops);
+ EXPECT_EQ(usage.alloc_bytes + kInnerLargerAllocSize, current.alloc_bytes);
+ EXPECT_EQ(usage.free_ops + 1, current.free_ops);
+ EXPECT_EQ(usage.free_bytes + kInnerLargerAllocSize, current.free_bytes);
+ EXPECT_EQ(kInnerLargerAllocSize, current.max_allocated_bytes);
+}
+
+TEST_F(ThreadHeapUsageTrackerTest, ExclusiveScopesWork) {
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ const size_t kOuterAllocSize = 1029U;
+ void* ptr = MockMalloc(kOuterAllocSize);
+ MockFree(ptr);
+
+ ThreadHeapUsage usage = ThreadHeapUsageTracker::GetUsageSnapshot();
+ EXPECT_EQ(kOuterAllocSize, usage.max_allocated_bytes);
+
+ {
+ ThreadHeapUsageTracker inner_usage_tracker;
+ inner_usage_tracker.Start();
+
+ // Make a larger allocation than the outer scope.
+ ptr = MockMalloc(kOuterAllocSize + 673U);
+ MockFree(ptr);
+
+ // This tracker is exlusive, all activity should be private to this scope.
+ inner_usage_tracker.Stop(true);
+ }
+
+ ThreadHeapUsage current = ThreadHeapUsageTracker::GetUsageSnapshot();
+ EXPECT_EQ(usage.alloc_ops, current.alloc_ops);
+ EXPECT_EQ(usage.alloc_bytes, current.alloc_bytes);
+ EXPECT_EQ(usage.alloc_overhead_bytes, current.alloc_overhead_bytes);
+ EXPECT_EQ(usage.free_ops, current.free_ops);
+ EXPECT_EQ(usage.free_bytes, current.free_bytes);
+ EXPECT_EQ(usage.max_allocated_bytes, current.max_allocated_bytes);
+}
+
+TEST_F(ThreadHeapUsageTrackerTest, AllShimFunctionsAreProvided) {
+ const size_t kAllocSize = 100;
+ void* alloc = MockMalloc(kAllocSize);
+ size_t estimate = MockGetSizeEstimate(alloc);
+ ASSERT_TRUE(estimate == 0 || estimate >= kAllocSize);
+ MockFree(alloc);
+
+ alloc = MockCalloc(kAllocSize, 1);
+ estimate = MockGetSizeEstimate(alloc);
+ ASSERT_TRUE(estimate == 0 || estimate >= kAllocSize);
+ MockFree(alloc);
+
+ alloc = MockAllocAligned(1, kAllocSize);
+ estimate = MockGetSizeEstimate(alloc);
+ ASSERT_TRUE(estimate == 0 || estimate >= kAllocSize);
+
+ alloc = MockRealloc(alloc, kAllocSize);
+ estimate = MockGetSizeEstimate(alloc);
+ ASSERT_TRUE(estimate == 0 || estimate >= kAllocSize);
+ MockFree(alloc);
+}
+
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+class ThreadHeapUsageShimTest : public testing::Test {
+#if defined(OS_MACOSX)
+ void SetUp() override { allocator::InitializeAllocatorShim(); }
+ void TearDown() override { allocator::UninterceptMallocZonesForTesting(); }
+#endif
+};
+
+TEST_F(ThreadHeapUsageShimTest, HooksIntoMallocWhenShimAvailable) {
+ ASSERT_FALSE(ThreadHeapUsageTracker::IsHeapTrackingEnabled());
+
+ ThreadHeapUsageTracker::EnableHeapTracking();
+
+ ASSERT_TRUE(ThreadHeapUsageTracker::IsHeapTrackingEnabled());
+
+ const size_t kAllocSize = 9993;
+ // This test verifies that the scoped heap data is affected by malloc &
+ // free only when the shim is available.
+ ThreadHeapUsageTracker usage_tracker;
+ usage_tracker.Start();
+
+ ThreadHeapUsage u1 = ThreadHeapUsageTracker::GetUsageSnapshot();
+ void* ptr = malloc(kAllocSize);
+ // Prevent the compiler from optimizing out the malloc/free pair.
+ ASSERT_NE(nullptr, ptr);
+
+ ThreadHeapUsage u2 = ThreadHeapUsageTracker::GetUsageSnapshot();
+ free(ptr);
+
+ usage_tracker.Stop(false);
+ ThreadHeapUsage u3 = usage_tracker.usage();
+
+ // Verify that at least one allocation operation was recorded, and that free
+ // operations are at least monotonically growing.
+ EXPECT_LE(0U, u1.alloc_ops);
+ EXPECT_LE(u1.alloc_ops + 1, u2.alloc_ops);
+ EXPECT_LE(u1.alloc_ops + 1, u3.alloc_ops);
+
+ // Verify that at least the bytes above were recorded.
+ EXPECT_LE(u1.alloc_bytes + kAllocSize, u2.alloc_bytes);
+
+ // Verify that at least the one free operation above was recorded.
+ EXPECT_LE(u2.free_ops + 1, u3.free_ops);
+
+ TestingThreadHeapUsageTracker::DisableHeapTrackingForTesting();
+
+ ASSERT_FALSE(ThreadHeapUsageTracker::IsHeapTrackingEnabled());
+}
+#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
+
+} // namespace debug
+} // namespace base
diff --git a/base/deferred_sequenced_task_runner.cc b/base/deferred_sequenced_task_runner.cc
new file mode 100644
index 0000000000..f88170c3db
--- /dev/null
+++ b/base/deferred_sequenced_task_runner.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/deferred_sequenced_task_runner.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+
+namespace base {
+
+DeferredSequencedTaskRunner::DeferredTask::DeferredTask()
+ : is_non_nestable(false) {
+}
+
+DeferredSequencedTaskRunner::DeferredTask::DeferredTask(DeferredTask&& other) =
+ default;
+
+DeferredSequencedTaskRunner::DeferredTask::~DeferredTask() = default;
+
+DeferredSequencedTaskRunner::DeferredTask&
+DeferredSequencedTaskRunner::DeferredTask::operator=(DeferredTask&& other) =
+ default;
+
+DeferredSequencedTaskRunner::DeferredSequencedTaskRunner(
+ scoped_refptr<SequencedTaskRunner> target_task_runner)
+ : DeferredSequencedTaskRunner() {
+ DCHECK(target_task_runner);
+ target_task_runner_ = std::move(target_task_runner);
+}
+
+DeferredSequencedTaskRunner::DeferredSequencedTaskRunner()
+ : created_thread_id_(PlatformThread::CurrentId()) {}
+
+bool DeferredSequencedTaskRunner::PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) {
+ AutoLock lock(lock_);
+ if (started_) {
+ DCHECK(deferred_tasks_queue_.empty());
+ return target_task_runner_->PostDelayedTask(from_here, std::move(task),
+ delay);
+ }
+
+ QueueDeferredTask(from_here, std::move(task), delay,
+ false /* is_non_nestable */);
+ return true;
+}
+
+bool DeferredSequencedTaskRunner::RunsTasksInCurrentSequence() const {
+ AutoLock lock(lock_);
+ if (target_task_runner_)
+ return target_task_runner_->RunsTasksInCurrentSequence();
+
+ return created_thread_id_ == PlatformThread::CurrentId();
+}
+
+bool DeferredSequencedTaskRunner::PostNonNestableDelayedTask(
+ const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) {
+ AutoLock lock(lock_);
+ if (started_) {
+ DCHECK(deferred_tasks_queue_.empty());
+ return target_task_runner_->PostNonNestableDelayedTask(
+ from_here, std::move(task), delay);
+ }
+ QueueDeferredTask(from_here, std::move(task), delay,
+ true /* is_non_nestable */);
+ return true;
+}
+
+void DeferredSequencedTaskRunner::Start() {
+ AutoLock lock(lock_);
+ StartImpl();
+}
+
+void DeferredSequencedTaskRunner::StartWithTaskRunner(
+ scoped_refptr<SequencedTaskRunner> target_task_runner) {
+ AutoLock lock(lock_);
+ DCHECK(!target_task_runner_);
+ DCHECK(target_task_runner);
+ target_task_runner_ = std::move(target_task_runner);
+ StartImpl();
+}
+
+DeferredSequencedTaskRunner::~DeferredSequencedTaskRunner() = default;
+
+void DeferredSequencedTaskRunner::QueueDeferredTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay,
+ bool is_non_nestable) {
+ lock_.AssertAcquired();
+
+ // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
+ // for details.
+ CHECK(task);
+
+ DeferredTask deferred_task;
+ deferred_task.posted_from = from_here;
+ deferred_task.task = std::move(task);
+ deferred_task.delay = delay;
+ deferred_task.is_non_nestable = is_non_nestable;
+ deferred_tasks_queue_.push_back(std::move(deferred_task));
+}
+
+void DeferredSequencedTaskRunner::StartImpl() {
+ lock_.AssertAcquired(); // Callers should have grabbed the lock.
+ DCHECK(!started_);
+ started_ = true;
+ DCHECK(target_task_runner_);
+ for (std::vector<DeferredTask>::iterator i = deferred_tasks_queue_.begin();
+ i != deferred_tasks_queue_.end();
+ ++i) {
+ DeferredTask& task = *i;
+ if (task.is_non_nestable) {
+ target_task_runner_->PostNonNestableDelayedTask(
+ task.posted_from, std::move(task.task), task.delay);
+ } else {
+ target_task_runner_->PostDelayedTask(task.posted_from,
+ std::move(task.task), task.delay);
+ }
+ }
+ deferred_tasks_queue_.clear();
+}
+
+} // namespace base
diff --git a/base/deferred_sequenced_task_runner.h b/base/deferred_sequenced_task_runner.h
new file mode 100644
index 0000000000..2805f479e6
--- /dev/null
+++ b/base/deferred_sequenced_task_runner.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_DEFERRED_SEQUENCED_TASK_RUNNER_H_
+#define BASE_DEFERRED_SEQUENCED_TASK_RUNNER_H_
+
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+
+namespace base {
+
+// A DeferredSequencedTaskRunner is a subclass of SequencedTaskRunner that
+// queues up all requests until the first call to Start() is issued.
+// DeferredSequencedTaskRunner may be created in two ways:
+// . with an explicit SequencedTaskRunner that the events are flushed to
+// . without a SequencedTaskRunner. In this configuration the
+// SequencedTaskRunner is supplied in StartWithTaskRunner().
+class BASE_EXPORT DeferredSequencedTaskRunner : public SequencedTaskRunner {
+ public:
+ explicit DeferredSequencedTaskRunner(
+ scoped_refptr<SequencedTaskRunner> target_runner);
+
+ // Use this constructor when you don't have the target SequencedTaskRunner.
+ // When using this call StartWithTaskRunner().
+ DeferredSequencedTaskRunner();
+
+ // TaskRunner implementation
+ bool PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override;
+ bool RunsTasksInCurrentSequence() const override;
+
+ // SequencedTaskRunner implementation
+ bool PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override;
+
+ // Start the execution - posts all queued tasks to the target executor. The
+ // deferred tasks are posted with their initial delay, meaning that the task
+ // execution delay is actually measured from Start.
+ // Fails when called a second time.
+ void Start();
+
+ // Same as Start(), but must be used with the no-arg constructor.
+ void StartWithTaskRunner(
+ scoped_refptr<SequencedTaskRunner> target_task_runner);
+
+ private:
+ struct DeferredTask {
+ DeferredTask();
+ DeferredTask(DeferredTask&& other);
+ ~DeferredTask();
+ DeferredTask& operator=(DeferredTask&& other);
+
+ Location posted_from;
+ OnceClosure task;
+ // The delay this task was initially posted with.
+ TimeDelta delay;
+ bool is_non_nestable;
+ };
+
+ ~DeferredSequencedTaskRunner() override;
+
+ // Both variants of Start() call into this.
+ void StartImpl();
+
+ // Creates a |Task| object and adds it to |deferred_tasks_queue_|.
+ void QueueDeferredTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay,
+ bool is_non_nestable);
+
+ // // Protects |started_| and |deferred_tasks_queue_|.
+ mutable Lock lock_;
+
+ const PlatformThreadId created_thread_id_;
+
+ bool started_ = false;
+ scoped_refptr<SequencedTaskRunner> target_task_runner_;
+ std::vector<DeferredTask> deferred_tasks_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeferredSequencedTaskRunner);
+};
+
+} // namespace base
+
+#endif // BASE_DEFERRED_SEQUENCED_TASK_RUNNER_H_
diff --git a/base/deferred_sequenced_task_runner_unittest.cc b/base/deferred_sequenced_task_runner_unittest.cc
new file mode 100644
index 0000000000..5cb220fdf9
--- /dev/null
+++ b/base/deferred_sequenced_task_runner_unittest.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/deferred_sequenced_task_runner.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_forward.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class DeferredSequencedTaskRunnerTest : public testing::Test {
+ public:
+ class ExecuteTaskOnDestructor : public RefCounted<ExecuteTaskOnDestructor> {
+ public:
+ ExecuteTaskOnDestructor(
+ DeferredSequencedTaskRunnerTest* executor,
+ int task_id)
+ : executor_(executor),
+ task_id_(task_id) {
+ }
+ private:
+ friend class RefCounted<ExecuteTaskOnDestructor>;
+ virtual ~ExecuteTaskOnDestructor() { executor_->ExecuteTask(task_id_); }
+ DeferredSequencedTaskRunnerTest* executor_;
+ int task_id_;
+ };
+
+ void ExecuteTask(int task_id) {
+ AutoLock lock(lock_);
+ executed_task_ids_.push_back(task_id);
+ }
+
+ void PostExecuteTask(int task_id) {
+ runner_->PostTask(FROM_HERE,
+ BindOnce(&DeferredSequencedTaskRunnerTest::ExecuteTask,
+ Unretained(this), task_id));
+ }
+
+ void StartRunner() {
+ runner_->Start();
+ }
+
+ void DoNothing(ExecuteTaskOnDestructor* object) {
+ }
+
+ protected:
+ DeferredSequencedTaskRunnerTest()
+ : loop_(),
+ runner_(new DeferredSequencedTaskRunner(loop_.task_runner())) {}
+
+ MessageLoop loop_;
+ scoped_refptr<DeferredSequencedTaskRunner> runner_;
+ mutable Lock lock_;
+ std::vector<int> executed_task_ids_;
+};
+
+TEST_F(DeferredSequencedTaskRunnerTest, Stopped) {
+ PostExecuteTask(1);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_, testing::ElementsAre());
+}
+
+TEST_F(DeferredSequencedTaskRunnerTest, Start) {
+ StartRunner();
+ PostExecuteTask(1);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1));
+}
+
+TEST_F(DeferredSequencedTaskRunnerTest, StartWithMultipleElements) {
+ StartRunner();
+ for (int i = 1; i < 5; ++i)
+ PostExecuteTask(i);
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST_F(DeferredSequencedTaskRunnerTest, DeferredStart) {
+ PostExecuteTask(1);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_, testing::ElementsAre());
+
+ StartRunner();
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1));
+
+ PostExecuteTask(2);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1, 2));
+}
+
+TEST_F(DeferredSequencedTaskRunnerTest, DeferredStartWithMultipleElements) {
+ for (int i = 1; i < 5; ++i)
+ PostExecuteTask(i);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_, testing::ElementsAre());
+
+ StartRunner();
+ for (int i = 5; i < 9; ++i)
+ PostExecuteTask(i);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_, testing::ElementsAre(1, 2, 3, 4, 5, 6, 7, 8));
+}
+
+TEST_F(DeferredSequencedTaskRunnerTest, DeferredStartWithMultipleThreads) {
+ {
+ Thread thread1("DeferredSequencedTaskRunnerTestThread1");
+ Thread thread2("DeferredSequencedTaskRunnerTestThread2");
+ thread1.Start();
+ thread2.Start();
+ for (int i = 0; i < 5; ++i) {
+ thread1.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&DeferredSequencedTaskRunnerTest::PostExecuteTask,
+ Unretained(this), 2 * i));
+ thread2.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&DeferredSequencedTaskRunnerTest::PostExecuteTask,
+ Unretained(this), 2 * i + 1));
+ if (i == 2) {
+ thread1.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&DeferredSequencedTaskRunnerTest::StartRunner,
+ Unretained(this)));
+ }
+ }
+ }
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(executed_task_ids_,
+ testing::WhenSorted(testing::ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)));
+}
+
+TEST_F(DeferredSequencedTaskRunnerTest, ObjectDestructionOrder) {
+ {
+ Thread thread("DeferredSequencedTaskRunnerTestThread");
+ thread.Start();
+ runner_ = new DeferredSequencedTaskRunner(thread.task_runner());
+ for (int i = 0; i < 5; ++i) {
+ {
+ // Use a block to ensure that no reference to |short_lived_object|
+ // is kept on the main thread after it is posted to |runner_|.
+ scoped_refptr<ExecuteTaskOnDestructor> short_lived_object =
+ new ExecuteTaskOnDestructor(this, 2 * i);
+ runner_->PostTask(
+ FROM_HERE,
+ BindOnce(&DeferredSequencedTaskRunnerTest::DoNothing,
+ Unretained(this), RetainedRef(short_lived_object)));
+ }
+ // |short_lived_object| with id |2 * i| should be destroyed before the
+ // task |2 * i + 1| is executed.
+ PostExecuteTask(2 * i + 1);
+ }
+ StartRunner();
+ }
+
+ // All |short_lived_object| with id |2 * i| are destroyed before the task
+ // |2 * i + 1| is executed.
+ EXPECT_THAT(executed_task_ids_,
+ testing::ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+}
+
+void GetRunsTasksInCurrentSequence(bool* result,
+ scoped_refptr<SequencedTaskRunner> runner,
+ OnceClosure quit) {
+ *result = runner->RunsTasksInCurrentSequence();
+ std::move(quit).Run();
+}
+
+TEST_F(DeferredSequencedTaskRunnerTest, RunsTasksInCurrentSequence) {
+ scoped_refptr<DeferredSequencedTaskRunner> runner =
+ MakeRefCounted<DeferredSequencedTaskRunner>();
+ EXPECT_TRUE(runner->RunsTasksInCurrentSequence());
+
+ Thread thread1("DeferredSequencedTaskRunnerTestThread1");
+ thread1.Start();
+ bool runs_task_in_current_thread = true;
+ base::RunLoop run_loop;
+ thread1.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(&GetRunsTasksInCurrentSequence, &runs_task_in_current_thread,
+ runner, run_loop.QuitClosure()));
+ run_loop.Run();
+ EXPECT_FALSE(runs_task_in_current_thread);
+}
+
+TEST_F(DeferredSequencedTaskRunnerTest, StartWithTaskRunner) {
+ scoped_refptr<DeferredSequencedTaskRunner> runner =
+ MakeRefCounted<DeferredSequencedTaskRunner>();
+ bool run_called = false;
+ base::RunLoop run_loop;
+ runner->PostTask(FROM_HERE,
+ BindOnce(
+ [](bool* run_called, base::Closure quit_closure) {
+ *run_called = true;
+ std::move(quit_closure).Run();
+ },
+ &run_called, run_loop.QuitClosure()));
+ runner->StartWithTaskRunner(loop_.task_runner());
+ run_loop.Run();
+ EXPECT_TRUE(run_called);
+}
+
+} // namespace
+} // namespace base
diff --git a/base/file_descriptor_store.cc b/base/file_descriptor_store.cc
new file mode 100644
index 0000000000..71cf2b3f51
--- /dev/null
+++ b/base/file_descriptor_store.cc
@@ -0,0 +1,73 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/file_descriptor_store.h"
+
+#include <utility>
+
+#include "base/logging.h"
+
+namespace base {
+
+FileDescriptorStore::Descriptor::Descriptor(const std::string& key,
+ base::ScopedFD fd)
+ : key(key),
+ fd(std::move(fd)),
+ region(base::MemoryMappedFile::Region::kWholeFile) {}
+
+FileDescriptorStore::Descriptor::Descriptor(
+ const std::string& key,
+ base::ScopedFD fd,
+ base::MemoryMappedFile::Region region)
+ : key(key), fd(std::move(fd)), region(region) {}
+
+FileDescriptorStore::Descriptor::Descriptor(
+ FileDescriptorStore::Descriptor&& other)
+ : key(other.key), fd(std::move(other.fd)), region(other.region) {}
+
+FileDescriptorStore::Descriptor::~Descriptor() = default;
+
+// static
+FileDescriptorStore& FileDescriptorStore::GetInstance() {
+ static FileDescriptorStore* store = new FileDescriptorStore;
+ return *store;
+}
+
+base::ScopedFD FileDescriptorStore::TakeFD(
+ const std::string& key,
+ base::MemoryMappedFile::Region* region) {
+ base::ScopedFD fd = MaybeTakeFD(key, region);
+ if (!fd.is_valid())
+ DLOG(DCHECK) << "Unknown global descriptor: " << key;
+ return fd;
+}
+
+base::ScopedFD FileDescriptorStore::MaybeTakeFD(
+ const std::string& key,
+ base::MemoryMappedFile::Region* region) {
+ auto iter = descriptors_.find(key);
+ if (iter == descriptors_.end())
+ return base::ScopedFD();
+ *region = iter->second.region;
+ base::ScopedFD result = std::move(iter->second.fd);
+ descriptors_.erase(iter);
+ return result;
+}
+
+void FileDescriptorStore::Set(const std::string& key, base::ScopedFD fd) {
+ Set(key, std::move(fd), base::MemoryMappedFile::Region::kWholeFile);
+}
+
+void FileDescriptorStore::Set(const std::string& key,
+ base::ScopedFD fd,
+ base::MemoryMappedFile::Region region) {
+ Descriptor descriptor(key, std::move(fd), region);
+ descriptors_.insert(std::make_pair(key, std::move(descriptor)));
+}
+
+FileDescriptorStore::FileDescriptorStore() = default;
+
+FileDescriptorStore::~FileDescriptorStore() = default;
+
+} // namespace base
diff --git a/base/file_descriptor_store.h b/base/file_descriptor_store.h
new file mode 100644
index 0000000000..b6bd079916
--- /dev/null
+++ b/base/file_descriptor_store.h
@@ -0,0 +1,73 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILE_DESCRIPTOR_STORE_H_
+#define BASE_FILE_DESCRIPTOR_STORE_H_
+
+#include <map>
+#include <string>
+
+#include "base/files/memory_mapped_file.h"
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+
+namespace base {
+
+// The file descriptor store is used to associate file descriptors with keys
+// that must be unique.
+// It is used to share file descriptors from a process to its child.
+class BASE_EXPORT FileDescriptorStore {
+ public:
+ struct Descriptor {
+ Descriptor(const std::string& key, base::ScopedFD fd);
+ Descriptor(const std::string& key,
+ base::ScopedFD fd,
+ base::MemoryMappedFile::Region region);
+ Descriptor(Descriptor&& other);
+ ~Descriptor();
+
+ Descriptor& operator=(Descriptor&& other) = default;
+
+ // Globally unique key.
+ std::string key;
+ // Actual FD.
+ base::ScopedFD fd;
+ // Optional region, defaults to kWholeFile.
+ base::MemoryMappedFile::Region region;
+ };
+ using Mapping = std::map<std::string, Descriptor>;
+
+ // Returns the singleton instance of FileDescriptorStore.
+ static FileDescriptorStore& GetInstance();
+
+ // Gets a descriptor given a key and also populates |region|.
+ // It is a fatal error if the key is not known.
+ base::ScopedFD TakeFD(const std::string& key,
+ base::MemoryMappedFile::Region* region);
+
+ // Gets a descriptor given a key. Returns an empty ScopedFD on error.
+ base::ScopedFD MaybeTakeFD(const std::string& key,
+ base::MemoryMappedFile::Region* region);
+
+ // Sets the descriptor for the given |key|. This sets the region associated
+ // with |key| to kWholeFile.
+ void Set(const std::string& key, base::ScopedFD fd);
+
+ // Sets the descriptor and |region| for the given |key|.
+ void Set(const std::string& key,
+ base::ScopedFD fd,
+ base::MemoryMappedFile::Region region);
+
+ private:
+ FileDescriptorStore();
+ ~FileDescriptorStore();
+
+ Mapping descriptors_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileDescriptorStore);
+};
+
+} // namespace base
+
+#endif // BASE_FILE_DESCRIPTOR_STORE_H_
diff --git a/base/files/file_locking_unittest.cc b/base/files/file_locking_unittest.cc
new file mode 100644
index 0000000000..e158b7d06a
--- /dev/null
+++ b/base/files/file_locking_unittest.cc
@@ -0,0 +1,232 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+using base::File;
+using base::FilePath;
+
+namespace {
+
+// Flag for the parent to share a temp dir to the child.
+const char kTempDirFlag[] = "temp-dir";
+
+// Flags to control how the subprocess unlocks the file.
+const char kFileUnlock[] = "file-unlock";
+const char kCloseUnlock[] = "close-unlock";
+const char kExitUnlock[] = "exit-unlock";
+
+// File to lock in temp dir.
+const char kLockFile[] = "lockfile";
+
+// Constants for various requests and responses, used as |signal_file| parameter
+// to signal/wait helpers.
+const char kSignalLockFileLocked[] = "locked.signal";
+const char kSignalLockFileClose[] = "close.signal";
+const char kSignalLockFileClosed[] = "closed.signal";
+const char kSignalLockFileUnlock[] = "unlock.signal";
+const char kSignalLockFileUnlocked[] = "unlocked.signal";
+const char kSignalExit[] = "exit.signal";
+
+// Signal an event by creating a file which didn't previously exist.
+bool SignalEvent(const FilePath& signal_dir, const char* signal_file) {
+ File file(signal_dir.AppendASCII(signal_file),
+ File::FLAG_CREATE | File::FLAG_WRITE);
+ return file.IsValid();
+}
+
+// Check whether an event was signaled.
+bool CheckEvent(const FilePath& signal_dir, const char* signal_file) {
+ File file(signal_dir.AppendASCII(signal_file),
+ File::FLAG_OPEN | File::FLAG_READ);
+ return file.IsValid();
+}
+
+// Busy-wait for an event to be signaled, returning false for timeout.
+bool WaitForEventWithTimeout(const FilePath& signal_dir,
+ const char* signal_file,
+ const base::TimeDelta& timeout) {
+ const base::Time finish_by = base::Time::Now() + timeout;
+ while (!CheckEvent(signal_dir, signal_file)) {
+ if (base::Time::Now() > finish_by)
+ return false;
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ }
+ return true;
+}
+
+// Wait forever for the event to be signaled (should never return false).
+bool WaitForEvent(const FilePath& signal_dir, const char* signal_file) {
+ return WaitForEventWithTimeout(signal_dir, signal_file,
+ base::TimeDelta::Max());
+}
+
+// Keep these in sync so StartChild*() can refer to correct test main.
+#define ChildMain ChildLockUnlock
+#define ChildMainString "ChildLockUnlock"
+
+// Subprocess to test getting a file lock then releasing it. |kTempDirFlag|
+// must pass in an existing temporary directory for the lockfile and signal
+// files. One of the following flags must be passed to determine how to unlock
+// the lock file:
+// - |kFileUnlock| calls Unlock() to unlock.
+// - |kCloseUnlock| calls Close() while the lock is held.
+// - |kExitUnlock| exits while the lock is held.
+MULTIPROCESS_TEST_MAIN(ChildMain) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag);
+ CHECK(base::DirectoryExists(temp_path));
+
+ // Immediately lock the file.
+ File file(temp_path.AppendASCII(kLockFile),
+ File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
+ CHECK(file.IsValid());
+ CHECK_EQ(File::FILE_OK, file.Lock());
+ CHECK(SignalEvent(temp_path, kSignalLockFileLocked));
+
+ if (command_line->HasSwitch(kFileUnlock)) {
+ // Wait for signal to unlock, then unlock the file.
+ CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock));
+ CHECK_EQ(File::FILE_OK, file.Unlock());
+ CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked));
+ } else if (command_line->HasSwitch(kCloseUnlock)) {
+ // Wait for the signal to close, then close the file.
+ CHECK(WaitForEvent(temp_path, kSignalLockFileClose));
+ file.Close();
+ CHECK(!file.IsValid());
+ CHECK(SignalEvent(temp_path, kSignalLockFileClosed));
+ } else {
+ CHECK(command_line->HasSwitch(kExitUnlock));
+ }
+
+ // Wait for signal to exit, so that unlock or close can be distinguished from
+ // exit.
+ CHECK(WaitForEvent(temp_path, kSignalExit));
+ return 0;
+}
+
+} // namespace
+
+class FileLockingTest : public testing::Test {
+ public:
+ FileLockingTest() = default;
+
+ protected:
+ void SetUp() override {
+ testing::Test::SetUp();
+
+ // Setup the temp dir and the lock file.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ lock_file_.Initialize(
+ temp_dir_.GetPath().AppendASCII(kLockFile),
+ File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE);
+ ASSERT_TRUE(lock_file_.IsValid());
+ }
+
+ bool SignalEvent(const char* signal_file) {
+ return ::SignalEvent(temp_dir_.GetPath(), signal_file);
+ }
+
+ bool WaitForEventOrTimeout(const char* signal_file) {
+ return ::WaitForEventWithTimeout(temp_dir_.GetPath(), signal_file,
+ TestTimeouts::action_timeout());
+ }
+
+ // Start a child process set to use the specified unlock action, and wait for
+ // it to lock the file.
+ void StartChildAndSignalLock(const char* unlock_action) {
+ // Create a temporary dir and spin up a ChildLockExit subprocess against it.
+ const FilePath temp_path = temp_dir_.GetPath();
+ base::CommandLine child_command_line(
+ base::GetMultiProcessTestChildBaseCommandLine());
+ child_command_line.AppendSwitchPath(kTempDirFlag, temp_path);
+ child_command_line.AppendSwitch(unlock_action);
+ lock_child_ = base::SpawnMultiProcessTestChild(
+ ChildMainString, child_command_line, base::LaunchOptions());
+ ASSERT_TRUE(lock_child_.IsValid());
+
+ // Wait for the child to lock the file.
+ ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileLocked));
+ }
+
+ // Signal the child to exit cleanly.
+ void ExitChildCleanly() {
+ ASSERT_TRUE(SignalEvent(kSignalExit));
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ lock_child_, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+
+ base::ScopedTempDir temp_dir_;
+ base::File lock_file_;
+ base::Process lock_child_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileLockingTest);
+};
+
+// Test that locks are released by Unlock().
+TEST_F(FileLockingTest, LockAndUnlock) {
+ StartChildAndSignalLock(kFileUnlock);
+
+ ASSERT_NE(File::FILE_OK, lock_file_.Lock());
+ ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock));
+ ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked));
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
+
+ ExitChildCleanly();
+}
+
+// Test that locks are released on Close().
+TEST_F(FileLockingTest, UnlockOnClose) {
+ StartChildAndSignalLock(kCloseUnlock);
+
+ ASSERT_NE(File::FILE_OK, lock_file_.Lock());
+ ASSERT_TRUE(SignalEvent(kSignalLockFileClose));
+ ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed));
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
+
+ ExitChildCleanly();
+}
+
+// Test that locks are released on exit.
+TEST_F(FileLockingTest, UnlockOnExit) {
+ StartChildAndSignalLock(kExitUnlock);
+
+ ASSERT_NE(File::FILE_OK, lock_file_.Lock());
+ ExitChildCleanly();
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
+}
+
+// Test that killing the process releases the lock. This should cover crashing.
+// Flaky on Android (http://crbug.com/747518)
+#if defined(OS_ANDROID)
+#define MAYBE_UnlockOnTerminate DISABLED_UnlockOnTerminate
+#else
+#define MAYBE_UnlockOnTerminate UnlockOnTerminate
+#endif
+TEST_F(FileLockingTest, MAYBE_UnlockOnTerminate) {
+ // The child will wait for an exit which never arrives.
+ StartChildAndSignalLock(kExitUnlock);
+
+ ASSERT_NE(File::FILE_OK, lock_file_.Lock());
+ ASSERT_TRUE(TerminateMultiProcessTestChild(lock_child_, 0, true));
+ ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
+ ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
+}
diff --git a/base/files/file_path_watcher_fsevents.cc b/base/files/file_path_watcher_fsevents.cc
new file mode 100644
index 0000000000..49ed36ba44
--- /dev/null
+++ b/base/files/file_path_watcher_fsevents.cc
@@ -0,0 +1,282 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path_watcher_fsevents.h"
+
+#include <dispatch/dispatch.h>
+
+#include <list>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+
+namespace base {
+
+namespace {
+
+// The latency parameter passed to FSEventsStreamCreate().
+const CFAbsoluteTime kEventLatencySeconds = 0.3;
+
+// Resolve any symlinks in the path.
+FilePath ResolvePath(const FilePath& path) {
+ const unsigned kMaxLinksToResolve = 255;
+
+ std::vector<FilePath::StringType> component_vector;
+ path.GetComponents(&component_vector);
+ std::list<FilePath::StringType>
+ components(component_vector.begin(), component_vector.end());
+
+ FilePath result;
+ unsigned resolve_count = 0;
+ while (resolve_count < kMaxLinksToResolve && !components.empty()) {
+ FilePath component(*components.begin());
+ components.pop_front();
+
+ FilePath current;
+ if (component.IsAbsolute()) {
+ current = component;
+ } else {
+ current = result.Append(component);
+ }
+
+ FilePath target;
+ if (ReadSymbolicLink(current, &target)) {
+ if (target.IsAbsolute())
+ result.clear();
+ std::vector<FilePath::StringType> target_components;
+ target.GetComponents(&target_components);
+ components.insert(components.begin(), target_components.begin(),
+ target_components.end());
+ resolve_count++;
+ } else {
+ result = current;
+ }
+ }
+
+ if (resolve_count >= kMaxLinksToResolve)
+ result.clear();
+ return result;
+}
+
+} // namespace
+
+FilePathWatcherFSEvents::FilePathWatcherFSEvents()
+ : queue_(dispatch_queue_create(
+ base::StringPrintf("org.chromium.base.FilePathWatcher.%p", this)
+ .c_str(),
+ DISPATCH_QUEUE_SERIAL)),
+ fsevent_stream_(nullptr),
+ weak_factory_(this) {}
+
+FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {
+ DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
+ DCHECK(callback_.is_null())
+ << "Cancel() must be called before FilePathWatcher is destroyed.";
+}
+
+bool FilePathWatcherFSEvents::Watch(const FilePath& path,
+ bool recursive,
+ const FilePathWatcher::Callback& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(callback_.is_null());
+
+ // This class could support non-recursive watches, but that is currently
+ // left to FilePathWatcherKQueue.
+ if (!recursive)
+ return false;
+
+ set_task_runner(SequencedTaskRunnerHandle::Get());
+ callback_ = callback;
+
+ FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
+ // The block runtime would implicitly capture the reference, not the object
+ // it's referencing. Copy the path into a local, so that the value is
+ // captured by the block's scope.
+ const FilePath path_copy(path);
+
+ dispatch_async(queue_, ^{
+ StartEventStream(start_event, path_copy);
+ });
+ return true;
+}
+
+void FilePathWatcherFSEvents::Cancel() {
+ set_cancelled();
+ callback_.Reset();
+
+ // Switch to the dispatch queue to tear down the event stream. As the queue is
+ // owned by |this|, and this method is called from the destructor, execute the
+ // block synchronously.
+ dispatch_sync(queue_, ^{
+ if (fsevent_stream_) {
+ DestroyEventStream();
+ target_.clear();
+ resolved_target_.clear();
+ }
+ });
+}
+
+// static
+void FilePathWatcherFSEvents::FSEventsCallback(
+ ConstFSEventStreamRef stream,
+ void* event_watcher,
+ size_t num_events,
+ void* event_paths,
+ const FSEventStreamEventFlags flags[],
+ const FSEventStreamEventId event_ids[]) {
+ FilePathWatcherFSEvents* watcher =
+ reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
+ bool root_changed = watcher->ResolveTargetPath();
+ std::vector<FilePath> paths;
+ FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
+ for (size_t i = 0; i < num_events; i++) {
+ if (flags[i] & kFSEventStreamEventFlagRootChanged)
+ root_changed = true;
+ if (event_ids[i])
+ root_change_at = std::min(root_change_at, event_ids[i]);
+ paths.push_back(FilePath(
+ reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
+ }
+
+ // Reinitialize the event stream if we find changes to the root. This is
+ // necessary since FSEvents doesn't report any events for the subtree after
+ // the directory to be watched gets created.
+ if (root_changed) {
+ // Resetting the event stream from within the callback fails (FSEvents spews
+ // bad file descriptor errors), so do the reset asynchronously.
+ //
+ // We can't dispatch_async a call to UpdateEventStream() directly because
+ // there would be no guarantee that |watcher| still exists when it runs.
+ //
+ // Instead, bounce on task_runner() and use a WeakPtr to verify that
+ // |watcher| still exists. If it does, dispatch_async a call to
+ // UpdateEventStream(). Because the destructor of |watcher| runs on
+ // task_runner() and calls dispatch_sync, it is guaranteed that |watcher|
+ // still exists when UpdateEventStream() runs.
+ watcher->task_runner()->PostTask(
+ FROM_HERE, Bind(
+ [](WeakPtr<FilePathWatcherFSEvents> weak_watcher,
+ FSEventStreamEventId root_change_at) {
+ if (!weak_watcher)
+ return;
+ FilePathWatcherFSEvents* watcher = weak_watcher.get();
+ dispatch_async(watcher->queue_, ^{
+ watcher->UpdateEventStream(root_change_at);
+ });
+ },
+ watcher->weak_factory_.GetWeakPtr(), root_change_at));
+ }
+
+ watcher->OnFilePathsChanged(paths);
+}
+
+void FilePathWatcherFSEvents::OnFilePathsChanged(
+ const std::vector<FilePath>& paths) {
+ DCHECK(!resolved_target_.empty());
+ task_runner()->PostTask(
+ FROM_HERE,
+ Bind(&FilePathWatcherFSEvents::DispatchEvents, weak_factory_.GetWeakPtr(),
+ paths, target_, resolved_target_));
+}
+
+void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths,
+ const FilePath& target,
+ const FilePath& resolved_target) {
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
+
+ // Don't issue callbacks after Cancel() has been called.
+ if (is_cancelled() || callback_.is_null()) {
+ return;
+ }
+
+ for (const FilePath& path : paths) {
+ if (resolved_target.IsParent(path) || resolved_target == path) {
+ callback_.Run(target, false);
+ return;
+ }
+ }
+}
+
+void FilePathWatcherFSEvents::UpdateEventStream(
+ FSEventStreamEventId start_event) {
+ // It can happen that the watcher gets canceled while tasks that call this
+ // function are still in flight, so abort if this situation is detected.
+ if (resolved_target_.empty())
+ return;
+
+ if (fsevent_stream_)
+ DestroyEventStream();
+
+ ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
+ NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
+ ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
+ NULL, resolved_target_.DirName().value().c_str(),
+ kCFStringEncodingMacHFS));
+ CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
+ ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate(
+ NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array),
+ &kCFTypeArrayCallBacks));
+
+ FSEventStreamContext context;
+ context.version = 0;
+ context.info = this;
+ context.retain = NULL;
+ context.release = NULL;
+ context.copyDescription = NULL;
+
+ fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
+ watched_paths,
+ start_event,
+ kEventLatencySeconds,
+ kFSEventStreamCreateFlagWatchRoot);
+ FSEventStreamSetDispatchQueue(fsevent_stream_, queue_);
+
+ if (!FSEventStreamStart(fsevent_stream_)) {
+ task_runner()->PostTask(FROM_HERE,
+ Bind(&FilePathWatcherFSEvents::ReportError,
+ weak_factory_.GetWeakPtr(), target_));
+ }
+}
+
+bool FilePathWatcherFSEvents::ResolveTargetPath() {
+ FilePath resolved = ResolvePath(target_).StripTrailingSeparators();
+ bool changed = resolved != resolved_target_;
+ resolved_target_ = resolved;
+ if (resolved_target_.empty()) {
+ task_runner()->PostTask(FROM_HERE,
+ Bind(&FilePathWatcherFSEvents::ReportError,
+ weak_factory_.GetWeakPtr(), target_));
+ }
+ return changed;
+}
+
+void FilePathWatcherFSEvents::ReportError(const FilePath& target) {
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
+ if (!callback_.is_null()) {
+ callback_.Run(target, true);
+ }
+}
+
+void FilePathWatcherFSEvents::DestroyEventStream() {
+ FSEventStreamStop(fsevent_stream_);
+ FSEventStreamInvalidate(fsevent_stream_);
+ FSEventStreamRelease(fsevent_stream_);
+ fsevent_stream_ = NULL;
+}
+
+void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event,
+ const FilePath& path) {
+ DCHECK(resolved_target_.empty());
+
+ target_ = path;
+ ResolveTargetPath();
+ UpdateEventStream(start_event);
+}
+
+} // namespace base
diff --git a/base/files/file_path_watcher_fsevents.h b/base/files/file_path_watcher_fsevents.h
new file mode 100644
index 0000000000..dcdf2fbf9d
--- /dev/null
+++ b/base/files/file_path_watcher_fsevents.h
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_
+#define BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_
+
+#include <CoreServices/CoreServices.h>
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "base/mac/scoped_dispatch_object.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+
+namespace base {
+
+// Mac-specific file watcher implementation based on FSEvents.
+// There are trade-offs between the FSEvents implementation and a kqueue
+// implementation. The biggest issues are that FSEvents on 10.6 sometimes drops
+// events and kqueue does not trigger for modifications to a file in a watched
+// directory. See file_path_watcher_mac.cc for the code that decides when to
+// use which one.
+class FilePathWatcherFSEvents : public FilePathWatcher::PlatformDelegate {
+ public:
+ FilePathWatcherFSEvents();
+ ~FilePathWatcherFSEvents() override;
+
+ // FilePathWatcher::PlatformDelegate overrides.
+ bool Watch(const FilePath& path,
+ bool recursive,
+ const FilePathWatcher::Callback& callback) override;
+ void Cancel() override;
+
+ private:
+ static void FSEventsCallback(ConstFSEventStreamRef stream,
+ void* event_watcher,
+ size_t num_events,
+ void* event_paths,
+ const FSEventStreamEventFlags flags[],
+ const FSEventStreamEventId event_ids[]);
+
+ // Called from FSEventsCallback whenever there is a change to the paths.
+ void OnFilePathsChanged(const std::vector<FilePath>& paths);
+
+ // Called on the message_loop() thread to dispatch path events. Can't access
+ // target_ and resolved_target_ directly as those are modified on the
+ // libdispatch thread.
+ void DispatchEvents(const std::vector<FilePath>& paths,
+ const FilePath& target,
+ const FilePath& resolved_target);
+
+ // (Re-)Initialize the event stream to start reporting events from
+ // |start_event|.
+ void UpdateEventStream(FSEventStreamEventId start_event);
+
+ // Returns true if resolving the target path got a different result than
+ // last time it was done.
+ bool ResolveTargetPath();
+
+ // Report an error watching the given target.
+ void ReportError(const FilePath& target);
+
+ // Destroy the event stream.
+ void DestroyEventStream();
+
+ // Start watching the FSEventStream.
+ void StartEventStream(FSEventStreamEventId start_event, const FilePath& path);
+
+ // Callback to notify upon changes.
+ // (Only accessed from the message_loop() thread.)
+ FilePathWatcher::Callback callback_;
+
+ // The dispatch queue on which the the event stream is scheduled.
+ ScopedDispatchObject<dispatch_queue_t> queue_;
+
+ // Target path to watch (passed to callback).
+ // (Only accessed from the libdispatch queue.)
+ FilePath target_;
+
+ // Target path with all symbolic links resolved.
+ // (Only accessed from the libdispatch queue.)
+ FilePath resolved_target_;
+
+ // Backend stream we receive event callbacks from (strong reference).
+ // (Only accessed from the libdispatch queue.)
+ FSEventStreamRef fsevent_stream_;
+
+ WeakPtrFactory<FilePathWatcherFSEvents> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FilePathWatcherFSEvents);
+};
+
+} // namespace base
+
+#endif // BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_
diff --git a/base/files/file_path_watcher_kqueue.cc b/base/files/file_path_watcher_kqueue.cc
new file mode 100644
index 0000000000..036809d6cf
--- /dev/null
+++ b/base/files/file_path_watcher_kqueue.cc
@@ -0,0 +1,372 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path_watcher_kqueue.h"
+
+#include <fcntl.h>
+#include <stddef.h>
+#include <sys/param.h>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+
+// On some platforms these are not defined.
+#if !defined(EV_RECEIPT)
+#define EV_RECEIPT 0
+#endif
+#if !defined(O_EVTONLY)
+#define O_EVTONLY O_RDONLY
+#endif
+
+namespace base {
+
+FilePathWatcherKQueue::FilePathWatcherKQueue() : kqueue_(-1) {}
+
+FilePathWatcherKQueue::~FilePathWatcherKQueue() {
+ DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
+}
+
+void FilePathWatcherKQueue::ReleaseEvent(struct kevent& event) {
+ CloseFileDescriptor(&event.ident);
+ EventData* entry = EventDataForKevent(event);
+ delete entry;
+ event.udata = NULL;
+}
+
+int FilePathWatcherKQueue::EventsForPath(FilePath path, EventVector* events) {
+ // Make sure that we are working with a clean slate.
+ DCHECK(events->empty());
+
+ std::vector<FilePath::StringType> components;
+ path.GetComponents(&components);
+
+ if (components.size() < 1) {
+ return -1;
+ }
+
+ int last_existing_entry = 0;
+ FilePath built_path;
+ bool path_still_exists = true;
+ for (std::vector<FilePath::StringType>::iterator i = components.begin();
+ i != components.end(); ++i) {
+ if (i == components.begin()) {
+ built_path = FilePath(*i);
+ } else {
+ built_path = built_path.Append(*i);
+ }
+ uintptr_t fd = kNoFileDescriptor;
+ if (path_still_exists) {
+ fd = FileDescriptorForPath(built_path);
+ if (fd == kNoFileDescriptor) {
+ path_still_exists = false;
+ } else {
+ ++last_existing_entry;
+ }
+ }
+ FilePath::StringType subdir = (i != (components.end() - 1)) ? *(i + 1) : "";
+ EventData* data = new EventData(built_path, subdir);
+ struct kevent event;
+ EV_SET(&event, fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR | EV_RECEIPT),
+ (NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB |
+ NOTE_RENAME | NOTE_REVOKE | NOTE_EXTEND), 0, data);
+ events->push_back(event);
+ }
+ return last_existing_entry;
+}
+
+uintptr_t FilePathWatcherKQueue::FileDescriptorForPath(const FilePath& path) {
+ int fd = HANDLE_EINTR(open(path.value().c_str(), O_EVTONLY));
+ if (fd == -1)
+ return kNoFileDescriptor;
+ return fd;
+}
+
+void FilePathWatcherKQueue::CloseFileDescriptor(uintptr_t* fd) {
+ if (*fd == kNoFileDescriptor) {
+ return;
+ }
+
+ if (IGNORE_EINTR(close(*fd)) != 0) {
+ DPLOG(ERROR) << "close";
+ }
+ *fd = kNoFileDescriptor;
+}
+
+bool FilePathWatcherKQueue::AreKeventValuesValid(struct kevent* kevents,
+ int count) {
+ if (count < 0) {
+ DPLOG(ERROR) << "kevent";
+ return false;
+ }
+ bool valid = true;
+ for (int i = 0; i < count; ++i) {
+ if (kevents[i].flags & EV_ERROR && kevents[i].data) {
+ // Find the kevent in |events_| that matches the kevent with the error.
+ EventVector::iterator event = events_.begin();
+ for (; event != events_.end(); ++event) {
+ if (event->ident == kevents[i].ident) {
+ break;
+ }
+ }
+ std::string path_name;
+ if (event != events_.end()) {
+ EventData* event_data = EventDataForKevent(*event);
+ if (event_data != NULL) {
+ path_name = event_data->path_.value();
+ }
+ }
+ if (path_name.empty()) {
+ path_name = base::StringPrintf(
+ "fd %ld", reinterpret_cast<long>(&kevents[i].ident));
+ }
+ DLOG(ERROR) << "Error: " << kevents[i].data << " for " << path_name;
+ valid = false;
+ }
+ }
+ return valid;
+}
+
+void FilePathWatcherKQueue::HandleAttributesChange(
+ const EventVector::iterator& event,
+ bool* target_file_affected,
+ bool* update_watches) {
+ EventVector::iterator next_event = event + 1;
+ EventData* next_event_data = EventDataForKevent(*next_event);
+ // Check to see if the next item in path is still accessible.
+ uintptr_t have_access = FileDescriptorForPath(next_event_data->path_);
+ if (have_access == kNoFileDescriptor) {
+ *target_file_affected = true;
+ *update_watches = true;
+ EventVector::iterator local_event(event);
+ for (; local_event != events_.end(); ++local_event) {
+ // Close all nodes from the event down. This has the side effect of
+ // potentially rendering other events in |updates| invalid.
+ // There is no need to remove the events from |kqueue_| because this
+ // happens as a side effect of closing the file descriptor.
+ CloseFileDescriptor(&local_event->ident);
+ }
+ } else {
+ CloseFileDescriptor(&have_access);
+ }
+}
+
+void FilePathWatcherKQueue::HandleDeleteOrMoveChange(
+ const EventVector::iterator& event,
+ bool* target_file_affected,
+ bool* update_watches) {
+ *target_file_affected = true;
+ *update_watches = true;
+ EventVector::iterator local_event(event);
+ for (; local_event != events_.end(); ++local_event) {
+ // Close all nodes from the event down. This has the side effect of
+ // potentially rendering other events in |updates| invalid.
+ // There is no need to remove the events from |kqueue_| because this
+ // happens as a side effect of closing the file descriptor.
+ CloseFileDescriptor(&local_event->ident);
+ }
+}
+
+void FilePathWatcherKQueue::HandleCreateItemChange(
+ const EventVector::iterator& event,
+ bool* target_file_affected,
+ bool* update_watches) {
+ // Get the next item in the path.
+ EventVector::iterator next_event = event + 1;
+ // Check to see if it already has a valid file descriptor.
+ if (!IsKeventFileDescriptorOpen(*next_event)) {
+ EventData* next_event_data = EventDataForKevent(*next_event);
+ // If not, attempt to open a file descriptor for it.
+ next_event->ident = FileDescriptorForPath(next_event_data->path_);
+ if (IsKeventFileDescriptorOpen(*next_event)) {
+ *update_watches = true;
+ if (next_event_data->subdir_.empty()) {
+ *target_file_affected = true;
+ }
+ }
+ }
+}
+
+bool FilePathWatcherKQueue::UpdateWatches(bool* target_file_affected) {
+ // Iterate over events adding kevents for items that exist to the kqueue.
+ // Then check to see if new components in the path have been created.
+ // Repeat until no new components in the path are detected.
+ // This is to get around races in directory creation in a watched path.
+ bool update_watches = true;
+ while (update_watches) {
+ size_t valid;
+ for (valid = 0; valid < events_.size(); ++valid) {
+ if (!IsKeventFileDescriptorOpen(events_[valid])) {
+ break;
+ }
+ }
+ if (valid == 0) {
+ // The root of the file path is inaccessible?
+ return false;
+ }
+
+ EventVector updates(valid);
+ int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], valid, &updates[0],
+ valid, NULL));
+ if (!AreKeventValuesValid(&updates[0], count)) {
+ return false;
+ }
+ update_watches = false;
+ for (; valid < events_.size(); ++valid) {
+ EventData* event_data = EventDataForKevent(events_[valid]);
+ events_[valid].ident = FileDescriptorForPath(event_data->path_);
+ if (IsKeventFileDescriptorOpen(events_[valid])) {
+ update_watches = true;
+ if (event_data->subdir_.empty()) {
+ *target_file_affected = true;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+bool FilePathWatcherKQueue::Watch(const FilePath& path,
+ bool recursive,
+ const FilePathWatcher::Callback& callback) {
+ DCHECK(target_.value().empty()); // Can only watch one path.
+ DCHECK(!callback.is_null());
+ DCHECK_EQ(kqueue_, -1);
+ // Recursive watch is not supported using kqueue.
+ DCHECK(!recursive);
+
+ callback_ = callback;
+ target_ = path;
+
+ set_task_runner(SequencedTaskRunnerHandle::Get());
+
+ kqueue_ = kqueue();
+ if (kqueue_ == -1) {
+ DPLOG(ERROR) << "kqueue";
+ return false;
+ }
+
+ int last_entry = EventsForPath(target_, &events_);
+ DCHECK_NE(last_entry, 0);
+
+ EventVector responses(last_entry);
+
+ int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], last_entry,
+ &responses[0], last_entry, NULL));
+ if (!AreKeventValuesValid(&responses[0], count)) {
+ // Calling Cancel() here to close any file descriptors that were opened.
+ // This would happen in the destructor anyways, but FilePathWatchers tend to
+ // be long lived, and if an error has occurred, there is no reason to waste
+ // the file descriptors.
+ Cancel();
+ return false;
+ }
+
+ // It's safe to use Unretained() because the watch is cancelled and the
+ // callback cannot be invoked after |kqueue_watch_controller_| (which is a
+ // member of |this|) has been deleted.
+ kqueue_watch_controller_ = FileDescriptorWatcher::WatchReadable(
+ kqueue_,
+ Bind(&FilePathWatcherKQueue::OnKQueueReadable, Unretained(this)));
+
+ return true;
+}
+
+void FilePathWatcherKQueue::Cancel() {
+ if (!task_runner()) {
+ set_cancelled();
+ return;
+ }
+
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
+ if (!is_cancelled()) {
+ set_cancelled();
+ kqueue_watch_controller_.reset();
+ if (IGNORE_EINTR(close(kqueue_)) != 0) {
+ DPLOG(ERROR) << "close kqueue";
+ }
+ kqueue_ = -1;
+ std::for_each(events_.begin(), events_.end(), ReleaseEvent);
+ events_.clear();
+ callback_.Reset();
+ }
+}
+
+void FilePathWatcherKQueue::OnKQueueReadable() {
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
+ DCHECK(events_.size());
+
+ // Request the file system update notifications that have occurred and return
+ // them in |updates|. |count| will contain the number of updates that have
+ // occurred.
+ EventVector updates(events_.size());
+ struct timespec timeout = {0, 0};
+ int count = HANDLE_EINTR(kevent(kqueue_, NULL, 0, &updates[0], updates.size(),
+ &timeout));
+
+ // Error values are stored within updates, so check to make sure that no
+ // errors occurred.
+ if (!AreKeventValuesValid(&updates[0], count)) {
+ callback_.Run(target_, true /* error */);
+ Cancel();
+ return;
+ }
+
+ bool update_watches = false;
+ bool send_notification = false;
+
+ // Iterate through each of the updates and react to them.
+ for (int i = 0; i < count; ++i) {
+ // Find our kevent record that matches the update notification.
+ EventVector::iterator event = events_.begin();
+ for (; event != events_.end(); ++event) {
+ if (!IsKeventFileDescriptorOpen(*event) ||
+ event->ident == updates[i].ident) {
+ break;
+ }
+ }
+ if (event == events_.end() || !IsKeventFileDescriptorOpen(*event)) {
+ // The event may no longer exist in |events_| because another event
+ // modified |events_| in such a way to make it invalid. For example if
+ // the path is /foo/bar/bam and foo is deleted, NOTE_DELETE events for
+ // foo, bar and bam will be sent. If foo is processed first, then
+ // the file descriptors for bar and bam will already be closed and set
+ // to -1 before they get a chance to be processed.
+ continue;
+ }
+
+ EventData* event_data = EventDataForKevent(*event);
+
+ // If the subdir is empty, this is the last item on the path and is the
+ // target file.
+ bool target_file_affected = event_data->subdir_.empty();
+ if ((updates[i].fflags & NOTE_ATTRIB) && !target_file_affected) {
+ HandleAttributesChange(event, &target_file_affected, &update_watches);
+ }
+ if (updates[i].fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) {
+ HandleDeleteOrMoveChange(event, &target_file_affected, &update_watches);
+ }
+ if ((updates[i].fflags & NOTE_WRITE) && !target_file_affected) {
+ HandleCreateItemChange(event, &target_file_affected, &update_watches);
+ }
+ send_notification |= target_file_affected;
+ }
+
+ if (update_watches) {
+ if (!UpdateWatches(&send_notification)) {
+ callback_.Run(target_, true /* error */);
+ Cancel();
+ }
+ }
+
+ if (send_notification) {
+ callback_.Run(target_, false);
+ }
+}
+
+} // namespace base
diff --git a/base/files/file_path_watcher_kqueue.h b/base/files/file_path_watcher_kqueue.h
new file mode 100644
index 0000000000..ef79be5596
--- /dev/null
+++ b/base/files/file_path_watcher_kqueue.h
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_
+#define BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_
+
+#include <sys/event.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/files/file_descriptor_watcher_posix.h"
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "base/macros.h"
+
+namespace base {
+
+// Mac-specific file watcher implementation based on kqueue.
+// The Linux and Windows versions are able to detect:
+// - file creation/deletion/modification in a watched directory
+// - file creation/deletion/modification for a watched file
+// - modifications to the paths to a watched object that would affect the
+// object such as renaming/attibute changes etc.
+// The kqueue implementation will handle all of the items in the list above
+// except for detecting modifications to files in a watched directory. It will
+// detect the creation and deletion of files, just not the modification of
+// files. It does however detect the attribute changes that the FSEvents impl
+// would miss.
+class FilePathWatcherKQueue : public FilePathWatcher::PlatformDelegate {
+ public:
+ FilePathWatcherKQueue();
+ ~FilePathWatcherKQueue() override;
+
+ // FilePathWatcher::PlatformDelegate overrides.
+ bool Watch(const FilePath& path,
+ bool recursive,
+ const FilePathWatcher::Callback& callback) override;
+ void Cancel() override;
+
+ private:
+ class EventData {
+ public:
+ EventData(const FilePath& path, const FilePath::StringType& subdir)
+ : path_(path), subdir_(subdir) { }
+ FilePath path_; // Full path to this item.
+ FilePath::StringType subdir_; // Path to any sub item.
+ };
+
+ typedef std::vector<struct kevent> EventVector;
+
+ // Called when data is available in |kqueue_|.
+ void OnKQueueReadable();
+
+ // Returns true if the kevent values are error free.
+ bool AreKeventValuesValid(struct kevent* kevents, int count);
+
+ // Respond to a change of attributes of the path component represented by
+ // |event|. Sets |target_file_affected| to true if |target_| is affected.
+ // Sets |update_watches| to true if |events_| need to be updated.
+ void HandleAttributesChange(const EventVector::iterator& event,
+ bool* target_file_affected,
+ bool* update_watches);
+
+ // Respond to a move or deletion of the path component represented by
+ // |event|. Sets |target_file_affected| to true if |target_| is affected.
+ // Sets |update_watches| to true if |events_| need to be updated.
+ void HandleDeleteOrMoveChange(const EventVector::iterator& event,
+ bool* target_file_affected,
+ bool* update_watches);
+
+ // Respond to a creation of an item in the path component represented by
+ // |event|. Sets |target_file_affected| to true if |target_| is affected.
+ // Sets |update_watches| to true if |events_| need to be updated.
+ void HandleCreateItemChange(const EventVector::iterator& event,
+ bool* target_file_affected,
+ bool* update_watches);
+
+ // Update |events_| with the current status of the system.
+ // Sets |target_file_affected| to true if |target_| is affected.
+ // Returns false if an error occurs.
+ bool UpdateWatches(bool* target_file_affected);
+
+ // Fills |events| with one kevent per component in |path|.
+ // Returns the number of valid events created where a valid event is
+ // defined as one that has a ident (file descriptor) field != -1.
+ static int EventsForPath(FilePath path, EventVector *events);
+
+ // Release a kevent generated by EventsForPath.
+ static void ReleaseEvent(struct kevent& event);
+
+ // Returns a file descriptor that will not block the system from deleting
+ // the file it references.
+ static uintptr_t FileDescriptorForPath(const FilePath& path);
+
+ static const uintptr_t kNoFileDescriptor = static_cast<uintptr_t>(-1);
+
+ // Closes |*fd| and sets |*fd| to -1.
+ static void CloseFileDescriptor(uintptr_t* fd);
+
+ // Returns true if kevent has open file descriptor.
+ static bool IsKeventFileDescriptorOpen(const struct kevent& event) {
+ return event.ident != kNoFileDescriptor;
+ }
+
+ static EventData* EventDataForKevent(const struct kevent& event) {
+ return reinterpret_cast<EventData*>(event.udata);
+ }
+
+ EventVector events_;
+ FilePathWatcher::Callback callback_;
+ FilePath target_;
+ int kqueue_;
+
+ // Throughout the lifetime of this, OnKQueueReadable() will be called when
+ // data is available in |kqueue_|.
+ std::unique_ptr<FileDescriptorWatcher::Controller> kqueue_watch_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(FilePathWatcherKQueue);
+};
+
+} // namespace base
+
+#endif // BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_
diff --git a/base/files/file_path_watcher_stub.cc b/base/files/file_path_watcher_stub.cc
new file mode 100644
index 0000000000..93a5babe9b
--- /dev/null
+++ b/base/files/file_path_watcher_stub.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file exists for Unix systems which don't have the inotify headers, and
+// thus cannot build file_watcher_inotify.cc
+
+#include "base/files/file_path_watcher.h"
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+
+namespace base {
+
+namespace {
+
+class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
+ public:
+ FilePathWatcherImpl() = default;
+ ~FilePathWatcherImpl() override = default;
+
+ bool Watch(const FilePath& path,
+ bool recursive,
+ const FilePathWatcher::Callback& callback) override {
+ return false;
+ }
+
+ void Cancel() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
+};
+
+} // namespace
+
+FilePathWatcher::FilePathWatcher() {
+ sequence_checker_.DetachFromSequence();
+ impl_ = std::make_unique<FilePathWatcherImpl>();
+}
+
+} // namespace base
diff --git a/base/files/file_proxy.cc b/base/files/file_proxy.cc
new file mode 100644
index 0000000000..f16e5940b5
--- /dev/null
+++ b/base/files/file_proxy.cc
@@ -0,0 +1,358 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_proxy.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/task_runner.h"
+#include "base/task_runner_util.h"
+
+namespace {
+
+void FileDeleter(base::File file) {
+}
+
+} // namespace
+
+namespace base {
+
+class FileHelper {
+ public:
+ FileHelper(FileProxy* proxy, File file)
+ : file_(std::move(file)),
+ error_(File::FILE_ERROR_FAILED),
+ task_runner_(proxy->task_runner()),
+ proxy_(AsWeakPtr(proxy)) {
+ }
+
+ void PassFile() {
+ if (proxy_)
+ proxy_->SetFile(std::move(file_));
+ else if (file_.IsValid())
+ task_runner_->PostTask(FROM_HERE,
+ BindOnce(&FileDeleter, std::move(file_)));
+ }
+
+ protected:
+ File file_;
+ File::Error error_;
+
+ private:
+ scoped_refptr<TaskRunner> task_runner_;
+ WeakPtr<FileProxy> proxy_;
+ DISALLOW_COPY_AND_ASSIGN(FileHelper);
+};
+
+namespace {
+
+class GenericFileHelper : public FileHelper {
+ public:
+ GenericFileHelper(FileProxy* proxy, File file)
+ : FileHelper(proxy, std::move(file)) {
+ }
+
+ void Close() {
+ file_.Close();
+ error_ = File::FILE_OK;
+ }
+
+ void SetTimes(Time last_access_time, Time last_modified_time) {
+ bool rv = file_.SetTimes(last_access_time, last_modified_time);
+ error_ = rv ? File::FILE_OK : File::FILE_ERROR_FAILED;
+ }
+
+ void SetLength(int64_t length) {
+ if (file_.SetLength(length))
+ error_ = File::FILE_OK;
+ }
+
+ void Flush() {
+ if (file_.Flush())
+ error_ = File::FILE_OK;
+ }
+
+ void Reply(FileProxy::StatusCallback callback) {
+ PassFile();
+ if (!callback.is_null())
+ std::move(callback).Run(error_);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GenericFileHelper);
+};
+
+class CreateOrOpenHelper : public FileHelper {
+ public:
+ CreateOrOpenHelper(FileProxy* proxy, File file)
+ : FileHelper(proxy, std::move(file)) {
+ }
+
+ void RunWork(const FilePath& file_path, int file_flags) {
+ file_.Initialize(file_path, file_flags);
+ error_ = file_.IsValid() ? File::FILE_OK : file_.error_details();
+ }
+
+ void Reply(FileProxy::StatusCallback callback) {
+ DCHECK(!callback.is_null());
+ PassFile();
+ std::move(callback).Run(error_);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CreateOrOpenHelper);
+};
+
+class CreateTemporaryHelper : public FileHelper {
+ public:
+ CreateTemporaryHelper(FileProxy* proxy, File file)
+ : FileHelper(proxy, std::move(file)) {
+ }
+
+ void RunWork(uint32_t additional_file_flags) {
+ // TODO(darin): file_util should have a variant of CreateTemporaryFile
+ // that returns a FilePath and a File.
+ if (!CreateTemporaryFile(&file_path_)) {
+ // TODO(davidben): base::CreateTemporaryFile should preserve the error
+ // code.
+ error_ = File::FILE_ERROR_FAILED;
+ return;
+ }
+
+ uint32_t file_flags = File::FLAG_WRITE | File::FLAG_TEMPORARY |
+ File::FLAG_CREATE_ALWAYS | additional_file_flags;
+
+ file_.Initialize(file_path_, file_flags);
+ if (file_.IsValid()) {
+ error_ = File::FILE_OK;
+ } else {
+ error_ = file_.error_details();
+ DeleteFile(file_path_, false);
+ file_path_.clear();
+ }
+ }
+
+ void Reply(FileProxy::CreateTemporaryCallback callback) {
+ DCHECK(!callback.is_null());
+ PassFile();
+ std::move(callback).Run(error_, file_path_);
+ }
+
+ private:
+ FilePath file_path_;
+ DISALLOW_COPY_AND_ASSIGN(CreateTemporaryHelper);
+};
+
+class GetInfoHelper : public FileHelper {
+ public:
+ GetInfoHelper(FileProxy* proxy, File file)
+ : FileHelper(proxy, std::move(file)) {
+ }
+
+ void RunWork() {
+ if (file_.GetInfo(&file_info_))
+ error_ = File::FILE_OK;
+ }
+
+ void Reply(FileProxy::GetFileInfoCallback callback) {
+ PassFile();
+ DCHECK(!callback.is_null());
+ std::move(callback).Run(error_, file_info_);
+ }
+
+ private:
+ File::Info file_info_;
+ DISALLOW_COPY_AND_ASSIGN(GetInfoHelper);
+};
+
+class ReadHelper : public FileHelper {
+ public:
+ ReadHelper(FileProxy* proxy, File file, int bytes_to_read)
+ : FileHelper(proxy, std::move(file)),
+ buffer_(new char[bytes_to_read]),
+ bytes_to_read_(bytes_to_read),
+ bytes_read_(0) {
+ }
+
+ void RunWork(int64_t offset) {
+ bytes_read_ = file_.Read(offset, buffer_.get(), bytes_to_read_);
+ error_ = (bytes_read_ < 0) ? File::FILE_ERROR_FAILED : File::FILE_OK;
+ }
+
+ void Reply(FileProxy::ReadCallback callback) {
+ PassFile();
+ DCHECK(!callback.is_null());
+ std::move(callback).Run(error_, buffer_.get(), bytes_read_);
+ }
+
+ private:
+ std::unique_ptr<char[]> buffer_;
+ int bytes_to_read_;
+ int bytes_read_;
+ DISALLOW_COPY_AND_ASSIGN(ReadHelper);
+};
+
+class WriteHelper : public FileHelper {
+ public:
+ WriteHelper(FileProxy* proxy,
+ File file,
+ const char* buffer, int bytes_to_write)
+ : FileHelper(proxy, std::move(file)),
+ buffer_(new char[bytes_to_write]),
+ bytes_to_write_(bytes_to_write),
+ bytes_written_(0) {
+ memcpy(buffer_.get(), buffer, bytes_to_write);
+ }
+
+ void RunWork(int64_t offset) {
+ bytes_written_ = file_.Write(offset, buffer_.get(), bytes_to_write_);
+ error_ = (bytes_written_ < 0) ? File::FILE_ERROR_FAILED : File::FILE_OK;
+ }
+
+ void Reply(FileProxy::WriteCallback callback) {
+ PassFile();
+ if (!callback.is_null())
+ std::move(callback).Run(error_, bytes_written_);
+ }
+
+ private:
+ std::unique_ptr<char[]> buffer_;
+ int bytes_to_write_;
+ int bytes_written_;
+ DISALLOW_COPY_AND_ASSIGN(WriteHelper);
+};
+
+} // namespace
+
+FileProxy::FileProxy(TaskRunner* task_runner) : task_runner_(task_runner) {
+}
+
+FileProxy::~FileProxy() {
+ if (file_.IsValid())
+ task_runner_->PostTask(FROM_HERE, BindOnce(&FileDeleter, std::move(file_)));
+}
+
+bool FileProxy::CreateOrOpen(const FilePath& file_path,
+ uint32_t file_flags,
+ StatusCallback callback) {
+ DCHECK(!file_.IsValid());
+ CreateOrOpenHelper* helper = new CreateOrOpenHelper(this, File());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ BindOnce(&CreateOrOpenHelper::RunWork, Unretained(helper), file_path,
+ file_flags),
+ BindOnce(&CreateOrOpenHelper::Reply, Owned(helper), std::move(callback)));
+}
+
+bool FileProxy::CreateTemporary(uint32_t additional_file_flags,
+ CreateTemporaryCallback callback) {
+ DCHECK(!file_.IsValid());
+ CreateTemporaryHelper* helper = new CreateTemporaryHelper(this, File());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ BindOnce(&CreateTemporaryHelper::RunWork, Unretained(helper),
+ additional_file_flags),
+ BindOnce(&CreateTemporaryHelper::Reply, Owned(helper),
+ std::move(callback)));
+}
+
+bool FileProxy::IsValid() const {
+ return file_.IsValid();
+}
+
+void FileProxy::SetFile(File file) {
+ DCHECK(!file_.IsValid());
+ file_ = std::move(file);
+}
+
+File FileProxy::TakeFile() {
+ return std::move(file_);
+}
+
+File FileProxy::DuplicateFile() {
+ return file_.Duplicate();
+}
+
+PlatformFile FileProxy::GetPlatformFile() const {
+ return file_.GetPlatformFile();
+}
+
+bool FileProxy::Close(StatusCallback callback) {
+ DCHECK(file_.IsValid());
+ GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE, BindOnce(&GenericFileHelper::Close, Unretained(helper)),
+ BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
+}
+
+bool FileProxy::GetInfo(GetFileInfoCallback callback) {
+ DCHECK(file_.IsValid());
+ GetInfoHelper* helper = new GetInfoHelper(this, std::move(file_));
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE, BindOnce(&GetInfoHelper::RunWork, Unretained(helper)),
+ BindOnce(&GetInfoHelper::Reply, Owned(helper), std::move(callback)));
+}
+
+bool FileProxy::Read(int64_t offset, int bytes_to_read, ReadCallback callback) {
+ DCHECK(file_.IsValid());
+ if (bytes_to_read < 0)
+ return false;
+
+ ReadHelper* helper = new ReadHelper(this, std::move(file_), bytes_to_read);
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE, BindOnce(&ReadHelper::RunWork, Unretained(helper), offset),
+ BindOnce(&ReadHelper::Reply, Owned(helper), std::move(callback)));
+}
+
+bool FileProxy::Write(int64_t offset,
+ const char* buffer,
+ int bytes_to_write,
+ WriteCallback callback) {
+ DCHECK(file_.IsValid());
+ if (bytes_to_write <= 0 || buffer == nullptr)
+ return false;
+
+ WriteHelper* helper =
+ new WriteHelper(this, std::move(file_), buffer, bytes_to_write);
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE, BindOnce(&WriteHelper::RunWork, Unretained(helper), offset),
+ BindOnce(&WriteHelper::Reply, Owned(helper), std::move(callback)));
+}
+
+bool FileProxy::SetTimes(Time last_access_time,
+ Time last_modified_time,
+ StatusCallback callback) {
+ DCHECK(file_.IsValid());
+ GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ BindOnce(&GenericFileHelper::SetTimes, Unretained(helper),
+ last_access_time, last_modified_time),
+ BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
+}
+
+bool FileProxy::SetLength(int64_t length, StatusCallback callback) {
+ DCHECK(file_.IsValid());
+ GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ BindOnce(&GenericFileHelper::SetLength, Unretained(helper), length),
+ BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
+}
+
+bool FileProxy::Flush(StatusCallback callback) {
+ DCHECK(file_.IsValid());
+ GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE, BindOnce(&GenericFileHelper::Flush, Unretained(helper)),
+ BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
+}
+
+} // namespace base
diff --git a/base/files/file_proxy.h b/base/files/file_proxy.h
new file mode 100644
index 0000000000..d17e4d3b09
--- /dev/null
+++ b/base/files/file_proxy.h
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_FILES_FILE_PROXY_H_
+#define BASE_FILES_FILE_PROXY_H_
+
+#include <stdint.h>
+
+#include "base/base_export.h"
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+
+namespace base {
+
+class TaskRunner;
+class Time;
+
+// This class provides asynchronous access to a File. All methods follow the
+// same rules of the equivalent File method, as they are implemented by bouncing
+// the operation to File using a TaskRunner.
+//
+// This class performs automatic proxying to close the underlying file at
+// destruction.
+//
+// The TaskRunner is in charge of any sequencing of the operations, but a single
+// operation can be proxied at a time, regardless of the use of a callback.
+// In other words, having a sequence like
+//
+// proxy.Write(...);
+// proxy.Write(...);
+//
+// means the second Write will always fail.
+class BASE_EXPORT FileProxy : public SupportsWeakPtr<FileProxy> {
+ public:
+ // This callback is used by methods that report only an error code. It is
+ // valid to pass a null callback to some functions that takes a
+ // StatusCallback, in which case the operation will complete silently.
+ using StatusCallback = OnceCallback<void(File::Error)>;
+ using CreateTemporaryCallback =
+ OnceCallback<void(File::Error, const FilePath&)>;
+ using GetFileInfoCallback =
+ OnceCallback<void(File::Error, const File::Info&)>;
+ using ReadCallback =
+ OnceCallback<void(File::Error, const char* data, int bytes_read)>;
+ using WriteCallback = OnceCallback<void(File::Error, int bytes_written)>;
+
+ FileProxy();
+ explicit FileProxy(TaskRunner* task_runner);
+ ~FileProxy();
+
+ // Creates or opens a file with the given flags. It is invalid to pass a null
+ // callback. If File::FLAG_CREATE is set in |file_flags| it always tries to
+ // create a new file at the given |file_path| and fails if the file already
+ // exists.
+ //
+ // This returns false if task posting to |task_runner| has failed.
+ bool CreateOrOpen(const FilePath& file_path,
+ uint32_t file_flags,
+ StatusCallback callback);
+
+ // Creates a temporary file for writing. The path and an open file are
+ // returned. It is invalid to pass a null callback. The additional file flags
+ // will be added on top of the default file flags which are:
+ // File::FLAG_CREATE_ALWAYS
+ // File::FLAG_WRITE
+ // File::FLAG_TEMPORARY.
+ //
+ // This returns false if task posting to |task_runner| has failed.
+ bool CreateTemporary(uint32_t additional_file_flags,
+ CreateTemporaryCallback callback);
+
+ // Returns true if the underlying |file_| is valid.
+ bool IsValid() const;
+
+ // Returns true if a new file was created (or an old one truncated to zero
+ // length to simulate a new file), and false otherwise.
+ bool created() const { return file_.created(); }
+
+ // Claims ownership of |file|. It is an error to call this method when
+ // IsValid() returns true.
+ void SetFile(File file);
+
+ File TakeFile();
+
+ // Returns a new File object that is a duplicate of the underlying |file_|.
+ // See the comment at File::Duplicate for caveats.
+ File DuplicateFile();
+
+ PlatformFile GetPlatformFile() const;
+
+ // Proxies File::Close. The callback can be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool Close(StatusCallback callback);
+
+ // Proxies File::GetInfo. The callback can't be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool GetInfo(GetFileInfoCallback callback);
+
+ // Proxies File::Read. The callback can't be null.
+ // This returns false if |bytes_to_read| is less than zero, or
+ // if task posting to |task_runner| has failed.
+ bool Read(int64_t offset, int bytes_to_read, ReadCallback callback);
+
+ // Proxies File::Write. The callback can be null.
+ // This returns false if |bytes_to_write| is less than or equal to zero,
+ // if |buffer| is NULL, or if task posting to |task_runner| has failed.
+ bool Write(int64_t offset,
+ const char* buffer,
+ int bytes_to_write,
+ WriteCallback callback);
+
+ // Proxies File::SetTimes. The callback can be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool SetTimes(Time last_access_time,
+ Time last_modified_time,
+ StatusCallback callback);
+
+ // Proxies File::SetLength. The callback can be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool SetLength(int64_t length, StatusCallback callback);
+
+ // Proxies File::Flush. The callback can be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool Flush(StatusCallback callback);
+
+ private:
+ friend class FileHelper;
+ TaskRunner* task_runner() { return task_runner_.get(); }
+
+ scoped_refptr<TaskRunner> task_runner_;
+ File file_;
+ DISALLOW_COPY_AND_ASSIGN(FileProxy);
+};
+
+} // namespace base
+
+#endif // BASE_FILES_FILE_PROXY_H_
diff --git a/base/files/file_proxy_unittest.cc b/base/files/file_proxy_unittest.cc
new file mode 100644
index 0000000000..cb689db2f6
--- /dev/null
+++ b/base/files/file_proxy_unittest.cc
@@ -0,0 +1,401 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_proxy.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+class FileProxyTest : public testing::Test {
+ public:
+ FileProxyTest()
+ : file_thread_("FileProxyTestFileThread"),
+ error_(File::FILE_OK),
+ bytes_written_(-1),
+ weak_factory_(this) {}
+
+ void SetUp() override {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(file_thread_.Start());
+ }
+
+ void DidFinish(File::Error error) {
+ error_ = error;
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ void DidCreateOrOpen(File::Error error) {
+ error_ = error;
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ void DidCreateTemporary(File::Error error,
+ const FilePath& path) {
+ error_ = error;
+ path_ = path;
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ void DidGetFileInfo(File::Error error,
+ const File::Info& file_info) {
+ error_ = error;
+ file_info_ = file_info;
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ void DidRead(File::Error error,
+ const char* data,
+ int bytes_read) {
+ error_ = error;
+ buffer_.resize(bytes_read);
+ memcpy(&buffer_[0], data, bytes_read);
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ void DidWrite(File::Error error,
+ int bytes_written) {
+ error_ = error;
+ bytes_written_ = bytes_written;
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ protected:
+ void CreateProxy(uint32_t flags, FileProxy* proxy) {
+ proxy->CreateOrOpen(
+ TestPath(), flags,
+ BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+ EXPECT_TRUE(proxy->IsValid());
+ }
+
+ TaskRunner* file_task_runner() const {
+ return file_thread_.task_runner().get();
+ }
+ const FilePath& TestDirPath() const { return dir_.GetPath(); }
+ const FilePath TestPath() const { return dir_.GetPath().AppendASCII("test"); }
+
+ ScopedTempDir dir_;
+ MessageLoopForIO message_loop_;
+ Thread file_thread_;
+
+ File::Error error_;
+ FilePath path_;
+ File::Info file_info_;
+ std::vector<char> buffer_;
+ int bytes_written_;
+ WeakPtrFactory<FileProxyTest> weak_factory_;
+};
+
+TEST_F(FileProxyTest, CreateOrOpen_Create) {
+ FileProxy proxy(file_task_runner());
+ proxy.CreateOrOpen(
+ TestPath(), File::FLAG_CREATE | File::FLAG_READ,
+ BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_TRUE(proxy.IsValid());
+ EXPECT_TRUE(proxy.created());
+ EXPECT_TRUE(PathExists(TestPath()));
+}
+
+TEST_F(FileProxyTest, CreateOrOpen_Open) {
+ // Creates a file.
+ base::WriteFile(TestPath(), nullptr, 0);
+ ASSERT_TRUE(PathExists(TestPath()));
+
+ // Opens the created file.
+ FileProxy proxy(file_task_runner());
+ proxy.CreateOrOpen(
+ TestPath(), File::FLAG_OPEN | File::FLAG_READ,
+ BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_TRUE(proxy.IsValid());
+ EXPECT_FALSE(proxy.created());
+}
+
+TEST_F(FileProxyTest, CreateOrOpen_OpenNonExistent) {
+ FileProxy proxy(file_task_runner());
+ proxy.CreateOrOpen(
+ TestPath(), File::FLAG_OPEN | File::FLAG_READ,
+ BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+ EXPECT_EQ(File::FILE_ERROR_NOT_FOUND, error_);
+ EXPECT_FALSE(proxy.IsValid());
+ EXPECT_FALSE(proxy.created());
+ EXPECT_FALSE(PathExists(TestPath()));
+}
+
+TEST_F(FileProxyTest, CreateOrOpen_AbandonedCreate) {
+ bool prev = ThreadRestrictions::SetIOAllowed(false);
+ {
+ FileProxy proxy(file_task_runner());
+ proxy.CreateOrOpen(
+ TestPath(), File::FLAG_CREATE | File::FLAG_READ,
+ BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ }
+ RunLoop().Run();
+ ThreadRestrictions::SetIOAllowed(prev);
+
+ EXPECT_TRUE(PathExists(TestPath()));
+}
+
+TEST_F(FileProxyTest, Close) {
+ // Creates a file.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_CREATE | File::FLAG_WRITE, &proxy);
+
+#if defined(OS_WIN)
+ // This fails on Windows if the file is not closed.
+ EXPECT_FALSE(base::Move(TestPath(), TestDirPath().AppendASCII("new")));
+#endif
+
+ proxy.Close(BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_FALSE(proxy.IsValid());
+
+ // Now it should pass on all platforms.
+ EXPECT_TRUE(base::Move(TestPath(), TestDirPath().AppendASCII("new")));
+}
+
+TEST_F(FileProxyTest, CreateTemporary) {
+ {
+ FileProxy proxy(file_task_runner());
+ proxy.CreateTemporary(0 /* additional_file_flags */,
+ BindOnce(&FileProxyTest::DidCreateTemporary,
+ weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+
+ EXPECT_TRUE(proxy.IsValid());
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_TRUE(PathExists(path_));
+
+ // The file should be writable.
+ proxy.Write(0, "test", 4,
+ BindOnce(&FileProxyTest::DidWrite, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_EQ(4, bytes_written_);
+ }
+
+ // Make sure the written data can be read from the returned path.
+ std::string data;
+ EXPECT_TRUE(ReadFileToString(path_, &data));
+ EXPECT_EQ("test", data);
+
+ // Make sure we can & do delete the created file to prevent leaks on the bots.
+ EXPECT_TRUE(base::DeleteFile(path_, false));
+}
+
+TEST_F(FileProxyTest, SetAndTake) {
+ File file(TestPath(), File::FLAG_CREATE | File::FLAG_READ);
+ ASSERT_TRUE(file.IsValid());
+ FileProxy proxy(file_task_runner());
+ EXPECT_FALSE(proxy.IsValid());
+ proxy.SetFile(std::move(file));
+ EXPECT_TRUE(proxy.IsValid());
+ EXPECT_FALSE(file.IsValid());
+
+ file = proxy.TakeFile();
+ EXPECT_FALSE(proxy.IsValid());
+ EXPECT_TRUE(file.IsValid());
+}
+
+TEST_F(FileProxyTest, DuplicateFile) {
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_CREATE | File::FLAG_WRITE, &proxy);
+ ASSERT_TRUE(proxy.IsValid());
+
+ base::File duplicate = proxy.DuplicateFile();
+ EXPECT_TRUE(proxy.IsValid());
+ EXPECT_TRUE(duplicate.IsValid());
+
+ FileProxy invalid_proxy(file_task_runner());
+ ASSERT_FALSE(invalid_proxy.IsValid());
+
+ base::File invalid_duplicate = invalid_proxy.DuplicateFile();
+ EXPECT_FALSE(invalid_proxy.IsValid());
+ EXPECT_FALSE(invalid_duplicate.IsValid());
+}
+
+TEST_F(FileProxyTest, GetInfo) {
+ // Setup.
+ ASSERT_EQ(4, base::WriteFile(TestPath(), "test", 4));
+ File::Info expected_info;
+ GetFileInfo(TestPath(), &expected_info);
+
+ // Run.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_OPEN | File::FLAG_READ, &proxy);
+ proxy.GetInfo(
+ BindOnce(&FileProxyTest::DidGetFileInfo, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+
+ // Verify.
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_EQ(expected_info.size, file_info_.size);
+ EXPECT_EQ(expected_info.is_directory, file_info_.is_directory);
+ EXPECT_EQ(expected_info.is_symbolic_link, file_info_.is_symbolic_link);
+ EXPECT_EQ(expected_info.last_modified, file_info_.last_modified);
+ EXPECT_EQ(expected_info.creation_time, file_info_.creation_time);
+}
+
+TEST_F(FileProxyTest, Read) {
+ // Setup.
+ const char expected_data[] = "bleh";
+ int expected_bytes = arraysize(expected_data);
+ ASSERT_EQ(expected_bytes,
+ base::WriteFile(TestPath(), expected_data, expected_bytes));
+
+ // Run.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_OPEN | File::FLAG_READ, &proxy);
+
+ proxy.Read(0, 128,
+ BindOnce(&FileProxyTest::DidRead, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+
+ // Verify.
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_EQ(expected_bytes, static_cast<int>(buffer_.size()));
+ for (size_t i = 0; i < buffer_.size(); ++i) {
+ EXPECT_EQ(expected_data[i], buffer_[i]);
+ }
+}
+
+TEST_F(FileProxyTest, WriteAndFlush) {
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_CREATE | File::FLAG_WRITE, &proxy);
+
+ const char data[] = "foo!";
+ int data_bytes = arraysize(data);
+ proxy.Write(0, data, data_bytes,
+ BindOnce(&FileProxyTest::DidWrite, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_EQ(data_bytes, bytes_written_);
+
+ // Flush the written data. (So that the following read should always
+ // succeed. On some platforms it may work with or without this flush.)
+ proxy.Flush(BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+ EXPECT_EQ(File::FILE_OK, error_);
+
+ // Verify the written data.
+ char buffer[10];
+ EXPECT_EQ(data_bytes, base::ReadFile(TestPath(), buffer, data_bytes));
+ for (int i = 0; i < data_bytes; ++i) {
+ EXPECT_EQ(data[i], buffer[i]);
+ }
+}
+
+#if defined(OS_ANDROID) || defined(OS_FUCHSIA)
+// Flaky on Android, see http://crbug.com/489602
+// TODO(crbug.com/851734): Implementation depends on stat, which is not
+// implemented on Fuchsia
+#define MAYBE_SetTimes DISABLED_SetTimes
+#else
+#define MAYBE_SetTimes SetTimes
+#endif
+TEST_F(FileProxyTest, MAYBE_SetTimes) {
+ FileProxy proxy(file_task_runner());
+ CreateProxy(
+ File::FLAG_CREATE | File::FLAG_WRITE | File::FLAG_WRITE_ATTRIBUTES,
+ &proxy);
+
+ Time last_accessed_time = Time::Now() - TimeDelta::FromDays(12345);
+ Time last_modified_time = Time::Now() - TimeDelta::FromHours(98765);
+
+ proxy.SetTimes(
+ last_accessed_time, last_modified_time,
+ BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+ EXPECT_EQ(File::FILE_OK, error_);
+
+ File::Info info;
+ GetFileInfo(TestPath(), &info);
+
+ // The returned values may only have the seconds precision, so we cast
+ // the double values to int here.
+ EXPECT_EQ(static_cast<int>(last_modified_time.ToDoubleT()),
+ static_cast<int>(info.last_modified.ToDoubleT()));
+ EXPECT_EQ(static_cast<int>(last_accessed_time.ToDoubleT()),
+ static_cast<int>(info.last_accessed.ToDoubleT()));
+}
+
+TEST_F(FileProxyTest, SetLength_Shrink) {
+ // Setup.
+ const char kTestData[] = "0123456789";
+ ASSERT_EQ(10, base::WriteFile(TestPath(), kTestData, 10));
+ File::Info info;
+ GetFileInfo(TestPath(), &info);
+ ASSERT_EQ(10, info.size);
+
+ // Run.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_OPEN | File::FLAG_WRITE, &proxy);
+ proxy.SetLength(
+ 7, BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+
+ // Verify.
+ GetFileInfo(TestPath(), &info);
+ ASSERT_EQ(7, info.size);
+
+ char buffer[7];
+ EXPECT_EQ(7, base::ReadFile(TestPath(), buffer, 7));
+ int i = 0;
+ for (; i < 7; ++i)
+ EXPECT_EQ(kTestData[i], buffer[i]);
+}
+
+TEST_F(FileProxyTest, SetLength_Expand) {
+ // Setup.
+ const char kTestData[] = "9876543210";
+ ASSERT_EQ(10, base::WriteFile(TestPath(), kTestData, 10));
+ File::Info info;
+ GetFileInfo(TestPath(), &info);
+ ASSERT_EQ(10, info.size);
+
+ // Run.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_OPEN | File::FLAG_WRITE, &proxy);
+ proxy.SetLength(
+ 53, BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ RunLoop().Run();
+
+ // Verify.
+ GetFileInfo(TestPath(), &info);
+ ASSERT_EQ(53, info.size);
+
+ char buffer[53];
+ EXPECT_EQ(53, base::ReadFile(TestPath(), buffer, 53));
+ int i = 0;
+ for (; i < 10; ++i)
+ EXPECT_EQ(kTestData[i], buffer[i]);
+ for (; i < 53; ++i)
+ EXPECT_EQ(0, buffer[i]);
+}
+
+} // namespace base
diff --git a/base/files/file_util_android.cc b/base/files/file_util_android.cc
new file mode 100644
index 0000000000..b8b3b3720f
--- /dev/null
+++ b/base/files/file_util_android.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+
+namespace base {
+
+bool GetShmemTempDir(bool executable, base::FilePath* path) {
+ return PathService::Get(base::DIR_CACHE, path);
+}
+
+} // namespace base
diff --git a/base/files/file_util_posix.cc b/base/files/file_util_posix.cc
index 066a1d1d5a..f5f32674e0 100644
--- a/base/files/file_util_posix.cc
+++ b/base/files/file_util_posix.cc
@@ -593,8 +593,9 @@ bool GetTempDir(FilePath* path) {
}
#if defined(OS_ANDROID)
+#if 0 // This is for building Chromium browser on Android.
return PathService::Get(DIR_CACHE, path);
-#elif defined(__ANDROID__)
+#endif
*path = FilePath("/data/local/tmp");
return true;
#else
diff --git a/base/files/file_util_unittest.cc b/base/files/file_util_unittest.cc
new file mode 100644
index 0000000000..68abc7c52d
--- /dev/null
+++ b/base/files/file_util_unittest.cc
@@ -0,0 +1,3749 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <fstream>
+#include <initializer_list>
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/guid.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/scoped_environment_variable_override.h"
+#include "base/test/test_file_util.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+#include "testing/platform_test.h"
+
+#if defined(OS_WIN)
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+#include <windows.h>
+#include <winioctl.h>
+#include "base/strings/string_number_conversions.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/win_util.h"
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#if defined(OS_LINUX)
+#include <linux/fs.h>
+#endif
+
+#if defined(OS_ANDROID)
+#include "base/android/content_uri_utils.h"
+#endif
+
+// This macro helps avoid wrapped lines in the test structs.
+#define FPL(x) FILE_PATH_LITERAL(x)
+
+namespace base {
+
+namespace {
+
+const size_t kLargeFileSize = (1 << 16) + 3;
+
+// To test that NormalizeFilePath() deals with NTFS reparse points correctly,
+// we need functions to create and delete reparse points.
+#if defined(OS_WIN)
+typedef struct _REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ };
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+
+// Sets a reparse point. |source| will now point to |target|. Returns true if
+// the call succeeds, false otherwise.
+bool SetReparsePoint(HANDLE source, const FilePath& target_path) {
+ std::wstring kPathPrefix = L"\\??\\";
+ std::wstring target_str;
+ // The juction will not work if the target path does not start with \??\ .
+ if (kPathPrefix != target_path.value().substr(0, kPathPrefix.size()))
+ target_str += kPathPrefix;
+ target_str += target_path.value();
+ const wchar_t* target = target_str.c_str();
+ USHORT size_target = static_cast<USHORT>(wcslen(target)) * sizeof(target[0]);
+ char buffer[2000] = {0};
+ DWORD returned;
+
+ REPARSE_DATA_BUFFER* data = reinterpret_cast<REPARSE_DATA_BUFFER*>(buffer);
+
+ data->ReparseTag = 0xa0000003;
+ memcpy(data->MountPointReparseBuffer.PathBuffer, target, size_target + 2);
+
+ data->MountPointReparseBuffer.SubstituteNameLength = size_target;
+ data->MountPointReparseBuffer.PrintNameOffset = size_target + 2;
+ data->ReparseDataLength = size_target + 4 + 8;
+
+ int data_size = data->ReparseDataLength + 8;
+
+ if (!DeviceIoControl(source, FSCTL_SET_REPARSE_POINT, &buffer, data_size,
+ NULL, 0, &returned, NULL)) {
+ return false;
+ }
+ return true;
+}
+
+// Delete the reparse point referenced by |source|. Returns true if the call
+// succeeds, false otherwise.
+bool DeleteReparsePoint(HANDLE source) {
+ DWORD returned;
+ REPARSE_DATA_BUFFER data = {0};
+ data.ReparseTag = 0xa0000003;
+ if (!DeviceIoControl(source, FSCTL_DELETE_REPARSE_POINT, &data, 8, NULL, 0,
+ &returned, NULL)) {
+ return false;
+ }
+ return true;
+}
+
+// Manages a reparse point for a test.
+class ReparsePoint {
+ public:
+ // Creates a reparse point from |source| (an empty directory) to |target|.
+ ReparsePoint(const FilePath& source, const FilePath& target) {
+ dir_.Set(
+ ::CreateFile(source.value().c_str(),
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory.
+ NULL));
+ created_ = dir_.IsValid() && SetReparsePoint(dir_.Get(), target);
+ }
+
+ ~ReparsePoint() {
+ if (created_)
+ DeleteReparsePoint(dir_.Get());
+ }
+
+ bool IsValid() { return created_; }
+
+ private:
+ win::ScopedHandle dir_;
+ bool created_;
+ DISALLOW_COPY_AND_ASSIGN(ReparsePoint);
+};
+
+#endif
+
+// Fuchsia doesn't support file permissions.
+#if !defined(OS_FUCHSIA)
+#if defined(OS_POSIX)
+// Provide a simple way to change the permissions bits on |path| in tests.
+// ASSERT failures will return, but not stop the test. Caller should wrap
+// calls to this function in ASSERT_NO_FATAL_FAILURE().
+void ChangePosixFilePermissions(const FilePath& path,
+ int mode_bits_to_set,
+ int mode_bits_to_clear) {
+ ASSERT_FALSE(mode_bits_to_set & mode_bits_to_clear)
+ << "Can't set and clear the same bits.";
+
+ int mode = 0;
+ ASSERT_TRUE(GetPosixFilePermissions(path, &mode));
+ mode |= mode_bits_to_set;
+ mode &= ~mode_bits_to_clear;
+ ASSERT_TRUE(SetPosixFilePermissions(path, mode));
+}
+#endif // defined(OS_POSIX)
+
+// Sets the source file to read-only.
+void SetReadOnly(const FilePath& path, bool read_only) {
+#if defined(OS_WIN)
+ // On Windows, it involves setting/removing the 'readonly' bit.
+ DWORD attrs = GetFileAttributes(path.value().c_str());
+ ASSERT_NE(INVALID_FILE_ATTRIBUTES, attrs);
+ ASSERT_TRUE(SetFileAttributes(
+ path.value().c_str(), read_only ? (attrs | FILE_ATTRIBUTE_READONLY)
+ : (attrs & ~FILE_ATTRIBUTE_READONLY)));
+
+ DWORD expected =
+ read_only
+ ? ((attrs & (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_DIRECTORY)) |
+ FILE_ATTRIBUTE_READONLY)
+ : (attrs & (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_DIRECTORY));
+
+ // Ignore FILE_ATTRIBUTE_NOT_CONTENT_INDEXED if present.
+ attrs = GetFileAttributes(path.value().c_str()) &
+ ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
+ ASSERT_EQ(expected, attrs);
+#else
+ // On all other platforms, it involves removing/setting the write bit.
+ mode_t mode = read_only ? S_IRUSR : (S_IRUSR | S_IWUSR);
+ EXPECT_TRUE(SetPosixFilePermissions(
+ path, DirectoryExists(path) ? (mode | S_IXUSR) : mode));
+#endif // defined(OS_WIN)
+}
+
+bool IsReadOnly(const FilePath& path) {
+#if defined(OS_WIN)
+ DWORD attrs = GetFileAttributes(path.value().c_str());
+ EXPECT_NE(INVALID_FILE_ATTRIBUTES, attrs);
+ return attrs & FILE_ATTRIBUTE_READONLY;
+#else
+ int mode = 0;
+ EXPECT_TRUE(GetPosixFilePermissions(path, &mode));
+ return !(mode & S_IWUSR);
+#endif // defined(OS_WIN)
+}
+
+#endif // defined(OS_FUCHSIA)
+
+const wchar_t bogus_content[] = L"I'm cannon fodder.";
+
+const int FILES_AND_DIRECTORIES =
+ FileEnumerator::FILES | FileEnumerator::DIRECTORIES;
+
+// file_util winds up using autoreleased objects on the Mac, so this needs
+// to be a PlatformTest
+class FileUtilTest : public PlatformTest {
+ protected:
+ void SetUp() override {
+ PlatformTest::SetUp();
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ ScopedTempDir temp_dir_;
+};
+
+// Collects all the results from the given file enumerator, and provides an
+// interface to query whether a given file is present.
+class FindResultCollector {
+ public:
+ explicit FindResultCollector(FileEnumerator* enumerator) {
+ FilePath cur_file;
+ while (!(cur_file = enumerator->Next()).value().empty()) {
+ FilePath::StringType path = cur_file.value();
+ // The file should not be returned twice.
+ EXPECT_TRUE(files_.end() == files_.find(path))
+ << "Same file returned twice";
+
+ // Save for later.
+ files_.insert(path);
+ }
+ }
+
+ // Returns true if the enumerator found the file.
+ bool HasFile(const FilePath& file) const {
+ return files_.find(file.value()) != files_.end();
+ }
+
+ int size() {
+ return static_cast<int>(files_.size());
+ }
+
+ private:
+ std::set<FilePath::StringType> files_;
+};
+
+// Simple function to dump some text into a new file.
+void CreateTextFile(const FilePath& filename,
+ const std::wstring& contents) {
+ std::wofstream file;
+ file.open(filename.value().c_str());
+ ASSERT_TRUE(file.is_open());
+ file << contents;
+ file.close();
+}
+
+// Simple function to take out some text from a file.
+std::wstring ReadTextFile(const FilePath& filename) {
+ wchar_t contents[64];
+ std::wifstream file;
+ file.open(filename.value().c_str());
+ EXPECT_TRUE(file.is_open());
+ file.getline(contents, arraysize(contents));
+ file.close();
+ return std::wstring(contents);
+}
+
+// Sets |is_inheritable| to indicate whether or not |stream| is set up to be
+// inerhited into child processes (i.e., HANDLE_FLAG_INHERIT is set on the
+// underlying handle on Windows, or FD_CLOEXEC is not set on the underlying file
+// descriptor on POSIX). Calls to this function must be wrapped with
+// ASSERT_NO_FATAL_FAILURE to properly abort tests in case of fatal failure.
+void GetIsInheritable(FILE* stream, bool* is_inheritable) {
+#if defined(OS_WIN)
+ HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stream)));
+ ASSERT_NE(INVALID_HANDLE_VALUE, handle);
+
+ DWORD info = 0;
+ ASSERT_EQ(TRUE, ::GetHandleInformation(handle, &info));
+ *is_inheritable = ((info & HANDLE_FLAG_INHERIT) != 0);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ int fd = fileno(stream);
+ ASSERT_NE(-1, fd);
+ int flags = fcntl(fd, F_GETFD, 0);
+ ASSERT_NE(-1, flags);
+ *is_inheritable = ((flags & FD_CLOEXEC) == 0);
+#else
+#error Not implemented
+#endif
+}
+
+TEST_F(FileUtilTest, FileAndDirectorySize) {
+ // Create three files of 20, 30 and 3 chars (utf8). ComputeDirectorySize
+ // should return 53 bytes.
+ FilePath file_01 = temp_dir_.GetPath().Append(FPL("The file 01.txt"));
+ CreateTextFile(file_01, L"12345678901234567890");
+ int64_t size_f1 = 0;
+ ASSERT_TRUE(GetFileSize(file_01, &size_f1));
+ EXPECT_EQ(20ll, size_f1);
+
+ FilePath subdir_path = temp_dir_.GetPath().Append(FPL("Level2"));
+ CreateDirectory(subdir_path);
+
+ FilePath file_02 = subdir_path.Append(FPL("The file 02.txt"));
+ CreateTextFile(file_02, L"123456789012345678901234567890");
+ int64_t size_f2 = 0;
+ ASSERT_TRUE(GetFileSize(file_02, &size_f2));
+ EXPECT_EQ(30ll, size_f2);
+
+ FilePath subsubdir_path = subdir_path.Append(FPL("Level3"));
+ CreateDirectory(subsubdir_path);
+
+ FilePath file_03 = subsubdir_path.Append(FPL("The file 03.txt"));
+ CreateTextFile(file_03, L"123");
+
+ int64_t computed_size = ComputeDirectorySize(temp_dir_.GetPath());
+ EXPECT_EQ(size_f1 + size_f2 + 3, computed_size);
+}
+
+TEST_F(FileUtilTest, NormalizeFilePathBasic) {
+ // Create a directory under the test dir. Because we create it,
+ // we know it is not a link.
+ FilePath file_a_path = temp_dir_.GetPath().Append(FPL("file_a"));
+ FilePath dir_path = temp_dir_.GetPath().Append(FPL("dir"));
+ FilePath file_b_path = dir_path.Append(FPL("file_b"));
+ CreateDirectory(dir_path);
+
+ FilePath normalized_file_a_path, normalized_file_b_path;
+ ASSERT_FALSE(PathExists(file_a_path));
+ ASSERT_FALSE(NormalizeFilePath(file_a_path, &normalized_file_a_path))
+ << "NormalizeFilePath() should fail on nonexistent paths.";
+
+ CreateTextFile(file_a_path, bogus_content);
+ ASSERT_TRUE(PathExists(file_a_path));
+ ASSERT_TRUE(NormalizeFilePath(file_a_path, &normalized_file_a_path));
+
+ CreateTextFile(file_b_path, bogus_content);
+ ASSERT_TRUE(PathExists(file_b_path));
+ ASSERT_TRUE(NormalizeFilePath(file_b_path, &normalized_file_b_path));
+
+ // Beacuse this test created |dir_path|, we know it is not a link
+ // or junction. So, the real path of the directory holding file a
+ // must be the parent of the path holding file b.
+ ASSERT_TRUE(normalized_file_a_path.DirName()
+ .IsParent(normalized_file_b_path.DirName()));
+}
+
+#if defined(OS_WIN)
+
+TEST_F(FileUtilTest, NormalizeFilePathReparsePoints) {
+ // Build the following directory structure:
+ //
+ // temp_dir
+ // |-> base_a
+ // | |-> sub_a
+ // | |-> file.txt
+ // | |-> long_name___... (Very long name.)
+ // | |-> sub_long
+ // | |-> deep.txt
+ // |-> base_b
+ // |-> to_sub_a (reparse point to temp_dir\base_a\sub_a)
+ // |-> to_base_b (reparse point to temp_dir\base_b)
+ // |-> to_sub_long (reparse point to temp_dir\sub_a\long_name_\sub_long)
+
+ FilePath base_a = temp_dir_.GetPath().Append(FPL("base_a"));
+#if defined(OS_WIN)
+ // TEMP can have a lower case drive letter.
+ string16 temp_base_a = base_a.value();
+ ASSERT_FALSE(temp_base_a.empty());
+ *temp_base_a.begin() = ToUpperASCII(*temp_base_a.begin());
+ base_a = FilePath(temp_base_a);
+#endif
+ ASSERT_TRUE(CreateDirectory(base_a));
+
+ FilePath sub_a = base_a.Append(FPL("sub_a"));
+ ASSERT_TRUE(CreateDirectory(sub_a));
+
+ FilePath file_txt = sub_a.Append(FPL("file.txt"));
+ CreateTextFile(file_txt, bogus_content);
+
+ // Want a directory whose name is long enough to make the path to the file
+ // inside just under MAX_PATH chars. This will be used to test that when
+ // a junction expands to a path over MAX_PATH chars in length,
+ // NormalizeFilePath() fails without crashing.
+ FilePath sub_long_rel(FPL("sub_long"));
+ FilePath deep_txt(FPL("deep.txt"));
+
+ int target_length = MAX_PATH;
+ target_length -= (sub_a.value().length() + 1); // +1 for the sepperator '\'.
+ target_length -= (sub_long_rel.Append(deep_txt).value().length() + 1);
+ // Without making the path a bit shorter, CreateDirectory() fails.
+ // the resulting path is still long enough to hit the failing case in
+ // NormalizePath().
+ const int kCreateDirLimit = 4;
+ target_length -= kCreateDirLimit;
+ FilePath::StringType long_name_str = FPL("long_name_");
+ long_name_str.resize(target_length, '_');
+
+ FilePath long_name = sub_a.Append(FilePath(long_name_str));
+ FilePath deep_file = long_name.Append(sub_long_rel).Append(deep_txt);
+ ASSERT_EQ(static_cast<size_t>(MAX_PATH - kCreateDirLimit),
+ deep_file.value().length());
+
+ FilePath sub_long = deep_file.DirName();
+ ASSERT_TRUE(CreateDirectory(sub_long));
+ CreateTextFile(deep_file, bogus_content);
+
+ FilePath base_b = temp_dir_.GetPath().Append(FPL("base_b"));
+ ASSERT_TRUE(CreateDirectory(base_b));
+
+ FilePath to_sub_a = base_b.Append(FPL("to_sub_a"));
+ ASSERT_TRUE(CreateDirectory(to_sub_a));
+ FilePath normalized_path;
+ {
+ ReparsePoint reparse_to_sub_a(to_sub_a, sub_a);
+ ASSERT_TRUE(reparse_to_sub_a.IsValid());
+
+ FilePath to_base_b = base_b.Append(FPL("to_base_b"));
+ ASSERT_TRUE(CreateDirectory(to_base_b));
+ ReparsePoint reparse_to_base_b(to_base_b, base_b);
+ ASSERT_TRUE(reparse_to_base_b.IsValid());
+
+ FilePath to_sub_long = base_b.Append(FPL("to_sub_long"));
+ ASSERT_TRUE(CreateDirectory(to_sub_long));
+ ReparsePoint reparse_to_sub_long(to_sub_long, sub_long);
+ ASSERT_TRUE(reparse_to_sub_long.IsValid());
+
+ // Normalize a junction free path: base_a\sub_a\file.txt .
+ ASSERT_TRUE(NormalizeFilePath(file_txt, &normalized_path));
+ ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str());
+
+ // Check that the path base_b\to_sub_a\file.txt can be normalized to exclude
+ // the junction to_sub_a.
+ ASSERT_TRUE(NormalizeFilePath(to_sub_a.Append(FPL("file.txt")),
+ &normalized_path));
+ ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str());
+
+ // Check that the path base_b\to_base_b\to_base_b\to_sub_a\file.txt can be
+ // normalized to exclude junctions to_base_b and to_sub_a .
+ ASSERT_TRUE(NormalizeFilePath(base_b.Append(FPL("to_base_b"))
+ .Append(FPL("to_base_b"))
+ .Append(FPL("to_sub_a"))
+ .Append(FPL("file.txt")),
+ &normalized_path));
+ ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str());
+
+ // A long enough path will cause NormalizeFilePath() to fail. Make a long
+ // path using to_base_b many times, and check that paths long enough to fail
+ // do not cause a crash.
+ FilePath long_path = base_b;
+ const int kLengthLimit = MAX_PATH + 200;
+ while (long_path.value().length() <= kLengthLimit) {
+ long_path = long_path.Append(FPL("to_base_b"));
+ }
+ long_path = long_path.Append(FPL("to_sub_a"))
+ .Append(FPL("file.txt"));
+
+ ASSERT_FALSE(NormalizeFilePath(long_path, &normalized_path));
+
+ // Normalizing the junction to deep.txt should fail, because the expanded
+ // path to deep.txt is longer than MAX_PATH.
+ ASSERT_FALSE(NormalizeFilePath(to_sub_long.Append(deep_txt),
+ &normalized_path));
+
+ // Delete the reparse points, and see that NormalizeFilePath() fails
+ // to traverse them.
+ }
+
+ ASSERT_FALSE(NormalizeFilePath(to_sub_a.Append(FPL("file.txt")),
+ &normalized_path));
+}
+
+TEST_F(FileUtilTest, DevicePathToDriveLetter) {
+ // Get a drive letter.
+ string16 real_drive_letter =
+ ToUpperASCII(temp_dir_.GetPath().value().substr(0, 2));
+ if (!isalpha(real_drive_letter[0]) || ':' != real_drive_letter[1]) {
+ LOG(ERROR) << "Can't get a drive letter to test with.";
+ return;
+ }
+
+ // Get the NT style path to that drive.
+ wchar_t device_path[MAX_PATH] = {'\0'};
+ ASSERT_TRUE(
+ ::QueryDosDevice(real_drive_letter.c_str(), device_path, MAX_PATH));
+ FilePath actual_device_path(device_path);
+ FilePath win32_path;
+
+ // Run DevicePathToDriveLetterPath() on the NT style path we got from
+ // QueryDosDevice(). Expect the drive letter we started with.
+ ASSERT_TRUE(DevicePathToDriveLetterPath(actual_device_path, &win32_path));
+ ASSERT_EQ(real_drive_letter, win32_path.value());
+
+ // Add some directories to the path. Expect those extra path componenets
+ // to be preserved.
+ FilePath kRelativePath(FPL("dir1\\dir2\\file.txt"));
+ ASSERT_TRUE(DevicePathToDriveLetterPath(
+ actual_device_path.Append(kRelativePath),
+ &win32_path));
+ EXPECT_EQ(FilePath(real_drive_letter + L"\\").Append(kRelativePath).value(),
+ win32_path.value());
+
+ // Deform the real path so that it is invalid by removing the last four
+ // characters. The way windows names devices that are hard disks
+ // (\Device\HardDiskVolume${NUMBER}) guarantees that the string is longer
+ // than three characters. The only way the truncated string could be a
+ // real drive is if more than 10^3 disks are mounted:
+ // \Device\HardDiskVolume10000 would be truncated to \Device\HardDiskVolume1
+ // Check that DevicePathToDriveLetterPath fails.
+ int path_length = actual_device_path.value().length();
+ int new_length = path_length - 4;
+ ASSERT_LT(0, new_length);
+ FilePath prefix_of_real_device_path(
+ actual_device_path.value().substr(0, new_length));
+ ASSERT_FALSE(DevicePathToDriveLetterPath(prefix_of_real_device_path,
+ &win32_path));
+
+ ASSERT_FALSE(DevicePathToDriveLetterPath(
+ prefix_of_real_device_path.Append(kRelativePath),
+ &win32_path));
+
+ // Deform the real path so that it is invalid by adding some characters. For
+ // example, if C: maps to \Device\HardDiskVolume8, then we simulate a
+ // request for the drive letter whose native path is
+ // \Device\HardDiskVolume812345 . We assume such a device does not exist,
+ // because drives are numbered in order and mounting 112345 hard disks will
+ // never happen.
+ const FilePath::StringType kExtraChars = FPL("12345");
+
+ FilePath real_device_path_plus_numbers(
+ actual_device_path.value() + kExtraChars);
+
+ ASSERT_FALSE(DevicePathToDriveLetterPath(
+ real_device_path_plus_numbers,
+ &win32_path));
+
+ ASSERT_FALSE(DevicePathToDriveLetterPath(
+ real_device_path_plus_numbers.Append(kRelativePath),
+ &win32_path));
+}
+
+TEST_F(FileUtilTest, CreateTemporaryFileInDirLongPathTest) {
+ // Test that CreateTemporaryFileInDir() creates a path and returns a long path
+ // if it is available. This test requires that:
+ // - the filesystem at |temp_dir_| supports long filenames.
+ // - the account has FILE_LIST_DIRECTORY permission for all ancestor
+ // directories of |temp_dir_|.
+ const FilePath::CharType kLongDirName[] = FPL("A long path");
+ const FilePath::CharType kTestSubDirName[] = FPL("test");
+ FilePath long_test_dir = temp_dir_.GetPath().Append(kLongDirName);
+ ASSERT_TRUE(CreateDirectory(long_test_dir));
+
+ // kLongDirName is not a 8.3 component. So GetShortName() should give us a
+ // different short name.
+ WCHAR path_buffer[MAX_PATH];
+ DWORD path_buffer_length = GetShortPathName(long_test_dir.value().c_str(),
+ path_buffer, MAX_PATH);
+ ASSERT_LT(path_buffer_length, DWORD(MAX_PATH));
+ ASSERT_NE(DWORD(0), path_buffer_length);
+ FilePath short_test_dir(path_buffer);
+ ASSERT_STRNE(kLongDirName, short_test_dir.BaseName().value().c_str());
+
+ FilePath temp_file;
+ ASSERT_TRUE(CreateTemporaryFileInDir(short_test_dir, &temp_file));
+ EXPECT_STREQ(kLongDirName, temp_file.DirName().BaseName().value().c_str());
+ EXPECT_TRUE(PathExists(temp_file));
+
+ // Create a subdirectory of |long_test_dir| and make |long_test_dir|
+ // unreadable. We should still be able to create a temp file in the
+ // subdirectory, but we won't be able to determine the long path for it. This
+ // mimics the environment that some users run where their user profiles reside
+ // in a location where the don't have full access to the higher level
+ // directories. (Note that this assumption is true for NTFS, but not for some
+ // network file systems. E.g. AFS).
+ FilePath access_test_dir = long_test_dir.Append(kTestSubDirName);
+ ASSERT_TRUE(CreateDirectory(access_test_dir));
+ FilePermissionRestorer long_test_dir_restorer(long_test_dir);
+ ASSERT_TRUE(MakeFileUnreadable(long_test_dir));
+
+ // Use the short form of the directory to create a temporary filename.
+ ASSERT_TRUE(CreateTemporaryFileInDir(
+ short_test_dir.Append(kTestSubDirName), &temp_file));
+ EXPECT_TRUE(PathExists(temp_file));
+ EXPECT_TRUE(short_test_dir.IsParent(temp_file.DirName()));
+
+ // Check that the long path can't be determined for |temp_file|.
+ path_buffer_length = GetLongPathName(temp_file.value().c_str(),
+ path_buffer, MAX_PATH);
+ EXPECT_EQ(DWORD(0), path_buffer_length);
+}
+
+#endif // defined(OS_WIN)
+
+#if defined(OS_POSIX)
+
+TEST_F(FileUtilTest, CreateAndReadSymlinks) {
+ FilePath link_from = temp_dir_.GetPath().Append(FPL("from_file"));
+ FilePath link_to = temp_dir_.GetPath().Append(FPL("to_file"));
+ CreateTextFile(link_to, bogus_content);
+
+ ASSERT_TRUE(CreateSymbolicLink(link_to, link_from))
+ << "Failed to create file symlink.";
+
+ // If we created the link properly, we should be able to read the contents
+ // through it.
+ EXPECT_EQ(bogus_content, ReadTextFile(link_from));
+
+ FilePath result;
+ ASSERT_TRUE(ReadSymbolicLink(link_from, &result));
+ EXPECT_EQ(link_to.value(), result.value());
+
+ // Link to a directory.
+ link_from = temp_dir_.GetPath().Append(FPL("from_dir"));
+ link_to = temp_dir_.GetPath().Append(FPL("to_dir"));
+ ASSERT_TRUE(CreateDirectory(link_to));
+ ASSERT_TRUE(CreateSymbolicLink(link_to, link_from))
+ << "Failed to create directory symlink.";
+
+ // Test failures.
+ EXPECT_FALSE(CreateSymbolicLink(link_to, link_to));
+ EXPECT_FALSE(ReadSymbolicLink(link_to, &result));
+ FilePath missing = temp_dir_.GetPath().Append(FPL("missing"));
+ EXPECT_FALSE(ReadSymbolicLink(missing, &result));
+}
+
+// The following test of NormalizeFilePath() require that we create a symlink.
+// This can not be done on Windows before Vista. On Vista, creating a symlink
+// requires privilege "SeCreateSymbolicLinkPrivilege".
+// TODO(skerner): Investigate the possibility of giving base_unittests the
+// privileges required to create a symlink.
+TEST_F(FileUtilTest, NormalizeFilePathSymlinks) {
+ // Link one file to another.
+ FilePath link_from = temp_dir_.GetPath().Append(FPL("from_file"));
+ FilePath link_to = temp_dir_.GetPath().Append(FPL("to_file"));
+ CreateTextFile(link_to, bogus_content);
+
+ ASSERT_TRUE(CreateSymbolicLink(link_to, link_from))
+ << "Failed to create file symlink.";
+
+ // Check that NormalizeFilePath sees the link.
+ FilePath normalized_path;
+ ASSERT_TRUE(NormalizeFilePath(link_from, &normalized_path));
+ EXPECT_NE(link_from, link_to);
+ EXPECT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value());
+ EXPECT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value());
+
+ // Link to a directory.
+ link_from = temp_dir_.GetPath().Append(FPL("from_dir"));
+ link_to = temp_dir_.GetPath().Append(FPL("to_dir"));
+ ASSERT_TRUE(CreateDirectory(link_to));
+ ASSERT_TRUE(CreateSymbolicLink(link_to, link_from))
+ << "Failed to create directory symlink.";
+
+ EXPECT_FALSE(NormalizeFilePath(link_from, &normalized_path))
+ << "Links to directories should return false.";
+
+ // Test that a loop in the links causes NormalizeFilePath() to return false.
+ link_from = temp_dir_.GetPath().Append(FPL("link_a"));
+ link_to = temp_dir_.GetPath().Append(FPL("link_b"));
+ ASSERT_TRUE(CreateSymbolicLink(link_to, link_from))
+ << "Failed to create loop symlink a.";
+ ASSERT_TRUE(CreateSymbolicLink(link_from, link_to))
+ << "Failed to create loop symlink b.";
+
+ // Infinite loop!
+ EXPECT_FALSE(NormalizeFilePath(link_from, &normalized_path));
+}
+
+TEST_F(FileUtilTest, DeleteSymlinkToExistentFile) {
+ // Create a file.
+ FilePath file_name = temp_dir_.GetPath().Append(FPL("Test DeleteFile 2.txt"));
+ CreateTextFile(file_name, bogus_content);
+ ASSERT_TRUE(PathExists(file_name));
+
+ // Create a symlink to the file.
+ FilePath file_link = temp_dir_.GetPath().Append("file_link_2");
+ ASSERT_TRUE(CreateSymbolicLink(file_name, file_link))
+ << "Failed to create symlink.";
+
+ // Delete the symbolic link.
+ EXPECT_TRUE(DeleteFile(file_link, false));
+
+ // Make sure original file is not deleted.
+ EXPECT_FALSE(PathExists(file_link));
+ EXPECT_TRUE(PathExists(file_name));
+}
+
+TEST_F(FileUtilTest, DeleteSymlinkToNonExistentFile) {
+ // Create a non-existent file path.
+ FilePath non_existent =
+ temp_dir_.GetPath().Append(FPL("Test DeleteFile 3.txt"));
+ EXPECT_FALSE(PathExists(non_existent));
+
+ // Create a symlink to the non-existent file.
+ FilePath file_link = temp_dir_.GetPath().Append("file_link_3");
+ ASSERT_TRUE(CreateSymbolicLink(non_existent, file_link))
+ << "Failed to create symlink.";
+
+ // Make sure the symbolic link is exist.
+ EXPECT_TRUE(IsLink(file_link));
+ EXPECT_FALSE(PathExists(file_link));
+
+ // Delete the symbolic link.
+ EXPECT_TRUE(DeleteFile(file_link, false));
+
+ // Make sure the symbolic link is deleted.
+ EXPECT_FALSE(IsLink(file_link));
+}
+
+TEST_F(FileUtilTest, CopyFileFollowsSymlinks) {
+ FilePath link_from = temp_dir_.GetPath().Append(FPL("from_file"));
+ FilePath link_to = temp_dir_.GetPath().Append(FPL("to_file"));
+ CreateTextFile(link_to, bogus_content);
+
+ ASSERT_TRUE(CreateSymbolicLink(link_to, link_from));
+
+ // If we created the link properly, we should be able to read the contents
+ // through it.
+ EXPECT_EQ(bogus_content, ReadTextFile(link_from));
+
+ FilePath result;
+ ASSERT_TRUE(ReadSymbolicLink(link_from, &result));
+ EXPECT_EQ(link_to.value(), result.value());
+
+ // Create another file and copy it to |link_from|.
+ FilePath src_file = temp_dir_.GetPath().Append(FPL("src.txt"));
+ const std::wstring file_contents(L"Gooooooooooooooooooooogle");
+ CreateTextFile(src_file, file_contents);
+ ASSERT_TRUE(CopyFile(src_file, link_from));
+
+ // Make sure |link_from| is still a symlink, and |link_to| has been written to
+ // by CopyFile().
+ EXPECT_TRUE(IsLink(link_from));
+ EXPECT_EQ(file_contents, ReadTextFile(link_from));
+ EXPECT_EQ(file_contents, ReadTextFile(link_to));
+}
+
+TEST_F(FileUtilTest, ChangeFilePermissionsAndRead) {
+ // Create a file path.
+ FilePath file_name =
+ temp_dir_.GetPath().Append(FPL("Test Readable File.txt"));
+ EXPECT_FALSE(PathExists(file_name));
+
+ static constexpr char kData[] = "hello";
+ static constexpr int kDataSize = sizeof(kData) - 1;
+ char buffer[kDataSize];
+
+ // Write file.
+ EXPECT_EQ(kDataSize, WriteFile(file_name, kData, kDataSize));
+ EXPECT_TRUE(PathExists(file_name));
+
+ // Make sure the file is readable.
+ int32_t mode = 0;
+ EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode));
+ EXPECT_TRUE(mode & FILE_PERMISSION_READ_BY_USER);
+
+ // Get rid of the read permission.
+ EXPECT_TRUE(SetPosixFilePermissions(file_name, 0u));
+ EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode));
+ EXPECT_FALSE(mode & FILE_PERMISSION_READ_BY_USER);
+ // Make sure the file can't be read.
+ EXPECT_EQ(-1, ReadFile(file_name, buffer, kDataSize));
+
+ // Give the read permission.
+ EXPECT_TRUE(SetPosixFilePermissions(file_name, FILE_PERMISSION_READ_BY_USER));
+ EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode));
+ EXPECT_TRUE(mode & FILE_PERMISSION_READ_BY_USER);
+ // Make sure the file can be read.
+ EXPECT_EQ(kDataSize, ReadFile(file_name, buffer, kDataSize));
+
+ // Delete the file.
+ EXPECT_TRUE(DeleteFile(file_name, false));
+ EXPECT_FALSE(PathExists(file_name));
+}
+
+TEST_F(FileUtilTest, ChangeFilePermissionsAndWrite) {
+ // Create a file path.
+ FilePath file_name =
+ temp_dir_.GetPath().Append(FPL("Test Readable File.txt"));
+ EXPECT_FALSE(PathExists(file_name));
+
+ const std::string kData("hello");
+
+ // Write file.
+ EXPECT_EQ(static_cast<int>(kData.length()),
+ WriteFile(file_name, kData.data(), kData.length()));
+ EXPECT_TRUE(PathExists(file_name));
+
+ // Make sure the file is writable.
+ int mode = 0;
+ EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode));
+ EXPECT_TRUE(mode & FILE_PERMISSION_WRITE_BY_USER);
+ EXPECT_TRUE(PathIsWritable(file_name));
+
+ // Get rid of the write permission.
+ EXPECT_TRUE(SetPosixFilePermissions(file_name, 0u));
+ EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode));
+ EXPECT_FALSE(mode & FILE_PERMISSION_WRITE_BY_USER);
+ // Make sure the file can't be write.
+ EXPECT_EQ(-1, WriteFile(file_name, kData.data(), kData.length()));
+ EXPECT_FALSE(PathIsWritable(file_name));
+
+ // Give read permission.
+ EXPECT_TRUE(SetPosixFilePermissions(file_name,
+ FILE_PERMISSION_WRITE_BY_USER));
+ EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode));
+ EXPECT_TRUE(mode & FILE_PERMISSION_WRITE_BY_USER);
+ // Make sure the file can be write.
+ EXPECT_EQ(static_cast<int>(kData.length()),
+ WriteFile(file_name, kData.data(), kData.length()));
+ EXPECT_TRUE(PathIsWritable(file_name));
+
+ // Delete the file.
+ EXPECT_TRUE(DeleteFile(file_name, false));
+ EXPECT_FALSE(PathExists(file_name));
+}
+
+TEST_F(FileUtilTest, ChangeDirectoryPermissionsAndEnumerate) {
+ // Create a directory path.
+ FilePath subdir_path = temp_dir_.GetPath().Append(FPL("PermissionTest1"));
+ CreateDirectory(subdir_path);
+ ASSERT_TRUE(PathExists(subdir_path));
+
+ // Create a dummy file to enumerate.
+ FilePath file_name = subdir_path.Append(FPL("Test Readable File.txt"));
+ EXPECT_FALSE(PathExists(file_name));
+ const std::string kData("hello");
+ EXPECT_EQ(static_cast<int>(kData.length()),
+ WriteFile(file_name, kData.data(), kData.length()));
+ EXPECT_TRUE(PathExists(file_name));
+
+ // Make sure the directory has the all permissions.
+ int mode = 0;
+ EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode));
+ EXPECT_EQ(FILE_PERMISSION_USER_MASK, mode & FILE_PERMISSION_USER_MASK);
+
+ // Get rid of the permissions from the directory.
+ EXPECT_TRUE(SetPosixFilePermissions(subdir_path, 0u));
+ EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode));
+ EXPECT_FALSE(mode & FILE_PERMISSION_USER_MASK);
+
+ // Make sure the file in the directory can't be enumerated.
+ FileEnumerator f1(subdir_path, true, FileEnumerator::FILES);
+ EXPECT_TRUE(PathExists(subdir_path));
+ FindResultCollector c1(&f1);
+ EXPECT_EQ(0, c1.size());
+ EXPECT_FALSE(GetPosixFilePermissions(file_name, &mode));
+
+ // Give the permissions to the directory.
+ EXPECT_TRUE(SetPosixFilePermissions(subdir_path, FILE_PERMISSION_USER_MASK));
+ EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode));
+ EXPECT_EQ(FILE_PERMISSION_USER_MASK, mode & FILE_PERMISSION_USER_MASK);
+
+ // Make sure the file in the directory can be enumerated.
+ FileEnumerator f2(subdir_path, true, FileEnumerator::FILES);
+ FindResultCollector c2(&f2);
+ EXPECT_TRUE(c2.HasFile(file_name));
+ EXPECT_EQ(1, c2.size());
+
+ // Delete the file.
+ EXPECT_TRUE(DeleteFile(subdir_path, true));
+ EXPECT_FALSE(PathExists(subdir_path));
+}
+
+TEST_F(FileUtilTest, ExecutableExistsInPath) {
+ // Create two directories that we will put in our PATH
+ const FilePath::CharType kDir1[] = FPL("dir1");
+ const FilePath::CharType kDir2[] = FPL("dir2");
+
+ FilePath dir1 = temp_dir_.GetPath().Append(kDir1);
+ FilePath dir2 = temp_dir_.GetPath().Append(kDir2);
+ ASSERT_TRUE(CreateDirectory(dir1));
+ ASSERT_TRUE(CreateDirectory(dir2));
+
+ test::ScopedEnvironmentVariableOverride scoped_env(
+ "PATH", dir1.value() + ":" + dir2.value());
+ ASSERT_TRUE(scoped_env.IsOverridden());
+
+ const FilePath::CharType kRegularFileName[] = FPL("regular_file");
+ const FilePath::CharType kExeFileName[] = FPL("exe");
+ const FilePath::CharType kDneFileName[] = FPL("does_not_exist");
+
+ const FilePath kExePath = dir1.Append(kExeFileName);
+ const FilePath kRegularFilePath = dir2.Append(kRegularFileName);
+
+ // Write file.
+ const std::string kData("hello");
+ ASSERT_EQ(static_cast<int>(kData.length()),
+ WriteFile(kExePath, kData.data(), kData.length()));
+ ASSERT_TRUE(PathExists(kExePath));
+ ASSERT_EQ(static_cast<int>(kData.length()),
+ WriteFile(kRegularFilePath, kData.data(), kData.length()));
+ ASSERT_TRUE(PathExists(kRegularFilePath));
+
+ ASSERT_TRUE(SetPosixFilePermissions(dir1.Append(kExeFileName),
+ FILE_PERMISSION_EXECUTE_BY_USER));
+
+ EXPECT_TRUE(ExecutableExistsInPath(scoped_env.GetEnv(), kExeFileName));
+ EXPECT_FALSE(ExecutableExistsInPath(scoped_env.GetEnv(), kRegularFileName));
+ EXPECT_FALSE(ExecutableExistsInPath(scoped_env.GetEnv(), kDneFileName));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryPermissions) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create some regular files under the directory with various permissions.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Reggy-1.txt"));
+ CreateTextFile(file_name_from, L"Mordecai");
+ ASSERT_TRUE(PathExists(file_name_from));
+ ASSERT_TRUE(SetPosixFilePermissions(file_name_from, 0755));
+
+ FilePath file2_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Reggy-2.txt"));
+ CreateTextFile(file2_name_from, L"Rigby");
+ ASSERT_TRUE(PathExists(file2_name_from));
+ ASSERT_TRUE(SetPosixFilePermissions(file2_name_from, 0777));
+
+ FilePath file3_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Reggy-3.txt"));
+ CreateTextFile(file3_name_from, L"Benson");
+ ASSERT_TRUE(PathExists(file3_name_from));
+ ASSERT_TRUE(SetPosixFilePermissions(file3_name_from, 0400));
+
+ // Copy the directory recursively.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Reggy-1.txt"));
+ FilePath file2_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Reggy-2.txt"));
+ FilePath file3_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Reggy-3.txt"));
+
+ ASSERT_FALSE(PathExists(dir_name_to));
+
+ EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, true));
+ ASSERT_TRUE(PathExists(file_name_to));
+ ASSERT_TRUE(PathExists(file2_name_to));
+ ASSERT_TRUE(PathExists(file3_name_to));
+
+ int mode = 0;
+ int expected_mode;
+ ASSERT_TRUE(GetPosixFilePermissions(file_name_to, &mode));
+#if defined(OS_MACOSX)
+ expected_mode = 0755;
+#elif defined(OS_CHROMEOS)
+ expected_mode = 0644;
+#else
+ expected_mode = 0600;
+#endif
+ EXPECT_EQ(expected_mode, mode);
+
+ ASSERT_TRUE(GetPosixFilePermissions(file2_name_to, &mode));
+#if defined(OS_MACOSX)
+ expected_mode = 0755;
+#elif defined(OS_CHROMEOS)
+ expected_mode = 0644;
+#else
+ expected_mode = 0600;
+#endif
+ EXPECT_EQ(expected_mode, mode);
+
+ ASSERT_TRUE(GetPosixFilePermissions(file3_name_to, &mode));
+#if defined(OS_MACOSX)
+ expected_mode = 0600;
+#elif defined(OS_CHROMEOS)
+ expected_mode = 0644;
+#else
+ expected_mode = 0600;
+#endif
+ EXPECT_EQ(expected_mode, mode);
+}
+
+TEST_F(FileUtilTest, CopyDirectoryPermissionsOverExistingFile) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Reggy-1.txt"));
+ CreateTextFile(file_name_from, L"Mordecai");
+ ASSERT_TRUE(PathExists(file_name_from));
+ ASSERT_TRUE(SetPosixFilePermissions(file_name_from, 0644));
+
+ // Create a directory.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ CreateDirectory(dir_name_to);
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ // Create a file under the directory with wider permissions.
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Reggy-1.txt"));
+ CreateTextFile(file_name_to, L"Rigby");
+ ASSERT_TRUE(PathExists(file_name_to));
+ ASSERT_TRUE(SetPosixFilePermissions(file_name_to, 0777));
+
+ // Ensure that when we copy the directory, the file contents are copied
+ // but the permissions on the destination are left alone.
+ EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, false));
+ ASSERT_TRUE(PathExists(file_name_to));
+ ASSERT_EQ(L"Mordecai", ReadTextFile(file_name_to));
+
+ int mode = 0;
+ ASSERT_TRUE(GetPosixFilePermissions(file_name_to, &mode));
+ EXPECT_EQ(0777, mode);
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExclDoesNotOverwrite) {
+ // Create source directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Reggy-1.txt"));
+ CreateTextFile(file_name_from, L"Mordecai");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create destination directory.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ CreateDirectory(dir_name_to);
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ // Create a file under the directory with the same name.
+ FilePath file_name_to = dir_name_to.Append(FILE_PATH_LITERAL("Reggy-1.txt"));
+ CreateTextFile(file_name_to, L"Rigby");
+ ASSERT_TRUE(PathExists(file_name_to));
+
+ // Ensure that copying failed and the file was not overwritten.
+ EXPECT_FALSE(CopyDirectoryExcl(dir_name_from, dir_name_to, false));
+ ASSERT_TRUE(PathExists(file_name_to));
+ ASSERT_EQ(L"Rigby", ReadTextFile(file_name_to));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExclDirectoryOverExistingFile) {
+ // Create source directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a subdirectory.
+ FilePath subdir_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Subsub"));
+ CreateDirectory(subdir_name_from);
+ ASSERT_TRUE(PathExists(subdir_name_from));
+
+ // Create destination directory.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ CreateDirectory(dir_name_to);
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ // Create a regular file under the directory with the same name.
+ FilePath file_name_to = dir_name_to.Append(FILE_PATH_LITERAL("Subsub"));
+ CreateTextFile(file_name_to, L"Rigby");
+ ASSERT_TRUE(PathExists(file_name_to));
+
+ // Ensure that copying failed and the file was not overwritten.
+ EXPECT_FALSE(CopyDirectoryExcl(dir_name_from, dir_name_to, false));
+ ASSERT_TRUE(PathExists(file_name_to));
+ ASSERT_EQ(L"Rigby", ReadTextFile(file_name_to));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExclDirectoryOverExistingDirectory) {
+ // Create source directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a subdirectory.
+ FilePath subdir_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Subsub"));
+ CreateDirectory(subdir_name_from);
+ ASSERT_TRUE(PathExists(subdir_name_from));
+
+ // Create destination directory.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ CreateDirectory(dir_name_to);
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ // Create a subdirectory under the directory with the same name.
+ FilePath subdir_name_to = dir_name_to.Append(FILE_PATH_LITERAL("Subsub"));
+ CreateDirectory(subdir_name_to);
+ ASSERT_TRUE(PathExists(subdir_name_to));
+
+ // Ensure that copying failed and the file was not overwritten.
+ EXPECT_FALSE(CopyDirectoryExcl(dir_name_from, dir_name_to, false));
+}
+
+TEST_F(FileUtilTest, CopyFileExecutablePermission) {
+ FilePath src = temp_dir_.GetPath().Append(FPL("src.txt"));
+ const std::wstring file_contents(L"Gooooooooooooooooooooogle");
+ CreateTextFile(src, file_contents);
+
+ ASSERT_TRUE(SetPosixFilePermissions(src, 0755));
+ int mode = 0;
+ ASSERT_TRUE(GetPosixFilePermissions(src, &mode));
+ EXPECT_EQ(0755, mode);
+
+ FilePath dst = temp_dir_.GetPath().Append(FPL("dst.txt"));
+ ASSERT_TRUE(CopyFile(src, dst));
+ EXPECT_EQ(file_contents, ReadTextFile(dst));
+
+ ASSERT_TRUE(GetPosixFilePermissions(dst, &mode));
+ int expected_mode;
+#if defined(OS_MACOSX)
+ expected_mode = 0755;
+#elif defined(OS_CHROMEOS)
+ expected_mode = 0644;
+#else
+ expected_mode = 0600;
+#endif
+ EXPECT_EQ(expected_mode, mode);
+ ASSERT_TRUE(DeleteFile(dst, false));
+
+ ASSERT_TRUE(SetPosixFilePermissions(src, 0777));
+ ASSERT_TRUE(GetPosixFilePermissions(src, &mode));
+ EXPECT_EQ(0777, mode);
+
+ ASSERT_TRUE(CopyFile(src, dst));
+ EXPECT_EQ(file_contents, ReadTextFile(dst));
+
+ ASSERT_TRUE(GetPosixFilePermissions(dst, &mode));
+#if defined(OS_MACOSX)
+ expected_mode = 0755;
+#elif defined(OS_CHROMEOS)
+ expected_mode = 0644;
+#else
+ expected_mode = 0600;
+#endif
+ EXPECT_EQ(expected_mode, mode);
+ ASSERT_TRUE(DeleteFile(dst, false));
+
+ ASSERT_TRUE(SetPosixFilePermissions(src, 0400));
+ ASSERT_TRUE(GetPosixFilePermissions(src, &mode));
+ EXPECT_EQ(0400, mode);
+
+ ASSERT_TRUE(CopyFile(src, dst));
+ EXPECT_EQ(file_contents, ReadTextFile(dst));
+
+ ASSERT_TRUE(GetPosixFilePermissions(dst, &mode));
+#if defined(OS_MACOSX)
+ expected_mode = 0600;
+#elif defined(OS_CHROMEOS)
+ expected_mode = 0644;
+#else
+ expected_mode = 0600;
+#endif
+ EXPECT_EQ(expected_mode, mode);
+
+ // This time, do not delete |dst|. Instead set its permissions to 0777.
+ ASSERT_TRUE(SetPosixFilePermissions(dst, 0777));
+ ASSERT_TRUE(GetPosixFilePermissions(dst, &mode));
+ EXPECT_EQ(0777, mode);
+
+ // Overwrite it and check the permissions again.
+ ASSERT_TRUE(CopyFile(src, dst));
+ EXPECT_EQ(file_contents, ReadTextFile(dst));
+ ASSERT_TRUE(GetPosixFilePermissions(dst, &mode));
+ EXPECT_EQ(0777, mode);
+}
+
+#endif // defined(OS_POSIX)
+
+#if !defined(OS_FUCHSIA)
+
+TEST_F(FileUtilTest, CopyFileACL) {
+ // While FileUtilTest.CopyFile asserts the content is correctly copied over,
+ // this test case asserts the access control bits are meeting expectations in
+ // CopyFile().
+ FilePath src = temp_dir_.GetPath().Append(FILE_PATH_LITERAL("src.txt"));
+ const std::wstring file_contents(L"Gooooooooooooooooooooogle");
+ CreateTextFile(src, file_contents);
+
+ // Set the source file to read-only.
+ ASSERT_FALSE(IsReadOnly(src));
+ SetReadOnly(src, true);
+ ASSERT_TRUE(IsReadOnly(src));
+
+ // Copy the file.
+ FilePath dst = temp_dir_.GetPath().Append(FILE_PATH_LITERAL("dst.txt"));
+ ASSERT_TRUE(CopyFile(src, dst));
+ EXPECT_EQ(file_contents, ReadTextFile(dst));
+
+ ASSERT_FALSE(IsReadOnly(dst));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryACL) {
+ // Create source directories.
+ FilePath src = temp_dir_.GetPath().Append(FILE_PATH_LITERAL("src"));
+ FilePath src_subdir = src.Append(FILE_PATH_LITERAL("subdir"));
+ CreateDirectory(src_subdir);
+ ASSERT_TRUE(PathExists(src_subdir));
+
+ // Create a file under the directory.
+ FilePath src_file = src.Append(FILE_PATH_LITERAL("src.txt"));
+ CreateTextFile(src_file, L"Gooooooooooooooooooooogle");
+ SetReadOnly(src_file, true);
+ ASSERT_TRUE(IsReadOnly(src_file));
+
+ // Make directory read-only.
+ SetReadOnly(src_subdir, true);
+ ASSERT_TRUE(IsReadOnly(src_subdir));
+
+ // Copy the directory recursively.
+ FilePath dst = temp_dir_.GetPath().Append(FILE_PATH_LITERAL("dst"));
+ FilePath dst_file = dst.Append(FILE_PATH_LITERAL("src.txt"));
+ EXPECT_TRUE(CopyDirectory(src, dst, true));
+
+ FilePath dst_subdir = dst.Append(FILE_PATH_LITERAL("subdir"));
+ ASSERT_FALSE(IsReadOnly(dst_subdir));
+ ASSERT_FALSE(IsReadOnly(dst_file));
+
+ // Give write permissions to allow deletion.
+ SetReadOnly(src_subdir, false);
+ ASSERT_FALSE(IsReadOnly(src_subdir));
+}
+
+#endif // !defined(OS_FUCHSIA)
+
+TEST_F(FileUtilTest, DeleteNonExistent) {
+ FilePath non_existent =
+ temp_dir_.GetPath().AppendASCII("bogus_file_dne.foobar");
+ ASSERT_FALSE(PathExists(non_existent));
+
+ EXPECT_TRUE(DeleteFile(non_existent, false));
+ ASSERT_FALSE(PathExists(non_existent));
+ EXPECT_TRUE(DeleteFile(non_existent, true));
+ ASSERT_FALSE(PathExists(non_existent));
+}
+
+TEST_F(FileUtilTest, DeleteNonExistentWithNonExistentParent) {
+ FilePath non_existent = temp_dir_.GetPath().AppendASCII("bogus_topdir");
+ non_existent = non_existent.AppendASCII("bogus_subdir");
+ ASSERT_FALSE(PathExists(non_existent));
+
+ EXPECT_TRUE(DeleteFile(non_existent, false));
+ ASSERT_FALSE(PathExists(non_existent));
+ EXPECT_TRUE(DeleteFile(non_existent, true));
+ ASSERT_FALSE(PathExists(non_existent));
+}
+
+TEST_F(FileUtilTest, DeleteFile) {
+ // Create a file
+ FilePath file_name = temp_dir_.GetPath().Append(FPL("Test DeleteFile 1.txt"));
+ CreateTextFile(file_name, bogus_content);
+ ASSERT_TRUE(PathExists(file_name));
+
+ // Make sure it's deleted
+ EXPECT_TRUE(DeleteFile(file_name, false));
+ EXPECT_FALSE(PathExists(file_name));
+
+ // Test recursive case, create a new file
+ file_name = temp_dir_.GetPath().Append(FPL("Test DeleteFile 2.txt"));
+ CreateTextFile(file_name, bogus_content);
+ ASSERT_TRUE(PathExists(file_name));
+
+ // Make sure it's deleted
+ EXPECT_TRUE(DeleteFile(file_name, true));
+ EXPECT_FALSE(PathExists(file_name));
+}
+
+#if defined(OS_WIN)
+// Tests that the Delete function works for wild cards, especially
+// with the recursion flag. Also coincidentally tests PathExists.
+// TODO(erikkay): see if anyone's actually using this feature of the API
+TEST_F(FileUtilTest, DeleteWildCard) {
+ // Create a file and a directory
+ FilePath file_name =
+ temp_dir_.GetPath().Append(FPL("Test DeleteWildCard.txt"));
+ CreateTextFile(file_name, bogus_content);
+ ASSERT_TRUE(PathExists(file_name));
+
+ FilePath subdir_path = temp_dir_.GetPath().Append(FPL("DeleteWildCardDir"));
+ CreateDirectory(subdir_path);
+ ASSERT_TRUE(PathExists(subdir_path));
+
+ // Create the wildcard path
+ FilePath directory_contents = temp_dir_.GetPath();
+ directory_contents = directory_contents.Append(FPL("*"));
+
+ // Delete non-recursively and check that only the file is deleted
+ EXPECT_TRUE(DeleteFile(directory_contents, false));
+ EXPECT_FALSE(PathExists(file_name));
+ EXPECT_TRUE(PathExists(subdir_path));
+
+ // Delete recursively and make sure all contents are deleted
+ EXPECT_TRUE(DeleteFile(directory_contents, true));
+ EXPECT_FALSE(PathExists(file_name));
+ EXPECT_FALSE(PathExists(subdir_path));
+}
+
+// TODO(erikkay): see if anyone's actually using this feature of the API
+TEST_F(FileUtilTest, DeleteNonExistantWildCard) {
+ // Create a file and a directory
+ FilePath subdir_path =
+ temp_dir_.GetPath().Append(FPL("DeleteNonExistantWildCard"));
+ CreateDirectory(subdir_path);
+ ASSERT_TRUE(PathExists(subdir_path));
+
+ // Create the wildcard path
+ FilePath directory_contents = subdir_path;
+ directory_contents = directory_contents.Append(FPL("*"));
+
+ // Delete non-recursively and check nothing got deleted
+ EXPECT_TRUE(DeleteFile(directory_contents, false));
+ EXPECT_TRUE(PathExists(subdir_path));
+
+ // Delete recursively and check nothing got deleted
+ EXPECT_TRUE(DeleteFile(directory_contents, true));
+ EXPECT_TRUE(PathExists(subdir_path));
+}
+#endif
+
+// Tests non-recursive Delete() for a directory.
+TEST_F(FileUtilTest, DeleteDirNonRecursive) {
+ // Create a subdirectory and put a file and two directories inside.
+ FilePath test_subdir =
+ temp_dir_.GetPath().Append(FPL("DeleteDirNonRecursive"));
+ CreateDirectory(test_subdir);
+ ASSERT_TRUE(PathExists(test_subdir));
+
+ FilePath file_name = test_subdir.Append(FPL("Test DeleteDir.txt"));
+ CreateTextFile(file_name, bogus_content);
+ ASSERT_TRUE(PathExists(file_name));
+
+ FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1"));
+ CreateDirectory(subdir_path1);
+ ASSERT_TRUE(PathExists(subdir_path1));
+
+ FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2"));
+ CreateDirectory(subdir_path2);
+ ASSERT_TRUE(PathExists(subdir_path2));
+
+ // Delete non-recursively and check that the empty dir got deleted
+ EXPECT_TRUE(DeleteFile(subdir_path2, false));
+ EXPECT_FALSE(PathExists(subdir_path2));
+
+ // Delete non-recursively and check that nothing got deleted
+ EXPECT_FALSE(DeleteFile(test_subdir, false));
+ EXPECT_TRUE(PathExists(test_subdir));
+ EXPECT_TRUE(PathExists(file_name));
+ EXPECT_TRUE(PathExists(subdir_path1));
+}
+
+// Tests recursive Delete() for a directory.
+TEST_F(FileUtilTest, DeleteDirRecursive) {
+ // Create a subdirectory and put a file and two directories inside.
+ FilePath test_subdir = temp_dir_.GetPath().Append(FPL("DeleteDirRecursive"));
+ CreateDirectory(test_subdir);
+ ASSERT_TRUE(PathExists(test_subdir));
+
+ FilePath file_name = test_subdir.Append(FPL("Test DeleteDirRecursive.txt"));
+ CreateTextFile(file_name, bogus_content);
+ ASSERT_TRUE(PathExists(file_name));
+
+ FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1"));
+ CreateDirectory(subdir_path1);
+ ASSERT_TRUE(PathExists(subdir_path1));
+
+ FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2"));
+ CreateDirectory(subdir_path2);
+ ASSERT_TRUE(PathExists(subdir_path2));
+
+ // Delete recursively and check that the empty dir got deleted
+ EXPECT_TRUE(DeleteFile(subdir_path2, true));
+ EXPECT_FALSE(PathExists(subdir_path2));
+
+ // Delete recursively and check that everything got deleted
+ EXPECT_TRUE(DeleteFile(test_subdir, true));
+ EXPECT_FALSE(PathExists(file_name));
+ EXPECT_FALSE(PathExists(subdir_path1));
+ EXPECT_FALSE(PathExists(test_subdir));
+}
+
+// Tests recursive Delete() for a directory.
+TEST_F(FileUtilTest, DeleteDirRecursiveWithOpenFile) {
+ // Create a subdirectory and put a file and two directories inside.
+ FilePath test_subdir = temp_dir_.GetPath().Append(FPL("DeleteWithOpenFile"));
+ CreateDirectory(test_subdir);
+ ASSERT_TRUE(PathExists(test_subdir));
+
+ FilePath file_name1 = test_subdir.Append(FPL("Undeletebable File1.txt"));
+ File file1(file_name1,
+ File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE);
+ ASSERT_TRUE(PathExists(file_name1));
+
+ FilePath file_name2 = test_subdir.Append(FPL("Deleteable File2.txt"));
+ CreateTextFile(file_name2, bogus_content);
+ ASSERT_TRUE(PathExists(file_name2));
+
+ FilePath file_name3 = test_subdir.Append(FPL("Undeletebable File3.txt"));
+ File file3(file_name3,
+ File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE);
+ ASSERT_TRUE(PathExists(file_name3));
+
+#if defined(OS_LINUX)
+ // On Windows, holding the file open in sufficient to make it un-deletable.
+ // The POSIX code is verifiable on Linux by creating an "immutable" file but
+ // this is best-effort because it's not supported by all file systems. Both
+ // files will have the same flags so no need to get them individually.
+ int flags;
+ bool file_attrs_supported =
+ ioctl(file1.GetPlatformFile(), FS_IOC_GETFLAGS, &flags) == 0;
+ // Some filesystems (e.g. tmpfs) don't support file attributes.
+ if (file_attrs_supported) {
+ flags |= FS_IMMUTABLE_FL;
+ ioctl(file1.GetPlatformFile(), FS_IOC_SETFLAGS, &flags);
+ ioctl(file3.GetPlatformFile(), FS_IOC_SETFLAGS, &flags);
+ }
+#endif
+
+ // Delete recursively and check that at least the second file got deleted.
+ // This ensures that un-deletable files don't impact those that can be.
+ DeleteFile(test_subdir, true);
+ EXPECT_FALSE(PathExists(file_name2));
+
+#if defined(OS_LINUX)
+ // Make sure that the test can clean up after itself.
+ if (file_attrs_supported) {
+ flags &= ~FS_IMMUTABLE_FL;
+ ioctl(file1.GetPlatformFile(), FS_IOC_SETFLAGS, &flags);
+ ioctl(file3.GetPlatformFile(), FS_IOC_SETFLAGS, &flags);
+ }
+#endif
+}
+
+TEST_F(FileUtilTest, MoveFileNew) {
+ // Create a file
+ FilePath file_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Move_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // The destination.
+ FilePath file_name_to = temp_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("Move_Test_File_Destination.txt"));
+ ASSERT_FALSE(PathExists(file_name_to));
+
+ EXPECT_TRUE(Move(file_name_from, file_name_to));
+
+ // Check everything has been moved.
+ EXPECT_FALSE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(file_name_to));
+}
+
+TEST_F(FileUtilTest, MoveFileExists) {
+ // Create a file
+ FilePath file_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Move_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // The destination name.
+ FilePath file_name_to = temp_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("Move_Test_File_Destination.txt"));
+ CreateTextFile(file_name_to, L"Old file content");
+ ASSERT_TRUE(PathExists(file_name_to));
+
+ EXPECT_TRUE(Move(file_name_from, file_name_to));
+
+ // Check everything has been moved.
+ EXPECT_FALSE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(file_name_to));
+ EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to));
+}
+
+TEST_F(FileUtilTest, MoveFileDirExists) {
+ // Create a file
+ FilePath file_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Move_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // The destination directory
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Destination"));
+ CreateDirectory(dir_name_to);
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ EXPECT_FALSE(Move(file_name_from, dir_name_to));
+}
+
+
+TEST_F(FileUtilTest, MoveNew) {
+ // Create a directory
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Move_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory
+ FilePath txt_file_name(FILE_PATH_LITERAL("Move_Test_File.txt"));
+ FilePath file_name_from = dir_name_from.Append(txt_file_name);
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Move the directory.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Move_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Move_Test_File.txt"));
+
+ ASSERT_FALSE(PathExists(dir_name_to));
+
+ EXPECT_TRUE(Move(dir_name_from, dir_name_to));
+
+ // Check everything has been moved.
+ EXPECT_FALSE(PathExists(dir_name_from));
+ EXPECT_FALSE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+
+ // Test path traversal.
+ file_name_from = dir_name_to.Append(txt_file_name);
+ file_name_to = dir_name_to.Append(FILE_PATH_LITERAL(".."));
+ file_name_to = file_name_to.Append(txt_file_name);
+ EXPECT_FALSE(Move(file_name_from, file_name_to));
+ EXPECT_TRUE(PathExists(file_name_from));
+ EXPECT_FALSE(PathExists(file_name_to));
+ EXPECT_TRUE(internal::MoveUnsafe(file_name_from, file_name_to));
+ EXPECT_FALSE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(file_name_to));
+}
+
+TEST_F(FileUtilTest, MoveExist) {
+ // Create a directory
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Move_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Move_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Move the directory
+ FilePath dir_name_exists =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Destination"));
+
+ FilePath dir_name_to =
+ dir_name_exists.Append(FILE_PATH_LITERAL("Move_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Move_Test_File.txt"));
+
+ // Create the destination directory.
+ CreateDirectory(dir_name_exists);
+ ASSERT_TRUE(PathExists(dir_name_exists));
+
+ EXPECT_TRUE(Move(dir_name_from, dir_name_to));
+
+ // Check everything has been moved.
+ EXPECT_FALSE(PathExists(dir_name_from));
+ EXPECT_FALSE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryRecursivelyNew) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create a subdirectory.
+ FilePath subdir_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Subdir"));
+ CreateDirectory(subdir_name_from);
+ ASSERT_TRUE(PathExists(subdir_name_from));
+
+ // Create a file under the subdirectory.
+ FilePath file_name2_from =
+ subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name2_from));
+
+ // Copy the directory recursively.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ FilePath subdir_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Subdir"));
+ FilePath file_name2_to =
+ subdir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+
+ ASSERT_FALSE(PathExists(dir_name_to));
+
+ EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, true));
+
+ // Check everything has been copied.
+ EXPECT_TRUE(PathExists(dir_name_from));
+ EXPECT_TRUE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(subdir_name_from));
+ EXPECT_TRUE(PathExists(file_name2_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+ EXPECT_TRUE(PathExists(subdir_name_to));
+ EXPECT_TRUE(PathExists(file_name2_to));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryRecursivelyExists) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create a subdirectory.
+ FilePath subdir_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Subdir"));
+ CreateDirectory(subdir_name_from);
+ ASSERT_TRUE(PathExists(subdir_name_from));
+
+ // Create a file under the subdirectory.
+ FilePath file_name2_from =
+ subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name2_from));
+
+ // Copy the directory recursively.
+ FilePath dir_name_exists =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Destination"));
+
+ FilePath dir_name_to =
+ dir_name_exists.Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ FilePath subdir_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Subdir"));
+ FilePath file_name2_to =
+ subdir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+
+ // Create the destination directory.
+ CreateDirectory(dir_name_exists);
+ ASSERT_TRUE(PathExists(dir_name_exists));
+
+ EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_exists, true));
+
+ // Check everything has been copied.
+ EXPECT_TRUE(PathExists(dir_name_from));
+ EXPECT_TRUE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(subdir_name_from));
+ EXPECT_TRUE(PathExists(file_name2_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+ EXPECT_TRUE(PathExists(subdir_name_to));
+ EXPECT_TRUE(PathExists(file_name2_to));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryNew) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create a subdirectory.
+ FilePath subdir_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Subdir"));
+ CreateDirectory(subdir_name_from);
+ ASSERT_TRUE(PathExists(subdir_name_from));
+
+ // Create a file under the subdirectory.
+ FilePath file_name2_from =
+ subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name2_from));
+
+ // Copy the directory not recursively.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ FilePath subdir_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Subdir"));
+
+ ASSERT_FALSE(PathExists(dir_name_to));
+
+ EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, false));
+
+ // Check everything has been copied.
+ EXPECT_TRUE(PathExists(dir_name_from));
+ EXPECT_TRUE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(subdir_name_from));
+ EXPECT_TRUE(PathExists(file_name2_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+ EXPECT_FALSE(PathExists(subdir_name_to));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExists) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create a subdirectory.
+ FilePath subdir_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Subdir"));
+ CreateDirectory(subdir_name_from);
+ ASSERT_TRUE(PathExists(subdir_name_from));
+
+ // Create a file under the subdirectory.
+ FilePath file_name2_from =
+ subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name2_from));
+
+ // Copy the directory not recursively.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ FilePath subdir_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Subdir"));
+
+ // Create the destination directory.
+ CreateDirectory(dir_name_to);
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, false));
+
+ // Check everything has been copied.
+ EXPECT_TRUE(PathExists(dir_name_from));
+ EXPECT_TRUE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(subdir_name_from));
+ EXPECT_TRUE(PathExists(file_name2_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+ EXPECT_FALSE(PathExists(subdir_name_to));
+}
+
+TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToNew) {
+ // Create a file
+ FilePath file_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // The destination name
+ FilePath file_name_to = temp_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("Copy_Test_File_Destination.txt"));
+ ASSERT_FALSE(PathExists(file_name_to));
+
+ EXPECT_TRUE(CopyDirectory(file_name_from, file_name_to, true));
+
+ // Check the has been copied
+ EXPECT_TRUE(PathExists(file_name_to));
+}
+
+TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToExisting) {
+ // Create a file
+ FilePath file_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // The destination name
+ FilePath file_name_to = temp_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("Copy_Test_File_Destination.txt"));
+ CreateTextFile(file_name_to, L"Old file content");
+ ASSERT_TRUE(PathExists(file_name_to));
+
+ EXPECT_TRUE(CopyDirectory(file_name_from, file_name_to, true));
+
+ // Check the has been copied
+ EXPECT_TRUE(PathExists(file_name_to));
+ EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to));
+}
+
+TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToExistingDirectory) {
+ // Create a file
+ FilePath file_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // The destination
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Destination"));
+ CreateDirectory(dir_name_to);
+ ASSERT_TRUE(PathExists(dir_name_to));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+
+ EXPECT_TRUE(CopyDirectory(file_name_from, dir_name_to, true));
+
+ // Check the has been copied
+ EXPECT_TRUE(PathExists(file_name_to));
+}
+
+TEST_F(FileUtilTest, CopyFileFailureWithCopyDirectoryExcl) {
+ // Create a file
+ FilePath file_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Make a destination file.
+ FilePath file_name_to = temp_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("Copy_Test_File_Destination.txt"));
+ CreateTextFile(file_name_to, L"Old file content");
+ ASSERT_TRUE(PathExists(file_name_to));
+
+ // Overwriting the destination should fail.
+ EXPECT_FALSE(CopyDirectoryExcl(file_name_from, file_name_to, true));
+ EXPECT_EQ(L"Old file content", ReadTextFile(file_name_to));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryWithTrailingSeparators) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Copy the directory recursively.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+
+ // Create from path with trailing separators.
+#if defined(OS_WIN)
+ FilePath from_path =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir\\\\\\"));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ FilePath from_path =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir///"));
+#endif
+
+ EXPECT_TRUE(CopyDirectory(from_path, dir_name_to, true));
+
+ // Check everything has been copied.
+ EXPECT_TRUE(PathExists(dir_name_from));
+ EXPECT_TRUE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+}
+
+#if defined(OS_POSIX)
+TEST_F(FileUtilTest, CopyDirectoryWithNonRegularFiles) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_from));
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create a symbolic link under the directory pointing to that file.
+ FilePath symlink_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Symlink"));
+ ASSERT_TRUE(CreateSymbolicLink(file_name_from, symlink_name_from));
+ ASSERT_TRUE(PathExists(symlink_name_from));
+
+ // Create a fifo under the directory.
+ FilePath fifo_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Fifo"));
+ ASSERT_EQ(0, mkfifo(fifo_name_from.value().c_str(), 0644));
+ ASSERT_TRUE(PathExists(fifo_name_from));
+
+ // Copy the directory.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ FilePath symlink_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Symlink"));
+ FilePath fifo_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Fifo"));
+
+ ASSERT_FALSE(PathExists(dir_name_to));
+
+ EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, false));
+
+ // Check that only directories and regular files are copied.
+ EXPECT_TRUE(PathExists(dir_name_from));
+ EXPECT_TRUE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(symlink_name_from));
+ EXPECT_TRUE(PathExists(fifo_name_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+ EXPECT_FALSE(PathExists(symlink_name_to));
+ EXPECT_FALSE(PathExists(fifo_name_to));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExclFileOverSymlink) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_from));
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create a destination directory with a symlink of the same name.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_to));
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ FilePath symlink_target =
+ dir_name_to.Append(FILE_PATH_LITERAL("Symlink_Target.txt"));
+ CreateTextFile(symlink_target, L"asdf");
+ ASSERT_TRUE(PathExists(symlink_target));
+
+ FilePath symlink_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ ASSERT_TRUE(CreateSymbolicLink(symlink_target, symlink_name_to));
+ ASSERT_TRUE(PathExists(symlink_name_to));
+
+ // Check that copying fails.
+ EXPECT_FALSE(CopyDirectoryExcl(dir_name_from, dir_name_to, false));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExclDirectoryOverSymlink) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_from));
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a subdirectory.
+ FilePath subdir_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Subsub"));
+ CreateDirectory(subdir_name_from);
+ ASSERT_TRUE(PathExists(subdir_name_from));
+
+ // Create a destination directory with a symlink of the same name.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_to));
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ FilePath symlink_target = dir_name_to.Append(FILE_PATH_LITERAL("Subsub"));
+ CreateTextFile(symlink_target, L"asdf");
+ ASSERT_TRUE(PathExists(symlink_target));
+
+ FilePath symlink_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ ASSERT_TRUE(CreateSymbolicLink(symlink_target, symlink_name_to));
+ ASSERT_TRUE(PathExists(symlink_name_to));
+
+ // Check that copying fails.
+ EXPECT_FALSE(CopyDirectoryExcl(dir_name_from, dir_name_to, false));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExclFileOverDanglingSymlink) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_from));
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create a destination directory with a dangling symlink of the same name.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_to));
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ FilePath symlink_target =
+ dir_name_to.Append(FILE_PATH_LITERAL("Symlink_Target.txt"));
+ CreateTextFile(symlink_target, L"asdf");
+ ASSERT_TRUE(PathExists(symlink_target));
+
+ FilePath symlink_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ ASSERT_TRUE(CreateSymbolicLink(symlink_target, symlink_name_to));
+ ASSERT_TRUE(PathExists(symlink_name_to));
+ ASSERT_TRUE(DeleteFile(symlink_target, false));
+
+ // Check that copying fails and that no file was created for the symlink's
+ // referent.
+ EXPECT_FALSE(CopyDirectoryExcl(dir_name_from, dir_name_to, false));
+ EXPECT_FALSE(PathExists(symlink_target));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExclDirectoryOverDanglingSymlink) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_from));
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a subdirectory.
+ FilePath subdir_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Subsub"));
+ CreateDirectory(subdir_name_from);
+ ASSERT_TRUE(PathExists(subdir_name_from));
+
+ // Create a destination directory with a dangling symlink of the same name.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_to));
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ FilePath symlink_target =
+ dir_name_to.Append(FILE_PATH_LITERAL("Symlink_Target.txt"));
+ CreateTextFile(symlink_target, L"asdf");
+ ASSERT_TRUE(PathExists(symlink_target));
+
+ FilePath symlink_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ ASSERT_TRUE(CreateSymbolicLink(symlink_target, symlink_name_to));
+ ASSERT_TRUE(PathExists(symlink_name_to));
+ ASSERT_TRUE(DeleteFile(symlink_target, false));
+
+ // Check that copying fails and that no directory was created for the
+ // symlink's referent.
+ EXPECT_FALSE(CopyDirectoryExcl(dir_name_from, dir_name_to, false));
+ EXPECT_FALSE(PathExists(symlink_target));
+}
+
+TEST_F(FileUtilTest, CopyDirectoryExclFileOverFifo) {
+ // Create a directory.
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_from));
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory.
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Create a destination directory with a fifo of the same name.
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_To_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_to));
+ ASSERT_TRUE(PathExists(dir_name_to));
+
+ FilePath fifo_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ ASSERT_EQ(0, mkfifo(fifo_name_to.value().c_str(), 0644));
+ ASSERT_TRUE(PathExists(fifo_name_to));
+
+ // Check that copying fails.
+ EXPECT_FALSE(CopyDirectoryExcl(dir_name_from, dir_name_to, false));
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(FileUtilTest, CopyFile) {
+ // Create a directory
+ FilePath dir_name_from =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Copy_From_Subdir"));
+ ASSERT_TRUE(CreateDirectory(dir_name_from));
+ ASSERT_TRUE(DirectoryExists(dir_name_from));
+
+ // Create a file under the directory
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt"));
+ const std::wstring file_contents(L"Gooooooooooooooooooooogle");
+ CreateTextFile(file_name_from, file_contents);
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Copy the file.
+ FilePath dest_file = dir_name_from.Append(FILE_PATH_LITERAL("DestFile.txt"));
+ ASSERT_TRUE(CopyFile(file_name_from, dest_file));
+
+ // Try to copy the file to another location using '..' in the path.
+ FilePath dest_file2(dir_name_from);
+ dest_file2 = dest_file2.AppendASCII("..");
+ dest_file2 = dest_file2.AppendASCII("DestFile.txt");
+ ASSERT_FALSE(CopyFile(file_name_from, dest_file2));
+
+ FilePath dest_file2_test(dir_name_from);
+ dest_file2_test = dest_file2_test.DirName();
+ dest_file2_test = dest_file2_test.AppendASCII("DestFile.txt");
+
+ // Check expected copy results.
+ EXPECT_TRUE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(dest_file));
+ EXPECT_EQ(file_contents, ReadTextFile(dest_file));
+ EXPECT_FALSE(PathExists(dest_file2_test));
+ EXPECT_FALSE(PathExists(dest_file2));
+
+ // Change |file_name_from| contents.
+ const std::wstring new_file_contents(L"Moogle");
+ CreateTextFile(file_name_from, new_file_contents);
+ ASSERT_TRUE(PathExists(file_name_from));
+ EXPECT_EQ(new_file_contents, ReadTextFile(file_name_from));
+
+ // Overwrite |dest_file|.
+ ASSERT_TRUE(CopyFile(file_name_from, dest_file));
+ EXPECT_TRUE(PathExists(dest_file));
+ EXPECT_EQ(new_file_contents, ReadTextFile(dest_file));
+
+ // Create another directory.
+ FilePath dest_dir = temp_dir_.GetPath().Append(FPL("dest_dir"));
+ ASSERT_TRUE(CreateDirectory(dest_dir));
+ EXPECT_TRUE(DirectoryExists(dest_dir));
+ EXPECT_TRUE(IsDirectoryEmpty(dest_dir));
+
+ // Make sure CopyFile() cannot overwrite a directory.
+ ASSERT_FALSE(CopyFile(file_name_from, dest_dir));
+ EXPECT_TRUE(DirectoryExists(dest_dir));
+ EXPECT_TRUE(IsDirectoryEmpty(dest_dir));
+}
+
+// file_util winds up using autoreleased objects on the Mac, so this needs
+// to be a PlatformTest.
+typedef PlatformTest ReadOnlyFileUtilTest;
+
+TEST_F(ReadOnlyFileUtilTest, ContentsEqual) {
+ FilePath data_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir));
+ data_dir = data_dir.AppendASCII("file_util");
+ ASSERT_TRUE(PathExists(data_dir));
+
+ FilePath original_file =
+ data_dir.Append(FILE_PATH_LITERAL("original.txt"));
+ FilePath same_file =
+ data_dir.Append(FILE_PATH_LITERAL("same.txt"));
+ FilePath same_length_file =
+ data_dir.Append(FILE_PATH_LITERAL("same_length.txt"));
+ FilePath different_file =
+ data_dir.Append(FILE_PATH_LITERAL("different.txt"));
+ FilePath different_first_file =
+ data_dir.Append(FILE_PATH_LITERAL("different_first.txt"));
+ FilePath different_last_file =
+ data_dir.Append(FILE_PATH_LITERAL("different_last.txt"));
+ FilePath empty1_file =
+ data_dir.Append(FILE_PATH_LITERAL("empty1.txt"));
+ FilePath empty2_file =
+ data_dir.Append(FILE_PATH_LITERAL("empty2.txt"));
+ FilePath shortened_file =
+ data_dir.Append(FILE_PATH_LITERAL("shortened.txt"));
+ FilePath binary_file =
+ data_dir.Append(FILE_PATH_LITERAL("binary_file.bin"));
+ FilePath binary_file_same =
+ data_dir.Append(FILE_PATH_LITERAL("binary_file_same.bin"));
+ FilePath binary_file_diff =
+ data_dir.Append(FILE_PATH_LITERAL("binary_file_diff.bin"));
+
+ EXPECT_TRUE(ContentsEqual(original_file, original_file));
+ EXPECT_TRUE(ContentsEqual(original_file, same_file));
+ EXPECT_FALSE(ContentsEqual(original_file, same_length_file));
+ EXPECT_FALSE(ContentsEqual(original_file, different_file));
+ EXPECT_FALSE(ContentsEqual(FilePath(FILE_PATH_LITERAL("bogusname")),
+ FilePath(FILE_PATH_LITERAL("bogusname"))));
+ EXPECT_FALSE(ContentsEqual(original_file, different_first_file));
+ EXPECT_FALSE(ContentsEqual(original_file, different_last_file));
+ EXPECT_TRUE(ContentsEqual(empty1_file, empty2_file));
+ EXPECT_FALSE(ContentsEqual(original_file, shortened_file));
+ EXPECT_FALSE(ContentsEqual(shortened_file, original_file));
+ EXPECT_TRUE(ContentsEqual(binary_file, binary_file_same));
+ EXPECT_FALSE(ContentsEqual(binary_file, binary_file_diff));
+}
+
+TEST_F(ReadOnlyFileUtilTest, TextContentsEqual) {
+ FilePath data_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir));
+ data_dir = data_dir.AppendASCII("file_util");
+ ASSERT_TRUE(PathExists(data_dir));
+
+ FilePath original_file =
+ data_dir.Append(FILE_PATH_LITERAL("original.txt"));
+ FilePath same_file =
+ data_dir.Append(FILE_PATH_LITERAL("same.txt"));
+ FilePath crlf_file =
+ data_dir.Append(FILE_PATH_LITERAL("crlf.txt"));
+ FilePath shortened_file =
+ data_dir.Append(FILE_PATH_LITERAL("shortened.txt"));
+ FilePath different_file =
+ data_dir.Append(FILE_PATH_LITERAL("different.txt"));
+ FilePath different_first_file =
+ data_dir.Append(FILE_PATH_LITERAL("different_first.txt"));
+ FilePath different_last_file =
+ data_dir.Append(FILE_PATH_LITERAL("different_last.txt"));
+ FilePath first1_file =
+ data_dir.Append(FILE_PATH_LITERAL("first1.txt"));
+ FilePath first2_file =
+ data_dir.Append(FILE_PATH_LITERAL("first2.txt"));
+ FilePath empty1_file =
+ data_dir.Append(FILE_PATH_LITERAL("empty1.txt"));
+ FilePath empty2_file =
+ data_dir.Append(FILE_PATH_LITERAL("empty2.txt"));
+ FilePath blank_line_file =
+ data_dir.Append(FILE_PATH_LITERAL("blank_line.txt"));
+ FilePath blank_line_crlf_file =
+ data_dir.Append(FILE_PATH_LITERAL("blank_line_crlf.txt"));
+
+ EXPECT_TRUE(TextContentsEqual(original_file, same_file));
+ EXPECT_TRUE(TextContentsEqual(original_file, crlf_file));
+ EXPECT_FALSE(TextContentsEqual(original_file, shortened_file));
+ EXPECT_FALSE(TextContentsEqual(original_file, different_file));
+ EXPECT_FALSE(TextContentsEqual(original_file, different_first_file));
+ EXPECT_FALSE(TextContentsEqual(original_file, different_last_file));
+ EXPECT_FALSE(TextContentsEqual(first1_file, first2_file));
+ EXPECT_TRUE(TextContentsEqual(empty1_file, empty2_file));
+ EXPECT_FALSE(TextContentsEqual(original_file, empty1_file));
+ EXPECT_TRUE(TextContentsEqual(blank_line_file, blank_line_crlf_file));
+}
+
+// We don't need equivalent functionality outside of Windows.
+#if defined(OS_WIN)
+TEST_F(FileUtilTest, CopyAndDeleteDirectoryTest) {
+ // Create a directory
+ FilePath dir_name_from = temp_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("CopyAndDelete_From_Subdir"));
+ CreateDirectory(dir_name_from);
+ ASSERT_TRUE(PathExists(dir_name_from));
+
+ // Create a file under the directory
+ FilePath file_name_from =
+ dir_name_from.Append(FILE_PATH_LITERAL("CopyAndDelete_Test_File.txt"));
+ CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle");
+ ASSERT_TRUE(PathExists(file_name_from));
+
+ // Move the directory by using CopyAndDeleteDirectory
+ FilePath dir_name_to =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("CopyAndDelete_To_Subdir"));
+ FilePath file_name_to =
+ dir_name_to.Append(FILE_PATH_LITERAL("CopyAndDelete_Test_File.txt"));
+
+ ASSERT_FALSE(PathExists(dir_name_to));
+
+ EXPECT_TRUE(internal::CopyAndDeleteDirectory(dir_name_from,
+ dir_name_to));
+
+ // Check everything has been moved.
+ EXPECT_FALSE(PathExists(dir_name_from));
+ EXPECT_FALSE(PathExists(file_name_from));
+ EXPECT_TRUE(PathExists(dir_name_to));
+ EXPECT_TRUE(PathExists(file_name_to));
+}
+
+TEST_F(FileUtilTest, GetTempDirTest) {
+ static const TCHAR* kTmpKey = _T("TMP");
+ static const TCHAR* kTmpValues[] = {
+ _T(""), _T("C:"), _T("C:\\"), _T("C:\\tmp"), _T("C:\\tmp\\")
+ };
+ // Save the original $TMP.
+ size_t original_tmp_size;
+ TCHAR* original_tmp;
+ ASSERT_EQ(0, ::_tdupenv_s(&original_tmp, &original_tmp_size, kTmpKey));
+ // original_tmp may be NULL.
+
+ for (unsigned int i = 0; i < arraysize(kTmpValues); ++i) {
+ FilePath path;
+ ::_tputenv_s(kTmpKey, kTmpValues[i]);
+ GetTempDir(&path);
+ EXPECT_TRUE(path.IsAbsolute()) << "$TMP=" << kTmpValues[i] <<
+ " result=" << path.value();
+ }
+
+ // Restore the original $TMP.
+ if (original_tmp) {
+ ::_tputenv_s(kTmpKey, original_tmp);
+ free(original_tmp);
+ } else {
+ ::_tputenv_s(kTmpKey, _T(""));
+ }
+}
+#endif // OS_WIN
+
+// Test that files opened by OpenFile are not set up for inheritance into child
+// procs.
+TEST_F(FileUtilTest, OpenFileNoInheritance) {
+ FilePath file_path(temp_dir_.GetPath().Append(FPL("a_file")));
+
+ for (const char* mode : {"wb", "r,ccs=UTF-8"}) {
+ SCOPED_TRACE(mode);
+ ASSERT_NO_FATAL_FAILURE(CreateTextFile(file_path, L"Geepers"));
+ FILE* file = OpenFile(file_path, mode);
+ ASSERT_NE(nullptr, file);
+ {
+ ScopedClosureRunner file_closer(Bind(IgnoreResult(&CloseFile), file));
+ bool is_inheritable = true;
+ ASSERT_NO_FATAL_FAILURE(GetIsInheritable(file, &is_inheritable));
+ EXPECT_FALSE(is_inheritable);
+ }
+ ASSERT_TRUE(DeleteFile(file_path, false));
+ }
+}
+
+TEST_F(FileUtilTest, CreateTemporaryFileTest) {
+ FilePath temp_files[3];
+ for (int i = 0; i < 3; i++) {
+ ASSERT_TRUE(CreateTemporaryFile(&(temp_files[i])));
+ EXPECT_TRUE(PathExists(temp_files[i]));
+ EXPECT_FALSE(DirectoryExists(temp_files[i]));
+ }
+ for (int i = 0; i < 3; i++)
+ EXPECT_FALSE(temp_files[i] == temp_files[(i+1)%3]);
+ for (int i = 0; i < 3; i++)
+ EXPECT_TRUE(DeleteFile(temp_files[i], false));
+}
+
+TEST_F(FileUtilTest, CreateAndOpenTemporaryFileTest) {
+ FilePath names[3];
+ FILE* fps[3];
+ int i;
+
+ // Create; make sure they are open and exist.
+ for (i = 0; i < 3; ++i) {
+ fps[i] = CreateAndOpenTemporaryFile(&(names[i]));
+ ASSERT_TRUE(fps[i]);
+ EXPECT_TRUE(PathExists(names[i]));
+ }
+
+ // Make sure all names are unique.
+ for (i = 0; i < 3; ++i) {
+ EXPECT_FALSE(names[i] == names[(i+1)%3]);
+ }
+
+ // Close and delete.
+ for (i = 0; i < 3; ++i) {
+ EXPECT_TRUE(CloseFile(fps[i]));
+ EXPECT_TRUE(DeleteFile(names[i], false));
+ }
+}
+
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/851747): Re-enable when the Fuchsia-side fix for fdopen has
+// been rolled into Chromium.
+#define MAYBE_FileToFILE DISABLED_FileToFILE
+#else
+#define MAYBE_FileToFILE FileToFILE
+#endif
+TEST_F(FileUtilTest, MAYBE_FileToFILE) {
+ File file;
+ FILE* stream = FileToFILE(std::move(file), "w");
+ EXPECT_FALSE(stream);
+
+ FilePath file_name = temp_dir_.GetPath().Append(FPL("The file.txt"));
+ file = File(file_name, File::FLAG_CREATE | File::FLAG_WRITE);
+ EXPECT_TRUE(file.IsValid());
+
+ stream = FileToFILE(std::move(file), "w");
+ EXPECT_TRUE(stream);
+ EXPECT_FALSE(file.IsValid());
+ EXPECT_TRUE(CloseFile(stream));
+}
+
+TEST_F(FileUtilTest, CreateNewTempDirectoryTest) {
+ FilePath temp_dir;
+ ASSERT_TRUE(CreateNewTempDirectory(FilePath::StringType(), &temp_dir));
+ EXPECT_TRUE(PathExists(temp_dir));
+ EXPECT_TRUE(DeleteFile(temp_dir, false));
+}
+
+TEST_F(FileUtilTest, CreateNewTemporaryDirInDirTest) {
+ FilePath new_dir;
+ ASSERT_TRUE(CreateTemporaryDirInDir(
+ temp_dir_.GetPath(), FILE_PATH_LITERAL("CreateNewTemporaryDirInDirTest"),
+ &new_dir));
+ EXPECT_TRUE(PathExists(new_dir));
+ EXPECT_TRUE(temp_dir_.GetPath().IsParent(new_dir));
+ EXPECT_TRUE(DeleteFile(new_dir, false));
+}
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+TEST_F(FileUtilTest, GetShmemTempDirTest) {
+ FilePath dir;
+ EXPECT_TRUE(GetShmemTempDir(false, &dir));
+ EXPECT_TRUE(DirectoryExists(dir));
+}
+#endif
+
+TEST_F(FileUtilTest, GetHomeDirTest) {
+#if !defined(OS_ANDROID) // Not implemented on Android.
+ // We don't actually know what the home directory is supposed to be without
+ // calling some OS functions which would just duplicate the implementation.
+ // So here we just test that it returns something "reasonable".
+ FilePath home = GetHomeDir();
+ ASSERT_FALSE(home.empty());
+ ASSERT_TRUE(home.IsAbsolute());
+#endif
+}
+
+TEST_F(FileUtilTest, CreateDirectoryTest) {
+ FilePath test_root =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("create_directory_test"));
+#if defined(OS_WIN)
+ FilePath test_path =
+ test_root.Append(FILE_PATH_LITERAL("dir\\tree\\likely\\doesnt\\exist\\"));
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ FilePath test_path =
+ test_root.Append(FILE_PATH_LITERAL("dir/tree/likely/doesnt/exist/"));
+#endif
+
+ EXPECT_FALSE(PathExists(test_path));
+ EXPECT_TRUE(CreateDirectory(test_path));
+ EXPECT_TRUE(PathExists(test_path));
+ // CreateDirectory returns true if the DirectoryExists returns true.
+ EXPECT_TRUE(CreateDirectory(test_path));
+
+ // Doesn't work to create it on top of a non-dir
+ test_path = test_path.Append(FILE_PATH_LITERAL("foobar.txt"));
+ EXPECT_FALSE(PathExists(test_path));
+ CreateTextFile(test_path, L"test file");
+ EXPECT_TRUE(PathExists(test_path));
+ EXPECT_FALSE(CreateDirectory(test_path));
+
+ EXPECT_TRUE(DeleteFile(test_root, true));
+ EXPECT_FALSE(PathExists(test_root));
+ EXPECT_FALSE(PathExists(test_path));
+
+ // Verify assumptions made by the Windows implementation:
+ // 1. The current directory always exists.
+ // 2. The root directory always exists.
+ ASSERT_TRUE(DirectoryExists(FilePath(FilePath::kCurrentDirectory)));
+ FilePath top_level = test_root;
+ while (top_level != top_level.DirName()) {
+ top_level = top_level.DirName();
+ }
+ ASSERT_TRUE(DirectoryExists(top_level));
+
+ // Given these assumptions hold, it should be safe to
+ // test that "creating" these directories succeeds.
+ EXPECT_TRUE(CreateDirectory(
+ FilePath(FilePath::kCurrentDirectory)));
+ EXPECT_TRUE(CreateDirectory(top_level));
+
+#if defined(OS_WIN)
+ FilePath invalid_drive(FILE_PATH_LITERAL("o:\\"));
+ FilePath invalid_path =
+ invalid_drive.Append(FILE_PATH_LITERAL("some\\inaccessible\\dir"));
+ if (!PathExists(invalid_drive)) {
+ EXPECT_FALSE(CreateDirectory(invalid_path));
+ }
+#endif
+}
+
+TEST_F(FileUtilTest, DetectDirectoryTest) {
+ // Check a directory
+ FilePath test_root =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("detect_directory_test"));
+ EXPECT_FALSE(PathExists(test_root));
+ EXPECT_TRUE(CreateDirectory(test_root));
+ EXPECT_TRUE(PathExists(test_root));
+ EXPECT_TRUE(DirectoryExists(test_root));
+ // Check a file
+ FilePath test_path =
+ test_root.Append(FILE_PATH_LITERAL("foobar.txt"));
+ EXPECT_FALSE(PathExists(test_path));
+ CreateTextFile(test_path, L"test file");
+ EXPECT_TRUE(PathExists(test_path));
+ EXPECT_FALSE(DirectoryExists(test_path));
+ EXPECT_TRUE(DeleteFile(test_path, false));
+
+ EXPECT_TRUE(DeleteFile(test_root, true));
+}
+
+TEST_F(FileUtilTest, FileEnumeratorTest) {
+ // Test an empty directory.
+ FileEnumerator f0(temp_dir_.GetPath(), true, FILES_AND_DIRECTORIES);
+ EXPECT_EQ(FPL(""), f0.Next().value());
+ EXPECT_EQ(FPL(""), f0.Next().value());
+
+ // Test an empty directory, non-recursively, including "..".
+ FileEnumerator f0_dotdot(
+ temp_dir_.GetPath(), false,
+ FILES_AND_DIRECTORIES | FileEnumerator::INCLUDE_DOT_DOT);
+ EXPECT_EQ(temp_dir_.GetPath().Append(FPL("..")).value(),
+ f0_dotdot.Next().value());
+ EXPECT_EQ(FPL(""), f0_dotdot.Next().value());
+
+ // create the directories
+ FilePath dir1 = temp_dir_.GetPath().Append(FPL("dir1"));
+ EXPECT_TRUE(CreateDirectory(dir1));
+ FilePath dir2 = temp_dir_.GetPath().Append(FPL("dir2"));
+ EXPECT_TRUE(CreateDirectory(dir2));
+ FilePath dir2inner = dir2.Append(FPL("inner"));
+ EXPECT_TRUE(CreateDirectory(dir2inner));
+
+ // create the files
+ FilePath dir2file = dir2.Append(FPL("dir2file.txt"));
+ CreateTextFile(dir2file, std::wstring());
+ FilePath dir2innerfile = dir2inner.Append(FPL("innerfile.txt"));
+ CreateTextFile(dir2innerfile, std::wstring());
+ FilePath file1 = temp_dir_.GetPath().Append(FPL("file1.txt"));
+ CreateTextFile(file1, std::wstring());
+ FilePath file2_rel = dir2.Append(FilePath::kParentDirectory)
+ .Append(FPL("file2.txt"));
+ CreateTextFile(file2_rel, std::wstring());
+ FilePath file2_abs = temp_dir_.GetPath().Append(FPL("file2.txt"));
+
+ // Only enumerate files.
+ FileEnumerator f1(temp_dir_.GetPath(), true, FileEnumerator::FILES);
+ FindResultCollector c1(&f1);
+ EXPECT_TRUE(c1.HasFile(file1));
+ EXPECT_TRUE(c1.HasFile(file2_abs));
+ EXPECT_TRUE(c1.HasFile(dir2file));
+ EXPECT_TRUE(c1.HasFile(dir2innerfile));
+ EXPECT_EQ(4, c1.size());
+
+ // Only enumerate directories.
+ FileEnumerator f2(temp_dir_.GetPath(), true, FileEnumerator::DIRECTORIES);
+ FindResultCollector c2(&f2);
+ EXPECT_TRUE(c2.HasFile(dir1));
+ EXPECT_TRUE(c2.HasFile(dir2));
+ EXPECT_TRUE(c2.HasFile(dir2inner));
+ EXPECT_EQ(3, c2.size());
+
+ // Only enumerate directories non-recursively.
+ FileEnumerator f2_non_recursive(temp_dir_.GetPath(), false,
+ FileEnumerator::DIRECTORIES);
+ FindResultCollector c2_non_recursive(&f2_non_recursive);
+ EXPECT_TRUE(c2_non_recursive.HasFile(dir1));
+ EXPECT_TRUE(c2_non_recursive.HasFile(dir2));
+ EXPECT_EQ(2, c2_non_recursive.size());
+
+ // Only enumerate directories, non-recursively, including "..".
+ FileEnumerator f2_dotdot(
+ temp_dir_.GetPath(), false,
+ FileEnumerator::DIRECTORIES | FileEnumerator::INCLUDE_DOT_DOT);
+ FindResultCollector c2_dotdot(&f2_dotdot);
+ EXPECT_TRUE(c2_dotdot.HasFile(dir1));
+ EXPECT_TRUE(c2_dotdot.HasFile(dir2));
+ EXPECT_TRUE(c2_dotdot.HasFile(temp_dir_.GetPath().Append(FPL(".."))));
+ EXPECT_EQ(3, c2_dotdot.size());
+
+ // Enumerate files and directories.
+ FileEnumerator f3(temp_dir_.GetPath(), true, FILES_AND_DIRECTORIES);
+ FindResultCollector c3(&f3);
+ EXPECT_TRUE(c3.HasFile(dir1));
+ EXPECT_TRUE(c3.HasFile(dir2));
+ EXPECT_TRUE(c3.HasFile(file1));
+ EXPECT_TRUE(c3.HasFile(file2_abs));
+ EXPECT_TRUE(c3.HasFile(dir2file));
+ EXPECT_TRUE(c3.HasFile(dir2inner));
+ EXPECT_TRUE(c3.HasFile(dir2innerfile));
+ EXPECT_EQ(7, c3.size());
+
+ // Non-recursive operation.
+ FileEnumerator f4(temp_dir_.GetPath(), false, FILES_AND_DIRECTORIES);
+ FindResultCollector c4(&f4);
+ EXPECT_TRUE(c4.HasFile(dir2));
+ EXPECT_TRUE(c4.HasFile(dir2));
+ EXPECT_TRUE(c4.HasFile(file1));
+ EXPECT_TRUE(c4.HasFile(file2_abs));
+ EXPECT_EQ(4, c4.size());
+
+ // Enumerate with a pattern.
+ FileEnumerator f5(temp_dir_.GetPath(), true, FILES_AND_DIRECTORIES,
+ FPL("dir*"));
+ FindResultCollector c5(&f5);
+ EXPECT_TRUE(c5.HasFile(dir1));
+ EXPECT_TRUE(c5.HasFile(dir2));
+ EXPECT_TRUE(c5.HasFile(dir2file));
+ EXPECT_TRUE(c5.HasFile(dir2inner));
+ EXPECT_TRUE(c5.HasFile(dir2innerfile));
+ EXPECT_EQ(5, c5.size());
+
+#if defined(OS_WIN)
+ {
+ // Make dir1 point to dir2.
+ ReparsePoint reparse_point(dir1, dir2);
+ EXPECT_TRUE(reparse_point.IsValid());
+
+ // There can be a delay for the enumeration code to see the change on
+ // the file system so skip this test for XP.
+ // Enumerate the reparse point.
+ FileEnumerator f6(dir1, true, FILES_AND_DIRECTORIES);
+ FindResultCollector c6(&f6);
+ FilePath inner2 = dir1.Append(FPL("inner"));
+ EXPECT_TRUE(c6.HasFile(inner2));
+ EXPECT_TRUE(c6.HasFile(inner2.Append(FPL("innerfile.txt"))));
+ EXPECT_TRUE(c6.HasFile(dir1.Append(FPL("dir2file.txt"))));
+ EXPECT_EQ(3, c6.size());
+
+ // No changes for non recursive operation.
+ FileEnumerator f7(temp_dir_.GetPath(), false, FILES_AND_DIRECTORIES);
+ FindResultCollector c7(&f7);
+ EXPECT_TRUE(c7.HasFile(dir2));
+ EXPECT_TRUE(c7.HasFile(dir2));
+ EXPECT_TRUE(c7.HasFile(file1));
+ EXPECT_TRUE(c7.HasFile(file2_abs));
+ EXPECT_EQ(4, c7.size());
+
+ // Should not enumerate inside dir1 when using recursion.
+ FileEnumerator f8(temp_dir_.GetPath(), true, FILES_AND_DIRECTORIES);
+ FindResultCollector c8(&f8);
+ EXPECT_TRUE(c8.HasFile(dir1));
+ EXPECT_TRUE(c8.HasFile(dir2));
+ EXPECT_TRUE(c8.HasFile(file1));
+ EXPECT_TRUE(c8.HasFile(file2_abs));
+ EXPECT_TRUE(c8.HasFile(dir2file));
+ EXPECT_TRUE(c8.HasFile(dir2inner));
+ EXPECT_TRUE(c8.HasFile(dir2innerfile));
+ EXPECT_EQ(7, c8.size());
+ }
+#endif
+
+ // Make sure the destructor closes the find handle while in the middle of a
+ // query to allow TearDown to delete the directory.
+ FileEnumerator f9(temp_dir_.GetPath(), true, FILES_AND_DIRECTORIES);
+ EXPECT_FALSE(f9.Next().value().empty()); // Should have found something
+ // (we don't care what).
+}
+
+TEST_F(FileUtilTest, AppendToFile) {
+ FilePath data_dir =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("FilePathTest"));
+
+ // Create a fresh, empty copy of this directory.
+ if (PathExists(data_dir)) {
+ ASSERT_TRUE(DeleteFile(data_dir, true));
+ }
+ ASSERT_TRUE(CreateDirectory(data_dir));
+
+ // Create a fresh, empty copy of this directory.
+ if (PathExists(data_dir)) {
+ ASSERT_TRUE(DeleteFile(data_dir, true));
+ }
+ ASSERT_TRUE(CreateDirectory(data_dir));
+ FilePath foobar(data_dir.Append(FILE_PATH_LITERAL("foobar.txt")));
+
+ std::string data("hello");
+ EXPECT_FALSE(AppendToFile(foobar, data.c_str(), data.size()));
+ EXPECT_EQ(static_cast<int>(data.length()),
+ WriteFile(foobar, data.c_str(), data.length()));
+ EXPECT_TRUE(AppendToFile(foobar, data.c_str(), data.size()));
+
+ const std::wstring read_content = ReadTextFile(foobar);
+ EXPECT_EQ(L"hellohello", read_content);
+}
+
+TEST_F(FileUtilTest, ReadFile) {
+ // Create a test file to be read.
+ const std::string kTestData("The quick brown fox jumps over the lazy dog.");
+ FilePath file_path =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("ReadFileTest"));
+
+ ASSERT_EQ(static_cast<int>(kTestData.size()),
+ WriteFile(file_path, kTestData.data(), kTestData.size()));
+
+ // Make buffers with various size.
+ std::vector<char> small_buffer(kTestData.size() / 2);
+ std::vector<char> exact_buffer(kTestData.size());
+ std::vector<char> large_buffer(kTestData.size() * 2);
+
+ // Read the file with smaller buffer.
+ int bytes_read_small = ReadFile(
+ file_path, &small_buffer[0], static_cast<int>(small_buffer.size()));
+ EXPECT_EQ(static_cast<int>(small_buffer.size()), bytes_read_small);
+ EXPECT_EQ(
+ std::string(kTestData.begin(), kTestData.begin() + small_buffer.size()),
+ std::string(small_buffer.begin(), small_buffer.end()));
+
+ // Read the file with buffer which have exactly same size.
+ int bytes_read_exact = ReadFile(
+ file_path, &exact_buffer[0], static_cast<int>(exact_buffer.size()));
+ EXPECT_EQ(static_cast<int>(kTestData.size()), bytes_read_exact);
+ EXPECT_EQ(kTestData, std::string(exact_buffer.begin(), exact_buffer.end()));
+
+ // Read the file with larger buffer.
+ int bytes_read_large = ReadFile(
+ file_path, &large_buffer[0], static_cast<int>(large_buffer.size()));
+ EXPECT_EQ(static_cast<int>(kTestData.size()), bytes_read_large);
+ EXPECT_EQ(kTestData, std::string(large_buffer.begin(),
+ large_buffer.begin() + kTestData.size()));
+
+ // Make sure the return value is -1 if the file doesn't exist.
+ FilePath file_path_not_exist =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("ReadFileNotExistTest"));
+ EXPECT_EQ(-1,
+ ReadFile(file_path_not_exist,
+ &exact_buffer[0],
+ static_cast<int>(exact_buffer.size())));
+}
+
+TEST_F(FileUtilTest, ReadFileToString) {
+ const char kTestData[] = "0123";
+ std::string data;
+
+ FilePath file_path =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("ReadFileToStringTest"));
+ FilePath file_path_dangerous =
+ temp_dir_.GetPath()
+ .Append(FILE_PATH_LITERAL(".."))
+ .Append(temp_dir_.GetPath().BaseName())
+ .Append(FILE_PATH_LITERAL("ReadFileToStringTest"));
+
+ // Create test file.
+ ASSERT_EQ(static_cast<int>(strlen(kTestData)),
+ WriteFile(file_path, kTestData, strlen(kTestData)));
+
+ EXPECT_TRUE(ReadFileToString(file_path, &data));
+ EXPECT_EQ(kTestData, data);
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 0));
+ EXPECT_EQ(0u, data.length());
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 2));
+ EXPECT_EQ("01", data);
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 3));
+ EXPECT_EQ("012", data);
+
+ data = "temp";
+ EXPECT_TRUE(ReadFileToStringWithMaxSize(file_path, &data, 4));
+ EXPECT_EQ("0123", data);
+
+ data = "temp";
+ EXPECT_TRUE(ReadFileToStringWithMaxSize(file_path, &data, 6));
+ EXPECT_EQ("0123", data);
+
+ EXPECT_TRUE(ReadFileToStringWithMaxSize(file_path, nullptr, 6));
+
+ EXPECT_TRUE(ReadFileToString(file_path, nullptr));
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToString(file_path_dangerous, &data));
+ EXPECT_EQ(0u, data.length());
+
+ // Delete test file.
+ EXPECT_TRUE(DeleteFile(file_path, false));
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToString(file_path, &data));
+ EXPECT_EQ(0u, data.length());
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 6));
+ EXPECT_EQ(0u, data.length());
+}
+
+#if !defined(OS_WIN)
+TEST_F(FileUtilTest, ReadFileToStringWithUnknownFileSize) {
+ FilePath file_path("/dev/zero");
+ std::string data = "temp";
+
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 0));
+ EXPECT_EQ(0u, data.length());
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 2));
+ EXPECT_EQ(std::string(2, '\0'), data);
+
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, nullptr, 6));
+
+ // Read more than buffer size.
+ data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, kLargeFileSize));
+ EXPECT_EQ(kLargeFileSize, data.length());
+ EXPECT_EQ(std::string(kLargeFileSize, '\0'), data);
+
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, nullptr, kLargeFileSize));
+}
+#endif // !defined(OS_WIN)
+
+#if !defined(OS_WIN) && !defined(OS_NACL) && !defined(OS_FUCHSIA) && \
+ !defined(OS_IOS)
+#define ChildMain WriteToPipeChildMain
+#define ChildMainString "WriteToPipeChildMain"
+
+MULTIPROCESS_TEST_MAIN(ChildMain) {
+ const char kTestData[] = "0123";
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ const FilePath pipe_path = command_line->GetSwitchValuePath("pipe-path");
+
+ int fd = open(pipe_path.value().c_str(), O_WRONLY);
+ CHECK_NE(-1, fd);
+ size_t written = 0;
+ while (written < strlen(kTestData)) {
+ ssize_t res = write(fd, kTestData + written, strlen(kTestData) - written);
+ if (res == -1)
+ break;
+ written += res;
+ }
+ CHECK_EQ(strlen(kTestData), written);
+ CHECK_EQ(0, close(fd));
+ return 0;
+}
+
+#define MoreThanBufferSizeChildMain WriteToPipeMoreThanBufferSizeChildMain
+#define MoreThanBufferSizeChildMainString \
+ "WriteToPipeMoreThanBufferSizeChildMain"
+
+MULTIPROCESS_TEST_MAIN(MoreThanBufferSizeChildMain) {
+ std::string data(kLargeFileSize, 'c');
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ const FilePath pipe_path = command_line->GetSwitchValuePath("pipe-path");
+
+ int fd = open(pipe_path.value().c_str(), O_WRONLY);
+ CHECK_NE(-1, fd);
+
+ size_t written = 0;
+ while (written < data.size()) {
+ ssize_t res = write(fd, data.c_str() + written, data.size() - written);
+ if (res == -1) {
+ // We are unable to write because reading process has already read
+ // requested number of bytes and closed pipe.
+ break;
+ }
+ written += res;
+ }
+ CHECK_EQ(0, close(fd));
+ return 0;
+}
+
+TEST_F(FileUtilTest, ReadFileToStringWithNamedPipe) {
+ FilePath pipe_path =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("test_pipe"));
+ ASSERT_EQ(0, mkfifo(pipe_path.value().c_str(), 0600));
+
+ base::CommandLine child_command_line(
+ base::GetMultiProcessTestChildBaseCommandLine());
+ child_command_line.AppendSwitchPath("pipe-path", pipe_path);
+
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ ChildMainString, child_command_line, base::LaunchOptions());
+ ASSERT_TRUE(child_process.IsValid());
+
+ std::string data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(pipe_path, &data, 2));
+ EXPECT_EQ("01", data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ ChildMainString, child_command_line, base::LaunchOptions());
+ ASSERT_TRUE(child_process.IsValid());
+
+ std::string data = "temp";
+ EXPECT_TRUE(ReadFileToStringWithMaxSize(pipe_path, &data, 6));
+ EXPECT_EQ("0123", data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ MoreThanBufferSizeChildMainString, child_command_line,
+ base::LaunchOptions());
+ ASSERT_TRUE(child_process.IsValid());
+
+ std::string data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(pipe_path, &data, 6));
+ EXPECT_EQ("cccccc", data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ MoreThanBufferSizeChildMainString, child_command_line,
+ base::LaunchOptions());
+ ASSERT_TRUE(child_process.IsValid());
+
+ std::string data = "temp";
+ EXPECT_FALSE(
+ ReadFileToStringWithMaxSize(pipe_path, &data, kLargeFileSize - 1));
+ EXPECT_EQ(std::string(kLargeFileSize - 1, 'c'), data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ MoreThanBufferSizeChildMainString, child_command_line,
+ base::LaunchOptions());
+ ASSERT_TRUE(child_process.IsValid());
+
+ std::string data = "temp";
+ EXPECT_TRUE(ReadFileToStringWithMaxSize(pipe_path, &data, kLargeFileSize));
+ EXPECT_EQ(std::string(kLargeFileSize, 'c'), data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ MoreThanBufferSizeChildMainString, child_command_line,
+ base::LaunchOptions());
+ ASSERT_TRUE(child_process.IsValid());
+
+ std::string data = "temp";
+ EXPECT_TRUE(
+ ReadFileToStringWithMaxSize(pipe_path, &data, kLargeFileSize * 5));
+ EXPECT_EQ(std::string(kLargeFileSize, 'c'), data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+
+ ASSERT_EQ(0, unlink(pipe_path.value().c_str()));
+}
+#endif // !defined(OS_WIN) && !defined(OS_NACL) && !defined(OS_FUCHSIA) &&
+ // !defined(OS_IOS)
+
+#if defined(OS_WIN)
+#define ChildMain WriteToPipeChildMain
+#define ChildMainString "WriteToPipeChildMain"
+
+MULTIPROCESS_TEST_MAIN(ChildMain) {
+ const char kTestData[] = "0123";
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ const FilePath pipe_path = command_line->GetSwitchValuePath("pipe-path");
+ std::string switch_string = command_line->GetSwitchValueASCII("sync_event");
+ EXPECT_FALSE(switch_string.empty());
+ unsigned int switch_uint = 0;
+ EXPECT_TRUE(StringToUint(switch_string, &switch_uint));
+ win::ScopedHandle sync_event(win::Uint32ToHandle(switch_uint));
+
+ HANDLE ph = CreateNamedPipe(pipe_path.value().c_str(), PIPE_ACCESS_OUTBOUND,
+ PIPE_WAIT, 1, 0, 0, 0, NULL);
+ EXPECT_NE(ph, INVALID_HANDLE_VALUE);
+ EXPECT_TRUE(SetEvent(sync_event.Get()));
+ EXPECT_TRUE(ConnectNamedPipe(ph, NULL));
+
+ DWORD written;
+ EXPECT_TRUE(::WriteFile(ph, kTestData, strlen(kTestData), &written, NULL));
+ EXPECT_EQ(strlen(kTestData), written);
+ CloseHandle(ph);
+ return 0;
+}
+
+#define MoreThanBufferSizeChildMain WriteToPipeMoreThanBufferSizeChildMain
+#define MoreThanBufferSizeChildMainString \
+ "WriteToPipeMoreThanBufferSizeChildMain"
+
+MULTIPROCESS_TEST_MAIN(MoreThanBufferSizeChildMain) {
+ std::string data(kLargeFileSize, 'c');
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ const FilePath pipe_path = command_line->GetSwitchValuePath("pipe-path");
+ std::string switch_string = command_line->GetSwitchValueASCII("sync_event");
+ EXPECT_FALSE(switch_string.empty());
+ unsigned int switch_uint = 0;
+ EXPECT_TRUE(StringToUint(switch_string, &switch_uint));
+ win::ScopedHandle sync_event(win::Uint32ToHandle(switch_uint));
+
+ HANDLE ph = CreateNamedPipe(pipe_path.value().c_str(), PIPE_ACCESS_OUTBOUND,
+ PIPE_WAIT, 1, data.size(), data.size(), 0, NULL);
+ EXPECT_NE(ph, INVALID_HANDLE_VALUE);
+ EXPECT_TRUE(SetEvent(sync_event.Get()));
+ EXPECT_TRUE(ConnectNamedPipe(ph, NULL));
+
+ DWORD written;
+ EXPECT_TRUE(::WriteFile(ph, data.c_str(), data.size(), &written, NULL));
+ EXPECT_EQ(data.size(), written);
+ CloseHandle(ph);
+ return 0;
+}
+
+TEST_F(FileUtilTest, ReadFileToStringWithNamedPipe) {
+ FilePath pipe_path(FILE_PATH_LITERAL("\\\\.\\pipe\\test_pipe"));
+ win::ScopedHandle sync_event(CreateEvent(0, false, false, nullptr));
+
+ base::CommandLine child_command_line(
+ base::GetMultiProcessTestChildBaseCommandLine());
+ child_command_line.AppendSwitchPath("pipe-path", pipe_path);
+ child_command_line.AppendSwitchASCII(
+ "sync_event", UintToString(win::HandleToUint32(sync_event.Get())));
+
+ base::LaunchOptions options;
+ options.handles_to_inherit.push_back(sync_event.Get());
+
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ ChildMainString, child_command_line, options);
+ ASSERT_TRUE(child_process.IsValid());
+ // Wait for pipe creation in child process.
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(sync_event.Get(), INFINITE));
+
+ std::string data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(pipe_path, &data, 2));
+ EXPECT_EQ("01", data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ ChildMainString, child_command_line, options);
+ ASSERT_TRUE(child_process.IsValid());
+ // Wait for pipe creation in child process.
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(sync_event.Get(), INFINITE));
+
+ std::string data = "temp";
+ EXPECT_TRUE(ReadFileToStringWithMaxSize(pipe_path, &data, 6));
+ EXPECT_EQ("0123", data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ MoreThanBufferSizeChildMainString, child_command_line, options);
+ ASSERT_TRUE(child_process.IsValid());
+ // Wait for pipe creation in child process.
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(sync_event.Get(), INFINITE));
+
+ std::string data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(pipe_path, &data, 6));
+ EXPECT_EQ("cccccc", data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ MoreThanBufferSizeChildMainString, child_command_line, options);
+ ASSERT_TRUE(child_process.IsValid());
+ // Wait for pipe creation in child process.
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(sync_event.Get(), INFINITE));
+
+ std::string data = "temp";
+ EXPECT_FALSE(
+ ReadFileToStringWithMaxSize(pipe_path, &data, kLargeFileSize - 1));
+ EXPECT_EQ(std::string(kLargeFileSize - 1, 'c'), data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ MoreThanBufferSizeChildMainString, child_command_line, options);
+ ASSERT_TRUE(child_process.IsValid());
+ // Wait for pipe creation in child process.
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(sync_event.Get(), INFINITE));
+
+ std::string data = "temp";
+ EXPECT_TRUE(ReadFileToStringWithMaxSize(pipe_path, &data, kLargeFileSize));
+ EXPECT_EQ(std::string(kLargeFileSize, 'c'), data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+ {
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ MoreThanBufferSizeChildMainString, child_command_line, options);
+ ASSERT_TRUE(child_process.IsValid());
+ // Wait for pipe creation in child process.
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(sync_event.Get(), INFINITE));
+
+ std::string data = "temp";
+ EXPECT_TRUE(
+ ReadFileToStringWithMaxSize(pipe_path, &data, kLargeFileSize * 5));
+ EXPECT_EQ(std::string(kLargeFileSize, 'c'), data);
+
+ int rv = -1;
+ ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &rv));
+ ASSERT_EQ(0, rv);
+ }
+}
+#endif // defined(OS_WIN)
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+TEST_F(FileUtilTest, ReadFileToStringWithProcFileSystem) {
+ FilePath file_path("/proc/cpuinfo");
+ std::string data = "temp";
+
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 0));
+ EXPECT_EQ(0u, data.length());
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 2));
+ EXPECT_TRUE(EqualsCaseInsensitiveASCII("pr", data));
+
+ data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &data, 4));
+ EXPECT_TRUE(EqualsCaseInsensitiveASCII("proc", data));
+
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, nullptr, 4));
+}
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX)
+
+TEST_F(FileUtilTest, ReadFileToStringWithLargeFile) {
+ std::string data(kLargeFileSize, 'c');
+
+ FilePath file_path =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("ReadFileToStringTest"));
+
+ // Create test file.
+ ASSERT_EQ(static_cast<int>(kLargeFileSize),
+ WriteFile(file_path, data.c_str(), kLargeFileSize));
+
+ std::string actual_data = "temp";
+ EXPECT_TRUE(ReadFileToString(file_path, &actual_data));
+ EXPECT_EQ(data, actual_data);
+
+ actual_data = "temp";
+ EXPECT_FALSE(ReadFileToStringWithMaxSize(file_path, &actual_data, 0));
+ EXPECT_EQ(0u, actual_data.length());
+
+ // Read more than buffer size.
+ actual_data = "temp";
+ EXPECT_FALSE(
+ ReadFileToStringWithMaxSize(file_path, &actual_data, kLargeFileSize - 1));
+ EXPECT_EQ(std::string(kLargeFileSize - 1, 'c'), actual_data);
+}
+
+TEST_F(FileUtilTest, TouchFile) {
+ FilePath data_dir =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("FilePathTest"));
+
+ // Create a fresh, empty copy of this directory.
+ if (PathExists(data_dir)) {
+ ASSERT_TRUE(DeleteFile(data_dir, true));
+ }
+ ASSERT_TRUE(CreateDirectory(data_dir));
+
+ FilePath foobar(data_dir.Append(FILE_PATH_LITERAL("foobar.txt")));
+ std::string data("hello");
+ ASSERT_EQ(static_cast<int>(data.length()),
+ WriteFile(foobar, data.c_str(), data.length()));
+
+ Time access_time;
+ // This timestamp is divisible by one day (in local timezone),
+ // to make it work on FAT too.
+ ASSERT_TRUE(Time::FromString("Wed, 16 Nov 1994, 00:00:00",
+ &access_time));
+
+ Time modification_time;
+ // Note that this timestamp is divisible by two (seconds) - FAT stores
+ // modification times with 2s resolution.
+ ASSERT_TRUE(Time::FromString("Tue, 15 Nov 1994, 12:45:26 GMT",
+ &modification_time));
+
+ ASSERT_TRUE(TouchFile(foobar, access_time, modification_time));
+ File::Info file_info;
+ ASSERT_TRUE(GetFileInfo(foobar, &file_info));
+#if !defined(OS_FUCHSIA)
+ // Access time is not supported on Fuchsia, see https://crbug.com/735233.
+ EXPECT_EQ(access_time.ToInternalValue(),
+ file_info.last_accessed.ToInternalValue());
+#endif
+ EXPECT_EQ(modification_time.ToInternalValue(),
+ file_info.last_modified.ToInternalValue());
+}
+
+TEST_F(FileUtilTest, IsDirectoryEmpty) {
+ FilePath empty_dir =
+ temp_dir_.GetPath().Append(FILE_PATH_LITERAL("EmptyDir"));
+
+ ASSERT_FALSE(PathExists(empty_dir));
+
+ ASSERT_TRUE(CreateDirectory(empty_dir));
+
+ EXPECT_TRUE(IsDirectoryEmpty(empty_dir));
+
+ FilePath foo(empty_dir.Append(FILE_PATH_LITERAL("foo.txt")));
+ std::string bar("baz");
+ ASSERT_EQ(static_cast<int>(bar.length()),
+ WriteFile(foo, bar.c_str(), bar.length()));
+
+ EXPECT_FALSE(IsDirectoryEmpty(empty_dir));
+}
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+TEST_F(FileUtilTest, SetNonBlocking) {
+ const int kInvalidFd = 99999;
+ EXPECT_FALSE(SetNonBlocking(kInvalidFd));
+
+ base::FilePath path;
+ ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &path));
+ path = path.Append(FPL("file_util")).Append(FPL("original.txt"));
+ ScopedFD fd(open(path.value().c_str(), O_RDONLY));
+ ASSERT_GE(fd.get(), 0);
+ EXPECT_TRUE(SetNonBlocking(fd.get()));
+}
+
+TEST_F(FileUtilTest, SetCloseOnExec) {
+ const int kInvalidFd = 99999;
+ EXPECT_FALSE(SetCloseOnExec(kInvalidFd));
+
+ base::FilePath path;
+ ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &path));
+ path = path.Append(FPL("file_util")).Append(FPL("original.txt"));
+ ScopedFD fd(open(path.value().c_str(), O_RDONLY));
+ ASSERT_GE(fd.get(), 0);
+ EXPECT_TRUE(SetCloseOnExec(fd.get()));
+}
+
+#endif
+
+#if defined(OS_POSIX)
+
+// Testing VerifyPathControlledByAdmin() is hard, because there is no
+// way a test can make a file owned by root, or change file paths
+// at the root of the file system. VerifyPathControlledByAdmin()
+// is implemented as a call to VerifyPathControlledByUser, which gives
+// us the ability to test with paths under the test's temp directory,
+// using a user id we control.
+// Pull tests of VerifyPathControlledByUserTest() into a separate test class
+// with a common SetUp() method.
+class VerifyPathControlledByUserTest : public FileUtilTest {
+ protected:
+ void SetUp() override {
+ FileUtilTest::SetUp();
+
+ // Create a basic structure used by each test.
+ // base_dir_
+ // |-> sub_dir_
+ // |-> text_file_
+
+ base_dir_ = temp_dir_.GetPath().AppendASCII("base_dir");
+ ASSERT_TRUE(CreateDirectory(base_dir_));
+
+ sub_dir_ = base_dir_.AppendASCII("sub_dir");
+ ASSERT_TRUE(CreateDirectory(sub_dir_));
+
+ text_file_ = sub_dir_.AppendASCII("file.txt");
+ CreateTextFile(text_file_, L"This text file has some text in it.");
+
+ // Get the user and group files are created with from |base_dir_|.
+ struct stat stat_buf;
+ ASSERT_EQ(0, stat(base_dir_.value().c_str(), &stat_buf));
+ uid_ = stat_buf.st_uid;
+ ok_gids_.insert(stat_buf.st_gid);
+ bad_gids_.insert(stat_buf.st_gid + 1);
+
+ ASSERT_EQ(uid_, getuid()); // This process should be the owner.
+
+ // To ensure that umask settings do not cause the initial state
+ // of permissions to be different from what we expect, explicitly
+ // set permissions on the directories we create.
+ // Make all files and directories non-world-writable.
+
+ // Users and group can read, write, traverse
+ int enabled_permissions =
+ FILE_PERMISSION_USER_MASK | FILE_PERMISSION_GROUP_MASK;
+ // Other users can't read, write, traverse
+ int disabled_permissions = FILE_PERMISSION_OTHERS_MASK;
+
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(
+ base_dir_, enabled_permissions, disabled_permissions));
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(
+ sub_dir_, enabled_permissions, disabled_permissions));
+ }
+
+ FilePath base_dir_;
+ FilePath sub_dir_;
+ FilePath text_file_;
+ uid_t uid_;
+
+ std::set<gid_t> ok_gids_;
+ std::set<gid_t> bad_gids_;
+};
+
+TEST_F(VerifyPathControlledByUserTest, BadPaths) {
+ // File does not exist.
+ FilePath does_not_exist = base_dir_.AppendASCII("does")
+ .AppendASCII("not")
+ .AppendASCII("exist");
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, does_not_exist, uid_, ok_gids_));
+
+ // |base| not a subpath of |path|.
+ EXPECT_FALSE(VerifyPathControlledByUser(sub_dir_, base_dir_, uid_, ok_gids_));
+
+ // An empty base path will fail to be a prefix for any path.
+ FilePath empty;
+ EXPECT_FALSE(VerifyPathControlledByUser(empty, base_dir_, uid_, ok_gids_));
+
+ // Finding that a bad call fails proves nothing unless a good call succeeds.
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+}
+
+TEST_F(VerifyPathControlledByUserTest, Symlinks) {
+ // Symlinks in the path should cause failure.
+
+ // Symlink to the file at the end of the path.
+ FilePath file_link = base_dir_.AppendASCII("file_link");
+ ASSERT_TRUE(CreateSymbolicLink(text_file_, file_link))
+ << "Failed to create symlink.";
+
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, file_link, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(file_link, file_link, uid_, ok_gids_));
+
+ // Symlink from one directory to another within the path.
+ FilePath link_to_sub_dir = base_dir_.AppendASCII("link_to_sub_dir");
+ ASSERT_TRUE(CreateSymbolicLink(sub_dir_, link_to_sub_dir))
+ << "Failed to create symlink.";
+
+ FilePath file_path_with_link = link_to_sub_dir.AppendASCII("file.txt");
+ ASSERT_TRUE(PathExists(file_path_with_link));
+
+ EXPECT_FALSE(VerifyPathControlledByUser(base_dir_, file_path_with_link, uid_,
+ ok_gids_));
+
+ EXPECT_FALSE(VerifyPathControlledByUser(link_to_sub_dir, file_path_with_link,
+ uid_, ok_gids_));
+
+ // Symlinks in parents of base path are allowed.
+ EXPECT_TRUE(VerifyPathControlledByUser(file_path_with_link,
+ file_path_with_link, uid_, ok_gids_));
+}
+
+TEST_F(VerifyPathControlledByUserTest, OwnershipChecks) {
+ // Get a uid that is not the uid of files we create.
+ uid_t bad_uid = uid_ + 1;
+
+ // Make all files and directories non-world-writable.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH));
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH));
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(text_file_, 0u, S_IWOTH));
+
+ // We control these paths.
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_TRUE(VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ // Another user does not control these paths.
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, sub_dir_, bad_uid, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, text_file_, bad_uid, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, bad_uid, ok_gids_));
+
+ // Another group does not control the paths.
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, bad_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, bad_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, uid_, bad_gids_));
+}
+
+TEST_F(VerifyPathControlledByUserTest, GroupWriteTest) {
+ // Make all files and directories writable only by their owner.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH|S_IWGRP));
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH|S_IWGRP));
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(text_file_, 0u, S_IWOTH|S_IWGRP));
+
+ // Any group is okay because the path is not group-writable.
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_TRUE(VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, bad_gids_));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, bad_gids_));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, uid_, bad_gids_));
+
+ // No group is okay, because we don't check the group
+ // if no group can write.
+ std::set<gid_t> no_gids; // Empty set of gids.
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, no_gids));
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, text_file_, uid_, no_gids));
+ EXPECT_TRUE(VerifyPathControlledByUser(sub_dir_, text_file_, uid_, no_gids));
+
+ // Make all files and directories writable by their group.
+ ASSERT_NO_FATAL_FAILURE(ChangePosixFilePermissions(base_dir_, S_IWGRP, 0u));
+ ASSERT_NO_FATAL_FAILURE(ChangePosixFilePermissions(sub_dir_, S_IWGRP, 0u));
+ ASSERT_NO_FATAL_FAILURE(ChangePosixFilePermissions(text_file_, S_IWGRP, 0u));
+
+ // Now |ok_gids_| works, but |bad_gids_| fails.
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_TRUE(VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, bad_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, bad_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, uid_, bad_gids_));
+
+ // Because any group in the group set is allowed,
+ // the union of good and bad gids passes.
+
+ std::set<gid_t> multiple_gids;
+ std::set_union(
+ ok_gids_.begin(), ok_gids_.end(),
+ bad_gids_.begin(), bad_gids_.end(),
+ std::inserter(multiple_gids, multiple_gids.begin()));
+
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, multiple_gids));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, multiple_gids));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, uid_, multiple_gids));
+}
+
+TEST_F(VerifyPathControlledByUserTest, WriteBitChecks) {
+ // Make all files and directories non-world-writable.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH));
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH));
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(text_file_, 0u, S_IWOTH));
+
+ // Initialy, we control all parts of the path.
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_TRUE(VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ // Make base_dir_ world-writable.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(base_dir_, S_IWOTH, 0u));
+ EXPECT_FALSE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_TRUE(VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ // Make sub_dir_ world writable.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(sub_dir_, S_IWOTH, 0u));
+ EXPECT_FALSE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ // Make text_file_ world writable.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(text_file_, S_IWOTH, 0u));
+ EXPECT_FALSE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ // Make sub_dir_ non-world writable.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(sub_dir_, 0u, S_IWOTH));
+ EXPECT_FALSE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ // Make base_dir_ non-world-writable.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(base_dir_, 0u, S_IWOTH));
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_FALSE(
+ VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+
+ // Back to the initial state: Nothing is writable, so every path
+ // should pass.
+ ASSERT_NO_FATAL_FAILURE(
+ ChangePosixFilePermissions(text_file_, 0u, S_IWOTH));
+ EXPECT_TRUE(VerifyPathControlledByUser(base_dir_, sub_dir_, uid_, ok_gids_));
+ EXPECT_TRUE(
+ VerifyPathControlledByUser(base_dir_, text_file_, uid_, ok_gids_));
+ EXPECT_TRUE(VerifyPathControlledByUser(sub_dir_, text_file_, uid_, ok_gids_));
+}
+
+#endif // defined(OS_POSIX)
+
+#if defined(OS_ANDROID)
+TEST_F(FileUtilTest, ValidContentUriTest) {
+ // Get the test image path.
+ FilePath data_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir));
+ data_dir = data_dir.AppendASCII("file_util");
+ ASSERT_TRUE(PathExists(data_dir));
+ FilePath image_file = data_dir.Append(FILE_PATH_LITERAL("red.png"));
+ int64_t image_size;
+ GetFileSize(image_file, &image_size);
+ ASSERT_GT(image_size, 0);
+
+ // Insert the image into MediaStore. MediaStore will do some conversions, and
+ // return the content URI.
+ FilePath path = InsertImageIntoMediaStore(image_file);
+ EXPECT_TRUE(path.IsContentUri());
+ EXPECT_TRUE(PathExists(path));
+ // The file size may not equal to the input image as MediaStore may convert
+ // the image.
+ int64_t content_uri_size;
+ GetFileSize(path, &content_uri_size);
+ EXPECT_EQ(image_size, content_uri_size);
+
+ // We should be able to read the file.
+ File file = OpenContentUriForRead(path);
+ EXPECT_TRUE(file.IsValid());
+ auto buffer = std::make_unique<char[]>(image_size);
+ EXPECT_TRUE(file.ReadAtCurrentPos(buffer.get(), image_size));
+}
+
+TEST_F(FileUtilTest, NonExistentContentUriTest) {
+ FilePath path("content://foo.bar");
+ EXPECT_TRUE(path.IsContentUri());
+ EXPECT_FALSE(PathExists(path));
+ // Size should be smaller than 0.
+ int64_t size;
+ EXPECT_FALSE(GetFileSize(path, &size));
+
+ // We should not be able to read the file.
+ File file = OpenContentUriForRead(path);
+ EXPECT_FALSE(file.IsValid());
+}
+#endif
+
+// Test that temp files obtained racily are all unique (no interference between
+// threads). Mimics file operations in DoLaunchChildTestProcess() to rule out
+// thread-safety issues @ https://crbug.com/826408#c17.
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/844416): Too slow to run on infra due to QEMU overloads.
+#define MAYBE_MultiThreadedTempFiles DISABLED_MultiThreadedTempFiles
+#else
+#define MAYBE_MultiThreadedTempFiles MultiThreadedTempFiles
+#endif
+TEST(FileUtilMultiThreadedTest, MAYBE_MultiThreadedTempFiles) {
+ constexpr int kNumThreads = 64;
+ constexpr int kNumWritesPerThread = 32;
+
+ std::unique_ptr<Thread> threads[kNumThreads];
+ for (auto& thread : threads) {
+ thread = std::make_unique<Thread>("test worker");
+ thread->Start();
+ }
+
+ // Wait until all threads are started for max parallelism.
+ for (auto& thread : threads)
+ thread->WaitUntilThreadStarted();
+
+ const RepeatingClosure open_write_close_read = BindRepeating([]() {
+ FilePath output_filename;
+ ScopedFILE output_file(CreateAndOpenTemporaryFile(&output_filename));
+ EXPECT_TRUE(output_file);
+
+ const std::string content = GenerateGUID();
+#if defined(OS_WIN)
+ HANDLE handle =
+ reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(output_file.get())));
+ DWORD bytes_written = 0;
+ ::WriteFile(handle, content.c_str(), content.length(), &bytes_written,
+ NULL);
+#else
+ size_t bytes_written =
+ ::write(::fileno(output_file.get()), content.c_str(), content.length());
+#endif
+ EXPECT_EQ(content.length(), bytes_written);
+ ::fflush(output_file.get());
+ output_file.reset();
+
+ std::string output_file_contents;
+ EXPECT_TRUE(ReadFileToString(output_filename, &output_file_contents))
+ << output_filename;
+
+ EXPECT_EQ(content, output_file_contents);
+
+ DeleteFile(output_filename, false);
+ });
+
+ // Post tasks to each thread in a round-robin fashion to ensure as much
+ // parallelism as possible.
+ for (int i = 0; i < kNumWritesPerThread; ++i) {
+ for (auto& thread : threads) {
+ thread->task_runner()->PostTask(FROM_HERE, open_write_close_read);
+ }
+ }
+
+ for (auto& thread : threads)
+ thread->Stop();
+}
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+TEST(ScopedFD, ScopedFDDoesClose) {
+ int fds[2];
+ char c = 0;
+ ASSERT_EQ(0, pipe(fds));
+ const int write_end = fds[1];
+ ScopedFD read_end_closer(fds[0]);
+ {
+ ScopedFD write_end_closer(fds[1]);
+ }
+ // This is the only thread. This file descriptor should no longer be valid.
+ int ret = close(write_end);
+ EXPECT_EQ(-1, ret);
+ EXPECT_EQ(EBADF, errno);
+ // Make sure read(2) won't block.
+ ASSERT_EQ(0, fcntl(fds[0], F_SETFL, O_NONBLOCK));
+ // Reading the pipe should EOF.
+ EXPECT_EQ(0, read(fds[0], &c, 1));
+}
+
+#if defined(GTEST_HAS_DEATH_TEST)
+void CloseWithScopedFD(int fd) {
+ ScopedFD fd_closer(fd);
+}
+#endif
+
+TEST(ScopedFD, ScopedFDCrashesOnCloseFailure) {
+ int fds[2];
+ ASSERT_EQ(0, pipe(fds));
+ ScopedFD read_end_closer(fds[0]);
+ EXPECT_EQ(0, IGNORE_EINTR(close(fds[1])));
+#if defined(GTEST_HAS_DEATH_TEST)
+ // This is the only thread. This file descriptor should no longer be valid.
+ // Trying to close it should crash. This is important for security.
+ EXPECT_DEATH(CloseWithScopedFD(fds[1]), "");
+#endif
+}
+
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+} // namespace
+
+} // namespace base
diff --git a/base/files/memory_mapped_file_unittest.cc b/base/files/memory_mapped_file_unittest.cc
new file mode 100644
index 0000000000..b7acc61887
--- /dev/null
+++ b/base/files/memory_mapped_file_unittest.cc
@@ -0,0 +1,243 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/memory_mapped_file.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace base {
+
+namespace {
+
+// Create a temporary buffer and fill it with a watermark sequence.
+std::unique_ptr<uint8_t[]> CreateTestBuffer(size_t size, size_t offset) {
+ std::unique_ptr<uint8_t[]> buf(new uint8_t[size]);
+ for (size_t i = 0; i < size; ++i)
+ buf.get()[i] = static_cast<uint8_t>((offset + i) % 253);
+ return buf;
+}
+
+// Check that the watermark sequence is consistent with the |offset| provided.
+bool CheckBufferContents(const uint8_t* data, size_t size, size_t offset) {
+ std::unique_ptr<uint8_t[]> test_data(CreateTestBuffer(size, offset));
+ return memcmp(test_data.get(), data, size) == 0;
+}
+
+class MemoryMappedFileTest : public PlatformTest {
+ protected:
+ void SetUp() override {
+ PlatformTest::SetUp();
+ CreateTemporaryFile(&temp_file_path_);
+ }
+
+ void TearDown() override { EXPECT_TRUE(DeleteFile(temp_file_path_, false)); }
+
+ void CreateTemporaryTestFile(size_t size) {
+ File file(temp_file_path_,
+ File::FLAG_CREATE_ALWAYS | File::FLAG_READ | File::FLAG_WRITE);
+ EXPECT_TRUE(file.IsValid());
+
+ std::unique_ptr<uint8_t[]> test_data(CreateTestBuffer(size, 0));
+ size_t bytes_written =
+ file.Write(0, reinterpret_cast<char*>(test_data.get()), size);
+ EXPECT_EQ(size, bytes_written);
+ file.Close();
+ }
+
+ const FilePath temp_file_path() const { return temp_file_path_; }
+
+ private:
+ FilePath temp_file_path_;
+};
+
+TEST_F(MemoryMappedFileTest, MapWholeFileByPath) {
+ const size_t kFileSize = 68 * 1024;
+ CreateTemporaryTestFile(kFileSize);
+ MemoryMappedFile map;
+ map.Initialize(temp_file_path());
+ ASSERT_EQ(kFileSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kFileSize, 0));
+}
+
+TEST_F(MemoryMappedFileTest, MapWholeFileByFD) {
+ const size_t kFileSize = 68 * 1024;
+ CreateTemporaryTestFile(kFileSize);
+ MemoryMappedFile map;
+ map.Initialize(File(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ));
+ ASSERT_EQ(kFileSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kFileSize, 0));
+}
+
+TEST_F(MemoryMappedFileTest, MapSmallFile) {
+ const size_t kFileSize = 127;
+ CreateTemporaryTestFile(kFileSize);
+ MemoryMappedFile map;
+ map.Initialize(temp_file_path());
+ ASSERT_EQ(kFileSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kFileSize, 0));
+}
+
+TEST_F(MemoryMappedFileTest, MapWholeFileUsingRegion) {
+ const size_t kFileSize = 157 * 1024;
+ CreateTemporaryTestFile(kFileSize);
+ MemoryMappedFile map;
+
+ File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
+ map.Initialize(std::move(file), MemoryMappedFile::Region::kWholeFile);
+ ASSERT_EQ(kFileSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kFileSize, 0));
+}
+
+TEST_F(MemoryMappedFileTest, MapPartialRegionAtBeginning) {
+ const size_t kFileSize = 157 * 1024;
+ const size_t kPartialSize = 4 * 1024 + 32;
+ CreateTemporaryTestFile(kFileSize);
+ MemoryMappedFile map;
+
+ File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
+ MemoryMappedFile::Region region = {0, kPartialSize};
+ map.Initialize(std::move(file), region);
+ ASSERT_EQ(kPartialSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kPartialSize, 0));
+}
+
+TEST_F(MemoryMappedFileTest, MapPartialRegionAtEnd) {
+ const size_t kFileSize = 157 * 1024;
+ const size_t kPartialSize = 5 * 1024 - 32;
+ const size_t kOffset = kFileSize - kPartialSize;
+ CreateTemporaryTestFile(kFileSize);
+ MemoryMappedFile map;
+
+ File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
+ MemoryMappedFile::Region region = {kOffset, kPartialSize};
+ map.Initialize(std::move(file), region);
+ ASSERT_EQ(kPartialSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kPartialSize, kOffset));
+}
+
+TEST_F(MemoryMappedFileTest, MapSmallPartialRegionInTheMiddle) {
+ const size_t kFileSize = 157 * 1024;
+ const size_t kOffset = 1024 * 5 + 32;
+ const size_t kPartialSize = 8;
+
+ CreateTemporaryTestFile(kFileSize);
+ MemoryMappedFile map;
+
+ File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
+ MemoryMappedFile::Region region = {kOffset, kPartialSize};
+ map.Initialize(std::move(file), region);
+ ASSERT_EQ(kPartialSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kPartialSize, kOffset));
+}
+
+TEST_F(MemoryMappedFileTest, MapLargePartialRegionInTheMiddle) {
+ const size_t kFileSize = 157 * 1024;
+ const size_t kOffset = 1024 * 5 + 32;
+ const size_t kPartialSize = 16 * 1024 - 32;
+
+ CreateTemporaryTestFile(kFileSize);
+ MemoryMappedFile map;
+
+ File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
+ MemoryMappedFile::Region region = {kOffset, kPartialSize};
+ map.Initialize(std::move(file), region);
+ ASSERT_EQ(kPartialSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kPartialSize, kOffset));
+}
+
+TEST_F(MemoryMappedFileTest, WriteableFile) {
+ const size_t kFileSize = 127;
+ CreateTemporaryTestFile(kFileSize);
+
+ {
+ MemoryMappedFile map;
+ map.Initialize(temp_file_path(), MemoryMappedFile::READ_WRITE);
+ ASSERT_EQ(kFileSize, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kFileSize, 0));
+
+ uint8_t* bytes = map.data();
+ bytes[0] = 'B';
+ bytes[1] = 'a';
+ bytes[2] = 'r';
+ bytes[kFileSize - 1] = '!';
+ EXPECT_FALSE(CheckBufferContents(map.data(), kFileSize, 0));
+ EXPECT_TRUE(CheckBufferContents(map.data() + 3, kFileSize - 4, 3));
+ }
+
+ int64_t file_size;
+ ASSERT_TRUE(GetFileSize(temp_file_path(), &file_size));
+ EXPECT_EQ(static_cast<int64_t>(kFileSize), file_size);
+
+ std::string contents;
+ ASSERT_TRUE(ReadFileToString(temp_file_path(), &contents));
+ EXPECT_EQ("Bar", contents.substr(0, 3));
+ EXPECT_EQ("!", contents.substr(kFileSize - 1, 1));
+}
+
+TEST_F(MemoryMappedFileTest, ExtendableFile) {
+ const size_t kFileSize = 127;
+ const size_t kFileExtend = 100;
+ CreateTemporaryTestFile(kFileSize);
+
+ {
+ File file(temp_file_path(),
+ File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
+ MemoryMappedFile::Region region = {0, kFileSize + kFileExtend};
+ MemoryMappedFile map;
+ map.Initialize(std::move(file), region,
+ MemoryMappedFile::READ_WRITE_EXTEND);
+ EXPECT_EQ(kFileSize + kFileExtend, map.length());
+ ASSERT_TRUE(map.data() != nullptr);
+ EXPECT_TRUE(map.IsValid());
+ ASSERT_TRUE(CheckBufferContents(map.data(), kFileSize, 0));
+
+ uint8_t* bytes = map.data();
+ EXPECT_EQ(0, bytes[kFileSize + 0]);
+ EXPECT_EQ(0, bytes[kFileSize + 1]);
+ EXPECT_EQ(0, bytes[kFileSize + 2]);
+ bytes[kFileSize + 0] = 'B';
+ bytes[kFileSize + 1] = 'A';
+ bytes[kFileSize + 2] = 'Z';
+ EXPECT_TRUE(CheckBufferContents(map.data(), kFileSize, 0));
+ }
+
+ int64_t file_size;
+ ASSERT_TRUE(GetFileSize(temp_file_path(), &file_size));
+ EXPECT_LE(static_cast<int64_t>(kFileSize + 3), file_size);
+ EXPECT_GE(static_cast<int64_t>(kFileSize + kFileExtend), file_size);
+
+ std::string contents;
+ ASSERT_TRUE(ReadFileToString(temp_file_path(), &contents));
+ EXPECT_EQ("BAZ", contents.substr(kFileSize, 3));
+}
+
+} // namespace
+
+} // namespace base
diff --git a/base/hash_unittest.cc b/base/hash_unittest.cc
new file mode 100644
index 0000000000..fc8a7519ee
--- /dev/null
+++ b/base/hash_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/hash.h"
+
+#include <string>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(HashTest, String) {
+ std::string str;
+ // Empty string (should hash to 0).
+ str = "";
+ EXPECT_EQ(0u, Hash(str));
+
+ // Simple test.
+ str = "hello world";
+ EXPECT_EQ(2794219650u, Hash(str));
+
+ // Change one bit.
+ str = "helmo world";
+ EXPECT_EQ(1006697176u, Hash(str));
+
+ // Insert a null byte.
+ str = "hello world";
+ str[5] = '\0';
+ EXPECT_EQ(2319902537u, Hash(str));
+
+ // Test that the bytes after the null contribute to the hash.
+ str = "hello worle";
+ str[5] = '\0';
+ EXPECT_EQ(553904462u, Hash(str));
+
+ // Extremely long string.
+ // Also tests strings with high bit set, and null byte.
+ std::vector<char> long_string_buffer;
+ for (int i = 0; i < 4096; ++i)
+ long_string_buffer.push_back((i % 256) - 128);
+ str.assign(&long_string_buffer.front(), long_string_buffer.size());
+ EXPECT_EQ(2797962408u, Hash(str));
+
+ // All possible lengths (mod 4). Tests separate code paths. Also test with
+ // final byte high bit set (regression test for http://crbug.com/90659).
+ // Note that the 1 and 3 cases have a weird bug where the final byte is
+ // treated as a signed char. It was decided on the above bug discussion to
+ // enshrine that behaviour as "correct" to avoid invalidating existing hashes.
+
+ // Length mod 4 == 0.
+ str = "hello w\xab";
+ EXPECT_EQ(615571198u, Hash(str));
+ // Length mod 4 == 1.
+ str = "hello wo\xab";
+ EXPECT_EQ(623474296u, Hash(str));
+ // Length mod 4 == 2.
+ str = "hello wor\xab";
+ EXPECT_EQ(4278562408u, Hash(str));
+ // Length mod 4 == 3.
+ str = "hello worl\xab";
+ EXPECT_EQ(3224633008u, Hash(str));
+}
+
+TEST(HashTest, CString) {
+ const char* str;
+ // Empty string (should hash to 0).
+ str = "";
+ EXPECT_EQ(0u, Hash(str, strlen(str)));
+
+ // Simple test.
+ str = "hello world";
+ EXPECT_EQ(2794219650u, Hash(str, strlen(str)));
+
+ // Ensure that it stops reading after the given length, and does not expect a
+ // null byte.
+ str = "hello world; don't read this part";
+ EXPECT_EQ(2794219650u, Hash(str, strlen("hello world")));
+}
+
+} // namespace base
diff --git a/base/i18n/base_i18n_switches.cc b/base/i18n/base_i18n_switches.cc
new file mode 100644
index 0000000000..103d6653d3
--- /dev/null
+++ b/base/i18n/base_i18n_switches.cc
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/base_i18n_switches.h"
+
+namespace switches {
+
+// Force the UI to a specific direction. Valid values are "ltr" (left-to-right)
+// and "rtl" (right-to-left).
+const char kForceUIDirection[] = "force-ui-direction";
+
+// Force the text rendering to a specific direction. Valid values are "ltr"
+// (left-to-right) and "rtl" (right-to-left). Only tested meaningfully with
+// RTL.
+const char kForceTextDirection[] = "force-text-direction";
+
+const char kForceDirectionLTR[] = "ltr";
+const char kForceDirectionRTL[] = "rtl";
+
+} // namespace switches
diff --git a/base/i18n/base_i18n_switches.h b/base/i18n/base_i18n_switches.h
new file mode 100644
index 0000000000..d1ba690642
--- /dev/null
+++ b/base/i18n/base_i18n_switches.h
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_BASE_I18N_SWITCHES_H_
+#define BASE_I18N_BASE_I18N_SWITCHES_H_
+
+#include "base/i18n/base_i18n_export.h"
+
+namespace switches {
+
+BASE_I18N_EXPORT extern const char kForceUIDirection[];
+BASE_I18N_EXPORT extern const char kForceTextDirection[];
+
+// kForce*Direction choices for the switches above.
+BASE_I18N_EXPORT extern const char kForceDirectionLTR[];
+BASE_I18N_EXPORT extern const char kForceDirectionRTL[];
+
+} // namespace switches
+
+#endif // BASE_I18N_BASE_I18N_SWITCHES_H_
diff --git a/base/i18n/bidi_line_iterator.cc b/base/i18n/bidi_line_iterator.cc
new file mode 100644
index 0000000000..3f7f868661
--- /dev/null
+++ b/base/i18n/bidi_line_iterator.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/bidi_line_iterator.h"
+
+#include "base/logging.h"
+
+namespace base {
+namespace i18n {
+
+namespace {
+
+UBiDiLevel GetParagraphLevelForDirection(TextDirection direction) {
+ switch (direction) {
+ case UNKNOWN_DIRECTION:
+ return UBIDI_DEFAULT_LTR;
+ break;
+ case RIGHT_TO_LEFT:
+ return 1; // Highest RTL level.
+ break;
+ case LEFT_TO_RIGHT:
+ return 0; // Highest LTR level.
+ break;
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+// Overrides the default bidi class for a given character, for the custom
+// "AS_URL" behavior. Returns U_BIDI_CLASS_DEFAULT to defer to the default ICU
+// behavior.
+//
+// Matches the C callback interface of ICU's UBiDiClassCallback type (which is
+// why there is an unused argument).
+UCharDirection GetURLBiDiClassCallback(const void* /*unused*/, UChar32 c) {
+ // Note: Use a switch statement instead of strchr() to avoid iterating over a
+ // string for each character (the switch allows for much better compiler
+ // optimization).
+ switch (c) {
+ // The set of characters that delimit URL components (separating the scheme,
+ // username, password, domain labels, host, path segments, query
+ // names/values and fragment).
+ case '#':
+ case '&':
+ case '.':
+ case '/':
+ case ':':
+ case '=':
+ case '?':
+ case '@':
+ // Treat all of these characters as strong LTR, which effectively
+ // surrounds all of the text components of a URL (e.g., the domain labels
+ // and path segments) in a left-to-right embedding. This ensures that the
+ // URL components read from left to right, regardless of any RTL
+ // characters. (Within each component, RTL sequences are rendered from
+ // right to left as expected.)
+ return U_LEFT_TO_RIGHT;
+ default:
+ return U_BIDI_CLASS_DEFAULT;
+ }
+}
+
+} // namespace
+
+BiDiLineIterator::BiDiLineIterator() : bidi_(nullptr) {}
+
+BiDiLineIterator::~BiDiLineIterator() {
+ if (bidi_) {
+ ubidi_close(bidi_);
+ bidi_ = nullptr;
+ }
+}
+
+bool BiDiLineIterator::Open(const string16& text,
+ TextDirection direction,
+ CustomBehavior behavior) {
+ DCHECK(!bidi_);
+ UErrorCode error = U_ZERO_ERROR;
+ bidi_ = ubidi_openSized(static_cast<int>(text.length()), 0, &error);
+ if (U_FAILURE(error))
+ return false;
+
+ if (behavior == CustomBehavior::AS_URL) {
+ ubidi_setClassCallback(bidi_, GetURLBiDiClassCallback, nullptr, nullptr,
+ nullptr, &error);
+ if (U_FAILURE(error))
+ return false;
+ }
+
+ ubidi_setPara(bidi_, text.data(), static_cast<int>(text.length()),
+ GetParagraphLevelForDirection(direction), nullptr, &error);
+ return (U_SUCCESS(error));
+}
+
+int BiDiLineIterator::CountRuns() const {
+ DCHECK(bidi_ != nullptr);
+ UErrorCode error = U_ZERO_ERROR;
+ const int runs = ubidi_countRuns(bidi_, &error);
+ return U_SUCCESS(error) ? runs : 0;
+}
+
+UBiDiDirection BiDiLineIterator::GetVisualRun(int index,
+ int* start,
+ int* length) const {
+ DCHECK(bidi_ != nullptr);
+ return ubidi_getVisualRun(bidi_, index, start, length);
+}
+
+void BiDiLineIterator::GetLogicalRun(int start,
+ int* end,
+ UBiDiLevel* level) const {
+ DCHECK(bidi_ != nullptr);
+ ubidi_getLogicalRun(bidi_, start, end, level);
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/bidi_line_iterator.h b/base/i18n/bidi_line_iterator.h
new file mode 100644
index 0000000000..d840f6197b
--- /dev/null
+++ b/base/i18n/bidi_line_iterator.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_BIDI_LINE_ITERATOR_H_
+#define BASE_I18N_BIDI_LINE_ITERATOR_H_
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/i18n/rtl.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "third_party/icu/source/common/unicode/ubidi.h"
+#include "third_party/icu/source/common/unicode/uchar.h"
+
+namespace base {
+namespace i18n {
+
+// A simple wrapper class for the bidirectional iterator of ICU.
+// This class uses the bidirectional iterator of ICU to split a line of
+// bidirectional texts into visual runs in its display order.
+class BASE_I18N_EXPORT BiDiLineIterator {
+ public:
+ // Specifies some alternative iteration behavior.
+ enum class CustomBehavior {
+ // No special behavior.
+ NONE,
+ // Treat URL delimiter characters as strong LTR. This is a special treatment
+ // for URLs that purposefully violates the URL Standard, as an experiment.
+ // It should only be used behind a flag.
+ AS_URL
+ };
+
+ BiDiLineIterator();
+ ~BiDiLineIterator();
+
+ // Initializes the bidirectional iterator with the specified text. Returns
+ // whether initialization succeeded.
+ bool Open(const string16& text,
+ TextDirection direction,
+ CustomBehavior behavior);
+
+ // Returns the number of visual runs in the text, or zero on error.
+ int CountRuns() const;
+
+ // Gets the logical offset, length, and direction of the specified visual run.
+ UBiDiDirection GetVisualRun(int index, int* start, int* length) const;
+
+ // Given a start position, figure out where the run ends (and the BiDiLevel).
+ void GetLogicalRun(int start, int* end, UBiDiLevel* level) const;
+
+ private:
+ UBiDi* bidi_;
+
+ DISALLOW_COPY_AND_ASSIGN(BiDiLineIterator);
+};
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_BIDI_LINE_ITERATOR_H_
diff --git a/base/i18n/bidi_line_iterator_unittest.cc b/base/i18n/bidi_line_iterator_unittest.cc
new file mode 100644
index 0000000000..d531313bf1
--- /dev/null
+++ b/base/i18n/bidi_line_iterator_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/bidi_line_iterator.h"
+
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace i18n {
+namespace {
+
+class BiDiLineIteratorTest : public testing::TestWithParam<TextDirection> {
+ public:
+ BiDiLineIteratorTest() = default;
+
+ BiDiLineIterator* iterator() { return &iterator_; }
+
+ private:
+ BiDiLineIterator iterator_;
+
+ DISALLOW_COPY_AND_ASSIGN(BiDiLineIteratorTest);
+};
+
+TEST_P(BiDiLineIteratorTest, OnlyLTR) {
+ iterator()->Open(UTF8ToUTF16("abc 😁 测试"), GetParam(),
+ BiDiLineIterator::CustomBehavior::NONE);
+ ASSERT_EQ(1, iterator()->CountRuns());
+
+ int start, length;
+ EXPECT_EQ(UBIDI_LTR, iterator()->GetVisualRun(0, &start, &length));
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(9, length);
+
+ int end;
+ UBiDiLevel level;
+ iterator()->GetLogicalRun(0, &end, &level);
+ EXPECT_EQ(9, end);
+ if (GetParam() == TextDirection::RIGHT_TO_LEFT)
+ EXPECT_EQ(2, level);
+ else
+ EXPECT_EQ(0, level);
+}
+
+TEST_P(BiDiLineIteratorTest, OnlyRTL) {
+ iterator()->Open(UTF8ToUTF16("מה השעה"), GetParam(),
+ BiDiLineIterator::CustomBehavior::NONE);
+ ASSERT_EQ(1, iterator()->CountRuns());
+
+ int start, length;
+ EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(0, &start, &length));
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(7, length);
+
+ int end;
+ UBiDiLevel level;
+ iterator()->GetLogicalRun(0, &end, &level);
+ EXPECT_EQ(7, end);
+ EXPECT_EQ(1, level);
+}
+
+TEST_P(BiDiLineIteratorTest, Mixed) {
+ iterator()->Open(UTF8ToUTF16("אני משתמש ב- Chrome כדפדפן האינטרנט שלי"),
+ GetParam(), BiDiLineIterator::CustomBehavior::NONE);
+ ASSERT_EQ(3, iterator()->CountRuns());
+
+ // We'll get completely different results depending on the top-level paragraph
+ // direction.
+ if (GetParam() == TextDirection::RIGHT_TO_LEFT) {
+ // If para direction is RTL, expect the LTR substring "Chrome" to be nested
+ // within the surrounding RTL text.
+ int start, length;
+ EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(0, &start, &length));
+ EXPECT_EQ(19, start);
+ EXPECT_EQ(20, length);
+ EXPECT_EQ(UBIDI_LTR, iterator()->GetVisualRun(1, &start, &length));
+ EXPECT_EQ(13, start);
+ EXPECT_EQ(6, length);
+ EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(2, &start, &length));
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(13, length);
+
+ int end;
+ UBiDiLevel level;
+ iterator()->GetLogicalRun(0, &end, &level);
+ EXPECT_EQ(13, end);
+ EXPECT_EQ(1, level);
+ iterator()->GetLogicalRun(13, &end, &level);
+ EXPECT_EQ(19, end);
+ EXPECT_EQ(2, level);
+ iterator()->GetLogicalRun(19, &end, &level);
+ EXPECT_EQ(39, end);
+ EXPECT_EQ(1, level);
+ } else {
+ // If the para direction is LTR, expect the LTR substring "- Chrome " to be
+ // at the top level, with two nested RTL runs on either side.
+ int start, length;
+ EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(0, &start, &length));
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(11, length);
+ EXPECT_EQ(UBIDI_LTR, iterator()->GetVisualRun(1, &start, &length));
+ EXPECT_EQ(11, start);
+ EXPECT_EQ(9, length);
+ EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(2, &start, &length));
+ EXPECT_EQ(20, start);
+ EXPECT_EQ(19, length);
+
+ int end;
+ UBiDiLevel level;
+ iterator()->GetLogicalRun(0, &end, &level);
+ EXPECT_EQ(11, end);
+ EXPECT_EQ(1, level);
+ iterator()->GetLogicalRun(11, &end, &level);
+ EXPECT_EQ(20, end);
+ EXPECT_EQ(0, level);
+ iterator()->GetLogicalRun(20, &end, &level);
+ EXPECT_EQ(39, end);
+ EXPECT_EQ(1, level);
+ }
+}
+
+TEST_P(BiDiLineIteratorTest, RTLPunctuationNoCustomBehavior) {
+ // This string features Hebrew characters interleaved with ASCII punctuation.
+ iterator()->Open(UTF8ToUTF16("א!ב\"ג#ד$ה%ו&ז'ח(ט)י*ך+כ,ל-ם.מ/"
+ "ן:נ;ס<ע=ף>פ?ץ@צ[ק\\ר]ש^ת_א`ב{ג|ד}ה~ו"),
+ GetParam(), BiDiLineIterator::CustomBehavior::NONE);
+
+ // Expect a single RTL run.
+ ASSERT_EQ(1, iterator()->CountRuns());
+
+ int start, length;
+ EXPECT_EQ(UBIDI_RTL, iterator()->GetVisualRun(0, &start, &length));
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(65, length);
+
+ int end;
+ UBiDiLevel level;
+ iterator()->GetLogicalRun(0, &end, &level);
+ EXPECT_EQ(65, end);
+ EXPECT_EQ(1, level);
+}
+
+TEST_P(BiDiLineIteratorTest, RTLPunctuationAsURL) {
+ // This string features Hebrew characters interleaved with ASCII punctuation.
+ iterator()->Open(UTF8ToUTF16("א!ב\"ג#ד$ה%ו&ז'ח(ט)י*ך+כ,ל-ם.מ/"
+ "ן:נ;ס<ע=ף>פ?ץ@צ[ק\\ר]ש^ת_א`ב{ג|ד}ה~ו"),
+ GetParam(), BiDiLineIterator::CustomBehavior::AS_URL);
+
+ const int kStringSize = 65;
+
+ // Expect a primary RTL run, broken up by each of the 8 punctuation marks that
+ // are considered strong LTR (17 runs total).
+ struct {
+ int start;
+ UBiDiDirection dir;
+ } expected_runs[] = {
+ {0, UBIDI_RTL}, {5, UBIDI_LTR}, // '#'
+ {6, UBIDI_RTL}, {11, UBIDI_LTR}, // '&'
+ {12, UBIDI_RTL}, {27, UBIDI_LTR}, // '.'
+ {28, UBIDI_RTL}, {29, UBIDI_LTR}, // '/'
+ {30, UBIDI_RTL}, {31, UBIDI_LTR}, // ':'
+ {32, UBIDI_RTL}, {37, UBIDI_LTR}, // '='
+ {38, UBIDI_RTL}, {41, UBIDI_LTR}, // '?'
+ {42, UBIDI_RTL}, {43, UBIDI_LTR}, // '@'
+ {44, UBIDI_RTL},
+ };
+
+ ASSERT_EQ(arraysize(expected_runs),
+ static_cast<size_t>(iterator()->CountRuns()));
+
+ for (size_t i = 0; i < arraysize(expected_runs); ++i) {
+ const auto& expected_run = expected_runs[i];
+ int expected_run_end = i >= arraysize(expected_runs) - 1
+ ? kStringSize
+ : expected_runs[i + 1].start;
+
+ size_t visual_index = GetParam() == TextDirection::RIGHT_TO_LEFT
+ ? arraysize(expected_runs) - 1 - i
+ : i;
+ int start, length;
+ EXPECT_EQ(expected_run.dir,
+ iterator()->GetVisualRun(visual_index, &start, &length))
+ << "(i = " << i << ")";
+ EXPECT_EQ(expected_run.start, start) << "(i = " << i << ")";
+ EXPECT_EQ(expected_run_end - expected_run.start, length)
+ << "(i = " << i << ")";
+
+ int expected_level =
+ expected_run.dir == UBIDI_RTL
+ ? 1
+ : (GetParam() == TextDirection::RIGHT_TO_LEFT ? 2 : 0);
+ int end;
+ UBiDiLevel level;
+ iterator()->GetLogicalRun(expected_run.start, &end, &level);
+ EXPECT_EQ(expected_run_end, end) << "(i = " << i << ")";
+ EXPECT_EQ(expected_level, level) << "(i = " << i << ")";
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(,
+ BiDiLineIteratorTest,
+ ::testing::Values(TextDirection::LEFT_TO_RIGHT,
+ TextDirection::RIGHT_TO_LEFT));
+
+} // namespace
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/break_iterator.cc b/base/i18n/break_iterator.cc
new file mode 100644
index 0000000000..251cd002e7
--- /dev/null
+++ b/base/i18n/break_iterator.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/break_iterator.h"
+
+#include <stdint.h>
+
+#include "base/logging.h"
+#include "third_party/icu/source/common/unicode/ubrk.h"
+#include "third_party/icu/source/common/unicode/uchar.h"
+#include "third_party/icu/source/common/unicode/ustring.h"
+
+namespace base {
+namespace i18n {
+
+const size_t npos = static_cast<size_t>(-1);
+
+BreakIterator::BreakIterator(const StringPiece16& str, BreakType break_type)
+ : iter_(nullptr),
+ string_(str),
+ break_type_(break_type),
+ prev_(npos),
+ pos_(0) {}
+
+BreakIterator::BreakIterator(const StringPiece16& str, const string16& rules)
+ : iter_(nullptr),
+ string_(str),
+ rules_(rules),
+ break_type_(RULE_BASED),
+ prev_(npos),
+ pos_(0) {}
+
+BreakIterator::~BreakIterator() {
+ if (iter_)
+ ubrk_close(static_cast<UBreakIterator*>(iter_));
+}
+
+bool BreakIterator::Init() {
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError parse_error;
+ UBreakIteratorType break_type;
+ switch (break_type_) {
+ case BREAK_CHARACTER:
+ break_type = UBRK_CHARACTER;
+ break;
+ case BREAK_WORD:
+ break_type = UBRK_WORD;
+ break;
+ case BREAK_LINE:
+ case BREAK_NEWLINE:
+ case RULE_BASED: // (Keep compiler happy, break_type not used in this case)
+ break_type = UBRK_LINE;
+ break;
+ default:
+ NOTREACHED() << "invalid break_type_";
+ return false;
+ }
+ if (break_type_ == RULE_BASED) {
+ iter_ = ubrk_openRules(rules_.c_str(),
+ static_cast<int32_t>(rules_.length()),
+ string_.data(),
+ static_cast<int32_t>(string_.size()),
+ &parse_error,
+ &status);
+ if (U_FAILURE(status)) {
+ NOTREACHED() << "ubrk_openRules failed to parse rule string at line "
+ << parse_error.line << ", offset " << parse_error.offset;
+ }
+ } else {
+ iter_ = ubrk_open(break_type, nullptr, string_.data(),
+ static_cast<int32_t>(string_.size()), &status);
+ if (U_FAILURE(status)) {
+ NOTREACHED() << "ubrk_open failed for type " << break_type
+ << " with error " << status;
+ }
+ }
+
+ if (U_FAILURE(status)) {
+ return false;
+ }
+
+ // Move the iterator to the beginning of the string.
+ ubrk_first(static_cast<UBreakIterator*>(iter_));
+ return true;
+}
+
+bool BreakIterator::Advance() {
+ int32_t pos;
+ int32_t status;
+ prev_ = pos_;
+ switch (break_type_) {
+ case BREAK_CHARACTER:
+ case BREAK_WORD:
+ case BREAK_LINE:
+ case RULE_BASED:
+ pos = ubrk_next(static_cast<UBreakIterator*>(iter_));
+ if (pos == UBRK_DONE) {
+ pos_ = npos;
+ return false;
+ }
+ pos_ = static_cast<size_t>(pos);
+ return true;
+ case BREAK_NEWLINE:
+ do {
+ pos = ubrk_next(static_cast<UBreakIterator*>(iter_));
+ if (pos == UBRK_DONE)
+ break;
+ pos_ = static_cast<size_t>(pos);
+ status = ubrk_getRuleStatus(static_cast<UBreakIterator*>(iter_));
+ } while (status >= UBRK_LINE_SOFT && status < UBRK_LINE_SOFT_LIMIT);
+ if (pos == UBRK_DONE && prev_ == pos_) {
+ pos_ = npos;
+ return false;
+ }
+ return true;
+ default:
+ NOTREACHED() << "invalid break_type_";
+ return false;
+ }
+}
+
+bool BreakIterator::SetText(const base::char16* text, const size_t length) {
+ UErrorCode status = U_ZERO_ERROR;
+ ubrk_setText(static_cast<UBreakIterator*>(iter_),
+ text, length, &status);
+ pos_ = 0; // implicit when ubrk_setText is done
+ prev_ = npos;
+ if (U_FAILURE(status)) {
+ NOTREACHED() << "ubrk_setText failed";
+ return false;
+ }
+ string_ = StringPiece16(text, length);
+ return true;
+}
+
+bool BreakIterator::IsWord() const {
+ return GetWordBreakStatus() == IS_WORD_BREAK;
+}
+
+BreakIterator::WordBreakStatus BreakIterator::GetWordBreakStatus() const {
+ int32_t status = ubrk_getRuleStatus(static_cast<UBreakIterator*>(iter_));
+ if (break_type_ != BREAK_WORD && break_type_ != RULE_BASED)
+ return IS_LINE_OR_CHAR_BREAK;
+ // In ICU 60, trying to advance past the end of the text does not change
+ // |status| so that |pos_| has to be checked as well as |status|.
+ // See http://bugs.icu-project.org/trac/ticket/13447 .
+ return (status == UBRK_WORD_NONE || pos_ == npos) ? IS_SKIPPABLE_WORD
+ : IS_WORD_BREAK;
+}
+
+bool BreakIterator::IsEndOfWord(size_t position) const {
+ if (break_type_ != BREAK_WORD && break_type_ != RULE_BASED)
+ return false;
+
+ UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
+ UBool boundary = ubrk_isBoundary(iter, static_cast<int32_t>(position));
+ int32_t status = ubrk_getRuleStatus(iter);
+ return (!!boundary && status != UBRK_WORD_NONE);
+}
+
+bool BreakIterator::IsStartOfWord(size_t position) const {
+ if (break_type_ != BREAK_WORD && break_type_ != RULE_BASED)
+ return false;
+
+ UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
+ UBool boundary = ubrk_isBoundary(iter, static_cast<int32_t>(position));
+ ubrk_next(iter);
+ int32_t next_status = ubrk_getRuleStatus(iter);
+ return (!!boundary && next_status != UBRK_WORD_NONE);
+}
+
+bool BreakIterator::IsGraphemeBoundary(size_t position) const {
+ if (break_type_ != BREAK_CHARACTER)
+ return false;
+
+ UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
+ return !!ubrk_isBoundary(iter, static_cast<int32_t>(position));
+}
+
+string16 BreakIterator::GetString() const {
+ return GetStringPiece().as_string();
+}
+
+StringPiece16 BreakIterator::GetStringPiece() const {
+ DCHECK(prev_ != npos && pos_ != npos);
+ return string_.substr(prev_, pos_ - prev_);
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/break_iterator.h b/base/i18n/break_iterator.h
new file mode 100644
index 0000000000..dc30b644f7
--- /dev/null
+++ b/base/i18n/break_iterator.h
@@ -0,0 +1,182 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_BREAK_ITERATOR_H_
+#define BASE_I18N_BREAK_ITERATOR_H_
+
+#include <stddef.h>
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+
+// The BreakIterator class iterates through the words, word breaks, and
+// line breaks in a UTF-16 string.
+//
+// It provides several modes, BREAK_WORD, BREAK_LINE, and BREAK_NEWLINE,
+// which modify how characters are aggregated into the returned string.
+//
+// Under BREAK_WORD mode, once a word is encountered any non-word
+// characters are not included in the returned string (e.g. in the
+// UTF-16 equivalent of the string " foo bar! ", the word breaks are at
+// the periods in ". .foo. .bar.!. .").
+// Note that Chinese/Japanese/Thai do not use spaces between words so that
+// boundaries can fall in the middle of a continuous run of non-space /
+// non-punctuation characters.
+//
+// Under BREAK_LINE mode, once a line breaking opportunity is encountered,
+// any non-word characters are included in the returned string, breaking
+// only when a space-equivalent character or a line breaking opportunity
+// is encountered (e.g. in the UTF16-equivalent of the string " foo bar! ",
+// the breaks are at the periods in ". .foo .bar! .").
+//
+// Note that lines can be broken at any character/syllable/grapheme cluster
+// boundary in Chinese/Japanese/Korean and at word boundaries in Thai
+// (Thai does not use spaces between words). Therefore, this is NOT the same
+// as breaking only at space-equivalent characters where its former
+// name (BREAK_SPACE) implied.
+//
+// Under BREAK_NEWLINE mode, all characters are included in the returned
+// string, breaking only when a newline-equivalent character is encountered
+// (eg. in the UTF-16 equivalent of the string "foo\nbar!\n\n", the line
+// breaks are at the periods in ".foo\n.bar\n.\n.").
+//
+// To extract the words from a string, move a BREAK_WORD BreakIterator
+// through the string and test whether IsWord() is true. E.g.,
+// BreakIterator iter(str, BreakIterator::BREAK_WORD);
+// if (!iter.Init())
+// return false;
+// while (iter.Advance()) {
+// if (iter.IsWord()) {
+// // Region [iter.prev(), iter.pos()) contains a word.
+// VLOG(1) << "word: " << iter.GetString();
+// }
+// }
+
+namespace base {
+namespace i18n {
+
+class BASE_I18N_EXPORT BreakIterator {
+ public:
+ enum BreakType {
+ BREAK_WORD,
+ BREAK_LINE,
+ // TODO(jshin): Remove this after reviewing call sites.
+ // If call sites really need break only on space-like characters
+ // implement it separately.
+ BREAK_SPACE = BREAK_LINE,
+ BREAK_NEWLINE,
+ BREAK_CHARACTER,
+ // But don't remove this one!
+ RULE_BASED,
+ };
+
+ enum WordBreakStatus {
+ // The end of text that the iterator recognizes as word characters.
+ // Non-word characters are things like punctuation and spaces.
+ IS_WORD_BREAK,
+ // Characters that the iterator can skip past, such as punctuation,
+ // whitespace, and, if using RULE_BASED mode, characters from another
+ // character set.
+ IS_SKIPPABLE_WORD,
+ // Only used if not in BREAK_WORD or RULE_BASED mode. This is returned for
+ // newlines, line breaks, and character breaks.
+ IS_LINE_OR_CHAR_BREAK
+ };
+
+ // Requires |str| to live as long as the BreakIterator does.
+ BreakIterator(const StringPiece16& str, BreakType break_type);
+ // Make a rule-based iterator. BreakType == RULE_BASED is implied.
+ // TODO(andrewhayden): This signature could easily be misinterpreted as
+ // "(const string16& str, const string16& locale)". We should do something
+ // better.
+ BreakIterator(const StringPiece16& str, const string16& rules);
+ ~BreakIterator();
+
+ // Init() must be called before any of the iterators are valid.
+ // Returns false if ICU failed to initialize.
+ bool Init();
+
+ // Advance to the next break. Returns false if we've run past the end of
+ // the string. (Note that the very last "break" is after the final
+ // character in the string, and when we advance to that position it's the
+ // last time Advance() returns true.)
+ bool Advance();
+
+ // Updates the text used by the iterator, resetting the iterator as if
+ // if Init() had been called again. Any old state is lost. Returns true
+ // unless there is an error setting the text.
+ bool SetText(const base::char16* text, const size_t length);
+
+ // Under BREAK_WORD mode, returns true if the break we just hit is the
+ // end of a word. (Otherwise, the break iterator just skipped over e.g.
+ // whitespace or punctuation.) Under BREAK_LINE and BREAK_NEWLINE modes,
+ // this distinction doesn't apply and it always returns false.
+ bool IsWord() const;
+
+ // Under BREAK_WORD mode:
+ // - Returns IS_SKIPPABLE_WORD if non-word characters, such as punctuation or
+ // spaces, are found.
+ // - Returns IS_WORD_BREAK if the break we just hit is the end of a sequence
+ // of word characters.
+ // Under RULE_BASED mode:
+ // - Returns IS_SKIPPABLE_WORD if characters outside the rules' character set
+ // or non-word characters, such as punctuation or spaces, are found.
+ // - Returns IS_WORD_BREAK if the break we just hit is the end of a sequence
+ // of word characters that are in the rules' character set.
+ // Not under BREAK_WORD or RULE_BASED mode:
+ // - Returns IS_LINE_OR_CHAR_BREAK.
+ BreakIterator::WordBreakStatus GetWordBreakStatus() const;
+
+ // Under BREAK_WORD mode, returns true if |position| is at the end of word or
+ // at the start of word. It always returns false under BREAK_LINE and
+ // BREAK_NEWLINE modes.
+ bool IsEndOfWord(size_t position) const;
+ bool IsStartOfWord(size_t position) const;
+
+ // Under BREAK_CHARACTER mode, returns whether |position| is a Unicode
+ // grapheme boundary.
+ bool IsGraphemeBoundary(size_t position) const;
+
+ // Returns the string between prev() and pos().
+ // Advance() must have been called successfully at least once for pos() to
+ // have advanced to somewhere useful.
+ string16 GetString() const;
+
+ StringPiece16 GetStringPiece() const;
+
+ // Returns the value of pos() returned before Advance() was last called.
+ size_t prev() const { return prev_; }
+
+ // Returns the current break position within the string,
+ // or BreakIterator::npos when done.
+ size_t pos() const { return pos_; }
+
+ private:
+ // ICU iterator, avoiding ICU ubrk.h dependence.
+ // This is actually an ICU UBreakiterator* type, which turns out to be
+ // a typedef for a void* in the ICU headers. Using void* directly prevents
+ // callers from needing access to the ICU public headers directory.
+ void* iter_;
+
+ // The string we're iterating over. Can be changed with SetText(...)
+ StringPiece16 string_;
+
+ // Rules for our iterator. Mutually exclusive with break_type_.
+ const string16 rules_;
+
+ // The breaking style (word/space/newline). Mutually exclusive with rules_
+ BreakType break_type_;
+
+ // Previous and current iterator positions.
+ size_t prev_, pos_;
+
+ DISALLOW_COPY_AND_ASSIGN(BreakIterator);
+};
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_BREAK_ITERATOR_H_
diff --git a/base/i18n/break_iterator_unittest.cc b/base/i18n/break_iterator_unittest.cc
new file mode 100644
index 0000000000..ed5de448a4
--- /dev/null
+++ b/base/i18n/break_iterator_unittest.cc
@@ -0,0 +1,584 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/break_iterator.h"
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace i18n {
+
+TEST(BreakIteratorTest, BreakWordEmpty) {
+ string16 empty;
+ BreakIterator iter(empty, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakWord) {
+ string16 space(UTF8ToUTF16(" "));
+ string16 str(UTF8ToUTF16(" foo bar! \npouet boom"));
+ BreakIterator iter(str, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(space, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("foo"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(space, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("bar"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("!"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(space, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("\n"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("pouet"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(space, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("boom"), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakWordWide16) {
+ // Two greek words separated by space.
+ const string16 str(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
+ L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2"));
+ const string16 word1(str.substr(0, 10));
+ const string16 word2(str.substr(11, 5));
+ BreakIterator iter(str, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(word1, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(" "), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(word2, iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakWordWide32) {
+ // U+1D49C MATHEMATICAL SCRIPT CAPITAL A
+ const char very_wide_char[] = "\xF0\x9D\x92\x9C";
+ const string16 str(
+ UTF8ToUTF16(base::StringPrintf("%s a", very_wide_char)));
+ const string16 very_wide_word(str.substr(0, 2));
+
+ BreakIterator iter(str, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(very_wide_word, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(" "), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("a"), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakWordThai) {
+ // Terms in Thai, without spaces in between.
+ const char term1[] = "พิมพ์";
+ const char term2[] = "น้อย";
+ const char term3[] = "ลง";
+ const string16 str(UTF8ToUTF16(base::JoinString({term1, term2, term3}, "")));
+
+ BreakIterator iter(str, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(term1), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(term2), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(term3), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+}
+
+// In some languages, the words are not broken by spaces. ICU provides a huge
+// dictionary to detect word boundaries in Thai, Chinese, Japanese, Burmese,
+// and Khmer. Due to the size of such a table, the part for Chinese and
+// Japanese is not shipped on mobile.
+#if !(defined(OS_IOS) || defined(OS_ANDROID))
+
+TEST(BreakIteratorTest, BreakWordChinese) {
+ // Terms in Traditional Chinese, without spaces in between.
+ const char term1[] = "瀏覽";
+ const char term2[] = "速度";
+ const char term3[] = "飛快";
+ const string16 str(UTF8ToUTF16(base::JoinString({term1, term2, term3}, "")));
+
+ BreakIterator iter(str, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(term1), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(term2), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(term3), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakWordJapanese) {
+ // Terms in Japanese, without spaces in between.
+ const char term1[] = "モバイル";
+ const char term2[] = "でも";
+ const string16 str(UTF8ToUTF16(base::JoinString({term1, term2}, "")));
+
+ BreakIterator iter(str, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(term1), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(term2), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakWordChineseEnglish) {
+ // Terms in Simplified Chinese mixed with English and wide punctuations.
+ string16 space(UTF8ToUTF16(" "));
+ const char token1[] = "下载";
+ const char token2[] = "Chrome";
+ const char token3[] = "(";
+ const char token4[] = "Mac";
+ const char token5[] = "版";
+ const char token6[] = ")";
+ const string16 str(UTF8ToUTF16(base::JoinString(
+ {token1, " ", token2, token3, token4, " ", token5, token6}, "")));
+
+ BreakIterator iter(str, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(token1), iter.GetString());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(space, iter.GetString());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(token2), iter.GetString());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(token3), iter.GetString());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(token4), iter.GetString());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(space, iter.GetString());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(token5), iter.GetString());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(token6), iter.GetString());
+
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+}
+
+#endif // !(defined(OS_IOS) || defined(OS_ANDROID))
+
+TEST(BreakIteratorTest, BreakSpaceEmpty) {
+ string16 empty;
+ BreakIterator iter(empty, BreakIterator::BREAK_SPACE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakSpace) {
+ string16 str(UTF8ToUTF16(" foo bar! \npouet boom"));
+ BreakIterator iter(str, BreakIterator::BREAK_SPACE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(" "), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("foo "), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("bar! \n"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("pouet "), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("boom"), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakSpaceSP) {
+ string16 str(UTF8ToUTF16(" foo bar! \npouet boom "));
+ BreakIterator iter(str, BreakIterator::BREAK_SPACE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16(" "), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("foo "), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("bar! \n"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("pouet "), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("boom "), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakSpacekWide16) {
+ // Two Greek words.
+ const string16 str(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
+ L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2"));
+ const string16 word1(str.substr(0, 11));
+ const string16 word2(str.substr(11, 5));
+ BreakIterator iter(str, BreakIterator::BREAK_SPACE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(word1, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(word2, iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakSpaceWide32) {
+ // U+1D49C MATHEMATICAL SCRIPT CAPITAL A
+ const char very_wide_char[] = "\xF0\x9D\x92\x9C";
+ const string16 str(
+ UTF8ToUTF16(base::StringPrintf("%s a", very_wide_char)));
+ const string16 very_wide_word(str.substr(0, 3));
+
+ BreakIterator iter(str, BreakIterator::BREAK_SPACE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(very_wide_word, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("a"), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakLineEmpty) {
+ string16 empty;
+ BreakIterator iter(empty, BreakIterator::BREAK_NEWLINE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakLine) {
+ string16 nl(UTF8ToUTF16("\n"));
+ string16 str(UTF8ToUTF16("\nfoo bar!\n\npouet boom"));
+ BreakIterator iter(str, BreakIterator::BREAK_NEWLINE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(nl, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("foo bar!\n"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(nl, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("pouet boom"), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakLineNL) {
+ string16 nl(UTF8ToUTF16("\n"));
+ string16 str(UTF8ToUTF16("\nfoo bar!\n\npouet boom\n"));
+ BreakIterator iter(str, BreakIterator::BREAK_NEWLINE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(nl, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("foo bar!\n"), iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(nl, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("pouet boom\n"), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakLineWide16) {
+ // Two Greek words separated by newline.
+ const string16 str(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
+ L"\x03bf\x03c2\x000a\x0399\x03c3\x03c4\x03cc\x03c2"));
+ const string16 line1(str.substr(0, 11));
+ const string16 line2(str.substr(11, 5));
+ BreakIterator iter(str, BreakIterator::BREAK_NEWLINE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(line1, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(line2, iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakLineWide32) {
+ // U+1D49C MATHEMATICAL SCRIPT CAPITAL A
+ const char very_wide_char[] = "\xF0\x9D\x92\x9C";
+ const string16 str(
+ UTF8ToUTF16(base::StringPrintf("%s\na", very_wide_char)));
+ const string16 very_wide_line(str.substr(0, 3));
+ BreakIterator iter(str, BreakIterator::BREAK_NEWLINE);
+ ASSERT_TRUE(iter.Init());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(very_wide_line, iter.GetString());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_EQ(UTF8ToUTF16("a"), iter.GetString());
+ EXPECT_FALSE(iter.Advance());
+ EXPECT_FALSE(iter.IsWord());
+ EXPECT_FALSE(iter.Advance()); // Test unexpected advance after end.
+ EXPECT_FALSE(iter.IsWord());
+}
+
+TEST(BreakIteratorTest, BreakCharacter) {
+ static const wchar_t* kCharacters[] = {
+ // An English word consisting of four ASCII characters.
+ L"w", L"o", L"r", L"d", L" ",
+ // A Hindi word (which means "Hindi") consisting of three Devanagari
+ // characters.
+ L"\x0939\x093F", L"\x0928\x094D", L"\x0926\x0940", L" ",
+ // A Thai word (which means "feel") consisting of three Thai characters.
+ L"\x0E23\x0E39\x0E49", L"\x0E2A\x0E36", L"\x0E01", L" ",
+ };
+ std::vector<string16> characters;
+ string16 text;
+ for (size_t i = 0; i < arraysize(kCharacters); ++i) {
+ characters.push_back(WideToUTF16(kCharacters[i]));
+ text.append(characters.back());
+ }
+ BreakIterator iter(text, BreakIterator::BREAK_CHARACTER);
+ ASSERT_TRUE(iter.Init());
+ for (size_t i = 0; i < arraysize(kCharacters); ++i) {
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_EQ(characters[i], iter.GetString());
+ }
+}
+
+// Test for https://code.google.com/p/chromium/issues/detail?id=411213
+// We should be able to get valid substrings with GetString() function
+// after setting new content by calling SetText().
+TEST(BreakIteratorTest, GetStringAfterSetText) {
+ const string16 initial_string(ASCIIToUTF16("str"));
+ BreakIterator iter(initial_string, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+
+ const string16 long_string(ASCIIToUTF16("another,string"));
+ EXPECT_TRUE(iter.SetText(long_string.c_str(), long_string.size()));
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.Advance()); // Advance to ',' in |long_string|
+
+ // Check that the current position is out of bounds of the |initial_string|.
+ EXPECT_LT(initial_string.size(), iter.pos());
+
+ // Check that we can get a valid substring of |long_string|.
+ EXPECT_EQ(ASCIIToUTF16(","), iter.GetString());
+}
+
+TEST(BreakIteratorTest, GetStringPiece) {
+ const string16 initial_string(ASCIIToUTF16("some string"));
+ BreakIterator iter(initial_string, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_EQ(iter.GetString(), iter.GetStringPiece().as_string());
+ EXPECT_EQ(StringPiece16(ASCIIToUTF16("some")), iter.GetStringPiece());
+
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_EQ(iter.GetString(), iter.GetStringPiece().as_string());
+ EXPECT_EQ(StringPiece16(ASCIIToUTF16("string")), iter.GetStringPiece());
+}
+
+// Make sure that when not in RULE_BASED or BREAK_WORD mode we're getting
+// IS_LINE_OR_CHAR_BREAK.
+TEST(BreakIteratorTest, GetWordBreakStatusBreakLine) {
+ // A string containing the English word "foo", followed by two Khmer
+ // characters, the English word "Can", and then two Russian characters and
+ // punctuation.
+ base::string16 text(
+ base::WideToUTF16(L"foo \x1791\x17C1 \nCan \x041C\x0438..."));
+ BreakIterator iter(text, BreakIterator::BREAK_LINE);
+ ASSERT_TRUE(iter.Init());
+
+ EXPECT_TRUE(iter.Advance());
+ // Finds "foo" and the space.
+ EXPECT_EQ(base::UTF8ToUTF16("foo "), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_LINE_OR_CHAR_BREAK);
+ EXPECT_TRUE(iter.Advance());
+ // Finds the Khmer characters, the next space, and the newline.
+ EXPECT_EQ(base::WideToUTF16(L"\x1791\x17C1 \n"), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_LINE_OR_CHAR_BREAK);
+ EXPECT_TRUE(iter.Advance());
+ // Finds "Can" and the space.
+ EXPECT_EQ(base::UTF8ToUTF16("Can "), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_LINE_OR_CHAR_BREAK);
+ EXPECT_TRUE(iter.Advance());
+ // Finds the Russian characters and periods.
+ EXPECT_EQ(base::WideToUTF16(L"\x041C\x0438..."), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_LINE_OR_CHAR_BREAK);
+ EXPECT_FALSE(iter.Advance());
+}
+
+// Make sure that in BREAK_WORD mode we're getting IS_WORD_BREAK and
+// IS_SKIPPABLE_WORD when we should be. IS_WORD_BREAK should be returned when we
+// finish going over non-punctuation characters while IS_SKIPPABLE_WORD should
+// be returned on punctuation and spaces.
+TEST(BreakIteratorTest, GetWordBreakStatusBreakWord) {
+ // A string containing the English word "foo", followed by two Khmer
+ // characters, the English word "Can", and then two Russian characters and
+ // punctuation.
+ base::string16 text(
+ base::WideToUTF16(L"foo \x1791\x17C1 \nCan \x041C\x0438..."));
+ BreakIterator iter(text, BreakIterator::BREAK_WORD);
+ ASSERT_TRUE(iter.Init());
+
+ EXPECT_TRUE(iter.Advance());
+ // Finds "foo".
+ EXPECT_EQ(base::UTF8ToUTF16("foo"), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_WORD_BREAK);
+ EXPECT_TRUE(iter.Advance());
+ // Finds the space, and the Khmer characters.
+ EXPECT_EQ(base::UTF8ToUTF16(" "), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_SKIPPABLE_WORD);
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_EQ(base::WideToUTF16(L"\x1791\x17C1"), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_WORD_BREAK);
+ EXPECT_TRUE(iter.Advance());
+ // Finds the space and the newline.
+ EXPECT_EQ(base::UTF8ToUTF16(" "), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_SKIPPABLE_WORD);
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_EQ(base::UTF8ToUTF16("\n"), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_SKIPPABLE_WORD);
+ EXPECT_TRUE(iter.Advance());
+ // Finds "Can".
+ EXPECT_EQ(base::UTF8ToUTF16("Can"), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_WORD_BREAK);
+ EXPECT_TRUE(iter.Advance());
+ // Finds the space and the Russian characters.
+ EXPECT_EQ(base::UTF8ToUTF16(" "), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_SKIPPABLE_WORD);
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_EQ(base::WideToUTF16(L"\x041C\x0438"), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_WORD_BREAK);
+ EXPECT_TRUE(iter.Advance());
+ // Finds the trailing periods.
+ EXPECT_EQ(base::UTF8ToUTF16("."), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_SKIPPABLE_WORD);
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_EQ(base::UTF8ToUTF16("."), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_SKIPPABLE_WORD);
+ EXPECT_TRUE(iter.Advance());
+ EXPECT_EQ(base::UTF8ToUTF16("."), iter.GetString());
+ EXPECT_EQ(iter.GetWordBreakStatus(), BreakIterator::IS_SKIPPABLE_WORD);
+ EXPECT_FALSE(iter.Advance());
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/build_utf8_validator_tables.cc b/base/i18n/build_utf8_validator_tables.cc
new file mode 100644
index 0000000000..0cdcc3519a
--- /dev/null
+++ b/base/i18n/build_utf8_validator_tables.cc
@@ -0,0 +1,470 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Create a state machine for validating UTF-8. The algorithm in brief:
+// 1. Convert the complete unicode range of code points, except for the
+// surrogate code points, to an ordered array of sequences of bytes in
+// UTF-8.
+// 2. Convert individual bytes to ranges, starting from the right of each byte
+// sequence. For each range, ensure the bytes on the left and the ranges
+// on the right are the identical.
+// 3. Convert the resulting list of ranges into a state machine, collapsing
+// identical states.
+// 4. Convert the state machine to an array of bytes.
+// 5. Output as a C++ file.
+//
+// To use:
+// $ ninja -C out/Release build_utf8_validator_tables
+// $ out/Release/build_utf8_validator_tables
+// --output=base/i18n/utf8_validator_tables.cc
+// $ git add base/i18n/utf8_validator_tables.cc
+//
+// Because the table is not expected to ever change, it is checked into the
+// repository rather than being regenerated at build time.
+//
+// This code uses type uint8_t throughout to represent bytes, to avoid
+// signed/unsigned char confusion.
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "third_party/icu/source/common/unicode/utf8.h"
+
+namespace {
+
+const char kHelpText[] =
+ "Usage: build_utf8_validator_tables [ --help ] [ --output=<file> ]\n";
+
+const char kProlog[] =
+ "// Copyright 2013 The Chromium Authors. All rights reserved.\n"
+ "// Use of this source code is governed by a BSD-style license that can "
+ "be\n"
+ "// found in the LICENSE file.\n"
+ "\n"
+ "// This file is auto-generated by build_utf8_validator_tables.\n"
+ "// DO NOT EDIT.\n"
+ "\n"
+ "#include \"base/i18n/utf8_validator_tables.h\"\n"
+ "\n"
+ "namespace base {\n"
+ "namespace internal {\n"
+ "\n"
+ "const uint8_t kUtf8ValidatorTables[] = {\n";
+
+const char kEpilog[] =
+ "};\n"
+ "\n"
+ "const size_t kUtf8ValidatorTablesSize = arraysize(kUtf8ValidatorTables);\n"
+ "\n"
+ "} // namespace internal\n"
+ "} // namespace base\n";
+
+// Ranges are inclusive at both ends--they represent [from, to]
+class Range {
+ public:
+ // Ranges always start with just one byte.
+ explicit Range(uint8_t value) : from_(value), to_(value) {}
+
+ // Range objects are copyable and assignable to be used in STL
+ // containers. Since they only contain non-pointer POD types, the default copy
+ // constructor, assignment operator and destructor will work.
+
+ // Add a byte to the range. We intentionally only support adding a byte at the
+ // end, since that is the only operation the code needs.
+ void AddByte(uint8_t to) {
+ CHECK(to == to_ + 1);
+ to_ = to;
+ }
+
+ uint8_t from() const { return from_; }
+ uint8_t to() const { return to_; }
+
+ bool operator<(const Range& rhs) const {
+ return (from() < rhs.from() || (from() == rhs.from() && to() < rhs.to()));
+ }
+
+ bool operator==(const Range& rhs) const {
+ return from() == rhs.from() && to() == rhs.to();
+ }
+
+ private:
+ uint8_t from_;
+ uint8_t to_;
+};
+
+// A vector of Ranges is like a simple regular expression--it corresponds to
+// a set of strings of the same length that have bytes in each position in
+// the appropriate range.
+typedef std::vector<Range> StringSet;
+
+// A UTF-8 "character" is represented by a sequence of bytes.
+typedef std::vector<uint8_t> Character;
+
+// In the second stage of the algorithm, we want to convert a large list of
+// Characters into a small list of StringSets.
+struct Pair {
+ Character character;
+ StringSet set;
+};
+
+typedef std::vector<Pair> PairVector;
+
+// A class to print a table of numbers in the same style as clang-format.
+class TablePrinter {
+ public:
+ explicit TablePrinter(FILE* stream)
+ : stream_(stream), values_on_this_line_(0), current_offset_(0) {}
+
+ void PrintValue(uint8_t value) {
+ if (values_on_this_line_ == 0) {
+ fputs(" ", stream_);
+ } else if (values_on_this_line_ == kMaxValuesPerLine) {
+ fprintf(stream_, " // 0x%02x\n ", current_offset_);
+ values_on_this_line_ = 0;
+ }
+ fprintf(stream_, " 0x%02x,", static_cast<int>(value));
+ ++values_on_this_line_;
+ ++current_offset_;
+ }
+
+ void NewLine() {
+ while (values_on_this_line_ < kMaxValuesPerLine) {
+ fputs(" ", stream_);
+ ++values_on_this_line_;
+ }
+ fprintf(stream_, " // 0x%02x\n", current_offset_);
+ values_on_this_line_ = 0;
+ }
+
+ private:
+ // stdio stream. Not owned.
+ FILE* stream_;
+
+ // Number of values so far printed on this line.
+ int values_on_this_line_;
+
+ // Total values printed so far.
+ int current_offset_;
+
+ static const int kMaxValuesPerLine = 8;
+
+ DISALLOW_COPY_AND_ASSIGN(TablePrinter);
+};
+
+// Start by filling a PairVector with characters. The resulting vector goes from
+// "\x00" to "\xf4\x8f\xbf\xbf".
+PairVector InitializeCharacters() {
+ PairVector vector;
+ for (int i = 0; i <= 0x10FFFF; ++i) {
+ if (i >= 0xD800 && i < 0xE000) {
+ // Surrogate codepoints are not permitted. Non-character code points are
+ // explicitly permitted.
+ continue;
+ }
+ uint8_t bytes[4];
+ unsigned int offset = 0;
+ UBool is_error = false;
+ U8_APPEND(bytes, offset, arraysize(bytes), i, is_error);
+ DCHECK(!is_error);
+ DCHECK_GT(offset, 0u);
+ DCHECK_LE(offset, arraysize(bytes));
+ Pair pair = {Character(bytes, bytes + offset), StringSet()};
+ vector.push_back(pair);
+ }
+ return vector;
+}
+
+// Construct a new Pair from |character| and the concatenation of |new_range|
+// and |existing_set|, and append it to |pairs|.
+void ConstructPairAndAppend(const Character& character,
+ const Range& new_range,
+ const StringSet& existing_set,
+ PairVector* pairs) {
+ Pair new_pair = {character, StringSet(1, new_range)};
+ new_pair.set.insert(
+ new_pair.set.end(), existing_set.begin(), existing_set.end());
+ pairs->push_back(new_pair);
+}
+
+// Each pass over the PairVector strips one byte off the right-hand-side of the
+// characters and adds a range to the set on the right. For example, the first
+// pass converts the range from "\xe0\xa0\x80" to "\xe0\xa0\xbf" to ("\xe0\xa0",
+// [\x80-\xbf]), then the second pass converts the range from ("\xe0\xa0",
+// [\x80-\xbf]) to ("\xe0\xbf", [\x80-\xbf]) to ("\xe0",
+// [\xa0-\xbf][\x80-\xbf]).
+void MoveRightMostCharToSet(PairVector* pairs) {
+ PairVector new_pairs;
+ PairVector::const_iterator it = pairs->begin();
+ while (it != pairs->end() && it->character.empty()) {
+ new_pairs.push_back(*it);
+ ++it;
+ }
+ CHECK(it != pairs->end());
+ Character unconverted_bytes(it->character.begin(), it->character.end() - 1);
+ Range new_range(it->character.back());
+ StringSet converted = it->set;
+ ++it;
+ while (it != pairs->end()) {
+ const Pair& current_pair = *it++;
+ if (current_pair.character.size() == unconverted_bytes.size() + 1 &&
+ std::equal(unconverted_bytes.begin(),
+ unconverted_bytes.end(),
+ current_pair.character.begin()) &&
+ converted == current_pair.set) {
+ // The particular set of UTF-8 codepoints we are validating guarantees
+ // that each byte range will be contiguous. This would not necessarily be
+ // true for an arbitrary set of UTF-8 codepoints.
+ DCHECK_EQ(new_range.to() + 1, current_pair.character.back());
+ new_range.AddByte(current_pair.character.back());
+ continue;
+ }
+ ConstructPairAndAppend(unconverted_bytes, new_range, converted, &new_pairs);
+ unconverted_bytes = Character(current_pair.character.begin(),
+ current_pair.character.end() - 1);
+ new_range = Range(current_pair.character.back());
+ converted = current_pair.set;
+ }
+ ConstructPairAndAppend(unconverted_bytes, new_range, converted, &new_pairs);
+ new_pairs.swap(*pairs);
+}
+
+void MoveAllCharsToSets(PairVector* pairs) {
+ // Since each pass of the function moves one character, and UTF-8 sequences
+ // are at most 4 characters long, this simply runs the algorithm four times.
+ for (int i = 0; i < 4; ++i) {
+ MoveRightMostCharToSet(pairs);
+ }
+#if DCHECK_IS_ON()
+ for (PairVector::const_iterator it = pairs->begin(); it != pairs->end();
+ ++it) {
+ DCHECK(it->character.empty());
+ }
+#endif
+}
+
+// Logs the generated string sets in regular-expression style, ie. [\x00-\x7f],
+// [\xc2-\xdf][\x80-\xbf], etc. This can be a useful sanity-check that the
+// algorithm is working. Use the command-line option
+// --vmodule=build_utf8_validator_tables=1 to see this output.
+void LogStringSets(const PairVector& pairs) {
+ for (PairVector::const_iterator pair_it = pairs.begin();
+ pair_it != pairs.end();
+ ++pair_it) {
+ std::string set_as_string;
+ for (StringSet::const_iterator set_it = pair_it->set.begin();
+ set_it != pair_it->set.end();
+ ++set_it) {
+ set_as_string += base::StringPrintf("[\\x%02x-\\x%02x]",
+ static_cast<int>(set_it->from()),
+ static_cast<int>(set_it->to()));
+ }
+ VLOG(1) << set_as_string;
+ }
+}
+
+// A single state in the state machine is represented by a sorted vector of
+// start bytes and target states. All input bytes in the range between the start
+// byte and the next entry in the vector (or 0xFF) result in a transition to the
+// target state.
+struct StateRange {
+ uint8_t from;
+ uint8_t target_state;
+};
+
+typedef std::vector<StateRange> State;
+
+// Generates a state where all bytes go to state 1 (invalid). This is also used
+// as an initialiser for other states (since bytes from outside the desired
+// range are invalid).
+State GenerateInvalidState() {
+ const StateRange range = {0, 1};
+ return State(1, range);
+}
+
+// A map from a state (ie. a set of strings which will match from this state) to
+// a number (which is an index into the array of states).
+typedef std::map<StringSet, uint8_t> StateMap;
+
+// Create a new state corresponding to |set|, add it |states| and |state_map|
+// and return the index it was given in |states|.
+uint8_t MakeState(const StringSet& set,
+ std::vector<State>* states,
+ StateMap* state_map) {
+ DCHECK(!set.empty());
+ const Range& range = set.front();
+ const StringSet rest(set.begin() + 1, set.end());
+ const StateMap::const_iterator where = state_map->find(rest);
+ const uint8_t target_state = where == state_map->end()
+ ? MakeState(rest, states, state_map)
+ : where->second;
+ DCHECK_LT(0, range.from());
+ DCHECK_LT(range.to(), 0xFF);
+ const StateRange new_state_initializer[] = {
+ {0, 1},
+ {range.from(), target_state},
+ {static_cast<uint8_t>(range.to() + 1), 1}};
+ states->push_back(
+ State(new_state_initializer,
+ new_state_initializer + arraysize(new_state_initializer)));
+ const uint8_t new_state_number =
+ base::checked_cast<uint8_t>(states->size() - 1);
+ CHECK(state_map->insert(std::make_pair(set, new_state_number)).second);
+ return new_state_number;
+}
+
+std::vector<State> GenerateStates(const PairVector& pairs) {
+ // States 0 and 1 are the initial/valid state and invalid state, respectively.
+ std::vector<State> states(2, GenerateInvalidState());
+ StateMap state_map;
+ state_map.insert(std::make_pair(StringSet(), 0));
+ for (PairVector::const_iterator it = pairs.begin(); it != pairs.end(); ++it) {
+ DCHECK(it->character.empty());
+ DCHECK(!it->set.empty());
+ const Range& range = it->set.front();
+ const StringSet rest(it->set.begin() + 1, it->set.end());
+ const StateMap::const_iterator where = state_map.find(rest);
+ const uint8_t target_state = where == state_map.end()
+ ? MakeState(rest, &states, &state_map)
+ : where->second;
+ if (states[0].back().from == range.from()) {
+ DCHECK_EQ(1, states[0].back().target_state);
+ states[0].back().target_state = target_state;
+ DCHECK_LT(range.to(), 0xFF);
+ const StateRange new_range = {static_cast<uint8_t>(range.to() + 1), 1};
+ states[0].push_back(new_range);
+ } else {
+ DCHECK_LT(range.to(), 0xFF);
+ const StateRange new_range_initializer[] = {
+ {range.from(), target_state},
+ {static_cast<uint8_t>(range.to() + 1), 1}};
+ states[0]
+ .insert(states[0].end(),
+ new_range_initializer,
+ new_range_initializer + arraysize(new_range_initializer));
+ }
+ }
+ return states;
+}
+
+// Output the generated states as a C++ table. Two tricks are used to compact
+// the table: each state in the table starts with a shift value which indicates
+// how many bits we can discard from the right-hand-side of the byte before
+// doing the table lookup. Secondly, only the state-transitions for bytes
+// with the top-bit set are included in the table; bytes without the top-bit set
+// are just ASCII and are handled directly by the code.
+void PrintStates(const std::vector<State>& states, FILE* stream) {
+ // First calculate the start-offset of each state. This allows the state
+ // machine to jump directly to the correct offset, avoiding an extra
+ // indirection. State 0 starts at offset 0.
+ std::vector<uint8_t> state_offset(1, 0);
+ std::vector<uint8_t> shifts;
+ uint8_t pos = 0;
+
+ for (std::vector<State>::const_iterator state_it = states.begin();
+ state_it != states.end();
+ ++state_it) {
+ // We want to set |shift| to the (0-based) index of the least-significant
+ // set bit in any of the ranges for this state, since this tells us how many
+ // bits we can discard and still determine what range a byte lies in. Sadly
+ // it appears that ffs() is not portable, so we do it clumsily.
+ uint8_t shift = 7;
+ for (State::const_iterator range_it = state_it->begin();
+ range_it != state_it->end();
+ ++range_it) {
+ while (shift > 0 && range_it->from % (1 << shift) != 0) {
+ --shift;
+ }
+ }
+ shifts.push_back(shift);
+ pos += 1 + (1 << (7 - shift));
+ state_offset.push_back(pos);
+ }
+
+ DCHECK_EQ(129, state_offset[1]);
+
+ fputs(kProlog, stream);
+ TablePrinter table_printer(stream);
+
+ for (uint8_t state_index = 0; state_index < states.size(); ++state_index) {
+ const uint8_t shift = shifts[state_index];
+ uint8_t next_range = 0;
+ uint8_t target_state = 1;
+ fprintf(stream,
+ " // State %d, offset 0x%02x\n",
+ static_cast<int>(state_index),
+ static_cast<int>(state_offset[state_index]));
+ table_printer.PrintValue(shift);
+ for (int i = 0; i < 0x100; i += (1 << shift)) {
+ if (next_range < states[state_index].size() &&
+ states[state_index][next_range].from == i) {
+ target_state = states[state_index][next_range].target_state;
+ ++next_range;
+ }
+ if (i >= 0x80) {
+ table_printer.PrintValue(state_offset[target_state]);
+ }
+ }
+ table_printer.NewLine();
+ }
+
+ fputs(kEpilog, stream);
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ base::CommandLine::Init(argc, argv);
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ logging::InitLogging(settings);
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch("help")) {
+ fwrite(kHelpText, 1, arraysize(kHelpText), stdout);
+ exit(EXIT_SUCCESS);
+ }
+ base::FilePath filename =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValuePath("output");
+
+ FILE* output = stdout;
+ if (!filename.empty()) {
+ output = base::OpenFile(filename, "wb");
+ if (!output)
+ PLOG(FATAL) << "Couldn't open '" << filename.AsUTF8Unsafe()
+ << "' for writing";
+ }
+
+ // Step 1: Enumerate the characters
+ PairVector pairs = InitializeCharacters();
+ // Step 2: Convert to sets.
+ MoveAllCharsToSets(&pairs);
+ if (VLOG_IS_ON(1)) {
+ LogStringSets(pairs);
+ }
+ // Step 3: Generate states.
+ std::vector<State> states = GenerateStates(pairs);
+ // Step 4/5: Print output
+ PrintStates(states, output);
+
+ if (!filename.empty()) {
+ if (!base::CloseFile(output))
+ PLOG(FATAL) << "Couldn't finish writing '" << filename.AsUTF8Unsafe()
+ << "'";
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/base/i18n/case_conversion.cc b/base/i18n/case_conversion.cc
new file mode 100644
index 0000000000..a4a104cf97
--- /dev/null
+++ b/base/i18n/case_conversion.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/case_conversion.h"
+
+#include <stdint.h>
+
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "third_party/icu/source/common/unicode/uchar.h"
+#include "third_party/icu/source/common/unicode/unistr.h"
+#include "third_party/icu/source/common/unicode/ustring.h"
+
+namespace base {
+namespace i18n {
+
+namespace {
+
+// Provides a uniform interface for upper/lower/folding which take take
+// slightly varying parameters.
+typedef int32_t (*CaseMapperFunction)(UChar* dest, int32_t dest_capacity,
+ const UChar* src, int32_t src_length,
+ UErrorCode* error);
+
+int32_t ToUpperMapper(UChar* dest, int32_t dest_capacity,
+ const UChar* src, int32_t src_length,
+ UErrorCode* error) {
+ // Use default locale.
+ return u_strToUpper(dest, dest_capacity, src, src_length, nullptr, error);
+}
+
+int32_t ToLowerMapper(UChar* dest, int32_t dest_capacity,
+ const UChar* src, int32_t src_length,
+ UErrorCode* error) {
+ // Use default locale.
+ return u_strToLower(dest, dest_capacity, src, src_length, nullptr, error);
+}
+
+int32_t FoldCaseMapper(UChar* dest, int32_t dest_capacity,
+ const UChar* src, int32_t src_length,
+ UErrorCode* error) {
+ return u_strFoldCase(dest, dest_capacity, src, src_length,
+ U_FOLD_CASE_DEFAULT, error);
+}
+
+// Provides similar functionality as UnicodeString::caseMap but on string16.
+string16 CaseMap(StringPiece16 string, CaseMapperFunction case_mapper) {
+ string16 dest;
+ if (string.empty())
+ return dest;
+
+ // Provide an initial guess that the string length won't change. The typical
+ // strings we use will very rarely change length in this process, so don't
+ // optimize for that case.
+ dest.resize(string.size());
+
+ UErrorCode error;
+ do {
+ error = U_ZERO_ERROR;
+
+ // ICU won't terminate the string if there's not enough room for the null
+ // terminator, but will otherwise. So we don't need to save room for that.
+ // Don't use WriteInto, which assumes null terminators.
+ int32_t new_length = case_mapper(
+ &dest[0], saturated_cast<int32_t>(dest.size()),
+ string.data(), saturated_cast<int32_t>(string.size()),
+ &error);
+ dest.resize(new_length);
+ } while (error == U_BUFFER_OVERFLOW_ERROR);
+ return dest;
+}
+
+} // namespace
+
+string16 ToLower(StringPiece16 string) {
+ return CaseMap(string, &ToLowerMapper);
+}
+
+string16 ToUpper(StringPiece16 string) {
+ return CaseMap(string, &ToUpperMapper);
+}
+
+string16 FoldCase(StringPiece16 string) {
+ return CaseMap(string, &FoldCaseMapper);
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/case_conversion.h b/base/i18n/case_conversion.h
new file mode 100644
index 0000000000..0631a800b7
--- /dev/null
+++ b/base/i18n/case_conversion.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_CASE_CONVERSION_H_
+#define BASE_I18N_CASE_CONVERSION_H_
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+namespace i18n {
+
+// UNICODE CASE-HANDLING ADVICE
+//
+// In English it's always safe to convert to upper-case or lower-case text
+// and get a good answer. But some languages have rules specific to those
+// locales. One example is the Turkish I:
+// http://www.i18nguy.com/unicode/turkish-i18n.html
+//
+// ToLower/ToUpper use the current ICU locale which will take into account
+// the user language preference. Use this when dealing with user typing.
+//
+// FoldCase canonicalizes to a standardized form independent of the current
+// locale. Use this when comparing general Unicode strings that don't
+// necessarily belong in the user's current locale (like commands, protocol
+// names, other strings from the web) for case-insensitive equality.
+//
+// Note that case conversions will change the length of the string in some
+// not-uncommon cases. Never assume that the output is the same length as
+// the input.
+
+// Returns the lower case equivalent of string. Uses ICU's current locale.
+BASE_I18N_EXPORT string16 ToLower(StringPiece16 string);
+
+// Returns the upper case equivalent of string. Uses ICU's current locale.
+BASE_I18N_EXPORT string16 ToUpper(StringPiece16 string);
+
+// Convert the given string to a canonical case, independent of the current
+// locale. For ASCII the canonical form is lower case.
+// See http://unicode.org/faq/casemap_charprop.html#2
+BASE_I18N_EXPORT string16 FoldCase(StringPiece16 string);
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_CASE_CONVERSION_H_
diff --git a/base/i18n/case_conversion_unittest.cc b/base/i18n/case_conversion_unittest.cc
new file mode 100644
index 0000000000..ee795bc6e3
--- /dev/null
+++ b/base/i18n/case_conversion_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/case_conversion.h"
+#include "base/i18n/rtl.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/icu_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/i18n/unicode/usearch.h"
+
+namespace base {
+namespace i18n {
+
+namespace {
+
+const wchar_t kNonASCIIMixed[] =
+ L"\xC4\xD6\xE4\xF6\x20\xCF\xEF\x20\xF7\x25"
+ L"\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F07\x1F0F"
+ L"\x20\x1E00\x1E01";
+const wchar_t kNonASCIILower[] =
+ L"\xE4\xF6\xE4\xF6\x20\xEF\xEF"
+ L"\x20\xF7\x25\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F07"
+ L"\x1F07\x20\x1E01\x1E01";
+const wchar_t kNonASCIIUpper[] =
+ L"\xC4\xD6\xC4\xD6\x20\xCF\xCF"
+ L"\x20\xF7\x25\xA4\x23\x2A\x5E\x60\x40\xA3\x24\x2030\x201A\x7E\x20\x1F0F"
+ L"\x1F0F\x20\x1E00\x1E00";
+
+} // namespace
+
+// Test upper and lower case string conversion.
+TEST(CaseConversionTest, UpperLower) {
+ const string16 mixed(ASCIIToUTF16("Text with UPPer & lowER casE."));
+ const string16 expected_lower(ASCIIToUTF16("text with upper & lower case."));
+ const string16 expected_upper(ASCIIToUTF16("TEXT WITH UPPER & LOWER CASE."));
+
+ string16 result = ToLower(mixed);
+ EXPECT_EQ(expected_lower, result);
+
+ result = ToUpper(mixed);
+ EXPECT_EQ(expected_upper, result);
+}
+
+TEST(CaseConversionTest, NonASCII) {
+ const string16 mixed(WideToUTF16(kNonASCIIMixed));
+ const string16 expected_lower(WideToUTF16(kNonASCIILower));
+ const string16 expected_upper(WideToUTF16(kNonASCIIUpper));
+
+ string16 result = ToLower(mixed);
+ EXPECT_EQ(expected_lower, result);
+
+ result = ToUpper(mixed);
+ EXPECT_EQ(expected_upper, result);
+}
+
+TEST(CaseConversionTest, TurkishLocaleConversion) {
+ const string16 mixed(WideToUTF16(L"\x49\x131"));
+ const string16 expected_lower(WideToUTF16(L"\x69\x131"));
+ const string16 expected_upper(WideToUTF16(L"\x49\x49"));
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("en_US");
+
+ string16 result = ToLower(mixed);
+ EXPECT_EQ(expected_lower, result);
+
+ result = ToUpper(mixed);
+ EXPECT_EQ(expected_upper, result);
+
+ i18n::SetICUDefaultLocale("tr");
+
+ const string16 expected_lower_turkish(WideToUTF16(L"\x131\x131"));
+ const string16 expected_upper_turkish(WideToUTF16(L"\x49\x49"));
+
+ result = ToLower(mixed);
+ EXPECT_EQ(expected_lower_turkish, result);
+
+ result = ToUpper(mixed);
+ EXPECT_EQ(expected_upper_turkish, result);
+}
+
+TEST(CaseConversionTest, FoldCase) {
+ // Simple ASCII, should lower-case.
+ EXPECT_EQ(ASCIIToUTF16("hello, world"),
+ FoldCase(ASCIIToUTF16("Hello, World")));
+
+ // Non-ASCII cases from above. They should all fold to the same result.
+ EXPECT_EQ(FoldCase(WideToUTF16(kNonASCIIMixed)),
+ FoldCase(WideToUTF16(kNonASCIILower)));
+ EXPECT_EQ(FoldCase(WideToUTF16(kNonASCIIMixed)),
+ FoldCase(WideToUTF16(kNonASCIIUpper)));
+
+ // Turkish cases from above. This is the lower-case expected result from the
+ // US locale. It should be the same even when the current locale is Turkish.
+ const string16 turkish(WideToUTF16(L"\x49\x131"));
+ const string16 turkish_expected(WideToUTF16(L"\x69\x131"));
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("en_US");
+ EXPECT_EQ(turkish_expected, FoldCase(turkish));
+
+ i18n::SetICUDefaultLocale("tr");
+ EXPECT_EQ(turkish_expected, FoldCase(turkish));
+
+ // Test a case that gets bigger when processed.
+ // U+130 = LATIN CAPITAL LETTER I WITH DOT ABOVE gets folded to a lower case
+ // "i" followed by U+307 COMBINING DOT ABOVE.
+ EXPECT_EQ(WideToUTF16(L"i\u0307j"), FoldCase(WideToUTF16(L"\u0130j")));
+
+ // U+00DF (SHARP S) and U+1E9E (CAPIRAL SHARP S) are both folded to "ss".
+ EXPECT_EQ(ASCIIToUTF16("ssss"), FoldCase(WideToUTF16(L"\u00DF\u1E9E")));
+}
+
+} // namespace i18n
+} // namespace base
+
+
+
diff --git a/base/i18n/char_iterator.cc b/base/i18n/char_iterator.cc
new file mode 100644
index 0000000000..d80b8b618d
--- /dev/null
+++ b/base/i18n/char_iterator.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/char_iterator.h"
+
+#include "third_party/icu/source/common/unicode/utf8.h"
+#include "third_party/icu/source/common/unicode/utf16.h"
+
+namespace base {
+namespace i18n {
+
+UTF8CharIterator::UTF8CharIterator(const std::string* str)
+ : str_(reinterpret_cast<const uint8_t*>(str->data())),
+ len_(str->size()),
+ array_pos_(0),
+ next_pos_(0),
+ char_pos_(0),
+ char_(0) {
+ if (len_)
+ U8_NEXT(str_, next_pos_, len_, char_);
+}
+
+UTF8CharIterator::~UTF8CharIterator() = default;
+
+bool UTF8CharIterator::Advance() {
+ if (array_pos_ >= len_)
+ return false;
+
+ array_pos_ = next_pos_;
+ char_pos_++;
+ if (next_pos_ < len_)
+ U8_NEXT(str_, next_pos_, len_, char_);
+
+ return true;
+}
+
+UTF16CharIterator::UTF16CharIterator(const string16* str)
+ : str_(reinterpret_cast<const char16*>(str->data())),
+ len_(str->size()),
+ array_pos_(0),
+ next_pos_(0),
+ char_pos_(0),
+ char_(0) {
+ if (len_)
+ ReadChar();
+}
+
+UTF16CharIterator::UTF16CharIterator(const char16* str, size_t str_len)
+ : str_(str),
+ len_(str_len),
+ array_pos_(0),
+ next_pos_(0),
+ char_pos_(0),
+ char_(0) {
+ if (len_)
+ ReadChar();
+}
+
+UTF16CharIterator::~UTF16CharIterator() = default;
+
+bool UTF16CharIterator::Advance() {
+ if (array_pos_ >= len_)
+ return false;
+
+ array_pos_ = next_pos_;
+ char_pos_++;
+ if (next_pos_ < len_)
+ ReadChar();
+
+ return true;
+}
+
+void UTF16CharIterator::ReadChar() {
+ // This is actually a huge macro, so is worth having in a separate function.
+ U16_NEXT(str_, next_pos_, len_, char_);
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/char_iterator.h b/base/i18n/char_iterator.h
new file mode 100644
index 0000000000..24024d4929
--- /dev/null
+++ b/base/i18n/char_iterator.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_CHAR_ITERATOR_H_
+#define BASE_I18N_CHAR_ITERATOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+
+// The CharIterator classes iterate through the characters in UTF8 and
+// UTF16 strings. Example usage:
+//
+// UTF8CharIterator iter(&str);
+// while (!iter.end()) {
+// VLOG(1) << iter.get();
+// iter.Advance();
+// }
+
+#if defined(OS_WIN)
+typedef unsigned char uint8_t;
+#endif
+
+namespace base {
+namespace i18n {
+
+class BASE_I18N_EXPORT UTF8CharIterator {
+ public:
+ // Requires |str| to live as long as the UTF8CharIterator does.
+ explicit UTF8CharIterator(const std::string* str);
+ ~UTF8CharIterator();
+
+ // Return the starting array index of the current character within the
+ // string.
+ int32_t array_pos() const { return array_pos_; }
+
+ // Return the logical index of the current character, independent of the
+ // number of bytes each character takes.
+ int32_t char_pos() const { return char_pos_; }
+
+ // Return the current char.
+ int32_t get() const { return char_; }
+
+ // Returns true if we're at the end of the string.
+ bool end() const { return array_pos_ == len_; }
+
+ // Advance to the next actual character. Returns false if we're at the
+ // end of the string.
+ bool Advance();
+
+ private:
+ // The string we're iterating over.
+ const uint8_t* str_;
+
+ // The length of the encoded string.
+ int32_t len_;
+
+ // Array index.
+ int32_t array_pos_;
+
+ // The next array index.
+ int32_t next_pos_;
+
+ // Character index.
+ int32_t char_pos_;
+
+ // The current character.
+ int32_t char_;
+
+ DISALLOW_COPY_AND_ASSIGN(UTF8CharIterator);
+};
+
+class BASE_I18N_EXPORT UTF16CharIterator {
+ public:
+ // Requires |str| to live as long as the UTF16CharIterator does.
+ explicit UTF16CharIterator(const string16* str);
+ UTF16CharIterator(const char16* str, size_t str_len);
+ ~UTF16CharIterator();
+
+ // Return the starting array index of the current character within the
+ // string.
+ int32_t array_pos() const { return array_pos_; }
+
+ // Return the logical index of the current character, independent of the
+ // number of codewords each character takes.
+ int32_t char_pos() const { return char_pos_; }
+
+ // Return the current char.
+ int32_t get() const { return char_; }
+
+ // Returns true if we're at the end of the string.
+ bool end() const { return array_pos_ == len_; }
+
+ // Advance to the next actual character. Returns false if we're at the
+ // end of the string.
+ bool Advance();
+
+ private:
+ // Fills in the current character we found and advances to the next
+ // character, updating all flags as necessary.
+ void ReadChar();
+
+ // The string we're iterating over.
+ const char16* str_;
+
+ // The length of the encoded string.
+ int32_t len_;
+
+ // Array index.
+ int32_t array_pos_;
+
+ // The next array index.
+ int32_t next_pos_;
+
+ // Character index.
+ int32_t char_pos_;
+
+ // The current character.
+ int32_t char_;
+
+ DISALLOW_COPY_AND_ASSIGN(UTF16CharIterator);
+};
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_CHAR_ITERATOR_H_
diff --git a/base/i18n/char_iterator_unittest.cc b/base/i18n/char_iterator_unittest.cc
new file mode 100644
index 0000000000..0cf8e6c07d
--- /dev/null
+++ b/base/i18n/char_iterator_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/char_iterator.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace i18n {
+
+TEST(CharIteratorsTest, TestUTF8) {
+ std::string empty;
+ UTF8CharIterator empty_iter(&empty);
+ ASSERT_TRUE(empty_iter.end());
+ ASSERT_EQ(0, empty_iter.array_pos());
+ ASSERT_EQ(0, empty_iter.char_pos());
+ ASSERT_FALSE(empty_iter.Advance());
+
+ std::string str("s\303\273r"); // [u with circumflex]
+ UTF8CharIterator iter(&str);
+ ASSERT_FALSE(iter.end());
+ ASSERT_EQ(0, iter.array_pos());
+ ASSERT_EQ(0, iter.char_pos());
+ ASSERT_EQ('s', iter.get());
+ ASSERT_TRUE(iter.Advance());
+
+ ASSERT_FALSE(iter.end());
+ ASSERT_EQ(1, iter.array_pos());
+ ASSERT_EQ(1, iter.char_pos());
+ ASSERT_EQ(251, iter.get());
+ ASSERT_TRUE(iter.Advance());
+
+ ASSERT_FALSE(iter.end());
+ ASSERT_EQ(3, iter.array_pos());
+ ASSERT_EQ(2, iter.char_pos());
+ ASSERT_EQ('r', iter.get());
+ ASSERT_TRUE(iter.Advance());
+
+ ASSERT_TRUE(iter.end());
+ ASSERT_EQ(4, iter.array_pos());
+ ASSERT_EQ(3, iter.char_pos());
+
+ // Don't care what it returns, but this shouldn't crash
+ iter.get();
+
+ ASSERT_FALSE(iter.Advance());
+}
+
+TEST(CharIteratorsTest, TestUTF16) {
+ string16 empty = UTF8ToUTF16("");
+ UTF16CharIterator empty_iter(&empty);
+ ASSERT_TRUE(empty_iter.end());
+ ASSERT_EQ(0, empty_iter.array_pos());
+ ASSERT_EQ(0, empty_iter.char_pos());
+ ASSERT_FALSE(empty_iter.Advance());
+
+ // This test string contains 4 characters:
+ // x
+ // u with circumflex - 2 bytes in UTF8, 1 codeword in UTF16
+ // math double-struck A - 4 bytes in UTF8, 2 codewords in UTF16
+ // z
+ string16 str = UTF8ToUTF16("x\303\273\360\235\224\270z");
+ UTF16CharIterator iter(&str);
+ ASSERT_FALSE(iter.end());
+ ASSERT_EQ(0, iter.array_pos());
+ ASSERT_EQ(0, iter.char_pos());
+ ASSERT_EQ('x', iter.get());
+ ASSERT_TRUE(iter.Advance());
+
+ ASSERT_FALSE(iter.end());
+ ASSERT_EQ(1, iter.array_pos());
+ ASSERT_EQ(1, iter.char_pos());
+ ASSERT_EQ(251, iter.get());
+ ASSERT_TRUE(iter.Advance());
+
+ ASSERT_FALSE(iter.end());
+ ASSERT_EQ(2, iter.array_pos());
+ ASSERT_EQ(2, iter.char_pos());
+ ASSERT_EQ(120120, iter.get());
+ ASSERT_TRUE(iter.Advance());
+
+ ASSERT_FALSE(iter.end());
+ ASSERT_EQ(4, iter.array_pos());
+ ASSERT_EQ(3, iter.char_pos());
+ ASSERT_EQ('z', iter.get());
+ ASSERT_TRUE(iter.Advance());
+
+ ASSERT_TRUE(iter.end());
+ ASSERT_EQ(5, iter.array_pos());
+ ASSERT_EQ(4, iter.char_pos());
+
+ // Don't care what it returns, but this shouldn't crash
+ iter.get();
+
+ ASSERT_FALSE(iter.Advance());
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/character_encoding.cc b/base/i18n/character_encoding.cc
new file mode 100644
index 0000000000..a1068c3c52
--- /dev/null
+++ b/base/i18n/character_encoding.cc
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/character_encoding.h"
+
+#include "base/macros.h"
+#include "third_party/icu/source/common/unicode/ucnv.h"
+
+namespace base {
+namespace {
+
+// An array of all supported canonical encoding names.
+const char* const kCanonicalEncodingNames[] = {
+ "Big5", "EUC-JP", "EUC-KR", "gb18030",
+ "GBK", "IBM866", "ISO-2022-JP", "ISO-8859-10",
+ "ISO-8859-13", "ISO-8859-14", "ISO-8859-15", "ISO-8859-16",
+ "ISO-8859-2", "ISO-8859-3", "ISO-8859-4", "ISO-8859-5",
+ "ISO-8859-6", "ISO-8859-7", "ISO-8859-8", "ISO-8859-8-I",
+ "KOI8-R", "KOI8-U", "macintosh", "Shift_JIS",
+ "UTF-16LE", "UTF-8", "windows-1250", "windows-1251",
+ "windows-1252", "windows-1253", "windows-1254", "windows-1255",
+ "windows-1256", "windows-1257", "windows-1258", "windows-874"};
+
+} // namespace
+
+std::string GetCanonicalEncodingNameByAliasName(const std::string& alias_name) {
+ for (auto* encoding_name : kCanonicalEncodingNames) {
+ if (alias_name == encoding_name)
+ return alias_name;
+ }
+ static const char* kStandards[3] = {"HTML", "MIME", "IANA"};
+ for (auto* standard : kStandards) {
+ UErrorCode error_code = U_ZERO_ERROR;
+ const char* canonical_name =
+ ucnv_getStandardName(alias_name.c_str(), standard, &error_code);
+ if (U_SUCCESS(error_code) && canonical_name)
+ return canonical_name;
+ }
+ return std::string();
+}
+} // namespace base
diff --git a/base/i18n/character_encoding.h b/base/i18n/character_encoding.h
new file mode 100644
index 0000000000..974cb5a6f9
--- /dev/null
+++ b/base/i18n/character_encoding.h
@@ -0,0 +1,20 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_CHARACTER_ENCODING_H_
+#define BASE_I18N_CHARACTER_ENCODING_H_
+
+#include <string>
+
+#include "base/i18n/base_i18n_export.h"
+
+namespace base {
+
+// Return canonical encoding name according to the encoding alias name.
+BASE_I18N_EXPORT std::string GetCanonicalEncodingNameByAliasName(
+ const std::string& alias_name);
+
+} // namespace base
+
+#endif // BASE_I18N_CHARACTER_ENCODING_H_
diff --git a/base/i18n/character_encoding_unittest.cc b/base/i18n/character_encoding_unittest.cc
new file mode 100644
index 0000000000..3c11ba30aa
--- /dev/null
+++ b/base/i18n/character_encoding_unittest.cc
@@ -0,0 +1,23 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/character_encoding.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(CharacterEncodingTest, GetCanonicalEncodingNameByAliasName) {
+ EXPECT_EQ("Big5", GetCanonicalEncodingNameByAliasName("Big5"));
+ EXPECT_EQ("windows-874", GetCanonicalEncodingNameByAliasName("windows-874"));
+ EXPECT_EQ("ISO-8859-8", GetCanonicalEncodingNameByAliasName("ISO-8859-8"));
+
+ // Non-canonical alias names should be converted to a canonical one.
+ EXPECT_EQ("UTF-8", GetCanonicalEncodingNameByAliasName("utf8"));
+ EXPECT_EQ("gb18030", GetCanonicalEncodingNameByAliasName("GB18030"));
+ EXPECT_EQ("windows-874", GetCanonicalEncodingNameByAliasName("tis-620"));
+ EXPECT_EQ("EUC-KR", GetCanonicalEncodingNameByAliasName("ks_c_5601-1987"));
+}
+
+} // namespace base
diff --git a/base/i18n/encoding_detection.cc b/base/i18n/encoding_detection.cc
new file mode 100644
index 0000000000..fef34e4ab1
--- /dev/null
+++ b/base/i18n/encoding_detection.cc
@@ -0,0 +1,40 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/encoding_detection.h"
+
+#include "build/build_config.h"
+#include "third_party/ced/src/compact_enc_det/compact_enc_det.h"
+
+// third_party/ced/src/util/encodings/encodings.h, which is included
+// by the include above, undefs UNICODE because that is a macro used
+// internally in ced. If we later in the same translation unit do
+// anything related to Windows or Windows headers those will then use
+// the ASCII versions which we do not want. To avoid that happening in
+// jumbo builds, we redefine UNICODE again here.
+#if defined(OS_WIN)
+#define UNICODE 1
+#endif // OS_WIN
+
+namespace base {
+
+bool DetectEncoding(const std::string& text, std::string* encoding) {
+ int consumed_bytes;
+ bool is_reliable;
+ Encoding enc = CompactEncDet::DetectEncoding(
+ text.c_str(), text.length(), nullptr, nullptr, nullptr,
+ UNKNOWN_ENCODING,
+ UNKNOWN_LANGUAGE,
+ CompactEncDet::QUERY_CORPUS, // plain text
+ false, // Include 7-bit encodings
+ &consumed_bytes,
+ &is_reliable);
+
+ if (enc == UNKNOWN_ENCODING)
+ return false;
+
+ *encoding = MimeEncodingName(enc);
+ return true;
+}
+} // namespace base
diff --git a/base/i18n/encoding_detection.h b/base/i18n/encoding_detection.h
new file mode 100644
index 0000000000..c8e660c44d
--- /dev/null
+++ b/base/i18n/encoding_detection.h
@@ -0,0 +1,21 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_ENCODING_DETECTION_H_
+#define BASE_I18N_ENCODING_DETECTION_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/i18n/base_i18n_export.h"
+
+namespace base {
+
+// Detect encoding of |text| and put the name of encoding in |encoding|.
+// Returns true on success.
+BASE_I18N_EXPORT bool DetectEncoding(const std::string& text,
+ std::string* encoding) WARN_UNUSED_RESULT;
+} // namespace base
+
+#endif // BASE_I18N_ENCODING_DETECTION_H_
diff --git a/base/i18n/file_util_icu.cc b/base/i18n/file_util_icu.cc
new file mode 100644
index 0000000000..c91aea1a44
--- /dev/null
+++ b/base/i18n/file_util_icu.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// File utilities that use the ICU library go in this file.
+
+#include "base/i18n/file_util_icu.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/i18n/string_compare.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "third_party/icu/source/common/unicode/uniset.h"
+#include "third_party/icu/source/i18n/unicode/coll.h"
+
+namespace base {
+namespace i18n {
+
+namespace {
+
+class IllegalCharacters {
+ public:
+ static IllegalCharacters* GetInstance() {
+ return Singleton<IllegalCharacters>::get();
+ }
+
+ bool DisallowedEverywhere(UChar32 ucs4) {
+ return !!illegal_anywhere_->contains(ucs4);
+ }
+
+ bool DisallowedLeadingOrTrailing(UChar32 ucs4) {
+ return !!illegal_at_ends_->contains(ucs4);
+ }
+
+ bool IsAllowedName(const string16& s) {
+ return s.empty() || (!!illegal_anywhere_->containsNone(
+ icu::UnicodeString(s.c_str(), s.size())) &&
+ !illegal_at_ends_->contains(*s.begin()) &&
+ !illegal_at_ends_->contains(*s.rbegin()));
+ }
+
+ private:
+ friend class Singleton<IllegalCharacters>;
+ friend struct DefaultSingletonTraits<IllegalCharacters>;
+
+ IllegalCharacters();
+ ~IllegalCharacters() = default;
+
+ // set of characters considered invalid anywhere inside a filename.
+ std::unique_ptr<icu::UnicodeSet> illegal_anywhere_;
+
+ // set of characters considered invalid at either end of a filename.
+ std::unique_ptr<icu::UnicodeSet> illegal_at_ends_;
+
+ DISALLOW_COPY_AND_ASSIGN(IllegalCharacters);
+};
+
+IllegalCharacters::IllegalCharacters() {
+ UErrorCode everywhere_status = U_ZERO_ERROR;
+ UErrorCode ends_status = U_ZERO_ERROR;
+ // Control characters, formatting characters, non-characters, path separators,
+ // and some printable ASCII characters regarded as dangerous ('"*/:<>?\\').
+ // See http://blogs.msdn.com/michkap/archive/2006/11/03/941420.aspx
+ // and http://msdn2.microsoft.com/en-us/library/Aa365247.aspx
+ // Note that code points in the "Other, Format" (Cf) category are ignored on
+ // HFS+ despite the ZERO_WIDTH_JOINER and ZERO_WIDTH_NON-JOINER being
+ // legitimate in Arabic and some S/SE Asian scripts. In addition tilde (~) is
+ // also excluded due to the possibility of interacting poorly with short
+ // filenames on VFAT. (Related to CVE-2014-9390)
+ illegal_anywhere_.reset(new icu::UnicodeSet(
+ UNICODE_STRING_SIMPLE("[[\"~*/:<>?\\\\|][:Cc:][:Cf:]]"),
+ everywhere_status));
+ illegal_at_ends_.reset(new icu::UnicodeSet(
+ UNICODE_STRING_SIMPLE("[[:WSpace:][.]]"), ends_status));
+ DCHECK(U_SUCCESS(everywhere_status));
+ DCHECK(U_SUCCESS(ends_status));
+
+ // Add non-characters. If this becomes a performance bottleneck by
+ // any chance, do not add these to |set| and change IsFilenameLegal()
+ // to check |ucs4 & 0xFFFEu == 0xFFFEu|, in addiition to calling
+ // IsAllowedName().
+ illegal_anywhere_->add(0xFDD0, 0xFDEF);
+ for (int i = 0; i <= 0x10; ++i) {
+ int plane_base = 0x10000 * i;
+ illegal_anywhere_->add(plane_base + 0xFFFE, plane_base + 0xFFFF);
+ }
+ illegal_anywhere_->freeze();
+ illegal_at_ends_->freeze();
+}
+
+} // namespace
+
+bool IsFilenameLegal(const string16& file_name) {
+ return IllegalCharacters::GetInstance()->IsAllowedName(file_name);
+}
+
+void ReplaceIllegalCharactersInPath(FilePath::StringType* file_name,
+ char replace_char) {
+ IllegalCharacters* illegal = IllegalCharacters::GetInstance();
+
+ DCHECK(!(illegal->DisallowedEverywhere(replace_char)));
+ DCHECK(!(illegal->DisallowedLeadingOrTrailing(replace_char)));
+
+ int cursor = 0; // The ICU macros expect an int.
+ while (cursor < static_cast<int>(file_name->size())) {
+ int char_begin = cursor;
+ uint32_t code_point;
+#if defined(OS_WIN)
+ // Windows uses UTF-16 encoding for filenames.
+ U16_NEXT(file_name->data(), cursor, static_cast<int>(file_name->length()),
+ code_point);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // Mac and Chrome OS use UTF-8 encoding for filenames.
+ // Linux doesn't actually define file system encoding. Try to parse as
+ // UTF-8.
+ U8_NEXT(file_name->data(), cursor, static_cast<int>(file_name->length()),
+ code_point);
+#else
+#error Unsupported platform
+#endif
+
+ if (illegal->DisallowedEverywhere(code_point) ||
+ ((char_begin == 0 || cursor == static_cast<int>(file_name->length())) &&
+ illegal->DisallowedLeadingOrTrailing(code_point))) {
+ file_name->replace(char_begin, cursor - char_begin, 1, replace_char);
+ // We just made the potentially multi-byte/word char into one that only
+ // takes one byte/word, so need to adjust the cursor to point to the next
+ // character again.
+ cursor = char_begin + 1;
+ }
+ }
+}
+
+bool LocaleAwareCompareFilenames(const FilePath& a, const FilePath& b) {
+ UErrorCode error_code = U_ZERO_ERROR;
+ // Use the default collator. The default locale should have been properly
+ // set by the time this constructor is called.
+ std::unique_ptr<icu::Collator> collator(
+ icu::Collator::createInstance(error_code));
+ DCHECK(U_SUCCESS(error_code));
+ // Make it case-sensitive.
+ collator->setStrength(icu::Collator::TERTIARY);
+
+#if defined(OS_WIN)
+ return CompareString16WithCollator(*collator, WideToUTF16(a.value()),
+ WideToUTF16(b.value())) == UCOL_LESS;
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // On linux, the file system encoding is not defined. We assume
+ // SysNativeMBToWide takes care of it.
+ return CompareString16WithCollator(
+ *collator, WideToUTF16(SysNativeMBToWide(a.value())),
+ WideToUTF16(SysNativeMBToWide(b.value()))) == UCOL_LESS;
+#endif
+}
+
+void NormalizeFileNameEncoding(FilePath* file_name) {
+#if defined(OS_CHROMEOS)
+ std::string normalized_str;
+ if (ConvertToUtf8AndNormalize(file_name->BaseName().value(), kCodepageUTF8,
+ &normalized_str) &&
+ !normalized_str.empty()) {
+ *file_name = file_name->DirName().Append(FilePath(normalized_str));
+ }
+#endif
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/file_util_icu.h b/base/i18n/file_util_icu.h
new file mode 100644
index 0000000000..f8bd9f44d5
--- /dev/null
+++ b/base/i18n/file_util_icu.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_FILE_UTIL_ICU_H_
+#define BASE_I18N_FILE_UTIL_ICU_H_
+
+// File utilities that use the ICU library go in this file.
+
+#include "base/files/file_path.h"
+#include "base/i18n/base_i18n_export.h"
+#include "base/strings/string16.h"
+
+namespace base {
+namespace i18n {
+
+// Returns true if file_name does not have any illegal character. The input
+// param has the same restriction as that for ReplaceIllegalCharacters.
+BASE_I18N_EXPORT bool IsFilenameLegal(const string16& file_name);
+
+// Replaces characters in |file_name| that are illegal for file names with
+// |replace_char|. |file_name| must not be a full or relative path, but just the
+// file name component (since slashes are considered illegal). Any leading or
+// trailing whitespace or periods in |file_name| is also replaced with the
+// |replace_char|.
+//
+// Example:
+// "bad:file*name?.txt" will be turned into "bad_file_name_.txt" when
+// |replace_char| is '_'.
+//
+// Warning: Do not use this function as the sole means of sanitizing a filename.
+// While the resulting filename itself would be legal, it doesn't necessarily
+// mean that the file will behave safely. On Windows, certain reserved names
+// refer to devices rather than files (E.g. LPT1), and some filenames could be
+// interpreted as shell namespace extensions (E.g. Foo.{<GUID>}).
+//
+// On Windows, Chrome OS and Mac, the file system encoding is already known and
+// parsed as UTF-8 and UTF-16 accordingly.
+// On Linux, the file name will be parsed as UTF8.
+// TODO(asanka): Move full filename sanitization logic here.
+BASE_I18N_EXPORT void ReplaceIllegalCharactersInPath(
+ FilePath::StringType* file_name,
+ char replace_char);
+
+// Compares two filenames using the current locale information. This can be
+// used to sort directory listings. It behaves like "operator<" for use in
+// std::sort.
+BASE_I18N_EXPORT bool LocaleAwareCompareFilenames(const FilePath& a,
+ const FilePath& b);
+
+// Calculates the canonical file-system representation of |file_name| base name.
+// Modifies |file_name| in place. No-op if not on ChromeOS.
+BASE_I18N_EXPORT void NormalizeFileNameEncoding(FilePath* file_name);
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_FILE_UTIL_ICU_H_
diff --git a/base/i18n/file_util_icu_unittest.cc b/base/i18n/file_util_icu_unittest.cc
new file mode 100644
index 0000000000..062d29b0d8
--- /dev/null
+++ b/base/i18n/file_util_icu_unittest.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/file_util_icu.h"
+
+#include <stddef.h>
+
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace base {
+namespace i18n {
+
+// file_util winds up using autoreleased objects on the Mac, so this needs
+// to be a PlatformTest
+class FileUtilICUTest : public PlatformTest {
+};
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+
+// On linux, file path is parsed and filtered as UTF-8.
+static const struct GoodBadPairLinux {
+ const char* bad_name;
+ const char* good_name;
+} kLinuxIllegalCharacterCases[] = {
+ {"bad*\\/file:name?.jpg", "bad---file-name-.jpg"},
+ {"**********::::.txt", "--------------.txt"},
+ {"\xe9\xf0zzzz.\xff", "\xe9\xf0zzzz.\xff"},
+ {" _ ", "-_-"},
+ {".", "-"},
+ {" .( ). ", "-.( ).-"},
+ {" ", "- -"},
+};
+
+TEST_F(FileUtilICUTest, ReplaceIllegalCharactersInPathLinuxTest) {
+ for (size_t i = 0; i < arraysize(kLinuxIllegalCharacterCases); ++i) {
+ std::string bad_name(kLinuxIllegalCharacterCases[i].bad_name);
+ ReplaceIllegalCharactersInPath(&bad_name, '-');
+ EXPECT_EQ(kLinuxIllegalCharacterCases[i].good_name, bad_name);
+ }
+}
+
+#endif
+
+// For Mac & Windows, which both do Unicode validation on filenames. These
+// characters are given as wide strings since its more convenient to specify
+// unicode characters. For Mac they should be converted to UTF-8.
+static const struct goodbad_pair {
+ const wchar_t* bad_name;
+ const wchar_t* good_name;
+} kIllegalCharacterCases[] = {
+ {L"bad*file:name?.jpg", L"bad-file-name-.jpg"},
+ {L"**********::::.txt", L"--------------.txt"},
+ // We can't use UCNs (universal character names) for C0/C1 characters and
+ // U+007F, but \x escape is interpreted by MSVC and gcc as we intend.
+ {L"bad\x0003\x0091 file\u200E\u200Fname.png", L"bad-- file--name.png"},
+ {L"bad*file\\?name.jpg", L"bad-file--name.jpg"},
+ {L"\t bad*file\\name/.jpg", L"- bad-file-name-.jpg"},
+ {L"this_file_name is okay!.mp3", L"this_file_name is okay!.mp3"},
+ {L"\u4E00\uAC00.mp3", L"\u4E00\uAC00.mp3"},
+ {L"\u0635\u200C\u0644.mp3", L"\u0635-\u0644.mp3"},
+ {L"\U00010330\U00010331.mp3", L"\U00010330\U00010331.mp3"},
+ // Unassigned codepoints are ok.
+ {L"\u0378\U00040001.mp3", L"\u0378\U00040001.mp3"},
+ // Non-characters are not allowed.
+ {L"bad\uFFFFfile\U0010FFFEname.jpg", L"bad-file-name.jpg"},
+ {L"bad\uFDD0file\uFDEFname.jpg", L"bad-file-name.jpg"},
+ // CVE-2014-9390
+ {L"(\u200C.\u200D.\u200E.\u200F.\u202A.\u202B.\u202C.\u202D.\u202E.\u206A."
+ L"\u206B.\u206C.\u206D.\u206F.\uFEFF)",
+ L"(-.-.-.-.-.-.-.-.-.-.-.-.-.-.-)"},
+ {L"config~1", L"config-1"},
+ {L" _ ", L"-_-"},
+ {L" ", L"-"},
+ {L"\u2008.(\u2007).\u3000", L"-.(\u2007).-"},
+ {L" ", L"- -"},
+ {L". ", L"- -"}
+};
+
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_POSIX)
+
+TEST_F(FileUtilICUTest, ReplaceIllegalCharactersInPathTest) {
+ for (size_t i = 0; i < arraysize(kIllegalCharacterCases); ++i) {
+#if defined(OS_WIN)
+ std::wstring bad_name(kIllegalCharacterCases[i].bad_name);
+ ReplaceIllegalCharactersInPath(&bad_name, '-');
+ EXPECT_EQ(kIllegalCharacterCases[i].good_name, bad_name);
+#else
+ std::string bad_name(WideToUTF8(kIllegalCharacterCases[i].bad_name));
+ ReplaceIllegalCharactersInPath(&bad_name, '-');
+ EXPECT_EQ(WideToUTF8(kIllegalCharacterCases[i].good_name), bad_name);
+#endif
+ }
+}
+
+#endif
+
+TEST_F(FileUtilICUTest, IsFilenameLegalTest) {
+ EXPECT_TRUE(IsFilenameLegal(string16()));
+
+ for (const auto& test_case : kIllegalCharacterCases) {
+ string16 bad_name = WideToUTF16(test_case.bad_name);
+ string16 good_name = WideToUTF16(test_case.good_name);
+
+ EXPECT_TRUE(IsFilenameLegal(good_name)) << good_name;
+ if (good_name != bad_name)
+ EXPECT_FALSE(IsFilenameLegal(bad_name)) << bad_name;
+ }
+}
+
+#if defined(OS_CHROMEOS)
+static const struct normalize_name_encoding_test_cases {
+ const char* original_path;
+ const char* normalized_path;
+} kNormalizeFileNameEncodingTestCases[] = {
+ { "foo_na\xcc\x88me.foo", "foo_n\xc3\xa4me.foo"},
+ { "foo_dir_na\xcc\x88me/foo_na\xcc\x88me.foo",
+ "foo_dir_na\xcc\x88me/foo_n\xc3\xa4me.foo"},
+ { "", ""},
+ { "foo_dir_na\xcc\x88me/", "foo_dir_n\xc3\xa4me"}
+};
+
+TEST_F(FileUtilICUTest, NormalizeFileNameEncoding) {
+ for (size_t i = 0; i < arraysize(kNormalizeFileNameEncodingTestCases); i++) {
+ FilePath path(kNormalizeFileNameEncodingTestCases[i].original_path);
+ NormalizeFileNameEncoding(&path);
+ EXPECT_EQ(FilePath(kNormalizeFileNameEncodingTestCases[i].normalized_path),
+ path);
+ }
+}
+
+#endif
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/i18n_constants.cc b/base/i18n/i18n_constants.cc
new file mode 100644
index 0000000000..7d2f5fc053
--- /dev/null
+++ b/base/i18n/i18n_constants.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/i18n_constants.h"
+
+namespace base {
+
+const char kCodepageLatin1[] = "ISO-8859-1";
+const char kCodepageUTF8[] = "UTF-8";
+
+} // namespace base
+
diff --git a/base/i18n/i18n_constants.h b/base/i18n/i18n_constants.h
new file mode 100644
index 0000000000..c1bd87dc48
--- /dev/null
+++ b/base/i18n/i18n_constants.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_I18N_CONSTANTS_H_
+#define BASE_I18N_I18N_CONSTANTS_H_
+
+#include "base/i18n/base_i18n_export.h"
+
+namespace base {
+
+// Names of codepages (charsets) understood by icu.
+BASE_I18N_EXPORT extern const char kCodepageLatin1[]; // a.k.a. ISO 8859-1
+BASE_I18N_EXPORT extern const char kCodepageUTF8[];
+
+// The other possible options are UTF-16BE and UTF-16LE, but they are unused in
+// Chromium as of this writing.
+
+} // namespace base
+
+#endif // BASE_I18N_I18N_CONSTANTS_H_
diff --git a/base/i18n/icu_string_conversions.cc b/base/i18n/icu_string_conversions.cc
new file mode 100644
index 0000000000..6ec99803a8
--- /dev/null
+++ b/base/i18n/icu_string_conversions.cc
@@ -0,0 +1,223 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/icu_string_conversions.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/icu/source/common/unicode/normalizer2.h"
+#include "third_party/icu/source/common/unicode/ucnv.h"
+#include "third_party/icu/source/common/unicode/ucnv_cb.h"
+#include "third_party/icu/source/common/unicode/ucnv_err.h"
+#include "third_party/icu/source/common/unicode/ustring.h"
+
+namespace base {
+
+namespace {
+// ToUnicodeCallbackSubstitute() is based on UCNV_TO_U_CALLBACK_SUBSTITUTE
+// in source/common/ucnv_err.c.
+
+// Copyright (c) 1995-2006 International Business Machines Corporation
+// and others
+//
+// All rights reserved.
+//
+
+// 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, and/or
+// sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, provided that the above copyright notice(s) and
+// this permission notice appear in all copies of the Software and that
+// both the above copyright notice(s) and this permission notice appear in
+// supporting documentation.
+//
+// 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
+// OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
+// INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
+// OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
+// OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Except as contained in this notice, the name of a copyright holder
+// shall not be used in advertising or otherwise to promote the sale, use
+// or other dealings in this Software without prior written authorization
+// of the copyright holder.
+
+// ___________________________________________________________________________
+//
+// All trademarks and registered trademarks mentioned herein are the property
+// of their respective owners.
+
+void ToUnicodeCallbackSubstitute(const void* context,
+ UConverterToUnicodeArgs *to_args,
+ const char* code_units,
+ int32_t length,
+ UConverterCallbackReason reason,
+ UErrorCode * err) {
+ static const UChar kReplacementChar = 0xFFFD;
+ if (reason <= UCNV_IRREGULAR) {
+ if (context == nullptr ||
+ (*(reinterpret_cast<const char*>(context)) == 'i' &&
+ reason == UCNV_UNASSIGNED)) {
+ *err = U_ZERO_ERROR;
+ ucnv_cbToUWriteUChars(to_args, &kReplacementChar, 1, 0, err);
+ }
+ // else the caller must have set the error code accordingly.
+ }
+ // else ignore the reset, close and clone calls.
+}
+
+bool ConvertFromUTF16(UConverter* converter, const UChar* uchar_src,
+ int uchar_len, OnStringConversionError::Type on_error,
+ std::string* encoded) {
+ int encoded_max_length = UCNV_GET_MAX_BYTES_FOR_STRING(uchar_len,
+ ucnv_getMaxCharSize(converter));
+ encoded->resize(encoded_max_length);
+
+ UErrorCode status = U_ZERO_ERROR;
+
+ // Setup our error handler.
+ switch (on_error) {
+ case OnStringConversionError::FAIL:
+ ucnv_setFromUCallBack(converter, UCNV_FROM_U_CALLBACK_STOP, nullptr,
+ nullptr, nullptr, &status);
+ break;
+ case OnStringConversionError::SKIP:
+ case OnStringConversionError::SUBSTITUTE:
+ ucnv_setFromUCallBack(converter, UCNV_FROM_U_CALLBACK_SKIP, nullptr,
+ nullptr, nullptr, &status);
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ // ucnv_fromUChars returns size not including terminating null
+ int actual_size = ucnv_fromUChars(converter, &(*encoded)[0],
+ encoded_max_length, uchar_src, uchar_len, &status);
+ encoded->resize(actual_size);
+ ucnv_close(converter);
+ if (U_SUCCESS(status))
+ return true;
+ encoded->clear(); // Make sure the output is empty on error.
+ return false;
+}
+
+// Set up our error handler for ToUTF-16 converters
+void SetUpErrorHandlerForToUChars(OnStringConversionError::Type on_error,
+ UConverter* converter, UErrorCode* status) {
+ switch (on_error) {
+ case OnStringConversionError::FAIL:
+ ucnv_setToUCallBack(converter, UCNV_TO_U_CALLBACK_STOP, nullptr, nullptr,
+ nullptr, status);
+ break;
+ case OnStringConversionError::SKIP:
+ ucnv_setToUCallBack(converter, UCNV_TO_U_CALLBACK_SKIP, nullptr, nullptr,
+ nullptr, status);
+ break;
+ case OnStringConversionError::SUBSTITUTE:
+ ucnv_setToUCallBack(converter, ToUnicodeCallbackSubstitute, nullptr,
+ nullptr, nullptr, status);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+} // namespace
+
+// Codepage <-> Wide/UTF-16 ---------------------------------------------------
+
+bool UTF16ToCodepage(const string16& utf16,
+ const char* codepage_name,
+ OnStringConversionError::Type on_error,
+ std::string* encoded) {
+ encoded->clear();
+
+ UErrorCode status = U_ZERO_ERROR;
+ UConverter* converter = ucnv_open(codepage_name, &status);
+ if (!U_SUCCESS(status))
+ return false;
+
+ return ConvertFromUTF16(converter, utf16.c_str(),
+ static_cast<int>(utf16.length()), on_error, encoded);
+}
+
+bool CodepageToUTF16(const std::string& encoded,
+ const char* codepage_name,
+ OnStringConversionError::Type on_error,
+ string16* utf16) {
+ utf16->clear();
+
+ UErrorCode status = U_ZERO_ERROR;
+ UConverter* converter = ucnv_open(codepage_name, &status);
+ if (!U_SUCCESS(status))
+ return false;
+
+ // Even in the worst case, the maximum length in 2-byte units of UTF-16
+ // output would be at most the same as the number of bytes in input. There
+ // is no single-byte encoding in which a character is mapped to a
+ // non-BMP character requiring two 2-byte units.
+ //
+ // Moreover, non-BMP characters in legacy multibyte encodings
+ // (e.g. EUC-JP, GB18030) take at least 2 bytes. The only exceptions are
+ // BOCU and SCSU, but we don't care about them.
+ size_t uchar_max_length = encoded.length() + 1;
+
+ SetUpErrorHandlerForToUChars(on_error, converter, &status);
+ std::unique_ptr<char16[]> buffer(new char16[uchar_max_length]);
+ int actual_size = ucnv_toUChars(converter, buffer.get(),
+ static_cast<int>(uchar_max_length), encoded.data(),
+ static_cast<int>(encoded.length()), &status);
+ ucnv_close(converter);
+ if (!U_SUCCESS(status)) {
+ utf16->clear(); // Make sure the output is empty on error.
+ return false;
+ }
+
+ utf16->assign(buffer.get(), actual_size);
+ return true;
+}
+
+bool ConvertToUtf8AndNormalize(const std::string& text,
+ const std::string& charset,
+ std::string* result) {
+ result->clear();
+ string16 utf16;
+ if (!CodepageToUTF16(
+ text, charset.c_str(), OnStringConversionError::FAIL, &utf16))
+ return false;
+
+ UErrorCode status = U_ZERO_ERROR;
+ const icu::Normalizer2* normalizer = icu::Normalizer2::getNFCInstance(status);
+ DCHECK(U_SUCCESS(status));
+ if (U_FAILURE(status))
+ return false;
+ int32_t utf16_length = static_cast<int32_t>(utf16.length());
+ icu::UnicodeString normalized(utf16.data(), utf16_length);
+ int32_t normalized_prefix_length =
+ normalizer->spanQuickCheckYes(normalized, status);
+ if (normalized_prefix_length < utf16_length) {
+ icu::UnicodeString un_normalized(normalized, normalized_prefix_length);
+ normalized.truncate(normalized_prefix_length);
+ normalizer->normalizeSecondAndAppend(normalized, un_normalized, status);
+ }
+ if (U_FAILURE(status))
+ return false;
+ normalized.toUTF8String(*result);
+ return true;
+}
+
+} // namespace base
diff --git a/base/i18n/icu_string_conversions.h b/base/i18n/icu_string_conversions.h
new file mode 100644
index 0000000000..cbdcb99e4c
--- /dev/null
+++ b/base/i18n/icu_string_conversions.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_ICU_STRING_CONVERSIONS_H_
+#define BASE_I18N_ICU_STRING_CONVERSIONS_H_
+
+#include <string>
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/i18n/i18n_constants.h"
+#include "base/strings/string16.h"
+
+namespace base {
+
+// Defines the error handling modes of UTF16ToCodepage and CodepageToUTF16.
+class OnStringConversionError {
+ public:
+ enum Type {
+ // The function will return failure. The output buffer will be empty.
+ FAIL,
+
+ // The offending characters are skipped and the conversion will proceed as
+ // if they did not exist.
+ SKIP,
+
+ // When converting to Unicode, the offending byte sequences are substituted
+ // by Unicode replacement character (U+FFFD). When converting from Unicode,
+ // this is the same as SKIP.
+ SUBSTITUTE,
+ };
+
+ private:
+ OnStringConversionError() = delete;
+};
+
+// Converts between UTF-16 strings and the encoding specified. If the
+// encoding doesn't exist or the encoding fails (when on_error is FAIL),
+// returns false.
+BASE_I18N_EXPORT bool UTF16ToCodepage(const string16& utf16,
+ const char* codepage_name,
+ OnStringConversionError::Type on_error,
+ std::string* encoded);
+BASE_I18N_EXPORT bool CodepageToUTF16(const std::string& encoded,
+ const char* codepage_name,
+ OnStringConversionError::Type on_error,
+ string16* utf16);
+
+// Converts from any codepage to UTF-8 and ensures the resulting UTF-8 is
+// normalized.
+BASE_I18N_EXPORT bool ConvertToUtf8AndNormalize(const std::string& text,
+ const std::string& charset,
+ std::string* result);
+
+} // namespace base
+
+#endif // BASE_I18N_ICU_STRING_CONVERSIONS_H_
diff --git a/base/i18n/icu_string_conversions_unittest.cc b/base/i18n/icu_string_conversions_unittest.cc
new file mode 100644
index 0000000000..d1559860cc
--- /dev/null
+++ b/base/i18n/icu_string_conversions_unittest.cc
@@ -0,0 +1,235 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+
+#include <limits>
+#include <sstream>
+
+#include "base/format_macros.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+// Given a null-terminated string of wchar_t with each wchar_t representing
+// a UTF-16 code unit, returns a string16 made up of wchar_t's in the input.
+// Each wchar_t should be <= 0xFFFF and a non-BMP character (> U+FFFF)
+// should be represented as a surrogate pair (two UTF-16 units)
+// *even* where wchar_t is 32-bit (Linux and Mac).
+//
+// This is to help write tests for functions with string16 params until
+// the C++ 0x UTF-16 literal is well-supported by compilers.
+string16 BuildString16(const wchar_t* s) {
+#if defined(WCHAR_T_IS_UTF16)
+ return string16(s);
+#elif defined(WCHAR_T_IS_UTF32)
+ string16 u16;
+ while (*s != 0) {
+ DCHECK_LE(static_cast<unsigned int>(*s), 0xFFFFu);
+ u16.push_back(*s++);
+ }
+ return u16;
+#endif
+}
+
+} // namespace
+
+// kConverterCodepageCases is not comprehensive. There are a number of cases
+// to add if we really want to have a comprehensive coverage of various
+// codepages and their 'idiosyncrasies'. Currently, the only implementation
+// for CodepageTo* and *ToCodepage uses ICU, which has a very extensive
+// set of tests for the charset conversion. So, we can get away with a
+// relatively small number of cases listed below.
+//
+// Note about |u16_wide| in the following struct.
+// On Windows, the field is always identical to |wide|. On Mac and Linux,
+// it's identical as long as there's no character outside the
+// BMP (<= U+FFFF). When there is, it is different from |wide| and
+// is not a real wide string (UTF-32 string) in that each wchar_t in
+// the string is a UTF-16 code unit zero-extended to be 32-bit
+// even when the code unit belongs to a surrogate pair.
+// For instance, a Unicode string (U+0041 U+010000) is represented as
+// L"\x0041\xD800\xDC00" instead of L"\x0041\x10000".
+// To avoid the clutter, |u16_wide| will be set to NULL
+// if it's identical to |wide| on *all* platforms.
+
+static const struct {
+ const char* codepage_name;
+ const char* encoded;
+ OnStringConversionError::Type on_error;
+ bool success;
+ const wchar_t* wide;
+ const wchar_t* u16_wide;
+} kConvertCodepageCases[] = {
+ // Test a case where the input cannot be decoded, using SKIP, FAIL
+ // and SUBSTITUTE error handling rules. "A7 41" is valid, but "A6" isn't.
+ {"big5", "\xA7\x41\xA6", OnStringConversionError::FAIL, false, L"",
+ nullptr},
+ {"big5", "\xA7\x41\xA6", OnStringConversionError::SKIP, true, L"\x4F60",
+ nullptr},
+ {"big5", "\xA7\x41\xA6", OnStringConversionError::SUBSTITUTE, true,
+ L"\x4F60\xFFFD", nullptr},
+ // Arabic (ISO-8859)
+ {"iso-8859-6",
+ "\xC7\xEE\xE4\xD3\xF1\xEE\xE4\xC7\xE5\xEF"
+ " "
+ "\xD9\xEE\xE4\xEE\xEA\xF2\xE3\xEF\xE5\xF2",
+ OnStringConversionError::FAIL, true,
+ L"\x0627\x064E\x0644\x0633\x0651\x064E\x0644\x0627\x0645\x064F"
+ L" "
+ L"\x0639\x064E\x0644\x064E\x064A\x0652\x0643\x064F\x0645\x0652",
+ nullptr},
+ // Chinese Simplified (GB2312)
+ {"gb2312", "\xC4\xE3\xBA\xC3", OnStringConversionError::FAIL, true,
+ L"\x4F60\x597D", nullptr},
+ // Chinese (GB18030) : 4 byte sequences mapped to BMP characters
+ {"gb18030", "\x81\x30\x84\x36\xA1\xA7", OnStringConversionError::FAIL, true,
+ L"\x00A5\x00A8", nullptr},
+ // Chinese (GB18030) : A 4 byte sequence mapped to plane 2 (U+20000)
+ {"gb18030", "\x95\x32\x82\x36\xD2\xBB", OnStringConversionError::FAIL, true,
+#if defined(WCHAR_T_IS_UTF16)
+ L"\xD840\xDC00\x4E00",
+#elif defined(WCHAR_T_IS_UTF32)
+ L"\x20000\x4E00",
+#endif
+ L"\xD840\xDC00\x4E00"},
+ {"big5", "\xA7\x41\xA6\x6E", OnStringConversionError::FAIL, true,
+ L"\x4F60\x597D", nullptr},
+ // Greek (ISO-8859)
+ {"iso-8859-7",
+ "\xE3\xE5\xE9\xDC"
+ " "
+ "\xF3\xEF\xF5",
+ OnStringConversionError::FAIL, true,
+ L"\x03B3\x03B5\x03B9\x03AC"
+ L" "
+ L"\x03C3\x03BF\x03C5",
+ nullptr},
+ // Hebrew (Windows)
+ {"windows-1255", "\xF9\xD1\xC8\xEC\xE5\xC9\xED",
+ OnStringConversionError::FAIL, true,
+ L"\x05E9\x05C1\x05B8\x05DC\x05D5\x05B9\x05DD", nullptr},
+ // Korean (EUC)
+ {"euc-kr", "\xBE\xC8\xB3\xE7\xC7\xCF\xBC\xBC\xBF\xE4",
+ OnStringConversionError::FAIL, true, L"\xC548\xB155\xD558\xC138\xC694",
+ nullptr},
+ // Japanese (EUC)
+ {"euc-jp", "\xA4\xB3\xA4\xF3\xA4\xCB\xA4\xC1\xA4\xCF\xB0\xEC\x8E\xA6",
+ OnStringConversionError::FAIL, true,
+ L"\x3053\x3093\x306B\x3061\x306F\x4E00\xFF66", nullptr},
+ // Japanese (ISO-2022)
+ {"iso-2022-jp",
+ "\x1B$B"
+ "\x24\x33\x24\x73\x24\x4B\x24\x41\x24\x4F\x30\x6C"
+ "\x1B(B"
+ "ab"
+ "\x1B(J"
+ "\x5C\x7E#$"
+ "\x1B(B",
+ OnStringConversionError::FAIL, true,
+ L"\x3053\x3093\x306B\x3061\x306F\x4E00"
+ L"ab\x00A5\x203E#$",
+ nullptr},
+ // Japanese (Shift-JIS)
+ {"sjis", "\x82\xB1\x82\xF1\x82\xC9\x82\xBF\x82\xCD\x88\xEA\xA6",
+ OnStringConversionError::FAIL, true,
+ L"\x3053\x3093\x306B\x3061\x306F\x4E00\xFF66", nullptr},
+ // Russian (KOI8)
+ {"koi8-r", "\xDA\xC4\xD2\xC1\xD7\xD3\xD4\xD7\xD5\xCA\xD4\xC5",
+ OnStringConversionError::FAIL, true,
+ L"\x0437\x0434\x0440\x0430\x0432\x0441\x0442\x0432"
+ L"\x0443\x0439\x0442\x0435",
+ nullptr},
+ // Thai (windows-874)
+ {"windows-874",
+ "\xCA\xC7\xD1\xCA\xB4\xD5"
+ "\xA4\xC3\xD1\xBA",
+ OnStringConversionError::FAIL, true,
+ L"\x0E2A\x0E27\x0E31\x0E2A\x0E14\x0E35"
+ L"\x0E04\x0E23\x0e31\x0E1A",
+ nullptr},
+};
+
+TEST(ICUStringConversionsTest, ConvertBetweenCodepageAndUTF16) {
+ for (size_t i = 0; i < arraysize(kConvertCodepageCases); ++i) {
+ SCOPED_TRACE(base::StringPrintf(
+ "Test[%" PRIuS "]: <encoded: %s> <codepage: %s>", i,
+ kConvertCodepageCases[i].encoded,
+ kConvertCodepageCases[i].codepage_name));
+
+ string16 utf16;
+ bool success = CodepageToUTF16(kConvertCodepageCases[i].encoded,
+ kConvertCodepageCases[i].codepage_name,
+ kConvertCodepageCases[i].on_error,
+ &utf16);
+ string16 utf16_expected;
+ if (kConvertCodepageCases[i].u16_wide == nullptr)
+ utf16_expected = BuildString16(kConvertCodepageCases[i].wide);
+ else
+ utf16_expected = BuildString16(kConvertCodepageCases[i].u16_wide);
+ EXPECT_EQ(kConvertCodepageCases[i].success, success);
+ EXPECT_EQ(utf16_expected, utf16);
+
+ // When decoding was successful and nothing was skipped, we also check the
+ // reverse conversion. See also the corresponding comment in
+ // ConvertBetweenCodepageAndWide.
+ if (success &&
+ kConvertCodepageCases[i].on_error == OnStringConversionError::FAIL) {
+ std::string encoded;
+ success = UTF16ToCodepage(utf16, kConvertCodepageCases[i].codepage_name,
+ kConvertCodepageCases[i].on_error, &encoded);
+ EXPECT_EQ(kConvertCodepageCases[i].success, success);
+ EXPECT_EQ(kConvertCodepageCases[i].encoded, encoded);
+ }
+ }
+}
+
+static const struct {
+ const char* encoded;
+ const char* codepage_name;
+ bool expected_success;
+ const char* expected_value;
+} kConvertAndNormalizeCases[] = {
+ {"foo-\xe4.html", "iso-8859-1", true, "foo-\xc3\xa4.html"},
+ {"foo-\xe4.html", "iso-8859-7", true, "foo-\xce\xb4.html"},
+ {"foo-\xe4.html", "foo-bar", false, ""},
+ // HTML Encoding spec treats US-ASCII as synonymous with windows-1252
+ {"foo-\xff.html", "ascii", true, "foo-\xc3\xbf.html"},
+ {"foo.html", "ascii", true, "foo.html"},
+ {"foo-a\xcc\x88.html", "utf-8", true, "foo-\xc3\xa4.html"},
+ {"\x95\x32\x82\x36\xD2\xBB", "gb18030", true, "\xF0\xA0\x80\x80\xE4\xB8\x80"},
+ {"\xA7\x41\xA6\x6E", "big5", true, "\xE4\xBD\xA0\xE5\xA5\xBD"},
+ // Windows-1258 does have a combining character at xD2 (which is U+0309).
+ // The sequence of (U+00E2, U+0309) is also encoded as U+1EA9.
+ {"foo\xE2\xD2", "windows-1258", true, "foo\xE1\xBA\xA9"},
+ {"", "iso-8859-1", true, ""},
+};
+TEST(ICUStringConversionsTest, ConvertToUtf8AndNormalize) {
+ std::string result;
+ for (size_t i = 0; i < arraysize(kConvertAndNormalizeCases); ++i) {
+ SCOPED_TRACE(base::StringPrintf(
+ "Test[%" PRIuS "]: <encoded: %s> <codepage: %s>", i,
+ kConvertAndNormalizeCases[i].encoded,
+ kConvertAndNormalizeCases[i].codepage_name));
+
+ bool success = ConvertToUtf8AndNormalize(
+ kConvertAndNormalizeCases[i].encoded,
+ kConvertAndNormalizeCases[i].codepage_name, &result);
+ EXPECT_EQ(kConvertAndNormalizeCases[i].expected_success, success);
+ EXPECT_EQ(kConvertAndNormalizeCases[i].expected_value, result);
+ }
+}
+
+} // namespace base
diff --git a/base/i18n/icu_util.cc b/base/i18n/icu_util.cc
new file mode 100644
index 0000000000..4d588c6160
--- /dev/null
+++ b/base/i18n/icu_util.cc
@@ -0,0 +1,333 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/icu_util.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include <string>
+
+#include "base/debug/alias.h"
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "build/build_config.h"
+#include "third_party/icu/source/common/unicode/putil.h"
+#include "third_party/icu/source/common/unicode/udata.h"
+#if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_ANDROID)
+#include "third_party/icu/source/i18n/unicode/timezone.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "base/android/apk_assets.h"
+#include "base/android/timezone_utils.h"
+#endif
+
+#if defined(OS_IOS)
+#include "base/ios/ios_util.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/foundation_util.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include "base/base_paths_fuchsia.h"
+#endif
+
+namespace base {
+namespace i18n {
+
+#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_SHARED
+#define ICU_UTIL_DATA_SYMBOL "icudt" U_ICU_VERSION_SHORT "_dat"
+#if defined(OS_WIN)
+#define ICU_UTIL_DATA_SHARED_MODULE_NAME "icudt.dll"
+#endif
+#endif
+
+namespace {
+#if !defined(OS_NACL)
+#if DCHECK_IS_ON()
+// Assert that we are not called more than once. Even though calling this
+// function isn't harmful (ICU can handle it), being called twice probably
+// indicates a programming error.
+bool g_check_called_once = true;
+bool g_called_once = false;
+#endif // DCHECK_IS_ON()
+
+#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
+
+// To debug http://crbug.com/445616.
+int g_debug_icu_last_error;
+int g_debug_icu_load;
+int g_debug_icu_pf_error_details;
+int g_debug_icu_pf_last_error;
+#if defined(OS_WIN)
+wchar_t g_debug_icu_pf_filename[_MAX_PATH];
+#endif // OS_WIN
+// Use an unversioned file name to simplify a icu version update down the road.
+// No need to change the filename in multiple places (gyp files, windows
+// build pkg configurations, etc). 'l' stands for Little Endian.
+// This variable is exported through the header file.
+const char kIcuDataFileName[] = "icudtl.dat";
+#if defined(OS_ANDROID)
+const char kAndroidAssetsIcuDataFileName[] = "assets/icudtl.dat";
+#endif
+
+// File handle intentionally never closed. Not using File here because its
+// Windows implementation guards against two instances owning the same
+// PlatformFile (which we allow since we know it is never freed).
+PlatformFile g_icudtl_pf = kInvalidPlatformFile;
+MemoryMappedFile* g_icudtl_mapped_file = nullptr;
+MemoryMappedFile::Region g_icudtl_region;
+
+void LazyInitIcuDataFile() {
+ if (g_icudtl_pf != kInvalidPlatformFile) {
+ return;
+ }
+#if defined(OS_ANDROID)
+ int fd = base::android::OpenApkAsset(kAndroidAssetsIcuDataFileName,
+ &g_icudtl_region);
+ g_icudtl_pf = fd;
+ if (fd != -1) {
+ return;
+ }
+// For unit tests, data file is located on disk, so try there as a fallback.
+#endif // defined(OS_ANDROID)
+#if !defined(OS_MACOSX)
+ FilePath data_path;
+ if (!PathService::Get(DIR_ASSETS, &data_path)) {
+ LOG(ERROR) << "Can't find " << kIcuDataFileName;
+ return;
+ }
+#if defined(OS_WIN)
+ // TODO(brucedawson): http://crbug.com/445616
+ wchar_t tmp_buffer[_MAX_PATH] = {0};
+ wcscpy_s(tmp_buffer, data_path.value().c_str());
+ debug::Alias(tmp_buffer);
+#endif
+ data_path = data_path.AppendASCII(kIcuDataFileName);
+
+#if defined(OS_WIN)
+ // TODO(brucedawson): http://crbug.com/445616
+ wchar_t tmp_buffer2[_MAX_PATH] = {0};
+ wcscpy_s(tmp_buffer2, data_path.value().c_str());
+ debug::Alias(tmp_buffer2);
+#endif
+
+#else // !defined(OS_MACOSX)
+ // Assume it is in the framework bundle's Resources directory.
+ ScopedCFTypeRef<CFStringRef> data_file_name(
+ SysUTF8ToCFStringRef(kIcuDataFileName));
+ FilePath data_path = mac::PathForFrameworkBundleResource(data_file_name);
+#if defined(OS_IOS)
+ FilePath override_data_path = base::ios::FilePathOfEmbeddedICU();
+ if (!override_data_path.empty()) {
+ data_path = override_data_path;
+ }
+#endif // !defined(OS_IOS)
+ if (data_path.empty()) {
+ LOG(ERROR) << kIcuDataFileName << " not found in bundle";
+ return;
+ }
+#endif // !defined(OS_MACOSX)
+ File file(data_path, File::FLAG_OPEN | File::FLAG_READ);
+ if (file.IsValid()) {
+ // TODO(brucedawson): http://crbug.com/445616.
+ g_debug_icu_pf_last_error = 0;
+ g_debug_icu_pf_error_details = 0;
+#if defined(OS_WIN)
+ g_debug_icu_pf_filename[0] = 0;
+#endif // OS_WIN
+
+ g_icudtl_pf = file.TakePlatformFile();
+ g_icudtl_region = MemoryMappedFile::Region::kWholeFile;
+ }
+#if defined(OS_WIN)
+ else {
+ // TODO(brucedawson): http://crbug.com/445616.
+ g_debug_icu_pf_last_error = ::GetLastError();
+ g_debug_icu_pf_error_details = file.error_details();
+ wcscpy_s(g_debug_icu_pf_filename, data_path.value().c_str());
+ }
+#endif // OS_WIN
+}
+
+bool InitializeICUWithFileDescriptorInternal(
+ PlatformFile data_fd,
+ const MemoryMappedFile::Region& data_region) {
+ // This can be called multiple times in tests.
+ if (g_icudtl_mapped_file) {
+ g_debug_icu_load = 0; // To debug http://crbug.com/445616.
+ return true;
+ }
+ if (data_fd == kInvalidPlatformFile) {
+ g_debug_icu_load = 1; // To debug http://crbug.com/445616.
+ LOG(ERROR) << "Invalid file descriptor to ICU data received.";
+ return false;
+ }
+
+ std::unique_ptr<MemoryMappedFile> icudtl_mapped_file(new MemoryMappedFile());
+ if (!icudtl_mapped_file->Initialize(File(data_fd), data_region)) {
+ g_debug_icu_load = 2; // To debug http://crbug.com/445616.
+ LOG(ERROR) << "Couldn't mmap icu data file";
+ return false;
+ }
+ g_icudtl_mapped_file = icudtl_mapped_file.release();
+
+ UErrorCode err = U_ZERO_ERROR;
+ udata_setCommonData(const_cast<uint8_t*>(g_icudtl_mapped_file->data()), &err);
+ if (err != U_ZERO_ERROR) {
+ g_debug_icu_load = 3; // To debug http://crbug.com/445616.
+ g_debug_icu_last_error = err;
+ }
+#if defined(OS_ANDROID)
+ else {
+ // On Android, we can't leave it up to ICU to set the default timezone
+ // because ICU's timezone detection does not work in many timezones (e.g.
+ // Australia/Sydney, Asia/Seoul, Europe/Paris ). Use JNI to detect the host
+ // timezone and set the ICU default timezone accordingly in advance of
+ // actual use. See crbug.com/722821 and
+ // https://ssl.icu-project.org/trac/ticket/13208 .
+ base::string16 timezone_id = base::android::GetDefaultTimeZoneId();
+ icu::TimeZone::adoptDefault(icu::TimeZone::createTimeZone(
+ icu::UnicodeString(FALSE, timezone_id.data(), timezone_id.length())));
+ }
+#endif
+ // Never try to load ICU data from files.
+ udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
+ return err == U_ZERO_ERROR;
+}
+#endif // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
+#endif // !defined(OS_NACL)
+
+} // namespace
+
+#if !defined(OS_NACL)
+#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
+#if defined(OS_ANDROID)
+bool InitializeICUWithFileDescriptor(
+ PlatformFile data_fd,
+ const MemoryMappedFile::Region& data_region) {
+#if DCHECK_IS_ON()
+ DCHECK(!g_check_called_once || !g_called_once);
+ g_called_once = true;
+#endif
+ return InitializeICUWithFileDescriptorInternal(data_fd, data_region);
+}
+
+PlatformFile GetIcuDataFileHandle(MemoryMappedFile::Region* out_region) {
+ CHECK_NE(g_icudtl_pf, kInvalidPlatformFile);
+ *out_region = g_icudtl_region;
+ return g_icudtl_pf;
+}
+#endif
+
+const uint8_t* GetRawIcuMemory() {
+ CHECK(g_icudtl_mapped_file);
+ return g_icudtl_mapped_file->data();
+}
+
+bool InitializeICUFromRawMemory(const uint8_t* raw_memory) {
+#if !defined(COMPONENT_BUILD)
+#if DCHECK_IS_ON()
+ DCHECK(!g_check_called_once || !g_called_once);
+ g_called_once = true;
+#endif
+
+ UErrorCode err = U_ZERO_ERROR;
+ udata_setCommonData(const_cast<uint8_t*>(raw_memory), &err);
+ // Never try to load ICU data from files.
+ udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
+ return err == U_ZERO_ERROR;
+#else
+ return true;
+#endif
+}
+
+#endif // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
+
+bool InitializeICU() {
+#if DCHECK_IS_ON()
+ DCHECK(!g_check_called_once || !g_called_once);
+ g_called_once = true;
+#endif
+
+ bool result;
+#if (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_SHARED)
+ FilePath data_path;
+ PathService::Get(DIR_ASSETS, &data_path);
+ data_path = data_path.AppendASCII(ICU_UTIL_DATA_SHARED_MODULE_NAME);
+
+ HMODULE module = LoadLibrary(data_path.value().c_str());
+ if (!module) {
+ LOG(ERROR) << "Failed to load " << ICU_UTIL_DATA_SHARED_MODULE_NAME;
+ return false;
+ }
+
+ FARPROC addr = GetProcAddress(module, ICU_UTIL_DATA_SYMBOL);
+ if (!addr) {
+ LOG(ERROR) << ICU_UTIL_DATA_SYMBOL << ": not found in "
+ << ICU_UTIL_DATA_SHARED_MODULE_NAME;
+ return false;
+ }
+
+ UErrorCode err = U_ZERO_ERROR;
+ udata_setCommonData(reinterpret_cast<void*>(addr), &err);
+ // Never try to load ICU data from files.
+ udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
+ result = (err == U_ZERO_ERROR);
+#elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_STATIC)
+ // The ICU data is statically linked.
+ result = true;
+#elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
+ // If the ICU data directory is set, ICU won't actually load the data until
+ // it is needed. This can fail if the process is sandboxed at that time.
+ // Instead, we map the file in and hand off the data so the sandbox won't
+ // cause any problems.
+ LazyInitIcuDataFile();
+ result =
+ InitializeICUWithFileDescriptorInternal(g_icudtl_pf, g_icudtl_region);
+#if defined(OS_WIN)
+ int debug_icu_load = g_debug_icu_load;
+ debug::Alias(&debug_icu_load);
+ int debug_icu_last_error = g_debug_icu_last_error;
+ debug::Alias(&debug_icu_last_error);
+ int debug_icu_pf_last_error = g_debug_icu_pf_last_error;
+ debug::Alias(&debug_icu_pf_last_error);
+ int debug_icu_pf_error_details = g_debug_icu_pf_error_details;
+ debug::Alias(&debug_icu_pf_error_details);
+ wchar_t debug_icu_pf_filename[_MAX_PATH] = {0};
+ wcscpy_s(debug_icu_pf_filename, g_debug_icu_pf_filename);
+ debug::Alias(&debug_icu_pf_filename);
+ CHECK(result); // TODO(brucedawson): http://crbug.com/445616
+#endif
+#endif
+
+// To respond to the timezone change properly, the default timezone
+// cache in ICU has to be populated on starting up.
+// TODO(jungshik): Some callers do not care about tz at all. If necessary,
+// add a boolean argument to this function to init'd the default tz only
+// when requested.
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ if (result)
+ std::unique_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault());
+#endif
+ return result;
+}
+#endif // !defined(OS_NACL)
+
+void AllowMultipleInitializeCallsForTesting() {
+#if DCHECK_IS_ON() && !defined(OS_NACL)
+ g_check_called_once = false;
+#endif
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/icu_util.h b/base/i18n/icu_util.h
new file mode 100644
index 0000000000..5f9948fa65
--- /dev/null
+++ b/base/i18n/icu_util.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_ICU_UTIL_H_
+#define BASE_I18N_ICU_UTIL_H_
+
+#include <stdint.h>
+
+#include "base/files/memory_mapped_file.h"
+#include "base/i18n/base_i18n_export.h"
+#include "build/build_config.h"
+
+#define ICU_UTIL_DATA_FILE 0
+#define ICU_UTIL_DATA_SHARED 1
+#define ICU_UTIL_DATA_STATIC 2
+
+namespace base {
+namespace i18n {
+
+#if !defined(OS_NACL)
+// Call this function to load ICU's data tables for the current process. This
+// function should be called before ICU is used.
+BASE_I18N_EXPORT bool InitializeICU();
+
+#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
+#if defined(OS_ANDROID)
+// Returns the PlatformFile and Region that was initialized by InitializeICU().
+// Use with InitializeICUWithFileDescriptor().
+BASE_I18N_EXPORT PlatformFile GetIcuDataFileHandle(
+ MemoryMappedFile::Region* out_region);
+
+// Android uses a file descriptor passed by browser process to initialize ICU
+// in render processes.
+BASE_I18N_EXPORT bool InitializeICUWithFileDescriptor(
+ PlatformFile data_fd,
+ const MemoryMappedFile::Region& data_region);
+#endif
+
+// Returns a void pointer to the memory mapped ICU data file.
+//
+// There are cases on Android where we would be unsafely reusing a file
+// descriptor within the same process when initializing two copies of ICU from
+// different binaries in the same address space. This returns an unowned
+// pointer to the memory mapped icu data file; consumers copies of base must
+// not outlive the copy of base that owns the memory mapped file.
+BASE_I18N_EXPORT const uint8_t* GetRawIcuMemory();
+
+// Initializes ICU memory
+//
+// This does nothing in component builds; this initialization should only be
+// done in cases where there could be two copies of base in a single process in
+// non-component builds. (The big example is standalone service libraries: the
+// Service Manager will have a copy of base linked in, and the majority of
+// service libraries will have base linked in but in non-component builds,
+// these will be separate copies of base.)
+BASE_I18N_EXPORT bool InitializeICUFromRawMemory(const uint8_t* raw_memory);
+#endif // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
+#endif // !defined(OS_NACL)
+
+// In a test binary, the call above might occur twice.
+BASE_I18N_EXPORT void AllowMultipleInitializeCallsForTesting();
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_ICU_UTIL_H_
diff --git a/base/i18n/message_formatter.cc b/base/i18n/message_formatter.cc
new file mode 100644
index 0000000000..c69dd07d3d
--- /dev/null
+++ b/base/i18n/message_formatter.cc
@@ -0,0 +1,142 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/message_formatter.h"
+
+#include "base/i18n/unicodestring.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/time/time.h"
+#include "third_party/icu/source/common/unicode/unistr.h"
+#include "third_party/icu/source/common/unicode/utypes.h"
+#include "third_party/icu/source/i18n/unicode/fmtable.h"
+#include "third_party/icu/source/i18n/unicode/msgfmt.h"
+
+using icu::UnicodeString;
+
+namespace base {
+namespace i18n {
+namespace {
+UnicodeString UnicodeStringFromStringPiece(StringPiece str) {
+ return UnicodeString::fromUTF8(
+ icu::StringPiece(str.data(), base::checked_cast<int32_t>(str.size())));
+}
+} // anonymous namespace
+
+namespace internal {
+MessageArg::MessageArg() : formattable(nullptr) {}
+
+MessageArg::MessageArg(const char* s)
+ : formattable(new icu::Formattable(UnicodeStringFromStringPiece(s))) {}
+
+MessageArg::MessageArg(StringPiece s)
+ : formattable(new icu::Formattable(UnicodeStringFromStringPiece(s))) {}
+
+MessageArg::MessageArg(const std::string& s)
+ : formattable(new icu::Formattable(UnicodeString::fromUTF8(s))) {}
+
+MessageArg::MessageArg(const string16& s)
+ : formattable(new icu::Formattable(UnicodeString(s.data(), s.size()))) {}
+
+MessageArg::MessageArg(int i) : formattable(new icu::Formattable(i)) {}
+
+MessageArg::MessageArg(int64_t i) : formattable(new icu::Formattable(i)) {}
+
+MessageArg::MessageArg(double d) : formattable(new icu::Formattable(d)) {}
+
+MessageArg::MessageArg(const Time& t)
+ : formattable(new icu::Formattable(static_cast<UDate>(t.ToJsTime()))) {}
+
+MessageArg::~MessageArg() = default;
+
+// Tests if this argument has a value, and if so increments *count.
+bool MessageArg::has_value(int *count) const {
+ if (formattable == nullptr)
+ return false;
+
+ ++*count;
+ return true;
+}
+
+} // namespace internal
+
+string16 MessageFormatter::FormatWithNumberedArgs(
+ StringPiece16 msg,
+ const internal::MessageArg& arg0,
+ const internal::MessageArg& arg1,
+ const internal::MessageArg& arg2,
+ const internal::MessageArg& arg3,
+ const internal::MessageArg& arg4,
+ const internal::MessageArg& arg5,
+ const internal::MessageArg& arg6) {
+ int32_t args_count = 0;
+ icu::Formattable args[] = {
+ arg0.has_value(&args_count) ? *arg0.formattable : icu::Formattable(),
+ arg1.has_value(&args_count) ? *arg1.formattable : icu::Formattable(),
+ arg2.has_value(&args_count) ? *arg2.formattable : icu::Formattable(),
+ arg3.has_value(&args_count) ? *arg3.formattable : icu::Formattable(),
+ arg4.has_value(&args_count) ? *arg4.formattable : icu::Formattable(),
+ arg5.has_value(&args_count) ? *arg5.formattable : icu::Formattable(),
+ arg6.has_value(&args_count) ? *arg6.formattable : icu::Formattable(),
+ };
+
+ UnicodeString msg_string(msg.data(), msg.size());
+ UErrorCode error = U_ZERO_ERROR;
+ icu::MessageFormat format(msg_string, error);
+ icu::UnicodeString formatted;
+ icu::FieldPosition ignore(icu::FieldPosition::DONT_CARE);
+ format.format(args, args_count, formatted, ignore, error);
+ if (U_FAILURE(error)) {
+ LOG(ERROR) << "MessageFormat(" << msg.as_string() << ") failed with "
+ << u_errorName(error);
+ return string16();
+ }
+ return i18n::UnicodeStringToString16(formatted);
+}
+
+string16 MessageFormatter::FormatWithNamedArgs(
+ StringPiece16 msg,
+ StringPiece name0, const internal::MessageArg& arg0,
+ StringPiece name1, const internal::MessageArg& arg1,
+ StringPiece name2, const internal::MessageArg& arg2,
+ StringPiece name3, const internal::MessageArg& arg3,
+ StringPiece name4, const internal::MessageArg& arg4,
+ StringPiece name5, const internal::MessageArg& arg5,
+ StringPiece name6, const internal::MessageArg& arg6) {
+ icu::UnicodeString names[] = {
+ UnicodeStringFromStringPiece(name0),
+ UnicodeStringFromStringPiece(name1),
+ UnicodeStringFromStringPiece(name2),
+ UnicodeStringFromStringPiece(name3),
+ UnicodeStringFromStringPiece(name4),
+ UnicodeStringFromStringPiece(name5),
+ UnicodeStringFromStringPiece(name6),
+ };
+ int32_t args_count = 0;
+ icu::Formattable args[] = {
+ arg0.has_value(&args_count) ? *arg0.formattable : icu::Formattable(),
+ arg1.has_value(&args_count) ? *arg1.formattable : icu::Formattable(),
+ arg2.has_value(&args_count) ? *arg2.formattable : icu::Formattable(),
+ arg3.has_value(&args_count) ? *arg3.formattable : icu::Formattable(),
+ arg4.has_value(&args_count) ? *arg4.formattable : icu::Formattable(),
+ arg5.has_value(&args_count) ? *arg5.formattable : icu::Formattable(),
+ arg6.has_value(&args_count) ? *arg6.formattable : icu::Formattable(),
+ };
+
+ UnicodeString msg_string(msg.data(), msg.size());
+ UErrorCode error = U_ZERO_ERROR;
+ icu::MessageFormat format(msg_string, error);
+
+ icu::UnicodeString formatted;
+ format.format(names, args, args_count, formatted, error);
+ if (U_FAILURE(error)) {
+ LOG(ERROR) << "MessageFormat(" << msg.as_string() << ") failed with "
+ << u_errorName(error);
+ return string16();
+ }
+ return i18n::UnicodeStringToString16(formatted);
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/message_formatter.h b/base/i18n/message_formatter.h
new file mode 100644
index 0000000000..36a656d771
--- /dev/null
+++ b/base/i18n/message_formatter.h
@@ -0,0 +1,128 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_MESSAGE_FORMATTER_H_
+#define BASE_I18N_MESSAGE_FORMATTER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+#include "third_party/icu/source/common/unicode/uversion.h"
+
+U_NAMESPACE_BEGIN
+class Formattable;
+U_NAMESPACE_END
+
+namespace base {
+
+class Time;
+
+namespace i18n {
+
+class MessageFormatter;
+
+namespace internal {
+
+class BASE_I18N_EXPORT MessageArg {
+ public:
+ MessageArg(const char* s);
+ MessageArg(StringPiece s);
+ MessageArg(const std::string& s);
+ MessageArg(const string16& s);
+ MessageArg(int i);
+ MessageArg(int64_t i);
+ MessageArg(double d);
+ MessageArg(const Time& t);
+ ~MessageArg();
+
+ private:
+ friend class base::i18n::MessageFormatter;
+ MessageArg();
+ // Tests if this argument has a value, and if so increments *count.
+ bool has_value(int* count) const;
+ std::unique_ptr<icu::Formattable> formattable;
+ DISALLOW_COPY_AND_ASSIGN(MessageArg);
+};
+
+} // namespace internal
+
+// Message Formatter with the ICU message format syntax support.
+// It can format strings (UTF-8 and UTF-16), numbers and base::Time with
+// plural, gender and other 'selectors' support. This is handy if you
+// have multiple parameters of differnt types and some of them require
+// plural or gender/selector support.
+//
+// To use this API for locale-sensitive formatting, retrieve a 'message
+// template' in the ICU message format from a message bundle (e.g. with
+// l10n_util::GetStringUTF16()) and pass it to FormatWith{Named,Numbered}Args.
+//
+// MessageFormat specs:
+// http://icu-project.org/apiref/icu4j/com/ibm/icu/text/MessageFormat.html
+// http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html#details
+// Examples:
+// http://userguide.icu-project.org/formatparse/messages
+// message_formatter_unittest.cc
+// go/plurals inside Google.
+// TODO(jshin): Document this API in md format docs.
+// Caveat:
+// When plural/select/gender is used along with other format specifiers such
+// as date or number, plural/select/gender should be at the top level. It's
+// not an ICU restriction but a constraint imposed by Google's translation
+// infrastructure. Message A does not work. It must be revised to Message B.
+//
+// A.
+// Rated <ph name="RATING">{0, number,0.0}<ex>3.2</ex></ph>
+// by {1, plural, =1{a user} other{# users}}
+//
+// B.
+// {1, plural,
+// =1{Rated <ph name="RATING">{0, number,0.0}<ex>3.2</ex></ph>
+// by a user.}
+// other{Rated <ph name="RATING">{0, number,0.0}<ex>3.2</ex></ph>
+// by # users.}}
+
+class BASE_I18N_EXPORT MessageFormatter {
+ public:
+ static string16 FormatWithNamedArgs(
+ StringPiece16 msg,
+ StringPiece name0 = StringPiece(),
+ const internal::MessageArg& arg0 = internal::MessageArg(),
+ StringPiece name1 = StringPiece(),
+ const internal::MessageArg& arg1 = internal::MessageArg(),
+ StringPiece name2 = StringPiece(),
+ const internal::MessageArg& arg2 = internal::MessageArg(),
+ StringPiece name3 = StringPiece(),
+ const internal::MessageArg& arg3 = internal::MessageArg(),
+ StringPiece name4 = StringPiece(),
+ const internal::MessageArg& arg4 = internal::MessageArg(),
+ StringPiece name5 = StringPiece(),
+ const internal::MessageArg& arg5 = internal::MessageArg(),
+ StringPiece name6 = StringPiece(),
+ const internal::MessageArg& arg6 = internal::MessageArg());
+
+ static string16 FormatWithNumberedArgs(
+ StringPiece16 msg,
+ const internal::MessageArg& arg0 = internal::MessageArg(),
+ const internal::MessageArg& arg1 = internal::MessageArg(),
+ const internal::MessageArg& arg2 = internal::MessageArg(),
+ const internal::MessageArg& arg3 = internal::MessageArg(),
+ const internal::MessageArg& arg4 = internal::MessageArg(),
+ const internal::MessageArg& arg5 = internal::MessageArg(),
+ const internal::MessageArg& arg6 = internal::MessageArg());
+
+ private:
+ MessageFormatter() = delete;
+ DISALLOW_COPY_AND_ASSIGN(MessageFormatter);
+};
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_MESSAGE_FORMATTER_H_
diff --git a/base/i18n/message_formatter_unittest.cc b/base/i18n/message_formatter_unittest.cc
new file mode 100644
index 0000000000..a6f461370c
--- /dev/null
+++ b/base/i18n/message_formatter_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/message_formatter.h"
+
+#include <memory>
+
+#include "base/i18n/rtl.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/common/unicode/unistr.h"
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
+#include "third_party/icu/source/i18n/unicode/msgfmt.h"
+
+typedef testing::Test MessageFormatterTest;
+
+namespace base {
+namespace i18n {
+
+class MessageFormatterTest : public testing::Test {
+ protected:
+ MessageFormatterTest() {
+ original_locale_ = GetConfiguredLocale();
+ SetICUDefaultLocale("en-US");
+ }
+ ~MessageFormatterTest() override {
+ SetICUDefaultLocale(original_locale_);
+ }
+
+ private:
+ std::string original_locale_;
+};
+
+namespace {
+
+void AppendFormattedDateTime(const std::unique_ptr<icu::DateFormat>& df,
+ const Time& now,
+ std::string* result) {
+ icu::UnicodeString formatted;
+ df->format(static_cast<UDate>(now.ToJsTime()), formatted).
+ toUTF8String(*result);
+}
+
+} // namespace
+
+TEST_F(MessageFormatterTest, PluralNamedArgs) {
+ const string16 pattern = ASCIIToUTF16(
+ "{num_people, plural, "
+ "=0 {I met nobody in {place}.}"
+ "=1 {I met a person in {place}.}"
+ "other {I met # people in {place}.}}");
+
+ std::string result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 0, "place", "Paris"));
+ EXPECT_EQ("I met nobody in Paris.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 1, "place", "Paris"));
+ EXPECT_EQ("I met a person in Paris.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 5, "place", "Paris"));
+ EXPECT_EQ("I met 5 people in Paris.", result);
+}
+
+TEST_F(MessageFormatterTest, PluralNamedArgsWithOffset) {
+ const string16 pattern = ASCIIToUTF16(
+ "{num_people, plural, offset:1 "
+ "=0 {I met nobody in {place}.}"
+ "=1 {I met {person} in {place}.}"
+ "=2 {I met {person} and one other person in {place}.}"
+ "=13 {I met {person} and a dozen other people in {place}.}"
+ "other {I met {person} and # other people in {place}.}}");
+
+ std::string result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 0, "place", "Paris"));
+ EXPECT_EQ("I met nobody in Paris.", result);
+ // {person} is ignored if {num_people} is 0.
+ result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 0, "place", "Paris", "person", "Peter"));
+ EXPECT_EQ("I met nobody in Paris.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 1, "place", "Paris", "person", "Peter"));
+ EXPECT_EQ("I met Peter in Paris.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 2, "place", "Paris", "person", "Peter"));
+ EXPECT_EQ("I met Peter and one other person in Paris.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 13, "place", "Paris", "person", "Peter"));
+ EXPECT_EQ("I met Peter and a dozen other people in Paris.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs(
+ pattern, "num_people", 50, "place", "Paris", "person", "Peter"));
+ EXPECT_EQ("I met Peter and 49 other people in Paris.", result);
+}
+
+TEST_F(MessageFormatterTest, PluralNumberedArgs) {
+ const string16 pattern = ASCIIToUTF16(
+ "{1, plural, "
+ "=1 {The cert for {0} expired yesterday.}"
+ "=7 {The cert for {0} expired a week ago.}"
+ "other {The cert for {0} expired # days ago.}}");
+
+ std::string result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, "example.com", 1));
+ EXPECT_EQ("The cert for example.com expired yesterday.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, "example.com", 7));
+ EXPECT_EQ("The cert for example.com expired a week ago.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, "example.com", 15));
+ EXPECT_EQ("The cert for example.com expired 15 days ago.", result);
+}
+
+TEST_F(MessageFormatterTest, PluralNumberedArgsWithDate) {
+ const string16 pattern = ASCIIToUTF16(
+ "{1, plural, "
+ "=1 {The cert for {0} expired yesterday. Today is {2,date,full}}"
+ "other {The cert for {0} expired # days ago. Today is {2,date,full}}}");
+
+ base::Time now = base::Time::Now();
+ using icu::DateFormat;
+ std::unique_ptr<DateFormat> df(
+ DateFormat::createDateInstance(DateFormat::FULL));
+ std::string second_sentence = " Today is ";
+ AppendFormattedDateTime(df, now, &second_sentence);
+
+ std::string result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, "example.com", 1, now));
+ EXPECT_EQ("The cert for example.com expired yesterday." + second_sentence,
+ result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, "example.com", 15, now));
+ EXPECT_EQ("The cert for example.com expired 15 days ago." + second_sentence,
+ result);
+}
+
+TEST_F(MessageFormatterTest, DateTimeAndNumber) {
+ // Note that using 'mph' for all locales is not a good i18n practice.
+ const string16 pattern = ASCIIToUTF16(
+ "At {0,time, short} on {0,date, medium}, "
+ "there was {1} at building {2,number,integer}. "
+ "The speed of the wind was {3,number,###.#} mph.");
+
+ using icu::DateFormat;
+ std::unique_ptr<DateFormat> tf(
+ DateFormat::createTimeInstance(DateFormat::SHORT));
+ std::unique_ptr<DateFormat> df(
+ DateFormat::createDateInstance(DateFormat::MEDIUM));
+
+ base::Time now = base::Time::Now();
+ std::string expected = "At ";
+ AppendFormattedDateTime(tf, now, &expected);
+ expected.append(" on ");
+ AppendFormattedDateTime(df, now, &expected);
+ expected.append(", there was an explosion at building 3. "
+ "The speed of the wind was 37.4 mph.");
+
+ EXPECT_EQ(expected, UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, now, "an explosion", 3, 37.413)));
+}
+
+TEST_F(MessageFormatterTest, SelectorSingleOrMultiple) {
+ const string16 pattern = ASCIIToUTF16(
+ "{0, select,"
+ "single {Select a file to upload.}"
+ "multiple {Select files to upload.}"
+ "other {UNUSED}}");
+
+ std::string result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, "single"));
+ EXPECT_EQ("Select a file to upload.", result);
+ result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, "multiple"));
+ EXPECT_EQ("Select files to upload.", result);
+
+ // fallback if a parameter is not selectors specified in the message pattern.
+ result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs(
+ pattern, "foobar"));
+ EXPECT_EQ("UNUSED", result);
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/number_formatting.cc b/base/i18n/number_formatting.cc
new file mode 100644
index 0000000000..0ab031ecaf
--- /dev/null
+++ b/base/i18n/number_formatting.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/number_formatting.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/format_macros.h"
+#include "base/i18n/message_formatter.h"
+#include "base/i18n/unicodestring.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/icu/source/common/unicode/ustring.h"
+#include "third_party/icu/source/i18n/unicode/numfmt.h"
+
+namespace base {
+
+namespace {
+
+// A simple wrapper around icu::NumberFormat that allows for resetting it
+// (as LazyInstance does not).
+struct NumberFormatWrapper {
+ NumberFormatWrapper() {
+ Reset();
+ }
+
+ void Reset() {
+ // There's no ICU call to destroy a NumberFormat object other than
+ // operator delete, so use the default Delete, which calls operator delete.
+ // This can cause problems if a different allocator is used by this file
+ // than by ICU.
+ UErrorCode status = U_ZERO_ERROR;
+ number_format.reset(icu::NumberFormat::createInstance(status));
+ DCHECK(U_SUCCESS(status));
+ }
+
+ std::unique_ptr<icu::NumberFormat> number_format;
+};
+
+LazyInstance<NumberFormatWrapper>::DestructorAtExit g_number_format_int =
+ LAZY_INSTANCE_INITIALIZER;
+LazyInstance<NumberFormatWrapper>::DestructorAtExit g_number_format_float =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+string16 FormatNumber(int64_t number) {
+ icu::NumberFormat* number_format =
+ g_number_format_int.Get().number_format.get();
+
+ if (!number_format) {
+ // As a fallback, just return the raw number in a string.
+ return ASCIIToUTF16(StringPrintf("%" PRId64, number));
+ }
+ icu::UnicodeString ustr;
+ number_format->format(number, ustr);
+
+ return i18n::UnicodeStringToString16(ustr);
+}
+
+string16 FormatDouble(double number, int fractional_digits) {
+ icu::NumberFormat* number_format =
+ g_number_format_float.Get().number_format.get();
+
+ if (!number_format) {
+ // As a fallback, just return the raw number in a string.
+ return ASCIIToUTF16(StringPrintf("%f", number));
+ }
+ number_format->setMaximumFractionDigits(fractional_digits);
+ number_format->setMinimumFractionDigits(fractional_digits);
+ icu::UnicodeString ustr;
+ number_format->format(number, ustr);
+
+ return i18n::UnicodeStringToString16(ustr);
+}
+
+string16 FormatPercent(int number) {
+ return i18n::MessageFormatter::FormatWithNumberedArgs(
+ ASCIIToUTF16("{0,number,percent}"), static_cast<double>(number) / 100.0);
+}
+
+namespace testing {
+
+void ResetFormatters() {
+ g_number_format_int.Get().Reset();
+ g_number_format_float.Get().Reset();
+}
+
+} // namespace testing
+
+} // namespace base
diff --git a/base/i18n/number_formatting.h b/base/i18n/number_formatting.h
new file mode 100644
index 0000000000..9636bf4d1b
--- /dev/null
+++ b/base/i18n/number_formatting.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_NUMBER_FORMATTING_H_
+#define BASE_I18N_NUMBER_FORMATTING_H_
+
+#include <stdint.h>
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/strings/string16.h"
+
+namespace base {
+
+// Return a number formatted with separators in the user's locale.
+// Ex: FormatNumber(1234567) => "1,234,567" in English, "1.234.567" in German
+BASE_I18N_EXPORT string16 FormatNumber(int64_t number);
+
+// Return a number formatted with separators in the user's locale.
+// Ex: FormatDouble(1234567.8, 1)
+// => "1,234,567.8" in English, "1.234.567,8" in German
+BASE_I18N_EXPORT string16 FormatDouble(double number, int fractional_digits);
+
+// Return a percentage formatted with space and symbol in the user's locale.
+// Ex: FormatPercent(12) => "12%" in English, "12 %" in Romanian
+BASE_I18N_EXPORT string16 FormatPercent(int number);
+
+namespace testing {
+
+// Causes cached formatters to be discarded and recreated. Only useful for
+// testing.
+BASE_I18N_EXPORT void ResetFormatters();
+
+} // namespace testing
+
+} // namespace base
+
+#endif // BASE_I18N_NUMBER_FORMATTING_H_
diff --git a/base/i18n/number_formatting_unittest.cc b/base/i18n/number_formatting_unittest.cc
new file mode 100644
index 0000000000..d2eb568796
--- /dev/null
+++ b/base/i18n/number_formatting_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/i18n/number_formatting.h"
+#include "base/i18n/rtl.h"
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/icu_test_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/i18n/unicode/usearch.h"
+
+namespace base {
+namespace {
+
+TEST(NumberFormattingTest, FormatNumber) {
+ static const struct {
+ int64_t number;
+ const char* expected_english;
+ const char* expected_german;
+ } cases[] = {
+ {0, "0", "0"},
+ {1024, "1,024", "1.024"},
+ {std::numeric_limits<int64_t>::max(),
+ "9,223,372,036,854,775,807", "9.223.372.036.854.775.807"},
+ {std::numeric_limits<int64_t>::min(),
+ "-9,223,372,036,854,775,808", "-9.223.372.036.854.775.808"},
+ {-42, "-42", "-42"},
+ };
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ i18n::SetICUDefaultLocale("en");
+ testing::ResetFormatters();
+ EXPECT_EQ(cases[i].expected_english,
+ UTF16ToUTF8(FormatNumber(cases[i].number)));
+ i18n::SetICUDefaultLocale("de");
+ testing::ResetFormatters();
+ EXPECT_EQ(cases[i].expected_german,
+ UTF16ToUTF8(FormatNumber(cases[i].number)));
+ }
+}
+
+TEST(NumberFormattingTest, FormatDouble) {
+ static const struct {
+ double number;
+ int frac_digits;
+ const char* expected_english;
+ const char* expected_german;
+ } cases[] = {
+ {0.0, 0, "0", "0"},
+#if !defined(OS_ANDROID)
+ // Bionic can't printf negative zero correctly.
+ {-0.0, 4, "-0.0000", "-0,0000"},
+#endif
+ {1024.2, 0, "1,024", "1.024"},
+ {-1024.223, 2, "-1,024.22", "-1.024,22"},
+ {std::numeric_limits<double>::max(), 6,
+ "179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,"
+ "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+ "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+ "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+ "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+ "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+ "000.000000",
+ "179.769.313.486.231.570.000.000.000.000.000.000.000.000.000.000.000."
+ "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+ "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+ "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+ "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+ "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+ "000,000000"},
+ {std::numeric_limits<double>::min(), 2, "0.00", "0,00"},
+ {-42.7, 3, "-42.700", "-42,700"},
+ };
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ i18n::SetICUDefaultLocale("en");
+ testing::ResetFormatters();
+ EXPECT_EQ(cases[i].expected_english,
+ UTF16ToUTF8(FormatDouble(cases[i].number, cases[i].frac_digits)));
+ i18n::SetICUDefaultLocale("de");
+ testing::ResetFormatters();
+ EXPECT_EQ(cases[i].expected_german,
+ UTF16ToUTF8(FormatDouble(cases[i].number, cases[i].frac_digits)));
+ }
+}
+
+TEST(NumberFormattingTest, FormatPercent) {
+ static const struct {
+ int64_t number;
+ const char* expected_english;
+ const char* expected_german; // Note: Space before % isn't \x20.
+ // Note: Eastern Arabic-Indic digits (U+06Fx) for Persian and
+ // Arabic-Indic digits (U+066x) for Arabic in Egypt(ar-EG). In Arabic (ar),
+ // uses European digits (Google-patch).
+ // See https://unicode.org/cldr/trac/ticket/9040 for details.
+ // See also https://unicode.org/cldr/trac/ticket/10176 .
+ // For now, take what CLDR 32 has (percent sign to the right of
+ // a number in Persian).
+ const char* expected_persian;
+ const char* expected_arabic;
+ const char* expected_arabic_egypt;
+ } cases[] = {
+ {0, "0%", u8"0\u00a0%", u8"\u06f0\u066a", u8"0\u200e%\u200e",
+ u8"\u0660\u066a\u061c"},
+ {42, "42%", "42\u00a0%", u8"\u06f4\u06f2\u066a", u8"42\u200e%\u200e",
+ "\u0664\u0662\u066a\u061c"},
+ {1024, "1,024%", "1.024\u00a0%", u8"\u06f1\u066c\u06f0\u06f2\u06f4\u066a",
+ "1,024\u200e%\u200e", "\u0661\u066c\u0660\u0662\u0664\u066a\u061c"},
+ };
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ i18n::SetICUDefaultLocale("en");
+ EXPECT_EQ(ASCIIToUTF16(cases[i].expected_english),
+ FormatPercent(cases[i].number));
+ i18n::SetICUDefaultLocale("de");
+ EXPECT_EQ(UTF8ToUTF16(cases[i].expected_german),
+ FormatPercent(cases[i].number));
+ i18n::SetICUDefaultLocale("fa");
+ EXPECT_EQ(UTF8ToUTF16(cases[i].expected_persian),
+ FormatPercent(cases[i].number));
+ i18n::SetICUDefaultLocale("ar");
+ EXPECT_EQ(UTF8ToUTF16(cases[i].expected_arabic),
+ FormatPercent(cases[i].number));
+ i18n::SetICUDefaultLocale("ar-EG");
+ EXPECT_EQ(UTF8ToUTF16(cases[i].expected_arabic_egypt),
+ FormatPercent(cases[i].number));
+ }
+}
+
+} // namespace
+} // namespace base
diff --git a/base/i18n/rtl.cc b/base/i18n/rtl.cc
new file mode 100644
index 0000000000..5a8db8ae1c
--- /dev/null
+++ b/base/i18n/rtl.cc
@@ -0,0 +1,496 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/rtl.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/i18n/base_i18n_switches.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "third_party/icu/source/common/unicode/locid.h"
+#include "third_party/icu/source/common/unicode/uchar.h"
+#include "third_party/icu/source/common/unicode/uscript.h"
+#include "third_party/icu/source/i18n/unicode/coll.h"
+
+#if defined(OS_IOS)
+#include "base/debug/crash_logging.h"
+#include "base/ios/ios_util.h"
+#endif
+
+namespace {
+
+// Extract language, country and variant, but ignore keywords. For example,
+// en-US, ca@valencia, ca-ES@valencia.
+std::string GetLocaleString(const icu::Locale& locale) {
+ const char* language = locale.getLanguage();
+ const char* country = locale.getCountry();
+ const char* variant = locale.getVariant();
+
+ std::string result =
+ (language != nullptr && *language != '\0') ? language : "und";
+
+ if (country != nullptr && *country != '\0') {
+ result += '-';
+ result += country;
+ }
+
+ if (variant != nullptr && *variant != '\0')
+ result += '@' + base::ToLowerASCII(variant);
+
+ return result;
+}
+
+// Returns LEFT_TO_RIGHT or RIGHT_TO_LEFT if |character| has strong
+// directionality, returns UNKNOWN_DIRECTION if it doesn't. Please refer to
+// http://unicode.org/reports/tr9/ for more information.
+base::i18n::TextDirection GetCharacterDirection(UChar32 character) {
+ static bool has_switch = base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kForceTextDirection);
+ if (has_switch) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ std::string force_flag =
+ command_line->GetSwitchValueASCII(switches::kForceTextDirection);
+
+ if (force_flag == switches::kForceDirectionRTL)
+ return base::i18n::RIGHT_TO_LEFT;
+ if (force_flag == switches::kForceDirectionLTR)
+ return base::i18n::LEFT_TO_RIGHT;
+ }
+ // Now that we have the character, we use ICU in order to query for the
+ // appropriate Unicode BiDi character type.
+ int32_t property = u_getIntPropertyValue(character, UCHAR_BIDI_CLASS);
+ if ((property == U_RIGHT_TO_LEFT) ||
+ (property == U_RIGHT_TO_LEFT_ARABIC) ||
+ (property == U_RIGHT_TO_LEFT_EMBEDDING) ||
+ (property == U_RIGHT_TO_LEFT_OVERRIDE)) {
+ return base::i18n::RIGHT_TO_LEFT;
+ } else if ((property == U_LEFT_TO_RIGHT) ||
+ (property == U_LEFT_TO_RIGHT_EMBEDDING) ||
+ (property == U_LEFT_TO_RIGHT_OVERRIDE)) {
+ return base::i18n::LEFT_TO_RIGHT;
+ }
+ return base::i18n::UNKNOWN_DIRECTION;
+}
+
+} // namespace
+
+namespace base {
+namespace i18n {
+
+// Represents the locale-specific ICU text direction.
+static TextDirection g_icu_text_direction = UNKNOWN_DIRECTION;
+
+// Convert the ICU default locale to a string.
+std::string GetConfiguredLocale() {
+ return GetLocaleString(icu::Locale::getDefault());
+}
+
+// Convert the ICU canonicalized locale to a string.
+std::string GetCanonicalLocale(const std::string& locale) {
+ return GetLocaleString(icu::Locale::createCanonical(locale.c_str()));
+}
+
+// Convert Chrome locale name to ICU locale name
+std::string ICULocaleName(const std::string& locale_string) {
+ // If not Spanish, just return it.
+ if (locale_string.substr(0, 2) != "es")
+ return locale_string;
+ // Expand es to es-ES.
+ if (LowerCaseEqualsASCII(locale_string, "es"))
+ return "es-ES";
+ // Map es-419 (Latin American Spanish) to es-FOO depending on the system
+ // locale. If it's es-RR other than es-ES, map to es-RR. Otherwise, map
+ // to es-MX (the most populous in Spanish-speaking Latin America).
+ if (LowerCaseEqualsASCII(locale_string, "es-419")) {
+ const icu::Locale& locale = icu::Locale::getDefault();
+ std::string language = locale.getLanguage();
+ const char* country = locale.getCountry();
+ if (LowerCaseEqualsASCII(language, "es") &&
+ !LowerCaseEqualsASCII(country, "es")) {
+ language += '-';
+ language += country;
+ return language;
+ }
+ return "es-MX";
+ }
+ // Currently, Chrome has only "es" and "es-419", but later we may have
+ // more specific "es-RR".
+ return locale_string;
+}
+
+void SetICUDefaultLocale(const std::string& locale_string) {
+#if defined(OS_IOS)
+ static base::debug::CrashKeyString* crash_key_locale =
+ base::debug::AllocateCrashKeyString("icu_locale_input",
+ base::debug::CrashKeySize::Size256);
+ base::debug::SetCrashKeyString(crash_key_locale, locale_string);
+#endif
+ icu::Locale locale(ICULocaleName(locale_string).c_str());
+ UErrorCode error_code = U_ZERO_ERROR;
+ const char* lang = locale.getLanguage();
+ if (lang != nullptr && *lang != '\0') {
+ icu::Locale::setDefault(locale, error_code);
+ } else {
+ LOG(ERROR) << "Failed to set the ICU default locale to " << locale_string
+ << ". Falling back to en-US.";
+ icu::Locale::setDefault(icu::Locale::getUS(), error_code);
+ }
+ g_icu_text_direction = UNKNOWN_DIRECTION;
+}
+
+bool IsRTL() {
+ return ICUIsRTL();
+}
+
+void SetRTLForTesting(bool rtl) {
+ SetICUDefaultLocale(rtl ? "he" : "en");
+ DCHECK_EQ(rtl, IsRTL());
+}
+
+bool ICUIsRTL() {
+ if (g_icu_text_direction == UNKNOWN_DIRECTION) {
+ const icu::Locale& locale = icu::Locale::getDefault();
+ g_icu_text_direction = GetTextDirectionForLocaleInStartUp(locale.getName());
+ }
+ return g_icu_text_direction == RIGHT_TO_LEFT;
+}
+
+TextDirection GetForcedTextDirection() {
+// On iOS, check for RTL forcing.
+#if defined(OS_IOS)
+ if (base::ios::IsInForcedRTL())
+ return base::i18n::RIGHT_TO_LEFT;
+#endif
+
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kForceUIDirection)) {
+ std::string force_flag =
+ command_line->GetSwitchValueASCII(switches::kForceUIDirection);
+
+ if (force_flag == switches::kForceDirectionLTR)
+ return base::i18n::LEFT_TO_RIGHT;
+
+ if (force_flag == switches::kForceDirectionRTL)
+ return base::i18n::RIGHT_TO_LEFT;
+ }
+
+ return base::i18n::UNKNOWN_DIRECTION;
+}
+
+TextDirection GetTextDirectionForLocaleInStartUp(const char* locale_name) {
+ // Check for direction forcing.
+ TextDirection forced_direction = GetForcedTextDirection();
+ if (forced_direction != UNKNOWN_DIRECTION)
+ return forced_direction;
+
+ // This list needs to be updated in alphabetical order if we add more RTL
+ // locales.
+ static const char kRTLLanguageCodes[][3] = {"ar", "fa", "he", "iw", "ur"};
+ std::vector<StringPiece> locale_split =
+ SplitStringPiece(locale_name, "-_", KEEP_WHITESPACE, SPLIT_WANT_ALL);
+ const StringPiece& language_code = locale_split[0];
+ if (std::binary_search(kRTLLanguageCodes,
+ kRTLLanguageCodes + arraysize(kRTLLanguageCodes),
+ language_code))
+ return RIGHT_TO_LEFT;
+ return LEFT_TO_RIGHT;
+}
+
+TextDirection GetTextDirectionForLocale(const char* locale_name) {
+ // Check for direction forcing.
+ TextDirection forced_direction = GetForcedTextDirection();
+ if (forced_direction != UNKNOWN_DIRECTION)
+ return forced_direction;
+
+ UErrorCode status = U_ZERO_ERROR;
+ ULayoutType layout_dir = uloc_getCharacterOrientation(locale_name, &status);
+ DCHECK(U_SUCCESS(status));
+ // Treat anything other than RTL as LTR.
+ return (layout_dir != ULOC_LAYOUT_RTL) ? LEFT_TO_RIGHT : RIGHT_TO_LEFT;
+}
+
+TextDirection GetFirstStrongCharacterDirection(const string16& text) {
+ const UChar* string = text.c_str();
+ size_t length = text.length();
+ size_t position = 0;
+ while (position < length) {
+ UChar32 character;
+ size_t next_position = position;
+ U16_NEXT(string, next_position, length, character);
+ TextDirection direction = GetCharacterDirection(character);
+ if (direction != UNKNOWN_DIRECTION)
+ return direction;
+ position = next_position;
+ }
+ return LEFT_TO_RIGHT;
+}
+
+TextDirection GetLastStrongCharacterDirection(const string16& text) {
+ const UChar* string = text.c_str();
+ size_t position = text.length();
+ while (position > 0) {
+ UChar32 character;
+ size_t prev_position = position;
+ U16_PREV(string, 0, prev_position, character);
+ TextDirection direction = GetCharacterDirection(character);
+ if (direction != UNKNOWN_DIRECTION)
+ return direction;
+ position = prev_position;
+ }
+ return LEFT_TO_RIGHT;
+}
+
+TextDirection GetStringDirection(const string16& text) {
+ const UChar* string = text.c_str();
+ size_t length = text.length();
+ size_t position = 0;
+
+ TextDirection result(UNKNOWN_DIRECTION);
+ while (position < length) {
+ UChar32 character;
+ size_t next_position = position;
+ U16_NEXT(string, next_position, length, character);
+ TextDirection direction = GetCharacterDirection(character);
+ if (direction != UNKNOWN_DIRECTION) {
+ if (result != UNKNOWN_DIRECTION && result != direction)
+ return UNKNOWN_DIRECTION;
+ result = direction;
+ }
+ position = next_position;
+ }
+
+ // Handle the case of a string not containing any strong directionality
+ // characters defaulting to LEFT_TO_RIGHT.
+ if (result == UNKNOWN_DIRECTION)
+ return LEFT_TO_RIGHT;
+
+ return result;
+}
+
+#if defined(OS_WIN)
+bool AdjustStringForLocaleDirection(string16* text) {
+ if (!IsRTL() || text->empty())
+ return false;
+
+ // Marking the string as LTR if the locale is RTL and the string does not
+ // contain strong RTL characters. Otherwise, mark the string as RTL.
+ bool has_rtl_chars = StringContainsStrongRTLChars(*text);
+ if (!has_rtl_chars)
+ WrapStringWithLTRFormatting(text);
+ else
+ WrapStringWithRTLFormatting(text);
+
+ return true;
+}
+
+bool UnadjustStringForLocaleDirection(string16* text) {
+ if (!IsRTL() || text->empty())
+ return false;
+
+ *text = StripWrappingBidiControlCharacters(*text);
+ return true;
+}
+#else
+bool AdjustStringForLocaleDirection(string16* text) {
+ // On OS X & GTK the directionality of a label is determined by the first
+ // strongly directional character.
+ // However, we want to make sure that in an LTR-language-UI all strings are
+ // left aligned and vice versa.
+ // A problem can arise if we display a string which starts with user input.
+ // User input may be of the opposite directionality to the UI. So the whole
+ // string will be displayed in the opposite directionality, e.g. if we want to
+ // display in an LTR UI [such as US English]:
+ //
+ // EMAN_NOISNETXE is now installed.
+ //
+ // Since EXTENSION_NAME begins with a strong RTL char, the label's
+ // directionality will be set to RTL and the string will be displayed visually
+ // as:
+ //
+ // .is now installed EMAN_NOISNETXE
+ //
+ // In order to solve this issue, we prepend an LRM to the string. An LRM is a
+ // strongly directional LTR char.
+ // We also append an LRM at the end, which ensures that we're in an LTR
+ // context.
+
+ // Unlike Windows, Linux and OS X can correctly display RTL glyphs out of the
+ // box so there is no issue with displaying zero-width bidi control characters
+ // on any system. Thus no need for the !IsRTL() check here.
+ if (text->empty())
+ return false;
+
+ bool ui_direction_is_rtl = IsRTL();
+
+ bool has_rtl_chars = StringContainsStrongRTLChars(*text);
+ if (!ui_direction_is_rtl && has_rtl_chars) {
+ WrapStringWithRTLFormatting(text);
+ text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
+ kLeftToRightMark);
+ text->push_back(kLeftToRightMark);
+ } else if (ui_direction_is_rtl && has_rtl_chars) {
+ WrapStringWithRTLFormatting(text);
+ text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
+ kRightToLeftMark);
+ text->push_back(kRightToLeftMark);
+ } else if (ui_direction_is_rtl) {
+ WrapStringWithLTRFormatting(text);
+ text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
+ kRightToLeftMark);
+ text->push_back(kRightToLeftMark);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool UnadjustStringForLocaleDirection(string16* text) {
+ if (text->empty())
+ return false;
+
+ size_t begin_index = 0;
+ char16 begin = text->at(begin_index);
+ if (begin == kLeftToRightMark ||
+ begin == kRightToLeftMark) {
+ ++begin_index;
+ }
+
+ size_t end_index = text->length() - 1;
+ char16 end = text->at(end_index);
+ if (end == kLeftToRightMark ||
+ end == kRightToLeftMark) {
+ --end_index;
+ }
+
+ string16 unmarked_text =
+ text->substr(begin_index, end_index - begin_index + 1);
+ *text = StripWrappingBidiControlCharacters(unmarked_text);
+ return true;
+}
+
+#endif // !OS_WIN
+
+void EnsureTerminatedDirectionalFormatting(string16* text) {
+ int count = 0;
+ for (auto c : *text) {
+ if (c == kLeftToRightEmbeddingMark || c == kRightToLeftEmbeddingMark ||
+ c == kLeftToRightOverride || c == kRightToLeftOverride) {
+ ++count;
+ } else if (c == kPopDirectionalFormatting && count > 0) {
+ --count;
+ }
+ }
+ for (int j = 0; j < count; j++)
+ text->push_back(kPopDirectionalFormatting);
+}
+
+void SanitizeUserSuppliedString(string16* text) {
+ EnsureTerminatedDirectionalFormatting(text);
+ AdjustStringForLocaleDirection(text);
+}
+
+bool StringContainsStrongRTLChars(const string16& text) {
+ const UChar* string = text.c_str();
+ size_t length = text.length();
+ size_t position = 0;
+ while (position < length) {
+ UChar32 character;
+ size_t next_position = position;
+ U16_NEXT(string, next_position, length, character);
+
+ // Now that we have the character, we use ICU in order to query for the
+ // appropriate Unicode BiDi character type.
+ int32_t property = u_getIntPropertyValue(character, UCHAR_BIDI_CLASS);
+ if ((property == U_RIGHT_TO_LEFT) || (property == U_RIGHT_TO_LEFT_ARABIC))
+ return true;
+
+ position = next_position;
+ }
+
+ return false;
+}
+
+void WrapStringWithLTRFormatting(string16* text) {
+ if (text->empty())
+ return;
+
+ // Inserting an LRE (Left-To-Right Embedding) mark as the first character.
+ text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
+ kLeftToRightEmbeddingMark);
+
+ // Inserting a PDF (Pop Directional Formatting) mark as the last character.
+ text->push_back(kPopDirectionalFormatting);
+}
+
+void WrapStringWithRTLFormatting(string16* text) {
+ if (text->empty())
+ return;
+
+ // Inserting an RLE (Right-To-Left Embedding) mark as the first character.
+ text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
+ kRightToLeftEmbeddingMark);
+
+ // Inserting a PDF (Pop Directional Formatting) mark as the last character.
+ text->push_back(kPopDirectionalFormatting);
+}
+
+void WrapPathWithLTRFormatting(const FilePath& path,
+ string16* rtl_safe_path) {
+ // Wrap the overall path with LRE-PDF pair which essentialy marks the
+ // string as a Left-To-Right string.
+ // Inserting an LRE (Left-To-Right Embedding) mark as the first character.
+ rtl_safe_path->push_back(kLeftToRightEmbeddingMark);
+#if defined(OS_MACOSX)
+ rtl_safe_path->append(UTF8ToUTF16(path.value()));
+#elif defined(OS_WIN)
+ rtl_safe_path->append(path.value());
+#else // defined(OS_POSIX) && !defined(OS_MACOSX)
+ std::wstring wide_path = base::SysNativeMBToWide(path.value());
+ rtl_safe_path->append(WideToUTF16(wide_path));
+#endif
+ // Inserting a PDF (Pop Directional Formatting) mark as the last character.
+ rtl_safe_path->push_back(kPopDirectionalFormatting);
+}
+
+string16 GetDisplayStringInLTRDirectionality(const string16& text) {
+ // Always wrap the string in RTL UI (it may be appended to RTL string).
+ // Also wrap strings with an RTL first strong character direction in LTR UI.
+ if (IsRTL() || GetFirstStrongCharacterDirection(text) == RIGHT_TO_LEFT) {
+ string16 text_mutable(text);
+ WrapStringWithLTRFormatting(&text_mutable);
+ return text_mutable;
+ }
+ return text;
+}
+
+string16 StripWrappingBidiControlCharacters(const string16& text) {
+ if (text.empty())
+ return text;
+ size_t begin_index = 0;
+ char16 begin = text[begin_index];
+ if (begin == kLeftToRightEmbeddingMark ||
+ begin == kRightToLeftEmbeddingMark ||
+ begin == kLeftToRightOverride ||
+ begin == kRightToLeftOverride)
+ ++begin_index;
+ size_t end_index = text.length() - 1;
+ if (text[end_index] == kPopDirectionalFormatting)
+ --end_index;
+ return text.substr(begin_index, end_index - begin_index + 1);
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/rtl_unittest.cc b/base/i18n/rtl_unittest.cc
new file mode 100644
index 0000000000..fbdd1a10ab
--- /dev/null
+++ b/base/i18n/rtl_unittest.cc
@@ -0,0 +1,556 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/rtl.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/icu_test_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#include "third_party/icu/source/common/unicode/locid.h"
+#include "third_party/icu/source/i18n/unicode/usearch.h"
+
+namespace base {
+namespace i18n {
+
+class RTLTest : public PlatformTest {
+};
+
+TEST_F(RTLTest, GetFirstStrongCharacterDirection) {
+ struct {
+ const wchar_t* text;
+ TextDirection direction;
+ } cases[] = {
+ // Test pure LTR string.
+ { L"foo bar", LEFT_TO_RIGHT },
+ // Test pure RTL string.
+ { L"\x05d0\x05d1\x05d2 \x05d3\x0d4\x05d5", RIGHT_TO_LEFT},
+ // Test bidi string in which the first character with strong directionality
+ // is a character with type L.
+ { L"foo \x05d0 bar", LEFT_TO_RIGHT },
+ // Test bidi string in which the first character with strong directionality
+ // is a character with type R.
+ { L"\x05d0 foo bar", RIGHT_TO_LEFT },
+ // Test bidi string which starts with a character with weak directionality
+ // and in which the first character with strong directionality is a
+ // character with type L.
+ { L"!foo \x05d0 bar", LEFT_TO_RIGHT },
+ // Test bidi string which starts with a character with weak directionality
+ // and in which the first character with strong directionality is a
+ // character with type R.
+ { L",\x05d0 foo bar", RIGHT_TO_LEFT },
+ // Test bidi string in which the first character with strong directionality
+ // is a character with type LRE.
+ { L"\x202a \x05d0 foo bar", LEFT_TO_RIGHT },
+ // Test bidi string in which the first character with strong directionality
+ // is a character with type LRO.
+ { L"\x202d \x05d0 foo bar", LEFT_TO_RIGHT },
+ // Test bidi string in which the first character with strong directionality
+ // is a character with type RLE.
+ { L"\x202b foo \x05d0 bar", RIGHT_TO_LEFT },
+ // Test bidi string in which the first character with strong directionality
+ // is a character with type RLO.
+ { L"\x202e foo \x05d0 bar", RIGHT_TO_LEFT },
+ // Test bidi string in which the first character with strong directionality
+ // is a character with type AL.
+ { L"\x0622 foo \x05d0 bar", RIGHT_TO_LEFT },
+ // Test a string without strong directionality characters.
+ { L",!.{}", LEFT_TO_RIGHT },
+ // Test empty string.
+ { L"", LEFT_TO_RIGHT },
+ // Test characters in non-BMP (e.g. Phoenician letters. Please refer to
+ // http://demo.icu-project.org/icu-bin/ubrowse?scr=151&b=10910 for more
+ // information).
+ {
+#if defined(WCHAR_T_IS_UTF32)
+ L" ! \x10910" L"abc 123",
+#elif defined(WCHAR_T_IS_UTF16)
+ L" ! \xd802\xdd10" L"abc 123",
+#else
+#error wchar_t should be either UTF-16 or UTF-32
+#endif
+ RIGHT_TO_LEFT },
+ {
+#if defined(WCHAR_T_IS_UTF32)
+ L" ! \x10401" L"abc 123",
+#elif defined(WCHAR_T_IS_UTF16)
+ L" ! \xd801\xdc01" L"abc 123",
+#else
+#error wchar_t should be either UTF-16 or UTF-32
+#endif
+ LEFT_TO_RIGHT },
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i)
+ EXPECT_EQ(cases[i].direction,
+ GetFirstStrongCharacterDirection(WideToUTF16(cases[i].text)));
+}
+
+
+// Note that the cases with LRE, LRO, RLE and RLO are invalid for
+// GetLastStrongCharacterDirection because they should be followed by PDF
+// character.
+TEST_F(RTLTest, GetLastStrongCharacterDirection) {
+ struct {
+ const wchar_t* text;
+ TextDirection direction;
+ } cases[] = {
+ // Test pure LTR string.
+ { L"foo bar", LEFT_TO_RIGHT },
+ // Test pure RTL string.
+ { L"\x05d0\x05d1\x05d2 \x05d3\x0d4\x05d5", RIGHT_TO_LEFT},
+ // Test bidi string in which the last character with strong directionality
+ // is a character with type L.
+ { L"foo \x05d0 bar", LEFT_TO_RIGHT },
+ // Test bidi string in which the last character with strong directionality
+ // is a character with type R.
+ { L"\x05d0 foo bar \x05d3", RIGHT_TO_LEFT },
+ // Test bidi string which ends with a character with weak directionality
+ // and in which the last character with strong directionality is a
+ // character with type L.
+ { L"!foo \x05d0 bar!", LEFT_TO_RIGHT },
+ // Test bidi string which ends with a character with weak directionality
+ // and in which the last character with strong directionality is a
+ // character with type R.
+ { L",\x05d0 foo bar \x05d1,", RIGHT_TO_LEFT },
+ // Test bidi string in which the last character with strong directionality
+ // is a character with type AL.
+ { L"\x0622 foo \x05d0 bar \x0622", RIGHT_TO_LEFT },
+ // Test a string without strong directionality characters.
+ { L",!.{}", LEFT_TO_RIGHT },
+ // Test empty string.
+ { L"", LEFT_TO_RIGHT },
+ // Test characters in non-BMP (e.g. Phoenician letters. Please refer to
+ // http://demo.icu-project.org/icu-bin/ubrowse?scr=151&b=10910 for more
+ // information).
+ {
+#if defined(WCHAR_T_IS_UTF32)
+ L"abc 123" L" ! \x10910 !",
+#elif defined(WCHAR_T_IS_UTF16)
+ L"abc 123" L" ! \xd802\xdd10 !",
+#else
+#error wchar_t should be either UTF-16 or UTF-32
+#endif
+ RIGHT_TO_LEFT },
+ {
+#if defined(WCHAR_T_IS_UTF32)
+ L"abc 123" L" ! \x10401 !",
+#elif defined(WCHAR_T_IS_UTF16)
+ L"abc 123" L" ! \xd801\xdc01 !",
+#else
+#error wchar_t should be either UTF-16 or UTF-32
+#endif
+ LEFT_TO_RIGHT },
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i)
+ EXPECT_EQ(cases[i].direction,
+ GetLastStrongCharacterDirection(WideToUTF16(cases[i].text)));
+}
+
+TEST_F(RTLTest, GetStringDirection) {
+ struct {
+ const wchar_t* text;
+ TextDirection direction;
+ } cases[] = {
+ // Test pure LTR string.
+ { L"foobar", LEFT_TO_RIGHT },
+ { L".foobar", LEFT_TO_RIGHT },
+ { L"foo, bar", LEFT_TO_RIGHT },
+ // Test pure LTR with strong directionality characters of type LRE.
+ { L"\x202a\x202a", LEFT_TO_RIGHT },
+ { L".\x202a\x202a", LEFT_TO_RIGHT },
+ { L"\x202a, \x202a", LEFT_TO_RIGHT },
+ // Test pure LTR with strong directionality characters of type LRO.
+ { L"\x202d\x202d", LEFT_TO_RIGHT },
+ { L".\x202d\x202d", LEFT_TO_RIGHT },
+ { L"\x202d, \x202d", LEFT_TO_RIGHT },
+ // Test pure LTR with various types of strong directionality characters.
+ { L"foo \x202a\x202d", LEFT_TO_RIGHT },
+ { L".\x202d foo \x202a", LEFT_TO_RIGHT },
+ { L"\x202a, \x202d foo", LEFT_TO_RIGHT },
+ // Test pure RTL with strong directionality characters of type R.
+ { L"\x05d0\x05d0", RIGHT_TO_LEFT },
+ { L".\x05d0\x05d0", RIGHT_TO_LEFT },
+ { L"\x05d0, \x05d0", RIGHT_TO_LEFT },
+ // Test pure RTL with strong directionality characters of type RLE.
+ { L"\x202b\x202b", RIGHT_TO_LEFT },
+ { L".\x202b\x202b", RIGHT_TO_LEFT },
+ { L"\x202b, \x202b", RIGHT_TO_LEFT },
+ // Test pure RTL with strong directionality characters of type RLO.
+ { L"\x202e\x202e", RIGHT_TO_LEFT },
+ { L".\x202e\x202e", RIGHT_TO_LEFT },
+ { L"\x202e, \x202e", RIGHT_TO_LEFT },
+ // Test pure RTL with strong directionality characters of type AL.
+ { L"\x0622\x0622", RIGHT_TO_LEFT },
+ { L".\x0622\x0622", RIGHT_TO_LEFT },
+ { L"\x0622, \x0622", RIGHT_TO_LEFT },
+ // Test pure RTL with various types of strong directionality characters.
+ { L"\x05d0\x202b\x202e\x0622", RIGHT_TO_LEFT },
+ { L".\x202b\x202e\x0622\x05d0", RIGHT_TO_LEFT },
+ { L"\x0622\x202e, \x202b\x05d0", RIGHT_TO_LEFT },
+ // Test bidi strings.
+ { L"foo \x05d0 bar", UNKNOWN_DIRECTION },
+ { L"\x202b foo bar", UNKNOWN_DIRECTION },
+ { L"!foo \x0622 bar", UNKNOWN_DIRECTION },
+ { L"\x202a\x202b", UNKNOWN_DIRECTION },
+ { L"\x202e\x202d", UNKNOWN_DIRECTION },
+ { L"\x0622\x202a", UNKNOWN_DIRECTION },
+ { L"\x202d\x05d0", UNKNOWN_DIRECTION },
+ // Test a string without strong directionality characters.
+ { L",!.{}", LEFT_TO_RIGHT },
+ // Test empty string.
+ { L"", LEFT_TO_RIGHT },
+ {
+#if defined(WCHAR_T_IS_UTF32)
+ L" ! \x10910" L"abc 123",
+#elif defined(WCHAR_T_IS_UTF16)
+ L" ! \xd802\xdd10" L"abc 123",
+#else
+#error wchar_t should be either UTF-16 or UTF-32
+#endif
+ UNKNOWN_DIRECTION },
+ {
+#if defined(WCHAR_T_IS_UTF32)
+ L" ! \x10401" L"abc 123",
+#elif defined(WCHAR_T_IS_UTF16)
+ L" ! \xd801\xdc01" L"abc 123",
+#else
+#error wchar_t should be either UTF-16 or UTF-32
+#endif
+ LEFT_TO_RIGHT },
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i)
+ EXPECT_EQ(cases[i].direction,
+ GetStringDirection(WideToUTF16(cases[i].text)));
+}
+
+TEST_F(RTLTest, WrapPathWithLTRFormatting) {
+ const wchar_t* cases[] = {
+ // Test common path, such as "c:\foo\bar".
+ L"c:/foo/bar",
+ // Test path with file name, such as "c:\foo\bar\test.jpg".
+ L"c:/foo/bar/test.jpg",
+ // Test path ending with punctuation, such as "c:\(foo)\bar.".
+ L"c:/(foo)/bar.",
+ // Test path ending with separator, such as "c:\foo\bar\".
+ L"c:/foo/bar/",
+ // Test path with RTL character.
+ L"c:/\x05d0",
+ // Test path with 2 level RTL directory names.
+ L"c:/\x05d0/\x0622",
+ // Test path with mixed RTL/LTR directory names and ending with punctuation.
+ L"c:/\x05d0/\x0622/(foo)/b.a.r.",
+ // Test path without driver name, such as "/foo/bar/test/jpg".
+ L"/foo/bar/test.jpg",
+ // Test path start with current directory, such as "./foo".
+ L"./foo",
+ // Test path start with parent directory, such as "../foo/bar.jpg".
+ L"../foo/bar.jpg",
+ // Test absolute path, such as "//foo/bar.jpg".
+ L"//foo/bar.jpg",
+ // Test path with mixed RTL/LTR directory names.
+ L"c:/foo/\x05d0/\x0622/\x05d1.jpg",
+ // Test empty path.
+ L""
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ FilePath path;
+#if defined(OS_WIN)
+ std::wstring win_path(cases[i]);
+ std::replace(win_path.begin(), win_path.end(), '/', '\\');
+ path = FilePath(win_path);
+ std::wstring wrapped_expected =
+ std::wstring(L"\x202a") + win_path + L"\x202c";
+#else
+ path = FilePath(base::SysWideToNativeMB(cases[i]));
+ std::wstring wrapped_expected =
+ std::wstring(L"\x202a") + cases[i] + L"\x202c";
+#endif
+ string16 localized_file_path_string;
+ WrapPathWithLTRFormatting(path, &localized_file_path_string);
+
+ std::wstring wrapped_actual = UTF16ToWide(localized_file_path_string);
+ EXPECT_EQ(wrapped_expected, wrapped_actual);
+ }
+}
+
+TEST_F(RTLTest, WrapString) {
+ const wchar_t* cases[] = {
+ L" . ",
+ L"abc",
+ L"a" L"\x5d0\x5d1",
+ L"a" L"\x5d1" L"b",
+ L"\x5d0\x5d1\x5d2",
+ L"\x5d0\x5d1" L"a",
+ L"\x5d0" L"a" L"\x5d1",
+ };
+
+ const bool was_rtl = IsRTL();
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ for (size_t i = 0; i < 2; ++i) {
+ // Toggle the application default text direction (to try each direction).
+ SetRTLForTesting(!IsRTL());
+
+ string16 empty;
+ WrapStringWithLTRFormatting(&empty);
+ EXPECT_TRUE(empty.empty());
+ WrapStringWithRTLFormatting(&empty);
+ EXPECT_TRUE(empty.empty());
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ string16 input = WideToUTF16(cases[i]);
+ string16 ltr_wrap = input;
+ WrapStringWithLTRFormatting(&ltr_wrap);
+ EXPECT_EQ(ltr_wrap[0], kLeftToRightEmbeddingMark);
+ EXPECT_EQ(ltr_wrap.substr(1, ltr_wrap.length() - 2), input);
+ EXPECT_EQ(ltr_wrap[ltr_wrap.length() -1], kPopDirectionalFormatting);
+
+ string16 rtl_wrap = input;
+ WrapStringWithRTLFormatting(&rtl_wrap);
+ EXPECT_EQ(rtl_wrap[0], kRightToLeftEmbeddingMark);
+ EXPECT_EQ(rtl_wrap.substr(1, rtl_wrap.length() - 2), input);
+ EXPECT_EQ(rtl_wrap[rtl_wrap.length() -1], kPopDirectionalFormatting);
+ }
+ }
+
+ EXPECT_EQ(was_rtl, IsRTL());
+}
+
+TEST_F(RTLTest, GetDisplayStringInLTRDirectionality) {
+ struct {
+ const wchar_t* path;
+ bool wrap_ltr;
+ bool wrap_rtl;
+ } cases[] = {
+ { L"test", false, true },
+ { L"test.html", false, true },
+ { L"\x05d0\x05d1\x05d2", true, true },
+ { L"\x05d0\x05d1\x05d2.txt", true, true },
+ { L"\x05d0" L"abc", true, true },
+ { L"\x05d0" L"abc.txt", true, true },
+ { L"abc\x05d0\x05d1", false, true },
+ { L"abc\x05d0\x05d1.jpg", false, true },
+ };
+
+ const bool was_rtl = IsRTL();
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ for (size_t i = 0; i < 2; ++i) {
+ // Toggle the application default text direction (to try each direction).
+ SetRTLForTesting(!IsRTL());
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ string16 input = WideToUTF16(cases[i].path);
+ string16 output = GetDisplayStringInLTRDirectionality(input);
+ // Test the expected wrapping behavior for the current UI directionality.
+ if (IsRTL() ? cases[i].wrap_rtl : cases[i].wrap_ltr)
+ EXPECT_NE(output, input);
+ else
+ EXPECT_EQ(output, input);
+ }
+ }
+
+ EXPECT_EQ(was_rtl, IsRTL());
+}
+
+TEST_F(RTLTest, GetTextDirection) {
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("ar"));
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("ar_EG"));
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("he"));
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("he_IL"));
+ // iw is an obsolete code for Hebrew.
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("iw"));
+ // Although we're not yet localized to Farsi and Urdu, we
+ // do have the text layout direction information for them.
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("fa"));
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("ur"));
+#if 0
+ // Enable these when we include the minimal locale data for Azerbaijani
+ // written in Arabic and Dhivehi. At the moment, our copy of
+ // ICU data does not have entries for them.
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("az_Arab"));
+ // Dhivehi that uses Thaana script.
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocale("dv"));
+#endif
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("en"));
+ // Chinese in China with '-'.
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("zh-CN"));
+ // Filipino : 3-letter code
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("fil"));
+ // Russian
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("ru"));
+ // Japanese that uses multiple scripts
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocale("ja"));
+}
+
+TEST_F(RTLTest, GetTextDirectionForLocaleInStartUp) {
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocaleInStartUp("ar"));
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocaleInStartUp("ar_EG"));
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocaleInStartUp("he"));
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocaleInStartUp("he_IL"));
+ // iw is an obsolete code for Hebrew.
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocaleInStartUp("iw"));
+ // Although we're not yet localized to Farsi and Urdu, we
+ // do have the text layout direction information for them.
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocaleInStartUp("fa"));
+ EXPECT_EQ(RIGHT_TO_LEFT, GetTextDirectionForLocaleInStartUp("ur"));
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocaleInStartUp("en"));
+ // Chinese in China with '-'.
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocaleInStartUp("zh-CN"));
+ // Filipino : 3-letter code
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocaleInStartUp("fil"));
+ // Russian
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocaleInStartUp("ru"));
+ // Japanese that uses multiple scripts
+ EXPECT_EQ(LEFT_TO_RIGHT, GetTextDirectionForLocaleInStartUp("ja"));
+}
+
+TEST_F(RTLTest, UnadjustStringForLocaleDirection) {
+ // These test strings are borrowed from WrapPathWithLTRFormatting
+ const wchar_t* cases[] = {
+ L"foo bar",
+ L"foo \x05d0 bar",
+ L"\x05d0 foo bar",
+ L"!foo \x05d0 bar",
+ L",\x05d0 foo bar",
+ L"\x202a \x05d0 foo bar",
+ L"\x202d \x05d0 foo bar",
+ L"\x202b foo \x05d0 bar",
+ L"\x202e foo \x05d0 bar",
+ L"\x0622 foo \x05d0 bar",
+ };
+
+ const bool was_rtl = IsRTL();
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ for (size_t i = 0; i < 2; ++i) {
+ // Toggle the application default text direction (to try each direction).
+ SetRTLForTesting(!IsRTL());
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ string16 test_case = WideToUTF16(cases[i]);
+ string16 adjusted_string = test_case;
+
+ if (!AdjustStringForLocaleDirection(&adjusted_string))
+ continue;
+
+ EXPECT_NE(test_case, adjusted_string);
+ EXPECT_TRUE(UnadjustStringForLocaleDirection(&adjusted_string));
+ EXPECT_EQ(test_case, adjusted_string) << " for test case [" << test_case
+ << "] with IsRTL() == " << IsRTL();
+ }
+ }
+
+ EXPECT_EQ(was_rtl, IsRTL());
+}
+
+TEST_F(RTLTest, EnsureTerminatedDirectionalFormatting) {
+ struct {
+ const wchar_t* unformated_text;
+ const wchar_t* formatted_text;
+ } cases[] = {
+ // Tests string without any dir-formatting characters.
+ {L"google.com", L"google.com"},
+ // Tests string with properly terminated dir-formatting character.
+ {L"\x202egoogle.com\x202c", L"\x202egoogle.com\x202c"},
+ // Tests string with over-terminated dir-formatting characters.
+ {L"\x202egoogle\x202c.com\x202c", L"\x202egoogle\x202c.com\x202c"},
+ // Tests string beginning with a dir-formatting character.
+ {L"\x202emoc.elgoog", L"\x202emoc.elgoog\x202c"},
+ // Tests string that over-terminates then re-opens.
+ {L"\x202egoogle\x202c\x202c.\x202eom",
+ L"\x202egoogle\x202c\x202c.\x202eom\x202c"},
+ // Tests string containing a dir-formatting character in the middle.
+ {L"google\x202e.com", L"google\x202e.com\x202c"},
+ // Tests string with multiple dir-formatting characters.
+ {L"\x202egoogle\x202e.com/\x202eguest",
+ L"\x202egoogle\x202e.com/\x202eguest\x202c\x202c\x202c"},
+ // Test the other dir-formatting characters (U+202A, U+202B, and U+202D).
+ {L"\x202agoogle.com", L"\x202agoogle.com\x202c"},
+ {L"\x202bgoogle.com", L"\x202bgoogle.com\x202c"},
+ {L"\x202dgoogle.com", L"\x202dgoogle.com\x202c"},
+ };
+
+ const bool was_rtl = IsRTL();
+
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ for (size_t i = 0; i < 2; ++i) {
+ // Toggle the application default text direction (to try each direction).
+ SetRTLForTesting(!IsRTL());
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ string16 unsanitized_text = WideToUTF16(cases[i].unformated_text);
+ string16 sanitized_text = WideToUTF16(cases[i].formatted_text);
+ EnsureTerminatedDirectionalFormatting(&unsanitized_text);
+ EXPECT_EQ(sanitized_text, unsanitized_text);
+ }
+ }
+ EXPECT_EQ(was_rtl, IsRTL());
+}
+
+TEST_F(RTLTest, SanitizeUserSuppliedString) {
+ struct {
+ const wchar_t* unformatted_text;
+ const wchar_t* formatted_text;
+ } cases[] = {
+ // Tests RTL string with properly terminated dir-formatting character.
+ {L"\x202eكبير Google التطبيق\x202c", L"\x202eكبير Google التطبيق\x202c"},
+ // Tests RTL string with over-terminated dir-formatting characters.
+ {L"\x202eكبير Google\x202cالتطبيق\x202c",
+ L"\x202eكبير Google\x202cالتطبيق\x202c"},
+ // Tests RTL string that over-terminates then re-opens.
+ {L"\x202eكبير Google\x202c\x202cالتطبيق\x202e",
+ L"\x202eكبير Google\x202c\x202cالتطبيق\x202e\x202c"},
+ // Tests RTL string with multiple dir-formatting characters.
+ {L"\x202eك\x202eبير Google الت\x202eطبيق",
+ L"\x202eك\x202eبير Google الت\x202eطبيق\x202c\x202c\x202c"},
+ // Test the other dir-formatting characters (U+202A, U+202B, and U+202D).
+ {L"\x202aكبير Google التطبيق", L"\x202aكبير Google التطبيق\x202c"},
+ {L"\x202bكبير Google التطبيق", L"\x202bكبير Google التطبيق\x202c"},
+ {L"\x202dكبير Google التطبيق", L"\x202dكبير Google التطبيق\x202c"},
+
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ // On Windows for an LTR locale, no changes to the string are made.
+ string16 prefix, suffix = WideToUTF16(L"");
+#if !defined(OS_WIN)
+ prefix = WideToUTF16(L"\x200e\x202b");
+ suffix = WideToUTF16(L"\x202c\x200e");
+#endif // !OS_WIN
+ string16 unsanitized_text = WideToUTF16(cases[i].unformatted_text);
+ string16 sanitized_text =
+ prefix + WideToUTF16(cases[i].formatted_text) + suffix;
+ SanitizeUserSuppliedString(&unsanitized_text);
+ EXPECT_EQ(sanitized_text, unsanitized_text);
+ }
+}
+
+class SetICULocaleTest : public PlatformTest {};
+
+TEST_F(SetICULocaleTest, OverlongLocaleId) {
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ std::string id("fr-ca-x-foo");
+ while (id.length() < 152)
+ id.append("-x-foo");
+ SetICUDefaultLocale(id);
+ EXPECT_STRNE("en_US", icu::Locale::getDefault().getName());
+ id.append("zzz");
+ SetICUDefaultLocale(id);
+ EXPECT_STREQ("en_US", icu::Locale::getDefault().getName());
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/streaming_utf8_validator.cc b/base/i18n/streaming_utf8_validator.cc
new file mode 100644
index 0000000000..19c86a37a4
--- /dev/null
+++ b/base/i18n/streaming_utf8_validator.cc
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This implementation doesn't use ICU. The ICU macros are oriented towards
+// character-at-a-time processing, whereas byte-at-a-time processing is easier
+// with streaming input.
+
+#include "base/i18n/streaming_utf8_validator.h"
+
+#include "base/i18n/utf8_validator_tables.h"
+#include "base/logging.h"
+
+namespace base {
+namespace {
+
+uint8_t StateTableLookup(uint8_t offset) {
+ DCHECK_LT(offset, internal::kUtf8ValidatorTablesSize);
+ return internal::kUtf8ValidatorTables[offset];
+}
+
+} // namespace
+
+StreamingUtf8Validator::State StreamingUtf8Validator::AddBytes(const char* data,
+ size_t size) {
+ // Copy |state_| into a local variable so that the compiler doesn't have to be
+ // careful of aliasing.
+ uint8_t state = state_;
+ for (const char* p = data; p != data + size; ++p) {
+ if ((*p & 0x80) == 0) {
+ if (state == 0)
+ continue;
+ state = internal::I18N_UTF8_VALIDATOR_INVALID_INDEX;
+ break;
+ }
+ const uint8_t shift_amount = StateTableLookup(state);
+ const uint8_t shifted_char = (*p & 0x7F) >> shift_amount;
+ state = StateTableLookup(state + shifted_char + 1);
+ // State may be INVALID here, but this code is optimised for the case of
+ // valid UTF-8 and it is more efficient (by about 2%) to not attempt an
+ // early loop exit unless we hit an ASCII character.
+ }
+ state_ = state;
+ return state == 0 ? VALID_ENDPOINT
+ : state == internal::I18N_UTF8_VALIDATOR_INVALID_INDEX
+ ? INVALID
+ : VALID_MIDPOINT;
+}
+
+void StreamingUtf8Validator::Reset() {
+ state_ = 0u;
+}
+
+bool StreamingUtf8Validator::Validate(const std::string& string) {
+ return StreamingUtf8Validator().AddBytes(string.data(), string.size()) ==
+ VALID_ENDPOINT;
+}
+
+} // namespace base
diff --git a/base/i18n/streaming_utf8_validator.h b/base/i18n/streaming_utf8_validator.h
new file mode 100644
index 0000000000..ebf38a69b3
--- /dev/null
+++ b/base/i18n/streaming_utf8_validator.h
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A streaming validator for UTF-8. Validation is based on the definition in
+// RFC-3629. In particular, it does not reject the invalid characters rejected
+// by base::IsStringUTF8().
+//
+// The implementation detects errors on the first possible byte.
+
+#ifndef BASE_I18N_STREAMING_UTF8_VALIDATOR_H_
+#define BASE_I18N_STREAMING_UTF8_VALIDATOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/macros.h"
+
+namespace base {
+
+class BASE_I18N_EXPORT StreamingUtf8Validator {
+ public:
+ // The validator exposes 3 states. It starts in state VALID_ENDPOINT. As it
+ // processes characters it alternates between VALID_ENDPOINT and
+ // VALID_MIDPOINT. If it encounters an invalid byte or UTF-8 sequence the
+ // state changes permanently to INVALID.
+ enum State {
+ VALID_ENDPOINT,
+ VALID_MIDPOINT,
+ INVALID
+ };
+
+ StreamingUtf8Validator() : state_(0u) {}
+ // Trivial destructor intentionally omitted.
+
+ // Validate |size| bytes starting at |data|. If the concatenation of all calls
+ // to AddBytes() since this object was constructed or reset is a valid UTF-8
+ // string, returns VALID_ENDPOINT. If it could be the prefix of a valid UTF-8
+ // string, returns VALID_MIDPOINT. If an invalid byte or UTF-8 sequence was
+ // present, returns INVALID.
+ State AddBytes(const char* data, size_t size);
+
+ // Return the object to a freshly-constructed state so that it can be re-used.
+ void Reset();
+
+ // Validate a complete string using the same criteria. Returns true if the
+ // string only contains complete, valid UTF-8 codepoints.
+ static bool Validate(const std::string& string);
+
+ private:
+ // The current state of the validator. Value 0 is the initial/valid state.
+ // The state is stored as an offset into |kUtf8ValidatorTables|. The special
+ // state |kUtf8InvalidState| is invalid.
+ uint8_t state_;
+
+ // This type could be made copyable but there is currently no use-case for
+ // it.
+ DISALLOW_COPY_AND_ASSIGN(StreamingUtf8Validator);
+};
+
+} // namespace base
+
+#endif // BASE_I18N_STREAMING_UTF8_VALIDATOR_H_
diff --git a/base/i18n/streaming_utf8_validator_perftest.cc b/base/i18n/streaming_utf8_validator_perftest.cc
new file mode 100644
index 0000000000..ad328f886d
--- /dev/null
+++ b/base/i18n/streaming_utf8_validator_perftest.cc
@@ -0,0 +1,240 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// All data that is passed through a WebSocket with type "Text" needs to be
+// validated as UTF8. Since this is done on the IO thread, it needs to be
+// reasonably fast.
+
+// We are only interested in the performance on valid UTF8. Invalid UTF8 will
+// result in a connection failure, so is unlikely to become a source of
+// performance issues.
+
+#include "base/i18n/streaming_utf8_validator.h"
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/perf_time_logger.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+// We want to test ranges of valid UTF-8 sequences. These ranges are inclusive.
+// They are intended to be large enough that the validator needs to do
+// meaningful work while being in some sense "realistic" (eg. control characters
+// are not included).
+const char kOneByteSeqRangeStart[] = " "; // U+0020
+const char kOneByteSeqRangeEnd[] = "~"; // U+007E
+
+const char kTwoByteSeqRangeStart[] = "\xc2\xa0"; // U+00A0 non-breaking space
+const char kTwoByteSeqRangeEnd[] = "\xc9\x8f"; // U+024F small y with stroke
+
+const char kThreeByteSeqRangeStart[] = "\xe3\x81\x82"; // U+3042 Hiragana "a"
+const char kThreeByteSeqRangeEnd[] = "\xe9\xbf\x83"; // U+9FC3 "to blink"
+
+const char kFourByteSeqRangeStart[] = "\xf0\xa0\x80\x8b"; // U+2000B
+const char kFourByteSeqRangeEnd[] = "\xf0\xaa\x9a\xb2"; // U+2A6B2
+
+// The different lengths of strings to test.
+const size_t kTestLengths[] = {1, 32, 256, 32768, 1 << 20};
+
+// Simplest possible byte-at-a-time validator, to provide a baseline
+// for comparison. This is only tried on 1-byte UTF-8 sequences, as
+// the results will not be meaningful with sequences containing
+// top-bit-set bytes.
+bool IsString7Bit(const std::string& s) {
+ for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) {
+ if (*it & 0x80)
+ return false;
+ }
+ return true;
+}
+
+// Assumes that |previous| is a valid UTF-8 sequence, and attempts to return
+// the next one. Is just barely smart enough to iterate through the ranges
+// defined about.
+std::string NextUtf8Sequence(const std::string& previous) {
+ DCHECK(StreamingUtf8Validator::Validate(previous));
+ std::string next = previous;
+ for (int i = static_cast<int>(previous.length() - 1); i >= 0; --i) {
+ // All bytes in a UTF-8 sequence except the first one are
+ // constrained to the range 0x80 to 0xbf, inclusive. When we
+ // increment past 0xbf, we carry into the previous byte.
+ if (i > 0 && next[i] == '\xbf') {
+ next[i] = '\x80';
+ continue; // carry
+ }
+ ++next[i];
+ break; // no carry
+ }
+ DCHECK(StreamingUtf8Validator::Validate(next))
+ << "Result \"" << next << "\" failed validation";
+ return next;
+}
+
+typedef bool (*TestTargetType)(const std::string&);
+
+// Run fuction |target| over |test_string| |times| times, and report the results
+// using |description|.
+bool RunTest(const std::string& description,
+ TestTargetType target,
+ const std::string& test_string,
+ int times) {
+ base::PerfTimeLogger timer(description.c_str());
+ bool result = true;
+ for (int i = 0; i < times; ++i) {
+ result = target(test_string) && result;
+ }
+ timer.Done();
+ return result;
+}
+
+// Construct a string by repeating |input| enough times to equal or exceed
+// |length|.
+std::string ConstructRepeatedTestString(const std::string& input,
+ size_t length) {
+ std::string output = input;
+ while (output.length() * 2 < length) {
+ output += output;
+ }
+ if (output.length() < length) {
+ output += ConstructRepeatedTestString(input, length - output.length());
+ }
+ return output;
+}
+
+// Construct a string by expanding the range of UTF-8 sequences
+// between |input_start| and |input_end|, inclusive, and then
+// repeating the resulting string until it equals or exceeds |length|
+// bytes. |input_start| and |input_end| must be valid UTF-8
+// sequences.
+std::string ConstructRangedTestString(const std::string& input_start,
+ const std::string& input_end,
+ size_t length) {
+ std::string output = input_start;
+ std::string input = input_start;
+ while (output.length() < length && input != input_end) {
+ input = NextUtf8Sequence(input);
+ output += input;
+ }
+ if (output.length() < length) {
+ output = ConstructRepeatedTestString(output, length);
+ }
+ return output;
+}
+
+struct TestFunctionDescription {
+ TestTargetType function;
+ const char* function_name;
+};
+
+bool IsStringUTF8(const std::string& str) {
+ return base::IsStringUTF8(base::StringPiece(str));
+}
+
+// IsString7Bit is intentionally placed last so it can be excluded easily.
+const TestFunctionDescription kTestFunctions[] = {
+ {&StreamingUtf8Validator::Validate, "StreamingUtf8Validator"},
+ {&IsStringUTF8, "IsStringUTF8"}, {&IsString7Bit, "IsString7Bit"}};
+
+// Construct a test string from |construct_test_string| for each of the lengths
+// in |kTestLengths| in turn. For each string, run each test in |test_functions|
+// for a number of iterations such that the total number of bytes validated
+// is around 16MB.
+void RunSomeTests(
+ const char format[],
+ base::Callback<std::string(size_t length)> construct_test_string,
+ const TestFunctionDescription* test_functions,
+ size_t test_count) {
+ for (size_t i = 0; i < arraysize(kTestLengths); ++i) {
+ const size_t length = kTestLengths[i];
+ const std::string test_string = construct_test_string.Run(length);
+ const int real_length = static_cast<int>(test_string.length());
+ const int times = (1 << 24) / real_length;
+ for (size_t test_index = 0; test_index < test_count; ++test_index) {
+ EXPECT_TRUE(RunTest(StringPrintf(format,
+ test_functions[test_index].function_name,
+ real_length,
+ times),
+ test_functions[test_index].function,
+ test_string,
+ times));
+ }
+ }
+}
+
+TEST(StreamingUtf8ValidatorPerfTest, OneByteRepeated) {
+ RunSomeTests("%s: bytes=1 repeated length=%d repeat=%d",
+ base::Bind(ConstructRepeatedTestString, kOneByteSeqRangeStart),
+ kTestFunctions,
+ 3);
+}
+
+TEST(StreamingUtf8ValidatorPerfTest, OneByteRange) {
+ RunSomeTests("%s: bytes=1 ranged length=%d repeat=%d",
+ base::Bind(ConstructRangedTestString,
+ kOneByteSeqRangeStart,
+ kOneByteSeqRangeEnd),
+ kTestFunctions,
+ 3);
+}
+
+TEST(StreamingUtf8ValidatorPerfTest, TwoByteRepeated) {
+ RunSomeTests("%s: bytes=2 repeated length=%d repeat=%d",
+ base::Bind(ConstructRepeatedTestString, kTwoByteSeqRangeStart),
+ kTestFunctions,
+ 2);
+}
+
+TEST(StreamingUtf8ValidatorPerfTest, TwoByteRange) {
+ RunSomeTests("%s: bytes=2 ranged length=%d repeat=%d",
+ base::Bind(ConstructRangedTestString,
+ kTwoByteSeqRangeStart,
+ kTwoByteSeqRangeEnd),
+ kTestFunctions,
+ 2);
+}
+
+TEST(StreamingUtf8ValidatorPerfTest, ThreeByteRepeated) {
+ RunSomeTests(
+ "%s: bytes=3 repeated length=%d repeat=%d",
+ base::Bind(ConstructRepeatedTestString, kThreeByteSeqRangeStart),
+ kTestFunctions,
+ 2);
+}
+
+TEST(StreamingUtf8ValidatorPerfTest, ThreeByteRange) {
+ RunSomeTests("%s: bytes=3 ranged length=%d repeat=%d",
+ base::Bind(ConstructRangedTestString,
+ kThreeByteSeqRangeStart,
+ kThreeByteSeqRangeEnd),
+ kTestFunctions,
+ 2);
+}
+
+TEST(StreamingUtf8ValidatorPerfTest, FourByteRepeated) {
+ RunSomeTests("%s: bytes=4 repeated length=%d repeat=%d",
+ base::Bind(ConstructRepeatedTestString, kFourByteSeqRangeStart),
+ kTestFunctions,
+ 2);
+}
+
+TEST(StreamingUtf8ValidatorPerfTest, FourByteRange) {
+ RunSomeTests("%s: bytes=4 ranged length=%d repeat=%d",
+ base::Bind(ConstructRangedTestString,
+ kFourByteSeqRangeStart,
+ kFourByteSeqRangeEnd),
+ kTestFunctions,
+ 2);
+}
+
+} // namespace
+} // namespace base
diff --git a/base/i18n/streaming_utf8_validator_unittest.cc b/base/i18n/streaming_utf8_validator_unittest.cc
new file mode 100644
index 0000000000..f9772d098a
--- /dev/null
+++ b/base/i18n/streaming_utf8_validator_unittest.cc
@@ -0,0 +1,412 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/streaming_utf8_validator.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Define BASE_I18N_UTF8_VALIDATOR_THOROUGH_TEST to verify that this class
+// accepts exactly the same set of 4-byte strings as ICU-based validation. This
+// tests every possible 4-byte string, so it is too slow to run routinely on
+// low-powered machines.
+//
+// #define BASE_I18N_UTF8_VALIDATOR_THOROUGH_TEST
+
+#ifdef BASE_I18N_UTF8_VALIDATOR_THOROUGH_TEST
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversion_utils.h"
+#include "base/synchronization/lock.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "third_party/icu/source/common/unicode/utf8.h"
+
+#endif // BASE_I18N_UTF8_VALIDATOR_THOROUGH_TEST
+
+namespace base {
+namespace {
+
+// Avoid having to qualify the enum values in the tests.
+const StreamingUtf8Validator::State VALID_ENDPOINT =
+ StreamingUtf8Validator::VALID_ENDPOINT;
+const StreamingUtf8Validator::State VALID_MIDPOINT =
+ StreamingUtf8Validator::VALID_MIDPOINT;
+const StreamingUtf8Validator::State INVALID = StreamingUtf8Validator::INVALID;
+
+#ifdef BASE_I18N_UTF8_VALIDATOR_THOROUGH_TEST
+
+const uint32_t kThoroughTestChunkSize = 1 << 24;
+
+class StreamingUtf8ValidatorThoroughTest : public ::testing::Test {
+ protected:
+ StreamingUtf8ValidatorThoroughTest()
+ : tasks_dispatched_(0), tasks_finished_(0) {}
+
+ // This uses the same logic as base::IsStringUTF8 except it considers
+ // non-characters valid (and doesn't require a string as input).
+ static bool IsStringUtf8(const char* src, int32_t src_len) {
+ int32_t char_index = 0;
+
+ while (char_index < src_len) {
+ int32_t code_point;
+ U8_NEXT(src, char_index, src_len, code_point);
+ if (!base::IsValidCodepoint(code_point))
+ return false;
+ }
+ return true;
+ }
+
+ // Converts the passed-in integer to a 4 byte string and then
+ // verifies that IsStringUtf8 and StreamingUtf8Validator agree on
+ // whether it is valid UTF-8 or not.
+ void TestNumber(uint32_t n) const {
+ char test[sizeof n];
+ memcpy(test, &n, sizeof n);
+ StreamingUtf8Validator validator;
+ EXPECT_EQ(IsStringUtf8(test, sizeof n),
+ validator.AddBytes(test, sizeof n) == VALID_ENDPOINT)
+ << "Difference of opinion for \""
+ << base::StringPrintf("\\x%02X\\x%02X\\x%02X\\x%02X",
+ test[0] & 0xFF,
+ test[1] & 0xFF,
+ test[2] & 0xFF,
+ test[3] & 0xFF) << "\"";
+ }
+
+ public:
+ // Tests the 4-byte sequences corresponding to the |size| integers
+ // starting at |begin|. This is intended to be run from a worker
+ // pool. Signals |all_done_| at the end if it thinks all tasks are
+ // finished.
+ void TestRange(uint32_t begin, uint32_t size) {
+ for (uint32_t i = 0; i < size; ++i) {
+ TestNumber(begin + i);
+ }
+ base::AutoLock al(lock_);
+ ++tasks_finished_;
+ LOG(INFO) << tasks_finished_ << " / " << tasks_dispatched_
+ << " tasks done\n";
+ }
+
+ protected:
+ base::Lock lock_;
+ int tasks_dispatched_;
+ int tasks_finished_;
+};
+
+TEST_F(StreamingUtf8ValidatorThoroughTest, TestEverything) {
+ base::TaskScheduler::CreateAndStartWithDefaultParams(
+ "StreamingUtf8ValidatorThoroughTest");
+ {
+ base::AutoLock al(lock_);
+ uint32_t begin = 0;
+ do {
+ base::PostTaskWithTraits(
+ FROM_HERE, {base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ base::BindOnce(&StreamingUtf8ValidatorThoroughTest::TestRange,
+ base::Unretained(this), begin,
+ kThoroughTestChunkSize));
+ ++tasks_dispatched_;
+ begin += kThoroughTestChunkSize;
+ } while (begin != 0);
+ }
+ base::TaskScheduler::GetInstance()->Shutdown();
+ base::TaskScheduler::GetInstance()->JoinForTesting();
+ base::TaskScheduler::SetInstance(nullptr);
+}
+
+#endif // BASE_I18N_UTF8_VALIDATOR_THOROUGH_TEST
+
+// These valid and invalid UTF-8 sequences are based on the tests from
+// base/strings/string_util_unittest.cc
+
+// All of the strings in |valid| must represent a single codepoint, because
+// partial sequences are constructed by taking non-empty prefixes of these
+// strings.
+const char* const valid[] = {"\r", "\n", "a",
+ "\xc2\x81", "\xe1\x80\xbf", "\xf1\x80\xa0\xbf",
+ "\xef\xbb\xbf", // UTF-8 BOM
+};
+
+const char* const* const valid_end = valid + arraysize(valid);
+
+const char* const invalid[] = {
+ // always invalid bytes
+ "\xc0", "\xc1",
+ "\xf5", "\xf6", "\xf7",
+ "\xf8", "\xf9", "\xfa", "\xfb", "\xfc", "\xfd", "\xfe", "\xff",
+ // surrogate code points
+ "\xed\xa0\x80", "\xed\x0a\x8f", "\xed\xbf\xbf",
+ //
+ // overlong sequences
+ "\xc0\x80", // U+0000
+ "\xc1\x80", // "A"
+ "\xc1\x81", // "B"
+ "\xe0\x80\x80", // U+0000
+ "\xe0\x82\x80", // U+0080
+ "\xe0\x9f\xbf", // U+07ff
+ "\xf0\x80\x80\x8D", // U+000D
+ "\xf0\x80\x82\x91", // U+0091
+ "\xf0\x80\xa0\x80", // U+0800
+ "\xf0\x8f\xbb\xbf", // U+FEFF (BOM)
+ "\xf8\x80\x80\x80\xbf", // U+003F
+ "\xfc\x80\x80\x80\xa0\xa5",
+ //
+ // Beyond U+10FFFF
+ "\xf4\x90\x80\x80", // U+110000
+ "\xf8\xa0\xbf\x80\xbf", // 5 bytes
+ "\xfc\x9c\xbf\x80\xbf\x80", // 6 bytes
+ //
+ // BOMs in UTF-16(BE|LE)
+ "\xfe\xff", "\xff\xfe",
+};
+
+const char* const* const invalid_end = invalid + arraysize(invalid);
+
+// A ForwardIterator which returns all the non-empty prefixes of the elements of
+// "valid".
+class PartialIterator {
+ public:
+ // The constructor returns the first iterator, ie. it is equivalent to
+ // begin().
+ PartialIterator() : index_(0), prefix_length_(0) { Advance(); }
+ // The trivial destructor left intentionally undefined.
+ // This is a value type; the default copy constructor and assignment operator
+ // generated by the compiler are used.
+
+ static PartialIterator end() { return PartialIterator(arraysize(valid), 1); }
+
+ PartialIterator& operator++() {
+ Advance();
+ return *this;
+ }
+
+ base::StringPiece operator*() const {
+ return base::StringPiece(valid[index_], prefix_length_);
+ }
+
+ bool operator==(const PartialIterator& rhs) const {
+ return index_ == rhs.index_ && prefix_length_ == rhs.prefix_length_;
+ }
+
+ bool operator!=(const PartialIterator& rhs) const { return !(rhs == *this); }
+
+ private:
+ // This constructor is used by the end() method.
+ PartialIterator(size_t index, size_t prefix_length)
+ : index_(index), prefix_length_(prefix_length) {}
+
+ void Advance() {
+ if (index_ < arraysize(valid) && prefix_length_ < strlen(valid[index_]))
+ ++prefix_length_;
+ while (index_ < arraysize(valid) &&
+ prefix_length_ == strlen(valid[index_])) {
+ ++index_;
+ prefix_length_ = 1;
+ }
+ }
+
+ // The UTF-8 sequence, as an offset into the |valid| array.
+ size_t index_;
+ size_t prefix_length_;
+};
+
+// A test fixture for tests which test one UTF-8 sequence (or invalid
+// byte sequence) at a time.
+class StreamingUtf8ValidatorSingleSequenceTest : public ::testing::Test {
+ protected:
+ // Iterator must be convertible when de-referenced to StringPiece.
+ template <typename Iterator>
+ void CheckRange(Iterator begin,
+ Iterator end,
+ StreamingUtf8Validator::State expected) {
+ for (Iterator it = begin; it != end; ++it) {
+ StreamingUtf8Validator validator;
+ base::StringPiece sequence = *it;
+ EXPECT_EQ(expected,
+ validator.AddBytes(sequence.data(), sequence.size()))
+ << "Failed for \"" << sequence << "\"";
+ }
+ }
+
+ // Adding input a byte at a time should make absolutely no difference.
+ template <typename Iterator>
+ void CheckRangeByteAtATime(Iterator begin,
+ Iterator end,
+ StreamingUtf8Validator::State expected) {
+ for (Iterator it = begin; it != end; ++it) {
+ StreamingUtf8Validator validator;
+ base::StringPiece sequence = *it;
+ StreamingUtf8Validator::State state = VALID_ENDPOINT;
+ for (base::StringPiece::const_iterator cit = sequence.begin();
+ cit != sequence.end();
+ ++cit) {
+ state = validator.AddBytes(&*cit, 1);
+ }
+ EXPECT_EQ(expected, state) << "Failed for \"" << sequence << "\"";
+ }
+ }
+};
+
+// A test fixture for tests which test the concatenation of byte sequences.
+class StreamingUtf8ValidatorDoubleSequenceTest : public ::testing::Test {
+ protected:
+ // Check every possible concatenation of byte sequences from two
+ // ranges, and verify that the combination matches the expected
+ // state.
+ template <typename Iterator1, typename Iterator2>
+ void CheckCombinations(Iterator1 begin1,
+ Iterator1 end1,
+ Iterator2 begin2,
+ Iterator2 end2,
+ StreamingUtf8Validator::State expected) {
+ StreamingUtf8Validator validator;
+ for (Iterator1 it1 = begin1; it1 != end1; ++it1) {
+ base::StringPiece c1 = *it1;
+ for (Iterator2 it2 = begin2; it2 != end2; ++it2) {
+ base::StringPiece c2 = *it2;
+ validator.AddBytes(c1.data(), c1.size());
+ EXPECT_EQ(expected, validator.AddBytes(c2.data(), c2.size()))
+ << "Failed for \"" << c1 << c2 << "\"";
+ validator.Reset();
+ }
+ }
+ }
+};
+
+TEST(StreamingUtf8ValidatorTest, NothingIsValid) {
+ static const char kNothing[] = "";
+ EXPECT_EQ(VALID_ENDPOINT, StreamingUtf8Validator().AddBytes(kNothing, 0));
+}
+
+// Because the members of the |valid| array need to be non-zero length
+// sequences and are measured with strlen(), |valid| cannot be used it
+// to test the NUL character '\0', so the NUL character gets its own
+// test.
+TEST(StreamingUtf8ValidatorTest, NulIsValid) {
+ static const char kNul[] = "\x00";
+ EXPECT_EQ(VALID_ENDPOINT, StreamingUtf8Validator().AddBytes(kNul, 1));
+}
+
+// Just a basic sanity test before we start getting fancy.
+TEST(StreamingUtf8ValidatorTest, HelloWorld) {
+ static const char kHelloWorld[] = "Hello, World!";
+ EXPECT_EQ(
+ VALID_ENDPOINT,
+ StreamingUtf8Validator().AddBytes(kHelloWorld, strlen(kHelloWorld)));
+}
+
+// Check that the Reset() method works.
+TEST(StreamingUtf8ValidatorTest, ResetWorks) {
+ StreamingUtf8Validator validator;
+ EXPECT_EQ(INVALID, validator.AddBytes("\xC0", 1));
+ EXPECT_EQ(INVALID, validator.AddBytes("a", 1));
+ validator.Reset();
+ EXPECT_EQ(VALID_ENDPOINT, validator.AddBytes("a", 1));
+}
+
+TEST_F(StreamingUtf8ValidatorSingleSequenceTest, Valid) {
+ CheckRange(valid, valid_end, VALID_ENDPOINT);
+}
+
+TEST_F(StreamingUtf8ValidatorSingleSequenceTest, Partial) {
+ CheckRange(PartialIterator(), PartialIterator::end(), VALID_MIDPOINT);
+}
+
+TEST_F(StreamingUtf8ValidatorSingleSequenceTest, Invalid) {
+ CheckRange(invalid, invalid_end, INVALID);
+}
+
+TEST_F(StreamingUtf8ValidatorSingleSequenceTest, ValidByByte) {
+ CheckRangeByteAtATime(valid, valid_end, VALID_ENDPOINT);
+}
+
+TEST_F(StreamingUtf8ValidatorSingleSequenceTest, PartialByByte) {
+ CheckRangeByteAtATime(
+ PartialIterator(), PartialIterator::end(), VALID_MIDPOINT);
+}
+
+TEST_F(StreamingUtf8ValidatorSingleSequenceTest, InvalidByByte) {
+ CheckRangeByteAtATime(invalid, invalid_end, INVALID);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, ValidPlusValidIsValid) {
+ CheckCombinations(valid, valid_end, valid, valid_end, VALID_ENDPOINT);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, ValidPlusPartialIsPartial) {
+ CheckCombinations(valid,
+ valid_end,
+ PartialIterator(),
+ PartialIterator::end(),
+ VALID_MIDPOINT);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, PartialPlusValidIsInvalid) {
+ CheckCombinations(
+ PartialIterator(), PartialIterator::end(), valid, valid_end, INVALID);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, PartialPlusPartialIsInvalid) {
+ CheckCombinations(PartialIterator(),
+ PartialIterator::end(),
+ PartialIterator(),
+ PartialIterator::end(),
+ INVALID);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, ValidPlusInvalidIsInvalid) {
+ CheckCombinations(valid, valid_end, invalid, invalid_end, INVALID);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, InvalidPlusValidIsInvalid) {
+ CheckCombinations(invalid, invalid_end, valid, valid_end, INVALID);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, InvalidPlusInvalidIsInvalid) {
+ CheckCombinations(invalid, invalid_end, invalid, invalid_end, INVALID);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, InvalidPlusPartialIsInvalid) {
+ CheckCombinations(
+ invalid, invalid_end, PartialIterator(), PartialIterator::end(), INVALID);
+}
+
+TEST_F(StreamingUtf8ValidatorDoubleSequenceTest, PartialPlusInvalidIsInvalid) {
+ CheckCombinations(
+ PartialIterator(), PartialIterator::end(), invalid, invalid_end, INVALID);
+}
+
+TEST(StreamingUtf8ValidatorValidateTest, EmptyIsValid) {
+ EXPECT_TRUE(StreamingUtf8Validator::Validate(std::string()));
+}
+
+TEST(StreamingUtf8ValidatorValidateTest, SimpleValidCase) {
+ EXPECT_TRUE(StreamingUtf8Validator::Validate("\xc2\x81"));
+}
+
+TEST(StreamingUtf8ValidatorValidateTest, SimpleInvalidCase) {
+ EXPECT_FALSE(StreamingUtf8Validator::Validate("\xc0\x80"));
+}
+
+TEST(StreamingUtf8ValidatorValidateTest, TruncatedIsInvalid) {
+ EXPECT_FALSE(StreamingUtf8Validator::Validate("\xc2"));
+}
+
+} // namespace
+} // namespace base
diff --git a/base/i18n/string_compare.cc b/base/i18n/string_compare.cc
new file mode 100644
index 0000000000..649c28119f
--- /dev/null
+++ b/base/i18n/string_compare.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/string_compare.h"
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/icu/source/common/unicode/unistr.h"
+
+namespace base {
+namespace i18n {
+
+// Compares the character data stored in two different string16 strings by
+// specified Collator instance.
+UCollationResult CompareString16WithCollator(const icu::Collator& collator,
+ const string16& lhs,
+ const string16& rhs) {
+ UErrorCode error = U_ZERO_ERROR;
+ UCollationResult result = collator.compare(
+ icu::UnicodeString(FALSE, lhs.c_str(), static_cast<int>(lhs.length())),
+ icu::UnicodeString(FALSE, rhs.c_str(), static_cast<int>(rhs.length())),
+ error);
+ DCHECK(U_SUCCESS(error));
+ return result;
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/string_compare.h b/base/i18n/string_compare.h
new file mode 100644
index 0000000000..5fcc5feaed
--- /dev/null
+++ b/base/i18n/string_compare.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_STRING_COMPARE_H_
+#define BASE_I18N_STRING_COMPARE_H_
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/strings/string16.h"
+#include "third_party/icu/source/i18n/unicode/coll.h"
+
+namespace base {
+namespace i18n {
+
+// Compares the two strings using the specified collator.
+BASE_I18N_EXPORT UCollationResult
+CompareString16WithCollator(const icu::Collator& collator,
+ const string16& lhs,
+ const string16& rhs);
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_STRING_COMPARE_H_
diff --git a/base/i18n/string_search.cc b/base/i18n/string_search.cc
new file mode 100644
index 0000000000..2f6fee4fe6
--- /dev/null
+++ b/base/i18n/string_search.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "base/i18n/string_search.h"
+#include "base/logging.h"
+
+#include "third_party/icu/source/i18n/unicode/usearch.h"
+
+namespace base {
+namespace i18n {
+
+FixedPatternStringSearchIgnoringCaseAndAccents::
+FixedPatternStringSearchIgnoringCaseAndAccents(const string16& find_this)
+ : find_this_(find_this) {
+ // usearch_open requires a valid string argument to be searched, even if we
+ // want to set it by usearch_setText afterwards. So, supplying a dummy text.
+ const string16& dummy = find_this_;
+
+ UErrorCode status = U_ZERO_ERROR;
+ search_ = usearch_open(find_this_.data(), find_this_.size(), dummy.data(),
+ dummy.size(), uloc_getDefault(),
+ nullptr, // breakiter
+ &status);
+ if (U_SUCCESS(status)) {
+ UCollator* collator = usearch_getCollator(search_);
+ ucol_setStrength(collator, UCOL_PRIMARY);
+ usearch_reset(search_);
+ }
+}
+
+FixedPatternStringSearchIgnoringCaseAndAccents::
+~FixedPatternStringSearchIgnoringCaseAndAccents() {
+ if (search_)
+ usearch_close(search_);
+}
+
+bool FixedPatternStringSearchIgnoringCaseAndAccents::Search(
+ const string16& in_this, size_t* match_index, size_t* match_length) {
+ UErrorCode status = U_ZERO_ERROR;
+ usearch_setText(search_, in_this.data(), in_this.size(), &status);
+
+ // Default to basic substring search if usearch fails. According to
+ // http://icu-project.org/apiref/icu4c/usearch_8h.html, usearch_open will fail
+ // if either |find_this| or |in_this| are empty. In either case basic
+ // substring search will give the correct return value.
+ if (!U_SUCCESS(status)) {
+ size_t index = in_this.find(find_this_);
+ if (index == string16::npos) {
+ return false;
+ } else {
+ if (match_index)
+ *match_index = index;
+ if (match_length)
+ *match_length = find_this_.size();
+ return true;
+ }
+ }
+
+ int32_t index = usearch_first(search_, &status);
+ if (!U_SUCCESS(status) || index == USEARCH_DONE)
+ return false;
+ if (match_index)
+ *match_index = static_cast<size_t>(index);
+ if (match_length)
+ *match_length = static_cast<size_t>(usearch_getMatchedLength(search_));
+ return true;
+}
+
+bool StringSearchIgnoringCaseAndAccents(const string16& find_this,
+ const string16& in_this,
+ size_t* match_index,
+ size_t* match_length) {
+ return FixedPatternStringSearchIgnoringCaseAndAccents(find_this).Search(
+ in_this, match_index, match_length);
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/string_search.h b/base/i18n/string_search.h
new file mode 100644
index 0000000000..07a29c19b7
--- /dev/null
+++ b/base/i18n/string_search.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_STRING_SEARCH_H_
+#define BASE_I18N_STRING_SEARCH_H_
+
+#include <stddef.h>
+
+#include "base/i18n/base_i18n_export.h"
+#include "base/strings/string16.h"
+
+struct UStringSearch;
+
+namespace base {
+namespace i18n {
+
+// Returns true if |in_this| contains |find_this|. If |match_index| or
+// |match_length| are non-NULL, they are assigned the start position and total
+// length of the match.
+//
+// Only differences between base letters are taken into consideration. Case and
+// accent differences are ignored. Please refer to 'primary level' in
+// http://userguide.icu-project.org/collation/concepts for additional details.
+BASE_I18N_EXPORT
+ bool StringSearchIgnoringCaseAndAccents(const string16& find_this,
+ const string16& in_this,
+ size_t* match_index,
+ size_t* match_length);
+
+// This class is for speeding up multiple StringSearchIgnoringCaseAndAccents()
+// with the same |find_this| argument. |find_this| is passed as the constructor
+// argument, and precomputation for searching is done only at that timing.
+class BASE_I18N_EXPORT FixedPatternStringSearchIgnoringCaseAndAccents {
+ public:
+ explicit FixedPatternStringSearchIgnoringCaseAndAccents(
+ const string16& find_this);
+ ~FixedPatternStringSearchIgnoringCaseAndAccents();
+
+ // Returns true if |in_this| contains |find_this|. If |match_index| or
+ // |match_length| are non-NULL, they are assigned the start position and total
+ // length of the match.
+ bool Search(const string16& in_this,
+ size_t* match_index,
+ size_t* match_length);
+
+ private:
+ string16 find_this_;
+ UStringSearch* search_;
+};
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_I18N_STRING_SEARCH_H_
diff --git a/base/i18n/string_search_unittest.cc b/base/i18n/string_search_unittest.cc
new file mode 100644
index 0000000000..69501d6c99
--- /dev/null
+++ b/base/i18n/string_search_unittest.cc
@@ -0,0 +1,228 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/i18n/rtl.h"
+#include "base/i18n/string_search.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/i18n/unicode/usearch.h"
+
+namespace base {
+namespace i18n {
+
+// Note on setting default locale for testing: The current default locale on
+// the Mac trybot is en_US_POSIX, with which primary-level collation strength
+// string search is case-sensitive, when normally it should be
+// case-insensitive. In other locales (including en_US which English speakers
+// in the U.S. use), this search would be case-insensitive as expected.
+
+TEST(StringSearchTest, ASCII) {
+ std::string default_locale(uloc_getDefault());
+ bool locale_is_posix = (default_locale == "en_US_POSIX");
+ if (locale_is_posix)
+ SetICUDefaultLocale("en_US");
+
+ size_t index = 0;
+ size_t length = 0;
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ ASCIIToUTF16("hello"), ASCIIToUTF16("hello world"), &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(5U, length);
+
+ EXPECT_FALSE(StringSearchIgnoringCaseAndAccents(
+ ASCIIToUTF16("h e l l o"), ASCIIToUTF16("h e l l o"),
+ &index, &length));
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ ASCIIToUTF16("aabaaa"), ASCIIToUTF16("aaabaabaaa"), &index, &length));
+ EXPECT_EQ(4U, index);
+ EXPECT_EQ(6U, length);
+
+ EXPECT_FALSE(StringSearchIgnoringCaseAndAccents(
+ ASCIIToUTF16("searching within empty string"), string16(),
+ &index, &length));
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ string16(), ASCIIToUTF16("searching for empty string"), &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(0U, length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ ASCIIToUTF16("case insensitivity"), ASCIIToUTF16("CaSe InSeNsItIvItY"),
+ &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(18U, length);
+
+ if (locale_is_posix)
+ SetICUDefaultLocale(default_locale.data());
+}
+
+TEST(StringSearchTest, UnicodeLocaleIndependent) {
+ // Base characters
+ const string16 e_base = WideToUTF16(L"e");
+ const string16 E_base = WideToUTF16(L"E");
+ const string16 a_base = WideToUTF16(L"a");
+
+ // Composed characters
+ const string16 e_with_acute_accent = WideToUTF16(L"\u00e9");
+ const string16 E_with_acute_accent = WideToUTF16(L"\u00c9");
+ const string16 e_with_grave_accent = WideToUTF16(L"\u00e8");
+ const string16 E_with_grave_accent = WideToUTF16(L"\u00c8");
+ const string16 a_with_acute_accent = WideToUTF16(L"\u00e1");
+
+ // Decomposed characters
+ const string16 e_with_acute_combining_mark = WideToUTF16(L"e\u0301");
+ const string16 E_with_acute_combining_mark = WideToUTF16(L"E\u0301");
+ const string16 e_with_grave_combining_mark = WideToUTF16(L"e\u0300");
+ const string16 E_with_grave_combining_mark = WideToUTF16(L"E\u0300");
+ const string16 a_with_acute_combining_mark = WideToUTF16(L"a\u0301");
+
+ std::string default_locale(uloc_getDefault());
+ bool locale_is_posix = (default_locale == "en_US_POSIX");
+ if (locale_is_posix)
+ SetICUDefaultLocale("en_US");
+
+ size_t index = 0;
+ size_t length = 0;
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_base, e_with_acute_accent, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_accent.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_with_acute_accent, e_base, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_base.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_base, e_with_acute_combining_mark, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_combining_mark.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_with_acute_combining_mark, e_base, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_base.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_with_acute_combining_mark, e_with_acute_accent,
+ &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_accent.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_with_acute_accent, e_with_acute_combining_mark,
+ &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_combining_mark.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_with_acute_combining_mark, e_with_grave_combining_mark,
+ &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_grave_combining_mark.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_with_grave_combining_mark, e_with_acute_combining_mark,
+ &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_combining_mark.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_with_acute_combining_mark, e_with_grave_accent, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_grave_accent.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ e_with_grave_accent, e_with_acute_combining_mark, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_combining_mark.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ E_with_acute_accent, e_with_acute_accent, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_accent.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ E_with_grave_accent, e_with_acute_accent, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_accent.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ E_with_acute_combining_mark, e_with_grave_accent, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_grave_accent.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ E_with_grave_combining_mark, e_with_acute_accent, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_acute_accent.size(), length);
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(
+ E_base, e_with_grave_accent, &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(e_with_grave_accent.size(), length);
+
+ EXPECT_FALSE(StringSearchIgnoringCaseAndAccents(
+ a_with_acute_accent, e_with_acute_accent, &index, &length));
+
+ EXPECT_FALSE(StringSearchIgnoringCaseAndAccents(
+ a_with_acute_combining_mark, e_with_acute_combining_mark,
+ &index, &length));
+
+ if (locale_is_posix)
+ SetICUDefaultLocale(default_locale.data());
+}
+
+TEST(StringSearchTest, UnicodeLocaleDependent) {
+ // Base characters
+ const string16 a_base = WideToUTF16(L"a");
+
+ // Composed characters
+ const string16 a_with_ring = WideToUTF16(L"\u00e5");
+
+ EXPECT_TRUE(StringSearchIgnoringCaseAndAccents(a_base, a_with_ring, nullptr,
+ nullptr));
+
+ const char* default_locale = uloc_getDefault();
+ SetICUDefaultLocale("da");
+
+ EXPECT_FALSE(StringSearchIgnoringCaseAndAccents(a_base, a_with_ring, nullptr,
+ nullptr));
+
+ SetICUDefaultLocale(default_locale);
+}
+
+TEST(StringSearchTest, FixedPatternMultipleSearch) {
+ std::string default_locale(uloc_getDefault());
+ bool locale_is_posix = (default_locale == "en_US_POSIX");
+ if (locale_is_posix)
+ SetICUDefaultLocale("en_US");
+
+ size_t index = 0;
+ size_t length = 0;
+
+ // Search "hello" over multiple texts.
+ FixedPatternStringSearchIgnoringCaseAndAccents query(ASCIIToUTF16("hello"));
+ EXPECT_TRUE(query.Search(ASCIIToUTF16("12hello34"), &index, &length));
+ EXPECT_EQ(2U, index);
+ EXPECT_EQ(5U, length);
+ EXPECT_FALSE(query.Search(ASCIIToUTF16("bye"), &index, &length));
+ EXPECT_TRUE(query.Search(ASCIIToUTF16("hELLo"), &index, &length));
+ EXPECT_EQ(0U, index);
+ EXPECT_EQ(5U, length);
+
+ if (locale_is_posix)
+ SetICUDefaultLocale(default_locale.data());
+}
+
+} // namespace i18n
+} // namespace base
diff --git a/base/i18n/time_formatting.cc b/base/i18n/time_formatting.cc
new file mode 100644
index 0000000000..3a5394ae3a
--- /dev/null
+++ b/base/i18n/time_formatting.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/time_formatting.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/i18n/unicodestring.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "third_party/icu/source/common/unicode/utypes.h"
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
+#include "third_party/icu/source/i18n/unicode/dtitvfmt.h"
+#include "third_party/icu/source/i18n/unicode/dtptngen.h"
+#include "third_party/icu/source/i18n/unicode/fmtable.h"
+#include "third_party/icu/source/i18n/unicode/measfmt.h"
+#include "third_party/icu/source/i18n/unicode/smpdtfmt.h"
+
+namespace base {
+namespace {
+
+string16 TimeFormat(const icu::DateFormat* formatter,
+ const Time& time) {
+ DCHECK(formatter);
+ icu::UnicodeString date_string;
+
+ formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string);
+ return i18n::UnicodeStringToString16(date_string);
+}
+
+string16 TimeFormatWithoutAmPm(const icu::DateFormat* formatter,
+ const Time& time) {
+ DCHECK(formatter);
+ icu::UnicodeString time_string;
+
+ icu::FieldPosition ampm_field(icu::DateFormat::kAmPmField);
+ formatter->format(
+ static_cast<UDate>(time.ToDoubleT() * 1000), time_string, ampm_field);
+ int ampm_length = ampm_field.getEndIndex() - ampm_field.getBeginIndex();
+ if (ampm_length) {
+ int begin = ampm_field.getBeginIndex();
+ // Doesn't include any spacing before the field.
+ if (begin)
+ begin--;
+ time_string.removeBetween(begin, ampm_field.getEndIndex());
+ }
+ return i18n::UnicodeStringToString16(time_string);
+}
+
+icu::SimpleDateFormat CreateSimpleDateFormatter(const char* pattern) {
+ // Generate a locale-dependent format pattern. The generator will take
+ // care of locale-dependent formatting issues like which separator to
+ // use (some locales use '.' instead of ':'), and where to put the am/pm
+ // marker.
+ UErrorCode status = U_ZERO_ERROR;
+ std::unique_ptr<icu::DateTimePatternGenerator> generator(
+ icu::DateTimePatternGenerator::createInstance(status));
+ DCHECK(U_SUCCESS(status));
+ icu::UnicodeString generated_pattern =
+ generator->getBestPattern(icu::UnicodeString(pattern), status);
+ DCHECK(U_SUCCESS(status));
+
+ // Then, format the time using the generated pattern.
+ icu::SimpleDateFormat formatter(generated_pattern, status);
+ DCHECK(U_SUCCESS(status));
+
+ return formatter;
+}
+
+UMeasureFormatWidth DurationWidthToMeasureWidth(DurationFormatWidth width) {
+ switch (width) {
+ case DURATION_WIDTH_WIDE: return UMEASFMT_WIDTH_WIDE;
+ case DURATION_WIDTH_SHORT: return UMEASFMT_WIDTH_SHORT;
+ case DURATION_WIDTH_NARROW: return UMEASFMT_WIDTH_NARROW;
+ case DURATION_WIDTH_NUMERIC: return UMEASFMT_WIDTH_NUMERIC;
+ }
+ NOTREACHED();
+ return UMEASFMT_WIDTH_COUNT;
+}
+
+const char* DateFormatToString(DateFormat format) {
+ switch (format) {
+ case DATE_FORMAT_YEAR_MONTH:
+ return UDAT_YEAR_MONTH;
+ case DATE_FORMAT_MONTH_WEEKDAY_DAY:
+ return UDAT_MONTH_WEEKDAY_DAY;
+ }
+ NOTREACHED();
+ return UDAT_YEAR_MONTH_DAY;
+}
+
+} // namespace
+
+string16 TimeFormatTimeOfDay(const Time& time) {
+ // We can omit the locale parameter because the default should match
+ // Chrome's application locale.
+ std::unique_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createTimeInstance(icu::DateFormat::kShort));
+ return TimeFormat(formatter.get(), time);
+}
+
+string16 TimeFormatTimeOfDayWithMilliseconds(const Time& time) {
+ icu::SimpleDateFormat formatter = CreateSimpleDateFormatter("HmsSSS");
+ return TimeFormatWithoutAmPm(&formatter, time);
+}
+
+string16 TimeFormatTimeOfDayWithHourClockType(const Time& time,
+ HourClockType type,
+ AmPmClockType ampm) {
+ // Just redirect to the normal function if the default type matches the
+ // given type.
+ HourClockType default_type = GetHourClockType();
+ if (default_type == type && (type == k24HourClock || ampm == kKeepAmPm)) {
+ return TimeFormatTimeOfDay(time);
+ }
+
+ const char* base_pattern = (type == k12HourClock ? "ahm" : "Hm");
+ icu::SimpleDateFormat formatter = CreateSimpleDateFormatter(base_pattern);
+
+ if (ampm == kKeepAmPm) {
+ return TimeFormat(&formatter, time);
+ } else {
+ return TimeFormatWithoutAmPm(&formatter, time);
+ }
+}
+
+string16 TimeFormatShortDate(const Time& time) {
+ std::unique_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createDateInstance(icu::DateFormat::kMedium));
+ return TimeFormat(formatter.get(), time);
+}
+
+string16 TimeFormatShortDateNumeric(const Time& time) {
+ std::unique_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createDateInstance(icu::DateFormat::kShort));
+ return TimeFormat(formatter.get(), time);
+}
+
+string16 TimeFormatShortDateAndTime(const Time& time) {
+ std::unique_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createDateTimeInstance(icu::DateFormat::kShort));
+ return TimeFormat(formatter.get(), time);
+}
+
+string16 TimeFormatShortDateAndTimeWithTimeZone(const Time& time) {
+ std::unique_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createDateTimeInstance(icu::DateFormat::kShort,
+ icu::DateFormat::kLong));
+ return TimeFormat(formatter.get(), time);
+}
+
+string16 TimeFormatMonthAndYear(const Time& time) {
+ icu::SimpleDateFormat formatter =
+ CreateSimpleDateFormatter(DateFormatToString(DATE_FORMAT_YEAR_MONTH));
+ return TimeFormat(&formatter, time);
+}
+
+string16 TimeFormatFriendlyDateAndTime(const Time& time) {
+ std::unique_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createDateTimeInstance(icu::DateFormat::kFull));
+ return TimeFormat(formatter.get(), time);
+}
+
+string16 TimeFormatFriendlyDate(const Time& time) {
+ std::unique_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createDateInstance(icu::DateFormat::kFull));
+ return TimeFormat(formatter.get(), time);
+}
+
+string16 TimeFormatWithPattern(const Time& time, const char* pattern) {
+ icu::SimpleDateFormat formatter = CreateSimpleDateFormatter(pattern);
+ return TimeFormat(&formatter, time);
+}
+
+bool TimeDurationFormat(const TimeDelta time,
+ const DurationFormatWidth width,
+ string16* out) {
+ DCHECK(out);
+ UErrorCode status = U_ZERO_ERROR;
+ const int total_minutes = static_cast<int>(time.InSecondsF() / 60 + 0.5);
+ const int hours = total_minutes / 60;
+ const int minutes = total_minutes % 60;
+ UMeasureFormatWidth u_width = DurationWidthToMeasureWidth(width);
+
+ // TODO(derat): Delete the |status| checks and LOG(ERROR) calls throughout
+ // this function once the cause of http://crbug.com/677043 is tracked down.
+ const icu::Measure measures[] = {
+ icu::Measure(hours, icu::MeasureUnit::createHour(status), status),
+ icu::Measure(minutes, icu::MeasureUnit::createMinute(status), status)};
+ if (U_FAILURE(status)) {
+ LOG(ERROR) << "Creating MeasureUnit or Measure for " << hours << "h"
+ << minutes << "m failed: " << u_errorName(status);
+ return false;
+ }
+
+ icu::MeasureFormat measure_format(icu::Locale::getDefault(), u_width, status);
+ if (U_FAILURE(status)) {
+ LOG(ERROR) << "Creating MeasureFormat for "
+ << icu::Locale::getDefault().getName()
+ << " failed: " << u_errorName(status);
+ return false;
+ }
+
+ icu::UnicodeString formatted;
+ icu::FieldPosition ignore(icu::FieldPosition::DONT_CARE);
+ measure_format.formatMeasures(measures, 2, formatted, ignore, status);
+ if (U_FAILURE(status)) {
+ LOG(ERROR) << "formatMeasures failed: " << u_errorName(status);
+ return false;
+ }
+
+ *out = i18n::UnicodeStringToString16(formatted);
+ return true;
+}
+
+bool TimeDurationFormatWithSeconds(const TimeDelta time,
+ const DurationFormatWidth width,
+ string16* out) {
+ DCHECK(out);
+ UErrorCode status = U_ZERO_ERROR;
+ const int64_t total_seconds = static_cast<int>(time.InSecondsF() + 0.5);
+ const int hours = total_seconds / 3600;
+ const int minutes = (total_seconds - hours * 3600) / 60;
+ const int seconds = total_seconds % 60;
+ UMeasureFormatWidth u_width = DurationWidthToMeasureWidth(width);
+
+ const icu::Measure measures[] = {
+ icu::Measure(hours, icu::MeasureUnit::createHour(status), status),
+ icu::Measure(minutes, icu::MeasureUnit::createMinute(status), status),
+ icu::Measure(seconds, icu::MeasureUnit::createSecond(status), status)};
+ icu::MeasureFormat measure_format(icu::Locale::getDefault(), u_width, status);
+ icu::UnicodeString formatted;
+ icu::FieldPosition ignore(icu::FieldPosition::DONT_CARE);
+ measure_format.formatMeasures(measures, 3, formatted, ignore, status);
+ *out = i18n::UnicodeStringToString16(formatted);
+ return U_SUCCESS(status) == TRUE;
+}
+
+string16 DateIntervalFormat(const Time& begin_time,
+ const Time& end_time,
+ DateFormat format) {
+ UErrorCode status = U_ZERO_ERROR;
+
+ std::unique_ptr<icu::DateIntervalFormat> formatter(
+ icu::DateIntervalFormat::createInstance(DateFormatToString(format),
+ status));
+
+ icu::FieldPosition pos = 0;
+ UDate start_date = static_cast<UDate>(begin_time.ToDoubleT() * 1000);
+ UDate end_date = static_cast<UDate>(end_time.ToDoubleT() * 1000);
+ icu::DateInterval interval(start_date, end_date);
+ icu::UnicodeString formatted;
+ formatter->format(&interval, formatted, pos, status);
+ return i18n::UnicodeStringToString16(formatted);
+}
+
+HourClockType GetHourClockType() {
+ // TODO(satorux,jshin): Rework this with ures_getByKeyWithFallback()
+ // once it becomes public. The short time format can be found at
+ // "calendar/gregorian/DateTimePatterns/3" in the resources.
+ std::unique_ptr<icu::SimpleDateFormat> formatter(
+ static_cast<icu::SimpleDateFormat*>(
+ icu::DateFormat::createTimeInstance(icu::DateFormat::kShort)));
+ // Retrieve the short time format.
+ icu::UnicodeString pattern_unicode;
+ formatter->toPattern(pattern_unicode);
+
+ // Determine what hour clock type the current locale uses, by checking
+ // "a" (am/pm marker) in the short time format. This is reliable as "a"
+ // is used by all of 12-hour clock formats, but not any of 24-hour clock
+ // formats, as shown below.
+ //
+ // % grep -A4 DateTimePatterns third_party/icu/source/data/locales/*.txt |
+ // grep -B1 -- -- |grep -v -- '--' |
+ // perl -nle 'print $1 if /^\S+\s+"(.*)"/' |sort -u
+ //
+ // H.mm
+ // H:mm
+ // HH.mm
+ // HH:mm
+ // a h:mm
+ // ah:mm
+ // ahh:mm
+ // h-mm a
+ // h:mm a
+ // hh:mm a
+ //
+ // See http://userguide.icu-project.org/formatparse/datetime for details
+ // about the date/time format syntax.
+ if (pattern_unicode.indexOf('a') == -1) {
+ return k24HourClock;
+ } else {
+ return k12HourClock;
+ }
+}
+
+} // namespace base
diff --git a/base/i18n/time_formatting.h b/base/i18n/time_formatting.h
new file mode 100644
index 0000000000..41793b339c
--- /dev/null
+++ b/base/i18n/time_formatting.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Basic time formatting methods. These methods use the current locale
+// formatting for displaying the time.
+
+#ifndef BASE_I18N_TIME_FORMATTING_H_
+#define BASE_I18N_TIME_FORMATTING_H_
+
+#include "base/compiler_specific.h"
+#include "base/i18n/base_i18n_export.h"
+#include "base/strings/string16.h"
+
+namespace base {
+
+class Time;
+class TimeDelta;
+
+// Argument type used to specify the hour clock type.
+enum HourClockType {
+ k12HourClock, // Uses 1-12. e.g., "3:07 PM"
+ k24HourClock, // Uses 0-23. e.g., "15:07"
+};
+
+// Argument type used to specify whether or not to include AM/PM sign.
+enum AmPmClockType {
+ kDropAmPm, // Drops AM/PM sign. e.g., "3:07"
+ kKeepAmPm, // Keeps AM/PM sign. e.g., "3:07 PM"
+};
+
+// Should match UMeasureFormatWidth in measfmt.h; replicated here to avoid
+// requiring third_party/icu dependencies with this file.
+enum DurationFormatWidth {
+ DURATION_WIDTH_WIDE, // "3 hours, 7 minutes"
+ DURATION_WIDTH_SHORT, // "3 hr, 7 min"
+ DURATION_WIDTH_NARROW, // "3h 7m"
+ DURATION_WIDTH_NUMERIC // "3:07"
+};
+
+// Date formats from third_party/icu/source/i18n/unicode/udat.h. Add more as
+// necessary.
+enum DateFormat {
+ // November 2007
+ DATE_FORMAT_YEAR_MONTH,
+ // Tuesday, 7 November
+ DATE_FORMAT_MONTH_WEEKDAY_DAY,
+};
+
+// TODO(derat@chromium.org): Update all of these functions to return boolean
+// "success" values and use out-params for formatted strings:
+// http://crbug.com/698802
+
+// Returns the time of day, e.g., "3:07 PM".
+BASE_I18N_EXPORT string16 TimeFormatTimeOfDay(const Time& time);
+
+// Returns the time of day in 24-hour clock format with millisecond accuracy,
+// e.g., "15:07:30.568"
+BASE_I18N_EXPORT string16 TimeFormatTimeOfDayWithMilliseconds(const Time& time);
+
+// Returns the time of day in the specified hour clock type. e.g.
+// "3:07 PM" (type == k12HourClock, ampm == kKeepAmPm).
+// "3:07" (type == k12HourClock, ampm == kDropAmPm).
+// "15:07" (type == k24HourClock).
+BASE_I18N_EXPORT string16 TimeFormatTimeOfDayWithHourClockType(
+ const Time& time,
+ HourClockType type,
+ AmPmClockType ampm);
+
+// Returns a shortened date, e.g. "Nov 7, 2007"
+BASE_I18N_EXPORT string16 TimeFormatShortDate(const Time& time);
+
+// Returns a numeric date such as 12/13/52.
+BASE_I18N_EXPORT string16 TimeFormatShortDateNumeric(const Time& time);
+
+// Returns a numeric date and time such as "12/13/52 2:44:30 PM".
+BASE_I18N_EXPORT string16 TimeFormatShortDateAndTime(const Time& time);
+
+// Returns a month and year, e.g. "November 2007"
+BASE_I18N_EXPORT string16 TimeFormatMonthAndYear(const Time& time);
+
+// Returns a numeric date and time with time zone such as
+// "12/13/52 2:44:30 PM PST".
+BASE_I18N_EXPORT string16
+TimeFormatShortDateAndTimeWithTimeZone(const Time& time);
+
+// Formats a time in a friendly sentence format, e.g.
+// "Monday, March 6, 2008 2:44:30 PM".
+BASE_I18N_EXPORT string16 TimeFormatFriendlyDateAndTime(const Time& time);
+
+// Formats a time in a friendly sentence format, e.g.
+// "Monday, March 6, 2008".
+BASE_I18N_EXPORT string16 TimeFormatFriendlyDate(const Time& time);
+
+// Formats a time using a skeleton to produce a format for different locales
+// when an unusual time format is needed, e.g. "Feb. 2, 18:00".
+//
+// See http://userguide.icu-project.org/formatparse/datetime for details.
+BASE_I18N_EXPORT string16 TimeFormatWithPattern(const Time& time,
+ const char* pattern);
+
+// Formats a time duration of hours and minutes into various formats, e.g.,
+// "3:07" or "3 hours, 7 minutes", and returns true on success. See
+// DurationFormatWidth for details.
+//
+// Please don't use width = DURATION_WIDTH_NUMERIC when the time duration
+// can possibly be larger than 24h, as the hour value will be cut below 24
+// after formatting.
+// TODO(chengx): fix function output when width = DURATION_WIDTH_NUMERIC
+// (http://crbug.com/675791)
+BASE_I18N_EXPORT bool TimeDurationFormat(const TimeDelta time,
+ const DurationFormatWidth width,
+ string16* out) WARN_UNUSED_RESULT;
+
+// Formats a time duration of hours, minutes and seconds into various formats,
+// e.g., "3:07:30" or "3 hours, 7 minutes, 30 seconds", and returns true on
+// success. See DurationFormatWidth for details.
+//
+// Please don't use width = DURATION_WIDTH_NUMERIC when the time duration
+// can possibly be larger than 24h, as the hour value will be cut below 24
+// after formatting.
+// TODO(chengx): fix function output when width = DURATION_WIDTH_NUMERIC
+// (http://crbug.com/675791)
+BASE_I18N_EXPORT bool TimeDurationFormatWithSeconds(
+ const TimeDelta time,
+ const DurationFormatWidth width,
+ string16* out) WARN_UNUSED_RESULT;
+
+// Formats a date interval into various formats, e.g. "2 December - 4 December"
+// or "March 2016 - December 2016". See DateFormat for details.
+BASE_I18N_EXPORT string16 DateIntervalFormat(const Time& begin_time,
+ const Time& end_time,
+ DateFormat format);
+
+// Gets the hour clock type of the current locale. e.g.
+// k12HourClock (en-US).
+// k24HourClock (en-GB).
+BASE_I18N_EXPORT HourClockType GetHourClockType();
+
+} // namespace base
+
+#endif // BASE_I18N_TIME_FORMATTING_H_
diff --git a/base/i18n/time_formatting_unittest.cc b/base/i18n/time_formatting_unittest.cc
new file mode 100644
index 0000000000..29b25972d2
--- /dev/null
+++ b/base/i18n/time_formatting_unittest.cc
@@ -0,0 +1,433 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/time_formatting.h"
+
+#include <memory>
+
+#include "base/i18n/rtl.h"
+#include "base/i18n/unicodestring.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/icu_test_util.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/common/unicode/uversion.h"
+#include "third_party/icu/source/i18n/unicode/calendar.h"
+#include "third_party/icu/source/i18n/unicode/timezone.h"
+#include "third_party/icu/source/i18n/unicode/tzfmt.h"
+
+namespace base {
+namespace {
+
+const Time::Exploded kTestDateTimeExploded = {
+ 2011, 4, 6, 30, // Sat, Apr 30, 2011
+ 22, 42, 7, 0 // 22:42:07.000 in UTC = 15:42:07 in US PDT.
+};
+
+// Returns difference between the local time and GMT formatted as string.
+// This function gets |time| because the difference depends on time,
+// see https://en.wikipedia.org/wiki/Daylight_saving_time for details.
+string16 GetShortTimeZone(const Time& time) {
+ UErrorCode status = U_ZERO_ERROR;
+ std::unique_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault());
+ std::unique_ptr<icu::TimeZoneFormat> zone_formatter(
+ icu::TimeZoneFormat::createInstance(icu::Locale::getDefault(), status));
+ EXPECT_TRUE(U_SUCCESS(status));
+ icu::UnicodeString name;
+ zone_formatter->format(UTZFMT_STYLE_SPECIFIC_SHORT, *zone,
+ static_cast<UDate>(time.ToDoubleT() * 1000),
+ name, nullptr);
+ return i18n::UnicodeStringToString16(name);
+}
+
+// Calls TimeDurationFormat() with |delta| and |width| and returns the resulting
+// string. On failure, adds a failed expectation and returns an empty string.
+string16 TimeDurationFormatString(const TimeDelta& delta,
+ DurationFormatWidth width) {
+ string16 str;
+ EXPECT_TRUE(TimeDurationFormat(delta, width, &str))
+ << "Failed to format " << delta.ToInternalValue() << " with width "
+ << width;
+ return str;
+}
+
+// Calls TimeDurationFormatWithSeconds() with |delta| and |width| and returns
+// the resulting string. On failure, adds a failed expectation and returns an
+// empty string.
+string16 TimeDurationFormatWithSecondsString(const TimeDelta& delta,
+ DurationFormatWidth width) {
+ string16 str;
+ EXPECT_TRUE(TimeDurationFormatWithSeconds(delta, width, &str))
+ << "Failed to format " << delta.ToInternalValue() << " with width "
+ << width;
+ return str;
+}
+
+class ScopedRestoreDefaultTimezone {
+ public:
+ ScopedRestoreDefaultTimezone(const char* zoneid) {
+ original_zone_.reset(icu::TimeZone::createDefault());
+ icu::TimeZone::adoptDefault(icu::TimeZone::createTimeZone(zoneid));
+ }
+ ~ScopedRestoreDefaultTimezone() {
+ icu::TimeZone::adoptDefault(original_zone_.release());
+ }
+
+ ScopedRestoreDefaultTimezone(const ScopedRestoreDefaultTimezone&) = delete;
+ ScopedRestoreDefaultTimezone& operator=(const ScopedRestoreDefaultTimezone&) =
+ delete;
+
+ private:
+ std::unique_ptr<icu::TimeZone> original_zone_;
+};
+
+TEST(TimeFormattingTest, TimeFormatTimeOfDayDefault12h) {
+ // Test for a locale defaulted to 12h clock.
+ // As an instance, we use third_party/icu/source/data/locales/en.txt.
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("en_US");
+ ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");
+
+ Time time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestDateTimeExploded, &time));
+ string16 clock24h(ASCIIToUTF16("15:42"));
+ string16 clock12h_pm(ASCIIToUTF16("3:42 PM"));
+ string16 clock12h(ASCIIToUTF16("3:42"));
+ string16 clock24h_millis(ASCIIToUTF16("15:42:07.000"));
+
+ // The default is 12h clock.
+ EXPECT_EQ(clock12h_pm, TimeFormatTimeOfDay(time));
+ EXPECT_EQ(clock24h_millis, TimeFormatTimeOfDayWithMilliseconds(time));
+ EXPECT_EQ(k12HourClock, GetHourClockType());
+ // k{Keep,Drop}AmPm should not affect for 24h clock.
+ EXPECT_EQ(clock24h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k24HourClock,
+ kKeepAmPm));
+ EXPECT_EQ(clock24h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k24HourClock,
+ kDropAmPm));
+ // k{Keep,Drop}AmPm affects for 12h clock.
+ EXPECT_EQ(clock12h_pm,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k12HourClock,
+ kKeepAmPm));
+ EXPECT_EQ(clock12h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k12HourClock,
+ kDropAmPm));
+}
+
+TEST(TimeFormattingTest, TimeFormatTimeOfDayDefault24h) {
+ // Test for a locale defaulted to 24h clock.
+ // As an instance, we use third_party/icu/source/data/locales/en_GB.txt.
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("en_GB");
+ ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");
+
+ Time time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestDateTimeExploded, &time));
+ string16 clock24h(ASCIIToUTF16("15:42"));
+ string16 clock12h_pm(ASCIIToUTF16("3:42 pm"));
+ string16 clock12h(ASCIIToUTF16("3:42"));
+ string16 clock24h_millis(ASCIIToUTF16("15:42:07.000"));
+
+ // The default is 24h clock.
+ EXPECT_EQ(clock24h, TimeFormatTimeOfDay(time));
+ EXPECT_EQ(clock24h_millis, TimeFormatTimeOfDayWithMilliseconds(time));
+ EXPECT_EQ(k24HourClock, GetHourClockType());
+ // k{Keep,Drop}AmPm should not affect for 24h clock.
+ EXPECT_EQ(clock24h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k24HourClock,
+ kKeepAmPm));
+ EXPECT_EQ(clock24h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k24HourClock,
+ kDropAmPm));
+ // k{Keep,Drop}AmPm affects for 12h clock.
+ EXPECT_EQ(clock12h_pm,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k12HourClock,
+ kKeepAmPm));
+ EXPECT_EQ(clock12h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k12HourClock,
+ kDropAmPm));
+}
+
+TEST(TimeFormattingTest, TimeFormatTimeOfDayJP) {
+ // Test for a locale that uses different mark than "AM" and "PM".
+ // As an instance, we use third_party/icu/source/data/locales/ja.txt.
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("ja_JP");
+ ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");
+
+ Time time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestDateTimeExploded, &time));
+ string16 clock24h(ASCIIToUTF16("15:42"));
+ string16 clock12h_pm(UTF8ToUTF16(u8"午後3:42"));
+ string16 clock12h(ASCIIToUTF16("3:42"));
+
+ // The default is 24h clock.
+ EXPECT_EQ(clock24h, TimeFormatTimeOfDay(time));
+ EXPECT_EQ(k24HourClock, GetHourClockType());
+ // k{Keep,Drop}AmPm should not affect for 24h clock.
+ EXPECT_EQ(clock24h, TimeFormatTimeOfDayWithHourClockType(time, k24HourClock,
+ kKeepAmPm));
+ EXPECT_EQ(clock24h, TimeFormatTimeOfDayWithHourClockType(time, k24HourClock,
+ kDropAmPm));
+ // k{Keep,Drop}AmPm affects for 12h clock.
+ EXPECT_EQ(clock12h_pm, TimeFormatTimeOfDayWithHourClockType(
+ time, k12HourClock, kKeepAmPm));
+ EXPECT_EQ(clock12h, TimeFormatTimeOfDayWithHourClockType(time, k12HourClock,
+ kDropAmPm));
+}
+
+TEST(TimeFormattingTest, TimeFormatTimeOfDayDE) {
+ // German uses 24h by default, but uses 'AM', 'PM' for 12h format.
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("de");
+ ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");
+
+ Time time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestDateTimeExploded, &time));
+ string16 clock24h(ASCIIToUTF16("15:42"));
+ string16 clock12h_pm(UTF8ToUTF16("3:42 PM"));
+ string16 clock12h(ASCIIToUTF16("3:42"));
+
+ // The default is 24h clock.
+ EXPECT_EQ(clock24h, TimeFormatTimeOfDay(time));
+ EXPECT_EQ(k24HourClock, GetHourClockType());
+ // k{Keep,Drop}AmPm should not affect for 24h clock.
+ EXPECT_EQ(clock24h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k24HourClock,
+ kKeepAmPm));
+ EXPECT_EQ(clock24h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k24HourClock,
+ kDropAmPm));
+ // k{Keep,Drop}AmPm affects for 12h clock.
+ EXPECT_EQ(clock12h_pm,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k12HourClock,
+ kKeepAmPm));
+ EXPECT_EQ(clock12h,
+ TimeFormatTimeOfDayWithHourClockType(time,
+ k12HourClock,
+ kDropAmPm));
+}
+
+TEST(TimeFormattingTest, TimeFormatDateUS) {
+ // See third_party/icu/source/data/locales/en.txt.
+ // The date patterns are "EEEE, MMMM d, y", "MMM d, y", and "M/d/yy".
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("en_US");
+ ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");
+
+ Time time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestDateTimeExploded, &time));
+
+ EXPECT_EQ(ASCIIToUTF16("Apr 30, 2011"), TimeFormatShortDate(time));
+ EXPECT_EQ(ASCIIToUTF16("4/30/11"), TimeFormatShortDateNumeric(time));
+
+ EXPECT_EQ(ASCIIToUTF16("4/30/11, 3:42:07 PM"),
+ TimeFormatShortDateAndTime(time));
+ EXPECT_EQ(ASCIIToUTF16("4/30/11, 3:42:07 PM ") + GetShortTimeZone(time),
+ TimeFormatShortDateAndTimeWithTimeZone(time));
+
+ EXPECT_EQ(ASCIIToUTF16("April 2011"), TimeFormatMonthAndYear(time));
+
+ EXPECT_EQ(ASCIIToUTF16("Saturday, April 30, 2011 at 3:42:07 PM"),
+ TimeFormatFriendlyDateAndTime(time));
+
+ EXPECT_EQ(ASCIIToUTF16("Saturday, April 30, 2011"),
+ TimeFormatFriendlyDate(time));
+}
+
+TEST(TimeFormattingTest, TimeFormatDateGB) {
+ // See third_party/icu/source/data/locales/en_GB.txt.
+ // The date patterns are "EEEE, d MMMM y", "d MMM y", and "dd/MM/yyyy".
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("en_GB");
+ ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");
+
+ Time time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestDateTimeExploded, &time));
+
+ EXPECT_EQ(ASCIIToUTF16("30 Apr 2011"), TimeFormatShortDate(time));
+ EXPECT_EQ(ASCIIToUTF16("30/04/2011"), TimeFormatShortDateNumeric(time));
+ EXPECT_EQ(ASCIIToUTF16("30/04/2011, 15:42:07"),
+ TimeFormatShortDateAndTime(time));
+ EXPECT_EQ(ASCIIToUTF16("30/04/2011, 15:42:07 ") + GetShortTimeZone(time),
+ TimeFormatShortDateAndTimeWithTimeZone(time));
+ EXPECT_EQ(ASCIIToUTF16("April 2011"), TimeFormatMonthAndYear(time));
+ EXPECT_EQ(ASCIIToUTF16("Saturday, 30 April 2011 at 15:42:07"),
+ TimeFormatFriendlyDateAndTime(time));
+ EXPECT_EQ(ASCIIToUTF16("Saturday, 30 April 2011"),
+ TimeFormatFriendlyDate(time));
+}
+
+TEST(TimeFormattingTest, TimeFormatWithPattern) {
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");
+
+ Time time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestDateTimeExploded, &time));
+
+ i18n::SetICUDefaultLocale("en_US");
+ EXPECT_EQ(ASCIIToUTF16("Apr 30, 2011"), TimeFormatWithPattern(time, "yMMMd"));
+ EXPECT_EQ(ASCIIToUTF16("April 30, 3:42:07 PM"),
+ TimeFormatWithPattern(time, "MMMMdjmmss"));
+
+ i18n::SetICUDefaultLocale("en_GB");
+ EXPECT_EQ(ASCIIToUTF16("30 Apr 2011"), TimeFormatWithPattern(time, "yMMMd"));
+ EXPECT_EQ(ASCIIToUTF16("30 April, 15:42:07"),
+ TimeFormatWithPattern(time, "MMMMdjmmss"));
+
+ i18n::SetICUDefaultLocale("ja_JP");
+ EXPECT_EQ(UTF8ToUTF16(u8"2011年4月30日"),
+ TimeFormatWithPattern(time, "yMMMd"));
+ EXPECT_EQ(UTF8ToUTF16(u8"4月30日 15:42:07"),
+ TimeFormatWithPattern(time, "MMMMdjmmss"));
+}
+
+TEST(TimeFormattingTest, TimeDurationFormat) {
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ TimeDelta delta = TimeDelta::FromMinutes(15 * 60 + 42);
+
+ // US English.
+ i18n::SetICUDefaultLocale("en_US");
+ EXPECT_EQ(ASCIIToUTF16("15 hours, 42 minutes"),
+ TimeDurationFormatString(delta, DURATION_WIDTH_WIDE));
+ EXPECT_EQ(ASCIIToUTF16("15 hr, 42 min"),
+ TimeDurationFormatString(delta, DURATION_WIDTH_SHORT));
+ EXPECT_EQ(ASCIIToUTF16("15h 42m"),
+ TimeDurationFormatString(delta, DURATION_WIDTH_NARROW));
+ EXPECT_EQ(ASCIIToUTF16("15:42"),
+ TimeDurationFormatString(delta, DURATION_WIDTH_NUMERIC));
+
+ // Danish, with Latin alphabet but different abbreviations and punctuation.
+ i18n::SetICUDefaultLocale("da");
+ EXPECT_EQ(ASCIIToUTF16("15 timer og 42 minutter"),
+ TimeDurationFormatString(delta, DURATION_WIDTH_WIDE));
+ EXPECT_EQ(ASCIIToUTF16("15 t og 42 min."),
+ TimeDurationFormatString(delta, DURATION_WIDTH_SHORT));
+ EXPECT_EQ(ASCIIToUTF16("15 t og 42 min"),
+ TimeDurationFormatString(delta, DURATION_WIDTH_NARROW));
+ EXPECT_EQ(ASCIIToUTF16("15.42"),
+ TimeDurationFormatString(delta, DURATION_WIDTH_NUMERIC));
+
+ // Persian, with non-Arabic numbers.
+ i18n::SetICUDefaultLocale("fa");
+ string16 fa_wide = UTF8ToUTF16(
+ u8"\u06f1\u06f5 \u0633\u0627\u0639\u062a \u0648 \u06f4\u06f2 \u062f\u0642"
+ u8"\u06cc\u0642\u0647");
+ string16 fa_short = UTF8ToUTF16(
+ u8"\u06f1\u06f5 \u0633\u0627\u0639\u062a\u060c\u200f \u06f4\u06f2 \u062f"
+ u8"\u0642\u06cc\u0642\u0647");
+ string16 fa_narrow = UTF8ToUTF16(
+ u8"\u06f1\u06f5 \u0633\u0627\u0639\u062a \u06f4\u06f2 \u062f\u0642\u06cc"
+ u8"\u0642\u0647");
+ string16 fa_numeric = UTF8ToUTF16(u8"\u06f1\u06f5:\u06f4\u06f2");
+ EXPECT_EQ(fa_wide, TimeDurationFormatString(delta, DURATION_WIDTH_WIDE));
+ EXPECT_EQ(fa_short, TimeDurationFormatString(delta, DURATION_WIDTH_SHORT));
+ EXPECT_EQ(fa_narrow, TimeDurationFormatString(delta, DURATION_WIDTH_NARROW));
+ EXPECT_EQ(fa_numeric,
+ TimeDurationFormatString(delta, DURATION_WIDTH_NUMERIC));
+}
+
+TEST(TimeFormattingTest, TimeDurationFormatWithSeconds) {
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+
+ // US English.
+ i18n::SetICUDefaultLocale("en_US");
+
+ // Test different formats.
+ TimeDelta delta = TimeDelta::FromSeconds(15 * 3600 + 42 * 60 + 30);
+ EXPECT_EQ(ASCIIToUTF16("15 hours, 42 minutes, 30 seconds"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_WIDE));
+ EXPECT_EQ(ASCIIToUTF16("15 hr, 42 min, 30 sec"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_SHORT));
+ EXPECT_EQ(ASCIIToUTF16("15h 42m 30s"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_NARROW));
+ EXPECT_EQ(ASCIIToUTF16("15:42:30"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_NUMERIC));
+
+ // Test edge case when hour >= 100.
+ delta = TimeDelta::FromSeconds(125 * 3600 + 42 * 60 + 30);
+ EXPECT_EQ(ASCIIToUTF16("125 hours, 42 minutes, 30 seconds"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_WIDE));
+ EXPECT_EQ(ASCIIToUTF16("125 hr, 42 min, 30 sec"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_SHORT));
+ EXPECT_EQ(ASCIIToUTF16("125h 42m 30s"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_NARROW));
+
+ // Test edge case when minute = 0.
+ delta = TimeDelta::FromSeconds(15 * 3600 + 0 * 60 + 30);
+ EXPECT_EQ(ASCIIToUTF16("15 hours, 0 minutes, 30 seconds"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_WIDE));
+ EXPECT_EQ(ASCIIToUTF16("15 hr, 0 min, 30 sec"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_SHORT));
+ EXPECT_EQ(ASCIIToUTF16("15h 0m 30s"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_NARROW));
+ EXPECT_EQ(ASCIIToUTF16("15:00:30"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_NUMERIC));
+
+ // Test edge case when second = 0.
+ delta = TimeDelta::FromSeconds(15 * 3600 + 42 * 60 + 0);
+ EXPECT_EQ(ASCIIToUTF16("15 hours, 42 minutes, 0 seconds"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_WIDE));
+ EXPECT_EQ(ASCIIToUTF16("15 hr, 42 min, 0 sec"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_SHORT));
+ EXPECT_EQ(ASCIIToUTF16("15h 42m 0s"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_NARROW));
+ EXPECT_EQ(ASCIIToUTF16("15:42:00"),
+ TimeDurationFormatWithSecondsString(delta, DURATION_WIDTH_NUMERIC));
+}
+
+TEST(TimeFormattingTest, TimeIntervalFormat) {
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+ i18n::SetICUDefaultLocale("en_US");
+ ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");
+
+ const Time::Exploded kTestIntervalEndTimeExploded = {
+ 2011, 5, 6, 28, // Sat, May 28, 2012
+ 22, 42, 7, 0 // 22:42:07.000
+ };
+
+ Time begin_time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestDateTimeExploded, &begin_time));
+ Time end_time;
+ EXPECT_TRUE(Time::FromUTCExploded(kTestIntervalEndTimeExploded, &end_time));
+
+ EXPECT_EQ(
+ UTF8ToUTF16(u8"Saturday, April 30 – Saturday, May 28"),
+ DateIntervalFormat(begin_time, end_time, DATE_FORMAT_MONTH_WEEKDAY_DAY));
+
+ const Time::Exploded kTestIntervalBeginTimeExploded = {
+ 2011, 5, 1, 16, // Mon, May 16, 2012
+ 22, 42, 7, 0 // 22:42:07.000
+ };
+ EXPECT_TRUE(
+ Time::FromUTCExploded(kTestIntervalBeginTimeExploded, &begin_time));
+ EXPECT_EQ(
+ UTF8ToUTF16(u8"Monday, May 16 – Saturday, May 28"),
+ DateIntervalFormat(begin_time, end_time, DATE_FORMAT_MONTH_WEEKDAY_DAY));
+
+ i18n::SetICUDefaultLocale("en_GB");
+ EXPECT_EQ(
+ UTF8ToUTF16(u8"Monday 16 – Saturday 28 May"),
+ DateIntervalFormat(begin_time, end_time, DATE_FORMAT_MONTH_WEEKDAY_DAY));
+
+ i18n::SetICUDefaultLocale("ja");
+ EXPECT_EQ(
+ UTF8ToUTF16(u8"5月16日(月曜日)~28日(土曜日)"),
+ DateIntervalFormat(begin_time, end_time, DATE_FORMAT_MONTH_WEEKDAY_DAY));
+}
+
+} // namespace
+} // namespace base
diff --git a/base/i18n/timezone.cc b/base/i18n/timezone.cc
new file mode 100644
index 0000000000..8624e07e7c
--- /dev/null
+++ b/base/i18n/timezone.cc
@@ -0,0 +1,34 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/timezone.h"
+
+#include <memory>
+#include <string>
+
+#include "third_party/icu/source/common/unicode/unistr.h"
+#include "third_party/icu/source/i18n/unicode/timezone.h"
+
+namespace base {
+
+std::string CountryCodeForCurrentTimezone() {
+ std::unique_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault());
+ icu::UnicodeString id;
+ // ICU returns '001' (world) for Etc/GMT. Preserve the old behavior
+ // only for Etc/GMT while returning an empty string for Etc/UTC and
+ // Etc/UCT because they're less likely to be chosen by mistake in UK in
+ // place of Europe/London (Briitish Time).
+ if (zone->getID(id) == UNICODE_STRING_SIMPLE("Etc/GMT"))
+ return "GB";
+ char region_code[4];
+ UErrorCode status = U_ZERO_ERROR;
+ int length = zone->getRegion(id, region_code, 4, status);
+ // Return an empty string if region_code is a 3-digit numeric code such
+ // as 001 (World) for Etc/UTC, Etc/UCT.
+ return (U_SUCCESS(status) && length == 2)
+ ? std::string(region_code, static_cast<size_t>(length))
+ : std::string();
+}
+
+} // namespace base
diff --git a/base/i18n/timezone.h b/base/i18n/timezone.h
new file mode 100644
index 0000000000..7557d44f36
--- /dev/null
+++ b/base/i18n/timezone.h
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_TIMEZONE_H_
+#define BASE_I18N_TIMEZONE_H_
+
+#include <string>
+
+#include "base/i18n/base_i18n_export.h"
+
+namespace base {
+
+// Checks the system timezone and turns it into a two-character ISO 3166 country
+// code. This may fail (for example, it used to always fail on Android), in
+// which case it will return an empty string. It'll also return an empty string
+// when the timezone is Etc/UTC or Etc/UCT, but will return 'GB" for Etc/GMT
+// because people in the UK tends to select Etc/GMT by mistake instead of
+// Europe/London (British Time).
+BASE_I18N_EXPORT std::string CountryCodeForCurrentTimezone();
+
+} // namespace base
+
+#endif // BASE_I18N_TIMEZONE_H_
diff --git a/base/i18n/timezone_unittest.cc b/base/i18n/timezone_unittest.cc
new file mode 100644
index 0000000000..57467dced1
--- /dev/null
+++ b/base/i18n/timezone_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/timezone.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+TEST(TimezoneTest, CountryCodeForCurrentTimezone) {
+ std::string country_code = CountryCodeForCurrentTimezone();
+ // On some systems (such as Android or some flavors of Linux), ICU may come up
+ // empty. With https://chromium-review.googlesource.com/c/512282/ , ICU will
+ // not fail any more. See also http://bugs.icu-project.org/trac/ticket/13208 .
+ // Even with that, ICU returns '001' (world) for region-agnostic timezones
+ // such as Etc/UTC and |CountryCodeForCurrentTimezone| returns an empty
+ // string so that the next fallback can be tried by a customer.
+ // TODO(jshin): Revise this to test for actual timezones using
+ // use ScopedRestoreICUDefaultTimezone.
+ if (!country_code.empty())
+ EXPECT_EQ(2U, country_code.size()) << "country_code = " << country_code;
+}
+
+} // namespace
+} // namespace base
diff --git a/base/i18n/unicodestring.h b/base/i18n/unicodestring.h
new file mode 100644
index 0000000000..b62c5264de
--- /dev/null
+++ b/base/i18n/unicodestring.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_UNICODESTRING_H_
+#define BASE_I18N_UNICODESTRING_H_
+
+#include "base/strings/string16.h"
+#include "third_party/icu/source/common/unicode/unistr.h"
+#include "third_party/icu/source/common/unicode/uvernum.h"
+
+#if U_ICU_VERSION_MAJOR_NUM >= 59
+#include "third_party/icu/source/common/unicode/char16ptr.h"
+#endif
+
+namespace base {
+namespace i18n {
+
+inline string16 UnicodeStringToString16(const icu::UnicodeString& unistr) {
+#if U_ICU_VERSION_MAJOR_NUM >= 59
+ return base::string16(icu::toUCharPtr(unistr.getBuffer()),
+ static_cast<size_t>(unistr.length()));
+#else
+ return base::string16(unistr.getBuffer(),
+ static_cast<size_t>(unistr.length()));
+#endif
+}
+
+} // namespace i18n
+} // namespace base
+
+#endif // BASE_UNICODESTRING_H_
diff --git a/base/i18n/utf8_validator_tables.cc b/base/i18n/utf8_validator_tables.cc
new file mode 100644
index 0000000000..913afc77c3
--- /dev/null
+++ b/base/i18n/utf8_validator_tables.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is auto-generated by build_utf8_validator_tables.
+// DO NOT EDIT.
+
+#include "base/i18n/utf8_validator_tables.h"
+
+namespace base {
+namespace internal {
+
+const uint8_t kUtf8ValidatorTables[] = {
+ // State 0, offset 0x00
+ 0x00, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x08
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x10
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x18
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x20
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x28
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x30
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x38
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x40
+ 0x81, 0x81, 0x81, 0x83, 0x83, 0x83, 0x83, 0x83, // 0x48
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, // 0x50
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, // 0x58
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, // 0x60
+ 0x83, 0x86, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, // 0x68
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8e, 0x8b, // 0x70
+ 0x8b, 0x93, 0x9c, 0x9c, 0x9c, 0x9f, 0x81, 0x81, // 0x78
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0x80
+ 0x81, // 0x81
+ // State 1, offset 0x81
+ 0x07, 0x81, // 0x83
+ // State 2, offset 0x83
+ 0x06, 0x00, 0x81, // 0x86
+ // State 3, offset 0x86
+ 0x05, 0x81, 0x83, 0x81, 0x81, // 0x8b
+ // State 4, offset 0x8b
+ 0x06, 0x83, 0x81, // 0x8e
+ // State 5, offset 0x8e
+ 0x05, 0x83, 0x81, 0x81, 0x81, // 0x93
+ // State 6, offset 0x93
+ 0x04, 0x81, 0x8b, 0x8b, 0x8b, 0x81, 0x81, 0x81, // 0x9b
+ 0x81, // 0x9c
+ // State 7, offset 0x9c
+ 0x06, 0x8b, 0x81, // 0x9f
+ // State 8, offset 0x9f
+ 0x04, 0x8b, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, // 0xa7
+ 0x81, // 0xa8
+};
+
+const size_t kUtf8ValidatorTablesSize = arraysize(kUtf8ValidatorTables);
+
+} // namespace internal
+} // namespace base
diff --git a/base/i18n/utf8_validator_tables.h b/base/i18n/utf8_validator_tables.h
new file mode 100644
index 0000000000..939616b804
--- /dev/null
+++ b/base/i18n/utf8_validator_tables.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_I18N_UTF8_VALIDATOR_TABLES_H_
+#define BASE_I18N_UTF8_VALIDATOR_TABLES_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+
+namespace base {
+namespace internal {
+
+// The tables for all states; a list of entries of the form (right_shift,
+// next_state, next_state, ....). The right_shifts are used to reduce the
+// overall size of the table. The table only covers bytes in the range
+// [0x80, 0xFF] to save space.
+extern const uint8_t kUtf8ValidatorTables[];
+
+extern const size_t kUtf8ValidatorTablesSize;
+
+// The offset of the INVALID state in kUtf8ValidatorTables.
+enum {
+ I18N_UTF8_VALIDATOR_INVALID_INDEX = 129
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_I18N_UTF8_VALIDATOR_TABLES_H_
diff --git a/base/json/json_perftest.cc b/base/json/json_perftest.cc
new file mode 100644
index 0000000000..fc05bdca61
--- /dev/null
+++ b/base/json/json_perftest.cc
@@ -0,0 +1,84 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/memory/ptr_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace base {
+
+namespace {
+// Generates a simple dictionary value with simple data types, a string and a
+// list.
+std::unique_ptr<DictionaryValue> GenerateDict() {
+ auto root = std::make_unique<DictionaryValue>();
+ root->SetDouble("Double", 3.141);
+ root->SetBoolean("Bool", true);
+ root->SetInteger("Int", 42);
+ root->SetString("String", "Foo");
+
+ auto list = std::make_unique<ListValue>();
+ list->Set(0, std::make_unique<Value>(2.718));
+ list->Set(1, std::make_unique<Value>(false));
+ list->Set(2, std::make_unique<Value>(123));
+ list->Set(3, std::make_unique<Value>("Bar"));
+ root->Set("List", std::move(list));
+
+ return root;
+}
+
+// Generates a tree-like dictionary value with a size of O(breadth ** depth).
+std::unique_ptr<DictionaryValue> GenerateLayeredDict(int breadth, int depth) {
+ if (depth == 1)
+ return GenerateDict();
+
+ auto root = GenerateDict();
+ auto next = GenerateLayeredDict(breadth, depth - 1);
+
+ for (int i = 0; i < breadth; ++i) {
+ root->Set("Dict" + std::to_string(i), next->CreateDeepCopy());
+ }
+
+ return root;
+}
+
+} // namespace
+
+class JSONPerfTest : public testing::Test {
+ public:
+ void TestWriteAndRead(int breadth, int depth) {
+ std::string description = "Breadth: " + std::to_string(breadth) +
+ ", Depth: " + std::to_string(depth);
+ auto dict = GenerateLayeredDict(breadth, depth);
+ std::string json;
+
+ TimeTicks start_write = TimeTicks::Now();
+ JSONWriter::Write(*dict, &json);
+ TimeTicks end_write = TimeTicks::Now();
+ perf_test::PrintResult("Write", "", description,
+ (end_write - start_write).InMillisecondsF(), "ms",
+ true);
+
+ TimeTicks start_read = TimeTicks::Now();
+ JSONReader::Read(json);
+ TimeTicks end_read = TimeTicks::Now();
+ perf_test::PrintResult("Read", "", description,
+ (end_read - start_read).InMillisecondsF(), "ms",
+ true);
+ }
+};
+
+TEST_F(JSONPerfTest, StressTest) {
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 12; ++j) {
+ TestWriteAndRead(i + 1, j + 1);
+ }
+ }
+}
+
+} // namespace base
diff --git a/base/linux_util.cc b/base/linux_util.cc
new file mode 100644
index 0000000000..ddf848eeb7
--- /dev/null
+++ b/base/linux_util.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/linux_util.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/memory/singleton.h"
+#include "base/process/launch.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+
+namespace {
+
+// Not needed for OS_CHROMEOS.
+#if defined(OS_LINUX)
+enum LinuxDistroState {
+ STATE_DID_NOT_CHECK = 0,
+ STATE_CHECK_STARTED = 1,
+ STATE_CHECK_FINISHED = 2,
+};
+
+// Helper class for GetLinuxDistro().
+class LinuxDistroHelper {
+ public:
+ // Retrieves the Singleton.
+ static LinuxDistroHelper* GetInstance() {
+ return base::Singleton<LinuxDistroHelper>::get();
+ }
+
+ // The simple state machine goes from:
+ // STATE_DID_NOT_CHECK -> STATE_CHECK_STARTED -> STATE_CHECK_FINISHED.
+ LinuxDistroHelper() : state_(STATE_DID_NOT_CHECK) {}
+ ~LinuxDistroHelper() = default;
+
+ // Retrieve the current state, if we're in STATE_DID_NOT_CHECK,
+ // we automatically move to STATE_CHECK_STARTED so nobody else will
+ // do the check.
+ LinuxDistroState State() {
+ base::AutoLock scoped_lock(lock_);
+ if (STATE_DID_NOT_CHECK == state_) {
+ state_ = STATE_CHECK_STARTED;
+ return STATE_DID_NOT_CHECK;
+ }
+ return state_;
+ }
+
+ // Indicate the check finished, move to STATE_CHECK_FINISHED.
+ void CheckFinished() {
+ base::AutoLock scoped_lock(lock_);
+ DCHECK_EQ(STATE_CHECK_STARTED, state_);
+ state_ = STATE_CHECK_FINISHED;
+ }
+
+ private:
+ base::Lock lock_;
+ LinuxDistroState state_;
+};
+#endif // if defined(OS_LINUX)
+
+bool GetTasksForProcess(pid_t pid, std::vector<pid_t>* tids) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
+
+ DIR* task = opendir(buf);
+ if (!task) {
+ DLOG(WARNING) << "Cannot open " << buf;
+ return false;
+ }
+
+ struct dirent* dent;
+ while ((dent = readdir(task))) {
+ char* endptr;
+ const unsigned long int tid_ul = strtoul(dent->d_name, &endptr, 10);
+ if (tid_ul == ULONG_MAX || *endptr)
+ continue;
+ tids->push_back(tid_ul);
+ }
+ closedir(task);
+ return true;
+}
+
+} // namespace
+
+namespace base {
+
+// Account for the terminating null character.
+static const int kDistroSize = 128 + 1;
+
+// We use this static string to hold the Linux distro info. If we
+// crash, the crash handler code will send this in the crash dump.
+char g_linux_distro[kDistroSize] =
+#if defined(OS_CHROMEOS)
+ "CrOS";
+#elif defined(OS_ANDROID)
+ "Android";
+#else // if defined(OS_LINUX)
+ "Unknown";
+#endif
+
+std::string GetLinuxDistro() {
+#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
+ return g_linux_distro;
+#elif defined(OS_LINUX)
+ LinuxDistroHelper* distro_state_singleton = LinuxDistroHelper::GetInstance();
+ LinuxDistroState state = distro_state_singleton->State();
+ if (STATE_CHECK_FINISHED == state)
+ return g_linux_distro;
+ if (STATE_CHECK_STARTED == state)
+ return "Unknown"; // Don't wait for other thread to finish.
+ DCHECK_EQ(state, STATE_DID_NOT_CHECK);
+ // We do this check only once per process. If it fails, there's
+ // little reason to believe it will work if we attempt to run
+ // lsb_release again.
+ std::vector<std::string> argv;
+ argv.push_back("lsb_release");
+ argv.push_back("-d");
+ std::string output;
+ GetAppOutput(CommandLine(argv), &output);
+ if (output.length() > 0) {
+ // lsb_release -d should return: Description:<tab>Distro Info
+ const char field[] = "Description:\t";
+ if (output.compare(0, strlen(field), field) == 0) {
+ SetLinuxDistro(output.substr(strlen(field)));
+ }
+ }
+ distro_state_singleton->CheckFinished();
+ return g_linux_distro;
+#else
+ NOTIMPLEMENTED();
+ return "Unknown";
+#endif
+}
+
+void SetLinuxDistro(const std::string& distro) {
+ std::string trimmed_distro;
+ TrimWhitespaceASCII(distro, TRIM_ALL, &trimmed_distro);
+ strlcpy(g_linux_distro, trimmed_distro.c_str(), kDistroSize);
+}
+
+pid_t FindThreadIDWithSyscall(pid_t pid, const std::string& expected_data,
+ bool* syscall_supported) {
+ if (syscall_supported != nullptr)
+ *syscall_supported = false;
+
+ std::vector<pid_t> tids;
+ if (!GetTasksForProcess(pid, &tids))
+ return -1;
+
+ std::unique_ptr<char[]> syscall_data(new char[expected_data.length()]);
+ for (pid_t tid : tids) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "/proc/%d/task/%d/syscall", pid, tid);
+ int fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ continue;
+ if (syscall_supported != nullptr)
+ *syscall_supported = true;
+ bool read_ret = ReadFromFD(fd, syscall_data.get(), expected_data.length());
+ close(fd);
+ if (!read_ret)
+ continue;
+
+ if (0 == strncmp(expected_data.c_str(), syscall_data.get(),
+ expected_data.length())) {
+ return tid;
+ }
+ }
+ return -1;
+}
+
+pid_t FindThreadID(pid_t pid, pid_t ns_tid, bool* ns_pid_supported) {
+ if (ns_pid_supported)
+ *ns_pid_supported = false;
+
+ std::vector<pid_t> tids;
+ if (!GetTasksForProcess(pid, &tids))
+ return -1;
+
+ for (pid_t tid : tids) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "/proc/%d/task/%d/status", pid, tid);
+ std::string status;
+ if (!ReadFileToString(FilePath(buf), &status))
+ return -1;
+ StringTokenizer tokenizer(status, "\n");
+ while (tokenizer.GetNext()) {
+ StringPiece value_str(tokenizer.token_piece());
+ if (!value_str.starts_with("NSpid"))
+ continue;
+ if (ns_pid_supported)
+ *ns_pid_supported = true;
+ std::vector<StringPiece> split_value_str = SplitStringPiece(
+ value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ DCHECK_GE(split_value_str.size(), 2u);
+ int value;
+ // The last value in the list is the PID in the namespace.
+ if (StringToInt(split_value_str.back(), &value) && value == ns_tid) {
+ // The second value in the list is the real PID.
+ if (StringToInt(split_value_str[1], &value))
+ return value;
+ }
+ break;
+ }
+ }
+ return -1;
+}
+
+} // namespace base
diff --git a/base/linux_util.h b/base/linux_util.h
new file mode 100644
index 0000000000..272e06b7d8
--- /dev/null
+++ b/base/linux_util.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_LINUX_UTIL_H_
+#define BASE_LINUX_UTIL_H_
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <string>
+
+#include "base/base_export.h"
+
+namespace base {
+
+// This is declared here so the crash reporter can access the memory directly
+// in compromised context without going through the standard library.
+BASE_EXPORT extern char g_linux_distro[];
+
+// Get the Linux Distro if we can, or return "Unknown".
+BASE_EXPORT std::string GetLinuxDistro();
+
+// Set the Linux Distro string.
+BASE_EXPORT void SetLinuxDistro(const std::string& distro);
+
+// For a given process |pid|, look through all its threads and find the first
+// thread with /proc/[pid]/task/[thread_id]/syscall whose first N bytes matches
+// |expected_data|, where N is the length of |expected_data|.
+// Returns the thread id or -1 on error. If |syscall_supported| is
+// set to false the kernel does not support syscall in procfs.
+BASE_EXPORT pid_t FindThreadIDWithSyscall(pid_t pid,
+ const std::string& expected_data,
+ bool* syscall_supported);
+
+// For a given process |pid|, look through all its threads and find the first
+// thread with /proc/[pid]/task/[thread_id]/status where NSpid matches |ns_tid|.
+// Returns the thread id or -1 on error. If |ns_pid_supported| is
+// set to false the kernel does not support NSpid in procfs.
+BASE_EXPORT pid_t FindThreadID(pid_t pid, pid_t ns_tid, bool* ns_pid_supported);
+
+} // namespace base
+
+#endif // BASE_LINUX_UTIL_H_
diff --git a/base/logging.cc b/base/logging.cc
index 112afb8e08..5781c637c9 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -58,7 +58,7 @@ typedef HANDLE MutexHandle;
#include <zircon/syscalls.h>
#endif
-#if defined(OS_ANDROID) || defined(__ANDROID__)
+#if defined(OS_ANDROID)
#include <android/log.h>
#endif
@@ -757,7 +757,7 @@ LogMessage::~LogMessage() {
str_newline.c_str());
#endif // defined(USE_ASL)
}
-#elif defined(OS_ANDROID) || defined(__ANDROID__)
+#elif defined(OS_ANDROID)
android_LogPriority priority =
(severity_ < 0) ? ANDROID_LOG_VERBOSE : ANDROID_LOG_UNKNOWN;
switch (severity_) {
@@ -774,7 +774,7 @@ LogMessage::~LogMessage() {
priority = ANDROID_LOG_FATAL;
break;
}
-#if defined(OS_ANDROID)
+#if 0 // This is for building Chromium browser on Android.
__android_log_write(priority, "chromium", str_newline.c_str());
#else
__android_log_write(
@@ -783,8 +783,8 @@ LogMessage::~LogMessage() {
base::CommandLine::ForCurrentProcess()->
GetProgram().BaseName().value().c_str() : nullptr,
str_newline.c_str());
+#endif // 0
#endif // defined(OS_ANDROID)
-#endif
ignore_result(fwrite(str_newline.data(), str_newline.size(), 1, stderr));
fflush(stderr);
} else if (severity_ >= kAlwaysPrintErrorLevel) {
diff --git a/base/memory/discardable_memory.cc b/base/memory/discardable_memory.cc
new file mode 100644
index 0000000000..f0730aa403
--- /dev/null
+++ b/base/memory/discardable_memory.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/discardable_memory.h"
+
+namespace base {
+
+DiscardableMemory::DiscardableMemory() = default;
+
+DiscardableMemory::~DiscardableMemory() = default;
+
+} // namespace base
diff --git a/base/memory/discardable_memory.h b/base/memory/discardable_memory.h
new file mode 100644
index 0000000000..5c632d1c95
--- /dev/null
+++ b/base/memory/discardable_memory.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_DISCARDABLE_MEMORY_H_
+#define BASE_MEMORY_DISCARDABLE_MEMORY_H_
+
+#include "base/base_export.h"
+#include "base/compiler_specific.h"
+
+namespace base {
+
+namespace trace_event {
+class MemoryAllocatorDump;
+class ProcessMemoryDump;
+}
+
+// Discardable memory is used to cache large objects without worrying about
+// blowing out memory, both on mobile devices where there is no swap, and
+// desktop devices where unused free memory should be used to help the user
+// experience. This is preferable to releasing memory in response to an OOM
+// signal because it is simpler and provides system-wide management of
+// purgable memory, though it has less flexibility as to which objects get
+// discarded.
+//
+// Discardable memory has two states: locked and unlocked. While the memory is
+// locked, it will not be discarded. Unlocking the memory allows the
+// discardable memory system and the OS to reclaim it if needed. Locks do not
+// nest.
+//
+// Notes:
+// - The paging behavior of memory while it is locked is not specified. While
+// mobile platforms will not swap it out, it may qualify for swapping
+// on desktop platforms. It is not expected that this will matter, as the
+// preferred pattern of usage for DiscardableMemory is to lock down the
+// memory, use it as quickly as possible, and then unlock it.
+// - Because of memory alignment, the amount of memory allocated can be
+// larger than the requested memory size. It is not very efficient for
+// small allocations.
+// - A discardable memory instance is not thread safe. It is the
+// responsibility of users of discardable memory to ensure there are no
+// races.
+//
+class BASE_EXPORT DiscardableMemory {
+ public:
+ DiscardableMemory();
+ virtual ~DiscardableMemory();
+
+ // Locks the memory so that it will not be purged by the system. Returns
+ // true on success. If the return value is false then this object should be
+ // discarded and a new one should be created.
+ virtual bool Lock() WARN_UNUSED_RESULT = 0;
+
+ // Unlocks the memory so that it can be purged by the system. Must be called
+ // after every successful lock call.
+ virtual void Unlock() = 0;
+
+ // Returns the memory address held by this object. The object must be locked
+ // before calling this.
+ virtual void* data() const = 0;
+
+ // Handy method to simplify calling data() with a reinterpret_cast.
+ template<typename T> T* data_as() const {
+ return reinterpret_cast<T*>(data());
+ }
+
+ // Used for dumping the statistics of discardable memory allocated in tracing.
+ // Returns a new MemoryAllocatorDump in the |pmd| with the size of the
+ // discardable memory. The MemoryAllocatorDump created is owned by |pmd|. See
+ // ProcessMemoryDump::CreateAllocatorDump.
+ virtual trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump(
+ const char* name,
+ trace_event::ProcessMemoryDump* pmd) const = 0;
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_DISCARDABLE_MEMORY_H_
diff --git a/base/memory/discardable_memory_allocator.cc b/base/memory/discardable_memory_allocator.cc
new file mode 100644
index 0000000000..3dbb27672b
--- /dev/null
+++ b/base/memory/discardable_memory_allocator.cc
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/discardable_memory_allocator.h"
+
+#include "base/logging.h"
+
+namespace base {
+namespace {
+
+DiscardableMemoryAllocator* g_discardable_allocator = nullptr;
+
+} // namespace
+
+// static
+void DiscardableMemoryAllocator::SetInstance(
+ DiscardableMemoryAllocator* allocator) {
+ DCHECK(!allocator || !g_discardable_allocator);
+ g_discardable_allocator = allocator;
+}
+
+// static
+DiscardableMemoryAllocator* DiscardableMemoryAllocator::GetInstance() {
+ DCHECK(g_discardable_allocator);
+ return g_discardable_allocator;
+}
+
+} // namespace base
diff --git a/base/memory/discardable_memory_allocator.h b/base/memory/discardable_memory_allocator.h
new file mode 100644
index 0000000000..8d74b16959
--- /dev/null
+++ b/base/memory/discardable_memory_allocator.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_
+#define BASE_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/base_export.h"
+
+namespace base {
+class DiscardableMemory;
+
+class BASE_EXPORT DiscardableMemoryAllocator {
+ public:
+ // Returns the allocator instance.
+ static DiscardableMemoryAllocator* GetInstance();
+
+ // Sets the allocator instance. Can only be called once, e.g. on startup.
+ // Ownership of |instance| remains with the caller.
+ static void SetInstance(DiscardableMemoryAllocator* allocator);
+
+ // Giant WARNING: Discardable[Shared]Memory is only implemented on Android. On
+ // non-Android platforms, it behaves exactly the same as SharedMemory.
+ // See LockPages() in discardable_shared_memory.cc.
+ virtual std::unique_ptr<DiscardableMemory> AllocateLockedDiscardableMemory(
+ size_t size) = 0;
+
+ protected:
+ virtual ~DiscardableMemoryAllocator() = default;
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_
diff --git a/base/memory/discardable_shared_memory.cc b/base/memory/discardable_shared_memory.cc
new file mode 100644
index 0000000000..3b6b4dbfa9
--- /dev/null
+++ b/base/memory/discardable_shared_memory.cc
@@ -0,0 +1,514 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/discardable_shared_memory.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/atomicops.h"
+#include "base/bits.h"
+#include "base/logging.h"
+#include "base/memory/shared_memory_tracker.h"
+#include "base/numerics/safe_math.h"
+#include "base/process/process_metrics.h"
+#include "base/trace_event/memory_allocator_dump.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "build/build_config.h"
+
+#if defined(OS_POSIX) && !defined(OS_NACL)
+// For madvise() which is available on all POSIX compatible systems.
+#include <sys/mman.h>
+#endif
+
+#if defined(OS_ANDROID)
+#include "third_party/ashmem/ashmem.h"
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include "base/win/windows_version.h"
+#endif
+
+namespace base {
+namespace {
+
+// Use a machine-sized pointer as atomic type. It will use the Atomic32 or
+// Atomic64 routines, depending on the architecture.
+typedef intptr_t AtomicType;
+typedef uintptr_t UAtomicType;
+
+// Template specialization for timestamp serialization/deserialization. This
+// is used to serialize timestamps using Unix time on systems where AtomicType
+// does not have enough precision to contain a timestamp in the standard
+// serialized format.
+template <int>
+Time TimeFromWireFormat(int64_t value);
+template <int>
+int64_t TimeToWireFormat(Time time);
+
+// Serialize to Unix time when using 4-byte wire format.
+// Note: 19 January 2038, this will cease to work.
+template <>
+Time ALLOW_UNUSED_TYPE TimeFromWireFormat<4>(int64_t value) {
+ return value ? Time::UnixEpoch() + TimeDelta::FromSeconds(value) : Time();
+}
+template <>
+int64_t ALLOW_UNUSED_TYPE TimeToWireFormat<4>(Time time) {
+ return time > Time::UnixEpoch() ? (time - Time::UnixEpoch()).InSeconds() : 0;
+}
+
+// Standard serialization format when using 8-byte wire format.
+template <>
+Time ALLOW_UNUSED_TYPE TimeFromWireFormat<8>(int64_t value) {
+ return Time::FromInternalValue(value);
+}
+template <>
+int64_t ALLOW_UNUSED_TYPE TimeToWireFormat<8>(Time time) {
+ return time.ToInternalValue();
+}
+
+struct SharedState {
+ enum LockState { UNLOCKED = 0, LOCKED = 1 };
+
+ explicit SharedState(AtomicType ivalue) { value.i = ivalue; }
+ SharedState(LockState lock_state, Time timestamp) {
+ int64_t wire_timestamp = TimeToWireFormat<sizeof(AtomicType)>(timestamp);
+ DCHECK_GE(wire_timestamp, 0);
+ DCHECK_EQ(lock_state & ~1, 0);
+ value.u = (static_cast<UAtomicType>(wire_timestamp) << 1) | lock_state;
+ }
+
+ LockState GetLockState() const { return static_cast<LockState>(value.u & 1); }
+
+ Time GetTimestamp() const {
+ return TimeFromWireFormat<sizeof(AtomicType)>(value.u >> 1);
+ }
+
+ // Bit 1: Lock state. Bit is set when locked.
+ // Bit 2..sizeof(AtomicType)*8: Usage timestamp. NULL time when locked or
+ // purged.
+ union {
+ AtomicType i;
+ UAtomicType u;
+ } value;
+};
+
+// Shared state is stored at offset 0 in shared memory segments.
+SharedState* SharedStateFromSharedMemory(
+ const WritableSharedMemoryMapping& shared_memory) {
+ DCHECK(shared_memory.IsValid());
+ return static_cast<SharedState*>(shared_memory.memory());
+}
+
+// Round up |size| to a multiple of page size.
+size_t AlignToPageSize(size_t size) {
+ return bits::Align(size, base::GetPageSize());
+}
+
+} // namespace
+
+DiscardableSharedMemory::DiscardableSharedMemory()
+ : mapped_size_(0), locked_page_count_(0) {
+}
+
+DiscardableSharedMemory::DiscardableSharedMemory(
+ UnsafeSharedMemoryRegion shared_memory_region)
+ : shared_memory_region_(std::move(shared_memory_region)),
+ mapped_size_(0),
+ locked_page_count_(0) {}
+
+DiscardableSharedMemory::~DiscardableSharedMemory() = default;
+
+bool DiscardableSharedMemory::CreateAndMap(size_t size) {
+ CheckedNumeric<size_t> checked_size = size;
+ checked_size += AlignToPageSize(sizeof(SharedState));
+ if (!checked_size.IsValid())
+ return false;
+
+ shared_memory_region_ =
+ UnsafeSharedMemoryRegion::Create(checked_size.ValueOrDie());
+
+ if (!shared_memory_region_.IsValid())
+ return false;
+
+ shared_memory_mapping_ = shared_memory_region_.Map();
+ if (!shared_memory_mapping_.IsValid())
+ return false;
+
+ mapped_size_ = shared_memory_mapping_.mapped_size() -
+ AlignToPageSize(sizeof(SharedState));
+
+ locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize();
+#if DCHECK_IS_ON()
+ for (size_t page = 0; page < locked_page_count_; ++page)
+ locked_pages_.insert(page);
+#endif
+
+ DCHECK(last_known_usage_.is_null());
+ SharedState new_state(SharedState::LOCKED, Time());
+ subtle::Release_Store(
+ &SharedStateFromSharedMemory(shared_memory_mapping_)->value.i,
+ new_state.value.i);
+ return true;
+}
+
+bool DiscardableSharedMemory::Map(size_t size) {
+ DCHECK(!shared_memory_mapping_.IsValid());
+ if (shared_memory_mapping_.IsValid())
+ return false;
+
+ shared_memory_mapping_ = shared_memory_region_.MapAt(
+ 0, AlignToPageSize(sizeof(SharedState)) + size);
+ if (!shared_memory_mapping_.IsValid())
+ return false;
+
+ mapped_size_ = shared_memory_mapping_.mapped_size() -
+ AlignToPageSize(sizeof(SharedState));
+
+ locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize();
+#if DCHECK_IS_ON()
+ for (size_t page = 0; page < locked_page_count_; ++page)
+ locked_pages_.insert(page);
+#endif
+
+ return true;
+}
+
+bool DiscardableSharedMemory::Unmap() {
+ if (!shared_memory_mapping_.IsValid())
+ return false;
+
+ shared_memory_mapping_ = WritableSharedMemoryMapping();
+ locked_page_count_ = 0;
+#if DCHECK_IS_ON()
+ locked_pages_.clear();
+#endif
+ mapped_size_ = 0;
+ return true;
+}
+
+DiscardableSharedMemory::LockResult DiscardableSharedMemory::Lock(
+ size_t offset, size_t length) {
+ DCHECK_EQ(AlignToPageSize(offset), offset);
+ DCHECK_EQ(AlignToPageSize(length), length);
+
+ // Calls to this function must be synchronized properly.
+ DFAKE_SCOPED_LOCK(thread_collision_warner_);
+
+ DCHECK(shared_memory_mapping_.IsValid());
+
+ // We need to successfully acquire the platform independent lock before
+ // individual pages can be locked.
+ if (!locked_page_count_) {
+ // Return false when instance has been purged or not initialized properly
+ // by checking if |last_known_usage_| is NULL.
+ if (last_known_usage_.is_null())
+ return FAILED;
+
+ SharedState old_state(SharedState::UNLOCKED, last_known_usage_);
+ SharedState new_state(SharedState::LOCKED, Time());
+ SharedState result(subtle::Acquire_CompareAndSwap(
+ &SharedStateFromSharedMemory(shared_memory_mapping_)->value.i,
+ old_state.value.i, new_state.value.i));
+ if (result.value.u != old_state.value.u) {
+ // Update |last_known_usage_| in case the above CAS failed because of
+ // an incorrect timestamp.
+ last_known_usage_ = result.GetTimestamp();
+ return FAILED;
+ }
+ }
+
+ // Zero for length means "everything onward".
+ if (!length)
+ length = AlignToPageSize(mapped_size_) - offset;
+
+ size_t start = offset / base::GetPageSize();
+ size_t end = start + length / base::GetPageSize();
+ DCHECK_LE(start, end);
+ DCHECK_LE(end, AlignToPageSize(mapped_size_) / base::GetPageSize());
+
+ // Add pages to |locked_page_count_|.
+ // Note: Locking a page that is already locked is an error.
+ locked_page_count_ += end - start;
+#if DCHECK_IS_ON()
+ // Detect incorrect usage by keeping track of exactly what pages are locked.
+ for (auto page = start; page < end; ++page) {
+ auto result = locked_pages_.insert(page);
+ DCHECK(result.second);
+ }
+ DCHECK_EQ(locked_pages_.size(), locked_page_count_);
+#endif
+
+ // Always behave as if memory was purged when trying to lock a 0 byte segment.
+ if (!length)
+ return PURGED;
+
+#if defined(OS_ANDROID)
+ // Ensure that the platform won't discard the required pages.
+ return LockPages(shared_memory_region_,
+ AlignToPageSize(sizeof(SharedState)) + offset, length);
+#elif defined(OS_MACOSX)
+ // On macOS, there is no mechanism to lock pages. However, we do need to call
+ // madvise(MADV_FREE_REUSE) in order to correctly update accounting for memory
+ // footprint via task_info().
+ //
+ // Note that calling madvise(MADV_FREE_REUSE) on regions that haven't had
+ // madvise(MADV_FREE_REUSABLE) called on them has no effect.
+ //
+ // Note that the corresponding call to MADV_FREE_REUSABLE is in Purge(), since
+ // that's where the memory is actually released, rather than Unlock(), which
+ // is a no-op on macOS.
+ //
+ // For more information, see
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=823915.
+ if (madvise(reinterpret_cast<char*>(shared_memory_mapping_.memory()) +
+ AlignToPageSize(sizeof(SharedState)),
+ AlignToPageSize(mapped_size_), MADV_FREE_REUSE))
+ ;
+ return DiscardableSharedMemory::SUCCESS;
+#else
+ return DiscardableSharedMemory::SUCCESS;
+#endif
+}
+
+void DiscardableSharedMemory::Unlock(size_t offset, size_t length) {
+ DCHECK_EQ(AlignToPageSize(offset), offset);
+ DCHECK_EQ(AlignToPageSize(length), length);
+
+ // Calls to this function must be synchronized properly.
+ DFAKE_SCOPED_LOCK(thread_collision_warner_);
+
+ // Passing zero for |length| means "everything onward". Note that |length| may
+ // still be zero after this calculation, e.g. if |mapped_size_| is zero.
+ if (!length)
+ length = AlignToPageSize(mapped_size_) - offset;
+
+ DCHECK(shared_memory_mapping_.IsValid());
+
+ // Allow the pages to be discarded by the platform, if supported.
+ UnlockPages(shared_memory_region_,
+ AlignToPageSize(sizeof(SharedState)) + offset, length);
+
+ size_t start = offset / base::GetPageSize();
+ size_t end = start + length / base::GetPageSize();
+ DCHECK_LE(start, end);
+ DCHECK_LE(end, AlignToPageSize(mapped_size_) / base::GetPageSize());
+
+ // Remove pages from |locked_page_count_|.
+ // Note: Unlocking a page that is not locked is an error.
+ DCHECK_GE(locked_page_count_, end - start);
+ locked_page_count_ -= end - start;
+#if DCHECK_IS_ON()
+ // Detect incorrect usage by keeping track of exactly what pages are locked.
+ for (auto page = start; page < end; ++page) {
+ auto erased_count = locked_pages_.erase(page);
+ DCHECK_EQ(1u, erased_count);
+ }
+ DCHECK_EQ(locked_pages_.size(), locked_page_count_);
+#endif
+
+ // Early out and avoid releasing the platform independent lock if some pages
+ // are still locked.
+ if (locked_page_count_)
+ return;
+
+ Time current_time = Now();
+ DCHECK(!current_time.is_null());
+
+ SharedState old_state(SharedState::LOCKED, Time());
+ SharedState new_state(SharedState::UNLOCKED, current_time);
+ // Note: timestamp cannot be NULL as that is a unique value used when
+ // locked or purged.
+ DCHECK(!new_state.GetTimestamp().is_null());
+ // Timestamp precision should at least be accurate to the second.
+ DCHECK_EQ((new_state.GetTimestamp() - Time::UnixEpoch()).InSeconds(),
+ (current_time - Time::UnixEpoch()).InSeconds());
+ SharedState result(subtle::Release_CompareAndSwap(
+ &SharedStateFromSharedMemory(shared_memory_mapping_)->value.i,
+ old_state.value.i, new_state.value.i));
+
+ DCHECK_EQ(old_state.value.u, result.value.u);
+
+ last_known_usage_ = current_time;
+}
+
+void* DiscardableSharedMemory::memory() const {
+ return reinterpret_cast<uint8_t*>(shared_memory_mapping_.memory()) +
+ AlignToPageSize(sizeof(SharedState));
+}
+
+bool DiscardableSharedMemory::Purge(Time current_time) {
+ // Calls to this function must be synchronized properly.
+ DFAKE_SCOPED_LOCK(thread_collision_warner_);
+ DCHECK(shared_memory_mapping_.IsValid());
+
+ SharedState old_state(SharedState::UNLOCKED, last_known_usage_);
+ SharedState new_state(SharedState::UNLOCKED, Time());
+ SharedState result(subtle::Acquire_CompareAndSwap(
+ &SharedStateFromSharedMemory(shared_memory_mapping_)->value.i,
+ old_state.value.i, new_state.value.i));
+
+ // Update |last_known_usage_| to |current_time| if the memory is locked. This
+ // allows the caller to determine if purging failed because last known usage
+ // was incorrect or memory was locked. In the second case, the caller should
+ // most likely wait for some amount of time before attempting to purge the
+ // the memory again.
+ if (result.value.u != old_state.value.u) {
+ last_known_usage_ = result.GetLockState() == SharedState::LOCKED
+ ? current_time
+ : result.GetTimestamp();
+ return false;
+ }
+
+// The next section will release as much resource as can be done
+// from the purging process, until the client process notices the
+// purge and releases its own references.
+// Note: this memory will not be accessed again. The segment will be
+// freed asynchronously at a later time, so just do the best
+// immediately.
+#if defined(OS_POSIX) && !defined(OS_NACL)
+// Linux and Android provide MADV_REMOVE which is preferred as it has a
+// behavior that can be verified in tests. Other POSIX flavors (MacOSX, BSDs),
+// provide MADV_FREE which has the same result but memory is purged lazily.
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+#define MADV_PURGE_ARGUMENT MADV_REMOVE
+#elif defined(OS_MACOSX)
+// MADV_FREE_REUSABLE is similar to MADV_FREE, but also marks the pages with the
+// reusable bit, which allows both Activity Monitor and memory-infra to
+// correctly track the pages.
+#define MADV_PURGE_ARGUMENT MADV_FREE_REUSABLE
+#else
+#define MADV_PURGE_ARGUMENT MADV_FREE
+#endif
+ // Advise the kernel to remove resources associated with purged pages.
+ // Subsequent accesses of memory pages will succeed, but might result in
+ // zero-fill-on-demand pages.
+ if (madvise(reinterpret_cast<char*>(shared_memory_mapping_.memory()) +
+ AlignToPageSize(sizeof(SharedState)),
+ AlignToPageSize(mapped_size_), MADV_PURGE_ARGUMENT)) {
+ DPLOG(ERROR) << "madvise() failed";
+ }
+#elif defined(OS_WIN)
+ if (base::win::GetVersion() >= base::win::VERSION_WIN8_1) {
+ // Discard the purged pages, which releases the physical storage (resident
+ // memory, compressed or swapped), but leaves them reserved & committed.
+ // This does not free commit for use by other applications, but allows the
+ // system to avoid compressing/swapping these pages to free physical memory.
+ static const auto discard_virtual_memory =
+ reinterpret_cast<decltype(&::DiscardVirtualMemory)>(GetProcAddress(
+ GetModuleHandle(L"kernel32.dll"), "DiscardVirtualMemory"));
+ if (discard_virtual_memory) {
+ DWORD discard_result = discard_virtual_memory(
+ reinterpret_cast<char*>(shared_memory_mapping_.memory()) +
+ AlignToPageSize(sizeof(SharedState)),
+ AlignToPageSize(mapped_size_));
+ if (discard_result != ERROR_SUCCESS) {
+ DLOG(DCHECK) << "DiscardVirtualMemory() failed in Purge(): "
+ << logging::SystemErrorCodeToString(discard_result);
+ }
+ }
+ }
+#endif
+
+ last_known_usage_ = Time();
+ return true;
+}
+
+bool DiscardableSharedMemory::IsMemoryResident() const {
+ DCHECK(shared_memory_mapping_.IsValid());
+
+ SharedState result(subtle::NoBarrier_Load(
+ &SharedStateFromSharedMemory(shared_memory_mapping_)->value.i));
+
+ return result.GetLockState() == SharedState::LOCKED ||
+ !result.GetTimestamp().is_null();
+}
+
+bool DiscardableSharedMemory::IsMemoryLocked() const {
+ DCHECK(shared_memory_mapping_.IsValid());
+
+ SharedState result(subtle::NoBarrier_Load(
+ &SharedStateFromSharedMemory(shared_memory_mapping_)->value.i));
+
+ return result.GetLockState() == SharedState::LOCKED;
+}
+
+void DiscardableSharedMemory::Close() {
+ shared_memory_region_ = UnsafeSharedMemoryRegion();
+}
+
+void DiscardableSharedMemory::CreateSharedMemoryOwnershipEdge(
+ trace_event::MemoryAllocatorDump* local_segment_dump,
+ trace_event::ProcessMemoryDump* pmd,
+ bool is_owned) const {
+ auto* shared_memory_dump = SharedMemoryTracker::GetOrCreateSharedMemoryDump(
+ shared_memory_mapping_, pmd);
+ // TODO(ssid): Clean this by a new api to inherit size of parent dump once the
+ // we send the full PMD and calculate sizes inside chrome, crbug.com/704203.
+ size_t resident_size = shared_memory_dump->GetSizeInternal();
+ local_segment_dump->AddScalar(trace_event::MemoryAllocatorDump::kNameSize,
+ trace_event::MemoryAllocatorDump::kUnitsBytes,
+ resident_size);
+
+ // By creating an edge with a higher |importance| (w.r.t non-owned dumps)
+ // the tracing UI will account the effective size of the segment to the
+ // client instead of manager.
+ // TODO(ssid): Define better constants in MemoryAllocatorDump for importance
+ // values, crbug.com/754793.
+ const int kImportance = is_owned ? 2 : 0;
+ auto shared_memory_guid = shared_memory_mapping_.guid();
+ local_segment_dump->AddString("id", "hash", shared_memory_guid.ToString());
+
+ // Owned discardable segments which are allocated by client process, could
+ // have been cleared by the discardable manager. So, the segment need not
+ // exist in memory and weak dumps are created to indicate the UI that the dump
+ // should exist only if the manager also created the global dump edge.
+ if (is_owned) {
+ pmd->CreateWeakSharedMemoryOwnershipEdge(local_segment_dump->guid(),
+ shared_memory_guid, kImportance);
+ } else {
+ pmd->CreateSharedMemoryOwnershipEdge(local_segment_dump->guid(),
+ shared_memory_guid, kImportance);
+ }
+}
+
+// static
+DiscardableSharedMemory::LockResult DiscardableSharedMemory::LockPages(
+ const UnsafeSharedMemoryRegion& region,
+ size_t offset,
+ size_t length) {
+#if defined(OS_ANDROID)
+ if (region.IsValid()) {
+ int pin_result =
+ ashmem_pin_region(region.GetPlatformHandle(), offset, length);
+ if (pin_result == ASHMEM_WAS_PURGED)
+ return PURGED;
+ if (pin_result < 0)
+ return FAILED;
+ }
+#endif
+ return SUCCESS;
+}
+
+// static
+void DiscardableSharedMemory::UnlockPages(
+ const UnsafeSharedMemoryRegion& region,
+ size_t offset,
+ size_t length) {
+#if defined(OS_ANDROID)
+ if (region.IsValid()) {
+ int unpin_result =
+ ashmem_unpin_region(region.GetPlatformHandle(), offset, length);
+ DCHECK_EQ(0, unpin_result);
+ }
+#endif
+}
+
+Time DiscardableSharedMemory::Now() const {
+ return Time::Now();
+}
+
+} // namespace base
diff --git a/base/memory/discardable_shared_memory.h b/base/memory/discardable_shared_memory.h
new file mode 100644
index 0000000000..52a78b131e
--- /dev/null
+++ b/base/memory/discardable_shared_memory.h
@@ -0,0 +1,187 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_DISCARDABLE_SHARED_MEMORY_H_
+#define BASE_MEMORY_DISCARDABLE_SHARED_MEMORY_H_
+
+#include <stddef.h>
+
+#include "base/base_export.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/threading/thread_collision_warner.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+#if DCHECK_IS_ON()
+#include <set>
+#endif
+
+// Linux (including Android) support the MADV_REMOVE argument with madvise()
+// which has the behavior of reliably causing zero-fill-on-demand pages to
+// be returned after a call. Here we define
+// DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE on Linux
+// and Android to indicate that this type of behavior can be expected on
+// those platforms. Note that madvise() will still be used on other POSIX
+// platforms but doesn't provide the zero-fill-on-demand pages guarantee.
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+#define DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE
+#endif
+
+namespace base {
+
+namespace trace_event {
+class MemoryAllocatorDump;
+class ProcessMemoryDump;
+} // namespace trace_event
+
+// Platform abstraction for discardable shared memory.
+//
+// This class is not thread-safe. Clients are responsible for synchronizing
+// access to an instance of this class.
+class BASE_EXPORT DiscardableSharedMemory {
+ public:
+ enum LockResult { SUCCESS, PURGED, FAILED };
+
+ DiscardableSharedMemory();
+
+ // Create a new DiscardableSharedMemory object from an existing, open shared
+ // memory file. Memory must be locked.
+ explicit DiscardableSharedMemory(UnsafeSharedMemoryRegion region);
+
+ // Closes any open files.
+ virtual ~DiscardableSharedMemory();
+
+ // Creates and maps a locked DiscardableSharedMemory object with |size|.
+ // Returns true on success and false on failure.
+ bool CreateAndMap(size_t size);
+
+ // Maps the locked discardable memory into the caller's address space.
+ // Returns true on success, false otherwise.
+ bool Map(size_t size);
+
+ // Unmaps the discardable shared memory from the caller's address space.
+ // Unmapping won't unlock previously locked range.
+ // Returns true if successful; returns false on error or if the memory is
+ // not mapped.
+ bool Unmap();
+
+ // The actual size of the mapped memory (may be larger than requested).
+ size_t mapped_size() const { return mapped_size_; }
+
+ // Returns a duplicated shared memory region for this DiscardableSharedMemory
+ // object.
+ UnsafeSharedMemoryRegion DuplicateRegion() const {
+ return shared_memory_region_.Duplicate();
+ }
+
+ // Returns an ID for the shared memory region. This is ID of the mapped region
+ // consistent across all processes and is valid as long as the region is not
+ // unmapped.
+ const UnguessableToken& mapped_id() const {
+ return shared_memory_mapping_.guid();
+ }
+
+ // Locks a range of memory so that it will not be purged by the system.
+ // The range of memory must be unlocked. The result of trying to lock an
+ // already locked range is undefined. |offset| and |length| must both be
+ // a multiple of the page size as returned by GetPageSize().
+ // Passing 0 for |length| means "everything onward".
+ // Returns SUCCESS if range was successfully locked and the memory is still
+ // resident, PURGED if range was successfully locked but has been purged
+ // since last time it was locked and FAILED if range could not be locked.
+ // Locking can fail for two reasons; object might have been purged, our
+ // last known usage timestamp might be out of date. Last known usage time
+ // is updated to the actual last usage timestamp if memory is still resident
+ // or 0 if not.
+ LockResult Lock(size_t offset, size_t length);
+
+ // Unlock a previously successfully locked range of memory. The range of
+ // memory must be locked. The result of trying to unlock a not
+ // previously locked range is undefined.
+ // |offset| and |length| must both be a multiple of the page size as returned
+ // by GetPageSize().
+ // Passing 0 for |length| means "everything onward".
+ void Unlock(size_t offset, size_t length);
+
+ // Gets a pointer to the opened discardable memory space. Discardable memory
+ // must have been mapped via Map().
+ void* memory() const;
+
+ // Returns the last known usage time for DiscardableSharedMemory object. This
+ // may be earlier than the "true" usage time when memory has been used by a
+ // different process. Returns NULL time if purged.
+ Time last_known_usage() const { return last_known_usage_; }
+
+ // This returns true and sets |last_known_usage_| to 0 if
+ // DiscardableSharedMemory object was successfully purged. Purging can fail
+ // for two reasons; object might be locked or our last known usage timestamp
+ // might be out of date. Last known usage time is updated to |current_time|
+ // if locked or the actual last usage timestamp if unlocked. It is often
+ // necessary to call this function twice for the object to successfully be
+ // purged. First call, updates |last_known_usage_|. Second call, successfully
+ // purges the object using the updated |last_known_usage_|.
+ // Note: there is no guarantee that multiple calls to this function will
+ // successfully purge object. DiscardableSharedMemory object might be locked
+ // or another thread/process might be able to lock and unlock it in between
+ // each call.
+ bool Purge(Time current_time);
+
+ // Returns true if memory is still resident.
+ bool IsMemoryResident() const;
+
+ // Returns true if memory is locked.
+ bool IsMemoryLocked() const;
+
+ // Closes the open discardable memory segment.
+ // It is safe to call Close repeatedly.
+ void Close();
+
+ // For tracing: Creates ownership edge to the underlying shared memory dump
+ // which is cross process in the given |pmd|. |local_segment_dump| is the dump
+ // associated with the local discardable shared memory segment and |is_owned|
+ // is true when the current process owns the segment and the effective memory
+ // is assigned to the current process.
+ void CreateSharedMemoryOwnershipEdge(
+ trace_event::MemoryAllocatorDump* local_segment_dump,
+ trace_event::ProcessMemoryDump* pmd,
+ bool is_owned) const;
+
+ private:
+ // LockPages/UnlockPages are platform-native discardable page management
+ // helper functions. Both expect |offset| to be specified relative to the
+ // base address at which |memory| is mapped, and that |offset| and |length|
+ // are page-aligned by the caller.
+ // Returns SUCCESS on platforms which do not support discardable pages.
+ static LockResult LockPages(const UnsafeSharedMemoryRegion& region,
+ size_t offset,
+ size_t length);
+ // UnlockPages() is a no-op on platforms not supporting discardable pages.
+ static void UnlockPages(const UnsafeSharedMemoryRegion& region,
+ size_t offset,
+ size_t length);
+
+ // Virtual for tests.
+ virtual Time Now() const;
+
+ UnsafeSharedMemoryRegion shared_memory_region_;
+ WritableSharedMemoryMapping shared_memory_mapping_;
+ size_t mapped_size_;
+ size_t locked_page_count_;
+#if DCHECK_IS_ON()
+ std::set<size_t> locked_pages_;
+#endif
+ // Implementation is not thread-safe but still usable if clients are
+ // synchronized somehow. Use a collision warner to detect incorrect usage.
+ DFAKE_MUTEX(thread_collision_warner_);
+ Time last_known_usage_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiscardableSharedMemory);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_DISCARDABLE_SHARED_MEMORY_H_
diff --git a/base/memory/discardable_shared_memory_unittest.cc b/base/memory/discardable_shared_memory_unittest.cc
new file mode 100644
index 0000000000..b3d21a7bd5
--- /dev/null
+++ b/base/memory/discardable_shared_memory_unittest.cc
@@ -0,0 +1,456 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fcntl.h>
+#include <stdint.h>
+
+#include "base/files/scoped_file.h"
+#include "base/memory/discardable_shared_memory.h"
+#include "base/memory/shared_memory_tracker.h"
+#include "base/process/process_metrics.h"
+#include "base/trace_event/memory_allocator_dump.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+class TestDiscardableSharedMemory : public DiscardableSharedMemory {
+ public:
+ TestDiscardableSharedMemory() = default;
+
+ explicit TestDiscardableSharedMemory(UnsafeSharedMemoryRegion region)
+ : DiscardableSharedMemory(std::move(region)) {}
+
+ void SetNow(Time now) { now_ = now; }
+
+ private:
+ // Overriden from DiscardableSharedMemory:
+ Time Now() const override { return now_; }
+
+ Time now_;
+};
+
+TEST(DiscardableSharedMemoryTest, CreateAndMap) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory;
+ bool rv = memory.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+ EXPECT_GE(memory.mapped_size(), kDataSize);
+ EXPECT_TRUE(memory.IsMemoryLocked());
+}
+
+TEST(DiscardableSharedMemoryTest, CreateFromHandle) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
+ ASSERT_TRUE(shared_region.IsValid());
+
+ TestDiscardableSharedMemory memory2(std::move(shared_region));
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+ EXPECT_TRUE(memory2.IsMemoryLocked());
+}
+
+TEST(DiscardableSharedMemoryTest, LockAndUnlock) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // Memory is initially locked. Unlock it.
+ memory1.SetNow(Time::FromDoubleT(1));
+ memory1.Unlock(0, 0);
+ EXPECT_FALSE(memory1.IsMemoryLocked());
+
+ // Lock and unlock memory.
+ DiscardableSharedMemory::LockResult lock_rv = memory1.Lock(0, 0);
+ EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
+ memory1.SetNow(Time::FromDoubleT(2));
+ memory1.Unlock(0, 0);
+
+ // Lock again before duplicating and passing ownership to new instance.
+ lock_rv = memory1.Lock(0, 0);
+ EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
+ EXPECT_TRUE(memory1.IsMemoryLocked());
+
+ UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
+ ASSERT_TRUE(shared_region.IsValid());
+
+ TestDiscardableSharedMemory memory2(std::move(shared_region));
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // Unlock second instance.
+ memory2.SetNow(Time::FromDoubleT(3));
+ memory2.Unlock(0, 0);
+
+ // Both memory instances should be unlocked now.
+ EXPECT_FALSE(memory2.IsMemoryLocked());
+ EXPECT_FALSE(memory1.IsMemoryLocked());
+
+ // Lock second instance before passing ownership back to first instance.
+ lock_rv = memory2.Lock(0, 0);
+ EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
+
+ // Memory should still be resident and locked.
+ rv = memory1.IsMemoryResident();
+ EXPECT_TRUE(rv);
+ EXPECT_TRUE(memory1.IsMemoryLocked());
+
+ // Unlock first instance.
+ memory1.SetNow(Time::FromDoubleT(4));
+ memory1.Unlock(0, 0);
+}
+
+TEST(DiscardableSharedMemoryTest, Purge) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
+ ASSERT_TRUE(shared_region.IsValid());
+
+ TestDiscardableSharedMemory memory2(std::move(shared_region));
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // This should fail as memory is locked.
+ rv = memory1.Purge(Time::FromDoubleT(1));
+ EXPECT_FALSE(rv);
+
+ memory2.SetNow(Time::FromDoubleT(2));
+ memory2.Unlock(0, 0);
+
+ ASSERT_TRUE(memory2.IsMemoryResident());
+
+ // Memory is unlocked, but our usage timestamp is incorrect.
+ rv = memory1.Purge(Time::FromDoubleT(3));
+ EXPECT_FALSE(rv);
+
+ ASSERT_TRUE(memory2.IsMemoryResident());
+
+ // Memory is unlocked and our usage timestamp should be correct.
+ rv = memory1.Purge(Time::FromDoubleT(4));
+ EXPECT_TRUE(rv);
+
+ // Lock should fail as memory has been purged.
+ DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
+ EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
+
+ ASSERT_FALSE(memory2.IsMemoryResident());
+}
+
+TEST(DiscardableSharedMemoryTest, LastUsed) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
+ ASSERT_TRUE(shared_region.IsValid());
+
+ TestDiscardableSharedMemory memory2(std::move(shared_region));
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ memory2.SetNow(Time::FromDoubleT(1));
+ memory2.Unlock(0, 0);
+
+ EXPECT_EQ(memory2.last_known_usage(), Time::FromDoubleT(1));
+
+ DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
+ EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
+
+ // This should fail as memory is locked.
+ rv = memory1.Purge(Time::FromDoubleT(2));
+ ASSERT_FALSE(rv);
+
+ // Last usage should have been updated to timestamp passed to Purge above.
+ EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(2));
+
+ memory2.SetNow(Time::FromDoubleT(3));
+ memory2.Unlock(0, 0);
+
+ // Usage time should be correct for |memory2| instance.
+ EXPECT_EQ(memory2.last_known_usage(), Time::FromDoubleT(3));
+
+ // However, usage time has not changed as far as |memory1| instance knows.
+ EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(2));
+
+ // Memory is unlocked, but our usage timestamp is incorrect.
+ rv = memory1.Purge(Time::FromDoubleT(4));
+ EXPECT_FALSE(rv);
+
+ // The failed purge attempt should have updated usage time to the correct
+ // value.
+ EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(3));
+
+ // Purge memory through |memory2| instance. The last usage time should be
+ // set to 0 as a result of this.
+ rv = memory2.Purge(Time::FromDoubleT(5));
+ EXPECT_TRUE(rv);
+ EXPECT_TRUE(memory2.last_known_usage().is_null());
+
+ // This should fail as memory has already been purged and |memory1|'s usage
+ // time is incorrect as a result.
+ rv = memory1.Purge(Time::FromDoubleT(6));
+ EXPECT_FALSE(rv);
+
+ // The failed purge attempt should have updated usage time to the correct
+ // value.
+ EXPECT_TRUE(memory1.last_known_usage().is_null());
+
+ // Purge should succeed now that usage time is correct.
+ rv = memory1.Purge(Time::FromDoubleT(7));
+ EXPECT_TRUE(rv);
+}
+
+TEST(DiscardableSharedMemoryTest, LockShouldAlwaysFailAfterSuccessfulPurge) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
+ ASSERT_TRUE(shared_region.IsValid());
+
+ TestDiscardableSharedMemory memory2(std::move(shared_region));
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ memory2.SetNow(Time::FromDoubleT(1));
+ memory2.Unlock(0, 0);
+
+ rv = memory2.Purge(Time::FromDoubleT(2));
+ EXPECT_TRUE(rv);
+
+ // Lock should fail as memory has been purged.
+ DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
+ EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
+}
+
+#if defined(OS_ANDROID)
+TEST(DiscardableSharedMemoryTest, LockShouldFailIfPlatformLockPagesFails) {
+ const uint32_t kDataSize = 1024;
+
+ DiscardableSharedMemory memory1;
+ bool rv1 = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv1);
+
+ base::UnsafeSharedMemoryRegion region = memory1.DuplicateRegion();
+ int fd = region.GetPlatformHandle();
+ DiscardableSharedMemory memory2(std::move(region));
+ bool rv2 = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv2);
+
+ // Unlock() the first page of memory, so we can test Lock()ing it.
+ memory2.Unlock(0, base::GetPageSize());
+ // To cause ashmem_pin_region() to fail, we arrange for it to be called with
+ // an invalid file-descriptor, which requires a valid-looking fd (i.e. we
+ // can't just Close() |memory|), but one on which the operation is invalid.
+ // We can overwrite the |memory| fd with a handle to a different file using
+ // dup2(), which has the nice properties that |memory| still has a valid fd
+ // that it can close, etc without errors, but on which ashmem_pin_region()
+ // will fail.
+ base::ScopedFD null(open("/dev/null", O_RDONLY));
+ ASSERT_EQ(fd, dup2(null.get(), fd));
+
+ // Now re-Lock()ing the first page should fail.
+ DiscardableSharedMemory::LockResult lock_rv =
+ memory2.Lock(0, base::GetPageSize());
+ EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
+}
+#endif // defined(OS_ANDROID)
+
+TEST(DiscardableSharedMemoryTest, LockAndUnlockRange) {
+ const uint32_t kDataSize = 32;
+
+ uint32_t data_size_in_bytes = kDataSize * base::GetPageSize();
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(data_size_in_bytes);
+ ASSERT_TRUE(rv);
+
+ UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
+ ASSERT_TRUE(shared_region.IsValid());
+
+ TestDiscardableSharedMemory memory2(std::move(shared_region));
+ rv = memory2.Map(data_size_in_bytes);
+ ASSERT_TRUE(rv);
+
+ // Unlock first page.
+ memory2.SetNow(Time::FromDoubleT(1));
+ memory2.Unlock(0, base::GetPageSize());
+
+ rv = memory1.Purge(Time::FromDoubleT(2));
+ EXPECT_FALSE(rv);
+
+ // Lock first page again.
+ memory2.SetNow(Time::FromDoubleT(3));
+ DiscardableSharedMemory::LockResult lock_rv =
+ memory2.Lock(0, base::GetPageSize());
+ EXPECT_NE(DiscardableSharedMemory::FAILED, lock_rv);
+
+ // Unlock first page.
+ memory2.SetNow(Time::FromDoubleT(4));
+ memory2.Unlock(0, base::GetPageSize());
+
+ rv = memory1.Purge(Time::FromDoubleT(5));
+ EXPECT_FALSE(rv);
+
+ // Unlock second page.
+ memory2.SetNow(Time::FromDoubleT(6));
+ memory2.Unlock(base::GetPageSize(), base::GetPageSize());
+
+ rv = memory1.Purge(Time::FromDoubleT(7));
+ EXPECT_FALSE(rv);
+
+ // Unlock anything onwards.
+ memory2.SetNow(Time::FromDoubleT(8));
+ memory2.Unlock(2 * base::GetPageSize(), 0);
+
+ // Memory is unlocked, but our usage timestamp is incorrect.
+ rv = memory1.Purge(Time::FromDoubleT(9));
+ EXPECT_FALSE(rv);
+
+ // The failed purge attempt should have updated usage time to the correct
+ // value.
+ EXPECT_EQ(Time::FromDoubleT(8), memory1.last_known_usage());
+
+ // Purge should now succeed.
+ rv = memory1.Purge(Time::FromDoubleT(10));
+ EXPECT_TRUE(rv);
+}
+
+TEST(DiscardableSharedMemoryTest, MappedSize) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory;
+ bool rv = memory.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ EXPECT_LE(kDataSize, memory.mapped_size());
+
+ // Mapped size should be 0 after memory segment has been unmapped.
+ rv = memory.Unmap();
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(0u, memory.mapped_size());
+}
+
+TEST(DiscardableSharedMemoryTest, Close) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory;
+ bool rv = memory.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // Mapped size should be unchanged after memory segment has been closed.
+ memory.Close();
+ EXPECT_LE(kDataSize, memory.mapped_size());
+
+ // Memory is initially locked. Unlock it.
+ memory.SetNow(Time::FromDoubleT(1));
+ memory.Unlock(0, 0);
+
+ // Lock and unlock memory.
+ DiscardableSharedMemory::LockResult lock_rv = memory.Lock(0, 0);
+ EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
+ memory.SetNow(Time::FromDoubleT(2));
+ memory.Unlock(0, 0);
+}
+
+TEST(DiscardableSharedMemoryTest, ZeroSize) {
+ TestDiscardableSharedMemory memory;
+ bool rv = memory.CreateAndMap(0);
+ ASSERT_TRUE(rv);
+
+ EXPECT_LE(0u, memory.mapped_size());
+
+ // Memory is initially locked. Unlock it.
+ memory.SetNow(Time::FromDoubleT(1));
+ memory.Unlock(0, 0);
+
+ // Lock and unlock memory.
+ DiscardableSharedMemory::LockResult lock_rv = memory.Lock(0, 0);
+ EXPECT_NE(DiscardableSharedMemory::FAILED, lock_rv);
+ memory.SetNow(Time::FromDoubleT(2));
+ memory.Unlock(0, 0);
+}
+
+// This test checks that zero-filled pages are returned after purging a segment
+// when DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE is
+// defined and MADV_REMOVE is supported.
+#if defined(DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE)
+TEST(DiscardableSharedMemoryTest, ZeroFilledPagesAfterPurge) {
+ const uint32_t kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
+ ASSERT_TRUE(shared_region.IsValid());
+
+ TestDiscardableSharedMemory memory2(std::move(shared_region));
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // Initialize all memory to '0xaa'.
+ memset(memory2.memory(), 0xaa, kDataSize);
+
+ // Unlock memory.
+ memory2.SetNow(Time::FromDoubleT(1));
+ memory2.Unlock(0, 0);
+ EXPECT_FALSE(memory1.IsMemoryLocked());
+
+ // Memory is unlocked, but our usage timestamp is incorrect.
+ rv = memory1.Purge(Time::FromDoubleT(2));
+ EXPECT_FALSE(rv);
+ rv = memory1.Purge(Time::FromDoubleT(3));
+ EXPECT_TRUE(rv);
+
+ // Check that reading memory after it has been purged is returning
+ // zero-filled pages.
+ uint8_t expected_data[kDataSize] = {};
+ EXPECT_EQ(memcmp(memory2.memory(), expected_data, kDataSize), 0);
+}
+#endif
+
+TEST(DiscardableSharedMemoryTest, TracingOwnershipEdges) {
+ const uint32_t kDataSize = 1024;
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ base::trace_event::MemoryDumpArgs args = {
+ base::trace_event::MemoryDumpLevelOfDetail::DETAILED};
+ trace_event::ProcessMemoryDump pmd(args);
+ trace_event::MemoryAllocatorDump* client_dump =
+ pmd.CreateAllocatorDump("discardable_manager/map1");
+ const bool is_owned = false;
+ memory1.CreateSharedMemoryOwnershipEdge(client_dump, &pmd, is_owned);
+ const auto* shm_dump = pmd.GetAllocatorDump(
+ SharedMemoryTracker::GetDumpNameForTracing(memory1.mapped_id()));
+ EXPECT_TRUE(shm_dump);
+ EXPECT_EQ(shm_dump->GetSizeInternal(), client_dump->GetSizeInternal());
+ const auto edges = pmd.allocator_dumps_edges();
+ EXPECT_EQ(2u, edges.size());
+ EXPECT_NE(edges.end(), edges.find(shm_dump->guid()));
+ EXPECT_NE(edges.end(), edges.find(client_dump->guid()));
+ // TODO(ssid): test for weak global dump once the
+ // CreateWeakSharedMemoryOwnershipEdge() is fixed, crbug.com/661257.
+}
+
+} // namespace base
diff --git a/base/memory/memory_coordinator_client.cc b/base/memory/memory_coordinator_client.cc
new file mode 100644
index 0000000000..7fa6232177
--- /dev/null
+++ b/base/memory/memory_coordinator_client.cc
@@ -0,0 +1,27 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_coordinator_client.h"
+
+#include "base/logging.h"
+
+namespace base {
+
+const char* MemoryStateToString(MemoryState state) {
+ switch (state) {
+ case MemoryState::UNKNOWN:
+ return "unknown";
+ case MemoryState::NORMAL:
+ return "normal";
+ case MemoryState::THROTTLED:
+ return "throttled";
+ case MemoryState::SUSPENDED:
+ return "suspended";
+ default:
+ NOTREACHED();
+ }
+ return "";
+}
+
+} // namespace base
diff --git a/base/memory/memory_coordinator_client.h b/base/memory/memory_coordinator_client.h
new file mode 100644
index 0000000000..804f0a6b85
--- /dev/null
+++ b/base/memory/memory_coordinator_client.h
@@ -0,0 +1,79 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_MEMORY_COORDINATOR_CLIENT_H_
+#define BASE_MEMORY_MEMORY_COORDINATOR_CLIENT_H_
+
+#include "base/base_export.h"
+
+namespace base {
+
+// OVERVIEW:
+//
+// MemoryCoordinatorClient is an interface which a component can implement to
+// adjust "future allocation" and "existing allocation". For "future allocation"
+// it provides a callback to observe memory state changes, and for "existing
+// allocation" it provides a callback to purge memory.
+//
+// Unlike MemoryPressureListener, memory state changes are stateful. State
+// transitions are throttled to avoid thrashing; the exact throttling period is
+// platform dependent, but will be at least 5-10 seconds. When a state change
+// notification is dispatched, clients are expected to update their allocation
+// policies (e.g. setting cache limit) that persist for the duration of the
+// memory state. Note that clients aren't expected to free up memory on memory
+// state changes. Clients should wait for a separate purge request to free up
+// memory. Purging requests will be throttled as well.
+
+// MemoryState is an indicator that processes can use to guide their memory
+// allocation policies. For example, a process that receives the throttled
+// state can use that as as signal to decrease memory cache limits.
+// NOTE: This enum is used to back an UMA histogram, and therefore should be
+// treated as append-only.
+enum class MemoryState : int {
+ // The state is unknown.
+ UNKNOWN = -1,
+ // No memory constraints.
+ NORMAL = 0,
+ // Running and interactive but memory allocation should be throttled.
+ // Clients should set lower budget for any memory that is used as an
+ // optimization but that is not necessary for the process to run.
+ // (e.g. caches)
+ THROTTLED = 1,
+ // Still resident in memory but core processing logic has been suspended.
+ // In most cases, OnPurgeMemory() will be called before entering this state.
+ SUSPENDED = 2,
+};
+
+const int kMemoryStateMax = static_cast<int>(MemoryState::SUSPENDED) + 1;
+
+// Returns a string representation of MemoryState.
+BASE_EXPORT const char* MemoryStateToString(MemoryState state);
+
+// This is an interface for components which can respond to memory status
+// changes. An initial state is NORMAL. See MemoryCoordinatorClientRegistry for
+// threading guarantees and ownership management.
+class BASE_EXPORT MemoryCoordinatorClient {
+ public:
+ // Called when memory state has changed. Any transition can occur except for
+ // UNKNOWN. General guidelines are:
+ // * NORMAL: Restore the default settings for memory allocation/usage if
+ // it has changed.
+ // * THROTTLED: Use smaller limits for future memory allocations. You don't
+ // need to take any action on existing allocations.
+ // * SUSPENDED: Use much smaller limits for future memory allocations. You
+ // don't need to take any action on existing allocations.
+ virtual void OnMemoryStateChange(MemoryState state) {}
+
+ // Called to purge memory.
+ // This callback should free up any memory that is used as an optimization, or
+ // any memory whose contents can be reproduced.
+ virtual void OnPurgeMemory() {}
+
+ protected:
+ virtual ~MemoryCoordinatorClient() = default;
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_MEMORY_COORDINATOR_CLIENT_H_
diff --git a/base/memory/memory_coordinator_client_registry.cc b/base/memory/memory_coordinator_client_registry.cc
new file mode 100644
index 0000000000..45b4a7f5bc
--- /dev/null
+++ b/base/memory/memory_coordinator_client_registry.cc
@@ -0,0 +1,41 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_coordinator_client_registry.h"
+
+namespace base {
+
+// static
+MemoryCoordinatorClientRegistry*
+MemoryCoordinatorClientRegistry::GetInstance() {
+ return Singleton<
+ MemoryCoordinatorClientRegistry,
+ LeakySingletonTraits<MemoryCoordinatorClientRegistry>>::get();
+}
+
+MemoryCoordinatorClientRegistry::MemoryCoordinatorClientRegistry()
+ : clients_(new ClientList) {}
+
+MemoryCoordinatorClientRegistry::~MemoryCoordinatorClientRegistry() = default;
+
+void MemoryCoordinatorClientRegistry::Register(
+ MemoryCoordinatorClient* client) {
+ clients_->AddObserver(client);
+}
+
+void MemoryCoordinatorClientRegistry::Unregister(
+ MemoryCoordinatorClient* client) {
+ clients_->RemoveObserver(client);
+}
+
+void MemoryCoordinatorClientRegistry::Notify(MemoryState state) {
+ clients_->Notify(FROM_HERE,
+ &base::MemoryCoordinatorClient::OnMemoryStateChange, state);
+}
+
+void MemoryCoordinatorClientRegistry::PurgeMemory() {
+ clients_->Notify(FROM_HERE, &base::MemoryCoordinatorClient::OnPurgeMemory);
+}
+
+} // namespace base
diff --git a/base/memory/memory_coordinator_client_registry.h b/base/memory/memory_coordinator_client_registry.h
new file mode 100644
index 0000000000..e2c81b7187
--- /dev/null
+++ b/base/memory/memory_coordinator_client_registry.h
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_MEMORY_CLIENT_REGISTRY_H_
+#define BASE_MEMORY_MEMORY_CLIENT_REGISTRY_H_
+
+#include "base/base_export.h"
+#include "base/memory/memory_coordinator_client.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list_threadsafe.h"
+
+namespace base {
+
+// MemoryCoordinatorClientRegistry is the registry of MemoryCoordinatorClients.
+// This class manages clients and provides a way to notify memory state changes
+// to clients, but this isn't responsible to determine how/when to change
+// memory states.
+//
+// Threading guarantees:
+// This class uses ObserverListThreadsafe internally, which means that
+// * Registering/unregistering callbacks are thread-safe.
+// * Callbacks are invoked on the same thread on which they are registered.
+// See base/observer_list_threadsafe.h for reference.
+//
+// Ownership management:
+// This class doesn't take the ownership of clients. Clients must be
+// unregistered before they are destroyed.
+class BASE_EXPORT MemoryCoordinatorClientRegistry {
+ public:
+ static MemoryCoordinatorClientRegistry* GetInstance();
+
+ ~MemoryCoordinatorClientRegistry();
+
+ // Registers/unregisters a client. Does not take ownership of client.
+ void Register(MemoryCoordinatorClient* client);
+ void Unregister(MemoryCoordinatorClient* client);
+
+ // Notify clients of a memory state change.
+ void Notify(MemoryState state);
+
+ // Requests purging memory.
+ void PurgeMemory();
+
+ private:
+ friend struct DefaultSingletonTraits<MemoryCoordinatorClientRegistry>;
+
+ MemoryCoordinatorClientRegistry();
+
+ using ClientList = ObserverListThreadSafe<MemoryCoordinatorClient>;
+ scoped_refptr<ClientList> clients_;
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_MEMORY_CLIENT_REGISTRY_H_
diff --git a/base/memory/memory_coordinator_client_registry_unittest.cc b/base/memory/memory_coordinator_client_registry_unittest.cc
new file mode 100644
index 0000000000..37ed7673d9
--- /dev/null
+++ b/base/memory/memory_coordinator_client_registry_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_coordinator_client_registry.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class TestMemoryCoordinatorClient : public MemoryCoordinatorClient {
+ public:
+ void OnMemoryStateChange(MemoryState state) override { state_ = state; }
+
+ void OnPurgeMemory() override { ++purge_count_; }
+
+ MemoryState state() const { return state_; }
+ size_t purge_count() const { return purge_count_; }
+
+ private:
+ MemoryState state_ = MemoryState::UNKNOWN;
+ size_t purge_count_ = 0;
+};
+
+void RunUntilIdle() {
+ base::RunLoop loop;
+ loop.RunUntilIdle();
+}
+
+TEST(MemoryCoordinatorClientRegistryTest, NotifyStateChange) {
+ MessageLoop loop;
+ auto* registry = MemoryCoordinatorClientRegistry::GetInstance();
+ TestMemoryCoordinatorClient client;
+ registry->Register(&client);
+ registry->Notify(MemoryState::THROTTLED);
+ RunUntilIdle();
+ ASSERT_EQ(MemoryState::THROTTLED, client.state());
+ registry->Unregister(&client);
+}
+
+TEST(MemoryCoordinatorClientRegistryTest, PurgeMemory) {
+ MessageLoop loop;
+ auto* registry = MemoryCoordinatorClientRegistry::GetInstance();
+ TestMemoryCoordinatorClient client;
+ registry->Register(&client);
+ registry->PurgeMemory();
+ RunUntilIdle();
+ ASSERT_EQ(1u, client.purge_count());
+ registry->Unregister(&client);
+}
+
+} // namespace
+
+} // namespace base
diff --git a/base/memory/memory_coordinator_proxy.cc b/base/memory/memory_coordinator_proxy.cc
new file mode 100644
index 0000000000..4e22fe04fc
--- /dev/null
+++ b/base/memory/memory_coordinator_proxy.cc
@@ -0,0 +1,37 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_coordinator_proxy.h"
+
+namespace base {
+
+namespace {
+
+MemoryCoordinator* g_memory_coordinator = nullptr;
+
+} // namespace
+
+MemoryCoordinatorProxy::MemoryCoordinatorProxy() = default;
+
+MemoryCoordinatorProxy::~MemoryCoordinatorProxy() = default;
+
+// static
+MemoryCoordinatorProxy* MemoryCoordinatorProxy::GetInstance() {
+ return Singleton<base::MemoryCoordinatorProxy>::get();
+}
+
+// static
+void MemoryCoordinatorProxy::SetMemoryCoordinator(
+ MemoryCoordinator* coordinator) {
+ DCHECK(!g_memory_coordinator || !coordinator);
+ g_memory_coordinator = coordinator;
+}
+
+MemoryState MemoryCoordinatorProxy::GetCurrentMemoryState() const {
+ if (!g_memory_coordinator)
+ return MemoryState::NORMAL;
+ return g_memory_coordinator->GetCurrentMemoryState();
+}
+
+} // namespace base
diff --git a/base/memory/memory_coordinator_proxy.h b/base/memory/memory_coordinator_proxy.h
new file mode 100644
index 0000000000..b6e7b3f6ef
--- /dev/null
+++ b/base/memory/memory_coordinator_proxy.h
@@ -0,0 +1,49 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_MEMORY_COORDINATOR_PROXY_H_
+#define BASE_MEMORY_MEMORY_COORDINATOR_PROXY_H_
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/memory/memory_coordinator_client.h"
+#include "base/memory/singleton.h"
+
+namespace base {
+
+// The MemoryCoordinator interface. See comments in MemoryCoordinatorProxy for
+// method descriptions.
+class BASE_EXPORT MemoryCoordinator {
+ public:
+ virtual ~MemoryCoordinator() = default;
+
+ virtual MemoryState GetCurrentMemoryState() const = 0;
+};
+
+// The proxy of MemoryCoordinator to be accessed from components that are not
+// in content/browser e.g. net.
+class BASE_EXPORT MemoryCoordinatorProxy {
+ public:
+ static MemoryCoordinatorProxy* GetInstance();
+
+ // Sets an implementation of MemoryCoordinator. MemoryCoordinatorProxy doesn't
+ // take the ownership of |coordinator|. It must outlive this proxy.
+ // This should be called before any components starts using this proxy.
+ static void SetMemoryCoordinator(MemoryCoordinator* coordinator);
+
+ // Returns the current memory state.
+ MemoryState GetCurrentMemoryState() const;
+
+ private:
+ friend struct base::DefaultSingletonTraits<MemoryCoordinatorProxy>;
+
+ MemoryCoordinatorProxy();
+ virtual ~MemoryCoordinatorProxy();
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryCoordinatorProxy);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_MEMORY_COORDINATOR_PROXY_H_
diff --git a/base/memory/memory_pressure_listener.cc b/base/memory/memory_pressure_listener.cc
new file mode 100644
index 0000000000..669fb17b7a
--- /dev/null
+++ b/base/memory/memory_pressure_listener.cc
@@ -0,0 +1,129 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_pressure_listener.h"
+
+#include "base/observer_list_threadsafe.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+
+namespace {
+
+// This class is thread safe and internally synchronized.
+class MemoryPressureObserver {
+ public:
+ // There is at most one MemoryPressureObserver and it is never deleted.
+ ~MemoryPressureObserver() = delete;
+
+ void AddObserver(MemoryPressureListener* listener, bool sync) {
+ async_observers_->AddObserver(listener);
+ if (sync) {
+ AutoLock lock(sync_observers_lock_);
+ sync_observers_.AddObserver(listener);
+ }
+ }
+
+ void RemoveObserver(MemoryPressureListener* listener) {
+ async_observers_->RemoveObserver(listener);
+ AutoLock lock(sync_observers_lock_);
+ sync_observers_.RemoveObserver(listener);
+ }
+
+ void Notify(
+ MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
+ async_observers_->Notify(FROM_HERE, &MemoryPressureListener::Notify,
+ memory_pressure_level);
+ AutoLock lock(sync_observers_lock_);
+ for (auto& observer : sync_observers_)
+ observer.SyncNotify(memory_pressure_level);
+ }
+
+ private:
+ const scoped_refptr<ObserverListThreadSafe<MemoryPressureListener>>
+ async_observers_ = base::MakeRefCounted<
+ ObserverListThreadSafe<MemoryPressureListener>>();
+ ObserverList<MemoryPressureListener> sync_observers_;
+ Lock sync_observers_lock_;
+};
+
+// Gets the shared MemoryPressureObserver singleton instance.
+MemoryPressureObserver* GetMemoryPressureObserver() {
+ static auto* const observer = new MemoryPressureObserver();
+ return observer;
+}
+
+subtle::Atomic32 g_notifications_suppressed = 0;
+
+} // namespace
+
+MemoryPressureListener::MemoryPressureListener(
+ const MemoryPressureListener::MemoryPressureCallback& callback)
+ : callback_(callback) {
+ GetMemoryPressureObserver()->AddObserver(this, false);
+}
+
+MemoryPressureListener::MemoryPressureListener(
+ const MemoryPressureListener::MemoryPressureCallback& callback,
+ const MemoryPressureListener::SyncMemoryPressureCallback&
+ sync_memory_pressure_callback)
+ : callback_(callback),
+ sync_memory_pressure_callback_(sync_memory_pressure_callback) {
+ GetMemoryPressureObserver()->AddObserver(this, true);
+}
+
+MemoryPressureListener::~MemoryPressureListener() {
+ GetMemoryPressureObserver()->RemoveObserver(this);
+}
+
+void MemoryPressureListener::Notify(MemoryPressureLevel memory_pressure_level) {
+ callback_.Run(memory_pressure_level);
+}
+
+void MemoryPressureListener::SyncNotify(
+ MemoryPressureLevel memory_pressure_level) {
+ if (!sync_memory_pressure_callback_.is_null()) {
+ sync_memory_pressure_callback_.Run(memory_pressure_level);
+ }
+}
+
+// static
+void MemoryPressureListener::NotifyMemoryPressure(
+ MemoryPressureLevel memory_pressure_level) {
+ DCHECK_NE(memory_pressure_level, MEMORY_PRESSURE_LEVEL_NONE);
+ TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("memory-infra"),
+ "MemoryPressureListener::NotifyMemoryPressure",
+ TRACE_EVENT_SCOPE_THREAD, "level",
+ memory_pressure_level);
+ if (AreNotificationsSuppressed())
+ return;
+ DoNotifyMemoryPressure(memory_pressure_level);
+}
+
+// static
+bool MemoryPressureListener::AreNotificationsSuppressed() {
+ return subtle::Acquire_Load(&g_notifications_suppressed) == 1;
+}
+
+// static
+void MemoryPressureListener::SetNotificationsSuppressed(bool suppress) {
+ subtle::Release_Store(&g_notifications_suppressed, suppress ? 1 : 0);
+}
+
+// static
+void MemoryPressureListener::SimulatePressureNotification(
+ MemoryPressureLevel memory_pressure_level) {
+ // Notify all listeners even if regular pressure notifications are suppressed.
+ DoNotifyMemoryPressure(memory_pressure_level);
+}
+
+// static
+void MemoryPressureListener::DoNotifyMemoryPressure(
+ MemoryPressureLevel memory_pressure_level) {
+ DCHECK_NE(memory_pressure_level, MEMORY_PRESSURE_LEVEL_NONE);
+
+ GetMemoryPressureObserver()->Notify(memory_pressure_level);
+}
+
+} // namespace base
diff --git a/base/memory/memory_pressure_listener.h b/base/memory/memory_pressure_listener.h
new file mode 100644
index 0000000000..7e97010085
--- /dev/null
+++ b/base/memory/memory_pressure_listener.h
@@ -0,0 +1,102 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// MemoryPressure provides static APIs for handling memory pressure on
+// platforms that have such signals, such as Android and ChromeOS.
+// The app will try to discard buffers that aren't deemed essential (individual
+// modules will implement their own policy).
+
+#ifndef BASE_MEMORY_MEMORY_PRESSURE_LISTENER_H_
+#define BASE_MEMORY_MEMORY_PRESSURE_LISTENER_H_
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace base {
+
+// To start listening, create a new instance, passing a callback to a
+// function that takes a MemoryPressureLevel parameter. To stop listening,
+// simply delete the listener object. The implementation guarantees
+// that the callback will always be called on the thread that created
+// the listener.
+// Note that even on the same thread, the callback is not guaranteed to be
+// called synchronously within the system memory pressure broadcast.
+// Please see notes in MemoryPressureLevel enum below: some levels are
+// absolutely critical, and if not enough memory is returned to the system,
+// it'll potentially kill the app, and then later the app will have to be
+// cold-started.
+//
+// Example:
+//
+// void OnMemoryPressure(MemoryPressureLevel memory_pressure_level) {
+// ...
+// }
+//
+// // Start listening.
+// MemoryPressureListener* my_listener =
+// new MemoryPressureListener(base::Bind(&OnMemoryPressure));
+//
+// ...
+//
+// // Stop listening.
+// delete my_listener;
+//
+class BASE_EXPORT MemoryPressureListener {
+ public:
+ // A Java counterpart will be generated for this enum.
+ // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base
+ enum MemoryPressureLevel {
+ // No problems, there is enough memory to use. This event is not sent via
+ // callback, but the enum is used in other places to find out the current
+ // state of the system.
+ MEMORY_PRESSURE_LEVEL_NONE,
+
+ // Modules are advised to free buffers that are cheap to re-allocate and not
+ // immediately needed.
+ MEMORY_PRESSURE_LEVEL_MODERATE,
+
+ // At this level, modules are advised to free all possible memory. The
+ // alternative is to be killed by the system, which means all memory will
+ // have to be re-created, plus the cost of a cold start.
+ MEMORY_PRESSURE_LEVEL_CRITICAL,
+ };
+
+ typedef Callback<void(MemoryPressureLevel)> MemoryPressureCallback;
+ typedef Callback<void(MemoryPressureLevel)> SyncMemoryPressureCallback;
+
+ explicit MemoryPressureListener(
+ const MemoryPressureCallback& memory_pressure_callback);
+ MemoryPressureListener(
+ const MemoryPressureCallback& memory_pressure_callback,
+ const SyncMemoryPressureCallback& sync_memory_pressure_callback);
+
+ ~MemoryPressureListener();
+
+ // Intended for use by the platform specific implementation.
+ static void NotifyMemoryPressure(MemoryPressureLevel memory_pressure_level);
+
+ // These methods should not be used anywhere else but in memory measurement
+ // code, where they are intended to maintain stable conditions across
+ // measurements.
+ static bool AreNotificationsSuppressed();
+ static void SetNotificationsSuppressed(bool suppressed);
+ static void SimulatePressureNotification(
+ MemoryPressureLevel memory_pressure_level);
+
+ void Notify(MemoryPressureLevel memory_pressure_level);
+ void SyncNotify(MemoryPressureLevel memory_pressure_level);
+
+ private:
+ static void DoNotifyMemoryPressure(MemoryPressureLevel memory_pressure_level);
+
+ MemoryPressureCallback callback_;
+ SyncMemoryPressureCallback sync_memory_pressure_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryPressureListener);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_MEMORY_PRESSURE_LISTENER_H_
diff --git a/base/memory/memory_pressure_listener_unittest.cc b/base/memory/memory_pressure_listener_unittest.cc
new file mode 100644
index 0000000000..87d5f4cbbe
--- /dev/null
+++ b/base/memory/memory_pressure_listener_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_pressure_listener.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+
+using MemoryPressureLevel = MemoryPressureListener::MemoryPressureLevel;
+
+class MemoryPressureListenerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ message_loop_.reset(new MessageLoopForUI());
+ listener_.reset(new MemoryPressureListener(
+ Bind(&MemoryPressureListenerTest::OnMemoryPressure, Unretained(this))));
+ }
+
+ void TearDown() override {
+ listener_.reset();
+ message_loop_.reset();
+ }
+
+ protected:
+ void ExpectNotification(
+ void (*notification_function)(MemoryPressureLevel),
+ MemoryPressureLevel level) {
+ EXPECT_CALL(*this, OnMemoryPressure(level)).Times(1);
+ notification_function(level);
+ RunLoop().RunUntilIdle();
+ }
+
+ void ExpectNoNotification(
+ void (*notification_function)(MemoryPressureLevel),
+ MemoryPressureLevel level) {
+ EXPECT_CALL(*this, OnMemoryPressure(testing::_)).Times(0);
+ notification_function(level);
+ RunLoop().RunUntilIdle();
+ }
+
+ private:
+ MOCK_METHOD1(OnMemoryPressure,
+ void(MemoryPressureListener::MemoryPressureLevel));
+
+ std::unique_ptr<MessageLoopForUI> message_loop_;
+ std::unique_ptr<MemoryPressureListener> listener_;
+};
+
+TEST_F(MemoryPressureListenerTest, NotifyMemoryPressure) {
+ // Memory pressure notifications are not suppressed by default.
+ EXPECT_FALSE(MemoryPressureListener::AreNotificationsSuppressed());
+ ExpectNotification(&MemoryPressureListener::NotifyMemoryPressure,
+ MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE);
+ ExpectNotification(&MemoryPressureListener::SimulatePressureNotification,
+ MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE);
+
+ // Enable suppressing memory pressure notifications.
+ MemoryPressureListener::SetNotificationsSuppressed(true);
+ EXPECT_TRUE(MemoryPressureListener::AreNotificationsSuppressed());
+ ExpectNoNotification(&MemoryPressureListener::NotifyMemoryPressure,
+ MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE);
+ ExpectNotification(&MemoryPressureListener::SimulatePressureNotification,
+ MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE);
+
+ // Disable suppressing memory pressure notifications.
+ MemoryPressureListener::SetNotificationsSuppressed(false);
+ EXPECT_FALSE(MemoryPressureListener::AreNotificationsSuppressed());
+ ExpectNotification(&MemoryPressureListener::NotifyMemoryPressure,
+ MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL);
+ ExpectNotification(&MemoryPressureListener::SimulatePressureNotification,
+ MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL);
+}
+
+} // namespace base
diff --git a/base/memory/memory_pressure_monitor.cc b/base/memory/memory_pressure_monitor.cc
new file mode 100644
index 0000000000..ed350b81b9
--- /dev/null
+++ b/base/memory/memory_pressure_monitor.cc
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_pressure_monitor.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+
+namespace base {
+namespace {
+
+MemoryPressureMonitor* g_monitor = nullptr;
+
+// Enumeration of UMA memory pressure levels. This needs to be kept in sync with
+// histograms.xml and the memory pressure levels defined in
+// MemoryPressureListener.
+enum MemoryPressureLevelUMA {
+ UMA_MEMORY_PRESSURE_LEVEL_NONE = 0,
+ UMA_MEMORY_PRESSURE_LEVEL_MODERATE = 1,
+ UMA_MEMORY_PRESSURE_LEVEL_CRITICAL = 2,
+ // This must be the last value in the enum.
+ UMA_MEMORY_PRESSURE_LEVEL_COUNT,
+};
+
+// Converts a memory pressure level to an UMA enumeration value.
+MemoryPressureLevelUMA MemoryPressureLevelToUmaEnumValue(
+ base::MemoryPressureListener::MemoryPressureLevel level) {
+ switch (level) {
+ case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
+ return UMA_MEMORY_PRESSURE_LEVEL_NONE;
+ case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
+ return UMA_MEMORY_PRESSURE_LEVEL_MODERATE;
+ case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
+ return UMA_MEMORY_PRESSURE_LEVEL_CRITICAL;
+ }
+ NOTREACHED();
+ return UMA_MEMORY_PRESSURE_LEVEL_NONE;
+}
+
+} // namespace
+
+MemoryPressureMonitor::MemoryPressureMonitor() {
+ DCHECK(!g_monitor);
+ g_monitor = this;
+}
+
+MemoryPressureMonitor::~MemoryPressureMonitor() {
+ DCHECK(g_monitor);
+ g_monitor = nullptr;
+}
+
+// static
+MemoryPressureMonitor* MemoryPressureMonitor::Get() {
+ return g_monitor;
+}
+void MemoryPressureMonitor::RecordMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel level,
+ int ticks) {
+ // Use the more primitive STATIC_HISTOGRAM_POINTER_BLOCK macro because the
+ // simple UMA_HISTOGRAM macros don't expose 'AddCount' functionality.
+ STATIC_HISTOGRAM_POINTER_BLOCK(
+ "Memory.PressureLevel",
+ AddCount(MemoryPressureLevelToUmaEnumValue(level), ticks),
+ base::LinearHistogram::FactoryGet(
+ "Memory.PressureLevel", 1, UMA_MEMORY_PRESSURE_LEVEL_COUNT,
+ UMA_MEMORY_PRESSURE_LEVEL_COUNT + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag));
+}
+
+} // namespace base
diff --git a/base/memory/memory_pressure_monitor.h b/base/memory/memory_pressure_monitor.h
new file mode 100644
index 0000000000..e48244b433
--- /dev/null
+++ b/base/memory/memory_pressure_monitor.h
@@ -0,0 +1,53 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_MEMORY_PRESSURE_MONITOR_H_
+#define BASE_MEMORY_MEMORY_PRESSURE_MONITOR_H_
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/memory_pressure_listener.h"
+
+namespace base {
+
+// TODO(chrisha): Make this a concrete class with per-OS implementations rather
+// than an abstract base class.
+
+// Declares the interface for a MemoryPressureMonitor. There are multiple
+// OS specific implementations of this class. An instance of the memory
+// pressure observer is created at the process level, tracks memory usage, and
+// pushes memory state change notifications to the static function
+// base::MemoryPressureListener::NotifyMemoryPressure. This is turn notifies
+// all MemoryPressureListener instances via a callback.
+class BASE_EXPORT MemoryPressureMonitor {
+ public:
+ using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
+ using DispatchCallback = base::Callback<void(MemoryPressureLevel level)>;
+
+ virtual ~MemoryPressureMonitor();
+
+ // Return the singleton MemoryPressureMonitor.
+ static MemoryPressureMonitor* Get();
+
+ // Record memory pressure UMA statistic. A tick is 5 seconds.
+ static void RecordMemoryPressure(MemoryPressureLevel level, int ticks);
+
+ // Returns the currently observed memory pressure.
+ virtual MemoryPressureLevel GetCurrentPressureLevel() = 0;
+
+ // Sets a notification callback. The default callback invokes
+ // base::MemoryPressureListener::NotifyMemoryPressure.
+ virtual void SetDispatchCallback(const DispatchCallback& callback) = 0;
+
+ protected:
+ MemoryPressureMonitor();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MemoryPressureMonitor);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_MEMORY_PRESSURE_MONITOR_H_
diff --git a/base/memory/memory_pressure_monitor_chromeos.cc b/base/memory/memory_pressure_monitor_chromeos.cc
new file mode 100644
index 0000000000..b4e4b94787
--- /dev/null
+++ b/base/memory/memory_pressure_monitor_chromeos.cc
@@ -0,0 +1,288 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_pressure_monitor_chromeos.h"
+
+#include <fcntl.h>
+#include <sys/select.h>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/process/process_metrics.h"
+#include "base/single_thread_task_runner.h"
+#include "base/sys_info.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+
+namespace base {
+namespace chromeos {
+
+namespace {
+
+// The time between memory pressure checks. While under critical pressure, this
+// is also the timer to repeat cleanup attempts.
+const int kMemoryPressureIntervalMs = 1000;
+
+// The time which should pass between two moderate memory pressure calls.
+const int kModerateMemoryPressureCooldownMs = 10000;
+
+// Number of event polls before the next moderate pressure event can be sent.
+const int kModerateMemoryPressureCooldown =
+ kModerateMemoryPressureCooldownMs / kMemoryPressureIntervalMs;
+
+// Threshold constants to emit pressure events.
+const int kNormalMemoryPressureModerateThresholdPercent = 60;
+const int kNormalMemoryPressureCriticalThresholdPercent = 95;
+const int kAggressiveMemoryPressureModerateThresholdPercent = 35;
+const int kAggressiveMemoryPressureCriticalThresholdPercent = 70;
+
+// The possible state for memory pressure level. The values should be in line
+// with values in MemoryPressureListener::MemoryPressureLevel and should be
+// updated if more memory pressure levels are introduced.
+enum MemoryPressureLevelUMA {
+ MEMORY_PRESSURE_LEVEL_NONE = 0,
+ MEMORY_PRESSURE_LEVEL_MODERATE,
+ MEMORY_PRESSURE_LEVEL_CRITICAL,
+ NUM_MEMORY_PRESSURE_LEVELS
+};
+
+// This is the file that will exist if low memory notification is available
+// on the device. Whenever it becomes readable, it signals a low memory
+// condition.
+const char kLowMemFile[] = "/dev/chromeos-low-mem";
+
+// Converts a |MemoryPressureThreshold| value into a used memory percentage for
+// the moderate pressure event.
+int GetModerateMemoryThresholdInPercent(
+ MemoryPressureMonitor::MemoryPressureThresholds thresholds) {
+ return thresholds == MemoryPressureMonitor::
+ THRESHOLD_AGGRESSIVE_CACHE_DISCARD ||
+ thresholds == MemoryPressureMonitor::THRESHOLD_AGGRESSIVE
+ ? kAggressiveMemoryPressureModerateThresholdPercent
+ : kNormalMemoryPressureModerateThresholdPercent;
+}
+
+// Converts a |MemoryPressureThreshold| value into a used memory percentage for
+// the critical pressure event.
+int GetCriticalMemoryThresholdInPercent(
+ MemoryPressureMonitor::MemoryPressureThresholds thresholds) {
+ return thresholds == MemoryPressureMonitor::
+ THRESHOLD_AGGRESSIVE_TAB_DISCARD ||
+ thresholds == MemoryPressureMonitor::THRESHOLD_AGGRESSIVE
+ ? kAggressiveMemoryPressureCriticalThresholdPercent
+ : kNormalMemoryPressureCriticalThresholdPercent;
+}
+
+// Converts free percent of memory into a memory pressure value.
+MemoryPressureListener::MemoryPressureLevel GetMemoryPressureLevelFromFillLevel(
+ int actual_fill_level,
+ int moderate_threshold,
+ int critical_threshold) {
+ if (actual_fill_level < moderate_threshold)
+ return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
+ return actual_fill_level < critical_threshold
+ ? MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE
+ : MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
+}
+
+// This function will be called less than once a second. It will check if
+// the kernel has detected a low memory situation.
+bool IsLowMemoryCondition(int file_descriptor) {
+ fd_set fds;
+ struct timeval tv;
+
+ FD_ZERO(&fds);
+ FD_SET(file_descriptor, &fds);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ return HANDLE_EINTR(select(file_descriptor + 1, &fds, NULL, NULL, &tv)) > 0;
+}
+
+} // namespace
+
+MemoryPressureMonitor::MemoryPressureMonitor(
+ MemoryPressureThresholds thresholds)
+ : current_memory_pressure_level_(
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE),
+ moderate_pressure_repeat_count_(0),
+ seconds_since_reporting_(0),
+ moderate_pressure_threshold_percent_(
+ GetModerateMemoryThresholdInPercent(thresholds)),
+ critical_pressure_threshold_percent_(
+ GetCriticalMemoryThresholdInPercent(thresholds)),
+ low_mem_file_(HANDLE_EINTR(::open(kLowMemFile, O_RDONLY))),
+ dispatch_callback_(
+ base::Bind(&MemoryPressureListener::NotifyMemoryPressure)),
+ weak_ptr_factory_(this) {
+ StartObserving();
+ LOG_IF(ERROR,
+ base::SysInfo::IsRunningOnChromeOS() && !low_mem_file_.is_valid())
+ << "Cannot open kernel listener";
+}
+
+MemoryPressureMonitor::~MemoryPressureMonitor() {
+ StopObserving();
+}
+
+void MemoryPressureMonitor::ScheduleEarlyCheck() {
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&MemoryPressureMonitor::CheckMemoryPressure,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+MemoryPressureListener::MemoryPressureLevel
+MemoryPressureMonitor::GetCurrentPressureLevel() {
+ return current_memory_pressure_level_;
+}
+
+// static
+MemoryPressureMonitor* MemoryPressureMonitor::Get() {
+ return static_cast<MemoryPressureMonitor*>(
+ base::MemoryPressureMonitor::Get());
+}
+
+void MemoryPressureMonitor::StartObserving() {
+ timer_.Start(FROM_HERE,
+ TimeDelta::FromMilliseconds(kMemoryPressureIntervalMs),
+ Bind(&MemoryPressureMonitor::
+ CheckMemoryPressureAndRecordStatistics,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void MemoryPressureMonitor::StopObserving() {
+ // If StartObserving failed, StopObserving will still get called.
+ timer_.Stop();
+}
+
+void MemoryPressureMonitor::CheckMemoryPressureAndRecordStatistics() {
+ CheckMemoryPressure();
+ if (seconds_since_reporting_++ == 5) {
+ seconds_since_reporting_ = 0;
+ RecordMemoryPressure(current_memory_pressure_level_, 1);
+ }
+ // Record UMA histogram statistics for the current memory pressure level.
+ // TODO(lgrey): Remove this once there's a usable history for the
+ // "Memory.PressureLevel" statistic
+ MemoryPressureLevelUMA memory_pressure_level_uma(MEMORY_PRESSURE_LEVEL_NONE);
+ switch (current_memory_pressure_level_) {
+ case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
+ memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_NONE;
+ break;
+ case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
+ memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_MODERATE;
+ break;
+ case MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
+ memory_pressure_level_uma = MEMORY_PRESSURE_LEVEL_CRITICAL;
+ break;
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("ChromeOS.MemoryPressureLevel",
+ memory_pressure_level_uma,
+ NUM_MEMORY_PRESSURE_LEVELS);
+}
+
+void MemoryPressureMonitor::CheckMemoryPressure() {
+ MemoryPressureListener::MemoryPressureLevel old_pressure =
+ current_memory_pressure_level_;
+
+ // If we have the kernel low memory observer, we use it's flag instead of our
+ // own computation (for now). Note that in "simulation mode" it can be null.
+ // TODO(skuhne): We need to add code which makes sure that the kernel and this
+ // computation come to similar results and then remove this override again.
+ // TODO(skuhne): Add some testing framework here to see how close the kernel
+ // and the internal functions are.
+ if (low_mem_file_.is_valid() && IsLowMemoryCondition(low_mem_file_.get())) {
+ current_memory_pressure_level_ =
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
+ } else {
+ current_memory_pressure_level_ = GetMemoryPressureLevelFromFillLevel(
+ GetUsedMemoryInPercent(),
+ moderate_pressure_threshold_percent_,
+ critical_pressure_threshold_percent_);
+
+ // When listening to the kernel, we ignore the reported memory pressure
+ // level from our own computation and reduce critical to moderate.
+ if (low_mem_file_.is_valid() &&
+ current_memory_pressure_level_ ==
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
+ current_memory_pressure_level_ =
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
+ }
+ }
+
+ // In case there is no memory pressure we do not notify.
+ if (current_memory_pressure_level_ ==
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
+ return;
+ }
+ if (old_pressure == current_memory_pressure_level_) {
+ // If the memory pressure is still at the same level, we notify again for a
+ // critical level. In case of a moderate level repeat however, we only send
+ // a notification after a certain time has passed.
+ if (current_memory_pressure_level_ ==
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE &&
+ ++moderate_pressure_repeat_count_ <
+ kModerateMemoryPressureCooldown) {
+ return;
+ }
+ } else if (current_memory_pressure_level_ ==
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE &&
+ old_pressure ==
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
+ // When we reducing the pressure level from critical to moderate, we
+ // restart the timeout and do not send another notification.
+ moderate_pressure_repeat_count_ = 0;
+ return;
+ }
+ moderate_pressure_repeat_count_ = 0;
+ dispatch_callback_.Run(current_memory_pressure_level_);
+}
+
+// Gets the used ChromeOS memory in percent.
+int MemoryPressureMonitor::GetUsedMemoryInPercent() {
+ base::SystemMemoryInfoKB info;
+ if (!base::GetSystemMemoryInfo(&info)) {
+ VLOG(1) << "Cannot determine the free memory of the system.";
+ return 0;
+ }
+ // TODO(skuhne): Instead of adding the kernel memory pressure calculation
+ // logic here, we should have a kernel mechanism similar to the low memory
+ // notifier in ChromeOS which offers multiple pressure states.
+ // To track this, we have crbug.com/381196.
+
+ // The available memory consists of "real" and virtual (z)ram memory.
+ // Since swappable memory uses a non pre-deterministic compression and
+ // the compression creates its own "dynamic" in the system, it gets
+ // de-emphasized by the |kSwapWeight| factor.
+ const int kSwapWeight = 4;
+
+ // The total memory we have is the "real memory" plus the virtual (z)ram.
+ int total_memory = info.total + info.swap_total / kSwapWeight;
+
+ // The kernel internally uses 50MB.
+ const int kMinFileMemory = 50 * 1024;
+
+ // Most file memory can be easily reclaimed.
+ int file_memory = info.active_file + info.inactive_file;
+ // unless it is dirty or it's a minimal portion which is required.
+ file_memory -= info.dirty + kMinFileMemory;
+
+ // Available memory is the sum of free, swap and easy reclaimable memory.
+ int available_memory =
+ info.free + info.swap_free / kSwapWeight + file_memory;
+
+ DCHECK(available_memory < total_memory);
+ int percentage = ((total_memory - available_memory) * 100) / total_memory;
+ return percentage;
+}
+
+void MemoryPressureMonitor::SetDispatchCallback(
+ const DispatchCallback& callback) {
+ dispatch_callback_ = callback;
+}
+
+} // namespace chromeos
+} // namespace base
diff --git a/base/memory/memory_pressure_monitor_chromeos.h b/base/memory/memory_pressure_monitor_chromeos.h
new file mode 100644
index 0000000000..563ba85081
--- /dev/null
+++ b/base/memory/memory_pressure_monitor_chromeos.h
@@ -0,0 +1,128 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_MEMORY_PRESSURE_MONITOR_CHROMEOS_H_
+#define BASE_MEMORY_MEMORY_PRESSURE_MONITOR_CHROMEOS_H_
+
+#include "base/base_export.h"
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/memory/memory_pressure_monitor.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+
+namespace base {
+namespace chromeos {
+
+class TestMemoryPressureMonitor;
+
+////////////////////////////////////////////////////////////////////////////////
+// MemoryPressureMonitor
+//
+// A class to handle the observation of our free memory. It notifies the
+// MemoryPressureListener of memory fill level changes, so that it can take
+// action to reduce memory resources accordingly.
+//
+class BASE_EXPORT MemoryPressureMonitor : public base::MemoryPressureMonitor {
+ public:
+ using GetUsedMemoryInPercentCallback = int (*)();
+
+ // There are two memory pressure events:
+ // MODERATE - which will mainly release caches.
+ // CRITICAL - which will discard tabs.
+ // The |MemoryPressureThresholds| enum selects the strategy of firing these
+ // events: A conservative strategy will keep as much content in memory as
+ // possible (causing the system to swap to zram) and an aggressive strategy
+ // will release memory earlier to avoid swapping.
+ enum MemoryPressureThresholds {
+ // Use the system default.
+ THRESHOLD_DEFAULT = 0,
+ // Try to keep as much content in memory as possible.
+ THRESHOLD_CONSERVATIVE = 1,
+ // Discard caches earlier, allowing to keep more tabs in memory.
+ THRESHOLD_AGGRESSIVE_CACHE_DISCARD = 2,
+ // Discard tabs earlier, allowing the system to get faster.
+ THRESHOLD_AGGRESSIVE_TAB_DISCARD = 3,
+ // Discard caches and tabs earlier to allow the system to be faster.
+ THRESHOLD_AGGRESSIVE = 4
+ };
+
+ explicit MemoryPressureMonitor(MemoryPressureThresholds thresholds);
+ ~MemoryPressureMonitor() override;
+
+ // Redo the memory pressure calculation soon and call again if a critical
+ // memory pressure prevails. Note that this call will trigger an asynchronous
+ // action which gives the system time to release memory back into the pool.
+ void ScheduleEarlyCheck();
+
+ // Get the current memory pressure level.
+ MemoryPressureListener::MemoryPressureLevel GetCurrentPressureLevel()
+ override;
+ void SetDispatchCallback(const DispatchCallback& callback) override;
+
+ // Returns a type-casted version of the current memory pressure monitor. A
+ // simple wrapper to base::MemoryPressureMonitor::Get.
+ static MemoryPressureMonitor* Get();
+
+ private:
+ friend TestMemoryPressureMonitor;
+ // Starts observing the memory fill level.
+ // Calls to StartObserving should always be matched with calls to
+ // StopObserving.
+ void StartObserving();
+
+ // Stop observing the memory fill level.
+ // May be safely called if StartObserving has not been called.
+ void StopObserving();
+
+ // The function which gets periodically called to check any changes in the
+ // memory pressure. It will report pressure changes as well as continuous
+ // critical pressure levels.
+ void CheckMemoryPressure();
+
+ // The function periodically checks the memory pressure changes and records
+ // the UMA histogram statistics for the current memory pressure level.
+ void CheckMemoryPressureAndRecordStatistics();
+
+ // Get the memory pressure in percent (virtual for testing).
+ virtual int GetUsedMemoryInPercent();
+
+ // The current memory pressure.
+ base::MemoryPressureListener::MemoryPressureLevel
+ current_memory_pressure_level_;
+
+ // A periodic timer to check for resource pressure changes. This will get
+ // replaced by a kernel triggered event system (see crbug.com/381196).
+ base::RepeatingTimer timer_;
+
+ // To slow down the amount of moderate pressure event calls, this counter
+ // gets used to count the number of events since the last event occured.
+ int moderate_pressure_repeat_count_;
+
+ // The "Memory.PressureLevel" statistic is recorded every
+ // 5 seconds, but the timer to report "ChromeOS.MemoryPressureLevel"
+ // fires every second. This counter is used to allow reporting
+ // "Memory.PressureLevel" correctly without adding another
+ // timer.
+ int seconds_since_reporting_;
+
+ // The thresholds for moderate and critical pressure.
+ const int moderate_pressure_threshold_percent_;
+ const int critical_pressure_threshold_percent_;
+
+ // File descriptor used to detect low memory condition.
+ ScopedFD low_mem_file_;
+
+ DispatchCallback dispatch_callback_;
+
+ base::WeakPtrFactory<MemoryPressureMonitor> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryPressureMonitor);
+};
+
+} // namespace chromeos
+} // namespace base
+
+#endif // BASE_MEMORY_MEMORY_PRESSURE_MONITOR_CHROMEOS_H_
diff --git a/base/memory/memory_pressure_monitor_chromeos_unittest.cc b/base/memory/memory_pressure_monitor_chromeos_unittest.cc
new file mode 100644
index 0000000000..ee000911ea
--- /dev/null
+++ b/base/memory/memory_pressure_monitor_chromeos_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_pressure_monitor_chromeos.h"
+
+#include "base/macros.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/sys_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace chromeos {
+
+namespace {
+
+// True if the memory notifier got called.
+// Do not read/modify value directly.
+bool on_memory_pressure_called = false;
+
+// If the memory notifier got called, this is the memory pressure reported.
+MemoryPressureListener::MemoryPressureLevel on_memory_pressure_level =
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
+
+// Processes OnMemoryPressure calls.
+void OnMemoryPressure(MemoryPressureListener::MemoryPressureLevel level) {
+ on_memory_pressure_called = true;
+ on_memory_pressure_level = level;
+}
+
+// Resets the indicator for memory pressure.
+void ResetOnMemoryPressureCalled() {
+ on_memory_pressure_called = false;
+}
+
+// Returns true when OnMemoryPressure was called (and resets it).
+bool WasOnMemoryPressureCalled() {
+ bool b = on_memory_pressure_called;
+ ResetOnMemoryPressureCalled();
+ return b;
+}
+
+} // namespace
+
+class TestMemoryPressureMonitor : public MemoryPressureMonitor {
+ public:
+ TestMemoryPressureMonitor()
+ : MemoryPressureMonitor(THRESHOLD_DEFAULT),
+ memory_in_percent_override_(0) {
+ // Disable any timers which are going on and set a special memory reporting
+ // function.
+ StopObserving();
+ }
+ ~TestMemoryPressureMonitor() override = default;
+
+ void SetMemoryInPercentOverride(int percent) {
+ memory_in_percent_override_ = percent;
+ }
+
+ void CheckMemoryPressureForTest() {
+ CheckMemoryPressure();
+ }
+
+ private:
+ int GetUsedMemoryInPercent() override {
+ return memory_in_percent_override_;
+ }
+
+ int memory_in_percent_override_;
+ DISALLOW_COPY_AND_ASSIGN(TestMemoryPressureMonitor);
+};
+
+// This test tests the various transition states from memory pressure, looking
+// for the correct behavior on event reposting as well as state updates.
+TEST(ChromeOSMemoryPressureMonitorTest, CheckMemoryPressure) {
+ // crbug.com/844102:
+ if (base::SysInfo::IsRunningOnChromeOS())
+ return;
+
+ base::MessageLoopForUI message_loop;
+ std::unique_ptr<TestMemoryPressureMonitor> monitor(
+ new TestMemoryPressureMonitor);
+ std::unique_ptr<MemoryPressureListener> listener(
+ new MemoryPressureListener(base::Bind(&OnMemoryPressure)));
+ // Checking the memory pressure while 0% are used should not produce any
+ // events.
+ monitor->SetMemoryInPercentOverride(0);
+ ResetOnMemoryPressureCalled();
+
+ monitor->CheckMemoryPressureForTest();
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(WasOnMemoryPressureCalled());
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
+ monitor->GetCurrentPressureLevel());
+
+ // Setting the memory level to 80% should produce a moderate pressure level.
+ monitor->SetMemoryInPercentOverride(80);
+ monitor->CheckMemoryPressureForTest();
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(WasOnMemoryPressureCalled());
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+ monitor->GetCurrentPressureLevel());
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+ on_memory_pressure_level);
+
+ // We need to check that the event gets reposted after a while.
+ int i = 0;
+ for (; i < 100; i++) {
+ monitor->CheckMemoryPressureForTest();
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+ monitor->GetCurrentPressureLevel());
+ if (WasOnMemoryPressureCalled()) {
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+ on_memory_pressure_level);
+ break;
+ }
+ }
+ // Should be more than 5 and less than 100.
+ EXPECT_LE(5, i);
+ EXPECT_GE(99, i);
+
+ // Setting the memory usage to 99% should produce critical levels.
+ monitor->SetMemoryInPercentOverride(99);
+ monitor->CheckMemoryPressureForTest();
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(WasOnMemoryPressureCalled());
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
+ on_memory_pressure_level);
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
+ monitor->GetCurrentPressureLevel());
+
+ // Calling it again should immediately produce a second call.
+ monitor->CheckMemoryPressureForTest();
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(WasOnMemoryPressureCalled());
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
+ on_memory_pressure_level);
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
+ monitor->GetCurrentPressureLevel());
+
+ // When lowering the pressure again we should not get an event, but the
+ // pressure should go back to moderate.
+ monitor->SetMemoryInPercentOverride(80);
+ monitor->CheckMemoryPressureForTest();
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(WasOnMemoryPressureCalled());
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+ monitor->GetCurrentPressureLevel());
+
+ // We should need exactly the same amount of calls as before, before the next
+ // call comes in.
+ int j = 0;
+ for (; j < 100; j++) {
+ monitor->CheckMemoryPressureForTest();
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+ monitor->GetCurrentPressureLevel());
+ if (WasOnMemoryPressureCalled()) {
+ EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+ on_memory_pressure_level);
+ break;
+ }
+ }
+ // We should have needed exactly the same amount of checks as before.
+ EXPECT_EQ(j, i);
+}
+
+} // namespace chromeos
+} // namespace base
diff --git a/base/memory/memory_pressure_monitor_unittest.cc b/base/memory/memory_pressure_monitor_unittest.cc
new file mode 100644
index 0000000000..10d9d2428f
--- /dev/null
+++ b/base/memory/memory_pressure_monitor_unittest.cc
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/memory_pressure_monitor.h"
+
+#include "base/macros.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(MemoryPressureMonitorTest, RecordMemoryPressure) {
+ base::HistogramTester tester;
+ const char* kHistogram = "Memory.PressureLevel";
+
+ MemoryPressureMonitor::RecordMemoryPressure(
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, 3);
+ tester.ExpectTotalCount(kHistogram, 3);
+ tester.ExpectBucketCount(kHistogram, 0, 3);
+
+ MemoryPressureMonitor::RecordMemoryPressure(
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, 2);
+ tester.ExpectTotalCount(kHistogram, 5);
+ tester.ExpectBucketCount(kHistogram, 1, 2);
+
+ MemoryPressureMonitor::RecordMemoryPressure(
+ MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, 1);
+ tester.ExpectTotalCount(kHistogram, 6);
+ tester.ExpectBucketCount(kHistogram, 2, 1);
+}
+} // namespace base
diff --git a/base/memory/platform_shared_memory_region_android.cc b/base/memory/platform_shared_memory_region_android.cc
new file mode 100644
index 0000000000..154e9e52df
--- /dev/null
+++ b/base/memory/platform_shared_memory_region_android.cc
@@ -0,0 +1,203 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/platform_shared_memory_region.h"
+
+#include <sys/mman.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "third_party/ashmem/ashmem.h"
+
+namespace base {
+namespace subtle {
+
+// For Android, we use ashmem to implement SharedMemory. ashmem_create_region
+// will automatically pin the region. We never explicitly call pin/unpin. When
+// all the file descriptors from different processes associated with the region
+// are closed, the memory buffer will go away.
+
+namespace {
+
+static int GetAshmemRegionProtectionMask(int fd) {
+ int prot = ashmem_get_prot_region(fd);
+ if (prot < 0) {
+ DPLOG(ERROR) << "ashmem_get_prot_region failed";
+ return -1;
+ }
+ return prot;
+}
+
+} // namespace
+
+// static
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take(
+ ScopedFD fd,
+ Mode mode,
+ size_t size,
+ const UnguessableToken& guid) {
+ if (!fd.is_valid())
+ return {};
+
+ if (size == 0)
+ return {};
+
+ if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
+ return {};
+
+ CHECK(CheckPlatformHandlePermissionsCorrespondToMode(fd.get(), mode, size));
+
+ return PlatformSharedMemoryRegion(std::move(fd), mode, size, guid);
+}
+
+int PlatformSharedMemoryRegion::GetPlatformHandle() const {
+ return handle_.get();
+}
+
+bool PlatformSharedMemoryRegion::IsValid() const {
+ return handle_.is_valid();
+}
+
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const {
+ if (!IsValid())
+ return {};
+
+ CHECK_NE(mode_, Mode::kWritable)
+ << "Duplicating a writable shared memory region is prohibited";
+
+ ScopedFD duped_fd(HANDLE_EINTR(dup(handle_.get())));
+ if (!duped_fd.is_valid()) {
+ DPLOG(ERROR) << "dup(" << handle_.get() << ") failed";
+ return {};
+ }
+
+ return PlatformSharedMemoryRegion(std::move(duped_fd), mode_, size_, guid_);
+}
+
+bool PlatformSharedMemoryRegion::ConvertToReadOnly() {
+ if (!IsValid())
+ return false;
+
+ CHECK_EQ(mode_, Mode::kWritable)
+ << "Only writable shared memory region can be converted to read-only";
+
+ ScopedFD handle_copy(handle_.release());
+
+ int prot = GetAshmemRegionProtectionMask(handle_copy.get());
+ if (prot < 0)
+ return false;
+
+ prot &= ~PROT_WRITE;
+ int ret = ashmem_set_prot_region(handle_copy.get(), prot);
+ if (ret != 0) {
+ DPLOG(ERROR) << "ashmem_set_prot_region failed";
+ return false;
+ }
+
+ handle_ = std::move(handle_copy);
+ mode_ = Mode::kReadOnly;
+ return true;
+}
+
+bool PlatformSharedMemoryRegion::ConvertToUnsafe() {
+ if (!IsValid())
+ return false;
+
+ CHECK_EQ(mode_, Mode::kWritable)
+ << "Only writable shared memory region can be converted to unsafe";
+
+ mode_ = Mode::kUnsafe;
+ return true;
+}
+
+bool PlatformSharedMemoryRegion::MapAt(off_t offset,
+ size_t size,
+ void** memory,
+ size_t* mapped_size) const {
+ if (!IsValid())
+ return false;
+
+ size_t end_byte;
+ if (!CheckAdd(offset, size).AssignIfValid(&end_byte) || end_byte > size_) {
+ return false;
+ }
+
+ bool write_allowed = mode_ != Mode::kReadOnly;
+ *memory = mmap(nullptr, size, PROT_READ | (write_allowed ? PROT_WRITE : 0),
+ MAP_SHARED, handle_.get(), offset);
+
+ bool mmap_succeeded = *memory && *memory != reinterpret_cast<void*>(-1);
+ if (!mmap_succeeded) {
+ DPLOG(ERROR) << "mmap " << handle_.get() << " failed";
+ return false;
+ }
+
+ *mapped_size = size;
+ DCHECK_EQ(0U,
+ reinterpret_cast<uintptr_t>(*memory) & (kMapMinimumAlignment - 1));
+ return true;
+}
+
+// static
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
+ size_t size) {
+ if (size == 0)
+ return {};
+
+ if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
+ return {};
+
+ CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will "
+ "lead to this region being non-modifiable";
+
+ UnguessableToken guid = UnguessableToken::Create();
+
+ // trace_event is not supported in libchrome. To avoid includes of more
+ // trace_event code by base/memory/shared_memory_tracker.h, replace
+ // SharedMemoryTracker::GetDumpNameForTracing by the actual implementation.
+ ScopedFD fd(ashmem_create_region(
+ ("shared_memory/"+guid.ToString()).c_str(), size));
+ if (!fd.is_valid()) {
+ DPLOG(ERROR) << "ashmem_create_region failed";
+ return {};
+ }
+
+ int err = ashmem_set_prot_region(fd.get(), PROT_READ | PROT_WRITE);
+ if (err < 0) {
+ DPLOG(ERROR) << "ashmem_set_prot_region failed";
+ return {};
+ }
+
+ return PlatformSharedMemoryRegion(std::move(fd), mode, size, guid);
+}
+
+bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode(
+ PlatformHandle handle,
+ Mode mode,
+ size_t size) {
+ int prot = GetAshmemRegionProtectionMask(handle);
+ if (prot < 0)
+ return false;
+
+ bool is_read_only = (prot & PROT_WRITE) == 0;
+ bool expected_read_only = mode == Mode::kReadOnly;
+
+ if (is_read_only != expected_read_only) {
+ DLOG(ERROR) << "Ashmem region has a wrong protection mask: it is"
+ << (is_read_only ? " " : " not ") << "read-only but it should"
+ << (expected_read_only ? " " : " not ") << "be";
+ return false;
+ }
+
+ return true;
+}
+
+PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
+ ScopedFD fd,
+ Mode mode,
+ size_t size,
+ const UnguessableToken& guid)
+ : handle_(std::move(fd)), mode_(mode), size_(size), guid_(guid) {}
+
+} // namespace subtle
+} // namespace base
diff --git a/base/memory/platform_shared_memory_region_mac.cc b/base/memory/platform_shared_memory_region_mac.cc
deleted file mode 100644
index 4a8b440c26..0000000000
--- a/base/memory/platform_shared_memory_region_mac.cc
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/memory/platform_shared_memory_region.h"
-
-#include <mach/mach_vm.h>
-
-#include "base/mac/mach_logging.h"
-#include "base/mac/scoped_mach_vm.h"
-#include "base/numerics/checked_math.h"
-#include "build/build_config.h"
-
-#if defined(OS_IOS)
-#error "MacOS only - iOS uses platform_shared_memory_region_posix.cc"
-#endif
-
-namespace base {
-namespace subtle {
-
-// static
-PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take(
- mac::ScopedMachSendRight handle,
- Mode mode,
- size_t size,
- const UnguessableToken& guid) {
- if (!handle.is_valid())
- return {};
-
- if (size == 0)
- return {};
-
- if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
- return {};
-
- CHECK(
- CheckPlatformHandlePermissionsCorrespondToMode(handle.get(), mode, size));
-
- return PlatformSharedMemoryRegion(std::move(handle), mode, size, guid);
-}
-
-mach_port_t PlatformSharedMemoryRegion::GetPlatformHandle() const {
- return handle_.get();
-}
-
-bool PlatformSharedMemoryRegion::IsValid() const {
- return handle_.is_valid();
-}
-
-PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const {
- if (!IsValid())
- return {};
-
- CHECK_NE(mode_, Mode::kWritable)
- << "Duplicating a writable shared memory region is prohibited";
-
- // Increment the ref count.
- kern_return_t kr = mach_port_mod_refs(mach_task_self(), handle_.get(),
- MACH_PORT_RIGHT_SEND, 1);
- if (kr != KERN_SUCCESS) {
- MACH_DLOG(ERROR, kr) << "mach_port_mod_refs";
- return {};
- }
-
- return PlatformSharedMemoryRegion(mac::ScopedMachSendRight(handle_.get()),
- mode_, size_, guid_);
-}
-
-bool PlatformSharedMemoryRegion::ConvertToReadOnly() {
- return ConvertToReadOnly(nullptr);
-}
-
-bool PlatformSharedMemoryRegion::ConvertToReadOnly(void* mapped_addr) {
- if (!IsValid())
- return false;
-
- CHECK_EQ(mode_, Mode::kWritable)
- << "Only writable shared memory region can be converted to read-only";
-
- mac::ScopedMachSendRight handle_copy(handle_.release());
-
- void* temp_addr = mapped_addr;
- mac::ScopedMachVM scoped_memory;
- if (!temp_addr) {
- // Intentionally lower current prot and max prot to |VM_PROT_READ|.
- kern_return_t kr = mach_vm_map(
- mach_task_self(), reinterpret_cast<mach_vm_address_t*>(&temp_addr),
- size_, 0, VM_FLAGS_ANYWHERE, handle_copy.get(), 0, FALSE, VM_PROT_READ,
- VM_PROT_READ, VM_INHERIT_NONE);
- if (kr != KERN_SUCCESS) {
- MACH_DLOG(ERROR, kr) << "mach_vm_map";
- return false;
- }
- scoped_memory.reset(reinterpret_cast<vm_address_t>(temp_addr),
- mach_vm_round_page(size_));
- }
-
- // Make new memory object.
- memory_object_size_t allocation_size = size_;
- mac::ScopedMachSendRight named_right;
- kern_return_t kr = mach_make_memory_entry_64(
- mach_task_self(), &allocation_size,
- reinterpret_cast<memory_object_offset_t>(temp_addr), VM_PROT_READ,
- named_right.receive(), MACH_PORT_NULL);
- if (kr != KERN_SUCCESS) {
- MACH_DLOG(ERROR, kr) << "mach_make_memory_entry_64";
- return false;
- }
- DCHECK_GE(allocation_size, size_);
-
- handle_ = std::move(named_right);
- mode_ = Mode::kReadOnly;
- return true;
-}
-
-bool PlatformSharedMemoryRegion::ConvertToUnsafe() {
- if (!IsValid())
- return false;
-
- CHECK_EQ(mode_, Mode::kWritable)
- << "Only writable shared memory region can be converted to unsafe";
-
- mode_ = Mode::kUnsafe;
- return true;
-}
-
-bool PlatformSharedMemoryRegion::MapAt(off_t offset,
- size_t size,
- void** memory,
- size_t* mapped_size) const {
- if (!IsValid())
- return false;
-
- size_t end_byte;
- if (!CheckAdd(offset, size).AssignIfValid(&end_byte) || end_byte > size_) {
- return false;
- }
-
- bool write_allowed = mode_ != Mode::kReadOnly;
- vm_prot_t vm_prot_write = write_allowed ? VM_PROT_WRITE : 0;
- kern_return_t kr = mach_vm_map(
- mach_task_self(),
- reinterpret_cast<mach_vm_address_t*>(memory), // Output parameter
- size,
- 0, // Alignment mask
- VM_FLAGS_ANYWHERE, handle_.get(), offset,
- FALSE, // Copy
- VM_PROT_READ | vm_prot_write, // Current protection
- VM_PROT_READ | vm_prot_write, // Maximum protection
- VM_INHERIT_NONE);
- if (kr != KERN_SUCCESS) {
- MACH_DLOG(ERROR, kr) << "mach_vm_map";
- return false;
- }
-
- *mapped_size = size;
- DCHECK_EQ(0U,
- reinterpret_cast<uintptr_t>(*memory) & (kMapMinimumAlignment - 1));
- return true;
-}
-
-// static
-PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
- size_t size) {
- if (size == 0)
- return {};
-
- if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
- return {};
-
- CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will "
- "lead to this region being non-modifiable";
-
- mach_vm_size_t vm_size = size;
- mac::ScopedMachSendRight named_right;
- kern_return_t kr = mach_make_memory_entry_64(
- mach_task_self(), &vm_size,
- 0, // Address.
- MAP_MEM_NAMED_CREATE | VM_PROT_READ | VM_PROT_WRITE,
- named_right.receive(),
- MACH_PORT_NULL); // Parent handle.
- if (kr != KERN_SUCCESS) {
- MACH_DLOG(ERROR, kr) << "mach_make_memory_entry_64";
- return {};
- }
- DCHECK_GE(vm_size, size);
-
- return PlatformSharedMemoryRegion(std::move(named_right), mode, size,
- UnguessableToken::Create());
-}
-
-// static
-bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode(
- PlatformHandle handle,
- Mode mode,
- size_t size) {
- mach_vm_address_t temp_addr = 0;
- kern_return_t kr =
- mach_vm_map(mach_task_self(), &temp_addr, size, 0, VM_FLAGS_ANYWHERE,
- handle, 0, FALSE, VM_PROT_READ | VM_PROT_WRITE,
- VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_NONE);
- if (kr == KERN_SUCCESS) {
- kern_return_t kr_deallocate =
- mach_vm_deallocate(mach_task_self(), temp_addr, size);
- MACH_DLOG_IF(ERROR, kr_deallocate != KERN_SUCCESS, kr_deallocate)
- << "mach_vm_deallocate";
- } else if (kr != KERN_INVALID_RIGHT) {
- MACH_DLOG(ERROR, kr) << "mach_vm_map";
- return false;
- }
-
- bool is_read_only = kr == KERN_INVALID_RIGHT;
- bool expected_read_only = mode == Mode::kReadOnly;
-
- if (is_read_only != expected_read_only) {
- DLOG(ERROR) << "VM region has a wrong protection mask: it is"
- << (is_read_only ? " " : " not ") << "read-only but it should"
- << (expected_read_only ? " " : " not ") << "be";
- return false;
- }
-
- return true;
-}
-
-PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
- mac::ScopedMachSendRight handle,
- Mode mode,
- size_t size,
- const UnguessableToken& guid)
- : handle_(std::move(handle)), mode_(mode), size_(size), guid_(guid) {}
-
-} // namespace subtle
-} // namespace base
diff --git a/base/memory/ptr_util_unittest.cc b/base/memory/ptr_util_unittest.cc
new file mode 100644
index 0000000000..3fa40d8098
--- /dev/null
+++ b/base/memory/ptr_util_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/ptr_util.h"
+
+#include <stddef.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class DeleteCounter {
+ public:
+ DeleteCounter() { ++count_; }
+ ~DeleteCounter() { --count_; }
+
+ static size_t count() { return count_; }
+
+ private:
+ static size_t count_;
+};
+
+size_t DeleteCounter::count_ = 0;
+
+} // namespace
+
+TEST(PtrUtilTest, WrapUnique) {
+ EXPECT_EQ(0u, DeleteCounter::count());
+ DeleteCounter* counter = new DeleteCounter;
+ EXPECT_EQ(1u, DeleteCounter::count());
+ std::unique_ptr<DeleteCounter> owned_counter = WrapUnique(counter);
+ EXPECT_EQ(1u, DeleteCounter::count());
+ owned_counter.reset();
+ EXPECT_EQ(0u, DeleteCounter::count());
+}
+
+} // namespace base
diff --git a/base/memory/ref_counted_unittest.nc b/base/memory/ref_counted_unittest.nc
new file mode 100644
index 0000000000..b8c371f748
--- /dev/null
+++ b/base/memory/ref_counted_unittest.nc
@@ -0,0 +1,28 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/ref_counted.h"
+
+namespace base {
+
+class InitialRefCountIsZero : public base::RefCounted<InitialRefCountIsZero> {
+ public:
+ InitialRefCountIsZero() {}
+ private:
+ friend class base::RefCounted<InitialRefCountIsZero>;
+ ~InitialRefCountIsZero() {}
+};
+
+// TODO(hans): Remove .* and update the static_assert expectations once we roll
+// past Clang r313315. https://crbug.com/765692.
+
+#if defined(NCTEST_ADOPT_REF_TO_ZERO_START) // [r"fatal error: static_assert failed .*\"Use AdoptRef only for the reference count starts from one\.\""]
+
+void WontCompile() {
+ AdoptRef(new InitialRefCountIsZero());
+}
+
+#endif
+
+} // namespace base
diff --git a/base/memory/shared_memory_handle.h b/base/memory/shared_memory_handle.h
index 7367188c8b..dd3d47aa0c 100644
--- a/base/memory/shared_memory_handle.h
+++ b/base/memory/shared_memory_handle.h
@@ -146,7 +146,7 @@ class BASE_EXPORT SharedMemoryHandle {
int Release();
#endif
-#if defined(OS_ANDROID) || defined(__ANDROID__)
+#if defined(OS_ANDROID)
// Marks the current file descriptor as read-only, for the purpose of
// mapping. This is independent of the region's read-only status.
void SetReadOnly() { read_only_ = true; }
@@ -218,7 +218,7 @@ class BASE_EXPORT SharedMemoryHandle {
bool ownership_passes_to_ipc_ = false;
};
};
-#elif defined(OS_ANDROID) || defined(__ANDROID__)
+#elif defined(OS_ANDROID)
friend class SharedMemory;
FileDescriptor file_descriptor_;
diff --git a/base/memory/shared_memory_posix.cc b/base/memory/shared_memory_posix.cc
index aa718957cf..6b811b9f66 100644
--- a/base/memory/shared_memory_posix.cc
+++ b/base/memory/shared_memory_posix.cc
@@ -30,8 +30,6 @@
#if defined(OS_ANDROID)
#include "base/os_compat_android.h"
-#endif
-#if defined(OS_ANDROID) || defined(__ANDROID__)
#include "third_party/ashmem/ashmem.h"
#endif
@@ -83,7 +81,7 @@ bool SharedMemory::CreateAndMapAnonymous(size_t size) {
return CreateAnonymous(size) && Map(size);
}
-#if !defined(OS_ANDROID) && !defined(__ANDROID__)
+#if !defined(OS_ANDROID)
// Chromium mostly only uses the unique/private shmem as specified by
// "name == L"". The exception is in the StatsTable.
@@ -255,7 +253,7 @@ bool SharedMemory::Open(const std::string& name, bool read_only) {
FileDescriptor(readonly_mapped_file, false), 0, shm_.GetGUID());
return result;
}
-#endif // !defined(OS_ANDROID) && !defined(__ANDROID__)
+#endif // !defined(OS_ANDROID)
bool SharedMemory::MapAt(off_t offset, size_t bytes) {
if (!shm_.IsValid())
@@ -267,7 +265,7 @@ bool SharedMemory::MapAt(off_t offset, size_t bytes) {
if (memory_)
return false;
-#if defined(OS_ANDROID) || defined(__ANDROID__)
+#if defined(OS_ANDROID)
// On Android, Map can be called with a size and offset of zero to use the
// ashmem-determined size.
if (bytes == 0) {
@@ -280,19 +278,19 @@ bool SharedMemory::MapAt(off_t offset, size_t bytes) {
// Sanity check. This shall catch invalid uses of the SharedMemory APIs
// but will not protect against direct mmap() attempts.
- // if (shm_.IsReadOnly()) {
- // // Use a DCHECK() to call writable mappings with read-only descriptors
- // // in debug builds immediately. Return an error for release builds
- // // or during unit-testing (assuming a ScopedLogAssertHandler was installed).
- // DCHECK(read_only_)
- // << "Trying to map a region writable with a read-only descriptor.";
- // if (!read_only_) {
- // return false;
- // }
- // if (!shm_.SetRegionReadOnly()) { // Ensure the region is read-only.
- // return false;
- // }
- // }
+ if (shm_.IsReadOnly()) {
+ // Use a DCHECK() to call writable mappings with read-only descriptors
+ // in debug builds immediately. Return an error for release builds
+ // or during unit-testing (assuming a ScopedLogAssertHandler was installed).
+ DCHECK(read_only_)
+ << "Trying to map a region writable with a read-only descriptor.";
+ if (!read_only_) {
+ return false;
+ }
+ if (!shm_.SetRegionReadOnly()) { // Ensure the region is read-only.
+ return false;
+ }
+ }
#endif
memory_ = mmap(nullptr, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
@@ -339,7 +337,7 @@ SharedMemoryHandle SharedMemory::TakeHandle() {
return handle_copy;
}
-#if !defined(OS_ANDROID) && !defined(__ANDROID__)
+#if !defined(OS_ANDROID)
void SharedMemory::Close() {
if (shm_.IsValid()) {
shm_.Close();
@@ -379,6 +377,6 @@ SharedMemoryHandle SharedMemory::GetReadOnlyHandle() const {
CHECK(readonly_shm_.IsValid());
return readonly_shm_.Duplicate();
}
-#endif // !defined(OS_ANDROID) && !defined(__ANDROID__)
+#endif // !defined(OS_ANDROID)
} // namespace base
diff --git a/base/memory/shared_memory_tracker.cc b/base/memory/shared_memory_tracker.cc
new file mode 100644
index 0000000000..5ca7c840be
--- /dev/null
+++ b/base/memory/shared_memory_tracker.cc
@@ -0,0 +1,147 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/shared_memory_tracker.h"
+
+#include "base/memory/shared_memory.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/trace_event/memory_allocator_dump_guid.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/process_memory_dump.h"
+
+namespace base {
+
+const char SharedMemoryTracker::kDumpRootName[] = "shared_memory";
+
+// static
+SharedMemoryTracker* SharedMemoryTracker::GetInstance() {
+ static SharedMemoryTracker* instance = new SharedMemoryTracker;
+ return instance;
+}
+
+// static
+std::string SharedMemoryTracker::GetDumpNameForTracing(
+ const UnguessableToken& id) {
+ DCHECK(!id.is_empty());
+ return std::string(kDumpRootName) + "/" + id.ToString();
+}
+
+// static
+trace_event::MemoryAllocatorDumpGuid
+SharedMemoryTracker::GetGlobalDumpIdForTracing(const UnguessableToken& id) {
+ std::string dump_name = GetDumpNameForTracing(id);
+ return trace_event::MemoryAllocatorDumpGuid(dump_name);
+}
+
+// static
+const trace_event::MemoryAllocatorDump*
+SharedMemoryTracker::GetOrCreateSharedMemoryDump(
+ const SharedMemory* shared_memory,
+ trace_event::ProcessMemoryDump* pmd) {
+ return GetOrCreateSharedMemoryDumpInternal(shared_memory->memory(),
+ shared_memory->mapped_size(),
+ shared_memory->mapped_id(), pmd);
+}
+
+const trace_event::MemoryAllocatorDump*
+SharedMemoryTracker::GetOrCreateSharedMemoryDump(
+ const SharedMemoryMapping& shared_memory,
+ trace_event::ProcessMemoryDump* pmd) {
+ return GetOrCreateSharedMemoryDumpInternal(shared_memory.raw_memory_ptr(),
+ shared_memory.mapped_size(),
+ shared_memory.guid(), pmd);
+}
+
+void SharedMemoryTracker::IncrementMemoryUsage(
+ const SharedMemory& shared_memory) {
+ AutoLock hold(usages_lock_);
+ DCHECK(usages_.find(shared_memory.memory()) == usages_.end());
+ usages_.emplace(shared_memory.memory(), UsageInfo(shared_memory.mapped_size(),
+ shared_memory.mapped_id()));
+}
+
+void SharedMemoryTracker::IncrementMemoryUsage(
+ const SharedMemoryMapping& mapping) {
+ AutoLock hold(usages_lock_);
+ DCHECK(usages_.find(mapping.raw_memory_ptr()) == usages_.end());
+ usages_.emplace(mapping.raw_memory_ptr(),
+ UsageInfo(mapping.mapped_size(), mapping.guid()));
+}
+
+void SharedMemoryTracker::DecrementMemoryUsage(
+ const SharedMemory& shared_memory) {
+ AutoLock hold(usages_lock_);
+ DCHECK(usages_.find(shared_memory.memory()) != usages_.end());
+ usages_.erase(shared_memory.memory());
+}
+
+void SharedMemoryTracker::DecrementMemoryUsage(
+ const SharedMemoryMapping& mapping) {
+ AutoLock hold(usages_lock_);
+ DCHECK(usages_.find(mapping.raw_memory_ptr()) != usages_.end());
+ usages_.erase(mapping.raw_memory_ptr());
+}
+
+SharedMemoryTracker::SharedMemoryTracker() {
+ trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+ this, "SharedMemoryTracker", nullptr);
+}
+
+SharedMemoryTracker::~SharedMemoryTracker() = default;
+
+bool SharedMemoryTracker::OnMemoryDump(const trace_event::MemoryDumpArgs& args,
+ trace_event::ProcessMemoryDump* pmd) {
+ AutoLock hold(usages_lock_);
+ for (const auto& usage : usages_) {
+ const trace_event::MemoryAllocatorDump* dump =
+ GetOrCreateSharedMemoryDumpInternal(
+ usage.first, usage.second.mapped_size, usage.second.mapped_id, pmd);
+ DCHECK(dump);
+ }
+ return true;
+}
+
+// static
+const trace_event::MemoryAllocatorDump*
+SharedMemoryTracker::GetOrCreateSharedMemoryDumpInternal(
+ void* mapped_memory,
+ size_t mapped_size,
+ const UnguessableToken& mapped_id,
+ trace_event::ProcessMemoryDump* pmd) {
+ const std::string dump_name = GetDumpNameForTracing(mapped_id);
+ trace_event::MemoryAllocatorDump* local_dump =
+ pmd->GetAllocatorDump(dump_name);
+ if (local_dump)
+ return local_dump;
+
+ size_t virtual_size = mapped_size;
+ // If resident size is not available, a virtual size is used as fallback.
+ size_t size = virtual_size;
+#if defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+ base::Optional<size_t> resident_size =
+ trace_event::ProcessMemoryDump::CountResidentBytesInSharedMemory(
+ mapped_memory, mapped_size);
+ if (resident_size.has_value())
+ size = resident_size.value();
+#endif
+
+ local_dump = pmd->CreateAllocatorDump(dump_name);
+ local_dump->AddScalar(trace_event::MemoryAllocatorDump::kNameSize,
+ trace_event::MemoryAllocatorDump::kUnitsBytes, size);
+ local_dump->AddScalar("virtual_size",
+ trace_event::MemoryAllocatorDump::kUnitsBytes,
+ virtual_size);
+ auto global_dump_guid = GetGlobalDumpIdForTracing(mapped_id);
+ trace_event::MemoryAllocatorDump* global_dump =
+ pmd->CreateSharedGlobalAllocatorDump(global_dump_guid);
+ global_dump->AddScalar(trace_event::MemoryAllocatorDump::kNameSize,
+ trace_event::MemoryAllocatorDump::kUnitsBytes, size);
+
+ // The edges will be overriden by the clients with correct importance.
+ pmd->AddOverridableOwnershipEdge(local_dump->guid(), global_dump->guid(),
+ 0 /* importance */);
+ return local_dump;
+}
+
+} // namespace
diff --git a/base/memory/shared_memory_tracker.h b/base/memory/shared_memory_tracker.h
new file mode 100644
index 0000000000..499b1728cb
--- /dev/null
+++ b/base/memory/shared_memory_tracker.h
@@ -0,0 +1,90 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_SHARED_MEMORY_TRACKER_H_
+#define BASE_MEMORY_SHARED_MEMORY_TRACKER_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/synchronization/lock.h"
+#include "base/trace_event/memory_dump_provider.h"
+
+namespace base {
+
+namespace trace_event {
+class MemoryAllocatorDump;
+class MemoryAllocatorDumpGuid;
+class ProcessMemoryDump;
+}
+
+// SharedMemoryTracker tracks shared memory usage.
+class BASE_EXPORT SharedMemoryTracker : public trace_event::MemoryDumpProvider {
+ public:
+ // Returns a singleton instance.
+ static SharedMemoryTracker* GetInstance();
+
+ static std::string GetDumpNameForTracing(const UnguessableToken& id);
+
+ static trace_event::MemoryAllocatorDumpGuid GetGlobalDumpIdForTracing(
+ const UnguessableToken& id);
+
+ // Gets or creates if non-existant, a memory dump for the |shared_memory|
+ // inside the given |pmd|. Also adds the necessary edges for the dump when
+ // creating the dump.
+ static const trace_event::MemoryAllocatorDump* GetOrCreateSharedMemoryDump(
+ const SharedMemory* shared_memory,
+ trace_event::ProcessMemoryDump* pmd);
+ // We're in the middle of a refactor https://crbug.com/795291. Eventually, the
+ // first call will go away.
+ static const trace_event::MemoryAllocatorDump* GetOrCreateSharedMemoryDump(
+ const SharedMemoryMapping& shared_memory,
+ trace_event::ProcessMemoryDump* pmd);
+
+ // Records shared memory usage on valid mapping.
+ void IncrementMemoryUsage(const SharedMemory& shared_memory);
+ void IncrementMemoryUsage(const SharedMemoryMapping& mapping);
+
+ // Records shared memory usage on unmapping.
+ void DecrementMemoryUsage(const SharedMemory& shared_memory);
+ void DecrementMemoryUsage(const SharedMemoryMapping& mapping);
+
+ // Root dump name for all shared memory dumps.
+ static const char kDumpRootName[];
+
+ private:
+ SharedMemoryTracker();
+ ~SharedMemoryTracker() override;
+
+ // trace_event::MemoryDumpProvider implementation.
+ bool OnMemoryDump(const trace_event::MemoryDumpArgs& args,
+ trace_event::ProcessMemoryDump* pmd) override;
+
+ static const trace_event::MemoryAllocatorDump*
+ GetOrCreateSharedMemoryDumpInternal(void* mapped_memory,
+ size_t mapped_size,
+ const UnguessableToken& mapped_id,
+ trace_event::ProcessMemoryDump* pmd);
+
+ // Information associated with each mapped address.
+ struct UsageInfo {
+ UsageInfo(size_t size, const UnguessableToken& id)
+ : mapped_size(size), mapped_id(id) {}
+
+ size_t mapped_size;
+ UnguessableToken mapped_id;
+ };
+
+ // Used to lock when |usages_| is modified or read.
+ Lock usages_lock_;
+ std::map<void*, UsageInfo> usages_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedMemoryTracker);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_SHARED_MEMORY_TRACKER_H_
diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc
index b3e32de0ef..9149a672d9 100644
--- a/base/message_loop/message_loop.cc
+++ b/base/message_loop/message_loop.cc
@@ -243,6 +243,7 @@ bool MessageLoop::InitMessagePumpForUIFactory(MessagePumpFactory* factory) {
// static
std::unique_ptr<MessagePump> MessageLoop::CreateMessagePumpForType(Type type) {
+#if !defined(OS_ANDROID)
if (type == MessageLoop::TYPE_UI) {
if (message_pump_for_ui_factory_)
return message_pump_for_ui_factory_();
@@ -257,11 +258,12 @@ std::unique_ptr<MessagePump> MessageLoop::CreateMessagePumpForType(Type type) {
return std::make_unique<MessagePumpForUI>();
#endif
}
+#endif
if (type == MessageLoop::TYPE_IO)
return std::unique_ptr<MessagePump>(new MessagePumpForIO());
-#if defined(OS_ANDROID)
+#if defined(OS_ANDROID) && 0
if (type == MessageLoop::TYPE_JAVA)
return std::unique_ptr<MessagePump>(new MessagePumpForUI());
#endif
@@ -360,7 +362,7 @@ void MessageLoop::BindToCurrentThread() {
RunLoop::RegisterDelegateForCurrentThread(this);
-#if defined(OS_ANDROID)
+#if defined(OS_ANDROID) && 0
// On Android, attach to the native loop when there is one.
if (type_ == TYPE_UI || type_ == TYPE_JAVA)
static_cast<MessagePumpForUI*>(pump_.get())->Attach(this);
@@ -674,7 +676,7 @@ bool MessageLoop::DoIdleWork() {
return false;
}
-#if !defined(OS_NACL)
+#if !defined(OS_NACL) && !defined(OS_ANDROID)
//------------------------------------------------------------------------------
// MessageLoopForUI
@@ -724,7 +726,7 @@ void MessageLoopForUI::EnableWmQuit() {
}
#endif // defined(OS_WIN)
-#endif // !defined(OS_NACL)
+#endif // !defined(OS_NACL) && !defined(OS_ANDROID)
//------------------------------------------------------------------------------
// MessageLoopForIO
diff --git a/base/message_loop/message_loop.h b/base/message_loop/message_loop.h
index e093502061..196e466001 100644
--- a/base/message_loop/message_loop.h
+++ b/base/message_loop/message_loop.h
@@ -344,7 +344,10 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate,
DISALLOW_COPY_AND_ASSIGN(MessageLoop);
};
-#if !defined(OS_NACL)
+// MessageLoopForUI is unsupported in libchrome for android target.
+// The Android UI thread is tied to the Android framework and is not used.
+// This is to avoid dependency on libandroid ALooper.
+#if !defined(OS_NACL) && !defined(OS_ANDROID)
//-----------------------------------------------------------------------------
// MessageLoopForUI extends MessageLoop with methods that are particular to a
@@ -398,7 +401,7 @@ class BASE_EXPORT MessageLoopForUI : public MessageLoop {
static_assert(sizeof(MessageLoop) == sizeof(MessageLoopForUI),
"MessageLoopForUI should not have extra member variables");
-#endif // !defined(OS_NACL)
+#endif // !defined(OS_NACL) && !defined(OS_ANDROID)
//-----------------------------------------------------------------------------
// MessageLoopForIO extends MessageLoop with methods that are particular to a
diff --git a/base/message_loop/message_loop_current.cc b/base/message_loop/message_loop_current.cc
index 4959b70e06..f8bce70c3f 100644
--- a/base/message_loop/message_loop_current.cc
+++ b/base/message_loop/message_loop_current.cc
@@ -117,7 +117,7 @@ bool MessageLoopCurrent::IsBoundToCurrentThreadInternal(
return GetTLSMessageLoop()->Get() == message_loop;
}
-#if !defined(OS_NACL)
+#if !defined(OS_NACL) && !defined(OS_ANDROID)
//------------------------------------------------------------------------------
// MessageLoopCurrentForUI
@@ -173,7 +173,7 @@ void MessageLoopCurrentForUI::Abort() {
}
#endif // defined(OS_ANDROID)
-#endif // !defined(OS_NACL)
+#endif // !defined(OS_NACL) && !defined(OS_ANDROID)
//------------------------------------------------------------------------------
// MessageLoopCurrentForIO
diff --git a/base/message_loop/message_loop_current.h b/base/message_loop/message_loop_current.h
index 61d1607e31..5eebcad944 100644
--- a/base/message_loop/message_loop_current.h
+++ b/base/message_loop/message_loop_current.h
@@ -190,7 +190,7 @@ class BASE_EXPORT MessageLoopCurrent {
MessageLoop* const current_;
};
-#if !defined(OS_NACL)
+#if !defined(OS_NACL) && !defined(OS_ANDROID)
// ForUI extension of MessageLoopCurrent.
class BASE_EXPORT MessageLoopCurrentForUI : public MessageLoopCurrent {
@@ -241,7 +241,7 @@ class BASE_EXPORT MessageLoopCurrentForUI : public MessageLoopCurrent {
MessagePumpForUI* const pump_;
};
-#endif // !defined(OS_NACL)
+#endif // !defined(OS_NACL) && !defined(OS_ANDROID)
// ForIO extension of MessageLoopCurrent.
class BASE_EXPORT MessageLoopCurrentForIO : public MessageLoopCurrent {
diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc
index 6cf6bc309d..2e339bd11a 100644
--- a/base/message_loop/message_loop_unittest.cc
+++ b/base/message_loop/message_loop_unittest.cc
@@ -34,7 +34,7 @@
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
-#if defined(OS_ANDROID)
+#if defined(OS_ANDROID) && 0
#include "base/android/java_handler_thread.h"
#include "base/android/jni_android.h"
#include "base/test/android/java_handler_thread_helpers.h"
@@ -315,7 +315,7 @@ class MessageLoopTest
DISALLOW_COPY_AND_ASSIGN(MessageLoopTest);
};
-#if defined(OS_ANDROID)
+#if defined(OS_ANDROID) && 0
void DoNotRun() {
ASSERT_TRUE(false);
}
@@ -396,7 +396,7 @@ TEST_P(MessageLoopTest, RunTasksWhileShuttingDownJavaThread) {
EXPECT_EQ(kNumPosts, observer.num_tasks_started());
EXPECT_EQ(kNumPosts, observer.num_tasks_processed());
}
-#endif // defined(OS_ANDROID)
+#endif // defined(OS_ANDROID) && 0
#if defined(OS_WIN)
diff --git a/base/message_loop/message_pump_android.cc b/base/message_loop/message_pump_android.cc
new file mode 100644
index 0000000000..3fd5567e6b
--- /dev/null
+++ b/base/message_loop/message_pump_android.cc
@@ -0,0 +1,313 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/message_loop/message_pump_android.h"
+
+#include <android/looper.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <sys/eventfd.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utility>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/callback_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/run_loop.h"
+
+// Android stripped sys/timerfd.h out of their platform headers, so we have to
+// use syscall to make use of timerfd. Once the min API level is 20, we can
+// directly use timerfd.h.
+#ifndef __NR_timerfd_create
+#error "Unable to find syscall for __NR_timerfd_create"
+#endif
+
+#ifndef TFD_TIMER_ABSTIME
+#define TFD_TIMER_ABSTIME (1 << 0)
+#endif
+
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
+
+namespace base {
+
+namespace {
+
+// See sys/timerfd.h
+int timerfd_create(int clockid, int flags) {
+ return syscall(__NR_timerfd_create, clockid, flags);
+}
+
+// See sys/timerfd.h
+int timerfd_settime(int ufc,
+ int flags,
+ const struct itimerspec* utmr,
+ struct itimerspec* otmr) {
+ return syscall(__NR_timerfd_settime, ufc, flags, utmr, otmr);
+}
+
+int NonDelayedLooperCallback(int fd, int events, void* data) {
+ if (events & ALOOPER_EVENT_HANGUP)
+ return 0;
+
+ DCHECK(events & ALOOPER_EVENT_INPUT);
+ MessagePumpForUI* pump = reinterpret_cast<MessagePumpForUI*>(data);
+ pump->OnNonDelayedLooperCallback();
+ return 1; // continue listening for events
+}
+
+int DelayedLooperCallback(int fd, int events, void* data) {
+ if (events & ALOOPER_EVENT_HANGUP)
+ return 0;
+
+ DCHECK(events & ALOOPER_EVENT_INPUT);
+ MessagePumpForUI* pump = reinterpret_cast<MessagePumpForUI*>(data);
+ pump->OnDelayedLooperCallback();
+ return 1; // continue listening for events
+}
+
+} // namespace
+
+MessagePumpForUI::MessagePumpForUI() {
+ // The Android native ALooper uses epoll to poll our file descriptors and wake
+ // us up. We use a simple level-triggered eventfd to signal that non-delayed
+ // work is available, and a timerfd to signal when delayed work is ready to
+ // be run.
+ non_delayed_fd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ CHECK_NE(non_delayed_fd_, -1);
+ DCHECK_EQ(TimeTicks::GetClock(), TimeTicks::Clock::LINUX_CLOCK_MONOTONIC);
+
+ // We can't create the timerfd with TFD_NONBLOCK | TFD_CLOEXEC as we can't
+ // include timerfd.h. See comments above on __NR_timerfd_create. It looks like
+ // they're just aliases to O_NONBLOCK and O_CLOEXEC anyways, so this should be
+ // fine.
+ delayed_fd_ = timerfd_create(CLOCK_MONOTONIC, O_NONBLOCK | O_CLOEXEC);
+ CHECK_NE(delayed_fd_, -1);
+
+ looper_ = ALooper_prepare(0);
+ DCHECK(looper_);
+ // Add a reference to the looper so it isn't deleted on us.
+ ALooper_acquire(looper_);
+ ALooper_addFd(looper_, non_delayed_fd_, 0, ALOOPER_EVENT_INPUT,
+ &NonDelayedLooperCallback, reinterpret_cast<void*>(this));
+ ALooper_addFd(looper_, delayed_fd_, 0, ALOOPER_EVENT_INPUT,
+ &DelayedLooperCallback, reinterpret_cast<void*>(this));
+}
+
+MessagePumpForUI::~MessagePumpForUI() {
+ DCHECK_EQ(ALooper_forThread(), looper_);
+ ALooper_removeFd(looper_, non_delayed_fd_);
+ ALooper_removeFd(looper_, delayed_fd_);
+ ALooper_release(looper_);
+ looper_ = nullptr;
+
+ close(non_delayed_fd_);
+ close(delayed_fd_);
+}
+
+void MessagePumpForUI::OnDelayedLooperCallback() {
+ if (ShouldQuit())
+ return;
+
+ // Clear the fd.
+ uint64_t value;
+ int ret = read(delayed_fd_, &value, sizeof(value));
+ DCHECK_GE(ret, 0);
+ delayed_scheduled_time_ = base::TimeTicks();
+
+ base::TimeTicks next_delayed_work_time;
+ delegate_->DoDelayedWork(&next_delayed_work_time);
+ if (!next_delayed_work_time.is_null()) {
+ ScheduleDelayedWork(next_delayed_work_time);
+ }
+ if (ShouldQuit())
+ return;
+ // We may be idle now, so pump the loop to find out.
+ ScheduleWork();
+}
+
+void MessagePumpForUI::OnNonDelayedLooperCallback() {
+ base::TimeTicks next_delayed_work_time;
+ bool did_any_work = false;
+
+ // Runs all native tasks scheduled to run, scheduling delayed work if
+ // necessary.
+ while (true) {
+ bool did_work_this_loop = false;
+ if (ShouldQuit())
+ return;
+ did_work_this_loop = delegate_->DoWork();
+ if (ShouldQuit())
+ return;
+
+ did_work_this_loop |= delegate_->DoDelayedWork(&next_delayed_work_time);
+
+ did_any_work |= did_work_this_loop;
+
+ // If we didn't do any work, we're out of native tasks to run, and we should
+ // return control to the looper to run Java tasks.
+ if (!did_work_this_loop)
+ break;
+ }
+ // If we did any work, return control to the looper to run java tasks before
+ // we call DoIdleWork(). We haven't cleared the fd yet, so we'll get woken up
+ // again soon to check for idle-ness.
+ if (did_any_work)
+ return;
+ if (ShouldQuit())
+ return;
+
+ // Read the file descriptor, resetting its contents to 0 and reading back the
+ // stored value.
+ // See http://man7.org/linux/man-pages/man2/eventfd.2.html
+ uint64_t value = 0;
+ int ret = read(non_delayed_fd_, &value, sizeof(value));
+ DCHECK_GE(ret, 0);
+
+ // If we read a value > 1, it means we lost the race to clear the fd before a
+ // new task was posted. This is okay, we can just re-schedule work.
+ if (value > 1) {
+ ScheduleWork();
+ } else {
+ // At this point, the java looper might not be idle - it's impossible to
+ // know pre-Android-M, so we may end up doing Idle work while java tasks are
+ // still queued up. Note that this won't cause us to fail to run java tasks
+ // using QuitWhenIdle, as the JavaHandlerThread will finish running all
+ // currently scheduled tasks before it quits. Also note that we can't just
+ // add an idle callback to the java looper, as that will fire even if native
+ // tasks are still queued up.
+ DoIdleWork();
+ if (!next_delayed_work_time.is_null()) {
+ ScheduleDelayedWork(next_delayed_work_time);
+ }
+ }
+}
+
+void MessagePumpForUI::DoIdleWork() {
+ if (delegate_->DoIdleWork()) {
+ // If DoIdleWork() resulted in any work, we're not idle yet. We need to pump
+ // the loop here because we may in fact be idle after doing idle work
+ // without any new tasks being queued.
+ ScheduleWork();
+ }
+}
+
+void MessagePumpForUI::Run(Delegate* delegate) {
+ DCHECK(IsTestImplementation());
+ // This function is only called in tests. We manually pump the native looper
+ // which won't run any java tasks.
+ quit_ = false;
+
+ SetDelegate(delegate);
+
+ // Pump the loop once in case we're starting off idle as ALooper_pollOnce will
+ // never return in that case.
+ ScheduleWork();
+ while (true) {
+ // Waits for either the delayed, or non-delayed fds to be signalled, calling
+ // either OnDelayedLooperCallback, or OnNonDelayedLooperCallback,
+ // respectively. This uses Android's Looper implementation, which is based
+ // off of epoll.
+ ALooper_pollOnce(-1, nullptr, nullptr, nullptr);
+ if (quit_)
+ break;
+ }
+}
+
+void MessagePumpForUI::Attach(Delegate* delegate) {
+ DCHECK(!quit_);
+
+ // Since the Looper is controlled by the UI thread or JavaHandlerThread, we
+ // can't use Run() like we do on other platforms or we would prevent Java
+ // tasks from running. Instead we create and initialize a run loop here, then
+ // return control back to the Looper.
+
+ SetDelegate(delegate);
+ run_loop_ = std::make_unique<RunLoop>();
+ // Since the RunLoop was just created above, BeforeRun should be guaranteed to
+ // return true (it only returns false if the RunLoop has been Quit already).
+ if (!run_loop_->BeforeRun())
+ NOTREACHED();
+}
+
+void MessagePumpForUI::Quit() {
+ if (quit_)
+ return;
+
+ quit_ = true;
+
+ int64_t value;
+ // Clear any pending timer.
+ read(delayed_fd_, &value, sizeof(value));
+ // Clear the eventfd.
+ read(non_delayed_fd_, &value, sizeof(value));
+
+ if (run_loop_) {
+ run_loop_->AfterRun();
+ run_loop_ = nullptr;
+ }
+ if (on_quit_callback_) {
+ std::move(on_quit_callback_).Run();
+ }
+}
+
+void MessagePumpForUI::ScheduleWork() {
+ if (ShouldQuit())
+ return;
+
+ // Write (add) 1 to the eventfd. This tells the Looper to wake up and call our
+ // callback, allowing us to run tasks. This also allows us to detect, when we
+ // clear the fd, whether additional work was scheduled after we finished
+ // performing work, but before we cleared the fd, as we'll read back >=2
+ // instead of 1 in that case.
+ // See the eventfd man pages
+ // (http://man7.org/linux/man-pages/man2/eventfd.2.html) for details on how
+ // the read and write APIs for this file descriptor work, specifically without
+ // EFD_SEMAPHORE.
+ uint64_t value = 1;
+ int ret = write(non_delayed_fd_, &value, sizeof(value));
+ DCHECK_GE(ret, 0);
+}
+
+void MessagePumpForUI::ScheduleDelayedWork(const TimeTicks& delayed_work_time) {
+ if (ShouldQuit())
+ return;
+
+ if (!delayed_scheduled_time_.is_null() &&
+ delayed_work_time >= delayed_scheduled_time_) {
+ return;
+ }
+
+ DCHECK(!delayed_work_time.is_null());
+ delayed_scheduled_time_ = delayed_work_time;
+ int64_t nanos = delayed_work_time.since_origin().InNanoseconds();
+ struct itimerspec ts;
+ ts.it_interval.tv_sec = 0; // Don't repeat.
+ ts.it_interval.tv_nsec = 0;
+ ts.it_value.tv_sec = nanos / TimeTicks::kNanosecondsPerSecond;
+ ts.it_value.tv_nsec = nanos % TimeTicks::kNanosecondsPerSecond;
+
+ int ret = timerfd_settime(delayed_fd_, TFD_TIMER_ABSTIME, &ts, nullptr);
+ DCHECK_GE(ret, 0);
+}
+
+void MessagePumpForUI::QuitWhenIdle(base::OnceClosure callback) {
+ DCHECK(!on_quit_callback_);
+ DCHECK(run_loop_);
+ on_quit_callback_ = std::move(callback);
+ run_loop_->QuitWhenIdle();
+ // Pump the loop in case we're already idle.
+ ScheduleWork();
+}
+
+bool MessagePumpForUI::IsTestImplementation() const {
+ return false;
+}
+
+} // namespace base
diff --git a/base/message_loop/message_pump_android.h b/base/message_loop/message_pump_android.h
new file mode 100644
index 0000000000..d7e0f50fde
--- /dev/null
+++ b/base/message_loop/message_pump_android.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_ANDROID_H_
+#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_ANDROID_H_
+
+#include <jni.h>
+#include <memory>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/message_loop/message_pump.h"
+#include "base/time/time.h"
+
+struct ALooper;
+
+namespace base {
+
+class RunLoop;
+
+// This class implements a MessagePump needed for TYPE_UI MessageLoops on
+// OS_ANDROID platform.
+class BASE_EXPORT MessagePumpForUI : public MessagePump {
+ public:
+ MessagePumpForUI();
+ ~MessagePumpForUI() override;
+
+ void Run(Delegate* delegate) override;
+ void Quit() override;
+ void ScheduleWork() override;
+ void ScheduleDelayedWork(const TimeTicks& delayed_work_time) override;
+
+ // Attaches |delegate| to this native MessagePump. |delegate| will from then
+ // on be invoked by the native loop to process application tasks.
+ virtual void Attach(Delegate* delegate);
+
+ // We call Abort when there is a pending JNI exception, meaning that the
+ // current thread will crash when we return to Java.
+ // We can't call any JNI-methods before returning to Java as we would then
+ // cause a native crash (instead of the original Java crash).
+ void Abort() { should_abort_ = true; }
+ bool IsAborted() { return should_abort_; }
+ bool ShouldQuit() const { return should_abort_ || quit_; }
+
+ // Tells the RunLoop to quit when idle, calling the callback when it's safe
+ // for the Thread to stop.
+ void QuitWhenIdle(base::OnceClosure callback);
+
+ // These functions are only public so that the looper callbacks can call them,
+ // and should not be called from outside this class.
+ void OnDelayedLooperCallback();
+ void OnNonDelayedLooperCallback();
+
+ protected:
+ void SetDelegate(Delegate* delegate) { delegate_ = delegate; }
+ virtual bool IsTestImplementation() const;
+
+ private:
+ void DoIdleWork();
+
+ // Unlike other platforms, we don't control the message loop as it's
+ // controlled by the Android Looper, so we can't run a RunLoop to keep the
+ // Thread this pump belongs to alive. However, threads are expected to have an
+ // active run loop, so we manage a RunLoop internally here, starting/stopping
+ // it as necessary.
+ std::unique_ptr<RunLoop> run_loop_;
+
+ // See Abort().
+ bool should_abort_ = false;
+
+ // Whether this message pump is quitting, or has quit.
+ bool quit_ = false;
+
+ // The MessageLoop::Delegate for this pump.
+ Delegate* delegate_ = nullptr;
+
+ // The time at which we are currently scheduled to wake up and perform a
+ // delayed task.
+ base::TimeTicks delayed_scheduled_time_;
+
+ // If set, a callback to fire when the message pump is quit.
+ base::OnceClosure on_quit_callback_;
+
+ // The file descriptor used to signal that non-delayed work is available.
+ int non_delayed_fd_;
+
+ // The file descriptor used to signal that delayed work is available.
+ int delayed_fd_;
+
+ // The Android Looper for this thread.
+ ALooper* looper_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePumpForUI);
+};
+
+} // namespace base
+
+#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_ANDROID_H_
diff --git a/base/message_loop/message_pump_for_ui.h b/base/message_loop/message_pump_for_ui.h
index 6614f3dfbf..ff6f788317 100644
--- a/base/message_loop/message_pump_for_ui.h
+++ b/base/message_loop/message_pump_for_ui.h
@@ -12,7 +12,7 @@
#if defined(OS_WIN)
#include "base/message_loop/message_pump_win.h"
-#elif defined(OS_ANDROID)
+#elif defined(OS_ANDROID) && 0
#include "base/message_loop/message_pump_android.h"
#elif defined(OS_MACOSX)
#include "base/message_loop/message_pump.h"
@@ -32,8 +32,10 @@ namespace base {
// Windows defines it as-is.
using MessagePumpForUI = MessagePumpForUI;
#elif defined(OS_ANDROID)
+#if 0 // Drop support for MessagePumpForUI for libchrome on Android targets.
// Android defines it as-is.
using MessagePumpForUI = MessagePumpForUI;
+#endif
#elif defined(OS_MACOSX)
// MessagePumpForUI isn't bound to a specific impl on Mac. While each impl can
// be represented by a plain MessagePump: MessagePumpMac::Create() must be used
diff --git a/base/message_loop/message_pump_libevent_unittest.cc b/base/message_loop/message_pump_libevent_unittest.cc
new file mode 100644
index 0000000000..55eb0b4d72
--- /dev/null
+++ b/base/message_loop/message_pump_libevent_unittest.cc
@@ -0,0 +1,263 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/message_loop/message_pump_libevent.h"
+
+#include <unistd.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_util.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/synchronization/waitable_event_watcher.h"
+#include "base/test/gtest_util.h"
+#include "base/third_party/libevent/event.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+class MessagePumpLibeventTest : public testing::Test {
+ protected:
+ MessagePumpLibeventTest()
+ : ui_loop_(new MessageLoop(MessageLoop::TYPE_UI)),
+ io_thread_("MessagePumpLibeventTestIOThread") {}
+ ~MessagePumpLibeventTest() override = default;
+
+ void SetUp() override {
+ Thread::Options options(MessageLoop::TYPE_IO, 0);
+ ASSERT_TRUE(io_thread_.StartWithOptions(options));
+ ASSERT_EQ(MessageLoop::TYPE_IO, io_thread_.message_loop()->type());
+ int ret = pipe(pipefds_);
+ ASSERT_EQ(0, ret);
+ }
+
+ void TearDown() override {
+ if (IGNORE_EINTR(close(pipefds_[0])) < 0)
+ PLOG(ERROR) << "close";
+ if (IGNORE_EINTR(close(pipefds_[1])) < 0)
+ PLOG(ERROR) << "close";
+ }
+
+ void WaitUntilIoThreadStarted() {
+ ASSERT_TRUE(io_thread_.WaitUntilThreadStarted());
+ }
+
+ scoped_refptr<SingleThreadTaskRunner> io_runner() const {
+ return io_thread_.task_runner();
+ }
+
+ void OnLibeventNotification(
+ MessagePumpLibevent* pump,
+ MessagePumpLibevent::FdWatchController* controller) {
+ pump->OnLibeventNotification(0, EV_WRITE | EV_READ, controller);
+ }
+
+ int pipefds_[2];
+ std::unique_ptr<MessageLoop> ui_loop_;
+
+ private:
+ Thread io_thread_;
+};
+
+namespace {
+
+// Concrete implementation of MessagePumpLibevent::FdWatcher that does
+// nothing useful.
+class StupidWatcher : public MessagePumpLibevent::FdWatcher {
+ public:
+ ~StupidWatcher() override = default;
+
+ // base:MessagePumpLibevent::FdWatcher interface
+ void OnFileCanReadWithoutBlocking(int fd) override {}
+ void OnFileCanWriteWithoutBlocking(int fd) override {}
+};
+
+TEST_F(MessagePumpLibeventTest, QuitOutsideOfRun) {
+ std::unique_ptr<MessagePumpLibevent> pump(new MessagePumpLibevent);
+ ASSERT_DCHECK_DEATH(pump->Quit());
+}
+
+class BaseWatcher : public MessagePumpLibevent::FdWatcher {
+ public:
+ explicit BaseWatcher(MessagePumpLibevent::FdWatchController* controller)
+ : controller_(controller) {
+ DCHECK(controller_);
+ }
+ ~BaseWatcher() override = default;
+
+ // base:MessagePumpLibevent::FdWatcher interface
+ void OnFileCanReadWithoutBlocking(int /* fd */) override { NOTREACHED(); }
+
+ void OnFileCanWriteWithoutBlocking(int /* fd */) override { NOTREACHED(); }
+
+ protected:
+ MessagePumpLibevent::FdWatchController* controller_;
+};
+
+class DeleteWatcher : public BaseWatcher {
+ public:
+ explicit DeleteWatcher(MessagePumpLibevent::FdWatchController* controller)
+ : BaseWatcher(controller) {}
+
+ ~DeleteWatcher() override { DCHECK(!controller_); }
+
+ void OnFileCanWriteWithoutBlocking(int /* fd */) override {
+ DCHECK(controller_);
+ delete controller_;
+ controller_ = nullptr;
+ }
+};
+
+TEST_F(MessagePumpLibeventTest, DeleteWatcher) {
+ std::unique_ptr<MessagePumpLibevent> pump(new MessagePumpLibevent);
+ MessagePumpLibevent::FdWatchController* watcher =
+ new MessagePumpLibevent::FdWatchController(FROM_HERE);
+ DeleteWatcher delegate(watcher);
+ pump->WatchFileDescriptor(pipefds_[1],
+ false, MessagePumpLibevent::WATCH_READ_WRITE, watcher, &delegate);
+
+ // Spoof a libevent notification.
+ OnLibeventNotification(pump.get(), watcher);
+}
+
+class StopWatcher : public BaseWatcher {
+ public:
+ explicit StopWatcher(MessagePumpLibevent::FdWatchController* controller)
+ : BaseWatcher(controller) {}
+
+ ~StopWatcher() override = default;
+
+ void OnFileCanWriteWithoutBlocking(int /* fd */) override {
+ controller_->StopWatchingFileDescriptor();
+ }
+};
+
+TEST_F(MessagePumpLibeventTest, StopWatcher) {
+ std::unique_ptr<MessagePumpLibevent> pump(new MessagePumpLibevent);
+ MessagePumpLibevent::FdWatchController watcher(FROM_HERE);
+ StopWatcher delegate(&watcher);
+ pump->WatchFileDescriptor(pipefds_[1],
+ false, MessagePumpLibevent::WATCH_READ_WRITE, &watcher, &delegate);
+
+ // Spoof a libevent notification.
+ OnLibeventNotification(pump.get(), &watcher);
+}
+
+void QuitMessageLoopAndStart(const Closure& quit_closure) {
+ quit_closure.Run();
+
+ RunLoop runloop(RunLoop::Type::kNestableTasksAllowed);
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, runloop.QuitClosure());
+ runloop.Run();
+}
+
+class NestedPumpWatcher : public MessagePumpLibevent::FdWatcher {
+ public:
+ NestedPumpWatcher() = default;
+ ~NestedPumpWatcher() override = default;
+
+ void OnFileCanReadWithoutBlocking(int /* fd */) override {
+ RunLoop runloop;
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&QuitMessageLoopAndStart, runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ void OnFileCanWriteWithoutBlocking(int /* fd */) override {}
+};
+
+TEST_F(MessagePumpLibeventTest, NestedPumpWatcher) {
+ std::unique_ptr<MessagePumpLibevent> pump(new MessagePumpLibevent);
+ MessagePumpLibevent::FdWatchController watcher(FROM_HERE);
+ NestedPumpWatcher delegate;
+ pump->WatchFileDescriptor(pipefds_[1],
+ false, MessagePumpLibevent::WATCH_READ, &watcher, &delegate);
+
+ // Spoof a libevent notification.
+ OnLibeventNotification(pump.get(), &watcher);
+}
+
+void FatalClosure() {
+ FAIL() << "Reached fatal closure.";
+}
+
+class QuitWatcher : public BaseWatcher {
+ public:
+ QuitWatcher(MessagePumpLibevent::FdWatchController* controller,
+ base::Closure quit_closure)
+ : BaseWatcher(controller), quit_closure_(std::move(quit_closure)) {}
+
+ void OnFileCanReadWithoutBlocking(int /* fd */) override {
+ // Post a fatal closure to the MessageLoop before we quit it.
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, BindOnce(&FatalClosure));
+
+ quit_closure_.Run();
+ }
+
+ private:
+ base::Closure quit_closure_;
+};
+
+void WriteFDWrapper(const int fd,
+ const char* buf,
+ int size,
+ WaitableEvent* event) {
+ ASSERT_TRUE(WriteFileDescriptor(fd, buf, size));
+}
+
+// Tests that MessagePumpLibevent quits immediately when it is quit from
+// libevent's event_base_loop().
+TEST_F(MessagePumpLibeventTest, QuitWatcher) {
+ // Delete the old MessageLoop so that we can manage our own one here.
+ ui_loop_.reset();
+
+ MessagePumpLibevent* pump = new MessagePumpLibevent; // owned by |loop|.
+ MessageLoop loop(WrapUnique(pump));
+ RunLoop run_loop;
+ MessagePumpLibevent::FdWatchController controller(FROM_HERE);
+ QuitWatcher delegate(&controller, run_loop.QuitClosure());
+ WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ std::unique_ptr<WaitableEventWatcher> watcher(new WaitableEventWatcher);
+
+ // Tell the pump to watch the pipe.
+ pump->WatchFileDescriptor(pipefds_[0], false, MessagePumpLibevent::WATCH_READ,
+ &controller, &delegate);
+
+ // Make the IO thread wait for |event| before writing to pipefds[1].
+ const char buf = 0;
+ WaitableEventWatcher::EventCallback write_fd_task =
+ BindOnce(&WriteFDWrapper, pipefds_[1], &buf, 1);
+ io_runner()->PostTask(
+ FROM_HERE, BindOnce(IgnoreResult(&WaitableEventWatcher::StartWatching),
+ Unretained(watcher.get()), &event,
+ std::move(write_fd_task), io_runner()));
+
+ // Queue |event| to signal on |loop|.
+ loop.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&event)));
+
+ // Now run the MessageLoop.
+ run_loop.Run();
+
+ // StartWatching can move |watcher| to IO thread. Release on IO thread.
+ io_runner()->PostTask(FROM_HERE, BindOnce(&WaitableEventWatcher::StopWatching,
+ Owned(watcher.release())));
+}
+
+} // namespace
+
+} // namespace base
diff --git a/base/message_loop/message_pump_perftest.cc b/base/message_loop/message_pump_perftest.cc
new file mode 100644
index 0000000000..71ed4912d8
--- /dev/null
+++ b/base/message_loop/message_pump_perftest.cc
@@ -0,0 +1,243 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/format_macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/java_handler_thread.h"
+#endif
+
+namespace base {
+
+class ScheduleWorkTest : public testing::Test {
+ public:
+ ScheduleWorkTest() : counter_(0) {}
+
+ void SetUp() override {
+ if (base::ThreadTicks::IsSupported())
+ base::ThreadTicks::WaitUntilInitialized();
+ }
+
+ void Increment(uint64_t amount) { counter_ += amount; }
+
+ void Schedule(int index) {
+ base::TimeTicks start = base::TimeTicks::Now();
+ base::ThreadTicks thread_start;
+ if (ThreadTicks::IsSupported())
+ thread_start = base::ThreadTicks::Now();
+ base::TimeDelta minimum = base::TimeDelta::Max();
+ base::TimeDelta maximum = base::TimeDelta();
+ base::TimeTicks now, lastnow = start;
+ uint64_t schedule_calls = 0u;
+ do {
+ for (size_t i = 0; i < kBatchSize; ++i) {
+ target_message_loop()->ScheduleWork();
+ schedule_calls++;
+ }
+ now = base::TimeTicks::Now();
+ base::TimeDelta laptime = now - lastnow;
+ lastnow = now;
+ minimum = std::min(minimum, laptime);
+ maximum = std::max(maximum, laptime);
+ } while (now - start < base::TimeDelta::FromSeconds(kTargetTimeSec));
+
+ scheduling_times_[index] = now - start;
+ if (ThreadTicks::IsSupported())
+ scheduling_thread_times_[index] =
+ base::ThreadTicks::Now() - thread_start;
+ min_batch_times_[index] = minimum;
+ max_batch_times_[index] = maximum;
+ target_message_loop()->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&ScheduleWorkTest::Increment,
+ base::Unretained(this), schedule_calls));
+ }
+
+ void ScheduleWork(MessageLoop::Type target_type, int num_scheduling_threads) {
+#if defined(OS_ANDROID)
+ if (target_type == MessageLoop::TYPE_JAVA) {
+ java_thread_.reset(new android::JavaHandlerThread("target"));
+ java_thread_->Start();
+ } else
+#endif
+ {
+ target_.reset(new Thread("target"));
+ target_->StartWithOptions(Thread::Options(target_type, 0u));
+
+ // Without this, it's possible for the scheduling threads to start and run
+ // before the target thread. In this case, the scheduling threads will
+ // call target_message_loop()->ScheduleWork(), which dereferences the
+ // loop's message pump, which is only created after the target thread has
+ // finished starting.
+ target_->WaitUntilThreadStarted();
+ }
+
+ std::vector<std::unique_ptr<Thread>> scheduling_threads;
+ scheduling_times_.reset(new base::TimeDelta[num_scheduling_threads]);
+ scheduling_thread_times_.reset(new base::TimeDelta[num_scheduling_threads]);
+ min_batch_times_.reset(new base::TimeDelta[num_scheduling_threads]);
+ max_batch_times_.reset(new base::TimeDelta[num_scheduling_threads]);
+
+ for (int i = 0; i < num_scheduling_threads; ++i) {
+ scheduling_threads.push_back(std::make_unique<Thread>("posting thread"));
+ scheduling_threads[i]->Start();
+ }
+
+ for (int i = 0; i < num_scheduling_threads; ++i) {
+ scheduling_threads[i]->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&ScheduleWorkTest::Schedule,
+ base::Unretained(this), i));
+ }
+
+ for (int i = 0; i < num_scheduling_threads; ++i) {
+ scheduling_threads[i]->Stop();
+ }
+#if defined(OS_ANDROID)
+ if (target_type == MessageLoop::TYPE_JAVA) {
+ java_thread_->Stop();
+ java_thread_.reset();
+ } else
+#endif
+ {
+ target_->Stop();
+ target_.reset();
+ }
+ base::TimeDelta total_time;
+ base::TimeDelta total_thread_time;
+ base::TimeDelta min_batch_time = base::TimeDelta::Max();
+ base::TimeDelta max_batch_time = base::TimeDelta();
+ for (int i = 0; i < num_scheduling_threads; ++i) {
+ total_time += scheduling_times_[i];
+ total_thread_time += scheduling_thread_times_[i];
+ min_batch_time = std::min(min_batch_time, min_batch_times_[i]);
+ max_batch_time = std::max(max_batch_time, max_batch_times_[i]);
+ }
+ std::string trace = StringPrintf(
+ "%d_threads_scheduling_to_%s_pump",
+ num_scheduling_threads,
+ target_type == MessageLoop::TYPE_IO
+ ? "io"
+ : (target_type == MessageLoop::TYPE_UI ? "ui" : "default"));
+ perf_test::PrintResult(
+ "task",
+ "",
+ trace,
+ total_time.InMicroseconds() / static_cast<double>(counter_),
+ "us/task",
+ true);
+ perf_test::PrintResult(
+ "task",
+ "_min_batch_time",
+ trace,
+ min_batch_time.InMicroseconds() / static_cast<double>(kBatchSize),
+ "us/task",
+ false);
+ perf_test::PrintResult(
+ "task",
+ "_max_batch_time",
+ trace,
+ max_batch_time.InMicroseconds() / static_cast<double>(kBatchSize),
+ "us/task",
+ false);
+ if (ThreadTicks::IsSupported()) {
+ perf_test::PrintResult(
+ "task",
+ "_thread_time",
+ trace,
+ total_thread_time.InMicroseconds() / static_cast<double>(counter_),
+ "us/task",
+ true);
+ }
+ }
+
+ MessageLoop* target_message_loop() {
+#if defined(OS_ANDROID)
+ if (java_thread_)
+ return java_thread_->message_loop();
+#endif
+ return target_->message_loop();
+ }
+
+ private:
+ std::unique_ptr<Thread> target_;
+#if defined(OS_ANDROID)
+ std::unique_ptr<android::JavaHandlerThread> java_thread_;
+#endif
+ std::unique_ptr<base::TimeDelta[]> scheduling_times_;
+ std::unique_ptr<base::TimeDelta[]> scheduling_thread_times_;
+ std::unique_ptr<base::TimeDelta[]> min_batch_times_;
+ std::unique_ptr<base::TimeDelta[]> max_batch_times_;
+ uint64_t counter_;
+
+ static const size_t kTargetTimeSec = 5;
+ static const size_t kBatchSize = 1000;
+};
+
+TEST_F(ScheduleWorkTest, ThreadTimeToIOFromOneThread) {
+ ScheduleWork(MessageLoop::TYPE_IO, 1);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToIOFromTwoThreads) {
+ ScheduleWork(MessageLoop::TYPE_IO, 2);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToIOFromFourThreads) {
+ ScheduleWork(MessageLoop::TYPE_IO, 4);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToUIFromOneThread) {
+ ScheduleWork(MessageLoop::TYPE_UI, 1);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToUIFromTwoThreads) {
+ ScheduleWork(MessageLoop::TYPE_UI, 2);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToUIFromFourThreads) {
+ ScheduleWork(MessageLoop::TYPE_UI, 4);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToDefaultFromOneThread) {
+ ScheduleWork(MessageLoop::TYPE_DEFAULT, 1);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToDefaultFromTwoThreads) {
+ ScheduleWork(MessageLoop::TYPE_DEFAULT, 2);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToDefaultFromFourThreads) {
+ ScheduleWork(MessageLoop::TYPE_DEFAULT, 4);
+}
+
+#if defined(OS_ANDROID)
+TEST_F(ScheduleWorkTest, ThreadTimeToJavaFromOneThread) {
+ ScheduleWork(MessageLoop::TYPE_JAVA, 1);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToJavaFromTwoThreads) {
+ ScheduleWork(MessageLoop::TYPE_JAVA, 2);
+}
+
+TEST_F(ScheduleWorkTest, ThreadTimeToJavaFromFourThreads) {
+ ScheduleWork(MessageLoop::TYPE_JAVA, 4);
+}
+#endif
+
+} // namespace base
diff --git a/base/metrics/field_trial_params_unittest.cc b/base/metrics/field_trial_params_unittest.cc
new file mode 100644
index 0000000000..d310c0d4f5
--- /dev/null
+++ b/base/metrics/field_trial_params_unittest.cc
@@ -0,0 +1,458 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/metrics/field_trial_params.h"
+
+#include "base/feature_list.h"
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_param_associator.h"
+#include "base/test/scoped_feature_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+// Call FieldTrialList::FactoryGetFieldTrial() with a future expiry date.
+scoped_refptr<FieldTrial> CreateFieldTrial(
+ const std::string& trial_name,
+ int total_probability,
+ const std::string& default_group_name,
+ int* default_group_number) {
+ return FieldTrialList::FactoryGetFieldTrial(
+ trial_name, total_probability, default_group_name,
+ FieldTrialList::kNoExpirationYear, 1, 1, FieldTrial::SESSION_RANDOMIZED,
+ default_group_number);
+}
+
+} // namespace
+
+class FieldTrialParamsTest : public ::testing::Test {
+ public:
+ FieldTrialParamsTest() : field_trial_list_(nullptr) {}
+
+ ~FieldTrialParamsTest() override {
+ // Ensure that the maps are cleared between tests, since they are stored as
+ // process singletons.
+ FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
+ }
+
+ void CreateFeatureWithTrial(const Feature& feature,
+ FeatureList::OverrideState override_state,
+ FieldTrial* trial) {
+ std::unique_ptr<FeatureList> feature_list(new FeatureList);
+ feature_list->RegisterFieldTrialOverride(feature.name, override_state,
+ trial);
+ scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
+ }
+
+ private:
+ FieldTrialList field_trial_list_;
+ test::ScopedFeatureList scoped_feature_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(FieldTrialParamsTest);
+};
+
+TEST_F(FieldTrialParamsTest, AssociateFieldTrialParams) {
+ const std::string kTrialName = "AssociateFieldTrialParams";
+
+ {
+ std::map<std::string, std::string> params;
+ params["a"] = "10";
+ params["b"] = "test";
+ ASSERT_TRUE(AssociateFieldTrialParams(kTrialName, "A", params));
+ }
+ {
+ std::map<std::string, std::string> params;
+ params["a"] = "5";
+ ASSERT_TRUE(AssociateFieldTrialParams(kTrialName, "B", params));
+ }
+
+ FieldTrialList::CreateFieldTrial(kTrialName, "B");
+ EXPECT_EQ("5", GetFieldTrialParamValue(kTrialName, "a"));
+ EXPECT_EQ(std::string(), GetFieldTrialParamValue(kTrialName, "b"));
+ EXPECT_EQ(std::string(), GetFieldTrialParamValue(kTrialName, "x"));
+
+ std::map<std::string, std::string> params;
+ EXPECT_TRUE(GetFieldTrialParams(kTrialName, &params));
+ EXPECT_EQ(1U, params.size());
+ EXPECT_EQ("5", params["a"]);
+}
+
+TEST_F(FieldTrialParamsTest, AssociateFieldTrialParams_Fail) {
+ const std::string kTrialName = "AssociateFieldTrialParams_Fail";
+ const std::string kGroupName = "A";
+
+ std::map<std::string, std::string> params;
+ params["a"] = "10";
+ ASSERT_TRUE(AssociateFieldTrialParams(kTrialName, kGroupName, params));
+ params["a"] = "1";
+ params["b"] = "2";
+ ASSERT_FALSE(AssociateFieldTrialParams(kTrialName, kGroupName, params));
+
+ FieldTrialList::CreateFieldTrial(kTrialName, kGroupName);
+ EXPECT_EQ("10", GetFieldTrialParamValue(kTrialName, "a"));
+ EXPECT_EQ(std::string(), GetFieldTrialParamValue(kTrialName, "b"));
+}
+
+TEST_F(FieldTrialParamsTest, AssociateFieldTrialParams_TrialActiveFail) {
+ const std::string kTrialName = "AssociateFieldTrialParams_TrialActiveFail";
+ FieldTrialList::CreateFieldTrial(kTrialName, "A");
+ ASSERT_EQ("A", FieldTrialList::FindFullName(kTrialName));
+
+ std::map<std::string, std::string> params;
+ params["a"] = "10";
+ EXPECT_FALSE(AssociateFieldTrialParams(kTrialName, "B", params));
+ EXPECT_FALSE(AssociateFieldTrialParams(kTrialName, "A", params));
+}
+
+TEST_F(FieldTrialParamsTest, AssociateFieldTrialParams_DoesntActivateTrial) {
+ const std::string kTrialName =
+ "AssociateFieldTrialParams_DoesntActivateTrial";
+
+ ASSERT_FALSE(FieldTrialList::IsTrialActive(kTrialName));
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+ ASSERT_FALSE(FieldTrialList::IsTrialActive(kTrialName));
+
+ std::map<std::string, std::string> params;
+ params["a"] = "10";
+ EXPECT_TRUE(AssociateFieldTrialParams(kTrialName, "A", params));
+ ASSERT_FALSE(FieldTrialList::IsTrialActive(kTrialName));
+}
+
+TEST_F(FieldTrialParamsTest, GetFieldTrialParams_NoTrial) {
+ const std::string kTrialName = "GetFieldTrialParams_NoParams";
+
+ std::map<std::string, std::string> params;
+ EXPECT_FALSE(GetFieldTrialParams(kTrialName, &params));
+ EXPECT_EQ(std::string(), GetFieldTrialParamValue(kTrialName, "x"));
+ EXPECT_EQ(std::string(), GetFieldTrialParamValue(kTrialName, "y"));
+}
+
+TEST_F(FieldTrialParamsTest, GetFieldTrialParams_NoParams) {
+ const std::string kTrialName = "GetFieldTrialParams_NoParams";
+
+ FieldTrialList::CreateFieldTrial(kTrialName, "A");
+
+ std::map<std::string, std::string> params;
+ EXPECT_FALSE(GetFieldTrialParams(kTrialName, &params));
+ EXPECT_EQ(std::string(), GetFieldTrialParamValue(kTrialName, "x"));
+ EXPECT_EQ(std::string(), GetFieldTrialParamValue(kTrialName, "y"));
+}
+
+TEST_F(FieldTrialParamsTest, GetFieldTrialParams_ActivatesTrial) {
+ const std::string kTrialName = "GetFieldTrialParams_ActivatesTrial";
+
+ ASSERT_FALSE(FieldTrialList::IsTrialActive(kTrialName));
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+ ASSERT_FALSE(FieldTrialList::IsTrialActive(kTrialName));
+
+ std::map<std::string, std::string> params;
+ EXPECT_FALSE(GetFieldTrialParams(kTrialName, &params));
+ ASSERT_TRUE(FieldTrialList::IsTrialActive(kTrialName));
+}
+
+TEST_F(FieldTrialParamsTest, GetFieldTrialParamValue_ActivatesTrial) {
+ const std::string kTrialName = "GetFieldTrialParamValue_ActivatesTrial";
+
+ ASSERT_FALSE(FieldTrialList::IsTrialActive(kTrialName));
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+ ASSERT_FALSE(FieldTrialList::IsTrialActive(kTrialName));
+
+ std::map<std::string, std::string> params;
+ EXPECT_EQ(std::string(), GetFieldTrialParamValue(kTrialName, "x"));
+ ASSERT_TRUE(FieldTrialList::IsTrialActive(kTrialName));
+}
+
+TEST_F(FieldTrialParamsTest, GetFieldTrialParamsByFeature) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+ const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+
+ std::map<std::string, std::string> params;
+ params["x"] = "1";
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_ENABLE_FEATURE,
+ trial.get());
+
+ std::map<std::string, std::string> actualParams;
+ EXPECT_TRUE(GetFieldTrialParamsByFeature(kFeature, &actualParams));
+ EXPECT_EQ(params, actualParams);
+}
+
+TEST_F(FieldTrialParamsTest, GetFieldTrialParamValueByFeature) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+ const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+
+ std::map<std::string, std::string> params;
+ params["x"] = "1";
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_ENABLE_FEATURE,
+ trial.get());
+
+ std::map<std::string, std::string> actualParams;
+ EXPECT_EQ(params["x"], GetFieldTrialParamValueByFeature(kFeature, "x"));
+}
+
+TEST_F(FieldTrialParamsTest, GetFieldTrialParamsByFeature_Disable) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+ const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+
+ std::map<std::string, std::string> params;
+ params["x"] = "1";
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_DISABLE_FEATURE,
+ trial.get());
+
+ std::map<std::string, std::string> actualParams;
+ EXPECT_FALSE(GetFieldTrialParamsByFeature(kFeature, &actualParams));
+}
+
+TEST_F(FieldTrialParamsTest, GetFieldTrialParamValueByFeature_Disable) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+ const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+
+ std::map<std::string, std::string> params;
+ params["x"] = "1";
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_DISABLE_FEATURE,
+ trial.get());
+
+ std::map<std::string, std::string> actualParams;
+ EXPECT_EQ(std::string(), GetFieldTrialParamValueByFeature(kFeature, "x"));
+}
+
+TEST_F(FieldTrialParamsTest, FeatureParamString) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+
+ static const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+ static const FeatureParam<std::string> a{&kFeature, "a", "default"};
+ static const FeatureParam<std::string> b{&kFeature, "b", ""};
+ static const FeatureParam<std::string> c{&kFeature, "c", "default"};
+ static const FeatureParam<std::string> d{&kFeature, "d", ""};
+ static const FeatureParam<std::string> e{&kFeature, "e", "default"};
+ static const FeatureParam<std::string> f{&kFeature, "f", ""};
+
+ std::map<std::string, std::string> params;
+ params["a"] = "";
+ params["b"] = "non-default";
+ params["c"] = "non-default";
+ params["d"] = "";
+ // "e" is not registered
+ // "f" is not registered
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_ENABLE_FEATURE,
+ trial.get());
+
+ EXPECT_EQ("default", a.Get()); // empty
+ EXPECT_EQ("non-default", b.Get());
+ EXPECT_EQ("non-default", c.Get());
+ EXPECT_EQ("", d.Get()); // empty
+ EXPECT_EQ("default", e.Get()); // not registered
+ EXPECT_EQ("", f.Get()); // not registered
+}
+
+TEST_F(FieldTrialParamsTest, FeatureParamInt) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+
+ static const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+ static const FeatureParam<int> a{&kFeature, "a", 0};
+ static const FeatureParam<int> b{&kFeature, "b", 0};
+ static const FeatureParam<int> c{&kFeature, "c", 0};
+ static const FeatureParam<int> d{&kFeature, "d", 0};
+ static const FeatureParam<int> e{&kFeature, "e", 0};
+
+ std::map<std::string, std::string> params;
+ params["a"] = "1";
+ params["b"] = "1.5";
+ params["c"] = "foo";
+ params["d"] = "";
+ // "e" is not registered
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_ENABLE_FEATURE,
+ trial.get());
+
+ EXPECT_EQ(1, GetFieldTrialParamByFeatureAsInt(kFeature, "a", 0));
+ EXPECT_EQ(0, GetFieldTrialParamByFeatureAsInt(kFeature, "b", 0)); // invalid
+ EXPECT_EQ(0, GetFieldTrialParamByFeatureAsInt(kFeature, "c", 0)); // invalid
+ EXPECT_EQ(0, GetFieldTrialParamByFeatureAsInt(kFeature, "d", 0)); // empty
+ EXPECT_EQ(0, GetFieldTrialParamByFeatureAsInt(kFeature, "e", 0)); // empty
+
+ EXPECT_EQ(1, a.Get());
+ EXPECT_EQ(0, b.Get()); // invalid
+ EXPECT_EQ(0, c.Get()); // invalid
+ EXPECT_EQ(0, d.Get()); // empty
+ EXPECT_EQ(0, e.Get()); // empty
+}
+
+TEST_F(FieldTrialParamsTest, FeatureParamDouble) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+
+ static const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+ static const FeatureParam<double> a{&kFeature, "a", 0.0};
+ static const FeatureParam<double> b{&kFeature, "b", 0.0};
+ static const FeatureParam<double> c{&kFeature, "c", 0.0};
+ static const FeatureParam<double> d{&kFeature, "d", 0.0};
+ static const FeatureParam<double> e{&kFeature, "e", 0.0};
+ static const FeatureParam<double> f{&kFeature, "f", 0.0};
+
+ std::map<std::string, std::string> params;
+ params["a"] = "1";
+ params["b"] = "1.5";
+ params["c"] = "1.0e-10";
+ params["d"] = "foo";
+ params["e"] = "";
+ // "f" is not registered
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_ENABLE_FEATURE,
+ trial.get());
+
+ EXPECT_EQ(1, GetFieldTrialParamByFeatureAsDouble(kFeature, "a", 0));
+ EXPECT_EQ(1.5, GetFieldTrialParamByFeatureAsDouble(kFeature, "b", 0));
+ EXPECT_EQ(1.0e-10, GetFieldTrialParamByFeatureAsDouble(kFeature, "c", 0));
+ EXPECT_EQ(0,
+ GetFieldTrialParamByFeatureAsDouble(kFeature, "d", 0)); // invalid
+ EXPECT_EQ(0, GetFieldTrialParamByFeatureAsDouble(kFeature, "e", 0)); // empty
+ EXPECT_EQ(0, GetFieldTrialParamByFeatureAsDouble(kFeature, "f", 0)); // empty
+
+ EXPECT_EQ(1, a.Get());
+ EXPECT_EQ(1.5, b.Get());
+ EXPECT_EQ(1.0e-10, c.Get());
+ EXPECT_EQ(0, d.Get()); // invalid
+ EXPECT_EQ(0, e.Get()); // empty
+ EXPECT_EQ(0, f.Get()); // empty
+}
+
+TEST_F(FieldTrialParamsTest, FeatureParamBool) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+
+ static const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+ static const FeatureParam<bool> a{&kFeature, "a", false};
+ static const FeatureParam<bool> b{&kFeature, "b", true};
+ static const FeatureParam<bool> c{&kFeature, "c", false};
+ static const FeatureParam<bool> d{&kFeature, "d", true};
+ static const FeatureParam<bool> e{&kFeature, "e", true};
+ static const FeatureParam<bool> f{&kFeature, "f", true};
+
+ std::map<std::string, std::string> params;
+ params["a"] = "true";
+ params["b"] = "false";
+ params["c"] = "1";
+ params["d"] = "False";
+ params["e"] = "";
+ // "f" is not registered
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_ENABLE_FEATURE,
+ trial.get());
+
+ EXPECT_TRUE(a.Get());
+ EXPECT_FALSE(b.Get());
+ EXPECT_FALSE(c.Get()); // invalid
+ EXPECT_TRUE(d.Get()); // invalid
+ EXPECT_TRUE(e.Get()); // empty
+ EXPECT_TRUE(f.Get()); // empty
+}
+
+enum Hand { ROCK, PAPER, SCISSORS };
+
+TEST_F(FieldTrialParamsTest, FeatureParamEnum) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+
+ static const FeatureParam<Hand>::Option hands[] = {
+ {ROCK, "rock"}, {PAPER, "paper"}, {SCISSORS, "scissors"}};
+ static const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+ static const FeatureParam<Hand> a{&kFeature, "a", ROCK, &hands};
+ static const FeatureParam<Hand> b{&kFeature, "b", ROCK, &hands};
+ static const FeatureParam<Hand> c{&kFeature, "c", ROCK, &hands};
+ static const FeatureParam<Hand> d{&kFeature, "d", ROCK, &hands};
+ static const FeatureParam<Hand> e{&kFeature, "e", PAPER, &hands};
+ static const FeatureParam<Hand> f{&kFeature, "f", SCISSORS, &hands};
+
+ std::map<std::string, std::string> params;
+ params["a"] = "rock";
+ params["b"] = "paper";
+ params["c"] = "scissors";
+ params["d"] = "lizard";
+ params["e"] = "";
+ // "f" is not registered
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_ENABLE_FEATURE,
+ trial.get());
+
+ EXPECT_EQ(ROCK, a.Get());
+ EXPECT_EQ(PAPER, b.Get());
+ EXPECT_EQ(SCISSORS, c.Get());
+ EXPECT_EQ(ROCK, d.Get()); // invalid
+ EXPECT_EQ(PAPER, e.Get()); // invalid/empty
+ EXPECT_EQ(SCISSORS, f.Get()); // not registered
+}
+
+enum class UI { ONE_D, TWO_D, THREE_D };
+
+TEST_F(FieldTrialParamsTest, FeatureParamEnumClass) {
+ const std::string kTrialName = "GetFieldTrialParamsByFeature";
+
+ static const FeatureParam<UI>::Option uis[] = {
+ {UI::ONE_D, "1d"}, {UI::TWO_D, "2d"}, {UI::THREE_D, "3d"}};
+ static const Feature kFeature{"TestFeature", FEATURE_DISABLED_BY_DEFAULT};
+ static const FeatureParam<UI> a{&kFeature, "a", UI::ONE_D, &uis};
+ static const FeatureParam<UI> b{&kFeature, "b", UI::ONE_D, &uis};
+ static const FeatureParam<UI> c{&kFeature, "c", UI::ONE_D, &uis};
+ static const FeatureParam<UI> d{&kFeature, "d", UI::ONE_D, &uis};
+ static const FeatureParam<UI> e{&kFeature, "e", UI::TWO_D, &uis};
+ static const FeatureParam<UI> f{&kFeature, "f", UI::THREE_D, &uis};
+
+ std::map<std::string, std::string> params;
+ params["a"] = "1d";
+ params["b"] = "2d";
+ params["c"] = "3d";
+ params["d"] = "4d";
+ params["e"] = "";
+ // "f" is not registered
+ AssociateFieldTrialParams(kTrialName, "A", params);
+ scoped_refptr<FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", nullptr));
+
+ CreateFeatureWithTrial(kFeature, FeatureList::OVERRIDE_ENABLE_FEATURE,
+ trial.get());
+
+ EXPECT_EQ(UI::ONE_D, a.Get());
+ EXPECT_EQ(UI::TWO_D, b.Get());
+ EXPECT_EQ(UI::THREE_D, c.Get());
+ EXPECT_EQ(UI::ONE_D, d.Get()); // invalid
+ EXPECT_EQ(UI::TWO_D, e.Get()); // invalid/empty
+ EXPECT_EQ(UI::THREE_D, f.Get()); // not registered
+}
+
+} // namespace base
diff --git a/base/metrics/histogram_functions_unittest.cc b/base/metrics/histogram_functions_unittest.cc
new file mode 100644
index 0000000000..32f439469b
--- /dev/null
+++ b/base/metrics/histogram_functions_unittest.cc
@@ -0,0 +1,127 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/metrics/histogram_functions.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+enum UmaHistogramTestingEnum {
+ UMA_HISTOGRAM_TESTING_ENUM_FIRST,
+ UMA_HISTOGRAM_TESTING_ENUM_SECOND,
+ UMA_HISTOGRAM_TESTING_ENUM_THIRD
+};
+
+TEST(HistogramFunctionsTest, ExactLinear) {
+ std::string histogram("Testing.UMA.HistogramExactLinear");
+ HistogramTester tester;
+ UmaHistogramExactLinear(histogram, 10, 100);
+ tester.ExpectUniqueSample(histogram, 10, 1);
+ UmaHistogramExactLinear(histogram, 20, 100);
+ UmaHistogramExactLinear(histogram, 10, 100);
+ tester.ExpectBucketCount(histogram, 10, 2);
+ tester.ExpectBucketCount(histogram, 20, 1);
+ tester.ExpectTotalCount(histogram, 3);
+ // Test linear buckets overflow.
+ UmaHistogramExactLinear(histogram, 200, 100);
+ tester.ExpectBucketCount(histogram, 101, 1);
+ tester.ExpectTotalCount(histogram, 4);
+ // Test linear buckets underflow.
+ UmaHistogramExactLinear(histogram, 0, 100);
+ tester.ExpectBucketCount(histogram, 0, 1);
+ tester.ExpectTotalCount(histogram, 5);
+}
+
+TEST(HistogramFunctionsTest, Enumeration) {
+ std::string histogram("Testing.UMA.HistogramEnumeration");
+ HistogramTester tester;
+ UmaHistogramEnumeration(histogram, UMA_HISTOGRAM_TESTING_ENUM_FIRST,
+ UMA_HISTOGRAM_TESTING_ENUM_THIRD);
+ tester.ExpectUniqueSample(histogram, UMA_HISTOGRAM_TESTING_ENUM_FIRST, 1);
+
+ // Verify the overflow & underflow bucket exists.
+ UMA_HISTOGRAM_ENUMERATION(
+ histogram, static_cast<int>(UMA_HISTOGRAM_TESTING_ENUM_THIRD) + 10,
+ static_cast<int>(UMA_HISTOGRAM_TESTING_ENUM_THIRD));
+ tester.ExpectBucketCount(
+ histogram, static_cast<int>(UMA_HISTOGRAM_TESTING_ENUM_THIRD) + 1, 1);
+ tester.ExpectTotalCount(histogram, 2);
+}
+
+TEST(HistogramFunctionsTest, Boolean) {
+ std::string histogram("Testing.UMA.HistogramBoolean");
+ HistogramTester tester;
+ UmaHistogramBoolean(histogram, true);
+ tester.ExpectUniqueSample(histogram, 1, 1);
+ UmaHistogramBoolean(histogram, false);
+ tester.ExpectBucketCount(histogram, 0, 1);
+ tester.ExpectTotalCount(histogram, 2);
+}
+
+TEST(HistogramFunctionsTest, Percentage) {
+ std::string histogram("Testing.UMA.HistogramPercentage");
+ HistogramTester tester;
+ UmaHistogramPercentage(histogram, 50);
+ tester.ExpectUniqueSample(histogram, 50, 1);
+ // Test overflows.
+ UmaHistogramPercentage(histogram, 110);
+ tester.ExpectBucketCount(histogram, 101, 1);
+ tester.ExpectTotalCount(histogram, 2);
+}
+
+TEST(HistogramFunctionsTest, Counts) {
+ std::string histogram("Testing.UMA.HistogramCount.Custom");
+ HistogramTester tester;
+ UmaHistogramCustomCounts(histogram, 10, 1, 100, 10);
+ tester.ExpectUniqueSample(histogram, 10, 1);
+ UmaHistogramCustomCounts(histogram, 20, 1, 100, 10);
+ UmaHistogramCustomCounts(histogram, 20, 1, 100, 10);
+ UmaHistogramCustomCounts(histogram, 20, 1, 100, 10);
+ tester.ExpectBucketCount(histogram, 20, 3);
+ tester.ExpectTotalCount(histogram, 4);
+ UmaHistogramCustomCounts(histogram, 110, 1, 100, 10);
+ tester.ExpectBucketCount(histogram, 101, 1);
+ tester.ExpectTotalCount(histogram, 5);
+}
+
+TEST(HistogramFunctionsTest, Times) {
+ std::string histogram("Testing.UMA.HistogramTimes");
+ HistogramTester tester;
+ UmaHistogramTimes(histogram, TimeDelta::FromSeconds(1));
+ tester.ExpectTimeBucketCount(histogram, TimeDelta::FromSeconds(1), 1);
+ tester.ExpectTotalCount(histogram, 1);
+ UmaHistogramTimes(histogram, TimeDelta::FromSeconds(9));
+ tester.ExpectTimeBucketCount(histogram, TimeDelta::FromSeconds(9), 1);
+ tester.ExpectTotalCount(histogram, 2);
+ UmaHistogramTimes(histogram, TimeDelta::FromSeconds(10)); // Overflows
+ tester.ExpectTimeBucketCount(histogram, TimeDelta::FromSeconds(10), 1);
+ UmaHistogramTimes(histogram, TimeDelta::FromSeconds(20)); // Overflows.
+ // Check the value by picking any overflow time.
+ tester.ExpectTimeBucketCount(histogram, TimeDelta::FromSeconds(11), 2);
+ tester.ExpectTotalCount(histogram, 4);
+}
+
+TEST(HistogramFunctionsTest, Sparse_SupportsLargeRange) {
+ std::string histogram("Testing.UMA.HistogramSparse");
+ HistogramTester tester;
+ UmaHistogramSparse(histogram, 0);
+ UmaHistogramSparse(histogram, 123456789);
+ UmaHistogramSparse(histogram, 123456789);
+ EXPECT_THAT(tester.GetAllSamples(histogram),
+ testing::ElementsAre(Bucket(0, 1), Bucket(123456789, 2)));
+}
+
+TEST(HistogramFunctionsTest, Sparse_SupportsNegativeValues) {
+ std::string histogram("Testing.UMA.HistogramSparse");
+ HistogramTester tester;
+ UmaHistogramSparse(histogram, -1);
+ tester.ExpectUniqueSample(histogram, -1, 1);
+}
+
+} // namespace base.
diff --git a/base/metrics/histogram_unittest.nc b/base/metrics/histogram_unittest.nc
new file mode 100644
index 0000000000..c9c2657997
--- /dev/null
+++ b/base/metrics/histogram_unittest.nc
@@ -0,0 +1,90 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+
+namespace base {
+
+#if defined(NCTEST_DIFFERENT_ENUM) // [r"\|sample\| and \|boundary\| shouldn't be of different enums"]
+
+void WontCompile() {
+ enum TypeA { A };
+ enum TypeB { B };
+ UMA_HISTOGRAM_ENUMERATION("", A, B);
+}
+
+#elif defined(NCTEST_DIFFERENT_ENUM_CLASS) // [r"\|sample\| and \|boundary\| shouldn't be of different enums"]
+
+void WontCompile() {
+ enum class TypeA { A };
+ enum class TypeB { B };
+ UMA_HISTOGRAM_ENUMERATION("", TypeA::A, TypeB::B);
+}
+
+#elif defined(NCTEST_DIFFERENT_ENUM_MIXED) // [r"\|sample\| and \|boundary\| shouldn't be of different enums"]
+
+void WontCompile() {
+ enum class TypeA { A };
+ enum TypeB { B };
+ UMA_HISTOGRAM_ENUMERATION("", TypeA::A, B);
+}
+
+#elif defined(NCTEST_NEGATIVE_ENUM_MAX) // [r'static_assert failed "\|boundary\| is out of range of HistogramBase::Sample"']
+
+void WontCompile() {
+ // Buckets for enumeration start from 0, so a boundary < 0 is illegal.
+ enum class TypeA { A = -1 };
+ UMA_HISTOGRAM_ENUMERATION("", TypeA::A, TypeA::A);
+}
+
+#elif defined(NCTEST_ENUM_MAX_OUT_OF_RANGE) // [r'static_assert failed "\|boundary\| is out of range of HistogramBase::Sample"']
+
+void WontCompile() {
+ // HistogramBase::Sample is an int and can't hold larger values.
+ enum class TypeA : uint32_t { A = 0xffffffff };
+ UMA_HISTOGRAM_ENUMERATION("", TypeA::A, TypeA::A);
+}
+
+#elif defined(NCTEST_SAMPLE_NOT_ENUM) // [r'static_assert failed "Unexpected: \|boundary\| is enum, but \|sample\| is not."']
+
+void WontCompile() {
+ enum TypeA { A };
+ UMA_HISTOGRAM_ENUMERATION("", 0, TypeA::A);
+}
+
+#elif defined(NCTEST_FUNCTION_INT) // [r"Non enum passed to UmaHistogramEnumeration"]
+
+void WontCompile() {
+ UmaHistogramEnumeration("", 1, 2);
+}
+
+#elif defined(NCTEST_FUNCTION_DIFFERENT_ENUM) // [r"no matching function for call to 'UmaHistogramEnumeration'"]
+
+void WontCompile() {
+ enum TypeA { A };
+ enum TypeB { B };
+ UmaHistogramEnumeration("", A, B);
+}
+
+#elif defined(NCTEST_FUNCTION_FIRST_NOT_ENUM) // [r"no matching function for call to 'UmaHistogramEnumeration'"]
+
+void WontCompile() {
+ enum TypeB { B };
+ UmaHistogramEnumeration("", 1, B);
+}
+
+#elif defined(NCTEST_FUNCTION_SECOND_NOT_ENUM) // [r"no matching function for call to 'UmaHistogramEnumeration'"]
+
+void WontCompile() {
+ enum TypeA { A };
+ UmaHistogramEnumeration("", A, 2);
+}
+
+#endif
+
+} // namespace base
diff --git a/base/native_library_unittest.cc b/base/native_library_unittest.cc
new file mode 100644
index 0000000000..2bfb9ec946
--- /dev/null
+++ b/base/native_library_unittest.cc
@@ -0,0 +1,166 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/native_library.h"
+#include "base/path_service.h"
+#include "base/test/native_library_test_utils.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+const FilePath::CharType kDummyLibraryPath[] =
+ FILE_PATH_LITERAL("dummy_library");
+
+TEST(NativeLibraryTest, LoadFailure) {
+ NativeLibraryLoadError error;
+ EXPECT_FALSE(LoadNativeLibrary(FilePath(kDummyLibraryPath), &error));
+ EXPECT_FALSE(error.ToString().empty());
+}
+
+// |error| is optional and can be null.
+TEST(NativeLibraryTest, LoadFailureWithNullError) {
+ EXPECT_FALSE(LoadNativeLibrary(FilePath(kDummyLibraryPath), nullptr));
+}
+
+TEST(NativeLibraryTest, GetNativeLibraryName) {
+ const char kExpectedName[] =
+#if defined(OS_WIN)
+ "mylib.dll";
+#elif defined(OS_IOS)
+ "mylib";
+#elif defined(OS_MACOSX)
+ "libmylib.dylib";
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ "libmylib.so";
+#endif
+ EXPECT_EQ(kExpectedName, GetNativeLibraryName("mylib"));
+}
+
+TEST(NativeLibraryTest, GetLoadableModuleName) {
+ const char kExpectedName[] =
+#if defined(OS_WIN)
+ "mylib.dll";
+#elif defined(OS_IOS)
+ "mylib";
+#elif defined(OS_MACOSX)
+ "mylib.so";
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ "libmylib.so";
+#endif
+ EXPECT_EQ(kExpectedName, GetLoadableModuleName("mylib"));
+}
+
+// We don't support dynamic loading on iOS, and ASAN will complain about our
+// intentional ODR violation because of |g_native_library_exported_value| being
+// defined globally both here and in the shared library.
+#if !defined(OS_IOS) && !defined(ADDRESS_SANITIZER)
+
+const char kTestLibraryName[] =
+#if defined(OS_WIN)
+ "test_shared_library.dll";
+#elif defined(OS_MACOSX)
+ "libtest_shared_library.dylib";
+#elif defined(OS_ANDROID) && defined(COMPONENT_BUILD)
+ "libtest_shared_library.cr.so";
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ "libtest_shared_library.so";
+#endif
+
+class TestLibrary {
+ public:
+ TestLibrary() : TestLibrary(NativeLibraryOptions()) {}
+
+ explicit TestLibrary(const NativeLibraryOptions& options)
+ : library_(nullptr) {
+ base::FilePath exe_path;
+
+#if !defined(OS_FUCHSIA)
+ // Libraries do not sit alongside the executable in Fuchsia. NativeLibrary
+ // is aware of this and is able to resolve library paths correctly.
+ CHECK(base::PathService::Get(base::DIR_EXE, &exe_path));
+#endif
+
+ library_ = LoadNativeLibraryWithOptions(
+ exe_path.AppendASCII(kTestLibraryName), options, nullptr);
+ CHECK(library_);
+ }
+
+ ~TestLibrary() {
+ UnloadNativeLibrary(library_);
+ }
+
+ template <typename ReturnType, typename... Args>
+ ReturnType Call(const char* function_name, Args... args) {
+ return reinterpret_cast<ReturnType(*)(Args...)>(
+ GetFunctionPointerFromNativeLibrary(library_, function_name))(args...);
+ }
+
+ private:
+ NativeLibrary library_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLibrary);
+};
+
+// NativeLibraaryTest.LoadLibrary is failing on M tablets only.
+// crbug/641309
+#if !defined(OS_ANDROID)
+
+// Verifies that we can load a native library and resolve its exported symbols.
+TEST(NativeLibraryTest, LoadLibrary) {
+ TestLibrary library;
+ EXPECT_EQ(5, library.Call<int>("GetSimpleTestValue"));
+}
+
+#endif // !defined(OS_ANDROID)
+
+// Android dlopen() requires further investigation, as it might vary across
+// versions with respect to symbol resolution scope.
+// TSan and MSan error out on RTLD_DEEPBIND, https://crbug.com/705255
+#if !defined(OS_ANDROID) && !defined(THREAD_SANITIZER) && \
+ !defined(MEMORY_SANITIZER)
+
+// Verifies that the |prefer_own_symbols| option satisfies its guarantee that
+// a loaded library will always prefer local symbol resolution before
+// considering global symbols.
+TEST(NativeLibraryTest, LoadLibraryPreferOwnSymbols) {
+ NativeLibraryOptions options;
+ options.prefer_own_symbols = true;
+ TestLibrary library(options);
+
+ // Verify that this binary and the DSO use different storage for
+ // |g_native_library_exported_value|.
+ g_native_library_exported_value = 1;
+ library.Call<void>("SetExportedValue", 2);
+ EXPECT_EQ(1, g_native_library_exported_value);
+ g_native_library_exported_value = 3;
+ EXPECT_EQ(2, library.Call<int>("GetExportedValue"));
+
+ // Both this binary and the library link against the
+ // native_library_test_utils source library, which in turn exports the
+ // NativeLibraryTestIncrement() function whose return value depends on some
+ // static internal state.
+ //
+ // The DSO's GetIncrementValue() forwards to that function inside the DSO.
+ //
+ // Here we verify that direct calls to NativeLibraryTestIncrement() in this
+ // binary return a sequence of values independent from the sequence returned
+ // by GetIncrementValue(), ensuring that the DSO is calling its own local
+ // definition of NativeLibraryTestIncrement().
+ EXPECT_EQ(1, library.Call<int>("GetIncrementValue"));
+ EXPECT_EQ(1, NativeLibraryTestIncrement());
+ EXPECT_EQ(2, library.Call<int>("GetIncrementValue"));
+ EXPECT_EQ(3, library.Call<int>("GetIncrementValue"));
+ EXPECT_EQ(4, library.Call<int>("NativeLibraryTestIncrement"));
+ EXPECT_EQ(2, NativeLibraryTestIncrement());
+ EXPECT_EQ(3, NativeLibraryTestIncrement());
+}
+
+#endif // !defined(OS_ANDROID)
+
+#endif // !defined(OS_IOS) && !defined(ADDRESS_SANITIZER)
+
+} // namespace base
diff --git a/base/nix/mime_util_xdg.cc b/base/nix/mime_util_xdg.cc
new file mode 100644
index 0000000000..6b5b11d639
--- /dev/null
+++ b/base/nix/mime_util_xdg.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/nix/mime_util_xdg.h"
+
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/synchronization/lock.h"
+#include "base/third_party/xdg_mime/xdgmime.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace base {
+namespace nix {
+
+namespace {
+
+// None of the XDG stuff is thread-safe, so serialize all access under
+// this lock.
+LazyInstance<Lock>::Leaky g_mime_util_xdg_lock = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+std::string GetFileMimeType(const FilePath& filepath) {
+ if (filepath.empty())
+ return std::string();
+ AssertBlockingAllowed();
+ AutoLock scoped_lock(g_mime_util_xdg_lock.Get());
+ return xdg_mime_get_mime_type_from_file_name(filepath.value().c_str());
+}
+
+} // namespace nix
+} // namespace base
diff --git a/base/nix/mime_util_xdg.h b/base/nix/mime_util_xdg.h
new file mode 100644
index 0000000000..e0f264a565
--- /dev/null
+++ b/base/nix/mime_util_xdg.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NIX_MIME_UTIL_XDG_H_
+#define BASE_NIX_MIME_UTIL_XDG_H_
+
+#include <string>
+
+#include "base/base_export.h"
+#include "build/build_config.h"
+
+namespace base {
+
+class FilePath;
+
+namespace nix {
+
+// Gets the mime type for a file at |filepath|.
+//
+// The mime type is calculated based only on the file name of |filepath|. In
+// particular |filepath| will not be touched on disk and |filepath| doesn't even
+// have to exist. This means that the function does not work for directories
+// (i.e. |filepath| is assumed to be a path to a file).
+//
+// Note that this function might need to read from disk the mime-types data
+// provided by the OS. Therefore this function should not be called from
+// threads that disallow IO via base::ThreadRestrictions::SetIOAllowed(false).
+//
+// If the mime type is unknown, this will return application/octet-stream.
+BASE_EXPORT std::string GetFileMimeType(const FilePath& filepath);
+
+} // namespace nix
+} // namespace base
+
+#endif // BASE_NIX_MIME_UTIL_XDG_H_
diff --git a/base/nix/xdg_util.cc b/base/nix/xdg_util.cc
new file mode 100644
index 0000000000..9ff4d88e83
--- /dev/null
+++ b/base/nix/xdg_util.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/nix/xdg_util.h"
+
+#include <string>
+
+#include "base/base_paths.h"
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/third_party/xdg_user_dirs/xdg_user_dir_lookup.h"
+
+namespace {
+
+// The KDE session version environment variable introduced in KDE 4.
+const char kKDESessionEnvVar[] = "KDE_SESSION_VERSION";
+
+} // namespace
+
+namespace base {
+namespace nix {
+
+const char kDotConfigDir[] = ".config";
+const char kXdgConfigHomeEnvVar[] = "XDG_CONFIG_HOME";
+
+FilePath GetXDGDirectory(Environment* env, const char* env_name,
+ const char* fallback_dir) {
+ FilePath path;
+ std::string env_value;
+ if (env->GetVar(env_name, &env_value) && !env_value.empty()) {
+ path = FilePath(env_value);
+ } else {
+ PathService::Get(DIR_HOME, &path);
+ path = path.Append(fallback_dir);
+ }
+ return path.StripTrailingSeparators();
+}
+
+FilePath GetXDGUserDirectory(const char* dir_name, const char* fallback_dir) {
+ FilePath path;
+ char* xdg_dir = xdg_user_dir_lookup(dir_name);
+ if (xdg_dir) {
+ path = FilePath(xdg_dir);
+ free(xdg_dir);
+ } else {
+ PathService::Get(DIR_HOME, &path);
+ path = path.Append(fallback_dir);
+ }
+ return path.StripTrailingSeparators();
+}
+
+DesktopEnvironment GetDesktopEnvironment(Environment* env) {
+ // XDG_CURRENT_DESKTOP is the newest standard circa 2012.
+ std::string xdg_current_desktop;
+ if (env->GetVar("XDG_CURRENT_DESKTOP", &xdg_current_desktop)) {
+ // Not all desktop environments set this env var as of this writing.
+ if (base::StartsWith(xdg_current_desktop, "Unity",
+ base::CompareCase::SENSITIVE)) {
+ // gnome-fallback sessions set XDG_CURRENT_DESKTOP to Unity
+ // DESKTOP_SESSION can be gnome-fallback or gnome-fallback-compiz
+ std::string desktop_session;
+ if (env->GetVar("DESKTOP_SESSION", &desktop_session) &&
+ desktop_session.find("gnome-fallback") != std::string::npos) {
+ return DESKTOP_ENVIRONMENT_GNOME;
+ }
+ return DESKTOP_ENVIRONMENT_UNITY;
+ }
+ if (xdg_current_desktop == "GNOME")
+ return DESKTOP_ENVIRONMENT_GNOME;
+ if (xdg_current_desktop == "X-Cinnamon")
+ return DESKTOP_ENVIRONMENT_CINNAMON;
+ if (xdg_current_desktop == "KDE") {
+ std::string kde_session;
+ if (env->GetVar(kKDESessionEnvVar, &kde_session)) {
+ if (kde_session == "5") {
+ return DESKTOP_ENVIRONMENT_KDE5;
+ }
+ }
+ return DESKTOP_ENVIRONMENT_KDE4;
+ }
+ if (xdg_current_desktop == "Pantheon")
+ return DESKTOP_ENVIRONMENT_PANTHEON;
+ if (xdg_current_desktop == "XFCE")
+ return DESKTOP_ENVIRONMENT_XFCE;
+ }
+
+ // DESKTOP_SESSION was what everyone used in 2010.
+ std::string desktop_session;
+ if (env->GetVar("DESKTOP_SESSION", &desktop_session)) {
+ if (desktop_session == "gnome" || desktop_session == "mate")
+ return DESKTOP_ENVIRONMENT_GNOME;
+ if (desktop_session == "kde4" || desktop_session == "kde-plasma")
+ return DESKTOP_ENVIRONMENT_KDE4;
+ if (desktop_session == "kde") {
+ // This may mean KDE4 on newer systems, so we have to check.
+ if (env->HasVar(kKDESessionEnvVar))
+ return DESKTOP_ENVIRONMENT_KDE4;
+ return DESKTOP_ENVIRONMENT_KDE3;
+ }
+ if (desktop_session.find("xfce") != std::string::npos ||
+ desktop_session == "xubuntu") {
+ return DESKTOP_ENVIRONMENT_XFCE;
+ }
+ }
+
+ // Fall back on some older environment variables.
+ // Useful particularly in the DESKTOP_SESSION=default case.
+ if (env->HasVar("GNOME_DESKTOP_SESSION_ID"))
+ return DESKTOP_ENVIRONMENT_GNOME;
+ if (env->HasVar("KDE_FULL_SESSION")) {
+ if (env->HasVar(kKDESessionEnvVar))
+ return DESKTOP_ENVIRONMENT_KDE4;
+ return DESKTOP_ENVIRONMENT_KDE3;
+ }
+
+ return DESKTOP_ENVIRONMENT_OTHER;
+}
+
+const char* GetDesktopEnvironmentName(DesktopEnvironment env) {
+ switch (env) {
+ case DESKTOP_ENVIRONMENT_OTHER:
+ return nullptr;
+ case DESKTOP_ENVIRONMENT_CINNAMON:
+ return "CINNAMON";
+ case DESKTOP_ENVIRONMENT_GNOME:
+ return "GNOME";
+ case DESKTOP_ENVIRONMENT_KDE3:
+ return "KDE3";
+ case DESKTOP_ENVIRONMENT_KDE4:
+ return "KDE4";
+ case DESKTOP_ENVIRONMENT_KDE5:
+ return "KDE5";
+ case DESKTOP_ENVIRONMENT_PANTHEON:
+ return "PANTHEON";
+ case DESKTOP_ENVIRONMENT_UNITY:
+ return "UNITY";
+ case DESKTOP_ENVIRONMENT_XFCE:
+ return "XFCE";
+ }
+ return nullptr;
+}
+
+const char* GetDesktopEnvironmentName(Environment* env) {
+ return GetDesktopEnvironmentName(GetDesktopEnvironment(env));
+}
+
+} // namespace nix
+} // namespace base
diff --git a/base/nix/xdg_util.h b/base/nix/xdg_util.h
new file mode 100644
index 0000000000..65f7d15705
--- /dev/null
+++ b/base/nix/xdg_util.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NIX_XDG_UTIL_H_
+#define BASE_NIX_XDG_UTIL_H_
+
+// XDG refers to http://en.wikipedia.org/wiki/Freedesktop.org .
+// This file contains utilities found across free desktop environments.
+//
+// TODO(brettw) this file should be in app/x11, but is currently used by
+// net. We should have a net API to allow the embedder to specify the behavior
+// that it uses XDG for, and then move this file.
+
+#include "base/base_export.h"
+
+#ifdef nix
+#error asdf
+#endif
+
+namespace base {
+
+class Environment;
+class FilePath;
+
+namespace nix {
+
+// The default XDG config directory name.
+BASE_EXPORT extern const char kDotConfigDir[];
+
+// The XDG config directory environment variable.
+BASE_EXPORT extern const char kXdgConfigHomeEnvVar[];
+
+// Utility function for getting XDG directories.
+// |env_name| is the name of an environment variable that we want to use to get
+// a directory path. |fallback_dir| is the directory relative to $HOME that we
+// use if |env_name| cannot be found or is empty. |fallback_dir| may be NULL.
+// Examples of |env_name| are XDG_CONFIG_HOME and XDG_DATA_HOME.
+BASE_EXPORT FilePath GetXDGDirectory(Environment* env, const char* env_name,
+ const char* fallback_dir);
+
+// Wrapper around xdg_user_dir_lookup() from src/base/third_party/xdg-user-dirs
+// This looks up "well known" user directories like the desktop and music
+// folder. Examples of |dir_name| are DESKTOP and MUSIC.
+BASE_EXPORT FilePath GetXDGUserDirectory(const char* dir_name,
+ const char* fallback_dir);
+
+enum DesktopEnvironment {
+ DESKTOP_ENVIRONMENT_OTHER,
+ DESKTOP_ENVIRONMENT_CINNAMON,
+ DESKTOP_ENVIRONMENT_GNOME,
+ // KDE3, KDE4 and KDE5 are sufficiently different that we count
+ // them as different desktop environments here.
+ DESKTOP_ENVIRONMENT_KDE3,
+ DESKTOP_ENVIRONMENT_KDE4,
+ DESKTOP_ENVIRONMENT_KDE5,
+ DESKTOP_ENVIRONMENT_PANTHEON,
+ DESKTOP_ENVIRONMENT_UNITY,
+ DESKTOP_ENVIRONMENT_XFCE,
+};
+
+// Return an entry from the DesktopEnvironment enum with a best guess
+// of which desktop environment we're using. We use this to know when
+// to attempt to use preferences from the desktop environment --
+// proxy settings, password manager, etc.
+BASE_EXPORT DesktopEnvironment GetDesktopEnvironment(Environment* env);
+
+// Return a string representation of the given desktop environment.
+// May return NULL in the case of DESKTOP_ENVIRONMENT_OTHER.
+BASE_EXPORT const char* GetDesktopEnvironmentName(DesktopEnvironment env);
+// Convenience wrapper that calls GetDesktopEnvironment() first.
+BASE_EXPORT const char* GetDesktopEnvironmentName(Environment* env);
+
+} // namespace nix
+} // namespace base
+
+#endif // BASE_NIX_XDG_UTIL_H_
diff --git a/base/nix/xdg_util_unittest.cc b/base/nix/xdg_util_unittest.cc
new file mode 100644
index 0000000000..e195303ca3
--- /dev/null
+++ b/base/nix/xdg_util_unittest.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/nix/xdg_util.h"
+
+#include "base/environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+
+namespace base {
+namespace nix {
+
+namespace {
+
+class MockEnvironment : public Environment {
+ public:
+ MOCK_METHOD2(GetVar, bool(StringPiece, std::string* result));
+ MOCK_METHOD2(SetVar, bool(StringPiece, const std::string& new_value));
+ MOCK_METHOD1(UnSetVar, bool(StringPiece));
+};
+
+// Needs to be const char* to make gmock happy.
+const char* const kDesktopGnome = "gnome";
+const char* const kDesktopGnomeFallback = "gnome-fallback";
+const char* const kDesktopMATE = "mate";
+const char* const kDesktopKDE4 = "kde4";
+const char* const kDesktopKDE = "kde";
+const char* const kDesktopXFCE = "xfce";
+const char* const kXdgDesktopCinnamon = "X-Cinnamon";
+const char* const kXdgDesktopGNOME = "GNOME";
+const char* const kXdgDesktopKDE = "KDE";
+const char* const kXdgDesktopPantheon = "Pantheon";
+const char* const kXdgDesktopUnity = "Unity";
+const char* const kXdgDesktopUnity7 = "Unity:Unity7";
+const char* const kXdgDesktopUnity8 = "Unity:Unity8";
+const char* const kKDESessionKDE5 = "5";
+
+const char kDesktopSession[] = "DESKTOP_SESSION";
+const char kKDESession[] = "KDE_SESSION_VERSION";
+const char kXdgDesktop[] = "XDG_CURRENT_DESKTOP";
+
+} // namespace
+
+TEST(XDGUtilTest, GetDesktopEnvironmentGnome) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kDesktopSession), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kDesktopGnome), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_GNOME, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetDesktopEnvironmentMATE) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kDesktopSession), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kDesktopMATE), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_GNOME, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetDesktopEnvironmentKDE4) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kDesktopSession), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kDesktopKDE4), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE4, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetDesktopEnvironmentKDE3) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kDesktopSession), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kDesktopKDE), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE3, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetDesktopEnvironmentXFCE) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kDesktopSession), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kDesktopXFCE), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_XFCE, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopCinnamon) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopCinnamon), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_CINNAMON, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopGnome) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopGNOME), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_GNOME, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopGnomeFallback) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopUnity), Return(true)));
+ EXPECT_CALL(getter, GetVar(Eq(kDesktopSession), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kDesktopGnomeFallback), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_GNOME, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopKDE5) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopKDE), Return(true)));
+ EXPECT_CALL(getter, GetVar(Eq(kKDESession), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kKDESessionKDE5), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE5, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopKDE4) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopKDE), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE4, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopPantheon) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopPantheon), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_PANTHEON, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopUnity) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopUnity), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_UNITY, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopUnity7) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopUnity7), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_UNITY, GetDesktopEnvironment(&getter));
+}
+
+TEST(XDGUtilTest, GetXdgDesktopUnity8) {
+ MockEnvironment getter;
+ EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false));
+ EXPECT_CALL(getter, GetVar(Eq(kXdgDesktop), _))
+ .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopUnity8), Return(true)));
+
+ EXPECT_EQ(DESKTOP_ENVIRONMENT_UNITY, GetDesktopEnvironment(&getter));
+}
+
+} // namespace nix
+} // namespace base
diff --git a/base/numerics/BUILD.gn b/base/numerics/BUILD.gn
deleted file mode 100644
index 0bb8dd1036..0000000000
--- a/base/numerics/BUILD.gn
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (c) 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is a dependency-free, header-only, library, and it needs to stay that
-# way to facilitate pulling it into various third-party projects. So, this
-# file is here to protect against accidentally introducing external
-# dependencies or depending on internal implementation details.
-source_set("base_numerics") {
- visibility = [ "//base/*" ]
- sources = [
- "checked_math_impl.h",
- "clamped_math_impl.h",
- "safe_conversions_arm_impl.h",
- "safe_conversions_impl.h",
- "safe_math_arm_impl.h",
- "safe_math_clang_gcc_impl.h",
- "safe_math_shared_impl.h",
- ]
- public = [
- "checked_math.h",
- "clamped_math.h",
- "math_constants.h",
- "ranges.h",
- "safe_conversions.h",
- "safe_math.h",
- ]
-}
diff --git a/base/os_compat_android.cc b/base/os_compat_android.cc
new file mode 100644
index 0000000000..c1a2ac86dd
--- /dev/null
+++ b/base/os_compat_android.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/os_compat_android.h"
+
+#include <asm/unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#if !defined(__LP64__)
+#include <time64.h>
+#endif
+
+#include "base/rand_util.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+
+extern "C" {
+// There is no futimes() avaiable in Bionic, so we provide our own
+// implementation until it is there.
+int futimes(int fd, const struct timeval tv[2]) {
+ if (tv == NULL)
+ return syscall(__NR_utimensat, fd, NULL, NULL, 0);
+
+ if (tv[0].tv_usec < 0 || tv[0].tv_usec >= 1000000 ||
+ tv[1].tv_usec < 0 || tv[1].tv_usec >= 1000000) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // Convert timeval to timespec.
+ struct timespec ts[2];
+ ts[0].tv_sec = tv[0].tv_sec;
+ ts[0].tv_nsec = tv[0].tv_usec * 1000;
+ ts[1].tv_sec = tv[1].tv_sec;
+ ts[1].tv_nsec = tv[1].tv_usec * 1000;
+ return syscall(__NR_utimensat, fd, NULL, ts, 0);
+}
+
+#if !defined(__LP64__)
+// 32-bit Android has only timegm64() and not timegm().
+// We replicate the behaviour of timegm() when the result overflows time_t.
+time_t timegm(struct tm* const t) {
+ // time_t is signed on Android.
+ static const time_t kTimeMax = ~(1L << (sizeof(time_t) * CHAR_BIT - 1));
+ static const time_t kTimeMin = (1L << (sizeof(time_t) * CHAR_BIT - 1));
+ time64_t result = timegm64(t);
+ if (result < kTimeMin || result > kTimeMax)
+ return -1;
+ return result;
+}
+#endif
+
+// The following is only needed when building with GCC 4.6 or higher
+// (i.e. not with Android GCC 4.4.3, nor with Clang).
+//
+// GCC is now capable of optimizing successive calls to sin() and cos() into
+// a single call to sincos(). This means that source code that looks like:
+//
+// double c, s;
+// c = cos(angle);
+// s = sin(angle);
+//
+// Will generate machine code that looks like:
+//
+// double c, s;
+// sincos(angle, &s, &c);
+//
+// Unfortunately, sincos() and friends are not part of the Android libm.so
+// library provided by the NDK for API level 9. When the optimization kicks
+// in, it makes the final build fail with a puzzling message (puzzling
+// because 'sincos' doesn't appear anywhere in the sources!).
+//
+// To solve this, we provide our own implementation of the sincos() function
+// and related friends. Note that we must also explicitely tell GCC to disable
+// optimizations when generating these. Otherwise, the generated machine code
+// for each function would simply end up calling itself, resulting in a
+// runtime crash due to stack overflow.
+//
+#if defined(__GNUC__) && !defined(__clang__) && \
+ !defined(ANDROID_SINCOS_PROVIDED)
+
+// For the record, Clang does not support the 'optimize' attribute.
+// In the unlikely event that it begins performing this optimization too,
+// we'll have to find a different way to achieve this. NOTE: Tested with O1
+// which still performs the optimization.
+//
+#define GCC_NO_OPTIMIZE __attribute__((optimize("O0")))
+
+GCC_NO_OPTIMIZE
+void sincos(double angle, double* s, double *c) {
+ *c = cos(angle);
+ *s = sin(angle);
+}
+
+GCC_NO_OPTIMIZE
+void sincosf(float angle, float* s, float* c) {
+ *c = cosf(angle);
+ *s = sinf(angle);
+}
+
+#endif // __GNUC__ && !__clang__
+
+// An implementation of mkdtemp, since it is not exposed by the NDK
+// for native API level 9 that we target.
+//
+// For any changes in the mkdtemp function, you should manually run the unittest
+// OsCompatAndroidTest.DISABLED_TestMkdTemp in your local machine to check if it
+// passes. Please don't enable it, since it creates a directory and may be
+// source of flakyness.
+char* mkdtemp(char* path) {
+ if (path == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ const int path_len = strlen(path);
+
+ // The last six characters of 'path' must be XXXXXX.
+ const base::StringPiece kSuffix("XXXXXX");
+ const int kSuffixLen = kSuffix.length();
+ if (!base::StringPiece(path, path_len).ends_with(kSuffix)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ // If the path contains a directory, as in /tmp/foo/XXXXXXXX, make sure
+ // that /tmp/foo exists, otherwise we're going to loop a really long
+ // time for nothing below
+ char* dirsep = strrchr(path, '/');
+ if (dirsep != NULL) {
+ struct stat st;
+ int ret;
+
+ *dirsep = '\0'; // Terminating directory path temporarily
+
+ ret = stat(path, &st);
+
+ *dirsep = '/'; // Restoring directory separator
+ if (ret < 0) // Directory probably does not exist
+ return NULL;
+ if (!S_ISDIR(st.st_mode)) { // Not a directory
+ errno = ENOTDIR;
+ return NULL;
+ }
+ }
+
+ // Max number of tries using different random suffixes.
+ const int kMaxTries = 100;
+
+ // Now loop until we CAN create a directory by that name or we reach the max
+ // number of tries.
+ for (int i = 0; i < kMaxTries; ++i) {
+ // Fill the suffix XXXXXX with a random string composed of a-z chars.
+ for (int pos = 0; pos < kSuffixLen; ++pos) {
+ char rand_char = static_cast<char>(base::RandInt('a', 'z'));
+ path[path_len - kSuffixLen + pos] = rand_char;
+ }
+ if (mkdir(path, 0700) == 0) {
+ // We just created the directory succesfully.
+ return path;
+ }
+ if (errno != EEXIST) {
+ // The directory doesn't exist, but an error occured
+ return NULL;
+ }
+ }
+
+ // We reached the max number of tries.
+ return NULL;
+}
+
+} // extern "C"
diff --git a/base/os_compat_android.h b/base/os_compat_android.h
new file mode 100644
index 0000000000..e33b1f7ac3
--- /dev/null
+++ b/base/os_compat_android.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_OS_COMPAT_ANDROID_H_
+#define BASE_OS_COMPAT_ANDROID_H_
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <utime.h>
+
+// Not implemented in Bionic.
+extern "C" int futimes(int fd, const struct timeval tv[2]);
+
+// Not exposed or implemented in Bionic.
+extern "C" char* mkdtemp(char* path);
+
+// Android has no timegm().
+extern "C" time_t timegm(struct tm* const t);
+
+#endif // BASE_OS_COMPAT_ANDROID_H_
diff --git a/base/os_compat_android_unittest.cc b/base/os_compat_android_unittest.cc
new file mode 100644
index 0000000000..7fbdc6dace
--- /dev/null
+++ b/base/os_compat_android_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/os_compat_android.h"
+
+#include "base/files/file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+typedef testing::Test OsCompatAndroidTest;
+
+// Keep this Unittest DISABLED_ , because it actually creates a directory in the
+// device and it may be source of flakyness. For any changes in the mkdtemp
+// function, you should run this unittest in your local machine to check if it
+// passes.
+TEST_F(OsCompatAndroidTest, DISABLED_TestMkdTemp) {
+ FilePath tmp_dir;
+ EXPECT_TRUE(base::GetTempDir(&tmp_dir));
+
+ // Not six XXXXXX at the suffix of the path.
+ FilePath sub_dir = tmp_dir.Append("XX");
+ std::string sub_dir_string = sub_dir.value();
+ // this should be OK since mkdtemp just replaces characters in place
+ char* buffer = const_cast<char*>(sub_dir_string.c_str());
+ EXPECT_EQ(NULL, mkdtemp(buffer));
+
+ // Directory does not exist
+ char invalid_path2[] = "doesntoexist/foobarXXXXXX";
+ EXPECT_EQ(NULL, mkdtemp(invalid_path2));
+
+ // Successfully create a tmp dir.
+ FilePath sub_dir2 = tmp_dir.Append("XXXXXX");
+ std::string sub_dir2_string = sub_dir2.value();
+ // this should be OK since mkdtemp just replaces characters in place
+ char* buffer2 = const_cast<char*>(sub_dir2_string.c_str());
+ EXPECT_TRUE(mkdtemp(buffer2) != NULL);
+}
+
+} // namespace base
diff --git a/base/path_service_unittest.cc b/base/path_service_unittest.cc
new file mode 100644
index 0000000000..cf69ef111c
--- /dev/null
+++ b/base/path_service_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/path_service.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest-spi.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace base {
+
+namespace {
+
+// Returns true if PathService::Get returns true and sets the path parameter
+// to non-empty for the given PathService::DirType enumeration value.
+bool ReturnsValidPath(int dir_type) {
+ FilePath path;
+ bool result = PathService::Get(dir_type, &path);
+
+ // Some paths might not exist on some platforms in which case confirming
+ // |result| is true and !path.empty() is the best we can do.
+ bool check_path_exists = true;
+#if defined(OS_POSIX)
+ // If chromium has never been started on this account, the cache path may not
+ // exist.
+ if (dir_type == DIR_CACHE)
+ check_path_exists = false;
+#endif
+#if defined(OS_LINUX)
+ // On the linux try-bots: a path is returned (e.g. /home/chrome-bot/Desktop),
+ // but it doesn't exist.
+ if (dir_type == DIR_USER_DESKTOP)
+ check_path_exists = false;
+#endif
+#if defined(OS_IOS)
+ // Bundled unittests on iOS may not have Resources directory in the bundle.
+ if (dir_type == DIR_ASSETS)
+ check_path_exists = false;
+#endif
+#if defined(OS_MACOSX)
+ if (dir_type != DIR_EXE && dir_type != DIR_MODULE && dir_type != FILE_EXE &&
+ dir_type != FILE_MODULE) {
+ if (path.ReferencesParent())
+ return false;
+ }
+#else
+ if (path.ReferencesParent())
+ return false;
+#endif
+ return result && !path.empty() && (!check_path_exists || PathExists(path));
+}
+
+#if defined(OS_WIN)
+// Function to test any directory keys that are not supported on some versions
+// of Windows. Checks that the function fails and that the returned path is
+// empty.
+bool ReturnsInvalidPath(int dir_type) {
+ FilePath path;
+ bool result = PathService::Get(dir_type, &path);
+ return !result && path.empty();
+}
+#endif
+
+} // namespace
+
+// On the Mac this winds up using some autoreleased objects, so we need to
+// be a PlatformTest.
+typedef PlatformTest PathServiceTest;
+
+// Test that all PathService::Get calls return a value and a true result
+// in the development environment. (This test was created because a few
+// later changes to Get broke the semantics of the function and yielded the
+// correct value while returning false.)
+TEST_F(PathServiceTest, Get) {
+ for (int key = PATH_START + 1; key < PATH_END; ++key) {
+#if defined(OS_ANDROID)
+ if (key == FILE_MODULE || key == DIR_USER_DESKTOP ||
+ key == DIR_HOME)
+ continue; // Android doesn't implement these.
+#elif defined(OS_IOS)
+ if (key == DIR_USER_DESKTOP)
+ continue; // iOS doesn't implement DIR_USER_DESKTOP.
+#elif defined(OS_FUCHSIA)
+ if (key == DIR_USER_DESKTOP || key == FILE_MODULE || key == DIR_MODULE)
+ continue; // Fuchsia doesn't implement DIR_USER_DESKTOP, FILE_MODULE and
+ // DIR_MODULE.
+#endif
+ EXPECT_PRED1(ReturnsValidPath, key);
+ }
+#if defined(OS_WIN)
+ for (int key = PATH_WIN_START + 1; key < PATH_WIN_END; ++key) {
+ bool valid = true;
+ if (key == DIR_APP_SHORTCUTS)
+ valid = base::win::GetVersion() >= base::win::VERSION_WIN8;
+
+ if (valid)
+ EXPECT_TRUE(ReturnsValidPath(key)) << key;
+ else
+ EXPECT_TRUE(ReturnsInvalidPath(key)) << key;
+ }
+#elif defined(OS_MACOSX)
+ for (int key = PATH_MAC_START + 1; key < PATH_MAC_END; ++key) {
+ EXPECT_PRED1(ReturnsValidPath, key);
+ }
+#elif defined(OS_ANDROID)
+ for (int key = PATH_ANDROID_START + 1; key < PATH_ANDROID_END;
+ ++key) {
+ EXPECT_PRED1(ReturnsValidPath, key);
+ }
+#elif defined(OS_POSIX)
+ for (int key = PATH_POSIX_START + 1; key < PATH_POSIX_END;
+ ++key) {
+ EXPECT_PRED1(ReturnsValidPath, key);
+ }
+#endif
+}
+
+// Test that all versions of the Override function of PathService do what they
+// are supposed to do.
+TEST_F(PathServiceTest, Override) {
+ int my_special_key = 666;
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath fake_cache_dir(temp_dir.GetPath().AppendASCII("cache"));
+ // PathService::Override should always create the path provided if it doesn't
+ // exist.
+ EXPECT_TRUE(PathService::Override(my_special_key, fake_cache_dir));
+ EXPECT_TRUE(PathExists(fake_cache_dir));
+
+ FilePath fake_cache_dir2(temp_dir.GetPath().AppendASCII("cache2"));
+ // PathService::OverrideAndCreateIfNeeded should obey the |create| parameter.
+ PathService::OverrideAndCreateIfNeeded(my_special_key,
+ fake_cache_dir2,
+ false,
+ false);
+ EXPECT_FALSE(PathExists(fake_cache_dir2));
+ EXPECT_TRUE(PathService::OverrideAndCreateIfNeeded(my_special_key,
+ fake_cache_dir2,
+ false,
+ true));
+ EXPECT_TRUE(PathExists(fake_cache_dir2));
+
+#if defined(OS_POSIX)
+ FilePath non_existent(
+ MakeAbsoluteFilePath(temp_dir.GetPath()).AppendASCII("non_existent"));
+ EXPECT_TRUE(non_existent.IsAbsolute());
+ EXPECT_FALSE(PathExists(non_existent));
+#if !defined(OS_ANDROID)
+ // This fails because MakeAbsoluteFilePath fails for non-existent files.
+ // Earlier versions of Bionic libc don't fail for non-existent files, so
+ // skip this check on Android.
+ EXPECT_FALSE(PathService::OverrideAndCreateIfNeeded(my_special_key,
+ non_existent,
+ false,
+ false));
+#endif
+ // This works because indicating that |non_existent| is absolute skips the
+ // internal MakeAbsoluteFilePath call.
+ EXPECT_TRUE(PathService::OverrideAndCreateIfNeeded(my_special_key,
+ non_existent,
+ true,
+ false));
+ // Check that the path has been overridden and no directory was created.
+ EXPECT_FALSE(PathExists(non_existent));
+ FilePath path;
+ EXPECT_TRUE(PathService::Get(my_special_key, &path));
+ EXPECT_EQ(non_existent, path);
+#endif
+}
+
+// Check if multiple overrides can co-exist.
+TEST_F(PathServiceTest, OverrideMultiple) {
+ int my_special_key = 666;
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath fake_cache_dir1(temp_dir.GetPath().AppendASCII("1"));
+ EXPECT_TRUE(PathService::Override(my_special_key, fake_cache_dir1));
+ EXPECT_TRUE(PathExists(fake_cache_dir1));
+ ASSERT_EQ(1, WriteFile(fake_cache_dir1.AppendASCII("t1"), ".", 1));
+
+ FilePath fake_cache_dir2(temp_dir.GetPath().AppendASCII("2"));
+ EXPECT_TRUE(PathService::Override(my_special_key + 1, fake_cache_dir2));
+ EXPECT_TRUE(PathExists(fake_cache_dir2));
+ ASSERT_EQ(1, WriteFile(fake_cache_dir2.AppendASCII("t2"), ".", 1));
+
+ FilePath result;
+ EXPECT_TRUE(PathService::Get(my_special_key, &result));
+ // Override might have changed the path representation but our test file
+ // should be still there.
+ EXPECT_TRUE(PathExists(result.AppendASCII("t1")));
+ EXPECT_TRUE(PathService::Get(my_special_key + 1, &result));
+ EXPECT_TRUE(PathExists(result.AppendASCII("t2")));
+}
+
+TEST_F(PathServiceTest, RemoveOverride) {
+ // Before we start the test we have to call RemoveOverride at least once to
+ // clear any overrides that might have been left from other tests.
+ PathService::RemoveOverride(DIR_TEMP);
+
+ FilePath original_user_data_dir;
+ EXPECT_TRUE(PathService::Get(DIR_TEMP, &original_user_data_dir));
+ EXPECT_FALSE(PathService::RemoveOverride(DIR_TEMP));
+
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ EXPECT_TRUE(PathService::Override(DIR_TEMP, temp_dir.GetPath()));
+ FilePath new_user_data_dir;
+ EXPECT_TRUE(PathService::Get(DIR_TEMP, &new_user_data_dir));
+ EXPECT_NE(original_user_data_dir, new_user_data_dir);
+
+ EXPECT_TRUE(PathService::RemoveOverride(DIR_TEMP));
+ EXPECT_TRUE(PathService::Get(DIR_TEMP, &new_user_data_dir));
+ EXPECT_EQ(original_user_data_dir, new_user_data_dir);
+}
+
+#if defined(OS_WIN)
+TEST_F(PathServiceTest, GetProgramFiles) {
+ FilePath programfiles_dir;
+#if defined(_WIN64)
+ // 64-bit on 64-bit.
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILES,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files"));
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILESX86,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files (x86)"));
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILES6432,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files"));
+#else
+ if (base::win::OSInfo::GetInstance()->wow64_status() ==
+ base::win::OSInfo::WOW64_ENABLED) {
+ // 32-bit on 64-bit.
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILES,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files (x86)"));
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILESX86,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files (x86)"));
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILES6432,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files"));
+ } else {
+ // 32-bit on 32-bit.
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILES,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files"));
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILESX86,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files"));
+ EXPECT_TRUE(PathService::Get(DIR_PROGRAM_FILES6432,
+ &programfiles_dir));
+ EXPECT_EQ(programfiles_dir.value(),
+ FILE_PATH_LITERAL("C:\\Program Files"));
+ }
+#endif
+}
+#endif
+
+} // namespace base
diff --git a/base/posix/safe_strerror.cc b/base/posix/safe_strerror.cc
index aef5742d33..aab8b87921 100644
--- a/base/posix/safe_strerror.cc
+++ b/base/posix/safe_strerror.cc
@@ -2,14 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#if defined(__ANDROID__)
-// Post-L versions of bionic define the GNU-specific strerror_r if _GNU_SOURCE
-// is defined, but the symbol is renamed to __gnu_strerror_r which only exists
-// on those later versions. To preserve ABI compatibility with older versions,
-// undefine _GNU_SOURCE and use the POSIX version.
-#undef _GNU_SOURCE
-#endif
-
#include "base/posix/safe_strerror.h"
#include <errno.h>
@@ -22,6 +14,11 @@ namespace base {
#if defined(__GLIBC__) || defined(OS_NACL)
#define USE_HISTORICAL_STRERRO_R 1
+// Post-L versions of bionic define the GNU-specific strerror_r if _GNU_SOURCE
+// is defined, but the symbol is renamed to __gnu_strerror_r which only exists
+// on those later versions. For parity, add the same condition as bionic.
+#elif defined(__BIONIC__) && defined(_GNU_SOURCE) && __ANDROID_API__ >= 23
+#define USE_HISTORICAL_STRERRO_R 1
#else
#define USE_HISTORICAL_STRERRO_R 0
#endif
diff --git a/base/power_monitor/power_monitor.cc b/base/power_monitor/power_monitor.cc
new file mode 100644
index 0000000000..ba0ef6bdfc
--- /dev/null
+++ b/base/power_monitor/power_monitor.cc
@@ -0,0 +1,66 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/power_monitor/power_monitor.h"
+
+#include <utility>
+
+#include "base/power_monitor/power_monitor_source.h"
+
+namespace base {
+
+static PowerMonitor* g_power_monitor = nullptr;
+
+PowerMonitor::PowerMonitor(std::unique_ptr<PowerMonitorSource> source)
+ : observers_(new ObserverListThreadSafe<PowerObserver>()),
+ source_(std::move(source)) {
+ DCHECK(!g_power_monitor);
+ g_power_monitor = this;
+}
+
+PowerMonitor::~PowerMonitor() {
+ source_->Shutdown();
+ DCHECK_EQ(this, g_power_monitor);
+ g_power_monitor = nullptr;
+}
+
+// static
+PowerMonitor* PowerMonitor::Get() {
+ return g_power_monitor;
+}
+
+void PowerMonitor::AddObserver(PowerObserver* obs) {
+ observers_->AddObserver(obs);
+}
+
+void PowerMonitor::RemoveObserver(PowerObserver* obs) {
+ observers_->RemoveObserver(obs);
+}
+
+PowerMonitorSource* PowerMonitor::Source() {
+ return source_.get();
+}
+
+bool PowerMonitor::IsOnBatteryPower() {
+ return source_->IsOnBatteryPower();
+}
+
+void PowerMonitor::NotifyPowerStateChange(bool battery_in_use) {
+ DVLOG(1) << "PowerStateChange: " << (battery_in_use ? "On" : "Off")
+ << " battery";
+ observers_->Notify(FROM_HERE, &PowerObserver::OnPowerStateChange,
+ battery_in_use);
+}
+
+void PowerMonitor::NotifySuspend() {
+ DVLOG(1) << "Power Suspending";
+ observers_->Notify(FROM_HERE, &PowerObserver::OnSuspend);
+}
+
+void PowerMonitor::NotifyResume() {
+ DVLOG(1) << "Power Resuming";
+ observers_->Notify(FROM_HERE, &PowerObserver::OnResume);
+}
+
+} // namespace base
diff --git a/base/power_monitor/power_monitor_device_source.cc b/base/power_monitor/power_monitor_device_source.cc
new file mode 100644
index 0000000000..f42065499f
--- /dev/null
+++ b/base/power_monitor/power_monitor_device_source.cc
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/power_monitor/power_monitor_device_source.h"
+
+namespace base {
+
+PowerMonitorDeviceSource::PowerMonitorDeviceSource() {
+#if defined(OS_MACOSX)
+ PlatformInit();
+#endif
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ // Provide the correct battery status if possible. Others platforms, such as
+ // Android and ChromeOS, will update their status once their backends are
+ // actually initialized.
+ SetInitialOnBatteryPowerState(IsOnBatteryPowerImpl());
+#endif
+}
+
+PowerMonitorDeviceSource::~PowerMonitorDeviceSource() {
+#if defined(OS_MACOSX)
+ PlatformDestroy();
+#endif
+}
+
+// PowerMonitorDeviceSource does not need to take any special action to ensure
+// that it doesn't callback into PowerMonitor after this phase of shutdown has
+// completed.
+void PowerMonitorDeviceSource::Shutdown() {}
+
+} // namespace base
diff --git a/base/power_monitor/power_monitor_device_source_android.cc b/base/power_monitor/power_monitor_device_source_android.cc
new file mode 100644
index 0000000000..7688513501
--- /dev/null
+++ b/base/power_monitor/power_monitor_device_source_android.cc
@@ -0,0 +1,39 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/power_monitor/power_monitor.h"
+#include "base/power_monitor/power_monitor_device_source.h"
+#include "base/power_monitor/power_monitor_source.h"
+#include "jni/PowerMonitor_jni.h"
+
+namespace base {
+
+// A helper function which is a friend of PowerMonitorSource.
+void ProcessPowerEventHelper(PowerMonitorSource::PowerEvent event) {
+ PowerMonitorSource::ProcessPowerEvent(event);
+}
+
+namespace android {
+
+// Native implementation of PowerMonitor.java. Note: This will be invoked by
+// PowerMonitor.java shortly after startup to set the correct initial value for
+// "is on battery power."
+void JNI_PowerMonitor_OnBatteryChargingChanged(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ ProcessPowerEventHelper(PowerMonitorSource::POWER_STATE_EVENT);
+}
+
+// Note: Android does not have the concept of suspend / resume as it's known by
+// other platforms. Thus we do not send Suspend/Resume notifications. See
+// http://crbug.com/644515
+
+} // namespace android
+
+bool PowerMonitorDeviceSource::IsOnBatteryPowerImpl() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return base::android::Java_PowerMonitor_isBatteryPower(env);
+}
+
+} // namespace base
diff --git a/base/power_monitor/power_monitor_device_source_chromeos.cc b/base/power_monitor/power_monitor_device_source_chromeos.cc
new file mode 100644
index 0000000000..c3466ee15a
--- /dev/null
+++ b/base/power_monitor/power_monitor_device_source_chromeos.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/power_monitor/power_monitor.h"
+#include "base/power_monitor/power_monitor_device_source.h"
+#include "base/power_monitor/power_monitor_source.h"
+
+namespace base {
+
+namespace {
+
+// The most-recently-seen power source.
+bool g_on_battery = false;
+
+} // namespace
+
+// static
+void PowerMonitorDeviceSource::SetPowerSource(bool on_battery) {
+ if (on_battery != g_on_battery) {
+ g_on_battery = on_battery;
+ ProcessPowerEvent(POWER_STATE_EVENT);
+ }
+}
+
+// static
+void PowerMonitorDeviceSource::HandleSystemSuspending() {
+ ProcessPowerEvent(SUSPEND_EVENT);
+}
+
+// static
+void PowerMonitorDeviceSource::HandleSystemResumed() {
+ ProcessPowerEvent(RESUME_EVENT);
+}
+
+bool PowerMonitorDeviceSource::IsOnBatteryPowerImpl() {
+ return g_on_battery;
+}
+
+} // namespace base
diff --git a/base/power_monitor/power_monitor_device_source_stub.cc b/base/power_monitor/power_monitor_device_source_stub.cc
new file mode 100644
index 0000000000..f24e5b23f0
--- /dev/null
+++ b/base/power_monitor/power_monitor_device_source_stub.cc
@@ -0,0 +1,14 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/power_monitor/power_monitor_device_source.h"
+
+namespace base {
+
+bool PowerMonitorDeviceSource::IsOnBatteryPowerImpl() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+} // namespace base
diff --git a/base/power_monitor/power_monitor_source.cc b/base/power_monitor/power_monitor_source.cc
new file mode 100644
index 0000000000..d4757b0629
--- /dev/null
+++ b/base/power_monitor/power_monitor_source.cc
@@ -0,0 +1,69 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/power_monitor/power_monitor_source.h"
+
+#include "base/power_monitor/power_monitor.h"
+#include "build/build_config.h"
+
+namespace base {
+
+PowerMonitorSource::PowerMonitorSource() = default;
+PowerMonitorSource::~PowerMonitorSource() = default;
+
+bool PowerMonitorSource::IsOnBatteryPower() {
+ AutoLock auto_lock(battery_lock_);
+ return on_battery_power_;
+}
+
+void PowerMonitorSource::ProcessPowerEvent(PowerEvent event_id) {
+ PowerMonitor* monitor = PowerMonitor::Get();
+ if (!monitor)
+ return;
+
+ PowerMonitorSource* source = monitor->Source();
+
+ // Suppress duplicate notifications. Some platforms may
+ // send multiple notifications of the same event.
+ switch (event_id) {
+ case POWER_STATE_EVENT:
+ {
+ bool new_on_battery_power = source->IsOnBatteryPowerImpl();
+ bool changed = false;
+
+ {
+ AutoLock auto_lock(source->battery_lock_);
+ if (source->on_battery_power_ != new_on_battery_power) {
+ changed = true;
+ source->on_battery_power_ = new_on_battery_power;
+ }
+ }
+
+ if (changed)
+ monitor->NotifyPowerStateChange(new_on_battery_power);
+ }
+ break;
+ case RESUME_EVENT:
+ if (source->suspended_) {
+ source->suspended_ = false;
+ monitor->NotifyResume();
+ }
+ break;
+ case SUSPEND_EVENT:
+ if (!source->suspended_) {
+ source->suspended_ = true;
+ monitor->NotifySuspend();
+ }
+ break;
+ }
+}
+
+void PowerMonitorSource::SetInitialOnBatteryPowerState(bool on_battery_power) {
+ // Must only be called before a monitor exists, otherwise the caller should
+ // have just used a normal ProcessPowerEvent(POWER_STATE_EVENT) call.
+ DCHECK(!PowerMonitor::Get());
+ on_battery_power_ = on_battery_power;
+}
+
+} // namespace base
diff --git a/base/power_monitor/power_monitor_unittest.cc b/base/power_monitor/power_monitor_unittest.cc
new file mode 100644
index 0000000000..7f2a84b774
--- /dev/null
+++ b/base/power_monitor/power_monitor_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/test/power_monitor_test_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+class PowerMonitorTest : public testing::Test {
+ protected:
+ PowerMonitorTest() {
+ power_monitor_source_ = new PowerMonitorTestSource();
+ power_monitor_.reset(new PowerMonitor(
+ std::unique_ptr<PowerMonitorSource>(power_monitor_source_)));
+ }
+ ~PowerMonitorTest() override = default;
+
+ PowerMonitorTestSource* source() { return power_monitor_source_; }
+ PowerMonitor* monitor() { return power_monitor_.get(); }
+
+ private:
+ base::MessageLoop message_loop_;
+ PowerMonitorTestSource* power_monitor_source_;
+ std::unique_ptr<PowerMonitor> power_monitor_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerMonitorTest);
+};
+
+// PowerMonitorSource is tightly coupled with the PowerMonitor, so this test
+// Will cover both classes
+TEST_F(PowerMonitorTest, PowerNotifications) {
+ const int kObservers = 5;
+
+ PowerMonitorTestObserver observers[kObservers];
+ for (int index = 0; index < kObservers; ++index)
+ monitor()->AddObserver(&observers[index]);
+
+ // Sending resume when not suspended should have no effect.
+ source()->GenerateResumeEvent();
+ EXPECT_EQ(observers[0].resumes(), 0);
+
+ // Pretend we suspended.
+ source()->GenerateSuspendEvent();
+ // Ensure all observers were notified of the event
+ for (int index = 0; index < kObservers; ++index)
+ EXPECT_EQ(observers[index].suspends(), 1);
+
+ // Send a second suspend notification. This should be suppressed.
+ source()->GenerateSuspendEvent();
+ EXPECT_EQ(observers[0].suspends(), 1);
+
+ // Pretend we were awakened.
+ source()->GenerateResumeEvent();
+ EXPECT_EQ(observers[0].resumes(), 1);
+
+ // Send a duplicate resume notification. This should be suppressed.
+ source()->GenerateResumeEvent();
+ EXPECT_EQ(observers[0].resumes(), 1);
+
+ // Pretend the device has gone on battery power
+ source()->GeneratePowerStateEvent(true);
+ EXPECT_EQ(observers[0].power_state_changes(), 1);
+ EXPECT_EQ(observers[0].last_power_state(), true);
+
+ // Repeated indications the device is on battery power should be suppressed.
+ source()->GeneratePowerStateEvent(true);
+ EXPECT_EQ(observers[0].power_state_changes(), 1);
+
+ // Pretend the device has gone off battery power
+ source()->GeneratePowerStateEvent(false);
+ EXPECT_EQ(observers[0].power_state_changes(), 2);
+ EXPECT_EQ(observers[0].last_power_state(), false);
+
+ // Repeated indications the device is off battery power should be suppressed.
+ source()->GeneratePowerStateEvent(false);
+ EXPECT_EQ(observers[0].power_state_changes(), 2);
+}
+
+} // namespace base
diff --git a/base/process/memory_unittest.cc b/base/process/memory_unittest.cc
new file mode 100644
index 0000000000..835cf7e485
--- /dev/null
+++ b/base/process/memory_unittest.cc
@@ -0,0 +1,533 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define _CRT_SECURE_NO_WARNINGS
+
+#include "base/process/memory.h"
+
+#include <stddef.h>
+
+#include <limits>
+
+#include "base/allocator/allocator_check.h"
+#include "base/allocator/buildflags.h"
+#include "base/compiler_specific.h"
+#include "base/debug/alias.h"
+#include "base/memory/aligned_memory.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+#if defined(OS_POSIX)
+#include <errno.h>
+#endif
+#if defined(OS_MACOSX)
+#include <malloc/malloc.h>
+#include "base/allocator/allocator_interception_mac.h"
+#include "base/allocator/allocator_shim.h"
+#include "base/process/memory_unittest_mac.h"
+#endif
+#if defined(OS_LINUX)
+#include <malloc.h>
+#include "base/test/malloc_wrapper.h"
+#endif
+
+#if defined(OS_WIN)
+
+#if defined(COMPILER_MSVC)
+// ssize_t needed for OutOfMemoryTest.
+#if defined(_WIN64)
+typedef __int64 ssize_t;
+#else
+typedef long ssize_t;
+#endif
+#endif
+
+// HeapQueryInformation function pointer.
+typedef BOOL (WINAPI* HeapQueryFn) \
+ (HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T);
+
+#endif // defined(OS_WIN)
+
+#if defined(OS_MACOSX)
+
+// For the following Mac tests:
+// Note that base::EnableTerminationOnHeapCorruption() is called as part of
+// test suite setup and does not need to be done again, else mach_override
+// will fail.
+
+TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) {
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::InitializeAllocatorShim();
+#endif
+ // Assert that freeing an unallocated pointer will crash the process.
+ char buf[9];
+ asm("" : "=r" (buf)); // Prevent clang from being too smart.
+#if ARCH_CPU_64_BITS
+ // On 64 bit Macs, the malloc system automatically abort()s on heap corruption
+ // but does not output anything.
+ ASSERT_DEATH(free(buf), "");
+#elif defined(ADDRESS_SANITIZER)
+ // AddressSanitizer replaces malloc() and prints a different error message on
+ // heap corruption.
+ ASSERT_DEATH(free(buf), "attempting free on address which "
+ "was not malloc\\(\\)-ed");
+#else
+ ADD_FAILURE() << "This test is not supported in this build configuration.";
+#endif
+
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::UninterceptMallocZonesForTesting();
+#endif
+}
+
+#endif // defined(OS_MACOSX)
+
+TEST(MemoryTest, AllocatorShimWorking) {
+#if defined(OS_MACOSX)
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::InitializeAllocatorShim();
+#endif
+ base::allocator::InterceptAllocationsMac();
+#endif
+ ASSERT_TRUE(base::allocator::IsAllocatorInitialized());
+
+#if defined(OS_MACOSX)
+ base::allocator::UninterceptMallocZonesForTesting();
+#endif
+}
+
+// OpenBSD does not support these tests. Don't test these on ASan/TSan/MSan
+// configurations: only test the real allocator.
+// Windows only supports these tests with the allocator shim in place.
+#if !defined(OS_OPENBSD) && BUILDFLAG(USE_ALLOCATOR_SHIM) && \
+ !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
+
+namespace {
+#if defined(OS_WIN)
+// Windows raises an exception rather than using LOG(FATAL) in order to make the
+// exit code unique to OOM.
+const char* kOomRegex = "";
+const int kExitCode = base::win::kOomExceptionCode;
+#else
+const char* kOomRegex = "Out of memory";
+const int kExitCode = 1;
+#endif
+} // namespace
+
+class OutOfMemoryTest : public testing::Test {
+ public:
+ OutOfMemoryTest()
+ : value_(nullptr),
+ // Make test size as large as possible minus a few pages so
+ // that alignment or other rounding doesn't make it wrap.
+ test_size_(std::numeric_limits<std::size_t>::max() - 12 * 1024),
+ // A test size that is > 2Gb and will cause the allocators to reject
+ // the allocation due to security restrictions. See crbug.com/169327.
+ insecure_test_size_(std::numeric_limits<int>::max()),
+ signed_test_size_(std::numeric_limits<ssize_t>::max()) {}
+
+ protected:
+ void* value_;
+ size_t test_size_;
+ size_t insecure_test_size_;
+ ssize_t signed_test_size_;
+};
+
+class OutOfMemoryDeathTest : public OutOfMemoryTest {
+ public:
+ void SetUpInDeathAssert() {
+#if defined(OS_MACOSX) && BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::InitializeAllocatorShim();
+#endif
+
+ // Must call EnableTerminationOnOutOfMemory() because that is called from
+ // chrome's main function and therefore hasn't been called yet.
+ // Since this call may result in another thread being created and death
+ // tests shouldn't be started in a multithread environment, this call
+ // should be done inside of the ASSERT_DEATH.
+ base::EnableTerminationOnOutOfMemory();
+ }
+
+#if defined(OS_MACOSX)
+ void TearDown() override {
+ base::allocator::UninterceptMallocZonesForTesting();
+ }
+#endif
+};
+
+TEST_F(OutOfMemoryDeathTest, New) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = operator new(test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, NewArray) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = new char[test_size_];
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, Malloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = malloc(test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, Realloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = realloc(nullptr, test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, Calloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = calloc(1024, test_size_ / 1024L);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, AlignedAlloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = base::AlignedAlloc(test_size_, 8);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+// POSIX does not define an aligned realloc function.
+#if defined(OS_WIN)
+TEST_F(OutOfMemoryDeathTest, AlignedRealloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = _aligned_realloc(NULL, test_size_, 8);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+namespace {
+
+constexpr uint32_t kUnhandledExceptionExitCode = 0xBADA55;
+
+// This unhandled exception filter exits the process with an exit code distinct
+// from the exception code. This is to verify that the out of memory new handler
+// causes an unhandled exception.
+LONG WINAPI ExitingUnhandledExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
+ _exit(kUnhandledExceptionExitCode);
+}
+
+} // namespace
+
+TEST_F(OutOfMemoryDeathTest, NewHandlerGeneratesUnhandledException) {
+ ASSERT_EXIT(
+ {
+ SetUpInDeathAssert();
+ SetUnhandledExceptionFilter(&ExitingUnhandledExceptionFilter);
+ value_ = new char[test_size_];
+ },
+ testing::ExitedWithCode(kUnhandledExceptionExitCode), kOomRegex);
+}
+#endif // defined(OS_WIN)
+
+// OS X and Android have no 2Gb allocation limit.
+// See https://crbug.com/169327.
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+TEST_F(OutOfMemoryDeathTest, SecurityNew) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = operator new(insecure_test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityNewArray) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = new char[insecure_test_size_];
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityMalloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = malloc(insecure_test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityRealloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = realloc(nullptr, insecure_test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityCalloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = calloc(1024, insecure_test_size_ / 1024L);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityAlignedAlloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = base::AlignedAlloc(insecure_test_size_, 8);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+// POSIX does not define an aligned realloc function.
+#if defined(OS_WIN)
+TEST_F(OutOfMemoryDeathTest, SecurityAlignedRealloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = _aligned_realloc(NULL, insecure_test_size_, 8);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+#endif // defined(OS_WIN)
+#endif // !defined(OS_MACOSX) && !defined(OS_ANDROID)
+
+#if defined(OS_LINUX)
+
+TEST_F(OutOfMemoryDeathTest, Valloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = valloc(test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityValloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = valloc(insecure_test_size_);
+ }, kOomRegex);
+}
+
+#if PVALLOC_AVAILABLE == 1
+TEST_F(OutOfMemoryDeathTest, Pvalloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = pvalloc(test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityPvalloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = pvalloc(insecure_test_size_);
+ }, kOomRegex);
+}
+#endif // PVALLOC_AVAILABLE == 1
+
+TEST_F(OutOfMemoryDeathTest, Memalign) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = memalign(4, test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, ViaSharedLibraries) {
+ // This tests that the run-time symbol resolution is overriding malloc for
+ // shared libraries as well as for our code.
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = MallocWrapper(test_size_);
+ }, kOomRegex);
+}
+#endif // OS_LINUX
+
+// Android doesn't implement posix_memalign().
+#if defined(OS_POSIX) && !defined(OS_ANDROID)
+TEST_F(OutOfMemoryDeathTest, Posix_memalign) {
+ // Grab the return value of posix_memalign to silence a compiler warning
+ // about unused return values. We don't actually care about the return
+ // value, since we're asserting death.
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ EXPECT_EQ(ENOMEM, posix_memalign(&value_, 8, test_size_));
+ }, kOomRegex);
+}
+#endif // defined(OS_POSIX) && !defined(OS_ANDROID)
+
+#if defined(OS_MACOSX)
+
+// Purgeable zone tests
+
+TEST_F(OutOfMemoryDeathTest, MallocPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_malloc(zone, test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_realloc(zone, NULL, test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, CallocPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_calloc(zone, 1024, test_size_ / 1024L);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, VallocPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_valloc(zone, test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_memalign(zone, 8, test_size_);
+ }, kOomRegex);
+}
+
+// Since these allocation functions take a signed size, it's possible that
+// calling them just once won't be enough to exhaust memory. In the 32-bit
+// environment, it's likely that these allocation attempts will fail because
+// not enough contiguous address space is available. In the 64-bit environment,
+// it's likely that they'll fail because they would require a preposterous
+// amount of (virtual) memory.
+
+TEST_F(OutOfMemoryDeathTest, CFAllocatorSystemDefault) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ while ((value_ =
+ base::AllocateViaCFAllocatorSystemDefault(signed_test_size_))) {}
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ while ((value_ =
+ base::AllocateViaCFAllocatorMalloc(signed_test_size_))) {}
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, CFAllocatorMallocZone) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ while ((value_ =
+ base::AllocateViaCFAllocatorMallocZone(signed_test_size_))) {}
+ }, kOomRegex);
+}
+
+#if !defined(ARCH_CPU_64_BITS)
+
+// See process_util_unittest_mac.mm for an explanation of why this test isn't
+// run in the 64-bit environment.
+
+TEST_F(OutOfMemoryDeathTest, PsychoticallyBigObjCObject) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ while ((value_ = base::AllocatePsychoticallyBigObjCObject())) {}
+ }, kOomRegex);
+}
+
+#endif // !ARCH_CPU_64_BITS
+#endif // OS_MACOSX
+
+class OutOfMemoryHandledTest : public OutOfMemoryTest {
+ public:
+ static const size_t kSafeMallocSize = 512;
+ static const size_t kSafeCallocSize = 128;
+ static const size_t kSafeCallocItems = 4;
+
+ void SetUp() override {
+ OutOfMemoryTest::SetUp();
+
+ // We enable termination on OOM - just as Chrome does at early
+ // initialization - and test that UncheckedMalloc and UncheckedCalloc
+ // properly by-pass this in order to allow the caller to handle OOM.
+ base::EnableTerminationOnOutOfMemory();
+ }
+
+ void TearDown() override {
+#if defined(OS_MACOSX)
+ base::allocator::UninterceptMallocZonesForTesting();
+#endif
+ }
+};
+
+#if defined(OS_WIN)
+
+namespace {
+
+DWORD HandleOutOfMemoryException(EXCEPTION_POINTERS* exception_ptrs,
+ size_t expected_size) {
+ EXPECT_EQ(base::win::kOomExceptionCode,
+ exception_ptrs->ExceptionRecord->ExceptionCode);
+ EXPECT_LE(1U, exception_ptrs->ExceptionRecord->NumberParameters);
+ EXPECT_EQ(expected_size,
+ exception_ptrs->ExceptionRecord->ExceptionInformation[0]);
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+} // namespace
+
+TEST_F(OutOfMemoryTest, TerminateBecauseOutOfMemoryReportsAllocSize) {
+// On Windows, TerminateBecauseOutOfMemory reports the attempted allocation
+// size in the exception raised.
+#if defined(ARCH_CPU_64_BITS)
+ // Test with a size larger than 32 bits on 64 bit machines.
+ const size_t kAttemptedAllocationSize = 0xBADA55F00DULL;
+#else
+ const size_t kAttemptedAllocationSize = 0xBADA55;
+#endif
+
+ __try {
+ base::TerminateBecauseOutOfMemory(kAttemptedAllocationSize);
+ } __except (HandleOutOfMemoryException(GetExceptionInformation(),
+ kAttemptedAllocationSize)) {
+ }
+}
+#endif // OS_WIN
+
+// TODO(b.kelemen): make UncheckedMalloc and UncheckedCalloc work
+// on Windows as well.
+TEST_F(OutOfMemoryHandledTest, UncheckedMalloc) {
+ EXPECT_TRUE(base::UncheckedMalloc(kSafeMallocSize, &value_));
+ EXPECT_TRUE(value_ != nullptr);
+ free(value_);
+
+ EXPECT_FALSE(base::UncheckedMalloc(test_size_, &value_));
+ EXPECT_TRUE(value_ == nullptr);
+}
+
+TEST_F(OutOfMemoryHandledTest, UncheckedCalloc) {
+ EXPECT_TRUE(base::UncheckedCalloc(1, kSafeMallocSize, &value_));
+ EXPECT_TRUE(value_ != nullptr);
+ const char* bytes = static_cast<const char*>(value_);
+ for (size_t i = 0; i < kSafeMallocSize; ++i)
+ EXPECT_EQ(0, bytes[i]);
+ free(value_);
+
+ EXPECT_TRUE(
+ base::UncheckedCalloc(kSafeCallocItems, kSafeCallocSize, &value_));
+ EXPECT_TRUE(value_ != nullptr);
+ bytes = static_cast<const char*>(value_);
+ for (size_t i = 0; i < (kSafeCallocItems * kSafeCallocSize); ++i)
+ EXPECT_EQ(0, bytes[i]);
+ free(value_);
+
+ EXPECT_FALSE(base::UncheckedCalloc(1, test_size_, &value_));
+ EXPECT_TRUE(value_ == nullptr);
+}
+#endif // !defined(OS_OPENBSD) && BUILDFLAG(ENABLE_WIN_ALLOCATOR_SHIM_TESTS) &&
+ // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
diff --git a/base/process/process_linux.cc b/base/process/process_linux.cc
new file mode 100644
index 0000000000..faf39afd4b
--- /dev/null
+++ b/base/process/process_linux.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process.h"
+
+#include <errno.h>
+#include <sys/resource.h>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+
+// Not defined on AIX by default.
+#if defined(OS_AIX)
+#define RLIMIT_NICE 20
+#endif
+
+namespace base {
+
+namespace {
+
+const int kForegroundPriority = 0;
+
+#if defined(OS_CHROMEOS)
+// We are more aggressive in our lowering of background process priority
+// for chromeos as we have much more control over other processes running
+// on the machine.
+//
+// TODO(davemoore) Refactor this by adding support for higher levels to set
+// the foregrounding / backgrounding process so we don't have to keep
+// chrome / chromeos specific logic here.
+const int kBackgroundPriority = 19;
+const char kControlPath[] = "/sys/fs/cgroup/cpu%s/cgroup.procs";
+const char kForeground[] = "/chrome_renderers/foreground";
+const char kBackground[] = "/chrome_renderers/background";
+const char kProcPath[] = "/proc/%d/cgroup";
+
+struct CGroups {
+ // Check for cgroups files. ChromeOS supports these by default. It creates
+ // a cgroup mount in /sys/fs/cgroup and then configures two cpu task groups,
+ // one contains at most a single foreground renderer and the other contains
+ // all background renderers. This allows us to limit the impact of background
+ // renderers on foreground ones to a greater level than simple renicing.
+ bool enabled;
+ base::FilePath foreground_file;
+ base::FilePath background_file;
+
+ CGroups() {
+ foreground_file =
+ base::FilePath(base::StringPrintf(kControlPath, kForeground));
+ background_file =
+ base::FilePath(base::StringPrintf(kControlPath, kBackground));
+ base::FileSystemType foreground_type;
+ base::FileSystemType background_type;
+ enabled =
+ base::GetFileSystemType(foreground_file, &foreground_type) &&
+ base::GetFileSystemType(background_file, &background_type) &&
+ foreground_type == FILE_SYSTEM_CGROUP &&
+ background_type == FILE_SYSTEM_CGROUP;
+ }
+
+ static CGroups& Get() {
+ static auto& groups = *new CGroups;
+ return groups;
+ }
+};
+#else
+const int kBackgroundPriority = 5;
+#endif // defined(OS_CHROMEOS)
+
+bool CanReraisePriority() {
+ // We won't be able to raise the priority if we don't have the right rlimit.
+ // The limit may be adjusted in /etc/security/limits.conf for PAM systems.
+ struct rlimit rlim;
+ return (getrlimit(RLIMIT_NICE, &rlim) == 0) &&
+ (20 - kForegroundPriority) <= static_cast<int>(rlim.rlim_cur);
+}
+
+} // namespace
+
+// static
+bool Process::CanBackgroundProcesses() {
+#if defined(OS_CHROMEOS)
+ if (CGroups::Get().enabled)
+ return true;
+#endif // defined(OS_CHROMEOS)
+
+ static const bool can_reraise_priority = CanReraisePriority();
+ return can_reraise_priority;
+}
+
+bool Process::IsProcessBackgrounded() const {
+ DCHECK(IsValid());
+
+#if defined(OS_CHROMEOS)
+ if (CGroups::Get().enabled) {
+ // Used to allow reading the process priority from proc on thread launch.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ std::string proc;
+ if (base::ReadFileToString(
+ base::FilePath(StringPrintf(kProcPath, process_)), &proc)) {
+ return IsProcessBackgroundedCGroup(proc);
+ }
+ return false;
+ }
+#endif // defined(OS_CHROMEOS)
+
+ return GetPriority() == kBackgroundPriority;
+}
+
+bool Process::SetProcessBackgrounded(bool background) {
+ DCHECK(IsValid());
+
+#if defined(OS_CHROMEOS)
+ if (CGroups::Get().enabled) {
+ std::string pid = IntToString(process_);
+ const base::FilePath file = background ? CGroups::Get().background_file
+ : CGroups::Get().foreground_file;
+ return base::WriteFile(file, pid.c_str(), pid.size()) > 0;
+ }
+#endif // defined(OS_CHROMEOS)
+
+ if (!CanBackgroundProcesses())
+ return false;
+
+ int priority = background ? kBackgroundPriority : kForegroundPriority;
+ int result = setpriority(PRIO_PROCESS, process_, priority);
+ DPCHECK(result == 0);
+ return result == 0;
+}
+
+#if defined(OS_CHROMEOS)
+bool IsProcessBackgroundedCGroup(const StringPiece& cgroup_contents) {
+ // The process can be part of multiple control groups, and for each cgroup
+ // hierarchy there's an entry in the file. We look for a control group
+ // named "/chrome_renderers/background" to determine if the process is
+ // backgrounded. crbug.com/548818.
+ std::vector<StringPiece> lines = SplitStringPiece(
+ cgroup_contents, "\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ for (const auto& line : lines) {
+ std::vector<StringPiece> fields =
+ SplitStringPiece(line, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+ if (fields.size() != 3U) {
+ NOTREACHED();
+ continue;
+ }
+ if (fields[2] == kBackground)
+ return true;
+ }
+
+ return false;
+}
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_CHROMEOS)
+// Reads /proc/<pid>/status and returns the PID in its PID namespace.
+// If the process is not in a PID namespace or /proc/<pid>/status does not
+// report NSpid, kNullProcessId is returned.
+ProcessId Process::GetPidInNamespace() const {
+ std::string status;
+ {
+ // Synchronously reading files in /proc does not hit the disk.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+ FilePath status_file =
+ FilePath("/proc").Append(IntToString(process_)).Append("status");
+ if (!ReadFileToString(status_file, &status)) {
+ return kNullProcessId;
+ }
+ }
+
+ StringPairs pairs;
+ SplitStringIntoKeyValuePairs(status, ':', '\n', &pairs);
+ for (const auto& pair : pairs) {
+ const std::string& key = pair.first;
+ const std::string& value_str = pair.second;
+ if (key == "NSpid") {
+ std::vector<StringPiece> split_value_str = SplitStringPiece(
+ value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (split_value_str.size() <= 1) {
+ return kNullProcessId;
+ }
+ int value;
+ // The last value in the list is the PID in the namespace.
+ if (!StringToInt(split_value_str.back(), &value)) {
+ NOTREACHED();
+ return kNullProcessId;
+ }
+ return value;
+ }
+ }
+ return kNullProcessId;
+}
+#endif // defined(OS_CHROMEOS)
+
+} // namespace base
diff --git a/base/process/process_metrics_unittest.cc b/base/process/process_metrics_unittest.cc
index ad741f8f13..b33b599618 100644
--- a/base/process/process_metrics_unittest.cc
+++ b/base/process/process_metrics_unittest.cc
@@ -571,7 +571,7 @@ MULTIPROCESS_TEST_MAIN(ChildMain) {
// ARC note: don't compile as SpawnMultiProcessTestChild brings in a lot of
// extra dependency.
-#if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
+#if !defined(OS_ANDROID) && !defined(__ANDROID_HOST__)
TEST(ProcessMetricsTest, GetChildOpenFdCount) {
ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
@@ -600,7 +600,7 @@ TEST(ProcessMetricsTest, GetChildOpenFdCount) {
EXPECT_EQ(0, open_fds);
ASSERT_TRUE(child.Terminate(0, true));
}
-#endif // !defined(__ANDROID__)
+#endif // !defined(OS_ANDROID) && !defined(__ANDROID_HOST__)
#endif // defined(OS_LINUX)
diff --git a/base/process/process_unittest.cc b/base/process/process_unittest.cc
new file mode 100644
index 0000000000..219944df7a
--- /dev/null
+++ b/base/process/process_unittest.cc
@@ -0,0 +1,330 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process.h"
+
+#include <utility>
+
+#include "base/at_exit.h"
+#include "base/process/kill.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_local.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace {
+
+#if defined(OS_WIN)
+const int kExpectedStillRunningExitCode = 0x102;
+#else
+const int kExpectedStillRunningExitCode = 0;
+#endif
+
+#if defined(OS_MACOSX)
+// Fake port provider that returns the calling process's
+// task port, ignoring its argument.
+class FakePortProvider : public base::PortProvider {
+ mach_port_t TaskForPid(base::ProcessHandle process) const override {
+ return mach_task_self();
+ }
+};
+#endif
+
+} // namespace
+
+namespace base {
+
+class ProcessTest : public MultiProcessTest {
+};
+
+TEST_F(ProcessTest, Create) {
+ Process process(SpawnChild("SimpleChildProcess"));
+ ASSERT_TRUE(process.IsValid());
+ ASSERT_FALSE(process.is_current());
+ EXPECT_NE(process.Pid(), kNullProcessId);
+ process.Close();
+ ASSERT_FALSE(process.IsValid());
+}
+
+TEST_F(ProcessTest, CreateCurrent) {
+ Process process = Process::Current();
+ ASSERT_TRUE(process.IsValid());
+ ASSERT_TRUE(process.is_current());
+ EXPECT_NE(process.Pid(), kNullProcessId);
+ process.Close();
+ ASSERT_FALSE(process.IsValid());
+}
+
+TEST_F(ProcessTest, Move) {
+ Process process1(SpawnChild("SimpleChildProcess"));
+ EXPECT_TRUE(process1.IsValid());
+
+ Process process2;
+ EXPECT_FALSE(process2.IsValid());
+
+ process2 = std::move(process1);
+ EXPECT_TRUE(process2.IsValid());
+ EXPECT_FALSE(process1.IsValid());
+ EXPECT_FALSE(process2.is_current());
+
+ Process process3 = Process::Current();
+ process2 = std::move(process3);
+ EXPECT_TRUE(process2.is_current());
+ EXPECT_TRUE(process2.IsValid());
+ EXPECT_FALSE(process3.IsValid());
+}
+
+TEST_F(ProcessTest, Duplicate) {
+ Process process1(SpawnChild("SimpleChildProcess"));
+ ASSERT_TRUE(process1.IsValid());
+
+ Process process2 = process1.Duplicate();
+ ASSERT_TRUE(process1.IsValid());
+ ASSERT_TRUE(process2.IsValid());
+ EXPECT_EQ(process1.Pid(), process2.Pid());
+ EXPECT_FALSE(process1.is_current());
+ EXPECT_FALSE(process2.is_current());
+
+ process1.Close();
+ ASSERT_TRUE(process2.IsValid());
+}
+
+TEST_F(ProcessTest, DuplicateCurrent) {
+ Process process1 = Process::Current();
+ ASSERT_TRUE(process1.IsValid());
+
+ Process process2 = process1.Duplicate();
+ ASSERT_TRUE(process1.IsValid());
+ ASSERT_TRUE(process2.IsValid());
+ EXPECT_EQ(process1.Pid(), process2.Pid());
+ EXPECT_TRUE(process1.is_current());
+ EXPECT_TRUE(process2.is_current());
+
+ process1.Close();
+ ASSERT_TRUE(process2.IsValid());
+}
+
+TEST_F(ProcessTest, DeprecatedGetProcessFromHandle) {
+ Process process1(SpawnChild("SimpleChildProcess"));
+ ASSERT_TRUE(process1.IsValid());
+
+ Process process2 = Process::DeprecatedGetProcessFromHandle(process1.Handle());
+ ASSERT_TRUE(process1.IsValid());
+ ASSERT_TRUE(process2.IsValid());
+ EXPECT_EQ(process1.Pid(), process2.Pid());
+ EXPECT_FALSE(process1.is_current());
+ EXPECT_FALSE(process2.is_current());
+
+ process1.Close();
+ ASSERT_TRUE(process2.IsValid());
+}
+
+MULTIPROCESS_TEST_MAIN(SleepyChildProcess) {
+ PlatformThread::Sleep(TestTimeouts::action_max_timeout());
+ return 0;
+}
+
+TEST_F(ProcessTest, Terminate) {
+ Process process(SpawnChild("SleepyChildProcess"));
+ ASSERT_TRUE(process.IsValid());
+
+ const int kDummyExitCode = 42;
+ int exit_code = kDummyExitCode;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ exit_code = kDummyExitCode;
+ int kExpectedExitCode = 250;
+ process.Terminate(kExpectedExitCode, false);
+ process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code);
+
+ EXPECT_NE(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+#if !defined(OS_POSIX) && !defined(OS_FUCHSIA)
+ // The POSIX & Fuchsia implementations actually ignore the exit_code.
+ EXPECT_EQ(kExpectedExitCode, exit_code);
+#endif
+}
+
+void AtExitHandler(void*) {
+ // At-exit handler should not be called at
+ // Process::TerminateCurrentProcessImmediately.
+ DCHECK(false);
+}
+
+class ThreadLocalObject {
+ ~ThreadLocalObject() {
+ // Thread-local storage should not be destructed at
+ // Process::TerminateCurrentProcessImmediately.
+ DCHECK(false);
+ }
+};
+
+MULTIPROCESS_TEST_MAIN(TerminateCurrentProcessImmediatelyWithCode0) {
+ base::ThreadLocalPointer<ThreadLocalObject> object;
+ base::AtExitManager::RegisterCallback(&AtExitHandler, nullptr);
+ Process::TerminateCurrentProcessImmediately(0);
+}
+
+TEST_F(ProcessTest, TerminateCurrentProcessImmediatelyWithZeroExitCode) {
+ Process process(SpawnChild("TerminateCurrentProcessImmediatelyWithCode0"));
+ ASSERT_TRUE(process.IsValid());
+ int exit_code = 42;
+ ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(TerminateCurrentProcessImmediatelyWithCode250) {
+ Process::TerminateCurrentProcessImmediately(250);
+}
+
+TEST_F(ProcessTest, TerminateCurrentProcessImmediatelyWithNonZeroExitCode) {
+ Process process(SpawnChild("TerminateCurrentProcessImmediatelyWithCode250"));
+ ASSERT_TRUE(process.IsValid());
+ int exit_code = 42;
+ ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+ EXPECT_EQ(250, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(FastSleepyChildProcess) {
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 10);
+ return 0;
+}
+
+TEST_F(ProcessTest, WaitForExit) {
+ Process process(SpawnChild("FastSleepyChildProcess"));
+ ASSERT_TRUE(process.IsValid());
+
+ const int kDummyExitCode = 42;
+ int exit_code = kDummyExitCode;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+TEST_F(ProcessTest, WaitForExitWithTimeout) {
+ Process process(SpawnChild("SleepyChildProcess"));
+ ASSERT_TRUE(process.IsValid());
+
+ const int kDummyExitCode = 42;
+ int exit_code = kDummyExitCode;
+ TimeDelta timeout = TestTimeouts::tiny_timeout();
+ EXPECT_FALSE(process.WaitForExitWithTimeout(timeout, &exit_code));
+ EXPECT_EQ(kDummyExitCode, exit_code);
+
+ process.Terminate(kDummyExitCode, false);
+}
+
+// Ensure that the priority of a process is restored correctly after
+// backgrounding and restoring.
+// Note: a platform may not be willing or able to lower the priority of
+// a process. The calls to SetProcessBackground should be noops then.
+TEST_F(ProcessTest, SetProcessBackgrounded) {
+ if (!Process::CanBackgroundProcesses())
+ return;
+ Process process(SpawnChild("SimpleChildProcess"));
+ int old_priority = process.GetPriority();
+#if defined(OS_WIN)
+ EXPECT_TRUE(process.SetProcessBackgrounded(true));
+ EXPECT_TRUE(process.IsProcessBackgrounded());
+ EXPECT_TRUE(process.SetProcessBackgrounded(false));
+ EXPECT_FALSE(process.IsProcessBackgrounded());
+#elif defined(OS_MACOSX)
+ // On the Mac, backgrounding a process requires a port to that process.
+ // In the browser it's available through the MachBroker class, which is not
+ // part of base. Additionally, there is an indefinite amount of time between
+ // spawning a process and receiving its port. Because this test just checks
+ // the ability to background/foreground a process, we can use the current
+ // process's port instead.
+ FakePortProvider provider;
+ EXPECT_TRUE(process.SetProcessBackgrounded(&provider, true));
+ EXPECT_TRUE(process.IsProcessBackgrounded(&provider));
+ EXPECT_TRUE(process.SetProcessBackgrounded(&provider, false));
+ EXPECT_FALSE(process.IsProcessBackgrounded(&provider));
+
+#else
+ process.SetProcessBackgrounded(true);
+ process.SetProcessBackgrounded(false);
+#endif
+ int new_priority = process.GetPriority();
+ EXPECT_EQ(old_priority, new_priority);
+}
+
+// Same as SetProcessBackgrounded but to this very process. It uses
+// a different code path at least for Windows.
+TEST_F(ProcessTest, SetProcessBackgroundedSelf) {
+ if (!Process::CanBackgroundProcesses())
+ return;
+ Process process = Process::Current();
+ int old_priority = process.GetPriority();
+#if defined(OS_WIN)
+ EXPECT_TRUE(process.SetProcessBackgrounded(true));
+ EXPECT_TRUE(process.IsProcessBackgrounded());
+ EXPECT_TRUE(process.SetProcessBackgrounded(false));
+ EXPECT_FALSE(process.IsProcessBackgrounded());
+#elif defined(OS_MACOSX)
+ FakePortProvider provider;
+ EXPECT_TRUE(process.SetProcessBackgrounded(&provider, true));
+ EXPECT_TRUE(process.IsProcessBackgrounded(&provider));
+ EXPECT_TRUE(process.SetProcessBackgrounded(&provider, false));
+ EXPECT_FALSE(process.IsProcessBackgrounded(&provider));
+#else
+ process.SetProcessBackgrounded(true);
+ process.SetProcessBackgrounded(false);
+#endif
+ int new_priority = process.GetPriority();
+ EXPECT_EQ(old_priority, new_priority);
+}
+
+// Consumers can use WaitForExitWithTimeout(base::TimeDelta(), nullptr) to check
+// whether the process is still running. This may not be safe because of the
+// potential reusing of the process id. So we won't export Process::IsRunning()
+// on all platforms. But for the controllable scenario in the test cases, the
+// behavior should be guaranteed.
+TEST_F(ProcessTest, CurrentProcessIsRunning) {
+ EXPECT_FALSE(Process::Current().WaitForExitWithTimeout(
+ base::TimeDelta(), nullptr));
+}
+
+#if defined(OS_MACOSX)
+// On Mac OSX, we can detect whether a non-child process is running.
+TEST_F(ProcessTest, PredefinedProcessIsRunning) {
+ // Process 1 is the /sbin/launchd, it should be always running.
+ EXPECT_FALSE(Process::Open(1).WaitForExitWithTimeout(
+ base::TimeDelta(), nullptr));
+}
+#endif
+
+TEST_F(ProcessTest, ChildProcessIsRunning) {
+ Process process(SpawnChild("SleepyChildProcess"));
+ EXPECT_FALSE(process.WaitForExitWithTimeout(
+ base::TimeDelta(), nullptr));
+ process.Terminate(0, true);
+ EXPECT_TRUE(process.WaitForExitWithTimeout(
+ base::TimeDelta(), nullptr));
+}
+
+#if defined(OS_CHROMEOS)
+
+// Tests that the function IsProcessBackgroundedCGroup() can parse the contents
+// of the /proc/<pid>/cgroup file successfully.
+TEST_F(ProcessTest, TestIsProcessBackgroundedCGroup) {
+ const char kNotBackgrounded[] = "5:cpuacct,cpu,cpuset:/daemons\n";
+ const char kBackgrounded[] =
+ "2:freezer:/chrome_renderers/to_be_frozen\n"
+ "1:cpu:/chrome_renderers/background\n";
+
+ EXPECT_FALSE(IsProcessBackgroundedCGroup(kNotBackgrounded));
+ EXPECT_TRUE(IsProcessBackgroundedCGroup(kBackgrounded));
+}
+
+#endif // defined(OS_CHROMEOS)
+
+} // namespace base
diff --git a/base/process/process_util_unittest.cc b/base/process/process_util_unittest.cc
new file mode 100644
index 0000000000..4e788b78a8
--- /dev/null
+++ b/base/process/process_util_unittest.cc
@@ -0,0 +1,1357 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define _CRT_SECURE_NO_WARNINGS
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/command_line.h"
+#include "base/debug/alias.h"
+#include "base/debug/stack_trace.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/process/memory.h"
+#include "base/process/process.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+#if defined(OS_LINUX)
+#include <malloc.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#endif
+#if defined(OS_POSIX)
+#include <sys/resource.h>
+#endif
+#if defined(OS_POSIX)
+#include <dlfcn.h>
+#include <errno.h>
+#include <sched.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#endif
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+#if defined(OS_MACOSX)
+#include <mach/vm_param.h>
+#include <malloc/malloc.h>
+#endif
+#if defined(OS_ANDROID)
+#include "third_party/lss/linux_syscall_support.h"
+#endif
+#if defined(OS_FUCHSIA)
+#include <lib/fdio/limits.h>
+#include <zircon/process.h>
+#include <zircon/processargs.h>
+#include <zircon/syscalls.h>
+#include "base/base_paths_fuchsia.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/fuchsia/file_utils.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#endif
+
+namespace base {
+
+namespace {
+
+const char kSignalFileSlow[] = "SlowChildProcess.die";
+const char kSignalFileKill[] = "KilledChildProcess.die";
+const char kTestHelper[] = "test_child_process";
+
+#if defined(OS_POSIX)
+const char kSignalFileTerm[] = "TerminatedChildProcess.die";
+#endif
+
+#if defined(OS_FUCHSIA)
+const char kSignalFileClone[] = "ClonedTmpDir.die";
+const char kDataDirHasStaged[] = "DataDirHasStaged.die";
+const char kFooDirHasStaged[] = "FooDirHasStaged.die";
+const char kFooDirDoesNotHaveStaged[] = "FooDirDoesNotHaveStaged.die";
+#endif
+
+#if defined(OS_WIN)
+const int kExpectedStillRunningExitCode = 0x102;
+const int kExpectedKilledExitCode = 1;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+const int kExpectedStillRunningExitCode = 0;
+#endif
+
+// Sleeps until file filename is created.
+void WaitToDie(const char* filename) {
+ FILE* fp;
+ do {
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
+ fp = fopen(filename, "r");
+ } while (!fp);
+ fclose(fp);
+}
+
+// Signals children they should die now.
+void SignalChildren(const char* filename) {
+ FILE* fp = fopen(filename, "w");
+ fclose(fp);
+}
+
+// Using a pipe to the child to wait for an event was considered, but
+// there were cases in the past where pipes caused problems (other
+// libraries closing the fds, child deadlocking). This is a simple
+// case, so it's not worth the risk. Using wait loops is discouraged
+// in most instances.
+TerminationStatus WaitForChildTermination(ProcessHandle handle,
+ int* exit_code) {
+ // Now we wait until the result is something other than STILL_RUNNING.
+ TerminationStatus status = TERMINATION_STATUS_STILL_RUNNING;
+ const TimeDelta kInterval = TimeDelta::FromMilliseconds(20);
+ TimeDelta waited;
+ do {
+ status = GetTerminationStatus(handle, exit_code);
+ PlatformThread::Sleep(kInterval);
+ waited += kInterval;
+ } while (status == TERMINATION_STATUS_STILL_RUNNING &&
+ waited < TestTimeouts::action_max_timeout());
+
+ return status;
+}
+
+} // namespace
+
+const int kSuccess = 0;
+
+class ProcessUtilTest : public MultiProcessTest {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(PathService::Get(DIR_ASSETS, &test_helper_path_));
+ test_helper_path_ = test_helper_path_.AppendASCII(kTestHelper);
+ }
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // Spawn a child process that counts how many file descriptors are open.
+ int CountOpenFDsInChild();
+#endif
+ // Converts the filename to a platform specific filepath.
+ // On Android files can not be created in arbitrary directories.
+ static std::string GetSignalFilePath(const char* filename);
+
+ protected:
+ base::FilePath test_helper_path_;
+};
+
+std::string ProcessUtilTest::GetSignalFilePath(const char* filename) {
+#if defined(OS_ANDROID) || defined(OS_FUCHSIA)
+ FilePath tmp_dir;
+ PathService::Get(DIR_TEMP, &tmp_dir);
+ tmp_dir = tmp_dir.Append(filename);
+ return tmp_dir.value();
+#else
+ return filename;
+#endif
+}
+
+MULTIPROCESS_TEST_MAIN(SimpleChildProcess) {
+ return kSuccess;
+}
+
+// TODO(viettrungluu): This should be in a "MultiProcessTestTest".
+TEST_F(ProcessUtilTest, SpawnChild) {
+ Process process = SpawnChild("SimpleChildProcess");
+ ASSERT_TRUE(process.IsValid());
+ int exit_code;
+ EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+}
+
+MULTIPROCESS_TEST_MAIN(SlowChildProcess) {
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileSlow).c_str());
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, KillSlowChild) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileSlow);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("SlowChildProcess");
+ ASSERT_TRUE(process.IsValid());
+ SignalChildren(signal_file.c_str());
+ int exit_code;
+ EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+ remove(signal_file.c_str());
+}
+
+// Times out on Linux and Win, flakes on other platforms, http://crbug.com/95058
+TEST_F(ProcessUtilTest, DISABLED_GetTerminationStatusExit) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileSlow);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("SlowChildProcess");
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ SignalChildren(signal_file.c_str());
+ exit_code = 42;
+ TerminationStatus status =
+ WaitForChildTermination(process.Handle(), &exit_code);
+ EXPECT_EQ(TERMINATION_STATUS_NORMAL_TERMINATION, status);
+ EXPECT_EQ(kSuccess, exit_code);
+ remove(signal_file.c_str());
+}
+
+#if defined(OS_FUCHSIA)
+
+MULTIPROCESS_TEST_MAIN(CheckDataDirHasStaged) {
+ if (!PathExists(base::FilePath("/data/staged"))) {
+ return 1;
+ }
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kDataDirHasStaged).c_str());
+ return kSuccess;
+}
+
+// Test transferred paths override cloned paths.
+TEST_F(ProcessUtilTest, HandleTransfersOverrideClones) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kDataDirHasStaged);
+ remove(signal_file.c_str());
+
+ // Create a tempdir with "staged" as its contents.
+ ScopedTempDir tmpdir_with_staged;
+ ASSERT_TRUE(tmpdir_with_staged.CreateUniqueTempDir());
+ {
+ base::FilePath staged_file_path =
+ tmpdir_with_staged.GetPath().Append("staged");
+ base::File staged_file(staged_file_path,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(staged_file.created());
+ staged_file.Close();
+ }
+
+ base::LaunchOptions options;
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ // Attach the tempdir to "data", but also try to duplicate the existing "data"
+ // directory.
+ options.paths_to_clone.push_back(base::FilePath("/data"));
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_transfer.push_back(
+ {FilePath("/data"),
+ fuchsia::GetHandleFromFile(
+ base::File(base::FilePath(tmpdir_with_staged.GetPath()),
+ base::File::FLAG_OPEN | base::File::FLAG_READ))
+ .release()});
+
+ // Verify from that "/data/staged" exists from the child process' perspective.
+ Process process(SpawnChildWithOptions("CheckDataDirHasStaged", options));
+ ASSERT_TRUE(process.IsValid());
+ SignalChildren(signal_file.c_str());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(CheckMountedDir) {
+ if (!PathExists(base::FilePath("/foo/staged"))) {
+ return 1;
+ }
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kFooDirHasStaged).c_str());
+ return kSuccess;
+}
+
+// Test that we can install an opaque handle in the child process' namespace.
+TEST_F(ProcessUtilTest, TransferHandleToPath) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kFooDirHasStaged);
+ remove(signal_file.c_str());
+
+ // Create a tempdir with "staged" as its contents.
+ ScopedTempDir new_tmpdir;
+ ASSERT_TRUE(new_tmpdir.CreateUniqueTempDir());
+ base::FilePath staged_file_path = new_tmpdir.GetPath().Append("staged");
+ base::File staged_file(staged_file_path,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(staged_file.created());
+ staged_file.Close();
+
+ // Mount the tempdir to "/foo".
+ zx::handle tmp_handle = fuchsia::GetHandleFromFile(
+ base::File(base::FilePath(new_tmpdir.GetPath()),
+ base::File::FLAG_OPEN | base::File::FLAG_READ));
+ ASSERT_TRUE(tmp_handle.is_valid());
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_transfer.push_back(
+ {base::FilePath("/foo"), tmp_handle.release()});
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ // Verify from that "/foo/staged" exists from the child process' perspective.
+ Process process(SpawnChildWithOptions("CheckMountedDir", options));
+ ASSERT_TRUE(process.IsValid());
+ SignalChildren(signal_file.c_str());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(CheckTmpFileExists) {
+ // Look through the filesystem to ensure that no other directories
+ // besides "tmp" are in the namespace.
+ base::FileEnumerator enumerator(
+ base::FilePath("/"), false,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+ base::FilePath next_path;
+ while (!(next_path = enumerator.Next()).empty()) {
+ if (next_path != base::FilePath("/tmp")) {
+ LOG(ERROR) << "Clone policy violation: found non-tmp directory "
+ << next_path.MaybeAsASCII();
+ return 1;
+ }
+ }
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileClone).c_str());
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, CloneTmp) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileClone);
+ remove(signal_file.c_str());
+
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
+ ASSERT_TRUE(process.IsValid());
+
+ SignalChildren(signal_file.c_str());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(CheckMountedDirDoesNotExist) {
+ if (PathExists(base::FilePath("/foo"))) {
+ return 1;
+ }
+ WaitToDie(
+ ProcessUtilTest::GetSignalFilePath(kFooDirDoesNotHaveStaged).c_str());
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, TransferInvalidHandleFails) {
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_transfer.push_back(
+ {base::FilePath("/foo"), ZX_HANDLE_INVALID});
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ // Verify that the process is never constructed.
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kFooDirDoesNotHaveStaged);
+ remove(signal_file.c_str());
+ Process process(
+ SpawnChildWithOptions("CheckMountedDirDoesNotExist", options));
+ ASSERT_FALSE(process.IsValid());
+}
+
+TEST_F(ProcessUtilTest, CloneInvalidDirFails) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileClone);
+ remove(signal_file.c_str());
+
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_clone.push_back(base::FilePath("/definitely_not_a_dir"));
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
+ ASSERT_FALSE(process.IsValid());
+}
+
+// Test that we can clone other directories. CheckTmpFileExists will return an
+// error code if it detects a directory other than "/tmp", so we can use that as
+// a signal that it successfully detected another entry in the root namespace.
+TEST_F(ProcessUtilTest, CloneAlternateDir) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileClone);
+ remove(signal_file.c_str());
+
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_clone.push_back(base::FilePath("/data"));
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
+ ASSERT_TRUE(process.IsValid());
+
+ SignalChildren(signal_file.c_str());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(1, exit_code);
+}
+
+TEST_F(ProcessUtilTest, HandlesToTransferClosedOnSpawnFailure) {
+ zx::handle handles[2];
+ zx_status_t result = zx_channel_create(0, handles[0].reset_and_get_address(),
+ handles[1].reset_and_get_address());
+ ZX_CHECK(ZX_OK == result, result) << "zx_channel_create";
+
+ LaunchOptions options;
+ options.handles_to_transfer.push_back({0, handles[0].get()});
+
+ // Launch a non-existent binary, causing fdio_spawn() to fail.
+ CommandLine command_line(FilePath(
+ FILE_PATH_LITERAL("💩magical_filename_that_will_never_exist_ever")));
+ Process process(LaunchProcess(command_line, options));
+ ASSERT_FALSE(process.IsValid());
+
+ // If LaunchProcess did its job then handles[0] is no longer valid, and
+ // handles[1] should observe a channel-closed signal.
+ EXPECT_EQ(
+ zx_object_wait_one(handles[1].get(), ZX_CHANNEL_PEER_CLOSED, 0, nullptr),
+ ZX_OK);
+ EXPECT_EQ(ZX_ERR_BAD_HANDLE, zx_handle_close(handles[0].get()));
+ ignore_result(handles[0].release());
+}
+
+TEST_F(ProcessUtilTest, HandlesToTransferClosedOnBadPathToMapFailure) {
+ zx::handle handles[2];
+ zx_status_t result = zx_channel_create(0, handles[0].reset_and_get_address(),
+ handles[1].reset_and_get_address());
+ ZX_CHECK(ZX_OK == result, result) << "zx_channel_create";
+
+ LaunchOptions options;
+ options.handles_to_transfer.push_back({0, handles[0].get()});
+ options.spawn_flags = options.spawn_flags & ~FDIO_SPAWN_CLONE_NAMESPACE;
+ options.paths_to_clone.emplace_back(
+ "💩magical_path_that_will_never_exist_ever");
+
+ // LaunchProces should fail to open() the path_to_map, and fail before
+ // fdio_spawn().
+ Process process(LaunchProcess(CommandLine(FilePath()), options));
+ ASSERT_FALSE(process.IsValid());
+
+ // If LaunchProcess did its job then handles[0] is no longer valid, and
+ // handles[1] should observe a channel-closed signal.
+ EXPECT_EQ(
+ zx_object_wait_one(handles[1].get(), ZX_CHANNEL_PEER_CLOSED, 0, nullptr),
+ ZX_OK);
+ EXPECT_EQ(ZX_ERR_BAD_HANDLE, zx_handle_close(handles[0].get()));
+ ignore_result(handles[0].release());
+}
+#endif // defined(OS_FUCHSIA)
+
+// On Android SpawnProcess() doesn't use LaunchProcess() and doesn't support
+// LaunchOptions::current_directory.
+#if !defined(OS_ANDROID)
+MULTIPROCESS_TEST_MAIN(CheckCwdProcess) {
+ FilePath expected;
+ CHECK(GetTempDir(&expected));
+ expected = MakeAbsoluteFilePath(expected);
+ CHECK(!expected.empty());
+
+ FilePath actual;
+ CHECK(GetCurrentDirectory(&actual));
+ actual = MakeAbsoluteFilePath(actual);
+ CHECK(!actual.empty());
+
+ CHECK(expected == actual) << "Expected: " << expected.value()
+ << " Actual: " << actual.value();
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, CurrentDirectory) {
+ // TODO(rickyz): Add support for passing arguments to multiprocess children,
+ // then create a special directory for this test.
+ FilePath tmp_dir;
+ ASSERT_TRUE(GetTempDir(&tmp_dir));
+
+ LaunchOptions options;
+ options.current_directory = tmp_dir;
+
+ Process process(SpawnChildWithOptions("CheckCwdProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_WIN)
+// TODO(cpu): figure out how to test this in other platforms.
+TEST_F(ProcessUtilTest, GetProcId) {
+ ProcessId id1 = GetProcId(GetCurrentProcess());
+ EXPECT_NE(0ul, id1);
+ Process process = SpawnChild("SimpleChildProcess");
+ ASSERT_TRUE(process.IsValid());
+ ProcessId id2 = process.Pid();
+ EXPECT_NE(0ul, id2);
+ EXPECT_NE(id1, id2);
+}
+#endif // defined(OS_WIN)
+
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+// This test is disabled on Mac, since it's flaky due to ReportCrash
+// taking a variable amount of time to parse and load the debug and
+// symbol data for this unit test's executable before firing the
+// signal handler.
+//
+// TODO(gspencer): turn this test process into a very small program
+// with no symbols (instead of using the multiprocess testing
+// framework) to reduce the ReportCrash overhead.
+//
+// It is disabled on Android as MultiprocessTests are started as services that
+// the framework restarts on crashes.
+const char kSignalFileCrash[] = "CrashingChildProcess.die";
+
+MULTIPROCESS_TEST_MAIN(CrashingChildProcess) {
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileCrash).c_str());
+#if defined(OS_POSIX)
+ // Have to disable to signal handler for segv so we can get a crash
+ // instead of an abnormal termination through the crash dump handler.
+ ::signal(SIGSEGV, SIG_DFL);
+#endif
+ // Make this process have a segmentation fault.
+ volatile int* oops = nullptr;
+ *oops = 0xDEAD;
+ return 1;
+}
+
+// This test intentionally crashes, so we don't need to run it under
+// AddressSanitizer.
+#if defined(ADDRESS_SANITIZER) || defined(OS_FUCHSIA)
+// TODO(crbug.com/753490): Access to the process termination reason is not
+// implemented in Fuchsia.
+#define MAYBE_GetTerminationStatusCrash DISABLED_GetTerminationStatusCrash
+#else
+#define MAYBE_GetTerminationStatusCrash GetTerminationStatusCrash
+#endif
+TEST_F(ProcessUtilTest, MAYBE_GetTerminationStatusCrash) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileCrash);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("CrashingChildProcess");
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ SignalChildren(signal_file.c_str());
+ exit_code = 42;
+ TerminationStatus status =
+ WaitForChildTermination(process.Handle(), &exit_code);
+ EXPECT_EQ(TERMINATION_STATUS_PROCESS_CRASHED, status);
+
+#if defined(OS_WIN)
+ EXPECT_EQ(static_cast<int>(0xc0000005), exit_code);
+#elif defined(OS_POSIX)
+ int signaled = WIFSIGNALED(exit_code);
+ EXPECT_NE(0, signaled);
+ int signal = WTERMSIG(exit_code);
+ EXPECT_EQ(SIGSEGV, signal);
+#endif
+
+ // Reset signal handlers back to "normal".
+ debug::EnableInProcessStackDumping();
+ remove(signal_file.c_str());
+}
+#endif // !defined(OS_MACOSX) && !defined(OS_ANDROID)
+
+MULTIPROCESS_TEST_MAIN(KilledChildProcess) {
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileKill).c_str());
+#if defined(OS_WIN)
+ // Kill ourselves.
+ HANDLE handle = ::OpenProcess(PROCESS_ALL_ACCESS, 0, ::GetCurrentProcessId());
+ ::TerminateProcess(handle, kExpectedKilledExitCode);
+#elif defined(OS_POSIX)
+ // Send a SIGKILL to this process, just like the OOM killer would.
+ ::kill(getpid(), SIGKILL);
+#elif defined(OS_FUCHSIA)
+ zx_task_kill(zx_process_self());
+#endif
+ return 1;
+}
+
+#if defined(OS_POSIX)
+MULTIPROCESS_TEST_MAIN(TerminatedChildProcess) {
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileTerm).c_str());
+ // Send a SIGTERM to this process.
+ ::kill(getpid(), SIGTERM);
+ return 1;
+}
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/753490): Access to the process termination reason is not
+// implemented in Fuchsia.
+#define MAYBE_GetTerminationStatusSigKill DISABLED_GetTerminationStatusSigKill
+#else
+#define MAYBE_GetTerminationStatusSigKill GetTerminationStatusSigKill
+#endif
+TEST_F(ProcessUtilTest, MAYBE_GetTerminationStatusSigKill) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileKill);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("KilledChildProcess");
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ SignalChildren(signal_file.c_str());
+ exit_code = 42;
+ TerminationStatus status =
+ WaitForChildTermination(process.Handle(), &exit_code);
+#if defined(OS_CHROMEOS)
+ EXPECT_EQ(TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM, status);
+#else
+ EXPECT_EQ(TERMINATION_STATUS_PROCESS_WAS_KILLED, status);
+#endif
+
+#if defined(OS_WIN)
+ EXPECT_EQ(kExpectedKilledExitCode, exit_code);
+#elif defined(OS_POSIX)
+ int signaled = WIFSIGNALED(exit_code);
+ EXPECT_NE(0, signaled);
+ int signal = WTERMSIG(exit_code);
+ EXPECT_EQ(SIGKILL, signal);
+#endif
+ remove(signal_file.c_str());
+}
+
+#if defined(OS_POSIX)
+// TODO(crbug.com/753490): Access to the process termination reason is not
+// implemented in Fuchsia. Unix signals are not implemented in Fuchsia so this
+// test might not be relevant anyway.
+TEST_F(ProcessUtilTest, GetTerminationStatusSigTerm) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileTerm);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("TerminatedChildProcess");
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ SignalChildren(signal_file.c_str());
+ exit_code = 42;
+ TerminationStatus status =
+ WaitForChildTermination(process.Handle(), &exit_code);
+ EXPECT_EQ(TERMINATION_STATUS_PROCESS_WAS_KILLED, status);
+
+ int signaled = WIFSIGNALED(exit_code);
+ EXPECT_NE(0, signaled);
+ int signal = WTERMSIG(exit_code);
+ EXPECT_EQ(SIGTERM, signal);
+ remove(signal_file.c_str());
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(ProcessUtilTest, EnsureTerminationUndying) {
+ test::ScopedTaskEnvironment task_environment;
+
+ Process child_process = SpawnChild("process_util_test_never_die");
+ ASSERT_TRUE(child_process.IsValid());
+
+ EnsureProcessTerminated(child_process.Duplicate());
+
+#if defined(OS_POSIX)
+ errno = 0;
+#endif // defined(OS_POSIX)
+
+ // Allow a generous timeout, to cope with slow/loaded test bots.
+ bool did_exit = child_process.WaitForExitWithTimeout(
+ TestTimeouts::action_max_timeout(), nullptr);
+
+#if defined(OS_POSIX)
+ // Both EnsureProcessTerminated() and WaitForExitWithTimeout() will call
+ // waitpid(). One will succeed, and the other will fail with ECHILD. If our
+ // wait failed then check for ECHILD, and assumed |did_exit| in that case.
+ did_exit = did_exit || (errno == ECHILD);
+#endif // defined(OS_POSIX)
+
+ EXPECT_TRUE(did_exit);
+}
+
+MULTIPROCESS_TEST_MAIN(process_util_test_never_die) {
+ while (1) {
+ PlatformThread::Sleep(TimeDelta::FromSeconds(500));
+ }
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, EnsureTerminationGracefulExit) {
+ test::ScopedTaskEnvironment task_environment;
+
+ Process child_process = SpawnChild("process_util_test_die_immediately");
+ ASSERT_TRUE(child_process.IsValid());
+
+ // Wait for the child process to actually exit.
+ child_process.Duplicate().WaitForExitWithTimeout(
+ TestTimeouts::action_max_timeout(), nullptr);
+
+ EnsureProcessTerminated(child_process.Duplicate());
+
+ // Verify that the process is really, truly gone.
+ EXPECT_TRUE(child_process.WaitForExitWithTimeout(
+ TestTimeouts::action_max_timeout(), nullptr));
+}
+
+MULTIPROCESS_TEST_MAIN(process_util_test_die_immediately) {
+ return kSuccess;
+}
+
+#if defined(OS_WIN)
+// TODO(estade): if possible, port this test.
+TEST_F(ProcessUtilTest, LaunchAsUser) {
+ UserTokenHandle token;
+ ASSERT_TRUE(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token));
+ LaunchOptions options;
+ options.as_user = token;
+ EXPECT_TRUE(
+ LaunchProcess(MakeCmdLine("SimpleChildProcess"), options).IsValid());
+}
+
+static const char kEventToTriggerHandleSwitch[] = "event-to-trigger-handle";
+
+MULTIPROCESS_TEST_MAIN(TriggerEventChildProcess) {
+ std::string handle_value_string =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kEventToTriggerHandleSwitch);
+ CHECK(!handle_value_string.empty());
+
+ uint64_t handle_value_uint64;
+ CHECK(StringToUint64(handle_value_string, &handle_value_uint64));
+ // Give ownership of the handle to |event|.
+ WaitableEvent event(
+ win::ScopedHandle(reinterpret_cast<HANDLE>(handle_value_uint64)));
+
+ event.Signal();
+
+ return 0;
+}
+
+TEST_F(ProcessUtilTest, InheritSpecifiedHandles) {
+ // Manually create the event, so that it can be inheritable.
+ SECURITY_ATTRIBUTES security_attributes = {};
+ security_attributes.nLength = static_cast<DWORD>(sizeof(security_attributes));
+ security_attributes.lpSecurityDescriptor = NULL;
+ security_attributes.bInheritHandle = true;
+
+ // Takes ownership of the event handle.
+ WaitableEvent event(
+ win::ScopedHandle(CreateEvent(&security_attributes, true, false, NULL)));
+ LaunchOptions options;
+ options.handles_to_inherit.emplace_back(event.handle());
+
+ CommandLine cmd_line = MakeCmdLine("TriggerEventChildProcess");
+ cmd_line.AppendSwitchASCII(
+ kEventToTriggerHandleSwitch,
+ NumberToString(reinterpret_cast<uint64_t>(event.handle())));
+
+ // Launch the process and wait for it to trigger the event.
+ ASSERT_TRUE(LaunchProcess(cmd_line, options).IsValid());
+ EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout()));
+}
+#endif // defined(OS_WIN)
+
+TEST_F(ProcessUtilTest, GetAppOutput) {
+ base::CommandLine command(test_helper_path_);
+ command.AppendArg("hello");
+ command.AppendArg("there");
+ command.AppendArg("good");
+ command.AppendArg("people");
+ std::string output;
+ EXPECT_TRUE(GetAppOutput(command, &output));
+ EXPECT_EQ("hello there good people", output);
+ output.clear();
+
+ const char* kEchoMessage = "blah";
+ command = base::CommandLine(test_helper_path_);
+ command.AppendArg("-x");
+ command.AppendArg("28");
+ command.AppendArg(kEchoMessage);
+ EXPECT_FALSE(GetAppOutput(command, &output));
+ EXPECT_EQ(kEchoMessage, output);
+}
+
+TEST_F(ProcessUtilTest, GetAppOutputWithExitCode) {
+ const char* kEchoMessage1 = "doge";
+ int exit_code = -1;
+ base::CommandLine command(test_helper_path_);
+ command.AppendArg(kEchoMessage1);
+ std::string output;
+ EXPECT_TRUE(GetAppOutputWithExitCode(command, &output, &exit_code));
+ EXPECT_EQ(kEchoMessage1, output);
+ EXPECT_EQ(0, exit_code);
+ output.clear();
+
+ const char* kEchoMessage2 = "pupper";
+ const int kExpectedExitCode = 42;
+ command = base::CommandLine(test_helper_path_);
+ command.AppendArg("-x");
+ command.AppendArg(base::IntToString(kExpectedExitCode));
+ command.AppendArg(kEchoMessage2);
+#if defined(OS_WIN)
+ // On Windows, anything that quits with a nonzero status code is handled as a
+ // "crash", so just ignore GetAppOutputWithExitCode's return value.
+ GetAppOutputWithExitCode(command, &output, &exit_code);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ EXPECT_TRUE(GetAppOutputWithExitCode(command, &output, &exit_code));
+#endif
+ EXPECT_EQ(kEchoMessage2, output);
+ EXPECT_EQ(kExpectedExitCode, exit_code);
+}
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+namespace {
+
+// Returns the maximum number of files that a process can have open.
+// Returns 0 on error.
+int GetMaxFilesOpenInProcess() {
+#if defined(OS_FUCHSIA)
+ return FDIO_MAX_FD;
+#else
+ struct rlimit rlim;
+ if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
+ return 0;
+ }
+
+ // rlim_t is a uint64_t - clip to maxint. We do this since FD #s are ints
+ // which are all 32 bits on the supported platforms.
+ rlim_t max_int = static_cast<rlim_t>(std::numeric_limits<int32_t>::max());
+ if (rlim.rlim_cur > max_int) {
+ return max_int;
+ }
+
+ return rlim.rlim_cur;
+#endif // defined(OS_FUCHSIA)
+}
+
+const int kChildPipe = 20; // FD # for write end of pipe in child process.
+
+#if defined(OS_MACOSX)
+
+// <http://opensource.apple.com/source/xnu/xnu-2422.1.72/bsd/sys/guarded.h>
+#if !defined(_GUARDID_T)
+#define _GUARDID_T
+typedef __uint64_t guardid_t;
+#endif // _GUARDID_T
+
+// From .../MacOSX10.9.sdk/usr/include/sys/syscall.h
+#if !defined(SYS_change_fdguard_np)
+#define SYS_change_fdguard_np 444
+#endif
+
+// <http://opensource.apple.com/source/xnu/xnu-2422.1.72/bsd/sys/guarded.h>
+#if !defined(GUARD_DUP)
+#define GUARD_DUP (1u << 1)
+#endif
+
+// <http://opensource.apple.com/source/xnu/xnu-2422.1.72/bsd/kern/kern_guarded.c?txt>
+//
+// Atomically replaces |guard|/|guardflags| with |nguard|/|nguardflags| on |fd|.
+int change_fdguard_np(int fd,
+ const guardid_t *guard, u_int guardflags,
+ const guardid_t *nguard, u_int nguardflags,
+ int *fdflagsp) {
+ return syscall(SYS_change_fdguard_np, fd, guard, guardflags,
+ nguard, nguardflags, fdflagsp);
+}
+
+// Attempt to set a file-descriptor guard on |fd|. In case of success, remove
+// it and return |true| to indicate that it can be guarded. Returning |false|
+// means either that |fd| is guarded by some other code, or more likely EBADF.
+//
+// Starting with 10.9, libdispatch began setting GUARD_DUP on a file descriptor.
+// Unfortunately, it is spun up as part of +[NSApplication initialize], which is
+// not really something that Chromium can avoid using on OSX. See
+// <http://crbug.com/338157>. This function allows querying whether the file
+// descriptor is guarded before attempting to close it.
+bool CanGuardFd(int fd) {
+ // Saves the original flags to reset later.
+ int original_fdflags = 0;
+
+ // This can be any value at all, it just has to match up between the two
+ // calls.
+ const guardid_t kGuard = 15;
+
+ // Attempt to change the guard. This can fail with EBADF if the file
+ // descriptor is bad, or EINVAL if the fd already has a guard set.
+ int ret =
+ change_fdguard_np(fd, NULL, 0, &kGuard, GUARD_DUP, &original_fdflags);
+ if (ret == -1)
+ return false;
+
+ // Remove the guard. It should not be possible to fail in removing the guard
+ // just added.
+ ret = change_fdguard_np(fd, &kGuard, GUARD_DUP, NULL, 0, &original_fdflags);
+ DPCHECK(ret == 0);
+
+ return true;
+}
+#endif // defined(OS_MACOSX)
+
+} // namespace
+
+MULTIPROCESS_TEST_MAIN(ProcessUtilsLeakFDChildProcess) {
+ // This child process counts the number of open FDs, it then writes that
+ // number out to a pipe connected to the parent.
+ int num_open_files = 0;
+ int write_pipe = kChildPipe;
+ int max_files = GetMaxFilesOpenInProcess();
+ for (int i = STDERR_FILENO + 1; i < max_files; i++) {
+#if defined(OS_MACOSX)
+ // Ignore guarded or invalid file descriptors.
+ if (!CanGuardFd(i))
+ continue;
+#endif
+
+ if (i != kChildPipe) {
+ int fd;
+ if ((fd = HANDLE_EINTR(dup(i))) != -1) {
+ close(fd);
+ num_open_files += 1;
+ }
+ }
+ }
+
+ int written = HANDLE_EINTR(write(write_pipe, &num_open_files,
+ sizeof(num_open_files)));
+ DCHECK_EQ(static_cast<size_t>(written), sizeof(num_open_files));
+ int ret = IGNORE_EINTR(close(write_pipe));
+ DPCHECK(ret == 0);
+
+ return 0;
+}
+
+int ProcessUtilTest::CountOpenFDsInChild() {
+ int fds[2];
+ if (pipe(fds) < 0)
+ NOTREACHED();
+
+ LaunchOptions options;
+ options.fds_to_remap.emplace_back(fds[1], kChildPipe);
+ Process process =
+ SpawnChildWithOptions("ProcessUtilsLeakFDChildProcess", options);
+ CHECK(process.IsValid());
+ int ret = IGNORE_EINTR(close(fds[1]));
+ DPCHECK(ret == 0);
+
+ // Read number of open files in client process from pipe;
+ int num_open_files = -1;
+ ssize_t bytes_read =
+ HANDLE_EINTR(read(fds[0], &num_open_files, sizeof(num_open_files)));
+ CHECK_EQ(bytes_read, static_cast<ssize_t>(sizeof(num_open_files)));
+
+#if defined(THREAD_SANITIZER)
+ // Compiler-based ThreadSanitizer makes this test slow.
+ TimeDelta timeout = TimeDelta::FromSeconds(3);
+#else
+ TimeDelta timeout = TimeDelta::FromSeconds(1);
+#endif
+ int exit_code;
+ CHECK(process.WaitForExitWithTimeout(timeout, &exit_code));
+ ret = IGNORE_EINTR(close(fds[0]));
+ DPCHECK(ret == 0);
+
+ return num_open_files;
+}
+
+#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
+// ProcessUtilTest.FDRemapping is flaky when ran under xvfb-run on Precise.
+// The problem is 100% reproducible with both ASan and TSan.
+// See http://crbug.com/136720.
+#define MAYBE_FDRemapping DISABLED_FDRemapping
+#else
+#define MAYBE_FDRemapping FDRemapping
+#endif // defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
+TEST_F(ProcessUtilTest, MAYBE_FDRemapping) {
+ int fds_before = CountOpenFDsInChild();
+
+ // open some dummy fds to make sure they don't propagate over to the
+ // child process.
+ int dev_null = open("/dev/null", O_RDONLY);
+ DPCHECK(dev_null != -1);
+ int sockets[2];
+ int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
+ DPCHECK(ret == 0);
+
+ int fds_after = CountOpenFDsInChild();
+
+ ASSERT_EQ(fds_after, fds_before);
+
+ ret = IGNORE_EINTR(close(sockets[0]));
+ DPCHECK(ret == 0);
+ ret = IGNORE_EINTR(close(sockets[1]));
+ DPCHECK(ret == 0);
+ ret = IGNORE_EINTR(close(dev_null));
+ DPCHECK(ret == 0);
+}
+
+const char kPipeValue = '\xcc';
+MULTIPROCESS_TEST_MAIN(ProcessUtilsVerifyStdio) {
+ // Write to stdio so the parent process can observe output.
+ CHECK_EQ(1, HANDLE_EINTR(write(STDOUT_FILENO, &kPipeValue, 1)));
+
+ // Close all of the handles, to verify they are valid.
+ CHECK_EQ(0, IGNORE_EINTR(close(STDIN_FILENO)));
+ CHECK_EQ(0, IGNORE_EINTR(close(STDOUT_FILENO)));
+ CHECK_EQ(0, IGNORE_EINTR(close(STDERR_FILENO)));
+ return 0;
+}
+
+TEST_F(ProcessUtilTest, FDRemappingIncludesStdio) {
+ int dev_null = open("/dev/null", O_RDONLY);
+ ASSERT_LT(2, dev_null);
+
+ // Backup stdio and replace it with the write end of a pipe, for our
+ // child process to inherit.
+ int pipe_fds[2];
+ int result = pipe(pipe_fds);
+ ASSERT_EQ(0, result);
+ int backup_stdio = HANDLE_EINTR(dup(STDOUT_FILENO));
+ ASSERT_LE(0, backup_stdio);
+ result = dup2(pipe_fds[1], STDOUT_FILENO);
+ ASSERT_EQ(STDOUT_FILENO, result);
+
+ // Launch the test process, which should inherit our pipe stdio.
+ LaunchOptions options;
+ options.fds_to_remap.emplace_back(dev_null, dev_null);
+ Process process = SpawnChildWithOptions("ProcessUtilsVerifyStdio", options);
+ ASSERT_TRUE(process.IsValid());
+
+ // Restore stdio, so we can output stuff.
+ result = dup2(backup_stdio, STDOUT_FILENO);
+ ASSERT_EQ(STDOUT_FILENO, result);
+
+ // Close our copy of the write end of the pipe, so that the read()
+ // from the other end will see EOF if it wasn't copied to the child.
+ result = IGNORE_EINTR(close(pipe_fds[1]));
+ ASSERT_EQ(0, result);
+
+ result = IGNORE_EINTR(close(backup_stdio));
+ ASSERT_EQ(0, result);
+ result = IGNORE_EINTR(close(dev_null));
+ ASSERT_EQ(0, result);
+
+ // Read from the pipe to verify that it is connected to the child
+ // process' stdio.
+ char buf[16] = {};
+ EXPECT_EQ(1, HANDLE_EINTR(read(pipe_fds[0], buf, sizeof(buf))));
+ EXPECT_EQ(kPipeValue, buf[0]);
+
+ result = IGNORE_EINTR(close(pipe_fds[0]));
+ ASSERT_EQ(0, result);
+
+ int exit_code;
+ ASSERT_TRUE(
+ process.WaitForExitWithTimeout(TimeDelta::FromSeconds(5), &exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+#if defined(OS_FUCHSIA)
+
+const uint16_t kStartupHandleId = 43;
+MULTIPROCESS_TEST_MAIN(ProcessUtilsVerifyHandle) {
+ zx_handle_t handle =
+ zx_take_startup_handle(PA_HND(PA_USER0, kStartupHandleId));
+ CHECK_NE(ZX_HANDLE_INVALID, handle);
+
+ // Write to the pipe so the parent process can observe output.
+ size_t bytes_written = 0;
+ zx_status_t result = zx_socket_write(handle, 0, &kPipeValue,
+ sizeof(kPipeValue), &bytes_written);
+ CHECK_EQ(ZX_OK, result);
+ CHECK_EQ(1u, bytes_written);
+
+ CHECK_EQ(ZX_OK, zx_handle_close(handle));
+ return 0;
+}
+
+TEST_F(ProcessUtilTest, LaunchWithHandleTransfer) {
+ // Create a pipe to pass to the child process.
+ zx_handle_t handles[2];
+ zx_status_t result =
+ zx_socket_create(ZX_SOCKET_STREAM, &handles[0], &handles[1]);
+ ASSERT_EQ(ZX_OK, result);
+
+ // Launch the test process, and pass it one end of the pipe.
+ LaunchOptions options;
+ options.handles_to_transfer.push_back(
+ {PA_HND(PA_USER0, kStartupHandleId), handles[0]});
+ Process process = SpawnChildWithOptions("ProcessUtilsVerifyHandle", options);
+ ASSERT_TRUE(process.IsValid());
+
+ // Read from the pipe to verify that the child received it.
+ zx_signals_t signals = 0;
+ result = zx_object_wait_one(
+ handles[1], ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED,
+ (base::TimeTicks::Now() + TestTimeouts::action_timeout()).ToZxTime(),
+ &signals);
+ ASSERT_EQ(ZX_OK, result);
+ ASSERT_TRUE(signals & ZX_SOCKET_READABLE);
+
+ size_t bytes_read = 0;
+ char buf[16] = {0};
+ result = zx_socket_read(handles[1], 0, buf, sizeof(buf), &bytes_read);
+ EXPECT_EQ(ZX_OK, result);
+ EXPECT_EQ(1u, bytes_read);
+ EXPECT_EQ(kPipeValue, buf[0]);
+
+ CHECK_EQ(ZX_OK, zx_handle_close(handles[1]));
+
+ int exit_code;
+ ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_timeout(),
+ &exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+#endif // defined(OS_FUCHSIA)
+
+namespace {
+
+std::string TestLaunchProcess(const std::vector<std::string>& args,
+ const EnvironmentMap& env_changes,
+ const bool clear_environ,
+ const int clone_flags) {
+ int fds[2];
+ PCHECK(pipe(fds) == 0);
+
+ LaunchOptions options;
+ options.wait = true;
+ options.environ = env_changes;
+ options.clear_environ = clear_environ;
+ options.fds_to_remap.emplace_back(fds[1], 1);
+#if defined(OS_LINUX)
+ options.clone_flags = clone_flags;
+#else
+ CHECK_EQ(0, clone_flags);
+#endif // defined(OS_LINUX)
+ EXPECT_TRUE(LaunchProcess(args, options).IsValid());
+ PCHECK(IGNORE_EINTR(close(fds[1])) == 0);
+
+ char buf[512];
+ const ssize_t n = HANDLE_EINTR(read(fds[0], buf, sizeof(buf)));
+
+ PCHECK(IGNORE_EINTR(close(fds[0])) == 0);
+
+ return std::string(buf, n);
+}
+
+const char kLargeString[] =
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789";
+
+} // namespace
+
+TEST_F(ProcessUtilTest, LaunchProcess) {
+ const int no_clone_flags = 0;
+ const bool no_clear_environ = false;
+ const char kBaseTest[] = "BASE_TEST";
+ const std::vector<std::string> kPrintEnvCommand = {test_helper_path_.value(),
+ "-e", kBaseTest};
+
+ EnvironmentMap env_changes;
+ env_changes[kBaseTest] = "bar";
+ EXPECT_EQ("bar", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+ env_changes.clear();
+
+ EXPECT_EQ(0, setenv(kBaseTest, "testing", 1 /* override */));
+ EXPECT_EQ("testing", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+
+ env_changes[kBaseTest] = std::string();
+ EXPECT_EQ("", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+
+ env_changes[kBaseTest] = "foo";
+ EXPECT_EQ("foo", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+
+ env_changes.clear();
+ EXPECT_EQ(0, setenv(kBaseTest, kLargeString, 1 /* override */));
+ EXPECT_EQ(std::string(kLargeString),
+ TestLaunchProcess(kPrintEnvCommand, env_changes, no_clear_environ,
+ no_clone_flags));
+
+ env_changes[kBaseTest] = "wibble";
+ EXPECT_EQ("wibble", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+
+#if defined(OS_LINUX)
+ // Test a non-trival value for clone_flags.
+ EXPECT_EQ("wibble", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, CLONE_FS));
+
+ EXPECT_EQ("wibble",
+ TestLaunchProcess(kPrintEnvCommand, env_changes,
+ true /* clear_environ */, no_clone_flags));
+ env_changes.clear();
+ EXPECT_EQ("", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ true /* clear_environ */, no_clone_flags));
+#endif // defined(OS_LINUX)
+}
+
+// There's no such thing as a parent process id on Fuchsia.
+#if !defined(OS_FUCHSIA)
+TEST_F(ProcessUtilTest, GetParentProcessId) {
+ ProcessId ppid = GetParentProcessId(GetCurrentProcessHandle());
+ EXPECT_EQ(ppid, static_cast<ProcessId>(getppid()));
+}
+#endif // !defined(OS_FUCHSIA)
+
+#if !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
+class WriteToPipeDelegate : public LaunchOptions::PreExecDelegate {
+ public:
+ explicit WriteToPipeDelegate(int fd) : fd_(fd) {}
+ ~WriteToPipeDelegate() override = default;
+ void RunAsyncSafe() override {
+ RAW_CHECK(HANDLE_EINTR(write(fd_, &kPipeValue, 1)) == 1);
+ RAW_CHECK(IGNORE_EINTR(close(fd_)) == 0);
+ }
+
+ private:
+ int fd_;
+ DISALLOW_COPY_AND_ASSIGN(WriteToPipeDelegate);
+};
+
+TEST_F(ProcessUtilTest, PreExecHook) {
+ int pipe_fds[2];
+ ASSERT_EQ(0, pipe(pipe_fds));
+
+ ScopedFD read_fd(pipe_fds[0]);
+ ScopedFD write_fd(pipe_fds[1]);
+
+ WriteToPipeDelegate write_to_pipe_delegate(write_fd.get());
+ LaunchOptions options;
+ options.fds_to_remap.emplace_back(write_fd.get(), write_fd.get());
+ options.pre_exec_delegate = &write_to_pipe_delegate;
+ Process process(SpawnChildWithOptions("SimpleChildProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ write_fd.reset();
+ char c;
+ ASSERT_EQ(1, HANDLE_EINTR(read(read_fd.get(), &c, 1)));
+ EXPECT_EQ(c, kPipeValue);
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+#endif // !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
+
+#endif // !defined(OS_MACOSX) && !defined(OS_ANDROID)
+
+#if defined(OS_LINUX)
+MULTIPROCESS_TEST_MAIN(CheckPidProcess) {
+ const pid_t kInitPid = 1;
+ const pid_t pid = syscall(__NR_getpid);
+ CHECK(pid == kInitPid);
+ CHECK(getpid() == pid);
+ return kSuccess;
+}
+
+#if defined(CLONE_NEWUSER) && defined(CLONE_NEWPID)
+TEST_F(ProcessUtilTest, CloneFlags) {
+ if (!PathExists(FilePath("/proc/self/ns/user")) ||
+ !PathExists(FilePath("/proc/self/ns/pid"))) {
+ // User or PID namespaces are not supported.
+ return;
+ }
+
+ LaunchOptions options;
+ options.clone_flags = CLONE_NEWUSER | CLONE_NEWPID;
+
+ Process process(SpawnChildWithOptions("CheckPidProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+#endif // defined(CLONE_NEWUSER) && defined(CLONE_NEWPID)
+
+TEST(ForkWithFlagsTest, UpdatesPidCache) {
+ // Warm up the libc pid cache, if there is one.
+ ASSERT_EQ(syscall(__NR_getpid), getpid());
+
+ pid_t ctid = 0;
+ const pid_t pid = ForkWithFlags(SIGCHLD | CLONE_CHILD_SETTID, nullptr, &ctid);
+ if (pid == 0) {
+ // In child. Check both the raw getpid syscall and the libc getpid wrapper
+ // (which may rely on a pid cache).
+ RAW_CHECK(syscall(__NR_getpid) == ctid);
+ RAW_CHECK(getpid() == ctid);
+ _exit(kSuccess);
+ }
+
+ ASSERT_NE(-1, pid);
+ int status = 42;
+ ASSERT_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0)));
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(kSuccess, WEXITSTATUS(status));
+}
+
+TEST_F(ProcessUtilTest, InvalidCurrentDirectory) {
+ LaunchOptions options;
+ options.current_directory = FilePath("/dev/null");
+
+ Process process(SpawnChildWithOptions("SimpleChildProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = kSuccess;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_NE(kSuccess, exit_code);
+}
+#endif // defined(OS_LINUX)
+
+} // namespace base
diff --git a/base/profiler/native_stack_sampler.cc b/base/profiler/native_stack_sampler.cc
new file mode 100644
index 0000000000..6eed54f046
--- /dev/null
+++ b/base/profiler/native_stack_sampler.cc
@@ -0,0 +1,34 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/profiler/native_stack_sampler.h"
+
+#include "base/memory/ptr_util.h"
+
+namespace base {
+
+NativeStackSampler::StackBuffer::StackBuffer(size_t buffer_size)
+ : buffer_(new uintptr_t[(buffer_size + sizeof(uintptr_t) - 1) /
+ sizeof(uintptr_t)]),
+ size_(buffer_size) {}
+
+NativeStackSampler::StackBuffer::~StackBuffer() = default;
+
+NativeStackSampler::NativeStackSampler() = default;
+
+NativeStackSampler::~NativeStackSampler() = default;
+
+std::unique_ptr<NativeStackSampler::StackBuffer>
+NativeStackSampler::CreateStackBuffer() {
+ size_t size = GetStackBufferSize();
+ if (size == 0)
+ return nullptr;
+ return std::make_unique<StackBuffer>(size);
+}
+
+NativeStackSamplerTestDelegate::~NativeStackSamplerTestDelegate() = default;
+
+NativeStackSamplerTestDelegate::NativeStackSamplerTestDelegate() = default;
+
+} // namespace base
diff --git a/base/profiler/native_stack_sampler.h b/base/profiler/native_stack_sampler.h
new file mode 100644
index 0000000000..5d7e9b0771
--- /dev/null
+++ b/base/profiler/native_stack_sampler.h
@@ -0,0 +1,97 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROFILER_NATIVE_STACK_SAMPLER_H_
+#define BASE_PROFILER_NATIVE_STACK_SAMPLER_H_
+
+#include <memory>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "base/threading/platform_thread.h"
+
+namespace base {
+
+class NativeStackSamplerTestDelegate;
+
+// NativeStackSampler is an implementation detail of StackSamplingProfiler. It
+// abstracts the native implementation required to record a set of stack frames
+// for a given thread.
+class NativeStackSampler {
+ public:
+ // This class contains a buffer for stack copies that can be shared across
+ // multiple instances of NativeStackSampler.
+ class StackBuffer {
+ public:
+ StackBuffer(size_t buffer_size);
+ ~StackBuffer();
+
+ void* buffer() const { return buffer_.get(); }
+ size_t size() const { return size_; }
+
+ private:
+ // The word-aligned buffer.
+ const std::unique_ptr<uintptr_t[]> buffer_;
+
+ // The size of the buffer.
+ const size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(StackBuffer);
+ };
+
+ virtual ~NativeStackSampler();
+
+ // Creates a stack sampler that records samples for thread with |thread_id|.
+ // Returns null if this platform does not support stack sampling.
+ static std::unique_ptr<NativeStackSampler> Create(
+ PlatformThreadId thread_id,
+ NativeStackSamplerTestDelegate* test_delegate);
+
+ // Gets the required size of the stack buffer.
+ static size_t GetStackBufferSize();
+
+ // Creates an instance of the a stack buffer that can be used for calls to
+ // any NativeStackSampler object.
+ static std::unique_ptr<StackBuffer> CreateStackBuffer();
+
+ // The following functions are all called on the SamplingThread (not the
+ // thread being sampled).
+
+ // Notifies the sampler that we're starting to record a new profile.
+ virtual void ProfileRecordingStarting() = 0;
+
+ // Records a set of internal frames and returns them.
+ virtual std::vector<StackSamplingProfiler::InternalFrame> RecordStackFrames(
+ StackBuffer* stackbuffer,
+ StackSamplingProfiler::ProfileBuilder* profile_builder) = 0;
+
+ protected:
+ NativeStackSampler();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NativeStackSampler);
+};
+
+// NativeStackSamplerTestDelegate provides seams for test code to execute during
+// stack collection.
+class BASE_EXPORT NativeStackSamplerTestDelegate {
+ public:
+ virtual ~NativeStackSamplerTestDelegate();
+
+ // Called after copying the stack and resuming the target thread, but prior to
+ // walking the stack. Invoked on the SamplingThread.
+ virtual void OnPreStackWalk() = 0;
+
+ protected:
+ NativeStackSamplerTestDelegate();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerTestDelegate);
+};
+
+} // namespace base
+
+#endif // BASE_PROFILER_NATIVE_STACK_SAMPLER_H_
+
diff --git a/base/profiler/native_stack_sampler_posix.cc b/base/profiler/native_stack_sampler_posix.cc
new file mode 100644
index 0000000000..fdc18e017f
--- /dev/null
+++ b/base/profiler/native_stack_sampler_posix.cc
@@ -0,0 +1,19 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/profiler/native_stack_sampler.h"
+
+namespace base {
+
+std::unique_ptr<NativeStackSampler> NativeStackSampler::Create(
+ PlatformThreadId thread_id,
+ NativeStackSamplerTestDelegate* test_delegate) {
+ return std::unique_ptr<NativeStackSampler>();
+}
+
+size_t NativeStackSampler::GetStackBufferSize() {
+ return 0;
+}
+
+} // namespace base
diff --git a/base/profiler/stack_sampling_profiler.cc b/base/profiler/stack_sampling_profiler.cc
new file mode 100644
index 0000000000..02df814f8c
--- /dev/null
+++ b/base/profiler/stack_sampling_profiler.cc
@@ -0,0 +1,808 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/profiler/stack_sampling_profiler.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/atomic_sequence_num.h"
+#include "base/atomicops.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/singleton.h"
+#include "base/profiler/native_stack_sampler.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/timer/elapsed_timer.h"
+
+namespace base {
+
+const size_t kUnknownModuleIndex = static_cast<size_t>(-1);
+
+namespace {
+
+// This value is used to initialize the WaitableEvent object. This MUST BE set
+// to MANUAL for correct operation of the IsSignaled() call in Start(). See the
+// comment there for why.
+constexpr WaitableEvent::ResetPolicy kResetPolicy =
+ WaitableEvent::ResetPolicy::MANUAL;
+
+// This value is used when there is no collection in progress and thus no ID
+// for referencing the active collection to the SamplingThread.
+const int kNullProfilerId = -1;
+
+} // namespace
+
+// StackSamplingProfiler::Module ----------------------------------------------
+
+StackSamplingProfiler::Module::Module() : base_address(0u) {}
+
+StackSamplingProfiler::Module::Module(uintptr_t base_address,
+ const std::string& id,
+ const FilePath& filename)
+ : base_address(base_address), id(id), filename(filename) {}
+
+StackSamplingProfiler::Module::~Module() = default;
+
+// StackSamplingProfiler::InternalModule --------------------------------------
+
+StackSamplingProfiler::InternalModule::InternalModule() : is_valid(false) {}
+
+StackSamplingProfiler::InternalModule::InternalModule(uintptr_t base_address,
+ const std::string& id,
+ const FilePath& filename)
+ : base_address(base_address), id(id), filename(filename), is_valid(true) {}
+
+StackSamplingProfiler::InternalModule::~InternalModule() = default;
+
+// StackSamplingProfiler::Frame -----------------------------------------------
+
+StackSamplingProfiler::Frame::Frame(uintptr_t instruction_pointer,
+ size_t module_index)
+ : instruction_pointer(instruction_pointer), module_index(module_index) {}
+
+StackSamplingProfiler::Frame::~Frame() = default;
+
+StackSamplingProfiler::Frame::Frame()
+ : instruction_pointer(0), module_index(kUnknownModuleIndex) {}
+
+// StackSamplingProfiler::InternalFrame -------------------------------------
+
+StackSamplingProfiler::InternalFrame::InternalFrame(
+ uintptr_t instruction_pointer,
+ InternalModule internal_module)
+ : instruction_pointer(instruction_pointer),
+ internal_module(std::move(internal_module)) {}
+
+StackSamplingProfiler::InternalFrame::~InternalFrame() = default;
+
+// StackSamplingProfiler::Sample ----------------------------------------------
+
+StackSamplingProfiler::Sample::Sample() = default;
+
+StackSamplingProfiler::Sample::Sample(const Sample& sample) = default;
+
+StackSamplingProfiler::Sample::~Sample() = default;
+
+StackSamplingProfiler::Sample::Sample(const Frame& frame) {
+ frames.push_back(std::move(frame));
+}
+
+StackSamplingProfiler::Sample::Sample(const std::vector<Frame>& frames)
+ : frames(frames) {}
+
+// StackSamplingProfiler::CallStackProfile ------------------------------------
+
+StackSamplingProfiler::CallStackProfile::CallStackProfile() = default;
+
+StackSamplingProfiler::CallStackProfile::CallStackProfile(
+ CallStackProfile&& other) = default;
+
+StackSamplingProfiler::CallStackProfile::~CallStackProfile() = default;
+
+StackSamplingProfiler::CallStackProfile&
+StackSamplingProfiler::CallStackProfile::operator=(CallStackProfile&& other) =
+ default;
+
+StackSamplingProfiler::CallStackProfile
+StackSamplingProfiler::CallStackProfile::CopyForTesting() const {
+ return CallStackProfile(*this);
+}
+
+StackSamplingProfiler::CallStackProfile::CallStackProfile(
+ const CallStackProfile& other) = default;
+
+// StackSamplingProfiler::SamplingThread --------------------------------------
+
+class StackSamplingProfiler::SamplingThread : public Thread {
+ public:
+ class TestAPI {
+ public:
+ // Reset the existing sampler. This will unfortunately create the object
+ // unnecessarily if it doesn't already exist but there's no way around that.
+ static void Reset();
+
+ // Disables inherent idle-shutdown behavior.
+ static void DisableIdleShutdown();
+
+ // Begins an idle shutdown as if the idle-timer had expired and wait for
+ // it to execute. Since the timer would have only been started at a time
+ // when the sampling thread actually was idle, this must be called only
+ // when it is known that there are no active sampling threads. If
+ // |simulate_intervening_add| is true then, when executed, the shutdown
+ // task will believe that a new collection has been added since it was
+ // posted.
+ static void ShutdownAssumingIdle(bool simulate_intervening_add);
+
+ private:
+ // Calls the sampling threads ShutdownTask and then signals an event.
+ static void ShutdownTaskAndSignalEvent(SamplingThread* sampler,
+ int add_events,
+ WaitableEvent* event);
+ };
+
+ struct CollectionContext {
+ CollectionContext(PlatformThreadId target,
+ const SamplingParams& params,
+ WaitableEvent* finished,
+ std::unique_ptr<NativeStackSampler> sampler,
+ std::unique_ptr<ProfileBuilder> profile_builder)
+ : collection_id(next_collection_id.GetNext()),
+ target(target),
+ params(params),
+ finished(finished),
+ native_sampler(std::move(sampler)),
+ profile_builder(std::move(profile_builder)) {}
+ ~CollectionContext() = default;
+
+ // An identifier for this collection, used to uniquely identify the
+ // collection to outside interests.
+ const int collection_id;
+
+ const PlatformThreadId target; // ID of The thread being sampled.
+ const SamplingParams params; // Information about how to sample.
+ WaitableEvent* const finished; // Signaled when all sampling complete.
+
+ // Platform-specific module that does the actual sampling.
+ std::unique_ptr<NativeStackSampler> native_sampler;
+
+ // Receives the sampling data and builds a CallStackProfile.
+ std::unique_ptr<ProfileBuilder> profile_builder;
+
+ // The absolute time for the next sample.
+ Time next_sample_time;
+
+ // The time that a profile was started, for calculating the total duration.
+ Time profile_start_time;
+
+ // Counter that indicates the current sample position along the acquisition.
+ int sample_count = 0;
+
+ // Sequence number for generating new collection ids.
+ static AtomicSequenceNumber next_collection_id;
+ };
+
+ // Gets the single instance of this class.
+ static SamplingThread* GetInstance();
+
+ // Adds a new CollectionContext to the thread. This can be called externally
+ // from any thread. This returns a collection id that can later be used to
+ // stop the sampling.
+ int Add(std::unique_ptr<CollectionContext> collection);
+
+ // Removes an active collection based on its collection id, forcing it to run
+ // its callback if any data has been collected. This can be called externally
+ // from any thread.
+ void Remove(int collection_id);
+
+ private:
+ friend class TestAPI;
+ friend struct DefaultSingletonTraits<SamplingThread>;
+
+ // The different states in which the sampling-thread can be.
+ enum ThreadExecutionState {
+ // The thread is not running because it has never been started. It will be
+ // started when a sampling request is received.
+ NOT_STARTED,
+
+ // The thread is running and processing tasks. This is the state when any
+ // sampling requests are active and during the "idle" period afterward
+ // before the thread is stopped.
+ RUNNING,
+
+ // Once all sampling requests have finished and the "idle" period has
+ // expired, the thread will be set to this state and its shutdown
+ // initiated. A call to Stop() must be made to ensure the previous thread
+ // has completely exited before calling Start() and moving back to the
+ // RUNNING state.
+ EXITING,
+ };
+
+ SamplingThread();
+ ~SamplingThread() override;
+
+ // Get task runner that is usable from the outside.
+ scoped_refptr<SingleThreadTaskRunner> GetOrCreateTaskRunnerForAdd();
+ scoped_refptr<SingleThreadTaskRunner> GetTaskRunner(
+ ThreadExecutionState* out_state);
+
+ // Get task runner that is usable from the sampling thread itself.
+ scoped_refptr<SingleThreadTaskRunner> GetTaskRunnerOnSamplingThread();
+
+ // Finishes a collection. The collection's |finished| waitable event will be
+ // signalled. The |collection| should already have been removed from
+ // |active_collections_| by the caller, as this is needed to avoid flakiness
+ // in unit tests.
+ void FinishCollection(CollectionContext* collection);
+
+ // Check if the sampling thread is idle and begin a shutdown if it is.
+ void ScheduleShutdownIfIdle();
+
+ // These methods are tasks that get posted to the internal message queue.
+ void AddCollectionTask(std::unique_ptr<CollectionContext> collection);
+ void RemoveCollectionTask(int collection_id);
+ void RecordSampleTask(int collection_id);
+ void ShutdownTask(int add_events);
+
+ // Thread:
+ void CleanUp() override;
+
+ // A stack-buffer used by the native sampler for its work. This buffer can
+ // be re-used for multiple native sampler objects so long as the API calls
+ // that take it are not called concurrently.
+ std::unique_ptr<NativeStackSampler::StackBuffer> stack_buffer_;
+
+ // A map of collection ids to collection contexts. Because this class is a
+ // singleton that is never destroyed, context objects will never be destructed
+ // except by explicit action. Thus, it's acceptable to pass unretained
+ // pointers to these objects when posting tasks.
+ std::map<int, std::unique_ptr<CollectionContext>> active_collections_;
+
+ // State maintained about the current execution (or non-execution) of
+ // the thread. This state must always be accessed while holding the
+ // lock. A copy of the task-runner is maintained here for use by any
+ // calling thread; this is necessary because Thread's accessor for it is
+ // not itself thread-safe. The lock is also used to order calls to the
+ // Thread API (Start, Stop, StopSoon, & DetachFromSequence) so that
+ // multiple threads may make those calls.
+ Lock thread_execution_state_lock_; // Protects all thread_execution_state_*
+ ThreadExecutionState thread_execution_state_ = NOT_STARTED;
+ scoped_refptr<SingleThreadTaskRunner> thread_execution_state_task_runner_;
+ bool thread_execution_state_disable_idle_shutdown_for_testing_ = false;
+
+ // A counter that notes adds of new collection requests. It is incremented
+ // when changes occur so that delayed shutdown tasks are able to detect if
+ // something new has happened while it was waiting. Like all "execution_state"
+ // vars, this must be accessed while holding |thread_execution_state_lock_|.
+ int thread_execution_state_add_events_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(SamplingThread);
+};
+
+// static
+void StackSamplingProfiler::SamplingThread::TestAPI::Reset() {
+ SamplingThread* sampler = SamplingThread::GetInstance();
+
+ ThreadExecutionState state;
+ {
+ AutoLock lock(sampler->thread_execution_state_lock_);
+ state = sampler->thread_execution_state_;
+ DCHECK(sampler->active_collections_.empty());
+ }
+
+ // Stop the thread and wait for it to exit. This has to be done through by
+ // the thread itself because it has taken ownership of its own lifetime.
+ if (state == RUNNING) {
+ ShutdownAssumingIdle(false);
+ state = EXITING;
+ }
+ // Make sure thread is cleaned up since state will be reset to NOT_STARTED.
+ if (state == EXITING)
+ sampler->Stop();
+
+ // Reset internal variables to the just-initialized state.
+ {
+ AutoLock lock(sampler->thread_execution_state_lock_);
+ sampler->thread_execution_state_ = NOT_STARTED;
+ sampler->thread_execution_state_task_runner_ = nullptr;
+ sampler->thread_execution_state_disable_idle_shutdown_for_testing_ = false;
+ sampler->thread_execution_state_add_events_ = 0;
+ }
+}
+
+// static
+void StackSamplingProfiler::SamplingThread::TestAPI::DisableIdleShutdown() {
+ SamplingThread* sampler = SamplingThread::GetInstance();
+
+ {
+ AutoLock lock(sampler->thread_execution_state_lock_);
+ sampler->thread_execution_state_disable_idle_shutdown_for_testing_ = true;
+ }
+}
+
+// static
+void StackSamplingProfiler::SamplingThread::TestAPI::ShutdownAssumingIdle(
+ bool simulate_intervening_add) {
+ SamplingThread* sampler = SamplingThread::GetInstance();
+
+ ThreadExecutionState state;
+ scoped_refptr<SingleThreadTaskRunner> task_runner =
+ sampler->GetTaskRunner(&state);
+ DCHECK_EQ(RUNNING, state);
+ DCHECK(task_runner);
+
+ int add_events;
+ {
+ AutoLock lock(sampler->thread_execution_state_lock_);
+ add_events = sampler->thread_execution_state_add_events_;
+ if (simulate_intervening_add)
+ ++sampler->thread_execution_state_add_events_;
+ }
+
+ WaitableEvent executed(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ // PostTaskAndReply won't work because thread and associated message-loop may
+ // be shut down.
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&ShutdownTaskAndSignalEvent, Unretained(sampler),
+ add_events, Unretained(&executed)));
+ executed.Wait();
+}
+
+// static
+void StackSamplingProfiler::SamplingThread::TestAPI::ShutdownTaskAndSignalEvent(
+ SamplingThread* sampler,
+ int add_events,
+ WaitableEvent* event) {
+ sampler->ShutdownTask(add_events);
+ event->Signal();
+}
+
+AtomicSequenceNumber StackSamplingProfiler::SamplingThread::CollectionContext::
+ next_collection_id;
+
+StackSamplingProfiler::SamplingThread::SamplingThread()
+ : Thread("StackSamplingProfiler") {}
+
+StackSamplingProfiler::SamplingThread::~SamplingThread() = default;
+
+StackSamplingProfiler::SamplingThread*
+StackSamplingProfiler::SamplingThread::GetInstance() {
+ return Singleton<SamplingThread, LeakySingletonTraits<SamplingThread>>::get();
+}
+
+int StackSamplingProfiler::SamplingThread::Add(
+ std::unique_ptr<CollectionContext> collection) {
+ // This is not to be run on the sampling thread.
+
+ int collection_id = collection->collection_id;
+ scoped_refptr<SingleThreadTaskRunner> task_runner =
+ GetOrCreateTaskRunnerForAdd();
+
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&SamplingThread::AddCollectionTask, Unretained(this),
+ std::move(collection)));
+
+ return collection_id;
+}
+
+void StackSamplingProfiler::SamplingThread::Remove(int collection_id) {
+ // This is not to be run on the sampling thread.
+
+ ThreadExecutionState state;
+ scoped_refptr<SingleThreadTaskRunner> task_runner = GetTaskRunner(&state);
+ if (state != RUNNING)
+ return;
+ DCHECK(task_runner);
+
+ // This can fail if the thread were to exit between acquisition of the task
+ // runner above and the call below. In that case, however, everything has
+ // stopped so there's no need to try to stop it.
+ task_runner->PostTask(FROM_HERE,
+ BindOnce(&SamplingThread::RemoveCollectionTask,
+ Unretained(this), collection_id));
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+StackSamplingProfiler::SamplingThread::GetOrCreateTaskRunnerForAdd() {
+ AutoLock lock(thread_execution_state_lock_);
+
+ // The increment of the "add events" count is why this method is to be only
+ // called from "add".
+ ++thread_execution_state_add_events_;
+
+ if (thread_execution_state_ == RUNNING) {
+ DCHECK(thread_execution_state_task_runner_);
+ // This shouldn't be called from the sampling thread as it's inefficient.
+ // Use GetTaskRunnerOnSamplingThread() instead.
+ DCHECK_NE(GetThreadId(), PlatformThread::CurrentId());
+ return thread_execution_state_task_runner_;
+ }
+
+ if (thread_execution_state_ == EXITING) {
+ // StopSoon() was previously called to shut down the thread
+ // asynchonously. Stop() must now be called before calling Start() again to
+ // reset the thread state.
+ //
+ // We must allow blocking here to satisfy the Thread implementation, but in
+ // practice the Stop() call is unlikely to actually block. For this to
+ // happen a new profiling request would have to be made within the narrow
+ // window between StopSoon() and thread exit following the end of the 60
+ // second idle period.
+ ScopedAllowBlocking allow_blocking;
+ Stop();
+ }
+
+ DCHECK(!stack_buffer_);
+ stack_buffer_ = NativeStackSampler::CreateStackBuffer();
+
+ // The thread is not running. Start it and get associated runner. The task-
+ // runner has to be saved for future use because though it can be used from
+ // any thread, it can be acquired via task_runner() only on the created
+ // thread and the thread that creates it (i.e. this thread) for thread-safety
+ // reasons which are alleviated in SamplingThread by gating access to it with
+ // the |thread_execution_state_lock_|.
+ Start();
+ thread_execution_state_ = RUNNING;
+ thread_execution_state_task_runner_ = Thread::task_runner();
+
+ // Detach the sampling thread from the "sequence" (i.e. thread) that
+ // started it so that it can be self-managed or stopped by another thread.
+ DetachFromSequence();
+
+ return thread_execution_state_task_runner_;
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+StackSamplingProfiler::SamplingThread::GetTaskRunner(
+ ThreadExecutionState* out_state) {
+ AutoLock lock(thread_execution_state_lock_);
+ if (out_state)
+ *out_state = thread_execution_state_;
+ if (thread_execution_state_ == RUNNING) {
+ // This shouldn't be called from the sampling thread as it's inefficient.
+ // Use GetTaskRunnerOnSamplingThread() instead.
+ DCHECK_NE(GetThreadId(), PlatformThread::CurrentId());
+ DCHECK(thread_execution_state_task_runner_);
+ } else {
+ DCHECK(!thread_execution_state_task_runner_);
+ }
+
+ return thread_execution_state_task_runner_;
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+StackSamplingProfiler::SamplingThread::GetTaskRunnerOnSamplingThread() {
+ // This should be called only from the sampling thread as it has limited
+ // accessibility.
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ return Thread::task_runner();
+}
+
+void StackSamplingProfiler::SamplingThread::FinishCollection(
+ CollectionContext* collection) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+ DCHECK_EQ(0u, active_collections_.count(collection->collection_id));
+
+ TimeDelta profile_duration = Time::Now() - collection->profile_start_time +
+ collection->params.sampling_interval;
+
+ collection->profile_builder->OnProfileCompleted(
+ profile_duration, collection->params.sampling_interval);
+
+ // Signal that this collection is finished.
+ collection->finished->Signal();
+
+ ScheduleShutdownIfIdle();
+}
+
+void StackSamplingProfiler::SamplingThread::ScheduleShutdownIfIdle() {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ if (!active_collections_.empty())
+ return;
+
+ int add_events;
+ {
+ AutoLock lock(thread_execution_state_lock_);
+ if (thread_execution_state_disable_idle_shutdown_for_testing_)
+ return;
+ add_events = thread_execution_state_add_events_;
+ }
+
+ GetTaskRunnerOnSamplingThread()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&SamplingThread::ShutdownTask, Unretained(this), add_events),
+ TimeDelta::FromSeconds(60));
+}
+
+void StackSamplingProfiler::SamplingThread::AddCollectionTask(
+ std::unique_ptr<CollectionContext> collection) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ const int collection_id = collection->collection_id;
+ const TimeDelta initial_delay = collection->params.initial_delay;
+
+ active_collections_.insert(
+ std::make_pair(collection_id, std::move(collection)));
+
+ GetTaskRunnerOnSamplingThread()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&SamplingThread::RecordSampleTask, Unretained(this),
+ collection_id),
+ initial_delay);
+
+ // Another increment of "add events" serves to invalidate any pending
+ // shutdown tasks that may have been initiated between the Add() and this
+ // task running.
+ {
+ AutoLock lock(thread_execution_state_lock_);
+ ++thread_execution_state_add_events_;
+ }
+}
+
+void StackSamplingProfiler::SamplingThread::RemoveCollectionTask(
+ int collection_id) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ auto found = active_collections_.find(collection_id);
+ if (found == active_collections_.end())
+ return;
+
+ // Remove |collection| from |active_collections_|.
+ std::unique_ptr<CollectionContext> collection = std::move(found->second);
+ size_t count = active_collections_.erase(collection_id);
+ DCHECK_EQ(1U, count);
+
+ FinishCollection(collection.get());
+}
+
+void StackSamplingProfiler::SamplingThread::RecordSampleTask(
+ int collection_id) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ auto found = active_collections_.find(collection_id);
+
+ // The task won't be found if it has been stopped.
+ if (found == active_collections_.end())
+ return;
+
+ CollectionContext* collection = found->second.get();
+
+ // If this is the first sample, the collection params need to be filled.
+ if (collection->sample_count == 0) {
+ collection->profile_start_time = Time::Now();
+ collection->next_sample_time = Time::Now();
+ collection->native_sampler->ProfileRecordingStarting();
+ }
+
+ // Record a single sample.
+ collection->profile_builder->OnSampleCompleted(
+ collection->native_sampler->RecordStackFrames(
+ stack_buffer_.get(), collection->profile_builder.get()));
+
+ // Schedule the next sample recording if there is one.
+ if (++collection->sample_count < collection->params.samples_per_profile) {
+ // This will keep a consistent average interval between samples but will
+ // result in constant series of acquisitions, thus nearly locking out the
+ // target thread, if the interval is smaller than the time it takes to
+ // actually acquire the sample. Anything sampling that quickly is going
+ // to be a problem anyway so don't worry about it.
+ collection->next_sample_time += collection->params.sampling_interval;
+ bool success = GetTaskRunnerOnSamplingThread()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&SamplingThread::RecordSampleTask, Unretained(this),
+ collection_id),
+ std::max(collection->next_sample_time - Time::Now(), TimeDelta()));
+ DCHECK(success);
+ return;
+ }
+
+ // Take ownership of |collection| and remove it from the map.
+ std::unique_ptr<CollectionContext> owned_collection =
+ std::move(found->second);
+ size_t count = active_collections_.erase(collection_id);
+ DCHECK_EQ(1U, count);
+
+ // All capturing has completed so finish the collection.
+ FinishCollection(collection);
+}
+
+void StackSamplingProfiler::SamplingThread::ShutdownTask(int add_events) {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ // Holding this lock ensures that any attempt to start another job will
+ // get postponed until |thread_execution_state_| is updated, thus eliminating
+ // the race in starting a new thread while the previous one is exiting.
+ AutoLock lock(thread_execution_state_lock_);
+
+ // If the current count of creation requests doesn't match the passed count
+ // then other tasks have been created since this was posted. Abort shutdown.
+ if (thread_execution_state_add_events_ != add_events)
+ return;
+
+ // There can be no new AddCollectionTasks at this point because creating
+ // those always increments "add events". There may be other requests, like
+ // Remove, but it's okay to schedule the thread to stop once they've been
+ // executed (i.e. "soon").
+ DCHECK(active_collections_.empty());
+ StopSoon();
+
+ // StopSoon will have set the owning sequence (again) so it must be detached
+ // (again) in order for Stop/Start to be called (again) should more work
+ // come in. Holding the |thread_execution_state_lock_| ensures the necessary
+ // happens-after with regard to this detach and future Thread API calls.
+ DetachFromSequence();
+
+ // Set the thread_state variable so the thread will be restarted when new
+ // work comes in. Remove the |thread_execution_state_task_runner_| to avoid
+ // confusion.
+ thread_execution_state_ = EXITING;
+ thread_execution_state_task_runner_ = nullptr;
+ stack_buffer_.reset();
+}
+
+void StackSamplingProfiler::SamplingThread::CleanUp() {
+ DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
+
+ // There should be no collections remaining when the thread stops.
+ DCHECK(active_collections_.empty());
+
+ // Let the parent clean up.
+ Thread::CleanUp();
+}
+
+// StackSamplingProfiler ------------------------------------------------------
+
+// static
+void StackSamplingProfiler::TestAPI::Reset() {
+ SamplingThread::TestAPI::Reset();
+}
+
+// static
+bool StackSamplingProfiler::TestAPI::IsSamplingThreadRunning() {
+ return SamplingThread::GetInstance()->IsRunning();
+}
+
+// static
+void StackSamplingProfiler::TestAPI::DisableIdleShutdown() {
+ SamplingThread::TestAPI::DisableIdleShutdown();
+}
+
+// static
+void StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(
+ bool simulate_intervening_start) {
+ SamplingThread::TestAPI::ShutdownAssumingIdle(simulate_intervening_start);
+}
+
+StackSamplingProfiler::StackSamplingProfiler(
+ const SamplingParams& params,
+ std::unique_ptr<ProfileBuilder> profile_builder,
+ NativeStackSamplerTestDelegate* test_delegate)
+ : StackSamplingProfiler(PlatformThread::CurrentId(),
+ params,
+ std::move(profile_builder),
+ test_delegate) {}
+
+StackSamplingProfiler::StackSamplingProfiler(
+ PlatformThreadId thread_id,
+ const SamplingParams& params,
+ std::unique_ptr<ProfileBuilder> profile_builder,
+ NativeStackSamplerTestDelegate* test_delegate)
+ : thread_id_(thread_id),
+ params_(params),
+ profile_builder_(std::move(profile_builder)),
+ // The event starts "signaled" so code knows it's safe to start thread
+ // and "manual" so that it can be waited in multiple places.
+ profiling_inactive_(kResetPolicy, WaitableEvent::InitialState::SIGNALED),
+ profiler_id_(kNullProfilerId),
+ test_delegate_(test_delegate) {
+ DCHECK(profile_builder_);
+}
+
+StackSamplingProfiler::~StackSamplingProfiler() {
+ // Stop returns immediately but the shutdown runs asynchronously. There is a
+ // non-zero probability that one more sample will be taken after this call
+ // returns.
+ Stop();
+
+ // The behavior of sampling a thread that has exited is undefined and could
+ // cause Bad Things(tm) to occur. The safety model provided by this class is
+ // that an instance of this object is expected to live at least as long as
+ // the thread it is sampling. However, because the sampling is performed
+ // asynchronously by the SamplingThread, there is no way to guarantee this
+ // is true without waiting for it to signal that it has finished.
+ //
+ // The wait time should, at most, be only as long as it takes to collect one
+ // sample (~200us) or none at all if sampling has already completed.
+ ThreadRestrictions::ScopedAllowWait allow_wait;
+ profiling_inactive_.Wait();
+}
+
+void StackSamplingProfiler::Start() {
+ // Multiple calls to Start() for a single StackSamplingProfiler object is not
+ // allowed. If profile_builder_ is nullptr, then Start() has been called
+ // already.
+ DCHECK(profile_builder_);
+
+ std::unique_ptr<NativeStackSampler> native_sampler =
+ NativeStackSampler::Create(thread_id_, test_delegate_);
+
+ if (!native_sampler)
+ return;
+
+ // The IsSignaled() check below requires that the WaitableEvent be manually
+ // reset, to avoid signaling the event in IsSignaled() itself.
+ static_assert(kResetPolicy == WaitableEvent::ResetPolicy::MANUAL,
+ "The reset policy must be set to MANUAL");
+
+ // If a previous profiling phase is still winding down, wait for it to
+ // complete. We can't use task posting for this coordination because the
+ // thread owning the profiler may not have a message loop.
+ if (!profiling_inactive_.IsSignaled())
+ profiling_inactive_.Wait();
+ profiling_inactive_.Reset();
+
+ DCHECK_EQ(kNullProfilerId, profiler_id_);
+ profiler_id_ = SamplingThread::GetInstance()->Add(
+ std::make_unique<SamplingThread::CollectionContext>(
+ thread_id_, params_, &profiling_inactive_, std::move(native_sampler),
+ std::move(profile_builder_)));
+ DCHECK_NE(kNullProfilerId, profiler_id_);
+}
+
+void StackSamplingProfiler::Stop() {
+ SamplingThread::GetInstance()->Remove(profiler_id_);
+ profiler_id_ = kNullProfilerId;
+}
+
+// StackSamplingProfiler::Frame global functions ------------------------------
+
+bool operator==(const StackSamplingProfiler::Module& a,
+ const StackSamplingProfiler::Module& b) {
+ return a.base_address == b.base_address && a.id == b.id &&
+ a.filename == b.filename;
+}
+
+bool operator==(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b) {
+ return a.process_milestones == b.process_milestones && a.frames == b.frames;
+}
+
+bool operator!=(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b) {
+ return !(a == b);
+}
+
+bool operator<(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b) {
+ if (a.process_milestones != b.process_milestones)
+ return a.process_milestones < b.process_milestones;
+
+ return a.frames < b.frames;
+}
+
+bool operator==(const StackSamplingProfiler::Frame& a,
+ const StackSamplingProfiler::Frame& b) {
+ return a.instruction_pointer == b.instruction_pointer &&
+ a.module_index == b.module_index;
+}
+
+bool operator<(const StackSamplingProfiler::Frame& a,
+ const StackSamplingProfiler::Frame& b) {
+ if (a.module_index != b.module_index)
+ return a.module_index < b.module_index;
+
+ return a.instruction_pointer < b.instruction_pointer;
+}
+
+} // namespace base
diff --git a/base/profiler/stack_sampling_profiler.h b/base/profiler/stack_sampling_profiler.h
new file mode 100644
index 0000000000..e43349a8fe
--- /dev/null
+++ b/base/profiler/stack_sampling_profiler.h
@@ -0,0 +1,354 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROFILER_STACK_SAMPLING_PROFILER_H_
+#define BASE_PROFILER_STACK_SAMPLING_PROFILER_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+
+namespace base {
+
+// Identifies an unknown module.
+BASE_EXPORT extern const size_t kUnknownModuleIndex;
+
+class NativeStackSamplerTestDelegate;
+
+// StackSamplingProfiler periodically stops a thread to sample its stack, for
+// the purpose of collecting information about which code paths are
+// executing. This information is used in aggregate by UMA to identify hot
+// and/or janky code paths.
+//
+// Sample StackSamplingProfiler usage:
+//
+// // Create and customize params as desired.
+// base::StackStackSamplingProfiler::SamplingParams params;
+//
+// // To process the profiles, use a custom ProfileBuilder subclass:
+// class SubProfileBuilder :
+// public base::StackSamplingProfiler::ProfileBuilder{...}
+// base::StackSamplingProfiler profiler(base::PlatformThread::CurrentId()),
+// params, std::make_unique<SubProfileBuilder>(...));
+//
+// profiler.Start();
+// // ... work being done on the target thread here ...
+// profiler.Stop(); // optional, stops collection before complete per params
+//
+// The default SamplingParams causes stacks to be recorded in a single profile
+// at a 10Hz interval for a total of 30 seconds. All of these parameters may be
+// altered as desired.
+//
+// When a call stack profile is complete, or the profiler is stopped,
+// ProfileBuilder's OnProfileCompleted function is called from a thread created
+// by the profiler.
+class BASE_EXPORT StackSamplingProfiler {
+ public:
+ // Module represents the module (DLL or exe) corresponding to a stack frame.
+ struct BASE_EXPORT Module {
+ Module();
+ Module(uintptr_t base_address,
+ const std::string& id,
+ const FilePath& filename);
+ ~Module();
+
+ // Points to the base address of the module.
+ uintptr_t base_address;
+
+ // An opaque binary string that uniquely identifies a particular program
+ // version with high probability. This is parsed from headers of the loaded
+ // module.
+ // For binaries generated by GNU tools:
+ // Contents of the .note.gnu.build-id field.
+ // On Windows:
+ // GUID + AGE in the debug image headers of a module.
+ std::string id;
+
+ // The filename of the module.
+ FilePath filename;
+ };
+
+ // InternalModule represents the module (DLL or exe) and its validness state.
+ // Different from Module, it has an additional field "is_valid".
+ //
+ // This struct is only used for sampling data transfer from NativeStackSampler
+ // to ProfileBuilder.
+ struct BASE_EXPORT InternalModule {
+ InternalModule();
+ InternalModule(uintptr_t base_address,
+ const std::string& id,
+ const FilePath& filename);
+ ~InternalModule();
+
+ // Points to the base address of the module.
+ uintptr_t base_address;
+
+ // An opaque binary string that uniquely identifies a particular program
+ // version with high probability. This is parsed from headers of the loaded
+ // module.
+ // For binaries generated by GNU tools:
+ // Contents of the .note.gnu.build-id field.
+ // On Windows:
+ // GUID + AGE in the debug image headers of a module.
+ std::string id;
+
+ // The filename of the module.
+ FilePath filename;
+
+ // The validness of the module.
+ bool is_valid;
+ };
+
+ // Frame represents an individual sampled stack frame with module information.
+ struct BASE_EXPORT Frame {
+ Frame(uintptr_t instruction_pointer, size_t module_index);
+ ~Frame();
+
+ // Default constructor to satisfy IPC macros. Do not use explicitly.
+ Frame();
+
+ // The sampled instruction pointer within the function.
+ uintptr_t instruction_pointer;
+
+ // Index of the module in CallStackProfile::modules. We don't represent
+ // module state directly here to save space.
+ size_t module_index;
+ };
+
+ // InternalFrame represents an individual sampled stack frame with full module
+ // information. This is different from Frame which only contains module index.
+ //
+ // This struct is only used for sampling data transfer from NativeStackSampler
+ // to ProfileBuilder.
+ struct BASE_EXPORT InternalFrame {
+ InternalFrame(uintptr_t instruction_pointer,
+ InternalModule internal_module);
+ ~InternalFrame();
+
+ // The sampled instruction pointer within the function.
+ uintptr_t instruction_pointer;
+
+ // The module information.
+ InternalModule internal_module;
+ };
+
+ // Sample represents a set of stack frames with some extra information.
+ struct BASE_EXPORT Sample {
+ Sample();
+ Sample(const Sample& sample);
+ ~Sample();
+
+ // These constructors are used only during testing.
+ Sample(const Frame& frame);
+ Sample(const std::vector<Frame>& frames);
+
+ // The entire stack frame when the sample is taken.
+ std::vector<Frame> frames;
+
+ // A bit-field indicating which process milestones have passed. This can be
+ // used to tell where in the process lifetime the samples are taken. Just
+ // as a "lifetime" can only move forward, these bits mark the milestones of
+ // the processes life as they occur. Bits can be set but never reset. The
+ // actual definition of the individual bits is left to the user of this
+ // module.
+ uint32_t process_milestones = 0;
+ };
+
+ // CallStackProfile represents a set of samples.
+ struct BASE_EXPORT CallStackProfile {
+ CallStackProfile();
+ CallStackProfile(CallStackProfile&& other);
+ ~CallStackProfile();
+
+ CallStackProfile& operator=(CallStackProfile&& other);
+
+ CallStackProfile CopyForTesting() const;
+
+ std::vector<Module> modules;
+ std::vector<Sample> samples;
+
+ // Duration of this profile.
+ TimeDelta profile_duration;
+
+ // Time between samples.
+ TimeDelta sampling_period;
+
+ private:
+ // Copying is possible but expensive so disallow it except for internal use
+ // (i.e. CopyForTesting); use std::move instead.
+ CallStackProfile(const CallStackProfile& other);
+
+ DISALLOW_ASSIGN(CallStackProfile);
+ };
+
+ // Represents parameters that configure the sampling.
+ struct BASE_EXPORT SamplingParams {
+ // Time to delay before first samples are taken.
+ TimeDelta initial_delay = TimeDelta::FromMilliseconds(0);
+
+ // Number of samples to record per profile.
+ int samples_per_profile = 300;
+
+ // Interval between samples during a sampling profile. This is the desired
+ // duration from the start of one sample to the start of the next sample.
+ TimeDelta sampling_interval = TimeDelta::FromMilliseconds(100);
+ };
+
+ // Testing support. These methods are static beause they interact with the
+ // sampling thread, a singleton used by all StackSamplingProfiler objects.
+ // These methods can only be called by the same thread that started the
+ // sampling.
+ class BASE_EXPORT TestAPI {
+ public:
+ // Resets the internal state to that of a fresh start. This is necessary
+ // so that tests don't inherit state from previous tests.
+ static void Reset();
+
+ // Returns whether the sampling thread is currently running or not.
+ static bool IsSamplingThreadRunning();
+
+ // Disables inherent idle-shutdown behavior.
+ static void DisableIdleShutdown();
+
+ // Initiates an idle shutdown task, as though the idle timer had expired,
+ // causing the thread to exit. There is no "idle" check so this must be
+ // called only when all sampling tasks have completed. This blocks until
+ // the task has been executed, though the actual stopping of the thread
+ // still happens asynchronously. Watch IsSamplingThreadRunning() to know
+ // when the thread has exited. If |simulate_intervening_start| is true then
+ // this method will make it appear to the shutdown task that a new profiler
+ // was started between when the idle-shutdown was initiated and when it
+ // runs.
+ static void PerformSamplingThreadIdleShutdown(
+ bool simulate_intervening_start);
+ };
+
+ // The ProfileBuilder interface allows the user to record profile information
+ // on the fly in whatever format is desired. Functions are invoked by the
+ // profiler on its own thread so must not block or perform expensive
+ // operations.
+ class BASE_EXPORT ProfileBuilder {
+ public:
+ ProfileBuilder() = default;
+ virtual ~ProfileBuilder() = default;
+
+ // Metadata associated with the sample to be saved off.
+ // The code implementing this method must not do anything that could acquire
+ // a mutex, including allocating memory (which includes LOG messages)
+ // because that mutex could be held by a stopped thread, thus resulting in
+ // deadlock.
+ virtual void RecordAnnotations() = 0;
+
+ // Records a new set of internal frames. Invoked when sampling a sample
+ // completes.
+ virtual void OnSampleCompleted(
+ std::vector<InternalFrame> internal_frames) = 0;
+
+ // Finishes the profile construction with |profile_duration| and
+ // |sampling_period|. Invoked when sampling a profile completes.
+ virtual void OnProfileCompleted(TimeDelta profile_duration,
+ TimeDelta sampling_period) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProfileBuilder);
+ };
+
+ // Creates a profiler for the CURRENT thread. An optional |test_delegate| can
+ // be supplied by tests. The caller must ensure that this object gets
+ // destroyed before the current thread exits.
+ StackSamplingProfiler(
+ const SamplingParams& params,
+ std::unique_ptr<ProfileBuilder> profile_builder,
+ NativeStackSamplerTestDelegate* test_delegate = nullptr);
+
+ // Creates a profiler for ANOTHER thread. An optional |test_delegate| can be
+ // supplied by tests.
+ //
+ // IMPORTANT: The caller must ensure that the thread being sampled does not
+ // exit before this object gets destructed or Bad Things(tm) may occur.
+ StackSamplingProfiler(
+ PlatformThreadId thread_id,
+ const SamplingParams& params,
+ std::unique_ptr<ProfileBuilder> profile_builder,
+ NativeStackSamplerTestDelegate* test_delegate = nullptr);
+
+ // Stops any profiling currently taking place before destroying the profiler.
+ // This will block until profile_builder_'s OnProfileCompleted function has
+ // executed if profiling has started but not already finished.
+ ~StackSamplingProfiler();
+
+ // Initializes the profiler and starts sampling. Might block on a
+ // WaitableEvent if this StackSamplingProfiler was previously started and
+ // recently stopped, while the previous profiling phase winds down.
+ void Start();
+
+ // Stops the profiler and any ongoing sampling. This method will return
+ // immediately with the profile_builder_'s OnProfileCompleted function being
+ // run asynchronously. At most one more stack sample will be taken after this
+ // method returns. Calling this function is optional; if not invoked profiling
+ // terminates when all the profiling samples specified in the SamplingParams
+ // are completed or the profiler object is destroyed, whichever occurs first.
+ void Stop();
+
+ private:
+ friend class TestAPI;
+
+ // SamplingThread is a separate thread used to suspend and sample stacks from
+ // the target thread.
+ class SamplingThread;
+
+ // The thread whose stack will be sampled.
+ PlatformThreadId thread_id_;
+
+ const SamplingParams params_;
+
+ // Receives the sampling data and builds a CallStackProfile. The ownership of
+ // this object will be transferred to the sampling thread when thread sampling
+ // starts.
+ std::unique_ptr<ProfileBuilder> profile_builder_;
+
+ // This starts "signaled", is reset when sampling begins, and is signaled
+ // when that sampling is complete and the profile_builder_'s
+ // OnProfileCompleted function has executed.
+ WaitableEvent profiling_inactive_;
+
+ // An ID uniquely identifying this profiler to the sampling thread. This
+ // will be an internal "null" value when no collection has been started.
+ int profiler_id_;
+
+ // Stored until it can be passed to the NativeStackSampler created in Start().
+ NativeStackSamplerTestDelegate* const test_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(StackSamplingProfiler);
+};
+
+// These operators permit types to be compared and used in a map of Samples, as
+// done in tests and by the metrics provider code.
+BASE_EXPORT bool operator==(const StackSamplingProfiler::Module& a,
+ const StackSamplingProfiler::Module& b);
+BASE_EXPORT bool operator==(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b);
+BASE_EXPORT bool operator!=(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b);
+BASE_EXPORT bool operator<(const StackSamplingProfiler::Sample& a,
+ const StackSamplingProfiler::Sample& b);
+BASE_EXPORT bool operator==(const StackSamplingProfiler::Frame& a,
+ const StackSamplingProfiler::Frame& b);
+BASE_EXPORT bool operator<(const StackSamplingProfiler::Frame& a,
+ const StackSamplingProfiler::Frame& b);
+
+} // namespace base
+
+#endif // BASE_PROFILER_STACK_SAMPLING_PROFILER_H_
diff --git a/base/profiler/stack_sampling_profiler_unittest.cc b/base/profiler/stack_sampling_profiler_unittest.cc
new file mode 100644
index 0000000000..b0f883624f
--- /dev/null
+++ b/base/profiler/stack_sampling_profiler_unittest.cc
@@ -0,0 +1,1522 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/native_library.h"
+#include "base/path_service.h"
+#include "base/profiler/native_stack_sampler.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "base/run_loop.h"
+#include "base/scoped_native_library.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/bind_test_util.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include <intrin.h>
+#include <malloc.h>
+#include <windows.h>
+#else
+#include <alloca.h>
+#endif
+
+// STACK_SAMPLING_PROFILER_SUPPORTED is used to conditionally enable the tests
+// below for supported platforms (currently Win x64 and Mac x64).
+#if defined(_WIN64) || (defined(OS_MACOSX) && !defined(OS_IOS))
+#define STACK_SAMPLING_PROFILER_SUPPORTED 1
+#endif
+
+#if defined(OS_WIN)
+#pragma intrinsic(_ReturnAddress)
+#endif
+
+namespace base {
+
+#if defined(STACK_SAMPLING_PROFILER_SUPPORTED)
+#define PROFILER_TEST_F(TestClass, TestName) TEST_F(TestClass, TestName)
+#else
+#define PROFILER_TEST_F(TestClass, TestName) \
+ TEST_F(TestClass, DISABLED_##TestName)
+#endif
+
+using SamplingParams = StackSamplingProfiler::SamplingParams;
+using Frame = StackSamplingProfiler::Frame;
+using Frames = std::vector<Frame>;
+using InternalFrame = StackSamplingProfiler::InternalFrame;
+using InternalFrames = std::vector<InternalFrame>;
+using InternalFrameSets = std::vector<std::vector<InternalFrame>>;
+using Module = StackSamplingProfiler::Module;
+using InternalModule = StackSamplingProfiler::InternalModule;
+using Sample = StackSamplingProfiler::Sample;
+
+namespace {
+
+// Configuration for the frames that appear on the stack.
+struct StackConfiguration {
+ enum Config { NORMAL, WITH_ALLOCA, WITH_OTHER_LIBRARY };
+
+ explicit StackConfiguration(Config config)
+ : StackConfiguration(config, nullptr) {
+ EXPECT_NE(config, WITH_OTHER_LIBRARY);
+ }
+
+ StackConfiguration(Config config, NativeLibrary library)
+ : config(config), library(library) {
+ EXPECT_TRUE(config != WITH_OTHER_LIBRARY || library);
+ }
+
+ Config config;
+
+ // Only used if config == WITH_OTHER_LIBRARY.
+ NativeLibrary library;
+};
+
+// Signature for a target function that is expected to appear in the stack. See
+// SignalAndWaitUntilSignaled() below. The return value should be a program
+// counter pointer near the end of the function.
+using TargetFunction = const void* (*)(WaitableEvent*,
+ WaitableEvent*,
+ const StackConfiguration*);
+
+// A thread to target for profiling, whose stack is guaranteed to contain
+// SignalAndWaitUntilSignaled() when coordinated with the main thread.
+class TargetThread : public PlatformThread::Delegate {
+ public:
+ explicit TargetThread(const StackConfiguration& stack_config);
+
+ // PlatformThread::Delegate:
+ void ThreadMain() override;
+
+ // Waits for the thread to have started and be executing in
+ // SignalAndWaitUntilSignaled().
+ void WaitForThreadStart();
+
+ // Allows the thread to return from SignalAndWaitUntilSignaled() and finish
+ // execution.
+ void SignalThreadToFinish();
+
+ // This function is guaranteed to be executing between calls to
+ // WaitForThreadStart() and SignalThreadToFinish() when invoked with
+ // |thread_started_event_| and |finish_event_|. Returns a program counter
+ // value near the end of the function. May be invoked with null WaitableEvents
+ // to just return the program counter.
+ //
+ // This function is static so that we can get a straightforward address
+ // for it in one of the tests below, rather than dealing with the complexity
+ // of a member function pointer representation.
+ static const void* SignalAndWaitUntilSignaled(
+ WaitableEvent* thread_started_event,
+ WaitableEvent* finish_event,
+ const StackConfiguration* stack_config);
+
+ // Calls into SignalAndWaitUntilSignaled() after allocating memory on the
+ // stack with alloca.
+ static const void* CallWithAlloca(WaitableEvent* thread_started_event,
+ WaitableEvent* finish_event,
+ const StackConfiguration* stack_config);
+
+ // Calls into SignalAndWaitUntilSignaled() via a function in
+ // base_profiler_test_support_library.
+ static const void* CallThroughOtherLibrary(
+ WaitableEvent* thread_started_event,
+ WaitableEvent* finish_event,
+ const StackConfiguration* stack_config);
+
+ PlatformThreadId id() const { return id_; }
+
+ private:
+ struct TargetFunctionArgs {
+ WaitableEvent* thread_started_event;
+ WaitableEvent* finish_event;
+ const StackConfiguration* stack_config;
+ };
+
+ // Callback function to be provided when calling through the other library.
+ static void OtherLibraryCallback(void* arg);
+
+ // Returns the current program counter, or a value very close to it.
+ static const void* GetProgramCounter();
+
+ WaitableEvent thread_started_event_;
+ WaitableEvent finish_event_;
+ PlatformThreadId id_;
+ const StackConfiguration stack_config_;
+
+ DISALLOW_COPY_AND_ASSIGN(TargetThread);
+};
+
+TargetThread::TargetThread(const StackConfiguration& stack_config)
+ : thread_started_event_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ finish_event_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ id_(0),
+ stack_config_(stack_config) {}
+
+void TargetThread::ThreadMain() {
+ id_ = PlatformThread::CurrentId();
+ switch (stack_config_.config) {
+ case StackConfiguration::NORMAL:
+ SignalAndWaitUntilSignaled(&thread_started_event_, &finish_event_,
+ &stack_config_);
+ break;
+
+ case StackConfiguration::WITH_ALLOCA:
+ CallWithAlloca(&thread_started_event_, &finish_event_, &stack_config_);
+ break;
+
+ case StackConfiguration::WITH_OTHER_LIBRARY:
+ CallThroughOtherLibrary(&thread_started_event_, &finish_event_,
+ &stack_config_);
+ break;
+ }
+}
+
+void TargetThread::WaitForThreadStart() {
+ thread_started_event_.Wait();
+}
+
+void TargetThread::SignalThreadToFinish() {
+ finish_event_.Signal();
+}
+
+// static
+// Disable inlining for this function so that it gets its own stack frame.
+NOINLINE const void* TargetThread::SignalAndWaitUntilSignaled(
+ WaitableEvent* thread_started_event,
+ WaitableEvent* finish_event,
+ const StackConfiguration* stack_config) {
+ if (thread_started_event && finish_event) {
+ thread_started_event->Signal();
+ finish_event->Wait();
+ }
+
+ // Volatile to prevent a tail call to GetProgramCounter().
+ const void* volatile program_counter = GetProgramCounter();
+ return program_counter;
+}
+
+// static
+// Disable inlining for this function so that it gets its own stack frame.
+NOINLINE const void* TargetThread::CallWithAlloca(
+ WaitableEvent* thread_started_event,
+ WaitableEvent* finish_event,
+ const StackConfiguration* stack_config) {
+ const size_t alloca_size = 100;
+ // Memset to 0 to generate a clean failure.
+ std::memset(alloca(alloca_size), 0, alloca_size);
+
+ SignalAndWaitUntilSignaled(thread_started_event, finish_event, stack_config);
+
+ // Volatile to prevent a tail call to GetProgramCounter().
+ const void* volatile program_counter = GetProgramCounter();
+ return program_counter;
+}
+
+// static
+NOINLINE const void* TargetThread::CallThroughOtherLibrary(
+ WaitableEvent* thread_started_event,
+ WaitableEvent* finish_event,
+ const StackConfiguration* stack_config) {
+ if (stack_config) {
+ // A function whose arguments are a function accepting void*, and a void*.
+ using InvokeCallbackFunction = void (*)(void (*)(void*), void*);
+ EXPECT_TRUE(stack_config->library);
+ InvokeCallbackFunction function = reinterpret_cast<InvokeCallbackFunction>(
+ GetFunctionPointerFromNativeLibrary(stack_config->library,
+ "InvokeCallbackFunction"));
+ EXPECT_TRUE(function);
+
+ TargetFunctionArgs args = {thread_started_event, finish_event,
+ stack_config};
+ (*function)(&OtherLibraryCallback, &args);
+ }
+
+ // Volatile to prevent a tail call to GetProgramCounter().
+ const void* volatile program_counter = GetProgramCounter();
+ return program_counter;
+}
+
+// static
+void TargetThread::OtherLibraryCallback(void* arg) {
+ const TargetFunctionArgs* args = static_cast<TargetFunctionArgs*>(arg);
+ SignalAndWaitUntilSignaled(args->thread_started_event, args->finish_event,
+ args->stack_config);
+ // Prevent tail call.
+ volatile int i = 0;
+ ALLOW_UNUSED_LOCAL(i);
+}
+
+// static
+// Disable inlining for this function so that it gets its own stack frame.
+NOINLINE const void* TargetThread::GetProgramCounter() {
+#if defined(OS_WIN)
+ return _ReturnAddress();
+#else
+ return __builtin_return_address(0);
+#endif
+}
+
+// Profile consists of a set of internal frame sets and other sampling
+// information.
+struct Profile {
+ Profile() = default;
+ Profile(Profile&& other) = default;
+ Profile(const InternalFrameSets& frame_sets,
+ int annotation_count,
+ TimeDelta profile_duration,
+ TimeDelta sampling_period);
+
+ ~Profile() = default;
+
+ Profile& operator=(Profile&& other) = default;
+
+ // The collected internal frame sets.
+ InternalFrameSets frame_sets;
+
+ // The number of invocations of RecordAnnotations().
+ int annotation_count;
+
+ // Duration of this profile.
+ TimeDelta profile_duration;
+
+ // Time between samples.
+ TimeDelta sampling_period;
+};
+
+Profile::Profile(const InternalFrameSets& frame_sets,
+ int annotation_count,
+ TimeDelta profile_duration,
+ TimeDelta sampling_period)
+ : frame_sets(frame_sets),
+ annotation_count(annotation_count),
+ profile_duration(profile_duration),
+ sampling_period(sampling_period) {}
+
+// The callback type used to collect a profile. The passed Profile is move-only.
+// Other threads, including the UI thread, may block on callback completion so
+// this should run as quickly as possible.
+using ProfileCompletedCallback = Callback<void(Profile)>;
+
+// TestProfileBuilder collects internal frames produced by the profiler.
+class TestProfileBuilder : public StackSamplingProfiler::ProfileBuilder {
+ public:
+ TestProfileBuilder(const ProfileCompletedCallback& callback);
+
+ ~TestProfileBuilder() override;
+
+ // StackSamplingProfiler::ProfileBuilder:
+ void RecordAnnotations() override;
+ void OnSampleCompleted(InternalFrames internal_frames) override;
+ void OnProfileCompleted(TimeDelta profile_duration,
+ TimeDelta sampling_period) override;
+
+ private:
+ // The sets of internal frames recorded.
+ std::vector<InternalFrames> frame_sets_;
+
+ // The number of invocations of RecordAnnotations().
+ int annotation_count_ = 0;
+
+ // Callback made when sampling a profile completes.
+ const ProfileCompletedCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestProfileBuilder);
+};
+
+TestProfileBuilder::TestProfileBuilder(const ProfileCompletedCallback& callback)
+ : callback_(callback) {}
+
+TestProfileBuilder::~TestProfileBuilder() = default;
+
+void TestProfileBuilder::RecordAnnotations() {
+ ++annotation_count_;
+}
+
+void TestProfileBuilder::OnSampleCompleted(InternalFrames internal_frames) {
+ frame_sets_.push_back(std::move(internal_frames));
+}
+
+void TestProfileBuilder::OnProfileCompleted(TimeDelta profile_duration,
+ TimeDelta sampling_period) {
+ callback_.Run(Profile(frame_sets_, annotation_count_, profile_duration,
+ sampling_period));
+}
+
+// Loads the other library, which defines a function to be called in the
+// WITH_OTHER_LIBRARY configuration.
+NativeLibrary LoadOtherLibrary() {
+ // The lambda gymnastics works around the fact that we can't use ASSERT_*
+ // macros in a function returning non-null.
+ const auto load = [](NativeLibrary* library) {
+ FilePath other_library_path;
+ ASSERT_TRUE(PathService::Get(DIR_EXE, &other_library_path));
+ other_library_path = other_library_path.AppendASCII(
+ GetNativeLibraryName("base_profiler_test_support_library"));
+ NativeLibraryLoadError load_error;
+ *library = LoadNativeLibrary(other_library_path, &load_error);
+ ASSERT_TRUE(*library) << "error loading " << other_library_path.value()
+ << ": " << load_error.ToString();
+ };
+
+ NativeLibrary library = nullptr;
+ load(&library);
+ return library;
+}
+
+// Unloads |library| and returns when it has completed unloading. Unloading a
+// library is asynchronous on Windows, so simply calling UnloadNativeLibrary()
+// is insufficient to ensure it's been unloaded.
+void SynchronousUnloadNativeLibrary(NativeLibrary library) {
+ UnloadNativeLibrary(library);
+#if defined(OS_WIN)
+ // NativeLibrary is a typedef for HMODULE, which is actually the base address
+ // of the module.
+ uintptr_t module_base_address = reinterpret_cast<uintptr_t>(library);
+ HMODULE module_handle;
+ // Keep trying to get the module handle until the call fails.
+ while (::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCTSTR>(module_base_address),
+ &module_handle) ||
+ ::GetLastError() != ERROR_MOD_NOT_FOUND) {
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
+ }
+#elif defined(OS_MACOSX)
+// Unloading a library on the Mac is synchronous.
+#else
+ NOTIMPLEMENTED();
+#endif
+}
+
+// Executes the function with the target thread running and executing within
+// SignalAndWaitUntilSignaled(). Performs all necessary target thread startup
+// and shutdown work before and afterward.
+template <class Function>
+void WithTargetThread(Function function,
+ const StackConfiguration& stack_config) {
+ TargetThread target_thread(stack_config);
+ PlatformThreadHandle target_thread_handle;
+ EXPECT_TRUE(PlatformThread::Create(0, &target_thread, &target_thread_handle));
+
+ target_thread.WaitForThreadStart();
+
+ function(target_thread.id());
+
+ target_thread.SignalThreadToFinish();
+
+ PlatformThread::Join(target_thread_handle);
+}
+
+template <class Function>
+void WithTargetThread(Function function) {
+ WithTargetThread(function, StackConfiguration(StackConfiguration::NORMAL));
+}
+
+struct TestProfilerInfo {
+ TestProfilerInfo(PlatformThreadId thread_id,
+ const SamplingParams& params,
+ NativeStackSamplerTestDelegate* delegate = nullptr)
+ : completed(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ profiler(thread_id,
+ params,
+ std::make_unique<TestProfileBuilder>(
+ BindLambdaForTesting([this](Profile result_profile) {
+ profile = std::move(result_profile);
+ completed.Signal();
+ })),
+ delegate) {}
+
+ // The order here is important to ensure objects being referenced don't get
+ // destructed until after the objects referencing them.
+ Profile profile;
+ WaitableEvent completed;
+ StackSamplingProfiler profiler;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestProfilerInfo);
+};
+
+// Creates multiple profilers based on a vector of parameters.
+std::vector<std::unique_ptr<TestProfilerInfo>> CreateProfilers(
+ PlatformThreadId target_thread_id,
+ const std::vector<SamplingParams>& params) {
+ DCHECK(!params.empty());
+
+ std::vector<std::unique_ptr<TestProfilerInfo>> profilers;
+ for (size_t i = 0; i < params.size(); ++i) {
+ profilers.push_back(
+ std::make_unique<TestProfilerInfo>(target_thread_id, params[i]));
+ }
+
+ return profilers;
+}
+
+// Captures internal frames as specified by |params| on the TargetThread, and
+// returns them. Waits up to |profiler_wait_time| for the profiler to complete.
+InternalFrameSets CaptureFrameSets(const SamplingParams& params,
+ TimeDelta profiler_wait_time) {
+ InternalFrameSets frame_sets;
+ WithTargetThread([&params, &frame_sets,
+ profiler_wait_time](PlatformThreadId target_thread_id) {
+ TestProfilerInfo info(target_thread_id, params);
+ info.profiler.Start();
+ info.completed.TimedWait(profiler_wait_time);
+ info.profiler.Stop();
+ info.completed.Wait();
+ frame_sets = std::move(info.profile.frame_sets);
+ });
+
+ return frame_sets;
+}
+
+// Waits for one of multiple samplings to complete.
+size_t WaitForSamplingComplete(
+ const std::vector<std::unique_ptr<TestProfilerInfo>>& infos) {
+ // Map unique_ptrs to something that WaitMany can accept.
+ std::vector<WaitableEvent*> sampling_completed_rawptrs(infos.size());
+ std::transform(infos.begin(), infos.end(), sampling_completed_rawptrs.begin(),
+ [](const std::unique_ptr<TestProfilerInfo>& info) {
+ return &info.get()->completed;
+ });
+ // Wait for one profiler to finish.
+ return WaitableEvent::WaitMany(sampling_completed_rawptrs.data(),
+ sampling_completed_rawptrs.size());
+}
+
+// If this executable was linked with /INCREMENTAL (the default for non-official
+// debug and release builds on Windows), function addresses do not correspond to
+// function code itself, but instead to instructions in the Incremental Link
+// Table that jump to the functions. Checks for a jump instruction and if
+// present does a little decompilation to find the function's actual starting
+// address.
+const void* MaybeFixupFunctionAddressForILT(const void* function_address) {
+#if defined(_WIN64)
+ const unsigned char* opcode =
+ reinterpret_cast<const unsigned char*>(function_address);
+ if (*opcode == 0xe9) {
+ // This is a relative jump instruction. Assume we're in the ILT and compute
+ // the function start address from the instruction offset.
+ const int32_t* offset = reinterpret_cast<const int32_t*>(opcode + 1);
+ const unsigned char* next_instruction =
+ reinterpret_cast<const unsigned char*>(offset + 1);
+ return next_instruction + *offset;
+ }
+#endif
+ return function_address;
+}
+
+// Searches through the frames in |sample|, returning an iterator to the first
+// frame that has an instruction pointer within |target_function|. Returns
+// sample.end() if no such frames are found.
+InternalFrames::const_iterator FindFirstFrameWithinFunction(
+ const InternalFrames& frames,
+ TargetFunction target_function) {
+ uintptr_t function_start =
+ reinterpret_cast<uintptr_t>(MaybeFixupFunctionAddressForILT(
+ reinterpret_cast<const void*>(target_function)));
+ uintptr_t function_end =
+ reinterpret_cast<uintptr_t>(target_function(nullptr, nullptr, nullptr));
+ for (auto it = frames.begin(); it != frames.end(); ++it) {
+ if (it->instruction_pointer >= function_start &&
+ it->instruction_pointer <= function_end) {
+ return it;
+ }
+ }
+ return frames.end();
+}
+
+// Formats a sample into a string that can be output for test diagnostics.
+std::string FormatSampleForDiagnosticOutput(const InternalFrames& frames) {
+ std::string output;
+ for (const auto& frame : frames) {
+ output += StringPrintf(
+ "0x%p %s\n", reinterpret_cast<const void*>(frame.instruction_pointer),
+ frame.internal_module.filename.AsUTF8Unsafe().c_str());
+ }
+ return output;
+}
+
+// Returns a duration that is longer than the test timeout. We would use
+// TimeDelta::Max() but https://crbug.com/465948.
+TimeDelta AVeryLongTimeDelta() {
+ return TimeDelta::FromDays(1);
+}
+
+// Tests the scenario where the library is unloaded after copying the stack, but
+// before walking it. If |wait_until_unloaded| is true, ensures that the
+// asynchronous library loading has completed before walking the stack. If
+// false, the unloading may still be occurring during the stack walk.
+void TestLibraryUnload(bool wait_until_unloaded) {
+ // Test delegate that supports intervening between the copying of the stack
+ // and the walking of the stack.
+ class StackCopiedSignaler : public NativeStackSamplerTestDelegate {
+ public:
+ StackCopiedSignaler(WaitableEvent* stack_copied,
+ WaitableEvent* start_stack_walk,
+ bool wait_to_walk_stack)
+ : stack_copied_(stack_copied),
+ start_stack_walk_(start_stack_walk),
+ wait_to_walk_stack_(wait_to_walk_stack) {}
+
+ void OnPreStackWalk() override {
+ stack_copied_->Signal();
+ if (wait_to_walk_stack_)
+ start_stack_walk_->Wait();
+ }
+
+ private:
+ WaitableEvent* const stack_copied_;
+ WaitableEvent* const start_stack_walk_;
+ const bool wait_to_walk_stack_;
+ };
+
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 1;
+
+ NativeLibrary other_library = LoadOtherLibrary();
+ TargetThread target_thread(StackConfiguration(
+ StackConfiguration::WITH_OTHER_LIBRARY, other_library));
+
+ PlatformThreadHandle target_thread_handle;
+ EXPECT_TRUE(PlatformThread::Create(0, &target_thread, &target_thread_handle));
+
+ target_thread.WaitForThreadStart();
+
+ WaitableEvent sampling_thread_completed(
+ WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ Profile profile;
+
+ WaitableEvent stack_copied(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent start_stack_walk(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ StackCopiedSignaler test_delegate(&stack_copied, &start_stack_walk,
+ wait_until_unloaded);
+ StackSamplingProfiler profiler(
+ target_thread.id(), params,
+ std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
+ [&profile, &sampling_thread_completed](Profile result_profile) {
+ profile = std::move(result_profile);
+ sampling_thread_completed.Signal();
+ })),
+ &test_delegate);
+
+ profiler.Start();
+
+ // Wait for the stack to be copied and the target thread to be resumed.
+ stack_copied.Wait();
+
+ // Cause the target thread to finish, so that it's no longer executing code in
+ // the library we're about to unload.
+ target_thread.SignalThreadToFinish();
+ PlatformThread::Join(target_thread_handle);
+
+ // Unload the library now that it's not being used.
+ if (wait_until_unloaded)
+ SynchronousUnloadNativeLibrary(other_library);
+ else
+ UnloadNativeLibrary(other_library);
+
+ // Let the stack walk commence after unloading the library, if we're waiting
+ // on that event.
+ start_stack_walk.Signal();
+
+ // Wait for the sampling thread to complete and fill out |profile|.
+ sampling_thread_completed.Wait();
+
+ // Look up the frames.
+ ASSERT_EQ(1u, profile.frame_sets.size());
+ const InternalFrames& frames = profile.frame_sets[0];
+
+ // Check that the stack contains a frame for
+ // TargetThread::SignalAndWaitUntilSignaled().
+ InternalFrames::const_iterator end_frame = FindFirstFrameWithinFunction(
+ frames, &TargetThread::SignalAndWaitUntilSignaled);
+ ASSERT_TRUE(end_frame != frames.end())
+ << "Function at "
+ << MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
+ &TargetThread::SignalAndWaitUntilSignaled))
+ << " was not found in stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+
+ if (wait_until_unloaded) {
+ // The stack should look like this, resulting one frame after
+ // SignalAndWaitUntilSignaled. The frame in the now-unloaded library is
+ // not recorded since we can't get module information.
+ //
+ // ... WaitableEvent and system frames ...
+ // TargetThread::SignalAndWaitUntilSignaled
+ // TargetThread::OtherLibraryCallback
+ EXPECT_EQ(2, frames.end() - end_frame)
+ << "Stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+ } else {
+ // We didn't wait for the asynchronous unloading to complete, so the results
+ // are non-deterministic: if the library finished unloading we should have
+ // the same stack as |wait_until_unloaded|, if not we should have the full
+ // stack. The important thing is that we should not crash.
+
+ if (frames.end() - end_frame == 2) {
+ // This is the same case as |wait_until_unloaded|.
+ return;
+ }
+
+ // Check that the stack contains a frame for
+ // TargetThread::CallThroughOtherLibrary().
+ InternalFrames::const_iterator other_library_frame =
+ FindFirstFrameWithinFunction(frames,
+ &TargetThread::CallThroughOtherLibrary);
+ ASSERT_TRUE(other_library_frame != frames.end())
+ << "Function at "
+ << MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
+ &TargetThread::CallThroughOtherLibrary))
+ << " was not found in stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+
+ // The stack should look like this, resulting in three frames between
+ // SignalAndWaitUntilSignaled and CallThroughOtherLibrary:
+ //
+ // ... WaitableEvent and system frames ...
+ // TargetThread::SignalAndWaitUntilSignaled
+ // TargetThread::OtherLibraryCallback
+ // InvokeCallbackFunction (in other library)
+ // TargetThread::CallThroughOtherLibrary
+ EXPECT_EQ(3, other_library_frame - end_frame)
+ << "Stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+ }
+}
+
+// Provide a suitable (and clean) environment for the tests below. All tests
+// must use this class to ensure that proper clean-up is done and thus be
+// usable in a later test.
+class StackSamplingProfilerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ // The idle-shutdown time is too long for convenient (and accurate) testing.
+ // That behavior is checked instead by artificially triggering it through
+ // the TestAPI.
+ StackSamplingProfiler::TestAPI::DisableIdleShutdown();
+ }
+
+ void TearDown() override {
+ // Be a good citizen and clean up after ourselves. This also re-enables the
+ // idle-shutdown behavior.
+ StackSamplingProfiler::TestAPI::Reset();
+ }
+};
+
+} // namespace
+
+// Checks that the basic expected information is present in sampled internal
+// frames.
+//
+// macOS ASAN is not yet supported - crbug.com/718628.
+#if !(defined(ADDRESS_SANITIZER) && defined(OS_MACOSX))
+#define MAYBE_Basic Basic
+#else
+#define MAYBE_Basic DISABLED_Basic
+#endif
+PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_Basic) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 1;
+
+ InternalFrameSets frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
+
+ // Check that the size of the frame sets are correct.
+ ASSERT_EQ(1u, frame_sets.size());
+ const InternalFrames& frames = frame_sets[0];
+
+ // Check that all the modules are valid.
+ for (const auto& frame : frames)
+ EXPECT_TRUE(frame.internal_module.is_valid);
+
+ // Check that the stack contains a frame for
+ // TargetThread::SignalAndWaitUntilSignaled().
+ InternalFrames::const_iterator loc = FindFirstFrameWithinFunction(
+ frames, &TargetThread::SignalAndWaitUntilSignaled);
+ ASSERT_TRUE(loc != frames.end())
+ << "Function at "
+ << MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
+ &TargetThread::SignalAndWaitUntilSignaled))
+ << " was not found in stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+}
+
+// Checks that the profiler handles stacks containing dynamically-allocated
+// stack memory.
+// macOS ASAN is not yet supported - crbug.com/718628.
+#if !(defined(ADDRESS_SANITIZER) && defined(OS_MACOSX))
+#define MAYBE_Alloca Alloca
+#else
+#define MAYBE_Alloca DISABLED_Alloca
+#endif
+PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_Alloca) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 1;
+
+ Profile profile;
+ WithTargetThread(
+ [&params, &profile](PlatformThreadId target_thread_id) {
+ WaitableEvent sampling_thread_completed(
+ WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ StackSamplingProfiler profiler(
+ target_thread_id, params,
+ std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
+ [&profile, &sampling_thread_completed](Profile result_profile) {
+ profile = std::move(result_profile);
+ sampling_thread_completed.Signal();
+ })));
+ profiler.Start();
+ sampling_thread_completed.Wait();
+ },
+ StackConfiguration(StackConfiguration::WITH_ALLOCA));
+
+ // Look up the frames.
+ ASSERT_EQ(1u, profile.frame_sets.size());
+ const InternalFrames& frames = profile.frame_sets[0];
+
+ // Check that the stack contains a frame for
+ // TargetThread::SignalAndWaitUntilSignaled().
+ InternalFrames::const_iterator end_frame = FindFirstFrameWithinFunction(
+ frames, &TargetThread::SignalAndWaitUntilSignaled);
+ ASSERT_TRUE(end_frame != frames.end())
+ << "Function at "
+ << MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
+ &TargetThread::SignalAndWaitUntilSignaled))
+ << " was not found in stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+
+ // Check that the stack contains a frame for TargetThread::CallWithAlloca().
+ InternalFrames::const_iterator alloca_frame =
+ FindFirstFrameWithinFunction(frames, &TargetThread::CallWithAlloca);
+ ASSERT_TRUE(alloca_frame != frames.end())
+ << "Function at "
+ << MaybeFixupFunctionAddressForILT(
+ reinterpret_cast<const void*>(&TargetThread::CallWithAlloca))
+ << " was not found in stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+
+ // These frames should be adjacent on the stack.
+ EXPECT_EQ(1, alloca_frame - end_frame)
+ << "Stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+}
+
+// Checks that a profiler can stop/destruct without ever having started.
+PROFILER_TEST_F(StackSamplingProfilerTest, StopWithoutStarting) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 1;
+
+ Profile profile;
+ WaitableEvent sampling_completed(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ StackSamplingProfiler profiler(
+ target_thread_id, params,
+ std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
+ [&profile, &sampling_completed](Profile result_profile) {
+ profile = std::move(result_profile);
+ sampling_completed.Signal();
+ })));
+
+ profiler.Stop(); // Constructed but never started.
+ EXPECT_FALSE(sampling_completed.IsSignaled());
+ });
+}
+
+// Checks that its okay to stop a profiler before it finishes even when the
+// sampling thread continues to run.
+PROFILER_TEST_F(StackSamplingProfilerTest, StopSafely) {
+ // Test delegate that counts samples.
+ class SampleRecordedCounter : public NativeStackSamplerTestDelegate {
+ public:
+ SampleRecordedCounter() = default;
+
+ void OnPreStackWalk() override {
+ AutoLock lock(lock_);
+ ++count_;
+ }
+
+ size_t Get() {
+ AutoLock lock(lock_);
+ return count_;
+ }
+
+ private:
+ Lock lock_;
+ size_t count_ = 0;
+ };
+
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ SamplingParams params[2];
+
+ // Providing an initial delay makes it more likely that both will be
+ // scheduled before either starts to run. Once started, samples will
+ // run ordered by their scheduled, interleaved times regardless of
+ // whatever interval the thread wakes up.
+ params[0].initial_delay = TimeDelta::FromMilliseconds(10);
+ params[0].sampling_interval = TimeDelta::FromMilliseconds(1);
+ params[0].samples_per_profile = 100000;
+
+ params[1].initial_delay = TimeDelta::FromMilliseconds(10);
+ params[1].sampling_interval = TimeDelta::FromMilliseconds(1);
+ params[1].samples_per_profile = 100000;
+
+ SampleRecordedCounter samples_recorded[size(params)];
+
+ TestProfilerInfo profiler_info0(target_thread_id, params[0],
+ &samples_recorded[0]);
+ TestProfilerInfo profiler_info1(target_thread_id, params[1],
+ &samples_recorded[1]);
+
+ profiler_info0.profiler.Start();
+ profiler_info1.profiler.Start();
+
+ // Wait for both to start accumulating samples. Using a WaitableEvent is
+ // possible but gets complicated later on because there's no way of knowing
+ // if 0 or 1 additional sample will be taken after Stop() and thus no way
+ // of knowing how many Wait() calls to make on it.
+ while (samples_recorded[0].Get() == 0 || samples_recorded[1].Get() == 0)
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
+
+ // Ensure that the first sampler can be safely stopped while the second
+ // continues to run. The stopped first profiler will still have a
+ // RecordSampleTask pending that will do nothing when executed because the
+ // collection will have been removed by Stop().
+ profiler_info0.profiler.Stop();
+ profiler_info0.completed.Wait();
+ size_t count0 = samples_recorded[0].Get();
+ size_t count1 = samples_recorded[1].Get();
+
+ // Waiting for the second sampler to collect a couple samples ensures that
+ // the pending RecordSampleTask for the first has executed because tasks are
+ // always ordered by their next scheduled time.
+ while (samples_recorded[1].Get() < count1 + 2)
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
+
+ // Ensure that the first profiler didn't do anything since it was stopped.
+ EXPECT_EQ(count0, samples_recorded[0].Get());
+ });
+}
+
+// Checks that no internal frames are captured if the profiling is stopped
+// during the initial delay.
+PROFILER_TEST_F(StackSamplingProfilerTest, StopDuringInitialDelay) {
+ SamplingParams params;
+ params.initial_delay = TimeDelta::FromSeconds(60);
+
+ InternalFrameSets frame_sets =
+ CaptureFrameSets(params, TimeDelta::FromMilliseconds(0));
+
+ EXPECT_TRUE(frame_sets.empty());
+}
+
+// Checks that tasks can be stopped before completion and incomplete internal
+// frames are captured.
+PROFILER_TEST_F(StackSamplingProfilerTest, StopDuringInterSampleInterval) {
+ // Test delegate that counts samples.
+ class SampleRecordedEvent : public NativeStackSamplerTestDelegate {
+ public:
+ SampleRecordedEvent()
+ : sample_recorded_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+ void OnPreStackWalk() override { sample_recorded_.Signal(); }
+
+ void WaitForSample() { sample_recorded_.Wait(); }
+
+ private:
+ WaitableEvent sample_recorded_;
+ };
+
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ SamplingParams params;
+
+ params.sampling_interval = AVeryLongTimeDelta();
+ params.samples_per_profile = 2;
+
+ SampleRecordedEvent samples_recorded;
+ TestProfilerInfo profiler_info(target_thread_id, params, &samples_recorded);
+
+ profiler_info.profiler.Start();
+
+ // Wait for profiler to start accumulating samples.
+ samples_recorded.WaitForSample();
+
+ // Ensure that it can stop safely.
+ profiler_info.profiler.Stop();
+ profiler_info.completed.Wait();
+
+ EXPECT_EQ(1u, profiler_info.profile.frame_sets.size());
+ });
+}
+
+// Checks that we can destroy the profiler while profiling.
+PROFILER_TEST_F(StackSamplingProfilerTest, DestroyProfilerWhileProfiling) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(10);
+
+ Profile profile;
+ WithTargetThread([&params, &profile](PlatformThreadId target_thread_id) {
+ std::unique_ptr<StackSamplingProfiler> profiler;
+ auto profile_builder = std::make_unique<TestProfileBuilder>(
+ BindLambdaForTesting([&profile](Profile result_profile) {
+ profile = std::move(result_profile);
+ }));
+ profiler.reset(new StackSamplingProfiler(target_thread_id, params,
+ std::move(profile_builder)));
+ profiler->Start();
+ profiler.reset();
+
+ // Wait longer than a sample interval to catch any use-after-free actions by
+ // the profiler thread.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(50));
+ });
+}
+
+// Checks that the different profilers may be run.
+PROFILER_TEST_F(StackSamplingProfilerTest, CanRunMultipleProfilers) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 1;
+
+ InternalFrameSets frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
+ ASSERT_EQ(1u, frame_sets.size());
+
+ frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
+ ASSERT_EQ(1u, frame_sets.size());
+}
+
+// Checks that a sampler can be started while another is running.
+PROFILER_TEST_F(StackSamplingProfilerTest, MultipleStart) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ std::vector<SamplingParams> params(2);
+
+ params[0].initial_delay = AVeryLongTimeDelta();
+ params[0].samples_per_profile = 1;
+
+ params[1].sampling_interval = TimeDelta::FromMilliseconds(1);
+ params[1].samples_per_profile = 1;
+
+ std::vector<std::unique_ptr<TestProfilerInfo>> profiler_infos =
+ CreateProfilers(target_thread_id, params);
+
+ profiler_infos[0]->profiler.Start();
+ profiler_infos[1]->profiler.Start();
+ profiler_infos[1]->completed.Wait();
+ EXPECT_EQ(1u, profiler_infos[1]->profile.frame_sets.size());
+ });
+}
+
+// Checks that the profile duration and the sampling interval are calculated
+// correctly. Also checks that RecordAnnotations() is invoked each time a sample
+// is recorded.
+PROFILER_TEST_F(StackSamplingProfilerTest, ProfileGeneralInfo) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(1);
+ params.samples_per_profile = 3;
+
+ TestProfilerInfo profiler_info(target_thread_id, params);
+
+ profiler_info.profiler.Start();
+ profiler_info.completed.Wait();
+ EXPECT_EQ(3u, profiler_info.profile.frame_sets.size());
+
+ // The profile duration should be greater than the total sampling intervals.
+ EXPECT_GT(profiler_info.profile.profile_duration,
+ profiler_info.profile.sampling_period * 3);
+
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1),
+ profiler_info.profile.sampling_period);
+
+ // The number of invocations of RecordAnnotations() should be equal to the
+ // number of samples recorded.
+ EXPECT_EQ(3, profiler_info.profile.annotation_count);
+ });
+}
+
+// Checks that the sampling thread can shut down.
+PROFILER_TEST_F(StackSamplingProfilerTest, SamplerIdleShutdown) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 1;
+
+ InternalFrameSets frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
+ ASSERT_EQ(1u, frame_sets.size());
+
+ // Capture thread should still be running at this point.
+ ASSERT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
+
+ // Initiate an "idle" shutdown and ensure it happens. Idle-shutdown was
+ // disabled by the test fixture so the test will fail due to a timeout if
+ // it does not exit.
+ StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(false);
+
+ // While the shutdown has been initiated, the actual exit of the thread still
+ // happens asynchronously. Watch until the thread actually exits. This test
+ // will time-out in the case of failure.
+ while (StackSamplingProfiler::TestAPI::IsSamplingThreadRunning())
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
+}
+
+// Checks that additional requests will restart a stopped profiler.
+PROFILER_TEST_F(StackSamplingProfilerTest,
+ WillRestartSamplerAfterIdleShutdown) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 1;
+
+ InternalFrameSets frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
+ ASSERT_EQ(1u, frame_sets.size());
+
+ // Capture thread should still be running at this point.
+ ASSERT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
+
+ // Post a ShutdownTask on the sampling thread which, when executed, will
+ // mark the thread as EXITING and begin shut down of the thread.
+ StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(false);
+
+ // Ensure another capture will start the sampling thread and run.
+ frame_sets = CaptureFrameSets(params, AVeryLongTimeDelta());
+ ASSERT_EQ(1u, frame_sets.size());
+ EXPECT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
+}
+
+// Checks that it's safe to stop a task after it's completed and the sampling
+// thread has shut-down for being idle.
+PROFILER_TEST_F(StackSamplingProfilerTest, StopAfterIdleShutdown) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ SamplingParams params;
+
+ params.sampling_interval = TimeDelta::FromMilliseconds(1);
+ params.samples_per_profile = 1;
+
+ TestProfilerInfo profiler_info(target_thread_id, params);
+
+ profiler_info.profiler.Start();
+ profiler_info.completed.Wait();
+
+ // Capture thread should still be running at this point.
+ ASSERT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
+
+ // Perform an idle shutdown.
+ StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(false);
+
+ // Stop should be safe though its impossible to know at this moment if the
+ // sampling thread has completely exited or will just "stop soon".
+ profiler_info.profiler.Stop();
+ });
+}
+
+// Checks that profilers can run both before and after the sampling thread has
+// started.
+PROFILER_TEST_F(StackSamplingProfilerTest,
+ ProfileBeforeAndAfterSamplingThreadRunning) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ std::vector<SamplingParams> params(2);
+
+ params[0].initial_delay = AVeryLongTimeDelta();
+ params[0].sampling_interval = TimeDelta::FromMilliseconds(1);
+ params[0].samples_per_profile = 1;
+
+ params[1].initial_delay = TimeDelta::FromMilliseconds(0);
+ params[1].sampling_interval = TimeDelta::FromMilliseconds(1);
+ params[1].samples_per_profile = 1;
+
+ std::vector<std::unique_ptr<TestProfilerInfo>> profiler_infos =
+ CreateProfilers(target_thread_id, params);
+
+ // First profiler is started when there has never been a sampling thread.
+ EXPECT_FALSE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
+ profiler_infos[0]->profiler.Start();
+ // Second profiler is started when sampling thread is already running.
+ EXPECT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
+ profiler_infos[1]->profiler.Start();
+
+ // Only the second profiler should finish before test times out.
+ size_t completed_profiler = WaitForSamplingComplete(profiler_infos);
+ EXPECT_EQ(1U, completed_profiler);
+ });
+}
+
+// Checks that an idle-shutdown task will abort if a new profiler starts
+// between when it was posted and when it runs.
+PROFILER_TEST_F(StackSamplingProfilerTest, IdleShutdownAbort) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ SamplingParams params;
+
+ params.sampling_interval = TimeDelta::FromMilliseconds(1);
+ params.samples_per_profile = 1;
+
+ TestProfilerInfo profiler_info(target_thread_id, params);
+
+ profiler_info.profiler.Start();
+ profiler_info.completed.Wait();
+ EXPECT_EQ(1u, profiler_info.profile.frame_sets.size());
+
+ // Perform an idle shutdown but simulate that a new capture is started
+ // before it can actually run.
+ StackSamplingProfiler::TestAPI::PerformSamplingThreadIdleShutdown(true);
+
+ // Though the shutdown-task has been executed, any actual exit of the
+ // thread is asynchronous so there is no way to detect that *didn't* exit
+ // except to wait a reasonable amount of time and then check. Since the
+ // thread was just running ("perform" blocked until it was), it should
+ // finish almost immediately and without any waiting for tasks or events.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(200));
+ EXPECT_TRUE(StackSamplingProfiler::TestAPI::IsSamplingThreadRunning());
+
+ // Ensure that it's still possible to run another sampler.
+ TestProfilerInfo another_info(target_thread_id, params);
+ another_info.profiler.Start();
+ another_info.completed.Wait();
+ EXPECT_EQ(1u, another_info.profile.frame_sets.size());
+ });
+}
+
+// Checks that synchronized multiple sampling requests execute in parallel.
+PROFILER_TEST_F(StackSamplingProfilerTest, ConcurrentProfiling_InSync) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ std::vector<SamplingParams> params(2);
+
+ // Providing an initial delay makes it more likely that both will be
+ // scheduled before either starts to run. Once started, samples will
+ // run ordered by their scheduled, interleaved times regardless of
+ // whatever interval the thread wakes up. Thus, total execution time
+ // will be 10ms (delay) + 10x1ms (sampling) + 1/2 timer minimum interval.
+ params[0].initial_delay = TimeDelta::FromMilliseconds(10);
+ params[0].sampling_interval = TimeDelta::FromMilliseconds(1);
+ params[0].samples_per_profile = 9;
+
+ params[1].initial_delay = TimeDelta::FromMilliseconds(11);
+ params[1].sampling_interval = TimeDelta::FromMilliseconds(1);
+ params[1].samples_per_profile = 8;
+
+ std::vector<std::unique_ptr<TestProfilerInfo>> profiler_infos =
+ CreateProfilers(target_thread_id, params);
+
+ profiler_infos[0]->profiler.Start();
+ profiler_infos[1]->profiler.Start();
+
+ // Wait for one profiler to finish.
+ size_t completed_profiler = WaitForSamplingComplete(profiler_infos);
+
+ size_t other_profiler = 1 - completed_profiler;
+ // Wait for the other profiler to finish.
+ profiler_infos[other_profiler]->completed.Wait();
+
+ // Ensure each got the correct number of frame sets.
+ EXPECT_EQ(9u, profiler_infos[0]->profile.frame_sets.size());
+ EXPECT_EQ(8u, profiler_infos[1]->profile.frame_sets.size());
+ });
+}
+
+// Checks that several mixed sampling requests execute in parallel.
+PROFILER_TEST_F(StackSamplingProfilerTest, ConcurrentProfiling_Mixed) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ std::vector<SamplingParams> params(3);
+
+ params[0].initial_delay = TimeDelta::FromMilliseconds(8);
+ params[0].sampling_interval = TimeDelta::FromMilliseconds(4);
+ params[0].samples_per_profile = 10;
+
+ params[1].initial_delay = TimeDelta::FromMilliseconds(9);
+ params[1].sampling_interval = TimeDelta::FromMilliseconds(3);
+ params[1].samples_per_profile = 10;
+
+ params[2].initial_delay = TimeDelta::FromMilliseconds(10);
+ params[2].sampling_interval = TimeDelta::FromMilliseconds(2);
+ params[2].samples_per_profile = 10;
+
+ std::vector<std::unique_ptr<TestProfilerInfo>> profiler_infos =
+ CreateProfilers(target_thread_id, params);
+
+ for (size_t i = 0; i < profiler_infos.size(); ++i)
+ profiler_infos[i]->profiler.Start();
+
+ // Wait for one profiler to finish.
+ size_t completed_profiler = WaitForSamplingComplete(profiler_infos);
+ EXPECT_EQ(10u,
+ profiler_infos[completed_profiler]->profile.frame_sets.size());
+ // Stop and destroy all profilers, always in the same order. Don't crash.
+ for (size_t i = 0; i < profiler_infos.size(); ++i)
+ profiler_infos[i]->profiler.Stop();
+ for (size_t i = 0; i < profiler_infos.size(); ++i)
+ profiler_infos[i].reset();
+ });
+}
+
+// Checks that a stack that runs through another library produces a stack with
+// the expected functions.
+// macOS ASAN is not yet supported - crbug.com/718628.
+#if !(defined(ADDRESS_SANITIZER) && defined(OS_MACOSX))
+#define MAYBE_OtherLibrary OtherLibrary
+#else
+#define MAYBE_OtherLibrary DISABLED_OtherLibrary
+#endif
+PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_OtherLibrary) {
+ SamplingParams params;
+ params.sampling_interval = TimeDelta::FromMilliseconds(0);
+ params.samples_per_profile = 1;
+
+ Profile profile;
+ {
+ ScopedNativeLibrary other_library(LoadOtherLibrary());
+ WithTargetThread(
+ [&params, &profile](PlatformThreadId target_thread_id) {
+ WaitableEvent sampling_thread_completed(
+ WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ StackSamplingProfiler profiler(
+ target_thread_id, params,
+ std::make_unique<TestProfileBuilder>(
+ BindLambdaForTesting([&profile, &sampling_thread_completed](
+ Profile result_profile) {
+ profile = std::move(result_profile);
+ sampling_thread_completed.Signal();
+ })));
+ profiler.Start();
+ sampling_thread_completed.Wait();
+ },
+ StackConfiguration(StackConfiguration::WITH_OTHER_LIBRARY,
+ other_library.get()));
+ }
+
+ // Look up the frames.
+ ASSERT_EQ(1u, profile.frame_sets.size());
+ const InternalFrames& frames = profile.frame_sets[0];
+
+ // Check that the stack contains a frame for
+ // TargetThread::CallThroughOtherLibrary().
+ InternalFrames::const_iterator other_library_frame =
+ FindFirstFrameWithinFunction(frames,
+ &TargetThread::CallThroughOtherLibrary);
+ ASSERT_TRUE(other_library_frame != frames.end())
+ << "Function at "
+ << MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
+ &TargetThread::CallThroughOtherLibrary))
+ << " was not found in stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+
+ // Check that the stack contains a frame for
+ // TargetThread::SignalAndWaitUntilSignaled().
+ InternalFrames::const_iterator end_frame = FindFirstFrameWithinFunction(
+ frames, &TargetThread::SignalAndWaitUntilSignaled);
+ ASSERT_TRUE(end_frame != frames.end())
+ << "Function at "
+ << MaybeFixupFunctionAddressForILT(reinterpret_cast<const void*>(
+ &TargetThread::SignalAndWaitUntilSignaled))
+ << " was not found in stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+
+ // The stack should look like this, resulting in three frames between
+ // SignalAndWaitUntilSignaled and CallThroughOtherLibrary:
+ //
+ // ... WaitableEvent and system frames ...
+ // TargetThread::SignalAndWaitUntilSignaled
+ // TargetThread::OtherLibraryCallback
+ // InvokeCallbackFunction (in other library)
+ // TargetThread::CallThroughOtherLibrary
+ EXPECT_EQ(3, other_library_frame - end_frame)
+ << "Stack:\n"
+ << FormatSampleForDiagnosticOutput(frames);
+}
+
+// Checks that a stack that runs through a library that is unloading produces a
+// stack, and doesn't crash.
+// Unloading is synchronous on the Mac, so this test is inapplicable.
+#if !defined(OS_MACOSX)
+#define MAYBE_UnloadingLibrary UnloadingLibrary
+#else
+#define MAYBE_UnloadingLibrary DISABLED_UnloadingLibrary
+#endif
+PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_UnloadingLibrary) {
+ TestLibraryUnload(false);
+}
+
+// Checks that a stack that runs through a library that has been unloaded
+// produces a stack, and doesn't crash.
+// macOS ASAN is not yet supported - crbug.com/718628.
+#if !(defined(ADDRESS_SANITIZER) && defined(OS_MACOSX))
+#define MAYBE_UnloadedLibrary UnloadedLibrary
+#else
+#define MAYBE_UnloadedLibrary DISABLED_UnloadedLibrary
+#endif
+PROFILER_TEST_F(StackSamplingProfilerTest, MAYBE_UnloadedLibrary) {
+ TestLibraryUnload(true);
+}
+
+// Checks that different threads can be sampled in parallel.
+PROFILER_TEST_F(StackSamplingProfilerTest, MultipleSampledThreads) {
+ // Create target threads. The extra parethesis around the StackConfiguration
+ // call are to avoid the most-vexing-parse problem.
+ TargetThread target_thread1((StackConfiguration(StackConfiguration::NORMAL)));
+ TargetThread target_thread2((StackConfiguration(StackConfiguration::NORMAL)));
+ PlatformThreadHandle target_thread_handle1, target_thread_handle2;
+ EXPECT_TRUE(
+ PlatformThread::Create(0, &target_thread1, &target_thread_handle1));
+ EXPECT_TRUE(
+ PlatformThread::Create(0, &target_thread2, &target_thread_handle2));
+ target_thread1.WaitForThreadStart();
+ target_thread2.WaitForThreadStart();
+
+ // Providing an initial delay makes it more likely that both will be
+ // scheduled before either starts to run. Once started, samples will
+ // run ordered by their scheduled, interleaved times regardless of
+ // whatever interval the thread wakes up.
+ SamplingParams params1, params2;
+ params1.initial_delay = TimeDelta::FromMilliseconds(10);
+ params1.sampling_interval = TimeDelta::FromMilliseconds(1);
+ params1.samples_per_profile = 9;
+ params2.initial_delay = TimeDelta::FromMilliseconds(10);
+ params2.sampling_interval = TimeDelta::FromMilliseconds(1);
+ params2.samples_per_profile = 8;
+
+ Profile profile1, profile2;
+
+ WaitableEvent sampling_thread_completed1(
+ WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ StackSamplingProfiler profiler1(
+ target_thread1.id(), params1,
+ std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
+ [&profile1, &sampling_thread_completed1](Profile result_profile) {
+ profile1 = std::move(result_profile);
+ sampling_thread_completed1.Signal();
+ })));
+
+ WaitableEvent sampling_thread_completed2(
+ WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ StackSamplingProfiler profiler2(
+ target_thread2.id(), params2,
+ std::make_unique<TestProfileBuilder>(BindLambdaForTesting(
+ [&profile2, &sampling_thread_completed2](Profile result_profile) {
+ profile2 = std::move(result_profile);
+ sampling_thread_completed2.Signal();
+ })));
+
+ // Finally the real work.
+ profiler1.Start();
+ profiler2.Start();
+ sampling_thread_completed1.Wait();
+ sampling_thread_completed2.Wait();
+ EXPECT_EQ(9u, profile1.frame_sets.size());
+ EXPECT_EQ(8u, profile2.frame_sets.size());
+
+ target_thread1.SignalThreadToFinish();
+ target_thread2.SignalThreadToFinish();
+ PlatformThread::Join(target_thread_handle1);
+ PlatformThread::Join(target_thread_handle2);
+}
+
+// A simple thread that runs a profiler on another thread.
+class ProfilerThread : public SimpleThread {
+ public:
+ ProfilerThread(const std::string& name,
+ PlatformThreadId thread_id,
+ const SamplingParams& params)
+ : SimpleThread(name, Options()),
+ run_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ completed_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ profiler_(thread_id,
+ params,
+ std::make_unique<TestProfileBuilder>(
+ BindLambdaForTesting([this](Profile result_profile) {
+ profile_ = std::move(result_profile);
+ completed_.Signal();
+ }))) {}
+
+ void Run() override {
+ run_.Wait();
+ profiler_.Start();
+ }
+
+ void Go() { run_.Signal(); }
+
+ void Wait() { completed_.Wait(); }
+
+ Profile& profile() { return profile_; }
+
+ private:
+ WaitableEvent run_;
+
+ Profile profile_;
+ WaitableEvent completed_;
+ StackSamplingProfiler profiler_;
+};
+
+// Checks that different threads can run samplers in parallel.
+PROFILER_TEST_F(StackSamplingProfilerTest, MultipleProfilerThreads) {
+ WithTargetThread([](PlatformThreadId target_thread_id) {
+ // Providing an initial delay makes it more likely that both will be
+ // scheduled before either starts to run. Once started, samples will
+ // run ordered by their scheduled, interleaved times regardless of
+ // whatever interval the thread wakes up.
+ SamplingParams params1, params2;
+ params1.initial_delay = TimeDelta::FromMilliseconds(10);
+ params1.sampling_interval = TimeDelta::FromMilliseconds(1);
+ params1.samples_per_profile = 9;
+ params2.initial_delay = TimeDelta::FromMilliseconds(10);
+ params2.sampling_interval = TimeDelta::FromMilliseconds(1);
+ params2.samples_per_profile = 8;
+
+ // Start the profiler threads and give them a moment to get going.
+ ProfilerThread profiler_thread1("profiler1", target_thread_id, params1);
+ ProfilerThread profiler_thread2("profiler2", target_thread_id, params2);
+ profiler_thread1.Start();
+ profiler_thread2.Start();
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
+
+ // This will (approximately) synchronize the two threads.
+ profiler_thread1.Go();
+ profiler_thread2.Go();
+
+ // Wait for them both to finish and validate collection.
+ profiler_thread1.Wait();
+ profiler_thread2.Wait();
+ EXPECT_EQ(9u, profiler_thread1.profile().frame_sets.size());
+ EXPECT_EQ(8u, profiler_thread2.profile().frame_sets.size());
+
+ profiler_thread1.Join();
+ profiler_thread2.Join();
+ });
+}
+
+} // namespace base
diff --git a/base/profiler/test_support_library.cc b/base/profiler/test_support_library.cc
new file mode 100644
index 0000000000..035f8f70c0
--- /dev/null
+++ b/base/profiler/test_support_library.cc
@@ -0,0 +1,30 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: there is intentionally no header file associated with this library so
+// we don't risk implicitly demand loading it by accessing a symbol.
+
+#if defined(WIN32)
+#define BASE_PROFILER_TEST_SUPPORT_LIBRARY_EXPORT __declspec(dllexport)
+#else // defined(WIN32)
+#define BASE_PROFILER_TEST_SUPPORT_LIBRARY_EXPORT __attribute__((visibility("default")))
+#endif
+
+namespace base {
+
+// Must be defined in an extern "C" block so we can look up the unmangled name.
+extern "C" {
+
+BASE_PROFILER_TEST_SUPPORT_LIBRARY_EXPORT void InvokeCallbackFunction(
+ void (*function)(void*),
+ void* arg) {
+ function(arg);
+ // Prevent tail call.
+ volatile int i = 0;
+ i = 1;
+}
+
+} // extern "C"
+
+} // namespace base
diff --git a/base/profiler/win32_stack_frame_unwinder.cc b/base/profiler/win32_stack_frame_unwinder.cc
new file mode 100644
index 0000000000..a3f5f74b85
--- /dev/null
+++ b/base/profiler/win32_stack_frame_unwinder.cc
@@ -0,0 +1,186 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/profiler/win32_stack_frame_unwinder.h"
+
+#include <windows.h>
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+
+namespace base {
+
+// Win32UnwindFunctions -------------------------------------------------------
+
+const HMODULE ModuleHandleTraits::kNonNullModuleForTesting =
+ reinterpret_cast<HMODULE>(static_cast<uintptr_t>(-1));
+
+// static
+bool ModuleHandleTraits::CloseHandle(HMODULE handle) {
+ if (handle == kNonNullModuleForTesting)
+ return true;
+
+ return ::FreeLibrary(handle) != 0;
+}
+
+// static
+bool ModuleHandleTraits::IsHandleValid(HMODULE handle) {
+ return handle != nullptr;
+}
+
+// static
+HMODULE ModuleHandleTraits::NullHandle() {
+ return nullptr;
+}
+
+namespace {
+
+// Implements the UnwindFunctions interface for the corresponding Win32
+// functions.
+class Win32UnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
+public:
+ Win32UnwindFunctions();
+ ~Win32UnwindFunctions() override;
+
+ PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
+ PDWORD64 image_base) override;
+
+ void VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) override;
+
+ ScopedModuleHandle GetModuleForProgramCounter(
+ DWORD64 program_counter) override;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(Win32UnwindFunctions);
+};
+
+Win32UnwindFunctions::Win32UnwindFunctions() {}
+Win32UnwindFunctions::~Win32UnwindFunctions() {}
+
+PRUNTIME_FUNCTION Win32UnwindFunctions::LookupFunctionEntry(
+ DWORD64 program_counter,
+ PDWORD64 image_base) {
+#ifdef _WIN64
+ return ::RtlLookupFunctionEntry(program_counter, image_base, nullptr);
+#else
+ NOTREACHED();
+ return nullptr;
+#endif
+}
+
+void Win32UnwindFunctions::VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) {
+#ifdef _WIN64
+ void* handler_data;
+ ULONG64 establisher_frame;
+ KNONVOLATILE_CONTEXT_POINTERS nvcontext = {};
+ ::RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, program_counter,
+ runtime_function, context, &handler_data,
+ &establisher_frame, &nvcontext);
+#else
+ NOTREACHED();
+#endif
+}
+
+ScopedModuleHandle Win32UnwindFunctions::GetModuleForProgramCounter(
+ DWORD64 program_counter) {
+ HMODULE module_handle = nullptr;
+ // GetModuleHandleEx() increments the module reference count, which is then
+ // managed and ultimately decremented by ScopedModuleHandle.
+ if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
+ reinterpret_cast<LPCTSTR>(program_counter),
+ &module_handle)) {
+ const DWORD error = ::GetLastError();
+ DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
+ }
+ return ScopedModuleHandle(module_handle);
+}
+
+} // namespace
+
+// Win32StackFrameUnwinder ----------------------------------------------------
+
+Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
+Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
+
+Win32StackFrameUnwinder::Win32StackFrameUnwinder()
+ : Win32StackFrameUnwinder(WrapUnique(new Win32UnwindFunctions)) {}
+
+Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
+
+bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
+ ScopedModuleHandle* module) {
+#ifdef _WIN64
+ ScopedModuleHandle frame_module =
+ unwind_functions_->GetModuleForProgramCounter(context->Rip);
+ if (!frame_module.IsValid()) {
+ // There's no loaded module containing the instruction pointer. This can be
+ // due to executing code that is not in a module. In particular,
+ // runtime-generated code associated with third-party injected DLLs
+ // typically is not in a module. It can also be due to the the module having
+ // been unloaded since we recorded the stack. In the latter case the
+ // function unwind information was part of the unloaded module, so it's not
+ // possible to unwind further.
+ //
+ // If a module was found, it's still theoretically possible for the detected
+ // module module to be different than the one that was loaded when the stack
+ // was copied (i.e. if the module was unloaded and a different module loaded
+ // in overlapping memory). This likely would cause a crash, but has not been
+ // observed in practice.
+ return false;
+ }
+
+ ULONG64 image_base;
+ // Try to look up unwind metadata for the current function.
+ PRUNTIME_FUNCTION runtime_function =
+ unwind_functions_->LookupFunctionEntry(context->Rip, &image_base);
+
+ if (runtime_function) {
+ unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function,
+ context);
+ at_top_frame_ = false;
+ } else {
+ if (at_top_frame_) {
+ at_top_frame_ = false;
+
+ // This is a leaf function (i.e. a function that neither calls a function,
+ // nor allocates any stack space itself) so the return address is at RSP.
+ context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
+ context->Rsp += 8;
+ } else {
+ // In theory we shouldn't get here, as it means we've encountered a
+ // function without unwind information below the top of the stack, which
+ // is forbidden by the Microsoft x64 calling convention.
+ //
+ // The one known case in Chrome code that executes this path occurs
+ // because of BoringSSL unwind information inconsistent with the actual
+ // function code. See https://crbug.com/542919.
+ //
+ // Note that dodgy third-party generated code that otherwise would enter
+ // this path should be caught by the module check above, since the code
+ // typically is located outside of a module.
+ return false;
+ }
+ }
+
+ module->Set(frame_module.Take());
+ return true;
+#else
+ NOTREACHED();
+ return false;
+#endif
+}
+
+Win32StackFrameUnwinder::Win32StackFrameUnwinder(
+ std::unique_ptr<UnwindFunctions> unwind_functions)
+ : at_top_frame_(true), unwind_functions_(std::move(unwind_functions)) {}
+
+} // namespace base
diff --git a/base/profiler/win32_stack_frame_unwinder.h b/base/profiler/win32_stack_frame_unwinder.h
new file mode 100644
index 0000000000..c92d50cd2b
--- /dev/null
+++ b/base/profiler/win32_stack_frame_unwinder.h
@@ -0,0 +1,102 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROFILER_WIN32_STACK_FRAME_UNWINDER_H_
+#define BASE_PROFILER_WIN32_STACK_FRAME_UNWINDER_H_
+
+#include <windows.h>
+
+#include <memory>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/win/scoped_handle.h"
+
+namespace base {
+
+#if !defined(_WIN64)
+// Allows code to compile for x86. Actual support for x86 will require either
+// refactoring these interfaces or separate architecture-specific interfaces.
+struct RUNTIME_FUNCTION {
+ DWORD BeginAddress;
+ DWORD EndAddress;
+};
+using PRUNTIME_FUNCTION = RUNTIME_FUNCTION*;
+#endif // !defined(_WIN64)
+
+// Traits class to adapt GenericScopedHandle for HMODULES.
+class ModuleHandleTraits : public win::HandleTraits {
+ public:
+ using Handle = HMODULE;
+
+ static bool BASE_EXPORT CloseHandle(HMODULE handle);
+ static bool BASE_EXPORT IsHandleValid(HMODULE handle);
+ static HMODULE BASE_EXPORT NullHandle();
+
+ BASE_EXPORT static const HMODULE kNonNullModuleForTesting;
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ModuleHandleTraits);
+};
+
+// HMODULE is not really a handle, and has reference count semantics, so the
+// standard VerifierTraits does not apply.
+using ScopedModuleHandle =
+ win::GenericScopedHandle<ModuleHandleTraits, win::DummyVerifierTraits>;
+
+// Instances of this class are expected to be created and destroyed for each
+// stack unwinding. This class is not used while the target thread is suspended,
+// so may allocate from the default heap.
+class BASE_EXPORT Win32StackFrameUnwinder {
+ public:
+ // Interface for Win32 unwind-related functionality this class depends
+ // on. Provides a seam for testing.
+ class BASE_EXPORT UnwindFunctions {
+ public:
+ virtual ~UnwindFunctions();
+
+ virtual PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
+ PDWORD64 image_base) = 0;
+ virtual void VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) = 0;
+
+ // Returns the module containing |program_counter|. Can return null if the
+ // module has been unloaded.
+ virtual ScopedModuleHandle GetModuleForProgramCounter(
+ DWORD64 program_counter) = 0;
+
+ protected:
+ UnwindFunctions();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UnwindFunctions);
+ };
+
+ Win32StackFrameUnwinder();
+ ~Win32StackFrameUnwinder();
+
+ // Attempts to unwind the frame represented by the stack and instruction
+ // pointers in |context|. If successful, updates |context| and provides the
+ // module associated with the frame in |module|.
+ bool TryUnwind(CONTEXT* context, ScopedModuleHandle* module);
+
+ private:
+ // This function is for internal and test purposes only.
+ Win32StackFrameUnwinder(std::unique_ptr<UnwindFunctions> unwind_functions);
+ friend class Win32StackFrameUnwinderTest;
+
+ // State associated with each stack unwinding.
+ bool at_top_frame_;
+ bool unwind_info_present_for_all_frames_;
+
+ std::unique_ptr<UnwindFunctions> unwind_functions_;
+
+ DISALLOW_COPY_AND_ASSIGN(Win32StackFrameUnwinder);
+};
+
+} // namespace base
+
+#endif // BASE_PROFILER_WIN32_STACK_FRAME_UNWINDER_H_
diff --git a/base/profiler/win32_stack_frame_unwinder_unittest.cc b/base/profiler/win32_stack_frame_unwinder_unittest.cc
new file mode 100644
index 0000000000..cecfe224ec
--- /dev/null
+++ b/base/profiler/win32_stack_frame_unwinder_unittest.cc
@@ -0,0 +1,223 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/profiler/win32_stack_frame_unwinder.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class TestUnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
+ public:
+ TestUnwindFunctions();
+
+ PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
+ PDWORD64 image_base) override;
+ void VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) override;
+ ScopedModuleHandle GetModuleForProgramCounter(
+ DWORD64 program_counter) override;
+
+ // Instructs GetModuleForProgramCounter to return null on the next call.
+ void SetUnloadedModule();
+
+ // These functions set whether the next frame will have a RUNTIME_FUNCTION.
+ void SetHasRuntimeFunction(CONTEXT* context);
+ void SetNoRuntimeFunction(CONTEXT* context);
+
+ private:
+ enum { kImageBaseIncrement = 1 << 20 };
+
+ static RUNTIME_FUNCTION* const kInvalidRuntimeFunction;
+
+ bool module_is_loaded_;
+ DWORD64 expected_program_counter_;
+ DWORD64 next_image_base_;
+ DWORD64 expected_image_base_;
+ RUNTIME_FUNCTION* next_runtime_function_;
+ std::vector<RUNTIME_FUNCTION> runtime_functions_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestUnwindFunctions);
+};
+
+RUNTIME_FUNCTION* const TestUnwindFunctions::kInvalidRuntimeFunction =
+ reinterpret_cast<RUNTIME_FUNCTION*>(static_cast<uintptr_t>(-1));
+
+TestUnwindFunctions::TestUnwindFunctions()
+ : module_is_loaded_(true),
+ expected_program_counter_(0),
+ next_image_base_(kImageBaseIncrement),
+ expected_image_base_(0),
+ next_runtime_function_(kInvalidRuntimeFunction) {
+}
+
+PRUNTIME_FUNCTION TestUnwindFunctions::LookupFunctionEntry(
+ DWORD64 program_counter,
+ PDWORD64 image_base) {
+ EXPECT_EQ(expected_program_counter_, program_counter);
+ *image_base = expected_image_base_ = next_image_base_;
+ next_image_base_ += kImageBaseIncrement;
+ RUNTIME_FUNCTION* return_value = next_runtime_function_;
+ next_runtime_function_ = kInvalidRuntimeFunction;
+ return return_value;
+}
+
+void TestUnwindFunctions::VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) {
+ ASSERT_NE(kInvalidRuntimeFunction, runtime_function)
+ << "expected call to SetHasRuntimeFunction() or SetNoRuntimeFunction() "
+ << "before invoking TryUnwind()";
+ EXPECT_EQ(expected_image_base_, image_base);
+ expected_image_base_ = 0;
+ EXPECT_EQ(expected_program_counter_, program_counter);
+ expected_program_counter_ = 0;
+ // This function should only be called when LookupFunctionEntry returns
+ // a RUNTIME_FUNCTION.
+ EXPECT_EQ(&runtime_functions_.back(), runtime_function);
+}
+
+ScopedModuleHandle TestUnwindFunctions::GetModuleForProgramCounter(
+ DWORD64 program_counter) {
+ bool return_non_null_value = module_is_loaded_;
+ module_is_loaded_ = true;
+ return ScopedModuleHandle(return_non_null_value ?
+ ModuleHandleTraits::kNonNullModuleForTesting :
+ nullptr);
+}
+
+void TestUnwindFunctions::SetUnloadedModule() {
+ module_is_loaded_ = false;
+}
+
+void TestUnwindFunctions::SetHasRuntimeFunction(CONTEXT* context) {
+ RUNTIME_FUNCTION runtime_function = {};
+ runtime_function.BeginAddress = 16;
+ runtime_function.EndAddress = runtime_function.BeginAddress + 256;
+ runtime_functions_.push_back(runtime_function);
+ next_runtime_function_ = &runtime_functions_.back();
+
+ expected_program_counter_ = context->Rip =
+ next_image_base_ + runtime_function.BeginAddress + 8;
+}
+
+void TestUnwindFunctions::SetNoRuntimeFunction(CONTEXT* context) {
+ expected_program_counter_ = context->Rip = 100;
+ next_runtime_function_ = nullptr;
+}
+
+} // namespace
+
+class Win32StackFrameUnwinderTest : public testing::Test {
+ protected:
+ Win32StackFrameUnwinderTest() {}
+
+ // This exists so that Win32StackFrameUnwinder's constructor can be private
+ // with a single friend declaration of this test fixture.
+ std::unique_ptr<Win32StackFrameUnwinder> CreateUnwinder();
+
+ // Weak pointer to the unwind functions used by last created unwinder.
+ TestUnwindFunctions* unwind_functions_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Win32StackFrameUnwinderTest);
+};
+
+std::unique_ptr<Win32StackFrameUnwinder>
+Win32StackFrameUnwinderTest::CreateUnwinder() {
+ std::unique_ptr<TestUnwindFunctions> unwind_functions(
+ new TestUnwindFunctions);
+ unwind_functions_ = unwind_functions.get();
+ return WrapUnique(
+ new Win32StackFrameUnwinder(std::move(unwind_functions)));
+}
+
+// Checks the case where all frames have unwind information.
+TEST_F(Win32StackFrameUnwinderTest, FramesWithUnwindInfo) {
+ std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ ScopedModuleHandle module;
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ module.Set(nullptr);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ module.Set(nullptr);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+}
+
+// Checks that an instruction pointer in an unloaded module fails to unwind.
+TEST_F(Win32StackFrameUnwinderTest, UnloadedModule) {
+ std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ ScopedModuleHandle module;
+
+ unwind_functions_->SetUnloadedModule();
+ EXPECT_FALSE(unwinder->TryUnwind(&context, &module));
+}
+
+// Checks that the CONTEXT's stack pointer gets popped when the top frame has no
+// unwind information.
+TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) {
+ std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ ScopedModuleHandle module;
+ DWORD64 next_ip = 0x0123456789abcdef;
+ DWORD64 original_rsp = reinterpret_cast<DWORD64>(&next_ip);
+ context.Rsp = original_rsp;
+
+ unwind_functions_->SetNoRuntimeFunction(&context);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_EQ(next_ip, context.Rip);
+ EXPECT_EQ(original_rsp + 8, context.Rsp);
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ module.Set(nullptr);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ module.Set(nullptr);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+}
+
+// Checks that a frame below the top of the stack with missing unwind info
+// terminates the unwinding.
+TEST_F(Win32StackFrameUnwinderTest, FrameBelowTopWithoutUnwindInfo) {
+ {
+ // First stack, with a bad function below the top of the stack.
+ std::unique_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ ScopedModuleHandle module;
+ unwind_functions_->SetHasRuntimeFunction(&context);
+ EXPECT_TRUE(unwinder->TryUnwind(&context, &module));
+ EXPECT_TRUE(module.IsValid());
+
+ unwind_functions_->SetNoRuntimeFunction(&context);
+ EXPECT_FALSE(unwinder->TryUnwind(&context, &module));
+ }
+}
+
+} // namespace base
diff --git a/base/run_loop.h b/base/run_loop.h
index 2582a69f1d..8f5e0099d0 100644
--- a/base/run_loop.h
+++ b/base/run_loop.h
@@ -250,7 +250,7 @@ class BASE_EXPORT RunLoop {
private:
FRIEND_TEST_ALL_PREFIXES(MessageLoopTypedTest, RunLoopQuitOrderAfter);
-#if defined(OS_ANDROID)
+#if defined(OS_ANDROID) && 0
// Android doesn't support the blocking RunLoop::Run, so it calls
// BeforeRun and AfterRun directly.
friend class base::MessagePumpForUI;
diff --git a/base/run_loop_unittest.cc b/base/run_loop_unittest.cc
new file mode 100644
index 0000000000..c7db14aa3d
--- /dev/null
+++ b/base/run_loop_unittest.cc
@@ -0,0 +1,636 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/run_loop.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/containers/queue.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/gtest_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker_impl.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+void QuitWhenIdleTask(RunLoop* run_loop, int* counter) {
+ run_loop->QuitWhenIdle();
+ ++(*counter);
+}
+
+void ShouldRunTask(int* counter) {
+ ++(*counter);
+}
+
+void ShouldNotRunTask() {
+ ADD_FAILURE() << "Ran a task that shouldn't run.";
+}
+
+void RunNestedLoopTask(int* counter) {
+ RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
+
+ // This task should quit |nested_run_loop| but not the main RunLoop.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&QuitWhenIdleTask, Unretained(&nested_run_loop),
+ Unretained(counter)));
+
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, BindOnce(&ShouldNotRunTask), TimeDelta::FromDays(1));
+
+ nested_run_loop.Run();
+
+ ++(*counter);
+}
+
+// A simple SingleThreadTaskRunner that just queues undelayed tasks (and ignores
+// delayed tasks). Tasks can then be processed one by one by ProcessTask() which
+// will return true if it processed a task and false otherwise.
+class SimpleSingleThreadTaskRunner : public SingleThreadTaskRunner {
+ public:
+ SimpleSingleThreadTaskRunner() = default;
+
+ bool PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ base::TimeDelta delay) override {
+ if (delay > base::TimeDelta())
+ return false;
+ AutoLock auto_lock(tasks_lock_);
+ pending_tasks_.push(std::move(task));
+ return true;
+ }
+
+ bool PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ base::TimeDelta delay) override {
+ return PostDelayedTask(from_here, std::move(task), delay);
+ }
+
+ bool RunsTasksInCurrentSequence() const override {
+ return origin_thread_checker_.CalledOnValidThread();
+ }
+
+ bool ProcessSingleTask() {
+ OnceClosure task;
+ {
+ AutoLock auto_lock(tasks_lock_);
+ if (pending_tasks_.empty())
+ return false;
+ task = std::move(pending_tasks_.front());
+ pending_tasks_.pop();
+ }
+ // It's important to Run() after pop() and outside the lock as |task| may
+ // run a nested loop which will re-enter ProcessSingleTask().
+ std::move(task).Run();
+ return true;
+ }
+
+ private:
+ ~SimpleSingleThreadTaskRunner() override = default;
+
+ Lock tasks_lock_;
+ base::queue<OnceClosure> pending_tasks_;
+
+ // RunLoop relies on RunsTasksInCurrentSequence() signal. Use a
+ // ThreadCheckerImpl to be able to reliably provide that signal even in
+ // non-dcheck builds.
+ ThreadCheckerImpl origin_thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleSingleThreadTaskRunner);
+};
+
+// The basis of all TestDelegates, allows safely injecting a OnceClosure to be
+// run in the next idle phase of this delegate's Run() implementation. This can
+// be used to have code run on a thread that is otherwise livelocked in an idle
+// phase (sometimes a simple PostTask() won't do it -- e.g. when processing
+// application tasks is disallowed).
+class InjectableTestDelegate : public RunLoop::Delegate {
+ public:
+ void InjectClosureOnDelegate(OnceClosure closure) {
+ AutoLock auto_lock(closure_lock_);
+ closure_ = std::move(closure);
+ }
+
+ bool RunInjectedClosure() {
+ AutoLock auto_lock(closure_lock_);
+ if (closure_.is_null())
+ return false;
+ std::move(closure_).Run();
+ return true;
+ }
+
+ private:
+ Lock closure_lock_;
+ OnceClosure closure_;
+};
+
+// A simple test RunLoop::Delegate to exercise Runloop logic independent of any
+// other base constructs. BindToCurrentThread() must be called before this
+// TestBoundDelegate is operational.
+class TestBoundDelegate final : public InjectableTestDelegate {
+ public:
+ TestBoundDelegate() = default;
+
+ // Makes this TestBoundDelegate become the RunLoop::Delegate and
+ // ThreadTaskRunnerHandle for this thread.
+ void BindToCurrentThread() {
+ thread_task_runner_handle_ =
+ std::make_unique<ThreadTaskRunnerHandle>(simple_task_runner_);
+ RunLoop::RegisterDelegateForCurrentThread(this);
+ }
+
+ private:
+ void Run(bool application_tasks_allowed) override {
+ if (nested_run_allowing_tasks_incoming_) {
+ EXPECT_TRUE(RunLoop::IsNestedOnCurrentThread());
+ EXPECT_TRUE(application_tasks_allowed);
+ } else if (RunLoop::IsNestedOnCurrentThread()) {
+ EXPECT_FALSE(application_tasks_allowed);
+ }
+ nested_run_allowing_tasks_incoming_ = false;
+
+ while (!should_quit_) {
+ if (application_tasks_allowed && simple_task_runner_->ProcessSingleTask())
+ continue;
+
+ if (ShouldQuitWhenIdle())
+ break;
+
+ if (RunInjectedClosure())
+ continue;
+
+ PlatformThread::YieldCurrentThread();
+ }
+ should_quit_ = false;
+ }
+
+ void Quit() override { should_quit_ = true; }
+
+ void EnsureWorkScheduled() override {
+ nested_run_allowing_tasks_incoming_ = true;
+ }
+
+ // True if the next invocation of Run() is expected to be from a
+ // kNestableTasksAllowed RunLoop.
+ bool nested_run_allowing_tasks_incoming_ = false;
+
+ scoped_refptr<SimpleSingleThreadTaskRunner> simple_task_runner_ =
+ MakeRefCounted<SimpleSingleThreadTaskRunner>();
+
+ std::unique_ptr<ThreadTaskRunnerHandle> thread_task_runner_handle_;
+
+ bool should_quit_ = false;
+};
+
+enum class RunLoopTestType {
+ // Runs all RunLoopTests under a ScopedTaskEnvironment to make sure real world
+ // scenarios work.
+ kRealEnvironment,
+
+ // Runs all RunLoopTests under a test RunLoop::Delegate to make sure the
+ // delegate interface fully works standalone.
+ kTestDelegate,
+};
+
+// The task environment for the RunLoopTest of a given type. A separate class
+// so it can be instantiated on the stack in the RunLoopTest fixture.
+class RunLoopTestEnvironment {
+ public:
+ RunLoopTestEnvironment(RunLoopTestType type) {
+ switch (type) {
+ case RunLoopTestType::kRealEnvironment: {
+ task_environment_ = std::make_unique<test::ScopedTaskEnvironment>();
+ break;
+ }
+ case RunLoopTestType::kTestDelegate: {
+ auto test_delegate = std::make_unique<TestBoundDelegate>();
+ test_delegate->BindToCurrentThread();
+ test_delegate_ = std::move(test_delegate);
+ break;
+ }
+ }
+ }
+
+ private:
+ // Instantiates one or the other based on the RunLoopTestType.
+ std::unique_ptr<test::ScopedTaskEnvironment> task_environment_;
+ std::unique_ptr<InjectableTestDelegate> test_delegate_;
+};
+
+class RunLoopTest : public testing::TestWithParam<RunLoopTestType> {
+ protected:
+ RunLoopTest() : test_environment_(GetParam()) {}
+
+ RunLoopTestEnvironment test_environment_;
+ RunLoop run_loop_;
+ int counter_ = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RunLoopTest);
+};
+
+} // namespace
+
+TEST_P(RunLoopTest, QuitWhenIdle) {
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&QuitWhenIdleTask, Unretained(&run_loop_),
+ Unretained(&counter_)));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&ShouldRunTask, Unretained(&counter_)));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, BindOnce(&ShouldNotRunTask), TimeDelta::FromDays(1));
+
+ run_loop_.Run();
+ EXPECT_EQ(2, counter_);
+}
+
+TEST_P(RunLoopTest, QuitWhenIdleNestedLoop) {
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RunNestedLoopTask, Unretained(&counter_)));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&QuitWhenIdleTask, Unretained(&run_loop_),
+ Unretained(&counter_)));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&ShouldRunTask, Unretained(&counter_)));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, BindOnce(&ShouldNotRunTask), TimeDelta::FromDays(1));
+
+ run_loop_.Run();
+ EXPECT_EQ(4, counter_);
+}
+
+TEST_P(RunLoopTest, QuitWhenIdleClosure) {
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ run_loop_.QuitWhenIdleClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&ShouldRunTask, Unretained(&counter_)));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, BindOnce(&ShouldNotRunTask), TimeDelta::FromDays(1));
+
+ run_loop_.Run();
+ EXPECT_EQ(1, counter_);
+}
+
+// Verify that the QuitWhenIdleClosure() can run after the RunLoop has been
+// deleted. It should have no effect.
+TEST_P(RunLoopTest, QuitWhenIdleClosureAfterRunLoopScope) {
+ Closure quit_when_idle_closure;
+ {
+ RunLoop run_loop;
+ quit_when_idle_closure = run_loop.QuitWhenIdleClosure();
+ run_loop.RunUntilIdle();
+ }
+ quit_when_idle_closure.Run();
+}
+
+// Verify that Quit can be executed from another sequence.
+TEST_P(RunLoopTest, QuitFromOtherSequence) {
+ Thread other_thread("test");
+ other_thread.Start();
+ scoped_refptr<SequencedTaskRunner> other_sequence =
+ other_thread.task_runner();
+
+ // Always expected to run before asynchronous Quit() kicks in.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ShouldRunTask, Unretained(&counter_)));
+
+ WaitableEvent loop_was_quit(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ other_sequence->PostTask(
+ FROM_HERE, base::BindOnce([](RunLoop* run_loop) { run_loop->Quit(); },
+ Unretained(&run_loop_)));
+ other_sequence->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WaitableEvent::Signal, base::Unretained(&loop_was_quit)));
+
+ // Anything that's posted after the Quit closure was posted back to this
+ // sequence shouldn't get a chance to run.
+ loop_was_quit.Wait();
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::BindOnce(&ShouldNotRunTask));
+
+ run_loop_.Run();
+
+ EXPECT_EQ(1, counter_);
+}
+
+// Verify that QuitClosure can be executed from another sequence.
+TEST_P(RunLoopTest, QuitFromOtherSequenceWithClosure) {
+ Thread other_thread("test");
+ other_thread.Start();
+ scoped_refptr<SequencedTaskRunner> other_sequence =
+ other_thread.task_runner();
+
+ // Always expected to run before asynchronous Quit() kicks in.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ShouldRunTask, Unretained(&counter_)));
+
+ WaitableEvent loop_was_quit(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ other_sequence->PostTask(FROM_HERE, run_loop_.QuitClosure());
+ other_sequence->PostTask(
+ FROM_HERE,
+ base::BindOnce(&WaitableEvent::Signal, base::Unretained(&loop_was_quit)));
+
+ // Anything that's posted after the Quit closure was posted back to this
+ // sequence shouldn't get a chance to run.
+ loop_was_quit.Wait();
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::BindOnce(&ShouldNotRunTask));
+
+ run_loop_.Run();
+
+ EXPECT_EQ(1, counter_);
+}
+
+// Verify that Quit can be executed from another sequence even when the
+// Quit is racing with Run() -- i.e. forgo the WaitableEvent used above.
+TEST_P(RunLoopTest, QuitFromOtherSequenceRacy) {
+ Thread other_thread("test");
+ other_thread.Start();
+ scoped_refptr<SequencedTaskRunner> other_sequence =
+ other_thread.task_runner();
+
+ // Always expected to run before asynchronous Quit() kicks in.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ShouldRunTask, Unretained(&counter_)));
+
+ other_sequence->PostTask(
+ FROM_HERE, base::BindOnce([](RunLoop* run_loop) { run_loop->Quit(); },
+ Unretained(&run_loop_)));
+
+ run_loop_.Run();
+
+ EXPECT_EQ(1, counter_);
+}
+
+// Verify that QuitClosure can be executed from another sequence even when the
+// Quit is racing with Run() -- i.e. forgo the WaitableEvent used above.
+TEST_P(RunLoopTest, QuitFromOtherSequenceRacyWithClosure) {
+ Thread other_thread("test");
+ other_thread.Start();
+ scoped_refptr<SequencedTaskRunner> other_sequence =
+ other_thread.task_runner();
+
+ // Always expected to run before asynchronous Quit() kicks in.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ShouldRunTask, Unretained(&counter_)));
+
+ other_sequence->PostTask(FROM_HERE, run_loop_.QuitClosure());
+
+ run_loop_.Run();
+
+ EXPECT_EQ(1, counter_);
+}
+
+// Verify that QuitWhenIdle can be executed from another sequence.
+TEST_P(RunLoopTest, QuitWhenIdleFromOtherSequence) {
+ Thread other_thread("test");
+ other_thread.Start();
+ scoped_refptr<SequencedTaskRunner> other_sequence =
+ other_thread.task_runner();
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ShouldRunTask, Unretained(&counter_)));
+
+ other_sequence->PostTask(
+ FROM_HERE,
+ base::BindOnce([](RunLoop* run_loop) { run_loop->QuitWhenIdle(); },
+ Unretained(&run_loop_)));
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ShouldRunTask, Unretained(&counter_)));
+
+ run_loop_.Run();
+
+ // Regardless of the outcome of the race this thread shouldn't have been idle
+ // until the counter was ticked twice.
+ EXPECT_EQ(2, counter_);
+}
+
+// Verify that QuitWhenIdleClosure can be executed from another sequence.
+TEST_P(RunLoopTest, QuitWhenIdleFromOtherSequenceWithClosure) {
+ Thread other_thread("test");
+ other_thread.Start();
+ scoped_refptr<SequencedTaskRunner> other_sequence =
+ other_thread.task_runner();
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ShouldRunTask, Unretained(&counter_)));
+
+ other_sequence->PostTask(FROM_HERE, run_loop_.QuitWhenIdleClosure());
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ShouldRunTask, Unretained(&counter_)));
+
+ run_loop_.Run();
+
+ // Regardless of the outcome of the race this thread shouldn't have been idle
+ // until the counter was ticked twice.
+ EXPECT_EQ(2, counter_);
+}
+
+TEST_P(RunLoopTest, IsRunningOnCurrentThread) {
+ EXPECT_FALSE(RunLoop::IsRunningOnCurrentThread());
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce([]() { EXPECT_TRUE(RunLoop::IsRunningOnCurrentThread()); }));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop_.QuitClosure());
+ run_loop_.Run();
+}
+
+TEST_P(RunLoopTest, IsNestedOnCurrentThread) {
+ EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce([]() {
+ EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
+
+ RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce([]() {
+ EXPECT_TRUE(RunLoop::IsNestedOnCurrentThread());
+ }));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_run_loop.QuitClosure());
+
+ EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
+ nested_run_loop.Run();
+ EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
+ }));
+
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop_.QuitClosure());
+ run_loop_.Run();
+}
+
+namespace {
+
+class MockNestingObserver : public RunLoop::NestingObserver {
+ public:
+ MockNestingObserver() = default;
+
+ // RunLoop::NestingObserver:
+ MOCK_METHOD0(OnBeginNestedRunLoop, void());
+ MOCK_METHOD0(OnExitNestedRunLoop, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockNestingObserver);
+};
+
+class MockTask {
+ public:
+ MockTask() = default;
+ MOCK_METHOD0(Task, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockTask);
+};
+
+} // namespace
+
+TEST_P(RunLoopTest, NestingObservers) {
+ testing::StrictMock<MockNestingObserver> nesting_observer;
+ testing::StrictMock<MockTask> mock_task_a;
+ testing::StrictMock<MockTask> mock_task_b;
+
+ RunLoop::AddNestingObserverOnCurrentThread(&nesting_observer);
+
+ const RepeatingClosure run_nested_loop = Bind([]() {
+ RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_run_loop.QuitClosure());
+ nested_run_loop.Run();
+ });
+
+ // Generate a stack of nested RunLoops. OnBeginNestedRunLoop() is expected
+ // when beginning each nesting depth and OnExitNestedRunLoop() is expected
+ // when exiting each nesting depth. Each one of these tasks is ahead of the
+ // QuitClosures as those are only posted at the end of the queue when
+ // |run_nested_loop| is executed.
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_nested_loop);
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&MockTask::Task, base::Unretained(&mock_task_a)));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_nested_loop);
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&MockTask::Task, base::Unretained(&mock_task_b)));
+
+ {
+ testing::InSequence in_sequence;
+ EXPECT_CALL(nesting_observer, OnBeginNestedRunLoop());
+ EXPECT_CALL(mock_task_a, Task());
+ EXPECT_CALL(nesting_observer, OnBeginNestedRunLoop());
+ EXPECT_CALL(mock_task_b, Task());
+ EXPECT_CALL(nesting_observer, OnExitNestedRunLoop()).Times(2);
+ }
+ run_loop_.RunUntilIdle();
+
+ RunLoop::RemoveNestingObserverOnCurrentThread(&nesting_observer);
+}
+
+TEST_P(RunLoopTest, DisallowRunningForTesting) {
+ RunLoop::ScopedDisallowRunningForTesting disallow_running;
+ EXPECT_DCHECK_DEATH({ run_loop_.RunUntilIdle(); });
+}
+
+TEST_P(RunLoopTest, ExpiredDisallowRunningForTesting) {
+ { RunLoop::ScopedDisallowRunningForTesting disallow_running; }
+ // Running should be fine after |disallow_running| goes out of scope.
+ run_loop_.RunUntilIdle();
+}
+
+INSTANTIATE_TEST_CASE_P(Real,
+ RunLoopTest,
+ testing::Values(RunLoopTestType::kRealEnvironment));
+INSTANTIATE_TEST_CASE_P(Mock,
+ RunLoopTest,
+ testing::Values(RunLoopTestType::kTestDelegate));
+
+TEST(RunLoopDeathTest, MustRegisterBeforeInstantiating) {
+ TestBoundDelegate unbound_test_delegate_;
+ // RunLoop::RunLoop() should CHECK fetching the ThreadTaskRunnerHandle.
+ EXPECT_DEATH_IF_SUPPORTED({ RunLoop(); }, "");
+}
+
+TEST(RunLoopDelegateTest, NestableTasksDontRunInDefaultNestedLoops) {
+ TestBoundDelegate test_delegate;
+ test_delegate.BindToCurrentThread();
+
+ base::Thread other_thread("test");
+ other_thread.Start();
+
+ RunLoop main_loop;
+ // A nested run loop which isn't kNestableTasksAllowed.
+ RunLoop nested_run_loop(RunLoop::Type::kDefault);
+
+ bool nested_run_loop_ended = false;
+
+ // The first task on the main loop will result in a nested run loop. Since
+ // it's not kNestableTasksAllowed, no further task should be processed until
+ // it's quit.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce([](RunLoop* nested_run_loop) { nested_run_loop->Run(); },
+ Unretained(&nested_run_loop)));
+
+ // Post a task that will fail if it runs inside the nested run loop.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(
+ [](const bool& nested_run_loop_ended,
+ OnceClosure continuation_callback) {
+ EXPECT_TRUE(nested_run_loop_ended);
+ EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
+ std::move(continuation_callback).Run();
+ },
+ ConstRef(nested_run_loop_ended), main_loop.QuitClosure()));
+
+ // Post a task flipping the boolean bit for extra verification right before
+ // quitting |nested_run_loop|.
+ other_thread.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(
+ [](bool* nested_run_loop_ended) {
+ EXPECT_FALSE(*nested_run_loop_ended);
+ *nested_run_loop_ended = true;
+ },
+ Unretained(&nested_run_loop_ended)),
+ TestTimeouts::tiny_timeout());
+ // Post an async delayed task to exit the run loop when idle. This confirms
+ // that (1) the test task only ran in the main loop after the nested loop
+ // exited and (2) the nested run loop actually considers itself idle while
+ // spinning. Note: The quit closure needs to be injected directly on the
+ // delegate as invoking QuitWhenIdle() off-thread results in a thread bounce
+ // which will not processed because of the very logic under test (nestable
+ // tasks don't run in |nested_run_loop|).
+ other_thread.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(
+ [](TestBoundDelegate* test_delegate, OnceClosure injected_closure) {
+ test_delegate->InjectClosureOnDelegate(std::move(injected_closure));
+ },
+ Unretained(&test_delegate), nested_run_loop.QuitWhenIdleClosure()),
+ TestTimeouts::tiny_timeout());
+
+ main_loop.Run();
+}
+
+} // namespace base
diff --git a/base/safe_numerics_unittest.cc b/base/safe_numerics_unittest.cc
new file mode 100644
index 0000000000..44675cf72c
--- /dev/null
+++ b/base/safe_numerics_unittest.cc
@@ -0,0 +1,1640 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <type_traits>
+
+#include "base/compiler_specific.h"
+
+// WARNING: This block must come before the base/numerics headers are included.
+// These tests deliberately cause arithmetic boundary errors. If the compiler is
+// aggressive enough, it can const detect these errors, so we disable warnings.
+#if defined(OS_WIN)
+#pragma warning(disable : 4756) // Arithmetic overflow.
+#pragma warning(disable : 4293) // Invalid shift.
+#endif
+
+// This may not need to come before the base/numerics headers, but let's keep
+// it close to the MSVC equivalent.
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Winteger-overflow"
+#endif
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math.h"
+#include "base/test/gtest_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS)
+#include <mmintrin.h>
+#endif
+
+namespace base {
+namespace internal {
+
+using std::numeric_limits;
+
+// This is a helper function for finding the maximum value in Src that can be
+// wholy represented as the destination floating-point type.
+template <typename Dst, typename Src>
+Dst GetMaxConvertibleToFloat() {
+ using DstLimits = numeric_limits<Dst>;
+ using SrcLimits = numeric_limits<Src>;
+ static_assert(SrcLimits::is_specialized, "Source must be numeric.");
+ static_assert(DstLimits::is_specialized, "Destination must be numeric.");
+ CHECK(DstLimits::is_iec559);
+
+ if (SrcLimits::digits <= DstLimits::digits &&
+ MaxExponent<Src>::value <= MaxExponent<Dst>::value)
+ return SrcLimits::max();
+ Src max = SrcLimits::max() / 2 + (SrcLimits::is_integer ? 1 : 0);
+ while (max != static_cast<Src>(static_cast<Dst>(max))) {
+ max /= 2;
+ }
+ return static_cast<Dst>(max);
+}
+
+// Test corner case promotions used
+static_assert(IsIntegerArithmeticSafe<int32_t, int8_t, int8_t>::value, "");
+static_assert(IsIntegerArithmeticSafe<int32_t, int16_t, int8_t>::value, "");
+static_assert(IsIntegerArithmeticSafe<int32_t, int8_t, int16_t>::value, "");
+static_assert(!IsIntegerArithmeticSafe<int32_t, int32_t, int8_t>::value, "");
+static_assert(BigEnoughPromotion<int16_t, int8_t>::is_contained, "");
+static_assert(BigEnoughPromotion<int32_t, uint32_t>::is_contained, "");
+static_assert(BigEnoughPromotion<intmax_t, int8_t>::is_contained, "");
+static_assert(!BigEnoughPromotion<uintmax_t, int8_t>::is_contained, "");
+static_assert(
+ std::is_same<BigEnoughPromotion<int16_t, int8_t>::type, int16_t>::value,
+ "");
+static_assert(
+ std::is_same<BigEnoughPromotion<int32_t, uint32_t>::type, int64_t>::value,
+ "");
+static_assert(
+ std::is_same<BigEnoughPromotion<intmax_t, int8_t>::type, intmax_t>::value,
+ "");
+static_assert(
+ std::is_same<BigEnoughPromotion<uintmax_t, int8_t>::type, uintmax_t>::value,
+ "");
+static_assert(BigEnoughPromotion<int16_t, int8_t>::is_contained, "");
+static_assert(BigEnoughPromotion<int32_t, uint32_t>::is_contained, "");
+static_assert(BigEnoughPromotion<intmax_t, int8_t>::is_contained, "");
+static_assert(!BigEnoughPromotion<uintmax_t, int8_t>::is_contained, "");
+static_assert(
+ std::is_same<FastIntegerArithmeticPromotion<int16_t, int8_t>::type,
+ int32_t>::value,
+ "");
+static_assert(
+ std::is_same<FastIntegerArithmeticPromotion<int32_t, uint32_t>::type,
+ int64_t>::value,
+ "");
+static_assert(
+ std::is_same<FastIntegerArithmeticPromotion<intmax_t, int8_t>::type,
+ intmax_t>::value,
+ "");
+static_assert(
+ std::is_same<FastIntegerArithmeticPromotion<uintmax_t, int8_t>::type,
+ uintmax_t>::value,
+ "");
+static_assert(FastIntegerArithmeticPromotion<int16_t, int8_t>::is_contained,
+ "");
+static_assert(FastIntegerArithmeticPromotion<int32_t, uint32_t>::is_contained,
+ "");
+static_assert(!FastIntegerArithmeticPromotion<intmax_t, int8_t>::is_contained,
+ "");
+static_assert(!FastIntegerArithmeticPromotion<uintmax_t, int8_t>::is_contained,
+ "");
+
+template <typename U>
+U GetNumericValueForTest(const CheckedNumeric<U>& src) {
+ return src.state_.value();
+}
+
+template <typename U>
+U GetNumericValueForTest(const ClampedNumeric<U>& src) {
+ return static_cast<U>(src);
+}
+
+template <typename U>
+U GetNumericValueForTest(const U& src) {
+ return src;
+}
+
+// Logs the ValueOrDie() failure instead of crashing.
+struct LogOnFailure {
+ template <typename T>
+ static T HandleFailure() {
+ LOG(WARNING) << "ValueOrDie() failed unexpectedly.";
+ return T();
+ }
+};
+
+template <typename T>
+constexpr T GetValue(const T& src) {
+ return src;
+}
+
+template <typename T, typename U>
+constexpr T GetValueAsDest(const U& src) {
+ return static_cast<T>(src);
+}
+
+template <typename T>
+constexpr T GetValue(const CheckedNumeric<T>& src) {
+ return src.template ValueOrDie<T, LogOnFailure>();
+}
+
+template <typename T, typename U>
+constexpr T GetValueAsDest(const CheckedNumeric<U>& src) {
+ return src.template ValueOrDie<T, LogOnFailure>();
+}
+
+template <typename T>
+constexpr T GetValue(const ClampedNumeric<T>& src) {
+ return static_cast<T>(src);
+}
+
+template <typename T, typename U>
+constexpr T GetValueAsDest(const ClampedNumeric<U>& src) {
+ return static_cast<T>(src);
+}
+
+// Helper macros to wrap displaying the conversion types and line numbers.
+#define TEST_EXPECTED_VALIDITY(expected, actual) \
+ EXPECT_EQ(expected, (actual).template Cast<Dst>().IsValid()) \
+ << "Result test: Value " << GetNumericValueForTest(actual) << " as " \
+ << dst << " on line " << line
+
+#define TEST_EXPECTED_SUCCESS(actual) TEST_EXPECTED_VALIDITY(true, actual)
+#define TEST_EXPECTED_FAILURE(actual) TEST_EXPECTED_VALIDITY(false, actual)
+
+// We have to handle promotions, so infer the underlying type below from actual.
+#define TEST_EXPECTED_VALUE(expected, actual) \
+ EXPECT_EQ(GetValue(expected), GetValueAsDest<decltype(expected)>(actual)) \
+ << "Result test: Value " << GetNumericValueForTest(actual) << " as " \
+ << dst << " on line " << line
+
+// Test the simple pointer arithmetic overrides.
+template <typename Dst>
+void TestStrictPointerMath() {
+ Dst dummy_value = 0;
+ Dst* dummy_ptr = &dummy_value;
+ static const Dst kDummyOffset = 2; // Don't want to go too far.
+ EXPECT_EQ(dummy_ptr + kDummyOffset,
+ dummy_ptr + StrictNumeric<Dst>(kDummyOffset));
+ EXPECT_EQ(dummy_ptr - kDummyOffset,
+ dummy_ptr - StrictNumeric<Dst>(kDummyOffset));
+ EXPECT_NE(dummy_ptr, dummy_ptr + StrictNumeric<Dst>(kDummyOffset));
+ EXPECT_NE(dummy_ptr, dummy_ptr - StrictNumeric<Dst>(kDummyOffset));
+ EXPECT_DEATH_IF_SUPPORTED(
+ dummy_ptr + StrictNumeric<size_t>(std::numeric_limits<size_t>::max()),
+ "");
+}
+
+// Signed integer arithmetic.
+template <typename Dst>
+static void TestSpecializedArithmetic(
+ const char* dst,
+ int line,
+ typename std::enable_if<numeric_limits<Dst>::is_integer &&
+ numeric_limits<Dst>::is_signed,
+ int>::type = 0) {
+ using DstLimits = SaturationDefaultLimits<Dst>;
+ TEST_EXPECTED_FAILURE(-CheckedNumeric<Dst>(DstLimits::lowest()));
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()).Abs());
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).Abs());
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ MakeCheckedNum(-DstLimits::max()).Abs());
+
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ -ClampedNumeric<Dst>(DstLimits::lowest()));
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()).Abs());
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(-1).Abs());
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ MakeClampedNum(-DstLimits::max()).Abs());
+
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::max()) + -1);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) + -1);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) +
+ DstLimits::lowest());
+
+ TEST_EXPECTED_VALUE(DstLimits::max() - 1,
+ ClampedNumeric<Dst>(DstLimits::max()) + -1);
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) + -1);
+ TEST_EXPECTED_VALUE(
+ DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) + DstLimits::lowest());
+
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) - 1);
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()) - -1);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) -
+ DstLimits::lowest());
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) -
+ DstLimits::max());
+
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) - 1);
+ TEST_EXPECTED_VALUE(DstLimits::lowest() + 1,
+ ClampedNumeric<Dst>(DstLimits::lowest()) - -1);
+ TEST_EXPECTED_VALUE(
+ DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::max()) - DstLimits::lowest());
+ TEST_EXPECTED_VALUE(
+ DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) - DstLimits::max());
+
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) * 2);
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) * 2);
+
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) / -1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(-1) / 2);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) * -1);
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ CheckedNumeric<Dst>(DstLimits::lowest() + 1) * Dst(-1));
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ CheckedNumeric<Dst>(-1) * Dst(DstLimits::lowest() + 1));
+ TEST_EXPECTED_VALUE(DstLimits::lowest(),
+ CheckedNumeric<Dst>(DstLimits::lowest()) * Dst(1));
+ TEST_EXPECTED_VALUE(DstLimits::lowest(),
+ CheckedNumeric<Dst>(1) * Dst(DstLimits::lowest()));
+ TEST_EXPECTED_VALUE(
+ typename std::make_unsigned<Dst>::type(0) - DstLimits::lowest(),
+ MakeCheckedNum(DstLimits::lowest()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ MakeCheckedNum(DstLimits::max()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0).UnsignedAbs());
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).UnsignedAbs());
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).UnsignedAbs());
+
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) / -1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(-1) / 2);
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) * -1);
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ ClampedNumeric<Dst>(DstLimits::lowest() + 1) * Dst(-1));
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ ClampedNumeric<Dst>(-1) * Dst(DstLimits::lowest() + 1));
+ TEST_EXPECTED_VALUE(DstLimits::lowest(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) * Dst(1));
+ TEST_EXPECTED_VALUE(DstLimits::lowest(),
+ ClampedNumeric<Dst>(1) * Dst(DstLimits::lowest()));
+ TEST_EXPECTED_VALUE(
+ typename std::make_unsigned<Dst>::type(0) - DstLimits::lowest(),
+ MakeClampedNum(DstLimits::lowest()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ MakeClampedNum(DstLimits::max()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(0).UnsignedAbs());
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1).UnsignedAbs());
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(-1).UnsignedAbs());
+
+ // Modulus is legal only for integers.
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1);
+ TEST_EXPECTED_VALUE(-1, CheckedNumeric<Dst>(-1) % 2);
+ TEST_EXPECTED_VALUE(-1, CheckedNumeric<Dst>(-1) % -2);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::lowest()) % 2);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(DstLimits::max()) % 2);
+ // Test all the different modulus combinations.
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, 1 % CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1);
+ CheckedNumeric<Dst> checked_dst = 1;
+ TEST_EXPECTED_VALUE(0, checked_dst %= 1);
+ // Test that div by 0 is avoided but returns invalid result.
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) % 0);
+ // Test bit shifts.
+ volatile Dst negative_one = -1;
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) << negative_one);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1)
+ << (IntegerBitsPlusSign<Dst>::value - 1));
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(0)
+ << IntegerBitsPlusSign<Dst>::value);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) << 1);
+ TEST_EXPECTED_VALUE(
+ static_cast<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 2),
+ CheckedNumeric<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 2));
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0)
+ << (IntegerBitsPlusSign<Dst>::value - 1));
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) << 0);
+ TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) << 1);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) >>
+ IntegerBitsPlusSign<Dst>::value);
+ TEST_EXPECTED_VALUE(
+ 0, CheckedNumeric<Dst>(1) >> (IntegerBitsPlusSign<Dst>::value - 1));
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) >> negative_one);
+
+ // Modulus is legal only for integers.
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>() % 1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) % 1);
+ TEST_EXPECTED_VALUE(-1, ClampedNumeric<Dst>(-1) % 2);
+ TEST_EXPECTED_VALUE(-1, ClampedNumeric<Dst>(-1) % -2);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(DstLimits::lowest()) % 2);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(DstLimits::max()) % 2);
+ // Test all the different modulus combinations.
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) % ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, 1 % ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) % 1);
+ ClampedNumeric<Dst> clamped_dst = 1;
+ TEST_EXPECTED_VALUE(0, clamped_dst %= 1);
+ TEST_EXPECTED_VALUE(Dst(1), ClampedNumeric<Dst>(1) % 0);
+ // Test bit shifts.
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(1)
+ << (IntegerBitsPlusSign<Dst>::value - 1U));
+ TEST_EXPECTED_VALUE(Dst(0), ClampedNumeric<Dst>(0)
+ << (IntegerBitsPlusSign<Dst>::value + 0U));
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::max()) << 1U);
+ TEST_EXPECTED_VALUE(
+ static_cast<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 2U),
+ ClampedNumeric<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 2U));
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(0)
+ << (IntegerBitsPlusSign<Dst>::value - 1U));
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) << 0U);
+ TEST_EXPECTED_VALUE(2, ClampedNumeric<Dst>(1) << 1U);
+ TEST_EXPECTED_VALUE(
+ 0, ClampedNumeric<Dst>(1) >> (IntegerBitsPlusSign<Dst>::value + 0U));
+ TEST_EXPECTED_VALUE(
+ 0, ClampedNumeric<Dst>(1) >> (IntegerBitsPlusSign<Dst>::value - 1U));
+ TEST_EXPECTED_VALUE(
+ -1, ClampedNumeric<Dst>(-1) >> (IntegerBitsPlusSign<Dst>::value - 1U));
+ TEST_EXPECTED_VALUE(-1, ClampedNumeric<Dst>(DstLimits::lowest()) >>
+ (IntegerBitsPlusSign<Dst>::value - 0U));
+
+ TestStrictPointerMath<Dst>();
+}
+
+// Unsigned integer arithmetic.
+template <typename Dst>
+static void TestSpecializedArithmetic(
+ const char* dst,
+ int line,
+ typename std::enable_if<numeric_limits<Dst>::is_integer &&
+ !numeric_limits<Dst>::is_signed,
+ int>::type = 0) {
+ using DstLimits = SaturationDefaultLimits<Dst>;
+ TEST_EXPECTED_SUCCESS(-CheckedNumeric<Dst>(DstLimits::lowest()));
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()).Abs());
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) + -1);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) - 1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::lowest()) * 2);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) / 2);
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()).UnsignedAbs());
+ TEST_EXPECTED_SUCCESS(
+ CheckedNumeric<typename std::make_signed<Dst>::type>(
+ std::numeric_limits<typename std::make_signed<Dst>::type>::lowest())
+ .UnsignedAbs());
+ TEST_EXPECTED_VALUE(DstLimits::lowest(),
+ MakeCheckedNum(DstLimits::lowest()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ MakeCheckedNum(DstLimits::max()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0).UnsignedAbs());
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).UnsignedAbs());
+
+ TEST_EXPECTED_VALUE(0, -ClampedNumeric<Dst>(DstLimits::lowest()));
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(DstLimits::lowest()).Abs());
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) + -1);
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) - 1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(DstLimits::lowest()) * 2);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) / 2);
+ TEST_EXPECTED_VALUE(0,
+ ClampedNumeric<Dst>(DstLimits::lowest()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(
+ as_unsigned(
+ std::numeric_limits<typename std::make_signed<Dst>::type>::lowest()),
+ ClampedNumeric<typename std::make_signed<Dst>::type>(
+ std::numeric_limits<typename std::make_signed<Dst>::type>::lowest())
+ .UnsignedAbs());
+ TEST_EXPECTED_VALUE(DstLimits::lowest(),
+ MakeClampedNum(DstLimits::lowest()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ MakeClampedNum(DstLimits::max()).UnsignedAbs());
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(0).UnsignedAbs());
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1).UnsignedAbs());
+
+ // Modulus is legal only for integers.
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) % 2);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::lowest()) % 2);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(DstLimits::max()) % 2);
+ // Test all the different modulus combinations.
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, 1 % CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1);
+ CheckedNumeric<Dst> checked_dst = 1;
+ TEST_EXPECTED_VALUE(0, checked_dst %= 1);
+ // Test that div by 0 is avoided but returns invalid result.
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) % 0);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1)
+ << IntegerBitsPlusSign<Dst>::value);
+ // Test bit shifts.
+ volatile int negative_one = -1;
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) << negative_one);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1)
+ << IntegerBitsPlusSign<Dst>::value);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(0)
+ << IntegerBitsPlusSign<Dst>::value);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) << 1);
+ TEST_EXPECTED_VALUE(
+ static_cast<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 1),
+ CheckedNumeric<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 1));
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) << 0);
+ TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) << 1);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) >>
+ IntegerBitsPlusSign<Dst>::value);
+ TEST_EXPECTED_VALUE(
+ 0, CheckedNumeric<Dst>(1) >> (IntegerBitsPlusSign<Dst>::value - 1));
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) >> negative_one);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) & 1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) & 0);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0) & 1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) & 0);
+ TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
+ MakeCheckedNum(DstLimits::max()) & -1);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) | 1);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) | 0);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(0) | 1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0) | 0);
+ TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
+ CheckedNumeric<Dst>(0) | static_cast<Dst>(-1));
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) ^ 1);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) ^ 0);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(0) ^ 1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0) ^ 0);
+ TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
+ CheckedNumeric<Dst>(0) ^ static_cast<Dst>(-1));
+ TEST_EXPECTED_VALUE(DstLimits::max(), ~CheckedNumeric<Dst>(0));
+
+ // Modulus is legal only for integers.
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>() % 1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) % 1);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) % 2);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(DstLimits::lowest()) % 2);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(DstLimits::max()) % 2);
+ // Test all the different modulus combinations.
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) % ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, 1 % ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) % 1);
+ ClampedNumeric<Dst> clamped_dst = 1;
+ TEST_EXPECTED_VALUE(0, clamped_dst %= 1);
+ // Test that div by 0 is avoided but returns invalid result.
+ TEST_EXPECTED_VALUE(Dst(1), ClampedNumeric<Dst>(1) % 0);
+ // Test bit shifts.
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(1)
+ << as_unsigned(IntegerBitsPlusSign<Dst>::value));
+ TEST_EXPECTED_VALUE(Dst(0), ClampedNumeric<Dst>(0) << as_unsigned(
+ IntegerBitsPlusSign<Dst>::value));
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::max()) << 1U);
+ TEST_EXPECTED_VALUE(
+ static_cast<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 1U),
+ ClampedNumeric<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 1U));
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) << 0U);
+ TEST_EXPECTED_VALUE(2, ClampedNumeric<Dst>(1) << 1U);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) >>
+ as_unsigned(IntegerBitsPlusSign<Dst>::value));
+ TEST_EXPECTED_VALUE(
+ 0, ClampedNumeric<Dst>(1) >> (IntegerBitsPlusSign<Dst>::value - 1U));
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) & 1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) & 0);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(0) & 1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) & 0);
+ TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
+ MakeClampedNum(DstLimits::max()) & -1);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) | 1);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) | 0);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(0) | 1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(0) | 0);
+ TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
+ ClampedNumeric<Dst>(0) | static_cast<Dst>(-1));
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) ^ 1);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) ^ 0);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(0) ^ 1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(0) ^ 0);
+ TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
+ ClampedNumeric<Dst>(0) ^ static_cast<Dst>(-1));
+ TEST_EXPECTED_VALUE(DstLimits::max(), ~ClampedNumeric<Dst>(0));
+
+ TestStrictPointerMath<Dst>();
+}
+
+// Floating point arithmetic.
+template <typename Dst>
+void TestSpecializedArithmetic(
+ const char* dst,
+ int line,
+ typename std::enable_if<numeric_limits<Dst>::is_iec559, int>::type = 0) {
+ using DstLimits = SaturationDefaultLimits<Dst>;
+ TEST_EXPECTED_SUCCESS(-CheckedNumeric<Dst>(DstLimits::lowest()));
+
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()).Abs());
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).Abs());
+
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()) + -1);
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::max()) + 1);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) +
+ DstLimits::lowest());
+
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) -
+ DstLimits::lowest());
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) -
+ DstLimits::max());
+
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) * 2);
+
+ TEST_EXPECTED_VALUE(-0.5, CheckedNumeric<Dst>(-1.0) / 2);
+
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ -ClampedNumeric<Dst>(DstLimits::lowest()));
+
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ ClampedNumeric<Dst>(DstLimits::lowest()).Abs());
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(-1).Abs());
+
+ TEST_EXPECTED_VALUE(DstLimits::lowest() - 1,
+ ClampedNumeric<Dst>(DstLimits::lowest()) + -1);
+ TEST_EXPECTED_VALUE(DstLimits::max() + 1,
+ ClampedNumeric<Dst>(DstLimits::max()) + 1);
+ TEST_EXPECTED_VALUE(
+ DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) + DstLimits::lowest());
+
+ TEST_EXPECTED_VALUE(
+ DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::max()) - DstLimits::lowest());
+ TEST_EXPECTED_VALUE(
+ DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) - DstLimits::max());
+
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::lowest()) * 2);
+
+ TEST_EXPECTED_VALUE(-0.5, ClampedNumeric<Dst>(-1.0) / 2);
+}
+
+// Generic arithmetic tests.
+template <typename Dst>
+static void TestArithmetic(const char* dst, int line) {
+ using DstLimits = SaturationDefaultLimits<Dst>;
+
+ EXPECT_EQ(true, CheckedNumeric<Dst>().IsValid());
+ EXPECT_EQ(false, CheckedNumeric<Dst>(CheckedNumeric<Dst>(DstLimits::max()) *
+ DstLimits::max())
+ .IsValid());
+ EXPECT_EQ(static_cast<Dst>(0), CheckedNumeric<Dst>().ValueOrDie());
+ EXPECT_EQ(static_cast<Dst>(0), CheckedNumeric<Dst>().ValueOrDefault(1));
+ EXPECT_EQ(static_cast<Dst>(1),
+ CheckedNumeric<Dst>(CheckedNumeric<Dst>(DstLimits::max()) *
+ DstLimits::max())
+ .ValueOrDefault(1));
+
+ // Test the operator combinations.
+ TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) + CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) - CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) * CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(2, 1 + CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, 1 - CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, 1 * CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, 1 / CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) + 1);
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) - 1);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) * 1);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / 1);
+ CheckedNumeric<Dst> checked_dst = 1;
+ TEST_EXPECTED_VALUE(2, checked_dst += 1);
+ checked_dst = 1;
+ TEST_EXPECTED_VALUE(0, checked_dst -= 1);
+ checked_dst = 1;
+ TEST_EXPECTED_VALUE(1, checked_dst *= 1);
+ checked_dst = 1;
+ TEST_EXPECTED_VALUE(1, checked_dst /= 1);
+
+ TEST_EXPECTED_VALUE(2, ClampedNumeric<Dst>(1) + ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) - ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) * ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) / ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(2, 1 + ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(0, 1 - ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, 1 * ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, 1 / ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(2, ClampedNumeric<Dst>(1) + 1);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(1) - 1);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) * 1);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) / 1);
+ ClampedNumeric<Dst> clamped_dst = 1;
+ TEST_EXPECTED_VALUE(2, clamped_dst += 1);
+ clamped_dst = 1;
+ TEST_EXPECTED_VALUE(0, clamped_dst -= 1);
+ clamped_dst = 1;
+ TEST_EXPECTED_VALUE(1, clamped_dst *= 1);
+ clamped_dst = 1;
+ TEST_EXPECTED_VALUE(1, clamped_dst /= 1);
+
+ // Generic negation.
+ if (DstLimits::is_signed) {
+ TEST_EXPECTED_VALUE(0, -CheckedNumeric<Dst>());
+ TEST_EXPECTED_VALUE(-1, -CheckedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, -CheckedNumeric<Dst>(-1));
+ TEST_EXPECTED_VALUE(static_cast<Dst>(DstLimits::max() * -1),
+ -CheckedNumeric<Dst>(DstLimits::max()));
+
+ TEST_EXPECTED_VALUE(0, -ClampedNumeric<Dst>());
+ TEST_EXPECTED_VALUE(-1, -ClampedNumeric<Dst>(1));
+ TEST_EXPECTED_VALUE(1, -ClampedNumeric<Dst>(-1));
+ TEST_EXPECTED_VALUE(static_cast<Dst>(DstLimits::max() * -1),
+ -ClampedNumeric<Dst>(DstLimits::max()));
+
+ // The runtime paths for saturated negation differ significantly from what
+ // gets evaluated at compile-time. Making this test volatile forces the
+ // compiler to generate code rather than fold constant expressions.
+ volatile Dst value = Dst(0);
+ TEST_EXPECTED_VALUE(0, -MakeClampedNum(value));
+ value = Dst(1);
+ TEST_EXPECTED_VALUE(-1, -MakeClampedNum(value));
+ value = Dst(2);
+ TEST_EXPECTED_VALUE(-2, -MakeClampedNum(value));
+ value = Dst(-1);
+ TEST_EXPECTED_VALUE(1, -MakeClampedNum(value));
+ value = Dst(-2);
+ TEST_EXPECTED_VALUE(2, -MakeClampedNum(value));
+ value = DstLimits::max();
+ TEST_EXPECTED_VALUE(Dst(DstLimits::max() * -1), -MakeClampedNum(value));
+ value = Dst(-1 * DstLimits::max());
+ TEST_EXPECTED_VALUE(DstLimits::max(), -MakeClampedNum(value));
+ value = DstLimits::lowest();
+ TEST_EXPECTED_VALUE(DstLimits::max(), -MakeClampedNum(value));
+ }
+
+ // Generic absolute value.
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>().Abs());
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).Abs());
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ CheckedNumeric<Dst>(DstLimits::max()).Abs());
+
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>().Abs());
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1).Abs());
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ ClampedNumeric<Dst>(DstLimits::max()).Abs());
+
+ // Generic addition.
+ TEST_EXPECTED_VALUE(1, (CheckedNumeric<Dst>() + 1));
+ TEST_EXPECTED_VALUE(2, (CheckedNumeric<Dst>(1) + 1));
+ if (numeric_limits<Dst>::is_signed)
+ TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(-1) + 1));
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()) + 1);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) +
+ DstLimits::max());
+
+ TEST_EXPECTED_VALUE(1, (ClampedNumeric<Dst>() + 1));
+ TEST_EXPECTED_VALUE(2, (ClampedNumeric<Dst>(1) + 1));
+ if (numeric_limits<Dst>::is_signed)
+ TEST_EXPECTED_VALUE(0, (ClampedNumeric<Dst>(-1) + 1));
+ TEST_EXPECTED_VALUE(DstLimits::lowest() + 1,
+ ClampedNumeric<Dst>(DstLimits::lowest()) + 1);
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::max()) + DstLimits::max());
+
+ // Generic subtraction.
+ TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(1) - 1));
+ TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::max()) - 1);
+ if (numeric_limits<Dst>::is_signed) {
+ TEST_EXPECTED_VALUE(-1, (CheckedNumeric<Dst>() - 1));
+ TEST_EXPECTED_VALUE(-2, (CheckedNumeric<Dst>(-1) - 1));
+ } else {
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) - -1);
+ }
+
+ TEST_EXPECTED_VALUE(0, (ClampedNumeric<Dst>(1) - 1));
+ TEST_EXPECTED_VALUE(DstLimits::max() - 1,
+ ClampedNumeric<Dst>(DstLimits::max()) - 1);
+ if (numeric_limits<Dst>::is_signed) {
+ TEST_EXPECTED_VALUE(-1, (ClampedNumeric<Dst>() - 1));
+ TEST_EXPECTED_VALUE(-2, (ClampedNumeric<Dst>(-1) - 1));
+ } else {
+ TEST_EXPECTED_VALUE(DstLimits::max(),
+ ClampedNumeric<Dst>(DstLimits::max()) - -1);
+ }
+
+ // Generic multiplication.
+ TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>() * 1));
+ TEST_EXPECTED_VALUE(1, (CheckedNumeric<Dst>(1) * 1));
+ TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(0) * 0));
+ if (numeric_limits<Dst>::is_signed) {
+ TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(-1) * 0));
+ TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(0) * -1));
+ TEST_EXPECTED_VALUE(-2, (CheckedNumeric<Dst>(-1) * 2));
+ } else {
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) * -2);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) *
+ CheckedNumeric<uintmax_t>(-2));
+ }
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) *
+ DstLimits::max());
+
+ TEST_EXPECTED_VALUE(0, (ClampedNumeric<Dst>() * 1));
+ TEST_EXPECTED_VALUE(1, (ClampedNumeric<Dst>(1) * 1));
+ TEST_EXPECTED_VALUE(0, (ClampedNumeric<Dst>(0) * 0));
+ if (numeric_limits<Dst>::is_signed) {
+ TEST_EXPECTED_VALUE(0, (ClampedNumeric<Dst>(-1) * 0));
+ TEST_EXPECTED_VALUE(0, (ClampedNumeric<Dst>(0) * -1));
+ TEST_EXPECTED_VALUE(-2, (ClampedNumeric<Dst>(-1) * 2));
+ } else {
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ ClampedNumeric<Dst>(DstLimits::max()) * -2);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(DstLimits::max()) *
+ ClampedNumeric<uintmax_t>(-2));
+ }
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ ClampedNumeric<Dst>(DstLimits::max()) * DstLimits::max());
+
+ // Generic division.
+ TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() / 1);
+ TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / 1);
+ TEST_EXPECTED_VALUE(DstLimits::lowest() / 2,
+ CheckedNumeric<Dst>(DstLimits::lowest()) / 2);
+ TEST_EXPECTED_VALUE(DstLimits::max() / 2,
+ CheckedNumeric<Dst>(DstLimits::max()) / 2);
+ TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) / 0);
+
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>() / 1);
+ TEST_EXPECTED_VALUE(1, ClampedNumeric<Dst>(1) / 1);
+ TEST_EXPECTED_VALUE(DstLimits::lowest() / 2,
+ ClampedNumeric<Dst>(DstLimits::lowest()) / 2);
+ TEST_EXPECTED_VALUE(DstLimits::max() / 2,
+ ClampedNumeric<Dst>(DstLimits::max()) / 2);
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), ClampedNumeric<Dst>(1) / 0);
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(), ClampedNumeric<Dst>(-1) / 0);
+ TEST_EXPECTED_VALUE(0, ClampedNumeric<Dst>(0) / 0);
+
+ TestSpecializedArithmetic<Dst>(dst, line);
+}
+
+// Helper macro to wrap displaying the conversion types and line numbers.
+#define TEST_ARITHMETIC(Dst) TestArithmetic<Dst>(#Dst, __LINE__)
+
+TEST(SafeNumerics, SignedIntegerMath) {
+ TEST_ARITHMETIC(int8_t);
+ TEST_ARITHMETIC(int16_t);
+ TEST_ARITHMETIC(int);
+ TEST_ARITHMETIC(intptr_t);
+ TEST_ARITHMETIC(intmax_t);
+}
+
+TEST(SafeNumerics, UnsignedIntegerMath) {
+ TEST_ARITHMETIC(uint8_t);
+ TEST_ARITHMETIC(uint16_t);
+ TEST_ARITHMETIC(unsigned int);
+ TEST_ARITHMETIC(uintptr_t);
+ TEST_ARITHMETIC(uintmax_t);
+}
+
+TEST(SafeNumerics, FloatingPointMath) {
+ TEST_ARITHMETIC(float);
+ TEST_ARITHMETIC(double);
+}
+
+// Enumerates the five different conversions types we need to test.
+enum NumericConversionType {
+ SIGN_PRESERVING_VALUE_PRESERVING,
+ SIGN_PRESERVING_NARROW,
+ SIGN_TO_UNSIGN_WIDEN_OR_EQUAL,
+ SIGN_TO_UNSIGN_NARROW,
+ UNSIGN_TO_SIGN_NARROW_OR_EQUAL,
+};
+
+// Template covering the different conversion tests.
+template <typename Dst, typename Src, NumericConversionType conversion>
+struct TestNumericConversion {};
+
+enum RangeConstraint {
+ RANGE_VALID = 0x0, // Value can be represented by the destination type.
+ RANGE_UNDERFLOW = 0x1, // Value would underflow.
+ RANGE_OVERFLOW = 0x2, // Value would overflow.
+ RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW // Invalid (i.e. NaN).
+};
+
+// These are some wrappers to make the tests a bit cleaner.
+constexpr RangeConstraint RangeCheckToEnum(const RangeCheck constraint) {
+ return static_cast<RangeConstraint>(
+ static_cast<int>(constraint.IsOverflowFlagSet()) << 1 |
+ static_cast<int>(constraint.IsUnderflowFlagSet()));
+}
+
+// EXPECT_EQ wrappers providing specific detail on test failures.
+#define TEST_EXPECTED_RANGE(expected, actual) \
+ EXPECT_EQ(expected, \
+ RangeCheckToEnum(DstRangeRelationToSrcRange<Dst>(actual))) \
+ << "Conversion test: " << src << " value " << actual << " to " << dst \
+ << " on line " << line
+
+template <typename Dst, typename Src>
+void TestStrictComparison(const char* dst, const char* src, int line) {
+ using DstLimits = numeric_limits<Dst>;
+ using SrcLimits = numeric_limits<Src>;
+ static_assert(StrictNumeric<Src>(SrcLimits::lowest()) < DstLimits::max(), "");
+ static_assert(StrictNumeric<Src>(SrcLimits::lowest()) < SrcLimits::max(), "");
+ static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) >= DstLimits::max()),
+ "");
+ static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) >= SrcLimits::max()),
+ "");
+ static_assert(StrictNumeric<Src>(SrcLimits::lowest()) <= DstLimits::max(),
+ "");
+ static_assert(StrictNumeric<Src>(SrcLimits::lowest()) <= SrcLimits::max(),
+ "");
+ static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) > DstLimits::max()),
+ "");
+ static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) > SrcLimits::max()),
+ "");
+ static_assert(StrictNumeric<Src>(SrcLimits::max()) > DstLimits::lowest(), "");
+ static_assert(StrictNumeric<Src>(SrcLimits::max()) > SrcLimits::lowest(), "");
+ static_assert(!(StrictNumeric<Src>(SrcLimits::max()) <= DstLimits::lowest()),
+ "");
+ static_assert(!(StrictNumeric<Src>(SrcLimits::max()) <= SrcLimits::lowest()),
+ "");
+ static_assert(StrictNumeric<Src>(SrcLimits::max()) >= DstLimits::lowest(),
+ "");
+ static_assert(StrictNumeric<Src>(SrcLimits::max()) >= SrcLimits::lowest(),
+ "");
+ static_assert(!(StrictNumeric<Src>(SrcLimits::max()) < DstLimits::lowest()),
+ "");
+ static_assert(!(StrictNumeric<Src>(SrcLimits::max()) < SrcLimits::lowest()),
+ "");
+ static_assert(StrictNumeric<Src>(static_cast<Src>(1)) == static_cast<Dst>(1),
+ "");
+ static_assert(StrictNumeric<Src>(static_cast<Src>(1)) != static_cast<Dst>(0),
+ "");
+ static_assert(StrictNumeric<Src>(SrcLimits::max()) != static_cast<Dst>(0),
+ "");
+ static_assert(StrictNumeric<Src>(SrcLimits::max()) != DstLimits::lowest(),
+ "");
+ static_assert(
+ !(StrictNumeric<Src>(static_cast<Src>(1)) != static_cast<Dst>(1)), "");
+ static_assert(
+ !(StrictNumeric<Src>(static_cast<Src>(1)) == static_cast<Dst>(0)), "");
+
+ // Due to differences in float handling between compilers, these aren't
+ // compile-time constants everywhere. So, we use run-time tests.
+ EXPECT_EQ(
+ SrcLimits::max(),
+ MakeCheckedNum(SrcLimits::max()).Max(DstLimits::lowest()).ValueOrDie());
+ EXPECT_EQ(
+ DstLimits::max(),
+ MakeCheckedNum(SrcLimits::lowest()).Max(DstLimits::max()).ValueOrDie());
+ EXPECT_EQ(
+ DstLimits::lowest(),
+ MakeCheckedNum(SrcLimits::max()).Min(DstLimits::lowest()).ValueOrDie());
+ EXPECT_EQ(
+ SrcLimits::lowest(),
+ MakeCheckedNum(SrcLimits::lowest()).Min(DstLimits::max()).ValueOrDie());
+ EXPECT_EQ(SrcLimits::lowest(), CheckMin(MakeStrictNum(1), MakeCheckedNum(0),
+ DstLimits::max(), SrcLimits::lowest())
+ .ValueOrDie());
+ EXPECT_EQ(DstLimits::max(), CheckMax(MakeStrictNum(1), MakeCheckedNum(0),
+ DstLimits::max(), SrcLimits::lowest())
+ .ValueOrDie());
+
+ EXPECT_EQ(SrcLimits::max(),
+ MakeClampedNum(SrcLimits::max()).Max(DstLimits::lowest()));
+ EXPECT_EQ(DstLimits::max(),
+ MakeClampedNum(SrcLimits::lowest()).Max(DstLimits::max()));
+ EXPECT_EQ(DstLimits::lowest(),
+ MakeClampedNum(SrcLimits::max()).Min(DstLimits::lowest()));
+ EXPECT_EQ(SrcLimits::lowest(),
+ MakeClampedNum(SrcLimits::lowest()).Min(DstLimits::max()));
+ EXPECT_EQ(SrcLimits::lowest(),
+ ClampMin(MakeStrictNum(1), MakeClampedNum(0), DstLimits::max(),
+ SrcLimits::lowest()));
+ EXPECT_EQ(DstLimits::max(), ClampMax(MakeStrictNum(1), MakeClampedNum(0),
+ DstLimits::max(), SrcLimits::lowest()));
+
+ if (IsValueInRangeForNumericType<Dst>(SrcLimits::max())) {
+ TEST_EXPECTED_VALUE(Dst(SrcLimits::max()), (CommonMax<Dst, Src>()));
+ TEST_EXPECTED_VALUE(Dst(SrcLimits::max()),
+ (CommonMaxOrMin<Dst, Src>(false)));
+ } else {
+ TEST_EXPECTED_VALUE(DstLimits::max(), (CommonMax<Dst, Src>()));
+ TEST_EXPECTED_VALUE(DstLimits::max(), (CommonMaxOrMin<Dst, Src>(false)));
+ }
+
+ if (IsValueInRangeForNumericType<Dst>(SrcLimits::lowest())) {
+ TEST_EXPECTED_VALUE(Dst(SrcLimits::lowest()), (CommonMin<Dst, Src>()));
+ TEST_EXPECTED_VALUE(Dst(SrcLimits::lowest()),
+ (CommonMaxOrMin<Dst, Src>(true)));
+ } else {
+ TEST_EXPECTED_VALUE(DstLimits::lowest(), (CommonMin<Dst, Src>()));
+ TEST_EXPECTED_VALUE(DstLimits::lowest(), (CommonMaxOrMin<Dst, Src>(true)));
+ }
+}
+
+template <typename Dst, typename Src>
+struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_VALUE_PRESERVING> {
+ static void Test(const char* dst, const char* src, int line) {
+ using SrcLimits = SaturationDefaultLimits<Src>;
+ using DstLimits = SaturationDefaultLimits<Dst>;
+ // Integral to floating.
+ static_assert((DstLimits::is_iec559 && SrcLimits::is_integer) ||
+ // Not floating to integral and...
+ (!(DstLimits::is_integer && SrcLimits::is_iec559) &&
+ // Same sign, same numeric, source is narrower or same.
+ ((SrcLimits::is_signed == DstLimits::is_signed &&
+ MaxExponent<Dst>::value >= MaxExponent<Src>::value) ||
+ // Or signed destination and source is smaller
+ (DstLimits::is_signed &&
+ MaxExponent<Dst>::value >= MaxExponent<Src>::value))),
+ "Comparison must be sign preserving and value preserving");
+
+ TestStrictComparison<Dst, Src>(dst, src, line);
+
+ const CheckedNumeric<Dst> checked_dst = SrcLimits::max();
+ const ClampedNumeric<Dst> clamped_dst = SrcLimits::max();
+ TEST_EXPECTED_SUCCESS(checked_dst);
+ TEST_EXPECTED_VALUE(Dst(SrcLimits::max()), clamped_dst);
+ if (MaxExponent<Dst>::value > MaxExponent<Src>::value) {
+ if (MaxExponent<Dst>::value >= MaxExponent<Src>::value * 2 - 1) {
+ // At least twice larger type.
+ TEST_EXPECTED_SUCCESS(SrcLimits::max() * checked_dst);
+ TEST_EXPECTED_VALUE(SrcLimits::max() * clamped_dst,
+ Dst(SrcLimits::max()) * SrcLimits::max());
+ } else { // Larger, but not at least twice as large.
+ TEST_EXPECTED_FAILURE(SrcLimits::max() * checked_dst);
+ TEST_EXPECTED_SUCCESS(checked_dst + 1);
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(),
+ SrcLimits::max() * clamped_dst);
+ TEST_EXPECTED_VALUE(Dst(SrcLimits::max()) + Dst(1),
+ clamped_dst + Dst(1));
+ }
+ } else { // Same width type.
+ TEST_EXPECTED_FAILURE(checked_dst + 1);
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), clamped_dst + Dst(1));
+ }
+
+ TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max());
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
+ if (SrcLimits::is_iec559) {
+ TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max() * static_cast<Src>(-1));
+ TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity());
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1);
+ TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN());
+ } else if (numeric_limits<Src>::is_signed) {
+ // This block reverses the Src to Dst relationship so we don't have to
+ // complicate the test macros.
+ if (!std::is_same<Src, Dst>::value) {
+ TEST_EXPECTED_SUCCESS(CheckDiv(SrcLimits::lowest(), Dst(-1)));
+ }
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1));
+ TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest());
+ }
+ }
+};
+
+template <typename Dst, typename Src>
+struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_NARROW> {
+ static void Test(const char* dst, const char* src, int line) {
+ using SrcLimits = SaturationDefaultLimits<Src>;
+ using DstLimits = SaturationDefaultLimits<Dst>;
+ static_assert(SrcLimits::is_signed == DstLimits::is_signed,
+ "Destination and source sign must be the same");
+ static_assert(MaxExponent<Dst>::value <= MaxExponent<Src>::value,
+ "Destination must be narrower than source");
+
+ TestStrictComparison<Dst, Src>(dst, src, line);
+
+ const CheckedNumeric<Dst> checked_dst;
+ TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::max());
+ TEST_EXPECTED_VALUE(1, checked_dst + Src(1));
+ TEST_EXPECTED_FAILURE(checked_dst - SrcLimits::max());
+
+ ClampedNumeric<Dst> clamped_dst;
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), clamped_dst + SrcLimits::max());
+ TEST_EXPECTED_VALUE(1, clamped_dst + Src(1));
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(), clamped_dst - SrcLimits::max());
+ clamped_dst += SrcLimits::max();
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), clamped_dst);
+ clamped_dst = DstLimits::max();
+ clamped_dst += SrcLimits::max();
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), clamped_dst);
+ clamped_dst = DstLimits::max();
+ clamped_dst -= SrcLimits::max();
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(), clamped_dst);
+ clamped_dst = 0;
+
+ TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
+ if (SrcLimits::is_iec559) {
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1);
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1));
+ TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity());
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1);
+ TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN());
+ if (DstLimits::is_integer) {
+ if (SrcLimits::digits < DstLimits::digits) {
+ TEST_EXPECTED_RANGE(RANGE_OVERFLOW,
+ static_cast<Src>(DstLimits::max()));
+ } else {
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(DstLimits::max()));
+ }
+ TEST_EXPECTED_RANGE(
+ RANGE_VALID,
+ static_cast<Src>(GetMaxConvertibleToFloat<Src, Dst>()));
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(DstLimits::lowest()));
+ }
+ } else if (SrcLimits::is_signed) {
+ TEST_EXPECTED_VALUE(-1, checked_dst - static_cast<Src>(1));
+ TEST_EXPECTED_VALUE(-1, clamped_dst - static_cast<Src>(1));
+ TEST_EXPECTED_VALUE(Src(Src(0) - DstLimits::lowest()),
+ ClampDiv(DstLimits::lowest(), Src(-1)));
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::lowest());
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1));
+ } else {
+ TEST_EXPECTED_FAILURE(checked_dst - static_cast<Src>(1));
+ TEST_EXPECTED_VALUE(Dst(0), clamped_dst - static_cast<Src>(1));
+ TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest());
+ }
+ }
+};
+
+template <typename Dst, typename Src>
+struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL> {
+ static void Test(const char* dst, const char* src, int line) {
+ using SrcLimits = SaturationDefaultLimits<Src>;
+ using DstLimits = SaturationDefaultLimits<Dst>;
+ static_assert(MaxExponent<Dst>::value >= MaxExponent<Src>::value,
+ "Destination must be equal or wider than source.");
+ static_assert(SrcLimits::is_signed, "Source must be signed");
+ static_assert(!DstLimits::is_signed, "Destination must be unsigned");
+
+ TestStrictComparison<Dst, Src>(dst, src, line);
+
+ const CheckedNumeric<Dst> checked_dst;
+ TEST_EXPECTED_VALUE(SrcLimits::max(), checked_dst + SrcLimits::max());
+ TEST_EXPECTED_FAILURE(checked_dst + static_cast<Src>(-1));
+ TEST_EXPECTED_SUCCESS(checked_dst * static_cast<Src>(-1));
+ TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::lowest());
+ TEST_EXPECTED_VALUE(Dst(0), CheckDiv(Dst(0), Src(-1)));
+
+ const ClampedNumeric<Dst> clamped_dst;
+ TEST_EXPECTED_VALUE(SrcLimits::max(), clamped_dst + SrcLimits::max());
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ clamped_dst + static_cast<Src>(-1));
+ TEST_EXPECTED_VALUE(0, clamped_dst * static_cast<Src>(-1));
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ clamped_dst + SrcLimits::lowest());
+
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::lowest());
+ TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max());
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1));
+ }
+};
+
+template <typename Dst, typename Src>
+struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_NARROW> {
+ static void Test(const char* dst, const char* src, int line) {
+ using SrcLimits = SaturationDefaultLimits<Src>;
+ using DstLimits = SaturationDefaultLimits<Dst>;
+ static_assert(MaxExponent<Dst>::value < MaxExponent<Src>::value,
+ "Destination must be narrower than source.");
+ static_assert(SrcLimits::is_signed, "Source must be signed.");
+ static_assert(!DstLimits::is_signed, "Destination must be unsigned.");
+
+ TestStrictComparison<Dst, Src>(dst, src, line);
+
+ const CheckedNumeric<Dst> checked_dst;
+ TEST_EXPECTED_VALUE(1, checked_dst + static_cast<Src>(1));
+ TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::max());
+ TEST_EXPECTED_FAILURE(checked_dst + static_cast<Src>(-1));
+ TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::lowest());
+
+ ClampedNumeric<Dst> clamped_dst;
+ TEST_EXPECTED_VALUE(1, clamped_dst + static_cast<Src>(1));
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), clamped_dst + SrcLimits::max());
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ clamped_dst + static_cast<Src>(-1));
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(),
+ clamped_dst + SrcLimits::lowest());
+ clamped_dst += SrcLimits::max();
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), clamped_dst);
+ clamped_dst = DstLimits::max();
+ clamped_dst += SrcLimits::max();
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), clamped_dst);
+ clamped_dst = DstLimits::max();
+ clamped_dst -= SrcLimits::max();
+ TEST_EXPECTED_VALUE(DstLimits::Underflow(), clamped_dst);
+ clamped_dst = 0;
+
+ TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1));
+
+ // Additional saturation tests.
+ EXPECT_EQ(DstLimits::max(), saturated_cast<Dst>(SrcLimits::max()));
+ EXPECT_EQ(DstLimits::lowest(), saturated_cast<Dst>(SrcLimits::lowest()));
+
+ if (SrcLimits::is_iec559) {
+ EXPECT_EQ(Dst(0), saturated_cast<Dst>(SrcLimits::quiet_NaN()));
+
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1);
+ TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity());
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1);
+ TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN());
+ if (DstLimits::is_integer) {
+ if (SrcLimits::digits < DstLimits::digits) {
+ TEST_EXPECTED_RANGE(RANGE_OVERFLOW,
+ static_cast<Src>(DstLimits::max()));
+ } else {
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(DstLimits::max()));
+ }
+ TEST_EXPECTED_RANGE(
+ RANGE_VALID,
+ static_cast<Src>(GetMaxConvertibleToFloat<Src, Dst>()));
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(DstLimits::lowest()));
+ }
+ } else {
+ TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::lowest());
+ }
+ }
+};
+
+template <typename Dst, typename Src>
+struct TestNumericConversion<Dst, Src, UNSIGN_TO_SIGN_NARROW_OR_EQUAL> {
+ static void Test(const char* dst, const char* src, int line) {
+ using SrcLimits = SaturationDefaultLimits<Src>;
+ using DstLimits = SaturationDefaultLimits<Dst>;
+ static_assert(MaxExponent<Dst>::value <= MaxExponent<Src>::value,
+ "Destination must be narrower or equal to source.");
+ static_assert(!SrcLimits::is_signed, "Source must be unsigned.");
+ static_assert(DstLimits::is_signed, "Destination must be signed.");
+
+ TestStrictComparison<Dst, Src>(dst, src, line);
+
+ const CheckedNumeric<Dst> checked_dst;
+ TEST_EXPECTED_VALUE(1, checked_dst + static_cast<Src>(1));
+ TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::max());
+ TEST_EXPECTED_VALUE(SrcLimits::lowest(), checked_dst + SrcLimits::lowest());
+
+ const ClampedNumeric<Dst> clamped_dst;
+ TEST_EXPECTED_VALUE(1, clamped_dst + static_cast<Src>(1));
+ TEST_EXPECTED_VALUE(DstLimits::Overflow(), clamped_dst + SrcLimits::max());
+ TEST_EXPECTED_VALUE(SrcLimits::lowest(), clamped_dst + SrcLimits::lowest());
+
+ TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest());
+ TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
+ TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
+
+ // Additional saturation tests.
+ EXPECT_EQ(DstLimits::max(), saturated_cast<Dst>(SrcLimits::max()));
+ EXPECT_EQ(Dst(0), saturated_cast<Dst>(SrcLimits::lowest()));
+ }
+};
+
+// Helper macro to wrap displaying the conversion types and line numbers
+#define TEST_NUMERIC_CONVERSION(d, s, t) \
+ TestNumericConversion<d, s, t>::Test(#d, #s, __LINE__)
+
+TEST(SafeNumerics, IntMinOperations) {
+ TEST_NUMERIC_CONVERSION(int8_t, int8_t, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(uint8_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING);
+
+ TEST_NUMERIC_CONVERSION(int8_t, int16_t, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(int8_t, int, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(uint8_t, uint16_t, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(uint8_t, unsigned int, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(int8_t, float, SIGN_PRESERVING_NARROW);
+
+ TEST_NUMERIC_CONVERSION(uint8_t, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
+
+ TEST_NUMERIC_CONVERSION(uint8_t, int16_t, SIGN_TO_UNSIGN_NARROW);
+ TEST_NUMERIC_CONVERSION(uint8_t, int, SIGN_TO_UNSIGN_NARROW);
+ TEST_NUMERIC_CONVERSION(uint8_t, intmax_t, SIGN_TO_UNSIGN_NARROW);
+ TEST_NUMERIC_CONVERSION(uint8_t, float, SIGN_TO_UNSIGN_NARROW);
+
+ TEST_NUMERIC_CONVERSION(int8_t, uint16_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+ TEST_NUMERIC_CONVERSION(int8_t, unsigned int, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+ TEST_NUMERIC_CONVERSION(int8_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+}
+
+TEST(SafeNumerics, Int16Operations) {
+ TEST_NUMERIC_CONVERSION(int16_t, int16_t, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(uint16_t, uint16_t, SIGN_PRESERVING_VALUE_PRESERVING);
+
+ TEST_NUMERIC_CONVERSION(int16_t, int, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(uint16_t, unsigned int, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(int16_t, float, SIGN_PRESERVING_NARROW);
+
+ TEST_NUMERIC_CONVERSION(uint16_t, int16_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
+
+ TEST_NUMERIC_CONVERSION(uint16_t, int, SIGN_TO_UNSIGN_NARROW);
+ TEST_NUMERIC_CONVERSION(uint16_t, intmax_t, SIGN_TO_UNSIGN_NARROW);
+ TEST_NUMERIC_CONVERSION(uint16_t, float, SIGN_TO_UNSIGN_NARROW);
+
+ TEST_NUMERIC_CONVERSION(int16_t, unsigned int,
+ UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+ TEST_NUMERIC_CONVERSION(int16_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+}
+
+TEST(SafeNumerics, IntOperations) {
+ TEST_NUMERIC_CONVERSION(int, int, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(unsigned int, unsigned int,
+ SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(int, int8_t, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(unsigned int, uint8_t,
+ SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(int, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING);
+
+ TEST_NUMERIC_CONVERSION(int, intmax_t, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(unsigned int, uintmax_t, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(int, float, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(int, double, SIGN_PRESERVING_NARROW);
+
+ TEST_NUMERIC_CONVERSION(unsigned int, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
+ TEST_NUMERIC_CONVERSION(unsigned int, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
+
+ TEST_NUMERIC_CONVERSION(unsigned int, intmax_t, SIGN_TO_UNSIGN_NARROW);
+ TEST_NUMERIC_CONVERSION(unsigned int, float, SIGN_TO_UNSIGN_NARROW);
+ TEST_NUMERIC_CONVERSION(unsigned int, double, SIGN_TO_UNSIGN_NARROW);
+
+ TEST_NUMERIC_CONVERSION(int, unsigned int, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+ TEST_NUMERIC_CONVERSION(int, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+}
+
+TEST(SafeNumerics, IntMaxOperations) {
+ TEST_NUMERIC_CONVERSION(intmax_t, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(uintmax_t, uintmax_t,
+ SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(intmax_t, int, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(uintmax_t, unsigned int,
+ SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(intmax_t, unsigned int,
+ SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(intmax_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING);
+
+ TEST_NUMERIC_CONVERSION(intmax_t, float, SIGN_PRESERVING_NARROW);
+ TEST_NUMERIC_CONVERSION(intmax_t, double, SIGN_PRESERVING_NARROW);
+
+ TEST_NUMERIC_CONVERSION(uintmax_t, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
+ TEST_NUMERIC_CONVERSION(uintmax_t, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
+
+ TEST_NUMERIC_CONVERSION(uintmax_t, float, SIGN_TO_UNSIGN_NARROW);
+ TEST_NUMERIC_CONVERSION(uintmax_t, double, SIGN_TO_UNSIGN_NARROW);
+
+ TEST_NUMERIC_CONVERSION(intmax_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+}
+
+TEST(SafeNumerics, FloatOperations) {
+ TEST_NUMERIC_CONVERSION(float, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(float, uintmax_t, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(float, int, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(float, unsigned int,
+ SIGN_PRESERVING_VALUE_PRESERVING);
+
+ TEST_NUMERIC_CONVERSION(float, double, SIGN_PRESERVING_NARROW);
+}
+
+TEST(SafeNumerics, DoubleOperations) {
+ TEST_NUMERIC_CONVERSION(double, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(double, uintmax_t, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(double, int, SIGN_PRESERVING_VALUE_PRESERVING);
+ TEST_NUMERIC_CONVERSION(double, unsigned int,
+ SIGN_PRESERVING_VALUE_PRESERVING);
+}
+
+TEST(SafeNumerics, SizeTOperations) {
+ TEST_NUMERIC_CONVERSION(size_t, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
+ TEST_NUMERIC_CONVERSION(int, size_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
+}
+
+// A one-off test to ensure StrictNumeric won't resolve to an incorrect type.
+// If this fails we'll just get a compiler error on an ambiguous overload.
+int TestOverload(int) { // Overload fails.
+ return 0;
+}
+uint8_t TestOverload(uint8_t) { // Overload fails.
+ return 0;
+}
+size_t TestOverload(size_t) { // Overload succeeds.
+ return 0;
+}
+
+static_assert(
+ std::is_same<decltype(TestOverload(StrictNumeric<int>())), int>::value,
+ "");
+static_assert(std::is_same<decltype(TestOverload(StrictNumeric<size_t>())),
+ size_t>::value,
+ "");
+
+template <typename T>
+struct CastTest1 {
+ static constexpr T NaN() { return -1; }
+ static constexpr T max() { return numeric_limits<T>::max() - 1; }
+ static constexpr T Overflow() { return max(); }
+ static constexpr T lowest() { return numeric_limits<T>::lowest() + 1; }
+ static constexpr T Underflow() { return lowest(); }
+};
+
+template <typename T>
+struct CastTest2 {
+ static constexpr T NaN() { return 11; }
+ static constexpr T max() { return 10; }
+ static constexpr T Overflow() { return max(); }
+ static constexpr T lowest() { return 1; }
+ static constexpr T Underflow() { return lowest(); }
+};
+
+TEST(SafeNumerics, CastTests) {
+// MSVC catches and warns that we're forcing saturation in these tests.
+// Since that's intentional, we need to shut this warning off.
+#if defined(COMPILER_MSVC)
+#pragma warning(disable : 4756)
+#endif
+
+ int small_positive = 1;
+ int small_negative = -1;
+ double double_small = 1.0;
+ double double_large = numeric_limits<double>::max();
+ double double_infinity = numeric_limits<float>::infinity();
+ double double_large_int = numeric_limits<int>::max();
+ double double_small_int = numeric_limits<int>::lowest();
+
+ // Just test that the casts compile, since the other tests cover logic.
+ EXPECT_EQ(0, checked_cast<int>(static_cast<size_t>(0)));
+ EXPECT_EQ(0, strict_cast<int>(static_cast<char>(0)));
+ EXPECT_EQ(0, strict_cast<int>(static_cast<unsigned char>(0)));
+ EXPECT_EQ(0U, strict_cast<unsigned>(static_cast<unsigned char>(0)));
+ EXPECT_EQ(1ULL, static_cast<uint64_t>(StrictNumeric<size_t>(1U)));
+ EXPECT_EQ(1ULL, static_cast<uint64_t>(SizeT(1U)));
+ EXPECT_EQ(1U, static_cast<size_t>(StrictNumeric<unsigned>(1U)));
+
+ EXPECT_TRUE(CheckedNumeric<uint64_t>(StrictNumeric<unsigned>(1U)).IsValid());
+ EXPECT_TRUE(CheckedNumeric<int>(StrictNumeric<unsigned>(1U)).IsValid());
+ EXPECT_FALSE(CheckedNumeric<unsigned>(StrictNumeric<int>(-1)).IsValid());
+
+ EXPECT_TRUE(IsValueNegative(-1));
+ EXPECT_TRUE(IsValueNegative(numeric_limits<int>::lowest()));
+ EXPECT_FALSE(IsValueNegative(numeric_limits<unsigned>::lowest()));
+ EXPECT_TRUE(IsValueNegative(numeric_limits<double>::lowest()));
+ EXPECT_FALSE(IsValueNegative(0));
+ EXPECT_FALSE(IsValueNegative(1));
+ EXPECT_FALSE(IsValueNegative(0u));
+ EXPECT_FALSE(IsValueNegative(1u));
+ EXPECT_FALSE(IsValueNegative(numeric_limits<int>::max()));
+ EXPECT_FALSE(IsValueNegative(numeric_limits<unsigned>::max()));
+ EXPECT_FALSE(IsValueNegative(numeric_limits<double>::max()));
+
+ // These casts and coercions will fail to compile:
+ // EXPECT_EQ(0, strict_cast<int>(static_cast<size_t>(0)));
+ // EXPECT_EQ(0, strict_cast<size_t>(static_cast<int>(0)));
+ // EXPECT_EQ(1ULL, StrictNumeric<size_t>(1));
+ // EXPECT_EQ(1, StrictNumeric<size_t>(1U));
+
+ // Test various saturation corner cases.
+ EXPECT_EQ(saturated_cast<int>(small_negative),
+ static_cast<int>(small_negative));
+ EXPECT_EQ(saturated_cast<int>(small_positive),
+ static_cast<int>(small_positive));
+ EXPECT_EQ(saturated_cast<unsigned>(small_negative), static_cast<unsigned>(0));
+ EXPECT_EQ(saturated_cast<int>(double_small), static_cast<int>(double_small));
+ EXPECT_EQ(saturated_cast<int>(double_large), numeric_limits<int>::max());
+ EXPECT_EQ(saturated_cast<float>(double_large), double_infinity);
+ EXPECT_EQ(saturated_cast<float>(-double_large), -double_infinity);
+ EXPECT_EQ(numeric_limits<int>::lowest(),
+ saturated_cast<int>(double_small_int));
+ EXPECT_EQ(numeric_limits<int>::max(), saturated_cast<int>(double_large_int));
+
+ // Test the saturated cast overrides.
+ using FloatLimits = numeric_limits<float>;
+ using IntLimits = numeric_limits<int>;
+ EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(FloatLimits::quiet_NaN())));
+ EXPECT_EQ(CastTest1<int>::max(),
+ (saturated_cast<int, CastTest1>(FloatLimits::infinity())));
+ EXPECT_EQ(CastTest1<int>::max(),
+ (saturated_cast<int, CastTest1>(FloatLimits::max())));
+ EXPECT_EQ(CastTest1<int>::max(),
+ (saturated_cast<int, CastTest1>(float(IntLimits::max()))));
+ EXPECT_EQ(CastTest1<int>::lowest(),
+ (saturated_cast<int, CastTest1>(-FloatLimits::infinity())));
+ EXPECT_EQ(CastTest1<int>::lowest(),
+ (saturated_cast<int, CastTest1>(FloatLimits::lowest())));
+ EXPECT_EQ(0, (saturated_cast<int, CastTest1>(0.0)));
+ EXPECT_EQ(1, (saturated_cast<int, CastTest1>(1.0)));
+ EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(-1.0)));
+ EXPECT_EQ(0, (saturated_cast<int, CastTest1>(0)));
+ EXPECT_EQ(1, (saturated_cast<int, CastTest1>(1)));
+ EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(-1)));
+ EXPECT_EQ(CastTest1<int>::lowest(),
+ (saturated_cast<int, CastTest1>(float(IntLimits::lowest()))));
+ EXPECT_EQ(11, (saturated_cast<int, CastTest2>(FloatLimits::quiet_NaN())));
+ EXPECT_EQ(10, (saturated_cast<int, CastTest2>(FloatLimits::infinity())));
+ EXPECT_EQ(10, (saturated_cast<int, CastTest2>(FloatLimits::max())));
+ EXPECT_EQ(1, (saturated_cast<int, CastTest2>(-FloatLimits::infinity())));
+ EXPECT_EQ(1, (saturated_cast<int, CastTest2>(FloatLimits::lowest())));
+ EXPECT_EQ(1, (saturated_cast<int, CastTest2>(0U)));
+
+ float not_a_number = std::numeric_limits<float>::infinity() -
+ std::numeric_limits<float>::infinity();
+ EXPECT_TRUE(std::isnan(not_a_number));
+ EXPECT_EQ(0, saturated_cast<int>(not_a_number));
+
+ // Test the CheckedNumeric value extractions functions.
+ auto int8_min = MakeCheckedNum(numeric_limits<int8_t>::lowest());
+ auto int8_max = MakeCheckedNum(numeric_limits<int8_t>::max());
+ auto double_max = MakeCheckedNum(numeric_limits<double>::max());
+ static_assert(
+ std::is_same<int16_t,
+ decltype(int8_min.ValueOrDie<int16_t>())::type>::value,
+ "ValueOrDie returning incorrect type.");
+ static_assert(
+ std::is_same<int16_t,
+ decltype(int8_min.ValueOrDefault<int16_t>(0))::type>::value,
+ "ValueOrDefault returning incorrect type.");
+ EXPECT_FALSE(IsValidForType<uint8_t>(int8_min));
+ EXPECT_TRUE(IsValidForType<uint8_t>(int8_max));
+ EXPECT_EQ(static_cast<int>(numeric_limits<int8_t>::lowest()),
+ ValueOrDieForType<int>(int8_min));
+ EXPECT_TRUE(IsValidForType<uint32_t>(int8_max));
+ EXPECT_EQ(static_cast<int>(numeric_limits<int8_t>::max()),
+ ValueOrDieForType<int>(int8_max));
+ EXPECT_EQ(0, ValueOrDefaultForType<int>(double_max, 0));
+ uint8_t uint8_dest = 0;
+ int16_t int16_dest = 0;
+ double double_dest = 0;
+ EXPECT_TRUE(int8_max.AssignIfValid(&uint8_dest));
+ EXPECT_EQ(static_cast<uint8_t>(numeric_limits<int8_t>::max()), uint8_dest);
+ EXPECT_FALSE(int8_min.AssignIfValid(&uint8_dest));
+ EXPECT_TRUE(int8_max.AssignIfValid(&int16_dest));
+ EXPECT_EQ(static_cast<int16_t>(numeric_limits<int8_t>::max()), int16_dest);
+ EXPECT_TRUE(int8_min.AssignIfValid(&int16_dest));
+ EXPECT_EQ(static_cast<int16_t>(numeric_limits<int8_t>::lowest()), int16_dest);
+ EXPECT_FALSE(double_max.AssignIfValid(&uint8_dest));
+ EXPECT_FALSE(double_max.AssignIfValid(&int16_dest));
+ EXPECT_TRUE(double_max.AssignIfValid(&double_dest));
+ EXPECT_EQ(numeric_limits<double>::max(), double_dest);
+ EXPECT_EQ(1, checked_cast<int>(StrictNumeric<int>(1)));
+ EXPECT_EQ(1, saturated_cast<int>(StrictNumeric<int>(1)));
+ EXPECT_EQ(1, strict_cast<int>(StrictNumeric<int>(1)));
+
+ enum class EnumTest { kOne = 1 };
+ EXPECT_EQ(1, checked_cast<int>(EnumTest::kOne));
+ EXPECT_EQ(1, saturated_cast<int>(EnumTest::kOne));
+ EXPECT_EQ(1, strict_cast<int>(EnumTest::kOne));
+}
+
+TEST(SafeNumerics, IsValueInRangeForNumericType) {
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(0));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(1));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(2));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(-1));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(0xffffffffu));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(UINT64_C(0xffffffff)));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(UINT64_C(0x100000000)));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(UINT64_C(0x100000001)));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(
+ std::numeric_limits<int32_t>::lowest()));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(
+ std::numeric_limits<int64_t>::lowest()));
+
+ EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(0));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(1));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(2));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(-1));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(0x7fffffff));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(0x7fffffffu));
+ EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(0x80000000u));
+ EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(0xffffffffu));
+ EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(INT64_C(0x80000000)));
+ EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(INT64_C(0xffffffff)));
+ EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(INT64_C(0x100000000)));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(
+ std::numeric_limits<int32_t>::lowest()));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(
+ static_cast<int64_t>(std::numeric_limits<int32_t>::lowest())));
+ EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(
+ static_cast<int64_t>(std::numeric_limits<int32_t>::lowest()) - 1));
+ EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(
+ std::numeric_limits<int64_t>::lowest()));
+
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(0));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(1));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(2));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint64_t>(-1));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(0xffffffffu));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(UINT64_C(0xffffffff)));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(UINT64_C(0x100000000)));
+ EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(UINT64_C(0x100000001)));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint64_t>(
+ std::numeric_limits<int32_t>::lowest()));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint64_t>(INT64_C(-1)));
+ EXPECT_FALSE(IsValueInRangeForNumericType<uint64_t>(
+ std::numeric_limits<int64_t>::lowest()));
+
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(1));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(2));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(-1));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0x7fffffff));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0x7fffffffu));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0x80000000u));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0xffffffffu));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(INT64_C(0x80000000)));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(INT64_C(0xffffffff)));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(INT64_C(0x100000000)));
+ EXPECT_TRUE(
+ IsValueInRangeForNumericType<int64_t>(INT64_C(0x7fffffffffffffff)));
+ EXPECT_TRUE(
+ IsValueInRangeForNumericType<int64_t>(UINT64_C(0x7fffffffffffffff)));
+ EXPECT_FALSE(
+ IsValueInRangeForNumericType<int64_t>(UINT64_C(0x8000000000000000)));
+ EXPECT_FALSE(
+ IsValueInRangeForNumericType<int64_t>(UINT64_C(0xffffffffffffffff)));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(
+ std::numeric_limits<int32_t>::lowest()));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(
+ static_cast<int64_t>(std::numeric_limits<int32_t>::lowest())));
+ EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(
+ std::numeric_limits<int64_t>::lowest()));
+}
+
+TEST(SafeNumerics, CompoundNumericOperations) {
+ CheckedNumeric<int> a = 1;
+ CheckedNumeric<int> b = 2;
+ CheckedNumeric<int> c = 3;
+ CheckedNumeric<int> d = 4;
+ a += b;
+ EXPECT_EQ(3, a.ValueOrDie());
+ a -= c;
+ EXPECT_EQ(0, a.ValueOrDie());
+ d /= b;
+ EXPECT_EQ(2, d.ValueOrDie());
+ d *= d;
+ EXPECT_EQ(4, d.ValueOrDie());
+
+ CheckedNumeric<int> too_large = std::numeric_limits<int>::max();
+ EXPECT_TRUE(too_large.IsValid());
+ too_large += d;
+ EXPECT_FALSE(too_large.IsValid());
+ too_large -= d;
+ EXPECT_FALSE(too_large.IsValid());
+ too_large /= d;
+ EXPECT_FALSE(too_large.IsValid());
+}
+
+TEST(SafeNumerics, VariadicNumericOperations) {
+ { // Synthetic scope to avoid variable naming collisions.
+ auto a = CheckAdd(1, 2UL, MakeCheckedNum(3LL), 4).ValueOrDie();
+ EXPECT_EQ(static_cast<decltype(a)::type>(10), a);
+ auto b = CheckSub(MakeCheckedNum(20.0), 2UL, 4).ValueOrDie();
+ EXPECT_EQ(static_cast<decltype(b)::type>(14.0), b);
+ auto c = CheckMul(20.0, MakeCheckedNum(1), 5, 3UL).ValueOrDie();
+ EXPECT_EQ(static_cast<decltype(c)::type>(300.0), c);
+ auto d = CheckDiv(20.0, 2.0, MakeCheckedNum(5LL), -4).ValueOrDie();
+ EXPECT_EQ(static_cast<decltype(d)::type>(-.5), d);
+ auto e = CheckMod(MakeCheckedNum(20), 3).ValueOrDie();
+ EXPECT_EQ(static_cast<decltype(e)::type>(2), e);
+ auto f = CheckLsh(1, MakeCheckedNum(2)).ValueOrDie();
+ EXPECT_EQ(static_cast<decltype(f)::type>(4), f);
+ auto g = CheckRsh(4, MakeCheckedNum(2)).ValueOrDie();
+ EXPECT_EQ(static_cast<decltype(g)::type>(1), g);
+ auto h = CheckRsh(CheckAdd(1, 1, 1, 1), CheckSub(4, 2)).ValueOrDie();
+ EXPECT_EQ(static_cast<decltype(h)::type>(1), h);
+ }
+
+ {
+ auto a = ClampAdd(1, 2UL, MakeClampedNum(3LL), 4);
+ EXPECT_EQ(static_cast<decltype(a)::type>(10), a);
+ auto b = ClampSub(MakeClampedNum(20.0), 2UL, 4);
+ EXPECT_EQ(static_cast<decltype(b)::type>(14.0), b);
+ auto c = ClampMul(20.0, MakeClampedNum(1), 5, 3UL);
+ EXPECT_EQ(static_cast<decltype(c)::type>(300.0), c);
+ auto d = ClampDiv(20.0, 2.0, MakeClampedNum(5LL), -4);
+ EXPECT_EQ(static_cast<decltype(d)::type>(-.5), d);
+ auto e = ClampMod(MakeClampedNum(20), 3);
+ EXPECT_EQ(static_cast<decltype(e)::type>(2), e);
+ auto f = ClampLsh(1, MakeClampedNum(2U));
+ EXPECT_EQ(static_cast<decltype(f)::type>(4), f);
+ auto g = ClampRsh(4, MakeClampedNum(2U));
+ EXPECT_EQ(static_cast<decltype(g)::type>(1), g);
+ auto h = ClampRsh(ClampAdd(1, 1, 1, 1), ClampSub(4U, 2));
+ EXPECT_EQ(static_cast<decltype(h)::type>(1), h);
+ }
+}
+
+#if defined(__clang__)
+#pragma clang diagnostic pop // -Winteger-overflow
+#endif
+
+} // namespace internal
+} // namespace base
diff --git a/base/scoped_native_library_unittest.cc b/base/scoped_native_library_unittest.cc
new file mode 100644
index 0000000000..763b45f6c3
--- /dev/null
+++ b/base/scoped_native_library_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/scoped_native_library.h"
+
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/files/file_path.h"
+#include "base/strings/utf_string_conversions.h"
+#endif
+
+namespace base {
+
+// Tests whether or not a function pointer retrieved via ScopedNativeLibrary
+// is available only in a scope.
+TEST(ScopedNativeLibrary, Basic) {
+#if defined(OS_WIN)
+ // Get the pointer to DirectDrawCreate() from "ddraw.dll" and verify it
+ // is valid only in this scope.
+ // FreeLibrary() doesn't actually unload a DLL until its reference count
+ // becomes zero, i.e. function pointer is still valid if the DLL used
+ // in this test is also used by another part of this executable.
+ // So, this test uses "ddraw.dll", which is not used by Chrome at all but
+ // installed on all versions of Windows.
+ const char kFunctionName[] = "DirectDrawCreate";
+ NativeLibrary native_library;
+ {
+ FilePath path(FilePath::FromUTF8Unsafe(GetNativeLibraryName("ddraw")));
+ native_library = LoadNativeLibrary(path, nullptr);
+ ScopedNativeLibrary library(native_library);
+ EXPECT_TRUE(library.is_valid());
+ EXPECT_EQ(native_library, library.get());
+ FARPROC test_function =
+ reinterpret_cast<FARPROC>(library.GetFunctionPointer(kFunctionName));
+ EXPECT_EQ(0, IsBadCodePtr(test_function));
+ EXPECT_EQ(
+ GetFunctionPointerFromNativeLibrary(native_library, kFunctionName),
+ test_function);
+ }
+ EXPECT_FALSE(
+ GetFunctionPointerFromNativeLibrary(native_library, kFunctionName));
+#endif
+}
+
+} // namespace base
diff --git a/base/sequenced_task_runner_unittest.cc b/base/sequenced_task_runner_unittest.cc
new file mode 100644
index 0000000000..4dcc7e5a30
--- /dev/null
+++ b/base/sequenced_task_runner_unittest.cc
@@ -0,0 +1,104 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/sequenced_task_runner.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/gtest_prod_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class FlagOnDelete {
+ public:
+ FlagOnDelete(bool* deleted,
+ scoped_refptr<SequencedTaskRunner> expected_deletion_sequence)
+ : deleted_(deleted),
+ expected_deletion_sequence_(std::move(expected_deletion_sequence)) {}
+
+ private:
+ friend class DeleteHelper<FlagOnDelete>;
+ FRIEND_TEST_ALL_PREFIXES(SequencedTaskRunnerTest,
+ OnTaskRunnerDeleterTargetStoppedEarly);
+
+ ~FlagOnDelete() {
+ EXPECT_FALSE(*deleted_);
+ *deleted_ = true;
+ if (expected_deletion_sequence_)
+ EXPECT_TRUE(expected_deletion_sequence_->RunsTasksInCurrentSequence());
+ }
+
+ bool* deleted_;
+ const scoped_refptr<SequencedTaskRunner> expected_deletion_sequence_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlagOnDelete);
+};
+
+class SequencedTaskRunnerTest : public testing::Test {
+ protected:
+ SequencedTaskRunnerTest() : foreign_thread_("foreign") {}
+
+ void SetUp() override {
+ main_runner_ = message_loop_.task_runner();
+
+ foreign_thread_.Start();
+ foreign_runner_ = foreign_thread_.task_runner();
+ }
+
+ scoped_refptr<SequencedTaskRunner> main_runner_;
+ scoped_refptr<SequencedTaskRunner> foreign_runner_;
+
+ Thread foreign_thread_;
+
+ private:
+ MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(SequencedTaskRunnerTest);
+};
+
+using SequenceBoundUniquePtr =
+ std::unique_ptr<FlagOnDelete, OnTaskRunnerDeleter>;
+
+TEST_F(SequencedTaskRunnerTest, OnTaskRunnerDeleterOnMainThread) {
+ bool deleted_on_main_thread = false;
+ SequenceBoundUniquePtr ptr(
+ new FlagOnDelete(&deleted_on_main_thread, main_runner_),
+ OnTaskRunnerDeleter(main_runner_));
+ EXPECT_FALSE(deleted_on_main_thread);
+ foreign_runner_->PostTask(
+ FROM_HERE, BindOnce([](SequenceBoundUniquePtr) {}, std::move(ptr)));
+
+ {
+ RunLoop run_loop;
+ foreign_runner_->PostTaskAndReply(FROM_HERE, BindOnce([] {}),
+ run_loop.QuitClosure());
+ run_loop.Run();
+ }
+ EXPECT_TRUE(deleted_on_main_thread);
+}
+
+TEST_F(SequencedTaskRunnerTest, OnTaskRunnerDeleterTargetStoppedEarly) {
+ bool deleted_on_main_thread = false;
+ FlagOnDelete* raw = new FlagOnDelete(&deleted_on_main_thread, main_runner_);
+ SequenceBoundUniquePtr ptr(raw, OnTaskRunnerDeleter(foreign_runner_));
+ EXPECT_FALSE(deleted_on_main_thread);
+
+ // Stopping the target ahead of deleting |ptr| should make its
+ // OnTaskRunnerDeleter no-op.
+ foreign_thread_.Stop();
+ ptr = nullptr;
+ EXPECT_FALSE(deleted_on_main_thread);
+
+ delete raw;
+ EXPECT_TRUE(deleted_on_main_thread);
+}
+
+} // namespace
+} // namespace base
diff --git a/base/strings/latin1_string_conversions.cc b/base/strings/latin1_string_conversions.cc
new file mode 100644
index 0000000000..dca62ced53
--- /dev/null
+++ b/base/strings/latin1_string_conversions.cc
@@ -0,0 +1,19 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/latin1_string_conversions.h"
+
+namespace base {
+
+string16 Latin1OrUTF16ToUTF16(size_t length,
+ const Latin1Char* latin1,
+ const char16* utf16) {
+ if (!length)
+ return string16();
+ if (latin1)
+ return string16(latin1, latin1 + length);
+ return string16(utf16, utf16 + length);
+}
+
+} // namespace base
diff --git a/base/strings/latin1_string_conversions.h b/base/strings/latin1_string_conversions.h
new file mode 100644
index 0000000000..42113ef612
--- /dev/null
+++ b/base/strings/latin1_string_conversions.h
@@ -0,0 +1,34 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_LATIN1_STRING_CONVERSIONS_H_
+#define BASE_STRINGS_LATIN1_STRING_CONVERSIONS_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/base_export.h"
+#include "base/strings/string16.h"
+
+namespace base {
+
+// This definition of Latin1Char matches the definition of LChar in Blink. We
+// use unsigned char rather than char to make less tempting to mix and match
+// Latin-1 and UTF-8 characters..
+typedef unsigned char Latin1Char;
+
+// This somewhat odd function is designed to help us convert from Blink Strings
+// to string16. A Blink string is either backed by an array of Latin-1
+// characters or an array of UTF-16 characters. This function is called by
+// WebString::operator string16() to convert one or the other character array
+// to string16. This function is defined here rather than in WebString.h to
+// avoid binary bloat in all the callers of the conversion operator.
+BASE_EXPORT string16 Latin1OrUTF16ToUTF16(size_t length,
+ const Latin1Char* latin1,
+ const char16* utf16);
+
+} // namespace base
+
+#endif // BASE_STRINGS_LATIN1_STRING_CONVERSIONS_H_
diff --git a/base/strings/utf_offset_string_conversions.cc b/base/strings/utf_offset_string_conversions.cc
new file mode 100644
index 0000000000..b91ee03832
--- /dev/null
+++ b/base/strings/utf_offset_string_conversions.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/utf_offset_string_conversions.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_string_conversion_utils.h"
+
+namespace base {
+
+OffsetAdjuster::Adjustment::Adjustment(size_t original_offset,
+ size_t original_length,
+ size_t output_length)
+ : original_offset(original_offset),
+ original_length(original_length),
+ output_length(output_length) {
+}
+
+// static
+void OffsetAdjuster::AdjustOffsets(const Adjustments& adjustments,
+ std::vector<size_t>* offsets_for_adjustment,
+ size_t limit) {
+ DCHECK(offsets_for_adjustment);
+ for (std::vector<size_t>::iterator i(offsets_for_adjustment->begin());
+ i != offsets_for_adjustment->end(); ++i)
+ AdjustOffset(adjustments, &(*i), limit);
+}
+
+// static
+void OffsetAdjuster::AdjustOffset(const Adjustments& adjustments,
+ size_t* offset,
+ size_t limit) {
+ DCHECK(offset);
+ if (*offset == string16::npos)
+ return;
+ int adjustment = 0;
+ for (Adjustments::const_iterator i = adjustments.begin();
+ i != adjustments.end(); ++i) {
+ if (*offset <= i->original_offset)
+ break;
+ if (*offset < (i->original_offset + i->original_length)) {
+ *offset = string16::npos;
+ return;
+ }
+ adjustment += static_cast<int>(i->original_length - i->output_length);
+ }
+ *offset -= adjustment;
+
+ if (*offset > limit)
+ *offset = string16::npos;
+}
+
+// static
+void OffsetAdjuster::UnadjustOffsets(
+ const Adjustments& adjustments,
+ std::vector<size_t>* offsets_for_unadjustment) {
+ if (!offsets_for_unadjustment || adjustments.empty())
+ return;
+ for (std::vector<size_t>::iterator i(offsets_for_unadjustment->begin());
+ i != offsets_for_unadjustment->end(); ++i)
+ UnadjustOffset(adjustments, &(*i));
+}
+
+// static
+void OffsetAdjuster::UnadjustOffset(const Adjustments& adjustments,
+ size_t* offset) {
+ if (*offset == string16::npos)
+ return;
+ int adjustment = 0;
+ for (Adjustments::const_iterator i = adjustments.begin();
+ i != adjustments.end(); ++i) {
+ if (*offset + adjustment <= i->original_offset)
+ break;
+ adjustment += static_cast<int>(i->original_length - i->output_length);
+ if ((*offset + adjustment) <
+ (i->original_offset + i->original_length)) {
+ *offset = string16::npos;
+ return;
+ }
+ }
+ *offset += adjustment;
+}
+
+// static
+void OffsetAdjuster::MergeSequentialAdjustments(
+ const Adjustments& first_adjustments,
+ Adjustments* adjustments_on_adjusted_string) {
+ Adjustments::iterator adjusted_iter = adjustments_on_adjusted_string->begin();
+ Adjustments::const_iterator first_iter = first_adjustments.begin();
+ // Simultaneously iterate over all |adjustments_on_adjusted_string| and
+ // |first_adjustments|, adding adjustments to or correcting the adjustments
+ // in |adjustments_on_adjusted_string| as we go. |shift| keeps track of the
+ // current number of characters collapsed by |first_adjustments| up to this
+ // point. |currently_collapsing| keeps track of the number of characters
+ // collapsed by |first_adjustments| into the current |adjusted_iter|'s
+ // length. These are characters that will change |shift| as soon as we're
+ // done processing the current |adjusted_iter|; they are not yet reflected in
+ // |shift|.
+ size_t shift = 0;
+ size_t currently_collapsing = 0;
+ while (adjusted_iter != adjustments_on_adjusted_string->end()) {
+ if ((first_iter == first_adjustments.end()) ||
+ ((adjusted_iter->original_offset + shift +
+ adjusted_iter->original_length) <= first_iter->original_offset)) {
+ // Entire |adjusted_iter| (accounting for its shift and including its
+ // whole original length) comes before |first_iter|.
+ //
+ // Correct the offset at |adjusted_iter| and move onto the next
+ // adjustment that needs revising.
+ adjusted_iter->original_offset += shift;
+ shift += currently_collapsing;
+ currently_collapsing = 0;
+ ++adjusted_iter;
+ } else if ((adjusted_iter->original_offset + shift) >
+ first_iter->original_offset) {
+ // |first_iter| comes before the |adjusted_iter| (as adjusted by |shift|).
+
+ // It's not possible for the adjustments to overlap. (It shouldn't
+ // be possible that we have an |adjusted_iter->original_offset| that,
+ // when adjusted by the computed |shift|, is in the middle of
+ // |first_iter|'s output's length. After all, that would mean the
+ // current adjustment_on_adjusted_string somehow points to an offset
+ // that was supposed to have been eliminated by the first set of
+ // adjustments.)
+ DCHECK_LE(first_iter->original_offset + first_iter->output_length,
+ adjusted_iter->original_offset + shift);
+
+ // Add the |first_adjustment_iter| to the full set of adjustments while
+ // making sure |adjusted_iter| continues pointing to the same element.
+ // We do this by inserting the |first_adjustment_iter| right before
+ // |adjusted_iter|, then incrementing |adjusted_iter| so it points to
+ // the following element.
+ shift += first_iter->original_length - first_iter->output_length;
+ adjusted_iter = adjustments_on_adjusted_string->insert(
+ adjusted_iter, *first_iter);
+ ++adjusted_iter;
+ ++first_iter;
+ } else {
+ // The first adjustment adjusted something that then got further adjusted
+ // by the second set of adjustments. In other words, |first_iter| points
+ // to something in the range covered by |adjusted_iter|'s length (after
+ // accounting for |shift|). Precisely,
+ // adjusted_iter->original_offset + shift
+ // <=
+ // first_iter->original_offset
+ // <=
+ // adjusted_iter->original_offset + shift +
+ // adjusted_iter->original_length
+
+ // Modify the current |adjusted_iter| to include whatever collapsing
+ // happened in |first_iter|, then advance to the next |first_adjustments|
+ // because we dealt with the current one.
+ const int collapse = static_cast<int>(first_iter->original_length) -
+ static_cast<int>(first_iter->output_length);
+ // This function does not know how to deal with a string that expands and
+ // then gets modified, only strings that collapse and then get modified.
+ DCHECK_GT(collapse, 0);
+ adjusted_iter->original_length += collapse;
+ currently_collapsing += collapse;
+ ++first_iter;
+ }
+ }
+ DCHECK_EQ(0u, currently_collapsing);
+ if (first_iter != first_adjustments.end()) {
+ // Only first adjustments are left. These do not need to be modified.
+ // (Their offsets are already correct with respect to the original string.)
+ // Append them all.
+ DCHECK(adjusted_iter == adjustments_on_adjusted_string->end());
+ adjustments_on_adjusted_string->insert(
+ adjustments_on_adjusted_string->end(), first_iter,
+ first_adjustments.end());
+ }
+}
+
+// Converts the given source Unicode character type to the given destination
+// Unicode character type as a STL string. The given input buffer and size
+// determine the source, and the given output STL string will be replaced by
+// the result. If non-NULL, |adjustments| is set to reflect the all the
+// alterations to the string that are not one-character-to-one-character.
+// It will always be sorted by increasing offset.
+template<typename SrcChar, typename DestStdString>
+bool ConvertUnicode(const SrcChar* src,
+ size_t src_len,
+ DestStdString* output,
+ OffsetAdjuster::Adjustments* adjustments) {
+ if (adjustments)
+ adjustments->clear();
+ // ICU requires 32-bit numbers.
+ bool success = true;
+ int32_t src_len32 = static_cast<int32_t>(src_len);
+ for (int32_t i = 0; i < src_len32; i++) {
+ uint32_t code_point;
+ size_t original_i = i;
+ size_t chars_written = 0;
+ if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) {
+ chars_written = WriteUnicodeCharacter(code_point, output);
+ } else {
+ chars_written = WriteUnicodeCharacter(0xFFFD, output);
+ success = false;
+ }
+
+ // Only bother writing an adjustment if this modification changed the
+ // length of this character.
+ // NOTE: ReadUnicodeCharacter() adjusts |i| to point _at_ the last
+ // character read, not after it (so that incrementing it in the loop
+ // increment will place it at the right location), so we need to account
+ // for that in determining the amount that was read.
+ if (adjustments && ((i - original_i + 1) != chars_written)) {
+ adjustments->push_back(OffsetAdjuster::Adjustment(
+ original_i, i - original_i + 1, chars_written));
+ }
+ }
+ return success;
+}
+
+bool UTF8ToUTF16WithAdjustments(
+ const char* src,
+ size_t src_len,
+ string16* output,
+ base::OffsetAdjuster::Adjustments* adjustments) {
+ PrepareForUTF16Or32Output(src, src_len, output);
+ return ConvertUnicode(src, src_len, output, adjustments);
+}
+
+string16 UTF8ToUTF16WithAdjustments(
+ const base::StringPiece& utf8,
+ base::OffsetAdjuster::Adjustments* adjustments) {
+ string16 result;
+ UTF8ToUTF16WithAdjustments(utf8.data(), utf8.length(), &result, adjustments);
+ return result;
+}
+
+string16 UTF8ToUTF16AndAdjustOffsets(
+ const base::StringPiece& utf8,
+ std::vector<size_t>* offsets_for_adjustment) {
+ for (size_t& offset : *offsets_for_adjustment) {
+ if (offset > utf8.length())
+ offset = string16::npos;
+ }
+ OffsetAdjuster::Adjustments adjustments;
+ string16 result = UTF8ToUTF16WithAdjustments(utf8, &adjustments);
+ OffsetAdjuster::AdjustOffsets(adjustments, offsets_for_adjustment);
+ return result;
+}
+
+std::string UTF16ToUTF8AndAdjustOffsets(
+ const base::StringPiece16& utf16,
+ std::vector<size_t>* offsets_for_adjustment) {
+ for (size_t& offset : *offsets_for_adjustment) {
+ if (offset > utf16.length())
+ offset = string16::npos;
+ }
+ std::string result;
+ PrepareForUTF8Output(utf16.data(), utf16.length(), &result);
+ OffsetAdjuster::Adjustments adjustments;
+ ConvertUnicode(utf16.data(), utf16.length(), &result, &adjustments);
+ OffsetAdjuster::AdjustOffsets(adjustments, offsets_for_adjustment);
+ return result;
+}
+
+} // namespace base
diff --git a/base/strings/utf_offset_string_conversions.h b/base/strings/utf_offset_string_conversions.h
new file mode 100644
index 0000000000..f7419551da
--- /dev/null
+++ b/base/strings/utf_offset_string_conversions.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_
+#define BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+
+// A helper class and associated data structures to adjust offsets into a
+// string in response to various adjustments one might do to that string
+// (e.g., eliminating a range). For details on offsets, see the comments by
+// the AdjustOffsets() function below.
+class BASE_EXPORT OffsetAdjuster {
+ public:
+ struct BASE_EXPORT Adjustment {
+ Adjustment(size_t original_offset,
+ size_t original_length,
+ size_t output_length);
+
+ size_t original_offset;
+ size_t original_length;
+ size_t output_length;
+ };
+ typedef std::vector<Adjustment> Adjustments;
+
+ // Adjusts all offsets in |offsets_for_adjustment| to reflect the adjustments
+ // recorded in |adjustments|. Adjusted offsets greater than |limit| will be
+ // set to string16::npos.
+ //
+ // Offsets represents insertion/selection points between characters: if |src|
+ // is "abcd", then 0 is before 'a', 2 is between 'b' and 'c', and 4 is at the
+ // end of the string. Valid input offsets range from 0 to |src_len|. On
+ // exit, each offset will have been modified to point at the same logical
+ // position in the output string. If an offset cannot be successfully
+ // adjusted (e.g., because it points into the middle of a multibyte sequence),
+ // it will be set to string16::npos.
+ static void AdjustOffsets(const Adjustments& adjustments,
+ std::vector<size_t>* offsets_for_adjustment,
+ size_t limit = string16::npos);
+
+ // Adjusts the single |offset| to reflect the adjustments recorded in
+ // |adjustments|.
+ static void AdjustOffset(const Adjustments& adjustments,
+ size_t* offset,
+ size_t limit = string16::npos);
+
+ // Adjusts all offsets in |offsets_for_unadjustment| to reflect the reverse
+ // of the adjustments recorded in |adjustments|. In other words, the offsets
+ // provided represent offsets into an adjusted string and the caller wants
+ // to know the offsets they correspond to in the original string. If an
+ // offset cannot be successfully unadjusted (e.g., because it points into
+ // the middle of a multibyte sequence), it will be set to string16::npos.
+ static void UnadjustOffsets(const Adjustments& adjustments,
+ std::vector<size_t>* offsets_for_unadjustment);
+
+ // Adjusts the single |offset| to reflect the reverse of the adjustments
+ // recorded in |adjustments|.
+ static void UnadjustOffset(const Adjustments& adjustments,
+ size_t* offset);
+
+ // Combines two sequential sets of adjustments, storing the combined revised
+ // adjustments in |adjustments_on_adjusted_string|. That is, suppose a
+ // string was altered in some way, with the alterations recorded as
+ // adjustments in |first_adjustments|. Then suppose the resulting string is
+ // further altered, with the alterations recorded as adjustments scored in
+ // |adjustments_on_adjusted_string|, with the offsets recorded in these
+ // adjustments being with respect to the intermediate string. This function
+ // combines the two sets of adjustments into one, storing the result in
+ // |adjustments_on_adjusted_string|, whose offsets are correct with respect
+ // to the original string.
+ //
+ // Assumes both parameters are sorted by increasing offset.
+ //
+ // WARNING: Only supports |first_adjustments| that involve collapsing ranges
+ // of text, not expanding ranges.
+ static void MergeSequentialAdjustments(
+ const Adjustments& first_adjustments,
+ Adjustments* adjustments_on_adjusted_string);
+};
+
+// Like the conversions in utf_string_conversions.h, but also fills in an
+// |adjustments| parameter that reflects the alterations done to the string.
+// It may be NULL.
+BASE_EXPORT bool UTF8ToUTF16WithAdjustments(
+ const char* src,
+ size_t src_len,
+ string16* output,
+ base::OffsetAdjuster::Adjustments* adjustments);
+BASE_EXPORT string16 UTF8ToUTF16WithAdjustments(
+ const base::StringPiece& utf8,
+ base::OffsetAdjuster::Adjustments* adjustments);
+// As above, but instead internally examines the adjustments and applies them
+// to |offsets_for_adjustment|. Input offsets greater than the length of the
+// input string will be set to string16::npos. See comments by AdjustOffsets().
+BASE_EXPORT string16 UTF8ToUTF16AndAdjustOffsets(
+ const base::StringPiece& utf8,
+ std::vector<size_t>* offsets_for_adjustment);
+BASE_EXPORT std::string UTF16ToUTF8AndAdjustOffsets(
+ const base::StringPiece16& utf16,
+ std::vector<size_t>* offsets_for_adjustment);
+
+} // namespace base
+
+#endif // BASE_STRINGS_UTF_OFFSET_STRING_CONVERSIONS_H_
diff --git a/base/strings/utf_offset_string_conversions_unittest.cc b/base/strings/utf_offset_string_conversions_unittest.cc
new file mode 100644
index 0000000000..c5ce647a99
--- /dev/null
+++ b/base/strings/utf_offset_string_conversions_unittest.cc
@@ -0,0 +1,300 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_offset_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+static const size_t kNpos = string16::npos;
+
+} // namespace
+
+TEST(UTFOffsetStringConversionsTest, AdjustOffset) {
+ struct UTF8ToUTF16Case {
+ const char* utf8;
+ size_t input_offset;
+ size_t output_offset;
+ } utf8_to_utf16_cases[] = {
+ {"", 0, 0},
+ {"", kNpos, kNpos},
+ {"\xe4\xbd\xa0\xe5\xa5\xbd", 1, kNpos},
+ {"\xe4\xbd\xa0\xe5\xa5\xbd", 3, 1},
+ {"\xed\xb0\x80z", 3, 3},
+ {"A\xF0\x90\x8C\x80z", 1, 1},
+ {"A\xF0\x90\x8C\x80z", 2, kNpos},
+ {"A\xF0\x90\x8C\x80z", 5, 3},
+ {"A\xF0\x90\x8C\x80z", 6, 4},
+ {"A\xF0\x90\x8C\x80z", kNpos, kNpos},
+ };
+ for (size_t i = 0; i < arraysize(utf8_to_utf16_cases); ++i) {
+ const size_t offset = utf8_to_utf16_cases[i].input_offset;
+ std::vector<size_t> offsets;
+ offsets.push_back(offset);
+ UTF8ToUTF16AndAdjustOffsets(utf8_to_utf16_cases[i].utf8, &offsets);
+ EXPECT_EQ(utf8_to_utf16_cases[i].output_offset, offsets[0]);
+ }
+
+ struct UTF16ToUTF8Case {
+ char16 utf16[10];
+ size_t input_offset;
+ size_t output_offset;
+ } utf16_to_utf8_cases[] = {
+ {{}, 0, 0},
+ // Converted to 3-byte utf-8 sequences
+ {{0x5909, 0x63DB}, 3, kNpos},
+ {{0x5909, 0x63DB}, 2, 6},
+ {{0x5909, 0x63DB}, 1, 3},
+ {{0x5909, 0x63DB}, 0, 0},
+ // Converted to 2-byte utf-8 sequences
+ {{'A', 0x00bc, 0x00be, 'z'}, 1, 1},
+ {{'A', 0x00bc, 0x00be, 'z'}, 2, 3},
+ {{'A', 0x00bc, 0x00be, 'z'}, 3, 5},
+ {{'A', 0x00bc, 0x00be, 'z'}, 4, 6},
+ // Surrogate pair
+ {{'A', 0xd800, 0xdf00, 'z'}, 1, 1},
+ {{'A', 0xd800, 0xdf00, 'z'}, 2, kNpos},
+ {{'A', 0xd800, 0xdf00, 'z'}, 3, 5},
+ {{'A', 0xd800, 0xdf00, 'z'}, 4, 6},
+ };
+ for (size_t i = 0; i < arraysize(utf16_to_utf8_cases); ++i) {
+ size_t offset = utf16_to_utf8_cases[i].input_offset;
+ std::vector<size_t> offsets;
+ offsets.push_back(offset);
+ UTF16ToUTF8AndAdjustOffsets(utf16_to_utf8_cases[i].utf16, &offsets);
+ EXPECT_EQ(utf16_to_utf8_cases[i].output_offset, offsets[0]) << i;
+ }
+}
+
+TEST(UTFOffsetStringConversionsTest, LimitOffsets) {
+ const OffsetAdjuster::Adjustments kNoAdjustments;
+ const size_t kLimit = 10;
+ const size_t kItems = 20;
+ std::vector<size_t> size_ts;
+ for (size_t t = 0; t < kItems; ++t) {
+ size_ts.push_back(t);
+ OffsetAdjuster::AdjustOffset(kNoAdjustments, &size_ts.back(), kLimit);
+ }
+ size_t unlimited_count = 0;
+ for (std::vector<size_t>::iterator ti = size_ts.begin(); ti != size_ts.end();
+ ++ti) {
+ if (*ti != kNpos)
+ ++unlimited_count;
+ }
+ EXPECT_EQ(11U, unlimited_count);
+
+ // Reverse the values in the vector and try again.
+ size_ts.clear();
+ for (size_t t = kItems; t > 0; --t) {
+ size_ts.push_back(t - 1);
+ OffsetAdjuster::AdjustOffset(kNoAdjustments, &size_ts.back(), kLimit);
+ }
+ unlimited_count = 0;
+ for (std::vector<size_t>::iterator ti = size_ts.begin(); ti != size_ts.end();
+ ++ti) {
+ if (*ti != kNpos)
+ ++unlimited_count;
+ }
+ EXPECT_EQ(11U, unlimited_count);
+}
+
+TEST(UTFOffsetStringConversionsTest, AdjustOffsets) {
+ // Imagine we have strings as shown in the following cases where the
+ // X's represent encoded characters.
+ // 1: abcXXXdef ==> abcXdef
+ {
+ std::vector<size_t> offsets;
+ for (size_t t = 0; t <= 9; ++t)
+ offsets.push_back(t);
+ OffsetAdjuster::Adjustments adjustments;
+ adjustments.push_back(OffsetAdjuster::Adjustment(3, 3, 1));
+ OffsetAdjuster::AdjustOffsets(adjustments, &offsets);
+ size_t expected_1[] = {0, 1, 2, 3, kNpos, kNpos, 4, 5, 6, 7};
+ EXPECT_EQ(offsets.size(), arraysize(expected_1));
+ for (size_t i = 0; i < arraysize(expected_1); ++i)
+ EXPECT_EQ(expected_1[i], offsets[i]);
+ }
+
+ // 2: XXXaXXXXbcXXXXXXXdefXXX ==> XaXXbcXXXXdefX
+ {
+ std::vector<size_t> offsets;
+ for (size_t t = 0; t <= 23; ++t)
+ offsets.push_back(t);
+ OffsetAdjuster::Adjustments adjustments;
+ adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 1));
+ adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 2));
+ adjustments.push_back(OffsetAdjuster::Adjustment(10, 7, 4));
+ adjustments.push_back(OffsetAdjuster::Adjustment(20, 3, 1));
+ OffsetAdjuster::AdjustOffsets(adjustments, &offsets);
+ size_t expected_2[] = {
+ 0, kNpos, kNpos, 1, 2, kNpos, kNpos, kNpos, 4, 5, 6, kNpos, kNpos, kNpos,
+ kNpos, kNpos, kNpos, 10, 11, 12, 13, kNpos, kNpos, 14
+ };
+ EXPECT_EQ(offsets.size(), arraysize(expected_2));
+ for (size_t i = 0; i < arraysize(expected_2); ++i)
+ EXPECT_EQ(expected_2[i], offsets[i]);
+ }
+
+ // 3: XXXaXXXXbcdXXXeXX ==> aXXXXbcdXXXe
+ {
+ std::vector<size_t> offsets;
+ for (size_t t = 0; t <= 17; ++t)
+ offsets.push_back(t);
+ OffsetAdjuster::Adjustments adjustments;
+ adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 0));
+ adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 4));
+ adjustments.push_back(OffsetAdjuster::Adjustment(11, 3, 3));
+ adjustments.push_back(OffsetAdjuster::Adjustment(15, 2, 0));
+ OffsetAdjuster::AdjustOffsets(adjustments, &offsets);
+ size_t expected_3[] = {
+ 0, kNpos, kNpos, 0, 1, kNpos, kNpos, kNpos, 5, 6, 7, 8, kNpos, kNpos, 11,
+ 12, kNpos, 12
+ };
+ EXPECT_EQ(offsets.size(), arraysize(expected_3));
+ for (size_t i = 0; i < arraysize(expected_3); ++i)
+ EXPECT_EQ(expected_3[i], offsets[i]);
+ }
+}
+
+TEST(UTFOffsetStringConversionsTest, UnadjustOffsets) {
+ // Imagine we have strings as shown in the following cases where the
+ // X's represent encoded characters.
+ // 1: abcXXXdef ==> abcXdef
+ {
+ std::vector<size_t> offsets;
+ for (size_t t = 0; t <= 7; ++t)
+ offsets.push_back(t);
+ OffsetAdjuster::Adjustments adjustments;
+ adjustments.push_back(OffsetAdjuster::Adjustment(3, 3, 1));
+ OffsetAdjuster::UnadjustOffsets(adjustments, &offsets);
+ size_t expected_1[] = {0, 1, 2, 3, 6, 7, 8, 9};
+ EXPECT_EQ(offsets.size(), arraysize(expected_1));
+ for (size_t i = 0; i < arraysize(expected_1); ++i)
+ EXPECT_EQ(expected_1[i], offsets[i]);
+ }
+
+ // 2: XXXaXXXXbcXXXXXXXdefXXX ==> XaXXbcXXXXdefX
+ {
+ std::vector<size_t> offsets;
+ for (size_t t = 0; t <= 14; ++t)
+ offsets.push_back(t);
+ OffsetAdjuster::Adjustments adjustments;
+ adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 1));
+ adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 2));
+ adjustments.push_back(OffsetAdjuster::Adjustment(10, 7, 4));
+ adjustments.push_back(OffsetAdjuster::Adjustment(20, 3, 1));
+ OffsetAdjuster::UnadjustOffsets(adjustments, &offsets);
+ size_t expected_2[] = {
+ 0, 3, 4, kNpos, 8, 9, 10, kNpos, kNpos, kNpos, 17, 18, 19, 20, 23
+ };
+ EXPECT_EQ(offsets.size(), arraysize(expected_2));
+ for (size_t i = 0; i < arraysize(expected_2); ++i)
+ EXPECT_EQ(expected_2[i], offsets[i]);
+ }
+
+ // 3: XXXaXXXXbcdXXXeXX ==> aXXXXbcdXXXe
+ {
+ std::vector<size_t> offsets;
+ for (size_t t = 0; t <= 12; ++t)
+ offsets.push_back(t);
+ OffsetAdjuster::Adjustments adjustments;
+ adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 0));
+ adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 4));
+ adjustments.push_back(OffsetAdjuster::Adjustment(11, 3, 3));
+ adjustments.push_back(OffsetAdjuster::Adjustment(15, 2, 0));
+ OffsetAdjuster::UnadjustOffsets(adjustments, &offsets);
+ size_t expected_3[] = {
+ 0, // this could just as easily be 3
+ 4, kNpos, kNpos, kNpos, 8, 9, 10, 11, kNpos, kNpos, 14,
+ 15 // this could just as easily be 17
+ };
+ EXPECT_EQ(offsets.size(), arraysize(expected_3));
+ for (size_t i = 0; i < arraysize(expected_3); ++i)
+ EXPECT_EQ(expected_3[i], offsets[i]);
+ }
+}
+
+// MergeSequentialAdjustments is used by net/base/escape.{h,cc} and
+// net/base/net_util.{h,cc}. The two tests EscapeTest.AdjustOffset and
+// NetUtilTest.FormatUrlWithOffsets test its behavior extensively. This
+// is simply a short, additional test.
+TEST(UTFOffsetStringConversionsTest, MergeSequentialAdjustments) {
+ // Pretend the input string is "abcdefghijklmnopqrstuvwxyz".
+
+ // Set up |first_adjustments| to
+ // - remove the leading "a"
+ // - combine the "bc" into one character (call it ".")
+ // - remove the "f"
+ // - remove the "tuv"
+ // The resulting string should be ".deghijklmnopqrswxyz".
+ OffsetAdjuster::Adjustments first_adjustments;
+ first_adjustments.push_back(OffsetAdjuster::Adjustment(0, 1, 0));
+ first_adjustments.push_back(OffsetAdjuster::Adjustment(1, 2, 1));
+ first_adjustments.push_back(OffsetAdjuster::Adjustment(5, 1, 0));
+ first_adjustments.push_back(OffsetAdjuster::Adjustment(19, 3, 0));
+
+ // Set up |adjustments_on_adjusted_string| to
+ // - combine the "." character that replaced "bc" with "d" into one character
+ // (call it "?")
+ // - remove the "egh"
+ // - expand the "i" into two characters (call them "12")
+ // - combine the "jkl" into one character (call it "@")
+ // - expand the "z" into two characters (call it "34")
+ // The resulting string should be "?12@mnopqrswxy34".
+ OffsetAdjuster::Adjustments adjustments_on_adjusted_string;
+ adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
+ 0, 2, 1));
+ adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
+ 2, 3, 0));
+ adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
+ 5, 1, 2));
+ adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
+ 6, 3, 1));
+ adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
+ 19, 1, 2));
+
+ // Now merge the adjustments and check the results.
+ OffsetAdjuster::MergeSequentialAdjustments(first_adjustments,
+ &adjustments_on_adjusted_string);
+ // The merged adjustments should look like
+ // - combine abcd into "?"
+ // - note: it's also reasonable for the Merge function to instead produce
+ // two adjustments instead of this, one to remove a and another to
+ // combine bcd into "?". This test verifies the current behavior.
+ // - remove efgh
+ // - expand i into "12"
+ // - combine jkl into "@"
+ // - remove tuv
+ // - expand z into "34"
+ ASSERT_EQ(6u, adjustments_on_adjusted_string.size());
+ EXPECT_EQ(0u, adjustments_on_adjusted_string[0].original_offset);
+ EXPECT_EQ(4u, adjustments_on_adjusted_string[0].original_length);
+ EXPECT_EQ(1u, adjustments_on_adjusted_string[0].output_length);
+ EXPECT_EQ(4u, adjustments_on_adjusted_string[1].original_offset);
+ EXPECT_EQ(4u, adjustments_on_adjusted_string[1].original_length);
+ EXPECT_EQ(0u, adjustments_on_adjusted_string[1].output_length);
+ EXPECT_EQ(8u, adjustments_on_adjusted_string[2].original_offset);
+ EXPECT_EQ(1u, adjustments_on_adjusted_string[2].original_length);
+ EXPECT_EQ(2u, adjustments_on_adjusted_string[2].output_length);
+ EXPECT_EQ(9u, adjustments_on_adjusted_string[3].original_offset);
+ EXPECT_EQ(3u, adjustments_on_adjusted_string[3].original_length);
+ EXPECT_EQ(1u, adjustments_on_adjusted_string[3].output_length);
+ EXPECT_EQ(19u, adjustments_on_adjusted_string[4].original_offset);
+ EXPECT_EQ(3u, adjustments_on_adjusted_string[4].original_length);
+ EXPECT_EQ(0u, adjustments_on_adjusted_string[4].output_length);
+ EXPECT_EQ(25u, adjustments_on_adjusted_string[5].original_offset);
+ EXPECT_EQ(1u, adjustments_on_adjusted_string[5].original_length);
+ EXPECT_EQ(2u, adjustments_on_adjusted_string[5].output_length);
+}
+
+} // namespace base
diff --git a/base/supports_user_data.cc b/base/supports_user_data.cc
new file mode 100644
index 0000000000..43ab21a679
--- /dev/null
+++ b/base/supports_user_data.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/supports_user_data.h"
+
+namespace base {
+
+SupportsUserData::SupportsUserData() {
+ // Harmless to construct on a different execution sequence to subsequent
+ // usage.
+ sequence_checker_.DetachFromSequence();
+}
+
+SupportsUserData::Data* SupportsUserData::GetUserData(const void* key) const {
+ DCHECK(sequence_checker_.CalledOnValidSequence());
+ // Avoid null keys; they are too vulnerable to collision.
+ DCHECK(key);
+ DataMap::const_iterator found = user_data_.find(key);
+ if (found != user_data_.end())
+ return found->second.get();
+ return nullptr;
+}
+
+void SupportsUserData::SetUserData(const void* key,
+ std::unique_ptr<Data> data) {
+ DCHECK(sequence_checker_.CalledOnValidSequence());
+ // Avoid null keys; they are too vulnerable to collision.
+ DCHECK(key);
+ user_data_[key] = std::move(data);
+}
+
+void SupportsUserData::RemoveUserData(const void* key) {
+ DCHECK(sequence_checker_.CalledOnValidSequence());
+ user_data_.erase(key);
+}
+
+void SupportsUserData::DetachFromSequence() {
+ sequence_checker_.DetachFromSequence();
+}
+
+SupportsUserData::~SupportsUserData() {
+ DCHECK(sequence_checker_.CalledOnValidSequence() || user_data_.empty());
+ DataMap local_user_data;
+ user_data_.swap(local_user_data);
+ // Now this->user_data_ is empty, and any destructors called transitively from
+ // the destruction of |local_user_data| will see it that way instead of
+ // examining a being-destroyed object.
+}
+
+} // namespace base
diff --git a/base/supports_user_data.h b/base/supports_user_data.h
new file mode 100644
index 0000000000..356c97329b
--- /dev/null
+++ b/base/supports_user_data.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_SUPPORTS_USER_DATA_H_
+#define BASE_SUPPORTS_USER_DATA_H_
+
+#include <map>
+#include <memory>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
+
+// TODO(gab): Removing this include causes IWYU failures in other headers,
+// remove it in a follow- up CL.
+#include "base/threading/thread_checker.h"
+
+namespace base {
+
+// This is a helper for classes that want to allow users to stash random data by
+// key. At destruction all the objects will be destructed.
+class BASE_EXPORT SupportsUserData {
+ public:
+ SupportsUserData();
+
+ // Derive from this class and add your own data members to associate extra
+ // information with this object. Alternatively, add this as a public base
+ // class to any class with a virtual destructor.
+ class BASE_EXPORT Data {
+ public:
+ virtual ~Data() = default;
+ };
+
+ // The user data allows the clients to associate data with this object.
+ // Multiple user data values can be stored under different keys.
+ // This object will TAKE OWNERSHIP of the given data pointer, and will
+ // delete the object if it is changed or the object is destroyed.
+ // |key| must not be null--that value is too vulnerable for collision.
+ Data* GetUserData(const void* key) const;
+ void SetUserData(const void* key, std::unique_ptr<Data> data);
+ void RemoveUserData(const void* key);
+
+ // SupportsUserData is not thread-safe, and on debug build will assert it is
+ // only used on one execution sequence. Calling this method allows the caller
+ // to hand the SupportsUserData instance across execution sequences. Use only
+ // if you are taking full control of the synchronization of that hand over.
+ void DetachFromSequence();
+
+ protected:
+ virtual ~SupportsUserData();
+
+ private:
+ using DataMap = std::map<const void*, std::unique_ptr<Data>>;
+
+ // Externally-defined data accessible by key.
+ DataMap user_data_;
+ // Guards usage of |user_data_|
+ SequenceChecker sequence_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(SupportsUserData);
+};
+
+// Adapter class that releases a refcounted object when the
+// SupportsUserData::Data object is deleted.
+template <typename T>
+class UserDataAdapter : public base::SupportsUserData::Data {
+ public:
+ static T* Get(const SupportsUserData* supports_user_data, const void* key) {
+ UserDataAdapter* data =
+ static_cast<UserDataAdapter*>(supports_user_data->GetUserData(key));
+ return data ? static_cast<T*>(data->object_.get()) : NULL;
+ }
+
+ UserDataAdapter(T* object) : object_(object) {}
+ T* release() { return object_.release(); }
+
+ private:
+ scoped_refptr<T> object_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserDataAdapter);
+};
+
+} // namespace base
+
+#endif // BASE_SUPPORTS_USER_DATA_H_
diff --git a/base/supports_user_data_unittest.cc b/base/supports_user_data_unittest.cc
new file mode 100644
index 0000000000..2e0a724bda
--- /dev/null
+++ b/base/supports_user_data_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/supports_user_data.h"
+
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+struct TestSupportsUserData : public SupportsUserData {};
+
+struct UsesItself : public SupportsUserData::Data {
+ UsesItself(SupportsUserData* supports_user_data, const void* key)
+ : supports_user_data_(supports_user_data),
+ key_(key) {
+ }
+
+ ~UsesItself() override {
+ EXPECT_EQ(nullptr, supports_user_data_->GetUserData(key_));
+ }
+
+ SupportsUserData* supports_user_data_;
+ const void* key_;
+};
+
+TEST(SupportsUserDataTest, ClearWorksRecursively) {
+ TestSupportsUserData supports_user_data;
+ char key = 0;
+ supports_user_data.SetUserData(
+ &key, std::make_unique<UsesItself>(&supports_user_data, &key));
+ // Destruction of supports_user_data runs the actual test.
+}
+
+} // namespace
+} // namespace base
diff --git a/base/synchronization/waitable_event_watcher_unittest.cc b/base/synchronization/waitable_event_watcher_unittest.cc
new file mode 100644
index 0000000000..ec056effe5
--- /dev/null
+++ b/base/synchronization/waitable_event_watcher_unittest.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/synchronization/waitable_event_watcher.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+// The message loops on which each waitable event timer should be tested.
+const MessageLoop::Type testing_message_loops[] = {
+ MessageLoop::TYPE_DEFAULT,
+ MessageLoop::TYPE_IO,
+#if !defined(OS_IOS) // iOS does not allow direct running of the UI loop.
+ MessageLoop::TYPE_UI,
+#endif
+};
+
+void QuitWhenSignaled(WaitableEvent* event) {
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+}
+
+class DecrementCountContainer {
+ public:
+ explicit DecrementCountContainer(int* counter) : counter_(counter) {}
+ void OnWaitableEventSignaled(WaitableEvent* object) {
+ // NOTE: |object| may be already deleted.
+ --(*counter_);
+ }
+
+ private:
+ int* counter_;
+};
+
+} // namespace
+
+class WaitableEventWatcherTest
+ : public testing::TestWithParam<MessageLoop::Type> {};
+
+TEST_P(WaitableEventWatcherTest, BasicSignalManual) {
+ MessageLoop message_loop(GetParam());
+
+ // A manual-reset event that is not yet signaled.
+ WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ WaitableEventWatcher watcher;
+ watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+
+ event.Signal();
+
+ RunLoop().Run();
+
+ EXPECT_TRUE(event.IsSignaled());
+}
+
+TEST_P(WaitableEventWatcherTest, BasicSignalAutomatic) {
+ MessageLoop message_loop(GetParam());
+
+ WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ WaitableEventWatcher watcher;
+ watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+
+ event.Signal();
+
+ RunLoop().Run();
+
+ // The WaitableEventWatcher consumes the event signal.
+ EXPECT_FALSE(event.IsSignaled());
+}
+
+TEST_P(WaitableEventWatcherTest, BasicCancel) {
+ MessageLoop message_loop(GetParam());
+
+ // A manual-reset event that is not yet signaled.
+ WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ WaitableEventWatcher watcher;
+
+ watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+
+ watcher.StopWatching();
+}
+
+TEST_P(WaitableEventWatcherTest, CancelAfterSet) {
+ MessageLoop message_loop(GetParam());
+
+ // A manual-reset event that is not yet signaled.
+ WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ WaitableEventWatcher watcher;
+
+ int counter = 1;
+ DecrementCountContainer delegate(&counter);
+ WaitableEventWatcher::EventCallback callback = BindOnce(
+ &DecrementCountContainer::OnWaitableEventSignaled, Unretained(&delegate));
+ watcher.StartWatching(&event, std::move(callback),
+ SequencedTaskRunnerHandle::Get());
+
+ event.Signal();
+
+ // Let the background thread do its business
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
+
+ watcher.StopWatching();
+
+ RunLoop().RunUntilIdle();
+
+ // Our delegate should not have fired.
+ EXPECT_EQ(1, counter);
+}
+
+TEST_P(WaitableEventWatcherTest, OutlivesMessageLoop) {
+ // Simulate a MessageLoop that dies before an WaitableEventWatcher. This
+ // ordinarily doesn't happen when people use the Thread class, but it can
+ // happen when people use the Singleton pattern or atexit.
+ WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ {
+ std::unique_ptr<WaitableEventWatcher> watcher;
+ {
+ MessageLoop message_loop(GetParam());
+ watcher = std::make_unique<WaitableEventWatcher>();
+
+ watcher->StartWatching(&event, BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+ }
+ }
+}
+
+TEST_P(WaitableEventWatcherTest, SignaledAtStartManual) {
+ MessageLoop message_loop(GetParam());
+
+ WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::SIGNALED);
+
+ WaitableEventWatcher watcher;
+ watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+
+ RunLoop().Run();
+
+ EXPECT_TRUE(event.IsSignaled());
+}
+
+TEST_P(WaitableEventWatcherTest, SignaledAtStartAutomatic) {
+ MessageLoop message_loop(GetParam());
+
+ WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::SIGNALED);
+
+ WaitableEventWatcher watcher;
+ watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+
+ RunLoop().Run();
+
+ // The watcher consumes the event signal.
+ EXPECT_FALSE(event.IsSignaled());
+}
+
+TEST_P(WaitableEventWatcherTest, StartWatchingInCallback) {
+ MessageLoop message_loop(GetParam());
+
+ WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ WaitableEventWatcher watcher;
+ watcher.StartWatching(
+ &event,
+ BindOnce(
+ [](WaitableEventWatcher* watcher, WaitableEvent* event) {
+ // |event| is manual, so the second watcher will run
+ // immediately.
+ watcher->StartWatching(event, BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+ },
+ &watcher),
+ SequencedTaskRunnerHandle::Get());
+
+ event.Signal();
+
+ RunLoop().Run();
+}
+
+TEST_P(WaitableEventWatcherTest, MultipleWatchersManual) {
+ MessageLoop message_loop(GetParam());
+
+ WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ int counter1 = 0;
+ int counter2 = 0;
+
+ auto callback = [](RunLoop* run_loop, int* counter, WaitableEvent* event) {
+ ++(*counter);
+ run_loop->QuitWhenIdle();
+ };
+
+ RunLoop run_loop;
+
+ WaitableEventWatcher watcher1;
+ watcher1.StartWatching(
+ &event, BindOnce(callback, Unretained(&run_loop), Unretained(&counter1)),
+ SequencedTaskRunnerHandle::Get());
+
+ WaitableEventWatcher watcher2;
+ watcher2.StartWatching(
+ &event, BindOnce(callback, Unretained(&run_loop), Unretained(&counter2)),
+ SequencedTaskRunnerHandle::Get());
+
+ event.Signal();
+ run_loop.Run();
+
+ EXPECT_EQ(1, counter1);
+ EXPECT_EQ(1, counter2);
+ EXPECT_TRUE(event.IsSignaled());
+}
+
+// Tests that only one async waiter gets called back for an auto-reset event.
+TEST_P(WaitableEventWatcherTest, MultipleWatchersAutomatic) {
+ MessageLoop message_loop(GetParam());
+
+ WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ int counter1 = 0;
+ int counter2 = 0;
+
+ auto callback = [](RunLoop** run_loop, int* counter, WaitableEvent* event) {
+ ++(*counter);
+ (*run_loop)->QuitWhenIdle();
+ };
+
+ // The same RunLoop instance cannot be Run more than once, and it is
+ // undefined which watcher will get called back first. Have the callback
+ // dereference this pointer to quit the loop, which will be updated on each
+ // Run.
+ RunLoop* current_run_loop;
+
+ WaitableEventWatcher watcher1;
+ watcher1.StartWatching(
+ &event,
+ BindOnce(callback, Unretained(&current_run_loop), Unretained(&counter1)),
+ SequencedTaskRunnerHandle::Get());
+
+ WaitableEventWatcher watcher2;
+ watcher2.StartWatching(
+ &event,
+ BindOnce(callback, Unretained(&current_run_loop), Unretained(&counter2)),
+ SequencedTaskRunnerHandle::Get());
+
+ event.Signal();
+ {
+ RunLoop run_loop;
+ current_run_loop = &run_loop;
+ run_loop.Run();
+ }
+
+ // Only one of the waiters should have been signaled.
+ EXPECT_TRUE((counter1 == 1) ^ (counter2 == 1));
+
+ EXPECT_FALSE(event.IsSignaled());
+
+ event.Signal();
+ {
+ RunLoop run_loop;
+ current_run_loop = &run_loop;
+ run_loop.Run();
+ }
+
+ EXPECT_FALSE(event.IsSignaled());
+
+ // The other watcher should have been signaled.
+ EXPECT_EQ(1, counter1);
+ EXPECT_EQ(1, counter2);
+}
+
+// To help detect errors around deleting WaitableEventWatcher, an additional
+// bool parameter is used to test sleeping between watching and deletion.
+class WaitableEventWatcherDeletionTest
+ : public testing::TestWithParam<std::tuple<MessageLoop::Type, bool>> {};
+
+TEST_P(WaitableEventWatcherDeletionTest, DeleteUnder) {
+ MessageLoop::Type message_loop_type;
+ bool delay_after_delete;
+ std::tie(message_loop_type, delay_after_delete) = GetParam();
+
+ // Delete the WaitableEvent out from under the Watcher. This is explictly
+ // allowed by the interface.
+
+ MessageLoop message_loop(message_loop_type);
+
+ {
+ WaitableEventWatcher watcher;
+
+ auto* event = new WaitableEvent(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ watcher.StartWatching(event, BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+
+ if (delay_after_delete) {
+ // On Windows that sleep() improves the chance to catch some problems.
+ // It postpones the dtor |watcher| (which immediately cancel the waiting)
+ // and gives some time to run to a created background thread.
+ // Unfortunately, that thread is under OS control and we can't
+ // manipulate it directly.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
+ }
+
+ delete event;
+ }
+}
+
+TEST_P(WaitableEventWatcherDeletionTest, SignalAndDelete) {
+ MessageLoop::Type message_loop_type;
+ bool delay_after_delete;
+ std::tie(message_loop_type, delay_after_delete) = GetParam();
+
+ // Signal and immediately delete the WaitableEvent out from under the Watcher.
+
+ MessageLoop message_loop(message_loop_type);
+
+ {
+ WaitableEventWatcher watcher;
+
+ auto event = std::make_unique<WaitableEvent>(
+ WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ watcher.StartWatching(event.get(), BindOnce(&QuitWhenSignaled),
+ SequencedTaskRunnerHandle::Get());
+ event->Signal();
+ event.reset();
+
+ if (delay_after_delete) {
+ // On Windows that sleep() improves the chance to catch some problems.
+ // It postpones the dtor |watcher| (which immediately cancel the waiting)
+ // and gives some time to run to a created background thread.
+ // Unfortunately, that thread is under OS control and we can't
+ // manipulate it directly.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
+ }
+
+ // Wait for the watcher callback.
+ RunLoop().Run();
+ }
+}
+
+// Tests deleting the WaitableEventWatcher between signaling the event and
+// when the callback should be run.
+TEST_P(WaitableEventWatcherDeletionTest, DeleteWatcherBeforeCallback) {
+ MessageLoop::Type message_loop_type;
+ bool delay_after_delete;
+ std::tie(message_loop_type, delay_after_delete) = GetParam();
+
+ MessageLoop message_loop(message_loop_type);
+ scoped_refptr<SingleThreadTaskRunner> task_runner =
+ message_loop.task_runner();
+
+ // Flag used to esnure that the |watcher_callback| never runs.
+ bool did_callback = false;
+
+ WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ auto watcher = std::make_unique<WaitableEventWatcher>();
+
+ // Queue up a series of tasks:
+ // 1. StartWatching the WaitableEvent
+ // 2. Signal the event (which will result in another task getting posted to
+ // the |task_runner|)
+ // 3. Delete the WaitableEventWatcher
+ // 4. WaitableEventWatcher callback should run (from #2)
+
+ WaitableEventWatcher::EventCallback watcher_callback = BindOnce(
+ [](bool* did_callback, WaitableEvent*) {
+ *did_callback = true;
+ },
+ Unretained(&did_callback));
+
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(IgnoreResult(&WaitableEventWatcher::StartWatching),
+ Unretained(watcher.get()), Unretained(&event),
+ std::move(watcher_callback), task_runner));
+ task_runner->PostTask(FROM_HERE,
+ BindOnce(&WaitableEvent::Signal, Unretained(&event)));
+ task_runner->DeleteSoon(FROM_HERE, std::move(watcher));
+ if (delay_after_delete) {
+ task_runner->PostTask(FROM_HERE, BindOnce(&PlatformThread::Sleep,
+ TimeDelta::FromMilliseconds(30)));
+ }
+
+ RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(did_callback);
+}
+
+INSTANTIATE_TEST_CASE_P(,
+ WaitableEventWatcherTest,
+ testing::ValuesIn(testing_message_loops));
+
+INSTANTIATE_TEST_CASE_P(
+ ,
+ WaitableEventWatcherDeletionTest,
+ testing::Combine(testing::ValuesIn(testing_message_loops),
+ testing::Bool()));
+
+} // namespace base
diff --git a/base/sys_byteorder_unittest.cc b/base/sys_byteorder_unittest.cc
new file mode 100644
index 0000000000..8167be3b9a
--- /dev/null
+++ b/base/sys_byteorder_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/sys_byteorder.h"
+
+#include <stdint.h>
+
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const uint16_t k16BitTestData = 0xaabb;
+const uint16_t k16BitSwappedTestData = 0xbbaa;
+const uint32_t k32BitTestData = 0xaabbccdd;
+const uint32_t k32BitSwappedTestData = 0xddccbbaa;
+const uint64_t k64BitTestData = 0xaabbccdd44332211;
+const uint64_t k64BitSwappedTestData = 0x11223344ddccbbaa;
+
+} // namespace
+
+TEST(ByteOrderTest, ByteSwap16) {
+ uint16_t swapped = base::ByteSwap(k16BitTestData);
+ EXPECT_EQ(k16BitSwappedTestData, swapped);
+ uint16_t reswapped = base::ByteSwap(swapped);
+ EXPECT_EQ(k16BitTestData, reswapped);
+}
+
+TEST(ByteOrderTest, ByteSwap32) {
+ uint32_t swapped = base::ByteSwap(k32BitTestData);
+ EXPECT_EQ(k32BitSwappedTestData, swapped);
+ uint32_t reswapped = base::ByteSwap(swapped);
+ EXPECT_EQ(k32BitTestData, reswapped);
+}
+
+TEST(ByteOrderTest, ByteSwap64) {
+ uint64_t swapped = base::ByteSwap(k64BitTestData);
+ EXPECT_EQ(k64BitSwappedTestData, swapped);
+ uint64_t reswapped = base::ByteSwap(swapped);
+ EXPECT_EQ(k64BitTestData, reswapped);
+}
+
+TEST(ByteOrderTest, ByteSwapUintPtrT) {
+#if defined(ARCH_CPU_64_BITS)
+ const uintptr_t test_data = static_cast<uintptr_t>(k64BitTestData);
+ const uintptr_t swapped_test_data =
+ static_cast<uintptr_t>(k64BitSwappedTestData);
+#elif defined(ARCH_CPU_32_BITS)
+ const uintptr_t test_data = static_cast<uintptr_t>(k32BitTestData);
+ const uintptr_t swapped_test_data =
+ static_cast<uintptr_t>(k32BitSwappedTestData);
+#else
+#error architecture not supported
+#endif
+
+ uintptr_t swapped = base::ByteSwapUintPtrT(test_data);
+ EXPECT_EQ(swapped_test_data, swapped);
+ uintptr_t reswapped = base::ByteSwapUintPtrT(swapped);
+ EXPECT_EQ(test_data, reswapped);
+}
+
+TEST(ByteOrderTest, ByteSwapToLE16) {
+ uint16_t le = base::ByteSwapToLE16(k16BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k16BitTestData, le);
+#else
+ EXPECT_EQ(k16BitSwappedTestData, le);
+#endif
+}
+
+TEST(ByteOrderTest, ByteSwapToLE32) {
+ uint32_t le = base::ByteSwapToLE32(k32BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k32BitTestData, le);
+#else
+ EXPECT_EQ(k32BitSwappedTestData, le);
+#endif
+}
+
+TEST(ByteOrderTest, ByteSwapToLE64) {
+ uint64_t le = base::ByteSwapToLE64(k64BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k64BitTestData, le);
+#else
+ EXPECT_EQ(k64BitSwappedTestData, le);
+#endif
+}
+
+TEST(ByteOrderTest, NetToHost16) {
+ uint16_t h = base::NetToHost16(k16BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k16BitSwappedTestData, h);
+#else
+ EXPECT_EQ(k16BitTestData, h);
+#endif
+}
+
+TEST(ByteOrderTest, NetToHost32) {
+ uint32_t h = base::NetToHost32(k32BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k32BitSwappedTestData, h);
+#else
+ EXPECT_EQ(k32BitTestData, h);
+#endif
+}
+
+TEST(ByteOrderTest, NetToHost64) {
+ uint64_t h = base::NetToHost64(k64BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k64BitSwappedTestData, h);
+#else
+ EXPECT_EQ(k64BitTestData, h);
+#endif
+}
+
+TEST(ByteOrderTest, HostToNet16) {
+ uint16_t n = base::HostToNet16(k16BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k16BitSwappedTestData, n);
+#else
+ EXPECT_EQ(k16BitTestData, n);
+#endif
+}
+
+TEST(ByteOrderTest, HostToNet32) {
+ uint32_t n = base::HostToNet32(k32BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k32BitSwappedTestData, n);
+#else
+ EXPECT_EQ(k32BitTestData, n);
+#endif
+}
+
+TEST(ByteOrderTest, HostToNet64) {
+ uint64_t n = base::HostToNet64(k64BitTestData);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+ EXPECT_EQ(k64BitSwappedTestData, n);
+#else
+ EXPECT_EQ(k64BitTestData, n);
+#endif
+}
diff --git a/base/sys_info_android.cc b/base/sys_info_android.cc
new file mode 100644
index 0000000000..77047962f1
--- /dev/null
+++ b/base/sys_info_android.cc
@@ -0,0 +1,241 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/sys_info.h"
+
+#include <dlfcn.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/system_properties.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/sys_utils.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info_internal.h"
+
+#if (__ANDROID_API__ >= 21 /* 5.0 - Lollipop */)
+
+namespace {
+
+typedef int (SystemPropertyGetFunction)(const char*, char*);
+
+SystemPropertyGetFunction* DynamicallyLoadRealSystemPropertyGet() {
+ // libc.so should already be open, get a handle to it.
+ void* handle = dlopen("libc.so", RTLD_NOLOAD);
+ if (!handle) {
+ LOG(FATAL) << "Cannot dlopen libc.so: " << dlerror();
+ }
+ SystemPropertyGetFunction* real_system_property_get =
+ reinterpret_cast<SystemPropertyGetFunction*>(
+ dlsym(handle, "__system_property_get"));
+ if (!real_system_property_get) {
+ LOG(FATAL) << "Cannot resolve __system_property_get(): " << dlerror();
+ }
+ return real_system_property_get;
+}
+
+static base::LazyInstance<base::internal::LazySysInfoValue<
+ SystemPropertyGetFunction*, DynamicallyLoadRealSystemPropertyGet> >::Leaky
+ g_lazy_real_system_property_get = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Android 'L' removes __system_property_get from the NDK, however it is still
+// a hidden symbol in libc. Until we remove all calls of __system_property_get
+// from Chrome we work around this by defining a weak stub here, which uses
+// dlsym to but ensures that Chrome uses the real system
+// implementatation when loaded. http://crbug.com/392191.
+BASE_EXPORT int __system_property_get(const char* name, char* value) {
+ return g_lazy_real_system_property_get.Get().value()(name, value);
+}
+
+#endif
+
+namespace {
+
+// Default version of Android to fall back to when actual version numbers
+// cannot be acquired. Use the latest Android release with a higher bug fix
+// version to avoid unnecessarily comparison errors with the latest release.
+// This should be manually kept up to date on each Android release.
+const int kDefaultAndroidMajorVersion = 8;
+const int kDefaultAndroidMinorVersion = 1;
+const int kDefaultAndroidBugfixVersion = 99;
+
+// Get and parse out the OS version numbers from the system properties.
+// Note if parse fails, the "default" version is returned as fallback.
+void GetOsVersionStringAndNumbers(std::string* version_string,
+ int32_t* major_version,
+ int32_t* minor_version,
+ int32_t* bugfix_version) {
+ // Read the version number string out from the properties.
+ char os_version_str[PROP_VALUE_MAX];
+ __system_property_get("ro.build.version.release", os_version_str);
+
+ if (os_version_str[0]) {
+ // Try to parse out the version numbers from the string.
+ int num_read = sscanf(os_version_str, "%d.%d.%d", major_version,
+ minor_version, bugfix_version);
+
+ if (num_read > 0) {
+ // If we don't have a full set of version numbers, make the extras 0.
+ if (num_read < 2)
+ *minor_version = 0;
+ if (num_read < 3)
+ *bugfix_version = 0;
+ *version_string = std::string(os_version_str);
+ return;
+ }
+ }
+
+ // For some reason, we couldn't parse the version number string.
+ *major_version = kDefaultAndroidMajorVersion;
+ *minor_version = kDefaultAndroidMinorVersion;
+ *bugfix_version = kDefaultAndroidBugfixVersion;
+ *version_string = ::base::StringPrintf("%d.%d.%d", *major_version,
+ *minor_version, *bugfix_version);
+}
+
+// Parses a system property (specified with unit 'k','m' or 'g').
+// Returns a value in bytes.
+// Returns -1 if the string could not be parsed.
+int64_t ParseSystemPropertyBytes(const base::StringPiece& str) {
+ const int64_t KB = 1024;
+ const int64_t MB = 1024 * KB;
+ const int64_t GB = 1024 * MB;
+ if (str.size() == 0u)
+ return -1;
+ int64_t unit_multiplier = 1;
+ size_t length = str.size();
+ if (str[length - 1] == 'k') {
+ unit_multiplier = KB;
+ length--;
+ } else if (str[length - 1] == 'm') {
+ unit_multiplier = MB;
+ length--;
+ } else if (str[length - 1] == 'g') {
+ unit_multiplier = GB;
+ length--;
+ }
+ int64_t result = 0;
+ bool parsed = base::StringToInt64(str.substr(0, length), &result);
+ bool negative = result <= 0;
+ bool overflow =
+ result >= std::numeric_limits<int64_t>::max() / unit_multiplier;
+ if (!parsed || negative || overflow)
+ return -1;
+ return result * unit_multiplier;
+}
+
+int GetDalvikHeapSizeMB() {
+ char heap_size_str[PROP_VALUE_MAX];
+ __system_property_get("dalvik.vm.heapsize", heap_size_str);
+ // dalvik.vm.heapsize property is writable by a root user.
+ // Clamp it to reasonable range as a sanity check,
+ // a typical android device will never have less than 48MB.
+ const int64_t MB = 1024 * 1024;
+ int64_t result = ParseSystemPropertyBytes(heap_size_str);
+ if (result == -1) {
+ // We should consider not exposing these values if they are not reliable.
+ LOG(ERROR) << "Can't parse dalvik.vm.heapsize: " << heap_size_str;
+ result = base::SysInfo::AmountOfPhysicalMemoryMB() / 3;
+ }
+ result =
+ std::min<int64_t>(std::max<int64_t>(32 * MB, result), 1024 * MB) / MB;
+ return static_cast<int>(result);
+}
+
+int GetDalvikHeapGrowthLimitMB() {
+ char heap_size_str[PROP_VALUE_MAX];
+ __system_property_get("dalvik.vm.heapgrowthlimit", heap_size_str);
+ // dalvik.vm.heapgrowthlimit property is writable by a root user.
+ // Clamp it to reasonable range as a sanity check,
+ // a typical android device will never have less than 24MB.
+ const int64_t MB = 1024 * 1024;
+ int64_t result = ParseSystemPropertyBytes(heap_size_str);
+ if (result == -1) {
+ // We should consider not exposing these values if they are not reliable.
+ LOG(ERROR) << "Can't parse dalvik.vm.heapgrowthlimit: " << heap_size_str;
+ result = base::SysInfo::AmountOfPhysicalMemoryMB() / 6;
+ }
+ result = std::min<int64_t>(std::max<int64_t>(16 * MB, result), 512 * MB) / MB;
+ return static_cast<int>(result);
+}
+
+} // anonymous namespace
+
+namespace base {
+
+std::string SysInfo::HardwareModelName() {
+ char device_model_str[PROP_VALUE_MAX];
+ __system_property_get("ro.product.model", device_model_str);
+ return std::string(device_model_str);
+}
+
+std::string SysInfo::OperatingSystemName() {
+ return "Android";
+}
+
+std::string SysInfo::OperatingSystemVersion() {
+ std::string version_string;
+ int32_t major, minor, bugfix;
+ GetOsVersionStringAndNumbers(&version_string, &major, &minor, &bugfix);
+ return version_string;
+}
+
+void SysInfo::OperatingSystemVersionNumbers(int32_t* major_version,
+ int32_t* minor_version,
+ int32_t* bugfix_version) {
+ std::string version_string;
+ GetOsVersionStringAndNumbers(&version_string, major_version, minor_version,
+ bugfix_version);
+}
+
+std::string SysInfo::GetAndroidBuildCodename() {
+ char os_version_codename_str[PROP_VALUE_MAX];
+ __system_property_get("ro.build.version.codename", os_version_codename_str);
+ return std::string(os_version_codename_str);
+}
+
+std::string SysInfo::GetAndroidBuildID() {
+ char os_build_id_str[PROP_VALUE_MAX];
+ __system_property_get("ro.build.id", os_build_id_str);
+ return std::string(os_build_id_str);
+}
+
+int SysInfo::DalvikHeapSizeMB() {
+ static int heap_size = GetDalvikHeapSizeMB();
+ return heap_size;
+}
+
+int SysInfo::DalvikHeapGrowthLimitMB() {
+ static int heap_growth_limit = GetDalvikHeapGrowthLimitMB();
+ return heap_growth_limit;
+}
+
+static base::LazyInstance<
+ base::internal::LazySysInfoValue<bool,
+ android::SysUtils::IsLowEndDeviceFromJni> >::Leaky
+ g_lazy_low_end_device = LAZY_INSTANCE_INITIALIZER;
+
+bool SysInfo::IsLowEndDeviceImpl() {
+ // This code might be used in some environments
+ // which might not have a Java environment.
+ // Note that we need to call the Java version here.
+ // There exists a complete native implementation in
+ // sys_info.cc but calling that here would mean that
+ // the Java code and the native code would call different
+ // implementations which could give different results.
+ // Also the Java code cannot depend on the native code
+ // since it might not be loaded yet.
+ if (!base::android::IsVMInitialized())
+ return false;
+ return g_lazy_low_end_device.Get().value();
+}
+
+
+} // namespace base
diff --git a/base/syslog_logging.cc b/base/syslog_logging.cc
new file mode 100644
index 0000000000..53bc1aaab6
--- /dev/null
+++ b/base/syslog_logging.cc
@@ -0,0 +1,119 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/syslog_logging.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/debug/stack_trace.h"
+#elif defined(OS_LINUX)
+// <syslog.h> defines LOG_INFO, LOG_WARNING macros that could conflict with
+// base::LOG_INFO, base::LOG_WARNING.
+#include <syslog.h>
+#undef LOG_INFO
+#undef LOG_WARNING
+#endif
+
+#include <ostream>
+#include <string>
+
+namespace logging {
+
+#if defined(OS_WIN)
+
+namespace {
+
+std::string* g_event_source_name = nullptr;
+uint16_t g_category = 0;
+uint32_t g_event_id = 0;
+
+} // namespace
+
+void SetEventSource(const std::string& name,
+ uint16_t category,
+ uint32_t event_id) {
+ DCHECK_EQ(nullptr, g_event_source_name);
+ g_event_source_name = new std::string(name);
+ g_category = category;
+ g_event_id = event_id;
+}
+
+#endif // defined(OS_WIN)
+
+EventLogMessage::EventLogMessage(const char* file,
+ int line,
+ LogSeverity severity)
+ : log_message_(file, line, severity) {
+}
+
+EventLogMessage::~EventLogMessage() {
+#if defined(OS_WIN)
+ // If g_event_source_name is nullptr (which it is per default) SYSLOG will
+ // degrade gracefully to regular LOG. If you see this happening most probably
+ // you are using SYSLOG before you called SetEventSourceName.
+ if (g_event_source_name == nullptr)
+ return;
+
+ HANDLE event_log_handle =
+ RegisterEventSourceA(nullptr, g_event_source_name->c_str());
+ if (event_log_handle == nullptr) {
+ stream() << " !!NOT ADDED TO EVENTLOG!!";
+ return;
+ }
+
+ base::ScopedClosureRunner auto_deregister(
+ base::Bind(base::IgnoreResult(&DeregisterEventSource), event_log_handle));
+ std::string message(log_message_.str());
+ WORD log_type = EVENTLOG_ERROR_TYPE;
+ switch (log_message_.severity()) {
+ case LOG_INFO:
+ log_type = EVENTLOG_INFORMATION_TYPE;
+ break;
+ case LOG_WARNING:
+ log_type = EVENTLOG_WARNING_TYPE;
+ break;
+ case LOG_ERROR:
+ case LOG_FATAL:
+ // The price of getting the stack trace is not worth the hassle for
+ // non-error conditions.
+ base::debug::StackTrace trace;
+ message.append(trace.ToString());
+ log_type = EVENTLOG_ERROR_TYPE;
+ break;
+ }
+ LPCSTR strings[1] = {message.data()};
+ if (!ReportEventA(event_log_handle, log_type, g_category, g_event_id, nullptr,
+ 1, 0, strings, nullptr)) {
+ stream() << " !!NOT ADDED TO EVENTLOG!!";
+ }
+#elif defined(OS_LINUX)
+ const char kEventSource[] = "chrome";
+ openlog(kEventSource, LOG_NOWAIT | LOG_PID, LOG_USER);
+ // We can't use the defined names for the logging severity from syslog.h
+ // because they collide with the names of our own severity levels. Therefore
+ // we use the actual values which of course do not match ours.
+ // See sys/syslog.h for reference.
+ int priority = 3;
+ switch (log_message_.severity()) {
+ case LOG_INFO:
+ priority = 6;
+ break;
+ case LOG_WARNING:
+ priority = 4;
+ break;
+ case LOG_ERROR:
+ priority = 3;
+ break;
+ case LOG_FATAL:
+ priority = 2;
+ break;
+ }
+ syslog(priority, "%s", log_message_.str().c_str());
+ closelog();
+#endif // defined(OS_WIN)
+}
+
+} // namespace logging
diff --git a/base/syslog_logging.h b/base/syslog_logging.h
new file mode 100644
index 0000000000..736a5b2efe
--- /dev/null
+++ b/base/syslog_logging.h
@@ -0,0 +1,50 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_SYSLOG_LOGGING_H_
+#define BASE_SYSLOG_LOGGING_H_
+
+#include <iosfwd>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+
+namespace logging {
+
+// Keep in mind that the syslog is always active regardless of the logging level
+// and applied flags. Use only for important information that a system
+// administrator might need to maintain the browser installation.
+#define SYSLOG_STREAM(severity) \
+ COMPACT_GOOGLE_LOG_EX_ ## severity(EventLogMessage).stream()
+#define SYSLOG(severity) \
+ SYSLOG_STREAM(severity)
+
+#if defined(OS_WIN)
+// Sets the name, category and event id of the event source for logging to the
+// Windows Event Log. Call this function once before using the SYSLOG macro or
+// otherwise it will behave as a regular LOG macro.
+void BASE_EXPORT SetEventSource(const std::string& name,
+ uint16_t category,
+ uint32_t event_id);
+#endif // defined(OS_WIN)
+
+// Creates a formatted message on the system event log. That would be the
+// Application Event log on Windows and the messages log file on POSIX systems.
+class BASE_EXPORT EventLogMessage {
+ public:
+ EventLogMessage(const char* file, int line, LogSeverity severity);
+
+ ~EventLogMessage();
+
+ std::ostream& stream() { return log_message_.stream(); }
+
+ private:
+ LogMessage log_message_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventLogMessage);
+};
+
+} // namespace logging
+
+#endif // BASE_SYSLOG_LOGGING_H_
diff --git a/base/system_monitor/system_monitor.cc b/base/system_monitor/system_monitor.cc
new file mode 100644
index 0000000000..71e4f07847
--- /dev/null
+++ b/base/system_monitor/system_monitor.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/system_monitor/system_monitor.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace base {
+
+static SystemMonitor* g_system_monitor = nullptr;
+
+SystemMonitor::SystemMonitor()
+ : devices_changed_observer_list_(
+ new ObserverListThreadSafe<DevicesChangedObserver>()) {
+ DCHECK(!g_system_monitor);
+ g_system_monitor = this;
+}
+
+SystemMonitor::~SystemMonitor() {
+ DCHECK_EQ(this, g_system_monitor);
+ g_system_monitor = nullptr;
+}
+
+// static
+SystemMonitor* SystemMonitor::Get() {
+ return g_system_monitor;
+}
+
+void SystemMonitor::ProcessDevicesChanged(DeviceType device_type) {
+ NotifyDevicesChanged(device_type);
+}
+
+void SystemMonitor::AddDevicesChangedObserver(DevicesChangedObserver* obs) {
+ devices_changed_observer_list_->AddObserver(obs);
+}
+
+void SystemMonitor::RemoveDevicesChangedObserver(DevicesChangedObserver* obs) {
+ devices_changed_observer_list_->RemoveObserver(obs);
+}
+
+void SystemMonitor::NotifyDevicesChanged(DeviceType device_type) {
+ DVLOG(1) << "DevicesChanged with device type " << device_type;
+ devices_changed_observer_list_->Notify(
+ FROM_HERE, &DevicesChangedObserver::OnDevicesChanged, device_type);
+}
+
+} // namespace base
diff --git a/base/system_monitor/system_monitor.h b/base/system_monitor/system_monitor.h
new file mode 100644
index 0000000000..7f21e47bad
--- /dev/null
+++ b/base/system_monitor/system_monitor.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_H_
+#define BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_H_
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list_threadsafe.h"
+#include "build/build_config.h"
+
+namespace base {
+
+// Class for monitoring various system-related subsystems
+// such as power management, network status, etc.
+// TODO(mbelshe): Add support beyond just power management.
+class BASE_EXPORT SystemMonitor {
+ public:
+ // Type of devices whose change need to be monitored, such as add/remove.
+ enum DeviceType {
+ DEVTYPE_AUDIO, // Audio device, e.g., microphone.
+ DEVTYPE_VIDEO_CAPTURE, // Video capture device, e.g., webcam.
+ DEVTYPE_UNKNOWN, // Other devices.
+ };
+
+ // Create SystemMonitor. Only one SystemMonitor instance per application
+ // is allowed.
+ SystemMonitor();
+ ~SystemMonitor();
+
+ // Get the application-wide SystemMonitor (if not present, returns NULL).
+ static SystemMonitor* Get();
+
+ class BASE_EXPORT DevicesChangedObserver {
+ public:
+ // Notification that the devices connected to the system have changed.
+ // This is only implemented on Windows currently.
+ virtual void OnDevicesChanged(DeviceType device_type) {}
+
+ protected:
+ virtual ~DevicesChangedObserver() = default;
+ };
+
+ // Add a new observer.
+ // Can be called from any thread.
+ // Must not be called from within a notification callback.
+ void AddDevicesChangedObserver(DevicesChangedObserver* obs);
+
+ // Remove an existing observer.
+ // Can be called from any thread.
+ // Must not be called from within a notification callback.
+ void RemoveDevicesChangedObserver(DevicesChangedObserver* obs);
+
+ // The ProcessFoo() style methods are a broken pattern and should not
+ // be copied. Any significant addition to this class is blocked on
+ // refactoring to improve the state of affairs. See http://crbug.com/149059
+
+ // Cross-platform handling of a device change event.
+ void ProcessDevicesChanged(DeviceType device_type);
+
+ private:
+ // Functions to trigger notifications.
+ void NotifyDevicesChanged(DeviceType device_type);
+
+ scoped_refptr<ObserverListThreadSafe<DevicesChangedObserver> >
+ devices_changed_observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemMonitor);
+};
+
+} // namespace base
+
+#endif // BASE_SYSTEM_MONITOR_SYSTEM_MONITOR_H_
diff --git a/base/system_monitor/system_monitor_unittest.cc b/base/system_monitor/system_monitor_unittest.cc
new file mode 100644
index 0000000000..8963f7b072
--- /dev/null
+++ b/base/system_monitor/system_monitor_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/system_monitor/system_monitor.h"
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/mock_devices_changed_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class SystemMonitorTest : public testing::Test {
+ protected:
+ SystemMonitorTest() {
+ system_monitor_.reset(new SystemMonitor);
+ }
+
+ MessageLoop message_loop_;
+ std::unique_ptr<SystemMonitor> system_monitor_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemMonitorTest);
+};
+
+TEST_F(SystemMonitorTest, DeviceChangeNotifications) {
+ const int kObservers = 5;
+
+ testing::Sequence mock_sequencer[kObservers];
+ MockDevicesChangedObserver observers[kObservers];
+ for (int index = 0; index < kObservers; ++index) {
+ system_monitor_->AddDevicesChangedObserver(&observers[index]);
+
+ EXPECT_CALL(observers[index],
+ OnDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN))
+ .Times(3)
+ .InSequence(mock_sequencer[index]);
+ }
+
+ system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN);
+ RunLoop().RunUntilIdle();
+
+ system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN);
+ system_monitor_->ProcessDevicesChanged(SystemMonitor::DEVTYPE_UNKNOWN);
+ RunLoop().RunUntilIdle();
+}
+
+} // namespace
+
+} // namespace base
diff --git a/base/task_scheduler/delayed_task_manager_unittest.cc b/base/task_scheduler/delayed_task_manager_unittest.cc
new file mode 100644
index 0000000000..67c797acde
--- /dev/null
+++ b/base/task_scheduler/delayed_task_manager_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/delayed_task_manager.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/task.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+namespace {
+
+constexpr TimeDelta kLongDelay = TimeDelta::FromHours(1);
+
+class MockTask {
+ public:
+ MOCK_METHOD0(Run, void());
+};
+
+void RunTask(Task task) {
+ std::move(task.task).Run();
+}
+
+class TaskSchedulerDelayedTaskManagerTest : public testing::Test {
+ protected:
+ TaskSchedulerDelayedTaskManagerTest()
+ : delayed_task_manager_(
+ service_thread_task_runner_->DeprecatedGetMockTickClock()),
+ task_(FROM_HERE,
+ BindOnce(&MockTask::Run, Unretained(&mock_task_)),
+ TaskTraits(),
+ kLongDelay) {
+ // The constructor of Task computes |delayed_run_time| by adding |delay| to
+ // the real time. Recompute it by adding |delay| to the mock time.
+ task_.delayed_run_time =
+ service_thread_task_runner_->GetMockTickClock()->NowTicks() +
+ kLongDelay;
+ }
+ ~TaskSchedulerDelayedTaskManagerTest() override = default;
+
+ const scoped_refptr<TestMockTimeTaskRunner> service_thread_task_runner_ =
+ MakeRefCounted<TestMockTimeTaskRunner>();
+ DelayedTaskManager delayed_task_manager_;
+ testing::StrictMock<MockTask> mock_task_;
+ Task task_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerDelayedTaskManagerTest);
+};
+
+} // namespace
+
+// Verify that a delayed task isn't forwarded before Start().
+TEST_F(TaskSchedulerDelayedTaskManagerTest, DelayedTaskDoesNotRunBeforeStart) {
+ // Send |task| to the DelayedTaskManager.
+ delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&RunTask));
+
+ // Fast-forward time until the task is ripe for execution. Since Start() has
+ // not been called, the task should not be forwarded to RunTask() (MockTask is
+ // a StrictMock without expectations so test will fail if RunTask() runs it).
+ service_thread_task_runner_->FastForwardBy(kLongDelay);
+}
+
+// Verify that a delayed task added before Start() and whose delay expires after
+// Start() is forwarded when its delay expires.
+TEST_F(TaskSchedulerDelayedTaskManagerTest,
+ DelayedTaskPostedBeforeStartExpiresAfterStartRunsOnExpire) {
+ // Send |task| to the DelayedTaskManager.
+ delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&RunTask));
+
+ delayed_task_manager_.Start(service_thread_task_runner_);
+
+ // Run tasks on the service thread. Don't expect any forwarding to
+ // |task_target_| since the task isn't ripe for execution.
+ service_thread_task_runner_->RunUntilIdle();
+
+ // Fast-forward time until the task is ripe for execution. Expect the task to
+ // be forwarded to RunTask().
+ EXPECT_CALL(mock_task_, Run());
+ service_thread_task_runner_->FastForwardBy(kLongDelay);
+}
+
+// Verify that a delayed task added before Start() and whose delay expires
+// before Start() is forwarded when Start() is called.
+TEST_F(TaskSchedulerDelayedTaskManagerTest,
+ DelayedTaskPostedBeforeStartExpiresBeforeStartRunsOnStart) {
+ // Send |task| to the DelayedTaskManager.
+ delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&RunTask));
+
+ // Run tasks on the service thread. Don't expect any forwarding to
+ // |task_target_| since the task isn't ripe for execution.
+ service_thread_task_runner_->RunUntilIdle();
+
+ // Fast-forward time until the task is ripe for execution. Don't expect the
+ // task to be forwarded since Start() hasn't been called yet.
+ service_thread_task_runner_->FastForwardBy(kLongDelay);
+
+ // Start the DelayedTaskManager. Expect the task to be forwarded to RunTask().
+ EXPECT_CALL(mock_task_, Run());
+ delayed_task_manager_.Start(service_thread_task_runner_);
+ service_thread_task_runner_->RunUntilIdle();
+}
+
+// Verify that a delayed task added after Start() isn't forwarded before it is
+// ripe for execution.
+TEST_F(TaskSchedulerDelayedTaskManagerTest, DelayedTaskDoesNotRunTooEarly) {
+ delayed_task_manager_.Start(service_thread_task_runner_);
+
+ // Send |task| to the DelayedTaskManager.
+ delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&RunTask));
+
+ // Run tasks that are ripe for execution. Don't expect any forwarding to
+ // RunTask().
+ service_thread_task_runner_->RunUntilIdle();
+}
+
+// Verify that a delayed task added after Start() is forwarded when it is ripe
+// for execution.
+TEST_F(TaskSchedulerDelayedTaskManagerTest, DelayedTaskRunsAfterDelay) {
+ delayed_task_manager_.Start(service_thread_task_runner_);
+
+ // Send |task| to the DelayedTaskManager.
+ delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&RunTask));
+
+ // Fast-forward time. Expect the task to be forwarded to RunTask().
+ EXPECT_CALL(mock_task_, Run());
+ service_thread_task_runner_->FastForwardBy(kLongDelay);
+}
+
+// Verify that multiple delayed tasks added after Start() are forwarded when
+// they are ripe for execution.
+TEST_F(TaskSchedulerDelayedTaskManagerTest, DelayedTasksRunAfterDelay) {
+ delayed_task_manager_.Start(service_thread_task_runner_);
+
+ testing::StrictMock<MockTask> mock_task_a;
+ Task task_a(FROM_HERE, BindOnce(&MockTask::Run, Unretained(&mock_task_a)),
+ TaskTraits(), TimeDelta::FromHours(1));
+
+ testing::StrictMock<MockTask> mock_task_b;
+ Task task_b(FROM_HERE, BindOnce(&MockTask::Run, Unretained(&mock_task_b)),
+ TaskTraits(), TimeDelta::FromHours(2));
+
+ testing::StrictMock<MockTask> mock_task_c;
+ Task task_c(FROM_HERE, BindOnce(&MockTask::Run, Unretained(&mock_task_c)),
+ TaskTraits(), TimeDelta::FromHours(1));
+
+ // Send tasks to the DelayedTaskManager.
+ delayed_task_manager_.AddDelayedTask(std::move(task_a), BindOnce(&RunTask));
+ delayed_task_manager_.AddDelayedTask(std::move(task_b), BindOnce(&RunTask));
+ delayed_task_manager_.AddDelayedTask(std::move(task_c), BindOnce(&RunTask));
+
+ // Run tasks that are ripe for execution on the service thread. Don't expect
+ // any call to RunTask().
+ service_thread_task_runner_->RunUntilIdle();
+
+ // Fast-forward time. Expect |task_a| and |task_c| to be forwarded to
+ // |task_target_|.
+ EXPECT_CALL(mock_task_a, Run());
+ EXPECT_CALL(mock_task_c, Run());
+ service_thread_task_runner_->FastForwardBy(TimeDelta::FromHours(1));
+ testing::Mock::VerifyAndClear(&mock_task_a);
+ testing::Mock::VerifyAndClear(&mock_task_c);
+
+ // Fast-forward time. Expect |task_b| to be forwarded to RunTask().
+ EXPECT_CALL(mock_task_b, Run());
+ service_thread_task_runner_->FastForwardBy(TimeDelta::FromHours(1));
+ testing::Mock::VerifyAndClear(&mock_task_b);
+}
+
+TEST_F(TaskSchedulerDelayedTaskManagerTest, PostTaskDuringStart) {
+ Thread other_thread("Test");
+ other_thread.StartAndWaitForTesting();
+
+ WaitableEvent task_posted;
+
+ other_thread.task_runner()->PostTask(FROM_HERE, BindLambdaForTesting([&]() {
+ delayed_task_manager_.AddDelayedTask(
+ std::move(task_),
+ BindOnce(&RunTask));
+ task_posted.Signal();
+ }));
+
+ delayed_task_manager_.Start(service_thread_task_runner_);
+
+ // The test is testing a race between AddDelayedTask/Start but it still needs
+ // synchronization to ensure we don't do the final verification before the
+ // task itself is posted.
+ task_posted.Wait();
+
+ // Fast-forward time. Expect the task to be forwarded to RunTask().
+ EXPECT_CALL(mock_task_, Run());
+ service_thread_task_runner_->FastForwardBy(kLongDelay);
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/initialization_util.cc b/base/task_scheduler/initialization_util.cc
new file mode 100644
index 0000000000..7accd19c6f
--- /dev/null
+++ b/base/task_scheduler/initialization_util.cc
@@ -0,0 +1,22 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/initialization_util.h"
+
+#include <algorithm>
+
+#include "base/sys_info.h"
+
+namespace base {
+
+int RecommendedMaxNumberOfThreadsInPool(int min,
+ int max,
+ double cores_multiplier,
+ int offset) {
+ const int num_of_cores = SysInfo::NumberOfProcessors();
+ const int threads = std::ceil<int>(num_of_cores * cores_multiplier) + offset;
+ return std::min(max, std::max(min, threads));
+}
+
+} // namespace base
diff --git a/base/task_scheduler/initialization_util.h b/base/task_scheduler/initialization_util.h
new file mode 100644
index 0000000000..c3bd9e7c4a
--- /dev/null
+++ b/base/task_scheduler/initialization_util.h
@@ -0,0 +1,21 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SCHEDULER_INITIALIZATION_UTIL_H_
+#define BASE_TASK_SCHEDULER_INITIALIZATION_UTIL_H_
+
+#include "base/base_export.h"
+
+namespace base {
+
+// Computes a value that may be used as the maximum number of threads in a
+// TaskScheduler pool. Developers may use other methods to choose this maximum.
+BASE_EXPORT int RecommendedMaxNumberOfThreadsInPool(int min,
+ int max,
+ double cores_multiplier,
+ int offset);
+
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_INITIALIZATION_UTIL_H_
diff --git a/base/task_scheduler/priority_queue_unittest.cc b/base/task_scheduler/priority_queue_unittest.cc
new file mode 100644
index 0000000000..f131c55fe7
--- /dev/null
+++ b/base/task_scheduler/priority_queue_unittest.cc
@@ -0,0 +1,170 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/priority_queue.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/sequence.h"
+#include "base/task_scheduler/task.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/test/gtest_util.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+class ThreadBeginningTransaction : public SimpleThread {
+ public:
+ explicit ThreadBeginningTransaction(PriorityQueue* priority_queue)
+ : SimpleThread("ThreadBeginningTransaction"),
+ priority_queue_(priority_queue) {}
+
+ // SimpleThread:
+ void Run() override {
+ std::unique_ptr<PriorityQueue::Transaction> transaction =
+ priority_queue_->BeginTransaction();
+ transaction_began_.Signal();
+ }
+
+ void ExpectTransactionDoesNotBegin() {
+ // After a few milliseconds, the call to BeginTransaction() should not have
+ // returned.
+ EXPECT_FALSE(
+ transaction_began_.TimedWait(TimeDelta::FromMilliseconds(250)));
+ }
+
+ private:
+ PriorityQueue* const priority_queue_;
+ WaitableEvent transaction_began_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadBeginningTransaction);
+};
+
+} // namespace
+
+TEST(TaskSchedulerPriorityQueueTest, PushPopPeek) {
+ // Create test sequences.
+ scoped_refptr<Sequence> sequence_a(new Sequence);
+ sequence_a->PushTask(Task(FROM_HERE, DoNothing(),
+ TaskTraits(TaskPriority::USER_VISIBLE),
+ TimeDelta()));
+ SequenceSortKey sort_key_a = sequence_a->GetSortKey();
+
+ scoped_refptr<Sequence> sequence_b(new Sequence);
+ sequence_b->PushTask(Task(FROM_HERE, DoNothing(),
+ TaskTraits(TaskPriority::USER_BLOCKING),
+ TimeDelta()));
+ SequenceSortKey sort_key_b = sequence_b->GetSortKey();
+
+ scoped_refptr<Sequence> sequence_c(new Sequence);
+ sequence_c->PushTask(Task(FROM_HERE, DoNothing(),
+ TaskTraits(TaskPriority::USER_BLOCKING),
+ TimeDelta()));
+ SequenceSortKey sort_key_c = sequence_c->GetSortKey();
+
+ scoped_refptr<Sequence> sequence_d(new Sequence);
+ sequence_d->PushTask(Task(FROM_HERE, DoNothing(),
+ TaskTraits(TaskPriority::BACKGROUND), TimeDelta()));
+ SequenceSortKey sort_key_d = sequence_d->GetSortKey();
+
+ // Create a PriorityQueue and a Transaction.
+ PriorityQueue pq;
+ auto transaction(pq.BeginTransaction());
+ EXPECT_TRUE(transaction->IsEmpty());
+
+ // Push |sequence_a| in the PriorityQueue. It becomes the sequence with the
+ // highest priority.
+ transaction->Push(sequence_a, sort_key_a);
+ EXPECT_EQ(sort_key_a, transaction->PeekSortKey());
+
+ // Push |sequence_b| in the PriorityQueue. It becomes the sequence with the
+ // highest priority.
+ transaction->Push(sequence_b, sort_key_b);
+ EXPECT_EQ(sort_key_b, transaction->PeekSortKey());
+
+ // Push |sequence_c| in the PriorityQueue. |sequence_b| is still the sequence
+ // with the highest priority.
+ transaction->Push(sequence_c, sort_key_c);
+ EXPECT_EQ(sort_key_b, transaction->PeekSortKey());
+
+ // Push |sequence_d| in the PriorityQueue. |sequence_b| is still the sequence
+ // with the highest priority.
+ transaction->Push(sequence_d, sort_key_d);
+ EXPECT_EQ(sort_key_b, transaction->PeekSortKey());
+
+ // Pop |sequence_b| from the PriorityQueue. |sequence_c| becomes the sequence
+ // with the highest priority.
+ EXPECT_EQ(sequence_b, transaction->PopSequence());
+ EXPECT_EQ(sort_key_c, transaction->PeekSortKey());
+
+ // Pop |sequence_c| from the PriorityQueue. |sequence_a| becomes the sequence
+ // with the highest priority.
+ EXPECT_EQ(sequence_c, transaction->PopSequence());
+ EXPECT_EQ(sort_key_a, transaction->PeekSortKey());
+
+ // Pop |sequence_a| from the PriorityQueue. |sequence_d| becomes the sequence
+ // with the highest priority.
+ EXPECT_EQ(sequence_a, transaction->PopSequence());
+ EXPECT_EQ(sort_key_d, transaction->PeekSortKey());
+
+ // Pop |sequence_d| from the PriorityQueue. It is now empty.
+ EXPECT_EQ(sequence_d, transaction->PopSequence());
+ EXPECT_TRUE(transaction->IsEmpty());
+}
+
+// Check that creating Transactions on the same thread for 2 unrelated
+// PriorityQueues causes a crash.
+TEST(TaskSchedulerPriorityQueueTest, IllegalTwoTransactionsSameThread) {
+ PriorityQueue pq_a;
+ PriorityQueue pq_b;
+
+ EXPECT_DCHECK_DEATH(
+ {
+ std::unique_ptr<PriorityQueue::Transaction> transaction_a =
+ pq_a.BeginTransaction();
+ std::unique_ptr<PriorityQueue::Transaction> transaction_b =
+ pq_b.BeginTransaction();
+ });
+}
+
+// Check that it is possible to begin multiple Transactions for the same
+// PriorityQueue on different threads. The call to BeginTransaction() on the
+// second thread should block until the Transaction has ended on the first
+// thread.
+TEST(TaskSchedulerPriorityQueueTest, TwoTransactionsTwoThreads) {
+ PriorityQueue pq;
+
+ // Call BeginTransaction() on this thread and keep the Transaction alive.
+ std::unique_ptr<PriorityQueue::Transaction> transaction =
+ pq.BeginTransaction();
+
+ // Call BeginTransaction() on another thread.
+ ThreadBeginningTransaction thread_beginning_transaction(&pq);
+ thread_beginning_transaction.Start();
+
+ // After a few milliseconds, the call to BeginTransaction() on the other
+ // thread should not have returned.
+ thread_beginning_transaction.ExpectTransactionDoesNotBegin();
+
+ // End the Transaction on the current thread.
+ transaction.reset();
+
+ // The other thread should exit after its call to BeginTransaction() returns.
+ thread_beginning_transaction.Join();
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc b/base/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc
new file mode 100644
index 0000000000..8eb02f3a9d
--- /dev/null
+++ b/base/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc
@@ -0,0 +1,662 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/scheduler_single_thread_task_runner_manager.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/atomic_flag.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/delayed_task_manager.h"
+#include "base/task_scheduler/environment_config.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/test/gtest_util.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "base/win/com_init_util.h"
+#include "base/win/current_module.h"
+#endif // defined(OS_WIN)
+
+namespace base {
+namespace internal {
+
+namespace {
+
+class TaskSchedulerSingleThreadTaskRunnerManagerTest : public testing::Test {
+ public:
+ TaskSchedulerSingleThreadTaskRunnerManagerTest()
+ : service_thread_("TaskSchedulerServiceThread") {}
+
+ void SetUp() override {
+ service_thread_.Start();
+ delayed_task_manager_.Start(service_thread_.task_runner());
+ single_thread_task_runner_manager_ =
+ std::make_unique<SchedulerSingleThreadTaskRunnerManager>(
+ task_tracker_.GetTrackedRef(), &delayed_task_manager_);
+ StartSingleThreadTaskRunnerManagerFromSetUp();
+ }
+
+ void TearDown() override {
+ if (single_thread_task_runner_manager_)
+ TearDownSingleThreadTaskRunnerManager();
+ service_thread_.Stop();
+ }
+
+ protected:
+ virtual void StartSingleThreadTaskRunnerManagerFromSetUp() {
+ single_thread_task_runner_manager_->Start();
+ }
+
+ virtual void TearDownSingleThreadTaskRunnerManager() {
+ single_thread_task_runner_manager_->JoinForTesting();
+ single_thread_task_runner_manager_.reset();
+ }
+
+ Thread service_thread_;
+ TaskTracker task_tracker_ = {"Test"};
+ DelayedTaskManager delayed_task_manager_;
+ std::unique_ptr<SchedulerSingleThreadTaskRunnerManager>
+ single_thread_task_runner_manager_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerSingleThreadTaskRunnerManagerTest);
+};
+
+void CaptureThreadRef(PlatformThreadRef* thread_ref) {
+ ASSERT_TRUE(thread_ref);
+ *thread_ref = PlatformThread::CurrentRef();
+}
+
+void CaptureThreadPriority(ThreadPriority* thread_priority) {
+ ASSERT_TRUE(thread_priority);
+ *thread_priority = PlatformThread::GetCurrentThreadPriority();
+}
+
+void CaptureThreadName(std::string* thread_name) {
+ *thread_name = PlatformThread::GetName();
+}
+
+void ShouldNotRun() {
+ ADD_FAILURE() << "Ran a task that shouldn't run.";
+}
+
+} // namespace
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest, DifferentThreadsUsed) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner_1 =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::DEDICATED);
+ scoped_refptr<SingleThreadTaskRunner> task_runner_2 =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::DEDICATED);
+
+ PlatformThreadRef thread_ref_1;
+ task_runner_1->PostTask(FROM_HERE,
+ BindOnce(&CaptureThreadRef, &thread_ref_1));
+ PlatformThreadRef thread_ref_2;
+ task_runner_2->PostTask(FROM_HERE,
+ BindOnce(&CaptureThreadRef, &thread_ref_2));
+
+ task_tracker_.Shutdown();
+
+ ASSERT_FALSE(thread_ref_1.is_null());
+ ASSERT_FALSE(thread_ref_2.is_null());
+ EXPECT_NE(thread_ref_1, thread_ref_2);
+}
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest, SameThreadUsed) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner_1 =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::SHARED);
+ scoped_refptr<SingleThreadTaskRunner> task_runner_2 =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::SHARED);
+
+ PlatformThreadRef thread_ref_1;
+ task_runner_1->PostTask(FROM_HERE,
+ BindOnce(&CaptureThreadRef, &thread_ref_1));
+ PlatformThreadRef thread_ref_2;
+ task_runner_2->PostTask(FROM_HERE,
+ BindOnce(&CaptureThreadRef, &thread_ref_2));
+
+ task_tracker_.Shutdown();
+
+ ASSERT_FALSE(thread_ref_1.is_null());
+ ASSERT_FALSE(thread_ref_2.is_null());
+ EXPECT_EQ(thread_ref_1, thread_ref_2);
+}
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest,
+ RunsTasksInCurrentSequence) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner_1 =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::DEDICATED);
+ scoped_refptr<SingleThreadTaskRunner> task_runner_2 =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::DEDICATED);
+
+ EXPECT_FALSE(task_runner_1->RunsTasksInCurrentSequence());
+ EXPECT_FALSE(task_runner_2->RunsTasksInCurrentSequence());
+
+ task_runner_1->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](scoped_refptr<SingleThreadTaskRunner> task_runner_1,
+ scoped_refptr<SingleThreadTaskRunner> task_runner_2) {
+ EXPECT_TRUE(task_runner_1->RunsTasksInCurrentSequence());
+ EXPECT_FALSE(task_runner_2->RunsTasksInCurrentSequence());
+ },
+ task_runner_1, task_runner_2));
+
+ task_runner_2->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](scoped_refptr<SingleThreadTaskRunner> task_runner_1,
+ scoped_refptr<SingleThreadTaskRunner> task_runner_2) {
+ EXPECT_FALSE(task_runner_1->RunsTasksInCurrentSequence());
+ EXPECT_TRUE(task_runner_2->RunsTasksInCurrentSequence());
+ },
+ task_runner_1, task_runner_2));
+
+ task_tracker_.Shutdown();
+}
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest,
+ SharedWithBaseSyncPrimitivesDCHECKs) {
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+ EXPECT_DCHECK_DEATH({
+ single_thread_task_runner_manager_->CreateSingleThreadTaskRunnerWithTraits(
+ {WithBaseSyncPrimitives()}, SingleThreadTaskRunnerThreadMode::SHARED);
+ });
+}
+
+// Regression test for https://crbug.com/829786
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest,
+ ContinueOnShutdownDoesNotBlockBlockShutdown) {
+ WaitableEvent task_has_started;
+ WaitableEvent task_can_continue;
+
+ // Post a CONTINUE_ON_SHUTDOWN task that waits on
+ // |task_can_continue| to a shared SingleThreadTaskRunner.
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::SHARED)
+ ->PostTask(FROM_HERE, base::BindOnce(
+ [](WaitableEvent* task_has_started,
+ WaitableEvent* task_can_continue) {
+ task_has_started->Signal();
+ ScopedAllowBaseSyncPrimitivesForTesting
+ allow_base_sync_primitives;
+ task_can_continue->Wait();
+ },
+ Unretained(&task_has_started),
+ Unretained(&task_can_continue)));
+
+ task_has_started.Wait();
+
+ // Post a BLOCK_SHUTDOWN task to a shared SingleThreadTaskRunner.
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::SHARED)
+ ->PostTask(FROM_HERE, DoNothing());
+
+ // Shutdown should not hang even though the first task hasn't finished.
+ task_tracker_.Shutdown();
+
+ // Let the first task finish.
+ task_can_continue.Signal();
+
+ // Tear down from the test body to prevent accesses to |task_can_continue|
+ // after it goes out of scope.
+ TearDownSingleThreadTaskRunnerManager();
+}
+
+namespace {
+
+class TaskSchedulerSingleThreadTaskRunnerManagerCommonTest
+ : public TaskSchedulerSingleThreadTaskRunnerManagerTest,
+ public ::testing::WithParamInterface<SingleThreadTaskRunnerThreadMode> {
+ public:
+ TaskSchedulerSingleThreadTaskRunnerManagerCommonTest() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ TaskSchedulerSingleThreadTaskRunnerManagerCommonTest);
+};
+
+} // namespace
+
+TEST_P(TaskSchedulerSingleThreadTaskRunnerManagerCommonTest,
+ PrioritySetCorrectly) {
+ // Why are events used here instead of the task tracker?
+ // Shutting down can cause priorities to get raised. This means we have to use
+ // events to determine when a task is run.
+ scoped_refptr<SingleThreadTaskRunner> task_runner_background =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits({TaskPriority::BACKGROUND},
+ GetParam());
+ scoped_refptr<SingleThreadTaskRunner> task_runner_normal =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits({TaskPriority::USER_VISIBLE},
+ GetParam());
+
+ ThreadPriority thread_priority_background;
+ task_runner_background->PostTask(
+ FROM_HERE, BindOnce(&CaptureThreadPriority, &thread_priority_background));
+ WaitableEvent waitable_event_background;
+ task_runner_background->PostTask(
+ FROM_HERE,
+ BindOnce(&WaitableEvent::Signal, Unretained(&waitable_event_background)));
+
+ ThreadPriority thread_priority_normal;
+ task_runner_normal->PostTask(
+ FROM_HERE, BindOnce(&CaptureThreadPriority, &thread_priority_normal));
+ WaitableEvent waitable_event_normal;
+ task_runner_normal->PostTask(
+ FROM_HERE,
+ BindOnce(&WaitableEvent::Signal, Unretained(&waitable_event_normal)));
+
+ waitable_event_background.Wait();
+ waitable_event_normal.Wait();
+
+ if (CanUseBackgroundPriorityForSchedulerWorker())
+ EXPECT_EQ(ThreadPriority::BACKGROUND, thread_priority_background);
+ else
+ EXPECT_EQ(ThreadPriority::NORMAL, thread_priority_background);
+ EXPECT_EQ(ThreadPriority::NORMAL, thread_priority_normal);
+}
+
+TEST_P(TaskSchedulerSingleThreadTaskRunnerManagerCommonTest, ThreadNamesSet) {
+ constexpr TaskTraits foo_traits = {TaskPriority::BACKGROUND,
+ TaskShutdownBehavior::BLOCK_SHUTDOWN};
+ scoped_refptr<SingleThreadTaskRunner> foo_task_runner =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(foo_traits, GetParam());
+ std::string foo_captured_name;
+ foo_task_runner->PostTask(FROM_HERE,
+ BindOnce(&CaptureThreadName, &foo_captured_name));
+
+ constexpr TaskTraits user_blocking_traits = {
+ TaskPriority::USER_BLOCKING, MayBlock(),
+ TaskShutdownBehavior::BLOCK_SHUTDOWN};
+ scoped_refptr<SingleThreadTaskRunner> user_blocking_task_runner =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(user_blocking_traits,
+ GetParam());
+
+ std::string user_blocking_captured_name;
+ user_blocking_task_runner->PostTask(
+ FROM_HERE, BindOnce(&CaptureThreadName, &user_blocking_captured_name));
+
+ task_tracker_.Shutdown();
+
+ EXPECT_NE(std::string::npos,
+ foo_captured_name.find(
+ kEnvironmentParams[GetEnvironmentIndexForTraits(foo_traits)]
+ .name_suffix));
+ EXPECT_NE(
+ std::string::npos,
+ user_blocking_captured_name.find(
+ kEnvironmentParams[GetEnvironmentIndexForTraits(user_blocking_traits)]
+ .name_suffix));
+
+ if (GetParam() == SingleThreadTaskRunnerThreadMode::DEDICATED) {
+ EXPECT_EQ(std::string::npos, foo_captured_name.find("Shared"));
+ EXPECT_EQ(std::string::npos, user_blocking_captured_name.find("Shared"));
+ } else {
+ EXPECT_NE(std::string::npos, foo_captured_name.find("Shared"));
+ EXPECT_NE(std::string::npos, user_blocking_captured_name.find("Shared"));
+ }
+}
+
+TEST_P(TaskSchedulerSingleThreadTaskRunnerManagerCommonTest,
+ PostTaskAfterShutdown) {
+ auto task_runner =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(TaskTraits(), GetParam());
+ task_tracker_.Shutdown();
+ EXPECT_FALSE(task_runner->PostTask(FROM_HERE, BindOnce(&ShouldNotRun)));
+}
+
+// Verify that a Task runs shortly after its delay expires.
+TEST_P(TaskSchedulerSingleThreadTaskRunnerManagerCommonTest, PostDelayedTask) {
+ TimeTicks start_time = TimeTicks::Now();
+
+ WaitableEvent task_ran(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ auto task_runner =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(TaskTraits(), GetParam());
+
+ // Wait until the task runner is up and running to make sure the test below is
+ // solely timing the delayed task, not bringing up a physical thread.
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&task_ran)));
+ task_ran.Wait();
+ ASSERT_TRUE(!task_ran.IsSignaled());
+
+ // Post a task with a short delay.
+ EXPECT_TRUE(task_runner->PostDelayedTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&task_ran)),
+ TestTimeouts::tiny_timeout()));
+
+ // Wait until the task runs.
+ task_ran.Wait();
+
+ // Expect the task to run after its delay expires, but no more than 250 ms
+ // after that.
+ const TimeDelta actual_delay = TimeTicks::Now() - start_time;
+ EXPECT_GE(actual_delay, TestTimeouts::tiny_timeout());
+ EXPECT_LT(actual_delay,
+ TimeDelta::FromMilliseconds(250) + TestTimeouts::tiny_timeout());
+}
+
+// Verify that posting tasks after the single-thread manager is destroyed fails
+// but doesn't crash.
+TEST_P(TaskSchedulerSingleThreadTaskRunnerManagerCommonTest,
+ PostTaskAfterDestroy) {
+ auto task_runner =
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(TaskTraits(), GetParam());
+ EXPECT_TRUE(task_runner->PostTask(FROM_HERE, DoNothing()));
+ task_tracker_.Shutdown();
+ TearDownSingleThreadTaskRunnerManager();
+ EXPECT_FALSE(task_runner->PostTask(FROM_HERE, BindOnce(&ShouldNotRun)));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ AllModes,
+ TaskSchedulerSingleThreadTaskRunnerManagerCommonTest,
+ ::testing::Values(SingleThreadTaskRunnerThreadMode::SHARED,
+ SingleThreadTaskRunnerThreadMode::DEDICATED));
+
+namespace {
+
+class CallJoinFromDifferentThread : public SimpleThread {
+ public:
+ CallJoinFromDifferentThread(
+ SchedulerSingleThreadTaskRunnerManager* manager_to_join)
+ : SimpleThread("SchedulerSingleThreadTaskRunnerManagerJoinThread"),
+ manager_to_join_(manager_to_join) {}
+
+ ~CallJoinFromDifferentThread() override = default;
+
+ void Run() override {
+ run_started_event_.Signal();
+ manager_to_join_->JoinForTesting();
+ }
+
+ void WaitForRunToStart() { run_started_event_.Wait(); }
+
+ private:
+ SchedulerSingleThreadTaskRunnerManager* const manager_to_join_;
+ WaitableEvent run_started_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallJoinFromDifferentThread);
+};
+
+class TaskSchedulerSingleThreadTaskRunnerManagerJoinTest
+ : public TaskSchedulerSingleThreadTaskRunnerManagerTest {
+ public:
+ TaskSchedulerSingleThreadTaskRunnerManagerJoinTest() = default;
+ ~TaskSchedulerSingleThreadTaskRunnerManagerJoinTest() override = default;
+
+ protected:
+ void TearDownSingleThreadTaskRunnerManager() override {
+ // The tests themselves are responsible for calling JoinForTesting().
+ single_thread_task_runner_manager_.reset();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerSingleThreadTaskRunnerManagerJoinTest);
+};
+
+} // namespace
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerJoinTest, ConcurrentJoin) {
+ // Exercises the codepath where the workers are unavailable for unregistration
+ // because of a Join call.
+ WaitableEvent task_running;
+ WaitableEvent task_blocking;
+
+ {
+ auto task_runner = single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {WithBaseSyncPrimitives()},
+ SingleThreadTaskRunnerThreadMode::DEDICATED);
+ EXPECT_TRUE(task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(&WaitableEvent::Signal, Unretained(&task_running))));
+ EXPECT_TRUE(task_runner->PostTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Wait, Unretained(&task_blocking))));
+ }
+
+ task_running.Wait();
+ CallJoinFromDifferentThread join_from_different_thread(
+ single_thread_task_runner_manager_.get());
+ join_from_different_thread.Start();
+ join_from_different_thread.WaitForRunToStart();
+ task_blocking.Signal();
+ join_from_different_thread.Join();
+}
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerJoinTest,
+ ConcurrentJoinExtraSkippedTask) {
+ // Tests to make sure that tasks are properly cleaned up at Join, allowing
+ // SingleThreadTaskRunners to unregister themselves.
+ WaitableEvent task_running;
+ WaitableEvent task_blocking;
+
+ {
+ auto task_runner = single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ {WithBaseSyncPrimitives()},
+ SingleThreadTaskRunnerThreadMode::DEDICATED);
+ EXPECT_TRUE(task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(&WaitableEvent::Signal, Unretained(&task_running))));
+ EXPECT_TRUE(task_runner->PostTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Wait, Unretained(&task_blocking))));
+ EXPECT_TRUE(task_runner->PostTask(FROM_HERE, DoNothing()));
+ }
+
+ task_running.Wait();
+ CallJoinFromDifferentThread join_from_different_thread(
+ single_thread_task_runner_manager_.get());
+ join_from_different_thread.Start();
+ join_from_different_thread.WaitForRunToStart();
+ task_blocking.Signal();
+ join_from_different_thread.Join();
+}
+
+#if defined(OS_WIN)
+
+TEST_P(TaskSchedulerSingleThreadTaskRunnerManagerCommonTest,
+ COMSTAInitialized) {
+ scoped_refptr<SingleThreadTaskRunner> com_task_runner =
+ single_thread_task_runner_manager_->CreateCOMSTATaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN}, GetParam());
+
+ com_task_runner->PostTask(FROM_HERE, BindOnce(&win::AssertComApartmentType,
+ win::ComApartmentType::STA));
+
+ task_tracker_.Shutdown();
+}
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest, COMSTASameThreadUsed) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner_1 =
+ single_thread_task_runner_manager_->CreateCOMSTATaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::SHARED);
+ scoped_refptr<SingleThreadTaskRunner> task_runner_2 =
+ single_thread_task_runner_manager_->CreateCOMSTATaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::SHARED);
+
+ PlatformThreadRef thread_ref_1;
+ task_runner_1->PostTask(FROM_HERE,
+ BindOnce(&CaptureThreadRef, &thread_ref_1));
+ PlatformThreadRef thread_ref_2;
+ task_runner_2->PostTask(FROM_HERE,
+ BindOnce(&CaptureThreadRef, &thread_ref_2));
+
+ task_tracker_.Shutdown();
+
+ ASSERT_FALSE(thread_ref_1.is_null());
+ ASSERT_FALSE(thread_ref_2.is_null());
+ EXPECT_EQ(thread_ref_1, thread_ref_2);
+}
+
+namespace {
+
+const wchar_t* const kTestWindowClassName =
+ L"TaskSchedulerSingleThreadTaskRunnerManagerTestWinMessageWindow";
+
+class TaskSchedulerSingleThreadTaskRunnerManagerTestWin
+ : public TaskSchedulerSingleThreadTaskRunnerManagerTest {
+ public:
+ TaskSchedulerSingleThreadTaskRunnerManagerTestWin() = default;
+
+ void SetUp() override {
+ TaskSchedulerSingleThreadTaskRunnerManagerTest::SetUp();
+ register_class_succeeded_ = RegisterTestWindowClass();
+ ASSERT_TRUE(register_class_succeeded_);
+ }
+
+ void TearDown() override {
+ if (register_class_succeeded_)
+ ::UnregisterClass(kTestWindowClassName, CURRENT_MODULE());
+
+ TaskSchedulerSingleThreadTaskRunnerManagerTest::TearDown();
+ }
+
+ HWND CreateTestWindow() {
+ return CreateWindow(kTestWindowClassName, kTestWindowClassName, 0, 0, 0, 0,
+ 0, HWND_MESSAGE, nullptr, CURRENT_MODULE(), nullptr);
+ }
+
+ private:
+ bool RegisterTestWindowClass() {
+ WNDCLASSEX window_class = {};
+ window_class.cbSize = sizeof(window_class);
+ window_class.lpfnWndProc = &::DefWindowProc;
+ window_class.hInstance = CURRENT_MODULE();
+ window_class.lpszClassName = kTestWindowClassName;
+ return !!::RegisterClassEx(&window_class);
+ }
+
+ bool register_class_succeeded_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerSingleThreadTaskRunnerManagerTestWin);
+};
+
+} // namespace
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTestWin, PumpsMessages) {
+ scoped_refptr<SingleThreadTaskRunner> com_task_runner =
+ single_thread_task_runner_manager_->CreateCOMSTATaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ SingleThreadTaskRunnerThreadMode::DEDICATED);
+ HWND hwnd = nullptr;
+ // HWNDs process messages on the thread that created them, so we have to
+ // create them within the context of the task runner to properly simulate a
+ // COM callback.
+ com_task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](TaskSchedulerSingleThreadTaskRunnerManagerTestWin* test_harness,
+ HWND* hwnd) { *hwnd = test_harness->CreateTestWindow(); },
+ Unretained(this), &hwnd));
+
+ task_tracker_.FlushForTesting();
+
+ ASSERT_NE(hwnd, nullptr);
+ // If the message pump isn't running, we will hang here. This simulates how
+ // COM would receive a callback with its own message HWND.
+ SendMessage(hwnd, WM_USER, 0, 0);
+
+ com_task_runner->PostTask(
+ FROM_HERE, BindOnce([](HWND hwnd) { ::DestroyWindow(hwnd); }, hwnd));
+
+ task_tracker_.Shutdown();
+}
+
+#endif // defined(OS_WIN)
+
+namespace {
+
+class TaskSchedulerSingleThreadTaskRunnerManagerStartTest
+ : public TaskSchedulerSingleThreadTaskRunnerManagerTest {
+ public:
+ TaskSchedulerSingleThreadTaskRunnerManagerStartTest() = default;
+
+ private:
+ void StartSingleThreadTaskRunnerManagerFromSetUp() override {
+ // Start() is called in the test body rather than in SetUp().
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerSingleThreadTaskRunnerManagerStartTest);
+};
+
+} // namespace
+
+// Verify that a task posted before Start() doesn't run until Start() is called.
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerStartTest,
+ PostTaskBeforeStart) {
+ AtomicFlag manager_started;
+ WaitableEvent task_finished;
+ single_thread_task_runner_manager_
+ ->CreateSingleThreadTaskRunnerWithTraits(
+ TaskTraits(), SingleThreadTaskRunnerThreadMode::DEDICATED)
+ ->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](WaitableEvent* task_finished, AtomicFlag* manager_started) {
+ // The task should not run before Start().
+ EXPECT_TRUE(manager_started->IsSet());
+ task_finished->Signal();
+ },
+ Unretained(&task_finished), Unretained(&manager_started)));
+
+ // Wait a little bit to make sure that the task doesn't run before start.
+ // Note: This test won't catch a case where the task runs between setting
+ // |manager_started| and calling Start(). However, we expect the test to be
+ // flaky if the tested code allows that to happen.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ manager_started.Set();
+ single_thread_task_runner_manager_->Start();
+
+ // Wait for the task to complete to keep |manager_started| alive.
+ task_finished.Wait();
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/scheduler_worker_pool_impl_unittest.cc b/base/task_scheduler/scheduler_worker_pool_impl_unittest.cc
new file mode 100644
index 0000000000..f510194770
--- /dev/null
+++ b/base/task_scheduler/scheduler_worker_pool_impl_unittest.cc
@@ -0,0 +1,1707 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/scheduler_worker_pool_impl.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <unordered_set>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/barrier_closure.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/synchronization/atomic_flag.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_runner.h"
+#include "base/task_scheduler/delayed_task_manager.h"
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/task_scheduler/sequence.h"
+#include "base/task_scheduler/sequence_sort_key.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/task_scheduler/test_task_factory.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/gtest_util.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker_impl.h"
+#include "base/threading/thread_local_storage.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/win/com_init_util.h"
+#endif // defined(OS_WIN)
+
+namespace base {
+namespace internal {
+namespace {
+
+constexpr size_t kMaxTasks = 4;
+constexpr size_t kNumThreadsPostingTasks = 4;
+constexpr size_t kNumTasksPostedPerThread = 150;
+// This can't be lower because Windows' WaitableEvent wakes up too early when a
+// small timeout is used. This results in many spurious wake ups before a worker
+// is allowed to cleanup.
+constexpr TimeDelta kReclaimTimeForCleanupTests =
+ TimeDelta::FromMilliseconds(500);
+
+// Waits on |event| in a scope where the blocking observer is null, to avoid
+// affecting the max tasks.
+void WaitWithoutBlockingObserver(WaitableEvent* event) {
+ internal::ScopedClearBlockingObserverForTesting clear_blocking_observer;
+ ScopedAllowBaseSyncPrimitivesForTesting allow_base_sync_primitives;
+ event->Wait();
+}
+
+class TaskSchedulerWorkerPoolImplTestBase {
+ protected:
+ TaskSchedulerWorkerPoolImplTestBase()
+ : service_thread_("TaskSchedulerServiceThread"){};
+
+ void CommonSetUp(TimeDelta suggested_reclaim_time = TimeDelta::Max()) {
+ CreateAndStartWorkerPool(suggested_reclaim_time, kMaxTasks);
+ }
+
+ void CommonTearDown() {
+ service_thread_.Stop();
+ task_tracker_.FlushForTesting();
+ if (worker_pool_)
+ worker_pool_->JoinForTesting();
+ }
+
+ void CreateWorkerPool() {
+ ASSERT_FALSE(worker_pool_);
+ service_thread_.Start();
+ delayed_task_manager_.Start(service_thread_.task_runner());
+ worker_pool_ = std::make_unique<SchedulerWorkerPoolImpl>(
+ "TestWorkerPool", "A", ThreadPriority::NORMAL,
+ task_tracker_.GetTrackedRef(), &delayed_task_manager_);
+ ASSERT_TRUE(worker_pool_);
+ }
+
+ virtual void StartWorkerPool(TimeDelta suggested_reclaim_time,
+ size_t max_tasks) {
+ ASSERT_TRUE(worker_pool_);
+ worker_pool_->Start(
+ SchedulerWorkerPoolParams(max_tasks, suggested_reclaim_time), max_tasks,
+ service_thread_.task_runner(), nullptr,
+ SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+ }
+
+ void CreateAndStartWorkerPool(TimeDelta suggested_reclaim_time,
+ size_t max_tasks) {
+ CreateWorkerPool();
+ StartWorkerPool(suggested_reclaim_time, max_tasks);
+ }
+
+ Thread service_thread_;
+ TaskTracker task_tracker_ = {"Test"};
+
+ std::unique_ptr<SchedulerWorkerPoolImpl> worker_pool_;
+
+ private:
+ DelayedTaskManager delayed_task_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolImplTestBase);
+};
+
+class TaskSchedulerWorkerPoolImplTest
+ : public TaskSchedulerWorkerPoolImplTestBase,
+ public testing::Test {
+ protected:
+ TaskSchedulerWorkerPoolImplTest() = default;
+
+ void SetUp() override { TaskSchedulerWorkerPoolImplTestBase::CommonSetUp(); }
+
+ void TearDown() override {
+ TaskSchedulerWorkerPoolImplTestBase::CommonTearDown();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolImplTest);
+};
+
+class TaskSchedulerWorkerPoolImplTestParam
+ : public TaskSchedulerWorkerPoolImplTestBase,
+ public testing::TestWithParam<test::ExecutionMode> {
+ protected:
+ TaskSchedulerWorkerPoolImplTestParam() = default;
+
+ void SetUp() override { TaskSchedulerWorkerPoolImplTestBase::CommonSetUp(); }
+
+ void TearDown() override {
+ TaskSchedulerWorkerPoolImplTestBase::CommonTearDown();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolImplTestParam);
+};
+
+using PostNestedTask = test::TestTaskFactory::PostNestedTask;
+
+class ThreadPostingTasksWaitIdle : public SimpleThread {
+ public:
+ // Constructs a thread that posts tasks to |worker_pool| through an
+ // |execution_mode| task runner. The thread waits until all workers in
+ // |worker_pool| are idle before posting a new task.
+ ThreadPostingTasksWaitIdle(SchedulerWorkerPoolImpl* worker_pool,
+ test::ExecutionMode execution_mode)
+ : SimpleThread("ThreadPostingTasksWaitIdle"),
+ worker_pool_(worker_pool),
+ factory_(CreateTaskRunnerWithExecutionMode(worker_pool, execution_mode),
+ execution_mode) {
+ DCHECK(worker_pool_);
+ }
+
+ const test::TestTaskFactory* factory() const { return &factory_; }
+
+ private:
+ void Run() override {
+ EXPECT_FALSE(factory_.task_runner()->RunsTasksInCurrentSequence());
+
+ for (size_t i = 0; i < kNumTasksPostedPerThread; ++i) {
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+ EXPECT_TRUE(factory_.PostTask(PostNestedTask::NO, Closure()));
+ }
+ }
+
+ SchedulerWorkerPoolImpl* const worker_pool_;
+ const scoped_refptr<TaskRunner> task_runner_;
+ test::TestTaskFactory factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadPostingTasksWaitIdle);
+};
+
+} // namespace
+
+TEST_P(TaskSchedulerWorkerPoolImplTestParam, PostTasksWaitAllWorkersIdle) {
+ // Create threads to post tasks. To verify that workers can sleep and be woken
+ // up when new tasks are posted, wait for all workers to become idle before
+ // posting a new task.
+ std::vector<std::unique_ptr<ThreadPostingTasksWaitIdle>>
+ threads_posting_tasks;
+ for (size_t i = 0; i < kNumThreadsPostingTasks; ++i) {
+ threads_posting_tasks.push_back(
+ std::make_unique<ThreadPostingTasksWaitIdle>(worker_pool_.get(),
+ GetParam()));
+ threads_posting_tasks.back()->Start();
+ }
+
+ // Wait for all tasks to run.
+ for (const auto& thread_posting_tasks : threads_posting_tasks) {
+ thread_posting_tasks->Join();
+ thread_posting_tasks->factory()->WaitForAllTasksToRun();
+ }
+
+ // Wait until all workers are idle to be sure that no task accesses its
+ // TestTaskFactory after |thread_posting_tasks| is destroyed.
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+}
+
+TEST_P(TaskSchedulerWorkerPoolImplTestParam, PostTasksWithOneAvailableWorker) {
+ // Post blocking tasks to keep all workers busy except one until |event| is
+ // signaled. Use different factories so that tasks are added to different
+ // sequences and can run simultaneously when the execution mode is SEQUENCED.
+ WaitableEvent event;
+ std::vector<std::unique_ptr<test::TestTaskFactory>> blocked_task_factories;
+ for (size_t i = 0; i < (kMaxTasks - 1); ++i) {
+ blocked_task_factories.push_back(std::make_unique<test::TestTaskFactory>(
+ CreateTaskRunnerWithExecutionMode(worker_pool_.get(), GetParam()),
+ GetParam()));
+ EXPECT_TRUE(blocked_task_factories.back()->PostTask(
+ PostNestedTask::NO,
+ BindOnce(&WaitWithoutBlockingObserver, Unretained(&event))));
+ blocked_task_factories.back()->WaitForAllTasksToRun();
+ }
+
+ // Post |kNumTasksPostedPerThread| tasks that should all run despite the fact
+ // that only one worker in |worker_pool_| isn't busy.
+ test::TestTaskFactory short_task_factory(
+ CreateTaskRunnerWithExecutionMode(worker_pool_.get(), GetParam()),
+ GetParam());
+ for (size_t i = 0; i < kNumTasksPostedPerThread; ++i)
+ EXPECT_TRUE(short_task_factory.PostTask(PostNestedTask::NO, Closure()));
+ short_task_factory.WaitForAllTasksToRun();
+
+ // Release tasks waiting on |event|.
+ event.Signal();
+
+ // Wait until all workers are idle to be sure that no task accesses
+ // its TestTaskFactory after it is destroyed.
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+}
+
+TEST_P(TaskSchedulerWorkerPoolImplTestParam, Saturate) {
+ // Verify that it is possible to have |kMaxTasks| tasks/sequences running
+ // simultaneously. Use different factories so that the blocking tasks are
+ // added to different sequences and can run simultaneously when the execution
+ // mode is SEQUENCED.
+ WaitableEvent event;
+ std::vector<std::unique_ptr<test::TestTaskFactory>> factories;
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ factories.push_back(std::make_unique<test::TestTaskFactory>(
+ CreateTaskRunnerWithExecutionMode(worker_pool_.get(), GetParam()),
+ GetParam()));
+ EXPECT_TRUE(factories.back()->PostTask(
+ PostNestedTask::NO,
+ BindOnce(&WaitWithoutBlockingObserver, Unretained(&event))));
+ factories.back()->WaitForAllTasksToRun();
+ }
+
+ // Release tasks waiting on |event|.
+ event.Signal();
+
+ // Wait until all workers are idle to be sure that no task accesses
+ // its TestTaskFactory after it is destroyed.
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+}
+
+#if defined(OS_WIN)
+TEST_P(TaskSchedulerWorkerPoolImplTestParam, NoEnvironment) {
+ // Verify that COM is not initialized in a SchedulerWorkerPoolImpl initialized
+ // with SchedulerWorkerPoolImpl::WorkerEnvironment::NONE.
+ scoped_refptr<TaskRunner> task_runner =
+ CreateTaskRunnerWithExecutionMode(worker_pool_.get(), GetParam());
+
+ WaitableEvent task_running;
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(
+ [](WaitableEvent* task_running) {
+ win::AssertComApartmentType(win::ComApartmentType::NONE);
+ task_running->Signal();
+ },
+ &task_running));
+
+ task_running.Wait();
+
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+}
+#endif // defined(OS_WIN)
+
+INSTANTIATE_TEST_CASE_P(Parallel,
+ TaskSchedulerWorkerPoolImplTestParam,
+ ::testing::Values(test::ExecutionMode::PARALLEL));
+INSTANTIATE_TEST_CASE_P(Sequenced,
+ TaskSchedulerWorkerPoolImplTestParam,
+ ::testing::Values(test::ExecutionMode::SEQUENCED));
+
+#if defined(OS_WIN)
+
+namespace {
+
+class TaskSchedulerWorkerPoolImplTestCOMMTAParam
+ : public TaskSchedulerWorkerPoolImplTestBase,
+ public testing::TestWithParam<test::ExecutionMode> {
+ protected:
+ TaskSchedulerWorkerPoolImplTestCOMMTAParam() = default;
+
+ void SetUp() override { TaskSchedulerWorkerPoolImplTestBase::CommonSetUp(); }
+
+ void TearDown() override {
+ TaskSchedulerWorkerPoolImplTestBase::CommonTearDown();
+ }
+
+ private:
+ void StartWorkerPool(TimeDelta suggested_reclaim_time,
+ size_t max_tasks) override {
+ ASSERT_TRUE(worker_pool_);
+ worker_pool_->Start(
+ SchedulerWorkerPoolParams(max_tasks, suggested_reclaim_time), max_tasks,
+ service_thread_.task_runner(), nullptr,
+ SchedulerWorkerPoolImpl::WorkerEnvironment::COM_MTA);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolImplTestCOMMTAParam);
+};
+
+} // namespace
+
+TEST_P(TaskSchedulerWorkerPoolImplTestCOMMTAParam, COMMTAInitialized) {
+ // Verify that SchedulerWorkerPoolImpl workers have a COM MTA available.
+ scoped_refptr<TaskRunner> task_runner =
+ CreateTaskRunnerWithExecutionMode(worker_pool_.get(), GetParam());
+
+ WaitableEvent task_running;
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(
+ [](WaitableEvent* task_running) {
+ win::AssertComApartmentType(win::ComApartmentType::MTA);
+ task_running->Signal();
+ },
+ &task_running));
+
+ task_running.Wait();
+
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+}
+
+INSTANTIATE_TEST_CASE_P(Parallel,
+ TaskSchedulerWorkerPoolImplTestCOMMTAParam,
+ ::testing::Values(test::ExecutionMode::PARALLEL));
+INSTANTIATE_TEST_CASE_P(Sequenced,
+ TaskSchedulerWorkerPoolImplTestCOMMTAParam,
+ ::testing::Values(test::ExecutionMode::SEQUENCED));
+
+#endif // defined(OS_WIN)
+
+namespace {
+
+class TaskSchedulerWorkerPoolImplStartInBodyTest
+ : public TaskSchedulerWorkerPoolImplTest {
+ public:
+ void SetUp() override {
+ CreateWorkerPool();
+ // Let the test start the worker pool.
+ }
+};
+
+void TaskPostedBeforeStart(PlatformThreadRef* platform_thread_ref,
+ WaitableEvent* task_running,
+ WaitableEvent* barrier) {
+ *platform_thread_ref = PlatformThread::CurrentRef();
+ task_running->Signal();
+ WaitWithoutBlockingObserver(barrier);
+}
+
+} // namespace
+
+// Verify that 2 tasks posted before Start() to a SchedulerWorkerPoolImpl with
+// more than 2 workers run on different workers when Start() is called.
+TEST_F(TaskSchedulerWorkerPoolImplStartInBodyTest, PostTasksBeforeStart) {
+ PlatformThreadRef task_1_thread_ref;
+ PlatformThreadRef task_2_thread_ref;
+ WaitableEvent task_1_running;
+ WaitableEvent task_2_running;
+
+ // This event is used to prevent a task from completing before the other task
+ // starts running. If that happened, both tasks could run on the same worker
+ // and this test couldn't verify that the correct number of workers were woken
+ // up.
+ WaitableEvent barrier;
+
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()})
+ ->PostTask(
+ FROM_HERE,
+ BindOnce(&TaskPostedBeforeStart, Unretained(&task_1_thread_ref),
+ Unretained(&task_1_running), Unretained(&barrier)));
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()})
+ ->PostTask(
+ FROM_HERE,
+ BindOnce(&TaskPostedBeforeStart, Unretained(&task_2_thread_ref),
+ Unretained(&task_2_running), Unretained(&barrier)));
+
+ // Workers should not be created and tasks should not run before the pool is
+ // started.
+ EXPECT_EQ(0U, worker_pool_->NumberOfWorkersForTesting());
+ EXPECT_FALSE(task_1_running.IsSignaled());
+ EXPECT_FALSE(task_2_running.IsSignaled());
+
+ StartWorkerPool(TimeDelta::Max(), kMaxTasks);
+
+ // Tasks should run shortly after the pool is started.
+ task_1_running.Wait();
+ task_2_running.Wait();
+
+ // Tasks should run on different threads.
+ EXPECT_NE(task_1_thread_ref, task_2_thread_ref);
+
+ barrier.Signal();
+ task_tracker_.FlushForTesting();
+}
+
+// Verify that posting many tasks before Start will cause the number of workers
+// to grow to |max_tasks_| during Start.
+TEST_F(TaskSchedulerWorkerPoolImplStartInBodyTest, PostManyTasks) {
+ scoped_refptr<TaskRunner> task_runner =
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+ constexpr size_t kNumTasksPosted = 2 * kMaxTasks;
+ for (size_t i = 0; i < kNumTasksPosted; ++i)
+ task_runner->PostTask(FROM_HERE, DoNothing());
+
+ EXPECT_EQ(0U, worker_pool_->NumberOfWorkersForTesting());
+
+ StartWorkerPool(TimeDelta::Max(), kMaxTasks);
+ ASSERT_GT(kNumTasksPosted, worker_pool_->GetMaxTasksForTesting());
+ EXPECT_EQ(kMaxTasks, worker_pool_->GetMaxTasksForTesting());
+
+ EXPECT_EQ(worker_pool_->NumberOfWorkersForTesting(),
+ worker_pool_->GetMaxTasksForTesting());
+}
+
+namespace {
+
+constexpr size_t kMagicTlsValue = 42;
+
+class TaskSchedulerWorkerPoolCheckTlsReuse
+ : public TaskSchedulerWorkerPoolImplTest {
+ public:
+ void SetTlsValueAndWait() {
+ slot_.Set(reinterpret_cast<void*>(kMagicTlsValue));
+ WaitWithoutBlockingObserver(&waiter_);
+ }
+
+ void CountZeroTlsValuesAndWait(WaitableEvent* count_waiter) {
+ if (!slot_.Get())
+ subtle::NoBarrier_AtomicIncrement(&zero_tls_values_, 1);
+
+ count_waiter->Signal();
+ WaitWithoutBlockingObserver(&waiter_);
+ }
+
+ protected:
+ TaskSchedulerWorkerPoolCheckTlsReuse() = default;
+
+ void SetUp() override {
+ CreateAndStartWorkerPool(kReclaimTimeForCleanupTests, kMaxTasks);
+ }
+
+ subtle::Atomic32 zero_tls_values_ = 0;
+
+ WaitableEvent waiter_;
+
+ private:
+ ThreadLocalStorage::Slot slot_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolCheckTlsReuse);
+};
+
+} // namespace
+
+// Checks that at least one worker has been cleaned up by checking the TLS.
+TEST_F(TaskSchedulerWorkerPoolCheckTlsReuse, CheckCleanupWorkers) {
+ // Saturate the workers and mark each worker's thread with a magic TLS value.
+ std::vector<std::unique_ptr<test::TestTaskFactory>> factories;
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ factories.push_back(std::make_unique<test::TestTaskFactory>(
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()}),
+ test::ExecutionMode::PARALLEL));
+ ASSERT_TRUE(factories.back()->PostTask(
+ PostNestedTask::NO,
+ Bind(&TaskSchedulerWorkerPoolCheckTlsReuse::SetTlsValueAndWait,
+ Unretained(this))));
+ factories.back()->WaitForAllTasksToRun();
+ }
+
+ // Release tasks waiting on |waiter_|.
+ waiter_.Signal();
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+
+ // All workers should be done running by now, so reset for the next phase.
+ waiter_.Reset();
+
+ // Wait for the worker pool to clean up at least one worker.
+ worker_pool_->WaitForWorkersCleanedUpForTesting(1U);
+
+ // Saturate and count the worker threads that do not have the magic TLS value.
+ // If the value is not there, that means we're at a new worker.
+ std::vector<std::unique_ptr<WaitableEvent>> count_waiters;
+ for (auto& factory : factories) {
+ count_waiters.push_back(std::make_unique<WaitableEvent>());
+ ASSERT_TRUE(factory->PostTask(
+ PostNestedTask::NO,
+ Bind(&TaskSchedulerWorkerPoolCheckTlsReuse::CountZeroTlsValuesAndWait,
+ Unretained(this),
+ count_waiters.back().get())));
+ factory->WaitForAllTasksToRun();
+ }
+
+ // Wait for all counters to complete.
+ for (auto& count_waiter : count_waiters)
+ count_waiter->Wait();
+
+ EXPECT_GT(subtle::NoBarrier_Load(&zero_tls_values_), 0);
+
+ // Release tasks waiting on |waiter_|.
+ waiter_.Signal();
+}
+
+namespace {
+
+class TaskSchedulerWorkerPoolHistogramTest
+ : public TaskSchedulerWorkerPoolImplTest {
+ public:
+ TaskSchedulerWorkerPoolHistogramTest() = default;
+
+ protected:
+ // Override SetUp() to allow every test case to initialize a worker pool with
+ // its own arguments.
+ void SetUp() override {}
+
+ // Floods |worker_pool_| with a single task each that blocks until
+ // |continue_event| is signaled. Every worker in the pool is blocked on
+ // |continue_event| when this method returns. Note: this helper can easily be
+ // generalized to be useful in other tests, but it's here for now because it's
+ // only used in a TaskSchedulerWorkerPoolHistogramTest at the moment.
+ void FloodPool(WaitableEvent* continue_event) {
+ ASSERT_FALSE(continue_event->IsSignaled());
+
+ auto task_runner =
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+
+ const auto max_tasks = worker_pool_->GetMaxTasksForTesting();
+
+ WaitableEvent workers_flooded;
+ RepeatingClosure all_workers_running_barrier = BarrierClosure(
+ max_tasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&workers_flooded)));
+ for (size_t i = 0; i < max_tasks; ++i) {
+ task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](OnceClosure on_running, WaitableEvent* continue_event) {
+ std::move(on_running).Run();
+ WaitWithoutBlockingObserver(continue_event);
+ },
+ all_workers_running_barrier, continue_event));
+ }
+ workers_flooded.Wait();
+ }
+
+ private:
+ std::unique_ptr<StatisticsRecorder> statistics_recorder_ =
+ StatisticsRecorder::CreateTemporaryForTesting();
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolHistogramTest);
+};
+
+} // namespace
+
+TEST_F(TaskSchedulerWorkerPoolHistogramTest, NumTasksBetweenWaits) {
+ WaitableEvent event;
+ CreateAndStartWorkerPool(TimeDelta::Max(), kMaxTasks);
+ auto task_runner = worker_pool_->CreateSequencedTaskRunnerWithTraits(
+ {WithBaseSyncPrimitives()});
+
+ // Post a task.
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&WaitWithoutBlockingObserver, Unretained(&event)));
+
+ // Post 2 more tasks while the first task hasn't completed its execution. It
+ // is guaranteed that these tasks will run immediately after the first task,
+ // without allowing the worker to sleep.
+ task_runner->PostTask(FROM_HERE, DoNothing());
+ task_runner->PostTask(FROM_HERE, DoNothing());
+
+ // Allow tasks to run and wait until the SchedulerWorker is idle.
+ event.Signal();
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+
+ // Wake up the SchedulerWorker that just became idle by posting a task and
+ // wait until it becomes idle again. The SchedulerWorker should record the
+ // TaskScheduler.NumTasksBetweenWaits.* histogram on wake up.
+ task_runner->PostTask(FROM_HERE, DoNothing());
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+
+ // Verify that counts were recorded to the histogram as expected.
+ const auto* histogram = worker_pool_->num_tasks_between_waits_histogram();
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(0));
+ EXPECT_EQ(1, histogram->SnapshotSamples()->GetCount(3));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(10));
+}
+
+// Verifies that NumTasksBetweenWaits histogram is logged as expected across
+// idle and cleanup periods.
+TEST_F(TaskSchedulerWorkerPoolHistogramTest,
+ NumTasksBetweenWaitsWithIdlePeriodAndCleanup) {
+ WaitableEvent tasks_can_exit_event;
+ CreateAndStartWorkerPool(kReclaimTimeForCleanupTests, kMaxTasks);
+
+ WaitableEvent workers_continue;
+
+ FloodPool(&workers_continue);
+
+ const auto* histogram = worker_pool_->num_tasks_between_waits_histogram();
+
+ // NumTasksBetweenWaits shouldn't be logged until idle.
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(0));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(1));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(10));
+
+ // Make all workers go idle.
+ workers_continue.Signal();
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+
+ // All workers should have reported a single hit in the "1" bucket per the the
+ // histogram being reported when going idle and each worker having processed
+ // precisely 1 task per the controlled flooding logic above.
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(0));
+ EXPECT_EQ(static_cast<int>(kMaxTasks),
+ histogram->SnapshotSamples()->GetCount(1));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(10));
+
+ worker_pool_->WaitForWorkersCleanedUpForTesting(kMaxTasks - 1);
+
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(0));
+ EXPECT_EQ(static_cast<int>(kMaxTasks),
+ histogram->SnapshotSamples()->GetCount(1));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(10));
+
+ // Flooding the pool once again (without letting any workers go idle)
+ // shouldn't affect the counts either.
+
+ workers_continue.Reset();
+ FloodPool(&workers_continue);
+
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(0));
+ EXPECT_EQ(static_cast<int>(kMaxTasks),
+ histogram->SnapshotSamples()->GetCount(1));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(10));
+
+ workers_continue.Signal();
+ worker_pool_->WaitForAllWorkersIdleForTesting();
+}
+
+TEST_F(TaskSchedulerWorkerPoolHistogramTest, NumTasksBeforeCleanup) {
+ CreateWorkerPool();
+ auto histogrammed_thread_task_runner =
+ worker_pool_->CreateSequencedTaskRunnerWithTraits(
+ {WithBaseSyncPrimitives()});
+
+ // Post 3 tasks and hold the thread for idle thread stack ordering.
+ // This test assumes |histogrammed_thread_task_runner| gets assigned the same
+ // thread for each of its tasks.
+ PlatformThreadRef thread_ref;
+ histogrammed_thread_task_runner->PostTask(
+ FROM_HERE, BindOnce(
+ [](PlatformThreadRef* thread_ref) {
+ ASSERT_TRUE(thread_ref);
+ *thread_ref = PlatformThread::CurrentRef();
+ },
+ Unretained(&thread_ref)));
+ histogrammed_thread_task_runner->PostTask(
+ FROM_HERE, BindOnce(
+ [](PlatformThreadRef* thread_ref) {
+ ASSERT_FALSE(thread_ref->is_null());
+ EXPECT_EQ(*thread_ref, PlatformThread::CurrentRef());
+ },
+ Unretained(&thread_ref)));
+
+ WaitableEvent cleanup_thread_running;
+ WaitableEvent cleanup_thread_continue;
+ histogrammed_thread_task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](PlatformThreadRef* thread_ref,
+ WaitableEvent* cleanup_thread_running,
+ WaitableEvent* cleanup_thread_continue) {
+ ASSERT_FALSE(thread_ref->is_null());
+ EXPECT_EQ(*thread_ref, PlatformThread::CurrentRef());
+ cleanup_thread_running->Signal();
+ WaitWithoutBlockingObserver(cleanup_thread_continue);
+ },
+ Unretained(&thread_ref), Unretained(&cleanup_thread_running),
+ Unretained(&cleanup_thread_continue)));
+
+ // Start the worker pool with 2 workers, to avoid depending on the scheduler's
+ // logic to always keep one extra idle worker.
+ //
+ // The pool is started after the 3 initial tasks have been posted to ensure
+ // that they are scheduled on the same worker. If the tasks could run as they
+ // are posted, there would be a chance that:
+ // 1. Worker #1: Runs a tasks and empties the sequence, without adding
+ // itself to the idle stack yet.
+ // 2. Posting thread: Posts another task to the now empty sequence. Wakes
+ // up a new worker, since worker #1 isn't on the idle
+ // stack yet.
+ // 3: Worker #2: Runs the tasks, violating the expectation that the 3
+ // initial tasks run on the same worker.
+ constexpr size_t kTwoWorkers = 2;
+ StartWorkerPool(kReclaimTimeForCleanupTests, kTwoWorkers);
+
+ // Wait until the 3rd task is scheduled.
+ cleanup_thread_running.Wait();
+
+ // To allow the SchedulerWorker associated with
+ // |histogrammed_thread_task_runner| to cleanup, make sure it isn't on top of
+ // the idle stack by waking up another SchedulerWorker via
+ // |task_runner_for_top_idle|. |histogrammed_thread_task_runner| should
+ // release and go idle first and then |task_runner_for_top_idle| should
+ // release and go idle. This allows the SchedulerWorker associated with
+ // |histogrammed_thread_task_runner| to cleanup.
+ WaitableEvent top_idle_thread_running;
+ WaitableEvent top_idle_thread_continue;
+ auto task_runner_for_top_idle =
+ worker_pool_->CreateSequencedTaskRunnerWithTraits(
+ {WithBaseSyncPrimitives()});
+ task_runner_for_top_idle->PostTask(
+ FROM_HERE, BindOnce(
+ [](PlatformThreadRef thread_ref,
+ WaitableEvent* top_idle_thread_running,
+ WaitableEvent* top_idle_thread_continue) {
+ ASSERT_FALSE(thread_ref.is_null());
+ EXPECT_NE(thread_ref, PlatformThread::CurrentRef())
+ << "Worker reused. Worker will not cleanup and the "
+ "histogram value will be wrong.";
+ top_idle_thread_running->Signal();
+ WaitWithoutBlockingObserver(top_idle_thread_continue);
+ },
+ thread_ref, Unretained(&top_idle_thread_running),
+ Unretained(&top_idle_thread_continue)));
+ top_idle_thread_running.Wait();
+ EXPECT_EQ(0U, worker_pool_->NumberOfIdleWorkersForTesting());
+ cleanup_thread_continue.Signal();
+ // Wait for the cleanup thread to also become idle.
+ worker_pool_->WaitForWorkersIdleForTesting(1U);
+ top_idle_thread_continue.Signal();
+ // Allow the thread processing the |histogrammed_thread_task_runner| work to
+ // cleanup.
+ worker_pool_->WaitForWorkersCleanedUpForTesting(1U);
+
+ // Verify that counts were recorded to the histogram as expected.
+ const auto* histogram = worker_pool_->num_tasks_before_detach_histogram();
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(0));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(1));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(2));
+ EXPECT_EQ(1, histogram->SnapshotSamples()->GetCount(3));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(4));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(5));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(6));
+ EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(10));
+}
+
+namespace {
+
+class TaskSchedulerWorkerPoolStandbyPolicyTest
+ : public TaskSchedulerWorkerPoolImplTestBase,
+ public testing::Test {
+ public:
+ TaskSchedulerWorkerPoolStandbyPolicyTest() = default;
+
+ void SetUp() override {
+ TaskSchedulerWorkerPoolImplTestBase::CommonSetUp(
+ kReclaimTimeForCleanupTests);
+ }
+
+ void TearDown() override {
+ TaskSchedulerWorkerPoolImplTestBase::CommonTearDown();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolStandbyPolicyTest);
+};
+
+} // namespace
+
+TEST_F(TaskSchedulerWorkerPoolStandbyPolicyTest, InitOne) {
+ EXPECT_EQ(1U, worker_pool_->NumberOfWorkersForTesting());
+}
+
+// Verify that the SchedulerWorkerPoolImpl keeps at least one idle standby
+// thread, capacity permitting.
+TEST_F(TaskSchedulerWorkerPoolStandbyPolicyTest, VerifyStandbyThread) {
+ auto task_runner =
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+
+ WaitableEvent thread_running(WaitableEvent::ResetPolicy::AUTOMATIC);
+ WaitableEvent threads_continue;
+
+ RepeatingClosure thread_blocker = BindLambdaForTesting([&]() {
+ thread_running.Signal();
+ WaitWithoutBlockingObserver(&threads_continue);
+ });
+
+ // There should be one idle thread until we reach capacity
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ EXPECT_EQ(i + 1, worker_pool_->NumberOfWorkersForTesting());
+ task_runner->PostTask(FROM_HERE, thread_blocker);
+ thread_running.Wait();
+ }
+
+ // There should not be an extra idle thread if it means going above capacity
+ EXPECT_EQ(kMaxTasks, worker_pool_->NumberOfWorkersForTesting());
+
+ threads_continue.Signal();
+ // Wait long enough for all but one worker to clean up.
+ worker_pool_->WaitForWorkersCleanedUpForTesting(kMaxTasks - 1);
+ EXPECT_EQ(1U, worker_pool_->NumberOfWorkersForTesting());
+ // Give extra time for a worker to cleanup : none should as the pool is
+ // expected to keep a worker ready regardless of how long it was idle for.
+ PlatformThread::Sleep(kReclaimTimeForCleanupTests);
+ EXPECT_EQ(1U, worker_pool_->NumberOfWorkersForTesting());
+}
+
+// Verify that being "the" idle thread counts as being active (i.e. won't be
+// reclaimed even if not on top of the idle stack when reclaim timeout expires).
+// Regression test for https://crbug.com/847501.
+TEST_F(TaskSchedulerWorkerPoolStandbyPolicyTest,
+ InAndOutStandbyThreadIsActive) {
+ auto sequenced_task_runner =
+ worker_pool_->CreateSequencedTaskRunnerWithTraits({});
+
+ WaitableEvent timer_started;
+
+ RepeatingTimer recurring_task;
+ sequenced_task_runner->PostTask(
+ FROM_HERE, BindLambdaForTesting([&]() {
+ recurring_task.Start(FROM_HERE, kReclaimTimeForCleanupTests / 2,
+ DoNothing());
+ timer_started.Signal();
+ }));
+
+ timer_started.Wait();
+
+ // Running a task should have brought up a new standby thread.
+ EXPECT_EQ(2U, worker_pool_->NumberOfWorkersForTesting());
+
+ // Give extra time for a worker to cleanup : none should as the two workers
+ // are both considered "active" per the timer ticking faster than the reclaim
+ // timeout.
+ PlatformThread::Sleep(kReclaimTimeForCleanupTests * 2);
+ EXPECT_EQ(2U, worker_pool_->NumberOfWorkersForTesting());
+
+ sequenced_task_runner->PostTask(
+ FROM_HERE, BindLambdaForTesting([&]() { recurring_task.Stop(); }));
+
+ // Stopping the recurring task should let the second worker be reclaimed per
+ // not being "the" standby thread for a full reclaim timeout.
+ worker_pool_->WaitForWorkersCleanedUpForTesting(1);
+ EXPECT_EQ(1U, worker_pool_->NumberOfWorkersForTesting());
+}
+
+// Verify that being "the" idle thread counts as being active but isn't sticky.
+// Regression test for https://crbug.com/847501.
+TEST_F(TaskSchedulerWorkerPoolStandbyPolicyTest, OnlyKeepActiveStandbyThreads) {
+ auto sequenced_task_runner =
+ worker_pool_->CreateSequencedTaskRunnerWithTraits({});
+
+ // Start this test like
+ // TaskSchedulerWorkerPoolStandbyPolicyTest.InAndOutStandbyThreadIsActive and
+ // give it some time to stabilize.
+ RepeatingTimer recurring_task;
+ sequenced_task_runner->PostTask(
+ FROM_HERE, BindLambdaForTesting([&]() {
+ recurring_task.Start(FROM_HERE, kReclaimTimeForCleanupTests / 2,
+ DoNothing());
+ }));
+
+ PlatformThread::Sleep(kReclaimTimeForCleanupTests * 2);
+ EXPECT_EQ(2U, worker_pool_->NumberOfWorkersForTesting());
+
+ // Then also flood the pool (cycling the top of the idle stack).
+ {
+ auto task_runner =
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+
+ WaitableEvent thread_running(WaitableEvent::ResetPolicy::AUTOMATIC);
+ WaitableEvent threads_continue;
+
+ RepeatingClosure thread_blocker = BindLambdaForTesting([&]() {
+ thread_running.Signal();
+ WaitWithoutBlockingObserver(&threads_continue);
+ });
+
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ task_runner->PostTask(FROM_HERE, thread_blocker);
+ thread_running.Wait();
+ }
+
+ EXPECT_EQ(kMaxTasks, worker_pool_->NumberOfWorkersForTesting());
+ threads_continue.Signal();
+
+ // Flush to ensure all references to |threads_continue| are gone before it
+ // goes out of scope.
+ task_tracker_.FlushForTesting();
+ }
+
+ // All workers should clean up but two (since the timer is still running).
+ worker_pool_->WaitForWorkersCleanedUpForTesting(kMaxTasks - 2);
+ EXPECT_EQ(2U, worker_pool_->NumberOfWorkersForTesting());
+
+ // Extra time shouldn't change this.
+ PlatformThread::Sleep(kReclaimTimeForCleanupTests * 2);
+ EXPECT_EQ(2U, worker_pool_->NumberOfWorkersForTesting());
+
+ // Stopping the timer should let the number of active threads go down to one.
+ sequenced_task_runner->PostTask(
+ FROM_HERE, BindLambdaForTesting([&]() { recurring_task.Stop(); }));
+ worker_pool_->WaitForWorkersCleanedUpForTesting(1);
+ EXPECT_EQ(1U, worker_pool_->NumberOfWorkersForTesting());
+}
+
+namespace {
+
+enum class OptionalBlockingType {
+ NO_BLOCK,
+ MAY_BLOCK,
+ WILL_BLOCK,
+};
+
+struct NestedBlockingType {
+ NestedBlockingType(BlockingType first_in,
+ OptionalBlockingType second_in,
+ BlockingType behaves_as_in)
+ : first(first_in), second(second_in), behaves_as(behaves_as_in) {}
+
+ BlockingType first;
+ OptionalBlockingType second;
+ BlockingType behaves_as;
+};
+
+class NestedScopedBlockingCall {
+ public:
+ NestedScopedBlockingCall(const NestedBlockingType& nested_blocking_type)
+ : first_scoped_blocking_call_(nested_blocking_type.first),
+ second_scoped_blocking_call_(
+ nested_blocking_type.second == OptionalBlockingType::WILL_BLOCK
+ ? std::make_unique<ScopedBlockingCall>(BlockingType::WILL_BLOCK)
+ : (nested_blocking_type.second ==
+ OptionalBlockingType::MAY_BLOCK
+ ? std::make_unique<ScopedBlockingCall>(
+ BlockingType::MAY_BLOCK)
+ : nullptr)) {}
+
+ private:
+ ScopedBlockingCall first_scoped_blocking_call_;
+ std::unique_ptr<ScopedBlockingCall> second_scoped_blocking_call_;
+
+ DISALLOW_COPY_AND_ASSIGN(NestedScopedBlockingCall);
+};
+
+} // namespace
+
+class TaskSchedulerWorkerPoolBlockingTest
+ : public TaskSchedulerWorkerPoolImplTestBase,
+ public testing::TestWithParam<NestedBlockingType> {
+ public:
+ TaskSchedulerWorkerPoolBlockingTest() = default;
+
+ static std::string ParamInfoToString(
+ ::testing::TestParamInfo<NestedBlockingType> param_info) {
+ std::string str = param_info.param.first == BlockingType::MAY_BLOCK
+ ? "MAY_BLOCK"
+ : "WILL_BLOCK";
+ if (param_info.param.second == OptionalBlockingType::MAY_BLOCK)
+ str += "_MAY_BLOCK";
+ else if (param_info.param.second == OptionalBlockingType::WILL_BLOCK)
+ str += "_WILL_BLOCK";
+ return str;
+ }
+
+ void SetUp() override {
+ TaskSchedulerWorkerPoolImplTestBase::CommonSetUp();
+ task_runner_ =
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+ }
+
+ void TearDown() override {
+ TaskSchedulerWorkerPoolImplTestBase::CommonTearDown();
+ }
+
+ protected:
+ // Saturates the worker pool with a task that first blocks, waits to be
+ // unblocked, then exits.
+ void SaturateWithBlockingTasks(
+ const NestedBlockingType& nested_blocking_type) {
+ ASSERT_FALSE(blocking_threads_running_.IsSignaled());
+
+ RepeatingClosure blocking_threads_running_closure = BarrierClosure(
+ kMaxTasks, BindOnce(&WaitableEvent::Signal,
+ Unretained(&blocking_threads_running_)));
+
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ task_runner_->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](Closure* blocking_threads_running_closure,
+ WaitableEvent* blocking_threads_continue_,
+ const NestedBlockingType& nested_blocking_type) {
+ NestedScopedBlockingCall nested_scoped_blocking_call(
+ nested_blocking_type);
+ blocking_threads_running_closure->Run();
+ WaitWithoutBlockingObserver(blocking_threads_continue_);
+ },
+ Unretained(&blocking_threads_running_closure),
+ Unretained(&blocking_threads_continue_), nested_blocking_type));
+ }
+ blocking_threads_running_.Wait();
+ }
+
+ // Returns how long we can expect a change to |max_tasks_| to occur
+ // after a task has become blocked.
+ TimeDelta GetMaxTasksChangeSleepTime() {
+ return std::max(SchedulerWorkerPoolImpl::kBlockedWorkersPollPeriod,
+ worker_pool_->MayBlockThreshold()) +
+ TestTimeouts::tiny_timeout();
+ }
+
+ // Waits indefinitely, until |worker_pool_|'s max tasks increases to
+ // |expected_max_tasks|.
+ void ExpectMaxTasksIncreasesTo(size_t expected_max_tasks) {
+ size_t max_tasks = worker_pool_->GetMaxTasksForTesting();
+ while (max_tasks != expected_max_tasks) {
+ PlatformThread::Sleep(GetMaxTasksChangeSleepTime());
+ size_t new_max_tasks = worker_pool_->GetMaxTasksForTesting();
+ ASSERT_GE(new_max_tasks, max_tasks);
+ max_tasks = new_max_tasks;
+ }
+ }
+
+ // Unblocks tasks posted by SaturateWithBlockingTasks().
+ void UnblockTasks() { blocking_threads_continue_.Signal(); }
+
+ scoped_refptr<TaskRunner> task_runner_;
+
+ private:
+ WaitableEvent blocking_threads_running_;
+ WaitableEvent blocking_threads_continue_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolBlockingTest);
+};
+
+// Verify that BlockingScopeEntered() causes max tasks to increase and creates a
+// worker if needed. Also verify that BlockingScopeExited() decreases max tasks
+// after an increase.
+TEST_P(TaskSchedulerWorkerPoolBlockingTest, ThreadBlockedUnblocked) {
+ ASSERT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+
+ SaturateWithBlockingTasks(GetParam());
+ if (GetParam().behaves_as == BlockingType::MAY_BLOCK)
+ ExpectMaxTasksIncreasesTo(2 * kMaxTasks);
+ // A range of possible number of workers is accepted because of
+ // crbug.com/757897.
+ EXPECT_GE(worker_pool_->NumberOfWorkersForTesting(), kMaxTasks + 1);
+ EXPECT_LE(worker_pool_->NumberOfWorkersForTesting(), 2 * kMaxTasks);
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(), 2 * kMaxTasks);
+
+ UnblockTasks();
+ task_tracker_.FlushForTesting();
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+}
+
+// Verify that tasks posted in a saturated pool before a ScopedBlockingCall will
+// execute after ScopedBlockingCall is instantiated.
+TEST_P(TaskSchedulerWorkerPoolBlockingTest, PostBeforeBlocking) {
+ WaitableEvent thread_running(WaitableEvent::ResetPolicy::AUTOMATIC);
+ WaitableEvent thread_can_block;
+ WaitableEvent threads_continue;
+
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ task_runner_->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](const NestedBlockingType& nested_blocking_type,
+ WaitableEvent* thread_running, WaitableEvent* thread_can_block,
+ WaitableEvent* threads_continue) {
+ thread_running->Signal();
+ WaitWithoutBlockingObserver(thread_can_block);
+
+ NestedScopedBlockingCall nested_scoped_blocking_call(
+ nested_blocking_type);
+ WaitWithoutBlockingObserver(threads_continue);
+ },
+ GetParam(), Unretained(&thread_running),
+ Unretained(&thread_can_block), Unretained(&threads_continue)));
+ thread_running.Wait();
+ }
+
+ // All workers should be occupied and the pool should be saturated. Workers
+ // have not entered ScopedBlockingCall yet.
+ EXPECT_EQ(worker_pool_->NumberOfWorkersForTesting(), kMaxTasks);
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+
+ WaitableEvent extra_threads_running;
+ WaitableEvent extra_threads_continue;
+ RepeatingClosure extra_threads_running_barrier = BarrierClosure(
+ kMaxTasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&extra_threads_running)));
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ task_runner_->PostTask(FROM_HERE,
+ BindOnce(
+ [](Closure* extra_threads_running_barrier,
+ WaitableEvent* extra_threads_continue) {
+ extra_threads_running_barrier->Run();
+ WaitWithoutBlockingObserver(
+ extra_threads_continue);
+ },
+ Unretained(&extra_threads_running_barrier),
+ Unretained(&extra_threads_continue)));
+ }
+
+ // Allow tasks to enter ScopedBlockingCall. Workers should be created for the
+ // tasks we just posted.
+ thread_can_block.Signal();
+ if (GetParam().behaves_as == BlockingType::MAY_BLOCK)
+ ExpectMaxTasksIncreasesTo(2 * kMaxTasks);
+
+ // Should not block forever.
+ extra_threads_running.Wait();
+ EXPECT_EQ(worker_pool_->NumberOfWorkersForTesting(), 2 * kMaxTasks);
+ extra_threads_continue.Signal();
+
+ threads_continue.Signal();
+ task_tracker_.FlushForTesting();
+}
+// Verify that workers become idle when the pool is over-capacity and that
+// those workers do no work.
+TEST_P(TaskSchedulerWorkerPoolBlockingTest, WorkersIdleWhenOverCapacity) {
+ ASSERT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+
+ SaturateWithBlockingTasks(GetParam());
+ if (GetParam().behaves_as == BlockingType::MAY_BLOCK)
+ ExpectMaxTasksIncreasesTo(2 * kMaxTasks);
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(), 2 * kMaxTasks);
+ // A range of possible number of workers is accepted because of
+ // crbug.com/757897.
+ EXPECT_GE(worker_pool_->NumberOfWorkersForTesting(), kMaxTasks + 1);
+ EXPECT_LE(worker_pool_->NumberOfWorkersForTesting(), 2 * kMaxTasks);
+
+ WaitableEvent threads_running;
+ WaitableEvent threads_continue;
+
+ RepeatingClosure threads_running_barrier = BarrierClosure(
+ kMaxTasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&threads_running)));
+ // Posting these tasks should cause new workers to be created.
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ auto callback = BindOnce(
+ [](Closure* threads_running_barrier, WaitableEvent* threads_continue) {
+ threads_running_barrier->Run();
+ WaitWithoutBlockingObserver(threads_continue);
+ },
+ Unretained(&threads_running_barrier), Unretained(&threads_continue));
+ task_runner_->PostTask(FROM_HERE, std::move(callback));
+ }
+ threads_running.Wait();
+
+ ASSERT_EQ(worker_pool_->NumberOfIdleWorkersForTesting(), 0U);
+ EXPECT_EQ(worker_pool_->NumberOfWorkersForTesting(), 2 * kMaxTasks);
+
+ AtomicFlag is_exiting;
+ // These tasks should not get executed until after other tasks become
+ // unblocked.
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ task_runner_->PostTask(FROM_HERE, BindOnce(
+ [](AtomicFlag* is_exiting) {
+ EXPECT_TRUE(is_exiting->IsSet());
+ },
+ Unretained(&is_exiting)));
+ }
+
+ // The original |kMaxTasks| will finish their tasks after being
+ // unblocked. There will be work in the work queue, but the pool should now
+ // be over-capacity and workers will become idle.
+ UnblockTasks();
+ worker_pool_->WaitForWorkersIdleForTesting(kMaxTasks);
+ EXPECT_EQ(worker_pool_->NumberOfIdleWorkersForTesting(), kMaxTasks);
+
+ // Posting more tasks should not cause workers idle from the pool being over
+ // capacity to begin doing work.
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ task_runner_->PostTask(FROM_HERE, BindOnce(
+ [](AtomicFlag* is_exiting) {
+ EXPECT_TRUE(is_exiting->IsSet());
+ },
+ Unretained(&is_exiting)));
+ }
+
+ // Give time for those idle workers to possibly do work (which should not
+ // happen).
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+
+ is_exiting.Set();
+ // Unblocks the new workers.
+ threads_continue.Signal();
+ task_tracker_.FlushForTesting();
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ,
+ TaskSchedulerWorkerPoolBlockingTest,
+ ::testing::Values(NestedBlockingType(BlockingType::MAY_BLOCK,
+ OptionalBlockingType::NO_BLOCK,
+ BlockingType::MAY_BLOCK),
+ NestedBlockingType(BlockingType::WILL_BLOCK,
+ OptionalBlockingType::NO_BLOCK,
+ BlockingType::WILL_BLOCK),
+ NestedBlockingType(BlockingType::MAY_BLOCK,
+ OptionalBlockingType::WILL_BLOCK,
+ BlockingType::WILL_BLOCK),
+ NestedBlockingType(BlockingType::WILL_BLOCK,
+ OptionalBlockingType::MAY_BLOCK,
+ BlockingType::WILL_BLOCK)),
+ TaskSchedulerWorkerPoolBlockingTest::ParamInfoToString);
+
+// Verify that if a thread enters the scope of a MAY_BLOCK ScopedBlockingCall,
+// but exits the scope before the MayBlockThreshold() is reached, that the max
+// tasks does not increase.
+TEST_F(TaskSchedulerWorkerPoolBlockingTest, ThreadBlockUnblockPremature) {
+ ASSERT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+
+ TimeDelta max_tasks_change_sleep = GetMaxTasksChangeSleepTime();
+ worker_pool_->MaximizeMayBlockThresholdForTesting();
+
+ SaturateWithBlockingTasks(NestedBlockingType(BlockingType::MAY_BLOCK,
+ OptionalBlockingType::NO_BLOCK,
+ BlockingType::MAY_BLOCK));
+ PlatformThread::Sleep(max_tasks_change_sleep);
+ EXPECT_EQ(worker_pool_->NumberOfWorkersForTesting(), kMaxTasks);
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+
+ UnblockTasks();
+ task_tracker_.FlushForTesting();
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+}
+
+// Verify that if max tasks is incremented because of a MAY_BLOCK
+// ScopedBlockingCall, it isn't incremented again when there is a nested
+// WILL_BLOCK ScopedBlockingCall.
+TEST_F(TaskSchedulerWorkerPoolBlockingTest,
+ MayBlockIncreaseCapacityNestedWillBlock) {
+ ASSERT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+ auto task_runner =
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+ WaitableEvent can_return;
+
+ // Saturate the pool so that a MAY_BLOCK ScopedBlockingCall would increment
+ // the max tasks.
+ for (size_t i = 0; i < kMaxTasks - 1; ++i) {
+ task_runner->PostTask(FROM_HERE, BindOnce(&WaitWithoutBlockingObserver,
+ Unretained(&can_return)));
+ }
+
+ WaitableEvent can_instantiate_will_block;
+ WaitableEvent did_instantiate_will_block;
+
+ // Post a task that instantiates a MAY_BLOCK ScopedBlockingCall.
+ task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](WaitableEvent* can_instantiate_will_block,
+ WaitableEvent* did_instantiate_will_block,
+ WaitableEvent* can_return) {
+ ScopedBlockingCall may_block(BlockingType::MAY_BLOCK);
+ WaitWithoutBlockingObserver(can_instantiate_will_block);
+ ScopedBlockingCall will_block(BlockingType::WILL_BLOCK);
+ did_instantiate_will_block->Signal();
+ WaitWithoutBlockingObserver(can_return);
+ },
+ Unretained(&can_instantiate_will_block),
+ Unretained(&did_instantiate_will_block), Unretained(&can_return)));
+
+ // After a short delay, max tasks should be incremented.
+ ExpectMaxTasksIncreasesTo(kMaxTasks + 1);
+
+ // Wait until the task instantiates a WILL_BLOCK ScopedBlockingCall.
+ can_instantiate_will_block.Signal();
+ did_instantiate_will_block.Wait();
+
+ // Max tasks shouldn't be incremented again.
+ EXPECT_EQ(kMaxTasks + 1, worker_pool_->GetMaxTasksForTesting());
+
+ // Tear down.
+ can_return.Signal();
+ task_tracker_.FlushForTesting();
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks);
+}
+
+// Verify that workers that become idle due to the pool being over capacity will
+// eventually cleanup.
+TEST(TaskSchedulerWorkerPoolOverCapacityTest, VerifyCleanup) {
+ constexpr size_t kLocalMaxTasks = 3;
+
+ TaskTracker task_tracker("Test");
+ DelayedTaskManager delayed_task_manager;
+ scoped_refptr<TaskRunner> service_thread_task_runner =
+ MakeRefCounted<TestSimpleTaskRunner>();
+ delayed_task_manager.Start(service_thread_task_runner);
+ SchedulerWorkerPoolImpl worker_pool(
+ "OverCapacityTestWorkerPool", "A", ThreadPriority::NORMAL,
+ task_tracker.GetTrackedRef(), &delayed_task_manager);
+ worker_pool.Start(
+ SchedulerWorkerPoolParams(kLocalMaxTasks, kReclaimTimeForCleanupTests),
+ kLocalMaxTasks, service_thread_task_runner, nullptr,
+ SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+
+ scoped_refptr<TaskRunner> task_runner =
+ worker_pool.CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+
+ WaitableEvent threads_running;
+ WaitableEvent threads_continue;
+ RepeatingClosure threads_running_barrier = BarrierClosure(
+ kLocalMaxTasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&threads_running)));
+
+ WaitableEvent blocked_call_continue;
+ RepeatingClosure closure = BindRepeating(
+ [](Closure* threads_running_barrier, WaitableEvent* threads_continue,
+ WaitableEvent* blocked_call_continue) {
+ threads_running_barrier->Run();
+ {
+ ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+ WaitWithoutBlockingObserver(blocked_call_continue);
+ }
+ WaitWithoutBlockingObserver(threads_continue);
+ },
+ Unretained(&threads_running_barrier), Unretained(&threads_continue),
+ Unretained(&blocked_call_continue));
+
+ for (size_t i = 0; i < kLocalMaxTasks; ++i)
+ task_runner->PostTask(FROM_HERE, closure);
+
+ threads_running.Wait();
+
+ WaitableEvent extra_threads_running;
+ WaitableEvent extra_threads_continue;
+
+ RepeatingClosure extra_threads_running_barrier = BarrierClosure(
+ kLocalMaxTasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&extra_threads_running)));
+ // These tasks should run on the new threads from increasing max tasks.
+ for (size_t i = 0; i < kLocalMaxTasks; ++i) {
+ task_runner->PostTask(FROM_HERE,
+ BindOnce(
+ [](Closure* extra_threads_running_barrier,
+ WaitableEvent* extra_threads_continue) {
+ extra_threads_running_barrier->Run();
+ WaitWithoutBlockingObserver(
+ extra_threads_continue);
+ },
+ Unretained(&extra_threads_running_barrier),
+ Unretained(&extra_threads_continue)));
+ }
+ extra_threads_running.Wait();
+
+ ASSERT_EQ(kLocalMaxTasks * 2, worker_pool.NumberOfWorkersForTesting());
+ EXPECT_EQ(kLocalMaxTasks * 2, worker_pool.GetMaxTasksForTesting());
+ blocked_call_continue.Signal();
+ extra_threads_continue.Signal();
+
+ // Periodically post tasks to ensure that posting tasks does not prevent
+ // workers that are idle due to the pool being over capacity from cleaning up.
+ for (int i = 0; i < 16; ++i) {
+ task_runner->PostDelayedTask(FROM_HERE, DoNothing(),
+ kReclaimTimeForCleanupTests * i * 0.5);
+ }
+
+ // Note: one worker above capacity will not get cleaned up since it's on the
+ // top of the idle stack.
+ worker_pool.WaitForWorkersCleanedUpForTesting(kLocalMaxTasks - 1);
+ EXPECT_EQ(kLocalMaxTasks + 1, worker_pool.NumberOfWorkersForTesting());
+
+ threads_continue.Signal();
+
+ worker_pool.JoinForTesting();
+}
+
+// Verify that the maximum number of workers is 256 and that hitting the max
+// leaves the pool in a valid state with regards to max tasks.
+TEST_F(TaskSchedulerWorkerPoolBlockingTest, MaximumWorkersTest) {
+ constexpr size_t kMaxNumberOfWorkers = 256;
+ constexpr size_t kNumExtraTasks = 10;
+
+ WaitableEvent early_blocking_threads_running;
+ RepeatingClosure early_threads_barrier_closure =
+ BarrierClosure(kMaxNumberOfWorkers,
+ BindOnce(&WaitableEvent::Signal,
+ Unretained(&early_blocking_threads_running)));
+
+ WaitableEvent early_threads_finished;
+ RepeatingClosure early_threads_finished_barrier = BarrierClosure(
+ kMaxNumberOfWorkers,
+ BindOnce(&WaitableEvent::Signal, Unretained(&early_threads_finished)));
+
+ WaitableEvent early_release_threads_continue;
+
+ // Post ScopedBlockingCall tasks to hit the worker cap.
+ for (size_t i = 0; i < kMaxNumberOfWorkers; ++i) {
+ task_runner_->PostTask(FROM_HERE,
+ BindOnce(
+ [](Closure* early_threads_barrier_closure,
+ WaitableEvent* early_release_threads_continue,
+ Closure* early_threads_finished) {
+ {
+ ScopedBlockingCall scoped_blocking_call(
+ BlockingType::WILL_BLOCK);
+ early_threads_barrier_closure->Run();
+ WaitWithoutBlockingObserver(
+ early_release_threads_continue);
+ }
+ early_threads_finished->Run();
+ },
+ Unretained(&early_threads_barrier_closure),
+ Unretained(&early_release_threads_continue),
+ Unretained(&early_threads_finished_barrier)));
+ }
+
+ early_blocking_threads_running.Wait();
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(),
+ kMaxTasks + kMaxNumberOfWorkers);
+
+ WaitableEvent late_release_thread_contine;
+ WaitableEvent late_blocking_threads_running;
+
+ RepeatingClosure late_threads_barrier_closure = BarrierClosure(
+ kNumExtraTasks, BindOnce(&WaitableEvent::Signal,
+ Unretained(&late_blocking_threads_running)));
+
+ // Posts additional tasks. Note: we should already have |kMaxNumberOfWorkers|
+ // tasks running. These tasks should not be able to get executed yet as
+ // the pool is already at its max worker cap.
+ for (size_t i = 0; i < kNumExtraTasks; ++i) {
+ task_runner_->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](Closure* late_threads_barrier_closure,
+ WaitableEvent* late_release_thread_contine) {
+ ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+ late_threads_barrier_closure->Run();
+ WaitWithoutBlockingObserver(late_release_thread_contine);
+ },
+ Unretained(&late_threads_barrier_closure),
+ Unretained(&late_release_thread_contine)));
+ }
+
+ // Give time to see if we exceed the max number of workers.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_LE(worker_pool_->NumberOfWorkersForTesting(), kMaxNumberOfWorkers);
+
+ early_release_threads_continue.Signal();
+ early_threads_finished.Wait();
+ late_blocking_threads_running.Wait();
+
+ WaitableEvent final_tasks_running;
+ WaitableEvent final_tasks_continue;
+ RepeatingClosure final_tasks_running_barrier = BarrierClosure(
+ kMaxTasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&final_tasks_running)));
+
+ // Verify that we are still able to saturate the pool.
+ for (size_t i = 0; i < kMaxTasks; ++i) {
+ task_runner_->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](Closure* closure, WaitableEvent* final_tasks_continue) {
+ closure->Run();
+ WaitWithoutBlockingObserver(final_tasks_continue);
+ },
+ Unretained(&final_tasks_running_barrier),
+ Unretained(&final_tasks_continue)));
+ }
+ final_tasks_running.Wait();
+ EXPECT_EQ(worker_pool_->GetMaxTasksForTesting(), kMaxTasks + kNumExtraTasks);
+ late_release_thread_contine.Signal();
+ final_tasks_continue.Signal();
+ task_tracker_.FlushForTesting();
+}
+
+// Verify that the maximum number of background tasks that can run concurrently
+// is honored.
+TEST_F(TaskSchedulerWorkerPoolImplStartInBodyTest, MaxBackgroundTasks) {
+ constexpr int kMaxBackgroundTasks = kMaxTasks / 2;
+ worker_pool_->Start(
+ SchedulerWorkerPoolParams(kMaxTasks, base::TimeDelta::Max()),
+ kMaxBackgroundTasks, service_thread_.task_runner(), nullptr,
+ SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+ const scoped_refptr<TaskRunner> foreground_runner =
+ worker_pool_->CreateTaskRunnerWithTraits({MayBlock()});
+ const scoped_refptr<TaskRunner> background_runner =
+ worker_pool_->CreateTaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND, MayBlock()});
+
+ // It should be possible to have |kMaxBackgroundTasks|
+ // TaskPriority::BACKGROUND tasks running concurrently.
+ WaitableEvent background_tasks_running;
+ WaitableEvent unblock_background_tasks;
+ RepeatingClosure background_tasks_running_barrier = BarrierClosure(
+ kMaxBackgroundTasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&background_tasks_running)));
+
+ for (int i = 0; i < kMaxBackgroundTasks; ++i) {
+ background_runner->PostTask(
+ FROM_HERE, base::BindLambdaForTesting([&]() {
+ background_tasks_running_barrier.Run();
+ WaitWithoutBlockingObserver(&unblock_background_tasks);
+ }));
+ }
+ background_tasks_running.Wait();
+
+ // No more TaskPriority::BACKGROUND task should run.
+ AtomicFlag extra_background_task_can_run;
+ WaitableEvent extra_background_task_running;
+ background_runner->PostTask(
+ FROM_HERE, base::BindLambdaForTesting([&]() {
+ EXPECT_TRUE(extra_background_task_can_run.IsSet());
+ extra_background_task_running.Signal();
+ }));
+
+ // An extra foreground task should be able to run.
+ WaitableEvent foreground_task_running;
+ foreground_runner->PostTask(
+ FROM_HERE, base::BindOnce(&WaitableEvent::Signal,
+ Unretained(&foreground_task_running)));
+ foreground_task_running.Wait();
+
+ // Completion of the TaskPriority::BACKGROUND tasks should allow the extra
+ // TaskPriority::BACKGROUND task to run.
+ extra_background_task_can_run.Set();
+ unblock_background_tasks.Signal();
+ extra_background_task_running.Wait();
+
+ // Tear down.
+ task_tracker_.FlushForTesting();
+}
+
+namespace {
+
+class TaskSchedulerWorkerPoolBlockingCallAndMaxBackgroundTasksTest
+ : public TaskSchedulerWorkerPoolImplTestBase,
+ public testing::TestWithParam<BlockingType> {
+ public:
+ static constexpr int kMaxBackgroundTasks = kMaxTasks / 2;
+
+ TaskSchedulerWorkerPoolBlockingCallAndMaxBackgroundTasksTest() = default;
+
+ void SetUp() override {
+ CreateWorkerPool();
+ worker_pool_->Start(
+ SchedulerWorkerPoolParams(kMaxTasks, base::TimeDelta::Max()),
+ kMaxBackgroundTasks, service_thread_.task_runner(), nullptr,
+ SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+ }
+
+ void TearDown() override {
+ TaskSchedulerWorkerPoolImplTestBase::CommonTearDown();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ TaskSchedulerWorkerPoolBlockingCallAndMaxBackgroundTasksTest);
+};
+
+} // namespace
+
+TEST_P(TaskSchedulerWorkerPoolBlockingCallAndMaxBackgroundTasksTest,
+ BlockingCallAndMaxBackgroundTasksTest) {
+ const scoped_refptr<TaskRunner> background_runner =
+ worker_pool_->CreateTaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND, MayBlock()});
+
+ // Post |kMaxBackgroundTasks| TaskPriority::BACKGROUND tasks that block in a
+ // ScopedBlockingCall.
+ WaitableEvent blocking_background_tasks_running;
+ WaitableEvent unblock_blocking_background_tasks;
+ RepeatingClosure blocking_background_tasks_running_barrier =
+ BarrierClosure(kMaxBackgroundTasks,
+ BindOnce(&WaitableEvent::Signal,
+ Unretained(&blocking_background_tasks_running)));
+ for (int i = 0; i < kMaxBackgroundTasks; ++i) {
+ background_runner->PostTask(
+ FROM_HERE, base::BindLambdaForTesting([&]() {
+ blocking_background_tasks_running_barrier.Run();
+ ScopedBlockingCall scoped_blocking_call(GetParam());
+ WaitWithoutBlockingObserver(&unblock_blocking_background_tasks);
+ }));
+ }
+ blocking_background_tasks_running.Wait();
+
+ // Post an extra |kMaxBackgroundTasks| TaskPriority::BACKGROUND tasks. They
+ // should be able to run, because the existing TaskPriority::BACKGROUND tasks
+ // are blocked within a ScopedBlockingCall.
+ //
+ // Note: We block the tasks until they have all started running to make sure
+ // that it is possible to run an extra |kMaxBackgroundTasks| concurrently.
+ WaitableEvent background_tasks_running;
+ WaitableEvent unblock_background_tasks;
+ RepeatingClosure background_tasks_running_barrier = BarrierClosure(
+ kMaxBackgroundTasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&background_tasks_running)));
+ for (int i = 0; i < kMaxBackgroundTasks; ++i) {
+ background_runner->PostTask(
+ FROM_HERE, base::BindLambdaForTesting([&]() {
+ background_tasks_running_barrier.Run();
+ WaitWithoutBlockingObserver(&unblock_background_tasks);
+ }));
+ }
+ background_tasks_running.Wait();
+
+ // Unblock all tasks and tear down.
+ unblock_blocking_background_tasks.Signal();
+ unblock_background_tasks.Signal();
+ task_tracker_.FlushForTesting();
+}
+
+INSTANTIATE_TEST_CASE_P(
+ MayBlock,
+ TaskSchedulerWorkerPoolBlockingCallAndMaxBackgroundTasksTest,
+ ::testing::Values(BlockingType::MAY_BLOCK));
+INSTANTIATE_TEST_CASE_P(
+ WillBlock,
+ TaskSchedulerWorkerPoolBlockingCallAndMaxBackgroundTasksTest,
+ ::testing::Values(BlockingType::WILL_BLOCK));
+
+// Verify that worker detachement doesn't race with worker cleanup, regression
+// test for https://crbug.com/810464.
+TEST_F(TaskSchedulerWorkerPoolImplStartInBodyTest, RacyCleanup) {
+#if defined(OS_FUCHSIA)
+ // Fuchsia + QEMU doesn't deal well with *many* threads being
+ // created/destroyed at once: https://crbug.com/816575.
+ constexpr size_t kLocalMaxTasks = 16;
+#else // defined(OS_FUCHSIA)
+ constexpr size_t kLocalMaxTasks = 256;
+#endif // defined(OS_FUCHSIA)
+ constexpr TimeDelta kReclaimTimeForRacyCleanupTest =
+ TimeDelta::FromMilliseconds(10);
+
+ worker_pool_->Start(
+ SchedulerWorkerPoolParams(kLocalMaxTasks, kReclaimTimeForRacyCleanupTest),
+ kLocalMaxTasks, service_thread_.task_runner(), nullptr,
+ SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+
+ scoped_refptr<TaskRunner> task_runner =
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+
+ WaitableEvent threads_running;
+ WaitableEvent unblock_threads;
+ RepeatingClosure threads_running_barrier = BarrierClosure(
+ kLocalMaxTasks,
+ BindOnce(&WaitableEvent::Signal, Unretained(&threads_running)));
+
+ for (size_t i = 0; i < kLocalMaxTasks; ++i) {
+ task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](OnceClosure on_running, WaitableEvent* unblock_threads) {
+ std::move(on_running).Run();
+ WaitWithoutBlockingObserver(unblock_threads);
+ },
+ threads_running_barrier, Unretained(&unblock_threads)));
+ }
+
+ // Wait for all workers to be ready and release them all at once.
+ threads_running.Wait();
+ unblock_threads.Signal();
+
+ // Sleep to wakeup precisely when all workers are going to try to cleanup per
+ // being idle.
+ PlatformThread::Sleep(kReclaimTimeForRacyCleanupTest);
+
+ worker_pool_->JoinForTesting();
+
+ // Unwinding this test will be racy if worker cleanup can race with
+ // SchedulerWorkerPoolImpl destruction : https://crbug.com/810464.
+ worker_pool_.reset();
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/scheduler_worker_stack_unittest.cc b/base/task_scheduler/scheduler_worker_stack_unittest.cc
new file mode 100644
index 0000000000..83d693a912
--- /dev/null
+++ b/base/task_scheduler/scheduler_worker_stack_unittest.cc
@@ -0,0 +1,254 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/scheduler_worker_stack.h"
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_scheduler/scheduler_worker.h"
+#include "base/task_scheduler/sequence.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/test/gtest_util.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+class MockSchedulerWorkerDelegate : public SchedulerWorker::Delegate {
+ public:
+ void OnCanScheduleSequence(scoped_refptr<Sequence> sequence) override {
+ ADD_FAILURE() << "Unexpected call to OnCanScheduleSequence().";
+ }
+ SchedulerWorker::ThreadLabel GetThreadLabel() const override {
+ return SchedulerWorker::ThreadLabel::DEDICATED;
+ }
+ void OnMainEntry(const SchedulerWorker* worker) override {}
+ scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override {
+ return nullptr;
+ }
+ void DidRunTask() override {
+ ADD_FAILURE() << "Unexpected call to DidRunTask()";
+ }
+ void ReEnqueueSequence(scoped_refptr<Sequence> sequence) override {
+ ADD_FAILURE() << "Unexpected call to ReEnqueueSequence()";
+ }
+ TimeDelta GetSleepTimeout() override {
+ return TimeDelta::Max();
+ }
+};
+
+class TaskSchedulerWorkerStackTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ worker_a_ = MakeRefCounted<SchedulerWorker>(
+ ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate),
+ task_tracker_.GetTrackedRef());
+ ASSERT_TRUE(worker_a_);
+ worker_b_ = MakeRefCounted<SchedulerWorker>(
+ ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate),
+ task_tracker_.GetTrackedRef());
+ ASSERT_TRUE(worker_b_);
+ worker_c_ = MakeRefCounted<SchedulerWorker>(
+ ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate),
+ task_tracker_.GetTrackedRef());
+ ASSERT_TRUE(worker_c_);
+ }
+
+ private:
+ TaskTracker task_tracker_ = {"Test"};
+
+ protected:
+ scoped_refptr<SchedulerWorker> worker_a_;
+ scoped_refptr<SchedulerWorker> worker_b_;
+ scoped_refptr<SchedulerWorker> worker_c_;
+};
+
+} // namespace
+
+// Verify that Push() and Pop() add/remove values in FIFO order.
+TEST_F(TaskSchedulerWorkerStackTest, PushPop) {
+ SchedulerWorkerStack stack;
+ EXPECT_EQ(nullptr, stack.Pop());
+
+ EXPECT_TRUE(stack.IsEmpty());
+ EXPECT_EQ(0U, stack.Size());
+
+ stack.Push(worker_a_.get());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(1U, stack.Size());
+
+ stack.Push(worker_b_.get());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(2U, stack.Size());
+
+ stack.Push(worker_c_.get());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(3U, stack.Size());
+
+ EXPECT_EQ(worker_c_.get(), stack.Pop());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(2U, stack.Size());
+
+ stack.Push(worker_c_.get());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(3U, stack.Size());
+
+ EXPECT_EQ(worker_c_.get(), stack.Pop());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(2U, stack.Size());
+
+ EXPECT_EQ(worker_b_.get(), stack.Pop());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(1U, stack.Size());
+
+ EXPECT_EQ(worker_a_.get(), stack.Pop());
+ EXPECT_TRUE(stack.IsEmpty());
+ EXPECT_EQ(0U, stack.Size());
+
+ EXPECT_EQ(nullptr, stack.Pop());
+}
+
+// Verify that Peek() returns the correct values in FIFO order.
+TEST_F(TaskSchedulerWorkerStackTest, PeekPop) {
+ SchedulerWorkerStack stack;
+ EXPECT_EQ(nullptr, stack.Peek());
+
+ EXPECT_TRUE(stack.IsEmpty());
+ EXPECT_EQ(0U, stack.Size());
+
+ stack.Push(worker_a_.get());
+ EXPECT_EQ(worker_a_.get(), stack.Peek());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(1U, stack.Size());
+
+ stack.Push(worker_b_.get());
+ EXPECT_EQ(worker_b_.get(), stack.Peek());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(2U, stack.Size());
+
+ stack.Push(worker_c_.get());
+ EXPECT_EQ(worker_c_.get(), stack.Peek());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(3U, stack.Size());
+
+ EXPECT_EQ(worker_c_.get(), stack.Pop());
+ EXPECT_EQ(worker_b_.get(), stack.Peek());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(2U, stack.Size());
+
+ EXPECT_EQ(worker_b_.get(), stack.Pop());
+ EXPECT_EQ(worker_a_.get(), stack.Peek());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(1U, stack.Size());
+
+ EXPECT_EQ(worker_a_.get(), stack.Pop());
+ EXPECT_TRUE(stack.IsEmpty());
+ EXPECT_EQ(0U, stack.Size());
+
+ EXPECT_EQ(nullptr, stack.Peek());
+}
+
+// Verify that Contains() returns true for workers on the stack.
+TEST_F(TaskSchedulerWorkerStackTest, Contains) {
+ SchedulerWorkerStack stack;
+ EXPECT_FALSE(stack.Contains(worker_a_.get()));
+ EXPECT_FALSE(stack.Contains(worker_b_.get()));
+ EXPECT_FALSE(stack.Contains(worker_c_.get()));
+
+ stack.Push(worker_a_.get());
+ EXPECT_TRUE(stack.Contains(worker_a_.get()));
+ EXPECT_FALSE(stack.Contains(worker_b_.get()));
+ EXPECT_FALSE(stack.Contains(worker_c_.get()));
+
+ stack.Push(worker_b_.get());
+ EXPECT_TRUE(stack.Contains(worker_a_.get()));
+ EXPECT_TRUE(stack.Contains(worker_b_.get()));
+ EXPECT_FALSE(stack.Contains(worker_c_.get()));
+
+ stack.Push(worker_c_.get());
+ EXPECT_TRUE(stack.Contains(worker_a_.get()));
+ EXPECT_TRUE(stack.Contains(worker_b_.get()));
+ EXPECT_TRUE(stack.Contains(worker_c_.get()));
+
+ stack.Pop();
+ EXPECT_TRUE(stack.Contains(worker_a_.get()));
+ EXPECT_TRUE(stack.Contains(worker_b_.get()));
+ EXPECT_FALSE(stack.Contains(worker_c_.get()));
+
+ stack.Pop();
+ EXPECT_TRUE(stack.Contains(worker_a_.get()));
+ EXPECT_FALSE(stack.Contains(worker_b_.get()));
+ EXPECT_FALSE(stack.Contains(worker_c_.get()));
+
+ stack.Pop();
+ EXPECT_FALSE(stack.Contains(worker_a_.get()));
+ EXPECT_FALSE(stack.Contains(worker_b_.get()));
+ EXPECT_FALSE(stack.Contains(worker_c_.get()));
+}
+
+// Verify that a value can be removed by Remove().
+TEST_F(TaskSchedulerWorkerStackTest, Remove) {
+ SchedulerWorkerStack stack;
+ EXPECT_TRUE(stack.IsEmpty());
+ EXPECT_EQ(0U, stack.Size());
+
+ stack.Push(worker_a_.get());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(1U, stack.Size());
+
+ stack.Push(worker_b_.get());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(2U, stack.Size());
+
+ stack.Push(worker_c_.get());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(3U, stack.Size());
+
+ stack.Remove(worker_b_.get());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(2U, stack.Size());
+
+ EXPECT_EQ(worker_c_.get(), stack.Pop());
+ EXPECT_FALSE(stack.IsEmpty());
+ EXPECT_EQ(1U, stack.Size());
+
+ EXPECT_EQ(worker_a_.get(), stack.Pop());
+ EXPECT_TRUE(stack.IsEmpty());
+ EXPECT_EQ(0U, stack.Size());
+}
+
+// Verify that a value can be pushed again after it has been removed.
+TEST_F(TaskSchedulerWorkerStackTest, PushAfterRemove) {
+ SchedulerWorkerStack stack;
+ EXPECT_EQ(0U, stack.Size());
+
+ stack.Push(worker_a_.get());
+ EXPECT_EQ(1U, stack.Size());
+
+ // Need to also push worker B for this test as it's illegal to Remove() the
+ // top of the stack.
+ stack.Push(worker_b_.get());
+ EXPECT_EQ(2U, stack.Size());
+
+ stack.Remove(worker_a_.get());
+ EXPECT_EQ(1U, stack.Size());
+
+ stack.Push(worker_a_.get());
+ EXPECT_EQ(2U, stack.Size());
+}
+
+// Verify that Push() DCHECKs when a value is inserted twice.
+TEST_F(TaskSchedulerWorkerStackTest, PushTwice) {
+ SchedulerWorkerStack stack;
+ stack.Push(worker_a_.get());
+ EXPECT_DCHECK_DEATH({ stack.Push(worker_a_.get()); });
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/scheduler_worker_unittest.cc b/base/task_scheduler/scheduler_worker_unittest.cc
new file mode 100644
index 0000000000..1112f55e6c
--- /dev/null
+++ b/base/task_scheduler/scheduler_worker_unittest.cc
@@ -0,0 +1,897 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/scheduler_worker.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/environment_config.h"
+#include "base/task_scheduler/scheduler_lock.h"
+#include "base/task_scheduler/scheduler_worker_observer.h"
+#include "base/task_scheduler/sequence.h"
+#include "base/task_scheduler/task.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include <objbase.h>
+
+#include "base/win/com_init_check_hook.h"
+#endif
+
+using testing::_;
+using testing::Mock;
+using testing::Ne;
+using testing::StrictMock;
+
+namespace base {
+namespace internal {
+namespace {
+
+const size_t kNumSequencesPerTest = 150;
+
+class SchedulerWorkerDefaultDelegate : public SchedulerWorker::Delegate {
+ public:
+ SchedulerWorkerDefaultDelegate() = default;
+
+ // SchedulerWorker::Delegate:
+ void OnCanScheduleSequence(scoped_refptr<Sequence> sequence) override {
+ ADD_FAILURE() << "Unexpected call to OnCanScheduleSequence().";
+ }
+ SchedulerWorker::ThreadLabel GetThreadLabel() const override {
+ return SchedulerWorker::ThreadLabel::DEDICATED;
+ }
+ void OnMainEntry(const SchedulerWorker* worker) override {}
+ scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override {
+ return nullptr;
+ }
+ void DidRunTask() override {
+ ADD_FAILURE() << "Unexpected call to DidRunTask()";
+ }
+ void ReEnqueueSequence(scoped_refptr<Sequence> sequence) override {
+ ADD_FAILURE() << "Unexpected call to ReEnqueueSequence()";
+ }
+ TimeDelta GetSleepTimeout() override { return TimeDelta::Max(); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SchedulerWorkerDefaultDelegate);
+};
+
+// The test parameter is the number of Tasks per Sequence returned by GetWork().
+class TaskSchedulerWorkerTest : public testing::TestWithParam<size_t> {
+ protected:
+ TaskSchedulerWorkerTest()
+ : num_get_work_cv_(lock_.CreateConditionVariable()) {}
+
+ void SetUp() override {
+ worker_ = MakeRefCounted<SchedulerWorker>(
+ ThreadPriority::NORMAL,
+ std::make_unique<TestSchedulerWorkerDelegate>(this),
+ task_tracker_.GetTrackedRef());
+ ASSERT_TRUE(worker_);
+ worker_->Start();
+ worker_set_.Signal();
+ main_entry_called_.Wait();
+ }
+
+ void TearDown() override {
+ // |worker_| needs to be released before ~TaskTracker() as it holds a
+ // TrackedRef to it.
+ worker_->JoinForTesting();
+ worker_ = nullptr;
+ }
+
+ size_t TasksPerSequence() const { return GetParam(); }
+
+ // Wait until GetWork() has been called |num_get_work| times.
+ void WaitForNumGetWork(size_t num_get_work) {
+ AutoSchedulerLock auto_lock(lock_);
+ while (num_get_work_ < num_get_work)
+ num_get_work_cv_->Wait();
+ }
+
+ void SetMaxGetWork(size_t max_get_work) {
+ AutoSchedulerLock auto_lock(lock_);
+ max_get_work_ = max_get_work;
+ }
+
+ void SetNumSequencesToCreate(size_t num_sequences_to_create) {
+ AutoSchedulerLock auto_lock(lock_);
+ EXPECT_EQ(0U, num_sequences_to_create_);
+ num_sequences_to_create_ = num_sequences_to_create;
+ }
+
+ size_t NumRunTasks() {
+ AutoSchedulerLock auto_lock(lock_);
+ return num_run_tasks_;
+ }
+
+ std::vector<scoped_refptr<Sequence>> CreatedSequences() {
+ AutoSchedulerLock auto_lock(lock_);
+ return created_sequences_;
+ }
+
+ std::vector<scoped_refptr<Sequence>> EnqueuedSequences() {
+ AutoSchedulerLock auto_lock(lock_);
+ return re_enqueued_sequences_;
+ }
+
+ scoped_refptr<SchedulerWorker> worker_;
+
+ private:
+ class TestSchedulerWorkerDelegate : public SchedulerWorkerDefaultDelegate {
+ public:
+ TestSchedulerWorkerDelegate(TaskSchedulerWorkerTest* outer)
+ : outer_(outer) {}
+
+ ~TestSchedulerWorkerDelegate() override {
+ EXPECT_FALSE(IsCallToDidRunTaskExpected());
+ }
+
+ // SchedulerWorker::Delegate:
+ void OnMainEntry(const SchedulerWorker* worker) override {
+ outer_->worker_set_.Wait();
+ EXPECT_EQ(outer_->worker_.get(), worker);
+ EXPECT_FALSE(IsCallToDidRunTaskExpected());
+
+ // Without synchronization, OnMainEntry() could be called twice without
+ // generating an error.
+ AutoSchedulerLock auto_lock(outer_->lock_);
+ EXPECT_FALSE(outer_->main_entry_called_.IsSignaled());
+ outer_->main_entry_called_.Signal();
+ }
+
+ scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override {
+ EXPECT_FALSE(IsCallToDidRunTaskExpected());
+ EXPECT_EQ(outer_->worker_.get(), worker);
+
+ {
+ AutoSchedulerLock auto_lock(outer_->lock_);
+
+ // Increment the number of times that this method has been called.
+ ++outer_->num_get_work_;
+ outer_->num_get_work_cv_->Signal();
+
+ // Verify that this method isn't called more times than expected.
+ EXPECT_LE(outer_->num_get_work_, outer_->max_get_work_);
+
+ // Check if a Sequence should be returned.
+ if (outer_->num_sequences_to_create_ == 0)
+ return nullptr;
+ --outer_->num_sequences_to_create_;
+ }
+
+ // Create a Sequence with TasksPerSequence() Tasks.
+ scoped_refptr<Sequence> sequence(new Sequence);
+ for (size_t i = 0; i < outer_->TasksPerSequence(); ++i) {
+ Task task(FROM_HERE,
+ BindOnce(&TaskSchedulerWorkerTest::RunTaskCallback,
+ Unretained(outer_)),
+ TaskTraits(), TimeDelta());
+ EXPECT_TRUE(outer_->task_tracker_.WillPostTask(&task));
+ sequence->PushTask(std::move(task));
+ }
+
+ ExpectCallToDidRunTask();
+
+ {
+ // Add the Sequence to the vector of created Sequences.
+ AutoSchedulerLock auto_lock(outer_->lock_);
+ outer_->created_sequences_.push_back(sequence);
+ }
+
+ sequence = outer_->task_tracker_.WillScheduleSequence(std::move(sequence),
+ nullptr);
+ EXPECT_TRUE(sequence);
+ return sequence;
+ }
+
+ void DidRunTask() override {
+ AutoSchedulerLock auto_lock(expect_did_run_task_lock_);
+ EXPECT_TRUE(expect_did_run_task_);
+ expect_did_run_task_ = false;
+ }
+
+ // This override verifies that |sequence| contains the expected number of
+ // Tasks and adds it to |enqueued_sequences_|. Unlike a normal
+ // EnqueueSequence implementation, it doesn't reinsert |sequence| into a
+ // queue for further execution.
+ void ReEnqueueSequence(scoped_refptr<Sequence> sequence) override {
+ EXPECT_FALSE(IsCallToDidRunTaskExpected());
+ EXPECT_GT(outer_->TasksPerSequence(), 1U);
+
+ // Verify that |sequence| contains TasksPerSequence() - 1 Tasks.
+ for (size_t i = 0; i < outer_->TasksPerSequence() - 1; ++i) {
+ EXPECT_TRUE(sequence->TakeTask());
+ EXPECT_EQ(i == outer_->TasksPerSequence() - 2, sequence->Pop());
+ }
+
+ // Add |sequence| to |re_enqueued_sequences_|.
+ AutoSchedulerLock auto_lock(outer_->lock_);
+ outer_->re_enqueued_sequences_.push_back(std::move(sequence));
+ EXPECT_LE(outer_->re_enqueued_sequences_.size(),
+ outer_->created_sequences_.size());
+ }
+
+ private:
+ // Expect a call to DidRunTask() before the next call to any other method of
+ // this delegate.
+ void ExpectCallToDidRunTask() {
+ AutoSchedulerLock auto_lock(expect_did_run_task_lock_);
+ expect_did_run_task_ = true;
+ }
+
+ bool IsCallToDidRunTaskExpected() const {
+ AutoSchedulerLock auto_lock(expect_did_run_task_lock_);
+ return expect_did_run_task_;
+ }
+
+ TaskSchedulerWorkerTest* outer_;
+
+ // Synchronizes access to |expect_did_run_task_|.
+ mutable SchedulerLock expect_did_run_task_lock_;
+
+ // Whether the next method called on this delegate should be DidRunTask().
+ bool expect_did_run_task_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSchedulerWorkerDelegate);
+ };
+
+ void RunTaskCallback() {
+ AutoSchedulerLock auto_lock(lock_);
+ ++num_run_tasks_;
+ EXPECT_LE(num_run_tasks_, created_sequences_.size());
+ }
+
+ TaskTracker task_tracker_ = {"Test"};
+
+ // Synchronizes access to all members below.
+ mutable SchedulerLock lock_;
+
+ // Signaled once OnMainEntry() has been called.
+ WaitableEvent main_entry_called_;
+
+ // Number of Sequences that should be created by GetWork(). When this
+ // is 0, GetWork() returns nullptr.
+ size_t num_sequences_to_create_ = 0;
+
+ // Number of times that GetWork() has been called.
+ size_t num_get_work_ = 0;
+
+ // Maximum number of times that GetWork() can be called.
+ size_t max_get_work_ = 0;
+
+ // Condition variable signaled when |num_get_work_| is incremented.
+ std::unique_ptr<ConditionVariable> num_get_work_cv_;
+
+ // Sequences created by GetWork().
+ std::vector<scoped_refptr<Sequence>> created_sequences_;
+
+ // Sequences passed to EnqueueSequence().
+ std::vector<scoped_refptr<Sequence>> re_enqueued_sequences_;
+
+ // Number of times that RunTaskCallback() has been called.
+ size_t num_run_tasks_ = 0;
+
+ // Signaled after |worker_| is set.
+ WaitableEvent worker_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerTest);
+};
+
+} // namespace
+
+// Verify that when GetWork() continuously returns Sequences, all Tasks in these
+// Sequences run successfully. The test wakes up the SchedulerWorker once.
+TEST_P(TaskSchedulerWorkerTest, ContinuousWork) {
+ // Set GetWork() to return |kNumSequencesPerTest| Sequences before starting to
+ // return nullptr.
+ SetNumSequencesToCreate(kNumSequencesPerTest);
+
+ // Expect |kNumSequencesPerTest| calls to GetWork() in which it returns a
+ // Sequence and one call in which its returns nullptr.
+ const size_t kExpectedNumGetWork = kNumSequencesPerTest + 1;
+ SetMaxGetWork(kExpectedNumGetWork);
+
+ // Wake up |worker_| and wait until GetWork() has been invoked the
+ // expected amount of times.
+ worker_->WakeUp();
+ WaitForNumGetWork(kExpectedNumGetWork);
+
+ // All tasks should have run.
+ EXPECT_EQ(kNumSequencesPerTest, NumRunTasks());
+
+ // If Sequences returned by GetWork() contain more than one Task, they aren't
+ // empty after the worker pops Tasks from them and thus should be returned to
+ // EnqueueSequence().
+ if (TasksPerSequence() > 1)
+ EXPECT_EQ(CreatedSequences(), EnqueuedSequences());
+ else
+ EXPECT_TRUE(EnqueuedSequences().empty());
+}
+
+// Verify that when GetWork() alternates between returning a Sequence and
+// returning nullptr, all Tasks in the returned Sequences run successfully. The
+// test wakes up the SchedulerWorker once for each Sequence.
+TEST_P(TaskSchedulerWorkerTest, IntermittentWork) {
+ for (size_t i = 0; i < kNumSequencesPerTest; ++i) {
+ // Set GetWork() to return 1 Sequence before starting to return
+ // nullptr.
+ SetNumSequencesToCreate(1);
+
+ // Expect |i + 1| calls to GetWork() in which it returns a Sequence and
+ // |i + 1| calls in which it returns nullptr.
+ const size_t expected_num_get_work = 2 * (i + 1);
+ SetMaxGetWork(expected_num_get_work);
+
+ // Wake up |worker_| and wait until GetWork() has been invoked
+ // the expected amount of times.
+ worker_->WakeUp();
+ WaitForNumGetWork(expected_num_get_work);
+
+ // The Task should have run
+ EXPECT_EQ(i + 1, NumRunTasks());
+
+ // If Sequences returned by GetWork() contain more than one Task, they
+ // aren't empty after the worker pops Tasks from them and thus should be
+ // returned to EnqueueSequence().
+ if (TasksPerSequence() > 1)
+ EXPECT_EQ(CreatedSequences(), EnqueuedSequences());
+ else
+ EXPECT_TRUE(EnqueuedSequences().empty());
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(OneTaskPerSequence,
+ TaskSchedulerWorkerTest,
+ ::testing::Values(1));
+INSTANTIATE_TEST_CASE_P(TwoTasksPerSequence,
+ TaskSchedulerWorkerTest,
+ ::testing::Values(2));
+
+namespace {
+
+class ControllableCleanupDelegate : public SchedulerWorkerDefaultDelegate {
+ public:
+ class Controls : public RefCountedThreadSafe<Controls> {
+ public:
+ Controls() = default;
+
+ void HaveWorkBlock() { work_running_.Reset(); }
+
+ void UnblockWork() { work_running_.Signal(); }
+
+ void WaitForWorkToRun() { work_processed_.Wait(); }
+
+ void WaitForCleanupRequest() { cleanup_requested_.Wait(); }
+
+ void WaitForDelegateDestroy() { destroyed_.Wait(); }
+
+ void WaitForMainExit() { exited_.Wait(); }
+
+ void set_expect_get_work(bool expect_get_work) {
+ expect_get_work_ = expect_get_work;
+ }
+
+ void ResetState() {
+ work_running_.Signal();
+ work_processed_.Reset();
+ cleanup_requested_.Reset();
+ exited_.Reset();
+ work_requested_ = false;
+ }
+
+ void set_can_cleanup(bool can_cleanup) { can_cleanup_ = can_cleanup; }
+
+ private:
+ friend class ControllableCleanupDelegate;
+ friend class RefCountedThreadSafe<Controls>;
+ ~Controls() = default;
+
+ WaitableEvent work_running_{WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::SIGNALED};
+ WaitableEvent work_processed_;
+ WaitableEvent cleanup_requested_;
+ WaitableEvent destroyed_;
+ WaitableEvent exited_;
+
+ bool expect_get_work_ = true;
+ bool can_cleanup_ = false;
+ bool work_requested_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(Controls);
+ };
+
+ ControllableCleanupDelegate(TaskTracker* task_tracker)
+ : task_tracker_(task_tracker), controls_(new Controls()) {}
+
+ ~ControllableCleanupDelegate() override { controls_->destroyed_.Signal(); }
+
+ scoped_refptr<Sequence> GetWork(SchedulerWorker* worker)
+ override {
+ EXPECT_TRUE(controls_->expect_get_work_);
+
+ // Sends one item of work to signal |work_processed_|. On subsequent calls,
+ // sends nullptr to indicate there's no more work to be done.
+ if (controls_->work_requested_) {
+ if (CanCleanup(worker)) {
+ OnCleanup();
+ worker->Cleanup();
+ controls_->set_expect_get_work(false);
+ }
+ return nullptr;
+ }
+
+ controls_->work_requested_ = true;
+ scoped_refptr<Sequence> sequence(new Sequence);
+ Task task(
+ FROM_HERE,
+ BindOnce(
+ [](WaitableEvent* work_processed, WaitableEvent* work_running) {
+ work_processed->Signal();
+ work_running->Wait();
+ },
+ Unretained(&controls_->work_processed_),
+ Unretained(&controls_->work_running_)),
+ {WithBaseSyncPrimitives(), TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ TimeDelta());
+ EXPECT_TRUE(task_tracker_->WillPostTask(&task));
+ sequence->PushTask(std::move(task));
+ sequence =
+ task_tracker_->WillScheduleSequence(std::move(sequence), nullptr);
+ EXPECT_TRUE(sequence);
+ return sequence;
+ }
+
+ void DidRunTask() override {}
+
+ void OnMainExit(SchedulerWorker* worker) override {
+ controls_->exited_.Signal();
+ }
+
+ bool CanCleanup(SchedulerWorker* worker) {
+ // Saving |can_cleanup_| now so that callers waiting on |cleanup_requested_|
+ // have the thread go to sleep and then allow timing out.
+ bool can_cleanup = controls_->can_cleanup_;
+ controls_->cleanup_requested_.Signal();
+ return can_cleanup;
+ }
+
+ void OnCleanup() {
+ EXPECT_TRUE(controls_->can_cleanup_);
+ EXPECT_TRUE(controls_->cleanup_requested_.IsSignaled());
+ }
+
+ // ControllableCleanupDelegate:
+ scoped_refptr<Controls> controls() { return controls_; }
+
+ private:
+ scoped_refptr<Sequence> work_sequence_;
+ TaskTracker* const task_tracker_;
+ scoped_refptr<Controls> controls_;
+
+ DISALLOW_COPY_AND_ASSIGN(ControllableCleanupDelegate);
+};
+
+class MockedControllableCleanupDelegate : public ControllableCleanupDelegate {
+ public:
+ MockedControllableCleanupDelegate(TaskTracker* task_tracker)
+ : ControllableCleanupDelegate(task_tracker){};
+ ~MockedControllableCleanupDelegate() override = default;
+
+ // SchedulerWorker::Delegate:
+ MOCK_METHOD1(OnMainEntry, void(const SchedulerWorker* worker));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockedControllableCleanupDelegate);
+};
+
+} // namespace
+
+// Verify that calling SchedulerWorker::Cleanup() from GetWork() causes
+// the SchedulerWorker's thread to exit.
+TEST(TaskSchedulerWorkerTest, WorkerCleanupFromGetWork) {
+ TaskTracker task_tracker("Test");
+ // Will be owned by SchedulerWorker.
+ MockedControllableCleanupDelegate* delegate =
+ new StrictMock<MockedControllableCleanupDelegate>(&task_tracker);
+ scoped_refptr<ControllableCleanupDelegate::Controls> controls =
+ delegate->controls();
+ controls->set_can_cleanup(true);
+ EXPECT_CALL(*delegate, OnMainEntry(_));
+ auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL,
+ WrapUnique(delegate),
+ task_tracker.GetTrackedRef());
+ worker->Start();
+ worker->WakeUp();
+ controls->WaitForWorkToRun();
+ Mock::VerifyAndClear(delegate);
+ controls->WaitForMainExit();
+}
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringWork) {
+ TaskTracker task_tracker("Test");
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableCleanupDelegate> delegate =
+ std::make_unique<ControllableCleanupDelegate>(&task_tracker);
+ scoped_refptr<ControllableCleanupDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->HaveWorkBlock();
+
+ auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL,
+ std::move(delegate),
+ task_tracker.GetTrackedRef());
+ worker->Start();
+ worker->WakeUp();
+
+ controls->WaitForWorkToRun();
+ worker->Cleanup();
+ worker = nullptr;
+ controls->UnblockWork();
+ controls->WaitForDelegateDestroy();
+}
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringWait) {
+ TaskTracker task_tracker("Test");
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableCleanupDelegate> delegate =
+ std::make_unique<ControllableCleanupDelegate>(&task_tracker);
+ scoped_refptr<ControllableCleanupDelegate::Controls> controls =
+ delegate->controls();
+
+ auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL,
+ std::move(delegate),
+ task_tracker.GetTrackedRef());
+ worker->Start();
+ worker->WakeUp();
+
+ controls->WaitForCleanupRequest();
+ worker->Cleanup();
+ worker = nullptr;
+ controls->WaitForDelegateDestroy();
+}
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringShutdown) {
+ TaskTracker task_tracker("Test");
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableCleanupDelegate> delegate =
+ std::make_unique<ControllableCleanupDelegate>(&task_tracker);
+ scoped_refptr<ControllableCleanupDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->HaveWorkBlock();
+
+ auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL,
+ std::move(delegate),
+ task_tracker.GetTrackedRef());
+ worker->Start();
+ worker->WakeUp();
+
+ controls->WaitForWorkToRun();
+ task_tracker.Shutdown();
+ worker->Cleanup();
+ worker = nullptr;
+ controls->UnblockWork();
+ controls->WaitForDelegateDestroy();
+}
+
+// Verify that Start() is a no-op after Cleanup().
+TEST(TaskSchedulerWorkerTest, CleanupBeforeStart) {
+ TaskTracker task_tracker("Test");
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableCleanupDelegate> delegate =
+ std::make_unique<ControllableCleanupDelegate>(&task_tracker);
+ scoped_refptr<ControllableCleanupDelegate::Controls> controls =
+ delegate->controls();
+ controls->set_expect_get_work(false);
+
+ auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL,
+ std::move(delegate),
+ task_tracker.GetTrackedRef());
+
+ worker->Cleanup();
+ worker->Start();
+
+ EXPECT_FALSE(worker->ThreadAliveForTesting());
+}
+
+namespace {
+
+class CallJoinFromDifferentThread : public SimpleThread {
+ public:
+ CallJoinFromDifferentThread(SchedulerWorker* worker_to_join)
+ : SimpleThread("SchedulerWorkerJoinThread"),
+ worker_to_join_(worker_to_join) {}
+
+ ~CallJoinFromDifferentThread() override = default;
+
+ void Run() override {
+ run_started_event_.Signal();
+ worker_to_join_->JoinForTesting();
+ }
+
+ void WaitForRunToStart() { run_started_event_.Wait(); }
+
+ private:
+ SchedulerWorker* const worker_to_join_;
+ WaitableEvent run_started_event_;
+ DISALLOW_COPY_AND_ASSIGN(CallJoinFromDifferentThread);
+};
+
+} // namespace
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringJoin) {
+ TaskTracker task_tracker("Test");
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the
+ // delegate may destroy on a different thread. Mocks aren't designed with that
+ // in mind.
+ std::unique_ptr<ControllableCleanupDelegate> delegate =
+ std::make_unique<ControllableCleanupDelegate>(&task_tracker);
+ scoped_refptr<ControllableCleanupDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->HaveWorkBlock();
+
+ auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL,
+ std::move(delegate),
+ task_tracker.GetTrackedRef());
+ worker->Start();
+ worker->WakeUp();
+
+ controls->WaitForWorkToRun();
+ CallJoinFromDifferentThread join_from_different_thread(worker.get());
+ join_from_different_thread.Start();
+ join_from_different_thread.WaitForRunToStart();
+ // Sleep here to give the other thread a chance to call JoinForTesting().
+ // Receiving a signal that Run() was called doesn't mean JoinForTesting() was
+ // necessarily called, and we can't signal after JoinForTesting() as
+ // JoinForTesting() blocks until we call UnblockWork().
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ worker->Cleanup();
+ worker = nullptr;
+ controls->UnblockWork();
+ controls->WaitForDelegateDestroy();
+ join_from_different_thread.Join();
+}
+
+namespace {
+
+class ExpectThreadPriorityDelegate : public SchedulerWorkerDefaultDelegate {
+ public:
+ ExpectThreadPriorityDelegate()
+ : priority_verified_in_get_work_event_(
+ WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ expected_thread_priority_(ThreadPriority::BACKGROUND) {}
+
+ void SetExpectedThreadPriority(ThreadPriority expected_thread_priority) {
+ expected_thread_priority_ = expected_thread_priority;
+ }
+
+ void WaitForPriorityVerifiedInGetWork() {
+ priority_verified_in_get_work_event_.Wait();
+ }
+
+ // SchedulerWorker::Delegate:
+ void OnMainEntry(const SchedulerWorker* worker) override {
+ VerifyThreadPriority();
+ }
+ scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override {
+ VerifyThreadPriority();
+ priority_verified_in_get_work_event_.Signal();
+ return nullptr;
+ }
+
+ private:
+ void VerifyThreadPriority() {
+ AutoSchedulerLock auto_lock(expected_thread_priority_lock_);
+ EXPECT_EQ(expected_thread_priority_,
+ PlatformThread::GetCurrentThreadPriority());
+ }
+
+ // Signaled after GetWork() has verified the priority of the worker thread.
+ WaitableEvent priority_verified_in_get_work_event_;
+
+ // Synchronizes access to |expected_thread_priority_|.
+ SchedulerLock expected_thread_priority_lock_;
+
+ // Expected thread priority for the next call to OnMainEntry() or GetWork().
+ ThreadPriority expected_thread_priority_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExpectThreadPriorityDelegate);
+};
+
+} // namespace
+
+TEST(TaskSchedulerWorkerTest, BumpPriorityOfAliveThreadDuringShutdown) {
+ if (!CanUseBackgroundPriorityForSchedulerWorker())
+ return;
+
+ TaskTracker task_tracker("Test");
+
+ std::unique_ptr<ExpectThreadPriorityDelegate> delegate(
+ new ExpectThreadPriorityDelegate);
+ ExpectThreadPriorityDelegate* delegate_raw = delegate.get();
+ delegate_raw->SetExpectedThreadPriority(ThreadPriority::BACKGROUND);
+ auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::BACKGROUND,
+ std::move(delegate),
+ task_tracker.GetTrackedRef());
+ worker->Start();
+
+ // Verify that the initial thread priority is BACKGROUND (or NORMAL if thread
+ // priority can't be increased).
+ worker->WakeUp();
+ delegate_raw->WaitForPriorityVerifiedInGetWork();
+
+ // Verify that the thread priority is bumped to NORMAL during shutdown.
+ delegate_raw->SetExpectedThreadPriority(ThreadPriority::NORMAL);
+ task_tracker.SetHasShutdownStartedForTesting();
+ worker->WakeUp();
+ delegate_raw->WaitForPriorityVerifiedInGetWork();
+
+ worker->JoinForTesting();
+}
+
+namespace {
+
+class VerifyCallsToObserverDelegate : public SchedulerWorkerDefaultDelegate {
+ public:
+ VerifyCallsToObserverDelegate(test::MockSchedulerWorkerObserver* observer)
+ : observer_(observer) {}
+
+ // SchedulerWorker::Delegate:
+ void OnMainEntry(const SchedulerWorker* worker) override {
+ Mock::VerifyAndClear(observer_);
+ }
+
+ void OnMainExit(SchedulerWorker* worker) override {
+ EXPECT_CALL(*observer_, OnSchedulerWorkerMainExit());
+ }
+
+ private:
+ test::MockSchedulerWorkerObserver* const observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(VerifyCallsToObserverDelegate);
+};
+
+} // namespace
+
+// Flaky: crbug.com/846121
+#if defined(OS_LINUX) && defined(ADDRESS_SANITIZER)
+#define MAYBE_SchedulerWorkerObserver DISABLED_SchedulerWorkerObserver
+#else
+#define MAYBE_SchedulerWorkerObserver SchedulerWorkerObserver
+#endif
+
+// Verify that the SchedulerWorkerObserver is notified when the worker enters
+// and exits its main function.
+TEST(TaskSchedulerWorkerTest, MAYBE_SchedulerWorkerObserver) {
+ StrictMock<test::MockSchedulerWorkerObserver> observer;
+ {
+ TaskTracker task_tracker("Test");
+ auto delegate = std::make_unique<VerifyCallsToObserverDelegate>(&observer);
+ auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL,
+ std::move(delegate),
+ task_tracker.GetTrackedRef());
+
+ EXPECT_CALL(observer, OnSchedulerWorkerMainEntry());
+ worker->Start(&observer);
+ worker->Cleanup();
+ worker = nullptr;
+ }
+ Mock::VerifyAndClear(&observer);
+}
+
+#if defined(OS_WIN)
+
+namespace {
+
+class CoInitializeDelegate : public SchedulerWorkerDefaultDelegate {
+ public:
+ CoInitializeDelegate() = default;
+
+ scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override {
+ EXPECT_FALSE(get_work_returned_.IsSignaled());
+ EXPECT_EQ(E_UNEXPECTED, coinitialize_hresult_);
+
+ coinitialize_hresult_ = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+ if (SUCCEEDED(coinitialize_hresult_))
+ CoUninitialize();
+
+ get_work_returned_.Signal();
+ return nullptr;
+ }
+
+ void WaitUntilGetWorkReturned() { get_work_returned_.Wait(); }
+
+ HRESULT coinitialize_hresult() const { return coinitialize_hresult_; }
+
+ private:
+ WaitableEvent get_work_returned_;
+ HRESULT coinitialize_hresult_ = E_UNEXPECTED;
+
+ DISALLOW_COPY_AND_ASSIGN(CoInitializeDelegate);
+};
+
+} // namespace
+
+TEST(TaskSchedulerWorkerTest, BackwardCompatibilityEnabled) {
+ TaskTracker task_tracker("Test");
+ auto delegate = std::make_unique<CoInitializeDelegate>();
+ CoInitializeDelegate* const delegate_raw = delegate.get();
+
+ // Create a worker with backward compatibility ENABLED. Wake it up and wait
+ // until GetWork() returns.
+ auto worker = MakeRefCounted<SchedulerWorker>(
+ ThreadPriority::NORMAL, std::move(delegate), task_tracker.GetTrackedRef(),
+ nullptr, SchedulerBackwardCompatibility::INIT_COM_STA);
+ worker->Start();
+ worker->WakeUp();
+ delegate_raw->WaitUntilGetWorkReturned();
+
+ // The call to CoInitializeEx() should have returned S_FALSE to indicate that
+ // the COM library was already initialized on the thread.
+ // See SchedulerWorker::Thread::ThreadMain for why we expect two different
+ // results here.
+#if defined(COM_INIT_CHECK_HOOK_ENABLED)
+ EXPECT_EQ(S_OK, delegate_raw->coinitialize_hresult());
+#else
+ EXPECT_EQ(S_FALSE, delegate_raw->coinitialize_hresult());
+#endif
+
+ worker->JoinForTesting();
+}
+
+TEST(TaskSchedulerWorkerTest, BackwardCompatibilityDisabled) {
+ TaskTracker task_tracker("Test");
+ auto delegate = std::make_unique<CoInitializeDelegate>();
+ CoInitializeDelegate* const delegate_raw = delegate.get();
+
+ // Create a worker with backward compatibility DISABLED. Wake it up and wait
+ // until GetWork() returns.
+ auto worker = MakeRefCounted<SchedulerWorker>(
+ ThreadPriority::NORMAL, std::move(delegate), task_tracker.GetTrackedRef(),
+ nullptr, SchedulerBackwardCompatibility::DISABLED);
+ worker->Start();
+ worker->WakeUp();
+ delegate_raw->WaitUntilGetWorkReturned();
+
+ // The call to CoInitializeEx() should have returned S_OK to indicate that the
+ // COM library wasn't already initialized on the thread.
+ EXPECT_EQ(S_OK, delegate_raw->coinitialize_hresult());
+
+ worker->JoinForTesting();
+}
+
+#endif // defined(OS_WIN)
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/task_scheduler_impl_unittest.cc b/base/task_scheduler/task_scheduler_impl_unittest.cc
new file mode 100644
index 0000000000..94c5293903
--- /dev/null
+++ b/base/task_scheduler/task_scheduler_impl_unittest.cc
@@ -0,0 +1,841 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/task_scheduler_impl.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/cfi_buildflags.h"
+#include "base/debug/stack_trace.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/environment_config.h"
+#include "base/task_scheduler/scheduler_worker_observer.h"
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/task_scheduler/test_task_factory.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/test/gtest_util.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequence_local_storage_slot.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX)
+#include <unistd.h>
+
+#include "base/debug/leak_annotations.h"
+#include "base/files/file_descriptor_watcher_posix.h"
+#include "base/files/file_util.h"
+#include "base/posix/eintr_wrapper.h"
+#endif // defined(OS_POSIX)
+
+#if defined(OS_WIN)
+#include "base/win/com_init_util.h"
+#endif // defined(OS_WIN)
+
+namespace base {
+namespace internal {
+
+namespace {
+
+struct TraitsExecutionModePair {
+ TraitsExecutionModePair(const TaskTraits& traits,
+ test::ExecutionMode execution_mode)
+ : traits(traits), execution_mode(execution_mode) {}
+
+ TaskTraits traits;
+ test::ExecutionMode execution_mode;
+};
+
+#if DCHECK_IS_ON()
+// Returns whether I/O calls are allowed on the current thread.
+bool GetIOAllowed() {
+ const bool previous_value = ThreadRestrictions::SetIOAllowed(true);
+ ThreadRestrictions::SetIOAllowed(previous_value);
+ return previous_value;
+}
+#endif
+
+// Verify that the current thread priority and I/O restrictions are appropriate
+// to run a Task with |traits|.
+// Note: ExecutionMode is verified inside TestTaskFactory.
+void VerifyTaskEnvironment(const TaskTraits& traits) {
+ EXPECT_EQ(CanUseBackgroundPriorityForSchedulerWorker() &&
+ traits.priority() == TaskPriority::BACKGROUND
+ ? ThreadPriority::BACKGROUND
+ : ThreadPriority::NORMAL,
+ PlatformThread::GetCurrentThreadPriority());
+
+#if DCHECK_IS_ON()
+ // The #if above is required because GetIOAllowed() always returns true when
+ // !DCHECK_IS_ON(), even when |traits| don't allow file I/O.
+ EXPECT_EQ(traits.may_block(), GetIOAllowed());
+#endif
+
+ // Verify that the thread the task is running on is named as expected.
+ const std::string current_thread_name(PlatformThread::GetName());
+ EXPECT_NE(std::string::npos, current_thread_name.find("TaskScheduler"));
+
+ if (current_thread_name.find("SingleThread") != std::string::npos) {
+ // For now, single-threaded background tasks run on their own threads.
+ // TODO(fdoray): Run single-threaded background tasks on foreground workers
+ // on platforms that don't support background thread priority.
+ EXPECT_NE(
+ std::string::npos,
+ current_thread_name.find(traits.priority() == TaskPriority::BACKGROUND
+ ? "Background"
+ : "Foreground"));
+ } else {
+ EXPECT_NE(std::string::npos,
+ current_thread_name.find(
+ CanUseBackgroundPriorityForSchedulerWorker() &&
+ traits.priority() == TaskPriority::BACKGROUND
+ ? "Background"
+ : "Foreground"));
+ }
+ EXPECT_EQ(traits.may_block(),
+ current_thread_name.find("Blocking") != std::string::npos);
+}
+
+void VerifyTaskEnvironmentAndSignalEvent(const TaskTraits& traits,
+ WaitableEvent* event) {
+ DCHECK(event);
+ VerifyTaskEnvironment(traits);
+ event->Signal();
+}
+
+void VerifyTimeAndTaskEnvironmentAndSignalEvent(const TaskTraits& traits,
+ TimeTicks expected_time,
+ WaitableEvent* event) {
+ DCHECK(event);
+ EXPECT_LE(expected_time, TimeTicks::Now());
+ VerifyTaskEnvironment(traits);
+ event->Signal();
+}
+
+scoped_refptr<TaskRunner> CreateTaskRunnerWithTraitsAndExecutionMode(
+ TaskScheduler* scheduler,
+ const TaskTraits& traits,
+ test::ExecutionMode execution_mode,
+ SingleThreadTaskRunnerThreadMode default_single_thread_task_runner_mode =
+ SingleThreadTaskRunnerThreadMode::SHARED) {
+ switch (execution_mode) {
+ case test::ExecutionMode::PARALLEL:
+ return scheduler->CreateTaskRunnerWithTraits(traits);
+ case test::ExecutionMode::SEQUENCED:
+ return scheduler->CreateSequencedTaskRunnerWithTraits(traits);
+ case test::ExecutionMode::SINGLE_THREADED: {
+ return scheduler->CreateSingleThreadTaskRunnerWithTraits(
+ traits, default_single_thread_task_runner_mode);
+ }
+ }
+ ADD_FAILURE() << "Unknown ExecutionMode";
+ return nullptr;
+}
+
+class ThreadPostingTasks : public SimpleThread {
+ public:
+ // Creates a thread that posts Tasks to |scheduler| with |traits| and
+ // |execution_mode|.
+ ThreadPostingTasks(TaskSchedulerImpl* scheduler,
+ const TaskTraits& traits,
+ test::ExecutionMode execution_mode)
+ : SimpleThread("ThreadPostingTasks"),
+ traits_(traits),
+ factory_(CreateTaskRunnerWithTraitsAndExecutionMode(scheduler,
+ traits,
+ execution_mode),
+ execution_mode) {}
+
+ void WaitForAllTasksToRun() { factory_.WaitForAllTasksToRun(); }
+
+ private:
+ void Run() override {
+ EXPECT_FALSE(factory_.task_runner()->RunsTasksInCurrentSequence());
+
+ const size_t kNumTasksPerThread = 150;
+ for (size_t i = 0; i < kNumTasksPerThread; ++i) {
+ factory_.PostTask(test::TestTaskFactory::PostNestedTask::NO,
+ Bind(&VerifyTaskEnvironment, traits_));
+ }
+ }
+
+ const TaskTraits traits_;
+ test::TestTaskFactory factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadPostingTasks);
+};
+
+// Returns a vector with a TraitsExecutionModePair for each valid
+// combination of {ExecutionMode, TaskPriority, MayBlock()}.
+std::vector<TraitsExecutionModePair> GetTraitsExecutionModePairs() {
+ std::vector<TraitsExecutionModePair> params;
+
+ const test::ExecutionMode execution_modes[] = {
+ test::ExecutionMode::PARALLEL, test::ExecutionMode::SEQUENCED,
+ test::ExecutionMode::SINGLE_THREADED};
+
+ for (test::ExecutionMode execution_mode : execution_modes) {
+ for (size_t priority_index = static_cast<size_t>(TaskPriority::LOWEST);
+ priority_index <= static_cast<size_t>(TaskPriority::HIGHEST);
+ ++priority_index) {
+ const TaskPriority priority = static_cast<TaskPriority>(priority_index);
+ params.push_back(TraitsExecutionModePair({priority}, execution_mode));
+ params.push_back(TraitsExecutionModePair({MayBlock()}, execution_mode));
+ }
+ }
+
+ return params;
+}
+
+class TaskSchedulerImplTest
+ : public testing::TestWithParam<TraitsExecutionModePair> {
+ protected:
+ TaskSchedulerImplTest() : scheduler_("Test"), field_trial_list_(nullptr) {}
+
+ void EnableAllTasksUserBlocking() {
+ constexpr char kFieldTrialName[] = "BrowserScheduler";
+ constexpr char kFieldTrialTestGroup[] = "DummyGroup";
+ std::map<std::string, std::string> variation_params;
+ variation_params["AllTasksUserBlocking"] = "true";
+ base::AssociateFieldTrialParams(kFieldTrialName, kFieldTrialTestGroup,
+ variation_params);
+ base::FieldTrialList::CreateFieldTrial(kFieldTrialName,
+ kFieldTrialTestGroup);
+ }
+
+ void set_scheduler_worker_observer(
+ SchedulerWorkerObserver* scheduler_worker_observer) {
+ scheduler_worker_observer_ = scheduler_worker_observer;
+ }
+
+ void StartTaskScheduler() {
+ constexpr TimeDelta kSuggestedReclaimTime = TimeDelta::FromSeconds(30);
+ constexpr int kMaxNumBackgroundThreads = 1;
+ constexpr int kMaxNumBackgroundBlockingThreads = 3;
+ constexpr int kMaxNumForegroundThreads = 4;
+ constexpr int kMaxNumForegroundBlockingThreads = 12;
+
+ scheduler_.Start(
+ {{kMaxNumBackgroundThreads, kSuggestedReclaimTime},
+ {kMaxNumBackgroundBlockingThreads, kSuggestedReclaimTime},
+ {kMaxNumForegroundThreads, kSuggestedReclaimTime},
+ {kMaxNumForegroundBlockingThreads, kSuggestedReclaimTime}},
+ scheduler_worker_observer_);
+ }
+
+ void TearDown() override {
+ if (did_tear_down_)
+ return;
+
+ scheduler_.FlushForTesting();
+ scheduler_.JoinForTesting();
+ did_tear_down_ = true;
+ }
+
+ TaskSchedulerImpl scheduler_;
+
+ private:
+ base::FieldTrialList field_trial_list_;
+ SchedulerWorkerObserver* scheduler_worker_observer_ = nullptr;
+ bool did_tear_down_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerImplTest);
+};
+
+} // namespace
+
+// Verifies that a Task posted via PostDelayedTaskWithTraits with parameterized
+// TaskTraits and no delay runs on a thread with the expected priority and I/O
+// restrictions. The ExecutionMode parameter is ignored by this test.
+TEST_P(TaskSchedulerImplTest, PostDelayedTaskWithTraitsNoDelay) {
+ StartTaskScheduler();
+ WaitableEvent task_ran;
+ scheduler_.PostDelayedTaskWithTraits(
+ FROM_HERE, GetParam().traits,
+ BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetParam().traits,
+ Unretained(&task_ran)),
+ TimeDelta());
+ task_ran.Wait();
+}
+
+// Verifies that a Task posted via PostDelayedTaskWithTraits with parameterized
+// TaskTraits and a non-zero delay runs on a thread with the expected priority
+// and I/O restrictions after the delay expires. The ExecutionMode parameter is
+// ignored by this test.
+TEST_P(TaskSchedulerImplTest, PostDelayedTaskWithTraitsWithDelay) {
+ StartTaskScheduler();
+ WaitableEvent task_ran;
+ scheduler_.PostDelayedTaskWithTraits(
+ FROM_HERE, GetParam().traits,
+ BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetParam().traits,
+ TimeTicks::Now() + TestTimeouts::tiny_timeout(),
+ Unretained(&task_ran)),
+ TestTimeouts::tiny_timeout());
+ task_ran.Wait();
+}
+
+// Verifies that Tasks posted via a TaskRunner with parameterized TaskTraits and
+// ExecutionMode run on a thread with the expected priority and I/O restrictions
+// and respect the characteristics of their ExecutionMode.
+TEST_P(TaskSchedulerImplTest, PostTasksViaTaskRunner) {
+ StartTaskScheduler();
+ test::TestTaskFactory factory(
+ CreateTaskRunnerWithTraitsAndExecutionMode(&scheduler_, GetParam().traits,
+ GetParam().execution_mode),
+ GetParam().execution_mode);
+ EXPECT_FALSE(factory.task_runner()->RunsTasksInCurrentSequence());
+
+ const size_t kNumTasksPerTest = 150;
+ for (size_t i = 0; i < kNumTasksPerTest; ++i) {
+ factory.PostTask(test::TestTaskFactory::PostNestedTask::NO,
+ Bind(&VerifyTaskEnvironment, GetParam().traits));
+ }
+
+ factory.WaitForAllTasksToRun();
+}
+
+// Verifies that a task posted via PostDelayedTaskWithTraits without a delay
+// doesn't run before Start() is called.
+TEST_P(TaskSchedulerImplTest, PostDelayedTaskWithTraitsNoDelayBeforeStart) {
+ WaitableEvent task_running;
+ scheduler_.PostDelayedTaskWithTraits(
+ FROM_HERE, GetParam().traits,
+ BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetParam().traits,
+ Unretained(&task_running)),
+ TimeDelta());
+
+ // Wait a little bit to make sure that the task doesn't run before Start().
+ // Note: This test won't catch a case where the task runs just after the check
+ // and before Start(). However, we expect the test to be flaky if the tested
+ // code allows that to happen.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(task_running.IsSignaled());
+
+ StartTaskScheduler();
+ task_running.Wait();
+}
+
+// Verifies that a task posted via PostDelayedTaskWithTraits with a delay
+// doesn't run before Start() is called.
+TEST_P(TaskSchedulerImplTest, PostDelayedTaskWithTraitsWithDelayBeforeStart) {
+ WaitableEvent task_running;
+ scheduler_.PostDelayedTaskWithTraits(
+ FROM_HERE, GetParam().traits,
+ BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetParam().traits,
+ TimeTicks::Now() + TestTimeouts::tiny_timeout(),
+ Unretained(&task_running)),
+ TestTimeouts::tiny_timeout());
+
+ // Wait a little bit to make sure that the task doesn't run before Start().
+ // Note: This test won't catch a case where the task runs just after the check
+ // and before Start(). However, we expect the test to be flaky if the tested
+ // code allows that to happen.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(task_running.IsSignaled());
+
+ StartTaskScheduler();
+ task_running.Wait();
+}
+
+// Verifies that a task posted via a TaskRunner doesn't run before Start() is
+// called.
+TEST_P(TaskSchedulerImplTest, PostTaskViaTaskRunnerBeforeStart) {
+ WaitableEvent task_running;
+ CreateTaskRunnerWithTraitsAndExecutionMode(&scheduler_, GetParam().traits,
+ GetParam().execution_mode)
+ ->PostTask(FROM_HERE,
+ BindOnce(&VerifyTaskEnvironmentAndSignalEvent,
+ GetParam().traits, Unretained(&task_running)));
+
+ // Wait a little bit to make sure that the task doesn't run before Start().
+ // Note: This test won't catch a case where the task runs just after the check
+ // and before Start(). However, we expect the test to be flaky if the tested
+ // code allows that to happen.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(task_running.IsSignaled());
+
+ StartTaskScheduler();
+
+ // This should not hang if the task runs after Start().
+ task_running.Wait();
+}
+
+// Verify that all tasks posted to a TaskRunner after Start() run in a
+// USER_BLOCKING environment when the AllTasksUserBlocking variation param of
+// the BrowserScheduler experiment is true.
+TEST_P(TaskSchedulerImplTest, AllTasksAreUserBlockingTaskRunner) {
+ EnableAllTasksUserBlocking();
+ StartTaskScheduler();
+
+ WaitableEvent task_running;
+ CreateTaskRunnerWithTraitsAndExecutionMode(&scheduler_, GetParam().traits,
+ GetParam().execution_mode)
+ ->PostTask(FROM_HERE,
+ BindOnce(&VerifyTaskEnvironmentAndSignalEvent,
+ TaskTraits::Override(GetParam().traits,
+ {TaskPriority::USER_BLOCKING}),
+ Unretained(&task_running)));
+ task_running.Wait();
+}
+
+// Verify that all tasks posted via PostDelayedTaskWithTraits() after Start()
+// run in a USER_BLOCKING environment when the AllTasksUserBlocking variation
+// param of the BrowserScheduler experiment is true.
+TEST_P(TaskSchedulerImplTest, AllTasksAreUserBlocking) {
+ EnableAllTasksUserBlocking();
+ StartTaskScheduler();
+
+ WaitableEvent task_running;
+ // Ignore |params.execution_mode| in this test.
+ scheduler_.PostDelayedTaskWithTraits(
+ FROM_HERE, GetParam().traits,
+ BindOnce(&VerifyTaskEnvironmentAndSignalEvent,
+ TaskTraits::Override(GetParam().traits,
+ {TaskPriority::USER_BLOCKING}),
+ Unretained(&task_running)),
+ TimeDelta());
+ task_running.Wait();
+}
+
+// Verifies that FlushAsyncForTesting() calls back correctly for all trait and
+// execution mode pairs.
+TEST_P(TaskSchedulerImplTest, FlushAsyncForTestingSimple) {
+ StartTaskScheduler();
+
+ WaitableEvent unblock_task;
+ CreateTaskRunnerWithTraitsAndExecutionMode(
+ &scheduler_,
+ TaskTraits::Override(GetParam().traits, {WithBaseSyncPrimitives()}),
+ GetParam().execution_mode, SingleThreadTaskRunnerThreadMode::DEDICATED)
+ ->PostTask(FROM_HERE,
+ BindOnce(&WaitableEvent::Wait, Unretained(&unblock_task)));
+
+ WaitableEvent flush_event;
+ scheduler_.FlushAsyncForTesting(
+ BindOnce(&WaitableEvent::Signal, Unretained(&flush_event)));
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(flush_event.IsSignaled());
+
+ unblock_task.Signal();
+
+ flush_event.Wait();
+}
+
+INSTANTIATE_TEST_CASE_P(OneTraitsExecutionModePair,
+ TaskSchedulerImplTest,
+ ::testing::ValuesIn(GetTraitsExecutionModePairs()));
+
+// Spawns threads that simultaneously post Tasks to TaskRunners with various
+// TaskTraits and ExecutionModes. Verifies that each Task runs on a thread with
+// the expected priority and I/O restrictions and respects the characteristics
+// of its ExecutionMode.
+TEST_F(TaskSchedulerImplTest, MultipleTraitsExecutionModePairs) {
+ StartTaskScheduler();
+ std::vector<std::unique_ptr<ThreadPostingTasks>> threads_posting_tasks;
+ for (const auto& traits_execution_mode_pair : GetTraitsExecutionModePairs()) {
+ threads_posting_tasks.push_back(WrapUnique(
+ new ThreadPostingTasks(&scheduler_, traits_execution_mode_pair.traits,
+ traits_execution_mode_pair.execution_mode)));
+ threads_posting_tasks.back()->Start();
+ }
+
+ for (const auto& thread : threads_posting_tasks) {
+ thread->WaitForAllTasksToRun();
+ thread->Join();
+ }
+}
+
+TEST_F(TaskSchedulerImplTest,
+ GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated) {
+ StartTaskScheduler();
+
+ // GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated() does not support
+ // TaskPriority::BACKGROUND.
+ EXPECT_DCHECK_DEATH({
+ scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
+ {TaskPriority::BACKGROUND});
+ });
+ EXPECT_DCHECK_DEATH({
+ scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
+ {MayBlock(), TaskPriority::BACKGROUND});
+ });
+
+ EXPECT_EQ(4, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
+ {TaskPriority::USER_VISIBLE}));
+ EXPECT_EQ(12, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
+ {MayBlock(), TaskPriority::USER_VISIBLE}));
+ EXPECT_EQ(4, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
+ {TaskPriority::USER_BLOCKING}));
+ EXPECT_EQ(12, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
+ {MayBlock(), TaskPriority::USER_BLOCKING}));
+}
+
+// Verify that the RunsTasksInCurrentSequence() method of a SequencedTaskRunner
+// returns false when called from a task that isn't part of the sequence.
+TEST_F(TaskSchedulerImplTest, SequencedRunsTasksInCurrentSequence) {
+ StartTaskScheduler();
+ auto single_thread_task_runner =
+ scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ TaskTraits(), SingleThreadTaskRunnerThreadMode::SHARED);
+ auto sequenced_task_runner =
+ scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits());
+
+ WaitableEvent task_ran;
+ single_thread_task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](scoped_refptr<TaskRunner> sequenced_task_runner,
+ WaitableEvent* task_ran) {
+ EXPECT_FALSE(sequenced_task_runner->RunsTasksInCurrentSequence());
+ task_ran->Signal();
+ },
+ sequenced_task_runner, Unretained(&task_ran)));
+ task_ran.Wait();
+}
+
+// Verify that the RunsTasksInCurrentSequence() method of a
+// SingleThreadTaskRunner returns false when called from a task that isn't part
+// of the sequence.
+TEST_F(TaskSchedulerImplTest, SingleThreadRunsTasksInCurrentSequence) {
+ StartTaskScheduler();
+ auto sequenced_task_runner =
+ scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits());
+ auto single_thread_task_runner =
+ scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ TaskTraits(), SingleThreadTaskRunnerThreadMode::SHARED);
+
+ WaitableEvent task_ran;
+ sequenced_task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](scoped_refptr<TaskRunner> single_thread_task_runner,
+ WaitableEvent* task_ran) {
+ EXPECT_FALSE(
+ single_thread_task_runner->RunsTasksInCurrentSequence());
+ task_ran->Signal();
+ },
+ single_thread_task_runner, Unretained(&task_ran)));
+ task_ran.Wait();
+}
+
+#if defined(OS_WIN)
+TEST_F(TaskSchedulerImplTest, COMSTATaskRunnersRunWithCOMSTA) {
+ StartTaskScheduler();
+ auto com_sta_task_runner = scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ TaskTraits(), SingleThreadTaskRunnerThreadMode::SHARED);
+
+ WaitableEvent task_ran;
+ com_sta_task_runner->PostTask(
+ FROM_HERE, Bind(
+ [](WaitableEvent* task_ran) {
+ win::AssertComApartmentType(win::ComApartmentType::STA);
+ task_ran->Signal();
+ },
+ Unretained(&task_ran)));
+ task_ran.Wait();
+}
+#endif // defined(OS_WIN)
+
+TEST_F(TaskSchedulerImplTest, DelayedTasksNotRunAfterShutdown) {
+ StartTaskScheduler();
+ // As with delayed tasks in general, this is racy. If the task does happen to
+ // run after Shutdown within the timeout, it will fail this test.
+ //
+ // The timeout should be set sufficiently long enough to ensure that the
+ // delayed task did not run. 2x is generally good enough.
+ //
+ // A non-racy way to do this would be to post two sequenced tasks:
+ // 1) Regular Post Task: A WaitableEvent.Wait
+ // 2) Delayed Task: ADD_FAILURE()
+ // and signalling the WaitableEvent after Shutdown() on a different thread
+ // since Shutdown() will block. However, the cost of managing this extra
+ // thread was deemed to be too great for the unlikely race.
+ scheduler_.PostDelayedTaskWithTraits(FROM_HERE, TaskTraits(),
+ BindOnce([]() { ADD_FAILURE(); }),
+ TestTimeouts::tiny_timeout());
+ scheduler_.Shutdown();
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 2);
+}
+
+#if defined(OS_POSIX)
+
+TEST_F(TaskSchedulerImplTest, FileDescriptorWatcherNoOpsAfterShutdown) {
+ StartTaskScheduler();
+
+ int pipes[2];
+ ASSERT_EQ(0, pipe(pipes));
+
+ scoped_refptr<TaskRunner> blocking_task_runner =
+ scheduler_.CreateSequencedTaskRunnerWithTraits(
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN});
+ blocking_task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](int read_fd) {
+ std::unique_ptr<FileDescriptorWatcher::Controller> controller =
+ FileDescriptorWatcher::WatchReadable(
+ read_fd, BindRepeating([]() { NOTREACHED(); }));
+
+ // This test is for components that intentionally leak their
+ // watchers at shutdown. We can't clean |controller| up because its
+ // destructor will assert that it's being called from the correct
+ // sequence. After the task scheduler is shutdown, it is not
+ // possible to run tasks on this sequence.
+ //
+ // Note: Do not inline the controller.release() call into the
+ // ANNOTATE_LEAKING_OBJECT_PTR as the annotation is removed
+ // by the preprocessor in non-LEAK_SANITIZER builds,
+ // effectively breaking this test.
+ ANNOTATE_LEAKING_OBJECT_PTR(controller.get());
+ controller.release();
+ },
+ pipes[0]));
+
+ scheduler_.Shutdown();
+
+ constexpr char kByte = '!';
+ ASSERT_TRUE(WriteFileDescriptor(pipes[1], &kByte, sizeof(kByte)));
+
+ // Give a chance for the file watcher to fire before closing the handles.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+
+ EXPECT_EQ(0, IGNORE_EINTR(close(pipes[0])));
+ EXPECT_EQ(0, IGNORE_EINTR(close(pipes[1])));
+}
+#endif // defined(OS_POSIX)
+
+// Verify that tasks posted on the same sequence access the same values on
+// SequenceLocalStorage, and tasks on different sequences see different values.
+TEST_F(TaskSchedulerImplTest, SequenceLocalStorage) {
+ StartTaskScheduler();
+
+ SequenceLocalStorageSlot<int> slot;
+ auto sequenced_task_runner1 =
+ scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits());
+ auto sequenced_task_runner2 =
+ scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits());
+
+ sequenced_task_runner1->PostTask(
+ FROM_HERE,
+ BindOnce([](SequenceLocalStorageSlot<int>* slot) { slot->Set(11); },
+ &slot));
+
+ sequenced_task_runner1->PostTask(FROM_HERE,
+ BindOnce(
+ [](SequenceLocalStorageSlot<int>* slot) {
+ EXPECT_EQ(slot->Get(), 11);
+ },
+ &slot));
+
+ sequenced_task_runner2->PostTask(FROM_HERE,
+ BindOnce(
+ [](SequenceLocalStorageSlot<int>* slot) {
+ EXPECT_NE(slot->Get(), 11);
+ },
+ &slot));
+
+ scheduler_.FlushForTesting();
+}
+
+TEST_F(TaskSchedulerImplTest, FlushAsyncNoTasks) {
+ StartTaskScheduler();
+ bool called_back = false;
+ scheduler_.FlushAsyncForTesting(
+ BindOnce([](bool* called_back) { *called_back = true; },
+ Unretained(&called_back)));
+ EXPECT_TRUE(called_back);
+}
+
+namespace {
+
+// Verifies that |query| is found on the current stack. Ignores failures if this
+// configuration doesn't have symbols.
+void VerifyHasStringOnStack(const std::string& query) {
+ const std::string stack = debug::StackTrace().ToString();
+ SCOPED_TRACE(stack);
+ const bool found_on_stack = stack.find(query) != std::string::npos;
+ const bool stack_has_symbols =
+ stack.find("SchedulerWorker") != std::string::npos;
+ EXPECT_TRUE(found_on_stack || !stack_has_symbols) << query;
+}
+
+} // namespace
+
+#if defined(OS_POSIX)
+// Many POSIX bots flakily crash on |debug::StackTrace().ToString()|,
+// https://crbug.com/840429.
+#define MAYBE_IdentifiableStacks DISABLED_IdentifiableStacks
+#elif defined(OS_WIN) && \
+ (defined(ADDRESS_SANITIZER) || BUILDFLAG(CFI_CAST_CHECK))
+// Hangs on WinASan and WinCFI (grabbing StackTrace() too slow?),
+// https://crbug.com/845010#c7.
+#define MAYBE_IdentifiableStacks DISABLED_IdentifiableStacks
+#else
+#define MAYBE_IdentifiableStacks IdentifiableStacks
+#endif
+
+// Integration test that verifies that workers have a frame on their stacks
+// which easily identifies the type of worker (useful to diagnose issues from
+// logs without memory dumps).
+TEST_F(TaskSchedulerImplTest, MAYBE_IdentifiableStacks) {
+ StartTaskScheduler();
+
+ scheduler_.CreateSequencedTaskRunnerWithTraits({})->PostTask(
+ FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunPooledWorker"));
+ scheduler_.CreateSequencedTaskRunnerWithTraits({TaskPriority::BACKGROUND})
+ ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack,
+ "RunBackgroundPooledWorker"));
+
+ scheduler_
+ .CreateSingleThreadTaskRunnerWithTraits(
+ {}, SingleThreadTaskRunnerThreadMode::SHARED)
+ ->PostTask(FROM_HERE,
+ BindOnce(&VerifyHasStringOnStack, "RunSharedWorker"));
+ scheduler_
+ .CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::SHARED)
+ ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack,
+ "RunBackgroundSharedWorker"));
+
+ scheduler_
+ .CreateSingleThreadTaskRunnerWithTraits(
+ {}, SingleThreadTaskRunnerThreadMode::DEDICATED)
+ ->PostTask(FROM_HERE,
+ BindOnce(&VerifyHasStringOnStack, "RunDedicatedWorker"));
+ scheduler_
+ .CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND},
+ SingleThreadTaskRunnerThreadMode::DEDICATED)
+ ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack,
+ "RunBackgroundDedicatedWorker"));
+
+#if defined(OS_WIN)
+ scheduler_
+ .CreateCOMSTATaskRunnerWithTraits(
+ {}, SingleThreadTaskRunnerThreadMode::SHARED)
+ ->PostTask(FROM_HERE,
+ BindOnce(&VerifyHasStringOnStack, "RunSharedCOMWorker"));
+ scheduler_
+ .CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::SHARED)
+ ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack,
+ "RunBackgroundSharedCOMWorker"));
+
+ scheduler_
+ .CreateCOMSTATaskRunnerWithTraits(
+ {}, SingleThreadTaskRunnerThreadMode::DEDICATED)
+ ->PostTask(FROM_HERE,
+ BindOnce(&VerifyHasStringOnStack, "RunDedicatedCOMWorker"));
+ scheduler_
+ .CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND},
+ SingleThreadTaskRunnerThreadMode::DEDICATED)
+ ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack,
+ "RunBackgroundDedicatedCOMWorker"));
+#endif // defined(OS_WIN)
+
+ scheduler_.FlushForTesting();
+}
+
+TEST_F(TaskSchedulerImplTest, SchedulerWorkerObserver) {
+ testing::StrictMock<test::MockSchedulerWorkerObserver> observer;
+ set_scheduler_worker_observer(&observer);
+
+ // A worker should be created for each pool. After that, 8 threads should be
+ // created for single-threaded work (16 on Windows).
+ const int kExpectedNumPoolWorkers =
+ CanUseBackgroundPriorityForSchedulerWorker() ? 4 : 2;
+#if defined(OS_WIN)
+ const int kExpectedNumSingleThreadedWorkers = 16;
+#else
+ const int kExpectedNumSingleThreadedWorkers = 8;
+#endif
+ const int kExpectedNumWorkers =
+ kExpectedNumPoolWorkers + kExpectedNumSingleThreadedWorkers;
+
+ EXPECT_CALL(observer, OnSchedulerWorkerMainEntry())
+ .Times(kExpectedNumWorkers);
+
+ StartTaskScheduler();
+
+ std::vector<scoped_refptr<SingleThreadTaskRunner>> task_runners;
+
+ task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::SHARED));
+ task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND, MayBlock()},
+ SingleThreadTaskRunnerThreadMode::SHARED));
+ task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::SHARED));
+ task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::USER_BLOCKING, MayBlock()},
+ SingleThreadTaskRunnerThreadMode::SHARED));
+
+ task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::DEDICATED));
+ task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND, MayBlock()},
+ SingleThreadTaskRunnerThreadMode::DEDICATED));
+ task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::USER_BLOCKING},
+ SingleThreadTaskRunnerThreadMode::DEDICATED));
+ task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits(
+ {TaskPriority::USER_BLOCKING, MayBlock()},
+ SingleThreadTaskRunnerThreadMode::DEDICATED));
+
+#if defined(OS_WIN)
+ task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::SHARED));
+ task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND, MayBlock()},
+ SingleThreadTaskRunnerThreadMode::SHARED));
+ task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::SHARED));
+ task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::USER_BLOCKING, MayBlock()},
+ SingleThreadTaskRunnerThreadMode::SHARED));
+
+ task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::DEDICATED));
+ task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::BACKGROUND, MayBlock()},
+ SingleThreadTaskRunnerThreadMode::DEDICATED));
+ task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::USER_BLOCKING},
+ SingleThreadTaskRunnerThreadMode::DEDICATED));
+ task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits(
+ {TaskPriority::USER_BLOCKING, MayBlock()},
+ SingleThreadTaskRunnerThreadMode::DEDICATED));
+#endif
+
+ for (auto& task_runner : task_runners)
+ task_runner->PostTask(FROM_HERE, DoNothing());
+
+ EXPECT_CALL(observer, OnSchedulerWorkerMainExit()).Times(kExpectedNumWorkers);
+
+ // Allow single-threaded workers to be released.
+ task_runners.clear();
+
+ TearDown();
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/task_tracker_posix_unittest.cc b/base/task_scheduler/task_tracker_posix_unittest.cc
new file mode 100644
index 0000000000..3ca753386b
--- /dev/null
+++ b/base/task_scheduler/task_tracker_posix_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/task_tracker_posix.h"
+
+#include <unistd.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_descriptor_watcher_posix.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/run_loop.h"
+#include "base/sequence_token.h"
+#include "base/task_scheduler/task.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/test/null_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+class TaskSchedulerTaskTrackerPosixTest : public testing::Test {
+ public:
+ TaskSchedulerTaskTrackerPosixTest() : service_thread_("ServiceThread") {
+ Thread::Options service_thread_options;
+ service_thread_options.message_loop_type = MessageLoop::TYPE_IO;
+ service_thread_.StartWithOptions(service_thread_options);
+ tracker_.set_watch_file_descriptor_message_loop(
+ static_cast<MessageLoopForIO*>(service_thread_.message_loop()));
+ }
+
+ protected:
+ Thread service_thread_;
+ TaskTrackerPosix tracker_ = {"Test"};
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerTaskTrackerPosixTest);
+};
+
+} // namespace
+
+// Verify that TaskTrackerPosix runs a Task it receives.
+TEST_F(TaskSchedulerTaskTrackerPosixTest, RunTask) {
+ bool did_run = false;
+ Task task(FROM_HERE,
+ Bind([](bool* did_run) { *did_run = true; }, Unretained(&did_run)),
+ TaskTraits(), TimeDelta());
+
+ EXPECT_TRUE(tracker_.WillPostTask(&task));
+
+ auto sequence = test::CreateSequenceWithTask(std::move(task));
+ EXPECT_EQ(sequence, tracker_.WillScheduleSequence(sequence, nullptr));
+ // Expect RunAndPopNextTask to return nullptr since |sequence| is empty after
+ // popping a task from it.
+ EXPECT_FALSE(tracker_.RunAndPopNextTask(sequence, nullptr));
+
+ EXPECT_TRUE(did_run);
+}
+
+// Verify that FileDescriptorWatcher::WatchReadable() can be called from a task
+// running in TaskTrackerPosix without a crash.
+TEST_F(TaskSchedulerTaskTrackerPosixTest, FileDescriptorWatcher) {
+ int fds[2];
+ ASSERT_EQ(0, pipe(fds));
+ Task task(FROM_HERE,
+ Bind(IgnoreResult(&FileDescriptorWatcher::WatchReadable), fds[0],
+ DoNothing()),
+ TaskTraits(), TimeDelta());
+ // FileDescriptorWatcher::WatchReadable needs a SequencedTaskRunnerHandle.
+ task.sequenced_task_runner_ref = MakeRefCounted<NullTaskRunner>();
+
+ EXPECT_TRUE(tracker_.WillPostTask(&task));
+
+ auto sequence = test::CreateSequenceWithTask(std::move(task));
+ EXPECT_EQ(sequence, tracker_.WillScheduleSequence(sequence, nullptr));
+ // Expect RunAndPopNextTask to return nullptr since |sequence| is empty after
+ // popping a task from it.
+ EXPECT_FALSE(tracker_.RunAndPopNextTask(sequence, nullptr));
+
+ // Join the service thread to make sure that the read watch is registered and
+ // unregistered before file descriptors are closed.
+ service_thread_.Stop();
+
+ EXPECT_EQ(0, IGNORE_EINTR(close(fds[0])));
+ EXPECT_EQ(0, IGNORE_EINTR(close(fds[1])));
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/task_tracker_unittest.cc b/base/task_scheduler/task_tracker_unittest.cc
new file mode 100644
index 0000000000..159c9a9674
--- /dev/null
+++ b/base/task_scheduler/task_tracker_unittest.cc
@@ -0,0 +1,1364 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/task_tracker.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/sequence_token.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/atomic_flag.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/scheduler_lock.h"
+#include "base/task_scheduler/task.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/test/gtest_util.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+constexpr size_t kLoadTestNumIterations = 75;
+
+class MockCanScheduleSequenceObserver : public CanScheduleSequenceObserver {
+ public:
+ void OnCanScheduleSequence(scoped_refptr<Sequence> sequence) override {
+ MockOnCanScheduleSequence(sequence.get());
+ }
+
+ MOCK_METHOD1(MockOnCanScheduleSequence, void(Sequence*));
+};
+
+// Invokes a closure asynchronously.
+class CallbackThread : public SimpleThread {
+ public:
+ explicit CallbackThread(const Closure& closure)
+ : SimpleThread("CallbackThread"), closure_(closure) {}
+
+ // Returns true once the callback returns.
+ bool has_returned() { return has_returned_.IsSet(); }
+
+ private:
+ void Run() override {
+ closure_.Run();
+ has_returned_.Set();
+ }
+
+ const Closure closure_;
+ AtomicFlag has_returned_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackThread);
+};
+
+class ThreadPostingAndRunningTask : public SimpleThread {
+ public:
+ enum class Action {
+ WILL_POST,
+ RUN,
+ WILL_POST_AND_RUN,
+ };
+
+ ThreadPostingAndRunningTask(TaskTracker* tracker,
+ Task* task,
+ Action action,
+ bool expect_post_succeeds)
+ : SimpleThread("ThreadPostingAndRunningTask"),
+ tracker_(tracker),
+ owned_task_(FROM_HERE, OnceClosure(), TaskTraits(), TimeDelta()),
+ task_(task),
+ action_(action),
+ expect_post_succeeds_(expect_post_succeeds) {
+ EXPECT_TRUE(task_);
+
+ // Ownership of the Task is required to run it.
+ EXPECT_NE(Action::RUN, action_);
+ EXPECT_NE(Action::WILL_POST_AND_RUN, action_);
+ }
+
+ ThreadPostingAndRunningTask(TaskTracker* tracker,
+ Task task,
+ Action action,
+ bool expect_post_succeeds)
+ : SimpleThread("ThreadPostingAndRunningTask"),
+ tracker_(tracker),
+ owned_task_(std::move(task)),
+ task_(&owned_task_),
+ action_(action),
+ expect_post_succeeds_(expect_post_succeeds) {
+ EXPECT_TRUE(owned_task_.task);
+ }
+
+ private:
+ void Run() override {
+ bool post_succeeded = true;
+ if (action_ == Action::WILL_POST || action_ == Action::WILL_POST_AND_RUN) {
+ post_succeeded = tracker_->WillPostTask(task_);
+ EXPECT_EQ(expect_post_succeeds_, post_succeeded);
+ }
+ if (post_succeeded &&
+ (action_ == Action::RUN || action_ == Action::WILL_POST_AND_RUN)) {
+ EXPECT_TRUE(owned_task_.task);
+
+ testing::StrictMock<MockCanScheduleSequenceObserver>
+ never_notified_observer;
+ auto sequence = tracker_->WillScheduleSequence(
+ test::CreateSequenceWithTask(std::move(owned_task_)),
+ &never_notified_observer);
+ ASSERT_TRUE(sequence);
+ // Expect RunAndPopNextTask to return nullptr since |sequence| is empty
+ // after popping a task from it.
+ EXPECT_FALSE(tracker_->RunAndPopNextTask(std::move(sequence),
+ &never_notified_observer));
+ }
+ }
+
+ TaskTracker* const tracker_;
+ Task owned_task_;
+ Task* task_;
+ const Action action_;
+ const bool expect_post_succeeds_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadPostingAndRunningTask);
+};
+
+class ScopedSetSingletonAllowed {
+ public:
+ ScopedSetSingletonAllowed(bool singleton_allowed)
+ : previous_value_(
+ ThreadRestrictions::SetSingletonAllowed(singleton_allowed)) {}
+ ~ScopedSetSingletonAllowed() {
+ ThreadRestrictions::SetSingletonAllowed(previous_value_);
+ }
+
+ private:
+ const bool previous_value_;
+};
+
+class TaskSchedulerTaskTrackerTest
+ : public testing::TestWithParam<TaskShutdownBehavior> {
+ protected:
+ TaskSchedulerTaskTrackerTest() = default;
+
+ // Creates a task with |shutdown_behavior|.
+ Task CreateTask(TaskShutdownBehavior shutdown_behavior) {
+ return Task(
+ FROM_HERE,
+ Bind(&TaskSchedulerTaskTrackerTest::RunTaskCallback, Unretained(this)),
+ TaskTraits(shutdown_behavior), TimeDelta());
+ }
+
+ void DispatchAndRunTaskWithTracker(Task task) {
+ auto sequence = tracker_.WillScheduleSequence(
+ test::CreateSequenceWithTask(std::move(task)),
+ &never_notified_observer_);
+ ASSERT_TRUE(sequence);
+ tracker_.RunAndPopNextTask(std::move(sequence), &never_notified_observer_);
+ }
+
+ // Calls tracker_->Shutdown() on a new thread. When this returns, Shutdown()
+ // method has been entered on the new thread, but it hasn't necessarily
+ // returned.
+ void CallShutdownAsync() {
+ ASSERT_FALSE(thread_calling_shutdown_);
+ thread_calling_shutdown_.reset(new CallbackThread(
+ Bind(&TaskTracker::Shutdown, Unretained(&tracker_))));
+ thread_calling_shutdown_->Start();
+ while (!tracker_.HasShutdownStarted())
+ PlatformThread::YieldCurrentThread();
+ }
+
+ void WaitForAsyncIsShutdownComplete() {
+ ASSERT_TRUE(thread_calling_shutdown_);
+ thread_calling_shutdown_->Join();
+ EXPECT_TRUE(thread_calling_shutdown_->has_returned());
+ EXPECT_TRUE(tracker_.IsShutdownComplete());
+ }
+
+ void VerifyAsyncShutdownInProgress() {
+ ASSERT_TRUE(thread_calling_shutdown_);
+ EXPECT_FALSE(thread_calling_shutdown_->has_returned());
+ EXPECT_TRUE(tracker_.HasShutdownStarted());
+ EXPECT_FALSE(tracker_.IsShutdownComplete());
+ }
+
+ // Calls tracker_->FlushForTesting() on a new thread.
+ void CallFlushFromAnotherThread() {
+ ASSERT_FALSE(thread_calling_flush_);
+ thread_calling_flush_.reset(new CallbackThread(
+ Bind(&TaskTracker::FlushForTesting, Unretained(&tracker_))));
+ thread_calling_flush_->Start();
+ }
+
+ void WaitForAsyncFlushReturned() {
+ ASSERT_TRUE(thread_calling_flush_);
+ thread_calling_flush_->Join();
+ EXPECT_TRUE(thread_calling_flush_->has_returned());
+ }
+
+ void VerifyAsyncFlushInProgress() {
+ ASSERT_TRUE(thread_calling_flush_);
+ EXPECT_FALSE(thread_calling_flush_->has_returned());
+ }
+
+ size_t NumTasksExecuted() {
+ AutoSchedulerLock auto_lock(lock_);
+ return num_tasks_executed_;
+ }
+
+ TaskTracker tracker_ = {"Test"};
+ testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer_;
+
+ private:
+ void RunTaskCallback() {
+ AutoSchedulerLock auto_lock(lock_);
+ ++num_tasks_executed_;
+ }
+
+ std::unique_ptr<CallbackThread> thread_calling_shutdown_;
+ std::unique_ptr<CallbackThread> thread_calling_flush_;
+
+ // Synchronizes accesses to |num_tasks_executed_|.
+ SchedulerLock lock_;
+
+ size_t num_tasks_executed_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerTaskTrackerTest);
+};
+
+#define WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED() \
+ do { \
+ SCOPED_TRACE(""); \
+ WaitForAsyncIsShutdownComplete(); \
+ } while (false)
+
+#define VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS() \
+ do { \
+ SCOPED_TRACE(""); \
+ VerifyAsyncShutdownInProgress(); \
+ } while (false)
+
+#define WAIT_FOR_ASYNC_FLUSH_RETURNED() \
+ do { \
+ SCOPED_TRACE(""); \
+ WaitForAsyncFlushReturned(); \
+ } while (false)
+
+#define VERIFY_ASYNC_FLUSH_IN_PROGRESS() \
+ do { \
+ SCOPED_TRACE(""); \
+ VerifyAsyncFlushInProgress(); \
+ } while (false)
+
+} // namespace
+
+TEST_P(TaskSchedulerTaskTrackerTest, WillPostAndRunBeforeShutdown) {
+ Task task(CreateTask(GetParam()));
+
+ // Inform |task_tracker_| that |task| will be posted.
+ EXPECT_TRUE(tracker_.WillPostTask(&task));
+
+ // Run the task.
+ EXPECT_EQ(0U, NumTasksExecuted());
+
+ DispatchAndRunTaskWithTracker(std::move(task));
+ EXPECT_EQ(1U, NumTasksExecuted());
+
+ // Shutdown() shouldn't block.
+ tracker_.Shutdown();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, WillPostAndRunLongTaskBeforeShutdown) {
+ // Create a task that signals |task_running| and blocks until |task_barrier|
+ // is signaled.
+ WaitableEvent task_running(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent task_barrier(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ Task blocked_task(
+ FROM_HERE,
+ Bind(
+ [](WaitableEvent* task_running, WaitableEvent* task_barrier) {
+ task_running->Signal();
+ task_barrier->Wait();
+ },
+ Unretained(&task_running), Unretained(&task_barrier)),
+ TaskTraits(WithBaseSyncPrimitives(), GetParam()), TimeDelta());
+
+ // Inform |task_tracker_| that |blocked_task| will be posted.
+ EXPECT_TRUE(tracker_.WillPostTask(&blocked_task));
+
+ // Create a thread to run the task. Wait until the task starts running.
+ ThreadPostingAndRunningTask thread_running_task(
+ &tracker_, std::move(blocked_task),
+ ThreadPostingAndRunningTask::Action::RUN, false);
+ thread_running_task.Start();
+ task_running.Wait();
+
+ // Initiate shutdown after the task has started to run.
+ CallShutdownAsync();
+
+ if (GetParam() == TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN) {
+ // Shutdown should complete even with a CONTINUE_ON_SHUTDOWN in progress.
+ WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED();
+ } else {
+ // Shutdown should block with any non CONTINUE_ON_SHUTDOWN task in progress.
+ VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS();
+ }
+
+ // Unblock the task.
+ task_barrier.Signal();
+ thread_running_task.Join();
+
+ // Shutdown should now complete for a non CONTINUE_ON_SHUTDOWN task.
+ if (GetParam() != TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN)
+ WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, WillPostBeforeShutdownRunDuringShutdown) {
+ // Inform |task_tracker_| that a task will be posted.
+ Task task(CreateTask(GetParam()));
+ EXPECT_TRUE(tracker_.WillPostTask(&task));
+
+ // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to
+ // block shutdown.
+ Task block_shutdown_task(CreateTask(TaskShutdownBehavior::BLOCK_SHUTDOWN));
+ EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task));
+
+ // Call Shutdown() asynchronously.
+ CallShutdownAsync();
+ VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS();
+
+ // Try to run |task|. It should only run it it's BLOCK_SHUTDOWN. Otherwise it
+ // should be discarded.
+ EXPECT_EQ(0U, NumTasksExecuted());
+ const bool should_run = GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN;
+
+ DispatchAndRunTaskWithTracker(std::move(task));
+ EXPECT_EQ(should_run ? 1U : 0U, NumTasksExecuted());
+ VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS();
+
+ // Unblock shutdown by running the remaining BLOCK_SHUTDOWN task.
+ DispatchAndRunTaskWithTracker(std::move(block_shutdown_task));
+ EXPECT_EQ(should_run ? 2U : 1U, NumTasksExecuted());
+ WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, WillPostBeforeShutdownRunAfterShutdown) {
+ // Inform |task_tracker_| that a task will be posted.
+ Task task(CreateTask(GetParam()));
+ EXPECT_TRUE(tracker_.WillPostTask(&task));
+
+ // Call Shutdown() asynchronously.
+ CallShutdownAsync();
+ EXPECT_EQ(0U, NumTasksExecuted());
+
+ if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) {
+ VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS();
+
+ // Run the task to unblock shutdown.
+ DispatchAndRunTaskWithTracker(std::move(task));
+ EXPECT_EQ(1U, NumTasksExecuted());
+ WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED();
+
+ // It is not possible to test running a BLOCK_SHUTDOWN task posted before
+ // shutdown after shutdown because Shutdown() won't return if there are
+ // pending BLOCK_SHUTDOWN tasks.
+ } else {
+ WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED();
+
+ // The task shouldn't be allowed to run after shutdown.
+ DispatchAndRunTaskWithTracker(std::move(task));
+ EXPECT_EQ(0U, NumTasksExecuted());
+ }
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, WillPostAndRunDuringShutdown) {
+ // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to
+ // block shutdown.
+ Task block_shutdown_task(CreateTask(TaskShutdownBehavior::BLOCK_SHUTDOWN));
+ EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task));
+
+ // Call Shutdown() asynchronously.
+ CallShutdownAsync();
+ VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS();
+
+ if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) {
+ // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted.
+ Task task(CreateTask(GetParam()));
+ EXPECT_TRUE(tracker_.WillPostTask(&task));
+
+ // Run the BLOCK_SHUTDOWN task.
+ EXPECT_EQ(0U, NumTasksExecuted());
+ DispatchAndRunTaskWithTracker(std::move(task));
+ EXPECT_EQ(1U, NumTasksExecuted());
+ } else {
+ // It shouldn't be allowed to post a non BLOCK_SHUTDOWN task.
+ Task task(CreateTask(GetParam()));
+ EXPECT_FALSE(tracker_.WillPostTask(&task));
+
+ // Don't try to run the task, because it wasn't allowed to be posted.
+ }
+
+ // Unblock shutdown by running |block_shutdown_task|.
+ VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS();
+ DispatchAndRunTaskWithTracker(std::move(block_shutdown_task));
+ EXPECT_EQ(GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN ? 2U : 1U,
+ NumTasksExecuted());
+ WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, WillPostAfterShutdown) {
+ tracker_.Shutdown();
+
+ Task task(CreateTask(GetParam()));
+
+ // |task_tracker_| shouldn't allow a task to be posted after shutdown.
+ EXPECT_FALSE(tracker_.WillPostTask(&task));
+}
+
+// Verify that BLOCK_SHUTDOWN and SKIP_ON_SHUTDOWN tasks can
+// AssertSingletonAllowed() but CONTINUE_ON_SHUTDOWN tasks can't.
+TEST_P(TaskSchedulerTaskTrackerTest, SingletonAllowed) {
+ const bool can_use_singletons =
+ (GetParam() != TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN);
+
+ Task task(FROM_HERE, BindOnce(&ThreadRestrictions::AssertSingletonAllowed),
+ TaskTraits(GetParam()), TimeDelta());
+ EXPECT_TRUE(tracker_.WillPostTask(&task));
+
+ // Set the singleton allowed bit to the opposite of what it is expected to be
+ // when |tracker| runs |task| to verify that |tracker| actually sets the
+ // correct value.
+ ScopedSetSingletonAllowed scoped_singleton_allowed(!can_use_singletons);
+
+ // Running the task should fail iff the task isn't allowed to use singletons.
+ if (can_use_singletons) {
+ DispatchAndRunTaskWithTracker(std::move(task));
+ } else {
+ EXPECT_DCHECK_DEATH({ DispatchAndRunTaskWithTracker(std::move(task)); });
+ }
+}
+
+// Verify that AssertIOAllowed() succeeds only for a MayBlock() task.
+TEST_P(TaskSchedulerTaskTrackerTest, IOAllowed) {
+ // Unset the IO allowed bit. Expect TaskTracker to set it before running a
+ // task with the MayBlock() trait.
+ ThreadRestrictions::SetIOAllowed(false);
+ Task task_with_may_block(FROM_HERE, Bind([]() {
+ // Shouldn't fail.
+ AssertBlockingAllowed();
+ }),
+ TaskTraits(MayBlock(), GetParam()), TimeDelta());
+ EXPECT_TRUE(tracker_.WillPostTask(&task_with_may_block));
+ DispatchAndRunTaskWithTracker(std::move(task_with_may_block));
+
+ // Set the IO allowed bit. Expect TaskTracker to unset it before running a
+ // task without the MayBlock() trait.
+ ThreadRestrictions::SetIOAllowed(true);
+ Task task_without_may_block(
+ FROM_HERE,
+ Bind([]() { EXPECT_DCHECK_DEATH({ AssertBlockingAllowed(); }); }),
+ TaskTraits(GetParam()), TimeDelta());
+ EXPECT_TRUE(tracker_.WillPostTask(&task_without_may_block));
+ DispatchAndRunTaskWithTracker(std::move(task_without_may_block));
+}
+
+static void RunTaskRunnerHandleVerificationTask(TaskTracker* tracker,
+ Task verify_task) {
+ // Pretend |verify_task| is posted to respect TaskTracker's contract.
+ EXPECT_TRUE(tracker->WillPostTask(&verify_task));
+
+ // Confirm that the test conditions are right (no TaskRunnerHandles set
+ // already).
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+
+ testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer;
+ auto sequence = tracker->WillScheduleSequence(
+ test::CreateSequenceWithTask(std::move(verify_task)),
+ &never_notified_observer);
+ ASSERT_TRUE(sequence);
+ tracker->RunAndPopNextTask(std::move(sequence), &never_notified_observer);
+
+ // TaskRunnerHandle state is reset outside of task's scope.
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+}
+
+static void VerifyNoTaskRunnerHandle() {
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, TaskRunnerHandleIsNotSetOnParallel) {
+ // Create a task that will verify that TaskRunnerHandles are not set in its
+ // scope per no TaskRunner ref being set to it.
+ Task verify_task(FROM_HERE, BindOnce(&VerifyNoTaskRunnerHandle),
+ TaskTraits(GetParam()), TimeDelta());
+
+ RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task));
+}
+
+static void VerifySequencedTaskRunnerHandle(
+ const SequencedTaskRunner* expected_task_runner) {
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+ EXPECT_EQ(expected_task_runner, SequencedTaskRunnerHandle::Get());
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest,
+ SequencedTaskRunnerHandleIsSetOnSequenced) {
+ scoped_refptr<SequencedTaskRunner> test_task_runner(new TestSimpleTaskRunner);
+
+ // Create a task that will verify that SequencedTaskRunnerHandle is properly
+ // set to |test_task_runner| in its scope per |sequenced_task_runner_ref|
+ // being set to it.
+ Task verify_task(FROM_HERE,
+ BindOnce(&VerifySequencedTaskRunnerHandle,
+ Unretained(test_task_runner.get())),
+ TaskTraits(GetParam()), TimeDelta());
+ verify_task.sequenced_task_runner_ref = test_task_runner;
+
+ RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task));
+}
+
+static void VerifyThreadTaskRunnerHandle(
+ const SingleThreadTaskRunner* expected_task_runner) {
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ // SequencedTaskRunnerHandle inherits ThreadTaskRunnerHandle for thread.
+ EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+ EXPECT_EQ(expected_task_runner, ThreadTaskRunnerHandle::Get());
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest,
+ ThreadTaskRunnerHandleIsSetOnSingleThreaded) {
+ scoped_refptr<SingleThreadTaskRunner> test_task_runner(
+ new TestSimpleTaskRunner);
+
+ // Create a task that will verify that ThreadTaskRunnerHandle is properly set
+ // to |test_task_runner| in its scope per |single_thread_task_runner_ref|
+ // being set on it.
+ Task verify_task(FROM_HERE,
+ BindOnce(&VerifyThreadTaskRunnerHandle,
+ Unretained(test_task_runner.get())),
+ TaskTraits(GetParam()), TimeDelta());
+ verify_task.single_thread_task_runner_ref = test_task_runner;
+
+ RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task));
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, FlushPendingDelayedTask) {
+ Task delayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta::FromDays(1));
+ tracker_.WillPostTask(&delayed_task);
+ // FlushForTesting() should return even if the delayed task didn't run.
+ tracker_.FlushForTesting();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, FlushAsyncForTestingPendingDelayedTask) {
+ Task delayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta::FromDays(1));
+ tracker_.WillPostTask(&delayed_task);
+ // FlushAsyncForTesting() should callback even if the delayed task didn't run.
+ bool called_back = false;
+ tracker_.FlushAsyncForTesting(
+ BindOnce([](bool* called_back) { *called_back = true; },
+ Unretained(&called_back)));
+ EXPECT_TRUE(called_back);
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, FlushPendingUndelayedTask) {
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushForTesting() shouldn't return before the undelayed task runs.
+ CallFlushFromAnotherThread();
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ VERIFY_ASYNC_FLUSH_IN_PROGRESS();
+
+ // FlushForTesting() should return after the undelayed task runs.
+ DispatchAndRunTaskWithTracker(std::move(undelayed_task));
+ WAIT_FOR_ASYNC_FLUSH_RETURNED();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, FlushAsyncForTestingPendingUndelayedTask) {
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushAsyncForTesting() shouldn't callback before the undelayed task runs.
+ WaitableEvent event;
+ tracker_.FlushAsyncForTesting(
+ BindOnce(&WaitableEvent::Signal, Unretained(&event)));
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(event.IsSignaled());
+
+ // FlushAsyncForTesting() should callback after the undelayed task runs.
+ DispatchAndRunTaskWithTracker(std::move(undelayed_task));
+ event.Wait();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, PostTaskDuringFlush) {
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushForTesting() shouldn't return before the undelayed task runs.
+ CallFlushFromAnotherThread();
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ VERIFY_ASYNC_FLUSH_IN_PROGRESS();
+
+ // Simulate posting another undelayed task.
+ Task other_undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&other_undelayed_task);
+
+ // Run the first undelayed task.
+ DispatchAndRunTaskWithTracker(std::move(undelayed_task));
+
+ // FlushForTesting() shouldn't return before the second undelayed task runs.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ VERIFY_ASYNC_FLUSH_IN_PROGRESS();
+
+ // FlushForTesting() should return after the second undelayed task runs.
+ DispatchAndRunTaskWithTracker(std::move(other_undelayed_task));
+ WAIT_FOR_ASYNC_FLUSH_RETURNED();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, PostTaskDuringFlushAsyncForTesting) {
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushAsyncForTesting() shouldn't callback before the undelayed task runs.
+ WaitableEvent event;
+ tracker_.FlushAsyncForTesting(
+ BindOnce(&WaitableEvent::Signal, Unretained(&event)));
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(event.IsSignaled());
+
+ // Simulate posting another undelayed task.
+ Task other_undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&other_undelayed_task);
+
+ // Run the first undelayed task.
+ DispatchAndRunTaskWithTracker(std::move(undelayed_task));
+
+ // FlushAsyncForTesting() shouldn't callback before the second undelayed task
+ // runs.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(event.IsSignaled());
+
+ // FlushAsyncForTesting() should callback after the second undelayed task
+ // runs.
+ DispatchAndRunTaskWithTracker(std::move(other_undelayed_task));
+ event.Wait();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, RunDelayedTaskDuringFlush) {
+ // Simulate posting a delayed and an undelayed task.
+ Task delayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta::FromDays(1));
+ tracker_.WillPostTask(&delayed_task);
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushForTesting() shouldn't return before the undelayed task runs.
+ CallFlushFromAnotherThread();
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ VERIFY_ASYNC_FLUSH_IN_PROGRESS();
+
+ // Run the delayed task.
+ DispatchAndRunTaskWithTracker(std::move(delayed_task));
+
+ // FlushForTesting() shouldn't return since there is still a pending undelayed
+ // task.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ VERIFY_ASYNC_FLUSH_IN_PROGRESS();
+
+ // Run the undelayed task.
+ DispatchAndRunTaskWithTracker(std::move(undelayed_task));
+
+ // FlushForTesting() should now return.
+ WAIT_FOR_ASYNC_FLUSH_RETURNED();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, RunDelayedTaskDuringFlushAsyncForTesting) {
+ // Simulate posting a delayed and an undelayed task.
+ Task delayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta::FromDays(1));
+ tracker_.WillPostTask(&delayed_task);
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushAsyncForTesting() shouldn't callback before the undelayed task runs.
+ WaitableEvent event;
+ tracker_.FlushAsyncForTesting(
+ BindOnce(&WaitableEvent::Signal, Unretained(&event)));
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(event.IsSignaled());
+
+ // Run the delayed task.
+ DispatchAndRunTaskWithTracker(std::move(delayed_task));
+
+ // FlushAsyncForTesting() shouldn't callback since there is still a pending
+ // undelayed task.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(event.IsSignaled());
+
+ // Run the undelayed task.
+ DispatchAndRunTaskWithTracker(std::move(undelayed_task));
+
+ // FlushAsyncForTesting() should now callback.
+ event.Wait();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, FlushAfterShutdown) {
+ if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN)
+ return;
+
+ // Simulate posting a task.
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // Shutdown() should return immediately since there are no pending
+ // BLOCK_SHUTDOWN tasks.
+ tracker_.Shutdown();
+
+ // FlushForTesting() should return immediately after shutdown, even if an
+ // undelayed task hasn't run.
+ tracker_.FlushForTesting();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, FlushAfterShutdownAsync) {
+ if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN)
+ return;
+
+ // Simulate posting a task.
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // Shutdown() should return immediately since there are no pending
+ // BLOCK_SHUTDOWN tasks.
+ tracker_.Shutdown();
+
+ // FlushForTesting() should callback immediately after shutdown, even if an
+ // undelayed task hasn't run.
+ bool called_back = false;
+ tracker_.FlushAsyncForTesting(
+ BindOnce([](bool* called_back) { *called_back = true; },
+ Unretained(&called_back)));
+ EXPECT_TRUE(called_back);
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, ShutdownDuringFlush) {
+ if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN)
+ return;
+
+ // Simulate posting a task.
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushForTesting() shouldn't return before the undelayed task runs or
+ // shutdown completes.
+ CallFlushFromAnotherThread();
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ VERIFY_ASYNC_FLUSH_IN_PROGRESS();
+
+ // Shutdown() should return immediately since there are no pending
+ // BLOCK_SHUTDOWN tasks.
+ tracker_.Shutdown();
+
+ // FlushForTesting() should now return, even if an undelayed task hasn't run.
+ WAIT_FOR_ASYNC_FLUSH_RETURNED();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, ShutdownDuringFlushAsyncForTesting) {
+ if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN)
+ return;
+
+ // Simulate posting a task.
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushAsyncForTesting() shouldn't callback before the undelayed task runs or
+ // shutdown completes.
+ WaitableEvent event;
+ tracker_.FlushAsyncForTesting(
+ BindOnce(&WaitableEvent::Signal, Unretained(&event)));
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(event.IsSignaled());
+
+ // Shutdown() should return immediately since there are no pending
+ // BLOCK_SHUTDOWN tasks.
+ tracker_.Shutdown();
+
+ // FlushAsyncForTesting() should now callback, even if an undelayed task
+ // hasn't run.
+ event.Wait();
+}
+
+TEST_P(TaskSchedulerTaskTrackerTest, DoublePendingFlushAsyncForTestingFails) {
+ Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()),
+ TimeDelta());
+ tracker_.WillPostTask(&undelayed_task);
+
+ // FlushAsyncForTesting() shouldn't callback before the undelayed task runs.
+ bool called_back = false;
+ tracker_.FlushAsyncForTesting(
+ BindOnce([](bool* called_back) { *called_back = true; },
+ Unretained(&called_back)));
+ EXPECT_FALSE(called_back);
+ EXPECT_DCHECK_DEATH({ tracker_.FlushAsyncForTesting(BindOnce([]() {})); });
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ContinueOnShutdown,
+ TaskSchedulerTaskTrackerTest,
+ ::testing::Values(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN));
+INSTANTIATE_TEST_CASE_P(
+ SkipOnShutdown,
+ TaskSchedulerTaskTrackerTest,
+ ::testing::Values(TaskShutdownBehavior::SKIP_ON_SHUTDOWN));
+INSTANTIATE_TEST_CASE_P(
+ BlockShutdown,
+ TaskSchedulerTaskTrackerTest,
+ ::testing::Values(TaskShutdownBehavior::BLOCK_SHUTDOWN));
+
+namespace {
+
+void ExpectSequenceToken(SequenceToken sequence_token) {
+ EXPECT_EQ(sequence_token, SequenceToken::GetForCurrentThread());
+}
+
+} // namespace
+
+// Verify that SequenceToken::GetForCurrentThread() returns the Sequence's token
+// when a Task runs.
+TEST_F(TaskSchedulerTaskTrackerTest, CurrentSequenceToken) {
+ scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>();
+
+ const SequenceToken sequence_token = sequence->token();
+ Task task(FROM_HERE, Bind(&ExpectSequenceToken, sequence_token), TaskTraits(),
+ TimeDelta());
+ tracker_.WillPostTask(&task);
+
+ sequence->PushTask(std::move(task));
+
+ EXPECT_FALSE(SequenceToken::GetForCurrentThread().IsValid());
+ sequence = tracker_.WillScheduleSequence(std::move(sequence),
+ &never_notified_observer_);
+ ASSERT_TRUE(sequence);
+ tracker_.RunAndPopNextTask(std::move(sequence), &never_notified_observer_);
+ EXPECT_FALSE(SequenceToken::GetForCurrentThread().IsValid());
+}
+
+TEST_F(TaskSchedulerTaskTrackerTest, LoadWillPostAndRunBeforeShutdown) {
+ // Post and run tasks asynchronously.
+ std::vector<std::unique_ptr<ThreadPostingAndRunningTask>> threads;
+
+ for (size_t i = 0; i < kLoadTestNumIterations; ++i) {
+ threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, CreateTask(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
+ ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true));
+ threads.back()->Start();
+
+ threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, CreateTask(TaskShutdownBehavior::SKIP_ON_SHUTDOWN),
+ ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true));
+ threads.back()->Start();
+
+ threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, CreateTask(TaskShutdownBehavior::BLOCK_SHUTDOWN),
+ ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true));
+ threads.back()->Start();
+ }
+
+ for (const auto& thread : threads)
+ thread->Join();
+
+ // Expect all tasks to be executed.
+ EXPECT_EQ(kLoadTestNumIterations * 3, NumTasksExecuted());
+
+ // Should return immediately because no tasks are blocking shutdown.
+ tracker_.Shutdown();
+}
+
+TEST_F(TaskSchedulerTaskTrackerTest,
+ LoadWillPostBeforeShutdownAndRunDuringShutdown) {
+ // Post tasks asynchronously.
+ std::vector<Task> tasks_continue_on_shutdown;
+ std::vector<Task> tasks_skip_on_shutdown;
+ std::vector<Task> tasks_block_shutdown;
+ for (size_t i = 0; i < kLoadTestNumIterations; ++i) {
+ tasks_continue_on_shutdown.push_back(
+ CreateTask(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN));
+ tasks_skip_on_shutdown.push_back(
+ CreateTask(TaskShutdownBehavior::SKIP_ON_SHUTDOWN));
+ tasks_block_shutdown.push_back(
+ CreateTask(TaskShutdownBehavior::BLOCK_SHUTDOWN));
+ }
+
+ std::vector<std::unique_ptr<ThreadPostingAndRunningTask>> post_threads;
+ for (size_t i = 0; i < kLoadTestNumIterations; ++i) {
+ post_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, &tasks_continue_on_shutdown[i],
+ ThreadPostingAndRunningTask::Action::WILL_POST, true));
+ post_threads.back()->Start();
+
+ post_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, &tasks_skip_on_shutdown[i],
+ ThreadPostingAndRunningTask::Action::WILL_POST, true));
+ post_threads.back()->Start();
+
+ post_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, &tasks_block_shutdown[i],
+ ThreadPostingAndRunningTask::Action::WILL_POST, true));
+ post_threads.back()->Start();
+ }
+
+ for (const auto& thread : post_threads)
+ thread->Join();
+
+ // Call Shutdown() asynchronously.
+ CallShutdownAsync();
+
+ // Run tasks asynchronously.
+ std::vector<std::unique_ptr<ThreadPostingAndRunningTask>> run_threads;
+ for (size_t i = 0; i < kLoadTestNumIterations; ++i) {
+ run_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, std::move(tasks_continue_on_shutdown[i]),
+ ThreadPostingAndRunningTask::Action::RUN, false));
+ run_threads.back()->Start();
+
+ run_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, std::move(tasks_skip_on_shutdown[i]),
+ ThreadPostingAndRunningTask::Action::RUN, false));
+ run_threads.back()->Start();
+
+ run_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, std::move(tasks_block_shutdown[i]),
+ ThreadPostingAndRunningTask::Action::RUN, false));
+ run_threads.back()->Start();
+ }
+
+ for (const auto& thread : run_threads)
+ thread->Join();
+
+ WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED();
+
+ // Expect BLOCK_SHUTDOWN tasks to have been executed.
+ EXPECT_EQ(kLoadTestNumIterations, NumTasksExecuted());
+}
+
+TEST_F(TaskSchedulerTaskTrackerTest, LoadWillPostAndRunDuringShutdown) {
+ // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to
+ // block shutdown.
+ Task block_shutdown_task(CreateTask(TaskShutdownBehavior::BLOCK_SHUTDOWN));
+ EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task));
+
+ // Call Shutdown() asynchronously.
+ CallShutdownAsync();
+
+ // Post and run tasks asynchronously.
+ std::vector<std::unique_ptr<ThreadPostingAndRunningTask>> threads;
+
+ for (size_t i = 0; i < kLoadTestNumIterations; ++i) {
+ threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, CreateTask(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
+ ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false));
+ threads.back()->Start();
+
+ threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, CreateTask(TaskShutdownBehavior::SKIP_ON_SHUTDOWN),
+ ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false));
+ threads.back()->Start();
+
+ threads.push_back(std::make_unique<ThreadPostingAndRunningTask>(
+ &tracker_, CreateTask(TaskShutdownBehavior::BLOCK_SHUTDOWN),
+ ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true));
+ threads.back()->Start();
+ }
+
+ for (const auto& thread : threads)
+ thread->Join();
+
+ // Expect BLOCK_SHUTDOWN tasks to have been executed.
+ EXPECT_EQ(kLoadTestNumIterations, NumTasksExecuted());
+
+ // Shutdown() shouldn't return before |block_shutdown_task| is executed.
+ VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS();
+
+ // Unblock shutdown by running |block_shutdown_task|.
+ DispatchAndRunTaskWithTracker(std::move(block_shutdown_task));
+ EXPECT_EQ(kLoadTestNumIterations + 1, NumTasksExecuted());
+ WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED();
+}
+
+// Verify that RunAndPopNextTask() returns the sequence from which it ran a task
+// when it can be rescheduled.
+TEST_F(TaskSchedulerTaskTrackerTest,
+ RunAndPopNextTaskReturnsSequenceToReschedule) {
+ Task task_1(FROM_HERE, DoNothing(), TaskTraits(), TimeDelta());
+ EXPECT_TRUE(tracker_.WillPostTask(&task_1));
+ Task task_2(FROM_HERE, DoNothing(), TaskTraits(), TimeDelta());
+ EXPECT_TRUE(tracker_.WillPostTask(&task_2));
+
+ scoped_refptr<Sequence> sequence =
+ test::CreateSequenceWithTask(std::move(task_1));
+ sequence->PushTask(std::move(task_2));
+ EXPECT_EQ(sequence, tracker_.WillScheduleSequence(sequence, nullptr));
+
+ EXPECT_EQ(sequence, tracker_.RunAndPopNextTask(sequence, nullptr));
+}
+
+// Verify that WillScheduleSequence() returns nullptr when it receives a
+// background sequence and the maximum number of background sequences that can
+// be scheduled concurrently is reached. Verify that an observer is notified
+// when a background sequence can be scheduled (i.e. when one of the previously
+// scheduled background sequences has run).
+TEST_F(TaskSchedulerTaskTrackerTest,
+ WillScheduleBackgroundSequenceWithMaxBackgroundSequences) {
+ constexpr int kMaxNumScheduledBackgroundSequences = 2;
+ TaskTracker tracker("Test", kMaxNumScheduledBackgroundSequences);
+
+ // Simulate posting |kMaxNumScheduledBackgroundSequences| background tasks
+ // and scheduling the associated sequences. This should succeed.
+ std::vector<scoped_refptr<Sequence>> scheduled_sequences;
+ testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer;
+ for (int i = 0; i < kMaxNumScheduledBackgroundSequences; ++i) {
+ Task task(FROM_HERE, DoNothing(), TaskTraits(TaskPriority::BACKGROUND),
+ TimeDelta());
+ EXPECT_TRUE(tracker.WillPostTask(&task));
+ scoped_refptr<Sequence> sequence =
+ test::CreateSequenceWithTask(std::move(task));
+ EXPECT_EQ(sequence,
+ tracker.WillScheduleSequence(sequence, &never_notified_observer));
+ scheduled_sequences.push_back(std::move(sequence));
+ }
+
+ // Simulate posting extra background tasks and scheduling the associated
+ // sequences. This should fail because the maximum number of background
+ // sequences that can be scheduled concurrently is already reached.
+ std::vector<std::unique_ptr<bool>> extra_tasks_did_run;
+ std::vector<
+ std::unique_ptr<testing::StrictMock<MockCanScheduleSequenceObserver>>>
+ extra_observers;
+ std::vector<scoped_refptr<Sequence>> extra_sequences;
+ for (int i = 0; i < kMaxNumScheduledBackgroundSequences; ++i) {
+ extra_tasks_did_run.push_back(std::make_unique<bool>());
+ Task extra_task(
+ FROM_HERE,
+ BindOnce([](bool* extra_task_did_run) { *extra_task_did_run = true; },
+ Unretained(extra_tasks_did_run.back().get())),
+ TaskTraits(TaskPriority::BACKGROUND), TimeDelta());
+ EXPECT_TRUE(tracker.WillPostTask(&extra_task));
+ extra_sequences.push_back(
+ test::CreateSequenceWithTask(std::move(extra_task)));
+ extra_observers.push_back(
+ std::make_unique<
+ testing::StrictMock<MockCanScheduleSequenceObserver>>());
+ EXPECT_EQ(nullptr,
+ tracker.WillScheduleSequence(extra_sequences.back(),
+ extra_observers.back().get()));
+ }
+
+ // Run the sequences scheduled at the beginning of the test. Expect an
+ // observer from |extra_observer| to be notified every time a task finishes to
+ // run.
+ for (int i = 0; i < kMaxNumScheduledBackgroundSequences; ++i) {
+ EXPECT_CALL(*extra_observers[i].get(),
+ MockOnCanScheduleSequence(extra_sequences[i].get()));
+ EXPECT_FALSE(tracker.RunAndPopNextTask(scheduled_sequences[i],
+ &never_notified_observer));
+ testing::Mock::VerifyAndClear(extra_observers[i].get());
+ }
+
+ // Run the extra sequences.
+ for (int i = 0; i < kMaxNumScheduledBackgroundSequences; ++i) {
+ EXPECT_FALSE(*extra_tasks_did_run[i]);
+ EXPECT_FALSE(tracker.RunAndPopNextTask(extra_sequences[i],
+ &never_notified_observer));
+ EXPECT_TRUE(*extra_tasks_did_run[i]);
+ }
+}
+
+namespace {
+
+void SetBool(bool* arg) {
+ ASSERT_TRUE(arg);
+ EXPECT_FALSE(*arg);
+ *arg = true;
+}
+
+} // namespace
+
+// Verify that RunAndPopNextTask() doesn't reschedule the background sequence it
+// was assigned if there is a preempted background sequence with an earlier
+// sequence time (compared to the next task in the sequence assigned to
+// RunAndPopNextTask()).
+TEST_F(TaskSchedulerTaskTrackerTest,
+ RunNextBackgroundTaskWithEarlierPendingBackgroundTask) {
+ constexpr int kMaxNumScheduledBackgroundSequences = 1;
+ TaskTracker tracker("Test", kMaxNumScheduledBackgroundSequences);
+ testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer;
+
+ // Simulate posting a background task and scheduling the associated sequence.
+ // This should succeed.
+ bool task_a_1_did_run = false;
+ Task task_a_1(FROM_HERE, BindOnce(&SetBool, Unretained(&task_a_1_did_run)),
+ TaskTraits(TaskPriority::BACKGROUND), TimeDelta());
+ EXPECT_TRUE(tracker.WillPostTask(&task_a_1));
+ scoped_refptr<Sequence> sequence_a =
+ test::CreateSequenceWithTask(std::move(task_a_1));
+ EXPECT_EQ(sequence_a,
+ tracker.WillScheduleSequence(sequence_a, &never_notified_observer));
+
+ // Simulate posting an extra background task and scheduling the associated
+ // sequence. This should fail because the maximum number of background
+ // sequences that can be scheduled concurrently is already reached.
+ bool task_b_1_did_run = false;
+ Task task_b_1(FROM_HERE, BindOnce(&SetBool, Unretained(&task_b_1_did_run)),
+ TaskTraits(TaskPriority::BACKGROUND), TimeDelta());
+ EXPECT_TRUE(tracker.WillPostTask(&task_b_1));
+ scoped_refptr<Sequence> sequence_b =
+ test::CreateSequenceWithTask(std::move(task_b_1));
+ testing::StrictMock<MockCanScheduleSequenceObserver> task_b_1_observer;
+ EXPECT_FALSE(tracker.WillScheduleSequence(sequence_b, &task_b_1_observer));
+
+ // Wait to be sure that the sequence time of |task_a_2| is after the sequenced
+ // time of |task_b_1|.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+
+ // Post an extra background task in |sequence_a|.
+ bool task_a_2_did_run = false;
+ Task task_a_2(FROM_HERE, BindOnce(&SetBool, Unretained(&task_a_2_did_run)),
+ TaskTraits(TaskPriority::BACKGROUND), TimeDelta());
+ EXPECT_TRUE(tracker.WillPostTask(&task_a_2));
+ sequence_a->PushTask(std::move(task_a_2));
+
+ // Run the first task in |sequence_a|. RunAndPopNextTask() should return
+ // nullptr since |sequence_a| can't be rescheduled immediately.
+ // |task_b_1_observer| should be notified that |sequence_b| can be scheduled.
+ testing::StrictMock<MockCanScheduleSequenceObserver> task_a_2_observer;
+ EXPECT_CALL(task_b_1_observer, MockOnCanScheduleSequence(sequence_b.get()));
+ EXPECT_FALSE(tracker.RunAndPopNextTask(sequence_a, &task_a_2_observer));
+ testing::Mock::VerifyAndClear(&task_b_1_observer);
+ EXPECT_TRUE(task_a_1_did_run);
+
+ // Run the first task in |sequence_b|. RunAndPopNextTask() should return
+ // nullptr since |sequence_b| is empty after popping a task from it.
+ // |task_a_2_observer| should be notified that |sequence_a| can be
+ // scheduled.
+ EXPECT_CALL(task_a_2_observer, MockOnCanScheduleSequence(sequence_a.get()));
+ EXPECT_FALSE(tracker.RunAndPopNextTask(sequence_b, &never_notified_observer));
+ testing::Mock::VerifyAndClear(&task_a_2_observer);
+ EXPECT_TRUE(task_b_1_did_run);
+
+ // Run the first task in |sequence_a|. RunAndPopNextTask() should return
+ // nullptr since |sequence_b| is empty after popping a task from it. No
+ // observer should be notified.
+ EXPECT_FALSE(tracker.RunAndPopNextTask(sequence_a, &never_notified_observer));
+ EXPECT_TRUE(task_a_2_did_run);
+}
+
+// Verify that preempted background sequences are scheduled when shutdown
+// starts.
+TEST_F(TaskSchedulerTaskTrackerTest,
+ SchedulePreemptedBackgroundSequencesOnShutdown) {
+ constexpr int kMaxNumScheduledBackgroundSequences = 0;
+ TaskTracker tracker("Test", kMaxNumScheduledBackgroundSequences);
+ testing::StrictMock<MockCanScheduleSequenceObserver> observer;
+
+ // Simulate scheduling sequences. TaskTracker should prevent this.
+ std::vector<scoped_refptr<Sequence>> preempted_sequences;
+ for (int i = 0; i < 3; ++i) {
+ Task task(FROM_HERE, DoNothing(),
+ TaskTraits(TaskPriority::BACKGROUND,
+ TaskShutdownBehavior::BLOCK_SHUTDOWN),
+ TimeDelta());
+ EXPECT_TRUE(tracker.WillPostTask(&task));
+ scoped_refptr<Sequence> sequence =
+ test::CreateSequenceWithTask(std::move(task));
+ EXPECT_FALSE(tracker.WillScheduleSequence(sequence, &observer));
+ preempted_sequences.push_back(std::move(sequence));
+
+ // Wait to be sure that tasks have different |sequenced_time|.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ }
+
+ // Perform shutdown. Expect |preempted_sequences| to be scheduled in posting
+ // order.
+ {
+ testing::InSequence in_sequence;
+ for (auto& preempted_sequence : preempted_sequences) {
+ EXPECT_CALL(observer, MockOnCanScheduleSequence(preempted_sequence.get()))
+ .WillOnce(testing::Invoke([&tracker](Sequence* sequence) {
+ // Run the task to unblock shutdown.
+ tracker.RunAndPopNextTask(sequence, nullptr);
+ }));
+ }
+ tracker.Shutdown();
+ }
+}
+
+namespace {
+
+class WaitAllowedTestThread : public SimpleThread {
+ public:
+ WaitAllowedTestThread() : SimpleThread("WaitAllowedTestThread") {}
+
+ private:
+ void Run() override {
+ auto task_tracker = std::make_unique<TaskTracker>("Test");
+
+ // Waiting is allowed by default. Expect TaskTracker to disallow it before
+ // running a task without the WithBaseSyncPrimitives() trait.
+ internal::AssertBaseSyncPrimitivesAllowed();
+ Task task_without_sync_primitives(
+ FROM_HERE, Bind([]() {
+ EXPECT_DCHECK_DEATH({ internal::AssertBaseSyncPrimitivesAllowed(); });
+ }),
+ TaskTraits(), TimeDelta());
+ EXPECT_TRUE(task_tracker->WillPostTask(&task_without_sync_primitives));
+ testing::StrictMock<MockCanScheduleSequenceObserver>
+ never_notified_observer;
+ auto sequence_without_sync_primitives = task_tracker->WillScheduleSequence(
+ test::CreateSequenceWithTask(std::move(task_without_sync_primitives)),
+ &never_notified_observer);
+ ASSERT_TRUE(sequence_without_sync_primitives);
+ task_tracker->RunAndPopNextTask(std::move(sequence_without_sync_primitives),
+ &never_notified_observer);
+
+ // Disallow waiting. Expect TaskTracker to allow it before running a task
+ // with the WithBaseSyncPrimitives() trait.
+ ThreadRestrictions::DisallowWaiting();
+ Task task_with_sync_primitives(
+ FROM_HERE, Bind([]() {
+ // Shouldn't fail.
+ internal::AssertBaseSyncPrimitivesAllowed();
+ }),
+ TaskTraits(WithBaseSyncPrimitives()), TimeDelta());
+ EXPECT_TRUE(task_tracker->WillPostTask(&task_with_sync_primitives));
+ auto sequence_with_sync_primitives = task_tracker->WillScheduleSequence(
+ test::CreateSequenceWithTask(std::move(task_with_sync_primitives)),
+ &never_notified_observer);
+ ASSERT_TRUE(sequence_with_sync_primitives);
+ task_tracker->RunAndPopNextTask(std::move(sequence_with_sync_primitives),
+ &never_notified_observer);
+
+ ScopedAllowBaseSyncPrimitivesForTesting
+ allow_wait_in_task_tracker_destructor;
+ task_tracker.reset();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(WaitAllowedTestThread);
+};
+
+} // namespace
+
+// Verify that AssertIOAllowed() succeeds only for a WithBaseSyncPrimitives()
+// task.
+TEST(TaskSchedulerTaskTrackerWaitAllowedTest, WaitAllowed) {
+ // Run the test on the separate thread since it is not possible to reset the
+ // "wait allowed" bit of a thread without being a friend of
+ // ThreadRestrictions.
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+ WaitAllowedTestThread wait_allowed_test_thread;
+ wait_allowed_test_thread.Start();
+ wait_allowed_test_thread.Join();
+}
+
+// Verify that TaskScheduler.TaskLatency.* histograms are correctly recorded
+// when a task runs.
+TEST(TaskSchedulerTaskTrackerHistogramTest, TaskLatency) {
+ auto statistics_recorder = StatisticsRecorder::CreateTemporaryForTesting();
+
+ TaskTracker tracker("Test");
+ testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer;
+
+ struct {
+ const TaskTraits traits;
+ const char* const expected_histogram;
+ } static constexpr kTests[] = {
+ {{TaskPriority::BACKGROUND},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "BackgroundTaskPriority"},
+ {{MayBlock(), TaskPriority::BACKGROUND},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "BackgroundTaskPriority_MayBlock"},
+ {{WithBaseSyncPrimitives(), TaskPriority::BACKGROUND},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "BackgroundTaskPriority_MayBlock"},
+ {{TaskPriority::USER_VISIBLE},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "UserVisibleTaskPriority"},
+ {{MayBlock(), TaskPriority::USER_VISIBLE},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "UserVisibleTaskPriority_MayBlock"},
+ {{WithBaseSyncPrimitives(), TaskPriority::USER_VISIBLE},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "UserVisibleTaskPriority_MayBlock"},
+ {{TaskPriority::USER_BLOCKING},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "UserBlockingTaskPriority"},
+ {{MayBlock(), TaskPriority::USER_BLOCKING},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "UserBlockingTaskPriority_MayBlock"},
+ {{WithBaseSyncPrimitives(), TaskPriority::USER_BLOCKING},
+ "TaskScheduler.TaskLatencyMicroseconds.Test."
+ "UserBlockingTaskPriority_MayBlock"}};
+
+ for (const auto& test : kTests) {
+ Task task(FROM_HERE, DoNothing(), test.traits, TimeDelta());
+ ASSERT_TRUE(tracker.WillPostTask(&task));
+
+ HistogramTester tester;
+
+ auto sequence = tracker.WillScheduleSequence(
+ test::CreateSequenceWithTask(std::move(task)),
+ &never_notified_observer);
+ ASSERT_TRUE(sequence);
+ tracker.RunAndPopNextTask(std::move(sequence), &never_notified_observer);
+ tester.ExpectTotalCount(test.expected_histogram, 1);
+ }
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/task_unittest.cc b/base/task_scheduler/task_unittest.cc
new file mode 100644
index 0000000000..31a59ded9f
--- /dev/null
+++ b/base/task_scheduler/task_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/task.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/location.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+// Verify that the shutdown behavior of a BLOCK_SHUTDOWN delayed task is
+// adjusted to SKIP_ON_SHUTDOWN. The shutown behavior of other delayed tasks
+// should not change.
+TEST(TaskSchedulerTaskTest, ShutdownBehaviorChangeWithDelay) {
+ Task continue_on_shutdown(FROM_HERE, DoNothing(),
+ {TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ TimeDelta::FromSeconds(1));
+ EXPECT_EQ(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN,
+ continue_on_shutdown.traits.shutdown_behavior());
+
+ Task skip_on_shutdown(FROM_HERE, DoNothing(),
+ {TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+ TimeDelta::FromSeconds(1));
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ skip_on_shutdown.traits.shutdown_behavior());
+
+ Task block_shutdown(FROM_HERE, DoNothing(),
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ TimeDelta::FromSeconds(1));
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ block_shutdown.traits.shutdown_behavior());
+}
+
+// Verify that the shutdown behavior of undelayed tasks is not adjusted.
+TEST(TaskSchedulerTaskTest, NoShutdownBehaviorChangeNoDelay) {
+ Task continue_on_shutdown(FROM_HERE, DoNothing(),
+ {TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ TimeDelta());
+ EXPECT_EQ(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN,
+ continue_on_shutdown.traits.shutdown_behavior());
+
+ Task skip_on_shutdown(FROM_HERE, DoNothing(),
+ {TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, TimeDelta());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ skip_on_shutdown.traits.shutdown_behavior());
+
+ Task block_shutdown(FROM_HERE, DoNothing(),
+ {TaskShutdownBehavior::BLOCK_SHUTDOWN}, TimeDelta());
+ EXPECT_EQ(TaskShutdownBehavior::BLOCK_SHUTDOWN,
+ block_shutdown.traits.shutdown_behavior());
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/test_task_factory.cc b/base/task_scheduler/test_task_factory.cc
new file mode 100644
index 0000000000..08675470a5
--- /dev/null
+++ b/base/task_scheduler/test_task_factory.cc
@@ -0,0 +1,106 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task_scheduler/test_task_factory.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+namespace test {
+
+TestTaskFactory::TestTaskFactory(scoped_refptr<TaskRunner> task_runner,
+ ExecutionMode execution_mode)
+ : cv_(&lock_),
+ task_runner_(std::move(task_runner)),
+ execution_mode_(execution_mode) {
+ // Detach |thread_checker_| from the current thread. It will be attached to
+ // the first thread that calls ThreadCheckerImpl::CalledOnValidThread().
+ thread_checker_.DetachFromThread();
+}
+
+TestTaskFactory::~TestTaskFactory() {
+ WaitForAllTasksToRun();
+}
+
+bool TestTaskFactory::PostTask(PostNestedTask post_nested_task,
+ OnceClosure after_task_closure) {
+ AutoLock auto_lock(lock_);
+ return task_runner_->PostTask(
+ FROM_HERE, BindOnce(&TestTaskFactory::RunTaskCallback, Unretained(this),
+ num_posted_tasks_++, post_nested_task,
+ std::move(after_task_closure)));
+}
+
+void TestTaskFactory::WaitForAllTasksToRun() const {
+ AutoLock auto_lock(lock_);
+ while (ran_tasks_.size() < num_posted_tasks_)
+ cv_.Wait();
+}
+
+void TestTaskFactory::RunTaskCallback(size_t task_index,
+ PostNestedTask post_nested_task,
+ OnceClosure after_task_closure) {
+ if (post_nested_task == PostNestedTask::YES)
+ PostTask(PostNestedTask::NO, Closure());
+
+ EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
+
+ // Verify TaskRunnerHandles are set as expected in the task's scope.
+ switch (execution_mode_) {
+ case ExecutionMode::PARALLEL:
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+ break;
+ case ExecutionMode::SEQUENCED:
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_, SequencedTaskRunnerHandle::Get());
+ break;
+ case ExecutionMode::SINGLE_THREADED:
+ // SequencedTaskRunnerHandle inherits from ThreadTaskRunnerHandle so
+ // both are expected to be "set" in the SINGLE_THREADED case.
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_, ThreadTaskRunnerHandle::Get());
+ EXPECT_EQ(task_runner_, SequencedTaskRunnerHandle::Get());
+ break;
+ }
+
+ {
+ AutoLock auto_lock(lock_);
+
+ DCHECK_LE(task_index, num_posted_tasks_);
+
+ if ((execution_mode_ == ExecutionMode::SINGLE_THREADED ||
+ execution_mode_ == ExecutionMode::SEQUENCED) &&
+ task_index != ran_tasks_.size()) {
+ ADD_FAILURE() << "A task didn't run in the expected order.";
+ }
+
+ if (execution_mode_ == ExecutionMode::SINGLE_THREADED)
+ EXPECT_TRUE(thread_checker_.CalledOnValidThread());
+
+ if (ran_tasks_.find(task_index) != ran_tasks_.end())
+ ADD_FAILURE() << "A task ran more than once.";
+ ran_tasks_.insert(task_index);
+
+ cv_.Signal();
+ }
+
+ if (!after_task_closure.is_null())
+ std::move(after_task_closure).Run();
+}
+
+} // namespace test
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/test_task_factory.h b/base/task_scheduler/test_task_factory.h
new file mode 100644
index 0000000000..300b7bfbc5
--- /dev/null
+++ b/base/task_scheduler/test_task_factory.h
@@ -0,0 +1,99 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SCHEDULER_TEST_TASK_FACTORY_H_
+#define BASE_TASK_SCHEDULER_TEST_TASK_FACTORY_H_
+
+#include <stddef.h>
+
+#include <unordered_set>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/threading/thread_checker_impl.h"
+
+namespace base {
+namespace internal {
+namespace test {
+
+// A TestTaskFactory posts tasks to a TaskRunner and verifies that they run as
+// expected. Generates a test failure when:
+// - The RunsTasksInCurrentSequence() method of the TaskRunner returns false on
+// a thread on which a Task is run.
+// - The TaskRunnerHandles set in the context of the task don't match what's
+// expected for the tested ExecutionMode.
+// - The ExecutionMode of the TaskRunner is SEQUENCED or SINGLE_THREADED and
+// Tasks don't run in posting order.
+// - The ExecutionMode of the TaskRunner is SINGLE_THREADED and Tasks don't run
+// on the same thread.
+// - A Task runs more than once.
+class TestTaskFactory {
+ public:
+ enum class PostNestedTask {
+ YES,
+ NO,
+ };
+
+ // Constructs a TestTaskFactory that posts tasks to |task_runner|.
+ // |execution_mode| is the ExecutionMode of |task_runner|.
+ TestTaskFactory(scoped_refptr<TaskRunner> task_runner,
+ ExecutionMode execution_mode);
+
+ ~TestTaskFactory();
+
+ // Posts a task. The posted task will:
+ // - Post a new task if |post_nested_task| is YES. The nested task won't run
+ // |after_task_closure|.
+ // - Verify conditions in which the task runs (see potential failures above).
+ // - Run |after_task_closure| if it is not null.
+ bool PostTask(PostNestedTask post_nested_task,
+ OnceClosure after_task_closure);
+
+ // Waits for all tasks posted by PostTask() to start running. It is not
+ // guaranteed that the tasks have completed their execution when this returns.
+ void WaitForAllTasksToRun() const;
+
+ const TaskRunner* task_runner() const { return task_runner_.get(); }
+
+ private:
+ void RunTaskCallback(size_t task_index,
+ PostNestedTask post_nested_task,
+ OnceClosure after_task_closure);
+
+ // Synchronizes access to all members.
+ mutable Lock lock_;
+
+ // Condition variable signaled when a task runs.
+ mutable ConditionVariable cv_;
+
+ // Task runner through which this factory posts tasks.
+ const scoped_refptr<TaskRunner> task_runner_;
+
+ // Execution mode of |task_runner_|.
+ const ExecutionMode execution_mode_;
+
+ // Number of tasks posted by PostTask().
+ size_t num_posted_tasks_ = 0;
+
+ // Indexes of tasks that ran.
+ std::unordered_set<size_t> ran_tasks_;
+
+ // Used to verify that all tasks run on the same thread when |execution_mode_|
+ // is SINGLE_THREADED.
+ ThreadCheckerImpl thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTaskFactory);
+};
+
+} // namespace test
+} // namespace internal
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_TEST_TASK_FACTORY_H_
diff --git a/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java b/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java
new file mode 100644
index 0000000000..fe9d5403de
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java
@@ -0,0 +1,46 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import org.chromium.base.annotations.CalledByNative;
+
+/**
+ * Utilities for testing operations on content URI.
+ */
+public class ContentUriTestUtils {
+ /**
+ * Insert an image into the MediaStore, and return the content URI. If the
+ * image already exists in the MediaStore, just retrieve the URI.
+ *
+ * @param path Path to the image file.
+ * @return Content URI of the image.
+ */
+ @CalledByNative
+ private static String insertImageIntoMediaStore(String path) {
+ // Check whether the content URI exists.
+ Cursor c = ContextUtils.getApplicationContext().getContentResolver().query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[] {MediaStore.Video.VideoColumns._ID},
+ MediaStore.Images.Media.DATA + " LIKE ?", new String[] {path}, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ int id = c.getInt(0);
+ return Uri.withAppendedPath(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id).toString();
+ }
+
+ // Insert the content URI into MediaStore.
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, path);
+ Uri uri = ContextUtils.getApplicationContext().getContentResolver().insert(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ return uri.toString();
+ }
+}
diff --git a/base/test/android/java/src/org/chromium/base/ITestCallback.aidl b/base/test/android/java/src/org/chromium/base/ITestCallback.aidl
new file mode 100644
index 0000000000..dd208d55da
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/ITestCallback.aidl
@@ -0,0 +1,23 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.ITestController;
+import org.chromium.base.process_launcher.FileDescriptorInfo;
+
+/**
+ * This interface is called by the child process to pass its controller to its parent.
+ */
+interface ITestCallback {
+ oneway void childConnected(ITestController controller);
+
+ /**
+ * Invoked by the service to notify that the main method returned.
+ * IMPORTANT! Should not be marked oneway as the caller will terminate the running process after
+ * this call. Marking it oneway would make the call asynchronous and the process could terminate
+ * before the call was actually sent.
+ */
+ void mainReturned(int returnCode);
+}
diff --git a/base/test/android/java/src/org/chromium/base/ITestController.aidl b/base/test/android/java/src/org/chromium/base/ITestController.aidl
new file mode 100644
index 0000000000..d927ee5c87
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/ITestController.aidl
@@ -0,0 +1,25 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.process_launcher.FileDescriptorInfo;
+
+/**
+ * This interface is used to control child processes.
+ */
+interface ITestController {
+ /**
+ * Forces the service process to terminate and block until the process stops.
+ * @param exitCode the exit code the process should terminate with.
+ * @return always true, a return value is only returned to force the call to be synchronous.
+ */
+ boolean forceStopSynchronous(int exitCode);
+
+ /**
+ * Forces the service process to terminate.
+ * @param exitCode the exit code the process should terminate with.
+ */
+ oneway void forceStop(int exitCode);
+}
diff --git a/base/test/android/java/src/org/chromium/base/JavaHandlerThreadHelpers.java b/base/test/android/java/src/org/chromium/base/JavaHandlerThreadHelpers.java
new file mode 100644
index 0000000000..3985e6a893
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/JavaHandlerThreadHelpers.java
@@ -0,0 +1,65 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Handler;
+import android.os.Process;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.CalledByNativeUnchecked;
+import org.chromium.base.annotations.JNINamespace;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@JNINamespace("base::android")
+class JavaHandlerThreadHelpers {
+ private static class TestException extends Exception {}
+
+ // This is executed as part of base_unittests. This tests that JavaHandlerThread can be used
+ // by itself without attaching to its native peer.
+ @CalledByNative
+ private static JavaHandlerThread testAndGetJavaHandlerThread() {
+ final AtomicBoolean taskExecuted = new AtomicBoolean();
+ final Object lock = new Object();
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (lock) {
+ taskExecuted.set(true);
+ lock.notifyAll();
+ }
+ }
+ };
+
+ JavaHandlerThread thread =
+ new JavaHandlerThread("base_unittests_java", Process.THREAD_PRIORITY_DEFAULT);
+ thread.maybeStart();
+
+ Handler handler = new Handler(thread.getLooper());
+ handler.post(runnable);
+ synchronized (lock) {
+ while (!taskExecuted.get()) {
+ try {
+ lock.wait();
+ } catch (InterruptedException e) {
+ // ignore interrupts
+ }
+ }
+ }
+
+ return thread;
+ }
+
+ @CalledByNativeUnchecked
+ private static void throwException() throws TestException {
+ throw new TestException();
+ }
+
+ @CalledByNative
+ private static boolean isExceptionTestException(Throwable exception) {
+ if (exception == null) return false;
+ return exception instanceof TestException;
+ }
+}
diff --git a/base/test/android/java/src/org/chromium/base/MainReturnCodeResult.java b/base/test/android/java/src/org/chromium/base/MainReturnCodeResult.java
new file mode 100644
index 0000000000..9756c97602
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MainReturnCodeResult.java
@@ -0,0 +1,40 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * Contains the result of a native main method that ran in a child process.
+ */
+@JNINamespace("base::android")
+public final class MainReturnCodeResult {
+ private final int mMainReturnCode;
+ private final boolean mTimedOut;
+
+ public static MainReturnCodeResult createMainResult(int returnCode) {
+ return new MainReturnCodeResult(returnCode, false /* timedOut */);
+ }
+
+ public static MainReturnCodeResult createTimeoutMainResult() {
+ return new MainReturnCodeResult(0, true /* timedOut */);
+ }
+
+ private MainReturnCodeResult(int mainReturnCode, boolean timedOut) {
+ mMainReturnCode = mainReturnCode;
+ mTimedOut = timedOut;
+ }
+
+ @CalledByNative
+ public int getReturnCode() {
+ return mMainReturnCode;
+ }
+
+ @CalledByNative
+ public boolean hasTimedOut() {
+ return mTimedOut;
+ }
+}
diff --git a/base/test/android/java/src/org/chromium/base/MultiprocessTestClientLauncher.java b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientLauncher.java
new file mode 100644
index 0000000000..d0b1850bfc
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientLauncher.java
@@ -0,0 +1,383 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.process_launcher.ChildConnectionAllocator;
+import org.chromium.base.process_launcher.ChildProcessConnection;
+import org.chromium.base.process_launcher.ChildProcessLauncher;
+import org.chromium.base.process_launcher.FileDescriptorInfo;
+import org.chromium.base.process_launcher.IChildProcessService;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Helper class for launching test client processes for multiprocess unit tests.
+ */
+@JNINamespace("base::android")
+public final class MultiprocessTestClientLauncher {
+ private static final String TAG = "cr_MProcTCLauncher";
+
+ private static final int CONNECTION_TIMEOUT_MS = 10 * 1000;
+
+ private static final SparseArray<MultiprocessTestClientLauncher> sPidToLauncher =
+ new SparseArray<>();
+
+ private static final SparseArray<Integer> sPidToMainResult = new SparseArray<>();
+
+ private static final Object sLauncherHandlerInitLock = new Object();
+ private static Handler sLauncherHandler;
+
+ private static ChildConnectionAllocator sConnectionAllocator;
+
+ private final ITestCallback.Stub mCallback = new ITestCallback.Stub() {
+ @Override
+ public void childConnected(ITestController controller) {
+ mTestController = controller;
+ // This method can be called before onServiceConnected below has set the PID.
+ // Wait for mPid to be set before notifying.
+ try {
+ mPidReceived.await();
+ } catch (InterruptedException ie) {
+ Log.e(TAG, "Interrupted while waiting for connection PID.");
+ return;
+ }
+ // Now we are fully initialized, notify clients.
+ mConnectedLock.lock();
+ try {
+ mConnected = true;
+ mConnectedCondition.signal();
+ } finally {
+ mConnectedLock.unlock();
+ }
+ }
+
+ @Override
+ public void mainReturned(int returnCode) {
+ mMainReturnCodeLock.lock();
+ try {
+ mMainReturnCode = returnCode;
+ mMainReturnCodeCondition.signal();
+ } finally {
+ mMainReturnCodeLock.unlock();
+ }
+
+ // Also store the return code in a map as the connection might get disconnected
+ // before waitForMainToReturn is called and then we would not have a way to retrieve
+ // the connection.
+ sPidToMainResult.put(mPid, returnCode);
+ }
+ };
+
+ private final ChildProcessLauncher.Delegate mLauncherDelegate =
+ new ChildProcessLauncher.Delegate() {
+ @Override
+ public void onConnectionEstablished(ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+ int pid = connection.getPid();
+ sPidToLauncher.put(pid, MultiprocessTestClientLauncher.this);
+ mPid = pid;
+ mPidReceived.countDown();
+ }
+
+ @Override
+ public void onConnectionLost(ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+ assert sPidToLauncher.get(connection.getPid())
+ == MultiprocessTestClientLauncher.this;
+ sPidToLauncher.remove(connection.getPid());
+ }
+ };
+
+ private final CountDownLatch mPidReceived = new CountDownLatch(1);
+
+ private final ChildProcessLauncher mLauncher;
+
+ private final ReentrantLock mConnectedLock = new ReentrantLock();
+ private final Condition mConnectedCondition = mConnectedLock.newCondition();
+ @GuardedBy("mConnectedLock")
+ private boolean mConnected;
+
+ private IChildProcessService mService = null;
+ private int mPid;
+ private ITestController mTestController;
+
+ private final ReentrantLock mMainReturnCodeLock = new ReentrantLock();
+ private final Condition mMainReturnCodeCondition = mMainReturnCodeLock.newCondition();
+ // The return code returned by the service's main method.
+ // null if the service has not sent it yet.
+ @GuardedBy("mMainReturnCodeLock")
+ private Integer mMainReturnCode;
+
+ private MultiprocessTestClientLauncher(String[] commandLine, FileDescriptorInfo[] filesToMap) {
+ assert isRunningOnLauncherThread();
+
+ if (sConnectionAllocator == null) {
+ sConnectionAllocator = ChildConnectionAllocator.create(
+ ContextUtils.getApplicationContext(), sLauncherHandler, null,
+ "org.chromium.native_test", "org.chromium.base.MultiprocessTestClientService",
+ "org.chromium.native_test.NUM_TEST_CLIENT_SERVICES", false /* bindToCaller */,
+ false /* bindAsExternalService */, false /* useStrongBinding */);
+ }
+ mLauncher = new ChildProcessLauncher(sLauncherHandler, mLauncherDelegate, commandLine,
+ filesToMap, sConnectionAllocator, Arrays.asList(mCallback));
+ }
+
+ private boolean waitForConnection(long timeoutMs) {
+ assert !isRunningOnLauncherThread();
+
+ long timeoutNs = TimeUnit.MILLISECONDS.toNanos(timeoutMs);
+ mConnectedLock.lock();
+ try {
+ while (!mConnected) {
+ if (timeoutNs <= 0L) {
+ return false;
+ }
+ try {
+ mConnectedCondition.awaitNanos(timeoutNs);
+ } catch (InterruptedException ie) {
+ Log.e(TAG, "Interrupted while waiting for connection.");
+ }
+ }
+ } finally {
+ mConnectedLock.unlock();
+ }
+ return true;
+ }
+
+ private Integer getMainReturnCode(long timeoutMs) {
+ assert isRunningOnLauncherThread();
+
+ long timeoutNs = TimeUnit.MILLISECONDS.toNanos(timeoutMs);
+ mMainReturnCodeLock.lock();
+ try {
+ while (mMainReturnCode == null) {
+ if (timeoutNs <= 0L) {
+ return null;
+ }
+ try {
+ timeoutNs = mMainReturnCodeCondition.awaitNanos(timeoutNs);
+ } catch (InterruptedException ie) {
+ Log.e(TAG, "Interrupted while waiting for main return code.");
+ }
+ }
+ return mMainReturnCode;
+ } finally {
+ mMainReturnCodeLock.unlock();
+ }
+ }
+
+ /**
+ * Spawns and connects to a child process.
+ * May not be called from the main thread.
+ *
+ * @param commandLine the child process command line argv.
+ * @return the PID of the started process or 0 if the process could not be started.
+ */
+ @CalledByNative
+ private static int launchClient(
+ final String[] commandLine, final FileDescriptorInfo[] filesToMap) {
+ initLauncherThread();
+
+ final MultiprocessTestClientLauncher launcher =
+ runOnLauncherAndGetResult(new Callable<MultiprocessTestClientLauncher>() {
+ @Override
+ public MultiprocessTestClientLauncher call() {
+ return createAndStartLauncherOnLauncherThread(commandLine, filesToMap);
+ }
+ });
+ if (launcher == null) {
+ return 0;
+ }
+
+ if (!launcher.waitForConnection(CONNECTION_TIMEOUT_MS)) {
+ return 0; // Timed-out.
+ }
+
+ return runOnLauncherAndGetResult(new Callable<Integer>() {
+ @Override
+ public Integer call() {
+ int pid = launcher.mLauncher.getPid();
+ assert pid > 0;
+ sPidToLauncher.put(pid, launcher);
+ return pid;
+ }
+ });
+ }
+
+ private static MultiprocessTestClientLauncher createAndStartLauncherOnLauncherThread(
+ String[] commandLine, FileDescriptorInfo[] filesToMap) {
+ assert isRunningOnLauncherThread();
+
+ MultiprocessTestClientLauncher launcher =
+ new MultiprocessTestClientLauncher(commandLine, filesToMap);
+ if (!launcher.mLauncher.start(
+ true /* setupConnection */, true /* queueIfNoFreeConnection */)) {
+ return null;
+ }
+
+ return launcher;
+ }
+
+ /**
+ * Blocks until the main method invoked by a previous call to launchClient terminates or until
+ * the specified time-out expires.
+ * Returns immediately if main has already returned.
+ * @param pid the process ID that was returned by the call to launchClient
+ * @param timeoutMs the timeout in milliseconds after which the method returns even if main has
+ * not returned.
+ * @return the return code returned by the main method or whether it timed-out.
+ */
+ @CalledByNative
+ private static MainReturnCodeResult waitForMainToReturn(final int pid, final int timeoutMs) {
+ return runOnLauncherAndGetResult(new Callable<MainReturnCodeResult>() {
+ @Override
+ public MainReturnCodeResult call() {
+ return waitForMainToReturnOnLauncherThread(pid, timeoutMs);
+ }
+ });
+ }
+
+ private static MainReturnCodeResult waitForMainToReturnOnLauncherThread(
+ int pid, int timeoutMs) {
+ assert isRunningOnLauncherThread();
+
+ MultiprocessTestClientLauncher launcher = sPidToLauncher.get(pid);
+ // The launcher can be null if it got cleaned-up (because the connection was lost) before
+ // this gets called.
+ if (launcher != null) {
+ Integer mainResult = launcher.getMainReturnCode(timeoutMs);
+ return mainResult == null ? MainReturnCodeResult.createTimeoutMainResult()
+ : MainReturnCodeResult.createMainResult(mainResult);
+ }
+
+ Integer mainResult = sPidToMainResult.get(pid);
+ if (mainResult == null) {
+ Log.e(TAG, "waitForMainToReturn called on unknown connection for pid " + pid);
+ return null;
+ }
+ sPidToMainResult.remove(pid);
+ return MainReturnCodeResult.createMainResult(mainResult);
+ }
+
+ @CalledByNative
+ private static boolean terminate(final int pid, final int exitCode, final boolean wait) {
+ return runOnLauncherAndGetResult(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return terminateOnLauncherThread(pid, exitCode, wait);
+ }
+ });
+ }
+
+ private static boolean terminateOnLauncherThread(int pid, int exitCode, boolean wait) {
+ assert isRunningOnLauncherThread();
+
+ MultiprocessTestClientLauncher launcher = sPidToLauncher.get(pid);
+ if (launcher == null) {
+ Log.e(TAG, "terminate called on unknown launcher for pid " + pid);
+ return false;
+ }
+ try {
+ if (wait) {
+ launcher.mTestController.forceStopSynchronous(exitCode);
+ } else {
+ launcher.mTestController.forceStop(exitCode);
+ }
+ } catch (RemoteException e) {
+ // We expect this failure, since the forceStop's service implementation calls
+ // System.exit().
+ }
+ return true;
+ }
+
+ private static void initLauncherThread() {
+ synchronized (sLauncherHandlerInitLock) {
+ if (sLauncherHandler != null) return;
+
+ HandlerThread launcherThread = new HandlerThread("LauncherThread");
+ launcherThread.start();
+ sLauncherHandler = new Handler(launcherThread.getLooper());
+ }
+ }
+
+ /** Does not take ownership of of fds. */
+ @CalledByNative
+ private static FileDescriptorInfo[] makeFdInfoArray(int[] keys, int[] fds) {
+ FileDescriptorInfo[] fdInfos = new FileDescriptorInfo[keys.length];
+ for (int i = 0; i < keys.length; i++) {
+ FileDescriptorInfo fdInfo = makeFdInfo(keys[i], fds[i]);
+ if (fdInfo == null) {
+ Log.e(TAG, "Failed to make file descriptor (" + keys[i] + ", " + fds[i] + ").");
+ return null;
+ }
+ fdInfos[i] = fdInfo;
+ }
+ return fdInfos;
+ }
+
+ private static FileDescriptorInfo makeFdInfo(int id, int fd) {
+ ParcelFileDescriptor parcelableFd = null;
+ try {
+ parcelableFd = ParcelFileDescriptor.fromFd(fd);
+ } catch (IOException e) {
+ Log.e(TAG, "Invalid FD provided for process connection, aborting connection.", e);
+ return null;
+ }
+ return new FileDescriptorInfo(id, parcelableFd, 0 /* offset */, 0 /* size */);
+ }
+
+ private static boolean isRunningOnLauncherThread() {
+ return sLauncherHandler.getLooper() == Looper.myLooper();
+ }
+
+ private static void runOnLauncherThreadBlocking(final Runnable runnable) {
+ assert !isRunningOnLauncherThread();
+ final Semaphore done = new Semaphore(0);
+ sLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ runnable.run();
+ done.release();
+ }
+ });
+ done.acquireUninterruptibly();
+ }
+
+ private static <R> R runOnLauncherAndGetResult(Callable<R> callable) {
+ if (isRunningOnLauncherThread()) {
+ try {
+ return callable.call();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ try {
+ FutureTask<R> task = new FutureTask<R>(callable);
+ sLauncherHandler.post(task);
+ return task.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService.java b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService.java
new file mode 100644
index 0000000000..9b500018bd
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService.java
@@ -0,0 +1,14 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import org.chromium.base.process_launcher.ChildProcessService;
+
+/** The service implementation used to host all multiprocess test client code. */
+public class MultiprocessTestClientService extends ChildProcessService {
+ public MultiprocessTestClientService() {
+ super(new MultiprocessTestClientServiceDelegate());
+ }
+}
diff --git a/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService0.java b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService0.java
new file mode 100644
index 0000000000..6bdd867e12
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService0.java
@@ -0,0 +1,10 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * A subclass used only to differentiate different test client service process instances.
+ */
+public class MultiprocessTestClientService0 extends MultiprocessTestClientService {}
diff --git a/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService1.java b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService1.java
new file mode 100644
index 0000000000..69827f0e8b
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService1.java
@@ -0,0 +1,10 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * A subclass used only to differentiate different test client service process instances.
+ */
+public class MultiprocessTestClientService1 extends MultiprocessTestClientService {}
diff --git a/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService2.java b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService2.java
new file mode 100644
index 0000000000..aad11f1c23
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService2.java
@@ -0,0 +1,10 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * A subclass used only to differentiate different test client service process instances.
+ */
+public class MultiprocessTestClientService2 extends MultiprocessTestClientService {}
diff --git a/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService3.java b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService3.java
new file mode 100644
index 0000000000..20d2561b93
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService3.java
@@ -0,0 +1,10 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * A subclass used only to differentiate different test client service process instances.
+ */
+public class MultiprocessTestClientService3 extends MultiprocessTestClientService {}
diff --git a/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService4.java b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService4.java
new file mode 100644
index 0000000000..4b14551dc8
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientService4.java
@@ -0,0 +1,10 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * A subclass used only to differentiate different test client service process instances.
+ */
+public class MultiprocessTestClientService4 extends MultiprocessTestClientService {}
diff --git a/base/test/android/java/src/org/chromium/base/MultiprocessTestClientServiceDelegate.java b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientServiceDelegate.java
new file mode 100644
index 0000000000..8a63fe8acb
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/MultiprocessTestClientServiceDelegate.java
@@ -0,0 +1,94 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package org.chromium.base;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.base.process_launcher.ChildProcessServiceDelegate;
+import org.chromium.native_test.MainRunner;
+
+import java.util.List;
+
+/** Implementation of the ChildProcessServiceDelegate used for the Multiprocess tests. */
+public class MultiprocessTestClientServiceDelegate implements ChildProcessServiceDelegate {
+ private static final String TAG = "MPTestCSDelegate";
+
+ private ITestCallback mTestCallback;
+
+ private final ITestController.Stub mTestController = new ITestController.Stub() {
+ @Override
+ public boolean forceStopSynchronous(int exitCode) {
+ System.exit(exitCode);
+ return true;
+ }
+
+ @Override
+ public void forceStop(int exitCode) {
+ System.exit(exitCode);
+ }
+ };
+
+ @Override
+ public void onServiceCreated() {
+ PathUtils.setPrivateDataDirectorySuffix("chrome_multiprocess_test_client_service");
+ }
+
+ @Override
+ public void onServiceBound(Intent intent) {}
+
+ @Override
+ public void onConnectionSetup(Bundle connectionBundle, List<IBinder> callbacks) {
+ mTestCallback = ITestCallback.Stub.asInterface(callbacks.get(0));
+ }
+
+ @Override
+ public void onDestroy() {}
+
+ @Override
+ public void preloadNativeLibrary(Context hostContext) {
+ LibraryLoader.getInstance().preloadNow();
+ }
+
+ @Override
+ public boolean loadNativeLibrary(Context hostContext) {
+ try {
+ LibraryLoader.getInstance().loadNow();
+ return true;
+ } catch (ProcessInitException pie) {
+ Log.e(TAG, "Unable to load native libraries.", pie);
+ return false;
+ }
+ }
+
+ @Override
+ public SparseArray<String> getFileDescriptorsIdsToKeys() {
+ return null;
+ }
+
+ @Override
+ public void onBeforeMain() {
+ try {
+ mTestCallback.childConnected(mTestController);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Failed to notify parent process of connection.");
+ }
+ }
+
+ @Override
+ public void runMain() {
+ int result = MainRunner.runMain(CommandLine.getJavaSwitchesOrNull());
+ try {
+ mTestCallback.mainReturned(result);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Failed to notify parent process of main returning.");
+ }
+ }
+}
diff --git a/base/test/android/java/src/org/chromium/base/TestUiThread.java b/base/test/android/java/src/org/chromium/base/TestUiThread.java
new file mode 100644
index 0000000000..237c0ec64b
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/TestUiThread.java
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Looper;
+
+import org.chromium.base.annotations.CalledByNative;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Set up a thread as the Chromium UI Thread, and run its looper. This is is intended for C++ unit
+ * tests (e.g. the net unit tests) that don't run with the UI thread as their main looper, but test
+ * code that, on Android, uses UI thread events, so need a running UI thread.
+ */
+@ThreadSafe
+public class TestUiThread {
+ private static final AtomicBoolean sStarted = new AtomicBoolean(false);
+ private static final String TAG = "cr.TestUiThread";
+
+ @CalledByNative
+ private static void loop() {
+ // @{link ThreadUtils#setUiThread(Looper)} can only be called once in a test run, so do this
+ // once, and leave it running.
+ if (sStarted.getAndSet(true)) return;
+
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ ThreadUtils.setUiThread(Looper.myLooper());
+ startLatch.countDown();
+ Looper.loop();
+ }
+
+ }).start();
+
+ try {
+ startLatch.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Failed to set UI Thread");
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
new file mode 100644
index 0000000000..1476e9ef4a
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
@@ -0,0 +1,291 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.internal.runner.RunnerArgs;
+import android.support.test.internal.runner.TestExecutor;
+import android.support.test.internal.runner.TestLoader;
+import android.support.test.internal.runner.TestRequest;
+import android.support.test.internal.runner.TestRequestBuilder;
+import android.support.test.runner.AndroidJUnitRunner;
+
+import dalvik.system.DexFile;
+
+import org.chromium.base.BuildConfig;
+import org.chromium.base.Log;
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.multidex.ChromiumMultiDexInstaller;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * A custom AndroidJUnitRunner that supports multidex installer and list out test information.
+ *
+ * This class is the equivalent of BaseChromiumInstrumentationTestRunner in JUnit3. Please
+ * beware that is this not a class runner. It is declared in test apk AndroidManifest.xml
+ * <instrumentation>
+ *
+ * TODO(yolandyan): remove this class after all tests are converted to JUnit4. Use class runner
+ * for test listing.
+ */
+@MainDex
+public class BaseChromiumAndroidJUnitRunner extends AndroidJUnitRunner {
+ private static final String LIST_ALL_TESTS_FLAG =
+ "org.chromium.base.test.BaseChromiumAndroidJUnitRunner.TestList";
+ private static final String LIST_TESTS_PACKAGE_FLAG =
+ "org.chromium.base.test.BaseChromiumAndroidJUnitRunner.TestListPackage";
+ /**
+ * This flag is supported by AndroidJUnitRunner.
+ *
+ * See the following page for detail
+ * https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html
+ */
+ private static final String ARGUMENT_TEST_PACKAGE = "package";
+
+ /**
+ * The following arguments are corresponding to AndroidJUnitRunner command line arguments.
+ * `annotation`: run with only the argument annotation
+ * `notAnnotation`: run all tests except the ones with argument annotation
+ * `log`: run in log only mode, do not execute tests
+ *
+ * For more detail, please check
+ * https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html
+ */
+ private static final String ARGUMENT_ANNOTATION = "annotation";
+ private static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation";
+ private static final String ARGUMENT_LOG_ONLY = "log";
+
+ private static final String TAG = "BaseJUnitRunner";
+
+ @Override
+ public Application newApplication(ClassLoader cl, String className, Context context)
+ throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+ // The multidex support library doesn't currently support having the test apk be multidex
+ // as well as the under-test apk being multidex. If MultiDex.install() is called for both,
+ // then re-extraction is triggered every time due to the support library caching only a
+ // single timestamp & crc.
+ //
+ // Attempt to install test apk multidex only if the apk-under-test is not multidex.
+ // It will likely continue to be true that the two are mutually exclusive because:
+ // * ProGuard enabled =>
+ // Under-test apk is single dex.
+ // Test apk duplicates under-test classes, so may need multidex.
+ // * ProGuard disabled =>
+ // Under-test apk might be multidex
+ // Test apk does not duplicate classes, so does not need multidex.
+ // https://crbug.com/824523
+ if (!BuildConfig.IS_MULTIDEX_ENABLED) {
+ ChromiumMultiDexInstaller.install(new BaseChromiumRunnerCommon.MultiDexContextWrapper(
+ getContext(), getTargetContext()));
+ BaseChromiumRunnerCommon.reorderDexPathElements(cl, getContext(), getTargetContext());
+ }
+ return super.newApplication(cl, className, context);
+ }
+
+ /**
+ * Add TestListInstrumentationRunListener when argument ask the runner to list tests info.
+ *
+ * The running mechanism when argument has "listAllTests" is equivalent to that of
+ * {@link android.support.test.runner.AndroidJUnitRunner#onStart()} except it adds
+ * only TestListInstrumentationRunListener to monitor the tests.
+ */
+ @Override
+ public void onStart() {
+ Bundle arguments = InstrumentationRegistry.getArguments();
+ if (arguments != null && arguments.getString(LIST_ALL_TESTS_FLAG) != null) {
+ Log.w(TAG,
+ String.format("Runner will list out tests info in JSON without running tests. "
+ + "Arguments: %s",
+ arguments.toString()));
+ listTests(); // Intentionally not calling super.onStart() to avoid additional work.
+ } else {
+ if (arguments != null && arguments.getString(ARGUMENT_LOG_ONLY) != null) {
+ Log.e(TAG,
+ String.format("Runner will log the tests without running tests."
+ + " If this cause a test run to fail, please report to"
+ + " crbug.com/754015. Arguments: %s",
+ arguments.toString()));
+ }
+ super.onStart();
+ }
+ }
+
+ // TODO(yolandyan): Move this to test harness side once this class gets removed
+ private void addTestListPackage(Bundle bundle) {
+ PackageManager pm = getContext().getPackageManager();
+ InstrumentationInfo info;
+ try {
+ info = pm.getInstrumentationInfo(getComponentName(), PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, String.format("Could not find component %s", getComponentName()));
+ throw new RuntimeException(e);
+ }
+ Bundle metaDataBundle = info.metaData;
+ if (metaDataBundle != null && metaDataBundle.getString(LIST_TESTS_PACKAGE_FLAG) != null) {
+ bundle.putString(
+ ARGUMENT_TEST_PACKAGE, metaDataBundle.getString(LIST_TESTS_PACKAGE_FLAG));
+ }
+ }
+
+ private void listTests() {
+ Bundle results = new Bundle();
+ TestListInstrumentationRunListener listener = new TestListInstrumentationRunListener();
+ try {
+ TestExecutor.Builder executorBuilder = new TestExecutor.Builder(this);
+ executorBuilder.addRunListener(listener);
+ Bundle junit3Arguments = new Bundle(InstrumentationRegistry.getArguments());
+ junit3Arguments.putString(ARGUMENT_NOT_ANNOTATION, "org.junit.runner.RunWith");
+ addTestListPackage(junit3Arguments);
+ TestRequest listJUnit3TestRequest = createListTestRequest(junit3Arguments);
+ results = executorBuilder.build().execute(listJUnit3TestRequest);
+
+ Bundle junit4Arguments = new Bundle(InstrumentationRegistry.getArguments());
+ junit4Arguments.putString(ARGUMENT_ANNOTATION, "org.junit.runner.RunWith");
+ addTestListPackage(junit4Arguments);
+
+ // Do not use Log runner from android test support.
+ //
+ // Test logging and execution skipping is handled by BaseJUnit4ClassRunner,
+ // having ARGUMENT_LOG_ONLY in argument bundle here causes AndroidJUnitRunner
+ // to use its own log-only class runner instead of BaseJUnit4ClassRunner.
+ junit4Arguments.remove(ARGUMENT_LOG_ONLY);
+
+ TestRequest listJUnit4TestRequest = createListTestRequest(junit4Arguments);
+ results.putAll(executorBuilder.build().execute(listJUnit4TestRequest));
+ listener.saveTestsToJson(
+ InstrumentationRegistry.getArguments().getString(LIST_ALL_TESTS_FLAG));
+ } catch (IOException | RuntimeException e) {
+ String msg = "Fatal exception when running tests";
+ Log.e(TAG, msg, e);
+ // report the exception to instrumentation out
+ results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+ msg + "\n" + Log.getStackTraceString(e));
+ }
+ finish(Activity.RESULT_OK, results);
+ }
+
+ private TestRequest createListTestRequest(Bundle arguments) {
+ RunnerArgs runnerArgs =
+ new RunnerArgs.Builder().fromManifest(this).fromBundle(arguments).build();
+ TestRequestBuilder builder = new IncrementalInstallTestRequestBuilder(this, arguments);
+ builder.addFromRunnerArgs(runnerArgs);
+ builder.addApkToScan(getContext().getPackageCodePath());
+ return builder.build();
+ }
+
+ static boolean shouldListTests(Bundle arguments) {
+ return arguments != null && arguments.getString(LIST_ALL_TESTS_FLAG) != null;
+ }
+
+ /**
+ * Wraps TestRequestBuilder to make it work with incremental install.
+ */
+ private static class IncrementalInstallTestRequestBuilder extends TestRequestBuilder {
+ List<String> mExcludedPrefixes = new ArrayList<String>();
+ boolean mHasClassList;
+
+ public IncrementalInstallTestRequestBuilder(Instrumentation instr, Bundle bundle) {
+ super(instr, bundle);
+ }
+
+ @Override
+ public TestRequestBuilder addFromRunnerArgs(RunnerArgs runnerArgs) {
+ mExcludedPrefixes.addAll(runnerArgs.notTestPackages);
+ return super.addFromRunnerArgs(runnerArgs);
+ }
+
+ @Override
+ public TestRequestBuilder addTestClass(String className) {
+ mHasClassList = true;
+ return super.addTestClass(className);
+ }
+
+ @Override
+ public TestRequestBuilder addTestMethod(String testClassName, String testMethodName) {
+ mHasClassList = true;
+ return super.addTestMethod(testClassName, testMethodName);
+ }
+
+ @Override
+ public TestRequest build() {
+ // See crbug://841695. TestLoader.isTestClass is incorrectly deciding that
+ // InstrumentationTestSuite is a test class.
+ removeTestClass("android.test.InstrumentationTestSuite");
+ // If a test class was requested, then no need to iterate class loader.
+ if (mHasClassList) {
+ return super.build();
+ }
+ maybeScanIncrementalClasspath();
+ return super.build();
+ }
+
+ private void maybeScanIncrementalClasspath() {
+ DexFile[] incrementalJars = null;
+ try {
+ Class<?> bootstrapClass =
+ Class.forName("org.chromium.incrementalinstall.BootstrapApplication");
+ incrementalJars =
+ (DexFile[]) bootstrapClass.getDeclaredField("sIncrementalDexFiles")
+ .get(null);
+ } catch (Exception e) {
+ // Not an incremental apk.
+ }
+ if (incrementalJars != null) {
+ // builder.addApkToScan uses new DexFile(path) under the hood, which on Dalvik OS's
+ // assumes that the optimized dex is in the default location (crashes).
+ // Perform our own dex file scanning instead as a workaround.
+ addTestClasses(incrementalJars, this);
+ }
+ }
+
+ private boolean startsWithAny(String str, List<String> prefixes) {
+ for (String prefix : prefixes) {
+ if (str.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void addTestClasses(DexFile[] dexFiles, TestRequestBuilder builder) {
+ Log.i(TAG, "Scanning incremental classpath.");
+ try {
+ Field excludedPackagesField =
+ TestRequestBuilder.class.getDeclaredField("DEFAULT_EXCLUDED_PACKAGES");
+ excludedPackagesField.setAccessible(true);
+ mExcludedPrefixes.addAll(Arrays.asList((String[]) excludedPackagesField.get(null)));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ // Mirror TestRequestBuilder.getClassNamesFromClassPath().
+ TestLoader loader = new TestLoader();
+ for (DexFile dexFile : dexFiles) {
+ Enumeration<String> classNames = dexFile.entries();
+ while (classNames.hasMoreElements()) {
+ String className = classNames.nextElement();
+ if (!className.contains("$") && !startsWithAny(className, mExcludedPrefixes)
+ && loader.loadIfTest(className) != null) {
+ addTestClass(className);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java
new file mode 100644
index 0000000000..e5eb2731b7
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java
@@ -0,0 +1,162 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.support.v4.content.ContextCompat;
+
+import org.chromium.android.support.PackageManagerWrapper;
+import org.chromium.base.Log;
+import org.chromium.base.annotations.MainDex;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Functionality common to the JUnit3 and JUnit4 runners.
+ */
+@MainDex
+class BaseChromiumRunnerCommon {
+ private static final String TAG = "base_test";
+
+ /**
+ * A ContextWrapper that allows multidex test APKs to extract secondary dexes into
+ * the APK under test's data directory.
+ */
+ @MainDex
+ static class MultiDexContextWrapper extends ContextWrapper {
+ private Context mAppContext;
+
+ MultiDexContextWrapper(Context instrContext, Context appContext) {
+ super(instrContext);
+ mAppContext = appContext;
+ }
+
+ @Override
+ public File getFilesDir() {
+ return mAppContext.getFilesDir();
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ return mAppContext.getSharedPreferences(name, mode);
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return new PackageManagerWrapper(super.getPackageManager()) {
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags) {
+ try {
+ ApplicationInfo ai = super.getApplicationInfo(packageName, flags);
+ if (packageName.equals(getPackageName())) {
+ File dataDir = new File(
+ ContextCompat.getCodeCacheDir(mAppContext), "test-multidex");
+ if (!dataDir.exists() && !dataDir.mkdirs()) {
+ throw new IOException(String.format(
+ "Unable to create test multidex directory \"%s\"",
+ dataDir.getPath()));
+ }
+ ai.dataDir = dataDir.getPath();
+ }
+ return ai;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get application info for %s", packageName, e);
+ }
+ return null;
+ }
+ };
+ }
+ }
+
+ /**
+ * Ensure all test dex entries precede app dex entries.
+ *
+ * @param cl ClassLoader to modify. Assumed to be a derivative of
+ * {@link dalvik.system.BaseDexClassLoader}. If this isn't
+ * the case, reordering will fail.
+ */
+ static void reorderDexPathElements(ClassLoader cl, Context context, Context targetContext) {
+ try {
+ Log.i(TAG,
+ "Reordering dex files. If you're building a multidex test APK and see a "
+ + "class resolving to an unexpected implementation, this may be why.");
+ Field pathListField = findField(cl, "pathList");
+ Object dexPathList = pathListField.get(cl);
+ Field dexElementsField = findField(dexPathList, "dexElements");
+ Object[] dexElementsList = (Object[]) dexElementsField.get(dexPathList);
+ Arrays.sort(dexElementsList,
+ new DexListReorderingComparator(
+ context.getPackageName(), targetContext.getPackageName()));
+ dexElementsField.set(dexPathList, dexElementsList);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to reorder dex elements for testing.", e);
+ }
+ }
+
+ /**
+ * Comparator for sorting dex list entries.
+ *
+ * Using this to sort a list of dex list entries will result in the following order:
+ * - Strings that contain neither the test package nor the app package in lexicographical
+ * order.
+ * - Strings that contain the test package in lexicographical order.
+ * - Strings that contain the app package but not the test package in lexicographical order.
+ */
+ private static class DexListReorderingComparator implements Comparator<Object>, Serializable {
+ private String mTestPackage;
+ private String mAppPackage;
+
+ public DexListReorderingComparator(String testPackage, String appPackage) {
+ mTestPackage = testPackage;
+ mAppPackage = appPackage;
+ }
+
+ @Override
+ public int compare(Object o1, Object o2) {
+ String s1 = o1.toString();
+ String s2 = o2.toString();
+ if (s1.contains(mTestPackage)) {
+ if (!s2.contains(mTestPackage)) {
+ if (s2.contains(mAppPackage)) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ } else if (s1.contains(mAppPackage)) {
+ if (s2.contains(mTestPackage)) {
+ return 1;
+ } else if (!s2.contains(mAppPackage)) {
+ return 1;
+ }
+ } else if (s2.contains(mTestPackage) || s2.contains(mAppPackage)) {
+ return -1;
+ }
+ return s1.compareTo(s2);
+ }
+ }
+
+ private static Field findField(Object instance, String name) throws NoSuchFieldException {
+ for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
+ try {
+ Field f = clazz.getDeclaredField(name);
+ f.setAccessible(true);
+ return f;
+ } catch (NoSuchFieldException e) {
+ }
+ }
+ throw new NoSuchFieldException(
+ "Unable to find field " + name + " in " + instance.getClass());
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
new file mode 100644
index 0000000000..49f27b5089
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
@@ -0,0 +1,277 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import static org.chromium.base.test.BaseChromiumAndroidJUnitRunner.shouldListTests;
+
+import android.content.Context;
+import android.support.annotation.CallSuper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import android.support.test.internal.util.AndroidRunnerParams;
+
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.test.BaseTestResult.PreTestHook;
+import org.chromium.base.test.params.MethodParamAnnotationRule;
+import org.chromium.base.test.util.DisableIfSkipCheck;
+import org.chromium.base.test.util.MinAndroidSdkLevelSkipCheck;
+import org.chromium.base.test.util.RestrictionSkipCheck;
+import org.chromium.base.test.util.SkipCheck;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A custom runner for JUnit4 tests that checks requirements to conditionally ignore tests.
+ *
+ * This ClassRunner imports from AndroidJUnit4ClassRunner which is a hidden but accessible
+ * class. The reason is that default JUnit4 runner for Android is a final class,
+ * AndroidJUnit4. We need to extends an inheritable class to change {@link #runChild}
+ * and {@link #isIgnored} to add SkipChecks and PreTesthook.
+ */
+public class BaseJUnit4ClassRunner extends AndroidJUnit4ClassRunner {
+ private static final String TAG = "BaseJUnit4ClassRunnr";
+
+ private static final String EXTRA_TRACE_FILE =
+ "org.chromium.base.test.BaseJUnit4ClassRunner.TraceFile";
+
+ /**
+ * Create a BaseJUnit4ClassRunner to run {@code klass} and initialize values.
+ *
+ * To add more SkipCheck or PreTestHook in subclass, create Lists of checks and hooks,
+ * pass them into the super constructors. If you want make a subclass extendable by other
+ * class runners, you also have to create a constructor similar to the following one that
+ * merges default checks or hooks with this checks and hooks passed in by constructor.
+ *
+ * <pre>
+ * <code>
+ * e.g.
+ * public ChildRunner extends BaseJUnit4ClassRunner {
+ * public ChildRunner(final Class<?> klass) {
+ * throws InitializationError {
+ * this(klass, Collections.emptyList(), Collections.emptyList(),
+ * Collections.emptyList());
+ * }
+ *
+ * public ChildRunner(
+ * final Class<?> klass, List<SkipCheck> checks, List<PreTestHook> hook,
+ * List<TestRule> rules) { throws InitializationError { super(klass, mergeList( checks,
+ * getSkipChecks()), mergeList(hooks, getPreTestHooks()));
+ * }
+ *
+ * public List<SkipCheck> getSkipChecks() {...}
+ *
+ * public List<PreTestHook> getPreTestHooks() {...}
+ * </code>
+ * </pre>
+ *
+ * @throws InitializationError if the test class malformed
+ */
+ public BaseJUnit4ClassRunner(final Class<?> klass) throws InitializationError {
+ super(klass,
+ new AndroidRunnerParams(InstrumentationRegistry.getInstrumentation(),
+ InstrumentationRegistry.getArguments(), false, 0L, false));
+
+ String traceOutput = InstrumentationRegistry.getArguments().getString(EXTRA_TRACE_FILE);
+
+ if (traceOutput != null) {
+ File traceOutputFile = new File(traceOutput);
+ File traceOutputDir = traceOutputFile.getParentFile();
+
+ if (traceOutputDir != null) {
+ if (traceOutputDir.exists() || traceOutputDir.mkdirs()) {
+ TestTraceEvent.enable(traceOutputFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Merge two List into a new ArrayList.
+ *
+ * Used to merge the default SkipChecks/PreTestHooks with the subclasses's
+ * SkipChecks/PreTestHooks.
+ */
+ private static <T> List<T> mergeList(List<T> listA, List<T> listB) {
+ List<T> l = new ArrayList<>(listA);
+ l.addAll(listB);
+ return l;
+ }
+
+ @SafeVarargs
+ protected static <T> List<T> addToList(List<T> list, T... additionalEntries) {
+ return mergeList(list, Arrays.asList(additionalEntries));
+ }
+
+ @Override
+ protected void collectInitializationErrors(List<Throwable> errors) {
+ super.collectInitializationErrors(errors);
+ // Log any initialization errors to help debugging, as the host-side test runner can get
+ // confused by the thrown exception.
+ if (!errors.isEmpty()) {
+ Log.e(TAG, "Initialization errors in %s: %s", getTestClass().getName(), errors);
+ }
+ }
+
+ /**
+ * Override this method to return a list of {@link SkipCheck}s}.
+ *
+ * Additional hooks can be added to the list using {@link #addToList}:
+ * {@code return addToList(super.getSkipChecks(), check1, check2);}
+ */
+ @CallSuper
+ protected List<SkipCheck> getSkipChecks() {
+ return Arrays.asList(new RestrictionSkipCheck(InstrumentationRegistry.getTargetContext()),
+ new MinAndroidSdkLevelSkipCheck(), new DisableIfSkipCheck());
+ }
+
+ /**
+ * Override this method to return a list of {@link PreTestHook}s.
+ *
+ * Additional hooks can be added to the list using {@link #addToList}:
+ * {@code return addToList(super.getPreTestHooks(), hook1, hook2);}
+ * TODO(bauerb): Migrate PreTestHook to TestRule.
+ */
+ @CallSuper
+ protected List<PreTestHook> getPreTestHooks() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Override this method to return a list of method rules that should be applied to all tests
+ * run with this test runner.
+ *
+ * Additional rules can be added to the list using {@link #addToList}:
+ * {@code return addToList(super.getDefaultMethodRules(), rule1, rule2);}
+ */
+ @CallSuper
+ protected List<MethodRule> getDefaultMethodRules() {
+ return Collections.singletonList(new MethodParamAnnotationRule());
+ }
+
+ /**
+ * Override this method to return a list of rules that should be applied to all tests run with
+ * this test runner.
+ *
+ * Additional rules can be added to the list using {@link #addToList}:
+ * {@code return addToList(super.getDefaultTestRules(), rule1, rule2);}
+ */
+ @CallSuper
+ protected List<TestRule> getDefaultTestRules() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Evaluate whether a FrameworkMethod is ignored based on {@code SkipCheck}s.
+ */
+ @Override
+ protected boolean isIgnored(FrameworkMethod method) {
+ return super.isIgnored(method) || shouldSkip(method);
+ }
+
+ @Override
+ protected List<MethodRule> rules(Object target) {
+ List<MethodRule> declaredRules = super.rules(target);
+ List<MethodRule> defaultRules = getDefaultMethodRules();
+ return mergeList(defaultRules, declaredRules);
+ }
+
+ @Override
+ protected final List<TestRule> getTestRules(Object target) {
+ List<TestRule> declaredRules = super.getTestRules(target);
+ List<TestRule> defaultRules = getDefaultTestRules();
+ return mergeList(declaredRules, defaultRules);
+ }
+
+ /**
+ * Run test with or without execution based on bundle arguments.
+ */
+ @Override
+ public void run(RunNotifier notifier) {
+ ContextUtils.initApplicationContext(
+ InstrumentationRegistry.getTargetContext().getApplicationContext());
+ if (shouldListTests(InstrumentationRegistry.getArguments())) {
+ for (Description child : getDescription().getChildren()) {
+ notifier.fireTestStarted(child);
+ notifier.fireTestFinished(child);
+ }
+ return;
+ }
+
+ if (!CommandLine.isInitialized()) {
+ initCommandLineForTest();
+ }
+ super.run(notifier);
+ }
+
+ /**
+ * Override this method to change how test class runner initiate commandline flags
+ */
+ protected void initCommandLineForTest() {
+ CommandLine.init(null);
+ }
+
+ @Override
+ protected void runChild(FrameworkMethod method, RunNotifier notifier) {
+ String testName = method.getName();
+ TestTraceEvent.begin(testName);
+
+ runPreTestHooks(method);
+
+ super.runChild(method, notifier);
+
+ TestTraceEvent.end(testName);
+
+ // A new instance of BaseJUnit4ClassRunner is created on the device
+ // for each new method, so runChild will only be called once. Thus, we
+ // can disable tracing, and dump the output, once we get here.
+ TestTraceEvent.disable();
+ }
+
+ /**
+ * Loop through all the {@code PreTestHook}s to run them
+ */
+ private void runPreTestHooks(FrameworkMethod frameworkMethod) {
+ Method testMethod = frameworkMethod.getMethod();
+ Context targetContext = InstrumentationRegistry.getTargetContext();
+ for (PreTestHook hook : getPreTestHooks()) {
+ hook.run(targetContext, testMethod);
+ }
+ }
+
+ /**
+ * Loop through all the {@code SkipCheck}s to confirm whether a test should be ignored
+ */
+ private boolean shouldSkip(FrameworkMethod method) {
+ for (SkipCheck s : getSkipChecks()) {
+ if (s.shouldSkip(method)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Overriding this method to take screenshot of failure before tear down functions are run.
+ */
+ @Override
+ protected Statement withAfters(FrameworkMethod method, Object test, Statement base) {
+ return super.withAfters(method, test, new ScreenshotOnFailureStatement(base));
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java b/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java
new file mode 100644
index 0000000000..a80e0cc4a0
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java
@@ -0,0 +1,137 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+import org.chromium.base.Log;
+import org.chromium.base.test.util.SkipCheck;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A test result that can skip tests.
+ */
+public class BaseTestResult extends TestResult {
+ private static final String TAG = "base_test";
+
+ private static final int SLEEP_INTERVAL_MS = 50;
+ private static final int WAIT_DURATION_MS = 5000;
+
+ private final Instrumentation mInstrumentation;
+ private final List<SkipCheck> mSkipChecks;
+ private final List<PreTestHook> mPreTestHooks;
+
+ /**
+ * Creates an instance of BaseTestResult.
+ */
+ public BaseTestResult(Instrumentation instrumentation) {
+ mSkipChecks = new ArrayList<>();
+ mPreTestHooks = new ArrayList<>();
+ mInstrumentation = instrumentation;
+ }
+
+ /**
+ * An interface for classes that have some code to run before a test. They run after
+ * {@link SkipCheck}s. Provides access to the test method (and the annotations defined for it)
+ * and the instrumentation context.
+ */
+ public interface PreTestHook {
+ /**
+ * @param targetContext the instrumentation context that will be used during the test.
+ * @param testMethod the test method to be run.
+ */
+ public void run(Context targetContext, Method testMethod);
+ }
+
+ /**
+ * Adds a check for whether a test should run.
+ *
+ * @param skipCheck The check to add.
+ */
+ public void addSkipCheck(SkipCheck skipCheck) {
+ mSkipChecks.add(skipCheck);
+ }
+
+ /**
+ * Adds hooks that will be executed before each test that runs.
+ *
+ * @param preTestHook The hook to add.
+ */
+ public void addPreTestHook(PreTestHook preTestHook) {
+ mPreTestHooks.add(preTestHook);
+ }
+
+ protected boolean shouldSkip(TestCase test) {
+ for (SkipCheck s : mSkipChecks) {
+ if (s.shouldSkip(test)) return true;
+ }
+ return false;
+ }
+
+ private void runPreTestHooks(TestCase test) {
+ try {
+ Method testMethod = test.getClass().getMethod(test.getName());
+ Context targetContext = getTargetContext();
+
+ for (PreTestHook hook : mPreTestHooks) {
+ hook.run(targetContext, testMethod);
+ }
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Unable to run pre test hooks.", e);
+ }
+ }
+
+ @Override
+ protected void run(TestCase test) {
+ runPreTestHooks(test);
+
+ if (shouldSkip(test)) {
+ startTest(test);
+
+ Bundle skipResult = new Bundle();
+ skipResult.putString("class", test.getClass().getName());
+ skipResult.putString("test", test.getName());
+ skipResult.putBoolean("test_skipped", true);
+ mInstrumentation.sendStatus(0, skipResult);
+
+ endTest(test);
+ } else {
+ super.run(test);
+ }
+ }
+
+ /**
+ * Gets the target context.
+ *
+ * On older versions of Android, getTargetContext() may initially return null, so we have to
+ * wait for it to become available.
+ *
+ * @return The target {@link Context} if available; null otherwise.
+ */
+ public Context getTargetContext() {
+ Context targetContext = mInstrumentation.getTargetContext();
+ try {
+ long startTime = SystemClock.uptimeMillis();
+ // TODO(jbudorick): Convert this to CriteriaHelper once that moves to base/.
+ while (targetContext == null
+ && SystemClock.uptimeMillis() - startTime < WAIT_DURATION_MS) {
+ Thread.sleep(SLEEP_INTERVAL_MS);
+ targetContext = mInstrumentation.getTargetContext();
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted while attempting to initialize the command line.");
+ }
+ return targetContext;
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/ScreenshotOnFailureStatement.java b/base/test/android/javatests/src/org/chromium/base/test/ScreenshotOnFailureStatement.java
new file mode 100644
index 0000000000..397e8abf13
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/ScreenshotOnFailureStatement.java
@@ -0,0 +1,83 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.Log;
+
+import java.io.File;
+
+/**
+ * Statement that captures screenshots if |base| statement fails.
+ *
+ * If --screenshot-path commandline flag is given, this |Statement|
+ * will save a screenshot to the specified path in the case of a test failure.
+ */
+public class ScreenshotOnFailureStatement extends Statement {
+ private static final String TAG = "ScreenshotOnFail";
+
+ private static final String EXTRA_SCREENSHOT_FILE =
+ "org.chromium.base.test.ScreenshotOnFailureStatement.ScreenshotFile";
+
+ private final Statement mBase;
+
+ public ScreenshotOnFailureStatement(final Statement base) {
+ mBase = base;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ mBase.evaluate();
+ } catch (Throwable e) {
+ takeScreenshot();
+ throw e;
+ }
+ }
+
+ private void takeScreenshot() {
+ String screenshotFilePath =
+ InstrumentationRegistry.getArguments().getString(EXTRA_SCREENSHOT_FILE);
+ if (screenshotFilePath == null) {
+ Log.d(TAG,
+ String.format("Did not save screenshot of failure. Must specify %s "
+ + "instrumentation argument to enable this feature.",
+ EXTRA_SCREENSHOT_FILE));
+ return;
+ }
+
+ UiDevice uiDevice = null;
+ try {
+ uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ } catch (RuntimeException ex) {
+ Log.d(TAG, "Failed to initialize UiDevice", ex);
+ return;
+ }
+
+ File screenshotFile = new File(screenshotFilePath);
+ File screenshotDir = screenshotFile.getParentFile();
+ if (screenshotDir == null) {
+ Log.d(TAG,
+ String.format(
+ "Failed to create parent directory for %s. Can't save screenshot.",
+ screenshotFile));
+ return;
+ }
+ if (!screenshotDir.exists()) {
+ if (!screenshotDir.mkdirs()) {
+ Log.d(TAG,
+ String.format(
+ "Failed to create %s. Can't save screenshot.", screenshotDir));
+ return;
+ }
+ }
+ Log.d(TAG, String.format("Saving screenshot of test failure, %s", screenshotFile));
+ uiDevice.takeScreenshot(screenshotFile);
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/SetUpStatement.java b/base/test/android/javatests/src/org/chromium/base/test/SetUpStatement.java
new file mode 100644
index 0000000000..30ac2b6c5c
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/SetUpStatement.java
@@ -0,0 +1,35 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom Statement for SetUpTestRules.
+ *
+ * Calls {@link SetUpTestRule#setUp} before evaluating {@link SetUpTestRule#base} if
+ * {@link SetUpTestRule#shouldSetUp} is true
+ */
+public class SetUpStatement extends Statement {
+ private final Statement mBase;
+ private final SetUpTestRule<? extends TestRule> mSetUpTestRule;
+ private final boolean mShouldSetUp;
+
+ public SetUpStatement(
+ final Statement base, SetUpTestRule<? extends TestRule> callback, boolean shouldSetUp) {
+ mBase = base;
+ mSetUpTestRule = callback;
+ mShouldSetUp = shouldSetUp;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ if (mShouldSetUp) {
+ mSetUpTestRule.setUp();
+ }
+ mBase.evaluate();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/SetUpTestRule.java b/base/test/android/javatests/src/org/chromium/base/test/SetUpTestRule.java
new file mode 100644
index 0000000000..57dd8db552
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/SetUpTestRule.java
@@ -0,0 +1,35 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import org.junit.rules.TestRule;
+
+/**
+ * An interface for TestRules that can be configured to automatically run set-up logic prior
+ * to &#064;Before.
+ *
+ * TestRules that implement this interface should return a {@link SetUpStatement} from their {@link
+ * TestRule#apply} method
+ *
+ * @param <T> TestRule type that implements this SetUpTestRule
+ */
+public interface SetUpTestRule<T extends TestRule> {
+ /**
+ * Set whether the TestRule should run setUp automatically.
+ *
+ * So TestRule can be declared in test like this:
+ * <code>
+ * &#064;Rule TestRule mRule = new MySetUpTestRule().shouldSetUp(true);
+ * </code>
+ *
+ * @return itself to chain up the calls for convenience
+ */
+ T shouldSetUp(boolean runSetUp);
+
+ /**
+ * Specify setUp action in this method
+ */
+ void setUp();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/TestChildProcessConnection.java b/base/test/android/javatests/src/org/chromium/base/test/TestChildProcessConnection.java
new file mode 100644
index 0000000000..ae91b44cf3
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/TestChildProcessConnection.java
@@ -0,0 +1,87 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.chromium.base.process_launcher.ChildProcessConnection;
+
+/** An implementation of ChildProcessConnection that does not connect to a real service. */
+public class TestChildProcessConnection extends ChildProcessConnection {
+ private static class MockChildServiceConnection
+ implements ChildProcessConnection.ChildServiceConnection {
+ private boolean mBound;
+
+ @Override
+ public boolean bind() {
+ mBound = true;
+ return true;
+ }
+
+ @Override
+ public void unbind() {
+ mBound = false;
+ }
+
+ @Override
+ public boolean isBound() {
+ return mBound;
+ }
+ }
+
+ private int mPid;
+ private boolean mConnected;
+ private ServiceCallback mServiceCallback;
+
+ /**
+ * Creates a mock binding corresponding to real ManagedChildProcessConnection after the
+ * connection is established: with initial binding bound and no strong binding.
+ */
+ public TestChildProcessConnection(ComponentName serviceName, boolean bindToCaller,
+ boolean bindAsExternalService, Bundle serviceBundle) {
+ super(null /* context */, serviceName, bindToCaller, bindAsExternalService, serviceBundle,
+ new ChildServiceConnectionFactory() {
+ @Override
+ public ChildServiceConnection createConnection(Intent bindIntent, int bindFlags,
+ ChildServiceConnectionDelegate delegate) {
+ return new MockChildServiceConnection();
+ }
+ });
+ }
+
+ public void setPid(int pid) {
+ mPid = pid;
+ }
+
+ @Override
+ public int getPid() {
+ return mPid;
+ }
+
+ // We don't have a real service so we have to mock the connection status.
+ @Override
+ public void start(boolean useStrongBinding, ServiceCallback serviceCallback) {
+ super.start(useStrongBinding, serviceCallback);
+ mConnected = true;
+ mServiceCallback = serviceCallback;
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ mConnected = false;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return mConnected;
+ }
+
+ public ServiceCallback getServiceCallback() {
+ return mServiceCallback;
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/TestListInstrumentationRunListener.java b/base/test/android/javatests/src/org/chromium/base/test/TestListInstrumentationRunListener.java
new file mode 100644
index 0000000000..8cde57003c
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/TestListInstrumentationRunListener.java
@@ -0,0 +1,142 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import android.support.test.internal.runner.listener.InstrumentationRunListener;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.runner.Description;
+
+import org.chromium.base.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A RunListener that list out all the test information into a json file.
+ */
+public class TestListInstrumentationRunListener extends InstrumentationRunListener {
+ private static final String TAG = "TestListRunListener";
+ private static final Set<String> SKIP_METHODS = new HashSet<>(
+ Arrays.asList(new String[] {"toString", "hashCode", "annotationType", "equals"}));
+
+ private final Map<Class<?>, JSONObject> mTestClassJsonMap = new HashMap<>();
+
+ /**
+ * Store the test method description to a Map at the beginning of a test run.
+ */
+ @Override
+ public void testStarted(Description desc) throws Exception {
+ if (mTestClassJsonMap.containsKey(desc.getTestClass())) {
+ ((JSONArray) mTestClassJsonMap.get(desc.getTestClass()).get("methods"))
+ .put(getTestMethodJSON(desc));
+ } else {
+ Class<?> testClass = desc.getTestClass();
+ mTestClassJsonMap.put(desc.getTestClass(), new JSONObject()
+ .put("class", testClass.getName())
+ .put("superclass", testClass.getSuperclass().getName())
+ .put("annotations",
+ getAnnotationJSON(Arrays.asList(testClass.getAnnotations())))
+ .put("methods", new JSONArray().put(getTestMethodJSON(desc))));
+ }
+ }
+
+ /**
+ * Create a JSONArray with all the test class JSONObjects and save it to listed output path.
+ */
+ public void saveTestsToJson(String outputPath) throws IOException {
+ Writer writer = null;
+ File file = new File(outputPath);
+ try {
+ writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
+ JSONArray allTestClassesJSON = new JSONArray(mTestClassJsonMap.values());
+ writer.write(allTestClassesJSON.toString());
+ } catch (IOException e) {
+ Log.e(TAG, "failed to write json to file", e);
+ throw e;
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ // Intentionally ignore IOException when closing writer
+ }
+ }
+ }
+ }
+
+ /**
+ * Return a JSONOject that represent a Description of a method".
+ */
+ static JSONObject getTestMethodJSON(Description desc) throws Exception {
+ return new JSONObject()
+ .put("method", desc.getMethodName())
+ .put("annotations", getAnnotationJSON(desc.getAnnotations()));
+ }
+
+ /**
+ * Create a JSONObject that represent a collection of anntations.
+ *
+ * For example, for the following group of annotations for ExampleClass
+ * <code>
+ * @A
+ * @B(message = "hello", level = 3)
+ * public class ExampleClass() {}
+ * </code>
+ *
+ * This method would return a JSONObject as such:
+ * <code>
+ * {
+ * "A": {},
+ * "B": {
+ * "message": "hello",
+ * "level": "3"
+ * }
+ * }
+ * </code>
+ *
+ * The method accomplish this by though through each annotation and reflectively call the
+ * annotation's method to get the element value, with exceptions to methods like "equals()"
+ * or "hashCode".
+ */
+ static JSONObject getAnnotationJSON(Collection<Annotation> annotations)
+ throws Exception {
+ JSONObject annotationsJsons = new JSONObject();
+ for (Annotation a : annotations) {
+ JSONObject elementJsonObject = new JSONObject();
+ for (Method method : a.annotationType().getMethods()) {
+ if (SKIP_METHODS.contains(method.getName())) {
+ continue;
+ }
+ try {
+ Object value = method.invoke(a);
+ if (value == null) {
+ elementJsonObject.put(method.getName(), null);
+ } else {
+ elementJsonObject.put(method.getName(),
+ value.getClass().isArray()
+ ? new JSONArray(Arrays.asList((Object[]) value))
+ : value.toString());
+ }
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ annotationsJsons.put(a.annotationType().getSimpleName(), elementJsonObject);
+ }
+ return annotationsJsons;
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/TestTraceEvent.java b/base/test/android/javatests/src/org/chromium/base/test/TestTraceEvent.java
new file mode 100644
index 0000000000..5e0f6b31f1
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/TestTraceEvent.java
@@ -0,0 +1,168 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import org.chromium.base.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+
+/**
+ * TestTraceEvent is a modified version of TraceEvent, intended for tracing test runs.
+ */
+public class TestTraceEvent {
+ private static final String TAG = "TestTraceEvent";
+
+ /** The event types understood by the trace scripts. */
+ private enum EventType {
+ BEGIN("B"),
+ END("E"),
+ INSTANT("I");
+
+ private final String mTypeStr;
+
+ EventType(String typeStr) {
+ mTypeStr = typeStr;
+ }
+
+ @Override
+ public String toString() {
+ return mTypeStr;
+ }
+ }
+
+ // Locks internal fields.
+ private static final Object sLock = new Object();
+
+ private static File sOutputFile;
+
+ private static boolean sEnabled;
+
+ // A list of trace event strings.
+ private static JSONArray sTraceStrings;
+
+ /**
+ * Enable tracing, and set a specific output file. If tracing was previously enabled and
+ * disabled, that data is cleared.
+ *
+ * @param file Which file to append the trace data to.
+ */
+ public static void enable(File outputFile) {
+ synchronized (sLock) {
+ if (sEnabled) return;
+
+ sEnabled = true;
+ sOutputFile = outputFile;
+ sTraceStrings = new JSONArray();
+ }
+ }
+
+ /**
+ * Disabling of tracing will dump trace data to the system log.
+ */
+ public static void disable() {
+ synchronized (sLock) {
+ if (!sEnabled) return;
+
+ sEnabled = false;
+ dumpTraceOutput();
+ sTraceStrings = null;
+ }
+ }
+
+ /**
+ * @return True if tracing is enabled, false otherwise.
+ */
+ public static boolean isEnabled() {
+ synchronized (sLock) {
+ return sEnabled;
+ }
+ }
+
+ /**
+ * Record an "instant" trace event. E.g. "screen update happened".
+ */
+ public static void instant(String name) {
+ synchronized (sLock) {
+ if (!sEnabled) return;
+
+ saveTraceString(name, name.hashCode(), EventType.INSTANT);
+ }
+ }
+
+ /**
+ * Record an "begin" trace event. Begin trace events should have a matching end event (recorded
+ * by calling {@link #end(String)}).
+ */
+ public static void begin(String name) {
+ synchronized (sLock) {
+ if (!sEnabled) return;
+
+ saveTraceString(name, name.hashCode(), EventType.BEGIN);
+ }
+ }
+
+ /**
+ * Record an "end" trace event, to match a begin event (recorded by calling {@link
+ * #begin(String)}). The time delta between begin and end is usually interesting to graph code.
+ */
+ public static void end(String name) {
+ synchronized (sLock) {
+ if (!sEnabled) return;
+
+ saveTraceString(name, name.hashCode(), EventType.END);
+ }
+ }
+
+ /**
+ * Save a trace event as a JSON dict.
+ *
+ * @param name The trace data.
+ * @param id An identifier for the event, to be saved as the thread ID.
+ * @param type the type of trace event (B, E, I).
+ */
+ private static void saveTraceString(String name, long id, EventType type) {
+ // We use System.currentTimeMillis() because it agrees with the value of
+ // the $EPOCHREALTIME environment variable. The Python test runner code
+ // uses that variable to synchronize timing.
+ long timeMicroseconds = System.currentTimeMillis() * 1000;
+
+ try {
+ JSONObject traceObj = new JSONObject();
+ traceObj.put("cat", "Java");
+ traceObj.put("ts", timeMicroseconds);
+ traceObj.put("ph", type);
+ traceObj.put("name", name);
+ traceObj.put("tid", id);
+
+ sTraceStrings.put(traceObj);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Dump all tracing data we have saved up to the log.
+ * Output as JSON for parsing convenience.
+ */
+ private static void dumpTraceOutput() {
+ try {
+ PrintStream stream = new PrintStream(new FileOutputStream(sOutputFile, true));
+ try {
+ stream.print(sTraceStrings);
+ } finally {
+ if (stream != null) stream.close();
+ }
+ } catch (FileNotFoundException ex) {
+ Log.e(TAG, "Unable to dump trace data to output file.");
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/BaseJUnit4RunnerDelegate.java b/base/test/android/javatests/src/org/chromium/base/test/params/BaseJUnit4RunnerDelegate.java
new file mode 100644
index 0000000000..c0dcd469d2
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/BaseJUnit4RunnerDelegate.java
@@ -0,0 +1,42 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.params.ParameterizedRunner.ParameterizedTestInstantiationException;
+
+import java.util.List;
+
+/**
+ * Class runner delegate that extends BaseJUnit4ClassRunner
+ */
+public final class BaseJUnit4RunnerDelegate
+ extends BaseJUnit4ClassRunner implements ParameterizedRunnerDelegate {
+ private ParameterizedRunnerDelegateCommon mDelegateCommon;
+
+ public BaseJUnit4RunnerDelegate(Class<?> klass,
+ ParameterizedRunnerDelegateCommon delegateCommon) throws InitializationError {
+ super(klass);
+ mDelegateCommon = delegateCommon;
+ }
+
+ @Override
+ public void collectInitializationErrors(List<Throwable> errors) {
+ ParameterizedRunnerDelegateCommon.collectInitializationErrors(errors);
+ }
+
+ @Override
+ public List<FrameworkMethod> computeTestMethods() {
+ return mDelegateCommon.computeTestMethods();
+ }
+
+ @Override
+ public Object createTest() throws ParameterizedTestInstantiationException {
+ return mDelegateCommon.createTest();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/BlockJUnit4RunnerDelegate.java b/base/test/android/javatests/src/org/chromium/base/test/params/BlockJUnit4RunnerDelegate.java
new file mode 100644
index 0000000000..7c948bb141
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/BlockJUnit4RunnerDelegate.java
@@ -0,0 +1,42 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+
+import org.chromium.base.test.params.ParameterizedRunner.ParameterizedTestInstantiationException;
+
+import java.util.List;
+
+/**
+ * Parameterized class runner delegate that extends BlockJUnit4ClassRunner
+ */
+public final class BlockJUnit4RunnerDelegate
+ extends BlockJUnit4ClassRunner implements ParameterizedRunnerDelegate {
+ private ParameterizedRunnerDelegateCommon mDelegateCommon;
+
+ public BlockJUnit4RunnerDelegate(Class<?> klass,
+ ParameterizedRunnerDelegateCommon delegateCommon) throws InitializationError {
+ super(klass);
+ mDelegateCommon = delegateCommon;
+ }
+
+ @Override
+ public void collectInitializationErrors(List<Throwable> errors) {
+ ParameterizedRunnerDelegateCommon.collectInitializationErrors(errors);
+ }
+
+ @Override
+ public List<FrameworkMethod> computeTestMethods() {
+ return mDelegateCommon.computeTestMethods();
+ }
+
+ @Override
+ public Object createTest() throws ParameterizedTestInstantiationException {
+ return mDelegateCommon.createTest();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/MethodParamAnnotationRule.java b/base/test/android/javatests/src/org/chromium/base/test/params/MethodParamAnnotationRule.java
new file mode 100644
index 0000000000..2986b96c08
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/MethodParamAnnotationRule.java
@@ -0,0 +1,62 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterAfter;
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Processes {@link UseMethodParameterBefore} and {@link UseMethodParameterAfter} annotations to run
+ * the corresponding methods. To use, add an instance to the test class and annotate it with
+ * {@code @}{@link org.junit.Rule Rule}.
+ */
+public class MethodParamAnnotationRule extends MethodParamRule {
+ @Override
+ protected Statement applyParameterAndValues(final Statement base, Object target,
+ Class<? extends ParameterProvider> parameterProvider, List<Object> values) {
+ final List<Method> beforeMethods = new ArrayList<>();
+ final List<Method> afterMethods = new ArrayList<>();
+ for (Method m : target.getClass().getDeclaredMethods()) {
+ if (!m.getReturnType().equals(Void.TYPE)) continue;
+ if (!Modifier.isPublic(m.getModifiers())) continue;
+
+ UseMethodParameterBefore beforeAnnotation =
+ m.getAnnotation(UseMethodParameterBefore.class);
+ if (beforeAnnotation != null && beforeAnnotation.value().equals(parameterProvider)) {
+ beforeMethods.add(m);
+ }
+
+ UseMethodParameterAfter afterAnnotation =
+ m.getAnnotation(UseMethodParameterAfter.class);
+ if (afterAnnotation != null && afterAnnotation.value().equals(parameterProvider)) {
+ afterMethods.add(m);
+ }
+ }
+
+ if (beforeMethods.isEmpty() && afterMethods.isEmpty()) return base;
+
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ for (Method m : beforeMethods) {
+ m.invoke(target, values.toArray());
+ }
+
+ base.evaluate();
+
+ for (Method m : afterMethods) {
+ m.invoke(target, values.toArray());
+ }
+ }
+ };
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/MethodParamRule.java b/base/test/android/javatests/src/org/chromium/base/test/params/MethodParamRule.java
new file mode 100644
index 0000000000..440831af2f
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/MethodParamRule.java
@@ -0,0 +1,35 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
+
+import java.util.List;
+
+/**
+ * Abstract base class for rules that are applied to test methods using
+ * {@link org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter method parameters}.
+ */
+public abstract class MethodParamRule implements MethodRule {
+ @Override
+ public Statement apply(final Statement base, FrameworkMethod method, Object target) {
+ UseMethodParameter useParameterProvider = method.getAnnotation(UseMethodParameter.class);
+ if (useParameterProvider == null) return base;
+ Class<? extends ParameterProvider> parameterProvider = useParameterProvider.value();
+
+ if (!(method instanceof ParameterizedFrameworkMethod)) return base;
+ ParameterSet parameters = ((ParameterizedFrameworkMethod) method).getParameterSet();
+ List<Object> values = parameters.getValues();
+
+ return applyParameterAndValues(base, target, parameterProvider, values);
+ }
+
+ protected abstract Statement applyParameterAndValues(final Statement base, Object target,
+ Class<? extends ParameterProvider> parameterProvider, List<Object> values);
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterAnnotations.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterAnnotations.java
new file mode 100644
index 0000000000..79183693ec
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterAnnotations.java
@@ -0,0 +1,78 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotations for Parameterized Tests
+ */
+public class ParameterAnnotations {
+ /**
+ * Annotation for test methods to indicate associated {@link ParameterProvider}.
+ * Note: the class referred to must be public and have a public default constructor.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface UseMethodParameter {
+ Class<? extends ParameterProvider> value();
+ }
+
+ /**
+ * Annotation for methods that should be called before running a test with method parameters.
+ *
+ * In order to use this, add a {@link MethodParamAnnotationRule} annotated with
+ * {@code @}{@link org.junit.Rule Rule} to your test class.
+ * @see ParameterProvider
+ * @see UseMethodParameterAfter
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface UseMethodParameterBefore {
+ Class<? extends ParameterProvider> value();
+ }
+
+ /**
+ * Annotation for methods that should be called after running a test with method parameters.
+ *
+ * In order to use this, add a {@link MethodParamAnnotationRule} annotated with
+ * {@code @}{@link org.junit.Rule Rule} to your test class.
+ * @see ParameterProvider
+ * @see UseMethodParameterBefore
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface UseMethodParameterAfter {
+ Class<? extends ParameterProvider> value();
+ }
+
+ /**
+ * Annotation for static field of a `List<ParameterSet>` for entire test class
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface ClassParameter {}
+
+ /**
+ * Annotation for static field of a `List<ParameterSet>` of TestRule
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface RuleParameter {}
+
+ /**
+ * Annotation for test class, it specifies which ParameterizeRunnerDelegate to use.
+ *
+ * The default ParameterizedRunnerDelegate is BaseJUnit4RunnerDelegate.class
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface UseRunnerDelegate {
+ Class<? extends ParameterizedRunnerDelegate> value();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterSet.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterSet.java
new file mode 100644
index 0000000000..1cdb576b05
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterSet.java
@@ -0,0 +1,129 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.Assert;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * A set of parameters for one *SINGLE* test method or test class constructor.
+ *
+ * For example, <code>new ParameterSet().value("a", "b")</code> is intended for
+ * a test method/constructor that takes in two string as arguments.
+ * <code>public void testSimple(String a, String b) {...}</code>
+ * or
+ * <code>public MyTestClass(String a, String b) {...}</code>
+ *
+ * To parameterize testSimple or MyTestClass's tests, create multiple ParameterSets
+ * <code>
+ * static List<ParameterSet> sAllParameterSets = new ArrayList<>();
+ * static {
+ * sAllParameterSets.add(new ParameterSet().value("a", "b");
+ * sAllParameterSets.add(new ParameterSet().value("c", "d");
+ * }
+ */
+public class ParameterSet {
+ private List<Object> mValues;
+ private String mName;
+
+ public ParameterSet() {}
+
+ public ParameterSet value(Object firstArg, Object... objects) {
+ List<Object> parameterList = new ArrayList<Object>();
+ parameterList.add(firstArg);
+ parameterList.addAll(Arrays.asList(objects));
+ Assert.assertTrue(
+ "Can not create ParameterSet with no parameters", parameterList.size() != 0);
+ mValues = validateAndCopy(parameterList);
+ return this;
+ }
+
+ public ParameterSet name(String name) {
+ mName = name;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ if (mValues == null) {
+ return "null";
+ }
+ return Arrays.toString(mValues.toArray());
+ }
+
+ private List<Object> validateAndCopy(List<Object> values) {
+ List<Object> tempValues = new ArrayList<>();
+ for (Object o : values) {
+ if (o == null) {
+ tempValues.add(null);
+ } else {
+ if (o.getClass().isPrimitive() || ACCEPTABLE_TYPES.contains(o.getClass())
+ || o instanceof Callable) {
+ tempValues.add(o);
+ } else {
+ // TODO(yolandyan): maybe come up with way to support
+ // complex object while handling immutability at the
+ // same time
+ throw new IllegalArgumentException("Type \"%s\" is not supported in"
+ + " parameterized testing at this time. Accepted types include"
+ + " all primitive types along with "
+ + Arrays.toString(ACCEPTABLE_TYPES.toArray(
+ new String[ACCEPTABLE_TYPES.size()])));
+ }
+ }
+ }
+ return Collections.unmodifiableList(tempValues);
+ }
+
+ String getName() {
+ if (mName == null) {
+ return "";
+ }
+ return mName;
+ }
+
+ List<Object> getValues() {
+ return mValues;
+ }
+
+ int size() {
+ if (mValues == null) return 0;
+ return mValues.size();
+ }
+
+ private static final Set<Class<?>> ACCEPTABLE_TYPES = getAcceptableTypes();
+
+ /**
+ * Any immutable class is acceptable.
+ */
+ private static Set<Class<?>> getAcceptableTypes() {
+ Set<Class<?>> ret = new HashSet<Class<?>>();
+ ret.add(Boolean.class);
+ ret.add(Byte.class);
+ ret.add(Character.class);
+ ret.add(Class.class);
+ ret.add(Double.class);
+ ret.add(File.class);
+ ret.add(Float.class);
+ ret.add(Integer.class);
+ ret.add(Long.class);
+ ret.add(Short.class);
+ ret.add(String.class);
+ ret.add(URI.class);
+ ret.add(URL.class);
+ ret.add(Void.class);
+ return ret;
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedFrameworkMethod.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedFrameworkMethod.java
new file mode 100644
index 0000000000..f3333b5720
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedFrameworkMethod.java
@@ -0,0 +1,94 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Custom FrameworkMethod that includes a {@code ParameterSet} that
+ * represents the parameters for this test method
+ */
+public class ParameterizedFrameworkMethod extends FrameworkMethod {
+ private ParameterSet mParameterSet;
+ private String mName;
+
+ public ParameterizedFrameworkMethod(
+ Method method, ParameterSet parameterSet, String classParameterSetName) {
+ super(method);
+ mParameterSet = parameterSet;
+ String postFix = "";
+ if (classParameterSetName != null && !classParameterSetName.isEmpty()) {
+ postFix += "_" + classParameterSetName;
+ }
+ if (parameterSet != null && !parameterSet.getName().isEmpty()) {
+ postFix += "_" + parameterSet.getName();
+ }
+ mName = postFix.isEmpty() ? method.getName() : method.getName() + "_" + postFix;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public Object invokeExplosively(Object target, Object... params) throws Throwable {
+ if (mParameterSet != null) {
+ return super.invokeExplosively(target, mParameterSet.getValues().toArray());
+ }
+ return super.invokeExplosively(target, params);
+ }
+
+ static List<FrameworkMethod> wrapAllFrameworkMethods(
+ Collection<FrameworkMethod> frameworkMethods, String classParameterSetName) {
+ List<FrameworkMethod> results = new ArrayList<>();
+ for (FrameworkMethod frameworkMethod : frameworkMethods) {
+ results.add(new ParameterizedFrameworkMethod(
+ frameworkMethod.getMethod(), null, classParameterSetName));
+ }
+ return results;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ParameterizedFrameworkMethod) {
+ ParameterizedFrameworkMethod method = (ParameterizedFrameworkMethod) obj;
+ return super.equals(obj) && method.getParameterSet().equals(getParameterSet())
+ && method.getName().equals(getName());
+ }
+ return false;
+ }
+
+ /**
+ * Override hashCode method to distinguish two ParameterizedFrameworkmethod with same
+ * Method object.
+ */
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + super.hashCode();
+ result = 31 * result + getName().hashCode();
+ if (getParameterSet() != null) {
+ result = 31 * result + getParameterSet().hashCode();
+ }
+ return result;
+ }
+
+ Annotation[] getTestAnnotations() {
+ // TODO(yolandyan): add annotation from the ParameterSet, enable
+ // test writing to add SkipCheck for an individual parameter
+ return getMethod().getAnnotations();
+ }
+
+ public ParameterSet getParameterSet() {
+ return mParameterSet;
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java
new file mode 100644
index 0000000000..834f26139f
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java
@@ -0,0 +1,221 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.Test;
+import org.junit.runner.Runner;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.FrameworkField;
+import org.junit.runners.model.TestClass;
+
+import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
+import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
+import org.chromium.base.test.params.ParameterizedRunnerDelegateFactory.ParameterizedRunnerDelegateInstantiationException;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * ParameterizedRunner generates a list of runners for each of class parameter set in a test class.
+ *
+ * ParameterizedRunner looks for {@code @ClassParameter} annotation in test class and
+ * generates a list of ParameterizedRunnerDelegate runners for each ParameterSet.
+ */
+public final class ParameterizedRunner extends Suite {
+ private final List<Runner> mRunners;
+
+ /**
+ * Create a ParameterizedRunner to run test class
+ *
+ * @param klass the Class of the test class, test class should be atomic
+ * (extends only Object)
+ */
+ public ParameterizedRunner(Class<?> klass) throws Throwable {
+ super(klass, Collections.emptyList()); // pass in empty list of runners
+ validate();
+ mRunners = createRunners(getTestClass());
+ }
+
+ @Override
+ protected List<Runner> getChildren() {
+ return mRunners;
+ }
+
+ /**
+ * ParentRunner calls collectInitializationErrors() to check for errors in Test class.
+ * Parameterized tests are written in unconventional ways, therefore, this method is
+ * overridden and validation is done seperately.
+ */
+ @Override
+ protected void collectInitializationErrors(List<Throwable> errors) {
+ // Do not call super collectInitializationErrors
+ }
+
+ private void validate() throws Throwable {
+ validateNoNonStaticInnerClass();
+ validateOnlyOneConstructor();
+ validateInstanceMethods();
+ validateOnlyOneClassParameterField();
+ validateAtLeastOneParameterSetField();
+ }
+
+ private void validateNoNonStaticInnerClass() throws Exception {
+ if (getTestClass().isANonStaticInnerClass()) {
+ throw new Exception("The inner class " + getTestClass().getName() + " is not static.");
+ }
+ }
+
+ private void validateOnlyOneConstructor() throws Exception {
+ if (!hasOneConstructor()) {
+ throw new Exception("Test class should have exactly one public constructor");
+ }
+ }
+
+ private boolean hasOneConstructor() {
+ return getTestClass().getJavaClass().getConstructors().length == 1;
+ }
+
+ private void validateOnlyOneClassParameterField() {
+ if (getTestClass().getAnnotatedFields(ClassParameter.class).size() > 1) {
+ throw new IllegalParameterArgumentException(String.format(Locale.getDefault(),
+ "%s class has more than one @ClassParameter, only one is allowed",
+ getTestClass().getName()));
+ }
+ }
+
+ private void validateAtLeastOneParameterSetField() {
+ if (getTestClass().getAnnotatedFields(ClassParameter.class).isEmpty()
+ && getTestClass().getAnnotatedMethods(UseMethodParameter.class).isEmpty()) {
+ throw new IllegalArgumentException(String.format(Locale.getDefault(),
+ "%s has no field annotated with @ClassParameter or method annotated with"
+ + "@UseMethodParameter; it should not use ParameterizedRunner",
+ getTestClass().getName()));
+ }
+ }
+
+ private void validateInstanceMethods() throws Exception {
+ if (getTestClass().getAnnotatedMethods(Test.class).size() == 0) {
+ throw new Exception("No runnable methods");
+ }
+ }
+
+ /**
+ * Return a list of runner delegates through ParameterizedRunnerDelegateFactory.
+ *
+ * For class parameter set: each class can only have one list of class parameter sets.
+ * Each parameter set will be used to create one runner.
+ *
+ * For method parameter set: a single list method parameter sets is associated with
+ * a string tag, an immutable map of string to parameter set list will be created and
+ * passed into factory for each runner delegate to create multiple tests. Only one
+ * Runner will be created for a method that uses @UseMethodParameter, regardless of the
+ * number of ParameterSets in the associated list.
+ *
+ * @return a list of runners
+ * @throws ParameterizedRunnerDelegateInstantiationException if runner delegate can not
+ * be instantiated with constructor reflectively
+ * @throws IllegalAccessError if the field in tests are not accessible
+ */
+ static List<Runner> createRunners(TestClass testClass)
+ throws IllegalAccessException, ParameterizedRunnerDelegateInstantiationException {
+ List<ParameterSet> classParameterSetList;
+ if (testClass.getAnnotatedFields(ClassParameter.class).isEmpty()) {
+ classParameterSetList = new ArrayList<>();
+ classParameterSetList.add(null);
+ } else {
+ classParameterSetList = getParameterSetList(
+ testClass.getAnnotatedFields(ClassParameter.class).get(0), testClass);
+ validateWidth(classParameterSetList);
+ }
+
+ Class<? extends ParameterizedRunnerDelegate> runnerDelegateClass =
+ getRunnerDelegateClass(testClass);
+ ParameterizedRunnerDelegateFactory factory = new ParameterizedRunnerDelegateFactory();
+ List<Runner> runnersForTestClass = new ArrayList<>();
+ for (ParameterSet classParameterSet : classParameterSetList) {
+ BlockJUnit4ClassRunner runner = (BlockJUnit4ClassRunner) factory.createRunner(
+ testClass, classParameterSet, runnerDelegateClass);
+ runnersForTestClass.add(runner);
+ }
+ return runnersForTestClass;
+ }
+
+ /**
+ * Return an unmodifiable list of ParameterSet through a FrameworkField
+ */
+ private static List<ParameterSet> getParameterSetList(FrameworkField field, TestClass testClass)
+ throws IllegalAccessException {
+ field.getField().setAccessible(true);
+ if (!Modifier.isStatic(field.getField().getModifiers())) {
+ throw new IllegalParameterArgumentException(String.format(Locale.getDefault(),
+ "ParameterSetList fields must be static, this field %s in %s is not",
+ field.getName(), testClass.getName()));
+ }
+ if (!(field.get(testClass.getJavaClass()) instanceof List)) {
+ throw new IllegalArgumentException(String.format(Locale.getDefault(),
+ "Fields with @ClassParameter annotations must be an instance of List, "
+ + "this field %s in %s is not list",
+ field.getName(), testClass.getName()));
+ }
+ @SuppressWarnings("unchecked") // checked above
+ List<ParameterSet> result = (List<ParameterSet>) field.get(testClass.getJavaClass());
+ return Collections.unmodifiableList(result);
+ }
+
+ static void validateWidth(Iterable<ParameterSet> parameterSetList) {
+ int lastSize = -1;
+ for (ParameterSet set : parameterSetList) {
+ if (set.size() == 0) {
+ throw new IllegalParameterArgumentException(
+ "No parameter is added to method ParameterSet");
+ }
+ if (lastSize == -1 || set.size() == lastSize) {
+ lastSize = set.size();
+ } else {
+ throw new IllegalParameterArgumentException(String.format(Locale.getDefault(),
+ "All ParameterSets in a list of ParameterSet must have equal"
+ + " length. The current ParameterSet (%s) contains %d parameters,"
+ + " while previous ParameterSet contains %d parameters",
+ Arrays.toString(set.getValues().toArray()), set.size(), lastSize));
+ }
+ }
+ }
+
+ /**
+ * Get the runner delegate class for the test class if {@code @UseRunnerDelegate} is used.
+ * The default runner delegate is BaseJUnit4RunnerDelegate.class
+ */
+ private static Class<? extends ParameterizedRunnerDelegate> getRunnerDelegateClass(
+ TestClass testClass) {
+ if (testClass.getAnnotation(UseRunnerDelegate.class) != null) {
+ return testClass.getAnnotation(UseRunnerDelegate.class).value();
+ }
+ return BaseJUnit4RunnerDelegate.class;
+ }
+
+ static class IllegalParameterArgumentException extends IllegalArgumentException {
+ IllegalParameterArgumentException(String msg) {
+ super(msg);
+ }
+ }
+
+ public static class ParameterizedTestInstantiationException extends Exception {
+ ParameterizedTestInstantiationException(
+ TestClass testClass, String parameterSetString, Exception e) {
+ super(String.format(
+ "Test class %s can not be initiated, the provided parameters are %s,"
+ + " the required parameter types are %s",
+ testClass.getJavaClass().toString(), parameterSetString,
+ Arrays.toString(testClass.getOnlyConstructor().getParameterTypes())),
+ e);
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegate.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegate.java
new file mode 100644
index 0000000000..d3698a95b4
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegate.java
@@ -0,0 +1,36 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.test.params.ParameterizedRunner.ParameterizedTestInstantiationException;
+
+import java.util.List;
+
+/**
+ * This interface defines the methods that needs to be overriden for a Runner to
+ * be used by ParameterizedRunner to generate individual runners for parameters.
+ *
+ * To create a ParameterizedRunnerDelegate, extends from any BlockJUnit4Runner
+ * children class. You can copy all the implementation from
+ * org.chromium.base.test.params.BaseJUnit4RunnerDelegate.
+ */
+public interface ParameterizedRunnerDelegate {
+ /**
+ * Override to use DelegateCommon's implementation
+ */
+ void collectInitializationErrors(List<Throwable> errors);
+
+ /**
+ * Override to use DelegateCommon's implementation
+ */
+ List<FrameworkMethod> computeTestMethods();
+
+ /**
+ * Override to use DelegateCommon's implementation
+ */
+ Object createTest() throws ParameterizedTestInstantiationException;
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegateCommon.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegateCommon.java
new file mode 100644
index 0000000000..f25e2b2ab9
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegateCommon.java
@@ -0,0 +1,69 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+import org.chromium.base.test.params.ParameterizedRunner.ParameterizedTestInstantiationException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+/**
+ * Parameterized runner delegate common that implements method that needed to be
+ * delegated for parameterization purposes
+ */
+public final class ParameterizedRunnerDelegateCommon {
+ private final TestClass mTestClass;
+ private final ParameterSet mClassParameterSet;
+ private final List<FrameworkMethod> mParameterizedFrameworkMethodList;
+
+ public ParameterizedRunnerDelegateCommon(TestClass testClass, ParameterSet classParameterSet,
+ List<FrameworkMethod> parameterizedFrameworkMethods) {
+ mTestClass = testClass;
+ mClassParameterSet = classParameterSet;
+ mParameterizedFrameworkMethodList = parameterizedFrameworkMethods;
+ }
+
+ /**
+ * Do not do any validation here because running the default class runner's
+ * collectInitializationErrors fail due to the overridden computeTestMethod relying on a local
+ * member variable
+ *
+ * The validation needed for parameterized tests is already done by ParameterizedRunner.
+ */
+ public static void collectInitializationErrors(
+ @SuppressWarnings("unused") List<Throwable> errors) {}
+
+ public List<FrameworkMethod> computeTestMethods() {
+ return mParameterizedFrameworkMethodList;
+ }
+
+ private void throwInstantiationException(Exception e)
+ throws ParameterizedTestInstantiationException {
+ String parameterSetString =
+ mClassParameterSet == null ? "null" : mClassParameterSet.toString();
+ throw new ParameterizedTestInstantiationException(mTestClass, parameterSetString, e);
+ }
+
+ public Object createTest() throws ParameterizedTestInstantiationException {
+ try {
+ if (mClassParameterSet == null) {
+ return mTestClass.getOnlyConstructor().newInstance();
+ }
+ return mTestClass.getOnlyConstructor().newInstance(
+ mClassParameterSet.getValues().toArray());
+ } catch (InstantiationException e) {
+ throwInstantiationException(e);
+ } catch (IllegalAccessException e) {
+ throwInstantiationException(e);
+ } catch (InvocationTargetException e) {
+ throwInstantiationException(e);
+ }
+ assert false;
+ return null;
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegateFactory.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegateFactory.java
new file mode 100644
index 0000000000..f829981c77
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunnerDelegateFactory.java
@@ -0,0 +1,115 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.Test;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Factory to generate delegate class runners for ParameterizedRunner
+ */
+public class ParameterizedRunnerDelegateFactory {
+ /**
+ * Create a runner that implements ParameterizedRunner and extends BlockJUnit4ClassRunner
+ *
+ * @param testClass the TestClass object for current test class
+ * @param classParameterSet A parameter set for test constructor arguments
+ * @param parameterizedRunnerDelegateClass the parameterized runner delegate class specified
+ * through {@code @UseRunnerDelegate}
+ */
+ <T extends ParameterizedRunnerDelegate> T createRunner(TestClass testClass,
+ ParameterSet classParameterSet, Class<T> parameterizedRunnerDelegateClass)
+ throws ParameterizedRunnerDelegateInstantiationException {
+ String testMethodPostfix = classParameterSet == null ? null : classParameterSet.getName();
+ List<FrameworkMethod> unmodifiableFrameworkMethodList =
+ generateUnmodifiableFrameworkMethodList(testClass, testMethodPostfix);
+ ParameterizedRunnerDelegateCommon delegateCommon = new ParameterizedRunnerDelegateCommon(
+ testClass, classParameterSet, unmodifiableFrameworkMethodList);
+ try {
+ return parameterizedRunnerDelegateClass
+ .getDeclaredConstructor(Class.class, ParameterizedRunnerDelegateCommon.class)
+ .newInstance(testClass.getJavaClass(), delegateCommon);
+ } catch (Exception e) {
+ throw new ParameterizedRunnerDelegateInstantiationException(
+ parameterizedRunnerDelegateClass.toString(), e);
+ }
+ }
+
+ /**
+ * Match test methods annotated by @UseMethodParameter(X) with
+ * ParameterSetList annotated by @MethodParameter(X)
+ *
+ * @param testClass a {@code TestClass} that wraps around the actual java
+ * test class
+ * @param postFix a name postfix for each test
+ * @return a list of ParameterizedFrameworkMethod
+ */
+ static List<FrameworkMethod> generateUnmodifiableFrameworkMethodList(
+ TestClass testClass, String postFix) {
+ // Represent the list of all ParameterizedFrameworkMethod in this test class
+ List<FrameworkMethod> returnList = new ArrayList<>();
+
+ for (FrameworkMethod method : testClass.getAnnotatedMethods(Test.class)) {
+ if (method.getMethod().isAnnotationPresent(UseMethodParameter.class)) {
+ Iterable<ParameterSet> parameterSets =
+ getParameters(method.getAnnotation(UseMethodParameter.class).value());
+ returnList.addAll(createParameterizedMethods(method, parameterSets, postFix));
+ } else {
+ // If test method is not parameterized (does not have UseMethodParameter annotation)
+ returnList.add(new ParameterizedFrameworkMethod(method.getMethod(), null, postFix));
+ }
+ }
+
+ return Collections.unmodifiableList(returnList);
+ }
+
+ /**
+ * Exception caused by instantiating the provided Runner delegate
+ * Potentially caused by not overriding collecInitializationErrors() method
+ * to be empty
+ */
+ public static class ParameterizedRunnerDelegateInstantiationException extends Exception {
+ private ParameterizedRunnerDelegateInstantiationException(
+ String runnerDelegateClass, Exception e) {
+ super(String.format("Current class runner delegate %s can not be instantiated.",
+ runnerDelegateClass),
+ e);
+ }
+ }
+
+ private static Iterable<ParameterSet> getParameters(Class<? extends ParameterProvider> clazz) {
+ ParameterProvider parameterProvider;
+ try {
+ parameterProvider = clazz.getDeclaredConstructor().newInstance();
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Failed instantiating " + clazz.getCanonicalName(), e);
+ } catch (InstantiationException e) {
+ throw new IllegalStateException("Failed instantiating " + clazz.getCanonicalName(), e);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException("Failed instantiating " + clazz.getCanonicalName(), e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException("Failed instantiating " + clazz.getCanonicalName(), e);
+ }
+ return parameterProvider.getParameters();
+ }
+
+ private static List<FrameworkMethod> createParameterizedMethods(
+ FrameworkMethod baseMethod, Iterable<ParameterSet> parameterSetList, String suffix) {
+ ParameterizedRunner.validateWidth(parameterSetList);
+ List<FrameworkMethod> returnList = new ArrayList<>();
+ for (ParameterSet set : parameterSetList) {
+ returnList.add(new ParameterizedFrameworkMethod(baseMethod.getMethod(), set, suffix));
+ }
+ return returnList;
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java b/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java
new file mode 100644
index 0000000000..c8117f7fad
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java
@@ -0,0 +1,118 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.content.ComponentCallbacks;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.SharedPreferences;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * ContextWrapper that adds functionality for SharedPreferences and a way to set and retrieve flags.
+ */
+public class AdvancedMockContext extends ContextWrapper {
+
+ private final MockContentResolver mMockContentResolver = new MockContentResolver();
+
+ private final Map<String, SharedPreferences> mSharedPreferences =
+ new HashMap<String, SharedPreferences>();
+
+ private final Map<String, Boolean> mFlags = new HashMap<String, Boolean>();
+
+ public AdvancedMockContext(Context base) {
+ super(base);
+ }
+
+ public AdvancedMockContext() {
+ super(new MockContext());
+ }
+
+ @Override
+ public String getPackageName() {
+ return getBaseContext().getPackageName();
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mMockContentResolver;
+ }
+
+ public MockContentResolver getMockContentResolver() {
+ return mMockContentResolver;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ synchronized (mSharedPreferences) {
+ if (!mSharedPreferences.containsKey(name)) {
+ // Auto-create shared preferences to mimic Android Context behavior
+ mSharedPreferences.put(name, new InMemorySharedPreferences());
+ }
+ return mSharedPreferences.get(name);
+ }
+ }
+
+ @Override
+ public void registerComponentCallbacks(ComponentCallbacks callback) {
+ getBaseContext().registerComponentCallbacks(callback);
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+ getBaseContext().unregisterComponentCallbacks(callback);
+ }
+
+ public void addSharedPreferences(String name, Map<String, Object> data) {
+ synchronized (mSharedPreferences) {
+ mSharedPreferences.put(name, new InMemorySharedPreferences(data));
+ }
+ }
+
+ public void setFlag(String key) {
+ mFlags.put(key, true);
+ }
+
+ public void clearFlag(String key) {
+ mFlags.remove(key);
+ }
+
+ public boolean isFlagSet(String key) {
+ return mFlags.containsKey(key) && mFlags.get(key);
+ }
+
+ /**
+ * Builder for maps of type Map<String, Object> to be used with
+ * {@link #addSharedPreferences(String, java.util.Map)}.
+ */
+ public static class MapBuilder {
+
+ private final Map<String, Object> mData = new HashMap<String, Object>();
+
+ public static MapBuilder create() {
+ return new MapBuilder();
+ }
+
+ public MapBuilder add(String key, Object value) {
+ mData.put(key, value);
+ return this;
+ }
+
+ public Map<String, Object> build() {
+ return mData;
+ }
+
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java b/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
new file mode 100644
index 0000000000..bf064c4fce
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
@@ -0,0 +1,252 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
+
+import org.junit.Assert;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A helper class that encapsulates listening and blocking for callbacks.
+ *
+ * Sample usage:
+ *
+ * // Let us assume that this interface is defined by some piece of production code and is used
+ * // to communicate events that occur in that piece of code. Let us further assume that the
+ * // production code runs on the main thread test code runs on a separate test thread.
+ * // An instance that implements this interface would be injected by test code to ensure that the
+ * // methods are being called on another thread.
+ * interface Delegate {
+ * void onOperationFailed(String errorMessage);
+ * void onDataPersisted();
+ * }
+ *
+ * // This is the inner class you'd write in your test case to later inject into the production
+ * // code.
+ * class TestDelegate implements Delegate {
+ * // This is the preferred way to create a helper that stores the parameters it receives
+ * // when called by production code.
+ * public static class OnOperationFailedHelper extends CallbackHelper {
+ * private String mErrorMessage;
+ *
+ * public void getErrorMessage() {
+ * assert getCallCount() > 0;
+ * return mErrorMessage;
+ * }
+ *
+ * public void notifyCalled(String errorMessage) {
+ * mErrorMessage = errorMessage;
+ * // It's important to call this after all parameter assignments.
+ * notifyCalled();
+ * }
+ * }
+ *
+ * // There should be one CallbackHelper instance per method.
+ * private OnOperationFailedHelper mOnOperationFailedHelper;
+ * private CallbackHelper mOnDataPersistedHelper;
+ *
+ * public OnOperationFailedHelper getOnOperationFailedHelper() {
+ * return mOnOperationFailedHelper;
+ * }
+ *
+ * public CallbackHelper getOnDataPersistedHelper() {
+ * return mOnDataPersistedHelper;
+ * }
+ *
+ * @Override
+ * public void onOperationFailed(String errorMessage) {
+ * mOnOperationFailedHelper.notifyCalled(errorMessage);
+ * }
+ *
+ * @Override
+ * public void onDataPersisted() {
+ * mOnDataPersistedHelper.notifyCalled();
+ * }
+ * }
+ *
+ * // This is a sample test case.
+ * public void testCase() throws Exception {
+ * // Create the TestDelegate to inject into production code.
+ * TestDelegate delegate = new TestDelegate();
+ * // Create the production class instance that is being tested and inject the test delegate.
+ * CodeUnderTest codeUnderTest = new CodeUnderTest();
+ * codeUnderTest.setDelegate(delegate);
+ *
+ * // Typically you'd get the current call count before performing the operation you expect to
+ * // trigger the callback. There can't be any callbacks 'in flight' at this moment, otherwise
+ * // the call count is unpredictable and the test will be flaky.
+ * int onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
+ * codeUnderTest.doSomethingThatEndsUpCallingOnOperationFailedFromAnotherThread();
+ * // It's safe to do other stuff here, if needed.
+ * ....
+ * // Wait for the callback if it hadn't been called yet, otherwise return immediately. This
+ * // can throw an exception if the callback doesn't arrive within the timeout.
+ * delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
+ * // Access to method parameters is now safe.
+ * assertEquals("server error", delegate.getOnOperationFailedHelper().getErrorMessage());
+ *
+ * // Being able to pass the helper around lets us build methods which encapsulate commonly
+ * // performed tasks.
+ * doSomeOperationAndWait(codeUnerTest, delegate.getOnOperationFailedHelper());
+ *
+ * // The helper can be reused for as many calls as needed, just be sure to get the count each
+ * // time.
+ * onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
+ * codeUnderTest.doSomethingElseButStillFailOnAnotherThread();
+ * delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
+ *
+ * // It is also possible to use more than one helper at a time.
+ * onOperationFailedCallCount = delegate.getOnOperationFailedHelper().getCallCount();
+ * int onDataPersistedCallCount = delegate.getOnDataPersistedHelper().getCallCount();
+ * codeUnderTest.doSomethingThatPersistsDataButFailsInSomeOtherWayOnAnotherThread();
+ * delegate.getOnDataPersistedHelper().waitForCallback(onDataPersistedCallCount);
+ * delegate.getOnOperationFailedHelper().waitForCallback(onOperationFailedCallCount);
+ * }
+ *
+ * // Shows how to turn an async operation + completion callback into a synchronous operation.
+ * private void doSomeOperationAndWait(final CodeUnderTest underTest,
+ * CallbackHelper operationHelper) throws InterruptedException, TimeoutException {
+ * final int callCount = operationHelper.getCallCount();
+ * getInstrumentation().runOnMainSync(new Runnable() {
+ * @Override
+ * public void run() {
+ * // This schedules a call to a method on the injected TestDelegate. The TestDelegate
+ * // implementation will then call operationHelper.notifyCalled().
+ * underTest.operation();
+ * }
+ * });
+ * operationHelper.waitForCallback(callCount);
+ * }
+ *
+ */
+public class CallbackHelper {
+ /** The default timeout (in seconds) for a callback to wait. */
+ public static final long WAIT_TIMEOUT_SECONDS = scaleTimeout(5);
+
+ private final Object mLock = new Object();
+ private int mCallCount;
+ private String mFailureString;
+
+ /**
+ * Gets the number of times the callback has been called.
+ *
+ * The call count can be used with the waitForCallback() method, indicating a point
+ * in time after which the caller wishes to record calls to the callback.
+ *
+ * In order to wait for a callback caused by X, the call count should be obtained
+ * before X occurs.
+ *
+ * NOTE: any call to the callback that occurs after the call count is obtained
+ * will result in the corresponding wait call to resume execution. The call count
+ * is intended to 'catch' callbacks that occur after X but before waitForCallback()
+ * is called.
+ */
+ public int getCallCount() {
+ synchronized (mLock) {
+ return mCallCount;
+ }
+ }
+
+ /**
+ * Blocks until the callback is called the specified number of
+ * times or throws an exception if we exceeded the specified time frame.
+ *
+ * This will wait for a callback to be called a specified number of times after
+ * the point in time at which the call count was obtained. The method will return
+ * immediately if a call occurred the specified number of times after the
+ * call count was obtained but before the method was called, otherwise the method will
+ * block until the specified call count is reached.
+ *
+ * @param msg The error message to use if the callback times out.
+ * @param currentCallCount the value obtained by calling getCallCount().
+ * @param numberOfCallsToWaitFor number of calls (counting since
+ * currentCallCount was obtained) that we will wait for.
+ * @param timeout timeout value. We will wait the specified amount of time for a single
+ * callback to occur so the method call may block up to
+ * <code>numberOfCallsToWaitFor * timeout</code> units.
+ * @param unit timeout unit.
+ * @throws InterruptedException
+ * @throws TimeoutException Thrown if the method times out before onPageFinished is called.
+ */
+ public void waitForCallback(String msg, int currentCallCount, int numberOfCallsToWaitFor,
+ long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ assert mCallCount >= currentCallCount;
+ assert numberOfCallsToWaitFor > 0;
+ synchronized (mLock) {
+ int callCountWhenDoneWaiting = currentCallCount + numberOfCallsToWaitFor;
+ while (callCountWhenDoneWaiting > mCallCount) {
+ int callCountBeforeWait = mCallCount;
+ mLock.wait(unit.toMillis(timeout));
+ if (mFailureString != null) {
+ String s = mFailureString;
+ mFailureString = null;
+ Assert.fail(s);
+ }
+ if (callCountBeforeWait == mCallCount) {
+ throw new TimeoutException(msg == null ? "waitForCallback timed out!" : msg);
+ }
+ }
+ }
+ }
+
+ /**
+ * @see #waitForCallback(String, int, int, long, TimeUnit)
+ */
+ public void waitForCallback(int currentCallCount, int numberOfCallsToWaitFor, long timeout,
+ TimeUnit unit) throws InterruptedException, TimeoutException {
+ waitForCallback(null, currentCallCount, numberOfCallsToWaitFor, timeout, unit);
+ }
+
+ /**
+ * @see #waitForCallback(String, int, int, long, TimeUnit)
+ */
+ public void waitForCallback(int currentCallCount, int numberOfCallsToWaitFor)
+ throws InterruptedException, TimeoutException {
+ waitForCallback(null, currentCallCount, numberOfCallsToWaitFor,
+ WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * @see #waitForCallback(String, int, int, long, TimeUnit)
+ */
+ public void waitForCallback(String msg, int currentCallCount)
+ throws InterruptedException, TimeoutException {
+ waitForCallback(msg, currentCallCount, 1, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * @see #waitForCallback(String, int, int, long, TimeUnit)
+ */
+ public void waitForCallback(int currentCallCount)
+ throws InterruptedException, TimeoutException {
+ waitForCallback(null, currentCallCount, 1, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Should be called when the callback associated with this helper object is called.
+ */
+ public void notifyCalled() {
+ synchronized (mLock) {
+ mCallCount++;
+ mLock.notifyAll();
+ }
+ }
+
+ /**
+ * Should be called when the callback associated with this helper object wants to
+ * indicate a failure.
+ *
+ * @param s The failure message.
+ */
+ public void notifyFailed(String s) {
+ synchronized (mLock) {
+ mFailureString = s;
+ mLock.notifyAll();
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
new file mode 100644
index 0000000000..71ef8e91ff
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
@@ -0,0 +1,188 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.text.TextUtils;
+
+import org.junit.Assert;
+import org.junit.Rule;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.CommandLineInitUtil;
+import org.chromium.base.test.BaseTestResult.PreTestHook;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Provides annotations related to command-line flag handling.
+ *
+ * Uses of these annotations on a derived class will take precedence over uses on its base classes,
+ * so a derived class can add a command-line flag that a base class has removed (or vice versa).
+ * Similarly, uses of these annotations on a test method will take precedence over uses on the
+ * containing class.
+ * <p>
+ * These annonations may also be used on Junit4 Rule classes and on their base classes. Note,
+ * however that the annotation processor only looks at the declared type of the Rule, not its actual
+ * type, so in, for example:
+ *
+ * <pre>
+ * &#64Rule
+ * TestRule mRule = new ChromeActivityTestRule();
+ * </pre>
+ *
+ * will only look for CommandLineFlags annotations on TestRule, not for CommandLineFlags annotations
+ * on ChromeActivityTestRule.
+ * <p>
+ * In addition a rule may not remove flags added by an independently invoked rule, although it may
+ * remove flags added by its base classes.
+ * <p>
+ * Uses of these annotations on the test class or methods take precedence over uses on Rule classes.
+ * <p>
+ * Note that this class should never be instantiated.
+ */
+public final class CommandLineFlags {
+ private static final String DISABLE_FEATURES = "disable-features";
+ private static final String ENABLE_FEATURES = "enable-features";
+
+ /**
+ * Adds command-line flags to the {@link org.chromium.base.CommandLine} for this test.
+ */
+ @Inherited
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface Add {
+ String[] value();
+ }
+
+ /**
+ * Removes command-line flags from the {@link org.chromium.base.CommandLine} from this test.
+ *
+ * Note that this can only remove flags added via {@link Add} above.
+ */
+ @Inherited
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface Remove {
+ String[] value();
+ }
+
+ /**
+ * Sets up the CommandLine with the appropriate flags.
+ *
+ * This will add the difference of the sets of flags specified by {@link CommandLineFlags.Add}
+ * and {@link CommandLineFlags.Remove} to the {@link org.chromium.base.CommandLine}. Note that
+ * trying to remove a flag set externally, i.e. by the command-line flags file, will not work.
+ */
+ public static void setUp(AnnotatedElement element) {
+ CommandLine.reset();
+ CommandLineInitUtil.initCommandLine(getTestCmdLineFile());
+ Set<String> enableFeatures = new HashSet<String>();
+ Set<String> disableFeatures = new HashSet<String>();
+ Set<String> flags = getFlags(element);
+ for (String flag : flags) {
+ String[] parsedFlags = flag.split("=", 2);
+ if (parsedFlags.length == 1) {
+ CommandLine.getInstance().appendSwitch(flag);
+ } else if (ENABLE_FEATURES.equals(parsedFlags[0])) {
+ // We collect enable/disable features flags separately and aggregate them because
+ // they may be specified multiple times, in which case the values will trample each
+ // other.
+ Collections.addAll(enableFeatures, parsedFlags[1].split(","));
+ } else if (DISABLE_FEATURES.equals(parsedFlags[0])) {
+ Collections.addAll(disableFeatures, parsedFlags[1].split(","));
+ } else {
+ CommandLine.getInstance().appendSwitchWithValue(parsedFlags[0], parsedFlags[1]);
+ }
+ }
+
+ if (enableFeatures.size() > 0) {
+ CommandLine.getInstance().appendSwitchWithValue(
+ ENABLE_FEATURES, TextUtils.join(",", enableFeatures));
+ }
+ if (disableFeatures.size() > 0) {
+ CommandLine.getInstance().appendSwitchWithValue(
+ DISABLE_FEATURES, TextUtils.join(",", disableFeatures));
+ }
+ }
+
+ private static Set<String> getFlags(AnnotatedElement type) {
+ Set<String> rule_flags = new HashSet<>();
+ updateFlagsForElement(type, rule_flags);
+ return rule_flags;
+ }
+
+ private static void updateFlagsForElement(AnnotatedElement element, Set<String> flags) {
+ if (element instanceof Class<?>) {
+ // Get flags from rules within the class.
+ for (Field field : ((Class<?>) element).getFields()) {
+ if (field.isAnnotationPresent(Rule.class)) {
+ // The order in which fields are returned is undefined, so, for consistency,
+ // a rule must not remove a flag added by a different rule. Ensure this by
+ // initially getting the flags into a new set.
+ Set<String> rule_flags = getFlags(field.getType());
+ flags.addAll(rule_flags);
+ }
+ }
+ for (Method method : ((Class<?>) element).getMethods()) {
+ if (method.isAnnotationPresent(Rule.class)) {
+ // The order in which methods are returned is undefined, so, for consistency,
+ // a rule must not remove a flag added by a different rule. Ensure this by
+ // initially getting the flags into a new set.
+ Set<String> rule_flags = getFlags(method.getReturnType());
+ flags.addAll(rule_flags);
+ }
+ }
+ }
+
+ // Add the flags from the parent. Override any flags defined by the rules.
+ AnnotatedElement parent = (element instanceof Method)
+ ? ((Method) element).getDeclaringClass()
+ : ((Class<?>) element).getSuperclass();
+ if (parent != null) updateFlagsForElement(parent, flags);
+
+ // Flags on the element itself override all other flag sources.
+ if (element.isAnnotationPresent(CommandLineFlags.Add.class)) {
+ flags.addAll(
+ Arrays.asList(element.getAnnotation(CommandLineFlags.Add.class).value()));
+ }
+
+ if (element.isAnnotationPresent(CommandLineFlags.Remove.class)) {
+ List<String> flagsToRemove =
+ Arrays.asList(element.getAnnotation(CommandLineFlags.Remove.class).value());
+ for (String flagToRemove : flagsToRemove) {
+ // If your test fails here, you have tried to remove a command-line flag via
+ // CommandLineFlags.Remove that was loaded into CommandLine via something other
+ // than CommandLineFlags.Add (probably the command-line flag file).
+ Assert.assertFalse("Unable to remove command-line flag \"" + flagToRemove + "\".",
+ CommandLine.getInstance().hasSwitch(flagToRemove));
+ }
+ flags.removeAll(flagsToRemove);
+ }
+ }
+
+ private CommandLineFlags() {
+ throw new AssertionError("CommandLineFlags is a non-instantiable class");
+ }
+
+ public static PreTestHook getRegistrationHook() {
+ return (targetContext, testMethod) -> CommandLineFlags.setUp(testMethod);
+ }
+
+ public static String getTestCmdLineFile() {
+ return "test-cmdline-file";
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java
new file mode 100644
index 0000000000..c0303b68d4
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIf.java
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotations to support conditional test disabling.
+ *
+ * These annotations should only be used to disable tests that are temporarily failing
+ * in some configurations. If a test should never run at all in some configurations, use
+ * {@link Restriction}.
+ */
+public class DisableIf {
+
+ /** Conditional disabling based on {@link android.os.Build}.
+ */
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public static @interface Build {
+ String message() default "";
+
+ int sdk_is_greater_than() default 0;
+ int sdk_is_less_than() default Integer.MAX_VALUE;
+
+ String supported_abis_includes() default "";
+
+ String hardware_is() default "";
+
+ String product_name_includes() default "";
+ }
+
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ public static @interface Device {
+ /**
+ * @return A list of disabled types.
+ */
+ public String[] type();
+ }
+
+ /* Objects of this type should not be created. */
+ private DisableIf() {}
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java
new file mode 100644
index 0000000000..e46b9799a6
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisableIfSkipCheck.java
@@ -0,0 +1,84 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.os.Build;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.Log;
+
+import java.util.Arrays;
+
+/**
+ * Checks for conditional disables.
+ *
+ * Currently, this only includes checks against a few {@link android.os.Build} values.
+ */
+public class DisableIfSkipCheck extends SkipCheck {
+
+ private static final String TAG = "cr_base_test";
+
+ @Override
+ public boolean shouldSkip(FrameworkMethod method) {
+ if (method == null) return true;
+ for (DisableIf.Build v : AnnotationProcessingUtils.getAnnotations(
+ method.getMethod(), DisableIf.Build.class)) {
+ if (abi(v) && hardware(v) && product(v) && sdk(v)) {
+ if (!v.message().isEmpty()) {
+ Log.i(TAG, "%s is disabled: %s", method.getName(), v.message());
+ }
+ return true;
+ }
+ }
+
+ for (DisableIf.Device d : AnnotationProcessingUtils.getAnnotations(
+ method.getMethod(), DisableIf.Device.class)) {
+ for (String deviceType : d.type()) {
+ if (deviceTypeApplies(deviceType)) {
+ Log.i(TAG, "Test " + method.getDeclaringClass().getName() + "#"
+ + method.getName() + " disabled because of "
+ + d);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean abi(DisableIf.Build v) {
+ if (v.supported_abis_includes().isEmpty()) return true;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return Arrays.asList(Build.SUPPORTED_ABIS).contains(
+ v.supported_abis_includes());
+ } else {
+ return Build.CPU_ABI.equals(v.supported_abis_includes())
+ || Build.CPU_ABI2.equals(v.supported_abis_includes());
+ }
+ }
+
+ private boolean hardware(DisableIf.Build v) {
+ return v.hardware_is().isEmpty() || Build.HARDWARE.equals(v.hardware_is());
+ }
+
+ private boolean product(DisableIf.Build v) {
+ return v.product_name_includes().isEmpty()
+ || Build.PRODUCT.contains(v.product_name_includes());
+ }
+
+ private boolean sdk(DisableIf.Build v) {
+ return Build.VERSION.SDK_INT > v.sdk_is_greater_than()
+ && Build.VERSION.SDK_INT < v.sdk_is_less_than();
+ }
+
+ protected boolean deviceTypeApplies(String type) {
+ return false;
+ }
+
+}
+
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java
new file mode 100644
index 0000000000..a3e4e8ee7f
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java
@@ -0,0 +1,22 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for disabled tests.
+ * <p>
+ * Tests with this annotation will not be run on any of the normal bots.
+ * Please note that they might eventually run on a special bot.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisabledTest {
+ String message() default "";
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java
new file mode 100644
index 0000000000..af483ec3f9
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java
@@ -0,0 +1,24 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for enormous tests.
+ * <p>
+ * Examples of enormous tests are tests that depend on external web sites or
+ * tests that are long running.
+ * <p>
+ * Such tests are likely NOT reliable enough to run on tree closing bots and
+ * should only be run on FYI bots.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnormousTest {
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java b/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java
new file mode 100644
index 0000000000..1bc9226441
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java
@@ -0,0 +1,29 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The java instrumentation tests are normally fairly large (in terms of
+ * dependencies), and the test suite ends up containing a large amount of
+ * tests that are not trivial to filter / group just by their names.
+ * Instead, we use this annotation: each test should be annotated as:
+ * @Feature({"Foo", "Bar"})
+ * in order for the test runner scripts to be able to filter and group
+ * them accordingly (for instance, this enable us to run all tests that exercise
+ * feature Foo).
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Feature {
+ /**
+ * @return A list of feature names.
+ */
+ public String[] value();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/FlakyTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/FlakyTest.java
new file mode 100644
index 0000000000..83f8e9f43d
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/FlakyTest.java
@@ -0,0 +1,22 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for flaky tests.
+ * <p>
+ * Tests with this annotation will not be run on any of the normal bots.
+ * Please note that they might eventually run on a special bot.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface FlakyTest {
+ String message() default "";
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
new file mode 100644
index 0000000000..2587d724a5
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
@@ -0,0 +1,238 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.content.SharedPreferences;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of SharedPreferences that can be used in tests.
+ * <p/>
+ * It keeps all state in memory, and there is no difference between apply() and commit().
+ */
+public class InMemorySharedPreferences implements SharedPreferences {
+
+ // Guarded on its own monitor.
+ private final Map<String, Object> mData;
+
+ public InMemorySharedPreferences() {
+ mData = new HashMap<String, Object>();
+ }
+
+ public InMemorySharedPreferences(Map<String, Object> data) {
+ mData = data;
+ }
+
+ @Override
+ public Map<String, ?> getAll() {
+ synchronized (mData) {
+ return Collections.unmodifiableMap(mData);
+ }
+ }
+
+ @Override
+ public String getString(String key, String defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (String) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Set<String> getStringSet(String key, Set<String> defValues) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return Collections.unmodifiableSet((Set<String>) mData.get(key));
+ }
+ }
+ return defValues;
+ }
+
+ @Override
+ public int getInt(String key, int defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Integer) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public long getLong(String key, long defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Long) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public float getFloat(String key, float defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Float) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Boolean) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public boolean contains(String key) {
+ synchronized (mData) {
+ return mData.containsKey(key);
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor edit() {
+ return new InMemoryEditor();
+ }
+
+ @Override
+ public void registerOnSharedPreferenceChangeListener(
+ SharedPreferences.OnSharedPreferenceChangeListener
+ listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterOnSharedPreferenceChangeListener(
+ SharedPreferences.OnSharedPreferenceChangeListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ private class InMemoryEditor implements SharedPreferences.Editor {
+
+ // All guarded by |mChanges|
+ private boolean mClearCalled;
+ private volatile boolean mApplyCalled;
+ private final Map<String, Object> mChanges = new HashMap<String, Object>();
+
+ @Override
+ public SharedPreferences.Editor putString(String key, String value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putStringSet(String key, Set<String> values) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, values);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putInt(String key, int value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putLong(String key, long value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putFloat(String key, float value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putBoolean(String key, boolean value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor remove(String key) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ // Magic value for removes
+ mChanges.put(key, this);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor clear() {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mClearCalled = true;
+ return this;
+ }
+ }
+
+ @Override
+ public boolean commit() {
+ apply();
+ return true;
+ }
+
+ @Override
+ public void apply() {
+ synchronized (mData) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ if (mClearCalled) {
+ mData.clear();
+ }
+ for (Map.Entry<String, Object> entry : mChanges.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value == this) {
+ // Special value for removal
+ mData.remove(key);
+ } else {
+ mData.put(key, value);
+ }
+ }
+ // The real shared prefs clears out the temporaries allowing the caller to
+ // reuse the Editor instance, however this is undocumented behavior and subtle
+ // to read, so instead we just ban any future use of this instance.
+ mApplyCalled = true;
+ }
+ }
+ }
+ }
+
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java
new file mode 100644
index 0000000000..20cfd9d620
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java
@@ -0,0 +1,32 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.app.Instrumentation;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Utility methods built around the android.app.Instrumentation class.
+ */
+public final class InstrumentationUtils {
+
+ private InstrumentationUtils() {
+ }
+
+ public static <R> R runOnMainSyncAndGetResult(Instrumentation instrumentation,
+ Callable<R> callable) throws Throwable {
+ FutureTask<R> task = new FutureTask<R>(callable);
+ instrumentation.runOnMainSync(task);
+ try {
+ return task.get();
+ } catch (ExecutionException e) {
+ // Unwrap the cause of the exception and re-throw it.
+ throw e.getCause();
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java
new file mode 100644
index 0000000000..8b6550d62d
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for integration tests.
+ * <p>
+ * Examples of integration tests are tests that rely on real instances of the
+ * application's services and components (e.g. Search) to test the system as
+ * a whole. These tests may use additional command-line flags to configure the
+ * existing backends to use.
+ * <p>
+ * Such tests are likely NOT reliable enough to run on tree closing bots and
+ * should only be run on FYI bots.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IntegrationTest {
+} \ No newline at end of file
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Manual.java b/base/test/android/javatests/src/org/chromium/base/test/util/Manual.java
new file mode 100644
index 0000000000..31f3977bef
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Manual.java
@@ -0,0 +1,21 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be used to mark a test that should only be run manually.
+ * <p>
+ * Tests with this annotation will not be run on bots, because they take too long
+ * or need manual monitoring.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Manual {
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Matchers.java b/base/test/android/javatests/src/org/chromium/base/test/util/Matchers.java
new file mode 100644
index 0000000000..fc9d68907b
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Matchers.java
@@ -0,0 +1,44 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Helper class containing Hamcrest matchers.
+ */
+public class Matchers extends CoreMatchers {
+ private static class GreaterThanOrEqualTo<T extends Comparable<T>>
+ extends TypeSafeMatcher<T> {
+
+ private final T mComparisonValue;
+
+ public GreaterThanOrEqualTo(T comparisonValue) {
+ mComparisonValue = comparisonValue;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("greater than or equal to ").appendValue(mComparisonValue);
+ }
+
+ @Override
+ protected boolean matchesSafely(T item) {
+ return item.compareTo(mComparisonValue) >= 0;
+ }
+ }
+
+ /**
+ * @param <T> A Comparable type.
+ * @param comparisonValue The value to be compared against.
+ * @return A matcher that expects the value to be greater than the |comparisonValue|.
+ */
+ public static <T extends Comparable<T>> Matcher<T> greaterThanOrEqualTo(T comparisonValue) {
+ return new GreaterThanOrEqualTo<>(comparisonValue);
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java
new file mode 100644
index 0000000000..c4664d68b0
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import org.chromium.base.metrics.RecordHistogram;
+
+/**
+ * Helpers for testing UMA metrics.
+ */
+public class MetricsUtils {
+ /**
+ * Helper class that snapshots the given bucket of the given UMA histogram on its creation,
+ * allowing to inspect the number of samples recorded during its lifetime.
+ */
+ public static class HistogramDelta {
+ private final String mHistogram;
+ private final int mSampleValue;
+
+ private final int mInitialCount;
+
+ private int get() {
+ return RecordHistogram.getHistogramValueCountForTesting(mHistogram, mSampleValue);
+ }
+
+ /**
+ * Snapshots the given bucket of the given histogram.
+ * @param histogram name of the histogram to snapshot
+ * @param sampleValue the bucket that contains this value will be snapshot
+ */
+ public HistogramDelta(String histogram, int sampleValue) {
+ mHistogram = histogram;
+ mSampleValue = sampleValue;
+ mInitialCount = get();
+ }
+
+ /** Returns the number of samples of the snapshot bucket recorded since creation */
+ public int getDelta() {
+ return get() - mInitialCount;
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java
new file mode 100644
index 0000000000..13e25786a7
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface MinAndroidSdkLevel {
+ int value() default 0;
+}
+
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java
new file mode 100644
index 0000000000..8b07c0f19b
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheck.java
@@ -0,0 +1,43 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.os.Build;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.Log;
+
+/**
+ * Checks the device's SDK level against any specified minimum requirement.
+ */
+public class MinAndroidSdkLevelSkipCheck extends SkipCheck {
+
+ private static final String TAG = "base_test";
+
+ /**
+ * If {@link MinAndroidSdkLevel} is present, checks its value
+ * against the device's SDK level.
+ *
+ * @param testCase The test to check.
+ * @return true if the device's SDK level is below the specified minimum.
+ */
+ @Override
+ public boolean shouldSkip(FrameworkMethod frameworkMethod) {
+ int minSdkLevel = 0;
+ for (MinAndroidSdkLevel m : AnnotationProcessingUtils.getAnnotations(
+ frameworkMethod.getMethod(), MinAndroidSdkLevel.class)) {
+ minSdkLevel = Math.max(minSdkLevel, m.value());
+ }
+ if (Build.VERSION.SDK_INT < minSdkLevel) {
+ Log.i(TAG, "Test " + frameworkMethod.getDeclaringClass().getName() + "#"
+ + frameworkMethod.getName() + " is not enabled at SDK level "
+ + Build.VERSION.SDK_INT + ".");
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java b/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java
new file mode 100644
index 0000000000..f39bfbd783
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java
@@ -0,0 +1,37 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for listing restrictions for a test method. For example, if a test method is only
+ * applicable on a phone with small memory:
+ * @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_SMALL_MEMORY})
+ * Test classes are free to define restrictions and enforce them using reflection at runtime.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Restriction {
+ /** Specifies the test is only valid on low end devices that have less memory. */
+ public static final String RESTRICTION_TYPE_LOW_END_DEVICE = "Low_End_Device";
+
+ /** Specifies the test is only valid on non-low end devices. */
+ public static final String RESTRICTION_TYPE_NON_LOW_END_DEVICE = "Non_Low_End_Device";
+
+ /** Specifies the test is only valid on a device that can reach the internet. */
+ public static final String RESTRICTION_TYPE_INTERNET = "Internet";
+
+ /** Specifies the test is only valid on a device that has a camera. */
+ public static final String RESTRICTION_TYPE_HAS_CAMERA = "Has_Camera";
+
+ /**
+ * @return A list of restrictions.
+ */
+ public String[] value();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java b/base/test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java
new file mode 100644
index 0000000000..a27dd1fe08
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/RestrictionSkipCheck.java
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.text.TextUtils;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.Log;
+import org.chromium.base.SysUtils;
+
+/**
+ * Checks if any restrictions exist and skip the test if it meets those restrictions.
+ */
+public class RestrictionSkipCheck extends SkipCheck {
+
+ private static final String TAG = "base_test";
+
+ private final Context mTargetContext;
+
+ public RestrictionSkipCheck(Context targetContext) {
+ mTargetContext = targetContext;
+ }
+
+ protected Context getTargetContext() {
+ return mTargetContext;
+ }
+
+ @Override
+ public boolean shouldSkip(FrameworkMethod frameworkMethod) {
+ if (frameworkMethod == null) return true;
+
+ for (Restriction restriction : AnnotationProcessingUtils.getAnnotations(
+ frameworkMethod.getMethod(), Restriction.class)) {
+ for (String restrictionVal : restriction.value()) {
+ if (restrictionApplies(restrictionVal)) {
+ Log.i(TAG, "Test " + frameworkMethod.getDeclaringClass().getName() + "#"
+ + frameworkMethod.getName() + " skipped because of restriction "
+ + restriction);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected boolean restrictionApplies(String restriction) {
+ if (TextUtils.equals(restriction, Restriction.RESTRICTION_TYPE_LOW_END_DEVICE)
+ && !SysUtils.isLowEndDevice()) {
+ return true;
+ }
+ if (TextUtils.equals(restriction, Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+ && SysUtils.isLowEndDevice()) {
+ return true;
+ }
+ if (TextUtils.equals(restriction, Restriction.RESTRICTION_TYPE_INTERNET)
+ && !isNetworkAvailable()) {
+ return true;
+ }
+ if (TextUtils.equals(restriction, Restriction.RESTRICTION_TYPE_HAS_CAMERA)
+ && !SysUtils.hasCamera(mTargetContext)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isNetworkAvailable() {
+ final ConnectivityManager connectivityManager = (ConnectivityManager)
+ mTargetContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+ return activeNetworkInfo != null && activeNetworkInfo.isConnected();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/RetryOnFailure.java b/base/test/android/javatests/src/org/chromium/base/test/util/RetryOnFailure.java
new file mode 100644
index 0000000000..eb98008d00
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/RetryOnFailure.java
@@ -0,0 +1,25 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// Note this annotation may be a NOOP. Check http://crbug.com/797002 for latest status (also see
+// http://crbug.com/619055). Current default behavior is to retry all tests on failure.
+/**
+ * Mark a test as flaky and should be retried on failure. The test is
+ * considered passed by the test script if any retry succeeds.
+ *
+ * Long term, this should be merged with @FlakyTest. But @FlakyTest means
+ * has specific meaning that is currently different from RetryOnFailure.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RetryOnFailure {
+ String message() default "";
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
new file mode 100644
index 0000000000..7a815c09a4
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
@@ -0,0 +1,29 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+/**
+ * Utility class for scaling various timeouts by a common factor.
+ * For example, to run tests under slow memory tools, you might do
+ * something like this:
+ * adb shell "echo 20.0 > /data/local/tmp/chrome_timeout_scale"
+ */
+public class ScalableTimeout {
+ private static Double sTimeoutScale;
+ public static final String PROPERTY_FILE = "/data/local/tmp/chrome_timeout_scale";
+
+ public static long scaleTimeout(long timeout) {
+ if (sTimeoutScale == null) {
+ try {
+ char[] data = TestFileUtil.readUtf8File(PROPERTY_FILE, 32);
+ sTimeoutScale = Double.parseDouble(new String(data));
+ } catch (Exception e) {
+ // NumberFormatException, FileNotFoundException, IOException
+ sTimeoutScale = 1.0;
+ }
+ }
+ return (long) (timeout * sTimeoutScale);
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java b/base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java
new file mode 100644
index 0000000000..d1dd7be17f
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import junit.framework.TestCase;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import org.chromium.base.Log;
+
+import java.lang.reflect.Method;
+
+/**
+ * Check whether a test case should be skipped.
+ */
+public abstract class SkipCheck {
+
+ private static final String TAG = "base_test";
+
+ /**
+ *
+ * Checks whether the given test method should be skipped.
+ *
+ * @param testMethod The test method to check.
+ * @return Whether the test case should be skipped.
+ */
+ public abstract boolean shouldSkip(FrameworkMethod testMethod);
+
+ /**
+ *
+ * Checks whether the given test case should be skipped.
+ *
+ * @param testCase The test case to check.
+ * @return Whether the test case should be skipped.
+ */
+ public boolean shouldSkip(TestCase testCase) {
+ try {
+ Method m = testCase.getClass().getMethod(testCase.getName(), (Class[]) null);
+ return shouldSkip(new FrameworkMethod(m));
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Unable to find %s in %s", testCase.getName(),
+ testCase.getClass().getName(), e);
+ return false;
+ }
+ }
+}
+
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java b/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java
new file mode 100644
index 0000000000..6d891210fc
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java
@@ -0,0 +1,85 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Arrays;
+
+/**
+ * Utility class for dealing with files for test.
+ */
+public class TestFileUtil {
+ public static void createNewHtmlFile(String name, String title, String body)
+ throws IOException {
+ createNewHtmlFile(new File(name), title, body);
+ }
+
+ public static void createNewHtmlFile(File file, String title, String body)
+ throws IOException {
+ if (!file.createNewFile()) {
+ throw new IOException("File \"" + file.getAbsolutePath() + "\" already exists");
+ }
+
+ Writer writer = null;
+ try {
+ writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
+ writer.write("<html><meta charset=\"UTF-8\" />"
+ + " <head><title>" + title + "</title></head>"
+ + " <body>"
+ + (body != null ? body : "")
+ + " </body>"
+ + " </html>");
+ } finally {
+ if (writer != null) {
+ writer.close();
+ }
+ }
+ }
+
+ public static void deleteFile(String name) {
+ deleteFile(new File(name));
+ }
+
+ public static void deleteFile(File file) {
+ boolean deleted = file.delete();
+ assert (deleted || !file.exists());
+ }
+
+ /**
+ * @param fileName the file to read in.
+ * @param sizeLimit cap on the file size: will throw an exception if exceeded
+ * @return Array of chars read from the file
+ * @throws FileNotFoundException file does not exceed
+ * @throws IOException error encountered accessing the file
+ */
+ public static char[] readUtf8File(String fileName, int sizeLimit) throws
+ FileNotFoundException, IOException {
+ Reader reader = null;
+ try {
+ File f = new File(fileName);
+ if (f.length() > sizeLimit) {
+ throw new IOException("File " + fileName + " length " + f.length()
+ + " exceeds limit " + sizeLimit);
+ }
+ char[] buffer = new char[(int) f.length()];
+ reader = new InputStreamReader(new FileInputStream(f), "UTF-8");
+ int charsRead = reader.read(buffer);
+ // Debug check that we've exhausted the input stream (will fail e.g. if the
+ // file grew after we inspected its length).
+ assert !reader.ready();
+ return charsRead < buffer.length ? Arrays.copyOfRange(buffer, 0, charsRead) : buffer;
+ } finally {
+ if (reader != null) reader.close();
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java b/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
new file mode 100644
index 0000000000..4f6296924c
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
@@ -0,0 +1,143 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class is usefull when writing instrumentation tests that exercise code that posts tasks
+ * (to the same thread).
+ * Since the test code is run in a single thread, the posted tasks are never executed.
+ * The TestThread class lets you run that code on a specific thread synchronously and flush the
+ * message loop on that thread.
+ *
+ * Example of test using this:
+ *
+ * public void testMyAwesomeClass() {
+ * TestThread testThread = new TestThread();
+ * testThread.startAndWaitForReadyState();
+ *
+ * testThread.runOnTestThreadSyncAndProcessPendingTasks(new Runnable() {
+ * @Override
+ * public void run() {
+ * MyAwesomeClass.doStuffAsync();
+ * }
+ * });
+ * // Once we get there we know doStuffAsync has been executed and all the tasks it posted.
+ * assertTrue(MyAwesomeClass.stuffWasDone());
+ * }
+ *
+ * Notes:
+ * - this is only for tasks posted to the same thread. Anyway if you were posting to a different
+ * thread, you'd probably need to set that other thread up.
+ * - this only supports tasks posted using Handler.post(), it won't work with postDelayed and
+ * postAtTime.
+ * - if your test instanciates an object and that object is the one doing the posting of tasks, you
+ * probably want to instanciate it on the test thread as it might create the Handler it posts
+ * tasks to in the constructor.
+ */
+
+public class TestThread extends Thread {
+ private final Object mThreadReadyLock;
+ private AtomicBoolean mThreadReady;
+ private Handler mMainThreadHandler;
+ private Handler mTestThreadHandler;
+
+ public TestThread() {
+ mMainThreadHandler = new Handler();
+ // We can't use the AtomicBoolean as the lock or findbugs will freak out...
+ mThreadReadyLock = new Object();
+ mThreadReady = new AtomicBoolean();
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ mTestThreadHandler = new Handler();
+ mTestThreadHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mThreadReadyLock) {
+ mThreadReady.set(true);
+ mThreadReadyLock.notify();
+ }
+ }
+ });
+ Looper.loop();
+ }
+
+ /**
+ * Starts this TestThread and blocks until it's ready to accept calls.
+ */
+ public void startAndWaitForReadyState() {
+ checkOnMainThread();
+ start();
+ synchronized (mThreadReadyLock) {
+ try {
+ // Note the mThreadReady and while are not really needed.
+ // There are there so findbugs don't report warnings.
+ while (!mThreadReady.get()) {
+ mThreadReadyLock.wait();
+ }
+ } catch (InterruptedException ie) {
+ System.err.println("Error starting TestThread.");
+ ie.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Runs the passed Runnable synchronously on the TestThread and returns when all pending
+ * runnables have been excuted.
+ * Should be called from the main thread.
+ */
+ public void runOnTestThreadSyncAndProcessPendingTasks(Runnable r) {
+ checkOnMainThread();
+
+ runOnTestThreadSync(r);
+
+ // Run another task, when it's done it means all pendings tasks have executed.
+ runOnTestThreadSync(null);
+ }
+
+ /**
+ * Runs the passed Runnable on the test thread and blocks until it has finished executing.
+ * Should be called from the main thread.
+ * @param r The runnable to be executed.
+ */
+ public void runOnTestThreadSync(final Runnable r) {
+ checkOnMainThread();
+ final Object lock = new Object();
+ // Task executed is not really needed since we are only on one thread, it is here to appease
+ // findbugs.
+ final AtomicBoolean taskExecuted = new AtomicBoolean();
+ mTestThreadHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (r != null) r.run();
+ synchronized (lock) {
+ taskExecuted.set(true);
+ lock.notify();
+ }
+ }
+ });
+ synchronized (lock) {
+ try {
+ while (!taskExecuted.get()) {
+ lock.wait();
+ }
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+
+ private void checkOnMainThread() {
+ assert Looper.myLooper() == mMainThreadHandler.getLooper();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java
new file mode 100644
index 0000000000..5aee05e73a
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java
@@ -0,0 +1,22 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be used to scale a specific test timeout.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TimeoutScale {
+ /**
+ * @return A number to scale the test timeout.
+ */
+ public int value();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java
new file mode 100644
index 0000000000..9ca3fcc33c
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java
@@ -0,0 +1,84 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import org.junit.Assert;
+
+import org.chromium.base.PathUtils;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+
+/**
+ * Collection of URL utilities.
+ */
+@MainDex
+public class UrlUtils {
+ private static final String DATA_DIR = "/chrome/test/data/";
+
+ /**
+ * Construct the full path of a test data file.
+ * @param path Pathname relative to external/chrome/test/data
+ */
+ public static String getTestFilePath(String path) {
+ // TODO(jbudorick): Remove DATA_DIR once everything has been isolated. crbug/400499
+ return getIsolatedTestFilePath(DATA_DIR + path);
+ }
+
+ // TODO(jbudorick): Remove this function once everything has been isolated and switched back
+ // to getTestFilePath. crbug/400499
+ /**
+ * Construct the full path of a test data file.
+ * @param path Pathname relative to external/
+ */
+ public static String getIsolatedTestFilePath(String path) {
+ return getIsolatedTestRoot() + "/" + path;
+ }
+
+ /**
+ * Returns the root of the test data directory.
+ */
+ @CalledByNative
+ public static String getIsolatedTestRoot() {
+ return PathUtils.getExternalStorageDirectory() + "/chromium_tests_root";
+ }
+
+ /**
+ * Construct a suitable URL for loading a test data file.
+ * @param path Pathname relative to external/chrome/test/data
+ */
+ public static String getTestFileUrl(String path) {
+ return "file://" + getTestFilePath(path);
+ }
+
+ // TODO(jbudorick): Remove this function once everything has been isolated and switched back
+ // to getTestFileUrl. crbug/400499
+ /**
+ * Construct a suitable URL for loading a test data file.
+ * @param path Pathname relative to external/
+ */
+ public static String getIsolatedTestFileUrl(String path) {
+ return "file://" + getIsolatedTestFilePath(path);
+ }
+
+ /**
+ * Construct a data:text/html URI for loading from an inline HTML.
+ * @param html An unencoded HTML
+ * @return String An URI that contains the given HTML
+ */
+ public static String encodeHtmlDataUri(String html) {
+ try {
+ // URLEncoder encodes into application/x-www-form-encoded, so
+ // ' '->'+' needs to be undone and replaced with ' '->'%20'
+ // to match the Data URI requirements.
+ String encoded =
+ "data:text/html;utf-8," + java.net.URLEncoder.encode(html, "UTF-8");
+ encoded = encoded.replace("+", "%20");
+ return encoded;
+ } catch (java.io.UnsupportedEncodingException e) {
+ Assert.fail("Unsupported encoding: " + e.getMessage());
+ return null;
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java b/base/test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java
new file mode 100644
index 0000000000..88e3551131
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.metrics.RecordUserAction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A util class that records UserActions.
+ */
+public class UserActionTester implements RecordUserAction.UserActionCallback {
+ private List<String> mActions;
+
+ public UserActionTester() {
+ mActions = new ArrayList<>();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ RecordUserAction.setActionCallbackForTesting(UserActionTester.this);
+ }
+ });
+ }
+
+ public void tearDown() {
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ RecordUserAction.removeActionCallbackForTesting();
+ }
+ });
+ }
+
+ @Override
+ public void onActionRecorded(String action) {
+ mActions.add(action);
+ }
+
+ public List<String> getActions() {
+ return mActions;
+ }
+
+ @Override
+ public String toString() {
+ return "Actions: " + mActions.toString();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/parameter/CommandLineParameter.java b/base/test/android/javatests/src/org/chromium/base/test/util/parameter/CommandLineParameter.java
new file mode 100644
index 0000000000..e6f5506899
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/parameter/CommandLineParameter.java
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util.parameter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The annotation for parametering CommandLineFlags in JUnit3 instrumentation tests.
+ *
+ * E.g. if you add the following annotation to your test class:
+ *
+ * <code>
+ * @CommandLineParameter({"", FLAG_A, FLAG_B})
+ * public class MyTestClass
+ * </code>
+ *
+ * The test harness would run the test 3 times with each of the flag added to commandline
+ * file.
+ */
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface CommandLineParameter {
+ String[] value() default {};
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/parameter/SkipCommandLineParameterization.java b/base/test/android/javatests/src/org/chromium/base/test/util/parameter/SkipCommandLineParameterization.java
new file mode 100644
index 0000000000..2181031617
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/parameter/SkipCommandLineParameterization.java
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package org.chromium.base.test.util.parameter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * BaseJUnit4ClassRunner and host side test harness skips commandline parameterization for test
+ * classes or methods annotated with SkipCommandLineParameterization.
+ *
+ * This usually used by test that only runs in WebView javatests that only runs in sandboxed mode
+ * or single process mode.
+ */
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SkipCommandLineParameterization {}
diff --git a/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java b/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java
new file mode 100644
index 0000000000..3ca756ad9a
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import org.junit.runners.model.InitializationError;
+import org.robolectric.DefaultTestLifecycle;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.TestLifecycle;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+import java.lang.reflect.Method;
+
+/**
+ * A Robolectric Test Runner that initializes base globals.
+ */
+public class BaseRobolectricTestRunner extends LocalRobolectricTestRunner {
+ /**
+ * Enables a per-test setUp / tearDown hook.
+ */
+ public static class BaseTestLifecycle extends DefaultTestLifecycle {
+ @Override
+ public void beforeTest(Method method) {
+ ContextUtils.initApplicationContextForTests(RuntimeEnvironment.application);
+ CommandLine.init(null);
+ super.beforeTest(method);
+ }
+
+ @Override
+ public void afterTest(Method method) {
+ ApplicationStatus.destroyForJUnitTests();
+ super.afterTest(method);
+ }
+ }
+
+ public BaseRobolectricTestRunner(Class<?> testClass) throws InitializationError {
+ super(testClass);
+ }
+
+ @Override
+ protected Class<? extends TestLifecycle> getTestLifecycleClass() {
+ return BaseTestLifecycle.class;
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/SetUpStatementTest.java b/base/test/android/junit/src/org/chromium/base/test/SetUpStatementTest.java
new file mode 100644
index 0000000000..722bd1acfe
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/SetUpStatementTest.java
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test SetUpStatement is working as intended with SetUpTestRule.
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class SetUpStatementTest {
+ private Statement mBase;
+ private SetUpTestRule<TestRule> mRule;
+ private List<Integer> mList;
+
+ @Before
+ public void setUp() {
+ mBase = new Statement() {
+ @Override
+ public void evaluate() {
+ mList.add(1);
+ }
+ };
+ mList = new ArrayList<>();
+ mRule = new SetUpTestRule<TestRule>() {
+ @Override
+ public void setUp() {
+ mList.add(0);
+ }
+
+ @Override
+ public TestRule shouldSetUp(boolean toSetUp) {
+ return null;
+ }
+ };
+ }
+
+ @Test
+ public void testSetUpStatementShouldSetUp() throws Throwable {
+ SetUpStatement statement = new SetUpStatement(mBase, mRule, true);
+ statement.evaluate();
+ Integer[] expected = {0, 1};
+ Assert.assertArrayEquals(expected, mList.toArray());
+ }
+
+ @Test
+ public void testSetUpStatementShouldNotSetUp() throws Throwable {
+ SetUpStatement statement = new SetUpStatement(mBase, mRule, false);
+ statement.evaluate();
+ Integer[] expected = {1};
+ Assert.assertArrayEquals(expected, mList.toArray());
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/TestListInstrumentationRunListenerTest.java b/base/test/android/junit/src/org/chromium/base/test/TestListInstrumentationRunListenerTest.java
new file mode 100644
index 0000000000..63fa5601fb
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/TestListInstrumentationRunListenerTest.java
@@ -0,0 +1,119 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import static org.chromium.base.test.TestListInstrumentationRunListener.getAnnotationJSON;
+import static org.chromium.base.test.TestListInstrumentationRunListener.getTestMethodJSON;
+
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.util.CommandLineFlags;
+
+import java.util.Arrays;
+
+/**
+ * Robolectric test to ensure static methods in TestListInstrumentationRunListener works properly.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class TestListInstrumentationRunListenerTest {
+ @CommandLineFlags.Add("hello")
+ private static class ParentClass {
+ public void testA() {}
+
+ @CommandLineFlags.Add("world")
+ public void testB() {}
+ }
+
+ @CommandLineFlags.Remove("hello")
+ private static class ChildClass extends ParentClass {
+ }
+
+ @Test
+ public void testGetTestMethodJSON_testA() throws Throwable {
+ Description desc = Description.createTestDescription(
+ ParentClass.class, "testA",
+ ParentClass.class.getMethod("testA").getAnnotations());
+ JSONObject json = getTestMethodJSON(desc);
+ String expectedJsonString =
+ "{"
+ + "'method': 'testA',"
+ + "'annotations': {}"
+ + "}";
+ expectedJsonString = expectedJsonString
+ .replaceAll("\\s", "")
+ .replaceAll("'", "\"");
+ Assert.assertEquals(expectedJsonString, json.toString());
+ }
+
+ @Test
+ public void testGetTestMethodJSON_testB() throws Throwable {
+ Description desc = Description.createTestDescription(
+ ParentClass.class, "testB",
+ ParentClass.class.getMethod("testB").getAnnotations());
+ JSONObject json = getTestMethodJSON(desc);
+ String expectedJsonString =
+ "{"
+ + "'method': 'testB',"
+ + "'annotations': {"
+ + " 'Add': {"
+ + " 'value': ['world']"
+ + " }"
+ + " }"
+ + "}";
+ expectedJsonString = expectedJsonString
+ .replaceAll("\\s", "")
+ .replaceAll("'", "\"");
+ Assert.assertEquals(expectedJsonString, json.toString());
+ }
+
+
+ @Test
+ public void testGetTestMethodJSONForInheritedClass() throws Throwable {
+ Description desc = Description.createTestDescription(
+ ChildClass.class, "testB",
+ ChildClass.class.getMethod("testB").getAnnotations());
+ JSONObject json = getTestMethodJSON(desc);
+ String expectedJsonString =
+ "{"
+ + "'method': 'testB',"
+ + "'annotations': {"
+ + " 'Add': {"
+ + " 'value': ['world']"
+ + " }"
+ + " }"
+ + "}";
+ expectedJsonString = expectedJsonString
+ .replaceAll("\\s", "")
+ .replaceAll("'", "\"");
+ Assert.assertEquals(expectedJsonString, json.toString());
+ }
+
+ @Test
+ public void testGetAnnotationJSONForParentClass() throws Throwable {
+ JSONObject json = getAnnotationJSON(Arrays.asList(ParentClass.class.getAnnotations()));
+ String expectedJsonString = "{'Add':{'value':['hello']}}";
+ expectedJsonString = expectedJsonString
+ .replaceAll("\\s", "")
+ .replaceAll("'", "\"");
+ Assert.assertEquals(expectedJsonString, json.toString());
+ }
+
+ @Test
+ public void testGetAnnotationJSONForChildClass() throws Throwable {
+ JSONObject json = getAnnotationJSON(Arrays.asList(ChildClass.class.getAnnotations()));
+ String expectedJsonString = "{'Add':{'value':['hello']},'Remove':{'value':['hello']}}";
+ expectedJsonString = expectedJsonString
+ .replaceAll("\\s", "")
+ .replaceAll("'", "\"");
+ Assert.assertEquals(expectedJsonString, json.toString());
+ }
+}
+
diff --git a/base/test/android/junit/src/org/chromium/base/test/asynctask/BackgroundShadowAsyncTask.java b/base/test/android/junit/src/org/chromium/base/test/asynctask/BackgroundShadowAsyncTask.java
new file mode 100644
index 0000000000..c75e7948f0
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/asynctask/BackgroundShadowAsyncTask.java
@@ -0,0 +1,72 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.asynctask;
+
+import static org.junit.Assert.fail;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowApplication;
+
+import org.chromium.base.AsyncTask;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Executes async tasks on a background thread and waits on the result on the main thread.
+ * This is useful for users of AsyncTask on Roboelectric who check if the code is actually being
+ * run on a background thread (i.e. through the use of {@link ThreadUtils#runningOnUiThread()}).
+ * @param <Params> type for execute function parameters
+ * @param <Progress> type for reporting Progress
+ * @param <Result> type for reporting result
+ */
+@Implements(AsyncTask.class)
+public class BackgroundShadowAsyncTask<Params, Progress, Result>
+ extends ShadowAsyncTask<Params, Progress, Result> {
+ private static final ExecutorService sExecutorService = Executors.newSingleThreadExecutor();
+
+ @Override
+ @Implementation
+ @SafeVarargs
+ public final AsyncTask<Params, Progress, Result> execute(final Params... params) {
+ try {
+ return sExecutorService
+ .submit(new Callable<AsyncTask<Params, Progress, Result>>() {
+ @Override
+ public AsyncTask<Params, Progress, Result> call() throws Exception {
+ return BackgroundShadowAsyncTask.super.execute(params);
+ }
+ })
+ .get();
+ } catch (Exception ex) {
+ fail(ex.getMessage());
+ return null;
+ }
+ }
+
+ @Override
+ @Implementation
+ public final Result get() {
+ try {
+ runBackgroundTasks();
+ return BackgroundShadowAsyncTask.super.get();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static void runBackgroundTasks() throws Exception {
+ sExecutorService
+ .submit(new Runnable() {
+ @Override
+ public void run() {
+ ShadowApplication.runBackgroundTasks();
+ }
+ })
+ .get();
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/asynctask/CustomShadowAsyncTask.java b/base/test/android/junit/src/org/chromium/base/test/asynctask/CustomShadowAsyncTask.java
new file mode 100644
index 0000000000..bd581c1377
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/asynctask/CustomShadowAsyncTask.java
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.asynctask;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import org.chromium.base.AsyncTask;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Forces async tasks to execute with the default executor.
+ * This works around Robolectric not working out of the box with custom executors.
+ *
+ * @param <Params>
+ * @param <Progress>
+ * @param <Result>
+ */
+@Implements(AsyncTask.class)
+public class CustomShadowAsyncTask<Params, Progress, Result>
+ extends ShadowAsyncTask<Params, Progress, Result> {
+ @SafeVarargs
+ @Override
+ @Implementation
+ public final AsyncTask<Params, Progress, Result> executeOnExecutor(
+ Executor executor, Params... params) {
+ return super.execute(params);
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/params/ExampleParameterizedTest.java b/base/test/android/junit/src/org/chromium/base/test/params/ExampleParameterizedTest.java
new file mode 100644
index 0000000000..6ffccad44b
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/params/ExampleParameterizedTest.java
@@ -0,0 +1,105 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterAfter;
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
+import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Example test that uses ParameterizedRunner
+ */
+@RunWith(ParameterizedRunner.class)
+@UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+public class ExampleParameterizedTest {
+ @ClassParameter
+ private static List<ParameterSet> sClassParams =
+ Arrays.asList(new ParameterSet().value("hello", "world").name("HelloWorld"),
+ new ParameterSet().value("Xxxx", "Yyyy").name("XxxxYyyy"),
+ new ParameterSet().value("aa", "yy").name("AaYy"));
+
+ public static class MethodParamsA implements ParameterProvider {
+ private static List<ParameterSet> sMethodParamA =
+ Arrays.asList(new ParameterSet().value(1, 2).name("OneTwo"),
+ new ParameterSet().value(2, 3).name("TwoThree"),
+ new ParameterSet().value(3, 4).name("ThreeFour"));
+
+ @Override
+ public List<ParameterSet> getParameters() {
+ return sMethodParamA;
+ }
+ }
+
+ public static class MethodParamsB implements ParameterProvider {
+ private static List<ParameterSet> sMethodParamB =
+ Arrays.asList(new ParameterSet().value("a", "b").name("Ab"),
+ new ParameterSet().value("b", "c").name("Bc"),
+ new ParameterSet().value("c", "d").name("Cd"),
+ new ParameterSet().value("d", "e").name("De"));
+
+ @Override
+ public List<ParameterSet> getParameters() {
+ return sMethodParamB;
+ }
+ }
+
+ private String mStringA;
+ private String mStringB;
+
+ public ExampleParameterizedTest(String a, String b) {
+ mStringA = a;
+ mStringB = b;
+ }
+
+ @Test
+ public void testSimple() {
+ Assert.assertEquals(
+ "A and B string length aren't equal", mStringA.length(), mStringB.length());
+ }
+
+ @Rule
+ public MethodRule mMethodParamAnnotationProcessor = new MethodParamAnnotationRule();
+
+ private Integer mSum;
+
+ @UseMethodParameterBefore(MethodParamsA.class)
+ public void setupWithOnlyA(int intA, int intB) {
+ mSum = intA + intB;
+ }
+
+ @Test
+ @UseMethodParameter(MethodParamsA.class)
+ public void testWithOnlyA(int intA, int intB) {
+ Assert.assertEquals(intA + 1, intB);
+ Assert.assertEquals(mSum, Integer.valueOf(intA + intB));
+ mSum = null;
+ }
+
+ private String mConcatenation;
+
+ @Test
+ @UseMethodParameter(MethodParamsB.class)
+ public void testWithOnlyB(String a, String b) {
+ Assert.assertTrue(!a.equals(b));
+ mConcatenation = a + b;
+ }
+
+ @UseMethodParameterAfter(MethodParamsB.class)
+ public void teardownWithOnlyB(String a, String b) {
+ Assert.assertEquals(mConcatenation, a + b);
+ mConcatenation = null;
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerDelegateCommonTest.java b/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerDelegateCommonTest.java
new file mode 100644
index 0000000000..6d854c57e6
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerDelegateCommonTest.java
@@ -0,0 +1,77 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.TestClass;
+
+import org.chromium.base.test.params.ParameterizedRunner.ParameterizedTestInstantiationException;
+
+import java.util.Collections;
+
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ParameterizedRunnerDelegateCommonTest {
+ /**
+ * Create a test object using the list of class parameter set
+ *
+ * @param testClass the {@link TestClass} object for current test class
+ * @param classParameterSet the parameter set needed for the test class constructor
+ */
+ private static Object createTest(TestClass testClass, ParameterSet classParameterSet)
+ throws ParameterizedTestInstantiationException {
+ return new ParameterizedRunnerDelegateCommon(
+ testClass, classParameterSet, Collections.emptyList())
+ .createTest();
+ }
+
+ static class BadTestClassWithMoreThanOneConstructor {
+ public BadTestClassWithMoreThanOneConstructor() {}
+ @SuppressWarnings("unused")
+ public BadTestClassWithMoreThanOneConstructor(String argument) {}
+ }
+
+ static class BadTestClassWithTwoArgumentConstructor {
+ @SuppressWarnings("unused")
+ public BadTestClassWithTwoArgumentConstructor(int a, int b) {}
+ }
+
+ static abstract class BadTestClassAbstract {
+ public BadTestClassAbstract() {}
+ }
+
+ static class BadTestClassConstructorThrows {
+ public BadTestClassConstructorThrows() {
+ throw new RuntimeException();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateTestWithMoreThanOneConstructor() throws Throwable {
+ TestClass testClass = new TestClass(BadTestClassWithMoreThanOneConstructor.class);
+ createTest(testClass, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateTestWithIncorrectArguments() throws Throwable {
+ TestClass testClass = new TestClass(BadTestClassWithTwoArgumentConstructor.class);
+ ParameterSet pSet = new ParameterSet().value(1, 2, 3);
+ createTest(testClass, pSet);
+ }
+
+ @Test(expected = ParameterizedTestInstantiationException.class)
+ public void testCreateTestWithAbstractClass() throws ParameterizedTestInstantiationException {
+ TestClass testClass = new TestClass(BadTestClassAbstract.class);
+ createTest(testClass, null);
+ }
+
+ @Test(expected = ParameterizedTestInstantiationException.class)
+ public void testCreateTestWithThrowingConstructor()
+ throws ParameterizedTestInstantiationException {
+ TestClass testClass = new TestClass(BadTestClassConstructorThrows.class);
+ createTest(testClass, null);
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerDelegateFactoryTest.java b/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerDelegateFactoryTest.java
new file mode 100644
index 0000000000..723382d71d
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerDelegateFactoryTest.java
@@ -0,0 +1,133 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
+
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
+import org.chromium.base.test.params.ParameterizedRunnerDelegateFactory.ParameterizedRunnerDelegateInstantiationException;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Test for org.chromium.base.test.params.ParameterizedRunnerDelegateFactory
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ParameterizedRunnerDelegateFactoryTest {
+ /**
+ * This RunnerDelegate calls `super.collectInitializationErrors()` and would
+ * cause BlockJUnit4ClassRunner to validate test classes.
+ */
+ public static class BadExampleRunnerDelegate
+ extends BlockJUnit4ClassRunner implements ParameterizedRunnerDelegate {
+ public static class LalaTestClass {}
+
+ private final List<FrameworkMethod> mParameterizedFrameworkMethodList;
+
+ BadExampleRunnerDelegate(Class<?> klass,
+ List<FrameworkMethod> parameterizedFrameworkMethods) throws InitializationError {
+ super(klass);
+ mParameterizedFrameworkMethodList = parameterizedFrameworkMethods;
+ }
+
+ @Override
+ public void collectInitializationErrors(List<Throwable> errors) {
+ super.collectInitializationErrors(errors); // This is wrong!!
+ }
+
+ @Override
+ public List<FrameworkMethod> computeTestMethods() {
+ return mParameterizedFrameworkMethodList;
+ }
+
+ @Override
+ public Object createTest() {
+ return null;
+ }
+ }
+
+ static class ExampleTestClass {
+ static class MethodParamsA implements ParameterProvider {
+ @Override
+ public Iterable<ParameterSet> getParameters() {
+ return Arrays.asList(
+ new ParameterSet().value("a").name("testWithValue_a"),
+ new ParameterSet().value("b").name("testWithValue_b")
+ );
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @UseMethodParameter(MethodParamsA.class)
+ @Test
+ public void testA(String a) {}
+
+ static class MethodParamsB implements ParameterProvider {
+ @Override
+ public Iterable<ParameterSet> getParameters() {
+ return Arrays.asList(
+ new ParameterSet().value(1).name("testWithValue_1"),
+ new ParameterSet().value(2).name("testWithValue_2"),
+ new ParameterSet().value(3).name("testWithValue_3")
+ );
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @UseMethodParameter(MethodParamsB.class)
+ @Test
+ public void testB(int b) {}
+
+ @Test
+ public void testByMyself() {}
+ }
+
+ /**
+ * This test validates ParameterizedRunnerDelegateFactory throws exception when
+ * a runner delegate does not override the collectInitializationErrors method.
+ */
+ @Test(expected = ParameterizedRunnerDelegateInstantiationException.class)
+ public void testBadRunnerDelegateWithIncorrectValidationCall() throws Throwable {
+ ParameterizedRunnerDelegateFactory factory = new ParameterizedRunnerDelegateFactory();
+ TestClass testClass = new TestClass(BadExampleRunnerDelegate.LalaTestClass.class);
+ factory.createRunner(testClass, null, BadExampleRunnerDelegate.class);
+ }
+
+ @Test
+ public void testGenerateParameterizedFrameworkMethod() throws Throwable {
+ List<FrameworkMethod> methods =
+ ParameterizedRunnerDelegateFactory.generateUnmodifiableFrameworkMethodList(
+ new TestClass(ExampleTestClass.class), "");
+
+ Assert.assertEquals(methods.size(), 6);
+
+ Map<String, Method> expectedTests = new HashMap<>();
+ Method testMethodA = ExampleTestClass.class.getDeclaredMethod("testA", String.class);
+ Method testMethodB = ExampleTestClass.class.getDeclaredMethod("testB", int.class);
+ Method testMethodByMyself = ExampleTestClass.class.getDeclaredMethod("testByMyself");
+ expectedTests.put("testA__testWithValue_a", testMethodA);
+ expectedTests.put("testA__testWithValue_b", testMethodA);
+ expectedTests.put("testB__testWithValue_1", testMethodB);
+ expectedTests.put("testB__testWithValue_2", testMethodB);
+ expectedTests.put("testB__testWithValue_3", testMethodB);
+ expectedTests.put("testByMyself", testMethodByMyself);
+ for (FrameworkMethod method : methods) {
+ Assert.assertNotNull(expectedTests.get(method.getName()));
+ Assert.assertEquals(expectedTests.get(method.getName()), method.getMethod());
+ expectedTests.remove(method.getName());
+ }
+ Assert.assertTrue(expectedTests.isEmpty());
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerTest.java b/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerTest.java
new file mode 100644
index 0000000000..170ff69eaf
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedRunnerTest.java
@@ -0,0 +1,108 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
+import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
+import org.chromium.base.test.params.ParameterizedRunner.IllegalParameterArgumentException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test for org.chromium.base.test.params.ParameterizedRunner
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ParameterizedRunnerTest {
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class BadTestClassWithMoreThanOneConstructor {
+ @ClassParameter
+ static List<ParameterSet> sClassParams = new ArrayList<>();
+
+ public BadTestClassWithMoreThanOneConstructor() {}
+
+ public BadTestClassWithMoreThanOneConstructor(String x) {}
+ }
+
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class BadTestClassWithNonListParameters {
+ @ClassParameter
+ static String[] sMethodParamA = {"1", "2"};
+
+ @Test
+ public void test() {}
+ }
+
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class BadTestClassWithoutNeedForParameterization {
+ @Test
+ public void test() {}
+ }
+
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class BadTestClassWithNonStaticParameterSetList {
+ @ClassParameter
+ public List<ParameterSet> mClassParams = new ArrayList<>();
+
+ @Test
+ public void test() {}
+ }
+
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class BadTestClassWithMultipleClassParameter {
+ @ClassParameter
+ private static List<ParameterSet> sParamA = new ArrayList<>();
+
+ @ClassParameter
+ private static List<ParameterSet> sParamB = new ArrayList<>();
+ }
+
+ @Test(expected = ParameterizedRunner.IllegalParameterArgumentException.class)
+ public void testEmptyParameterSet() {
+ List<ParameterSet> paramList = new ArrayList<>();
+ paramList.add(new ParameterSet());
+ ParameterizedRunner.validateWidth(paramList);
+ }
+
+ @Test(expected = ParameterizedRunner.IllegalParameterArgumentException.class)
+ public void testUnequalWidthParameterSetList() {
+ List<ParameterSet> paramList = new ArrayList<>();
+ paramList.add(new ParameterSet().value(1, 2));
+ paramList.add(new ParameterSet().value(3, 4, 5));
+ ParameterizedRunner.validateWidth(paramList);
+ }
+
+ @Test(expected = ParameterizedRunner.IllegalParameterArgumentException.class)
+ public void testUnequalWidthParameterSetListWithNull() {
+ List<ParameterSet> paramList = new ArrayList<>();
+ paramList.add(new ParameterSet().value(null));
+ paramList.add(new ParameterSet().value(1, 2));
+ ParameterizedRunner.validateWidth(paramList);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadClassWithNonListParameters() throws Throwable {
+ new ParameterizedRunner(BadTestClassWithNonListParameters.class);
+ }
+
+ @Test(expected = IllegalParameterArgumentException.class)
+ public void testBadClassWithNonStaticParameterSetList() throws Throwable {
+ new ParameterizedRunner(BadTestClassWithNonStaticParameterSetList.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadClassWithoutNeedForParameterization() throws Throwable {
+ new ParameterizedRunner(BadTestClassWithoutNeedForParameterization.class);
+ }
+
+ @Test(expected = Exception.class)
+ public void testBadClassWithMoreThanOneConstructor() throws Throwable {
+ new ParameterizedRunner(BadTestClassWithMoreThanOneConstructor.class);
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedTestNameTest.java b/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedTestNameTest.java
new file mode 100644
index 0000000000..e79f5c5304
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/params/ParameterizedTestNameTest.java
@@ -0,0 +1,201 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.params;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runner.Runner;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
+import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
+import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Test for verify the names and test method Description works properly
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ParameterizedTestNameTest {
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class TestClassWithClassParameterAppendName {
+ @ClassParameter
+ static List<ParameterSet> sAllName = Arrays.asList(
+ new ParameterSet().value("hello").name("Hello"),
+ new ParameterSet().value("world").name("World")
+ );
+
+ public TestClassWithClassParameterAppendName(String a) {}
+
+ @Test
+ public void test() {}
+ }
+
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class TestClassWithClassParameterDefaultName {
+ @ClassParameter
+ static List<ParameterSet> sAllName = Arrays.asList(
+ new ParameterSet().value("hello"),
+ new ParameterSet().value("world")
+ );
+
+ public TestClassWithClassParameterDefaultName(String a) {}
+
+ @Test
+ public void test() {}
+ }
+
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class TestClassWithMethodParameter {
+ static class AppendNameParams implements ParameterProvider {
+ @Override
+ public Iterable<ParameterSet> getParameters() {
+ return Arrays.asList(
+ new ParameterSet().value("hello").name("Hello"),
+ new ParameterSet().value("world").name("World")
+ );
+ }
+ }
+
+ static class DefaultNameParams implements ParameterProvider {
+ @Override
+ public Iterable<ParameterSet> getParameters() {
+ return Arrays.asList(
+ new ParameterSet().value("hello"),
+ new ParameterSet().value("world")
+ );
+ }
+ }
+
+ @UseMethodParameter(AppendNameParams.class)
+ @Test
+ public void test(String a) {}
+
+ @UseMethodParameter(DefaultNameParams.class)
+ @Test
+ public void testDefaultName(String b) {}
+ }
+
+ @UseRunnerDelegate(BlockJUnit4RunnerDelegate.class)
+ public static class TestClassWithMixedParameter {
+ @ClassParameter
+ static List<ParameterSet> sAllName = Arrays.asList(
+ new ParameterSet().value("hello").name("Hello"),
+ new ParameterSet().value("world").name("World")
+ );
+
+ static class AppendNameParams implements ParameterProvider {
+ @Override
+ public Iterable<ParameterSet> getParameters() {
+ return Arrays.asList(
+ new ParameterSet().value("1").name("A"),
+ new ParameterSet().value("2").name("B")
+ );
+ }
+ }
+
+ public TestClassWithMixedParameter(String a) {}
+
+ @UseMethodParameter(AppendNameParams.class)
+ @Test
+ public void testA(String a) {}
+
+ @Test
+ public void test() {}
+ }
+
+ @Test
+ public void testClassParameterAppendName() throws Throwable {
+ List<Runner> runners = ParameterizedRunner.createRunners(
+ new TestClass(TestClassWithClassParameterAppendName.class));
+ List<String> expectedTestNames =
+ new LinkedList<String>(Arrays.asList("test__Hello", "test__World"));
+ List<String> computedMethodNames = new ArrayList<>();
+ for (Runner r : runners) {
+ BlockJUnit4RunnerDelegate castedRunner = (BlockJUnit4RunnerDelegate) r;
+ for (FrameworkMethod method : castedRunner.computeTestMethods()) {
+ computedMethodNames.add(method.getName());
+ Assert.assertTrue("This test name is not expected: " + method.getName(),
+ expectedTestNames.contains(method.getName()));
+ expectedTestNames.remove(method.getName());
+ method.getName();
+ }
+ }
+ Assert.assertTrue(
+ String.format(
+ "These names were provided: %s, these expected names are not found: %s",
+ Arrays.toString(computedMethodNames.toArray()),
+ Arrays.toString(expectedTestNames.toArray())),
+ expectedTestNames.isEmpty());
+ }
+
+ @Test
+ public void testClassParameterDefaultName() throws Throwable {
+ List<Runner> runners = ParameterizedRunner.createRunners(
+ new TestClass(TestClassWithClassParameterDefaultName.class));
+ List<String> expectedTestNames = new LinkedList<String>(Arrays.asList("test", "test"));
+ for (Runner r : runners) {
+ @SuppressWarnings("unchecked")
+ BlockJUnit4RunnerDelegate castedRunner = (BlockJUnit4RunnerDelegate) r;
+ for (FrameworkMethod method : castedRunner.computeTestMethods()) {
+ Assert.assertTrue("This test name is not expected: " + method.getName(),
+ expectedTestNames.contains(method.getName()));
+ expectedTestNames.remove(method.getName());
+ method.getName();
+ }
+ }
+ Assert.assertTrue("These expected names are not found: "
+ + Arrays.toString(expectedTestNames.toArray()),
+ expectedTestNames.isEmpty());
+ }
+
+ @Test
+ public void testMethodParameter() throws Throwable {
+ List<Runner> runners = ParameterizedRunner.createRunners(
+ new TestClass(TestClassWithMethodParameter.class));
+ List<String> expectedTestNames = new LinkedList<String>(
+ Arrays.asList("test__Hello", "test__World", "testDefaultName", "testDefaultName"));
+ for (Runner r : runners) {
+ BlockJUnit4RunnerDelegate castedRunner = (BlockJUnit4RunnerDelegate) r;
+ for (FrameworkMethod method : castedRunner.computeTestMethods()) {
+ Assert.assertTrue("This test name is not expected: " + method.getName(),
+ expectedTestNames.contains(method.getName()));
+ expectedTestNames.remove(method.getName());
+ method.getName();
+ }
+ }
+ Assert.assertTrue("These expected names are not found: "
+ + Arrays.toString(expectedTestNames.toArray()),
+ expectedTestNames.isEmpty());
+ }
+
+ @Test
+ public void testMixedParameterTestA() throws Throwable {
+ List<Runner> runners =
+ ParameterizedRunner.createRunners(new TestClass(TestClassWithMixedParameter.class));
+ List<String> expectedTestNames =
+ new LinkedList<String>(Arrays.asList("testA__Hello_A", "testA__World_A",
+ "testA__Hello_B", "testA__World_B", "test__Hello", "test__World"));
+ for (Runner r : runners) {
+ BlockJUnit4RunnerDelegate castedRunner = (BlockJUnit4RunnerDelegate) r;
+ for (FrameworkMethod method : castedRunner.computeTestMethods()) {
+ Assert.assertTrue("This test name is not expected: " + method.getName(),
+ expectedTestNames.contains(method.getName()));
+ expectedTestNames.remove(method.getName());
+ method.getName();
+ }
+ }
+ Assert.assertTrue("These expected names are not found: "
+ + Arrays.toString(expectedTestNames.toArray()),
+ expectedTestNames.isEmpty());
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/util/DisableIfTest.java b/base/test/android/junit/src/org/chromium/base/test/util/DisableIfTest.java
new file mode 100644
index 0000000000..a147435355
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/util/DisableIfTest.java
@@ -0,0 +1,193 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.os.Build;
+
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for the DisableIf annotation and its SkipCheck implementation. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 21)
+public class DisableIfTest {
+ @Test
+ public void testSdkIsLessThanAndIsLessThan() {
+ TestCase sdkIsLessThan = new TestCase("sdkIsLessThan") {
+ @DisableIf.Build(sdk_is_less_than = 22)
+ public void sdkIsLessThan() {}
+ };
+ Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(sdkIsLessThan));
+ }
+
+ @Test
+ public void testSdkIsLessThanButIsEqual() {
+ TestCase sdkIsEqual = new TestCase("sdkIsEqual") {
+ @DisableIf.Build(sdk_is_less_than = 21)
+ public void sdkIsEqual() {}
+ };
+ Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(sdkIsEqual));
+ }
+
+ @Test
+ public void testSdkIsLessThanButIsGreaterThan() {
+ TestCase sdkIsGreaterThan = new TestCase("sdkIsGreaterThan") {
+ @DisableIf.Build(sdk_is_less_than = 20)
+ public void sdkIsGreaterThan() {}
+ };
+ Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(sdkIsGreaterThan));
+ }
+
+ @Test
+ public void testSdkIsGreaterThanButIsLessThan() {
+ TestCase sdkIsLessThan = new TestCase("sdkIsLessThan") {
+ @DisableIf.Build(sdk_is_greater_than = 22)
+ public void sdkIsLessThan() {}
+ };
+ Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(sdkIsLessThan));
+ }
+
+ @Test
+ public void testSdkIsGreaterThanButIsEqual() {
+ TestCase sdkIsEqual = new TestCase("sdkIsEqual") {
+ @DisableIf.Build(sdk_is_greater_than = 21)
+ public void sdkIsEqual() {}
+ };
+ Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(sdkIsEqual));
+ }
+
+ @Test
+ public void testSdkIsGreaterThanAndIsGreaterThan() {
+ TestCase sdkIsGreaterThan = new TestCase("sdkIsGreaterThan") {
+ @DisableIf.Build(sdk_is_greater_than = 20)
+ public void sdkIsGreaterThan() {}
+ };
+ Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(sdkIsGreaterThan));
+ }
+
+ @Test
+ public void testSupportedAbiIncludesAndCpuAbiMatches() {
+ TestCase supportedAbisCpuAbiMatch = new TestCase("supportedAbisCpuAbiMatch") {
+ @DisableIf.Build(supported_abis_includes = "foo")
+ public void supportedAbisCpuAbiMatch() {}
+ };
+ String[] originalAbis = Build.SUPPORTED_ABIS;
+ try {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS",
+ new String[] {"foo", "bar"});
+ Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(supportedAbisCpuAbiMatch));
+ } finally {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS", originalAbis);
+ }
+ }
+
+ @Test
+ public void testSupportedAbiIncludesAndCpuAbi2Matches() {
+ TestCase supportedAbisCpuAbi2Match = new TestCase("supportedAbisCpuAbi2Match") {
+ @DisableIf.Build(supported_abis_includes = "bar")
+ public void supportedAbisCpuAbi2Match() {}
+ };
+ String[] originalAbis = Build.SUPPORTED_ABIS;
+ try {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS",
+ new String[] {"foo", "bar"});
+ Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(supportedAbisCpuAbi2Match));
+ } finally {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS", originalAbis);
+ }
+ }
+
+ @Test
+ public void testSupportedAbiIncludesButNoMatch() {
+ TestCase supportedAbisNoMatch = new TestCase("supportedAbisNoMatch") {
+ @DisableIf.Build(supported_abis_includes = "baz")
+ public void supportedAbisNoMatch() {}
+ };
+ String[] originalAbis = Build.SUPPORTED_ABIS;
+ try {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS",
+ new String[] {"foo", "bar"});
+ Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(supportedAbisNoMatch));
+ } finally {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS", originalAbis);
+ }
+ }
+
+ @Test
+ public void testHardwareIsMatches() {
+ TestCase hardwareIsMatches = new TestCase("hardwareIsMatches") {
+ @DisableIf.Build(hardware_is = "hammerhead")
+ public void hardwareIsMatches() {}
+ };
+ String originalHardware = Build.HARDWARE;
+ try {
+ ReflectionHelpers.setStaticField(Build.class, "HARDWARE", "hammerhead");
+ Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(hardwareIsMatches));
+ } finally {
+ ReflectionHelpers.setStaticField(Build.class, "HARDWARE", originalHardware);
+ }
+ }
+
+ @Test
+ public void testHardwareIsDoesntMatch() {
+ TestCase hardwareIsDoesntMatch = new TestCase("hardwareIsDoesntMatch") {
+ @DisableIf.Build(hardware_is = "hammerhead")
+ public void hardwareIsDoesntMatch() {}
+ };
+ String originalHardware = Build.HARDWARE;
+ try {
+ ReflectionHelpers.setStaticField(Build.class, "HARDWARE", "mako");
+ Assert.assertFalse(new DisableIfSkipCheck().shouldSkip(hardwareIsDoesntMatch));
+ } finally {
+ ReflectionHelpers.setStaticField(Build.class, "HARDWARE", originalHardware);
+ }
+ }
+
+ @DisableIf.Build(supported_abis_includes = "foo")
+ private static class DisableIfSuperclassTestCase extends TestCase {
+ public DisableIfSuperclassTestCase(String name) {
+ super(name);
+ }
+ }
+
+ @DisableIf.Build(hardware_is = "hammerhead")
+ private static class DisableIfTestCase extends DisableIfSuperclassTestCase {
+ public DisableIfTestCase(String name) {
+ super(name);
+ }
+ public void sampleTestMethod() {}
+ }
+
+ @Test
+ public void testDisableClass() {
+ TestCase sampleTestMethod = new DisableIfTestCase("sampleTestMethod");
+ String originalHardware = Build.HARDWARE;
+ try {
+ ReflectionHelpers.setStaticField(Build.class, "HARDWARE", "hammerhead");
+ Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(sampleTestMethod));
+ } finally {
+ ReflectionHelpers.setStaticField(Build.class, "HARDWARE", originalHardware);
+ }
+ }
+
+ @Test
+ public void testDisableSuperClass() {
+ TestCase sampleTestMethod = new DisableIfTestCase("sampleTestMethod");
+ String[] originalAbis = Build.SUPPORTED_ABIS;
+ try {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS", new String[] {"foo"});
+ Assert.assertTrue(new DisableIfSkipCheck().shouldSkip(sampleTestMethod));
+ } finally {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS", originalAbis);
+ }
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheckTest.java b/base/test/android/junit/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheckTest.java
new file mode 100644
index 0000000000..2236646938
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/util/MinAndroidSdkLevelSkipCheckTest.java
@@ -0,0 +1,95 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.isIn;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.FrameworkMethod;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for MinAndroidSdkLevelSkipCheck. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 18)
+public class MinAndroidSdkLevelSkipCheckTest {
+ public static class UnannotatedBaseClass {
+ @Test @MinAndroidSdkLevel(17) public void min17Method() {}
+ @Test @MinAndroidSdkLevel(20) public void min20Method() {}
+ }
+
+ @MinAndroidSdkLevel(17)
+ public static class Min17Class extends UnannotatedBaseClass {
+ @Test public void unannotatedMethod() {}
+ }
+
+ @MinAndroidSdkLevel(20)
+ public static class Min20Class extends UnannotatedBaseClass {
+ @Test public void unannotatedMethod() {}
+ }
+
+ public static class ExtendsMin17Class extends Min17Class {
+ @Override
+ @Test public void unannotatedMethod() {}
+ }
+
+ public static class ExtendsMin20Class extends Min20Class {
+ @Override
+ @Test public void unannotatedMethod() {}
+ }
+
+ private MinAndroidSdkLevelSkipCheck mSkipCheck = new MinAndroidSdkLevelSkipCheck();
+
+ @Rule
+ public TestRunnerTestRule mTestRunnerTestRule =
+ new TestRunnerTestRule(BaseJUnit4ClassRunner.class);
+
+ private void expectShouldSkip(Class<?> testClass, String methodName, boolean shouldSkip)
+ throws Exception {
+ Assert.assertThat(
+ mSkipCheck.shouldSkip(new FrameworkMethod(testClass.getMethod(methodName))),
+ equalTo(shouldSkip));
+ TestRunnerTestRule.TestLog runListener = mTestRunnerTestRule.runTest(testClass);
+ Assert.assertThat(Description.createTestDescription(testClass, methodName),
+ isIn(shouldSkip ? runListener.skippedTests : runListener.runTests));
+ }
+
+ @Test
+ public void testAnnotatedMethodAboveMin() throws Exception {
+ expectShouldSkip(UnannotatedBaseClass.class, "min17Method", false);
+ }
+
+ @Test
+ public void testAnnotatedMethodBelowMin() throws Exception {
+ expectShouldSkip(UnannotatedBaseClass.class, "min20Method", true);
+ }
+
+ @Test
+ public void testAnnotatedClassAboveMin() throws Exception {
+ expectShouldSkip(Min17Class.class, "unannotatedMethod", false);
+ }
+
+ @Test
+ public void testAnnotatedClassBelowMin() throws Exception {
+ expectShouldSkip(Min20Class.class, "unannotatedMethod", true);
+ }
+
+ @Test
+ public void testAnnotatedSuperclassAboveMin() throws Exception {
+ expectShouldSkip(ExtendsMin17Class.class, "unannotatedMethod", false);
+ }
+
+ @Test
+ public void testAnnotatedSuperclassBelowMin() throws Exception {
+ expectShouldSkip(ExtendsMin20Class.class, "unannotatedMethod", true);
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/util/RestrictionSkipCheckTest.java b/base/test/android/junit/src/org/chromium/base/test/util/RestrictionSkipCheckTest.java
new file mode 100644
index 0000000000..86285de3f0
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/util/RestrictionSkipCheckTest.java
@@ -0,0 +1,129 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import android.text.TextUtils;
+
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for RestrictionSkipCheck. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class RestrictionSkipCheckTest {
+ private static final String TEST_RESTRICTION_APPLIES =
+ "org.chromium.base.test.util.RestrictionSkipCheckTest.TEST_RESTRICTION_APPLIES";
+ private static final String TEST_RESTRICTION_DOES_NOT_APPLY =
+ "org.chromium.base.test.util.RestrictionSkipCheckTest.TEST_RESTRICTION_DOES_NOT_APPLY";
+
+ private static class TestRestrictionSkipCheck extends RestrictionSkipCheck {
+ public TestRestrictionSkipCheck() {
+ super(null);
+ }
+ @Override
+ protected boolean restrictionApplies(String restriction) {
+ return TextUtils.equals(restriction, TEST_RESTRICTION_APPLIES);
+ }
+ }
+
+ private static class UnannotatedBaseClass extends TestCase {
+ public UnannotatedBaseClass(String name) {
+ super(name);
+ }
+ @Restriction({TEST_RESTRICTION_APPLIES}) public void restrictedMethod() {}
+ @Restriction({TEST_RESTRICTION_DOES_NOT_APPLY}) public void unrestrictedMethod() {}
+ }
+
+ @Restriction({TEST_RESTRICTION_APPLIES})
+ private static class RestrictedClass extends UnannotatedBaseClass {
+ public RestrictedClass(String name) {
+ super(name);
+ }
+ public void unannotatedMethod() {}
+ }
+
+ @Restriction({TEST_RESTRICTION_DOES_NOT_APPLY})
+ private static class UnrestrictedClass extends UnannotatedBaseClass {
+ public UnrestrictedClass(String name) {
+ super(name);
+ }
+ public void unannotatedMethod() {}
+ }
+
+ @Restriction({
+ TEST_RESTRICTION_APPLIES,
+ TEST_RESTRICTION_DOES_NOT_APPLY})
+ private static class MultipleRestrictionsRestrictedClass extends UnannotatedBaseClass {
+ public MultipleRestrictionsRestrictedClass(String name) {
+ super(name);
+ }
+ public void unannotatedMethod() {}
+ }
+
+ private static class ExtendsRestrictedClass extends RestrictedClass {
+ public ExtendsRestrictedClass(String name) {
+ super(name);
+ }
+ @Override
+ public void unannotatedMethod() {}
+ }
+
+ private static class ExtendsUnrestrictedClass extends UnrestrictedClass {
+ public ExtendsUnrestrictedClass(String name) {
+ super(name);
+ }
+ @Override
+ public void unannotatedMethod() {}
+ }
+
+ @Test
+ public void testMethodRestricted() {
+ Assert.assertTrue(new TestRestrictionSkipCheck().shouldSkip(
+ new UnannotatedBaseClass("restrictedMethod")));
+ }
+
+ @Test
+ public void testMethodUnrestricted() {
+ Assert.assertFalse(new TestRestrictionSkipCheck().shouldSkip(
+ new UnannotatedBaseClass("unrestrictedMethod")));
+ }
+
+ @Test
+ public void testClassRestricted() {
+ Assert.assertTrue(new TestRestrictionSkipCheck().shouldSkip(
+ new RestrictedClass("unannotatedMethod")));
+ }
+
+ @Test
+ public void testClassUnrestricted() {
+ Assert.assertFalse(new TestRestrictionSkipCheck().shouldSkip(
+ new UnrestrictedClass("unannotatedMethod")));
+ }
+
+ @Test
+ public void testMultipleRestrictionsClassRestricted() {
+ Assert.assertTrue(new TestRestrictionSkipCheck().shouldSkip(
+ new MultipleRestrictionsRestrictedClass("unannotatedMethod")));
+ }
+
+ @Test
+ public void testSuperclassRestricted() {
+ Assert.assertTrue(new TestRestrictionSkipCheck().shouldSkip(
+ new ExtendsRestrictedClass("unannotatedMethod")));
+ }
+
+ @Test
+ public void testSuperclassUnrestricted() {
+ Assert.assertFalse(new TestRestrictionSkipCheck().shouldSkip(
+ new ExtendsUnrestrictedClass("unannotatedMethod")));
+ }
+}
+
diff --git a/base/test/android/junit/src/org/chromium/base/test/util/SkipCheckTest.java b/base/test/android/junit/src/org/chromium/base/test/util/SkipCheckTest.java
new file mode 100644
index 0000000000..51c7516e71
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/util/SkipCheckTest.java
@@ -0,0 +1,130 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.FrameworkMethod;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/** Unit tests for SkipCheck. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class SkipCheckTest {
+ private static class TestableSkipCheck extends SkipCheck {
+ public static <T extends Annotation> List<T> getAnnotationsForTesting(
+ AnnotatedElement element, Class<T> annotationClass) {
+ return AnnotationProcessingUtils.getAnnotations(element, annotationClass);
+ }
+
+ @Override
+ public boolean shouldSkip(FrameworkMethod m) {
+ return false;
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface TestAnnotation {}
+
+ @TestAnnotation
+ private class AnnotatedBaseClass {
+ public void unannotatedMethod() {}
+ @TestAnnotation public void annotatedMethod() {}
+ }
+
+ private class ExtendsAnnotatedBaseClass extends AnnotatedBaseClass {
+ public void anotherUnannotatedMethod() {}
+ }
+
+ private class ExtendsTestCaseClass extends TestCase {
+ public ExtendsTestCaseClass(String name) {
+ super(name);
+ }
+ public void testMethodA() {}
+ }
+
+ private class UnannotatedBaseClass {
+ public void unannotatedMethod() {}
+ @TestAnnotation public void annotatedMethod() {}
+ }
+
+ @Test
+ public void getAnnotationsForClassNone() {
+ List<TestAnnotation> annotations = TestableSkipCheck.getAnnotationsForTesting(
+ UnannotatedBaseClass.class, TestAnnotation.class);
+ Assert.assertEquals(0, annotations.size());
+ }
+
+ @Test
+ public void getAnnotationsForClassOnClass() {
+ List<TestAnnotation> annotations = TestableSkipCheck.getAnnotationsForTesting(
+ AnnotatedBaseClass.class, TestAnnotation.class);
+ Assert.assertEquals(1, annotations.size());
+ }
+
+ @Test
+ public void getAnnotationsForClassOnSuperclass() {
+ List<TestAnnotation> annotations = TestableSkipCheck.getAnnotationsForTesting(
+ ExtendsAnnotatedBaseClass.class, TestAnnotation.class);
+ Assert.assertEquals(1, annotations.size());
+ }
+
+ @Test
+ public void getAnnotationsForMethodNone() throws NoSuchMethodException {
+ Method testMethod = UnannotatedBaseClass.class.getMethod("unannotatedMethod",
+ (Class[]) null);
+ List<TestAnnotation> annotations = TestableSkipCheck.getAnnotationsForTesting(
+ testMethod, TestAnnotation.class);
+ Assert.assertEquals(0, annotations.size());
+ }
+
+ @Test
+ public void getAnnotationsForMethodOnMethod() throws NoSuchMethodException {
+ Method testMethod = UnannotatedBaseClass.class.getMethod("annotatedMethod",
+ (Class[]) null);
+ List<TestAnnotation> annotations = TestableSkipCheck.getAnnotationsForTesting(
+ testMethod, TestAnnotation.class);
+ Assert.assertEquals(1, annotations.size());
+ }
+
+ @Test
+ public void getAnnotationsForMethodOnClass() throws NoSuchMethodException {
+ Method testMethod = AnnotatedBaseClass.class.getMethod("unannotatedMethod",
+ (Class[]) null);
+ List<TestAnnotation> annotations = TestableSkipCheck.getAnnotationsForTesting(
+ testMethod, TestAnnotation.class);
+ Assert.assertEquals(1, annotations.size());
+ }
+
+ @Test
+ public void getAnnotationsForMethodOnSuperclass() throws NoSuchMethodException {
+ Method testMethod = ExtendsAnnotatedBaseClass.class.getMethod("unannotatedMethod",
+ (Class[]) null);
+ List<TestAnnotation> annotations = TestableSkipCheck.getAnnotationsForTesting(
+ testMethod, TestAnnotation.class);
+ Assert.assertEquals(1, annotations.size());
+ }
+
+ @Test
+ public void getAnnotationsOverlapping() throws NoSuchMethodException {
+ Method testMethod = AnnotatedBaseClass.class.getMethod("annotatedMethod",
+ (Class[]) null);
+ List<TestAnnotation> annotations = TestableSkipCheck.getAnnotationsForTesting(
+ testMethod, TestAnnotation.class);
+ Assert.assertEquals(2, annotations.size());
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/util/TestRunnerTestRule.java b/base/test/android/junit/src/org/chromium/base/test/util/TestRunnerTestRule.java
new file mode 100644
index 0000000000..64805c124e
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/util/TestRunnerTestRule.java
@@ -0,0 +1,132 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import static org.hamcrest.Matchers.isIn;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Assert;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+import org.robolectric.RuntimeEnvironment;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper rule to allow executing test runners in tests.
+ *
+ * Quis probat ipsas probas?
+ */
+class TestRunnerTestRule extends ExternalResource {
+ final Class<? extends BlockJUnit4ClassRunner> mRunnerClass;
+
+ /**
+ * @param runnerClass The runner class to run the test
+ */
+ TestRunnerTestRule(Class<? extends BlockJUnit4ClassRunner> runnerClass) {
+ mRunnerClass = runnerClass;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ // Register a fake Instrumentation so that class runners for instrumentation tests
+ // can be run even in Robolectric tests.
+ Instrumentation instrumentation = new Instrumentation() {
+ @Override
+ public Context getTargetContext() {
+ return RuntimeEnvironment.application;
+ }
+ };
+ InstrumentationRegistry.registerInstance(instrumentation, new Bundle());
+ }
+
+ @Override
+ protected void after() {
+ InstrumentationRegistry.registerInstance(null, new Bundle());
+ }
+
+ /**
+ * A struct-like class containing lists of run and skipped tests.
+ */
+ public static class TestLog {
+ public final List<Description> runTests = new ArrayList<>();
+ public final List<Description> skippedTests = new ArrayList<>();
+ }
+
+ /**
+ * Creates a new test runner and executes the test in the given {@code testClass} on it,
+ * returning lists of tests that were run and tests that were skipped.
+ * @param testClass The test class
+ * @return A {@link TestLog} that contains lists of run and skipped tests.
+ */
+ public TestLog runTest(Class<?> testClass) throws InvocationTargetException,
+ NoSuchMethodException, InstantiationException,
+ IllegalAccessException {
+ TestLog testLog = new TestLog();
+
+ // TODO(bauerb): Using Mockito mock() or spy() throws a ClassCastException.
+ RunListener runListener = new RunListener() {
+ @Override
+ public void testStarted(Description description) throws Exception {
+ testLog.runTests.add(description);
+ }
+
+ @Override
+ public void testFinished(Description description) throws Exception {
+ Assert.assertThat(description, isIn(testLog.runTests));
+ }
+
+ @Override
+ public void testFailure(Failure failure) throws Exception {
+ fail(failure.toString());
+ }
+
+ @Override
+ public void testAssumptionFailure(Failure failure) {
+ fail(failure.toString());
+ }
+
+ @Override
+ public void testIgnored(Description description) throws Exception {
+ testLog.skippedTests.add(description);
+ }
+ };
+ RunNotifier runNotifier = new RunNotifier();
+ runNotifier.addListener(runListener);
+ Runner runner;
+ try {
+ runner = mRunnerClass.getConstructor(Class.class).newInstance(testClass);
+ } catch (InvocationTargetException e) {
+ // If constructing the runner caused initialization errors, unwrap them from the
+ // InvocationTargetException.
+ Throwable cause = e.getCause();
+ if (!(cause instanceof InitializationError)) throw e;
+ List<Throwable> causes = ((InitializationError) cause).getCauses();
+
+ // If there was exactly one initialization error, rewrap that one.
+ if (causes.size() == 1) {
+ throw new InvocationTargetException(causes.get(0), "Initialization error");
+ }
+
+ // Otherwise, serialize all initialization errors to a string and throw that.
+ throw new AssertionError(causes.toString());
+ }
+ runner.run(runNotifier);
+ return testLog;
+ }
+}
diff --git a/base/test/fuzzed_data_provider.cc b/base/test/fuzzed_data_provider.cc
new file mode 100644
index 0000000000..b2d443a9b9
--- /dev/null
+++ b/base/test/fuzzed_data_provider.cc
@@ -0,0 +1,98 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/fuzzed_data_provider.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+
+namespace base {
+
+FuzzedDataProvider::FuzzedDataProvider(const uint8_t* data, size_t size)
+ : remaining_data_(reinterpret_cast<const char*>(data), size) {}
+
+FuzzedDataProvider::~FuzzedDataProvider() = default;
+
+std::string FuzzedDataProvider::ConsumeBytes(size_t num_bytes) {
+ num_bytes = std::min(num_bytes, remaining_data_.length());
+ StringPiece result(remaining_data_.data(), num_bytes);
+ remaining_data_ = remaining_data_.substr(num_bytes);
+ return result.as_string();
+}
+
+std::string FuzzedDataProvider::ConsumeRemainingBytes() {
+ return ConsumeBytes(remaining_data_.length());
+}
+
+uint32_t FuzzedDataProvider::ConsumeUint32InRange(uint32_t min, uint32_t max) {
+ CHECK_LE(min, max);
+
+ uint32_t range = max - min;
+ uint32_t offset = 0;
+ uint32_t result = 0;
+
+ while (offset < 32 && (range >> offset) > 0 && !remaining_data_.empty()) {
+ // Pull bytes off the end of the seed data. Experimentally, this seems to
+ // allow the fuzzer to more easily explore the input space. This makes
+ // sense, since it works by modifying inputs that caused new code to run,
+ // and this data is often used to encode length of data read by
+ // ConsumeBytes. Separating out read lengths makes it easier modify the
+ // contents of the data that is actually read.
+ uint8_t next_byte = remaining_data_.back();
+ remaining_data_.remove_suffix(1);
+ result = (result << 8) | next_byte;
+ offset += 8;
+ }
+
+ // Avoid division by 0, in the case |range + 1| results in overflow.
+ if (range == std::numeric_limits<uint32_t>::max())
+ return result;
+
+ return min + result % (range + 1);
+}
+
+std::string FuzzedDataProvider::ConsumeRandomLengthString(size_t max_length) {
+ // Reads bytes from start of |remaining_data_|. Maps "\\" to "\", and maps "\"
+ // followed by anything else to the end of the string. As a result of this
+ // logic, a fuzzer can insert characters into the string, and the string will
+ // be lengthened to include those new characters, resulting in a more stable
+ // fuzzer than picking the length of a string independently from picking its
+ // contents.
+ std::string out;
+ for (size_t i = 0; i < max_length && !remaining_data_.empty(); ++i) {
+ char next = remaining_data_[0];
+ remaining_data_.remove_prefix(1);
+ if (next == '\\' && !remaining_data_.empty()) {
+ next = remaining_data_[0];
+ remaining_data_.remove_prefix(1);
+ if (next != '\\')
+ return out;
+ }
+ out += next;
+ }
+ return out;
+}
+
+int FuzzedDataProvider::ConsumeInt32InRange(int min, int max) {
+ CHECK_LE(min, max);
+
+ uint32_t range = max - min;
+ return min + ConsumeUint32InRange(0, range);
+}
+
+bool FuzzedDataProvider::ConsumeBool() {
+ return (ConsumeUint8() & 0x01) == 0x01;
+}
+
+uint8_t FuzzedDataProvider::ConsumeUint8() {
+ return ConsumeUint32InRange(0, 0xFF);
+}
+
+uint16_t FuzzedDataProvider::ConsumeUint16() {
+ return ConsumeUint32InRange(0, 0xFFFF);
+}
+
+} // namespace base
diff --git a/base/test/fuzzed_data_provider.h b/base/test/fuzzed_data_provider.h
new file mode 100644
index 0000000000..425c820a21
--- /dev/null
+++ b/base/test/fuzzed_data_provider.h
@@ -0,0 +1,80 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_FUZZED_DATA_PROVIDER_H_
+#define BASE_TEST_FUZZED_DATA_PROVIDER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+
+// Utility class to break up fuzzer input for multiple consumers. Whenever run
+// on the same input, provides the same output, as long as its methods are
+// called in the same order, with the same arguments.
+class FuzzedDataProvider {
+ public:
+ // |data| is an array of length |size| that the FuzzedDataProvider wraps to
+ // provide more granular access. |data| must outlive the FuzzedDataProvider.
+ FuzzedDataProvider(const uint8_t* data, size_t size);
+ ~FuzzedDataProvider();
+
+ // Returns a std::string containing |num_bytes| of input data. If fewer than
+ // |num_bytes| of data remain, returns a shorter std::string containing all
+ // of the data that's left.
+ std::string ConsumeBytes(size_t num_bytes);
+
+ // Returns a std::string containing all remaining bytes of the input data.
+ std::string ConsumeRemainingBytes();
+
+ // Returns a std::string of length from 0 to |max_length|. When it runs out of
+ // input data, returns what remains of the input. Designed to be more stable
+ // with respect to a fuzzer inserting characters than just picking a random
+ // length and then consuming that many bytes with ConsumeBytes().
+ std::string ConsumeRandomLengthString(size_t max_length);
+
+ // Returns a number in the range [min, max] by consuming bytes from the input
+ // data. The value might not be uniformly distributed in the given range. If
+ // there's no input data left, always returns |min|. |min| must be less than
+ // or equal to |max|.
+ uint32_t ConsumeUint32InRange(uint32_t min, uint32_t max);
+ int ConsumeInt32InRange(int min, int max);
+
+ // Returns a bool, or false when no data remains.
+ bool ConsumeBool();
+
+ // Returns a uint8_t from the input or 0 if nothing remains. This is
+ // equivalent to ConsumeUint32InRange(0, 0xFF).
+ uint8_t ConsumeUint8();
+
+ // Returns a uint16_t from the input. If fewer than 2 bytes of data remain
+ // will fill the most significant bytes with 0. This is equivalent to
+ // ConsumeUint32InRange(0, 0xFFFF).
+ uint16_t ConsumeUint16();
+
+ // Returns a value from |array|, consuming as many bytes as needed to do so.
+ // |array| must be a fixed-size array. Equivalent to
+ // array[ConsumeUint32InRange(sizeof(array)-1)];
+ template <typename Type, size_t size>
+ Type PickValueInArray(Type (&array)[size]) {
+ return array[ConsumeUint32InRange(0, size - 1)];
+ }
+
+ // Reports the remaining bytes available for fuzzed input.
+ size_t remaining_bytes() { return remaining_data_.length(); }
+
+ private:
+ StringPiece remaining_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FuzzedDataProvider);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_FUZZED_DATA_PROVIDER_H_
diff --git a/base/test/gtest_xml_unittest_result_printer.cc b/base/test/gtest_xml_unittest_result_printer.cc
new file mode 100644
index 0000000000..558a9867b4
--- /dev/null
+++ b/base/test/gtest_xml_unittest_result_printer.cc
@@ -0,0 +1,162 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/gtest_xml_unittest_result_printer.h"
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/test/test_switches.h"
+#include "base/time/time.h"
+
+namespace base {
+
+namespace {
+const int kDefaultTestPartResultsLimit = 10;
+
+const char kTestPartLesultsLimitExceeded[] =
+ "Test part results limit exceeded. Use --test-launcher-test-part-limit to "
+ "increase or disable limit.";
+} // namespace
+
+XmlUnitTestResultPrinter::XmlUnitTestResultPrinter()
+ : output_file_(nullptr), open_failed_(false) {}
+
+XmlUnitTestResultPrinter::~XmlUnitTestResultPrinter() {
+ if (output_file_ && !open_failed_) {
+ fprintf(output_file_, "</testsuites>\n");
+ fflush(output_file_);
+ CloseFile(output_file_);
+ }
+}
+
+bool XmlUnitTestResultPrinter::Initialize(const FilePath& output_file_path) {
+ DCHECK(!output_file_);
+ output_file_ = OpenFile(output_file_path, "w");
+ if (!output_file_) {
+ // If the file open fails, we set the output location to stderr. This is
+ // because in current usage our caller CHECKs the result of this function.
+ // But that in turn causes a LogMessage that comes back to this object,
+ // which in turn causes a (double) crash. By pointing at stderr, there might
+ // be some indication what's going wrong. See https://crbug.com/736783.
+ output_file_ = stderr;
+ open_failed_ = true;
+ return false;
+ }
+
+ fprintf(output_file_,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites>\n");
+ fflush(output_file_);
+
+ return true;
+}
+
+void XmlUnitTestResultPrinter::OnAssert(const char* file,
+ int line,
+ const std::string& summary,
+ const std::string& message) {
+ WriteTestPartResult(file, line, testing::TestPartResult::kFatalFailure,
+ summary, message);
+}
+
+void XmlUnitTestResultPrinter::OnTestCaseStart(
+ const testing::TestCase& test_case) {
+ fprintf(output_file_, " <testsuite>\n");
+ fflush(output_file_);
+}
+
+void XmlUnitTestResultPrinter::OnTestStart(
+ const testing::TestInfo& test_info) {
+ // This is our custom extension - it helps to recognize which test was
+ // running when the test binary crashed. Note that we cannot even open the
+ // <testcase> tag here - it requires e.g. run time of the test to be known.
+ fprintf(output_file_,
+ " <x-teststart name=\"%s\" classname=\"%s\" />\n",
+ test_info.name(),
+ test_info.test_case_name());
+ fflush(output_file_);
+}
+
+void XmlUnitTestResultPrinter::OnTestEnd(const testing::TestInfo& test_info) {
+ fprintf(output_file_,
+ " <testcase name=\"%s\" status=\"run\" time=\"%.3f\""
+ " classname=\"%s\">\n",
+ test_info.name(),
+ static_cast<double>(test_info.result()->elapsed_time()) /
+ Time::kMillisecondsPerSecond,
+ test_info.test_case_name());
+ if (test_info.result()->Failed()) {
+ fprintf(output_file_,
+ " <failure message=\"\" type=\"\"></failure>\n");
+ }
+
+ int limit = test_info.result()->total_part_count();
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherTestPartResultsLimit)) {
+ std::string limit_str =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kTestLauncherTestPartResultsLimit);
+ int test_part_results_limit = std::strtol(limit_str.c_str(), nullptr, 10);
+ if (test_part_results_limit >= 0)
+ limit = std::min(limit, test_part_results_limit);
+ } else {
+ limit = std::min(limit, kDefaultTestPartResultsLimit);
+ }
+
+ for (int i = 0; i < limit; ++i) {
+ const auto& test_part_result = test_info.result()->GetTestPartResult(i);
+ WriteTestPartResult(test_part_result.file_name(),
+ test_part_result.line_number(), test_part_result.type(),
+ test_part_result.summary(), test_part_result.message());
+ }
+
+ if (test_info.result()->total_part_count() > limit) {
+ WriteTestPartResult(
+ "<unknown>", 0, testing::TestPartResult::kNonFatalFailure,
+ kTestPartLesultsLimitExceeded, kTestPartLesultsLimitExceeded);
+ }
+
+ fprintf(output_file_, " </testcase>\n");
+ fflush(output_file_);
+}
+
+void XmlUnitTestResultPrinter::OnTestCaseEnd(
+ const testing::TestCase& test_case) {
+ fprintf(output_file_, " </testsuite>\n");
+ fflush(output_file_);
+}
+
+void XmlUnitTestResultPrinter::WriteTestPartResult(
+ const char* file,
+ int line,
+ testing::TestPartResult::Type result_type,
+ const std::string& summary,
+ const std::string& message) {
+ const char* type = "unknown";
+ switch (result_type) {
+ case testing::TestPartResult::kSuccess:
+ type = "success";
+ break;
+ case testing::TestPartResult::kNonFatalFailure:
+ type = "failure";
+ break;
+ case testing::TestPartResult::kFatalFailure:
+ type = "fatal_failure";
+ break;
+ }
+ std::string summary_encoded;
+ Base64Encode(summary, &summary_encoded);
+ std::string message_encoded;
+ Base64Encode(message, &message_encoded);
+ fprintf(output_file_,
+ " <x-test-result-part type=\"%s\" file=\"%s\" line=\"%d\">\n"
+ " <summary>%s</summary>\n"
+ " <message>%s</message>\n"
+ " </x-test-result-part>\n",
+ type, file, line, summary_encoded.c_str(), message_encoded.c_str());
+ fflush(output_file_);
+}
+
+} // namespace base
diff --git a/base/test/gtest_xml_unittest_result_printer.h b/base/test/gtest_xml_unittest_result_printer.h
new file mode 100644
index 0000000000..93403822cf
--- /dev/null
+++ b/base/test/gtest_xml_unittest_result_printer.h
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_GTEST_XML_UNITTEST_RESULT_PRINTER_H_
+#define BASE_TEST_GTEST_XML_UNITTEST_RESULT_PRINTER_H_
+
+#include <stdio.h>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+class FilePath;
+
+// Generates an XML output file. Format is very close to GTest, but has
+// extensions needed by the test launcher.
+class XmlUnitTestResultPrinter : public testing::EmptyTestEventListener {
+ public:
+ XmlUnitTestResultPrinter();
+ ~XmlUnitTestResultPrinter() override;
+
+ // Must be called before adding as a listener. Returns true on success.
+ bool Initialize(const FilePath& output_file_path) WARN_UNUSED_RESULT;
+
+ // CHECK/DCHECK failed. Print file/line and message to the xml.
+ void OnAssert(const char* file,
+ int line,
+ const std::string& summary,
+ const std::string& message);
+
+ private:
+ // testing::EmptyTestEventListener:
+ void OnTestCaseStart(const testing::TestCase& test_case) override;
+ void OnTestStart(const testing::TestInfo& test_info) override;
+ void OnTestEnd(const testing::TestInfo& test_info) override;
+ void OnTestCaseEnd(const testing::TestCase& test_case) override;
+
+ void WriteTestPartResult(const char* file,
+ int line,
+ testing::TestPartResult::Type type,
+ const std::string& summary,
+ const std::string& message);
+
+ FILE* output_file_;
+ bool open_failed_;
+
+ DISALLOW_COPY_AND_ASSIGN(XmlUnitTestResultPrinter);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_GTEST_XML_UNITTEST_RESULT_PRINTER_H_
diff --git a/base/test/gtest_xml_util.cc b/base/test/gtest_xml_util.cc
new file mode 100644
index 0000000000..37104e88ae
--- /dev/null
+++ b/base/test/gtest_xml_util.cc
@@ -0,0 +1,234 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/gtest_xml_util.h"
+
+#include <stdint.h>
+
+#include "base/base64.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/gtest_util.h"
+#include "base/test/launcher/test_launcher.h"
+#include "third_party/libxml/chromium/libxml_utils.h"
+
+namespace base {
+
+namespace {
+
+// This is used for the xml parser to report errors. This assumes the context
+// is a pointer to a std::string where the error message should be appended.
+static void XmlErrorFunc(void *context, const char *message, ...) {
+ va_list args;
+ va_start(args, message);
+ std::string* error = static_cast<std::string*>(context);
+ StringAppendV(error, message, args);
+ va_end(args);
+}
+
+} // namespace
+
+bool ProcessGTestOutput(const base::FilePath& output_file,
+ std::vector<TestResult>* results,
+ bool* crashed) {
+ DCHECK(results);
+
+ std::string xml_contents;
+ if (!ReadFileToString(output_file, &xml_contents))
+ return false;
+
+ // Silence XML errors - otherwise they go to stderr.
+ std::string xml_errors;
+ ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
+
+ XmlReader xml_reader;
+ if (!xml_reader.Load(xml_contents))
+ return false;
+
+ enum {
+ STATE_INIT,
+ STATE_TESTSUITE,
+ STATE_TESTCASE,
+ STATE_TEST_RESULT,
+ STATE_FAILURE,
+ STATE_END,
+ } state = STATE_INIT;
+
+ while (xml_reader.Read()) {
+ xml_reader.SkipToElement();
+ std::string node_name(xml_reader.NodeName());
+
+ switch (state) {
+ case STATE_INIT:
+ if (node_name == "testsuites" && !xml_reader.IsClosingElement())
+ state = STATE_TESTSUITE;
+ else
+ return false;
+ break;
+ case STATE_TESTSUITE:
+ if (node_name == "testsuites" && xml_reader.IsClosingElement())
+ state = STATE_END;
+ else if (node_name == "testsuite" && !xml_reader.IsClosingElement())
+ state = STATE_TESTCASE;
+ else
+ return false;
+ break;
+ case STATE_TESTCASE:
+ if (node_name == "testsuite" && xml_reader.IsClosingElement()) {
+ state = STATE_TESTSUITE;
+ } else if (node_name == "x-teststart" &&
+ !xml_reader.IsClosingElement()) {
+ // This is our custom extension that helps recognize which test was
+ // running when the test binary crashed.
+ TestResult result;
+
+ std::string test_case_name;
+ if (!xml_reader.NodeAttribute("classname", &test_case_name))
+ return false;
+ std::string test_name;
+ if (!xml_reader.NodeAttribute("name", &test_name))
+ return false;
+ result.full_name = FormatFullTestName(test_case_name, test_name);
+
+ result.elapsed_time = TimeDelta();
+
+ // Assume the test crashed - we can correct that later.
+ result.status = TestResult::TEST_CRASH;
+
+ results->push_back(result);
+ } else if (node_name == "testcase" && !xml_reader.IsClosingElement()) {
+ std::string test_status;
+ if (!xml_reader.NodeAttribute("status", &test_status))
+ return false;
+
+ if (test_status != "run" && test_status != "notrun")
+ return false;
+ if (test_status != "run")
+ break;
+
+ TestResult result;
+
+ std::string test_case_name;
+ if (!xml_reader.NodeAttribute("classname", &test_case_name))
+ return false;
+ std::string test_name;
+ if (!xml_reader.NodeAttribute("name", &test_name))
+ return false;
+ result.full_name = test_case_name + "." + test_name;
+
+ std::string test_time_str;
+ if (!xml_reader.NodeAttribute("time", &test_time_str))
+ return false;
+ result.elapsed_time = TimeDelta::FromMicroseconds(
+ static_cast<int64_t>(strtod(test_time_str.c_str(), nullptr) *
+ Time::kMicrosecondsPerSecond));
+
+ result.status = TestResult::TEST_SUCCESS;
+
+ if (!results->empty() &&
+ results->back().full_name == result.full_name &&
+ results->back().status == TestResult::TEST_CRASH) {
+ // Erase the fail-safe "crashed" result - now we know the test did
+ // not crash.
+ results->pop_back();
+ }
+
+ results->push_back(result);
+ } else if (node_name == "failure" && !xml_reader.IsClosingElement()) {
+ std::string failure_message;
+ if (!xml_reader.NodeAttribute("message", &failure_message))
+ return false;
+
+ DCHECK(!results->empty());
+ results->back().status = TestResult::TEST_FAILURE;
+
+ state = STATE_FAILURE;
+ } else if (node_name == "testcase" && xml_reader.IsClosingElement()) {
+ // Deliberately empty.
+ } else if (node_name == "x-test-result-part" &&
+ !xml_reader.IsClosingElement()) {
+ std::string result_type;
+ if (!xml_reader.NodeAttribute("type", &result_type))
+ return false;
+
+ std::string file_name;
+ if (!xml_reader.NodeAttribute("file", &file_name))
+ return false;
+
+ std::string line_number_str;
+ if (!xml_reader.NodeAttribute("line", &line_number_str))
+ return false;
+
+ int line_number;
+ if (!StringToInt(line_number_str, &line_number))
+ return false;
+
+ TestResultPart::Type type;
+ if (!TestResultPart::TypeFromString(result_type, &type))
+ return false;
+
+ TestResultPart test_result_part;
+ test_result_part.type = type;
+ test_result_part.file_name = file_name,
+ test_result_part.line_number = line_number;
+ DCHECK(!results->empty());
+ results->back().test_result_parts.push_back(test_result_part);
+
+ state = STATE_TEST_RESULT;
+ } else {
+ return false;
+ }
+ break;
+ case STATE_TEST_RESULT:
+ if (node_name == "summary" && !xml_reader.IsClosingElement()) {
+ std::string summary;
+ if (!xml_reader.ReadElementContent(&summary))
+ return false;
+
+ if (!Base64Decode(summary, &summary))
+ return false;
+
+ DCHECK(!results->empty());
+ DCHECK(!results->back().test_result_parts.empty());
+ results->back().test_result_parts.back().summary = summary;
+ } else if (node_name == "summary" && xml_reader.IsClosingElement()) {
+ } else if (node_name == "message" && !xml_reader.IsClosingElement()) {
+ std::string message;
+ if (!xml_reader.ReadElementContent(&message))
+ return false;
+
+ if (!Base64Decode(message, &message))
+ return false;
+
+ DCHECK(!results->empty());
+ DCHECK(!results->back().test_result_parts.empty());
+ results->back().test_result_parts.back().message = message;
+ } else if (node_name == "message" && xml_reader.IsClosingElement()) {
+ } else if (node_name == "x-test-result-part" &&
+ xml_reader.IsClosingElement()) {
+ state = STATE_TESTCASE;
+ } else {
+ return false;
+ }
+ break;
+ case STATE_FAILURE:
+ if (node_name == "failure" && xml_reader.IsClosingElement())
+ state = STATE_TESTCASE;
+ else
+ return false;
+ break;
+ case STATE_END:
+ // If we are here and there are still XML elements, the file has wrong
+ // format.
+ return false;
+ }
+ }
+
+ *crashed = (state != STATE_END);
+ return true;
+}
+
+} // namespace base
diff --git a/base/test/gtest_xml_util.h b/base/test/gtest_xml_util.h
new file mode 100644
index 0000000000..b023f80da1
--- /dev/null
+++ b/base/test/gtest_xml_util.h
@@ -0,0 +1,27 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_GTEST_XML_UTIL_H_
+#define BASE_TEST_GTEST_XML_UTIL_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+
+namespace base {
+
+class FilePath;
+struct TestResult;
+
+// Produces a vector of test results based on GTest output file.
+// Returns true iff the output file exists and has been successfully parsed.
+// On successful return |crashed| is set to true if the test results
+// are valid but incomplete.
+bool ProcessGTestOutput(const base::FilePath& output_file,
+ std::vector<TestResult>* results,
+ bool* crashed) WARN_UNUSED_RESULT;
+
+} // namespace base
+
+#endif // BASE_TEST_GTEST_XML_UTIL_H_
diff --git a/base/test/icu_test_util.cc b/base/test/icu_test_util.cc
new file mode 100644
index 0000000000..a6f3e55665
--- /dev/null
+++ b/base/test/icu_test_util.cc
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/icu_test_util.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/i18n/icu_util.h"
+#include "base/i18n/rtl.h"
+#include "third_party/icu/source/common/unicode/uloc.h"
+
+namespace base {
+namespace test {
+
+ScopedRestoreICUDefaultLocale::ScopedRestoreICUDefaultLocale()
+ : ScopedRestoreICUDefaultLocale(std::string()) {}
+
+ScopedRestoreICUDefaultLocale::ScopedRestoreICUDefaultLocale(
+ const std::string& locale)
+ : default_locale_(uloc_getDefault()) {
+ if (!locale.empty())
+ i18n::SetICUDefaultLocale(locale.data());
+}
+
+ScopedRestoreICUDefaultLocale::~ScopedRestoreICUDefaultLocale() {
+ i18n::SetICUDefaultLocale(default_locale_.data());
+}
+
+void InitializeICUForTesting() {
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestDoNotInitializeIcu)) {
+ i18n::AllowMultipleInitializeCallsForTesting();
+ i18n::InitializeICU();
+ }
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/icu_test_util.h b/base/test/icu_test_util.h
new file mode 100644
index 0000000000..1a6e47d3ab
--- /dev/null
+++ b/base/test/icu_test_util.h
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_ICU_TEST_UTIL_H_
+#define BASE_TEST_ICU_TEST_UTIL_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace base {
+namespace test {
+
+// In unit tests, prefer ScopedRestoreICUDefaultLocale over
+// calling base::i18n::SetICUDefaultLocale() directly. This scoper makes it
+// harder to accidentally forget to reset the locale.
+class ScopedRestoreICUDefaultLocale {
+ public:
+ ScopedRestoreICUDefaultLocale();
+ explicit ScopedRestoreICUDefaultLocale(const std::string& locale);
+ ~ScopedRestoreICUDefaultLocale();
+
+ private:
+ const std::string default_locale_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedRestoreICUDefaultLocale);
+};
+
+void InitializeICUForTesting();
+
+} // namespace test
+} // namespace base
+
+#endif // BASE_TEST_ICU_TEST_UTIL_H_
diff --git a/base/test/launcher/test_launcher.cc b/base/test/launcher/test_launcher.cc
new file mode 100644
index 0000000000..ddd9a4dc18
--- /dev/null
+++ b/base/test/launcher/test_launcher.cc
@@ -0,0 +1,1345 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/launcher/test_launcher.h"
+
+#include <stdio.h>
+
+#include <algorithm>
+#include <map>
+#include <random>
+#include <utility>
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/format_macros.h"
+#include "base/hash.h"
+#include "base/lazy_instance.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringize_macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "base/test/gtest_util.h"
+#include "base/test/launcher/test_launcher_tracer.h"
+#include "base/test/launcher/test_results_tracker.h"
+#include "base/test/test_switches.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX)
+#include <fcntl.h>
+
+#include "base/files/file_descriptor_watcher_posix.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/scoped_nsautorelease_pool.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include <lib/zx/job.h>
+#include "base/fuchsia/default_job.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#endif
+
+namespace base {
+
+// See https://groups.google.com/a/chromium.org/d/msg/chromium-dev/nkdTP7sstSc/uT3FaE_sgkAJ .
+using ::operator<<;
+
+// The environment variable name for the total number of test shards.
+const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS";
+// The environment variable name for the test shard index.
+const char kTestShardIndex[] = "GTEST_SHARD_INDEX";
+
+namespace {
+
+// Global tag for test runs where the results are incomplete or unreliable
+// for any reason, e.g. early exit because of too many broken tests.
+const char kUnreliableResultsTag[] = "UNRELIABLE_RESULTS";
+
+// Maximum time of no output after which we print list of processes still
+// running. This deliberately doesn't use TestTimeouts (which is otherwise
+// a recommended solution), because they can be increased. This would defeat
+// the purpose of this timeout, which is 1) to avoid buildbot "no output for
+// X seconds" timeout killing the process 2) help communicate status of
+// the test launcher to people looking at the output (no output for a long
+// time is mysterious and gives no info about what is happening) 3) help
+// debugging in case the process hangs anyway.
+constexpr TimeDelta kOutputTimeout = TimeDelta::FromSeconds(15);
+
+// Limit of output snippet lines when printing to stdout.
+// Avoids flooding the logs with amount of output that gums up
+// the infrastructure.
+const size_t kOutputSnippetLinesLimit = 5000;
+
+// Limit of output snippet size. Exceeding this limit
+// results in truncating the output and failing the test.
+const size_t kOutputSnippetBytesLimit = 300 * 1024;
+
+// Limit of seed values for gtest shuffling. Arbitrary, but based on
+// gtest's similarly arbitrary choice.
+const uint32_t kRandomSeedUpperBound = 100000;
+
+// Set of live launch test processes with corresponding lock (it is allowed
+// for callers to launch processes on different threads).
+Lock* GetLiveProcessesLock() {
+ static auto* lock = new Lock;
+ return lock;
+}
+
+std::map<ProcessHandle, CommandLine>* GetLiveProcesses() {
+ static auto* map = new std::map<ProcessHandle, CommandLine>;
+ return map;
+}
+
+// Performance trace generator.
+TestLauncherTracer* GetTestLauncherTracer() {
+ static auto* tracer = new TestLauncherTracer;
+ return tracer;
+}
+
+// Creates and starts a TaskScheduler with |num_parallel_jobs| dedicated to
+// foreground blocking tasks (corresponds to the traits used to launch and wait
+// for child processes).
+void CreateAndStartTaskScheduler(int num_parallel_jobs) {
+ // These values are taken from TaskScheduler::StartWithDefaultParams(), which
+ // is not used directly to allow a custom number of threads in the foreground
+ // blocking pool.
+ constexpr int kMaxBackgroundThreads = 1;
+ constexpr int kMaxBackgroundBlockingThreads = 2;
+ const int max_foreground_threads =
+ std::max(1, base::SysInfo::NumberOfProcessors());
+ constexpr base::TimeDelta kSuggestedReclaimTime =
+ base::TimeDelta::FromSeconds(30);
+ base::TaskScheduler::Create("TestLauncher");
+ base::TaskScheduler::GetInstance()->Start(
+ {{kMaxBackgroundThreads, kSuggestedReclaimTime},
+ {kMaxBackgroundBlockingThreads, kSuggestedReclaimTime},
+ {max_foreground_threads, kSuggestedReclaimTime},
+ {num_parallel_jobs, kSuggestedReclaimTime}});
+}
+
+#if defined(OS_POSIX)
+// Self-pipe that makes it possible to do complex shutdown handling
+// outside of the signal handler.
+int g_shutdown_pipe[2] = { -1, -1 };
+
+void ShutdownPipeSignalHandler(int signal) {
+ HANDLE_EINTR(write(g_shutdown_pipe[1], "q", 1));
+}
+
+void KillSpawnedTestProcesses() {
+ // Keep the lock until exiting the process to prevent further processes
+ // from being spawned.
+ AutoLock lock(*GetLiveProcessesLock());
+
+ fprintf(stdout, "Sending SIGTERM to %" PRIuS " child processes... ",
+ GetLiveProcesses()->size());
+ fflush(stdout);
+
+ for (const auto& pair : *GetLiveProcesses()) {
+ // Send the signal to entire process group.
+ kill((-1) * (pair.first), SIGTERM);
+ }
+
+ fprintf(stdout,
+ "done.\nGiving processes a chance to terminate cleanly... ");
+ fflush(stdout);
+
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(500));
+
+ fprintf(stdout, "done.\n");
+ fflush(stdout);
+
+ fprintf(stdout, "Sending SIGKILL to %" PRIuS " child processes... ",
+ GetLiveProcesses()->size());
+ fflush(stdout);
+
+ for (const auto& pair : *GetLiveProcesses()) {
+ // Send the signal to entire process group.
+ kill((-1) * (pair.first), SIGKILL);
+ }
+
+ fprintf(stdout, "done.\n");
+ fflush(stdout);
+}
+#endif // defined(OS_POSIX)
+
+// Parses the environment variable var as an Int32. If it is unset, returns
+// true. If it is set, unsets it then converts it to Int32 before
+// returning it in |result|. Returns true on success.
+bool TakeInt32FromEnvironment(const char* const var, int32_t* result) {
+ std::unique_ptr<Environment> env(Environment::Create());
+ std::string str_val;
+
+ if (!env->GetVar(var, &str_val))
+ return true;
+
+ if (!env->UnSetVar(var)) {
+ LOG(ERROR) << "Invalid environment: we could not unset " << var << ".\n";
+ return false;
+ }
+
+ if (!StringToInt(str_val, result)) {
+ LOG(ERROR) << "Invalid environment: " << var << " is not an integer.\n";
+ return false;
+ }
+
+ return true;
+}
+
+// Unsets the environment variable |name| and returns true on success.
+// Also returns true if the variable just doesn't exist.
+bool UnsetEnvironmentVariableIfExists(const std::string& name) {
+ std::unique_ptr<Environment> env(Environment::Create());
+ std::string str_val;
+ if (!env->GetVar(name, &str_val))
+ return true;
+ return env->UnSetVar(name);
+}
+
+// Returns true if bot mode has been requested, i.e. defaults optimized
+// for continuous integration bots. This way developers don't have to remember
+// special command-line flags.
+bool BotModeEnabled() {
+ std::unique_ptr<Environment> env(Environment::Create());
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherBotMode) ||
+ env->HasVar("CHROMIUM_TEST_LAUNCHER_BOT_MODE");
+}
+
+// Returns command line command line after gtest-specific processing
+// and applying |wrapper|.
+CommandLine PrepareCommandLineForGTest(const CommandLine& command_line,
+ const std::string& wrapper) {
+ CommandLine new_command_line(command_line.GetProgram());
+ CommandLine::SwitchMap switches = command_line.GetSwitches();
+
+ // Handled by the launcher process.
+ switches.erase(kGTestRepeatFlag);
+ switches.erase(kGTestShuffleFlag);
+ switches.erase(kGTestRandomSeedFlag);
+
+ // Don't try to write the final XML report in child processes.
+ switches.erase(kGTestOutputFlag);
+
+ for (CommandLine::SwitchMap::const_iterator iter = switches.begin();
+ iter != switches.end(); ++iter) {
+ new_command_line.AppendSwitchNative((*iter).first, (*iter).second);
+ }
+
+ // Prepend wrapper after last CommandLine quasi-copy operation. CommandLine
+ // does not really support removing switches well, and trying to do that
+ // on a CommandLine with a wrapper is known to break.
+ // TODO(phajdan.jr): Give it a try to support CommandLine removing switches.
+#if defined(OS_WIN)
+ new_command_line.PrependWrapper(ASCIIToUTF16(wrapper));
+#else
+ new_command_line.PrependWrapper(wrapper);
+#endif
+
+ return new_command_line;
+}
+
+// Launches a child process using |command_line|. If the child process is still
+// running after |timeout|, it is terminated and |*was_timeout| is set to true.
+// Returns exit code of the process.
+int LaunchChildTestProcessWithOptions(const CommandLine& command_line,
+ const LaunchOptions& options,
+ int flags,
+ TimeDelta timeout,
+ ProcessLifetimeObserver* observer,
+ bool* was_timeout) {
+ TimeTicks start_time(TimeTicks::Now());
+
+#if defined(OS_POSIX)
+ // Make sure an option we rely on is present - see LaunchChildGTestProcess.
+ DCHECK(options.new_process_group);
+#endif
+
+ LaunchOptions new_options(options);
+
+#if defined(OS_WIN)
+ DCHECK(!new_options.job_handle);
+
+ win::ScopedHandle job_handle;
+ if (flags & TestLauncher::USE_JOB_OBJECTS) {
+ job_handle.Set(CreateJobObject(NULL, NULL));
+ if (!job_handle.IsValid()) {
+ LOG(ERROR) << "Could not create JobObject.";
+ return -1;
+ }
+
+ DWORD job_flags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ // Allow break-away from job since sandbox and few other places rely on it
+ // on Windows versions prior to Windows 8 (which supports nested jobs).
+ if (win::GetVersion() < win::VERSION_WIN8 &&
+ flags & TestLauncher::ALLOW_BREAKAWAY_FROM_JOB) {
+ job_flags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+ }
+
+ if (!SetJobObjectLimitFlags(job_handle.Get(), job_flags)) {
+ LOG(ERROR) << "Could not SetJobObjectLimitFlags.";
+ return -1;
+ }
+
+ new_options.job_handle = job_handle.Get();
+ }
+#elif defined(OS_FUCHSIA)
+ DCHECK(!new_options.job_handle);
+
+ zx::job job_handle;
+ zx_status_t result = zx::job::create(*GetDefaultJob(), 0, &job_handle);
+ ZX_CHECK(ZX_OK == result, result) << "zx_job_create";
+ new_options.job_handle = job_handle.get();
+#endif // defined(OS_FUCHSIA)
+
+#if defined(OS_LINUX)
+ // To prevent accidental privilege sharing to an untrusted child, processes
+ // are started with PR_SET_NO_NEW_PRIVS. Do not set that here, since this
+ // new child will be privileged and trusted.
+ new_options.allow_new_privs = true;
+#endif
+
+ Process process;
+
+ {
+ // Note how we grab the lock before the process possibly gets created.
+ // This ensures that when the lock is held, ALL the processes are registered
+ // in the set.
+ AutoLock lock(*GetLiveProcessesLock());
+
+#if defined(OS_WIN)
+ // Allow the handle used to capture stdio and stdout to be inherited by the
+ // child. Note that this is done under GetLiveProcessesLock() to ensure that
+ // only the desired child receives the handle.
+ if (new_options.stdout_handle) {
+ ::SetHandleInformation(new_options.stdout_handle, HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT);
+ }
+#endif
+
+ process = LaunchProcess(command_line, new_options);
+
+#if defined(OS_WIN)
+ // Revoke inheritance so that the handle isn't leaked into other children.
+ // Note that this is done under GetLiveProcessesLock() to ensure that only
+ // the desired child receives the handle.
+ if (new_options.stdout_handle)
+ ::SetHandleInformation(new_options.stdout_handle, HANDLE_FLAG_INHERIT, 0);
+#endif
+
+ if (!process.IsValid())
+ return -1;
+
+ // TODO(rvargas) crbug.com/417532: Don't store process handles.
+ GetLiveProcesses()->insert(std::make_pair(process.Handle(), command_line));
+ }
+
+ if (observer)
+ observer->OnLaunched(process.Handle(), process.Pid());
+
+ int exit_code = 0;
+ bool did_exit = false;
+
+ {
+ base::ScopedAllowBaseSyncPrimitivesForTesting allow_base_sync_primitives;
+ did_exit = process.WaitForExitWithTimeout(timeout, &exit_code);
+ }
+
+ if (!did_exit) {
+ if (observer)
+ observer->OnTimedOut(command_line);
+
+ *was_timeout = true;
+ exit_code = -1; // Set a non-zero exit code to signal a failure.
+
+ {
+ base::ScopedAllowBaseSyncPrimitivesForTesting allow_base_sync_primitives;
+ // Ensure that the process terminates.
+ process.Terminate(-1, true);
+ }
+ }
+
+ {
+ // Note how we grab the log before issuing a possibly broad process kill.
+ // Other code parts that grab the log kill processes, so avoid trying
+ // to do that twice and trigger all kinds of log messages.
+ AutoLock lock(*GetLiveProcessesLock());
+
+#if defined(OS_FUCHSIA)
+ zx_status_t status = job_handle.kill();
+ ZX_CHECK(status == ZX_OK, status);
+#elif defined(OS_POSIX)
+ if (exit_code != 0) {
+ // On POSIX, in case the test does not exit cleanly, either due to a crash
+ // or due to it timing out, we need to clean up any child processes that
+ // it might have created. On Windows, child processes are automatically
+ // cleaned up using JobObjects.
+ KillProcessGroup(process.Handle());
+ }
+#endif
+
+ GetLiveProcesses()->erase(process.Handle());
+ }
+
+ GetTestLauncherTracer()->RecordProcessExecution(
+ start_time, TimeTicks::Now() - start_time);
+
+ return exit_code;
+}
+
+void DoLaunchChildTestProcess(
+ const CommandLine& command_line,
+ TimeDelta timeout,
+ const TestLauncher::LaunchOptions& test_launch_options,
+ bool redirect_stdio,
+ SingleThreadTaskRunner* task_runner,
+ std::unique_ptr<ProcessLifetimeObserver> observer) {
+ TimeTicks start_time = TimeTicks::Now();
+
+ ScopedFILE output_file;
+ FilePath output_filename;
+ if (redirect_stdio) {
+ FILE* raw_output_file = CreateAndOpenTemporaryFile(&output_filename);
+ output_file.reset(raw_output_file);
+ CHECK(output_file);
+ }
+
+ LaunchOptions options;
+#if defined(OS_WIN)
+
+ options.inherit_mode = test_launch_options.inherit_mode;
+ options.handles_to_inherit = test_launch_options.handles_to_inherit;
+ if (redirect_stdio) {
+ HANDLE handle =
+ reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(output_file.get())));
+ CHECK_NE(INVALID_HANDLE_VALUE, handle);
+ options.stdin_handle = INVALID_HANDLE_VALUE;
+ options.stdout_handle = handle;
+ options.stderr_handle = handle;
+ // See LaunchOptions.stdout_handle comments for why this compares against
+ // FILE_TYPE_CHAR.
+ if (options.inherit_mode == base::LaunchOptions::Inherit::kSpecific &&
+ GetFileType(handle) != FILE_TYPE_CHAR) {
+ options.handles_to_inherit.push_back(handle);
+ }
+ }
+
+#else // if !defined(OS_WIN)
+
+ options.fds_to_remap = test_launch_options.fds_to_remap;
+ if (redirect_stdio) {
+ int output_file_fd = fileno(output_file.get());
+ CHECK_LE(0, output_file_fd);
+ options.fds_to_remap.push_back(
+ std::make_pair(output_file_fd, STDOUT_FILENO));
+ options.fds_to_remap.push_back(
+ std::make_pair(output_file_fd, STDERR_FILENO));
+ }
+
+#if !defined(OS_FUCHSIA)
+ options.new_process_group = true;
+#endif
+#if defined(OS_LINUX)
+ options.kill_on_parent_death = true;
+#endif
+
+#endif // !defined(OS_WIN)
+
+ bool was_timeout = false;
+ int exit_code = LaunchChildTestProcessWithOptions(
+ command_line, options, test_launch_options.flags, timeout, observer.get(),
+ &was_timeout);
+
+ std::string output_file_contents;
+ if (redirect_stdio) {
+ fflush(output_file.get());
+ output_file.reset();
+ // Reading the file can sometimes fail when the process was killed midflight
+ // (e.g. on test suite timeout): https://crbug.com/826408. Attempt to read
+ // the output file anyways, but do not crash on failure in this case.
+ CHECK(ReadFileToString(output_filename, &output_file_contents) ||
+ exit_code != 0);
+
+ if (!DeleteFile(output_filename, false)) {
+ // This needs to be non-fatal at least for Windows.
+ LOG(WARNING) << "Failed to delete " << output_filename.AsUTF8Unsafe();
+ }
+ }
+
+ // Invoke OnCompleted on the thread it was originating from, not on a worker
+ // pool thread.
+ task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(&ProcessLifetimeObserver::OnCompleted, std::move(observer),
+ exit_code, TimeTicks::Now() - start_time, was_timeout,
+ output_file_contents));
+}
+
+} // namespace
+
+const char kGTestBreakOnFailure[] = "gtest_break_on_failure";
+const char kGTestFilterFlag[] = "gtest_filter";
+const char kGTestFlagfileFlag[] = "gtest_flagfile";
+const char kGTestHelpFlag[] = "gtest_help";
+const char kGTestListTestsFlag[] = "gtest_list_tests";
+const char kGTestRepeatFlag[] = "gtest_repeat";
+const char kGTestRunDisabledTestsFlag[] = "gtest_also_run_disabled_tests";
+const char kGTestOutputFlag[] = "gtest_output";
+const char kGTestShuffleFlag[] = "gtest_shuffle";
+const char kGTestRandomSeedFlag[] = "gtest_random_seed";
+
+TestLauncherDelegate::~TestLauncherDelegate() = default;
+
+TestLauncher::LaunchOptions::LaunchOptions() = default;
+TestLauncher::LaunchOptions::LaunchOptions(const LaunchOptions& other) =
+ default;
+TestLauncher::LaunchOptions::~LaunchOptions() = default;
+
+TestLauncher::TestLauncher(TestLauncherDelegate* launcher_delegate,
+ size_t parallel_jobs)
+ : launcher_delegate_(launcher_delegate),
+ total_shards_(1),
+ shard_index_(0),
+ cycles_(1),
+ test_found_count_(0),
+ test_started_count_(0),
+ test_finished_count_(0),
+ test_success_count_(0),
+ test_broken_count_(0),
+ retry_count_(0),
+ retry_limit_(0),
+ force_run_broken_tests_(false),
+ run_result_(true),
+ shuffle_(false),
+ shuffle_seed_(0),
+ watchdog_timer_(FROM_HERE,
+ kOutputTimeout,
+ this,
+ &TestLauncher::OnOutputTimeout),
+ parallel_jobs_(parallel_jobs) {}
+
+TestLauncher::~TestLauncher() {
+ if (base::TaskScheduler::GetInstance()) {
+ base::TaskScheduler::GetInstance()->Shutdown();
+ }
+}
+
+bool TestLauncher::Run() {
+ if (!Init())
+ return false;
+
+ // Value of |cycles_| changes after each iteration. Keep track of the
+ // original value.
+ int requested_cycles = cycles_;
+
+#if defined(OS_POSIX)
+ CHECK_EQ(0, pipe(g_shutdown_pipe));
+
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ sigemptyset(&action.sa_mask);
+ action.sa_handler = &ShutdownPipeSignalHandler;
+
+ CHECK_EQ(0, sigaction(SIGINT, &action, nullptr));
+ CHECK_EQ(0, sigaction(SIGQUIT, &action, nullptr));
+ CHECK_EQ(0, sigaction(SIGTERM, &action, nullptr));
+
+ auto controller = base::FileDescriptorWatcher::WatchReadable(
+ g_shutdown_pipe[0],
+ base::Bind(&TestLauncher::OnShutdownPipeReadable, Unretained(this)));
+#endif // defined(OS_POSIX)
+
+ // Start the watchdog timer.
+ watchdog_timer_.Reset();
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&TestLauncher::RunTestIteration, Unretained(this)));
+
+ RunLoop().Run();
+
+ if (requested_cycles != 1)
+ results_tracker_.PrintSummaryOfAllIterations();
+
+ MaybeSaveSummaryAsJSON(std::vector<std::string>());
+
+ return run_result_;
+}
+
+void TestLauncher::LaunchChildGTestProcess(
+ const CommandLine& command_line,
+ const std::string& wrapper,
+ TimeDelta timeout,
+ const LaunchOptions& options,
+ std::unique_ptr<ProcessLifetimeObserver> observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Record the exact command line used to launch the child.
+ CommandLine new_command_line(
+ PrepareCommandLineForGTest(command_line, wrapper));
+
+ // When running in parallel mode we need to redirect stdio to avoid mixed-up
+ // output. We also always redirect on the bots to get the test output into
+ // JSON summary.
+ bool redirect_stdio = (parallel_jobs_ > 1) || BotModeEnabled();
+
+ PostTaskWithTraits(
+ FROM_HERE, {MayBlock(), TaskShutdownBehavior::BLOCK_SHUTDOWN},
+ BindOnce(&DoLaunchChildTestProcess, new_command_line, timeout, options,
+ redirect_stdio, RetainedRef(ThreadTaskRunnerHandle::Get()),
+ std::move(observer)));
+}
+
+void TestLauncher::OnTestFinished(const TestResult& original_result) {
+ ++test_finished_count_;
+
+ TestResult result(original_result);
+
+ if (result.output_snippet.length() > kOutputSnippetBytesLimit) {
+ if (result.status == TestResult::TEST_SUCCESS)
+ result.status = TestResult::TEST_EXCESSIVE_OUTPUT;
+
+ // Keep the top and bottom of the log and truncate the middle part.
+ result.output_snippet =
+ result.output_snippet.substr(0, kOutputSnippetBytesLimit / 2) + "\n" +
+ StringPrintf("<truncated (%" PRIuS " bytes)>\n",
+ result.output_snippet.length()) +
+ result.output_snippet.substr(result.output_snippet.length() -
+ kOutputSnippetBytesLimit / 2) +
+ "\n";
+ }
+
+ bool print_snippet = false;
+ std::string print_test_stdio("auto");
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherPrintTestStdio)) {
+ print_test_stdio = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kTestLauncherPrintTestStdio);
+ }
+ if (print_test_stdio == "auto") {
+ print_snippet = (result.status != TestResult::TEST_SUCCESS);
+ } else if (print_test_stdio == "always") {
+ print_snippet = true;
+ } else if (print_test_stdio == "never") {
+ print_snippet = false;
+ } else {
+ LOG(WARNING) << "Invalid value of " << switches::kTestLauncherPrintTestStdio
+ << ": " << print_test_stdio;
+ }
+ if (print_snippet) {
+ std::vector<base::StringPiece> snippet_lines =
+ SplitStringPiece(result.output_snippet, "\n", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ if (snippet_lines.size() > kOutputSnippetLinesLimit) {
+ size_t truncated_size = snippet_lines.size() - kOutputSnippetLinesLimit;
+ snippet_lines.erase(
+ snippet_lines.begin(),
+ snippet_lines.begin() + truncated_size);
+ snippet_lines.insert(snippet_lines.begin(), "<truncated>");
+ }
+ fprintf(stdout, "%s", base::JoinString(snippet_lines, "\n").c_str());
+ fflush(stdout);
+ }
+
+ if (result.status == TestResult::TEST_SUCCESS) {
+ ++test_success_count_;
+ } else {
+ tests_to_retry_.insert(result.full_name);
+ }
+
+ results_tracker_.AddTestResult(result);
+
+ // TODO(phajdan.jr): Align counter (padding).
+ std::string status_line(
+ StringPrintf("[%" PRIuS "/%" PRIuS "] %s ",
+ test_finished_count_,
+ test_started_count_,
+ result.full_name.c_str()));
+ if (result.completed()) {
+ status_line.append(StringPrintf("(%" PRId64 " ms)",
+ result.elapsed_time.InMilliseconds()));
+ } else if (result.status == TestResult::TEST_TIMEOUT) {
+ status_line.append("(TIMED OUT)");
+ } else if (result.status == TestResult::TEST_CRASH) {
+ status_line.append("(CRASHED)");
+ } else if (result.status == TestResult::TEST_SKIPPED) {
+ status_line.append("(SKIPPED)");
+ } else if (result.status == TestResult::TEST_UNKNOWN) {
+ status_line.append("(UNKNOWN)");
+ } else {
+ // Fail very loudly so it's not ignored.
+ CHECK(false) << "Unhandled test result status: " << result.status;
+ }
+ fprintf(stdout, "%s\n", status_line.c_str());
+ fflush(stdout);
+
+ // We just printed a status line, reset the watchdog timer.
+ watchdog_timer_.Reset();
+
+ // Do not waste time on timeouts. We include tests with unknown results here
+ // because sometimes (e.g. hang in between unit tests) that's how a timeout
+ // gets reported.
+ if (result.status == TestResult::TEST_TIMEOUT ||
+ result.status == TestResult::TEST_UNKNOWN) {
+ test_broken_count_++;
+ }
+ size_t broken_threshold =
+ std::max(static_cast<size_t>(20), test_found_count_ / 10);
+ if (!force_run_broken_tests_ && test_broken_count_ >= broken_threshold) {
+ fprintf(stdout, "Too many badly broken tests (%" PRIuS "), exiting now.\n",
+ test_broken_count_);
+ fflush(stdout);
+
+#if defined(OS_POSIX)
+ KillSpawnedTestProcesses();
+#endif // defined(OS_POSIX)
+
+ MaybeSaveSummaryAsJSON({"BROKEN_TEST_EARLY_EXIT", kUnreliableResultsTag});
+
+ exit(1);
+ }
+
+ if (test_finished_count_ != test_started_count_)
+ return;
+
+ if (tests_to_retry_.empty() || retry_count_ >= retry_limit_) {
+ OnTestIterationFinished();
+ return;
+ }
+
+ if (!force_run_broken_tests_ && tests_to_retry_.size() >= broken_threshold) {
+ fprintf(stdout,
+ "Too many failing tests (%" PRIuS "), skipping retries.\n",
+ tests_to_retry_.size());
+ fflush(stdout);
+
+ results_tracker_.AddGlobalTag("BROKEN_TEST_SKIPPED_RETRIES");
+ results_tracker_.AddGlobalTag(kUnreliableResultsTag);
+
+ OnTestIterationFinished();
+ return;
+ }
+
+ retry_count_++;
+
+ std::vector<std::string> test_names(tests_to_retry_.begin(),
+ tests_to_retry_.end());
+
+ tests_to_retry_.clear();
+
+ size_t retry_started_count = launcher_delegate_->RetryTests(this, test_names);
+ if (retry_started_count == 0) {
+ // Signal failure, but continue to run all requested test iterations.
+ // With the summary of all iterations at the end this is a good default.
+ run_result_ = false;
+
+ OnTestIterationFinished();
+ return;
+ }
+
+ fprintf(stdout, "Retrying %" PRIuS " test%s (retry #%" PRIuS ")\n",
+ retry_started_count,
+ retry_started_count > 1 ? "s" : "",
+ retry_count_);
+ fflush(stdout);
+
+ test_started_count_ += retry_started_count;
+}
+
+// Helper used to parse test filter files. Syntax is documented in
+// //testing/buildbot/filters/README.md .
+bool LoadFilterFile(const FilePath& file_path,
+ std::vector<std::string>* positive_filter,
+ std::vector<std::string>* negative_filter) {
+ std::string file_content;
+ if (!ReadFileToString(file_path, &file_content)) {
+ LOG(ERROR) << "Failed to read the filter file.";
+ return false;
+ }
+
+ std::vector<std::string> filter_lines = SplitString(
+ file_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ int line_num = 0;
+ for (const std::string& filter_line : filter_lines) {
+ line_num++;
+
+ size_t hash_pos = filter_line.find('#');
+
+ // In case when # symbol is not in the beginning of the line and is not
+ // proceeded with a space then it's likely that the comment was
+ // unintentional.
+ if (hash_pos != std::string::npos && hash_pos > 0 &&
+ filter_line[hash_pos - 1] != ' ') {
+ LOG(WARNING) << "Content of line " << line_num << " in " << file_path
+ << " after # is treated as a comment, " << filter_line;
+ }
+
+ // Strip comments and whitespace from each line.
+ std::string trimmed_line =
+ TrimWhitespaceASCII(filter_line.substr(0, hash_pos), TRIM_ALL)
+ .as_string();
+
+ if (trimmed_line.substr(0, 2) == "//") {
+ LOG(ERROR) << "Line " << line_num << " in " << file_path
+ << " starts with //, use # for comments.";
+ return false;
+ }
+
+ // Treat a line starting with '//' as a comment.
+ if (trimmed_line.empty())
+ continue;
+
+ if (trimmed_line[0] == '-')
+ negative_filter->push_back(trimmed_line.substr(1));
+ else
+ positive_filter->push_back(trimmed_line);
+ }
+
+ return true;
+}
+
+bool TestLauncher::Init() {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+
+ // Initialize sharding. Command line takes precedence over legacy environment
+ // variables.
+ if (command_line->HasSwitch(switches::kTestLauncherTotalShards) &&
+ command_line->HasSwitch(switches::kTestLauncherShardIndex)) {
+ if (!StringToInt(
+ command_line->GetSwitchValueASCII(
+ switches::kTestLauncherTotalShards),
+ &total_shards_)) {
+ LOG(ERROR) << "Invalid value for " << switches::kTestLauncherTotalShards;
+ return false;
+ }
+ if (!StringToInt(
+ command_line->GetSwitchValueASCII(
+ switches::kTestLauncherShardIndex),
+ &shard_index_)) {
+ LOG(ERROR) << "Invalid value for " << switches::kTestLauncherShardIndex;
+ return false;
+ }
+ fprintf(stdout,
+ "Using sharding settings from command line. This is shard %d/%d\n",
+ shard_index_, total_shards_);
+ fflush(stdout);
+ } else {
+ if (!TakeInt32FromEnvironment(kTestTotalShards, &total_shards_))
+ return false;
+ if (!TakeInt32FromEnvironment(kTestShardIndex, &shard_index_))
+ return false;
+ fprintf(stdout,
+ "Using sharding settings from environment. This is shard %d/%d\n",
+ shard_index_, total_shards_);
+ fflush(stdout);
+ }
+ if (shard_index_ < 0 ||
+ total_shards_ < 0 ||
+ shard_index_ >= total_shards_) {
+ LOG(ERROR) << "Invalid sharding settings: we require 0 <= "
+ << kTestShardIndex << " < " << kTestTotalShards
+ << ", but you have " << kTestShardIndex << "=" << shard_index_
+ << ", " << kTestTotalShards << "=" << total_shards_ << ".\n";
+ return false;
+ }
+
+ // Make sure we don't pass any sharding-related environment to the child
+ // processes. This test launcher implements the sharding completely.
+ CHECK(UnsetEnvironmentVariableIfExists("GTEST_TOTAL_SHARDS"));
+ CHECK(UnsetEnvironmentVariableIfExists("GTEST_SHARD_INDEX"));
+
+ if (command_line->HasSwitch(kGTestRepeatFlag) &&
+ !StringToInt(command_line->GetSwitchValueASCII(kGTestRepeatFlag),
+ &cycles_)) {
+ LOG(ERROR) << "Invalid value for " << kGTestRepeatFlag;
+ return false;
+ }
+
+ if (command_line->HasSwitch(switches::kTestLauncherRetryLimit)) {
+ int retry_limit = -1;
+ if (!StringToInt(command_line->GetSwitchValueASCII(
+ switches::kTestLauncherRetryLimit), &retry_limit) ||
+ retry_limit < 0) {
+ LOG(ERROR) << "Invalid value for " << switches::kTestLauncherRetryLimit;
+ return false;
+ }
+
+ retry_limit_ = retry_limit;
+ } else if (!command_line->HasSwitch(kGTestFilterFlag) || BotModeEnabled()) {
+ // Retry failures 3 times by default if we are running all of the tests or
+ // in bot mode.
+ retry_limit_ = 3;
+ }
+
+ if (command_line->HasSwitch(switches::kTestLauncherForceRunBrokenTests))
+ force_run_broken_tests_ = true;
+
+ // Some of the TestLauncherDelegate implementations don't call into gtest
+ // until they've already split into test-specific processes. This results
+ // in gtest's native shuffle implementation attempting to shuffle one test.
+ // Shuffling the list of tests in the test launcher (before the delegate
+ // gets involved) ensures that the entire shard is shuffled.
+ if (command_line->HasSwitch(kGTestShuffleFlag)) {
+ shuffle_ = true;
+
+ if (command_line->HasSwitch(kGTestRandomSeedFlag)) {
+ const std::string custom_seed_str =
+ command_line->GetSwitchValueASCII(kGTestRandomSeedFlag);
+ uint32_t custom_seed = 0;
+ if (!StringToUint(custom_seed_str, &custom_seed)) {
+ LOG(ERROR) << "Unable to parse seed \"" << custom_seed_str << "\".";
+ return false;
+ }
+ if (custom_seed >= kRandomSeedUpperBound) {
+ LOG(ERROR) << "Seed " << custom_seed << " outside of expected range "
+ << "[0, " << kRandomSeedUpperBound << ")";
+ return false;
+ }
+ shuffle_seed_ = custom_seed;
+ } else {
+ std::uniform_int_distribution<uint32_t> dist(0, kRandomSeedUpperBound);
+ std::random_device random_dev;
+ shuffle_seed_ = dist(random_dev);
+ }
+ } else if (command_line->HasSwitch(kGTestRandomSeedFlag)) {
+ LOG(ERROR) << kGTestRandomSeedFlag << " requires " << kGTestShuffleFlag;
+ return false;
+ }
+
+ fprintf(stdout, "Using %" PRIuS " parallel jobs.\n", parallel_jobs_);
+ fflush(stdout);
+
+ CreateAndStartTaskScheduler(static_cast<int>(parallel_jobs_));
+
+ std::vector<std::string> positive_file_filter;
+ std::vector<std::string> positive_gtest_filter;
+
+ if (command_line->HasSwitch(switches::kTestLauncherFilterFile)) {
+ base::FilePath filter_file_path = base::MakeAbsoluteFilePath(
+ command_line->GetSwitchValuePath(switches::kTestLauncherFilterFile));
+ if (!LoadFilterFile(filter_file_path, &positive_file_filter,
+ &negative_test_filter_))
+ return false;
+ }
+
+ // Split --gtest_filter at '-', if there is one, to separate into
+ // positive filter and negative filter portions.
+ std::string filter = command_line->GetSwitchValueASCII(kGTestFilterFlag);
+ size_t dash_pos = filter.find('-');
+ if (dash_pos == std::string::npos) {
+ positive_gtest_filter =
+ SplitString(filter, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ } else {
+ // Everything up to the dash.
+ positive_gtest_filter =
+ SplitString(filter.substr(0, dash_pos), ":", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+
+ // Everything after the dash.
+ for (std::string pattern :
+ SplitString(filter.substr(dash_pos + 1), ":", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_ALL)) {
+ negative_test_filter_.push_back(pattern);
+ }
+ }
+
+ if (!launcher_delegate_->GetTests(&tests_)) {
+ LOG(ERROR) << "Failed to get list of tests.";
+ return false;
+ }
+
+ CombinePositiveTestFilters(std::move(positive_gtest_filter),
+ std::move(positive_file_filter));
+
+ if (!results_tracker_.Init(*command_line)) {
+ LOG(ERROR) << "Failed to initialize test results tracker.";
+ return 1;
+ }
+
+#if defined(NDEBUG)
+ results_tracker_.AddGlobalTag("MODE_RELEASE");
+#else
+ results_tracker_.AddGlobalTag("MODE_DEBUG");
+#endif
+
+ // Operating systems (sorted alphabetically).
+ // Note that they can deliberately overlap, e.g. OS_LINUX is a subset
+ // of OS_POSIX.
+#if defined(OS_ANDROID)
+ results_tracker_.AddGlobalTag("OS_ANDROID");
+#endif
+
+#if defined(OS_BSD)
+ results_tracker_.AddGlobalTag("OS_BSD");
+#endif
+
+#if defined(OS_FREEBSD)
+ results_tracker_.AddGlobalTag("OS_FREEBSD");
+#endif
+
+#if defined(OS_FUCHSIA)
+ results_tracker_.AddGlobalTag("OS_FUCHSIA");
+#endif
+
+#if defined(OS_IOS)
+ results_tracker_.AddGlobalTag("OS_IOS");
+#endif
+
+#if defined(OS_LINUX)
+ results_tracker_.AddGlobalTag("OS_LINUX");
+#endif
+
+#if defined(OS_MACOSX)
+ results_tracker_.AddGlobalTag("OS_MACOSX");
+#endif
+
+#if defined(OS_NACL)
+ results_tracker_.AddGlobalTag("OS_NACL");
+#endif
+
+#if defined(OS_OPENBSD)
+ results_tracker_.AddGlobalTag("OS_OPENBSD");
+#endif
+
+#if defined(OS_POSIX)
+ results_tracker_.AddGlobalTag("OS_POSIX");
+#endif
+
+#if defined(OS_SOLARIS)
+ results_tracker_.AddGlobalTag("OS_SOLARIS");
+#endif
+
+#if defined(OS_WIN)
+ results_tracker_.AddGlobalTag("OS_WIN");
+#endif
+
+ // CPU-related tags.
+#if defined(ARCH_CPU_32_BITS)
+ results_tracker_.AddGlobalTag("CPU_32_BITS");
+#endif
+
+#if defined(ARCH_CPU_64_BITS)
+ results_tracker_.AddGlobalTag("CPU_64_BITS");
+#endif
+
+ return true;
+}
+
+void TestLauncher::CombinePositiveTestFilters(
+ std::vector<std::string> filter_a,
+ std::vector<std::string> filter_b) {
+ has_at_least_one_positive_filter_ = !filter_a.empty() || !filter_b.empty();
+ if (!has_at_least_one_positive_filter_) {
+ return;
+ }
+ // If two positive filters are present, only run tests that match a pattern
+ // in both filters.
+ if (!filter_a.empty() && !filter_b.empty()) {
+ for (size_t i = 0; i < tests_.size(); i++) {
+ std::string test_name =
+ FormatFullTestName(tests_[i].test_case_name, tests_[i].test_name);
+ bool found_a = false;
+ bool found_b = false;
+ for (size_t k = 0; k < filter_a.size(); ++k) {
+ found_a = found_a || MatchPattern(test_name, filter_a[k]);
+ }
+ for (size_t k = 0; k < filter_b.size(); ++k) {
+ found_b = found_b || MatchPattern(test_name, filter_b[k]);
+ }
+ if (found_a && found_b) {
+ positive_test_filter_.push_back(test_name);
+ }
+ }
+ } else if (!filter_a.empty()) {
+ positive_test_filter_ = std::move(filter_a);
+ } else {
+ positive_test_filter_ = std::move(filter_b);
+ }
+}
+
+void TestLauncher::RunTests() {
+ std::vector<std::string> test_names;
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ for (const TestIdentifier& test_id : tests_) {
+ std::string test_name =
+ FormatFullTestName(test_id.test_case_name, test_id.test_name);
+
+ results_tracker_.AddTest(test_name);
+
+ if (test_name.find("DISABLED") != std::string::npos) {
+ results_tracker_.AddDisabledTest(test_name);
+
+ // Skip disabled tests unless explicitly requested.
+ if (!command_line->HasSwitch(kGTestRunDisabledTestsFlag))
+ continue;
+ }
+
+ if (!launcher_delegate_->ShouldRunTest(test_id.test_case_name,
+ test_id.test_name)) {
+ continue;
+ }
+
+ // Count tests in the binary, before we apply filter and sharding.
+ test_found_count_++;
+
+ std::string test_name_no_disabled =
+ TestNameWithoutDisabledPrefix(test_name);
+
+ // Skip the test that doesn't match the filter (if given).
+ if (has_at_least_one_positive_filter_) {
+ bool found = false;
+ for (auto filter : positive_test_filter_) {
+ if (MatchPattern(test_name, filter) ||
+ MatchPattern(test_name_no_disabled, filter)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ continue;
+ }
+ if (!negative_test_filter_.empty()) {
+ bool excluded = false;
+ for (auto filter : negative_test_filter_) {
+ if (MatchPattern(test_name, filter) ||
+ MatchPattern(test_name_no_disabled, filter)) {
+ excluded = true;
+ break;
+ }
+ }
+
+ if (excluded)
+ continue;
+ }
+
+ if (Hash(test_name) % total_shards_ != static_cast<uint32_t>(shard_index_))
+ continue;
+
+ // Report test locations after applying all filters, so that we report test
+ // locations only for those tests that were run as part of this shard.
+ results_tracker_.AddTestLocation(test_name, test_id.file, test_id.line);
+
+ test_names.push_back(test_name);
+ }
+
+ if (shuffle_) {
+ std::mt19937 randomizer;
+ randomizer.seed(shuffle_seed_);
+ std::shuffle(test_names.begin(), test_names.end(), randomizer);
+
+ fprintf(stdout, "Randomizing with seed %u\n", shuffle_seed_);
+ fflush(stdout);
+ }
+
+ // Save an early test summary in case the launcher crashes or gets killed.
+ MaybeSaveSummaryAsJSON({"EARLY_SUMMARY", kUnreliableResultsTag});
+
+ test_started_count_ = launcher_delegate_->RunTests(this, test_names);
+
+ if (test_started_count_ == 0) {
+ fprintf(stdout, "0 tests run\n");
+ fflush(stdout);
+
+ // No tests have actually been started, so kick off the next iteration.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&TestLauncher::RunTestIteration, Unretained(this)));
+ }
+}
+
+void TestLauncher::RunTestIteration() {
+ const bool stop_on_failure =
+ CommandLine::ForCurrentProcess()->HasSwitch(kGTestBreakOnFailure);
+ if (cycles_ == 0 ||
+ (stop_on_failure && test_success_count_ != test_finished_count_)) {
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ return;
+ }
+
+ // Special value "-1" means "repeat indefinitely".
+ cycles_ = (cycles_ == -1) ? cycles_ : cycles_ - 1;
+
+ test_found_count_ = 0;
+ test_started_count_ = 0;
+ test_finished_count_ = 0;
+ test_success_count_ = 0;
+ test_broken_count_ = 0;
+ retry_count_ = 0;
+ tests_to_retry_.clear();
+ results_tracker_.OnTestIterationStarting();
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&TestLauncher::RunTests, Unretained(this)));
+}
+
+#if defined(OS_POSIX)
+// I/O watcher for the reading end of the self-pipe above.
+// Terminates any launched child processes and exits the process.
+void TestLauncher::OnShutdownPipeReadable() {
+ fprintf(stdout, "\nCaught signal. Killing spawned test processes...\n");
+ fflush(stdout);
+
+ KillSpawnedTestProcesses();
+
+ MaybeSaveSummaryAsJSON({"CAUGHT_TERMINATION_SIGNAL", kUnreliableResultsTag});
+
+ // The signal would normally kill the process, so exit now.
+ _exit(1);
+}
+#endif // defined(OS_POSIX)
+
+void TestLauncher::MaybeSaveSummaryAsJSON(
+ const std::vector<std::string>& additional_tags) {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kTestLauncherSummaryOutput)) {
+ FilePath summary_path(command_line->GetSwitchValuePath(
+ switches::kTestLauncherSummaryOutput));
+ if (!results_tracker_.SaveSummaryAsJSON(summary_path, additional_tags)) {
+ LOG(ERROR) << "Failed to save test launcher output summary.";
+ }
+ }
+ if (command_line->HasSwitch(switches::kTestLauncherTrace)) {
+ FilePath trace_path(
+ command_line->GetSwitchValuePath(switches::kTestLauncherTrace));
+ if (!GetTestLauncherTracer()->Dump(trace_path)) {
+ LOG(ERROR) << "Failed to save test launcher trace.";
+ }
+ }
+}
+
+void TestLauncher::OnTestIterationFinished() {
+ TestResultsTracker::TestStatusMap tests_by_status(
+ results_tracker_.GetTestStatusMapForCurrentIteration());
+ if (!tests_by_status[TestResult::TEST_UNKNOWN].empty())
+ results_tracker_.AddGlobalTag(kUnreliableResultsTag);
+
+ // When we retry tests, success is determined by having nothing more
+ // to retry (everything eventually passed), as opposed to having
+ // no failures at all.
+ if (tests_to_retry_.empty()) {
+ fprintf(stdout, "SUCCESS: all tests passed.\n");
+ fflush(stdout);
+ } else {
+ // Signal failure, but continue to run all requested test iterations.
+ // With the summary of all iterations at the end this is a good default.
+ run_result_ = false;
+ }
+
+ results_tracker_.PrintSummaryOfCurrentIteration();
+
+ // Kick off the next iteration.
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&TestLauncher::RunTestIteration, Unretained(this)));
+}
+
+void TestLauncher::OnOutputTimeout() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ AutoLock lock(*GetLiveProcessesLock());
+
+ fprintf(stdout, "Still waiting for the following processes to finish:\n");
+
+ for (const auto& pair : *GetLiveProcesses()) {
+#if defined(OS_WIN)
+ fwprintf(stdout, L"\t%s\n", pair.second.GetCommandLineString().c_str());
+#else
+ fprintf(stdout, "\t%s\n", pair.second.GetCommandLineString().c_str());
+#endif
+ }
+
+ fflush(stdout);
+
+ // Arm the timer again - otherwise it would fire only once.
+ watchdog_timer_.Reset();
+}
+
+size_t NumParallelJobs() {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kTestLauncherJobs)) {
+ // If the number of test launcher jobs was specified, return that number.
+ size_t jobs = 0U;
+
+ if (!StringToSizeT(
+ command_line->GetSwitchValueASCII(switches::kTestLauncherJobs),
+ &jobs) ||
+ !jobs) {
+ LOG(ERROR) << "Invalid value for " << switches::kTestLauncherJobs;
+ return 0U;
+ }
+ return jobs;
+ }
+ if (command_line->HasSwitch(kGTestFilterFlag) && !BotModeEnabled()) {
+ // Do not run jobs in parallel by default if we are running a subset of
+ // the tests and if bot mode is off.
+ return 1U;
+ }
+
+ // Default to the number of processor cores.
+ return base::checked_cast<size_t>(SysInfo::NumberOfProcessors());
+}
+
+std::string GetTestOutputSnippet(const TestResult& result,
+ const std::string& full_output) {
+ size_t run_pos = full_output.find(std::string("[ RUN ] ") +
+ result.full_name);
+ if (run_pos == std::string::npos)
+ return std::string();
+
+ size_t end_pos = full_output.find(std::string("[ FAILED ] ") +
+ result.full_name,
+ run_pos);
+ // Only clip the snippet to the "OK" message if the test really
+ // succeeded. It still might have e.g. crashed after printing it.
+ if (end_pos == std::string::npos &&
+ result.status == TestResult::TEST_SUCCESS) {
+ end_pos = full_output.find(std::string("[ OK ] ") +
+ result.full_name,
+ run_pos);
+ }
+ if (end_pos != std::string::npos) {
+ size_t newline_pos = full_output.find("\n", end_pos);
+ if (newline_pos != std::string::npos)
+ end_pos = newline_pos + 1;
+ }
+
+ std::string snippet(full_output.substr(run_pos));
+ if (end_pos != std::string::npos)
+ snippet = full_output.substr(run_pos, end_pos - run_pos);
+
+ return snippet;
+}
+
+} // namespace base
diff --git a/base/test/launcher/test_launcher.h b/base/test/launcher/test_launcher.h
new file mode 100644
index 0000000000..9ac45ba689
--- /dev/null
+++ b/base/test/launcher/test_launcher.h
@@ -0,0 +1,268 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_LAUNCHER_TEST_LAUNCHER_H_
+#define BASE_TEST_LAUNCHER_TEST_LAUNCHER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/process/launch.h"
+#include "base/test/gtest_util.h"
+#include "base/test/launcher/test_result.h"
+#include "base/test/launcher/test_results_tracker.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "build/build_config.h"
+
+namespace base {
+
+class CommandLine;
+struct LaunchOptions;
+class TestLauncher;
+
+// Constants for GTest command-line flags.
+extern const char kGTestFilterFlag[];
+extern const char kGTestFlagfileFlag[];
+extern const char kGTestHelpFlag[];
+extern const char kGTestListTestsFlag[];
+extern const char kGTestRepeatFlag[];
+extern const char kGTestRunDisabledTestsFlag[];
+extern const char kGTestOutputFlag[];
+extern const char kGTestShuffleFlag[];
+extern const char kGTestRandomSeedFlag[];
+
+// Interface for use with LaunchTests that abstracts away exact details
+// which tests and how are run.
+class TestLauncherDelegate {
+ public:
+ // Called to get names of tests available for running. The delegate
+ // must put the result in |output| and return true on success.
+ virtual bool GetTests(std::vector<TestIdentifier>* output) = 0;
+
+ // Called before a test is considered for running. If it returns false,
+ // the test is not run. If it returns true, the test will be run provided
+ // it is part of the current shard.
+ virtual bool ShouldRunTest(const std::string& test_case_name,
+ const std::string& test_name) = 0;
+
+ // Called to make the delegate run the specified tests. The delegate must
+ // return the number of actual tests it's going to run (can be smaller,
+ // equal to, or larger than size of |test_names|). It must also call
+ // |test_launcher|'s OnTestFinished method once per every run test,
+ // regardless of its success.
+ virtual size_t RunTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) = 0;
+
+ // Called to make the delegate retry the specified tests. The delegate must
+ // return the number of actual tests it's going to retry (can be smaller,
+ // equal to, or larger than size of |test_names|). It must also call
+ // |test_launcher|'s OnTestFinished method once per every retried test,
+ // regardless of its success.
+ virtual size_t RetryTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) = 0;
+
+ protected:
+ virtual ~TestLauncherDelegate();
+};
+
+// An observer of child process lifetime events generated by
+// LaunchChildGTestProcess.
+class ProcessLifetimeObserver {
+ public:
+ virtual ~ProcessLifetimeObserver() = default;
+
+ // Invoked once the child process is started. |handle| is a handle to the
+ // child process and |id| is its pid. NOTE: this method is invoked on the
+ // thread the process is launched on immediately after it is launched. The
+ // caller owns the ProcessHandle.
+ virtual void OnLaunched(ProcessHandle handle, ProcessId id) {}
+
+ // Invoked when a test process exceeds its runtime, immediately before it is
+ // terminated. |command_line| is the command line used to launch the process.
+ // NOTE: this method is invoked on the thread the process is launched on.
+ virtual void OnTimedOut(const CommandLine& command_line) {}
+
+ // Invoked after a child process finishes, reporting the process |exit_code|,
+ // child process |elapsed_time|, whether or not the process was terminated as
+ // a result of a timeout, and the output of the child (stdout and stderr
+ // together). NOTE: this method is invoked on the same thread as
+ // LaunchChildGTestProcess.
+ virtual void OnCompleted(int exit_code,
+ TimeDelta elapsed_time,
+ bool was_timeout,
+ const std::string& output) {}
+
+ protected:
+ ProcessLifetimeObserver() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProcessLifetimeObserver);
+};
+
+// Launches tests using a TestLauncherDelegate.
+class TestLauncher {
+ public:
+ // Flags controlling behavior of LaunchChildGTestProcess.
+ enum LaunchChildGTestProcessFlags {
+ // Allows usage of job objects on Windows. Helps properly clean up child
+ // processes.
+ USE_JOB_OBJECTS = (1 << 0),
+
+ // Allows breakaway from job on Windows. May result in some child processes
+ // not being properly terminated after launcher dies if these processes
+ // fail to cooperate.
+ ALLOW_BREAKAWAY_FROM_JOB = (1 << 1),
+ };
+
+ struct LaunchOptions {
+ LaunchOptions();
+ LaunchOptions(const LaunchOptions& other);
+ ~LaunchOptions();
+
+ int flags = 0;
+ // These mirror values in base::LaunchOptions, see it for details.
+#if defined(OS_WIN)
+ base::LaunchOptions::Inherit inherit_mode =
+ base::LaunchOptions::Inherit::kSpecific;
+ base::HandlesToInheritVector handles_to_inherit;
+#else
+ FileHandleMappingVector fds_to_remap;
+#endif
+ };
+
+ // Constructor. |parallel_jobs| is the limit of simultaneous parallel test
+ // jobs.
+ TestLauncher(TestLauncherDelegate* launcher_delegate, size_t parallel_jobs);
+ ~TestLauncher();
+
+ // Runs the launcher. Must be called at most once.
+ bool Run() WARN_UNUSED_RESULT;
+
+ // Launches a child process (assumed to be gtest-based binary) using
+ // |command_line|. If |wrapper| is not empty, it is prepended to the final
+ // command line. |observer|, if not null, is used to convey process lifetime
+ // events to the caller. |observer| is destroyed after its OnCompleted
+ // method is invoked.
+ void LaunchChildGTestProcess(
+ const CommandLine& command_line,
+ const std::string& wrapper,
+ TimeDelta timeout,
+ const LaunchOptions& options,
+ std::unique_ptr<ProcessLifetimeObserver> observer);
+
+ // Called when a test has finished running.
+ void OnTestFinished(const TestResult& result);
+
+ private:
+ bool Init() WARN_UNUSED_RESULT;
+
+ // Runs all tests in current iteration.
+ void RunTests();
+
+ void CombinePositiveTestFilters(std::vector<std::string> filter_a,
+ std::vector<std::string> filter_b);
+
+ void RunTestIteration();
+
+#if defined(OS_POSIX)
+ void OnShutdownPipeReadable();
+#endif
+
+ // Saves test results summary as JSON if requested from command line.
+ void MaybeSaveSummaryAsJSON(const std::vector<std::string>& additional_tags);
+
+ // Called when a test iteration is finished.
+ void OnTestIterationFinished();
+
+ // Called by the delay timer when no output was made for a while.
+ void OnOutputTimeout();
+
+ // Make sure we don't accidentally call the wrong methods e.g. on the worker
+ // pool thread. Should be the first member so that it's destroyed last: when
+ // destroying other members, especially the worker pool, we may check the code
+ // is running on the correct thread.
+ ThreadChecker thread_checker_;
+
+ TestLauncherDelegate* launcher_delegate_;
+
+ // Support for outer sharding, just like gtest does.
+ int32_t total_shards_; // Total number of outer shards, at least one.
+ int32_t shard_index_; // Index of shard the launcher is to run.
+
+ int cycles_; // Number of remaining test iterations, or -1 for infinite.
+
+ // Test filters (empty means no filter).
+ bool has_at_least_one_positive_filter_;
+ std::vector<std::string> positive_test_filter_;
+ std::vector<std::string> negative_test_filter_;
+
+ // Tests to use (cached result of TestLauncherDelegate::GetTests).
+ std::vector<TestIdentifier> tests_;
+
+ // Number of tests found in this binary.
+ size_t test_found_count_;
+
+ // Number of tests started in this iteration.
+ size_t test_started_count_;
+
+ // Number of tests finished in this iteration.
+ size_t test_finished_count_;
+
+ // Number of tests successfully finished in this iteration.
+ size_t test_success_count_;
+
+ // Number of tests either timing out or having an unknown result,
+ // likely indicating a more systemic problem if widespread.
+ size_t test_broken_count_;
+
+ // Number of retries in this iteration.
+ size_t retry_count_;
+
+ // Maximum number of retries per iteration.
+ size_t retry_limit_;
+
+ // If true will not early exit nor skip retries even if too many tests are
+ // broken.
+ bool force_run_broken_tests_;
+
+ // Tests to retry in this iteration.
+ std::set<std::string> tests_to_retry_;
+
+ // Result to be returned from Run.
+ bool run_result_;
+
+ // Support for test shuffling, just like gtest does.
+ bool shuffle_;
+ uint32_t shuffle_seed_;
+
+ TestResultsTracker results_tracker_;
+
+ // Watchdog timer to make sure we do not go without output for too long.
+ DelayTimer watchdog_timer_;
+
+ // Number of jobs to run in parallel.
+ size_t parallel_jobs_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLauncher);
+};
+
+// Return the number of parallel jobs to use, or 0U in case of error.
+size_t NumParallelJobs();
+
+// Extract part from |full_output| that applies to |result|.
+std::string GetTestOutputSnippet(const TestResult& result,
+ const std::string& full_output);
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_TEST_LAUNCHER_H_
diff --git a/base/test/launcher/test_launcher_tracer.cc b/base/test/launcher/test_launcher_tracer.cc
new file mode 100644
index 0000000000..d525df7523
--- /dev/null
+++ b/base/test/launcher/test_launcher_tracer.cc
@@ -0,0 +1,53 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/launcher/test_launcher_tracer.h"
+
+#include "base/json/json_file_value_serializer.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+
+namespace base {
+
+TestLauncherTracer::TestLauncherTracer()
+ : trace_start_time_(TimeTicks::Now()) {}
+
+TestLauncherTracer::~TestLauncherTracer() = default;
+
+void TestLauncherTracer::RecordProcessExecution(TimeTicks start_time,
+ TimeDelta duration) {
+ AutoLock lock(lock_);
+
+ Event event;
+ event.name = StringPrintf("process #%zu", events_.size());
+ event.timestamp = start_time;
+ event.duration = duration;
+ event.thread_id = PlatformThread::CurrentId();
+ events_.push_back(event);
+}
+
+bool TestLauncherTracer::Dump(const FilePath& path) {
+ AutoLock lock(lock_);
+
+ std::unique_ptr<ListValue> json_events(new ListValue);
+ for (const Event& event : events_) {
+ std::unique_ptr<DictionaryValue> json_event(new DictionaryValue);
+ json_event->SetString("name", event.name);
+ json_event->SetString("ph", "X");
+ json_event->SetInteger(
+ "ts", (event.timestamp - trace_start_time_).InMicroseconds());
+ json_event->SetInteger("dur", event.duration.InMicroseconds());
+ json_event->SetInteger("tid", event.thread_id);
+
+ // Add fake values required by the trace viewer.
+ json_event->SetInteger("pid", 0);
+
+ json_events->Append(std::move(json_event));
+ }
+
+ JSONFileValueSerializer serializer(path);
+ return serializer.Serialize(*json_events);
+}
+
+} // namespace base
diff --git a/base/test/launcher/test_launcher_tracer.h b/base/test/launcher/test_launcher_tracer.h
new file mode 100644
index 0000000000..58bc1b071e
--- /dev/null
+++ b/base/test/launcher/test_launcher_tracer.h
@@ -0,0 +1,55 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_LAUNCHER_TEST_LAUNCHER_TRACER_H_
+#define BASE_TEST_LAUNCHER_TEST_LAUNCHER_TRACER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+
+namespace base {
+
+class FilePath;
+
+// Records traces of test execution, e.g. to analyze performance.
+// Thread safe.
+class TestLauncherTracer {
+ public:
+ TestLauncherTracer();
+ ~TestLauncherTracer();
+
+ // Records an event corresponding to test process execution.
+ void RecordProcessExecution(TimeTicks start_time, TimeDelta duration);
+
+ // Dumps trace data as JSON. Returns true on success.
+ bool Dump(const FilePath& path) WARN_UNUSED_RESULT;
+
+ private:
+ // Simplified version of base::TraceEvent.
+ struct Event {
+ std::string name; // Displayed name.
+ TimeTicks timestamp; // Timestamp when this event began.
+ TimeDelta duration; // How long was this event.
+ PlatformThreadId thread_id; // Thread ID where event was reported.
+ };
+
+ // Timestamp when tracing started.
+ TimeTicks trace_start_time_;
+
+ // Log of trace events.
+ std::vector<Event> events_;
+
+ // Lock to protect all member variables.
+ Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLauncherTracer);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_TEST_LAUNCHER_TRACER_H_
diff --git a/base/test/launcher/test_result.cc b/base/test/launcher/test_result.cc
new file mode 100644
index 0000000000..9f37a2b96d
--- /dev/null
+++ b/base/test/launcher/test_result.cc
@@ -0,0 +1,96 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/launcher/test_result.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+
+namespace base {
+
+TestResultPart::TestResultPart() = default;
+TestResultPart::~TestResultPart() = default;
+
+TestResultPart::TestResultPart(const TestResultPart& other) = default;
+TestResultPart::TestResultPart(TestResultPart&& other) = default;
+TestResultPart& TestResultPart::operator=(const TestResultPart& other) =
+ default;
+TestResultPart& TestResultPart::operator=(TestResultPart&& other) = default;
+
+// static
+bool TestResultPart::TypeFromString(const std::string& str, Type* type) {
+ if (str == "success")
+ *type = kSuccess;
+ else if (str == "failure")
+ *type = kNonFatalFailure;
+ else if (str == "fatal_failure")
+ *type = kFatalFailure;
+ else
+ return false;
+ return true;
+}
+
+std::string TestResultPart::TypeAsString() const {
+ switch (type) {
+ case kSuccess:
+ return "success";
+ case kNonFatalFailure:
+ return "failure";
+ case kFatalFailure:
+ return "fatal_failure";
+ default:
+ NOTREACHED();
+ }
+ return "unknown";
+}
+
+TestResult::TestResult() : status(TEST_UNKNOWN) {
+}
+
+TestResult::~TestResult() = default;
+
+TestResult::TestResult(const TestResult& other) = default;
+TestResult::TestResult(TestResult&& other) = default;
+TestResult& TestResult::operator=(const TestResult& other) = default;
+TestResult& TestResult::operator=(TestResult&& other) = default;
+
+std::string TestResult::StatusAsString() const {
+ switch (status) {
+ case TEST_UNKNOWN:
+ return "UNKNOWN";
+ case TEST_SUCCESS:
+ return "SUCCESS";
+ case TEST_FAILURE:
+ return "FAILURE";
+ case TEST_FAILURE_ON_EXIT:
+ return "FAILURE_ON_EXIT";
+ case TEST_CRASH:
+ return "CRASH";
+ case TEST_TIMEOUT:
+ return "TIMEOUT";
+ case TEST_SKIPPED:
+ return "SKIPPED";
+ case TEST_EXCESSIVE_OUTPUT:
+ return "EXCESSIVE_OUTPUT";
+ // Rely on compiler warnings to ensure all possible values are handled.
+ }
+
+ NOTREACHED();
+ return std::string();
+}
+
+std::string TestResult::GetTestName() const {
+ size_t dot_pos = full_name.find('.');
+ CHECK_NE(dot_pos, std::string::npos);
+ return full_name.substr(dot_pos + 1);
+}
+
+std::string TestResult::GetTestCaseName() const {
+ size_t dot_pos = full_name.find('.');
+ CHECK_NE(dot_pos, std::string::npos);
+ return full_name.substr(0, dot_pos);
+}
+
+} // namespace base
diff --git a/base/test/launcher/test_result.h b/base/test/launcher/test_result.h
new file mode 100644
index 0000000000..07338b372f
--- /dev/null
+++ b/base/test/launcher/test_result.h
@@ -0,0 +1,104 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_LAUNCHER_TEST_RESULT_H_
+#define BASE_TEST_LAUNCHER_TEST_RESULT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/time/time.h"
+
+namespace base {
+
+// Structure contains result of a single EXPECT/ASSERT/SUCCESS.
+struct TestResultPart {
+ enum Type {
+ kSuccess, // SUCCESS
+ kNonFatalFailure, // EXPECT
+ kFatalFailure, // ASSERT
+ };
+ Type type;
+
+ TestResultPart();
+ ~TestResultPart();
+
+ TestResultPart(const TestResultPart& other);
+ TestResultPart(TestResultPart&& other);
+ TestResultPart& operator=(const TestResultPart& other);
+ TestResultPart& operator=(TestResultPart&& other);
+
+ // Convert type to string and back.
+ static bool TypeFromString(const std::string& str, Type* type);
+ std::string TypeAsString() const;
+
+ // Filename and line of EXPECT/ASSERT.
+ std::string file_name;
+ int line_number;
+
+ // Message without stacktrace, etc.
+ std::string summary;
+
+ // Complete message.
+ std::string message;
+};
+
+// Structure containing result of a single test.
+struct TestResult {
+ enum Status {
+ TEST_UNKNOWN, // Status not set.
+ TEST_SUCCESS, // Test passed.
+ TEST_FAILURE, // Assertion failure (e.g. EXPECT_TRUE, not DCHECK).
+ TEST_FAILURE_ON_EXIT, // Passed but executable exit code was non-zero.
+ TEST_TIMEOUT, // Test timed out and was killed.
+ TEST_CRASH, // Test crashed (includes CHECK/DCHECK failures).
+ TEST_SKIPPED, // Test skipped (not run at all).
+ TEST_EXCESSIVE_OUTPUT, // Test exceeded output limit.
+ };
+
+ TestResult();
+ ~TestResult();
+
+ TestResult(const TestResult& other);
+ TestResult(TestResult&& other);
+ TestResult& operator=(const TestResult& other);
+ TestResult& operator=(TestResult&& other);
+
+ // Returns the test status as string (e.g. for display).
+ std::string StatusAsString() const;
+
+ // Returns the test name (e.g. "B" for "A.B").
+ std::string GetTestName() const;
+
+ // Returns the test case name (e.g. "A" for "A.B").
+ std::string GetTestCaseName() const;
+
+ // Returns true if the test has completed (i.e. the test binary exited
+ // normally, possibly with an exit code indicating failure, but didn't crash
+ // or time out in the middle of the test).
+ bool completed() const {
+ return status == TEST_SUCCESS ||
+ status == TEST_FAILURE ||
+ status == TEST_FAILURE_ON_EXIT ||
+ status == TEST_EXCESSIVE_OUTPUT;
+ }
+
+ // Full name of the test (e.g. "A.B").
+ std::string full_name;
+
+ Status status;
+
+ // Time it took to run the test.
+ base::TimeDelta elapsed_time;
+
+ // Output of just this test (optional).
+ std::string output_snippet;
+
+ // Information about failed expectations.
+ std::vector<TestResultPart> test_result_parts;
+};
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_TEST_RESULT_H_
diff --git a/base/test/launcher/test_results_tracker.cc b/base/test/launcher/test_results_tracker.cc
new file mode 100644
index 0000000000..a7e590c2ed
--- /dev/null
+++ b/base/test/launcher/test_results_tracker.cc
@@ -0,0 +1,541 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/launcher/test_results_tracker.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/json/json_writer.h"
+#include "base/json/string_escape.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/gtest_util.h"
+#include "base/test/launcher/test_launcher.h"
+#include "base/time/time.h"
+#include "base/values.h"
+
+namespace base {
+
+namespace {
+
+// The default output file for XML output.
+const FilePath::CharType kDefaultOutputFile[] = FILE_PATH_LITERAL(
+ "test_detail.xml");
+
+// Converts the given epoch time in milliseconds to a date string in the ISO
+// 8601 format, without the timezone information.
+// TODO(xyzzyz): Find a good place in Chromium to put it and refactor all uses
+// to point to it.
+std::string FormatTimeAsIso8601(Time time) {
+ Time::Exploded exploded;
+ time.UTCExplode(&exploded);
+ return StringPrintf("%04d-%02d-%02dT%02d:%02d:%02d",
+ exploded.year,
+ exploded.month,
+ exploded.day_of_month,
+ exploded.hour,
+ exploded.minute,
+ exploded.second);
+}
+
+struct TestSuiteResultsAggregator {
+ TestSuiteResultsAggregator()
+ : tests(0), failures(0), disabled(0), errors(0) {}
+
+ void Add(const TestResult& result) {
+ tests++;
+ elapsed_time += result.elapsed_time;
+
+ switch (result.status) {
+ case TestResult::TEST_SUCCESS:
+ break;
+ case TestResult::TEST_FAILURE:
+ failures++;
+ break;
+ case TestResult::TEST_EXCESSIVE_OUTPUT:
+ case TestResult::TEST_FAILURE_ON_EXIT:
+ case TestResult::TEST_TIMEOUT:
+ case TestResult::TEST_CRASH:
+ case TestResult::TEST_UNKNOWN:
+ errors++;
+ break;
+ case TestResult::TEST_SKIPPED:
+ disabled++;
+ break;
+ }
+ }
+
+ int tests;
+ int failures;
+ int disabled;
+ int errors;
+
+ TimeDelta elapsed_time;
+};
+
+} // namespace
+
+TestResultsTracker::TestResultsTracker() : iteration_(-1), out_(nullptr) {}
+
+TestResultsTracker::~TestResultsTracker() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!out_)
+ return;
+
+ // Maps test case names to test results.
+ typedef std::map<std::string, std::vector<TestResult> > TestCaseMap;
+ TestCaseMap test_case_map;
+
+ TestSuiteResultsAggregator all_tests_aggregator;
+ for (const PerIterationData::ResultsMap::value_type& i
+ : per_iteration_data_[iteration_].results) {
+ // Use the last test result as the final one.
+ TestResult result = i.second.test_results.back();
+ test_case_map[result.GetTestCaseName()].push_back(result);
+ all_tests_aggregator.Add(result);
+ }
+
+ fprintf(out_, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ fprintf(out_,
+ "<testsuites name=\"AllTests\" tests=\"%d\" failures=\"%d\""
+ " disabled=\"%d\" errors=\"%d\" time=\"%.3f\" timestamp=\"%s\">\n",
+ all_tests_aggregator.tests, all_tests_aggregator.failures,
+ all_tests_aggregator.disabled, all_tests_aggregator.errors,
+ all_tests_aggregator.elapsed_time.InSecondsF(),
+ FormatTimeAsIso8601(Time::Now()).c_str());
+
+ for (const TestCaseMap::value_type& i : test_case_map) {
+ const std::string testsuite_name = i.first;
+ const std::vector<TestResult>& results = i.second;
+
+ TestSuiteResultsAggregator aggregator;
+ for (const TestResult& result : results) {
+ aggregator.Add(result);
+ }
+ fprintf(out_,
+ " <testsuite name=\"%s\" tests=\"%d\" "
+ "failures=\"%d\" disabled=\"%d\" errors=\"%d\" time=\"%.3f\" "
+ "timestamp=\"%s\">\n",
+ testsuite_name.c_str(), aggregator.tests, aggregator.failures,
+ aggregator.disabled, aggregator.errors,
+ aggregator.elapsed_time.InSecondsF(),
+ FormatTimeAsIso8601(Time::Now()).c_str());
+
+ for (const TestResult& result : results) {
+ fprintf(out_, " <testcase name=\"%s\" status=\"run\" time=\"%.3f\""
+ " classname=\"%s\">\n",
+ result.GetTestName().c_str(),
+ result.elapsed_time.InSecondsF(),
+ result.GetTestCaseName().c_str());
+ if (result.status != TestResult::TEST_SUCCESS) {
+ // The actual failure message is not propagated up to here, as it's too
+ // much work to escape it properly, and in case of failure, almost
+ // always one needs to look into full log anyway.
+ fprintf(out_, " <failure message=\"\" type=\"\"></failure>\n");
+ }
+ fprintf(out_, " </testcase>\n");
+ }
+ fprintf(out_, " </testsuite>\n");
+ }
+
+ fprintf(out_, "</testsuites>\n");
+ fclose(out_);
+}
+
+bool TestResultsTracker::Init(const CommandLine& command_line) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Prevent initializing twice.
+ if (out_) {
+ NOTREACHED();
+ return false;
+ }
+
+ if (!command_line.HasSwitch(kGTestOutputFlag))
+ return true;
+
+ std::string flag = command_line.GetSwitchValueASCII(kGTestOutputFlag);
+ size_t colon_pos = flag.find(':');
+ FilePath path;
+ if (colon_pos != std::string::npos) {
+ FilePath flag_path =
+ command_line.GetSwitchValuePath(kGTestOutputFlag);
+ FilePath::StringType path_string = flag_path.value();
+ path = FilePath(path_string.substr(colon_pos + 1));
+ // If the given path ends with '/', consider it is a directory.
+ // Note: This does NOT check that a directory (or file) actually exists
+ // (the behavior is same as what gtest does).
+ if (path.EndsWithSeparator()) {
+ FilePath executable = command_line.GetProgram().BaseName();
+ path = path.Append(executable.ReplaceExtension(
+ FilePath::StringType(FILE_PATH_LITERAL("xml"))));
+ }
+ }
+ if (path.value().empty())
+ path = FilePath(kDefaultOutputFile);
+ FilePath dir_name = path.DirName();
+ if (!DirectoryExists(dir_name)) {
+ LOG(WARNING) << "The output directory does not exist. "
+ << "Creating the directory: " << dir_name.value();
+ // Create the directory if necessary (because the gtest does the same).
+ if (!CreateDirectory(dir_name)) {
+ LOG(ERROR) << "Failed to created directory " << dir_name.value();
+ return false;
+ }
+ }
+ out_ = OpenFile(path, "w");
+ if (!out_) {
+ LOG(ERROR) << "Cannot open output file: "
+ << path.value() << ".";
+ return false;
+ }
+
+ return true;
+}
+
+void TestResultsTracker::OnTestIterationStarting() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Start with a fresh state for new iteration.
+ iteration_++;
+ per_iteration_data_.push_back(PerIterationData());
+}
+
+void TestResultsTracker::AddTest(const std::string& test_name) {
+ // Record disabled test names without DISABLED_ prefix so that they are easy
+ // to compare with regular test names, e.g. before or after disabling.
+ all_tests_.insert(TestNameWithoutDisabledPrefix(test_name));
+}
+
+void TestResultsTracker::AddDisabledTest(const std::string& test_name) {
+ // Record disabled test names without DISABLED_ prefix so that they are easy
+ // to compare with regular test names, e.g. before or after disabling.
+ disabled_tests_.insert(TestNameWithoutDisabledPrefix(test_name));
+}
+
+void TestResultsTracker::AddTestLocation(const std::string& test_name,
+ const std::string& file,
+ int line) {
+ test_locations_.insert(std::make_pair(test_name, CodeLocation(file, line)));
+}
+
+void TestResultsTracker::AddTestResult(const TestResult& result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Record disabled test names without DISABLED_ prefix so that they are easy
+ // to compare with regular test names, e.g. before or after disabling.
+ per_iteration_data_[iteration_].results[
+ TestNameWithoutDisabledPrefix(result.full_name)].test_results.push_back(
+ result);
+}
+
+void TestResultsTracker::PrintSummaryOfCurrentIteration() const {
+ TestStatusMap tests_by_status(GetTestStatusMapForCurrentIteration());
+
+ PrintTests(tests_by_status[TestResult::TEST_FAILURE].begin(),
+ tests_by_status[TestResult::TEST_FAILURE].end(),
+ "failed");
+ PrintTests(tests_by_status[TestResult::TEST_FAILURE_ON_EXIT].begin(),
+ tests_by_status[TestResult::TEST_FAILURE_ON_EXIT].end(),
+ "failed on exit");
+ PrintTests(tests_by_status[TestResult::TEST_EXCESSIVE_OUTPUT].begin(),
+ tests_by_status[TestResult::TEST_EXCESSIVE_OUTPUT].end(),
+ "produced excessive output");
+ PrintTests(tests_by_status[TestResult::TEST_TIMEOUT].begin(),
+ tests_by_status[TestResult::TEST_TIMEOUT].end(),
+ "timed out");
+ PrintTests(tests_by_status[TestResult::TEST_CRASH].begin(),
+ tests_by_status[TestResult::TEST_CRASH].end(),
+ "crashed");
+ PrintTests(tests_by_status[TestResult::TEST_SKIPPED].begin(),
+ tests_by_status[TestResult::TEST_SKIPPED].end(),
+ "skipped");
+ PrintTests(tests_by_status[TestResult::TEST_UNKNOWN].begin(),
+ tests_by_status[TestResult::TEST_UNKNOWN].end(),
+ "had unknown result");
+}
+
+void TestResultsTracker::PrintSummaryOfAllIterations() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ TestStatusMap tests_by_status(GetTestStatusMapForAllIterations());
+
+ fprintf(stdout, "Summary of all test iterations:\n");
+ fflush(stdout);
+
+ PrintTests(tests_by_status[TestResult::TEST_FAILURE].begin(),
+ tests_by_status[TestResult::TEST_FAILURE].end(),
+ "failed");
+ PrintTests(tests_by_status[TestResult::TEST_FAILURE_ON_EXIT].begin(),
+ tests_by_status[TestResult::TEST_FAILURE_ON_EXIT].end(),
+ "failed on exit");
+ PrintTests(tests_by_status[TestResult::TEST_EXCESSIVE_OUTPUT].begin(),
+ tests_by_status[TestResult::TEST_EXCESSIVE_OUTPUT].end(),
+ "produced excessive output");
+ PrintTests(tests_by_status[TestResult::TEST_TIMEOUT].begin(),
+ tests_by_status[TestResult::TEST_TIMEOUT].end(),
+ "timed out");
+ PrintTests(tests_by_status[TestResult::TEST_CRASH].begin(),
+ tests_by_status[TestResult::TEST_CRASH].end(),
+ "crashed");
+ PrintTests(tests_by_status[TestResult::TEST_SKIPPED].begin(),
+ tests_by_status[TestResult::TEST_SKIPPED].end(),
+ "skipped");
+ PrintTests(tests_by_status[TestResult::TEST_UNKNOWN].begin(),
+ tests_by_status[TestResult::TEST_UNKNOWN].end(),
+ "had unknown result");
+
+ fprintf(stdout, "End of the summary.\n");
+ fflush(stdout);
+}
+
+void TestResultsTracker::AddGlobalTag(const std::string& tag) {
+ global_tags_.insert(tag);
+}
+
+bool TestResultsTracker::SaveSummaryAsJSON(
+ const FilePath& path,
+ const std::vector<std::string>& additional_tags) const {
+ std::unique_ptr<DictionaryValue> summary_root(new DictionaryValue);
+
+ std::unique_ptr<ListValue> global_tags(new ListValue);
+ for (const auto& global_tag : global_tags_) {
+ global_tags->AppendString(global_tag);
+ }
+ for (const auto& tag : additional_tags) {
+ global_tags->AppendString(tag);
+ }
+ summary_root->Set("global_tags", std::move(global_tags));
+
+ std::unique_ptr<ListValue> all_tests(new ListValue);
+ for (const auto& test : all_tests_) {
+ all_tests->AppendString(test);
+ }
+ summary_root->Set("all_tests", std::move(all_tests));
+
+ std::unique_ptr<ListValue> disabled_tests(new ListValue);
+ for (const auto& disabled_test : disabled_tests_) {
+ disabled_tests->AppendString(disabled_test);
+ }
+ summary_root->Set("disabled_tests", std::move(disabled_tests));
+
+ std::unique_ptr<ListValue> per_iteration_data(new ListValue);
+
+ for (int i = 0; i <= iteration_; i++) {
+ std::unique_ptr<DictionaryValue> current_iteration_data(
+ new DictionaryValue);
+
+ for (PerIterationData::ResultsMap::const_iterator j =
+ per_iteration_data_[i].results.begin();
+ j != per_iteration_data_[i].results.end();
+ ++j) {
+ std::unique_ptr<ListValue> test_results(new ListValue);
+
+ for (size_t k = 0; k < j->second.test_results.size(); k++) {
+ const TestResult& test_result = j->second.test_results[k];
+
+ std::unique_ptr<DictionaryValue> test_result_value(new DictionaryValue);
+
+ test_result_value->SetString("status", test_result.StatusAsString());
+ test_result_value->SetInteger(
+ "elapsed_time_ms",
+ static_cast<int>(test_result.elapsed_time.InMilliseconds()));
+
+ bool lossless_snippet = false;
+ if (IsStringUTF8(test_result.output_snippet)) {
+ test_result_value->SetString(
+ "output_snippet", test_result.output_snippet);
+ lossless_snippet = true;
+ } else {
+ test_result_value->SetString(
+ "output_snippet",
+ "<non-UTF-8 snippet, see output_snippet_base64>");
+ }
+
+ // TODO(phajdan.jr): Fix typo in JSON key (losless -> lossless)
+ // making sure not to break any consumers of this data.
+ test_result_value->SetBoolean("losless_snippet", lossless_snippet);
+
+ // Also include the raw version (base64-encoded so that it can be safely
+ // JSON-serialized - there are no guarantees about character encoding
+ // of the snippet). This can be very useful piece of information when
+ // debugging a test failure related to character encoding.
+ std::string base64_output_snippet;
+ Base64Encode(test_result.output_snippet, &base64_output_snippet);
+ test_result_value->SetString("output_snippet_base64",
+ base64_output_snippet);
+
+ std::unique_ptr<ListValue> test_result_parts(new ListValue);
+ for (const TestResultPart& result_part :
+ test_result.test_result_parts) {
+ std::unique_ptr<DictionaryValue> result_part_value(
+ new DictionaryValue);
+ result_part_value->SetString("type", result_part.TypeAsString());
+ result_part_value->SetString("file", result_part.file_name);
+ result_part_value->SetInteger("line", result_part.line_number);
+
+ bool lossless_summary = IsStringUTF8(result_part.summary);
+ if (lossless_summary) {
+ result_part_value->SetString("summary", result_part.summary);
+ } else {
+ result_part_value->SetString(
+ "summary", "<non-UTF-8 snippet, see summary_base64>");
+ }
+ result_part_value->SetBoolean("lossless_summary", lossless_summary);
+
+ std::string encoded_summary;
+ Base64Encode(result_part.summary, &encoded_summary);
+ result_part_value->SetString("summary_base64", encoded_summary);
+
+ bool lossless_message = IsStringUTF8(result_part.message);
+ if (lossless_message) {
+ result_part_value->SetString("message", result_part.message);
+ } else {
+ result_part_value->SetString(
+ "message", "<non-UTF-8 snippet, see message_base64>");
+ }
+ result_part_value->SetBoolean("lossless_message", lossless_message);
+
+ std::string encoded_message;
+ Base64Encode(result_part.message, &encoded_message);
+ result_part_value->SetString("message_base64", encoded_message);
+
+ test_result_parts->Append(std::move(result_part_value));
+ }
+ test_result_value->Set("result_parts", std::move(test_result_parts));
+
+ test_results->Append(std::move(test_result_value));
+ }
+
+ current_iteration_data->SetWithoutPathExpansion(j->first,
+ std::move(test_results));
+ }
+ per_iteration_data->Append(std::move(current_iteration_data));
+ }
+ summary_root->Set("per_iteration_data", std::move(per_iteration_data));
+
+ std::unique_ptr<DictionaryValue> test_locations(new DictionaryValue);
+ for (const auto& item : test_locations_) {
+ std::string test_name = item.first;
+ CodeLocation location = item.second;
+ std::unique_ptr<DictionaryValue> location_value(new DictionaryValue);
+ location_value->SetString("file", location.file);
+ location_value->SetInteger("line", location.line);
+ test_locations->SetWithoutPathExpansion(test_name,
+ std::move(location_value));
+ }
+ summary_root->Set("test_locations", std::move(test_locations));
+
+ std::string json;
+ if (!JSONWriter::Write(*summary_root, &json))
+ return false;
+
+ File output(path, File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE);
+ if (!output.IsValid())
+ return false;
+
+ int json_size = static_cast<int>(json.size());
+ if (output.WriteAtCurrentPos(json.data(), json_size) != json_size) {
+ return false;
+ }
+
+ // File::Flush() will call fsync(). This is important on Fuchsia to ensure
+ // that the file is written to the disk - the system running under qemu will
+ // shutdown shortly after the test completes. On Fuchsia fsync() times out
+ // after 15 seconds. Apparently this may not be enough in some cases,
+ // particularly when running net_unittests on buildbots, see
+ // https://crbug.com/796318. Try calling fsync() more than once to workaround
+ // this issue.
+ //
+ // TODO(sergeyu): Figure out a better solution.
+ int flush_attempts_left = 4;
+ while (flush_attempts_left-- > 0) {
+ if (output.Flush())
+ return true;
+ LOG(ERROR) << "fsync() failed when saving test output summary. "
+ << ((flush_attempts_left > 0) ? "Retrying." : " Giving up.");
+ }
+
+ return false;
+}
+
+TestResultsTracker::TestStatusMap
+ TestResultsTracker::GetTestStatusMapForCurrentIteration() const {
+ TestStatusMap tests_by_status;
+ GetTestStatusForIteration(iteration_, &tests_by_status);
+ return tests_by_status;
+}
+
+TestResultsTracker::TestStatusMap
+ TestResultsTracker::GetTestStatusMapForAllIterations() const {
+ TestStatusMap tests_by_status;
+ for (int i = 0; i <= iteration_; i++)
+ GetTestStatusForIteration(i, &tests_by_status);
+ return tests_by_status;
+}
+
+void TestResultsTracker::GetTestStatusForIteration(
+ int iteration, TestStatusMap* map) const {
+ for (PerIterationData::ResultsMap::const_iterator j =
+ per_iteration_data_[iteration].results.begin();
+ j != per_iteration_data_[iteration].results.end();
+ ++j) {
+ // Use the last test result as the final one.
+ const TestResult& result = j->second.test_results.back();
+ (*map)[result.status].insert(result.full_name);
+ }
+}
+
+// Utility function to print a list of test names. Uses iterator to be
+// compatible with different containers, like vector and set.
+template<typename InputIterator>
+void TestResultsTracker::PrintTests(InputIterator first,
+ InputIterator last,
+ const std::string& description) const {
+ size_t count = std::distance(first, last);
+ if (count == 0)
+ return;
+
+ fprintf(stdout,
+ "%" PRIuS " test%s %s:\n",
+ count,
+ count != 1 ? "s" : "",
+ description.c_str());
+ for (InputIterator it = first; it != last; ++it) {
+ const std::string& test_name = *it;
+ const auto location_it = test_locations_.find(test_name);
+ DCHECK(location_it != test_locations_.end()) << test_name;
+ const CodeLocation& location = location_it->second;
+ fprintf(stdout, " %s (%s:%d)\n", test_name.c_str(),
+ location.file.c_str(), location.line);
+ }
+ fflush(stdout);
+}
+
+TestResultsTracker::AggregateTestResult::AggregateTestResult() = default;
+
+TestResultsTracker::AggregateTestResult::AggregateTestResult(
+ const AggregateTestResult& other) = default;
+
+TestResultsTracker::AggregateTestResult::~AggregateTestResult() = default;
+
+TestResultsTracker::PerIterationData::PerIterationData() = default;
+
+TestResultsTracker::PerIterationData::PerIterationData(
+ const PerIterationData& other) = default;
+
+TestResultsTracker::PerIterationData::~PerIterationData() = default;
+
+} // namespace base
diff --git a/base/test/launcher/test_results_tracker.h b/base/test/launcher/test_results_tracker.h
new file mode 100644
index 0000000000..d89821d2af
--- /dev/null
+++ b/base/test/launcher/test_results_tracker.h
@@ -0,0 +1,149 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_LAUNCHER_TEST_RESULTS_TRACKER_H_
+#define BASE_TEST_LAUNCHER_TEST_RESULTS_TRACKER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/test/launcher/test_result.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+
+class CommandLine;
+class FilePath;
+
+// A helper class to output results.
+// Note: as currently XML is the only supported format by gtest, we don't
+// check output format (e.g. "xml:" prefix) here and output an XML file
+// unconditionally.
+// Note: we don't output per-test-case or total summary info like
+// total failed_test_count, disabled_test_count, elapsed_time and so on.
+// Only each test (testcase element in the XML) will have the correct
+// failed/disabled/elapsed_time information. Each test won't include
+// detailed failure messages either.
+class TestResultsTracker {
+ public:
+ TestResultsTracker();
+ ~TestResultsTracker();
+
+ // Initialize the result tracker. Must be called exactly once before
+ // calling any other methods. Returns true on success.
+ bool Init(const CommandLine& command_line) WARN_UNUSED_RESULT;
+
+ // Called when a test iteration is starting.
+ void OnTestIterationStarting();
+
+ // Adds |test_name| to the set of discovered tests (this includes all tests
+ // present in the executable, not necessarily run).
+ void AddTest(const std::string& test_name);
+
+ // Adds |test_name| to the set of disabled tests.
+ void AddDisabledTest(const std::string& test_name);
+
+ // Adds location for the |test_name|.
+ void AddTestLocation(const std::string& test_name,
+ const std::string& file,
+ int line);
+
+ // Adds |result| to the stored test results.
+ void AddTestResult(const TestResult& result);
+
+ // Prints a summary of current test iteration to stdout.
+ void PrintSummaryOfCurrentIteration() const;
+
+ // Prints a summary of all test iterations (not just the last one) to stdout.
+ void PrintSummaryOfAllIterations() const;
+
+ // Adds a string tag to the JSON summary. This is intended to indicate
+ // conditions that affect the entire test run, as opposed to individual tests.
+ void AddGlobalTag(const std::string& tag);
+
+ // Saves a JSON summary of all test iterations results to |path|. Adds
+ // |additional_tags| to the summary (just for this invocation). Returns
+ // true on success.
+ bool SaveSummaryAsJSON(
+ const FilePath& path,
+ const std::vector<std::string>& additional_tags) const WARN_UNUSED_RESULT;
+
+ // Map where keys are test result statuses, and values are sets of tests
+ // which finished with that status.
+ typedef std::map<TestResult::Status, std::set<std::string> > TestStatusMap;
+
+ // Returns a test status map (see above) for current test iteration.
+ TestStatusMap GetTestStatusMapForCurrentIteration() const;
+
+ // Returns a test status map (see above) for all test iterations.
+ TestStatusMap GetTestStatusMapForAllIterations() const;
+
+ private:
+ void GetTestStatusForIteration(int iteration, TestStatusMap* map) const;
+
+ template<typename InputIterator>
+ void PrintTests(InputIterator first,
+ InputIterator last,
+ const std::string& description) const;
+
+ struct AggregateTestResult {
+ AggregateTestResult();
+ AggregateTestResult(const AggregateTestResult& other);
+ ~AggregateTestResult();
+
+ std::vector<TestResult> test_results;
+ };
+
+ struct PerIterationData {
+ PerIterationData();
+ PerIterationData(const PerIterationData& other);
+ ~PerIterationData();
+
+ // Aggregate test results grouped by full test name.
+ typedef std::map<std::string, AggregateTestResult> ResultsMap;
+ ResultsMap results;
+ };
+
+ struct CodeLocation {
+ CodeLocation(const std::string& f, int l) : file(f), line(l) {
+ }
+
+ std::string file;
+ int line;
+ };
+
+ ThreadChecker thread_checker_;
+
+ // Set of global tags, i.e. strings indicating conditions that apply to
+ // the entire test run.
+ std::set<std::string> global_tags_;
+
+ // Set of all test names discovered in the current executable.
+ std::set<std::string> all_tests_;
+
+ std::map<std::string, CodeLocation> test_locations_;
+
+ // Set of all disabled tests in the current executable.
+ std::set<std::string> disabled_tests_;
+
+ // Store test results for each iteration.
+ std::vector<PerIterationData> per_iteration_data_;
+
+ // Index of current iteration (starting from 0). -1 before the first
+ // iteration.
+ int iteration_;
+
+ // File handle of output file (can be NULL if no file).
+ FILE* out_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestResultsTracker);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_TEST_RESULTS_TRACKER_H_
diff --git a/base/test/launcher/unit_test_launcher.cc b/base/test/launcher/unit_test_launcher.cc
new file mode 100644
index 0000000000..1d4439c681
--- /dev/null
+++ b/base/test/launcher/unit_test_launcher.cc
@@ -0,0 +1,750 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/launcher/unit_test_launcher.h"
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/debug/debugger.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/format_macros.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sequence_checker.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/sys_info.h"
+#include "base/test/gtest_xml_util.h"
+#include "base/test/launcher/test_launcher.h"
+#include "base/test/test_switches.h"
+#include "base/test/test_timeouts.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX)
+#include "base/files/file_descriptor_watcher_posix.h"
+#endif
+
+namespace base {
+
+namespace {
+
+// This constant controls how many tests are run in a single batch by default.
+const size_t kDefaultTestBatchLimit = 10;
+
+const char kHelpFlag[] = "help";
+
+// Flag to run all tests in a single process.
+const char kSingleProcessTestsFlag[] = "single-process-tests";
+
+void PrintUsage() {
+ fprintf(stdout,
+ "Runs tests using the gtest framework, each batch of tests being\n"
+ "run in their own process. Supported command-line flags:\n"
+ "\n"
+ " Common flags:\n"
+ " --gtest_filter=...\n"
+ " Runs a subset of tests (see --gtest_help for more info).\n"
+ "\n"
+ " --help\n"
+ " Shows this message.\n"
+ "\n"
+ " --gtest_help\n"
+ " Shows the gtest help message.\n"
+ "\n"
+ " --test-launcher-jobs=N\n"
+ " Sets the number of parallel test jobs to N.\n"
+ "\n"
+ " --single-process-tests\n"
+ " Runs the tests and the launcher in the same process. Useful\n"
+ " for debugging a specific test in a debugger.\n"
+ "\n"
+ " Other flags:\n"
+ " --test-launcher-filter-file=PATH\n"
+ " Like --gtest_filter, but read the test filter from PATH.\n"
+ " One pattern per line; lines starting with '-' are exclusions.\n"
+ " See also //testing/buildbot/filters/README.md file.\n"
+ "\n"
+ " --test-launcher-batch-limit=N\n"
+ " Sets the limit of test batch to run in a single process to N.\n"
+ "\n"
+ " --test-launcher-debug-launcher\n"
+ " Disables autodetection of debuggers and similar tools,\n"
+ " making it possible to use them to debug launcher itself.\n"
+ "\n"
+ " --test-launcher-retry-limit=N\n"
+ " Sets the limit of test retries on failures to N.\n"
+ "\n"
+ " --test-launcher-summary-output=PATH\n"
+ " Saves a JSON machine-readable summary of the run.\n"
+ "\n"
+ " --test-launcher-print-test-stdio=auto|always|never\n"
+ " Controls when full test output is printed.\n"
+ " auto means to print it when the test failed.\n"
+ "\n"
+ " --test-launcher-test-part-results-limit=N\n"
+ " Sets the limit of failed EXPECT/ASSERT entries in the xml and\n"
+ " JSON outputs per test to N (default N=10). Negative value \n"
+ " will disable this limit.\n"
+ "\n"
+ " --test-launcher-total-shards=N\n"
+ " Sets the total number of shards to N.\n"
+ "\n"
+ " --test-launcher-shard-index=N\n"
+ " Sets the shard index to run to N (from 0 to TOTAL - 1).\n");
+ fflush(stdout);
+}
+
+class DefaultUnitTestPlatformDelegate : public UnitTestPlatformDelegate {
+ public:
+ DefaultUnitTestPlatformDelegate() = default;
+
+ private:
+ // UnitTestPlatformDelegate:
+ bool GetTests(std::vector<TestIdentifier>* output) override {
+ *output = GetCompiledInTests();
+ return true;
+ }
+
+ bool CreateResultsFile(base::FilePath* path) override {
+ if (!CreateNewTempDirectory(FilePath::StringType(), path))
+ return false;
+ *path = path->AppendASCII("test_results.xml");
+ return true;
+ }
+
+ bool CreateTemporaryFile(base::FilePath* path) override {
+ if (!temp_dir_.IsValid() && !temp_dir_.CreateUniqueTempDir())
+ return false;
+ return CreateTemporaryFileInDir(temp_dir_.GetPath(), path);
+ }
+
+ CommandLine GetCommandLineForChildGTestProcess(
+ const std::vector<std::string>& test_names,
+ const base::FilePath& output_file,
+ const base::FilePath& flag_file) override {
+ CommandLine new_cmd_line(*CommandLine::ForCurrentProcess());
+
+ CHECK(base::PathExists(flag_file));
+
+ std::string long_flags(
+ std::string("--") + kGTestFilterFlag + "=" +
+ JoinString(test_names, ":"));
+ CHECK_EQ(static_cast<int>(long_flags.size()),
+ WriteFile(flag_file, long_flags.data(),
+ static_cast<int>(long_flags.size())));
+
+ new_cmd_line.AppendSwitchPath(switches::kTestLauncherOutput, output_file);
+ new_cmd_line.AppendSwitchPath(kGTestFlagfileFlag, flag_file);
+ new_cmd_line.AppendSwitch(kSingleProcessTestsFlag);
+
+ return new_cmd_line;
+ }
+
+ std::string GetWrapperForChildGTestProcess() override {
+ return std::string();
+ }
+
+ void RelaunchTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names,
+ int launch_flags) override {
+ // Relaunch requested tests in parallel, but only use single
+ // test per batch for more precise results (crashes, etc).
+ for (const std::string& test_name : test_names) {
+ std::vector<std::string> batch;
+ batch.push_back(test_name);
+ RunUnitTestsBatch(test_launcher, this, batch, launch_flags);
+ }
+ }
+
+ ScopedTempDir temp_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultUnitTestPlatformDelegate);
+};
+
+bool GetSwitchValueAsInt(const std::string& switch_name, int* result) {
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(switch_name))
+ return true;
+
+ std::string switch_value =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switch_name);
+ if (!StringToInt(switch_value, result) || *result < 0) {
+ LOG(ERROR) << "Invalid value for " << switch_name << ": " << switch_value;
+ return false;
+ }
+
+ return true;
+}
+
+int LaunchUnitTestsInternal(RunTestSuiteCallback run_test_suite,
+ size_t parallel_jobs,
+ int default_batch_limit,
+ bool use_job_objects,
+ OnceClosure gtest_init) {
+#if defined(OS_ANDROID)
+ // We can't easily fork on Android, just run the test suite directly.
+ return std::move(run_test_suite).Run();
+#else
+ bool force_single_process = false;
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherDebugLauncher)) {
+ fprintf(stdout, "Forcing test launcher debugging mode.\n");
+ fflush(stdout);
+ } else {
+ if (base::debug::BeingDebugged()) {
+ fprintf(stdout,
+ "Debugger detected, switching to single process mode.\n"
+ "Pass --test-launcher-debug-launcher to debug the launcher "
+ "itself.\n");
+ fflush(stdout);
+ force_single_process = true;
+ }
+ }
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(kGTestHelpFlag) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(kGTestListTestsFlag) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(kSingleProcessTestsFlag) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestChildProcess) ||
+ force_single_process) {
+ return std::move(run_test_suite).Run();
+ }
+#endif
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(kHelpFlag)) {
+ PrintUsage();
+ return 0;
+ }
+
+ TimeTicks start_time(TimeTicks::Now());
+
+ std::move(gtest_init).Run();
+ TestTimeouts::Initialize();
+
+ int batch_limit = default_batch_limit;
+ if (!GetSwitchValueAsInt(switches::kTestLauncherBatchLimit, &batch_limit))
+ return 1;
+
+ fprintf(stdout,
+ "IMPORTANT DEBUGGING NOTE: batches of tests are run inside their\n"
+ "own process. For debugging a test inside a debugger, use the\n"
+ "--gtest_filter=<your_test_name> flag along with\n"
+ "--single-process-tests.\n");
+ fflush(stdout);
+
+ MessageLoopForIO message_loop;
+#if defined(OS_POSIX)
+ FileDescriptorWatcher file_descriptor_watcher(&message_loop);
+#endif
+
+ DefaultUnitTestPlatformDelegate platform_delegate;
+ UnitTestLauncherDelegate delegate(
+ &platform_delegate, batch_limit, use_job_objects);
+ TestLauncher launcher(&delegate, parallel_jobs);
+ bool success = launcher.Run();
+
+ fprintf(stdout, "Tests took %" PRId64 " seconds.\n",
+ (TimeTicks::Now() - start_time).InSeconds());
+ fflush(stdout);
+
+ return (success ? 0 : 1);
+}
+
+void InitGoogleTestChar(int* argc, char** argv) {
+ testing::InitGoogleTest(argc, argv);
+}
+
+#if defined(OS_WIN)
+void InitGoogleTestWChar(int* argc, wchar_t** argv) {
+ testing::InitGoogleTest(argc, argv);
+}
+#endif // defined(OS_WIN)
+
+// Interprets test results and reports to the test launcher. Returns true
+// on success.
+bool ProcessTestResults(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names,
+ const base::FilePath& output_file,
+ const std::string& output,
+ int exit_code,
+ bool was_timeout,
+ std::vector<std::string>* tests_to_relaunch) {
+ std::vector<TestResult> test_results;
+ bool crashed = false;
+ bool have_test_results =
+ ProcessGTestOutput(output_file, &test_results, &crashed);
+
+ bool called_any_callback = false;
+
+ if (have_test_results) {
+ // TODO(phajdan.jr): Check for duplicates and mismatches between
+ // the results we got from XML file and tests we intended to run.
+ std::map<std::string, TestResult> results_map;
+ for (size_t i = 0; i < test_results.size(); i++)
+ results_map[test_results[i].full_name] = test_results[i];
+
+ bool had_interrupted_test = false;
+
+ // Results to be reported back to the test launcher.
+ std::vector<TestResult> final_results;
+
+ for (size_t i = 0; i < test_names.size(); i++) {
+ if (ContainsKey(results_map, test_names[i])) {
+ TestResult test_result = results_map[test_names[i]];
+ if (test_result.status == TestResult::TEST_CRASH) {
+ had_interrupted_test = true;
+
+ if (was_timeout) {
+ // Fix up the test status: we forcibly kill the child process
+ // after the timeout, so from XML results it looks just like
+ // a crash.
+ test_result.status = TestResult::TEST_TIMEOUT;
+ }
+ } else if (test_result.status == TestResult::TEST_SUCCESS ||
+ test_result.status == TestResult::TEST_FAILURE) {
+ // We run multiple tests in a batch with a timeout applied
+ // to the entire batch. It is possible that with other tests
+ // running quickly some tests take longer than the per-test timeout.
+ // For consistent handling of tests independent of order and other
+ // factors, mark them as timing out.
+ if (test_result.elapsed_time >
+ TestTimeouts::test_launcher_timeout()) {
+ test_result.status = TestResult::TEST_TIMEOUT;
+ }
+ }
+ test_result.output_snippet = GetTestOutputSnippet(test_result, output);
+ final_results.push_back(test_result);
+ } else if (had_interrupted_test) {
+ tests_to_relaunch->push_back(test_names[i]);
+ } else {
+ // TODO(phajdan.jr): Explicitly pass the info that the test didn't
+ // run for a mysterious reason.
+ LOG(ERROR) << "no test result for " << test_names[i];
+ TestResult test_result;
+ test_result.full_name = test_names[i];
+ test_result.status = TestResult::TEST_UNKNOWN;
+ test_result.output_snippet = GetTestOutputSnippet(test_result, output);
+ final_results.push_back(test_result);
+ }
+ }
+
+ // TODO(phajdan.jr): Handle the case where processing XML output
+ // indicates a crash but none of the test results is marked as crashing.
+
+ if (final_results.empty())
+ return false;
+
+ bool has_non_success_test = false;
+ for (size_t i = 0; i < final_results.size(); i++) {
+ if (final_results[i].status != TestResult::TEST_SUCCESS) {
+ has_non_success_test = true;
+ break;
+ }
+ }
+
+ if (!has_non_success_test && exit_code != 0) {
+ // This is a bit surprising case: all tests are marked as successful,
+ // but the exit code was not zero. This can happen e.g. under memory
+ // tools that report leaks this way. Mark all tests as a failure on exit,
+ // and for more precise info they'd need to be retried serially.
+ for (size_t i = 0; i < final_results.size(); i++)
+ final_results[i].status = TestResult::TEST_FAILURE_ON_EXIT;
+ }
+
+ for (size_t i = 0; i < final_results.size(); i++) {
+ // Fix the output snippet after possible changes to the test result.
+ final_results[i].output_snippet =
+ GetTestOutputSnippet(final_results[i], output);
+ test_launcher->OnTestFinished(final_results[i]);
+ called_any_callback = true;
+ }
+ } else {
+ fprintf(stdout,
+ "Failed to get out-of-band test success data, "
+ "dumping full stdio below:\n%s\n",
+ output.c_str());
+ fflush(stdout);
+
+ // We do not have reliable details about test results (parsing test
+ // stdout is known to be unreliable).
+ if (test_names.size() == 1) {
+ // There is only one test. Try to determine status by exit code.
+ const std::string& test_name = test_names.front();
+ TestResult test_result;
+ test_result.full_name = test_name;
+
+ if (was_timeout) {
+ test_result.status = TestResult::TEST_TIMEOUT;
+ } else if (exit_code != 0) {
+ test_result.status = TestResult::TEST_FAILURE;
+ } else {
+ // It's strange case when test executed successfully,
+ // but we failed to read machine-readable report for it.
+ test_result.status = TestResult::TEST_UNKNOWN;
+ }
+
+ test_launcher->OnTestFinished(test_result);
+ called_any_callback = true;
+ } else {
+ // There is more than one test. Retry them individually.
+ for (const std::string& test_name : test_names)
+ tests_to_relaunch->push_back(test_name);
+ }
+ }
+
+ return called_any_callback;
+}
+
+class UnitTestProcessLifetimeObserver : public ProcessLifetimeObserver {
+ public:
+ ~UnitTestProcessLifetimeObserver() override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ }
+
+ TestLauncher* test_launcher() { return test_launcher_; }
+ UnitTestPlatformDelegate* platform_delegate() { return platform_delegate_; }
+ const std::vector<std::string>& test_names() { return test_names_; }
+ int launch_flags() { return launch_flags_; }
+ const FilePath& output_file() { return output_file_; }
+ const FilePath& flag_file() { return flag_file_; }
+
+ protected:
+ UnitTestProcessLifetimeObserver(TestLauncher* test_launcher,
+ UnitTestPlatformDelegate* platform_delegate,
+ const std::vector<std::string>& test_names,
+ int launch_flags,
+ const FilePath& output_file,
+ const FilePath& flag_file)
+ : ProcessLifetimeObserver(),
+ test_launcher_(test_launcher),
+ platform_delegate_(platform_delegate),
+ test_names_(test_names),
+ launch_flags_(launch_flags),
+ output_file_(output_file),
+ flag_file_(flag_file) {}
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ private:
+ TestLauncher* const test_launcher_;
+ UnitTestPlatformDelegate* const platform_delegate_;
+ const std::vector<std::string> test_names_;
+ const int launch_flags_;
+ const FilePath output_file_;
+ const FilePath flag_file_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnitTestProcessLifetimeObserver);
+};
+
+class ParallelUnitTestProcessLifetimeObserver
+ : public UnitTestProcessLifetimeObserver {
+ public:
+ ParallelUnitTestProcessLifetimeObserver(
+ TestLauncher* test_launcher,
+ UnitTestPlatformDelegate* platform_delegate,
+ const std::vector<std::string>& test_names,
+ int launch_flags,
+ const FilePath& output_file,
+ const FilePath& flag_file)
+ : UnitTestProcessLifetimeObserver(test_launcher,
+ platform_delegate,
+ test_names,
+ launch_flags,
+ output_file,
+ flag_file) {}
+ ~ParallelUnitTestProcessLifetimeObserver() override = default;
+
+ private:
+ // ProcessLifetimeObserver:
+ void OnCompleted(int exit_code,
+ TimeDelta elapsed_time,
+ bool was_timeout,
+ const std::string& output) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ParallelUnitTestProcessLifetimeObserver);
+};
+
+void ParallelUnitTestProcessLifetimeObserver::OnCompleted(
+ int exit_code,
+ TimeDelta elapsed_time,
+ bool was_timeout,
+ const std::string& output) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ std::vector<std::string> tests_to_relaunch;
+ ProcessTestResults(test_launcher(), test_names(), output_file(), output,
+ exit_code, was_timeout, &tests_to_relaunch);
+
+ if (!tests_to_relaunch.empty()) {
+ platform_delegate()->RelaunchTests(test_launcher(), tests_to_relaunch,
+ launch_flags());
+ }
+
+ // The temporary file's directory is also temporary.
+ DeleteFile(output_file().DirName(), true);
+ if (!flag_file().empty())
+ DeleteFile(flag_file(), false);
+}
+
+class SerialUnitTestProcessLifetimeObserver
+ : public UnitTestProcessLifetimeObserver {
+ public:
+ SerialUnitTestProcessLifetimeObserver(
+ TestLauncher* test_launcher,
+ UnitTestPlatformDelegate* platform_delegate,
+ const std::vector<std::string>& test_names,
+ int launch_flags,
+ const FilePath& output_file,
+ const FilePath& flag_file,
+ std::vector<std::string>&& next_test_names)
+ : UnitTestProcessLifetimeObserver(test_launcher,
+ platform_delegate,
+ test_names,
+ launch_flags,
+ output_file,
+ flag_file),
+ next_test_names_(std::move(next_test_names)) {}
+ ~SerialUnitTestProcessLifetimeObserver() override = default;
+
+ private:
+ // ProcessLifetimeObserver:
+ void OnCompleted(int exit_code,
+ TimeDelta elapsed_time,
+ bool was_timeout,
+ const std::string& output) override;
+
+ std::vector<std::string> next_test_names_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerialUnitTestProcessLifetimeObserver);
+};
+
+void SerialUnitTestProcessLifetimeObserver::OnCompleted(
+ int exit_code,
+ TimeDelta elapsed_time,
+ bool was_timeout,
+ const std::string& output) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ std::vector<std::string> tests_to_relaunch;
+ bool called_any_callbacks =
+ ProcessTestResults(test_launcher(), test_names(), output_file(), output,
+ exit_code, was_timeout, &tests_to_relaunch);
+
+ // There is only one test, there cannot be other tests to relaunch
+ // due to a crash.
+ DCHECK(tests_to_relaunch.empty());
+
+ // There is only one test, we should have called back with its result.
+ DCHECK(called_any_callbacks);
+
+ // The temporary file's directory is also temporary.
+ DeleteFile(output_file().DirName(), true);
+
+ if (!flag_file().empty())
+ DeleteFile(flag_file(), false);
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&RunUnitTestsSerially, test_launcher(), platform_delegate(),
+ std::move(next_test_names_), launch_flags()));
+}
+
+} // namespace
+
+int LaunchUnitTests(int argc,
+ char** argv,
+ RunTestSuiteCallback run_test_suite) {
+ CommandLine::Init(argc, argv);
+ size_t parallel_jobs = NumParallelJobs();
+ if (parallel_jobs == 0U) {
+ return 1;
+ }
+ return LaunchUnitTestsInternal(std::move(run_test_suite), parallel_jobs,
+ kDefaultTestBatchLimit, true,
+ BindOnce(&InitGoogleTestChar, &argc, argv));
+}
+
+int LaunchUnitTestsSerially(int argc,
+ char** argv,
+ RunTestSuiteCallback run_test_suite) {
+ CommandLine::Init(argc, argv);
+ return LaunchUnitTestsInternal(std::move(run_test_suite), 1U,
+ kDefaultTestBatchLimit, true,
+ BindOnce(&InitGoogleTestChar, &argc, argv));
+}
+
+int LaunchUnitTestsWithOptions(int argc,
+ char** argv,
+ size_t parallel_jobs,
+ int default_batch_limit,
+ bool use_job_objects,
+ RunTestSuiteCallback run_test_suite) {
+ CommandLine::Init(argc, argv);
+ return LaunchUnitTestsInternal(std::move(run_test_suite), parallel_jobs,
+ default_batch_limit, use_job_objects,
+ BindOnce(&InitGoogleTestChar, &argc, argv));
+}
+
+#if defined(OS_WIN)
+int LaunchUnitTests(int argc,
+ wchar_t** argv,
+ bool use_job_objects,
+ RunTestSuiteCallback run_test_suite) {
+ // Windows CommandLine::Init ignores argv anyway.
+ CommandLine::Init(argc, NULL);
+ size_t parallel_jobs = NumParallelJobs();
+ if (parallel_jobs == 0U) {
+ return 1;
+ }
+ return LaunchUnitTestsInternal(std::move(run_test_suite), parallel_jobs,
+ kDefaultTestBatchLimit, use_job_objects,
+ BindOnce(&InitGoogleTestWChar, &argc, argv));
+}
+#endif // defined(OS_WIN)
+
+void RunUnitTestsSerially(
+ TestLauncher* test_launcher,
+ UnitTestPlatformDelegate* platform_delegate,
+ const std::vector<std::string>& test_names,
+ int launch_flags) {
+ if (test_names.empty())
+ return;
+
+ // Create a dedicated temporary directory to store the xml result data
+ // per run to ensure clean state and make it possible to launch multiple
+ // processes in parallel.
+ FilePath output_file;
+ CHECK(platform_delegate->CreateResultsFile(&output_file));
+ FilePath flag_file;
+ platform_delegate->CreateTemporaryFile(&flag_file);
+
+ auto observer = std::make_unique<SerialUnitTestProcessLifetimeObserver>(
+ test_launcher, platform_delegate,
+ std::vector<std::string>(1, test_names.back()), launch_flags, output_file,
+ flag_file,
+ std::vector<std::string>(test_names.begin(), test_names.end() - 1));
+
+ CommandLine cmd_line(platform_delegate->GetCommandLineForChildGTestProcess(
+ observer->test_names(), output_file, flag_file));
+
+ TestLauncher::LaunchOptions launch_options;
+ launch_options.flags = launch_flags;
+ test_launcher->LaunchChildGTestProcess(
+ cmd_line, platform_delegate->GetWrapperForChildGTestProcess(),
+ TestTimeouts::test_launcher_timeout(), launch_options,
+ std::move(observer));
+}
+
+void RunUnitTestsBatch(
+ TestLauncher* test_launcher,
+ UnitTestPlatformDelegate* platform_delegate,
+ const std::vector<std::string>& test_names,
+ int launch_flags) {
+ if (test_names.empty())
+ return;
+
+ // Create a dedicated temporary directory to store the xml result data
+ // per run to ensure clean state and make it possible to launch multiple
+ // processes in parallel.
+ FilePath output_file;
+ CHECK(platform_delegate->CreateResultsFile(&output_file));
+ FilePath flag_file;
+ platform_delegate->CreateTemporaryFile(&flag_file);
+
+ auto observer = std::make_unique<ParallelUnitTestProcessLifetimeObserver>(
+ test_launcher, platform_delegate, test_names, launch_flags, output_file,
+ flag_file);
+
+ CommandLine cmd_line(platform_delegate->GetCommandLineForChildGTestProcess(
+ test_names, output_file, flag_file));
+
+ // Adjust the timeout depending on how many tests we're running
+ // (note that e.g. the last batch of tests will be smaller).
+ // TODO(phajdan.jr): Consider an adaptive timeout, which can change
+ // depending on how many tests ran and how many remain.
+ // Note: do NOT parse child's stdout to do that, it's known to be
+ // unreliable (e.g. buffering issues can mix up the output).
+ TimeDelta timeout = test_names.size() * TestTimeouts::test_launcher_timeout();
+
+ TestLauncher::LaunchOptions options;
+ options.flags = launch_flags;
+ test_launcher->LaunchChildGTestProcess(
+ cmd_line, platform_delegate->GetWrapperForChildGTestProcess(), timeout,
+ options, std::move(observer));
+}
+
+UnitTestLauncherDelegate::UnitTestLauncherDelegate(
+ UnitTestPlatformDelegate* platform_delegate,
+ size_t batch_limit,
+ bool use_job_objects)
+ : platform_delegate_(platform_delegate),
+ batch_limit_(batch_limit),
+ use_job_objects_(use_job_objects) {
+}
+
+UnitTestLauncherDelegate::~UnitTestLauncherDelegate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+bool UnitTestLauncherDelegate::GetTests(std::vector<TestIdentifier>* output) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return platform_delegate_->GetTests(output);
+}
+
+bool UnitTestLauncherDelegate::ShouldRunTest(const std::string& test_case_name,
+ const std::string& test_name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // There is no additional logic to disable specific tests.
+ return true;
+}
+
+size_t UnitTestLauncherDelegate::RunTests(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ int launch_flags = use_job_objects_ ? TestLauncher::USE_JOB_OBJECTS : 0;
+
+ std::vector<std::string> batch;
+ for (size_t i = 0; i < test_names.size(); i++) {
+ batch.push_back(test_names[i]);
+
+ // Use 0 to indicate unlimited batch size.
+ if (batch.size() >= batch_limit_ && batch_limit_ != 0) {
+ RunUnitTestsBatch(test_launcher, platform_delegate_, batch, launch_flags);
+ batch.clear();
+ }
+ }
+
+ RunUnitTestsBatch(test_launcher, platform_delegate_, batch, launch_flags);
+
+ return test_names.size();
+}
+
+size_t UnitTestLauncherDelegate::RetryTests(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) {
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&RunUnitTestsSerially, test_launcher, platform_delegate_,
+ test_names,
+ use_job_objects_ ? TestLauncher::USE_JOB_OBJECTS : 0));
+ return test_names.size();
+}
+
+} // namespace base
diff --git a/base/test/launcher/unit_test_launcher.h b/base/test/launcher/unit_test_launcher.h
new file mode 100644
index 0000000000..0d1c21e084
--- /dev/null
+++ b/base/test/launcher/unit_test_launcher.h
@@ -0,0 +1,134 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_LAUNCHER_UNIT_TEST_LAUNCHER_H_
+#define BASE_TEST_LAUNCHER_UNIT_TEST_LAUNCHER_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/test/launcher/test_launcher.h"
+#include "build/build_config.h"
+
+namespace base {
+
+// Callback that runs a test suite and returns exit code.
+using RunTestSuiteCallback = OnceCallback<int(void)>;
+
+// Launches unit tests in given test suite. Returns exit code.
+int LaunchUnitTests(int argc, char** argv, RunTestSuiteCallback run_test_suite);
+
+// Same as above, but always runs tests serially.
+int LaunchUnitTestsSerially(int argc,
+ char** argv,
+ RunTestSuiteCallback run_test_suite);
+
+// Launches unit tests in given test suite. Returns exit code.
+// |parallel_jobs| is the number of parallel test jobs.
+// |default_batch_limit| is the default size of test batch
+// (use 0 to disable batching).
+// |use_job_objects| determines whether to use job objects.
+int LaunchUnitTestsWithOptions(int argc,
+ char** argv,
+ size_t parallel_jobs,
+ int default_batch_limit,
+ bool use_job_objects,
+ RunTestSuiteCallback run_test_suite);
+
+#if defined(OS_WIN)
+// Launches unit tests in given test suite. Returns exit code.
+// |use_job_objects| determines whether to use job objects.
+int LaunchUnitTests(int argc,
+ wchar_t** argv,
+ bool use_job_objects,
+ RunTestSuiteCallback run_test_suite);
+#endif // defined(OS_WIN)
+
+// Delegate to abstract away platform differences for unit tests.
+class UnitTestPlatformDelegate {
+ public:
+ // Called to get names of tests available for running. The delegate
+ // must put the result in |output| and return true on success.
+ virtual bool GetTests(std::vector<TestIdentifier>* output) = 0;
+
+ // Called to create a temporary for storing test results. The delegate
+ // must put the resulting path in |path| and return true on success.
+ virtual bool CreateResultsFile(base::FilePath* path) = 0;
+
+ // Called to create a new temporary file. The delegate must put the resulting
+ // path in |path| and return true on success.
+ virtual bool CreateTemporaryFile(base::FilePath* path) = 0;
+
+ // Returns command line for child GTest process based on the command line
+ // of current process. |test_names| is a vector of test full names
+ // (e.g. "A.B"), |output_file| is path to the GTest XML output file.
+ virtual CommandLine GetCommandLineForChildGTestProcess(
+ const std::vector<std::string>& test_names,
+ const base::FilePath& output_file,
+ const base::FilePath& flag_file) = 0;
+
+ // Returns wrapper to use for child GTest process. Empty string means
+ // no wrapper.
+ virtual std::string GetWrapperForChildGTestProcess() = 0;
+
+ // Relaunch tests, e.g. after a crash.
+ virtual void RelaunchTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names,
+ int launch_flags) = 0;
+
+ protected:
+ ~UnitTestPlatformDelegate() = default;
+};
+
+// Runs tests serially, each in its own process.
+void RunUnitTestsSerially(TestLauncher* test_launcher,
+ UnitTestPlatformDelegate* platform_delegate,
+ const std::vector<std::string>& test_names,
+ int launch_flags);
+
+// Runs tests in batches (each batch in its own process).
+void RunUnitTestsBatch(TestLauncher* test_launcher,
+ UnitTestPlatformDelegate* platform_delegate,
+ const std::vector<std::string>& test_names,
+ int launch_flags);
+
+// Test launcher delegate for unit tests (mostly to support batching).
+class UnitTestLauncherDelegate : public TestLauncherDelegate {
+ public:
+ UnitTestLauncherDelegate(UnitTestPlatformDelegate* delegate,
+ size_t batch_limit,
+ bool use_job_objects);
+ ~UnitTestLauncherDelegate() override;
+
+ private:
+ // TestLauncherDelegate:
+ bool GetTests(std::vector<TestIdentifier>* output) override;
+ bool ShouldRunTest(const std::string& test_case_name,
+ const std::string& test_name) override;
+ size_t RunTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) override;
+ size_t RetryTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) override;
+
+ ThreadChecker thread_checker_;
+
+ UnitTestPlatformDelegate* platform_delegate_;
+
+ // Maximum number of tests to run in a single batch.
+ size_t batch_limit_;
+
+ // Determines whether we use job objects on Windows.
+ bool use_job_objects_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnitTestLauncherDelegate);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_UNIT_TEST_LAUNCHER_H_
diff --git a/base/test/malloc_wrapper.cc b/base/test/malloc_wrapper.cc
new file mode 100644
index 0000000000..eb280a3eee
--- /dev/null
+++ b/base/test/malloc_wrapper.cc
@@ -0,0 +1,11 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "malloc_wrapper.h"
+
+#include <stdlib.h>
+
+void* MallocWrapper(size_t size) {
+ return malloc(size);
+}
diff --git a/base/test/malloc_wrapper.h b/base/test/malloc_wrapper.h
new file mode 100644
index 0000000000..d06228d37d
--- /dev/null
+++ b/base/test/malloc_wrapper.h
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_MALLOC_WRAPPER_H_
+#define BASE_TEST_MALLOC_WRAPPER_H_
+
+#include <stddef.h>
+
+// BASE_EXPORT depends on COMPONENT_BUILD.
+// This will always be a separate shared library, so don't use BASE_EXPORT here.
+#if defined(WIN32)
+#define MALLOC_WRAPPER_EXPORT __declspec(dllexport)
+#else
+#define MALLOC_WRAPPER_EXPORT __attribute__((visibility("default")))
+#endif // defined(WIN32)
+
+// Calls malloc directly.
+MALLOC_WRAPPER_EXPORT void* MallocWrapper(size_t size);
+
+#endif // BASE_TEST_MALLOC_WRAPPER_H_
diff --git a/base/test/metrics/histogram_tester_unittest.cc b/base/test/metrics/histogram_tester_unittest.cc
new file mode 100644
index 0000000000..e9b9f20fbc
--- /dev/null
+++ b/base/test/metrics/histogram_tester_unittest.cc
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/metrics/histogram_tester.h"
+
+#include <memory>
+#include <string>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_samples.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+
+namespace {
+
+const char kHistogram1[] = "Test1";
+const char kHistogram2[] = "Test2";
+const char kHistogram3[] = "Test3";
+const char kHistogram4[] = "Test4";
+const char kHistogram5[] = "Test5";
+
+} // namespace
+
+namespace base {
+
+typedef testing::Test HistogramTesterTest;
+
+TEST_F(HistogramTesterTest, Scope) {
+ // Record a histogram before the creation of the recorder.
+ UMA_HISTOGRAM_BOOLEAN(kHistogram1, true);
+
+ HistogramTester tester;
+
+ // Verify that no histogram is recorded.
+ tester.ExpectTotalCount(kHistogram1, 0);
+
+ // Record a histogram after the creation of the recorder.
+ UMA_HISTOGRAM_BOOLEAN(kHistogram1, true);
+
+ // Verify that one histogram is recorded.
+ std::unique_ptr<HistogramSamples> samples(
+ tester.GetHistogramSamplesSinceCreation(kHistogram1));
+ EXPECT_TRUE(samples);
+ EXPECT_EQ(1, samples->TotalCount());
+}
+
+TEST_F(HistogramTesterTest, GetHistogramSamplesSinceCreationNotNull) {
+ // Chose the histogram name uniquely, to ensure nothing was recorded for it so
+ // far.
+ static const char kHistogram[] =
+ "GetHistogramSamplesSinceCreationNotNullHistogram";
+ HistogramTester tester;
+
+ // Verify that the returned samples are empty but not null.
+ std::unique_ptr<HistogramSamples> samples(
+ tester.GetHistogramSamplesSinceCreation(kHistogram1));
+ EXPECT_TRUE(samples);
+ tester.ExpectTotalCount(kHistogram, 0);
+}
+
+TEST_F(HistogramTesterTest, TestUniqueSample) {
+ HistogramTester tester;
+
+ // Record into a sample thrice
+ UMA_HISTOGRAM_COUNTS_100(kHistogram2, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram2, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram2, 2);
+
+ tester.ExpectUniqueSample(kHistogram2, 2, 3);
+}
+
+TEST_F(HistogramTesterTest, TestBucketsSample) {
+ HistogramTester tester;
+
+ // Record into a sample twice
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 3);
+
+ tester.ExpectBucketCount(kHistogram3, 2, 4);
+ tester.ExpectBucketCount(kHistogram3, 3, 1);
+
+ tester.ExpectTotalCount(kHistogram3, 5);
+}
+
+TEST_F(HistogramTesterTest, TestBucketsSampleWithScope) {
+ // Record into a sample twice, once before the tester creation and once after.
+ UMA_HISTOGRAM_COUNTS_100(kHistogram4, 2);
+
+ HistogramTester tester;
+ UMA_HISTOGRAM_COUNTS_100(kHistogram4, 3);
+
+ tester.ExpectBucketCount(kHistogram4, 2, 0);
+ tester.ExpectBucketCount(kHistogram4, 3, 1);
+
+ tester.ExpectTotalCount(kHistogram4, 1);
+}
+
+TEST_F(HistogramTesterTest, TestGetAllSamples) {
+ HistogramTester tester;
+ UMA_HISTOGRAM_ENUMERATION(kHistogram5, 2, 5);
+ UMA_HISTOGRAM_ENUMERATION(kHistogram5, 3, 5);
+ UMA_HISTOGRAM_ENUMERATION(kHistogram5, 3, 5);
+ UMA_HISTOGRAM_ENUMERATION(kHistogram5, 5, 5);
+
+ EXPECT_THAT(tester.GetAllSamples(kHistogram5),
+ ElementsAre(Bucket(2, 1), Bucket(3, 2), Bucket(5, 1)));
+}
+
+TEST_F(HistogramTesterTest, TestGetAllSamples_NoSamples) {
+ HistogramTester tester;
+ EXPECT_THAT(tester.GetAllSamples(kHistogram5), IsEmpty());
+}
+
+TEST_F(HistogramTesterTest, TestGetTotalCountsForPrefix) {
+ HistogramTester tester;
+ UMA_HISTOGRAM_ENUMERATION("Test1.Test2.Test3", 2, 5);
+
+ // Regression check for bug https://crbug.com/659977.
+ EXPECT_TRUE(tester.GetTotalCountsForPrefix("Test2.").empty());
+
+ EXPECT_EQ(1u, tester.GetTotalCountsForPrefix("Test1.").size());
+}
+
+TEST_F(HistogramTesterTest, TestGetAllChangedHistograms) {
+ // Record into a sample twice, once before the tester creation.
+ UMA_HISTOGRAM_COUNTS_100(kHistogram1, true);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram4, 4);
+
+ HistogramTester tester;
+ UMA_HISTOGRAM_COUNTS_100(kHistogram4, 3);
+
+ UMA_HISTOGRAM_ENUMERATION(kHistogram5, 2, 5);
+ UMA_HISTOGRAM_ENUMERATION(kHistogram5, 3, 5);
+ UMA_HISTOGRAM_ENUMERATION(kHistogram5, 4, 5);
+ UMA_HISTOGRAM_ENUMERATION(kHistogram5, 5, 5);
+
+ UMA_HISTOGRAM_ENUMERATION("Test1.Test2.Test3", 2, 5);
+ std::string results = tester.GetAllHistogramsRecorded();
+
+ EXPECT_EQ(std::string::npos, results.find("Histogram: Test1 recorded"));
+ EXPECT_NE(std::string::npos,
+ results.find("Histogram: Test4 recorded 1 new samples"));
+ EXPECT_NE(std::string::npos,
+ results.find("Histogram: Test5 recorded 4 new samples"));
+ EXPECT_NE(
+ std::string::npos,
+ results.find("Histogram: Test1.Test2.Test3 recorded 1 new samples"));
+}
+
+} // namespace base
diff --git a/base/test/metrics/user_action_tester.cc b/base/test/metrics/user_action_tester.cc
new file mode 100644
index 0000000000..a845bca78d
--- /dev/null
+++ b/base/test/metrics/user_action_tester.cc
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/metrics/user_action_tester.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/test/test_simple_task_runner.h"
+
+namespace base {
+
+UserActionTester::UserActionTester()
+ : task_runner_(new base::TestSimpleTaskRunner),
+ action_callback_(
+ base::Bind(&UserActionTester::OnUserAction, base::Unretained(this))) {
+ base::SetRecordActionTaskRunner(task_runner_);
+ base::AddActionCallback(action_callback_);
+}
+
+UserActionTester::~UserActionTester() {
+ base::RemoveActionCallback(action_callback_);
+}
+
+int UserActionTester::GetActionCount(const std::string& user_action) const {
+ UserActionCountMap::const_iterator iter = count_map_.find(user_action);
+ return iter == count_map_.end() ? 0 : iter->second;
+}
+
+void UserActionTester::ResetCounts() {
+ count_map_.clear();
+}
+
+void UserActionTester::OnUserAction(const std::string& user_action) {
+ ++(count_map_[user_action]);
+}
+
+} // namespace base
diff --git a/base/test/metrics/user_action_tester.h b/base/test/metrics/user_action_tester.h
new file mode 100644
index 0000000000..a422c9a8e9
--- /dev/null
+++ b/base/test/metrics/user_action_tester.h
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_METRICS_USER_ACTION_TESTER_H_
+#define BASE_TEST_METRICS_USER_ACTION_TESTER_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/metrics/user_metrics.h"
+
+namespace base {
+
+// This class observes and collects user action notifications that are sent
+// by the tests, so that they can be examined afterwards for correctness.
+// Note: This class is NOT thread-safe.
+class UserActionTester {
+ public:
+ UserActionTester();
+ ~UserActionTester();
+
+ // Returns the number of times the given |user_action| occurred.
+ int GetActionCount(const std::string& user_action) const;
+
+ // Resets all user action counts to 0.
+ void ResetCounts();
+
+ private:
+ typedef std::map<std::string, int> UserActionCountMap;
+
+ // The callback that is notified when a user actions occurs.
+ void OnUserAction(const std::string& user_action);
+
+ // A map that tracks the number of times a user action has occurred.
+ UserActionCountMap count_map_;
+
+ // A test task runner used by user metrics.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // The callback that is added to the global action callback list.
+ base::ActionCallback action_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserActionTester);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_METRICS_USER_ACTION_TESTER_H_
diff --git a/base/test/metrics/user_action_tester_unittest.cc b/base/test/metrics/user_action_tester_unittest.cc
new file mode 100644
index 0000000000..14557d24e4
--- /dev/null
+++ b/base/test/metrics/user_action_tester_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/metrics/user_action_tester.h"
+
+#include "base/metrics/user_metrics.h"
+#include "base/metrics/user_metrics_action.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+const char kUserAction1[] = "user.action.1";
+const char kUserAction2[] = "user.action.2";
+const char kUserAction3[] = "user.action.3";
+
+// Record an action and cause all ActionCallback observers to be notified.
+void RecordAction(const char user_action[]) {
+ base::RecordAction(base::UserMetricsAction(user_action));
+}
+
+} // namespace
+
+// Verify user action counts are zero initially.
+TEST(UserActionTesterTest, GetActionCountWhenNoActionsHaveBeenRecorded) {
+ UserActionTester user_action_tester;
+ EXPECT_EQ(0, user_action_tester.GetActionCount(kUserAction1));
+}
+
+// Verify user action counts are tracked properly.
+TEST(UserActionTesterTest, GetActionCountWhenActionsHaveBeenRecorded) {
+ UserActionTester user_action_tester;
+
+ RecordAction(kUserAction1);
+ RecordAction(kUserAction2);
+ RecordAction(kUserAction2);
+
+ EXPECT_EQ(1, user_action_tester.GetActionCount(kUserAction1));
+ EXPECT_EQ(2, user_action_tester.GetActionCount(kUserAction2));
+ EXPECT_EQ(0, user_action_tester.GetActionCount(kUserAction3));
+}
+
+// Verify no seg faults occur when resetting action counts when none have been
+// recorded.
+TEST(UserActionTesterTest, ResetCountsWhenNoActionsHaveBeenRecorded) {
+ UserActionTester user_action_tester;
+ user_action_tester.ResetCounts();
+}
+
+// Verify user action counts are set to zero on a ResetCounts.
+TEST(UserActionTesterTest, ResetCountsWhenActionsHaveBeenRecorded) {
+ UserActionTester user_action_tester;
+
+ RecordAction(kUserAction1);
+ RecordAction(kUserAction1);
+ RecordAction(kUserAction2);
+ user_action_tester.ResetCounts();
+
+ EXPECT_EQ(0, user_action_tester.GetActionCount(kUserAction1));
+ EXPECT_EQ(0, user_action_tester.GetActionCount(kUserAction2));
+ EXPECT_EQ(0, user_action_tester.GetActionCount(kUserAction3));
+}
+
+// Verify the UserActionsTester is notified when base::RecordAction is called.
+TEST(UserActionTesterTest, VerifyUserActionTesterListensForUserActions) {
+ UserActionTester user_action_tester;
+
+ base::RecordAction(base::UserMetricsAction(kUserAction1));
+
+ EXPECT_EQ(1, user_action_tester.GetActionCount(kUserAction1));
+}
+
+// Verify the UserActionsTester is notified when base::RecordComputedAction is
+// called.
+TEST(UserActionTesterTest,
+ VerifyUserActionTesterListensForComputedUserActions) {
+ UserActionTester user_action_tester;
+
+ base::RecordComputedAction(kUserAction1);
+
+ EXPECT_EQ(1, user_action_tester.GetActionCount(kUserAction1));
+}
+
+} // namespace base
diff --git a/base/test/mock_callback.h b/base/test/mock_callback.h
new file mode 100644
index 0000000000..7ac4d3467e
--- /dev/null
+++ b/base/test/mock_callback.h
@@ -0,0 +1,366 @@
+// This file was GENERATED by command:
+// pump.py mock_callback.h.pump
+// DO NOT EDIT BY HAND!!!
+
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Analogous to GMock's built-in MockFunction, but for base::Callback instead of
+// std::function. It takes the full callback type as a parameter, so that it can
+// support both OnceCallback and RepeatingCallback.
+//
+// Use:
+// using FooCallback = base::Callback<int(std::string)>;
+//
+// TEST(FooTest, RunsCallbackWithBarArgument) {
+// base::MockCallback<FooCallback> callback;
+// EXPECT_CALL(callback, Run("bar")).WillOnce(Return(1));
+// Foo(callback.Get());
+// }
+//
+// Can be used with StrictMock and NiceMock. Caller must ensure that it outlives
+// any base::Callback obtained from it.
+
+#ifndef BASE_TEST_MOCK_CALLBACK_H_
+#define BASE_TEST_MOCK_CALLBACK_H_
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+
+// clang-format off
+
+template <typename F>
+class MockCallback;
+
+template <typename R>
+class MockCallback<Callback<R()>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD0_T(Run, R());
+
+ Callback<R()> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R>
+class MockCallback<OnceCallback<R()>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD0_T(Run, R());
+
+ OnceCallback<R()> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1>
+class MockCallback<Callback<R(A1)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD1_T(Run, R(A1));
+
+ Callback<R(A1)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1>
+class MockCallback<OnceCallback<R(A1)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD1_T(Run, R(A1));
+
+ OnceCallback<R(A1)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2>
+class MockCallback<Callback<R(A1, A2)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD2_T(Run, R(A1, A2));
+
+ Callback<R(A1, A2)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2>
+class MockCallback<OnceCallback<R(A1, A2)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD2_T(Run, R(A1, A2));
+
+ OnceCallback<R(A1, A2)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3>
+class MockCallback<Callback<R(A1, A2, A3)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD3_T(Run, R(A1, A2, A3));
+
+ Callback<R(A1, A2, A3)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3>
+class MockCallback<OnceCallback<R(A1, A2, A3)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD3_T(Run, R(A1, A2, A3));
+
+ OnceCallback<R(A1, A2, A3)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4>
+class MockCallback<Callback<R(A1, A2, A3, A4)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD4_T(Run, R(A1, A2, A3, A4));
+
+ Callback<R(A1, A2, A3, A4)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4>
+class MockCallback<OnceCallback<R(A1, A2, A3, A4)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD4_T(Run, R(A1, A2, A3, A4));
+
+ OnceCallback<R(A1, A2, A3, A4)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5>
+class MockCallback<Callback<R(A1, A2, A3, A4, A5)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD5_T(Run, R(A1, A2, A3, A4, A5));
+
+ Callback<R(A1, A2, A3, A4, A5)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5>
+class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD5_T(Run, R(A1, A2, A3, A4, A5));
+
+ OnceCallback<R(A1, A2, A3, A4, A5)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6>
+class MockCallback<Callback<R(A1, A2, A3, A4, A5, A6)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD6_T(Run, R(A1, A2, A3, A4, A5, A6));
+
+ Callback<R(A1, A2, A3, A4, A5, A6)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6>
+class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD6_T(Run, R(A1, A2, A3, A4, A5, A6));
+
+ OnceCallback<R(A1, A2, A3, A4, A5, A6)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6, typename A7>
+class MockCallback<Callback<R(A1, A2, A3, A4, A5, A6, A7)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD7_T(Run, R(A1, A2, A3, A4, A5, A6, A7));
+
+ Callback<R(A1, A2, A3, A4, A5, A6, A7)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6, typename A7>
+class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6, A7)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD7_T(Run, R(A1, A2, A3, A4, A5, A6, A7));
+
+ OnceCallback<R(A1, A2, A3, A4, A5, A6, A7)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6, typename A7, typename A8>
+class MockCallback<Callback<R(A1, A2, A3, A4, A5, A6, A7, A8)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD8_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8));
+
+ Callback<R(A1, A2, A3, A4, A5, A6, A7, A8)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6, typename A7, typename A8>
+class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD8_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8));
+
+ OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6, typename A7, typename A8, typename A9>
+class MockCallback<Callback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD9_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8, A9));
+
+ Callback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6, typename A7, typename A8, typename A9>
+class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD9_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8, A9));
+
+ OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6, typename A7, typename A8, typename A9,
+ typename A10>
+class MockCallback<Callback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD10_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10));
+
+ Callback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R, typename A1, typename A2, typename A3, typename A4,
+ typename A5, typename A6, typename A7, typename A8, typename A9,
+ typename A10>
+class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD10_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10));
+
+ OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+// clang-format on
+
+} // namespace base
+
+#endif // BASE_TEST_MOCK_CALLBACK_H_
diff --git a/base/test/mock_callback.h.pump b/base/test/mock_callback.h.pump
new file mode 100644
index 0000000000..3372789db5
--- /dev/null
+++ b/base/test/mock_callback.h.pump
@@ -0,0 +1,85 @@
+$$ This is a pump file for generating file templates. Pump is a python
+$$ script that is part of the Google Test suite of utilities. Description
+$$ can be found here:
+$$
+$$ https://github.com/google/googletest/blob/master/googletest/docs/PumpManual.md
+$$
+$$ MAX_ARITY controls the number of arguments that MockCallback supports.
+$$ It is choosen to match the number GMock supports.
+$var MAX_ARITY = 10
+$$
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Analogous to GMock's built-in MockFunction, but for base::Callback instead of
+// std::function. It takes the full callback type as a parameter, so that it can
+// support both OnceCallback and RepeatingCallback.
+//
+// Use:
+// using FooCallback = base::Callback<int(std::string)>;
+//
+// TEST(FooTest, RunsCallbackWithBarArgument) {
+// base::MockCallback<FooCallback> callback;
+// EXPECT_CALL(callback, Run("bar")).WillOnce(Return(1));
+// Foo(callback.Get());
+// }
+//
+// Can be used with StrictMock and NiceMock. Caller must ensure that it outlives
+// any base::Callback obtained from it.
+
+#ifndef BASE_TEST_MOCK_CALLBACK_H_
+#define BASE_TEST_MOCK_CALLBACK_H_
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+
+// clang-format off
+
+template <typename F>
+class MockCallback;
+
+$range i 0..MAX_ARITY
+$for i [[
+$range j 1..i
+$var run_type = [[R($for j, [[A$j]])]]
+
+template <typename R$for j [[, typename A$j]]>
+class MockCallback<Callback<$run_type>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD$(i)_T(Run, $run_type);
+
+ Callback<$run_type> Get() {
+ return Bind(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+template <typename R$for j [[, typename A$j]]>
+class MockCallback<OnceCallback<$run_type>> {
+ public:
+ MockCallback() = default;
+ MOCK_METHOD$(i)_T(Run, $run_type);
+
+ OnceCallback<$run_type> Get() {
+ return BindOnce(&MockCallback::Run, Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallback);
+};
+
+]]
+
+// clang-format on
+
+} // namespace base
+
+#endif // BASE_TEST_MOCK_CALLBACK_H_
diff --git a/base/test/mock_callback_unittest.cc b/base/test/mock_callback_unittest.cc
new file mode 100644
index 0000000000..c5f109f0e4
--- /dev/null
+++ b/base/test/mock_callback_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/mock_callback.h"
+
+#include "base/callback.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::InSequence;
+using testing::Return;
+
+namespace base {
+namespace {
+
+TEST(MockCallbackTest, ZeroArgs) {
+ MockCallback<Closure> mock_closure;
+ EXPECT_CALL(mock_closure, Run());
+ mock_closure.Get().Run();
+
+ MockCallback<Callback<int()>> mock_int_callback;
+ {
+ InSequence sequence;
+ EXPECT_CALL(mock_int_callback, Run()).WillOnce(Return(42));
+ EXPECT_CALL(mock_int_callback, Run()).WillOnce(Return(88));
+ }
+ EXPECT_EQ(42, mock_int_callback.Get().Run());
+ EXPECT_EQ(88, mock_int_callback.Get().Run());
+}
+
+TEST(MockCallbackTest, WithArgs) {
+ MockCallback<Callback<int(int, int)>> mock_two_int_callback;
+ EXPECT_CALL(mock_two_int_callback, Run(1, 2)).WillOnce(Return(42));
+ EXPECT_CALL(mock_two_int_callback, Run(0, 0)).WillRepeatedly(Return(-1));
+ Callback<int(int, int)> two_int_callback = mock_two_int_callback.Get();
+ EXPECT_EQ(-1, two_int_callback.Run(0, 0));
+ EXPECT_EQ(42, two_int_callback.Run(1, 2));
+ EXPECT_EQ(-1, two_int_callback.Run(0, 0));
+}
+
+TEST(MockCallbackTest, ZeroArgsOnce) {
+ MockCallback<OnceClosure> mock_closure;
+ EXPECT_CALL(mock_closure, Run());
+ mock_closure.Get().Run();
+
+ MockCallback<OnceCallback<int()>> mock_int_callback;
+ EXPECT_CALL(mock_int_callback, Run()).WillOnce(Return(88));
+ EXPECT_EQ(88, mock_int_callback.Get().Run());
+}
+
+TEST(MockCallbackTest, WithArgsOnce) {
+ MockCallback<OnceCallback<int(int, int)>> mock_two_int_callback;
+ EXPECT_CALL(mock_two_int_callback, Run(1, 2)).WillOnce(Return(42));
+ OnceCallback<int(int, int)> two_int_callback = mock_two_int_callback.Get();
+ EXPECT_EQ(42, std::move(two_int_callback).Run(1, 2));
+}
+
+} // namespace
+} // namespace base
diff --git a/base/test/mock_devices_changed_observer.cc b/base/test/mock_devices_changed_observer.cc
new file mode 100644
index 0000000000..9fc57cd93e
--- /dev/null
+++ b/base/test/mock_devices_changed_observer.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/mock_devices_changed_observer.h"
+
+namespace base {
+
+MockDevicesChangedObserver::MockDevicesChangedObserver() = default;
+
+MockDevicesChangedObserver::~MockDevicesChangedObserver() = default;
+
+} // namespace base
diff --git a/base/test/mock_devices_changed_observer.h b/base/test/mock_devices_changed_observer.h
new file mode 100644
index 0000000000..0734fb4f6a
--- /dev/null
+++ b/base/test/mock_devices_changed_observer.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_
+#define BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/system_monitor/system_monitor.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+
+class MockDevicesChangedObserver
+ : public base::SystemMonitor::DevicesChangedObserver {
+ public:
+ MockDevicesChangedObserver();
+ ~MockDevicesChangedObserver() override;
+
+ MOCK_METHOD1(OnDevicesChanged,
+ void(base::SystemMonitor::DeviceType device_type));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockDevicesChangedObserver);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_
diff --git a/base/test/mock_log.cc b/base/test/mock_log.cc
new file mode 100644
index 0000000000..a09000d8ed
--- /dev/null
+++ b/base/test/mock_log.cc
@@ -0,0 +1,68 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/mock_log.h"
+
+namespace base {
+namespace test {
+
+// static
+MockLog* MockLog::g_instance_ = nullptr;
+Lock MockLog::g_lock;
+
+MockLog::MockLog() : is_capturing_logs_(false) {
+}
+
+MockLog::~MockLog() {
+ if (is_capturing_logs_) {
+ StopCapturingLogs();
+ }
+}
+
+void MockLog::StartCapturingLogs() {
+ AutoLock scoped_lock(g_lock);
+
+ // We don't use CHECK(), which can generate a new LOG message, and
+ // thus can confuse MockLog objects or other registered
+ // LogSinks.
+ RAW_CHECK(!is_capturing_logs_);
+ RAW_CHECK(!g_instance_);
+
+ is_capturing_logs_ = true;
+ g_instance_ = this;
+ previous_handler_ = logging::GetLogMessageHandler();
+ logging::SetLogMessageHandler(LogMessageHandler);
+}
+
+void MockLog::StopCapturingLogs() {
+ AutoLock scoped_lock(g_lock);
+
+ // We don't use CHECK(), which can generate a new LOG message, and
+ // thus can confuse MockLog objects or other registered
+ // LogSinks.
+ RAW_CHECK(is_capturing_logs_);
+ RAW_CHECK(g_instance_ == this);
+
+ is_capturing_logs_ = false;
+ logging::SetLogMessageHandler(previous_handler_);
+ g_instance_ = nullptr;
+}
+
+// static
+bool MockLog::LogMessageHandler(int severity,
+ const char* file,
+ int line,
+ size_t message_start,
+ const std::string& str) {
+ // gMock guarantees thread-safety for calling a mocked method
+ // (https://github.com/google/googlemock/blob/master/googlemock/docs/CookBook.md#using-google-mock-and-threads)
+ // but we also need to make sure that Start/StopCapturingLogs are synchronized
+ // with LogMessageHandler.
+ AutoLock scoped_lock(g_lock);
+
+ return g_instance_->Log(severity, file, line, message_start, str);
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/mock_log.h b/base/test/mock_log.h
new file mode 100644
index 0000000000..cda2fcd625
--- /dev/null
+++ b/base/test/mock_log.h
@@ -0,0 +1,100 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_MOCK_LOG_H_
+#define BASE_TEST_MOCK_LOG_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+namespace test {
+
+// A MockLog object intercepts LOG() messages issued during its lifespan. Using
+// this together with gMock, it's very easy to test how a piece of code calls
+// LOG(). The typical usage:
+//
+// TEST(FooTest, LogsCorrectly) {
+// MockLog log;
+//
+// // We expect the WARNING "Something bad!" exactly twice.
+// EXPECT_CALL(log, Log(WARNING, _, "Something bad!"))
+// .Times(2);
+//
+// // We allow foo.cc to call LOG(INFO) any number of times.
+// EXPECT_CALL(log, Log(INFO, HasSubstr("/foo.cc"), _))
+// .Times(AnyNumber());
+//
+// log.StartCapturingLogs(); // Call this after done setting expectations.
+// Foo(); // Exercises the code under test.
+// }
+//
+// CAVEAT: base/logging does not allow a thread to call LOG() again when it's
+// already inside a LOG() call. Doing so will cause a deadlock. Therefore,
+// it's the user's responsibility to not call LOG() in an action triggered by
+// MockLog::Log(). You may call RAW_LOG() instead.
+class MockLog {
+ public:
+ // Creates a MockLog object that is not capturing logs. If it were to start
+ // to capture logs, it could be a problem if some other threads already exist
+ // and are logging, as the user hasn't had a chance to set up expectation on
+ // this object yet (calling a mock method before setting the expectation is
+ // UNDEFINED behavior).
+ MockLog();
+
+ // When the object is destructed, it stops intercepting logs.
+ ~MockLog();
+
+ // Starts log capturing if the object isn't already doing so.
+ // Otherwise crashes.
+ void StartCapturingLogs();
+
+ // Stops log capturing if the object is capturing logs. Otherwise crashes.
+ void StopCapturingLogs();
+
+ // Log method is invoked for every log message before it's sent to other log
+ // destinations (if any). The method should return true to signal that it
+ // handled the message and the message should not be sent to other log
+ // destinations.
+ MOCK_METHOD5(Log,
+ bool(int severity,
+ const char* file,
+ int line,
+ size_t message_start,
+ const std::string& str));
+
+ private:
+ // The currently active mock log.
+ static MockLog* g_instance_;
+
+ // Lock protecting access to g_instance_.
+ static Lock g_lock;
+
+ // Static function which is set as the logging message handler.
+ // Called once for each message.
+ static bool LogMessageHandler(int severity,
+ const char* file,
+ int line,
+ size_t message_start,
+ const std::string& str);
+
+ // True if this object is currently capturing logs.
+ bool is_capturing_logs_;
+
+ // The previous handler to restore when the MockLog is destroyed.
+ logging::LogMessageHandlerFunction previous_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockLog);
+};
+
+} // namespace test
+} // namespace base
+
+#endif // BASE_TEST_MOCK_LOG_H_
diff --git a/base/test/multiprocess_test.cc b/base/test/multiprocess_test.cc
index 48af3226a0..c6ee420cb1 100644
--- a/base/test/multiprocess_test.cc
+++ b/base/test/multiprocess_test.cc
@@ -13,7 +13,7 @@
namespace base {
-#if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
+#if !defined(OS_ANDROID) && !defined(__ANDROID_HOST__)
Process SpawnMultiProcessTestChild(const std::string& procname,
const CommandLine& base_command_line,
const LaunchOptions& options) {
@@ -39,7 +39,7 @@ bool TerminateMultiProcessTestChild(const Process& process,
return process.Terminate(exit_code, wait);
}
-#endif // !OS_ANDROID && !__ANDROID__ && !__ANDROID_HOST__
+#endif // !OS_ANDROID && !__ANDROID_HOST__
CommandLine GetMultiProcessTestChildBaseCommandLine() {
base::ScopedAllowBlockingForTesting allow_blocking;
diff --git a/base/test/native_library_test_utils.cc b/base/test/native_library_test_utils.cc
new file mode 100644
index 0000000000..adcb1b01e9
--- /dev/null
+++ b/base/test/native_library_test_utils.cc
@@ -0,0 +1,19 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/native_library_test_utils.h"
+
+namespace {
+
+int g_static_value = 0;
+
+} // namespace
+
+extern "C" {
+
+int g_native_library_exported_value = 0;
+
+int NativeLibraryTestIncrement() { return ++g_static_value; }
+
+} // extern "C"
diff --git a/base/test/native_library_test_utils.h b/base/test/native_library_test_utils.h
new file mode 100644
index 0000000000..e26fd1a04e
--- /dev/null
+++ b/base/test/native_library_test_utils.h
@@ -0,0 +1,26 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_
+#define BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#define NATIVE_LIBRARY_TEST_ALWAYS_EXPORT __declspec(dllexport)
+#else
+#define NATIVE_LIBRARY_TEST_ALWAYS_EXPORT __attribute__((visibility("default")))
+#endif
+
+extern "C" {
+
+extern NATIVE_LIBRARY_TEST_ALWAYS_EXPORT int g_native_library_exported_value;
+
+// A function which increments an internal counter value and returns its value.
+// The first call returns 1, then 2, etc.
+NATIVE_LIBRARY_TEST_ALWAYS_EXPORT int NativeLibraryTestIncrement();
+
+} // extern "C"
+
+#endif // BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_
diff --git a/base/test/null_task_runner.cc b/base/test/null_task_runner.cc
new file mode 100644
index 0000000000..dfa26fa313
--- /dev/null
+++ b/base/test/null_task_runner.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/null_task_runner.h"
+
+namespace base {
+
+NullTaskRunner::NullTaskRunner() = default;
+
+NullTaskRunner::~NullTaskRunner() = default;
+
+bool NullTaskRunner::PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ base::TimeDelta delay) {
+ return false;
+}
+
+bool NullTaskRunner::PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ base::TimeDelta delay) {
+ return false;
+}
+
+bool NullTaskRunner::RunsTasksInCurrentSequence() const {
+ return true;
+}
+
+} // namespace base
diff --git a/base/test/null_task_runner.h b/base/test/null_task_runner.h
new file mode 100644
index 0000000000..c11ab6b0c6
--- /dev/null
+++ b/base/test/null_task_runner.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_NULL_TASK_RUNNER_H_
+#define BASE_TEST_NULL_TASK_RUNNER_H_
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+
+namespace base {
+
+// Helper class for tests that need to provide an implementation of a
+// *TaskRunner class but don't actually care about tasks being run.
+
+class NullTaskRunner : public base::SingleThreadTaskRunner {
+ public:
+ NullTaskRunner();
+
+ bool PostDelayedTask(const Location& from_here,
+ base::OnceClosure task,
+ base::TimeDelta delay) override;
+ bool PostNonNestableDelayedTask(const Location& from_here,
+ base::OnceClosure task,
+ base::TimeDelta delay) override;
+ // Always returns true to avoid triggering DCHECKs.
+ bool RunsTasksInCurrentSequence() const override;
+
+ protected:
+ ~NullTaskRunner() override;
+
+ DISALLOW_COPY_AND_ASSIGN(NullTaskRunner);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_NULL_TASK_RUNNER_H_
diff --git a/base/test/perf_log.cc b/base/test/perf_log.cc
new file mode 100644
index 0000000000..9212f4b355
--- /dev/null
+++ b/base/test/perf_log.cc
@@ -0,0 +1,45 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/perf_log.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+
+namespace base {
+
+static FILE* perf_log_file = nullptr;
+
+bool InitPerfLog(const FilePath& log_file) {
+ if (perf_log_file) {
+ // trying to initialize twice
+ NOTREACHED();
+ return false;
+ }
+
+ perf_log_file = OpenFile(log_file, "w");
+ return perf_log_file != nullptr;
+}
+
+void FinalizePerfLog() {
+ if (!perf_log_file) {
+ // trying to cleanup without initializing
+ NOTREACHED();
+ return;
+ }
+ base::CloseFile(perf_log_file);
+}
+
+void LogPerfResult(const char* test_name, double value, const char* units) {
+ if (!perf_log_file) {
+ NOTREACHED();
+ return;
+ }
+
+ fprintf(perf_log_file, "%s\t%g\t%s\n", test_name, value, units);
+ printf("%s\t%g\t%s\n", test_name, value, units);
+ fflush(stdout);
+}
+
+} // namespace base
diff --git a/base/test/perf_log.h b/base/test/perf_log.h
new file mode 100644
index 0000000000..5d6ed9f8ba
--- /dev/null
+++ b/base/test/perf_log.h
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_PERF_LOG_H_
+#define BASE_TEST_PERF_LOG_H_
+
+namespace base {
+
+class FilePath;
+
+// Initializes and finalizes the perf log. These functions should be
+// called at the beginning and end (respectively) of running all the
+// performance tests. The init function returns true on success.
+bool InitPerfLog(const FilePath& log_path);
+void FinalizePerfLog();
+
+// Writes to the perf result log the given 'value' resulting from the
+// named 'test'. The units are to aid in reading the log by people.
+void LogPerfResult(const char* test_name, double value, const char* units);
+
+} // namespace base
+
+#endif // BASE_TEST_PERF_LOG_H_
diff --git a/base/test/perf_test_suite.cc b/base/test/perf_test_suite.cc
new file mode 100644
index 0000000000..2e2cdbb751
--- /dev/null
+++ b/base/test/perf_test_suite.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/perf_test_suite.h"
+
+#include "base/command_line.h"
+#include "base/debug/debugger.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/strings/string_util.h"
+#include "base/test/perf_log.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+PerfTestSuite::PerfTestSuite(int argc, char** argv) : TestSuite(argc, argv) {}
+
+void PerfTestSuite::Initialize() {
+ TestSuite::Initialize();
+
+ // Initialize the perf timer log
+ FilePath log_path =
+ CommandLine::ForCurrentProcess()->GetSwitchValuePath("log-file");
+ if (log_path.empty()) {
+ PathService::Get(FILE_EXE, &log_path);
+#if defined(OS_ANDROID) || defined(OS_FUCHSIA)
+ base::FilePath tmp_dir;
+ PathService::Get(base::DIR_CACHE, &tmp_dir);
+ log_path = tmp_dir.Append(log_path.BaseName());
+#endif
+ log_path = log_path.ReplaceExtension(FILE_PATH_LITERAL("log"));
+ log_path = log_path.InsertBeforeExtension(FILE_PATH_LITERAL("_perf"));
+ }
+ ASSERT_TRUE(InitPerfLog(log_path));
+
+ // Raise to high priority to have more precise measurements. Since we don't
+ // aim at 1% precision, it is not necessary to run at realtime level.
+ if (!debug::BeingDebugged())
+ RaiseProcessToHighPriority();
+}
+
+void PerfTestSuite::Shutdown() {
+ TestSuite::Shutdown();
+ FinalizePerfLog();
+}
+
+} // namespace base
diff --git a/base/test/perf_test_suite.h b/base/test/perf_test_suite.h
new file mode 100644
index 0000000000..52528f0ac4
--- /dev/null
+++ b/base/test/perf_test_suite.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_PERF_TEST_SUITE_H_
+#define BASE_TEST_PERF_TEST_SUITE_H_
+
+#include "base/test/test_suite.h"
+
+namespace base {
+
+class PerfTestSuite : public TestSuite {
+ public:
+ PerfTestSuite(int argc, char** argv);
+
+ void Initialize() override;
+ void Shutdown() override;
+};
+
+} // namespace base
+
+#endif // BASE_TEST_PERF_TEST_SUITE_H_
diff --git a/base/test/perf_time_logger.cc b/base/test/perf_time_logger.cc
new file mode 100644
index 0000000000..c05ba51b7d
--- /dev/null
+++ b/base/test/perf_time_logger.cc
@@ -0,0 +1,27 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/perf_time_logger.h"
+
+#include "base/test/perf_log.h"
+
+namespace base {
+
+PerfTimeLogger::PerfTimeLogger(const char* test_name)
+ : logged_(false), test_name_(test_name) {}
+
+PerfTimeLogger::~PerfTimeLogger() {
+ if (!logged_)
+ Done();
+}
+
+void PerfTimeLogger::Done() {
+ // we use a floating-point millisecond value because it is more
+ // intuitive than microseconds and we want more precision than
+ // integer milliseconds
+ LogPerfResult(test_name_.c_str(), timer_.Elapsed().InMillisecondsF(), "ms");
+ logged_ = true;
+}
+
+} // namespace base
diff --git a/base/test/perf_time_logger.h b/base/test/perf_time_logger.h
new file mode 100644
index 0000000000..a5f3e8a70c
--- /dev/null
+++ b/base/test/perf_time_logger.h
@@ -0,0 +1,37 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_PERF_TIME_LOGGER_H_
+#define BASE_TEST_PERF_TIME_LOGGER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/timer/elapsed_timer.h"
+
+namespace base {
+
+// Automates calling LogPerfResult for the common case where you want
+// to measure the time that something took. Call Done() when the test
+// is complete if you do extra work after the test or there are stack
+// objects with potentially expensive constructors. Otherwise, this
+// class with automatically log on destruction.
+class PerfTimeLogger {
+ public:
+ explicit PerfTimeLogger(const char* test_name);
+ ~PerfTimeLogger();
+
+ void Done();
+
+ private:
+ bool logged_;
+ std::string test_name_;
+ ElapsedTimer timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(PerfTimeLogger);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_PERF_TIME_LOGGER_H_
diff --git a/base/test/power_monitor_test_base.cc b/base/test/power_monitor_test_base.cc
new file mode 100644
index 0000000000..f3625662bf
--- /dev/null
+++ b/base/test/power_monitor_test_base.cc
@@ -0,0 +1,68 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/power_monitor_test_base.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/power_monitor/power_monitor_source.h"
+#include "base/run_loop.h"
+
+namespace base {
+
+PowerMonitorTestSource::PowerMonitorTestSource()
+ : test_on_battery_power_(false) {
+ DCHECK(MessageLoopCurrent::Get())
+ << "PowerMonitorTestSource requires a MessageLoop.";
+}
+
+PowerMonitorTestSource::~PowerMonitorTestSource() = default;
+
+void PowerMonitorTestSource::Shutdown() {}
+
+void PowerMonitorTestSource::GeneratePowerStateEvent(bool on_battery_power) {
+ test_on_battery_power_ = on_battery_power;
+ ProcessPowerEvent(POWER_STATE_EVENT);
+ RunLoop().RunUntilIdle();
+}
+
+void PowerMonitorTestSource::GenerateSuspendEvent() {
+ ProcessPowerEvent(SUSPEND_EVENT);
+ RunLoop().RunUntilIdle();
+}
+
+void PowerMonitorTestSource::GenerateResumeEvent() {
+ ProcessPowerEvent(RESUME_EVENT);
+ RunLoop().RunUntilIdle();
+}
+
+bool PowerMonitorTestSource::IsOnBatteryPowerImpl() {
+ return test_on_battery_power_;
+};
+
+PowerMonitorTestObserver::PowerMonitorTestObserver()
+ : last_power_state_(false),
+ power_state_changes_(0),
+ suspends_(0),
+ resumes_(0) {
+}
+
+PowerMonitorTestObserver::~PowerMonitorTestObserver() = default;
+
+// PowerObserver callbacks.
+void PowerMonitorTestObserver::OnPowerStateChange(bool on_battery_power) {
+ last_power_state_ = on_battery_power;
+ power_state_changes_++;
+}
+
+void PowerMonitorTestObserver::OnSuspend() {
+ suspends_++;
+}
+
+void PowerMonitorTestObserver::OnResume() {
+ resumes_++;
+}
+
+} // namespace base
diff --git a/base/test/power_monitor_test_base.h b/base/test/power_monitor_test_base.h
new file mode 100644
index 0000000000..9b14fb0721
--- /dev/null
+++ b/base/test/power_monitor_test_base.h
@@ -0,0 +1,54 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_POWER_MONITOR_TEST_BASE_H_
+#define BASE_TEST_POWER_MONITOR_TEST_BASE_H_
+
+#include "base/power_monitor/power_monitor.h"
+#include "base/power_monitor/power_monitor_source.h"
+
+namespace base {
+
+class PowerMonitorTestSource : public PowerMonitorSource {
+ public:
+ PowerMonitorTestSource();
+ ~PowerMonitorTestSource() override;
+ void Shutdown() override;
+
+ void GeneratePowerStateEvent(bool on_battery_power);
+ void GenerateSuspendEvent();
+ void GenerateResumeEvent();
+
+ protected:
+ bool IsOnBatteryPowerImpl() override;
+
+ bool test_on_battery_power_;
+};
+
+class PowerMonitorTestObserver : public PowerObserver {
+ public:
+ PowerMonitorTestObserver();
+ ~PowerMonitorTestObserver() override;
+
+ // PowerObserver callbacks.
+ void OnPowerStateChange(bool on_battery_power) override;
+ void OnSuspend() override;
+ void OnResume() override;
+
+ // Test status counts.
+ bool last_power_state() { return last_power_state_; }
+ int power_state_changes() { return power_state_changes_; }
+ int suspends() { return suspends_; }
+ int resumes() { return resumes_; }
+
+ private:
+ bool last_power_state_; // Last power state we were notified of.
+ int power_state_changes_; // Count of OnPowerStateChange notifications.
+ int suspends_; // Count of OnSuspend notifications.
+ int resumes_; // Count of OnResume notifications.
+};
+
+} // namespace base
+
+#endif // BASE_TEST_POWER_MONITOR_TEST_BASE_H_
diff --git a/base/test/run_all_base_unittests.cc b/base/test/run_all_base_unittests.cc
new file mode 100644
index 0000000000..da52310fb5
--- /dev/null
+++ b/base/test/run_all_base_unittests.cc
@@ -0,0 +1,15 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "build/build_config.h"
+
+int main(int argc, char** argv) {
+ base::TestSuite test_suite(argc, argv);
+ return base::LaunchUnitTests(
+ argc, argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/base/test/run_all_perftests.cc b/base/test/run_all_perftests.cc
new file mode 100644
index 0000000000..6e38109376
--- /dev/null
+++ b/base/test/run_all_perftests.cc
@@ -0,0 +1,9 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/perf_test_suite.h"
+
+int main(int argc, char** argv) {
+ return base::PerfTestSuite(argc, argv).Run();
+}
diff --git a/base/test/run_all_unittests.cc b/base/test/run_all_unittests.cc
new file mode 100644
index 0000000000..0ad84ed53d
--- /dev/null
+++ b/base/test/run_all_unittests.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "build/build_config.h"
+
+int main(int argc, char** argv) {
+ base::TestSuite test_suite(argc, argv);
+ return base::LaunchUnitTests(
+ argc, argv,
+ base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/base/test/scoped_command_line.cc b/base/test/scoped_command_line.cc
new file mode 100644
index 0000000000..c74d243f44
--- /dev/null
+++ b/base/test/scoped_command_line.cc
@@ -0,0 +1,22 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_command_line.h"
+
+namespace base {
+namespace test {
+
+ScopedCommandLine::ScopedCommandLine()
+ : original_command_line_(*base::CommandLine::ForCurrentProcess()) {}
+
+ScopedCommandLine::~ScopedCommandLine() {
+ *base::CommandLine::ForCurrentProcess() = original_command_line_;
+}
+
+CommandLine* ScopedCommandLine::GetProcessCommandLine() {
+ return base::CommandLine::ForCurrentProcess();
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/scoped_command_line.h b/base/test/scoped_command_line.h
new file mode 100644
index 0000000000..dea0c6ac1e
--- /dev/null
+++ b/base/test/scoped_command_line.h
@@ -0,0 +1,34 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_SCOPED_COMMAND_LINE_H_
+#define BASE_TEST_SCOPED_COMMAND_LINE_H_
+
+#include "base/command_line.h"
+
+namespace base {
+namespace test {
+
+// Helper class to restore the original command line at the end of the scope.
+// NOTE: In most unit tests, the command line is automatically restored per
+// test, so this class is not necessary if the command line applies to
+// the entire single test.
+class ScopedCommandLine final {
+ public:
+ ScopedCommandLine();
+ ~ScopedCommandLine();
+
+ // Gets the command line for the current process.
+ // NOTE: Do not name this GetCommandLine as this will conflict with Windows's
+ // GetCommandLine and get renamed to GetCommandLineW.
+ CommandLine* GetProcessCommandLine();
+
+ private:
+ const CommandLine original_command_line_;
+};
+
+} // namespace test
+} // namespace base
+
+#endif // BASE_TEST_SCOPED_COMMAND_LINE_H_
diff --git a/base/test/scoped_mock_time_message_loop_task_runner.cc b/base/test/scoped_mock_time_message_loop_task_runner.cc
new file mode 100644
index 0000000000..8e855e58fa
--- /dev/null
+++ b/base/test/scoped_mock_time_message_loop_task_runner.cc
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_mock_time_message_loop_task_runner.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/run_loop.h"
+#include "base/test/test_pending_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+
+namespace base {
+
+ScopedMockTimeMessageLoopTaskRunner::ScopedMockTimeMessageLoopTaskRunner()
+ : task_runner_(new TestMockTimeTaskRunner),
+ previous_task_runner_(ThreadTaskRunnerHandle::Get()) {
+ DCHECK(MessageLoopCurrent::Get());
+ // To ensure that we process any initialization tasks posted to the
+ // MessageLoop by a test fixture before replacing its TaskRunner.
+ RunLoop().RunUntilIdle();
+ MessageLoopCurrent::Get()->SetTaskRunner(task_runner_);
+}
+
+ScopedMockTimeMessageLoopTaskRunner::~ScopedMockTimeMessageLoopTaskRunner() {
+ DCHECK(previous_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_EQ(task_runner_, ThreadTaskRunnerHandle::Get());
+ for (auto& pending_task : task_runner_->TakePendingTasks()) {
+ previous_task_runner_->PostDelayedTask(
+ pending_task.location, std::move(pending_task.task),
+ pending_task.GetTimeToRun() - task_runner_->NowTicks());
+ }
+ MessageLoopCurrent::Get()->SetTaskRunner(std::move(previous_task_runner_));
+}
+
+} // namespace base
diff --git a/base/test/scoped_mock_time_message_loop_task_runner.h b/base/test/scoped_mock_time_message_loop_task_runner.h
new file mode 100644
index 0000000000..2a034ee8f8
--- /dev/null
+++ b/base/test/scoped_mock_time_message_loop_task_runner.h
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_SCOPED_MOCK_TIME_MESSAGE_LOOP_TASK_RUNNER_H_
+#define BASE_TEST_SCOPED_MOCK_TIME_MESSAGE_LOOP_TASK_RUNNER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/test_mock_time_task_runner.h"
+
+namespace base {
+
+class SingleThreadTaskRunner;
+
+// A scoped wrapper around TestMockTimeTaskRunner that replaces
+// MessageLoopCurrent::Get()'s task runner (and consequently
+// ThreadTaskRunnerHandle) with a TestMockTimeTaskRunner and resets it back at
+// the end of its scope.
+//
+// Note: RunLoop() will not work in the scope of a
+// ScopedMockTimeMessageLoopTaskRunner, the underlying TestMockTimeTaskRunner's
+// methods must be used instead to pump tasks.
+//
+// DEPRECATED: Use a TestMockTimeTaskRunner::Type::kBoundToThread instead of a
+// MessageLoop + ScopedMockTimeMessageLoopTaskRunner.
+// TODO(gab): Remove usage of this API and delete it.
+class ScopedMockTimeMessageLoopTaskRunner {
+ public:
+ ScopedMockTimeMessageLoopTaskRunner();
+ ~ScopedMockTimeMessageLoopTaskRunner();
+
+ TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); }
+ TestMockTimeTaskRunner* operator->() { return task_runner_.get(); }
+
+ private:
+ const scoped_refptr<TestMockTimeTaskRunner> task_runner_;
+ scoped_refptr<SingleThreadTaskRunner> previous_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedMockTimeMessageLoopTaskRunner);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_SCOPED_MOCK_TIME_MESSAGE_LOOP_TASK_RUNNER_H_
diff --git a/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc b/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc
new file mode 100644
index 0000000000..b08323d380
--- /dev/null
+++ b/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_mock_time_message_loop_task_runner.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_forward.h"
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/test/test_pending_task.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+TaskRunner* GetCurrentTaskRunner() {
+ return MessageLoopCurrent::Get()->task_runner().get();
+}
+
+void AssignTrue(bool* out) {
+ *out = true;
+}
+
+// Pops a task from the front of |pending_tasks| and returns it.
+TestPendingTask PopFront(base::circular_deque<TestPendingTask>* pending_tasks) {
+ TestPendingTask task = std::move(pending_tasks->front());
+ pending_tasks->pop_front();
+ return task;
+}
+
+class ScopedMockTimeMessageLoopTaskRunnerTest : public testing::Test {
+ public:
+ ScopedMockTimeMessageLoopTaskRunnerTest()
+ : original_task_runner_(new TestMockTimeTaskRunner()) {
+ MessageLoopCurrent::Get()->SetTaskRunner(original_task_runner_);
+ }
+
+ protected:
+ TestMockTimeTaskRunner* original_task_runner() {
+ return original_task_runner_.get();
+ }
+
+ private:
+ scoped_refptr<TestMockTimeTaskRunner> original_task_runner_;
+
+ MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedMockTimeMessageLoopTaskRunnerTest);
+};
+
+// Verifies a new TaskRunner is installed while a
+// ScopedMockTimeMessageLoopTaskRunner exists and the previous one is installed
+// after destruction.
+TEST_F(ScopedMockTimeMessageLoopTaskRunnerTest, CurrentTaskRunners) {
+ auto scoped_task_runner_ =
+ std::make_unique<ScopedMockTimeMessageLoopTaskRunner>();
+ EXPECT_EQ(scoped_task_runner_->task_runner(), GetCurrentTaskRunner());
+ scoped_task_runner_.reset();
+ EXPECT_EQ(original_task_runner(), GetCurrentTaskRunner());
+}
+
+TEST_F(ScopedMockTimeMessageLoopTaskRunnerTest,
+ IncompleteTasksAreCopiedToPreviousTaskRunnerAfterDestruction) {
+ auto scoped_task_runner_ =
+ std::make_unique<ScopedMockTimeMessageLoopTaskRunner>();
+
+ bool task_10_has_run = false;
+ bool task_11_has_run = false;
+
+ Closure task_1 = DoNothing();
+ Closure task_2 = DoNothing();
+ Closure task_10 = Bind(&AssignTrue, &task_10_has_run);
+ Closure task_11 = Bind(&AssignTrue, &task_11_has_run);
+
+ constexpr TimeDelta task_1_delay = TimeDelta::FromSeconds(1);
+ constexpr TimeDelta task_2_delay = TimeDelta::FromSeconds(2);
+ constexpr TimeDelta task_10_delay = TimeDelta::FromSeconds(10);
+ constexpr TimeDelta task_11_delay = TimeDelta::FromSeconds(11);
+
+ constexpr TimeDelta step_time_by = TimeDelta::FromSeconds(5);
+
+ GetCurrentTaskRunner()->PostDelayedTask(FROM_HERE, task_1, task_1_delay);
+ GetCurrentTaskRunner()->PostDelayedTask(FROM_HERE, task_2, task_2_delay);
+ GetCurrentTaskRunner()->PostDelayedTask(FROM_HERE, task_10, task_10_delay);
+ GetCurrentTaskRunner()->PostDelayedTask(FROM_HERE, task_11, task_11_delay);
+
+ scoped_task_runner_->task_runner()->FastForwardBy(step_time_by);
+
+ scoped_task_runner_.reset();
+
+ base::circular_deque<TestPendingTask> pending_tasks =
+ original_task_runner()->TakePendingTasks();
+
+ EXPECT_EQ(2U, pending_tasks.size());
+
+ TestPendingTask pending_task = PopFront(&pending_tasks);
+ EXPECT_FALSE(task_10_has_run);
+ std::move(pending_task.task).Run();
+ EXPECT_TRUE(task_10_has_run);
+ EXPECT_EQ(task_10_delay - step_time_by, pending_task.delay);
+
+ pending_task = PopFront(&pending_tasks);
+ EXPECT_FALSE(task_11_has_run);
+ std::move(pending_task.task).Run();
+ EXPECT_TRUE(task_11_has_run);
+ EXPECT_EQ(task_11_delay - step_time_by, pending_task.delay);
+}
+
+} // namespace
+} // namespace base
diff --git a/base/test/scoped_path_override.cc b/base/test/scoped_path_override.cc
new file mode 100644
index 0000000000..b8cfd4a22a
--- /dev/null
+++ b/base/test/scoped_path_override.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_path_override.h"
+
+#include "base/logging.h"
+#include "base/path_service.h"
+
+namespace base {
+
+ScopedPathOverride::ScopedPathOverride(int key) : key_(key) {
+ bool result = temp_dir_.CreateUniqueTempDir();
+ CHECK(result);
+ result = PathService::Override(key, temp_dir_.GetPath());
+ CHECK(result);
+}
+
+ScopedPathOverride::ScopedPathOverride(int key, const base::FilePath& dir)
+ : key_(key) {
+ bool result = PathService::Override(key, dir);
+ CHECK(result);
+}
+
+ScopedPathOverride::ScopedPathOverride(int key,
+ const FilePath& path,
+ bool is_absolute,
+ bool create)
+ : key_(key) {
+ bool result =
+ PathService::OverrideAndCreateIfNeeded(key, path, is_absolute, create);
+ CHECK(result);
+}
+
+ScopedPathOverride::~ScopedPathOverride() {
+ bool result = PathService::RemoveOverride(key_);
+ CHECK(result) << "The override seems to have been removed already!";
+}
+
+} // namespace base
diff --git a/base/test/scoped_path_override.h b/base/test/scoped_path_override.h
new file mode 100644
index 0000000000..f5891490b1
--- /dev/null
+++ b/base/test/scoped_path_override.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_SCOPED_PATH_OVERRIDE_H_
+#define BASE_TEST_SCOPED_PATH_OVERRIDE_H_
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+
+namespace base {
+
+class FilePath;
+
+// Sets a path override on construction, and removes it when the object goes out
+// of scope. This class is intended to be used by tests that need to override
+// paths to ensure their overrides are properly handled and reverted when the
+// scope of the test is left.
+class ScopedPathOverride {
+ public:
+ // Contructor that initializes the override to a scoped temp directory.
+ explicit ScopedPathOverride(int key);
+
+ // Constructor that would use a path provided by the user.
+ ScopedPathOverride(int key, const FilePath& dir);
+
+ // See PathService::OverrideAndCreateIfNeeded.
+ ScopedPathOverride(int key,
+ const FilePath& path,
+ bool is_absolute,
+ bool create);
+ ~ScopedPathOverride();
+
+ private:
+ int key_;
+ ScopedTempDir temp_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPathOverride);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_SCOPED_PATH_OVERRIDE_H_
diff --git a/base/test/sequenced_task_runner_test_template.cc b/base/test/sequenced_task_runner_test_template.cc
new file mode 100644
index 0000000000..de6849230e
--- /dev/null
+++ b/base/test/sequenced_task_runner_test_template.cc
@@ -0,0 +1,269 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/sequenced_task_runner_test_template.h"
+
+#include <ostream>
+
+#include "base/location.h"
+
+namespace base {
+
+namespace internal {
+
+TaskEvent::TaskEvent(int i, Type type)
+ : i(i), type(type) {
+}
+
+SequencedTaskTracker::SequencedTaskTracker()
+ : next_post_i_(0),
+ task_end_count_(0),
+ task_end_cv_(&lock_) {
+}
+
+void SequencedTaskTracker::PostWrappedNonNestableTask(
+ SequencedTaskRunner* task_runner,
+ const Closure& task) {
+ AutoLock event_lock(lock_);
+ const int post_i = next_post_i_++;
+ Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this,
+ task, post_i);
+ task_runner->PostNonNestableTask(FROM_HERE, wrapped_task);
+ TaskPosted(post_i);
+}
+
+void SequencedTaskTracker::PostWrappedNestableTask(
+ SequencedTaskRunner* task_runner,
+ const Closure& task) {
+ AutoLock event_lock(lock_);
+ const int post_i = next_post_i_++;
+ Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this,
+ task, post_i);
+ task_runner->PostTask(FROM_HERE, wrapped_task);
+ TaskPosted(post_i);
+}
+
+void SequencedTaskTracker::PostWrappedDelayedNonNestableTask(
+ SequencedTaskRunner* task_runner,
+ const Closure& task,
+ TimeDelta delay) {
+ AutoLock event_lock(lock_);
+ const int post_i = next_post_i_++;
+ Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this,
+ task, post_i);
+ task_runner->PostNonNestableDelayedTask(FROM_HERE, wrapped_task, delay);
+ TaskPosted(post_i);
+}
+
+void SequencedTaskTracker::PostNonNestableTasks(
+ SequencedTaskRunner* task_runner,
+ int task_count) {
+ for (int i = 0; i < task_count; ++i) {
+ PostWrappedNonNestableTask(task_runner, Closure());
+ }
+}
+
+void SequencedTaskTracker::RunTask(const Closure& task, int task_i) {
+ TaskStarted(task_i);
+ if (!task.is_null())
+ task.Run();
+ TaskEnded(task_i);
+}
+
+void SequencedTaskTracker::TaskPosted(int i) {
+ // Caller must own |lock_|.
+ events_.push_back(TaskEvent(i, TaskEvent::POST));
+}
+
+void SequencedTaskTracker::TaskStarted(int i) {
+ AutoLock lock(lock_);
+ events_.push_back(TaskEvent(i, TaskEvent::START));
+}
+
+void SequencedTaskTracker::TaskEnded(int i) {
+ AutoLock lock(lock_);
+ events_.push_back(TaskEvent(i, TaskEvent::END));
+ ++task_end_count_;
+ task_end_cv_.Signal();
+}
+
+const std::vector<TaskEvent>&
+SequencedTaskTracker::GetTaskEvents() const {
+ return events_;
+}
+
+void SequencedTaskTracker::WaitForCompletedTasks(int count) {
+ AutoLock lock(lock_);
+ while (task_end_count_ < count)
+ task_end_cv_.Wait();
+}
+
+SequencedTaskTracker::~SequencedTaskTracker() = default;
+
+void PrintTo(const TaskEvent& event, std::ostream* os) {
+ *os << "(i=" << event.i << ", type=";
+ switch (event.type) {
+ case TaskEvent::POST: *os << "POST"; break;
+ case TaskEvent::START: *os << "START"; break;
+ case TaskEvent::END: *os << "END"; break;
+ }
+ *os << ")";
+}
+
+namespace {
+
+// Returns the task ordinals for the task event type |type| in the order that
+// they were recorded.
+std::vector<int> GetEventTypeOrder(const std::vector<TaskEvent>& events,
+ TaskEvent::Type type) {
+ std::vector<int> tasks;
+ std::vector<TaskEvent>::const_iterator event;
+ for (event = events.begin(); event != events.end(); ++event) {
+ if (event->type == type)
+ tasks.push_back(event->i);
+ }
+ return tasks;
+}
+
+// Returns all task events for task |task_i|.
+std::vector<TaskEvent::Type> GetEventsForTask(
+ const std::vector<TaskEvent>& events,
+ int task_i) {
+ std::vector<TaskEvent::Type> task_event_orders;
+ std::vector<TaskEvent>::const_iterator event;
+ for (event = events.begin(); event != events.end(); ++event) {
+ if (event->i == task_i)
+ task_event_orders.push_back(event->type);
+ }
+ return task_event_orders;
+}
+
+// Checks that the task events for each task in |events| occur in the order
+// {POST, START, END}, and that there is only one instance of each event type
+// per task.
+::testing::AssertionResult CheckEventOrdersForEachTask(
+ const std::vector<TaskEvent>& events,
+ int task_count) {
+ std::vector<TaskEvent::Type> expected_order;
+ expected_order.push_back(TaskEvent::POST);
+ expected_order.push_back(TaskEvent::START);
+ expected_order.push_back(TaskEvent::END);
+
+ // This is O(n^2), but it runs fast enough currently so is not worth
+ // optimizing.
+ for (int i = 0; i < task_count; ++i) {
+ const std::vector<TaskEvent::Type> task_events =
+ GetEventsForTask(events, i);
+ if (task_events != expected_order) {
+ return ::testing::AssertionFailure()
+ << "Events for task " << i << " are out of order; expected: "
+ << ::testing::PrintToString(expected_order) << "; actual: "
+ << ::testing::PrintToString(task_events);
+ }
+ }
+ return ::testing::AssertionSuccess();
+}
+
+// Checks that no two tasks were running at the same time. I.e. the only
+// events allowed between the START and END of a task are the POSTs of other
+// tasks.
+::testing::AssertionResult CheckNoTaskRunsOverlap(
+ const std::vector<TaskEvent>& events) {
+ // If > -1, we're currently inside a START, END pair.
+ int current_task_i = -1;
+
+ std::vector<TaskEvent>::const_iterator event;
+ for (event = events.begin(); event != events.end(); ++event) {
+ bool spurious_event_found = false;
+
+ if (current_task_i == -1) { // Not inside a START, END pair.
+ switch (event->type) {
+ case TaskEvent::POST:
+ break;
+ case TaskEvent::START:
+ current_task_i = event->i;
+ break;
+ case TaskEvent::END:
+ spurious_event_found = true;
+ break;
+ }
+
+ } else { // Inside a START, END pair.
+ bool interleaved_task_detected = false;
+
+ switch (event->type) {
+ case TaskEvent::POST:
+ if (event->i == current_task_i)
+ spurious_event_found = true;
+ break;
+ case TaskEvent::START:
+ interleaved_task_detected = true;
+ break;
+ case TaskEvent::END:
+ if (event->i != current_task_i)
+ interleaved_task_detected = true;
+ else
+ current_task_i = -1;
+ break;
+ }
+
+ if (interleaved_task_detected) {
+ return ::testing::AssertionFailure()
+ << "Found event " << ::testing::PrintToString(*event)
+ << " between START and END events for task " << current_task_i
+ << "; event dump: " << ::testing::PrintToString(events);
+ }
+ }
+
+ if (spurious_event_found) {
+ const int event_i = event - events.begin();
+ return ::testing::AssertionFailure()
+ << "Spurious event " << ::testing::PrintToString(*event)
+ << " at position " << event_i << "; event dump: "
+ << ::testing::PrintToString(events);
+ }
+ }
+
+ return ::testing::AssertionSuccess();
+}
+
+} // namespace
+
+::testing::AssertionResult CheckNonNestableInvariants(
+ const std::vector<TaskEvent>& events,
+ int task_count) {
+ const std::vector<int> post_order =
+ GetEventTypeOrder(events, TaskEvent::POST);
+ const std::vector<int> start_order =
+ GetEventTypeOrder(events, TaskEvent::START);
+ const std::vector<int> end_order =
+ GetEventTypeOrder(events, TaskEvent::END);
+
+ if (start_order != post_order) {
+ return ::testing::AssertionFailure()
+ << "Expected START order (which equals actual POST order): \n"
+ << ::testing::PrintToString(post_order)
+ << "\n Actual START order:\n"
+ << ::testing::PrintToString(start_order);
+ }
+
+ if (end_order != post_order) {
+ return ::testing::AssertionFailure()
+ << "Expected END order (which equals actual POST order): \n"
+ << ::testing::PrintToString(post_order)
+ << "\n Actual END order:\n"
+ << ::testing::PrintToString(end_order);
+ }
+
+ const ::testing::AssertionResult result =
+ CheckEventOrdersForEachTask(events, task_count);
+ if (!result)
+ return result;
+
+ return CheckNoTaskRunsOverlap(events);
+}
+
+} // namespace internal
+
+} // namespace base
diff --git a/base/test/sequenced_task_runner_test_template.h b/base/test/sequenced_task_runner_test_template.h
new file mode 100644
index 0000000000..a510030fb1
--- /dev/null
+++ b/base/test/sequenced_task_runner_test_template.h
@@ -0,0 +1,350 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// SequencedTaskRunnerTest defines tests that implementations of
+// SequencedTaskRunner should pass in order to be conformant.
+// See task_runner_test_template.h for a description of how to use the
+// constructs in this file; these work the same.
+
+#ifndef BASE_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_
+#define BASE_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_
+
+#include <cstddef>
+#include <iosfwd>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace internal {
+
+struct TaskEvent {
+ enum Type { POST, START, END };
+ TaskEvent(int i, Type type);
+ int i;
+ Type type;
+};
+
+// Utility class used in the tests below.
+class SequencedTaskTracker : public RefCountedThreadSafe<SequencedTaskTracker> {
+ public:
+ SequencedTaskTracker();
+
+ // Posts the non-nestable task |task|, and records its post event.
+ void PostWrappedNonNestableTask(SequencedTaskRunner* task_runner,
+ const Closure& task);
+
+ // Posts the nestable task |task|, and records its post event.
+ void PostWrappedNestableTask(SequencedTaskRunner* task_runner,
+ const Closure& task);
+
+ // Posts the delayed non-nestable task |task|, and records its post event.
+ void PostWrappedDelayedNonNestableTask(SequencedTaskRunner* task_runner,
+ const Closure& task,
+ TimeDelta delay);
+
+ // Posts |task_count| non-nestable tasks.
+ void PostNonNestableTasks(SequencedTaskRunner* task_runner, int task_count);
+
+ const std::vector<TaskEvent>& GetTaskEvents() const;
+
+ // Returns after the tracker observes a total of |count| task completions.
+ void WaitForCompletedTasks(int count);
+
+ private:
+ friend class RefCountedThreadSafe<SequencedTaskTracker>;
+
+ ~SequencedTaskTracker();
+
+ // A task which runs |task|, recording the start and end events.
+ void RunTask(const Closure& task, int task_i);
+
+ // Records a post event for task |i|. The owner is expected to be holding
+ // |lock_| (unlike |TaskStarted| and |TaskEnded|).
+ void TaskPosted(int i);
+
+ // Records a start event for task |i|.
+ void TaskStarted(int i);
+
+ // Records a end event for task |i|.
+ void TaskEnded(int i);
+
+ // Protects events_, next_post_i_, task_end_count_ and task_end_cv_.
+ Lock lock_;
+
+ // The events as they occurred for each task (protected by lock_).
+ std::vector<TaskEvent> events_;
+
+ // The ordinal to be used for the next task-posting task (protected by
+ // lock_).
+ int next_post_i_;
+
+ // The number of task end events we've received.
+ int task_end_count_;
+ ConditionVariable task_end_cv_;
+
+ DISALLOW_COPY_AND_ASSIGN(SequencedTaskTracker);
+};
+
+void PrintTo(const TaskEvent& event, std::ostream* os);
+
+// Checks the non-nestable task invariants for all tasks in |events|.
+//
+// The invariants are:
+// 1) Events started and ended in the same order that they were posted.
+// 2) Events for an individual tasks occur in the order {POST, START, END},
+// and there is only one instance of each event type for a task.
+// 3) The only events between a task's START and END events are the POSTs of
+// other tasks. I.e. tasks were run sequentially, not interleaved.
+::testing::AssertionResult CheckNonNestableInvariants(
+ const std::vector<TaskEvent>& events,
+ int task_count);
+
+} // namespace internal
+
+template <typename TaskRunnerTestDelegate>
+class SequencedTaskRunnerTest : public testing::Test {
+ protected:
+ SequencedTaskRunnerTest()
+ : task_tracker_(new internal::SequencedTaskTracker()) {}
+
+ const scoped_refptr<internal::SequencedTaskTracker> task_tracker_;
+ TaskRunnerTestDelegate delegate_;
+};
+
+TYPED_TEST_CASE_P(SequencedTaskRunnerTest);
+
+// This test posts N non-nestable tasks in sequence, and expects them to run
+// in FIFO order, with no part of any two tasks' execution
+// overlapping. I.e. that each task starts only after the previously-posted
+// one has finished.
+TYPED_TEST_P(SequencedTaskRunnerTest, SequentialNonNestable) {
+ const int kTaskCount = 1000;
+
+ this->delegate_.StartTaskRunner();
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ this->delegate_.GetTaskRunner();
+
+ this->task_tracker_->PostWrappedNonNestableTask(
+ task_runner.get(),
+ Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1)));
+ for (int i = 1; i < kTaskCount; ++i) {
+ this->task_tracker_->PostWrappedNonNestableTask(task_runner.get(),
+ Closure());
+ }
+
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(),
+ kTaskCount));
+}
+
+// This test posts N nestable tasks in sequence. It has the same expectations
+// as SequentialNonNestable because even though the tasks are nestable, they
+// will not be run nestedly in this case.
+TYPED_TEST_P(SequencedTaskRunnerTest, SequentialNestable) {
+ const int kTaskCount = 1000;
+
+ this->delegate_.StartTaskRunner();
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ this->delegate_.GetTaskRunner();
+
+ this->task_tracker_->PostWrappedNestableTask(
+ task_runner.get(),
+ Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1)));
+ for (int i = 1; i < kTaskCount; ++i) {
+ this->task_tracker_->PostWrappedNestableTask(task_runner.get(), Closure());
+ }
+
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(),
+ kTaskCount));
+}
+
+// This test posts non-nestable tasks in order of increasing delay, and checks
+// that that the tasks are run in FIFO order and that there is no execution
+// overlap whatsoever between any two tasks.
+TYPED_TEST_P(SequencedTaskRunnerTest, SequentialDelayedNonNestable) {
+ const int kTaskCount = 20;
+ const int kDelayIncrementMs = 50;
+
+ this->delegate_.StartTaskRunner();
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ this->delegate_.GetTaskRunner();
+
+ for (int i = 0; i < kTaskCount; ++i) {
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(
+ task_runner.get(), Closure(),
+ TimeDelta::FromMilliseconds(kDelayIncrementMs * i));
+ }
+
+ this->task_tracker_->WaitForCompletedTasks(kTaskCount);
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(),
+ kTaskCount));
+}
+
+// This test posts a fast, non-nestable task from within each of a number of
+// slow, non-nestable tasks and checks that they all run in the sequence they
+// were posted in and that there is no execution overlap whatsoever.
+TYPED_TEST_P(SequencedTaskRunnerTest, NonNestablePostFromNonNestableTask) {
+ const int kParentCount = 10;
+ const int kChildrenPerParent = 10;
+
+ this->delegate_.StartTaskRunner();
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ this->delegate_.GetTaskRunner();
+
+ for (int i = 0; i < kParentCount; ++i) {
+ Closure task = Bind(
+ &internal::SequencedTaskTracker::PostNonNestableTasks,
+ this->task_tracker_,
+ RetainedRef(task_runner),
+ kChildrenPerParent);
+ this->task_tracker_->PostWrappedNonNestableTask(task_runner.get(), task);
+ }
+
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(
+ this->task_tracker_->GetTaskEvents(),
+ kParentCount * (kChildrenPerParent + 1)));
+}
+
+// This test posts two tasks with the same delay, and checks that the tasks are
+// run in the order in which they were posted.
+//
+// NOTE: This is actually an approximate test since the API only takes a
+// "delay" parameter, so we are not exactly simulating two tasks that get
+// posted at the exact same time. It would be nice if the API allowed us to
+// specify the desired run time.
+TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTasksSameDelay) {
+ const int kTaskCount = 2;
+ const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
+
+ this->delegate_.StartTaskRunner();
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ this->delegate_.GetTaskRunner();
+
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(task_runner.get(),
+ Closure(), kDelay);
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(task_runner.get(),
+ Closure(), kDelay);
+ this->task_tracker_->WaitForCompletedTasks(kTaskCount);
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(),
+ kTaskCount));
+}
+
+// This test posts a normal task and a delayed task, and checks that the
+// delayed task runs after the normal task even if the normal task takes
+// a long time to run.
+TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskAfterLongTask) {
+ const int kTaskCount = 2;
+
+ this->delegate_.StartTaskRunner();
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ this->delegate_.GetTaskRunner();
+
+ this->task_tracker_->PostWrappedNonNestableTask(
+ task_runner.get(),
+ base::Bind(&PlatformThread::Sleep, TimeDelta::FromMilliseconds(50)));
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(
+ task_runner.get(), Closure(), TimeDelta::FromMilliseconds(10));
+ this->task_tracker_->WaitForCompletedTasks(kTaskCount);
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(),
+ kTaskCount));
+}
+
+// Test that a pile of normal tasks and a delayed task run in the
+// time-to-run order.
+TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskAfterManyLongTasks) {
+ const int kTaskCount = 11;
+
+ this->delegate_.StartTaskRunner();
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ this->delegate_.GetTaskRunner();
+
+ for (int i = 0; i < kTaskCount - 1; i++) {
+ this->task_tracker_->PostWrappedNonNestableTask(
+ task_runner.get(),
+ base::Bind(&PlatformThread::Sleep, TimeDelta::FromMilliseconds(50)));
+ }
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(
+ task_runner.get(), Closure(), TimeDelta::FromMilliseconds(10));
+ this->task_tracker_->WaitForCompletedTasks(kTaskCount);
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(),
+ kTaskCount));
+}
+
+
+// TODO(francoisk777@gmail.com) Add a test, similiar to the above, which runs
+// some tasked nestedly (which should be implemented in the test
+// delegate). Also add, to the the test delegate, a predicate which checks
+// whether the implementation supports nested tasks.
+//
+
+// The SequencedTaskRunnerTest test case verifies behaviour that is expected
+// from a sequenced task runner in order to be conformant.
+REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerTest,
+ SequentialNonNestable,
+ SequentialNestable,
+ SequentialDelayedNonNestable,
+ NonNestablePostFromNonNestableTask,
+ DelayedTasksSameDelay,
+ DelayedTaskAfterLongTask,
+ DelayedTaskAfterManyLongTasks);
+
+template <typename TaskRunnerTestDelegate>
+class SequencedTaskRunnerDelayedTest
+ : public SequencedTaskRunnerTest<TaskRunnerTestDelegate> {};
+
+TYPED_TEST_CASE_P(SequencedTaskRunnerDelayedTest);
+
+// This test posts a delayed task, and checks that the task is run later than
+// the specified time.
+TYPED_TEST_P(SequencedTaskRunnerDelayedTest, DelayedTaskBasic) {
+ const int kTaskCount = 1;
+ const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
+
+ this->delegate_.StartTaskRunner();
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ this->delegate_.GetTaskRunner();
+
+ Time time_before_run = Time::Now();
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(task_runner.get(),
+ Closure(), kDelay);
+ this->task_tracker_->WaitForCompletedTasks(kTaskCount);
+ this->delegate_.StopTaskRunner();
+ Time time_after_run = Time::Now();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(),
+ kTaskCount));
+ EXPECT_LE(kDelay, time_after_run - time_before_run);
+}
+
+// SequencedTaskRunnerDelayedTest tests that the |delay| parameter of
+// is used to actually wait for |delay| ms before executing the task.
+// This is not mandatory for a SequencedTaskRunner to be compliant.
+REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerDelayedTest, DelayedTaskBasic);
+
+} // namespace base
+
+#endif // BASE_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_
diff --git a/base/test/task_runner_test_template.cc b/base/test/task_runner_test_template.cc
new file mode 100644
index 0000000000..fe702472c7
--- /dev/null
+++ b/base/test/task_runner_test_template.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/task_runner_test_template.h"
+
+namespace base {
+
+namespace test {
+
+TaskTracker::TaskTracker() : task_runs_(0), task_runs_cv_(&lock_) {}
+
+TaskTracker::~TaskTracker() = default;
+
+Closure TaskTracker::WrapTask(const Closure& task, int i) {
+ return Bind(&TaskTracker::RunTask, this, task, i);
+}
+
+void TaskTracker::RunTask(const Closure& task, int i) {
+ AutoLock lock(lock_);
+ if (!task.is_null()) {
+ task.Run();
+ }
+ ++task_run_counts_[i];
+ ++task_runs_;
+ task_runs_cv_.Signal();
+}
+
+std::map<int, int> TaskTracker::GetTaskRunCounts() const {
+ AutoLock lock(lock_);
+ return task_run_counts_;
+}
+
+void TaskTracker::WaitForCompletedTasks(int count) {
+ AutoLock lock(lock_);
+ while (task_runs_ < count)
+ task_runs_cv_.Wait();
+}
+
+void ExpectRunsTasksInCurrentSequence(bool expected_value,
+ TaskRunner* task_runner) {
+ EXPECT_EQ(expected_value, task_runner->RunsTasksInCurrentSequence());
+}
+
+} // namespace test
+
+} // namespace base
diff --git a/base/test/task_runner_test_template.h b/base/test/task_runner_test_template.h
new file mode 100644
index 0000000000..4670522e42
--- /dev/null
+++ b/base/test/task_runner_test_template.h
@@ -0,0 +1,230 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines tests that implementations of TaskRunner should
+// pass in order to be conformant, as well as test cases for optional behavior.
+// Here's how you use it to test your implementation.
+//
+// Say your class is called MyTaskRunner. Then you need to define a
+// class called MyTaskRunnerTestDelegate in my_task_runner_unittest.cc
+// like this:
+//
+// class MyTaskRunnerTestDelegate {
+// public:
+// // Tasks posted to the task runner after this and before
+// // StopTaskRunner() is called is called should run successfully.
+// void StartTaskRunner() {
+// ...
+// }
+//
+// // Should return the task runner implementation. Only called
+// // after StartTaskRunner and before StopTaskRunner.
+// scoped_refptr<MyTaskRunner> GetTaskRunner() {
+// ...
+// }
+//
+// // Stop the task runner and make sure all tasks posted before
+// // this is called are run. Caveat: delayed tasks are not run,
+ // they're simply deleted.
+// void StopTaskRunner() {
+// ...
+// }
+// };
+//
+// The TaskRunnerTest test harness will have a member variable of
+// this delegate type and will call its functions in the various
+// tests.
+//
+// Then you simply #include this file as well as gtest.h and add the
+// following statement to my_task_runner_unittest.cc:
+//
+// INSTANTIATE_TYPED_TEST_CASE_P(
+// MyTaskRunner, TaskRunnerTest, MyTaskRunnerTestDelegate);
+//
+// Easy!
+//
+// The optional test harnesses TaskRunnerAffinityTest can be
+// instanciated in the same way, using the same delegate:
+//
+// INSTANTIATE_TYPED_TEST_CASE_P(
+// MyTaskRunner, TaskRunnerAffinityTest, MyTaskRunnerTestDelegate);
+
+
+#ifndef BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_
+#define BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_
+
+#include <cstddef>
+#include <map>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace test {
+
+// Utility class that keeps track of how many times particular tasks
+// are run.
+class TaskTracker : public RefCountedThreadSafe<TaskTracker> {
+ public:
+ TaskTracker();
+
+ // Returns a closure that runs the given task and increments the run
+ // count of |i| by one. |task| may be null. It is guaranteed that
+ // only one task wrapped by a given tracker will be run at a time.
+ Closure WrapTask(const Closure& task, int i);
+
+ std::map<int, int> GetTaskRunCounts() const;
+
+ // Returns after the tracker observes a total of |count| task completions.
+ void WaitForCompletedTasks(int count);
+
+ private:
+ friend class RefCountedThreadSafe<TaskTracker>;
+
+ ~TaskTracker();
+
+ void RunTask(const Closure& task, int i);
+
+ mutable Lock lock_;
+ std::map<int, int> task_run_counts_;
+ int task_runs_;
+ ConditionVariable task_runs_cv_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskTracker);
+};
+
+} // namespace test
+
+template <typename TaskRunnerTestDelegate>
+class TaskRunnerTest : public testing::Test {
+ protected:
+ TaskRunnerTest() : task_tracker_(new test::TaskTracker()) {}
+
+ const scoped_refptr<test::TaskTracker> task_tracker_;
+ TaskRunnerTestDelegate delegate_;
+};
+
+TYPED_TEST_CASE_P(TaskRunnerTest);
+
+// We can't really test much, since TaskRunner provides very few
+// guarantees.
+
+// Post a bunch of tasks to the task runner. They should all
+// complete.
+TYPED_TEST_P(TaskRunnerTest, Basic) {
+ std::map<int, int> expected_task_run_counts;
+
+ this->delegate_.StartTaskRunner();
+ scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner();
+ // Post each ith task i+1 times.
+ for (int i = 0; i < 20; ++i) {
+ const Closure& ith_task = this->task_tracker_->WrapTask(Closure(), i);
+ for (int j = 0; j < i + 1; ++j) {
+ task_runner->PostTask(FROM_HERE, ith_task);
+ ++expected_task_run_counts[i];
+ }
+ }
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_EQ(expected_task_run_counts,
+ this->task_tracker_->GetTaskRunCounts());
+}
+
+// Post a bunch of delayed tasks to the task runner. They should all
+// complete.
+TYPED_TEST_P(TaskRunnerTest, Delayed) {
+ std::map<int, int> expected_task_run_counts;
+ int expected_total_tasks = 0;
+
+ this->delegate_.StartTaskRunner();
+ scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner();
+ // Post each ith task i+1 times with delays from 0-i.
+ for (int i = 0; i < 20; ++i) {
+ const Closure& ith_task = this->task_tracker_->WrapTask(Closure(), i);
+ for (int j = 0; j < i + 1; ++j) {
+ task_runner->PostDelayedTask(
+ FROM_HERE, ith_task, base::TimeDelta::FromMilliseconds(j));
+ ++expected_task_run_counts[i];
+ ++expected_total_tasks;
+ }
+ }
+ this->task_tracker_->WaitForCompletedTasks(expected_total_tasks);
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_EQ(expected_task_run_counts,
+ this->task_tracker_->GetTaskRunCounts());
+}
+
+// The TaskRunnerTest test case verifies behaviour that is expected from a
+// task runner in order to be conformant.
+REGISTER_TYPED_TEST_CASE_P(TaskRunnerTest, Basic, Delayed);
+
+namespace test {
+
+// Calls RunsTasksInCurrentSequence() on |task_runner| and expects it to
+// equal |expected_value|.
+void ExpectRunsTasksInCurrentSequence(bool expected_value,
+ TaskRunner* task_runner);
+
+} // namespace test
+
+template <typename TaskRunnerTestDelegate>
+class TaskRunnerAffinityTest : public TaskRunnerTest<TaskRunnerTestDelegate> {};
+
+TYPED_TEST_CASE_P(TaskRunnerAffinityTest);
+
+// Post a bunch of tasks to the task runner as well as to a separate
+// thread, each checking the value of RunsTasksInCurrentSequence(),
+// which should return true for the tasks posted on the task runner
+// and false for the tasks posted on the separate thread.
+TYPED_TEST_P(TaskRunnerAffinityTest, RunsTasksInCurrentSequence) {
+ std::map<int, int> expected_task_run_counts;
+
+ Thread thread("Non-task-runner thread");
+ ASSERT_TRUE(thread.Start());
+ this->delegate_.StartTaskRunner();
+
+ scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner();
+ // Post each ith task i+1 times on the task runner and i+1 times on
+ // the non-task-runner thread.
+ for (int i = 0; i < 20; ++i) {
+ const Closure& ith_task_runner_task = this->task_tracker_->WrapTask(
+ Bind(&test::ExpectRunsTasksInCurrentSequence, true,
+ base::RetainedRef(task_runner)),
+ i);
+ const Closure& ith_non_task_runner_task = this->task_tracker_->WrapTask(
+ Bind(&test::ExpectRunsTasksInCurrentSequence, false,
+ base::RetainedRef(task_runner)),
+ i);
+ for (int j = 0; j < i + 1; ++j) {
+ task_runner->PostTask(FROM_HERE, ith_task_runner_task);
+ thread.task_runner()->PostTask(FROM_HERE, ith_non_task_runner_task);
+ expected_task_run_counts[i] += 2;
+ }
+ }
+
+ this->delegate_.StopTaskRunner();
+ thread.Stop();
+
+ EXPECT_EQ(expected_task_run_counts,
+ this->task_tracker_->GetTaskRunCounts());
+}
+
+// TaskRunnerAffinityTest tests that the TaskRunner implementation
+// can determine if tasks will never be run on a specific thread.
+REGISTER_TYPED_TEST_CASE_P(TaskRunnerAffinityTest, RunsTasksInCurrentSequence);
+
+} // namespace base
+
+#endif // BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_
diff --git a/base/test/test_discardable_memory_allocator.cc b/base/test/test_discardable_memory_allocator.cc
new file mode 100644
index 0000000000..a9bd097c9d
--- /dev/null
+++ b/base/test/test_discardable_memory_allocator.cc
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/test_discardable_memory_allocator.h"
+
+#include <cstdint>
+#include <cstring>
+
+#include "base/logging.h"
+#include "base/memory/discardable_memory.h"
+#include "base/memory/ptr_util.h"
+
+namespace base {
+namespace {
+
+class DiscardableMemoryImpl : public DiscardableMemory {
+ public:
+ explicit DiscardableMemoryImpl(size_t size)
+ : data_(new uint8_t[size]), size_(size) {}
+
+ // Overridden from DiscardableMemory:
+ bool Lock() override {
+ DCHECK(!is_locked_);
+ is_locked_ = true;
+ return false;
+ }
+
+ void Unlock() override {
+ DCHECK(is_locked_);
+ is_locked_ = false;
+ // Force eviction to catch clients not correctly checking the return value
+ // of Lock().
+ memset(data_.get(), 0, size_);
+ }
+
+ void* data() const override {
+ DCHECK(is_locked_);
+ return data_.get();
+ }
+
+ trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump(
+ const char* name,
+ trace_event::ProcessMemoryDump* pmd) const override {
+ return nullptr;
+ }
+
+ private:
+ bool is_locked_ = true;
+ std::unique_ptr<uint8_t[]> data_;
+ size_t size_;
+};
+
+} // namespace
+
+std::unique_ptr<DiscardableMemory>
+TestDiscardableMemoryAllocator::AllocateLockedDiscardableMemory(size_t size) {
+ return std::make_unique<DiscardableMemoryImpl>(size);
+}
+
+} // namespace base
diff --git a/base/test/test_discardable_memory_allocator.h b/base/test/test_discardable_memory_allocator.h
new file mode 100644
index 0000000000..87436e3bf4
--- /dev/null
+++ b/base/test/test_discardable_memory_allocator.h
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_TEST_DISCARDABLE_MEMORY_ALLOCATOR_H_
+#define BASE_TEST_TEST_DISCARDABLE_MEMORY_ALLOCATOR_H_
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/memory/discardable_memory_allocator.h"
+
+namespace base {
+
+// TestDiscardableMemoryAllocator is a simple DiscardableMemoryAllocator
+// implementation that can be used for testing. It allocates one-shot
+// DiscardableMemory instances backed by heap memory.
+class TestDiscardableMemoryAllocator : public DiscardableMemoryAllocator {
+ public:
+ constexpr TestDiscardableMemoryAllocator() = default;
+
+ // Overridden from DiscardableMemoryAllocator:
+ std::unique_ptr<DiscardableMemory> AllocateLockedDiscardableMemory(
+ size_t size) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestDiscardableMemoryAllocator);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_DISCARDABLE_MEMORY_ALLOCATOR_H_
diff --git a/base/test/test_file_util_android.cc b/base/test/test_file_util_android.cc
new file mode 100644
index 0000000000..6e93e245ba
--- /dev/null
+++ b/base/test/test_file_util_android.cc
@@ -0,0 +1,26 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/test_file_util.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "jni/ContentUriTestUtils_jni.h"
+
+using base::android::ScopedJavaLocalRef;
+
+namespace base {
+
+FilePath InsertImageIntoMediaStore(const FilePath& path) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> j_path =
+ base::android::ConvertUTF8ToJavaString(env, path.value());
+ ScopedJavaLocalRef<jstring> j_uri =
+ Java_ContentUriTestUtils_insertImageIntoMediaStore(env, j_path);
+ std::string uri = base::android::ConvertJavaStringToUTF8(j_uri);
+ return FilePath(uri);
+}
+
+} // namespace base
diff --git a/base/test/test_message_loop.cc b/base/test/test_message_loop.cc
new file mode 100644
index 0000000000..bd3610fe65
--- /dev/null
+++ b/base/test/test_message_loop.cc
@@ -0,0 +1,18 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/run_loop.h"
+#include "base/test/test_message_loop.h"
+
+namespace base {
+
+TestMessageLoop::TestMessageLoop() = default;
+
+TestMessageLoop::TestMessageLoop(MessageLoop::Type type) : loop_(type) {}
+
+TestMessageLoop::~TestMessageLoop() {
+ RunLoop().RunUntilIdle();
+}
+
+} // namespace base
diff --git a/base/test/test_message_loop.h b/base/test/test_message_loop.h
new file mode 100644
index 0000000000..9c0aed8b8b
--- /dev/null
+++ b/base/test/test_message_loop.h
@@ -0,0 +1,34 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_TEST_MESSAGE_LOOP_H_
+#define BASE_TEST_TEST_MESSAGE_LOOP_H_
+
+#include "base/message_loop/message_loop.h"
+
+namespace base {
+
+// TestMessageLoop is a convenience class for unittests that need to create a
+// message loop without a real thread backing it. For most tests,
+// it is sufficient to just instantiate TestMessageLoop as a member variable.
+//
+// TestMessageLoop will attempt to drain the underlying MessageLoop on
+// destruction for clean teardown of tests.
+class TestMessageLoop {
+ public:
+ TestMessageLoop();
+ explicit TestMessageLoop(MessageLoop::Type type);
+ ~TestMessageLoop();
+
+ const scoped_refptr<SingleThreadTaskRunner>& task_runner() {
+ return loop_.task_runner();
+ }
+
+ private:
+ MessageLoop loop_;
+};
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_MESSAGE_LOOP_H_
diff --git a/base/test/test_pending_task_unittest.cc b/base/test/test_pending_task_unittest.cc
new file mode 100644
index 0000000000..6e01c8c66a
--- /dev/null
+++ b/base/test/test_pending_task_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/test_pending_task.h"
+
+#include "base/bind.h"
+#include "base/trace_event/trace_event.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest-spi.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(TestPendingTaskTest, TraceSupport) {
+ base::TestPendingTask task;
+
+ // Check that TestPendingTask can be sent to the trace subsystem.
+ TRACE_EVENT1("test", "TestPendingTask::TraceSupport", "task", task.AsValue());
+
+ // Just a basic check that the trace output has *something* in it.
+ std::unique_ptr<base::trace_event::ConvertableToTraceFormat> task_value(
+ task.AsValue());
+ EXPECT_THAT(task_value->ToString(), ::testing::HasSubstr("post_time"));
+}
+
+TEST(TestPendingTaskTest, ToString) {
+ base::TestPendingTask task;
+
+ // Just a basic check that ToString has *something* in it.
+ EXPECT_THAT(task.ToString(), ::testing::StartsWith("TestPendingTask("));
+}
+
+TEST(TestPendingTaskTest, GTestPrettyPrint) {
+ base::TestPendingTask task;
+
+ // Check that gtest is calling the TestPendingTask's PrintTo method.
+ EXPECT_THAT(::testing::PrintToString(task),
+ ::testing::StartsWith("TestPendingTask("));
+
+ // Check that pretty printing works with the gtest iostreams operator.
+ EXPECT_NONFATAL_FAILURE(EXPECT_TRUE(false) << task, "TestPendingTask(");
+}
+
+TEST(TestPendingTaskTest, ShouldRunBefore) {
+ base::TestPendingTask task_first;
+ task_first.delay = base::TimeDelta::FromMilliseconds(1);
+ base::TestPendingTask task_after;
+ task_after.delay = base::TimeDelta::FromMilliseconds(2);
+
+ EXPECT_FALSE(task_after.ShouldRunBefore(task_first))
+ << task_after << ".ShouldRunBefore(" << task_first << ")\n";
+ EXPECT_TRUE(task_first.ShouldRunBefore(task_after))
+ << task_first << ".ShouldRunBefore(" << task_after << ")\n";
+}
+
+} // namespace base
diff --git a/base/test/test_shared_library.cc b/base/test/test_shared_library.cc
new file mode 100644
index 0000000000..99c04674ce
--- /dev/null
+++ b/base/test/test_shared_library.cc
@@ -0,0 +1,30 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/native_library_test_utils.h"
+
+extern "C" {
+
+int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetExportedValue() {
+ return g_native_library_exported_value;
+}
+
+void NATIVE_LIBRARY_TEST_ALWAYS_EXPORT SetExportedValue(int value) {
+ g_native_library_exported_value = value;
+}
+
+// A test function used only to verify basic dynamic symbol resolution.
+int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetSimpleTestValue() {
+ return 5;
+}
+
+// When called by |NativeLibraryTest.LoadLibraryPreferOwnSymbols|, this should
+// forward to the local definition of NativeLibraryTestIncrement(), even though
+// the test module also links in the native_library_test_utils source library
+// which exports it.
+int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetIncrementValue() {
+ return NativeLibraryTestIncrement();
+}
+
+} // extern "C"
diff --git a/base/test/test_suite.cc b/base/test/test_suite.cc
new file mode 100644
index 0000000000..1862940f04
--- /dev/null
+++ b/base/test/test_suite.cc
@@ -0,0 +1,486 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/test_suite.h"
+
+#include <signal.h>
+
+#include <memory>
+
+#include "base/at_exit.h"
+#include "base/base_paths.h"
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/debugger.h"
+#include "base/debug/profiler.h"
+#include "base/debug/stack_trace.h"
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/i18n/icu_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/process/memory.h"
+#include "base/test/gtest_xml_unittest_result_printer.h"
+#include "base/test/gtest_xml_util.h"
+#include "base/test/icu_test_util.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_switches.h"
+#include "base/test/test_timeouts.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/scoped_nsautorelease_pool.h"
+#if defined(OS_IOS)
+#include "base/test/test_listener_ios.h"
+#endif // OS_IOS
+#endif // OS_MACOSX
+
+#if !defined(OS_WIN)
+#include "base/i18n/rtl.h"
+#if !defined(OS_IOS)
+#include "base/strings/string_util.h"
+#include "third_party/icu/source/common/unicode/uloc.h"
+#endif
+#endif
+
+#if defined(OS_ANDROID)
+#include "base/test/test_support_android.h"
+#endif
+
+#if defined(OS_IOS)
+#include "base/test/test_support_ios.h"
+#endif
+
+#if defined(OS_LINUX)
+#include "base/test/fontconfig_util_linux.h"
+#endif
+
+namespace base {
+
+namespace {
+
+class MaybeTestDisabler : public testing::EmptyTestEventListener {
+ public:
+ void OnTestStart(const testing::TestInfo& test_info) override {
+ ASSERT_FALSE(TestSuite::IsMarkedMaybe(test_info))
+ << "Probably the OS #ifdefs don't include all of the necessary "
+ "platforms.\nPlease ensure that no tests have the MAYBE_ prefix "
+ "after the code is preprocessed.";
+ }
+};
+
+class TestClientInitializer : public testing::EmptyTestEventListener {
+ public:
+ TestClientInitializer()
+ : old_command_line_(CommandLine::NO_PROGRAM) {
+ }
+
+ void OnTestStart(const testing::TestInfo& test_info) override {
+ old_command_line_ = *CommandLine::ForCurrentProcess();
+ }
+
+ void OnTestEnd(const testing::TestInfo& test_info) override {
+ *CommandLine::ForCurrentProcess() = old_command_line_;
+ }
+
+ private:
+ CommandLine old_command_line_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestClientInitializer);
+};
+
+std::string GetProfileName() {
+ static const char kDefaultProfileName[] = "test-profile-{pid}";
+ CR_DEFINE_STATIC_LOCAL(std::string, profile_name, ());
+ if (profile_name.empty()) {
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kProfilingFile))
+ profile_name = command_line.GetSwitchValueASCII(switches::kProfilingFile);
+ else
+ profile_name = std::string(kDefaultProfileName);
+ }
+ return profile_name;
+}
+
+void InitializeLogging() {
+#if defined(OS_ANDROID)
+ InitAndroidTestLogging();
+#else
+ FilePath exe;
+ PathService::Get(FILE_EXE, &exe);
+ FilePath log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log"));
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_ALL;
+ settings.log_file = log_filename.value().c_str();
+ settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+ logging::InitLogging(settings);
+ // We want process and thread IDs because we may have multiple processes.
+ // Note: temporarily enabled timestamps in an effort to catch bug 6361.
+ logging::SetLogItems(true, true, true, true);
+#endif // !defined(OS_ANDROID)
+}
+
+} // namespace
+
+int RunUnitTestsUsingBaseTestSuite(int argc, char **argv) {
+ TestSuite test_suite(argc, argv);
+ return LaunchUnitTests(argc, argv,
+ Bind(&TestSuite::Run, Unretained(&test_suite)));
+}
+
+TestSuite::TestSuite(int argc, char** argv) : initialized_command_line_(false) {
+ PreInitialize();
+ InitializeFromCommandLine(argc, argv);
+ // Logging must be initialized before any thread has a chance to call logging
+ // functions.
+ InitializeLogging();
+}
+
+#if defined(OS_WIN)
+TestSuite::TestSuite(int argc, wchar_t** argv)
+ : initialized_command_line_(false) {
+ PreInitialize();
+ InitializeFromCommandLine(argc, argv);
+ // Logging must be initialized before any thread has a chance to call logging
+ // functions.
+ InitializeLogging();
+}
+#endif // defined(OS_WIN)
+
+TestSuite::~TestSuite() {
+ if (initialized_command_line_)
+ CommandLine::Reset();
+}
+
+void TestSuite::InitializeFromCommandLine(int argc, char** argv) {
+ initialized_command_line_ = CommandLine::Init(argc, argv);
+ testing::InitGoogleTest(&argc, argv);
+ testing::InitGoogleMock(&argc, argv);
+
+#if defined(OS_IOS)
+ InitIOSRunHook(this, argc, argv);
+#endif
+}
+
+#if defined(OS_WIN)
+void TestSuite::InitializeFromCommandLine(int argc, wchar_t** argv) {
+ // Windows CommandLine::Init ignores argv anyway.
+ initialized_command_line_ = CommandLine::Init(argc, NULL);
+ testing::InitGoogleTest(&argc, argv);
+ testing::InitGoogleMock(&argc, argv);
+}
+#endif // defined(OS_WIN)
+
+void TestSuite::PreInitialize() {
+#if defined(OS_WIN)
+ testing::GTEST_FLAG(catch_exceptions) = false;
+#endif
+ EnableTerminationOnHeapCorruption();
+#if defined(OS_LINUX) && defined(USE_AURA)
+ // When calling native char conversion functions (e.g wrctomb) we need to
+ // have the locale set. In the absence of such a call the "C" locale is the
+ // default. In the gtk code (below) gtk_init() implicitly sets a locale.
+ setlocale(LC_ALL, "");
+#endif // defined(OS_LINUX) && defined(USE_AURA)
+
+ // On Android, AtExitManager is created in
+ // testing/android/native_test_wrapper.cc before main() is called.
+#if !defined(OS_ANDROID)
+ at_exit_manager_.reset(new AtExitManager);
+#endif
+
+ // Don't add additional code to this function. Instead add it to
+ // Initialize(). See bug 6436.
+}
+
+
+// static
+bool TestSuite::IsMarkedMaybe(const testing::TestInfo& test) {
+ return strncmp(test.name(), "MAYBE_", 6) == 0;
+}
+
+void TestSuite::CatchMaybeTests() {
+ testing::TestEventListeners& listeners =
+ testing::UnitTest::GetInstance()->listeners();
+ listeners.Append(new MaybeTestDisabler);
+}
+
+void TestSuite::ResetCommandLine() {
+ testing::TestEventListeners& listeners =
+ testing::UnitTest::GetInstance()->listeners();
+ listeners.Append(new TestClientInitializer);
+}
+
+void TestSuite::AddTestLauncherResultPrinter() {
+ // Only add the custom printer if requested.
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherOutput)) {
+ return;
+ }
+
+ FilePath output_path(CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+ switches::kTestLauncherOutput));
+
+ // Do not add the result printer if output path already exists. It's an
+ // indicator there is a process printing to that file, and we're likely
+ // its child. Do not clobber the results in that case.
+ if (PathExists(output_path)) {
+ LOG(WARNING) << "Test launcher output path " << output_path.AsUTF8Unsafe()
+ << " exists. Not adding test launcher result printer.";
+ return;
+ }
+
+ printer_ = new XmlUnitTestResultPrinter;
+ CHECK(printer_->Initialize(output_path))
+ << "Output path is " << output_path.AsUTF8Unsafe()
+ << " and PathExists(output_path) is " << PathExists(output_path);
+ testing::TestEventListeners& listeners =
+ testing::UnitTest::GetInstance()->listeners();
+ listeners.Append(printer_);
+}
+
+// Don't add additional code to this method. Instead add it to
+// Initialize(). See bug 6436.
+int TestSuite::Run() {
+#if defined(OS_IOS)
+ RunTestsFromIOSApp();
+#endif
+
+#if defined(OS_MACOSX)
+ mac::ScopedNSAutoreleasePool scoped_pool;
+#endif
+
+ Initialize();
+ std::string client_func =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kTestChildProcess);
+
+ // Check to see if we are being run as a client process.
+ if (!client_func.empty())
+ return multi_process_function_list::InvokeChildProcessTest(client_func);
+#if defined(OS_IOS)
+ test_listener_ios::RegisterTestEndListener();
+#endif
+
+ int result = RUN_ALL_TESTS();
+
+#if defined(OS_MACOSX)
+ // This MUST happen before Shutdown() since Shutdown() tears down
+ // objects (such as NotificationService::current()) that Cocoa
+ // objects use to remove themselves as observers.
+ scoped_pool.Recycle();
+#endif
+
+ Shutdown();
+
+ return result;
+}
+
+void TestSuite::UnitTestAssertHandler(const char* file,
+ int line,
+ const base::StringPiece summary,
+ const base::StringPiece stack_trace) {
+#if defined(OS_ANDROID)
+ // Correlating test stdio with logcat can be difficult, so we emit this
+ // helpful little hint about what was running. Only do this for Android
+ // because other platforms don't separate out the relevant logs in the same
+ // way.
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ if (test_info) {
+ LOG(ERROR) << "Currently running: " << test_info->test_case_name() << "."
+ << test_info->name();
+ fflush(stderr);
+ }
+#endif // defined(OS_ANDROID)
+
+ // XmlUnitTestResultPrinter inherits gtest format, where assert has summary
+ // and message. In GTest, summary is just a logged text, and message is a
+ // logged text, concatenated with stack trace of assert.
+ // Concatenate summary and stack_trace here, to pass it as a message.
+ if (printer_) {
+ const std::string summary_str = summary.as_string();
+ const std::string stack_trace_str = summary_str + stack_trace.as_string();
+ printer_->OnAssert(file, line, summary_str, stack_trace_str);
+ }
+
+ // The logging system actually prints the message before calling the assert
+ // handler. Just exit now to avoid printing too many stack traces.
+ _exit(1);
+}
+
+#if defined(OS_WIN)
+namespace {
+
+// Disable optimizations to prevent function folding or other transformations
+// that will make the call stacks on failures more confusing.
+#pragma optimize("", off)
+// Handlers for invalid parameter, pure call, and abort. They generate a
+// breakpoint to ensure that we get a call stack on these failures.
+void InvalidParameter(const wchar_t* expression,
+ const wchar_t* function,
+ const wchar_t* file,
+ unsigned int line,
+ uintptr_t reserved) {
+ // CRT printed message is sufficient.
+ __debugbreak();
+ _exit(1);
+}
+
+void PureCall() {
+ fprintf(stderr, "Pure-virtual function call. Terminating.\n");
+ __debugbreak();
+ _exit(1);
+}
+
+void AbortHandler(int signal) {
+ // Print EOL after the CRT abort message.
+ fprintf(stderr, "\n");
+ __debugbreak();
+}
+#pragma optimize("", on)
+
+} // namespace
+#endif
+
+void TestSuite::SuppressErrorDialogs() {
+#if defined(OS_WIN)
+ UINT new_flags = SEM_FAILCRITICALERRORS |
+ SEM_NOGPFAULTERRORBOX |
+ SEM_NOOPENFILEERRORBOX;
+
+ // Preserve existing error mode, as discussed at
+ // http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx
+ UINT existing_flags = SetErrorMode(new_flags);
+ SetErrorMode(existing_flags | new_flags);
+
+#if defined(_DEBUG)
+ // Suppress the "Debug Assertion Failed" dialog.
+ // TODO(hbono): remove this code when gtest has it.
+ // http://groups.google.com/d/topic/googletestframework/OjuwNlXy5ac/discussion
+ _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+ _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+#endif // defined(_DEBUG)
+
+ // See crbug.com/783040 for test code to trigger all of these failures.
+ _set_invalid_parameter_handler(InvalidParameter);
+ _set_purecall_handler(PureCall);
+ signal(SIGABRT, AbortHandler);
+#endif // defined(OS_WIN)
+}
+
+void TestSuite::Initialize() {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+#if !defined(OS_IOS)
+ if (command_line->HasSwitch(switches::kWaitForDebugger)) {
+ debug::WaitForDebugger(60, true);
+ }
+#endif
+ // Set up a FeatureList instance, so that code using that API will not hit a
+ // an error that it's not set. It will be cleared automatically.
+ // TestFeatureForBrowserTest1 and TestFeatureForBrowserTest2 used in
+ // ContentBrowserTestScopedFeatureListTest to ensure ScopedFeatureList keeps
+ // features from command line.
+ std::string enabled =
+ command_line->GetSwitchValueASCII(switches::kEnableFeatures);
+ std::string disabled =
+ command_line->GetSwitchValueASCII(switches::kDisableFeatures);
+ enabled += ",TestFeatureForBrowserTest1";
+ disabled += ",TestFeatureForBrowserTest2";
+ scoped_feature_list_.InitFromCommandLine(enabled, disabled);
+
+ // The enable-features and disable-features flags were just slurped into a
+ // FeatureList, so remove them from the command line. Tests should enable and
+ // disable features via the ScopedFeatureList API rather than command-line
+ // flags.
+ CommandLine new_command_line(command_line->GetProgram());
+ CommandLine::SwitchMap switches = command_line->GetSwitches();
+
+ switches.erase(switches::kEnableFeatures);
+ switches.erase(switches::kDisableFeatures);
+
+ for (const auto& iter : switches)
+ new_command_line.AppendSwitchNative(iter.first, iter.second);
+
+ *CommandLine::ForCurrentProcess() = new_command_line;
+
+#if defined(OS_IOS)
+ InitIOSTestMessageLoop();
+#endif // OS_IOS
+
+#if defined(OS_ANDROID)
+ InitAndroidTestMessageLoop();
+#endif // else defined(OS_ANDROID)
+
+ CHECK(debug::EnableInProcessStackDumping());
+#if defined(OS_WIN)
+ RouteStdioToConsole(true);
+ // Make sure we run with high resolution timer to minimize differences
+ // between production code and test code.
+ Time::EnableHighResolutionTimer(true);
+#endif // defined(OS_WIN)
+
+ // In some cases, we do not want to see standard error dialogs.
+ if (!debug::BeingDebugged() &&
+ !command_line->HasSwitch("show-error-dialogs")) {
+ SuppressErrorDialogs();
+ debug::SetSuppressDebugUI(true);
+ assert_handler_ = std::make_unique<logging::ScopedLogAssertHandler>(
+ base::Bind(&TestSuite::UnitTestAssertHandler, base::Unretained(this)));
+ }
+
+ base::test::InitializeICUForTesting();
+
+ // On the Mac OS X command line, the default locale is *_POSIX. In Chromium,
+ // the locale is set via an OS X locale API and is never *_POSIX.
+ // Some tests (such as those involving word break iterator) will behave
+ // differently and fail if we use *POSIX locale. Setting it to en_US here
+ // does not affect tests that explicitly overrides the locale for testing.
+ // This can be an issue on all platforms other than Windows.
+ // TODO(jshin): Should we set the locale via an OS X locale API here?
+#if !defined(OS_WIN)
+#if defined(OS_IOS)
+ i18n::SetICUDefaultLocale("en_US");
+#else
+ std::string default_locale(uloc_getDefault());
+ if (EndsWith(default_locale, "POSIX", CompareCase::INSENSITIVE_ASCII))
+ i18n::SetICUDefaultLocale("en_US");
+#endif
+#endif
+
+#if defined(OS_LINUX)
+ // TODO(thomasanderson): Call TearDownFontconfig() in Shutdown(). It would
+ // currently crash because of leaked FcFontSet's in font_fallback_linux.cc.
+ SetUpFontconfig();
+#endif
+
+ CatchMaybeTests();
+ ResetCommandLine();
+ AddTestLauncherResultPrinter();
+
+ TestTimeouts::Initialize();
+
+ trace_to_file_.BeginTracingFromCommandLineOptions();
+
+ base::debug::StartProfiling(GetProfileName());
+}
+
+void TestSuite::Shutdown() {
+ base::debug::StopProfiling();
+}
+
+} // namespace base
diff --git a/base/test/test_suite.h b/base/test/test_suite.h
new file mode 100644
index 0000000000..6d852bafb9
--- /dev/null
+++ b/base/test/test_suite.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_TEST_SUITE_H_
+#define BASE_TEST_TEST_SUITE_H_
+
+// Defines a basic test suite framework for running gtest based tests. You can
+// instantiate this class in your main function and call its Run method to run
+// any gtest based tests that are linked into your executable.
+
+#include <memory>
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/trace_to_file.h"
+#include "build/build_config.h"
+
+namespace testing {
+class TestInfo;
+}
+
+namespace base {
+
+class XmlUnitTestResultPrinter;
+
+// Instantiates TestSuite, runs it and returns exit code.
+int RunUnitTestsUsingBaseTestSuite(int argc, char **argv);
+
+class TestSuite {
+ public:
+ // Match function used by the GetTestCount method.
+ typedef bool (*TestMatch)(const testing::TestInfo&);
+
+ TestSuite(int argc, char** argv);
+#if defined(OS_WIN)
+ TestSuite(int argc, wchar_t** argv);
+#endif // defined(OS_WIN)
+ virtual ~TestSuite();
+
+ // Returns true if the test is marked as "MAYBE_".
+ // When using different prefixes depending on platform, we use MAYBE_ and
+ // preprocessor directives to replace MAYBE_ with the target prefix.
+ static bool IsMarkedMaybe(const testing::TestInfo& test);
+
+ void CatchMaybeTests();
+
+ void ResetCommandLine();
+
+ void AddTestLauncherResultPrinter();
+
+ int Run();
+
+ protected:
+ // By default fatal log messages (e.g. from DCHECKs) result in error dialogs
+ // which gum up buildbots. Use a minimalistic assert handler which just
+ // terminates the process.
+ void UnitTestAssertHandler(const char* file,
+ int line,
+ const base::StringPiece summary,
+ const base::StringPiece stack_trace);
+
+ // Disable crash dialogs so that it doesn't gum up the buildbot
+ virtual void SuppressErrorDialogs();
+
+ // Override these for custom initialization and shutdown handling. Use these
+ // instead of putting complex code in your constructor/destructor.
+
+ virtual void Initialize();
+ virtual void Shutdown();
+
+ // Make sure that we setup an AtExitManager so Singleton objects will be
+ // destroyed.
+ std::unique_ptr<base::AtExitManager> at_exit_manager_;
+
+ private:
+ void InitializeFromCommandLine(int argc, char** argv);
+#if defined(OS_WIN)
+ void InitializeFromCommandLine(int argc, wchar_t** argv);
+#endif // defined(OS_WIN)
+
+ // Basic initialization for the test suite happens here.
+ void PreInitialize();
+
+ test::TraceToFile trace_to_file_;
+
+ bool initialized_command_line_;
+
+ test::ScopedFeatureList scoped_feature_list_;
+
+ XmlUnitTestResultPrinter* printer_ = nullptr;
+
+ std::unique_ptr<logging::ScopedLogAssertHandler> assert_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSuite);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_SUITE_H_
diff --git a/base/test/test_support_android.cc b/base/test/test_support_android.cc
new file mode 100644
index 0000000000..d5b656a292
--- /dev/null
+++ b/base/test/test_support_android.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdarg.h>
+#include <string.h>
+
+#include "base/android/path_utils.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_pump_android.h"
+#include "base/path_service.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/multiprocess_test.h"
+
+namespace {
+
+base::FilePath* g_test_data_dir = nullptr;
+
+struct RunState {
+ RunState(base::MessagePump::Delegate* delegate, int run_depth)
+ : delegate(delegate),
+ run_depth(run_depth),
+ should_quit(false) {
+ }
+
+ base::MessagePump::Delegate* delegate;
+
+ // Used to count how many Run() invocations are on the stack.
+ int run_depth;
+
+ // Used to flag that the current Run() invocation should return ASAP.
+ bool should_quit;
+};
+
+RunState* g_state = nullptr;
+
+// A singleton WaitableEvent wrapper so we avoid a busy loop in
+// MessagePumpForUIStub. Other platforms use the native event loop which blocks
+// when there are no pending messages.
+class Waitable {
+ public:
+ static Waitable* GetInstance() {
+ return base::Singleton<Waitable,
+ base::LeakySingletonTraits<Waitable>>::get();
+ }
+
+ // Signals that there are more work to do.
+ void Signal() { waitable_event_.Signal(); }
+
+ // Blocks until more work is scheduled.
+ void Block() { waitable_event_.Wait(); }
+
+ void Quit() {
+ g_state->should_quit = true;
+ Signal();
+ }
+
+ private:
+ friend struct base::DefaultSingletonTraits<Waitable>;
+
+ Waitable()
+ : waitable_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+ base::WaitableEvent waitable_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(Waitable);
+};
+
+// The MessagePumpForUI implementation for test purpose.
+class MessagePumpForUIStub : public base::MessagePumpForUI {
+ public:
+ MessagePumpForUIStub() : base::MessagePumpForUI() { Waitable::GetInstance(); }
+ ~MessagePumpForUIStub() override {}
+
+ bool IsTestImplementation() const override { return true; }
+
+ // In tests, there isn't a native thread, as such RunLoop::Run() should be
+ // used to run the loop instead of attaching and delegating to the native
+ // loop. As such, this override ignores the Attach() request.
+ void Attach(base::MessagePump::Delegate* delegate) override {}
+
+ void Run(base::MessagePump::Delegate* delegate) override {
+ // The following was based on message_pump_glib.cc, except we're using a
+ // WaitableEvent since there are no native message loop to use.
+ RunState state(delegate, g_state ? g_state->run_depth + 1 : 1);
+
+ RunState* previous_state = g_state;
+ g_state = &state;
+
+ // When not nested we can use the real implementation, otherwise fall back
+ // to the stub implementation.
+ if (g_state->run_depth > 1) {
+ RunNested(delegate);
+ } else {
+ MessagePumpForUI::Run(delegate);
+ }
+
+ g_state = previous_state;
+ }
+
+ void RunNested(base::MessagePump::Delegate* delegate) {
+ bool more_work_is_plausible = true;
+
+ for (;;) {
+ if (!more_work_is_plausible) {
+ Waitable::GetInstance()->Block();
+ if (g_state->should_quit)
+ break;
+ }
+
+ more_work_is_plausible = g_state->delegate->DoWork();
+ if (g_state->should_quit)
+ break;
+
+ base::TimeTicks delayed_work_time;
+ more_work_is_plausible |=
+ g_state->delegate->DoDelayedWork(&delayed_work_time);
+ if (g_state->should_quit)
+ break;
+
+ if (more_work_is_plausible)
+ continue;
+
+ more_work_is_plausible = g_state->delegate->DoIdleWork();
+ if (g_state->should_quit)
+ break;
+
+ more_work_is_plausible |= !delayed_work_time.is_null();
+ }
+ }
+
+ void Quit() override {
+ CHECK(g_state);
+ if (g_state->run_depth > 1) {
+ Waitable::GetInstance()->Quit();
+ } else {
+ MessagePumpForUI::Quit();
+ }
+ }
+
+ void ScheduleWork() override {
+ if (g_state && g_state->run_depth > 1) {
+ Waitable::GetInstance()->Signal();
+ } else {
+ MessagePumpForUI::ScheduleWork();
+ }
+ }
+
+ void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time) override {
+ if (g_state && g_state->run_depth > 1) {
+ Waitable::GetInstance()->Signal();
+ } else {
+ MessagePumpForUI::ScheduleDelayedWork(delayed_work_time);
+ }
+ }
+};
+
+std::unique_ptr<base::MessagePump> CreateMessagePumpForUIStub() {
+ return std::unique_ptr<base::MessagePump>(new MessagePumpForUIStub());
+};
+
+// Provides the test path for DIR_SOURCE_ROOT and DIR_ANDROID_APP_DATA.
+bool GetTestProviderPath(int key, base::FilePath* result) {
+ switch (key) {
+ // TODO(agrieve): Stop overriding DIR_ANDROID_APP_DATA.
+ // https://crbug.com/617734
+ // Instead DIR_ASSETS should be used to discover assets file location in
+ // tests.
+ case base::DIR_ANDROID_APP_DATA:
+ case base::DIR_ASSETS:
+ case base::DIR_SOURCE_ROOT:
+ CHECK(g_test_data_dir != nullptr);
+ *result = *g_test_data_dir;
+ return true;
+ default:
+ return false;
+ }
+}
+
+void InitPathProvider(int key) {
+ base::FilePath path;
+ // If failed to override the key, that means the way has not been registered.
+ if (GetTestProviderPath(key, &path) &&
+ !base::PathService::Override(key, path)) {
+ base::PathService::RegisterProvider(&GetTestProviderPath, key, key + 1);
+ }
+}
+
+} // namespace
+
+namespace base {
+
+void InitAndroidTestLogging() {
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ logging::InitLogging(settings);
+ // To view log output with IDs and timestamps use "adb logcat -v threadtime".
+ logging::SetLogItems(false, // Process ID
+ false, // Thread ID
+ false, // Timestamp
+ false); // Tick count
+}
+
+void InitAndroidTestPaths(const FilePath& test_data_dir) {
+ if (g_test_data_dir) {
+ CHECK(test_data_dir == *g_test_data_dir);
+ return;
+ }
+ g_test_data_dir = new FilePath(test_data_dir);
+ InitPathProvider(DIR_SOURCE_ROOT);
+ InitPathProvider(DIR_ANDROID_APP_DATA);
+ InitPathProvider(DIR_ASSETS);
+}
+
+void InitAndroidTestMessageLoop() {
+ if (!MessageLoop::InitMessagePumpForUIFactory(&CreateMessagePumpForUIStub))
+ LOG(INFO) << "MessagePumpForUIFactory already set, unable to override.";
+}
+
+} // namespace base
diff --git a/base/test/test_support_android.h b/base/test/test_support_android.h
new file mode 100644
index 0000000000..4942e54611
--- /dev/null
+++ b/base/test/test_support_android.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_TEST_SUPPORT_ANDROID_H_
+#define BASE_TEST_TEST_SUPPORT_ANDROID_H_
+
+#include "base/base_export.h"
+
+namespace base {
+
+class FilePath;
+
+// Init logging for tests on Android. Logs will be output into Android's logcat.
+BASE_EXPORT void InitAndroidTestLogging();
+
+// Init path providers for tests on Android.
+BASE_EXPORT void InitAndroidTestPaths(const FilePath& test_data_dir);
+
+// Init the message loop for tests on Android.
+BASE_EXPORT void InitAndroidTestMessageLoop();
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_SUPPORT_ANDROID_H_
diff --git a/base/test/test_ui_thread_android.cc b/base/test/test_ui_thread_android.cc
new file mode 100644
index 0000000000..d19fefaa6b
--- /dev/null
+++ b/base/test/test_ui_thread_android.cc
@@ -0,0 +1,14 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "base/test/test_ui_thread_android.h"
+
+#include "jni/TestUiThread_jni.h"
+
+namespace base {
+
+void StartTestUiThreadLooper() {
+ Java_TestUiThread_loop(base::android::AttachCurrentThread());
+}
+
+} // namespace base
diff --git a/base/test/test_ui_thread_android.h b/base/test/test_ui_thread_android.h
new file mode 100644
index 0000000000..233911aa86
--- /dev/null
+++ b/base/test/test_ui_thread_android.h
@@ -0,0 +1,20 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_TEST_UI_THREAD_ANDROID_
+#define BASE_TEST_TEST_UI_THREAD_ANDROID_
+
+#include <jni.h>
+
+namespace base {
+
+// Set up a thread as the Chromium UI Thread, and run its looper. This is is
+// intended for C++ unit tests (e.g. the net unit tests) that don't run with the
+// UI thread as their main looper, but test code that, on Android, uses UI
+// thread events, so need a running UI thread.
+void StartTestUiThreadLooper();
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_UI_THREAD_ANDROID_
diff --git a/base/test/thread_test_helper.cc b/base/test/thread_test_helper.cc
new file mode 100644
index 0000000000..f3ca7807f5
--- /dev/null
+++ b/base/test/thread_test_helper.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/thread_test_helper.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace base {
+
+ThreadTestHelper::ThreadTestHelper(
+ scoped_refptr<SequencedTaskRunner> target_sequence)
+ : test_result_(false),
+ target_sequence_(std::move(target_sequence)),
+ done_event_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+bool ThreadTestHelper::Run() {
+ if (!target_sequence_->PostTask(
+ FROM_HERE, base::BindOnce(&ThreadTestHelper::RunOnSequence, this))) {
+ return false;
+ }
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ done_event_.Wait();
+ return test_result_;
+}
+
+void ThreadTestHelper::RunTest() { set_test_result(true); }
+
+ThreadTestHelper::~ThreadTestHelper() = default;
+
+void ThreadTestHelper::RunOnSequence() {
+ RunTest();
+ done_event_.Signal();
+}
+
+} // namespace base
diff --git a/base/test/thread_test_helper.h b/base/test/thread_test_helper.h
new file mode 100644
index 0000000000..935e7efc6b
--- /dev/null
+++ b/base/test/thread_test_helper.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_THREAD_TEST_HELPER_H_
+#define BASE_TEST_THREAD_TEST_HELPER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace base {
+
+// Helper class that executes code on a given target sequence/thread while
+// blocking on the invoking sequence/thread. To use, derive from this class and
+// overwrite RunTest. An alternative use of this class is to use it directly. It
+// will then block until all pending tasks on a given sequence/thread have been
+// executed.
+class ThreadTestHelper : public RefCountedThreadSafe<ThreadTestHelper> {
+ public:
+ explicit ThreadTestHelper(scoped_refptr<SequencedTaskRunner> target_sequence);
+
+ // True if RunTest() was successfully executed on the target sequence.
+ bool Run() WARN_UNUSED_RESULT;
+
+ virtual void RunTest();
+
+ protected:
+ friend class RefCountedThreadSafe<ThreadTestHelper>;
+
+ virtual ~ThreadTestHelper();
+
+ // Use this method to store the result of RunTest().
+ void set_test_result(bool test_result) { test_result_ = test_result; }
+
+ private:
+ void RunOnSequence();
+
+ bool test_result_;
+ scoped_refptr<SequencedTaskRunner> target_sequence_;
+ WaitableEvent done_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadTestHelper);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_THREAD_TEST_HELPER_H_
diff --git a/base/test/trace_event_analyzer.cc b/base/test/trace_event_analyzer.cc
new file mode 100644
index 0000000000..cab2899613
--- /dev/null
+++ b/base/test/trace_event_analyzer.cc
@@ -0,0 +1,1069 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/trace_event_analyzer.h"
+
+#include <math.h>
+
+#include <algorithm>
+#include <set>
+
+#include "base/json/json_reader.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/run_loop.h"
+#include "base/strings/pattern.h"
+#include "base/trace_event/trace_buffer.h"
+#include "base/trace_event/trace_config.h"
+#include "base/trace_event/trace_log.h"
+#include "base/values.h"
+
+namespace {
+void OnTraceDataCollected(base::OnceClosure quit_closure,
+ base::trace_event::TraceResultBuffer* buffer,
+ const scoped_refptr<base::RefCountedString>& json,
+ bool has_more_events) {
+ buffer->AddFragment(json->data());
+ if (!has_more_events)
+ std::move(quit_closure).Run();
+}
+} // namespace
+
+namespace trace_analyzer {
+
+// TraceEvent
+
+TraceEvent::TraceEvent()
+ : thread(0, 0),
+ timestamp(0),
+ duration(0),
+ phase(TRACE_EVENT_PHASE_BEGIN),
+ other_event(nullptr) {}
+
+TraceEvent::TraceEvent(TraceEvent&& other) = default;
+
+TraceEvent::~TraceEvent() = default;
+
+TraceEvent& TraceEvent::operator=(TraceEvent&& rhs) = default;
+
+bool TraceEvent::SetFromJSON(const base::Value* event_value) {
+ if (event_value->type() != base::Value::Type::DICTIONARY) {
+ LOG(ERROR) << "Value must be Type::DICTIONARY";
+ return false;
+ }
+ const base::DictionaryValue* dictionary =
+ static_cast<const base::DictionaryValue*>(event_value);
+
+ std::string phase_str;
+ const base::DictionaryValue* args = nullptr;
+
+ if (!dictionary->GetString("ph", &phase_str)) {
+ LOG(ERROR) << "ph is missing from TraceEvent JSON";
+ return false;
+ }
+
+ phase = *phase_str.data();
+
+ bool may_have_duration = (phase == TRACE_EVENT_PHASE_COMPLETE);
+ bool require_origin = (phase != TRACE_EVENT_PHASE_METADATA);
+ bool require_id = (phase == TRACE_EVENT_PHASE_ASYNC_BEGIN ||
+ phase == TRACE_EVENT_PHASE_ASYNC_STEP_INTO ||
+ phase == TRACE_EVENT_PHASE_ASYNC_STEP_PAST ||
+ phase == TRACE_EVENT_PHASE_MEMORY_DUMP ||
+ phase == TRACE_EVENT_PHASE_ENTER_CONTEXT ||
+ phase == TRACE_EVENT_PHASE_LEAVE_CONTEXT ||
+ phase == TRACE_EVENT_PHASE_CREATE_OBJECT ||
+ phase == TRACE_EVENT_PHASE_DELETE_OBJECT ||
+ phase == TRACE_EVENT_PHASE_SNAPSHOT_OBJECT ||
+ phase == TRACE_EVENT_PHASE_ASYNC_END);
+
+ if (require_origin && !dictionary->GetInteger("pid", &thread.process_id)) {
+ LOG(ERROR) << "pid is missing from TraceEvent JSON";
+ return false;
+ }
+ if (require_origin && !dictionary->GetInteger("tid", &thread.thread_id)) {
+ LOG(ERROR) << "tid is missing from TraceEvent JSON";
+ return false;
+ }
+ if (require_origin && !dictionary->GetDouble("ts", &timestamp)) {
+ LOG(ERROR) << "ts is missing from TraceEvent JSON";
+ return false;
+ }
+ if (may_have_duration) {
+ dictionary->GetDouble("dur", &duration);
+ }
+ if (!dictionary->GetString("cat", &category)) {
+ LOG(ERROR) << "cat is missing from TraceEvent JSON";
+ return false;
+ }
+ if (!dictionary->GetString("name", &name)) {
+ LOG(ERROR) << "name is missing from TraceEvent JSON";
+ return false;
+ }
+ if (!dictionary->GetDictionary("args", &args)) {
+ LOG(ERROR) << "args is missing from TraceEvent JSON";
+ return false;
+ }
+ if (require_id && !dictionary->GetString("id", &id)) {
+ LOG(ERROR) << "id is missing from ASYNC_BEGIN/ASYNC_END TraceEvent JSON";
+ return false;
+ }
+
+ dictionary->GetDouble("tdur", &thread_duration);
+ dictionary->GetDouble("tts", &thread_timestamp);
+ dictionary->GetString("scope", &scope);
+ dictionary->GetString("bind_id", &bind_id);
+ dictionary->GetBoolean("flow_out", &flow_out);
+ dictionary->GetBoolean("flow_in", &flow_in);
+
+ const base::DictionaryValue* id2;
+ if (dictionary->GetDictionary("id2", &id2)) {
+ id2->GetString("global", &global_id2);
+ id2->GetString("local", &local_id2);
+ }
+
+ // For each argument, copy the type and create a trace_analyzer::TraceValue.
+ for (base::DictionaryValue::Iterator it(*args); !it.IsAtEnd();
+ it.Advance()) {
+ std::string str;
+ bool boolean = false;
+ int int_num = 0;
+ double double_num = 0.0;
+ if (it.value().GetAsString(&str)) {
+ arg_strings[it.key()] = str;
+ } else if (it.value().GetAsInteger(&int_num)) {
+ arg_numbers[it.key()] = static_cast<double>(int_num);
+ } else if (it.value().GetAsBoolean(&boolean)) {
+ arg_numbers[it.key()] = static_cast<double>(boolean ? 1 : 0);
+ } else if (it.value().GetAsDouble(&double_num)) {
+ arg_numbers[it.key()] = double_num;
+ }
+ // Record all arguments as values.
+ arg_values[it.key()] = it.value().CreateDeepCopy();
+ }
+
+ return true;
+}
+
+double TraceEvent::GetAbsTimeToOtherEvent() const {
+ return fabs(other_event->timestamp - timestamp);
+}
+
+bool TraceEvent::GetArgAsString(const std::string& name,
+ std::string* arg) const {
+ const auto it = arg_strings.find(name);
+ if (it != arg_strings.end()) {
+ *arg = it->second;
+ return true;
+ }
+ return false;
+}
+
+bool TraceEvent::GetArgAsNumber(const std::string& name,
+ double* arg) const {
+ const auto it = arg_numbers.find(name);
+ if (it != arg_numbers.end()) {
+ *arg = it->second;
+ return true;
+ }
+ return false;
+}
+
+bool TraceEvent::GetArgAsValue(const std::string& name,
+ std::unique_ptr<base::Value>* arg) const {
+ const auto it = arg_values.find(name);
+ if (it != arg_values.end()) {
+ *arg = it->second->CreateDeepCopy();
+ return true;
+ }
+ return false;
+}
+
+bool TraceEvent::HasStringArg(const std::string& name) const {
+ return (arg_strings.find(name) != arg_strings.end());
+}
+
+bool TraceEvent::HasNumberArg(const std::string& name) const {
+ return (arg_numbers.find(name) != arg_numbers.end());
+}
+
+bool TraceEvent::HasArg(const std::string& name) const {
+ return (arg_values.find(name) != arg_values.end());
+}
+
+std::string TraceEvent::GetKnownArgAsString(const std::string& name) const {
+ std::string arg_string;
+ bool result = GetArgAsString(name, &arg_string);
+ DCHECK(result);
+ return arg_string;
+}
+
+double TraceEvent::GetKnownArgAsDouble(const std::string& name) const {
+ double arg_double = 0;
+ bool result = GetArgAsNumber(name, &arg_double);
+ DCHECK(result);
+ return arg_double;
+}
+
+int TraceEvent::GetKnownArgAsInt(const std::string& name) const {
+ double arg_double = 0;
+ bool result = GetArgAsNumber(name, &arg_double);
+ DCHECK(result);
+ return static_cast<int>(arg_double);
+}
+
+bool TraceEvent::GetKnownArgAsBool(const std::string& name) const {
+ double arg_double = 0;
+ bool result = GetArgAsNumber(name, &arg_double);
+ DCHECK(result);
+ return (arg_double != 0.0);
+}
+
+std::unique_ptr<base::Value> TraceEvent::GetKnownArgAsValue(
+ const std::string& name) const {
+ std::unique_ptr<base::Value> arg_value;
+ bool result = GetArgAsValue(name, &arg_value);
+ DCHECK(result);
+ return arg_value;
+}
+
+// QueryNode
+
+QueryNode::QueryNode(const Query& query) : query_(query) {
+}
+
+QueryNode::~QueryNode() = default;
+
+// Query
+
+Query::Query(TraceEventMember member)
+ : type_(QUERY_EVENT_MEMBER),
+ operator_(OP_INVALID),
+ member_(member),
+ number_(0),
+ is_pattern_(false) {
+}
+
+Query::Query(TraceEventMember member, const std::string& arg_name)
+ : type_(QUERY_EVENT_MEMBER),
+ operator_(OP_INVALID),
+ member_(member),
+ number_(0),
+ string_(arg_name),
+ is_pattern_(false) {
+}
+
+Query::Query(const Query& query) = default;
+
+Query::~Query() = default;
+
+Query Query::String(const std::string& str) {
+ return Query(str);
+}
+
+Query Query::Double(double num) {
+ return Query(num);
+}
+
+Query Query::Int(int32_t num) {
+ return Query(static_cast<double>(num));
+}
+
+Query Query::Uint(uint32_t num) {
+ return Query(static_cast<double>(num));
+}
+
+Query Query::Bool(bool boolean) {
+ return Query(boolean ? 1.0 : 0.0);
+}
+
+Query Query::Phase(char phase) {
+ return Query(static_cast<double>(phase));
+}
+
+Query Query::Pattern(const std::string& pattern) {
+ Query query(pattern);
+ query.is_pattern_ = true;
+ return query;
+}
+
+bool Query::Evaluate(const TraceEvent& event) const {
+ // First check for values that can convert to bool.
+
+ // double is true if != 0:
+ double bool_value = 0.0;
+ bool is_bool = GetAsDouble(event, &bool_value);
+ if (is_bool)
+ return (bool_value != 0.0);
+
+ // string is true if it is non-empty:
+ std::string str_value;
+ bool is_str = GetAsString(event, &str_value);
+ if (is_str)
+ return !str_value.empty();
+
+ DCHECK_EQ(QUERY_BOOLEAN_OPERATOR, type_)
+ << "Invalid query: missing boolean expression";
+ DCHECK(left_.get());
+ DCHECK(right_.get() || is_unary_operator());
+
+ if (is_comparison_operator()) {
+ DCHECK(left().is_value() && right().is_value())
+ << "Invalid query: comparison operator used between event member and "
+ "value.";
+ bool compare_result = false;
+ if (CompareAsDouble(event, &compare_result))
+ return compare_result;
+ if (CompareAsString(event, &compare_result))
+ return compare_result;
+ return false;
+ }
+ // It's a logical operator.
+ switch (operator_) {
+ case OP_AND:
+ return left().Evaluate(event) && right().Evaluate(event);
+ case OP_OR:
+ return left().Evaluate(event) || right().Evaluate(event);
+ case OP_NOT:
+ return !left().Evaluate(event);
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool Query::CompareAsDouble(const TraceEvent& event, bool* result) const {
+ double lhs, rhs;
+ if (!left().GetAsDouble(event, &lhs) || !right().GetAsDouble(event, &rhs))
+ return false;
+ switch (operator_) {
+ case OP_EQ:
+ *result = (lhs == rhs);
+ return true;
+ case OP_NE:
+ *result = (lhs != rhs);
+ return true;
+ case OP_LT:
+ *result = (lhs < rhs);
+ return true;
+ case OP_LE:
+ *result = (lhs <= rhs);
+ return true;
+ case OP_GT:
+ *result = (lhs > rhs);
+ return true;
+ case OP_GE:
+ *result = (lhs >= rhs);
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool Query::CompareAsString(const TraceEvent& event, bool* result) const {
+ std::string lhs, rhs;
+ if (!left().GetAsString(event, &lhs) || !right().GetAsString(event, &rhs))
+ return false;
+ switch (operator_) {
+ case OP_EQ:
+ if (right().is_pattern_)
+ *result = base::MatchPattern(lhs, rhs);
+ else if (left().is_pattern_)
+ *result = base::MatchPattern(rhs, lhs);
+ else
+ *result = (lhs == rhs);
+ return true;
+ case OP_NE:
+ if (right().is_pattern_)
+ *result = !base::MatchPattern(lhs, rhs);
+ else if (left().is_pattern_)
+ *result = !base::MatchPattern(rhs, lhs);
+ else
+ *result = (lhs != rhs);
+ return true;
+ case OP_LT:
+ *result = (lhs < rhs);
+ return true;
+ case OP_LE:
+ *result = (lhs <= rhs);
+ return true;
+ case OP_GT:
+ *result = (lhs > rhs);
+ return true;
+ case OP_GE:
+ *result = (lhs >= rhs);
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool Query::EvaluateArithmeticOperator(const TraceEvent& event,
+ double* num) const {
+ DCHECK_EQ(QUERY_ARITHMETIC_OPERATOR, type_);
+ DCHECK(left_.get());
+ DCHECK(right_.get() || is_unary_operator());
+
+ double lhs = 0, rhs = 0;
+ if (!left().GetAsDouble(event, &lhs))
+ return false;
+ if (!is_unary_operator() && !right().GetAsDouble(event, &rhs))
+ return false;
+
+ switch (operator_) {
+ case OP_ADD:
+ *num = lhs + rhs;
+ return true;
+ case OP_SUB:
+ *num = lhs - rhs;
+ return true;
+ case OP_MUL:
+ *num = lhs * rhs;
+ return true;
+ case OP_DIV:
+ *num = lhs / rhs;
+ return true;
+ case OP_MOD:
+ *num = static_cast<double>(static_cast<int64_t>(lhs) %
+ static_cast<int64_t>(rhs));
+ return true;
+ case OP_NEGATE:
+ *num = -lhs;
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool Query::GetAsDouble(const TraceEvent& event, double* num) const {
+ switch (type_) {
+ case QUERY_ARITHMETIC_OPERATOR:
+ return EvaluateArithmeticOperator(event, num);
+ case QUERY_EVENT_MEMBER:
+ return GetMemberValueAsDouble(event, num);
+ case QUERY_NUMBER:
+ *num = number_;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Query::GetAsString(const TraceEvent& event, std::string* str) const {
+ switch (type_) {
+ case QUERY_EVENT_MEMBER:
+ return GetMemberValueAsString(event, str);
+ case QUERY_STRING:
+ *str = string_;
+ return true;
+ default:
+ return false;
+ }
+}
+
+const TraceEvent* Query::SelectTargetEvent(const TraceEvent* event,
+ TraceEventMember member) {
+ if (member >= OTHER_FIRST_MEMBER && member <= OTHER_LAST_MEMBER) {
+ return event->other_event;
+ } else if (member >= PREV_FIRST_MEMBER && member <= PREV_LAST_MEMBER) {
+ return event->prev_event;
+ } else {
+ return event;
+ }
+}
+
+bool Query::GetMemberValueAsDouble(const TraceEvent& event,
+ double* num) const {
+ DCHECK_EQ(QUERY_EVENT_MEMBER, type_);
+
+ // This could be a request for a member of |event| or a member of |event|'s
+ // associated previous or next event. Store the target event in the_event:
+ const TraceEvent* the_event = SelectTargetEvent(&event, member_);
+
+ // Request for member of associated event, but there is no associated event.
+ if (!the_event)
+ return false;
+
+ switch (member_) {
+ case EVENT_PID:
+ case OTHER_PID:
+ case PREV_PID:
+ *num = static_cast<double>(the_event->thread.process_id);
+ return true;
+ case EVENT_TID:
+ case OTHER_TID:
+ case PREV_TID:
+ *num = static_cast<double>(the_event->thread.thread_id);
+ return true;
+ case EVENT_TIME:
+ case OTHER_TIME:
+ case PREV_TIME:
+ *num = the_event->timestamp;
+ return true;
+ case EVENT_DURATION:
+ if (!the_event->has_other_event())
+ return false;
+ *num = the_event->GetAbsTimeToOtherEvent();
+ return true;
+ case EVENT_COMPLETE_DURATION:
+ if (the_event->phase != TRACE_EVENT_PHASE_COMPLETE)
+ return false;
+ *num = the_event->duration;
+ return true;
+ case EVENT_PHASE:
+ case OTHER_PHASE:
+ case PREV_PHASE:
+ *num = static_cast<double>(the_event->phase);
+ return true;
+ case EVENT_HAS_STRING_ARG:
+ case OTHER_HAS_STRING_ARG:
+ case PREV_HAS_STRING_ARG:
+ *num = (the_event->HasStringArg(string_) ? 1.0 : 0.0);
+ return true;
+ case EVENT_HAS_NUMBER_ARG:
+ case OTHER_HAS_NUMBER_ARG:
+ case PREV_HAS_NUMBER_ARG:
+ *num = (the_event->HasNumberArg(string_) ? 1.0 : 0.0);
+ return true;
+ case EVENT_ARG:
+ case OTHER_ARG:
+ case PREV_ARG: {
+ // Search for the argument name and return its value if found.
+ std::map<std::string, double>::const_iterator num_i =
+ the_event->arg_numbers.find(string_);
+ if (num_i == the_event->arg_numbers.end())
+ return false;
+ *num = num_i->second;
+ return true;
+ }
+ case EVENT_HAS_OTHER:
+ // return 1.0 (true) if the other event exists
+ *num = event.other_event ? 1.0 : 0.0;
+ return true;
+ case EVENT_HAS_PREV:
+ *num = event.prev_event ? 1.0 : 0.0;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Query::GetMemberValueAsString(const TraceEvent& event,
+ std::string* str) const {
+ DCHECK_EQ(QUERY_EVENT_MEMBER, type_);
+
+ // This could be a request for a member of |event| or a member of |event|'s
+ // associated previous or next event. Store the target event in the_event:
+ const TraceEvent* the_event = SelectTargetEvent(&event, member_);
+
+ // Request for member of associated event, but there is no associated event.
+ if (!the_event)
+ return false;
+
+ switch (member_) {
+ case EVENT_CATEGORY:
+ case OTHER_CATEGORY:
+ case PREV_CATEGORY:
+ *str = the_event->category;
+ return true;
+ case EVENT_NAME:
+ case OTHER_NAME:
+ case PREV_NAME:
+ *str = the_event->name;
+ return true;
+ case EVENT_ID:
+ case OTHER_ID:
+ case PREV_ID:
+ *str = the_event->id;
+ return true;
+ case EVENT_ARG:
+ case OTHER_ARG:
+ case PREV_ARG: {
+ // Search for the argument name and return its value if found.
+ std::map<std::string, std::string>::const_iterator str_i =
+ the_event->arg_strings.find(string_);
+ if (str_i == the_event->arg_strings.end())
+ return false;
+ *str = str_i->second;
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+Query::Query(const std::string& str)
+ : type_(QUERY_STRING),
+ operator_(OP_INVALID),
+ member_(EVENT_INVALID),
+ number_(0),
+ string_(str),
+ is_pattern_(false) {
+}
+
+Query::Query(double num)
+ : type_(QUERY_NUMBER),
+ operator_(OP_INVALID),
+ member_(EVENT_INVALID),
+ number_(num),
+ is_pattern_(false) {
+}
+const Query& Query::left() const {
+ return left_->query();
+}
+
+const Query& Query::right() const {
+ return right_->query();
+}
+
+Query Query::operator==(const Query& rhs) const {
+ return Query(*this, rhs, OP_EQ);
+}
+
+Query Query::operator!=(const Query& rhs) const {
+ return Query(*this, rhs, OP_NE);
+}
+
+Query Query::operator<(const Query& rhs) const {
+ return Query(*this, rhs, OP_LT);
+}
+
+Query Query::operator<=(const Query& rhs) const {
+ return Query(*this, rhs, OP_LE);
+}
+
+Query Query::operator>(const Query& rhs) const {
+ return Query(*this, rhs, OP_GT);
+}
+
+Query Query::operator>=(const Query& rhs) const {
+ return Query(*this, rhs, OP_GE);
+}
+
+Query Query::operator&&(const Query& rhs) const {
+ return Query(*this, rhs, OP_AND);
+}
+
+Query Query::operator||(const Query& rhs) const {
+ return Query(*this, rhs, OP_OR);
+}
+
+Query Query::operator!() const {
+ return Query(*this, OP_NOT);
+}
+
+Query Query::operator+(const Query& rhs) const {
+ return Query(*this, rhs, OP_ADD);
+}
+
+Query Query::operator-(const Query& rhs) const {
+ return Query(*this, rhs, OP_SUB);
+}
+
+Query Query::operator*(const Query& rhs) const {
+ return Query(*this, rhs, OP_MUL);
+}
+
+Query Query::operator/(const Query& rhs) const {
+ return Query(*this, rhs, OP_DIV);
+}
+
+Query Query::operator%(const Query& rhs) const {
+ return Query(*this, rhs, OP_MOD);
+}
+
+Query Query::operator-() const {
+ return Query(*this, OP_NEGATE);
+}
+
+
+Query::Query(const Query& left, const Query& right, Operator binary_op)
+ : operator_(binary_op),
+ left_(new QueryNode(left)),
+ right_(new QueryNode(right)),
+ member_(EVENT_INVALID),
+ number_(0) {
+ type_ = (binary_op < OP_ADD ?
+ QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR);
+}
+
+Query::Query(const Query& left, Operator unary_op)
+ : operator_(unary_op),
+ left_(new QueryNode(left)),
+ member_(EVENT_INVALID),
+ number_(0) {
+ type_ = (unary_op < OP_ADD ?
+ QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR);
+}
+
+namespace {
+
+// Search |events| for |query| and add matches to |output|.
+size_t FindMatchingEvents(const std::vector<TraceEvent>& events,
+ const Query& query,
+ TraceEventVector* output,
+ bool ignore_metadata_events) {
+ for (size_t i = 0; i < events.size(); ++i) {
+ if (ignore_metadata_events && events[i].phase == TRACE_EVENT_PHASE_METADATA)
+ continue;
+ if (query.Evaluate(events[i]))
+ output->push_back(&events[i]);
+ }
+ return output->size();
+}
+
+bool ParseEventsFromJson(const std::string& json,
+ std::vector<TraceEvent>* output) {
+ std::unique_ptr<base::Value> root = base::JSONReader::Read(json);
+
+ base::ListValue* root_list = nullptr;
+ if (!root.get() || !root->GetAsList(&root_list))
+ return false;
+
+ for (size_t i = 0; i < root_list->GetSize(); ++i) {
+ base::Value* item = nullptr;
+ if (root_list->Get(i, &item)) {
+ TraceEvent event;
+ if (event.SetFromJSON(item))
+ output->push_back(std::move(event));
+ else
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+// TraceAnalyzer
+
+TraceAnalyzer::TraceAnalyzer()
+ : ignore_metadata_events_(false), allow_association_changes_(true) {}
+
+TraceAnalyzer::~TraceAnalyzer() = default;
+
+// static
+TraceAnalyzer* TraceAnalyzer::Create(const std::string& json_events) {
+ std::unique_ptr<TraceAnalyzer> analyzer(new TraceAnalyzer());
+ if (analyzer->SetEvents(json_events))
+ return analyzer.release();
+ return nullptr;
+}
+
+bool TraceAnalyzer::SetEvents(const std::string& json_events) {
+ raw_events_.clear();
+ if (!ParseEventsFromJson(json_events, &raw_events_))
+ return false;
+ std::stable_sort(raw_events_.begin(), raw_events_.end());
+ ParseMetadata();
+ return true;
+}
+
+void TraceAnalyzer::AssociateBeginEndEvents() {
+ using trace_analyzer::Query;
+
+ Query begin(Query::EventPhaseIs(TRACE_EVENT_PHASE_BEGIN));
+ Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_END));
+ Query match(Query::EventName() == Query::OtherName() &&
+ Query::EventCategory() == Query::OtherCategory() &&
+ Query::EventTid() == Query::OtherTid() &&
+ Query::EventPid() == Query::OtherPid());
+
+ AssociateEvents(begin, end, match);
+}
+
+void TraceAnalyzer::AssociateAsyncBeginEndEvents(bool match_pid) {
+ using trace_analyzer::Query;
+
+ Query begin(
+ Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_BEGIN) ||
+ Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_INTO) ||
+ Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_PAST));
+ Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_END) ||
+ Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_INTO) ||
+ Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_PAST));
+ Query match(Query::EventCategory() == Query::OtherCategory() &&
+ Query::EventId() == Query::OtherId());
+
+ if (match_pid) {
+ match = match && Query::EventPid() == Query::OtherPid();
+ }
+
+ AssociateEvents(begin, end, match);
+}
+
+void TraceAnalyzer::AssociateEvents(const Query& first,
+ const Query& second,
+ const Query& match) {
+ DCHECK(allow_association_changes_)
+ << "AssociateEvents not allowed after FindEvents";
+
+ // Search for matching begin/end event pairs. When a matching end is found,
+ // it is associated with the begin event.
+ std::vector<TraceEvent*> begin_stack;
+ for (size_t event_index = 0; event_index < raw_events_.size();
+ ++event_index) {
+
+ TraceEvent& this_event = raw_events_[event_index];
+
+ if (second.Evaluate(this_event)) {
+ // Search stack for matching begin, starting from end.
+ for (int stack_index = static_cast<int>(begin_stack.size()) - 1;
+ stack_index >= 0; --stack_index) {
+ TraceEvent& begin_event = *begin_stack[stack_index];
+
+ // Temporarily set other to test against the match query.
+ const TraceEvent* other_backup = begin_event.other_event;
+ begin_event.other_event = &this_event;
+ if (match.Evaluate(begin_event)) {
+ // Found a matching begin/end pair.
+ // Set the associated previous event
+ this_event.prev_event = &begin_event;
+ // Erase the matching begin event index from the stack.
+ begin_stack.erase(begin_stack.begin() + stack_index);
+ break;
+ }
+
+ // Not a match, restore original other and continue.
+ begin_event.other_event = other_backup;
+ }
+ }
+ // Even if this_event is a |second| event that has matched an earlier
+ // |first| event, it can still also be a |first| event and be associated
+ // with a later |second| event.
+ if (first.Evaluate(this_event)) {
+ begin_stack.push_back(&this_event);
+ }
+ }
+}
+
+void TraceAnalyzer::MergeAssociatedEventArgs() {
+ for (size_t i = 0; i < raw_events_.size(); ++i) {
+ // Merge all associated events with the first event.
+ const TraceEvent* other = raw_events_[i].other_event;
+ // Avoid looping by keeping set of encountered TraceEvents.
+ std::set<const TraceEvent*> encounters;
+ encounters.insert(&raw_events_[i]);
+ while (other && encounters.find(other) == encounters.end()) {
+ encounters.insert(other);
+ raw_events_[i].arg_numbers.insert(
+ other->arg_numbers.begin(),
+ other->arg_numbers.end());
+ raw_events_[i].arg_strings.insert(
+ other->arg_strings.begin(),
+ other->arg_strings.end());
+ other = other->other_event;
+ }
+ }
+}
+
+size_t TraceAnalyzer::FindEvents(const Query& query, TraceEventVector* output) {
+ allow_association_changes_ = false;
+ output->clear();
+ return FindMatchingEvents(
+ raw_events_, query, output, ignore_metadata_events_);
+}
+
+const TraceEvent* TraceAnalyzer::FindFirstOf(const Query& query) {
+ TraceEventVector output;
+ if (FindEvents(query, &output) > 0)
+ return output.front();
+ return nullptr;
+}
+
+const TraceEvent* TraceAnalyzer::FindLastOf(const Query& query) {
+ TraceEventVector output;
+ if (FindEvents(query, &output) > 0)
+ return output.back();
+ return nullptr;
+}
+
+const std::string& TraceAnalyzer::GetThreadName(
+ const TraceEvent::ProcessThreadID& thread) {
+ // If thread is not found, just add and return empty string.
+ return thread_names_[thread];
+}
+
+void TraceAnalyzer::ParseMetadata() {
+ for (size_t i = 0; i < raw_events_.size(); ++i) {
+ TraceEvent& this_event = raw_events_[i];
+ // Check for thread name metadata.
+ if (this_event.phase != TRACE_EVENT_PHASE_METADATA ||
+ this_event.name != "thread_name")
+ continue;
+ std::map<std::string, std::string>::const_iterator string_it =
+ this_event.arg_strings.find("name");
+ if (string_it != this_event.arg_strings.end())
+ thread_names_[this_event.thread] = string_it->second;
+ }
+}
+
+// Utility functions for collecting process-local traces and creating a
+// |TraceAnalyzer| from the result.
+
+void Start(const std::string& category_filter_string) {
+ DCHECK(!base::trace_event::TraceLog::GetInstance()->IsEnabled());
+ base::trace_event::TraceLog::GetInstance()->SetEnabled(
+ base::trace_event::TraceConfig(category_filter_string, ""),
+ base::trace_event::TraceLog::RECORDING_MODE);
+}
+
+std::unique_ptr<TraceAnalyzer> Stop() {
+ DCHECK(base::trace_event::TraceLog::GetInstance()->IsEnabled());
+ base::trace_event::TraceLog::GetInstance()->SetDisabled();
+
+ base::trace_event::TraceResultBuffer buffer;
+ base::trace_event::TraceResultBuffer::SimpleOutput trace_output;
+ buffer.SetOutputCallback(trace_output.GetCallback());
+ base::RunLoop run_loop;
+ buffer.Start();
+ base::trace_event::TraceLog::GetInstance()->Flush(
+ base::BindRepeating(&OnTraceDataCollected, run_loop.QuitClosure(),
+ base::Unretained(&buffer)));
+ run_loop.Run();
+ buffer.Finish();
+
+ return base::WrapUnique(TraceAnalyzer::Create(trace_output.json_output));
+}
+
+// TraceEventVector utility functions.
+
+bool GetRateStats(const TraceEventVector& events,
+ RateStats* stats,
+ const RateStatsOptions* options) {
+ DCHECK(stats);
+ // Need at least 3 events to calculate rate stats.
+ const size_t kMinEvents = 3;
+ if (events.size() < kMinEvents) {
+ LOG(ERROR) << "Not enough events: " << events.size();
+ return false;
+ }
+
+ std::vector<double> deltas;
+ size_t num_deltas = events.size() - 1;
+ for (size_t i = 0; i < num_deltas; ++i) {
+ double delta = events.at(i + 1)->timestamp - events.at(i)->timestamp;
+ if (delta < 0.0) {
+ LOG(ERROR) << "Events are out of order";
+ return false;
+ }
+ deltas.push_back(delta);
+ }
+
+ std::sort(deltas.begin(), deltas.end());
+
+ if (options) {
+ if (options->trim_min + options->trim_max > events.size() - kMinEvents) {
+ LOG(ERROR) << "Attempt to trim too many events";
+ return false;
+ }
+ deltas.erase(deltas.begin(), deltas.begin() + options->trim_min);
+ deltas.erase(deltas.end() - options->trim_max, deltas.end());
+ }
+
+ num_deltas = deltas.size();
+ double delta_sum = 0.0;
+ for (size_t i = 0; i < num_deltas; ++i)
+ delta_sum += deltas[i];
+
+ stats->min_us = *std::min_element(deltas.begin(), deltas.end());
+ stats->max_us = *std::max_element(deltas.begin(), deltas.end());
+ stats->mean_us = delta_sum / static_cast<double>(num_deltas);
+
+ double sum_mean_offsets_squared = 0.0;
+ for (size_t i = 0; i < num_deltas; ++i) {
+ double offset = fabs(deltas[i] - stats->mean_us);
+ sum_mean_offsets_squared += offset * offset;
+ }
+ stats->standard_deviation_us =
+ sqrt(sum_mean_offsets_squared / static_cast<double>(num_deltas - 1));
+
+ return true;
+}
+
+bool FindFirstOf(const TraceEventVector& events,
+ const Query& query,
+ size_t position,
+ size_t* return_index) {
+ DCHECK(return_index);
+ for (size_t i = position; i < events.size(); ++i) {
+ if (query.Evaluate(*events[i])) {
+ *return_index = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool FindLastOf(const TraceEventVector& events,
+ const Query& query,
+ size_t position,
+ size_t* return_index) {
+ DCHECK(return_index);
+ for (size_t i = std::min(position + 1, events.size()); i != 0; --i) {
+ if (query.Evaluate(*events[i - 1])) {
+ *return_index = i - 1;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool FindClosest(const TraceEventVector& events,
+ const Query& query,
+ size_t position,
+ size_t* return_closest,
+ size_t* return_second_closest) {
+ DCHECK(return_closest);
+ if (events.empty() || position >= events.size())
+ return false;
+ size_t closest = events.size();
+ size_t second_closest = events.size();
+ for (size_t i = 0; i < events.size(); ++i) {
+ if (!query.Evaluate(*events.at(i)))
+ continue;
+ if (closest == events.size()) {
+ closest = i;
+ continue;
+ }
+ if (fabs(events.at(i)->timestamp - events.at(position)->timestamp) <
+ fabs(events.at(closest)->timestamp - events.at(position)->timestamp)) {
+ second_closest = closest;
+ closest = i;
+ } else if (second_closest == events.size()) {
+ second_closest = i;
+ }
+ }
+
+ if (closest < events.size() &&
+ (!return_second_closest || second_closest < events.size())) {
+ *return_closest = closest;
+ if (return_second_closest)
+ *return_second_closest = second_closest;
+ return true;
+ }
+
+ return false;
+}
+
+size_t CountMatches(const TraceEventVector& events,
+ const Query& query,
+ size_t begin_position,
+ size_t end_position) {
+ if (begin_position >= events.size())
+ return 0u;
+ end_position = (end_position < events.size()) ? end_position : events.size();
+ size_t count = 0u;
+ for (size_t i = begin_position; i < end_position; ++i) {
+ if (query.Evaluate(*events.at(i)))
+ ++count;
+ }
+ return count;
+}
+
+} // namespace trace_analyzer
diff --git a/base/test/trace_event_analyzer.h b/base/test/trace_event_analyzer.h
new file mode 100644
index 0000000000..dcdd2e4b5e
--- /dev/null
+++ b/base/test/trace_event_analyzer.h
@@ -0,0 +1,842 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use trace_analyzer::Query and trace_analyzer::TraceAnalyzer to search for
+// specific trace events that were generated by the trace_event.h API.
+//
+// Basic procedure:
+// - Get trace events JSON string from base::trace_event::TraceLog.
+// - Create TraceAnalyzer with JSON string.
+// - Call TraceAnalyzer::AssociateBeginEndEvents (optional).
+// - Call TraceAnalyzer::AssociateEvents (zero or more times).
+// - Call TraceAnalyzer::FindEvents with queries to find specific events.
+//
+// A Query is a boolean expression tree that evaluates to true or false for a
+// given trace event. Queries can be combined into a tree using boolean,
+// arithmetic and comparison operators that refer to data of an individual trace
+// event.
+//
+// The events are returned as trace_analyzer::TraceEvent objects.
+// TraceEvent contains a single trace event's data, as well as a pointer to
+// a related trace event. The related trace event is typically the matching end
+// of a begin event or the matching begin of an end event.
+//
+// The following examples use this basic setup code to construct TraceAnalyzer
+// with the json trace string retrieved from TraceLog and construct an event
+// vector for retrieving events:
+//
+// TraceAnalyzer analyzer(json_events);
+// TraceEventVector events;
+//
+// EXAMPLE 1: Find events named "my_event".
+//
+// analyzer.FindEvents(Query(EVENT_NAME) == "my_event", &events);
+//
+// EXAMPLE 2: Find begin events named "my_event" with duration > 1 second.
+//
+// Query q = (Query(EVENT_NAME) == Query::String("my_event") &&
+// Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_BEGIN) &&
+// Query(EVENT_DURATION) > Query::Double(1000000.0));
+// analyzer.FindEvents(q, &events);
+//
+// EXAMPLE 3: Associating event pairs across threads.
+//
+// If the test needs to analyze something that starts and ends on different
+// threads, the test needs to use INSTANT events. The typical procedure is to
+// specify the same unique ID as a TRACE_EVENT argument on both the start and
+// finish INSTANT events. Then use the following procedure to associate those
+// events.
+//
+// Step 1: instrument code with custom begin/end trace events.
+// [Thread 1 tracing code]
+// TRACE_EVENT_INSTANT1("test_latency", "timing1_begin", "id", 3);
+// [Thread 2 tracing code]
+// TRACE_EVENT_INSTANT1("test_latency", "timing1_end", "id", 3);
+//
+// Step 2: associate these custom begin/end pairs.
+// Query begin(Query(EVENT_NAME) == Query::String("timing1_begin"));
+// Query end(Query(EVENT_NAME) == Query::String("timing1_end"));
+// Query match(Query(EVENT_ARG, "id") == Query(OTHER_ARG, "id"));
+// analyzer.AssociateEvents(begin, end, match);
+//
+// Step 3: search for "timing1_begin" events with existing other event.
+// Query q = (Query(EVENT_NAME) == Query::String("timing1_begin") &&
+// Query(EVENT_HAS_OTHER));
+// analyzer.FindEvents(q, &events);
+//
+// Step 4: analyze events, such as checking durations.
+// for (size_t i = 0; i < events.size(); ++i) {
+// double duration;
+// EXPECT_TRUE(events[i].GetAbsTimeToOtherEvent(&duration));
+// EXPECT_LT(duration, 1000000.0/60.0); // expect less than 1/60 second.
+// }
+//
+// There are two helper functions, Start(category_filter_string) and Stop(), for
+// facilitating the collection of process-local traces and building a
+// TraceAnalyzer from them. A typical test, that uses the helper functions,
+// looks like the following:
+//
+// TEST_F(...) {
+// Start("*");
+// [Invoke the functions you want to test their traces]
+// auto analyzer = Stop();
+//
+// [Use the analyzer to verify produced traces, as explained above]
+// }
+//
+// Note: The Stop() function needs a SingleThreadTaskRunner.
+
+#ifndef BASE_TEST_TRACE_EVENT_ANALYZER_H_
+#define BASE_TEST_TRACE_EVENT_ANALYZER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+class Value;
+}
+
+namespace trace_analyzer {
+class QueryNode;
+
+// trace_analyzer::TraceEvent is a more convenient form of the
+// base::trace_event::TraceEvent class to make tracing-based tests easier to
+// write.
+struct TraceEvent {
+ // ProcessThreadID contains a Process ID and Thread ID.
+ struct ProcessThreadID {
+ ProcessThreadID() : process_id(0), thread_id(0) {}
+ ProcessThreadID(int process_id, int thread_id)
+ : process_id(process_id), thread_id(thread_id) {}
+ bool operator< (const ProcessThreadID& rhs) const {
+ if (process_id != rhs.process_id)
+ return process_id < rhs.process_id;
+ return thread_id < rhs.thread_id;
+ }
+ int process_id;
+ int thread_id;
+ };
+
+ TraceEvent();
+ TraceEvent(TraceEvent&& other);
+ ~TraceEvent();
+
+ bool SetFromJSON(const base::Value* event_value) WARN_UNUSED_RESULT;
+
+ bool operator< (const TraceEvent& rhs) const {
+ return timestamp < rhs.timestamp;
+ }
+
+ TraceEvent& operator=(TraceEvent&& rhs);
+
+ bool has_other_event() const { return other_event; }
+
+ // Returns absolute duration in microseconds between this event and other
+ // event. Must have already verified that other_event exists by
+ // Query(EVENT_HAS_OTHER) or by calling has_other_event().
+ double GetAbsTimeToOtherEvent() const;
+
+ // Return the argument value if it exists and it is a string.
+ bool GetArgAsString(const std::string& name, std::string* arg) const;
+ // Return the argument value if it exists and it is a number.
+ bool GetArgAsNumber(const std::string& name, double* arg) const;
+ // Return the argument value if it exists.
+ bool GetArgAsValue(const std::string& name,
+ std::unique_ptr<base::Value>* arg) const;
+
+ // Check if argument exists and is string.
+ bool HasStringArg(const std::string& name) const;
+ // Check if argument exists and is number (double, int or bool).
+ bool HasNumberArg(const std::string& name) const;
+ // Check if argument exists.
+ bool HasArg(const std::string& name) const;
+
+ // Get known existing arguments as specific types.
+ // Useful when you have already queried the argument with
+ // Query(HAS_NUMBER_ARG) or Query(HAS_STRING_ARG).
+ std::string GetKnownArgAsString(const std::string& name) const;
+ double GetKnownArgAsDouble(const std::string& name) const;
+ int GetKnownArgAsInt(const std::string& name) const;
+ bool GetKnownArgAsBool(const std::string& name) const;
+ std::unique_ptr<base::Value> GetKnownArgAsValue(
+ const std::string& name) const;
+
+ // Process ID and Thread ID.
+ ProcessThreadID thread;
+
+ // Time since epoch in microseconds.
+ // Stored as double to match its JSON representation.
+ double timestamp;
+ double duration;
+ char phase;
+ std::string category;
+ std::string name;
+ std::string id;
+ double thread_duration = 0.0;
+ double thread_timestamp = 0.0;
+ std::string scope;
+ std::string bind_id;
+ bool flow_out = false;
+ bool flow_in = false;
+ std::string global_id2;
+ std::string local_id2;
+
+ // All numbers and bool values from TraceEvent args are cast to double.
+ // bool becomes 1.0 (true) or 0.0 (false).
+ std::map<std::string, double> arg_numbers;
+ std::map<std::string, std::string> arg_strings;
+ std::map<std::string, std::unique_ptr<base::Value>> arg_values;
+
+ // The other event associated with this event (or NULL).
+ const TraceEvent* other_event;
+
+ // A back-link for |other_event|. That is, if other_event is not null, then
+ // |event->other_event->prev_event == event| is always true.
+ const TraceEvent* prev_event;
+};
+
+typedef std::vector<const TraceEvent*> TraceEventVector;
+
+class Query {
+ public:
+ Query(const Query& query);
+
+ ~Query();
+
+ ////////////////////////////////////////////////////////////////
+ // Query literal values
+
+ // Compare with the given string.
+ static Query String(const std::string& str);
+
+ // Compare with the given number.
+ static Query Double(double num);
+ static Query Int(int32_t num);
+ static Query Uint(uint32_t num);
+
+ // Compare with the given bool.
+ static Query Bool(bool boolean);
+
+ // Compare with the given phase.
+ static Query Phase(char phase);
+
+ // Compare with the given string pattern. Only works with == and != operators.
+ // Example: Query(EVENT_NAME) == Query::Pattern("MyEvent*")
+ static Query Pattern(const std::string& pattern);
+
+ ////////////////////////////////////////////////////////////////
+ // Query event members
+
+ static Query EventPid() { return Query(EVENT_PID); }
+
+ static Query EventTid() { return Query(EVENT_TID); }
+
+ // Return the timestamp of the event in microseconds since epoch.
+ static Query EventTime() { return Query(EVENT_TIME); }
+
+ // Return the absolute time between event and other event in microseconds.
+ // Only works if Query::EventHasOther() == true.
+ static Query EventDuration() { return Query(EVENT_DURATION); }
+
+ // Return the duration of a COMPLETE event.
+ static Query EventCompleteDuration() {
+ return Query(EVENT_COMPLETE_DURATION);
+ }
+
+ static Query EventPhase() { return Query(EVENT_PHASE); }
+
+ static Query EventCategory() { return Query(EVENT_CATEGORY); }
+
+ static Query EventName() { return Query(EVENT_NAME); }
+
+ static Query EventId() { return Query(EVENT_ID); }
+
+ static Query EventPidIs(int process_id) {
+ return Query(EVENT_PID) == Query::Int(process_id);
+ }
+
+ static Query EventTidIs(int thread_id) {
+ return Query(EVENT_TID) == Query::Int(thread_id);
+ }
+
+ static Query EventThreadIs(const TraceEvent::ProcessThreadID& thread) {
+ return EventPidIs(thread.process_id) && EventTidIs(thread.thread_id);
+ }
+
+ static Query EventTimeIs(double timestamp) {
+ return Query(EVENT_TIME) == Query::Double(timestamp);
+ }
+
+ static Query EventDurationIs(double duration) {
+ return Query(EVENT_DURATION) == Query::Double(duration);
+ }
+
+ static Query EventPhaseIs(char phase) {
+ return Query(EVENT_PHASE) == Query::Phase(phase);
+ }
+
+ static Query EventCategoryIs(const std::string& category) {
+ return Query(EVENT_CATEGORY) == Query::String(category);
+ }
+
+ static Query EventNameIs(const std::string& name) {
+ return Query(EVENT_NAME) == Query::String(name);
+ }
+
+ static Query EventIdIs(const std::string& id) {
+ return Query(EVENT_ID) == Query::String(id);
+ }
+
+ // Evaluates to true if arg exists and is a string.
+ static Query EventHasStringArg(const std::string& arg_name) {
+ return Query(EVENT_HAS_STRING_ARG, arg_name);
+ }
+
+ // Evaluates to true if arg exists and is a number.
+ // Number arguments include types double, int and bool.
+ static Query EventHasNumberArg(const std::string& arg_name) {
+ return Query(EVENT_HAS_NUMBER_ARG, arg_name);
+ }
+
+ // Evaluates to arg value (string or number).
+ static Query EventArg(const std::string& arg_name) {
+ return Query(EVENT_ARG, arg_name);
+ }
+
+ // Return true if associated event exists.
+ static Query EventHasOther() { return Query(EVENT_HAS_OTHER); }
+
+ // Access the associated other_event's members:
+
+ static Query OtherPid() { return Query(OTHER_PID); }
+
+ static Query OtherTid() { return Query(OTHER_TID); }
+
+ static Query OtherTime() { return Query(OTHER_TIME); }
+
+ static Query OtherPhase() { return Query(OTHER_PHASE); }
+
+ static Query OtherCategory() { return Query(OTHER_CATEGORY); }
+
+ static Query OtherName() { return Query(OTHER_NAME); }
+
+ static Query OtherId() { return Query(OTHER_ID); }
+
+ static Query OtherPidIs(int process_id) {
+ return Query(OTHER_PID) == Query::Int(process_id);
+ }
+
+ static Query OtherTidIs(int thread_id) {
+ return Query(OTHER_TID) == Query::Int(thread_id);
+ }
+
+ static Query OtherThreadIs(const TraceEvent::ProcessThreadID& thread) {
+ return OtherPidIs(thread.process_id) && OtherTidIs(thread.thread_id);
+ }
+
+ static Query OtherTimeIs(double timestamp) {
+ return Query(OTHER_TIME) == Query::Double(timestamp);
+ }
+
+ static Query OtherPhaseIs(char phase) {
+ return Query(OTHER_PHASE) == Query::Phase(phase);
+ }
+
+ static Query OtherCategoryIs(const std::string& category) {
+ return Query(OTHER_CATEGORY) == Query::String(category);
+ }
+
+ static Query OtherNameIs(const std::string& name) {
+ return Query(OTHER_NAME) == Query::String(name);
+ }
+
+ static Query OtherIdIs(const std::string& id) {
+ return Query(OTHER_ID) == Query::String(id);
+ }
+
+ // Evaluates to true if arg exists and is a string.
+ static Query OtherHasStringArg(const std::string& arg_name) {
+ return Query(OTHER_HAS_STRING_ARG, arg_name);
+ }
+
+ // Evaluates to true if arg exists and is a number.
+ // Number arguments include types double, int and bool.
+ static Query OtherHasNumberArg(const std::string& arg_name) {
+ return Query(OTHER_HAS_NUMBER_ARG, arg_name);
+ }
+
+ // Evaluates to arg value (string or number).
+ static Query OtherArg(const std::string& arg_name) {
+ return Query(OTHER_ARG, arg_name);
+ }
+
+ // Access the associated prev_event's members:
+
+ static Query PrevPid() { return Query(PREV_PID); }
+
+ static Query PrevTid() { return Query(PREV_TID); }
+
+ static Query PrevTime() { return Query(PREV_TIME); }
+
+ static Query PrevPhase() { return Query(PREV_PHASE); }
+
+ static Query PrevCategory() { return Query(PREV_CATEGORY); }
+
+ static Query PrevName() { return Query(PREV_NAME); }
+
+ static Query PrevId() { return Query(PREV_ID); }
+
+ static Query PrevPidIs(int process_id) {
+ return Query(PREV_PID) == Query::Int(process_id);
+ }
+
+ static Query PrevTidIs(int thread_id) {
+ return Query(PREV_TID) == Query::Int(thread_id);
+ }
+
+ static Query PrevThreadIs(const TraceEvent::ProcessThreadID& thread) {
+ return PrevPidIs(thread.process_id) && PrevTidIs(thread.thread_id);
+ }
+
+ static Query PrevTimeIs(double timestamp) {
+ return Query(PREV_TIME) == Query::Double(timestamp);
+ }
+
+ static Query PrevPhaseIs(char phase) {
+ return Query(PREV_PHASE) == Query::Phase(phase);
+ }
+
+ static Query PrevCategoryIs(const std::string& category) {
+ return Query(PREV_CATEGORY) == Query::String(category);
+ }
+
+ static Query PrevNameIs(const std::string& name) {
+ return Query(PREV_NAME) == Query::String(name);
+ }
+
+ static Query PrevIdIs(const std::string& id) {
+ return Query(PREV_ID) == Query::String(id);
+ }
+
+ // Evaluates to true if arg exists and is a string.
+ static Query PrevHasStringArg(const std::string& arg_name) {
+ return Query(PREV_HAS_STRING_ARG, arg_name);
+ }
+
+ // Evaluates to true if arg exists and is a number.
+ // Number arguments include types double, int and bool.
+ static Query PrevHasNumberArg(const std::string& arg_name) {
+ return Query(PREV_HAS_NUMBER_ARG, arg_name);
+ }
+
+ // Evaluates to arg value (string or number).
+ static Query PrevArg(const std::string& arg_name) {
+ return Query(PREV_ARG, arg_name);
+ }
+
+ ////////////////////////////////////////////////////////////////
+ // Common queries:
+
+ // Find BEGIN events that have a corresponding END event.
+ static Query MatchBeginWithEnd() {
+ return (Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_BEGIN)) &&
+ Query(EVENT_HAS_OTHER);
+ }
+
+ // Find COMPLETE events.
+ static Query MatchComplete() {
+ return (Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_COMPLETE));
+ }
+
+ // Find ASYNC_BEGIN events that have a corresponding ASYNC_END event.
+ static Query MatchAsyncBeginWithNext() {
+ return (Query(EVENT_PHASE) ==
+ Query::Phase(TRACE_EVENT_PHASE_ASYNC_BEGIN)) &&
+ Query(EVENT_HAS_OTHER);
+ }
+
+ // Find BEGIN events of given |name| which also have associated END events.
+ static Query MatchBeginName(const std::string& name) {
+ return (Query(EVENT_NAME) == Query(name)) && MatchBeginWithEnd();
+ }
+
+ // Find COMPLETE events of given |name|.
+ static Query MatchCompleteName(const std::string& name) {
+ return (Query(EVENT_NAME) == Query(name)) && MatchComplete();
+ }
+
+ // Match given Process ID and Thread ID.
+ static Query MatchThread(const TraceEvent::ProcessThreadID& thread) {
+ return (Query(EVENT_PID) == Query::Int(thread.process_id)) &&
+ (Query(EVENT_TID) == Query::Int(thread.thread_id));
+ }
+
+ // Match event pair that spans multiple threads.
+ static Query MatchCrossThread() {
+ return (Query(EVENT_PID) != Query(OTHER_PID)) ||
+ (Query(EVENT_TID) != Query(OTHER_TID));
+ }
+
+ ////////////////////////////////////////////////////////////////
+ // Operators:
+
+ // Boolean operators:
+ Query operator==(const Query& rhs) const;
+ Query operator!=(const Query& rhs) const;
+ Query operator< (const Query& rhs) const;
+ Query operator<=(const Query& rhs) const;
+ Query operator> (const Query& rhs) const;
+ Query operator>=(const Query& rhs) const;
+ Query operator&&(const Query& rhs) const;
+ Query operator||(const Query& rhs) const;
+ Query operator!() const;
+
+ // Arithmetic operators:
+ // Following operators are applied to double arguments:
+ Query operator+(const Query& rhs) const;
+ Query operator-(const Query& rhs) const;
+ Query operator*(const Query& rhs) const;
+ Query operator/(const Query& rhs) const;
+ Query operator-() const;
+ // Mod operates on int64_t args (doubles are casted to int64_t beforehand):
+ Query operator%(const Query& rhs) const;
+
+ // Return true if the given event matches this query tree.
+ // This is a recursive method that walks the query tree.
+ bool Evaluate(const TraceEvent& event) const;
+
+ enum TraceEventMember {
+ EVENT_INVALID,
+ EVENT_PID,
+ EVENT_TID,
+ EVENT_TIME,
+ EVENT_DURATION,
+ EVENT_COMPLETE_DURATION,
+ EVENT_PHASE,
+ EVENT_CATEGORY,
+ EVENT_NAME,
+ EVENT_ID,
+ EVENT_HAS_STRING_ARG,
+ EVENT_HAS_NUMBER_ARG,
+ EVENT_ARG,
+ EVENT_HAS_OTHER,
+ EVENT_HAS_PREV,
+
+ OTHER_PID,
+ OTHER_TID,
+ OTHER_TIME,
+ OTHER_PHASE,
+ OTHER_CATEGORY,
+ OTHER_NAME,
+ OTHER_ID,
+ OTHER_HAS_STRING_ARG,
+ OTHER_HAS_NUMBER_ARG,
+ OTHER_ARG,
+
+ PREV_PID,
+ PREV_TID,
+ PREV_TIME,
+ PREV_PHASE,
+ PREV_CATEGORY,
+ PREV_NAME,
+ PREV_ID,
+ PREV_HAS_STRING_ARG,
+ PREV_HAS_NUMBER_ARG,
+ PREV_ARG,
+
+ OTHER_FIRST_MEMBER = OTHER_PID,
+ OTHER_LAST_MEMBER = OTHER_ARG,
+
+ PREV_FIRST_MEMBER = PREV_PID,
+ PREV_LAST_MEMBER = PREV_ARG,
+ };
+
+ enum Operator {
+ OP_INVALID,
+ // Boolean operators:
+ OP_EQ,
+ OP_NE,
+ OP_LT,
+ OP_LE,
+ OP_GT,
+ OP_GE,
+ OP_AND,
+ OP_OR,
+ OP_NOT,
+ // Arithmetic operators:
+ OP_ADD,
+ OP_SUB,
+ OP_MUL,
+ OP_DIV,
+ OP_MOD,
+ OP_NEGATE
+ };
+
+ enum QueryType {
+ QUERY_BOOLEAN_OPERATOR,
+ QUERY_ARITHMETIC_OPERATOR,
+ QUERY_EVENT_MEMBER,
+ QUERY_NUMBER,
+ QUERY_STRING
+ };
+
+ // Compare with the given member.
+ explicit Query(TraceEventMember member);
+
+ // Compare with the given member argument value.
+ Query(TraceEventMember member, const std::string& arg_name);
+
+ // Compare with the given string.
+ explicit Query(const std::string& str);
+
+ // Compare with the given number.
+ explicit Query(double num);
+
+ // Construct a boolean Query that returns (left <binary_op> right).
+ Query(const Query& left, const Query& right, Operator binary_op);
+
+ // Construct a boolean Query that returns (<binary_op> left).
+ Query(const Query& left, Operator unary_op);
+
+ // Try to compare left_ against right_ based on operator_.
+ // If either left or right does not convert to double, false is returned.
+ // Otherwise, true is returned and |result| is set to the comparison result.
+ bool CompareAsDouble(const TraceEvent& event, bool* result) const;
+
+ // Try to compare left_ against right_ based on operator_.
+ // If either left or right does not convert to string, false is returned.
+ // Otherwise, true is returned and |result| is set to the comparison result.
+ bool CompareAsString(const TraceEvent& event, bool* result) const;
+
+ // Attempt to convert this Query to a double. On success, true is returned
+ // and the double value is stored in |num|.
+ bool GetAsDouble(const TraceEvent& event, double* num) const;
+
+ // Attempt to convert this Query to a string. On success, true is returned
+ // and the string value is stored in |str|.
+ bool GetAsString(const TraceEvent& event, std::string* str) const;
+
+ // Evaluate this Query as an arithmetic operator on left_ and right_.
+ bool EvaluateArithmeticOperator(const TraceEvent& event,
+ double* num) const;
+
+ // For QUERY_EVENT_MEMBER Query: attempt to get the double value of the Query.
+ bool GetMemberValueAsDouble(const TraceEvent& event, double* num) const;
+
+ // For QUERY_EVENT_MEMBER Query: attempt to get the string value of the Query.
+ bool GetMemberValueAsString(const TraceEvent& event, std::string* num) const;
+
+ // Does this Query represent a value?
+ bool is_value() const { return type_ != QUERY_BOOLEAN_OPERATOR; }
+
+ bool is_unary_operator() const {
+ return operator_ == OP_NOT || operator_ == OP_NEGATE;
+ }
+
+ bool is_comparison_operator() const {
+ return operator_ != OP_INVALID && operator_ < OP_AND;
+ }
+
+ static const TraceEvent* SelectTargetEvent(const TraceEvent* ev,
+ TraceEventMember member);
+
+ const Query& left() const;
+ const Query& right() const;
+
+ private:
+ QueryType type_;
+ Operator operator_;
+ scoped_refptr<QueryNode> left_;
+ scoped_refptr<QueryNode> right_;
+ TraceEventMember member_;
+ double number_;
+ std::string string_;
+ bool is_pattern_;
+};
+
+// Implementation detail:
+// QueryNode allows Query to store a ref-counted query tree.
+class QueryNode : public base::RefCounted<QueryNode> {
+ public:
+ explicit QueryNode(const Query& query);
+ const Query& query() const { return query_; }
+
+ private:
+ friend class base::RefCounted<QueryNode>;
+ ~QueryNode();
+
+ Query query_;
+};
+
+// TraceAnalyzer helps tests search for trace events.
+class TraceAnalyzer {
+ public:
+ ~TraceAnalyzer();
+
+ // Use trace events from JSON string generated by tracing API.
+ // Returns non-NULL if the JSON is successfully parsed.
+ static TraceAnalyzer* Create(const std::string& json_events)
+ WARN_UNUSED_RESULT;
+
+ void SetIgnoreMetadataEvents(bool ignore) {
+ ignore_metadata_events_ = ignore;
+ }
+
+ // Associate BEGIN and END events with each other. This allows Query(OTHER_*)
+ // to access the associated event and enables Query(EVENT_DURATION).
+ // An end event will match the most recent begin event with the same name,
+ // category, process ID and thread ID. This matches what is shown in
+ // about:tracing. After association, the BEGIN event will point to the
+ // matching END event, but the END event will not point to the BEGIN event.
+ void AssociateBeginEndEvents();
+
+ // Associate ASYNC_BEGIN, ASYNC_STEP and ASYNC_END events with each other.
+ // An ASYNC_END event will match the most recent ASYNC_BEGIN or ASYNC_STEP
+ // event with the same name, category, and ID. This creates a singly linked
+ // list of ASYNC_BEGIN->ASYNC_STEP...->ASYNC_END.
+ // |match_pid| - If true, will only match async events which are running
+ // under the same process ID, otherwise will allow linking
+ // async events from different processes.
+ void AssociateAsyncBeginEndEvents(bool match_pid = true);
+
+ // AssociateEvents can be used to customize event associations by setting the
+ // other_event member of TraceEvent. This should be used to associate two
+ // INSTANT events.
+ //
+ // The assumptions are:
+ // - |first| events occur before |second| events.
+ // - the closest matching |second| event is the correct match.
+ //
+ // |first| - Eligible |first| events match this query.
+ // |second| - Eligible |second| events match this query.
+ // |match| - This query is run on the |first| event. The OTHER_* EventMember
+ // queries will point to an eligible |second| event. The query
+ // should evaluate to true if the |first|/|second| pair is a match.
+ //
+ // When a match is found, the pair will be associated by having the first
+ // event's other_event member point to the other. AssociateEvents does not
+ // clear previous associations, so it is possible to associate multiple pairs
+ // of events by calling AssociateEvents more than once with different queries.
+ //
+ // NOTE: AssociateEvents will overwrite existing other_event associations if
+ // the queries pass for events that already had a previous association.
+ //
+ // After calling any Find* method, it is not allowed to call AssociateEvents
+ // again.
+ void AssociateEvents(const Query& first,
+ const Query& second,
+ const Query& match);
+
+ // For each event, copy its arguments to the other_event argument map. If
+ // argument name already exists, it will not be overwritten.
+ void MergeAssociatedEventArgs();
+
+ // Find all events that match query and replace output vector.
+ size_t FindEvents(const Query& query, TraceEventVector* output);
+
+ // Find first event that matches query or NULL if not found.
+ const TraceEvent* FindFirstOf(const Query& query);
+
+ // Find last event that matches query or NULL if not found.
+ const TraceEvent* FindLastOf(const Query& query);
+
+ const std::string& GetThreadName(const TraceEvent::ProcessThreadID& thread);
+
+ private:
+ TraceAnalyzer();
+
+ bool SetEvents(const std::string& json_events) WARN_UNUSED_RESULT;
+
+ // Read metadata (thread names, etc) from events.
+ void ParseMetadata();
+
+ std::map<TraceEvent::ProcessThreadID, std::string> thread_names_;
+ std::vector<TraceEvent> raw_events_;
+ bool ignore_metadata_events_;
+ bool allow_association_changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceAnalyzer);
+};
+
+// Utility functions for collecting process-local traces and creating a
+// |TraceAnalyzer| from the result. Please see comments in trace_config.h to
+// understand how the |category_filter_string| works. Use "*" to enable all
+// default categories.
+void Start(const std::string& category_filter_string);
+std::unique_ptr<TraceAnalyzer> Stop();
+
+// Utility functions for TraceEventVector.
+
+struct RateStats {
+ double min_us;
+ double max_us;
+ double mean_us;
+ double standard_deviation_us;
+};
+
+struct RateStatsOptions {
+ RateStatsOptions() : trim_min(0u), trim_max(0u) {}
+ // After the times between events are sorted, the number of specified elements
+ // will be trimmed before calculating the RateStats. This is useful in cases
+ // where extreme outliers are tolerable and should not skew the overall
+ // average.
+ size_t trim_min; // Trim this many minimum times.
+ size_t trim_max; // Trim this many maximum times.
+};
+
+// Calculate min/max/mean and standard deviation from the times between
+// adjacent events.
+bool GetRateStats(const TraceEventVector& events,
+ RateStats* stats,
+ const RateStatsOptions* options);
+
+// Starting from |position|, find the first event that matches |query|.
+// Returns true if found, false otherwise.
+bool FindFirstOf(const TraceEventVector& events,
+ const Query& query,
+ size_t position,
+ size_t* return_index);
+
+// Starting from |position|, find the last event that matches |query|.
+// Returns true if found, false otherwise.
+bool FindLastOf(const TraceEventVector& events,
+ const Query& query,
+ size_t position,
+ size_t* return_index);
+
+// Find the closest events to |position| in time that match |query|.
+// return_second_closest may be NULL. Closeness is determined by comparing
+// with the event timestamp.
+// Returns true if found, false otherwise. If both return parameters are
+// requested, both must be found for a successful result.
+bool FindClosest(const TraceEventVector& events,
+ const Query& query,
+ size_t position,
+ size_t* return_closest,
+ size_t* return_second_closest);
+
+// Count matches, inclusive of |begin_position|, exclusive of |end_position|.
+size_t CountMatches(const TraceEventVector& events,
+ const Query& query,
+ size_t begin_position,
+ size_t end_position);
+
+// Count all matches.
+static inline size_t CountMatches(const TraceEventVector& events,
+ const Query& query) {
+ return CountMatches(events, query, 0u, events.size());
+}
+
+} // namespace trace_analyzer
+
+#endif // BASE_TEST_TRACE_EVENT_ANALYZER_H_
diff --git a/base/test/trace_event_analyzer_unittest.cc b/base/test/trace_event_analyzer_unittest.cc
new file mode 100644
index 0000000000..6461b0fe14
--- /dev/null
+++ b/base/test/trace_event_analyzer_unittest.cc
@@ -0,0 +1,961 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/trace_event_analyzer.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/trace_event/trace_buffer.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace trace_analyzer {
+
+namespace {
+
+class TraceEventAnalyzerTest : public testing::Test {
+ public:
+ void ManualSetUp();
+ void OnTraceDataCollected(
+ base::WaitableEvent* flush_complete_event,
+ const scoped_refptr<base::RefCountedString>& json_events_str,
+ bool has_more_events);
+ void BeginTracing();
+ void EndTracing();
+
+ base::trace_event::TraceResultBuffer::SimpleOutput output_;
+ base::trace_event::TraceResultBuffer buffer_;
+};
+
+void TraceEventAnalyzerTest::ManualSetUp() {
+ ASSERT_TRUE(base::trace_event::TraceLog::GetInstance());
+ buffer_.SetOutputCallback(output_.GetCallback());
+ output_.json_output.clear();
+}
+
+void TraceEventAnalyzerTest::OnTraceDataCollected(
+ base::WaitableEvent* flush_complete_event,
+ const scoped_refptr<base::RefCountedString>& json_events_str,
+ bool has_more_events) {
+ buffer_.AddFragment(json_events_str->data());
+ if (!has_more_events)
+ flush_complete_event->Signal();
+}
+
+void TraceEventAnalyzerTest::BeginTracing() {
+ output_.json_output.clear();
+ buffer_.Start();
+ base::trace_event::TraceLog::GetInstance()->SetEnabled(
+ base::trace_event::TraceConfig("*", ""),
+ base::trace_event::TraceLog::RECORDING_MODE);
+}
+
+void TraceEventAnalyzerTest::EndTracing() {
+ base::trace_event::TraceLog::GetInstance()->SetDisabled();
+ base::WaitableEvent flush_complete_event(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ base::trace_event::TraceLog::GetInstance()->Flush(
+ base::Bind(&TraceEventAnalyzerTest::OnTraceDataCollected,
+ base::Unretained(this),
+ base::Unretained(&flush_complete_event)));
+ flush_complete_event.Wait();
+ buffer_.Finish();
+}
+
+} // namespace
+
+TEST_F(TraceEventAnalyzerTest, NoEvents) {
+ ManualSetUp();
+
+ // Create an empty JSON event string:
+ buffer_.Start();
+ buffer_.Finish();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+
+ // Search for all events and verify that nothing is returned.
+ TraceEventVector found;
+ analyzer->FindEvents(Query::Bool(true), &found);
+ EXPECT_EQ(0u, found.size());
+}
+
+TEST_F(TraceEventAnalyzerTest, TraceEvent) {
+ ManualSetUp();
+
+ int int_num = 2;
+ double double_num = 3.5;
+ const char str[] = "the string";
+
+ TraceEvent event;
+ event.arg_numbers["false"] = 0.0;
+ event.arg_numbers["true"] = 1.0;
+ event.arg_numbers["int"] = static_cast<double>(int_num);
+ event.arg_numbers["double"] = double_num;
+ event.arg_strings["string"] = str;
+ event.arg_values["dict"] = WrapUnique(new base::DictionaryValue());
+
+ ASSERT_TRUE(event.HasNumberArg("false"));
+ ASSERT_TRUE(event.HasNumberArg("true"));
+ ASSERT_TRUE(event.HasNumberArg("int"));
+ ASSERT_TRUE(event.HasNumberArg("double"));
+ ASSERT_TRUE(event.HasStringArg("string"));
+ ASSERT_FALSE(event.HasNumberArg("notfound"));
+ ASSERT_FALSE(event.HasStringArg("notfound"));
+ ASSERT_TRUE(event.HasArg("dict"));
+ ASSERT_FALSE(event.HasArg("notfound"));
+
+ EXPECT_FALSE(event.GetKnownArgAsBool("false"));
+ EXPECT_TRUE(event.GetKnownArgAsBool("true"));
+ EXPECT_EQ(int_num, event.GetKnownArgAsInt("int"));
+ EXPECT_EQ(double_num, event.GetKnownArgAsDouble("double"));
+ EXPECT_STREQ(str, event.GetKnownArgAsString("string").c_str());
+
+ std::unique_ptr<base::Value> arg;
+ EXPECT_TRUE(event.GetArgAsValue("dict", &arg));
+ EXPECT_EQ(base::Value::Type::DICTIONARY, arg->type());
+}
+
+TEST_F(TraceEventAnalyzerTest, QueryEventMember) {
+ ManualSetUp();
+
+ TraceEvent event;
+ event.thread.process_id = 3;
+ event.thread.thread_id = 4;
+ event.timestamp = 1.5;
+ event.phase = TRACE_EVENT_PHASE_BEGIN;
+ event.category = "category";
+ event.name = "name";
+ event.id = "1";
+ event.arg_numbers["num"] = 7.0;
+ event.arg_strings["str"] = "the string";
+
+ // Other event with all different members:
+ TraceEvent other;
+ other.thread.process_id = 5;
+ other.thread.thread_id = 6;
+ other.timestamp = 2.5;
+ other.phase = TRACE_EVENT_PHASE_END;
+ other.category = "category2";
+ other.name = "name2";
+ other.id = "2";
+ other.arg_numbers["num2"] = 8.0;
+ other.arg_strings["str2"] = "the string 2";
+
+ event.other_event = &other;
+ ASSERT_TRUE(event.has_other_event());
+ double duration = event.GetAbsTimeToOtherEvent();
+
+ Query event_pid = Query::EventPidIs(event.thread.process_id);
+ Query event_tid = Query::EventTidIs(event.thread.thread_id);
+ Query event_time = Query::EventTimeIs(event.timestamp);
+ Query event_duration = Query::EventDurationIs(duration);
+ Query event_phase = Query::EventPhaseIs(event.phase);
+ Query event_category = Query::EventCategoryIs(event.category);
+ Query event_name = Query::EventNameIs(event.name);
+ Query event_id = Query::EventIdIs(event.id);
+ Query event_has_arg1 = Query::EventHasNumberArg("num");
+ Query event_has_arg2 = Query::EventHasStringArg("str");
+ Query event_arg1 =
+ (Query::EventArg("num") == Query::Double(event.arg_numbers["num"]));
+ Query event_arg2 =
+ (Query::EventArg("str") == Query::String(event.arg_strings["str"]));
+ Query event_has_other = Query::EventHasOther();
+ Query other_pid = Query::OtherPidIs(other.thread.process_id);
+ Query other_tid = Query::OtherTidIs(other.thread.thread_id);
+ Query other_time = Query::OtherTimeIs(other.timestamp);
+ Query other_phase = Query::OtherPhaseIs(other.phase);
+ Query other_category = Query::OtherCategoryIs(other.category);
+ Query other_name = Query::OtherNameIs(other.name);
+ Query other_id = Query::OtherIdIs(other.id);
+ Query other_has_arg1 = Query::OtherHasNumberArg("num2");
+ Query other_has_arg2 = Query::OtherHasStringArg("str2");
+ Query other_arg1 =
+ (Query::OtherArg("num2") == Query::Double(other.arg_numbers["num2"]));
+ Query other_arg2 =
+ (Query::OtherArg("str2") == Query::String(other.arg_strings["str2"]));
+
+ EXPECT_TRUE(event_pid.Evaluate(event));
+ EXPECT_TRUE(event_tid.Evaluate(event));
+ EXPECT_TRUE(event_time.Evaluate(event));
+ EXPECT_TRUE(event_duration.Evaluate(event));
+ EXPECT_TRUE(event_phase.Evaluate(event));
+ EXPECT_TRUE(event_category.Evaluate(event));
+ EXPECT_TRUE(event_name.Evaluate(event));
+ EXPECT_TRUE(event_id.Evaluate(event));
+ EXPECT_TRUE(event_has_arg1.Evaluate(event));
+ EXPECT_TRUE(event_has_arg2.Evaluate(event));
+ EXPECT_TRUE(event_arg1.Evaluate(event));
+ EXPECT_TRUE(event_arg2.Evaluate(event));
+ EXPECT_TRUE(event_has_other.Evaluate(event));
+ EXPECT_TRUE(other_pid.Evaluate(event));
+ EXPECT_TRUE(other_tid.Evaluate(event));
+ EXPECT_TRUE(other_time.Evaluate(event));
+ EXPECT_TRUE(other_phase.Evaluate(event));
+ EXPECT_TRUE(other_category.Evaluate(event));
+ EXPECT_TRUE(other_name.Evaluate(event));
+ EXPECT_TRUE(other_id.Evaluate(event));
+ EXPECT_TRUE(other_has_arg1.Evaluate(event));
+ EXPECT_TRUE(other_has_arg2.Evaluate(event));
+ EXPECT_TRUE(other_arg1.Evaluate(event));
+ EXPECT_TRUE(other_arg2.Evaluate(event));
+
+ // Evaluate event queries against other to verify the queries fail when the
+ // event members are wrong.
+ EXPECT_FALSE(event_pid.Evaluate(other));
+ EXPECT_FALSE(event_tid.Evaluate(other));
+ EXPECT_FALSE(event_time.Evaluate(other));
+ EXPECT_FALSE(event_duration.Evaluate(other));
+ EXPECT_FALSE(event_phase.Evaluate(other));
+ EXPECT_FALSE(event_category.Evaluate(other));
+ EXPECT_FALSE(event_name.Evaluate(other));
+ EXPECT_FALSE(event_id.Evaluate(other));
+ EXPECT_FALSE(event_has_arg1.Evaluate(other));
+ EXPECT_FALSE(event_has_arg2.Evaluate(other));
+ EXPECT_FALSE(event_arg1.Evaluate(other));
+ EXPECT_FALSE(event_arg2.Evaluate(other));
+ EXPECT_FALSE(event_has_other.Evaluate(other));
+}
+
+TEST_F(TraceEventAnalyzerTest, BooleanOperators) {
+ ManualSetUp();
+
+ BeginTracing();
+ {
+ TRACE_EVENT_INSTANT1("cat1", "name1", TRACE_EVENT_SCOPE_THREAD, "num", 1);
+ TRACE_EVENT_INSTANT1("cat1", "name2", TRACE_EVENT_SCOPE_THREAD, "num", 2);
+ TRACE_EVENT_INSTANT1("cat2", "name3", TRACE_EVENT_SCOPE_THREAD, "num", 3);
+ TRACE_EVENT_INSTANT1("cat2", "name4", TRACE_EVENT_SCOPE_THREAD, "num", 4);
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer);
+ analyzer->SetIgnoreMetadataEvents(true);
+
+ TraceEventVector found;
+
+ // ==
+
+ analyzer->FindEvents(Query::EventCategory() == Query::String("cat1"), &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+ EXPECT_STREQ("name2", found[1]->name.c_str());
+
+ analyzer->FindEvents(Query::EventArg("num") == Query::Int(2), &found);
+ ASSERT_EQ(1u, found.size());
+ EXPECT_STREQ("name2", found[0]->name.c_str());
+
+ // !=
+
+ analyzer->FindEvents(Query::EventCategory() != Query::String("cat1"), &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STREQ("name3", found[0]->name.c_str());
+ EXPECT_STREQ("name4", found[1]->name.c_str());
+
+ analyzer->FindEvents(Query::EventArg("num") != Query::Int(2), &found);
+ ASSERT_EQ(3u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+ EXPECT_STREQ("name3", found[1]->name.c_str());
+ EXPECT_STREQ("name4", found[2]->name.c_str());
+
+ // <
+ analyzer->FindEvents(Query::EventArg("num") < Query::Int(2), &found);
+ ASSERT_EQ(1u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+
+ // <=
+ analyzer->FindEvents(Query::EventArg("num") <= Query::Int(2), &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+ EXPECT_STREQ("name2", found[1]->name.c_str());
+
+ // >
+ analyzer->FindEvents(Query::EventArg("num") > Query::Int(3), &found);
+ ASSERT_EQ(1u, found.size());
+ EXPECT_STREQ("name4", found[0]->name.c_str());
+
+ // >=
+ analyzer->FindEvents(Query::EventArg("num") >= Query::Int(4), &found);
+ ASSERT_EQ(1u, found.size());
+ EXPECT_STREQ("name4", found[0]->name.c_str());
+
+ // &&
+ analyzer->FindEvents(Query::EventName() != Query::String("name1") &&
+ Query::EventArg("num") < Query::Int(3), &found);
+ ASSERT_EQ(1u, found.size());
+ EXPECT_STREQ("name2", found[0]->name.c_str());
+
+ // ||
+ analyzer->FindEvents(Query::EventName() == Query::String("name1") ||
+ Query::EventArg("num") == Query::Int(3), &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+ EXPECT_STREQ("name3", found[1]->name.c_str());
+
+ // !
+ analyzer->FindEvents(!(Query::EventName() == Query::String("name1") ||
+ Query::EventArg("num") == Query::Int(3)), &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STREQ("name2", found[0]->name.c_str());
+ EXPECT_STREQ("name4", found[1]->name.c_str());
+}
+
+TEST_F(TraceEventAnalyzerTest, ArithmeticOperators) {
+ ManualSetUp();
+
+ BeginTracing();
+ {
+ // These events are searched for:
+ TRACE_EVENT_INSTANT2("cat1", "math1", TRACE_EVENT_SCOPE_THREAD,
+ "a", 10, "b", 5);
+ TRACE_EVENT_INSTANT2("cat1", "math2", TRACE_EVENT_SCOPE_THREAD,
+ "a", 10, "b", 10);
+ // Extra events that never match, for noise:
+ TRACE_EVENT_INSTANT2("noise", "math3", TRACE_EVENT_SCOPE_THREAD,
+ "a", 1, "b", 3);
+ TRACE_EVENT_INSTANT2("noise", "math4", TRACE_EVENT_SCOPE_THREAD,
+ "c", 10, "d", 5);
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+
+ TraceEventVector found;
+
+ // Verify that arithmetic operators function:
+
+ // +
+ analyzer->FindEvents(Query::EventArg("a") + Query::EventArg("b") ==
+ Query::Int(20), &found);
+ EXPECT_EQ(1u, found.size());
+ EXPECT_STREQ("math2", found.front()->name.c_str());
+
+ // -
+ analyzer->FindEvents(Query::EventArg("a") - Query::EventArg("b") ==
+ Query::Int(5), &found);
+ EXPECT_EQ(1u, found.size());
+ EXPECT_STREQ("math1", found.front()->name.c_str());
+
+ // *
+ analyzer->FindEvents(Query::EventArg("a") * Query::EventArg("b") ==
+ Query::Int(50), &found);
+ EXPECT_EQ(1u, found.size());
+ EXPECT_STREQ("math1", found.front()->name.c_str());
+
+ // /
+ analyzer->FindEvents(Query::EventArg("a") / Query::EventArg("b") ==
+ Query::Int(2), &found);
+ EXPECT_EQ(1u, found.size());
+ EXPECT_STREQ("math1", found.front()->name.c_str());
+
+ // %
+ analyzer->FindEvents(Query::EventArg("a") % Query::EventArg("b") ==
+ Query::Int(0), &found);
+ EXPECT_EQ(2u, found.size());
+
+ // - (negate)
+ analyzer->FindEvents(-Query::EventArg("b") == Query::Int(-10), &found);
+ EXPECT_EQ(1u, found.size());
+ EXPECT_STREQ("math2", found.front()->name.c_str());
+}
+
+TEST_F(TraceEventAnalyzerTest, StringPattern) {
+ ManualSetUp();
+
+ BeginTracing();
+ {
+ TRACE_EVENT_INSTANT0("cat1", "name1", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat1", "name2", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat1", "no match", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat1", "name3x", TRACE_EVENT_SCOPE_THREAD);
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+ analyzer->SetIgnoreMetadataEvents(true);
+
+ TraceEventVector found;
+
+ analyzer->FindEvents(Query::EventName() == Query::Pattern("name?"), &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+ EXPECT_STREQ("name2", found[1]->name.c_str());
+
+ analyzer->FindEvents(Query::EventName() == Query::Pattern("name*"), &found);
+ ASSERT_EQ(3u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+ EXPECT_STREQ("name2", found[1]->name.c_str());
+ EXPECT_STREQ("name3x", found[2]->name.c_str());
+
+ analyzer->FindEvents(Query::EventName() != Query::Pattern("name*"), &found);
+ ASSERT_EQ(1u, found.size());
+ EXPECT_STREQ("no match", found[0]->name.c_str());
+}
+
+// Test that duration queries work.
+TEST_F(TraceEventAnalyzerTest, BeginEndDuration) {
+ ManualSetUp();
+
+ const base::TimeDelta kSleepTime = base::TimeDelta::FromMilliseconds(200);
+ // We will search for events that have a duration of greater than 90% of the
+ // sleep time, so that there is no flakiness.
+ int64_t duration_cutoff_us = (kSleepTime.InMicroseconds() * 9) / 10;
+
+ BeginTracing();
+ {
+ TRACE_EVENT_BEGIN0("cat1", "name1"); // found by duration query
+ TRACE_EVENT_BEGIN0("noise", "name2"); // not searched for, just noise
+ {
+ TRACE_EVENT_BEGIN0("cat2", "name3"); // found by duration query
+ // next event not searched for, just noise
+ TRACE_EVENT_INSTANT0("noise", "name4", TRACE_EVENT_SCOPE_THREAD);
+ base::PlatformThread::Sleep(kSleepTime);
+ TRACE_EVENT_BEGIN0("cat2", "name5"); // not found (duration too short)
+ TRACE_EVENT_END0("cat2", "name5"); // not found (duration too short)
+ TRACE_EVENT_END0("cat2", "name3"); // found by duration query
+ }
+ TRACE_EVENT_END0("noise", "name2"); // not searched for, just noise
+ TRACE_EVENT_END0("cat1", "name1"); // found by duration query
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+ analyzer->AssociateBeginEndEvents();
+
+ TraceEventVector found;
+ analyzer->FindEvents(
+ Query::MatchBeginWithEnd() &&
+ Query::EventDuration() >
+ Query::Int(static_cast<int>(duration_cutoff_us)) &&
+ (Query::EventCategory() == Query::String("cat1") ||
+ Query::EventCategory() == Query::String("cat2") ||
+ Query::EventCategory() == Query::String("cat3")),
+ &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+ EXPECT_STREQ("name3", found[1]->name.c_str());
+}
+
+// Test that duration queries work.
+TEST_F(TraceEventAnalyzerTest, CompleteDuration) {
+ ManualSetUp();
+
+ const base::TimeDelta kSleepTime = base::TimeDelta::FromMilliseconds(200);
+ // We will search for events that have a duration of greater than 90% of the
+ // sleep time, so that there is no flakiness.
+ int64_t duration_cutoff_us = (kSleepTime.InMicroseconds() * 9) / 10;
+
+ BeginTracing();
+ {
+ TRACE_EVENT0("cat1", "name1"); // found by duration query
+ TRACE_EVENT0("noise", "name2"); // not searched for, just noise
+ {
+ TRACE_EVENT0("cat2", "name3"); // found by duration query
+ // next event not searched for, just noise
+ TRACE_EVENT_INSTANT0("noise", "name4", TRACE_EVENT_SCOPE_THREAD);
+ base::PlatformThread::Sleep(kSleepTime);
+ TRACE_EVENT0("cat2", "name5"); // not found (duration too short)
+ }
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+ analyzer->AssociateBeginEndEvents();
+
+ TraceEventVector found;
+ analyzer->FindEvents(
+ Query::EventCompleteDuration() >
+ Query::Int(static_cast<int>(duration_cutoff_us)) &&
+ (Query::EventCategory() == Query::String("cat1") ||
+ Query::EventCategory() == Query::String("cat2") ||
+ Query::EventCategory() == Query::String("cat3")),
+ &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STREQ("name1", found[0]->name.c_str());
+ EXPECT_STREQ("name3", found[1]->name.c_str());
+}
+
+// Test AssociateBeginEndEvents
+TEST_F(TraceEventAnalyzerTest, BeginEndAssocations) {
+ ManualSetUp();
+
+ BeginTracing();
+ {
+ TRACE_EVENT_END0("cat1", "name1"); // does not match out of order begin
+ TRACE_EVENT_BEGIN0("cat1", "name2");
+ TRACE_EVENT_INSTANT0("cat1", "name3", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_BEGIN0("cat1", "name1");
+ TRACE_EVENT_END0("cat1", "name2");
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+ analyzer->AssociateBeginEndEvents();
+
+ TraceEventVector found;
+ analyzer->FindEvents(Query::MatchBeginWithEnd(), &found);
+ ASSERT_EQ(1u, found.size());
+ EXPECT_STREQ("name2", found[0]->name.c_str());
+}
+
+// Test MergeAssociatedEventArgs
+TEST_F(TraceEventAnalyzerTest, MergeAssociatedEventArgs) {
+ ManualSetUp();
+
+ const char arg_string[] = "arg_string";
+ BeginTracing();
+ {
+ TRACE_EVENT_BEGIN0("cat1", "name1");
+ TRACE_EVENT_END1("cat1", "name1", "arg", arg_string);
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+ analyzer->AssociateBeginEndEvents();
+
+ TraceEventVector found;
+ analyzer->FindEvents(Query::MatchBeginName("name1"), &found);
+ ASSERT_EQ(1u, found.size());
+ std::string arg_actual;
+ EXPECT_FALSE(found[0]->GetArgAsString("arg", &arg_actual));
+
+ analyzer->MergeAssociatedEventArgs();
+ EXPECT_TRUE(found[0]->GetArgAsString("arg", &arg_actual));
+ EXPECT_STREQ(arg_string, arg_actual.c_str());
+}
+
+// Test AssociateAsyncBeginEndEvents
+TEST_F(TraceEventAnalyzerTest, AsyncBeginEndAssocations) {
+ ManualSetUp();
+
+ BeginTracing();
+ {
+ TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xA); // no match / out of order
+ TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xB);
+ TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xC);
+ TRACE_EVENT_INSTANT0("cat1", "name1", TRACE_EVENT_SCOPE_THREAD); // noise
+ TRACE_EVENT0("cat1", "name1"); // noise
+ TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xB);
+ TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xC);
+ TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xA); // no match / out of order
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+ analyzer->AssociateAsyncBeginEndEvents();
+
+ TraceEventVector found;
+ analyzer->FindEvents(Query::MatchAsyncBeginWithNext(), &found);
+ ASSERT_EQ(2u, found.size());
+ EXPECT_STRCASEEQ("0xb", found[0]->id.c_str());
+ EXPECT_STRCASEEQ("0xc", found[1]->id.c_str());
+}
+
+// Test AssociateAsyncBeginEndEvents
+TEST_F(TraceEventAnalyzerTest, AsyncBeginEndAssocationsWithSteps) {
+ ManualSetUp();
+
+ BeginTracing();
+ {
+ TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xA, "s1");
+ TRACE_EVENT_ASYNC_END0("c", "n", 0xA);
+ TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xB);
+ TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xC);
+ TRACE_EVENT_ASYNC_STEP_PAST0("c", "n", 0xB, "s1");
+ TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xC, "s1");
+ TRACE_EVENT_ASYNC_STEP_INTO1("c", "n", 0xC, "s2", "a", 1);
+ TRACE_EVENT_ASYNC_END0("c", "n", 0xB);
+ TRACE_EVENT_ASYNC_END0("c", "n", 0xC);
+ TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xA);
+ TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xA, "s2");
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+ analyzer->AssociateAsyncBeginEndEvents();
+
+ TraceEventVector found;
+ analyzer->FindEvents(Query::MatchAsyncBeginWithNext(), &found);
+ ASSERT_EQ(3u, found.size());
+
+ EXPECT_STRCASEEQ("0xb", found[0]->id.c_str());
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_PAST, found[0]->other_event->phase);
+ EXPECT_EQ(found[0], found[0]->other_event->prev_event);
+ EXPECT_TRUE(found[0]->other_event->other_event);
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END,
+ found[0]->other_event->other_event->phase);
+ EXPECT_EQ(found[0]->other_event,
+ found[0]->other_event->other_event->prev_event);
+
+ EXPECT_STRCASEEQ("0xc", found[1]->id.c_str());
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, found[1]->other_event->phase);
+ EXPECT_EQ(found[1], found[1]->other_event->prev_event);
+ EXPECT_TRUE(found[1]->other_event->other_event);
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO,
+ found[1]->other_event->other_event->phase);
+ EXPECT_EQ(found[1]->other_event,
+ found[1]->other_event->other_event->prev_event);
+ double arg_actual = 0;
+ EXPECT_TRUE(found[1]->other_event->other_event->GetArgAsNumber(
+ "a", &arg_actual));
+ EXPECT_EQ(1.0, arg_actual);
+ EXPECT_TRUE(found[1]->other_event->other_event->other_event);
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END,
+ found[1]->other_event->other_event->other_event->phase);
+
+ EXPECT_STRCASEEQ("0xa", found[2]->id.c_str());
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, found[2]->other_event->phase);
+}
+
+// Test that the TraceAnalyzer custom associations work.
+TEST_F(TraceEventAnalyzerTest, CustomAssociations) {
+ ManualSetUp();
+
+ // Add events that begin/end in pipelined ordering with unique ID parameter
+ // to match up the begin/end pairs.
+ BeginTracing();
+ {
+ // no begin match
+ TRACE_EVENT_INSTANT1("cat1", "end", TRACE_EVENT_SCOPE_THREAD, "id", 1);
+ // end is cat4
+ TRACE_EVENT_INSTANT1("cat2", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 2);
+ // end is cat5
+ TRACE_EVENT_INSTANT1("cat3", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 3);
+ TRACE_EVENT_INSTANT1("cat4", "end", TRACE_EVENT_SCOPE_THREAD, "id", 2);
+ TRACE_EVENT_INSTANT1("cat5", "end", TRACE_EVENT_SCOPE_THREAD, "id", 3);
+ // no end match
+ TRACE_EVENT_INSTANT1("cat6", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 1);
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+
+ // begin, end, and match queries to find proper begin/end pairs.
+ Query begin(Query::EventName() == Query::String("begin"));
+ Query end(Query::EventName() == Query::String("end"));
+ Query match(Query::EventArg("id") == Query::OtherArg("id"));
+ analyzer->AssociateEvents(begin, end, match);
+
+ TraceEventVector found;
+
+ // cat1 has no other_event.
+ analyzer->FindEvents(Query::EventCategory() == Query::String("cat1") &&
+ Query::EventHasOther(), &found);
+ EXPECT_EQ(0u, found.size());
+
+ // cat1 has no other_event.
+ analyzer->FindEvents(Query::EventCategory() == Query::String("cat1") &&
+ !Query::EventHasOther(), &found);
+ EXPECT_EQ(1u, found.size());
+
+ // cat6 has no other_event.
+ analyzer->FindEvents(Query::EventCategory() == Query::String("cat6") &&
+ !Query::EventHasOther(), &found);
+ EXPECT_EQ(1u, found.size());
+
+ // cat2 and cat4 are associated.
+ analyzer->FindEvents(Query::EventCategory() == Query::String("cat2") &&
+ Query::OtherCategory() == Query::String("cat4"), &found);
+ EXPECT_EQ(1u, found.size());
+
+ // cat4 and cat2 are not associated.
+ analyzer->FindEvents(Query::EventCategory() == Query::String("cat4") &&
+ Query::OtherCategory() == Query::String("cat2"), &found);
+ EXPECT_EQ(0u, found.size());
+
+ // cat3 and cat5 are associated.
+ analyzer->FindEvents(Query::EventCategory() == Query::String("cat3") &&
+ Query::OtherCategory() == Query::String("cat5"), &found);
+ EXPECT_EQ(1u, found.size());
+
+ // cat5 and cat3 are not associated.
+ analyzer->FindEvents(Query::EventCategory() == Query::String("cat5") &&
+ Query::OtherCategory() == Query::String("cat3"), &found);
+ EXPECT_EQ(0u, found.size());
+}
+
+// Verify that Query literals and types are properly casted.
+TEST_F(TraceEventAnalyzerTest, Literals) {
+ ManualSetUp();
+
+ // Since these queries don't refer to the event data, the dummy event below
+ // will never be accessed.
+ TraceEvent dummy;
+ char char_num = 5;
+ short short_num = -5;
+ EXPECT_TRUE((Query::Double(5.0) == Query::Int(char_num)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Double(-5.0) == Query::Int(short_num)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Double(1.0) == Query::Uint(1u)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Double(1.0) == Query::Int(1)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Double(-1.0) == Query::Int(-1)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Double(1.0) == Query::Double(1.0f)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Bool(true) == Query::Int(1)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Bool(false) == Query::Int(0)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Bool(true) == Query::Double(1.0f)).Evaluate(dummy));
+ EXPECT_TRUE((Query::Bool(false) == Query::Double(0.0f)).Evaluate(dummy));
+}
+
+// Test GetRateStats.
+TEST_F(TraceEventAnalyzerTest, RateStats) {
+ std::vector<TraceEvent> events;
+ events.reserve(100);
+ TraceEventVector event_ptrs;
+ double timestamp = 0.0;
+ double little_delta = 1.0;
+ double big_delta = 10.0;
+ double tiny_delta = 0.1;
+ RateStats stats;
+ RateStatsOptions options;
+
+ // Insert 10 events, each apart by little_delta.
+ for (int i = 0; i < 10; ++i) {
+ timestamp += little_delta;
+ TraceEvent event;
+ event.timestamp = timestamp;
+ events.push_back(std::move(event));
+ event_ptrs.push_back(&events.back());
+ }
+
+ ASSERT_TRUE(GetRateStats(event_ptrs, &stats, nullptr));
+ EXPECT_EQ(little_delta, stats.mean_us);
+ EXPECT_EQ(little_delta, stats.min_us);
+ EXPECT_EQ(little_delta, stats.max_us);
+ EXPECT_EQ(0.0, stats.standard_deviation_us);
+
+ // Add an event apart by big_delta.
+ {
+ timestamp += big_delta;
+ TraceEvent event;
+ event.timestamp = timestamp;
+ events.push_back(std::move(event));
+ event_ptrs.push_back(&events.back());
+ }
+
+ ASSERT_TRUE(GetRateStats(event_ptrs, &stats, nullptr));
+ EXPECT_LT(little_delta, stats.mean_us);
+ EXPECT_EQ(little_delta, stats.min_us);
+ EXPECT_EQ(big_delta, stats.max_us);
+ EXPECT_LT(0.0, stats.standard_deviation_us);
+
+ // Trim off the biggest delta and verify stats.
+ options.trim_min = 0;
+ options.trim_max = 1;
+ ASSERT_TRUE(GetRateStats(event_ptrs, &stats, &options));
+ EXPECT_EQ(little_delta, stats.mean_us);
+ EXPECT_EQ(little_delta, stats.min_us);
+ EXPECT_EQ(little_delta, stats.max_us);
+ EXPECT_EQ(0.0, stats.standard_deviation_us);
+
+ // Add an event apart by tiny_delta.
+ {
+ timestamp += tiny_delta;
+ TraceEvent event;
+ event.timestamp = timestamp;
+ events.push_back(std::move(event));
+ event_ptrs.push_back(&events.back());
+ }
+
+ // Trim off both the biggest and tiniest delta and verify stats.
+ options.trim_min = 1;
+ options.trim_max = 1;
+ ASSERT_TRUE(GetRateStats(event_ptrs, &stats, &options));
+ EXPECT_EQ(little_delta, stats.mean_us);
+ EXPECT_EQ(little_delta, stats.min_us);
+ EXPECT_EQ(little_delta, stats.max_us);
+ EXPECT_EQ(0.0, stats.standard_deviation_us);
+
+ // Verify smallest allowed number of events.
+ {
+ TraceEvent event;
+ TraceEventVector few_event_ptrs;
+ few_event_ptrs.push_back(&event);
+ few_event_ptrs.push_back(&event);
+ ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, nullptr));
+ few_event_ptrs.push_back(&event);
+ ASSERT_TRUE(GetRateStats(few_event_ptrs, &stats, nullptr));
+
+ // Trim off more than allowed and verify failure.
+ options.trim_min = 0;
+ options.trim_max = 1;
+ ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, &options));
+ }
+}
+
+// Test FindFirstOf and FindLastOf.
+TEST_F(TraceEventAnalyzerTest, FindOf) {
+ size_t num_events = 100;
+ size_t index = 0;
+ TraceEventVector event_ptrs;
+ EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(true), 0, &index));
+ EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(true), 10, &index));
+ EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(true), 0, &index));
+ EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(true), 10, &index));
+
+ std::vector<TraceEvent> events;
+ events.resize(num_events);
+ for (size_t i = 0; i < events.size(); ++i)
+ event_ptrs.push_back(&events[i]);
+ size_t bam_index = num_events/2;
+ events[bam_index].name = "bam";
+ Query query_bam = Query::EventName() == Query::String(events[bam_index].name);
+
+ // FindFirstOf
+ EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(false), 0, &index));
+ EXPECT_TRUE(FindFirstOf(event_ptrs, Query::Bool(true), 0, &index));
+ EXPECT_EQ(0u, index);
+ EXPECT_TRUE(FindFirstOf(event_ptrs, Query::Bool(true), 5, &index));
+ EXPECT_EQ(5u, index);
+
+ EXPECT_FALSE(FindFirstOf(event_ptrs, query_bam, bam_index + 1, &index));
+ EXPECT_TRUE(FindFirstOf(event_ptrs, query_bam, 0, &index));
+ EXPECT_EQ(bam_index, index);
+ EXPECT_TRUE(FindFirstOf(event_ptrs, query_bam, bam_index, &index));
+ EXPECT_EQ(bam_index, index);
+
+ // FindLastOf
+ EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(false), 1000, &index));
+ EXPECT_TRUE(FindLastOf(event_ptrs, Query::Bool(true), 1000, &index));
+ EXPECT_EQ(num_events - 1, index);
+ EXPECT_TRUE(FindLastOf(event_ptrs, Query::Bool(true), num_events - 5,
+ &index));
+ EXPECT_EQ(num_events - 5, index);
+
+ EXPECT_FALSE(FindLastOf(event_ptrs, query_bam, bam_index - 1, &index));
+ EXPECT_TRUE(FindLastOf(event_ptrs, query_bam, num_events, &index));
+ EXPECT_EQ(bam_index, index);
+ EXPECT_TRUE(FindLastOf(event_ptrs, query_bam, bam_index, &index));
+ EXPECT_EQ(bam_index, index);
+}
+
+// Test FindClosest.
+TEST_F(TraceEventAnalyzerTest, FindClosest) {
+ size_t index_1 = 0;
+ size_t index_2 = 0;
+ TraceEventVector event_ptrs;
+ EXPECT_FALSE(FindClosest(event_ptrs, Query::Bool(true), 0,
+ &index_1, &index_2));
+
+ size_t num_events = 5;
+ std::vector<TraceEvent> events;
+ events.resize(num_events);
+ for (size_t i = 0; i < events.size(); ++i) {
+ // timestamps go up exponentially so the lower index is always closer in
+ // time than the higher index.
+ events[i].timestamp = static_cast<double>(i) * static_cast<double>(i);
+ event_ptrs.push_back(&events[i]);
+ }
+ events[0].name = "one";
+ events[2].name = "two";
+ events[4].name = "three";
+ Query query_named = Query::EventName() != Query::String(std::string());
+ Query query_one = Query::EventName() == Query::String("one");
+
+ // Only one event matches query_one, so two closest can't be found.
+ EXPECT_FALSE(FindClosest(event_ptrs, query_one, 0, &index_1, &index_2));
+
+ EXPECT_TRUE(FindClosest(event_ptrs, query_one, 3, &index_1, nullptr));
+ EXPECT_EQ(0u, index_1);
+
+ EXPECT_TRUE(FindClosest(event_ptrs, query_named, 1, &index_1, &index_2));
+ EXPECT_EQ(0u, index_1);
+ EXPECT_EQ(2u, index_2);
+
+ EXPECT_TRUE(FindClosest(event_ptrs, query_named, 4, &index_1, &index_2));
+ EXPECT_EQ(4u, index_1);
+ EXPECT_EQ(2u, index_2);
+
+ EXPECT_TRUE(FindClosest(event_ptrs, query_named, 3, &index_1, &index_2));
+ EXPECT_EQ(2u, index_1);
+ EXPECT_EQ(0u, index_2);
+}
+
+// Test CountMatches.
+TEST_F(TraceEventAnalyzerTest, CountMatches) {
+ TraceEventVector event_ptrs;
+ EXPECT_EQ(0u, CountMatches(event_ptrs, Query::Bool(true), 0, 10));
+
+ size_t num_events = 5;
+ size_t num_named = 3;
+ std::vector<TraceEvent> events;
+ events.resize(num_events);
+ for (size_t i = 0; i < events.size(); ++i)
+ event_ptrs.push_back(&events[i]);
+ events[0].name = "one";
+ events[2].name = "two";
+ events[4].name = "three";
+ Query query_named = Query::EventName() != Query::String(std::string());
+ Query query_one = Query::EventName() == Query::String("one");
+
+ EXPECT_EQ(0u, CountMatches(event_ptrs, Query::Bool(false)));
+ EXPECT_EQ(num_events, CountMatches(event_ptrs, Query::Bool(true)));
+ EXPECT_EQ(num_events - 1, CountMatches(event_ptrs, Query::Bool(true),
+ 1, num_events));
+ EXPECT_EQ(1u, CountMatches(event_ptrs, query_one));
+ EXPECT_EQ(num_events - 1, CountMatches(event_ptrs, !query_one));
+ EXPECT_EQ(num_named, CountMatches(event_ptrs, query_named));
+}
+
+TEST_F(TraceEventAnalyzerTest, ComplexArgument) {
+ ManualSetUp();
+
+ BeginTracing();
+ {
+ std::unique_ptr<base::trace_event::TracedValue> value(
+ new base::trace_event::TracedValue);
+ value->SetString("property", "value");
+ TRACE_EVENT1("cat", "name", "arg", std::move(value));
+ }
+ EndTracing();
+
+ std::unique_ptr<TraceAnalyzer> analyzer(
+ TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(analyzer.get());
+
+ TraceEventVector events;
+ analyzer->FindEvents(Query::EventName() == Query::String("name"), &events);
+
+ EXPECT_EQ(1u, events.size());
+ EXPECT_EQ("cat", events[0]->category);
+ EXPECT_EQ("name", events[0]->name);
+ EXPECT_TRUE(events[0]->HasArg("arg"));
+
+ std::unique_ptr<base::Value> arg;
+ events[0]->GetArgAsValue("arg", &arg);
+ base::DictionaryValue* arg_dict;
+ EXPECT_TRUE(arg->GetAsDictionary(&arg_dict));
+ std::string property;
+ EXPECT_TRUE(arg_dict->GetString("property", &property));
+ EXPECT_EQ("value", property);
+}
+
+} // namespace trace_analyzer
diff --git a/base/test/trace_to_file.cc b/base/test/trace_to_file.cc
new file mode 100644
index 0000000000..17aa80b39b
--- /dev/null
+++ b/base/test/trace_to_file.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/trace_to_file.h"
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/run_loop.h"
+#include "base/trace_event/trace_buffer.h"
+#include "base/trace_event/trace_log.h"
+
+namespace base {
+namespace test {
+
+TraceToFile::TraceToFile() : started_(false) {
+}
+
+TraceToFile::~TraceToFile() {
+ EndTracingIfNeeded();
+}
+
+void TraceToFile::BeginTracingFromCommandLineOptions() {
+ DCHECK(CommandLine::InitializedForCurrentProcess());
+ DCHECK(!started_);
+
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kTraceToFile))
+ return;
+
+ // Empty filter (i.e. just --trace-to-file) turns into default categories in
+ // TraceEventImpl
+ std::string filter = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kTraceToFile);
+
+ FilePath path;
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTraceToFileName)) {
+ path = FilePath(CommandLine::ForCurrentProcess()
+ ->GetSwitchValuePath(switches::kTraceToFileName));
+ } else {
+ path = FilePath(FILE_PATH_LITERAL("trace.json"));
+ }
+
+ BeginTracing(path, filter);
+}
+
+void TraceToFile::BeginTracing(const FilePath& path,
+ const std::string& categories) {
+ DCHECK(!started_);
+ started_ = true;
+ path_ = path;
+ WriteFileHeader();
+
+ trace_event::TraceLog::GetInstance()->SetEnabled(
+ trace_event::TraceConfig(categories, trace_event::RECORD_UNTIL_FULL),
+ trace_event::TraceLog::RECORDING_MODE);
+}
+
+void TraceToFile::WriteFileHeader() {
+ const char str[] = "{\"traceEvents\": [";
+ WriteFile(path_, str, static_cast<int>(strlen(str)));
+}
+
+void TraceToFile::AppendFileFooter() {
+ const char str[] = "]}";
+ AppendToFile(path_, str, static_cast<int>(strlen(str)));
+}
+
+void TraceToFile::TraceOutputCallback(const std::string& data) {
+ bool ret = AppendToFile(path_, data.c_str(), static_cast<int>(data.size()));
+ DCHECK(ret);
+}
+
+static void OnTraceDataCollected(
+ Closure quit_closure,
+ trace_event::TraceResultBuffer* buffer,
+ const scoped_refptr<RefCountedString>& json_events_str,
+ bool has_more_events) {
+ buffer->AddFragment(json_events_str->data());
+ if (!has_more_events)
+ quit_closure.Run();
+}
+
+void TraceToFile::EndTracingIfNeeded() {
+ if (!started_)
+ return;
+ started_ = false;
+
+ trace_event::TraceLog::GetInstance()->SetDisabled();
+
+ trace_event::TraceResultBuffer buffer;
+ buffer.SetOutputCallback(
+ Bind(&TraceToFile::TraceOutputCallback, Unretained(this)));
+
+ RunLoop run_loop;
+ trace_event::TraceLog::GetInstance()->Flush(
+ Bind(&OnTraceDataCollected, run_loop.QuitClosure(), Unretained(&buffer)));
+ run_loop.Run();
+
+ AppendFileFooter();
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/trace_to_file.h b/base/test/trace_to_file.h
new file mode 100644
index 0000000000..43087367c3
--- /dev/null
+++ b/base/test/trace_to_file.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_TRACE_TO_FILE_H_
+#define BASE_TEST_TRACE_TO_FILE_H_
+
+#include "base/files/file_path.h"
+
+namespace base {
+namespace test {
+
+class TraceToFile {
+ public:
+ TraceToFile();
+ ~TraceToFile();
+
+ void BeginTracingFromCommandLineOptions();
+ void BeginTracing(const base::FilePath& path, const std::string& categories);
+ void EndTracingIfNeeded();
+
+ private:
+ void WriteFileHeader();
+ void AppendFileFooter();
+
+ void TraceOutputCallback(const std::string& data);
+
+ base::FilePath path_;
+ bool started_;
+};
+
+} // namespace test
+} // namespace base
+
+#endif // BASE_TEST_TRACE_TO_FILE_H_
diff --git a/base/test/values_test_util.cc b/base/test/values_test_util.cc
new file mode 100644
index 0000000000..a65c2c0674
--- /dev/null
+++ b/base/test/values_test_util.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/values_test_util.h"
+
+#include <memory>
+
+#include "base/json/json_reader.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+void ExpectDictBooleanValue(bool expected_value,
+ const DictionaryValue& value,
+ const std::string& key) {
+ bool boolean_value = false;
+ EXPECT_TRUE(value.GetBoolean(key, &boolean_value)) << key;
+ EXPECT_EQ(expected_value, boolean_value) << key;
+}
+
+void ExpectDictDictionaryValue(const DictionaryValue& expected_value,
+ const DictionaryValue& value,
+ const std::string& key) {
+ const DictionaryValue* dict_value = nullptr;
+ EXPECT_TRUE(value.GetDictionary(key, &dict_value)) << key;
+ EXPECT_EQ(expected_value, *dict_value) << key;
+}
+
+void ExpectDictIntegerValue(int expected_value,
+ const DictionaryValue& value,
+ const std::string& key) {
+ int integer_value = 0;
+ EXPECT_TRUE(value.GetInteger(key, &integer_value)) << key;
+ EXPECT_EQ(expected_value, integer_value) << key;
+}
+
+void ExpectDictListValue(const ListValue& expected_value,
+ const DictionaryValue& value,
+ const std::string& key) {
+ const ListValue* list_value = nullptr;
+ EXPECT_TRUE(value.GetList(key, &list_value)) << key;
+ EXPECT_EQ(expected_value, *list_value) << key;
+}
+
+void ExpectDictStringValue(const std::string& expected_value,
+ const DictionaryValue& value,
+ const std::string& key) {
+ std::string string_value;
+ EXPECT_TRUE(value.GetString(key, &string_value)) << key;
+ EXPECT_EQ(expected_value, string_value) << key;
+}
+
+void ExpectStringValue(const std::string& expected_str, const Value& actual) {
+ EXPECT_EQ(Value::Type::STRING, actual.type());
+ EXPECT_EQ(expected_str, actual.GetString());
+}
+
+namespace test {
+
+std::unique_ptr<Value> ParseJson(base::StringPiece json) {
+ std::string error_msg;
+ std::unique_ptr<Value> result = base::JSONReader::ReadAndReturnError(
+ json, base::JSON_ALLOW_TRAILING_COMMAS, nullptr, &error_msg);
+ if (!result) {
+ ADD_FAILURE() << "Failed to parse \"" << json << "\": " << error_msg;
+ result = std::make_unique<Value>();
+ }
+ return result;
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/values_test_util.h b/base/test/values_test_util.h
new file mode 100644
index 0000000000..02ebca104d
--- /dev/null
+++ b/base/test/values_test_util.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_VALUES_TEST_UTIL_H_
+#define BASE_TEST_VALUES_TEST_UTIL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/strings/string_piece.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+
+// All the functions below expect that the value for the given key in
+// the given dictionary equals the given expected value.
+
+void ExpectDictBooleanValue(bool expected_value,
+ const DictionaryValue& value,
+ const std::string& key);
+
+void ExpectDictDictionaryValue(const DictionaryValue& expected_value,
+ const DictionaryValue& value,
+ const std::string& key);
+
+void ExpectDictIntegerValue(int expected_value,
+ const DictionaryValue& value,
+ const std::string& key);
+
+void ExpectDictListValue(const ListValue& expected_value,
+ const DictionaryValue& value,
+ const std::string& key);
+
+void ExpectDictStringValue(const std::string& expected_value,
+ const DictionaryValue& value,
+ const std::string& key);
+
+void ExpectStringValue(const std::string& expected_str, const Value& actual);
+
+namespace test {
+
+// Parses |json| as JSON, allowing trailing commas, and returns the
+// resulting value. If the json fails to parse, causes an EXPECT
+// failure and returns the Null Value (but never a NULL pointer).
+std::unique_ptr<Value> ParseJson(base::StringPiece json);
+
+} // namespace test
+} // namespace base
+
+#endif // BASE_TEST_VALUES_TEST_UTIL_H_
diff --git a/base/third_party/dynamic_annotations/LICENSE b/base/third_party/dynamic_annotations/LICENSE
new file mode 100644
index 0000000000..5c581a9391
--- /dev/null
+++ b/base/third_party/dynamic_annotations/LICENSE
@@ -0,0 +1,28 @@
+/* Copyright (c) 2008-2009, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ---
+ * Author: Kostya Serebryany
+ */
diff --git a/base/third_party/dynamic_annotations/README.chromium b/base/third_party/dynamic_annotations/README.chromium
new file mode 100644
index 0000000000..c029f8eda7
--- /dev/null
+++ b/base/third_party/dynamic_annotations/README.chromium
@@ -0,0 +1,23 @@
+Name: dynamic annotations
+URL: http://code.google.com/p/data-race-test/wiki/DynamicAnnotations
+Version: 4384
+License: BSD
+
+ATTENTION: please avoid using these annotations in Chromium code.
+They were mainly intended to instruct the Valgrind-based version of
+ThreadSanitizer to handle atomic operations. The new version of ThreadSanitizer
+based on compiler instrumentation understands atomic operations out of the box,
+so normally you don't need the annotations.
+If you still think you do, please consider writing a comment at http://crbug.com/349861
+
+One header and one source file (dynamic_annotations.h and dynamic_annotations.c)
+in this directory define runtime macros useful for annotating synchronization
+utilities and benign data races so data race detectors can handle Chromium code
+with better precision.
+
+These files were taken from
+http://code.google.com/p/data-race-test/source/browse/?#svn/trunk/dynamic_annotations
+The files are covered under BSD license as described within the files.
+
+Local modifications:
+* made lineno an unsigned short (for -Wconstant-conversion warning fixes)
diff --git a/base/third_party/valgrind/LICENSE b/base/third_party/valgrind/LICENSE
new file mode 100644
index 0000000000..41f677bd17
--- /dev/null
+++ b/base/third_party/valgrind/LICENSE
@@ -0,0 +1,39 @@
+ Notice that the following BSD-style license applies to the Valgrind header
+ files used by Chromium (valgrind.h and memcheck.h). However, the rest of
+ Valgrind is licensed under the terms of the GNU General Public License,
+ version 2, unless otherwise indicated.
+
+ ----------------------------------------------------------------
+
+ Copyright (C) 2000-2008 Julian Seward. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. The origin of this software must not be misrepresented; you must
+ not claim that you wrote the original software. If you use this
+ software in a product, an acknowledgment in the product
+ documentation would be appreciated but is not required.
+
+ 3. Altered source versions must be plainly marked as such, and must
+ not be misrepresented as being the original software.
+
+ 4. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/base/third_party/valgrind/README.chromium b/base/third_party/valgrind/README.chromium
new file mode 100644
index 0000000000..56a1cbb492
--- /dev/null
+++ b/base/third_party/valgrind/README.chromium
@@ -0,0 +1,11 @@
+Name: valgrind
+URL: http://valgrind.org
+License: BSD
+
+Header files in this directory define runtime macros that determine whether the
+current process is running under Valgrind and tell Memcheck tool about custom
+memory allocators.
+
+These header files were taken from Valgrind source code
+(svn://svn.valgrind.org/valgrind/trunk@11504, dated 21 Jan 2011). The files are
+covered under BSD license as described within.
diff --git a/base/threading/platform_thread_android.cc b/base/threading/platform_thread_android.cc
new file mode 100644
index 0000000000..fd90d35102
--- /dev/null
+++ b/base/threading/platform_thread_android.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/platform_thread.h"
+
+#include <errno.h>
+#include <stddef.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/android/jni_android.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/threading/platform_thread_internal_posix.h"
+#include "base/threading/thread_id_name_manager.h"
+#include "jni/ThreadUtils_jni.h"
+
+namespace base {
+
+namespace internal {
+
+// - BACKGROUND corresponds to Android's PRIORITY_BACKGROUND = 10 value and can
+// result in heavy throttling and force the thread onto a little core on
+// big.LITTLE devices.
+// - DISPLAY corresponds to Android's PRIORITY_DISPLAY = -4 value.
+// - REALTIME_AUDIO corresponds to Android's PRIORITY_AUDIO = -16 value.
+const ThreadPriorityToNiceValuePair kThreadPriorityToNiceValueMap[4] = {
+ {ThreadPriority::BACKGROUND, 10},
+ {ThreadPriority::NORMAL, 0},
+ {ThreadPriority::DISPLAY, -4},
+ {ThreadPriority::REALTIME_AUDIO, -16},
+};
+
+bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority) {
+ // On Android, we set the Audio priority through JNI as Audio priority
+ // will also allow the process to run while it is backgrounded.
+ if (priority == ThreadPriority::REALTIME_AUDIO) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_ThreadUtils_setThreadPriorityAudio(env, PlatformThread::CurrentId());
+ return true;
+ }
+ return false;
+}
+
+bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority) {
+ DCHECK(priority);
+ *priority = ThreadPriority::NORMAL;
+ JNIEnv* env = base::android::AttachCurrentThread();
+ if (Java_ThreadUtils_isThreadPriorityAudio(
+ env, PlatformThread::CurrentId())) {
+ *priority = ThreadPriority::REALTIME_AUDIO;
+ return true;
+ }
+ return false;
+}
+
+} // namespace internal
+
+void PlatformThread::SetName(const std::string& name) {
+ ThreadIdNameManager::GetInstance()->SetName(name);
+
+ // Like linux, on android we can get the thread names to show up in the
+ // debugger by setting the process name for the LWP.
+ // We don't want to do this for the main thread because that would rename
+ // the process, causing tools like killall to stop working.
+ if (PlatformThread::CurrentId() == getpid())
+ return;
+
+ // Set the name for the LWP (which gets truncated to 15 characters).
+ int err = prctl(PR_SET_NAME, name.c_str());
+ if (err < 0 && errno != EPERM)
+ DPLOG(ERROR) << "prctl(PR_SET_NAME)";
+}
+
+
+void InitThreading() {
+}
+
+void TerminateOnThread() {
+ base::android::DetachFromVM();
+}
+
+size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) {
+#if !defined(ADDRESS_SANITIZER)
+ return 0;
+#else
+ // AddressSanitizer bloats the stack approximately 2x. Default stack size of
+ // 1Mb is not enough for some tests (see http://crbug.com/263749 for example).
+ return 2 * (1 << 20); // 2Mb
+#endif
+}
+
+} // namespace base
diff --git a/base/threading/post_task_and_reply_impl_unittest.cc b/base/threading/post_task_and_reply_impl_unittest.cc
new file mode 100644
index 0000000000..319327dfea
--- /dev/null
+++ b/base/threading/post_task_and_reply_impl_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/post_task_and_reply_impl.h"
+
+#include <utility>
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace base {
+namespace internal {
+
+namespace {
+
+class PostTaskAndReplyTaskRunner : public internal::PostTaskAndReplyImpl {
+ public:
+ explicit PostTaskAndReplyTaskRunner(TaskRunner* destination)
+ : destination_(destination) {}
+
+ private:
+ bool PostTask(const Location& from_here, OnceClosure task) override {
+ return destination_->PostTask(from_here, std::move(task));
+ }
+
+ // Non-owning.
+ TaskRunner* const destination_;
+};
+
+class ObjectToDelete : public RefCounted<ObjectToDelete> {
+ public:
+ // |delete_flag| is set to true when this object is deleted
+ ObjectToDelete(bool* delete_flag) : delete_flag_(delete_flag) {
+ EXPECT_FALSE(*delete_flag_);
+ }
+
+ private:
+ friend class RefCounted<ObjectToDelete>;
+ ~ObjectToDelete() { *delete_flag_ = true; }
+
+ bool* const delete_flag_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObjectToDelete);
+};
+
+class MockObject {
+ public:
+ MockObject() = default;
+
+ MOCK_METHOD1(Task, void(scoped_refptr<ObjectToDelete>));
+ MOCK_METHOD1(Reply, void(scoped_refptr<ObjectToDelete>));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockObject);
+};
+
+class MockRunsTasksInCurrentSequenceTaskRunner : public TestMockTimeTaskRunner {
+ public:
+ MockRunsTasksInCurrentSequenceTaskRunner(
+ TestMockTimeTaskRunner::Type type =
+ TestMockTimeTaskRunner::Type::kStandalone)
+ : TestMockTimeTaskRunner(type) {}
+
+ void RunUntilIdleWithRunsTasksInCurrentSequence() {
+ AutoReset<bool> reset(&runs_tasks_in_current_sequence_, true);
+ RunUntilIdle();
+ }
+
+ void ClearPendingTasksWithRunsTasksInCurrentSequence() {
+ AutoReset<bool> reset(&runs_tasks_in_current_sequence_, true);
+ ClearPendingTasks();
+ }
+
+ // TestMockTimeTaskRunner:
+ bool RunsTasksInCurrentSequence() const override {
+ return runs_tasks_in_current_sequence_;
+ }
+
+ private:
+ ~MockRunsTasksInCurrentSequenceTaskRunner() override = default;
+
+ bool runs_tasks_in_current_sequence_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(MockRunsTasksInCurrentSequenceTaskRunner);
+};
+
+class PostTaskAndReplyImplTest : public testing::Test {
+ protected:
+ PostTaskAndReplyImplTest() = default;
+
+ void PostTaskAndReplyToMockObject() {
+ // Expect the post to succeed.
+ EXPECT_TRUE(
+ PostTaskAndReplyTaskRunner(post_runner_.get())
+ .PostTaskAndReply(
+ FROM_HERE,
+ BindOnce(&MockObject::Task, Unretained(&mock_object_),
+ MakeRefCounted<ObjectToDelete>(&delete_task_flag_)),
+ BindOnce(&MockObject::Reply, Unretained(&mock_object_),
+ MakeRefCounted<ObjectToDelete>(&delete_reply_flag_))));
+
+ // Expect the first task to be posted to |post_runner_|.
+ EXPECT_TRUE(post_runner_->HasPendingTask());
+ EXPECT_FALSE(reply_runner_->HasPendingTask());
+ EXPECT_FALSE(delete_task_flag_);
+ EXPECT_FALSE(delete_reply_flag_);
+ }
+
+ scoped_refptr<MockRunsTasksInCurrentSequenceTaskRunner> post_runner_ =
+ MakeRefCounted<MockRunsTasksInCurrentSequenceTaskRunner>();
+ scoped_refptr<MockRunsTasksInCurrentSequenceTaskRunner> reply_runner_ =
+ MakeRefCounted<MockRunsTasksInCurrentSequenceTaskRunner>(
+ TestMockTimeTaskRunner::Type::kBoundToThread);
+ testing::StrictMock<MockObject> mock_object_;
+ bool delete_task_flag_ = false;
+ bool delete_reply_flag_ = false;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PostTaskAndReplyImplTest);
+};
+
+} // namespace
+
+TEST_F(PostTaskAndReplyImplTest, PostTaskAndReply) {
+ PostTaskAndReplyToMockObject();
+
+ EXPECT_CALL(mock_object_, Task(_));
+ post_runner_->RunUntilIdleWithRunsTasksInCurrentSequence();
+ testing::Mock::VerifyAndClear(&mock_object_);
+ // The task should have been deleted right after being run.
+ EXPECT_TRUE(delete_task_flag_);
+ EXPECT_FALSE(delete_reply_flag_);
+
+ // Expect the reply to be posted to |reply_runner_|.
+ EXPECT_FALSE(post_runner_->HasPendingTask());
+ EXPECT_TRUE(reply_runner_->HasPendingTask());
+
+ EXPECT_CALL(mock_object_, Reply(_));
+ reply_runner_->RunUntilIdleWithRunsTasksInCurrentSequence();
+ testing::Mock::VerifyAndClear(&mock_object_);
+ EXPECT_TRUE(delete_task_flag_);
+ // The reply should have been deleted right after being run.
+ EXPECT_TRUE(delete_reply_flag_);
+
+ // Expect no pending task in |post_runner_| and |reply_runner_|.
+ EXPECT_FALSE(post_runner_->HasPendingTask());
+ EXPECT_FALSE(reply_runner_->HasPendingTask());
+}
+
+TEST_F(PostTaskAndReplyImplTest, TaskDoesNotRun) {
+ PostTaskAndReplyToMockObject();
+
+ // Clear the |post_runner_|. Both callbacks should be scheduled for deletion
+ // on the |reply_runner_|.
+ post_runner_->ClearPendingTasksWithRunsTasksInCurrentSequence();
+ EXPECT_FALSE(post_runner_->HasPendingTask());
+ EXPECT_TRUE(reply_runner_->HasPendingTask());
+ EXPECT_FALSE(delete_task_flag_);
+ EXPECT_FALSE(delete_reply_flag_);
+
+ // Run the |reply_runner_|. Both callbacks should be deleted.
+ reply_runner_->RunUntilIdleWithRunsTasksInCurrentSequence();
+ EXPECT_TRUE(delete_task_flag_);
+ EXPECT_TRUE(delete_reply_flag_);
+}
+
+TEST_F(PostTaskAndReplyImplTest, ReplyDoesNotRun) {
+ PostTaskAndReplyToMockObject();
+
+ EXPECT_CALL(mock_object_, Task(_));
+ post_runner_->RunUntilIdleWithRunsTasksInCurrentSequence();
+ testing::Mock::VerifyAndClear(&mock_object_);
+ // The task should have been deleted right after being run.
+ EXPECT_TRUE(delete_task_flag_);
+ EXPECT_FALSE(delete_reply_flag_);
+
+ // Expect the reply to be posted to |reply_runner_|.
+ EXPECT_FALSE(post_runner_->HasPendingTask());
+ EXPECT_TRUE(reply_runner_->HasPendingTask());
+
+ // Clear the |reply_runner_| queue without running tasks. The reply callback
+ // should be deleted.
+ reply_runner_->ClearPendingTasksWithRunsTasksInCurrentSequence();
+ EXPECT_TRUE(delete_task_flag_);
+ EXPECT_TRUE(delete_reply_flag_);
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/threading/sequenced_task_runner_handle_unittest.cc b/base/threading/sequenced_task_runner_handle_unittest.cc
new file mode 100644
index 0000000000..48394da6c2
--- /dev/null
+++ b/base/threading/sequenced_task_runner_handle_unittest.cc
@@ -0,0 +1,90 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/sequenced_task_runner_handle.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/sequence_checker_impl.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class SequencedTaskRunnerHandleTest : public ::testing::Test {
+ protected:
+ // Verifies that the context it runs on has a SequencedTaskRunnerHandle
+ // and that posting to it results in the posted task running in that same
+ // context (sequence).
+ static void VerifyCurrentSequencedTaskRunner() {
+ ASSERT_TRUE(SequencedTaskRunnerHandle::IsSet());
+ scoped_refptr<SequencedTaskRunner> task_runner =
+ SequencedTaskRunnerHandle::Get();
+ ASSERT_TRUE(task_runner);
+
+ // Use SequenceCheckerImpl to make sure it's not a no-op in Release builds.
+ std::unique_ptr<SequenceCheckerImpl> sequence_checker(
+ new SequenceCheckerImpl);
+ task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&SequencedTaskRunnerHandleTest::CheckValidSequence,
+ std::move(sequence_checker)));
+ }
+
+ static void CheckValidSequence(
+ std::unique_ptr<SequenceCheckerImpl> sequence_checker) {
+ EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+TEST_F(SequencedTaskRunnerHandleTest, FromMessageLoop) {
+ VerifyCurrentSequencedTaskRunner();
+ RunLoop().RunUntilIdle();
+}
+
+TEST_F(SequencedTaskRunnerHandleTest, FromTaskSchedulerSequencedTask) {
+ base::CreateSequencedTaskRunnerWithTraits({})->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &SequencedTaskRunnerHandleTest::VerifyCurrentSequencedTaskRunner));
+ scoped_task_environment_.RunUntilIdle();
+}
+
+TEST_F(SequencedTaskRunnerHandleTest, NoHandleFromUnsequencedTask) {
+ base::PostTask(FROM_HERE, base::BindOnce([]() {
+ EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+ }));
+ scoped_task_environment_.RunUntilIdle();
+}
+
+TEST(SequencedTaskRunnerHandleTestWithoutMessageLoop, FromHandleInScope) {
+ scoped_refptr<SequencedTaskRunner> test_task_runner(new TestSimpleTaskRunner);
+ EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ {
+ SequencedTaskRunnerHandle handle(test_task_runner);
+ EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(test_task_runner, SequencedTaskRunnerHandle::Get());
+ }
+ EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+}
+
+} // namespace
+} // namespace base
diff --git a/base/threading/thread_perftest.cc b/base/threading/thread_perftest.cc
new file mode 100644
index 0000000000..d3fad972f8
--- /dev/null
+++ b/base/threading/thread_perftest.cc
@@ -0,0 +1,321 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+#if defined(OS_POSIX)
+#include <pthread.h>
+#endif
+
+namespace base {
+
+namespace {
+
+const int kNumRuns = 100000;
+
+// Base class for a threading perf-test. This sets up some threads for the
+// test and measures the clock-time in addition to time spent on each thread.
+class ThreadPerfTest : public testing::Test {
+ public:
+ ThreadPerfTest()
+ : done_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+ // To be implemented by each test. Subclass must uses threads_ such that
+ // their cpu-time can be measured. Test must return from PingPong() _and_
+ // call FinishMeasurement from any thread to complete the test.
+ virtual void Init() {
+ if (ThreadTicks::IsSupported())
+ ThreadTicks::WaitUntilInitialized();
+ }
+ virtual void PingPong(int hops) = 0;
+ virtual void Reset() {}
+
+ void TimeOnThread(base::ThreadTicks* ticks, base::WaitableEvent* done) {
+ *ticks = base::ThreadTicks::Now();
+ done->Signal();
+ }
+
+ base::ThreadTicks ThreadNow(const base::Thread& thread) {
+ base::WaitableEvent done(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ base::ThreadTicks ticks;
+ thread.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&ThreadPerfTest::TimeOnThread,
+ base::Unretained(this), &ticks, &done));
+ done.Wait();
+ return ticks;
+ }
+
+ void RunPingPongTest(const std::string& name, unsigned num_threads) {
+ // Create threads and collect starting cpu-time for each thread.
+ std::vector<base::ThreadTicks> thread_starts;
+ while (threads_.size() < num_threads) {
+ threads_.push_back(std::make_unique<base::Thread>("PingPonger"));
+ threads_.back()->Start();
+ if (base::ThreadTicks::IsSupported())
+ thread_starts.push_back(ThreadNow(*threads_.back()));
+ }
+
+ Init();
+
+ base::TimeTicks start = base::TimeTicks::Now();
+ PingPong(kNumRuns);
+ done_.Wait();
+ base::TimeTicks end = base::TimeTicks::Now();
+
+ // Gather the cpu-time spent on each thread. This does one extra tasks,
+ // but that should be in the noise given enough runs.
+ base::TimeDelta thread_time;
+ while (threads_.size()) {
+ if (base::ThreadTicks::IsSupported()) {
+ thread_time += ThreadNow(*threads_.back()) - thread_starts.back();
+ thread_starts.pop_back();
+ }
+ threads_.pop_back();
+ }
+
+ Reset();
+
+ double num_runs = static_cast<double>(kNumRuns);
+ double us_per_task_clock = (end - start).InMicroseconds() / num_runs;
+ double us_per_task_cpu = thread_time.InMicroseconds() / num_runs;
+
+ // Clock time per task.
+ perf_test::PrintResult(
+ "task", "", name + "_time ", us_per_task_clock, "us/hop", true);
+
+ // Total utilization across threads if available (likely higher).
+ if (base::ThreadTicks::IsSupported()) {
+ perf_test::PrintResult(
+ "task", "", name + "_cpu ", us_per_task_cpu, "us/hop", true);
+ }
+ }
+
+ protected:
+ void FinishMeasurement() { done_.Signal(); }
+ std::vector<std::unique_ptr<base::Thread>> threads_;
+
+ private:
+ base::WaitableEvent done_;
+};
+
+// Class to test task performance by posting empty tasks back and forth.
+class TaskPerfTest : public ThreadPerfTest {
+ base::Thread* NextThread(int count) {
+ return threads_[count % threads_.size()].get();
+ }
+
+ void PingPong(int hops) override {
+ if (!hops) {
+ FinishMeasurement();
+ return;
+ }
+ NextThread(hops)->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&ThreadPerfTest::PingPong,
+ base::Unretained(this), hops - 1));
+ }
+};
+
+// This tries to test the 'best-case' as well as the 'worst-case' task posting
+// performance. The best-case keeps one thread alive such that it never yeilds,
+// while the worse-case forces a context switch for every task. Four threads are
+// used to ensure the threads do yeild (with just two it might be possible for
+// both threads to stay awake if they can signal each other fast enough).
+TEST_F(TaskPerfTest, TaskPingPong) {
+ RunPingPongTest("1_Task_Threads", 1);
+ RunPingPongTest("4_Task_Threads", 4);
+}
+
+
+// Same as above, but add observers to test their perf impact.
+class MessageLoopObserver : public base::MessageLoop::TaskObserver {
+ public:
+ void WillProcessTask(const base::PendingTask& pending_task) override {}
+ void DidProcessTask(const base::PendingTask& pending_task) override {}
+};
+MessageLoopObserver message_loop_observer;
+
+class TaskObserverPerfTest : public TaskPerfTest {
+ public:
+ void Init() override {
+ TaskPerfTest::Init();
+ for (size_t i = 0; i < threads_.size(); i++) {
+ threads_[i]->message_loop()->task_runner()->PostTask(
+ FROM_HERE, BindOnce(&MessageLoop::AddTaskObserver,
+ Unretained(threads_[i]->message_loop()),
+ Unretained(&message_loop_observer)));
+ }
+ }
+};
+
+TEST_F(TaskObserverPerfTest, TaskPingPong) {
+ RunPingPongTest("1_Task_Threads_With_Observer", 1);
+ RunPingPongTest("4_Task_Threads_With_Observer", 4);
+}
+
+// Class to test our WaitableEvent performance by signaling back and fort.
+// WaitableEvent is templated so we can also compare with other versions.
+template <typename WaitableEventType>
+class EventPerfTest : public ThreadPerfTest {
+ public:
+ void Init() override {
+ for (size_t i = 0; i < threads_.size(); i++) {
+ events_.push_back(std::make_unique<WaitableEventType>(
+ WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED));
+ }
+ }
+
+ void Reset() override { events_.clear(); }
+
+ void WaitAndSignalOnThread(size_t event) {
+ size_t next_event = (event + 1) % events_.size();
+ int my_hops = 0;
+ do {
+ events_[event]->Wait();
+ my_hops = --remaining_hops_; // We own 'hops' between Wait and Signal.
+ events_[next_event]->Signal();
+ } while (my_hops > 0);
+ // Once we are done, all threads will signal as hops passes zero.
+ // We only signal completion once, on the thread that reaches zero.
+ if (!my_hops)
+ FinishMeasurement();
+ }
+
+ void PingPong(int hops) override {
+ remaining_hops_ = hops;
+ for (size_t i = 0; i < threads_.size(); i++) {
+ threads_[i]->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&EventPerfTest::WaitAndSignalOnThread,
+ base::Unretained(this), i));
+ }
+
+ // Kick off the Signal ping-ponging.
+ events_.front()->Signal();
+ }
+
+ int remaining_hops_;
+ std::vector<std::unique_ptr<WaitableEventType>> events_;
+};
+
+// Similar to the task posting test, this just tests similar functionality
+// using WaitableEvents. We only test four threads (worst-case), but we
+// might want to craft a way to test the best-case (where the thread doesn't
+// end up blocking because the event is already signalled).
+typedef EventPerfTest<base::WaitableEvent> WaitableEventThreadPerfTest;
+TEST_F(WaitableEventThreadPerfTest, EventPingPong) {
+ RunPingPongTest("4_WaitableEvent_Threads", 4);
+}
+
+// Build a minimal event using ConditionVariable.
+class ConditionVariableEvent {
+ public:
+ ConditionVariableEvent(WaitableEvent::ResetPolicy reset_policy,
+ WaitableEvent::InitialState initial_state)
+ : cond_(&lock_), signaled_(false) {
+ DCHECK_EQ(WaitableEvent::ResetPolicy::AUTOMATIC, reset_policy);
+ DCHECK_EQ(WaitableEvent::InitialState::NOT_SIGNALED, initial_state);
+ }
+
+ void Signal() {
+ {
+ base::AutoLock scoped_lock(lock_);
+ signaled_ = true;
+ }
+ cond_.Signal();
+ }
+
+ void Wait() {
+ base::AutoLock scoped_lock(lock_);
+ while (!signaled_)
+ cond_.Wait();
+ signaled_ = false;
+ }
+
+ private:
+ base::Lock lock_;
+ base::ConditionVariable cond_;
+ bool signaled_;
+};
+
+// This is meant to test the absolute minimal context switching time
+// using our own base synchronization code.
+typedef EventPerfTest<ConditionVariableEvent> ConditionVariablePerfTest;
+TEST_F(ConditionVariablePerfTest, EventPingPong) {
+ RunPingPongTest("4_ConditionVariable_Threads", 4);
+}
+#if defined(OS_POSIX)
+
+// Absolutely 100% minimal posix waitable event. If there is a better/faster
+// way to force a context switch, we should use that instead.
+class PthreadEvent {
+ public:
+ PthreadEvent(WaitableEvent::ResetPolicy reset_policy,
+ WaitableEvent::InitialState initial_state) {
+ DCHECK_EQ(WaitableEvent::ResetPolicy::AUTOMATIC, reset_policy);
+ DCHECK_EQ(WaitableEvent::InitialState::NOT_SIGNALED, initial_state);
+ pthread_mutex_init(&mutex_, nullptr);
+ pthread_cond_init(&cond_, nullptr);
+ signaled_ = false;
+ }
+
+ ~PthreadEvent() {
+ pthread_cond_destroy(&cond_);
+ pthread_mutex_destroy(&mutex_);
+ }
+
+ void Signal() {
+ pthread_mutex_lock(&mutex_);
+ signaled_ = true;
+ pthread_mutex_unlock(&mutex_);
+ pthread_cond_signal(&cond_);
+ }
+
+ void Wait() {
+ pthread_mutex_lock(&mutex_);
+ while (!signaled_)
+ pthread_cond_wait(&cond_, &mutex_);
+ signaled_ = false;
+ pthread_mutex_unlock(&mutex_);
+ }
+
+ private:
+ bool signaled_;
+ pthread_mutex_t mutex_;
+ pthread_cond_t cond_;
+};
+
+// This is meant to test the absolute minimal context switching time.
+// If there is any faster way to do this we should substitute it in.
+typedef EventPerfTest<PthreadEvent> PthreadEventPerfTest;
+TEST_F(PthreadEventPerfTest, EventPingPong) {
+ RunPingPongTest("4_PthreadCondVar_Threads", 4);
+}
+
+#endif
+
+} // namespace
+
+} // namespace base
diff --git a/base/threading/thread_task_runner_handle_unittest.cc b/base/threading/thread_task_runner_handle_unittest.cc
new file mode 100644
index 0000000000..1aa02d151d
--- /dev/null
+++ b/base/threading/thread_task_runner_handle_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/thread_task_runner_handle.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/test/gtest_util.h"
+#include "base/test/test_simple_task_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(ThreadTaskRunnerHandleTest, Basic) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner(new TestSimpleTaskRunner);
+
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ {
+ ThreadTaskRunnerHandle ttrh1(task_runner);
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner, ThreadTaskRunnerHandle::Get());
+ }
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+}
+
+TEST(ThreadTaskRunnerHandleTest, DeathOnImplicitOverride) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner(new TestSimpleTaskRunner);
+ scoped_refptr<SingleThreadTaskRunner> overidding_task_runner(
+ new TestSimpleTaskRunner);
+
+ ThreadTaskRunnerHandle ttrh(task_runner);
+ EXPECT_DCHECK_DEATH(
+ { ThreadTaskRunnerHandle overriding_ttrh(overidding_task_runner); });
+}
+
+TEST(ThreadTaskRunnerHandleTest, OverrideForTestingExistingTTRH) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner_1(new TestSimpleTaskRunner);
+ scoped_refptr<SingleThreadTaskRunner> task_runner_2(new TestSimpleTaskRunner);
+ scoped_refptr<SingleThreadTaskRunner> task_runner_3(new TestSimpleTaskRunner);
+ scoped_refptr<SingleThreadTaskRunner> task_runner_4(new TestSimpleTaskRunner);
+
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ {
+ // TTRH in place prior to override.
+ ThreadTaskRunnerHandle ttrh1(task_runner_1);
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
+
+ {
+ // Override.
+ ScopedClosureRunner undo_override_2 =
+ ThreadTaskRunnerHandle::OverrideForTesting(task_runner_2);
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_2, ThreadTaskRunnerHandle::Get());
+
+ {
+ // Nested override.
+ ScopedClosureRunner undo_override_3 =
+ ThreadTaskRunnerHandle::OverrideForTesting(task_runner_3);
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_3, ThreadTaskRunnerHandle::Get());
+ }
+
+ // Back to single override.
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_2, ThreadTaskRunnerHandle::Get());
+
+ {
+ // Backup to double override with another TTRH.
+ ScopedClosureRunner undo_override_4 =
+ ThreadTaskRunnerHandle::OverrideForTesting(task_runner_4);
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_4, ThreadTaskRunnerHandle::Get());
+ }
+ }
+
+ // Back to simple TTRH.
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
+ }
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+}
+
+TEST(ThreadTaskRunnerHandleTest, OverrideForTestingNoExistingTTRH) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner_1(new TestSimpleTaskRunner);
+ scoped_refptr<SingleThreadTaskRunner> task_runner_2(new TestSimpleTaskRunner);
+
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ {
+ // Override with no TTRH in place.
+ ScopedClosureRunner undo_override_1 =
+ ThreadTaskRunnerHandle::OverrideForTesting(task_runner_1);
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
+
+ {
+ // Nested override works the same.
+ ScopedClosureRunner undo_override_2 =
+ ThreadTaskRunnerHandle::OverrideForTesting(task_runner_2);
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_2, ThreadTaskRunnerHandle::Get());
+ }
+
+ // Back to single override.
+ EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
+ }
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+}
+
+TEST(ThreadTaskRunnerHandleTest, DeathOnTTRHOverOverride) {
+ scoped_refptr<SingleThreadTaskRunner> task_runner(new TestSimpleTaskRunner);
+ scoped_refptr<SingleThreadTaskRunner> overidding_task_runner(
+ new TestSimpleTaskRunner);
+
+ ScopedClosureRunner undo_override =
+ ThreadTaskRunnerHandle::OverrideForTesting(task_runner);
+ EXPECT_DCHECK_DEATH(
+ { ThreadTaskRunnerHandle overriding_ttrh(overidding_task_runner); });
+}
+
+} // namespace base
diff --git a/base/threading/watchdog.cc b/base/threading/watchdog.cc
new file mode 100644
index 0000000000..0e48c8e452
--- /dev/null
+++ b/base/threading/watchdog.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/watchdog.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/threading/platform_thread.h"
+
+namespace base {
+
+namespace {
+
+// When the debugger breaks (when we alarm), all the other alarms that are
+// armed will expire (also alarm). To diminish this effect, we track any
+// delay due to debugger breaks, and we *try* to adjust the effective start
+// time of other alarms to step past the debugging break.
+// Without this safety net, any alarm will typically trigger a host of follow
+// on alarms from callers that specify old times.
+
+struct StaticData {
+ // Lock for access of static data...
+ Lock lock;
+
+ // When did we last alarm and get stuck (for a while) in a debugger?
+ TimeTicks last_debugged_alarm_time;
+
+ // How long did we sit on a break in the debugger?
+ TimeDelta last_debugged_alarm_delay;
+};
+
+StaticData* GetStaticData() {
+ static base::NoDestructor<StaticData> static_data;
+ return static_data.get();
+}
+
+} // namespace
+
+// Start thread running in a Disarmed state.
+Watchdog::Watchdog(const TimeDelta& duration,
+ const std::string& thread_watched_name,
+ bool enabled)
+ : enabled_(enabled),
+ lock_(),
+ condition_variable_(&lock_),
+ state_(DISARMED),
+ duration_(duration),
+ thread_watched_name_(thread_watched_name),
+ delegate_(this) {
+ if (!enabled_)
+ return; // Don't start thread, or doing anything really.
+ enabled_ = PlatformThread::Create(0, // Default stack size.
+ &delegate_,
+ &handle_);
+ DCHECK(enabled_);
+}
+
+// Notify watchdog thread, and wait for it to finish up.
+Watchdog::~Watchdog() {
+ if (!enabled_)
+ return;
+ if (!IsJoinable())
+ Cleanup();
+ PlatformThread::Join(handle_);
+}
+
+void Watchdog::Cleanup() {
+ if (!enabled_)
+ return;
+ AutoLock lock(lock_);
+ state_ = SHUTDOWN;
+ condition_variable_.Signal();
+}
+
+bool Watchdog::IsJoinable() {
+ if (!enabled_)
+ return true;
+ AutoLock lock(lock_);
+ return (state_ == JOINABLE);
+}
+
+void Watchdog::Arm() {
+ ArmAtStartTime(TimeTicks::Now());
+}
+
+void Watchdog::ArmSomeTimeDeltaAgo(const TimeDelta& time_delta) {
+ ArmAtStartTime(TimeTicks::Now() - time_delta);
+}
+
+// Start clock for watchdog.
+void Watchdog::ArmAtStartTime(const TimeTicks start_time) {
+ AutoLock lock(lock_);
+ start_time_ = start_time;
+ state_ = ARMED;
+ // Force watchdog to wake up, and go to sleep with the timer ticking with the
+ // proper duration.
+ condition_variable_.Signal();
+}
+
+// Disable watchdog so that it won't do anything when time expires.
+void Watchdog::Disarm() {
+ AutoLock lock(lock_);
+ state_ = DISARMED;
+ // We don't need to signal, as the watchdog will eventually wake up, and it
+ // will check its state and time, and act accordingly.
+}
+
+void Watchdog::Alarm() {
+ DVLOG(1) << "Watchdog alarmed for " << thread_watched_name_;
+}
+
+//------------------------------------------------------------------------------
+// Internal private methods that the watchdog thread uses.
+
+void Watchdog::ThreadDelegate::ThreadMain() {
+ SetThreadName();
+ TimeDelta remaining_duration;
+ StaticData* static_data = GetStaticData();
+ while (1) {
+ AutoLock lock(watchdog_->lock_);
+ while (DISARMED == watchdog_->state_)
+ watchdog_->condition_variable_.Wait();
+ if (SHUTDOWN == watchdog_->state_) {
+ watchdog_->state_ = JOINABLE;
+ return;
+ }
+ DCHECK(ARMED == watchdog_->state_);
+ remaining_duration = watchdog_->duration_ -
+ (TimeTicks::Now() - watchdog_->start_time_);
+ if (remaining_duration.InMilliseconds() > 0) {
+ // Spurios wake? Timer drifts? Go back to sleep for remaining time.
+ watchdog_->condition_variable_.TimedWait(remaining_duration);
+ continue;
+ }
+ // We overslept, so this seems like a real alarm.
+ // Watch out for a user that stopped the debugger on a different alarm!
+ {
+ AutoLock static_lock(static_data->lock);
+ if (static_data->last_debugged_alarm_time > watchdog_->start_time_) {
+ // False alarm: we started our clock before the debugger break (last
+ // alarm time).
+ watchdog_->start_time_ += static_data->last_debugged_alarm_delay;
+ if (static_data->last_debugged_alarm_time > watchdog_->start_time_)
+ // Too many alarms must have taken place.
+ watchdog_->state_ = DISARMED;
+ continue;
+ }
+ }
+ watchdog_->state_ = DISARMED; // Only alarm at most once.
+ TimeTicks last_alarm_time = TimeTicks::Now();
+ {
+ AutoUnlock unlock(watchdog_->lock_);
+ watchdog_->Alarm(); // Set a break point here to debug on alarms.
+ }
+ TimeDelta last_alarm_delay = TimeTicks::Now() - last_alarm_time;
+ if (last_alarm_delay <= TimeDelta::FromMilliseconds(2))
+ continue;
+ // Ignore race of two alarms/breaks going off at roughly the same time.
+ AutoLock static_lock(static_data->lock);
+ // This was a real debugger break.
+ static_data->last_debugged_alarm_time = last_alarm_time;
+ static_data->last_debugged_alarm_delay = last_alarm_delay;
+ }
+}
+
+void Watchdog::ThreadDelegate::SetThreadName() const {
+ std::string name = watchdog_->thread_watched_name_ + " Watchdog";
+ PlatformThread::SetName(name);
+ DVLOG(1) << "Watchdog active: " << name;
+}
+
+// static
+void Watchdog::ResetStaticData() {
+ StaticData* static_data = GetStaticData();
+ AutoLock lock(static_data->lock);
+ // See https://crbug.com/734232 for why this cannot be zero-initialized.
+ static_data->last_debugged_alarm_time = TimeTicks::Min();
+ static_data->last_debugged_alarm_delay = TimeDelta();
+}
+
+} // namespace base
diff --git a/base/threading/watchdog.h b/base/threading/watchdog.h
new file mode 100644
index 0000000000..f8069846e4
--- /dev/null
+++ b/base/threading/watchdog.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The Watchdog class creates a second thread that can Alarm if a specific
+// duration of time passes without proper attention. The duration of time is
+// specified at construction time. The Watchdog may be used many times by
+// simply calling Arm() (to start timing) and Disarm() (to reset the timer).
+// The Watchdog is typically used under a debugger, where the stack traces on
+// other threads can be examined if/when the Watchdog alarms.
+
+// Some watchdogs will be enabled or disabled via command line switches. To
+// facilitate such code, an "enabled" argument for the constuctor can be used
+// to permanently disable the watchdog. Disabled watchdogs don't even spawn
+// a second thread, and their methods call (Arm() and Disarm()) return very
+// quickly.
+
+#ifndef BASE_THREADING_WATCHDOG_H_
+#define BASE_THREADING_WATCHDOG_H_
+
+#include <string>
+
+#include "base/base_export.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+
+namespace base {
+
+class BASE_EXPORT Watchdog {
+ public:
+ // Constructor specifies how long the Watchdog will wait before alarming.
+ Watchdog(const TimeDelta& duration,
+ const std::string& thread_watched_name,
+ bool enabled);
+ virtual ~Watchdog();
+
+ // Notify watchdog thread to finish up. Sets the state_ to SHUTDOWN.
+ void Cleanup();
+
+ // Returns true if we state_ is JOINABLE (which indicates that Watchdog has
+ // exited).
+ bool IsJoinable();
+
+ // Start timing, and alarm when time expires (unless we're disarm()ed.)
+ void Arm(); // Arm starting now.
+ void ArmSomeTimeDeltaAgo(const TimeDelta& time_delta);
+ void ArmAtStartTime(const TimeTicks start_time);
+
+ // Reset time, and do not set off the alarm.
+ void Disarm();
+
+ // Alarm is called if the time expires after an Arm() without someone calling
+ // Disarm(). This method can be overridden to create testable classes.
+ virtual void Alarm();
+
+ // Reset static data to initial state. Useful for tests, to ensure
+ // they are independent.
+ static void ResetStaticData();
+
+ private:
+ class ThreadDelegate : public PlatformThread::Delegate {
+ public:
+ explicit ThreadDelegate(Watchdog* watchdog) : watchdog_(watchdog) {
+ }
+ void ThreadMain() override;
+
+ private:
+ void SetThreadName() const;
+
+ Watchdog* watchdog_;
+ };
+
+ enum State {ARMED, DISARMED, SHUTDOWN, JOINABLE };
+
+ bool enabled_;
+
+ Lock lock_; // Mutex for state_.
+ ConditionVariable condition_variable_;
+ State state_;
+ const TimeDelta duration_; // How long after start_time_ do we alarm?
+ const std::string thread_watched_name_;
+ PlatformThreadHandle handle_;
+ ThreadDelegate delegate_; // Store it, because it must outlive the thread.
+
+ TimeTicks start_time_; // Start of epoch, and alarm after duration_.
+
+ DISALLOW_COPY_AND_ASSIGN(Watchdog);
+};
+
+} // namespace base
+
+#endif // BASE_THREADING_WATCHDOG_H_
diff --git a/base/threading/watchdog_unittest.cc b/base/threading/watchdog_unittest.cc
new file mode 100644
index 0000000000..f534a863d4
--- /dev/null
+++ b/base/threading/watchdog_unittest.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/watchdog.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/synchronization/spin_wait.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+//------------------------------------------------------------------------------
+// Provide a derived class to facilitate testing.
+
+class WatchdogCounter : public Watchdog {
+ public:
+ WatchdogCounter(const TimeDelta& duration,
+ const std::string& thread_watched_name,
+ bool enabled)
+ : Watchdog(duration, thread_watched_name, enabled),
+ alarm_counter_(0) {
+ }
+
+ ~WatchdogCounter() override = default;
+
+ void Alarm() override {
+ alarm_counter_++;
+ Watchdog::Alarm();
+ }
+
+ int alarm_counter() { return alarm_counter_; }
+
+ private:
+ int alarm_counter_;
+
+ DISALLOW_COPY_AND_ASSIGN(WatchdogCounter);
+};
+
+class WatchdogTest : public testing::Test {
+ public:
+ void SetUp() override { Watchdog::ResetStaticData(); }
+};
+
+} // namespace
+
+//------------------------------------------------------------------------------
+// Actual tests
+
+// Minimal constructor/destructor test.
+TEST_F(WatchdogTest, StartupShutdownTest) {
+ Watchdog watchdog1(TimeDelta::FromMilliseconds(300), "Disabled", false);
+ Watchdog watchdog2(TimeDelta::FromMilliseconds(300), "Enabled", true);
+}
+
+// Test ability to call Arm and Disarm repeatedly.
+TEST_F(WatchdogTest, ArmDisarmTest) {
+ Watchdog watchdog1(TimeDelta::FromMilliseconds(300), "Disabled", false);
+ watchdog1.Arm();
+ watchdog1.Disarm();
+ watchdog1.Arm();
+ watchdog1.Disarm();
+
+ Watchdog watchdog2(TimeDelta::FromMilliseconds(300), "Enabled", true);
+ watchdog2.Arm();
+ watchdog2.Disarm();
+ watchdog2.Arm();
+ watchdog2.Disarm();
+}
+
+// Make sure a basic alarm fires when the time has expired.
+TEST_F(WatchdogTest, AlarmTest) {
+ WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), "Enabled", true);
+ watchdog.Arm();
+ SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5),
+ watchdog.alarm_counter() > 0);
+ EXPECT_EQ(1, watchdog.alarm_counter());
+}
+
+// Make sure a basic alarm fires when the time has expired.
+TEST_F(WatchdogTest, AlarmPriorTimeTest) {
+ WatchdogCounter watchdog(TimeDelta(), "Enabled2", true);
+ // Set a time in the past.
+ watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(2));
+ // It should instantly go off, but certainly in less than 5 minutes.
+ SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5),
+ watchdog.alarm_counter() > 0);
+
+ EXPECT_EQ(1, watchdog.alarm_counter());
+}
+
+// Make sure a disable alarm does nothing, even if we arm it.
+TEST_F(WatchdogTest, ConstructorDisabledTest) {
+ WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), "Disabled", false);
+ watchdog.Arm();
+ // Alarm should not fire, as it was disabled.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(500));
+ EXPECT_EQ(0, watchdog.alarm_counter());
+}
+
+// Make sure Disarming will prevent firing, even after Arming.
+TEST_F(WatchdogTest, DisarmTest) {
+ WatchdogCounter watchdog(TimeDelta::FromSeconds(1), "Enabled3", true);
+
+ TimeTicks start = TimeTicks::Now();
+ watchdog.Arm();
+ // Sleep a bit, but not past the alarm point.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ watchdog.Disarm();
+ TimeTicks end = TimeTicks::Now();
+
+ if (end - start > TimeDelta::FromMilliseconds(500)) {
+ LOG(WARNING) << "100ms sleep took over 500ms, making the results of this "
+ << "timing-sensitive test suspicious. Aborting now.";
+ return;
+ }
+
+ // Alarm should not have fired before it was disarmed.
+ EXPECT_EQ(0, watchdog.alarm_counter());
+
+ // Sleep past the point where it would have fired if it wasn't disarmed,
+ // and verify that it didn't fire.
+ PlatformThread::Sleep(TimeDelta::FromSeconds(1));
+ EXPECT_EQ(0, watchdog.alarm_counter());
+
+ // ...but even after disarming, we can still use the alarm...
+ // Set a time greater than the timeout into the past.
+ watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(10));
+ // It should almost instantly go off, but certainly in less than 5 minutes.
+ SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5),
+ watchdog.alarm_counter() > 0);
+
+ EXPECT_EQ(1, watchdog.alarm_counter());
+}
+
+} // namespace base
diff --git a/base/time/time.h b/base/time/time.h
index b3c77e7289..ba7be4b8c4 100644
--- a/base/time/time.h
+++ b/base/time/time.h
@@ -199,10 +199,8 @@ class BASE_EXPORT TimeDelta {
double InMicrosecondsF() const;
int64_t InNanoseconds() const;
- constexpr TimeDelta& operator=(TimeDelta other) {
- delta_ = other.delta_;
- return *this;
- }
+ constexpr TimeDelta& operator=(const TimeDelta&) = default;
+ constexpr TimeDelta(const TimeDelta&) = default;
// Computations with other deltas. Can easily be made constexpr with C++17 but
// hard to do until then per limitations around
diff --git a/base/tools_sanity_unittest.cc b/base/tools_sanity_unittest.cc
new file mode 100644
index 0000000000..98c30df47d
--- /dev/null
+++ b/base/tools_sanity_unittest.cc
@@ -0,0 +1,423 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file contains intentional memory errors, some of which may lead to
+// crashes if the test is ran without special memory testing tools. We use these
+// errors to verify the sanity of the tools.
+
+#include <stddef.h>
+
+#include "base/atomicops.h"
+#include "base/cfi_buildflags.h"
+#include "base/debug/asan_invalid_access.h"
+#include "base/debug/profiler.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+const base::subtle::Atomic32 kMagicValue = 42;
+
+// Helper for memory accesses that can potentially corrupt memory or cause a
+// crash during a native run.
+#if defined(ADDRESS_SANITIZER)
+#if defined(OS_IOS)
+// EXPECT_DEATH is not supported on IOS.
+#define HARMFUL_ACCESS(action,error_regexp) do { action; } while (0)
+#else
+#define HARMFUL_ACCESS(action,error_regexp) EXPECT_DEATH(action,error_regexp)
+#endif // !OS_IOS
+#else
+#define HARMFUL_ACCESS(action, error_regexp)
+#define HARMFUL_ACCESS_IS_NOOP
+#endif
+
+void DoReadUninitializedValue(char *ptr) {
+ // Comparison with 64 is to prevent clang from optimizing away the
+ // jump -- valgrind only catches jumps and conditional moves, but clang uses
+ // the borrow flag if the condition is just `*ptr == '\0'`. We no longer
+ // support valgrind, but this constant should be fine to keep as-is.
+ if (*ptr == 64) {
+ VLOG(1) << "Uninit condition is true";
+ } else {
+ VLOG(1) << "Uninit condition is false";
+ }
+}
+
+void ReadUninitializedValue(char *ptr) {
+#if defined(MEMORY_SANITIZER)
+ EXPECT_DEATH(DoReadUninitializedValue(ptr),
+ "use-of-uninitialized-value");
+#else
+ DoReadUninitializedValue(ptr);
+#endif
+}
+
+#ifndef HARMFUL_ACCESS_IS_NOOP
+void ReadValueOutOfArrayBoundsLeft(char *ptr) {
+ char c = ptr[-2];
+ VLOG(1) << "Reading a byte out of bounds: " << c;
+}
+
+void ReadValueOutOfArrayBoundsRight(char *ptr, size_t size) {
+ char c = ptr[size + 1];
+ VLOG(1) << "Reading a byte out of bounds: " << c;
+}
+
+void WriteValueOutOfArrayBoundsLeft(char *ptr) {
+ ptr[-1] = kMagicValue;
+}
+
+void WriteValueOutOfArrayBoundsRight(char *ptr, size_t size) {
+ ptr[size] = kMagicValue;
+}
+#endif // HARMFUL_ACCESS_IS_NOOP
+
+void MakeSomeErrors(char *ptr, size_t size) {
+ ReadUninitializedValue(ptr);
+
+ HARMFUL_ACCESS(ReadValueOutOfArrayBoundsLeft(ptr),
+ "2 bytes to the left");
+ HARMFUL_ACCESS(ReadValueOutOfArrayBoundsRight(ptr, size),
+ "1 bytes to the right");
+ HARMFUL_ACCESS(WriteValueOutOfArrayBoundsLeft(ptr),
+ "1 bytes to the left");
+ HARMFUL_ACCESS(WriteValueOutOfArrayBoundsRight(ptr, size),
+ "0 bytes to the right");
+}
+
+} // namespace
+
+// A memory leak detector should report an error in this test.
+TEST(ToolsSanityTest, MemoryLeak) {
+ // Without the |volatile|, clang optimizes away the next two lines.
+ int* volatile leak = new int[256]; // Leak some memory intentionally.
+ leak[4] = 1; // Make sure the allocated memory is used.
+}
+
+#if (defined(ADDRESS_SANITIZER) && defined(OS_IOS))
+// Because iOS doesn't support death tests, each of the following tests will
+// crash the whole program under Asan.
+#define MAYBE_AccessesToNewMemory DISABLED_AccessesToNewMemory
+#define MAYBE_AccessesToMallocMemory DISABLED_AccessesToMallocMemory
+#else
+#define MAYBE_AccessesToNewMemory AccessesToNewMemory
+#define MAYBE_AccessesToMallocMemory AccessesToMallocMemory
+#endif // (defined(ADDRESS_SANITIZER) && defined(OS_IOS))
+
+// The following tests pass with Clang r170392, but not r172454, which
+// makes AddressSanitizer detect errors in them. We disable these tests under
+// AddressSanitizer until we fully switch to Clang r172454. After that the
+// tests should be put back under the (defined(OS_IOS) || defined(OS_WIN))
+// clause above.
+// See also http://crbug.com/172614.
+#if defined(ADDRESS_SANITIZER)
+#define MAYBE_SingleElementDeletedWithBraces \
+ DISABLED_SingleElementDeletedWithBraces
+#define MAYBE_ArrayDeletedWithoutBraces DISABLED_ArrayDeletedWithoutBraces
+#else
+#define MAYBE_ArrayDeletedWithoutBraces ArrayDeletedWithoutBraces
+#define MAYBE_SingleElementDeletedWithBraces SingleElementDeletedWithBraces
+#endif // defined(ADDRESS_SANITIZER)
+
+TEST(ToolsSanityTest, MAYBE_AccessesToNewMemory) {
+ char *foo = new char[10];
+ MakeSomeErrors(foo, 10);
+ delete [] foo;
+ // Use after delete.
+ HARMFUL_ACCESS(foo[5] = 0, "heap-use-after-free");
+}
+
+TEST(ToolsSanityTest, MAYBE_AccessesToMallocMemory) {
+ char *foo = reinterpret_cast<char*>(malloc(10));
+ MakeSomeErrors(foo, 10);
+ free(foo);
+ // Use after free.
+ HARMFUL_ACCESS(foo[5] = 0, "heap-use-after-free");
+}
+
+#if defined(ADDRESS_SANITIZER)
+
+static int* allocateArray() {
+ // Clang warns about the mismatched new[]/delete if they occur in the same
+ // function.
+ return new int[10];
+}
+
+// This test may corrupt memory if not compiled with AddressSanitizer.
+TEST(ToolsSanityTest, MAYBE_ArrayDeletedWithoutBraces) {
+ // Without the |volatile|, clang optimizes away the next two lines.
+ int* volatile foo = allocateArray();
+ delete foo;
+}
+#endif
+
+#if defined(ADDRESS_SANITIZER)
+static int* allocateScalar() {
+ // Clang warns about the mismatched new/delete[] if they occur in the same
+ // function.
+ return new int;
+}
+
+// This test may corrupt memory if not compiled with AddressSanitizer.
+TEST(ToolsSanityTest, MAYBE_SingleElementDeletedWithBraces) {
+ // Without the |volatile|, clang optimizes away the next two lines.
+ int* volatile foo = allocateScalar();
+ (void) foo;
+ delete [] foo;
+}
+#endif
+
+#if defined(ADDRESS_SANITIZER)
+
+TEST(ToolsSanityTest, DISABLED_AddressSanitizerNullDerefCrashTest) {
+ // Intentionally crash to make sure AddressSanitizer is running.
+ // This test should not be ran on bots.
+ int* volatile zero = NULL;
+ *zero = 0;
+}
+
+TEST(ToolsSanityTest, DISABLED_AddressSanitizerLocalOOBCrashTest) {
+ // Intentionally crash to make sure AddressSanitizer is instrumenting
+ // the local variables.
+ // This test should not be ran on bots.
+ int array[5];
+ // Work around the OOB warning reported by Clang.
+ int* volatile access = &array[5];
+ *access = 43;
+}
+
+namespace {
+int g_asan_test_global_array[10];
+} // namespace
+
+TEST(ToolsSanityTest, DISABLED_AddressSanitizerGlobalOOBCrashTest) {
+ // Intentionally crash to make sure AddressSanitizer is instrumenting
+ // the global variables.
+ // This test should not be ran on bots.
+
+ // Work around the OOB warning reported by Clang.
+ int* volatile access = g_asan_test_global_array - 1;
+ *access = 43;
+}
+
+#ifndef HARMFUL_ACCESS_IS_NOOP
+TEST(ToolsSanityTest, AsanHeapOverflow) {
+ HARMFUL_ACCESS(debug::AsanHeapOverflow() ,"to the right");
+}
+
+TEST(ToolsSanityTest, AsanHeapUnderflow) {
+ HARMFUL_ACCESS(debug::AsanHeapUnderflow(), "to the left");
+}
+
+TEST(ToolsSanityTest, AsanHeapUseAfterFree) {
+ HARMFUL_ACCESS(debug::AsanHeapUseAfterFree(), "heap-use-after-free");
+}
+
+#if defined(OS_WIN)
+// The ASAN runtime doesn't detect heap corruption, this needs fixing before
+// ASAN builds can ship to the wild. See https://crbug.com/818747.
+TEST(ToolsSanityTest, DISABLED_AsanCorruptHeapBlock) {
+ HARMFUL_ACCESS(debug::AsanCorruptHeapBlock(), "");
+}
+
+TEST(ToolsSanityTest, DISABLED_AsanCorruptHeap) {
+ // This test will kill the process by raising an exception, there's no
+ // particular string to look for in the stack trace.
+ EXPECT_DEATH(debug::AsanCorruptHeap(), "");
+}
+#endif // OS_WIN
+#endif // !HARMFUL_ACCESS_IS_NOOP
+
+#endif // ADDRESS_SANITIZER
+
+namespace {
+
+// We use caps here just to ensure that the method name doesn't interfere with
+// the wildcarded suppressions.
+class TOOLS_SANITY_TEST_CONCURRENT_THREAD : public PlatformThread::Delegate {
+ public:
+ explicit TOOLS_SANITY_TEST_CONCURRENT_THREAD(bool *value) : value_(value) {}
+ ~TOOLS_SANITY_TEST_CONCURRENT_THREAD() override = default;
+ void ThreadMain() override {
+ *value_ = true;
+
+ // Sleep for a few milliseconds so the two threads are more likely to live
+ // simultaneously. Otherwise we may miss the report due to mutex
+ // lock/unlock's inside thread creation code in pure-happens-before mode...
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ }
+ private:
+ bool *value_;
+};
+
+class ReleaseStoreThread : public PlatformThread::Delegate {
+ public:
+ explicit ReleaseStoreThread(base::subtle::Atomic32 *value) : value_(value) {}
+ ~ReleaseStoreThread() override = default;
+ void ThreadMain() override {
+ base::subtle::Release_Store(value_, kMagicValue);
+
+ // Sleep for a few milliseconds so the two threads are more likely to live
+ // simultaneously. Otherwise we may miss the report due to mutex
+ // lock/unlock's inside thread creation code in pure-happens-before mode...
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ }
+ private:
+ base::subtle::Atomic32 *value_;
+};
+
+class AcquireLoadThread : public PlatformThread::Delegate {
+ public:
+ explicit AcquireLoadThread(base::subtle::Atomic32 *value) : value_(value) {}
+ ~AcquireLoadThread() override = default;
+ void ThreadMain() override {
+ // Wait for the other thread to make Release_Store
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ base::subtle::Acquire_Load(value_);
+ }
+ private:
+ base::subtle::Atomic32 *value_;
+};
+
+void RunInParallel(PlatformThread::Delegate *d1, PlatformThread::Delegate *d2) {
+ PlatformThreadHandle a;
+ PlatformThreadHandle b;
+ PlatformThread::Create(0, d1, &a);
+ PlatformThread::Create(0, d2, &b);
+ PlatformThread::Join(a);
+ PlatformThread::Join(b);
+}
+
+#if defined(THREAD_SANITIZER)
+void DataRace() {
+ bool *shared = new bool(false);
+ TOOLS_SANITY_TEST_CONCURRENT_THREAD thread1(shared), thread2(shared);
+ RunInParallel(&thread1, &thread2);
+ EXPECT_TRUE(*shared);
+ delete shared;
+ // We're in a death test - crash.
+ CHECK(0);
+}
+#endif
+
+} // namespace
+
+#if defined(THREAD_SANITIZER)
+// A data race detector should report an error in this test.
+TEST(ToolsSanityTest, DataRace) {
+ // The suppression regexp must match that in base/debug/tsan_suppressions.cc.
+ EXPECT_DEATH(DataRace(), "1 race:base/tools_sanity_unittest.cc");
+}
+#endif
+
+TEST(ToolsSanityTest, AnnotateBenignRace) {
+ bool shared = false;
+ ANNOTATE_BENIGN_RACE(&shared, "Intentional race - make sure doesn't show up");
+ TOOLS_SANITY_TEST_CONCURRENT_THREAD thread1(&shared), thread2(&shared);
+ RunInParallel(&thread1, &thread2);
+ EXPECT_TRUE(shared);
+}
+
+TEST(ToolsSanityTest, AtomicsAreIgnored) {
+ base::subtle::Atomic32 shared = 0;
+ ReleaseStoreThread thread1(&shared);
+ AcquireLoadThread thread2(&shared);
+ RunInParallel(&thread1, &thread2);
+ EXPECT_EQ(kMagicValue, shared);
+}
+
+#if BUILDFLAG(CFI_ENFORCEMENT_TRAP)
+#if defined(OS_WIN)
+#define CFI_ERROR_MSG "EXCEPTION_ILLEGAL_INSTRUCTION"
+#elif defined(OS_ANDROID)
+// TODO(pcc): Produce proper stack dumps on Android and test for the correct
+// si_code here.
+#define CFI_ERROR_MSG "^$"
+#else
+#define CFI_ERROR_MSG "ILL_ILLOPN"
+#endif
+#elif BUILDFLAG(CFI_ENFORCEMENT_DIAGNOSTIC)
+#define CFI_ERROR_MSG "runtime error: control flow integrity check"
+#endif // BUILDFLAG(CFI_ENFORCEMENT_TRAP || CFI_ENFORCEMENT_DIAGNOSTIC)
+
+#if defined(CFI_ERROR_MSG)
+class A {
+ public:
+ A(): n_(0) {}
+ virtual void f() { n_++; }
+ protected:
+ int n_;
+};
+
+class B: public A {
+ public:
+ void f() override { n_--; }
+};
+
+class C: public B {
+ public:
+ void f() override { n_ += 2; }
+};
+
+NOINLINE void KillVptrAndCall(A *obj) {
+ *reinterpret_cast<void **>(obj) = 0;
+ obj->f();
+}
+
+TEST(ToolsSanityTest, BadVirtualCallNull) {
+ A a;
+ B b;
+ EXPECT_DEATH({ KillVptrAndCall(&a); KillVptrAndCall(&b); }, CFI_ERROR_MSG);
+}
+
+NOINLINE void OverwriteVptrAndCall(B *obj, A *vptr) {
+ *reinterpret_cast<void **>(obj) = *reinterpret_cast<void **>(vptr);
+ obj->f();
+}
+
+TEST(ToolsSanityTest, BadVirtualCallWrongType) {
+ A a;
+ B b;
+ C c;
+ EXPECT_DEATH({ OverwriteVptrAndCall(&b, &a); OverwriteVptrAndCall(&b, &c); },
+ CFI_ERROR_MSG);
+}
+
+// TODO(pcc): remove CFI_CAST_CHECK, see https://crbug.com/626794.
+#if BUILDFLAG(CFI_CAST_CHECK)
+TEST(ToolsSanityTest, BadDerivedCast) {
+ A a;
+ EXPECT_DEATH((void)(B*)&a, CFI_ERROR_MSG);
+}
+
+TEST(ToolsSanityTest, BadUnrelatedCast) {
+ class A {
+ virtual void f() {}
+ };
+
+ class B {
+ virtual void f() {}
+ };
+
+ A a;
+ EXPECT_DEATH((void)(B*)&a, CFI_ERROR_MSG);
+}
+#endif // BUILDFLAG(CFI_CAST_CHECK)
+
+#endif // CFI_ERROR_MSG
+
+#undef CFI_ERROR_MSG
+#undef MAYBE_AccessesToNewMemory
+#undef MAYBE_AccessesToMallocMemory
+#undef MAYBE_ArrayDeletedWithoutBraces
+#undef MAYBE_SingleElementDeletedWithBraces
+#undef HARMFUL_ACCESS
+#undef HARMFUL_ACCESS_IS_NOOP
+
+} // namespace base
diff --git a/base/trace_event/auto_open_close_event.cc b/base/trace_event/auto_open_close_event.cc
new file mode 100644
index 0000000000..1879700b39
--- /dev/null
+++ b/base/trace_event/auto_open_close_event.cc
@@ -0,0 +1,52 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/auto_open_close_event.h"
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace trace_event {
+
+AutoOpenCloseEvent::AutoOpenCloseEvent(AutoOpenCloseEvent::Type type,
+ const char* category, const char* event_name):
+ category_(category),
+ event_name_(event_name),
+ weak_factory_(this) {
+ base::trace_event::TraceLog::GetInstance()->AddAsyncEnabledStateObserver(
+ weak_factory_.GetWeakPtr());
+}
+
+AutoOpenCloseEvent::~AutoOpenCloseEvent() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::trace_event::TraceLog::GetInstance()->RemoveAsyncEnabledStateObserver(
+ this);
+}
+
+void AutoOpenCloseEvent::Begin() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ start_time_ = TRACE_TIME_TICKS_NOW();
+ TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP0(
+ category_, event_name_, static_cast<void*>(this), start_time_);
+}
+
+void AutoOpenCloseEvent::End() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ TRACE_EVENT_ASYNC_END0(category_, event_name_, static_cast<void*>(this));
+ start_time_ = base::TimeTicks();
+}
+
+void AutoOpenCloseEvent::OnTraceLogEnabled() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (start_time_.ToInternalValue() != 0)
+ TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP0(
+ category_, event_name_, static_cast<void*>(this), start_time_);
+}
+
+void AutoOpenCloseEvent::OnTraceLogDisabled() {}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/auto_open_close_event.h b/base/trace_event/auto_open_close_event.h
new file mode 100644
index 0000000000..795a4948ac
--- /dev/null
+++ b/base/trace_event/auto_open_close_event.h
@@ -0,0 +1,57 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_AUTO_OPEN_CLOSE_EVENT_H_
+#define BASE_AUTO_OPEN_CLOSE_EVENT_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace trace_event {
+
+// Class for tracing events that support "auto-opening" and "auto-closing".
+// "auto-opening" = if the trace event is started (call Begin() before
+// tracing is started,the trace event will be opened, with the start time
+// being the time that the trace event was actually started.
+// "auto-closing" = if the trace event is started but not ended by the time
+// tracing ends, then the trace event will be automatically closed at the
+// end of tracing.
+class BASE_EXPORT AutoOpenCloseEvent
+ : public TraceLog::AsyncEnabledStateObserver {
+ public:
+ enum Type {
+ ASYNC
+ };
+
+ // As in the rest of the tracing macros, the const char* arguments here
+ // must be pointers to indefinitely lived strings (e.g. hard-coded string
+ // literals are okay, but not strings created by c_str())
+ AutoOpenCloseEvent(Type type, const char* category, const char* event_name);
+ ~AutoOpenCloseEvent() override;
+
+ void Begin();
+ void End();
+
+ // AsyncEnabledStateObserver implementation
+ void OnTraceLogEnabled() override;
+ void OnTraceLogDisabled() override;
+
+ private:
+ const char* const category_;
+ const char* const event_name_;
+ base::TimeTicks start_time_;
+ base::ThreadChecker thread_checker_;
+ WeakPtrFactory<AutoOpenCloseEvent> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoOpenCloseEvent);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_AUTO_OPEN_CLOSE_EVENT_H_ \ No newline at end of file
diff --git a/base/trace_event/blame_context.cc b/base/trace_event/blame_context.cc
new file mode 100644
index 0000000000..ae0b718201
--- /dev/null
+++ b/base/trace_event/blame_context.cc
@@ -0,0 +1,111 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/blame_context.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_argument.h"
+
+namespace base {
+namespace trace_event {
+
+BlameContext::BlameContext(const char* category,
+ const char* name,
+ const char* type,
+ const char* scope,
+ int64_t id,
+ const BlameContext* parent_context)
+ : category_(category),
+ name_(name),
+ type_(type),
+ scope_(scope),
+ id_(id),
+ parent_scope_(parent_context ? parent_context->scope() : nullptr),
+ parent_id_(parent_context ? parent_context->id() : 0),
+ category_group_enabled_(nullptr),
+ weak_factory_(this) {
+ DCHECK(!parent_context || !std::strcmp(name_, parent_context->name()))
+ << "Parent blame context must have the same name";
+}
+
+BlameContext::~BlameContext() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(WasInitialized());
+ TRACE_EVENT_API_ADD_TRACE_EVENT(
+ TRACE_EVENT_PHASE_DELETE_OBJECT, category_group_enabled_, type_, scope_,
+ id_, 0, nullptr, nullptr, nullptr, nullptr, TRACE_EVENT_FLAG_HAS_ID);
+ trace_event::TraceLog::GetInstance()->RemoveAsyncEnabledStateObserver(this);
+}
+
+void BlameContext::Enter() {
+ DCHECK(WasInitialized());
+ TRACE_EVENT_API_ADD_TRACE_EVENT(TRACE_EVENT_PHASE_ENTER_CONTEXT,
+ category_group_enabled_, name_, scope_, id_,
+ 0 /* num_args */, nullptr, nullptr, nullptr,
+ nullptr, TRACE_EVENT_FLAG_HAS_ID);
+}
+
+void BlameContext::Leave() {
+ DCHECK(WasInitialized());
+ TRACE_EVENT_API_ADD_TRACE_EVENT(TRACE_EVENT_PHASE_LEAVE_CONTEXT,
+ category_group_enabled_, name_, scope_, id_,
+ 0 /* num_args */, nullptr, nullptr, nullptr,
+ nullptr, TRACE_EVENT_FLAG_HAS_ID);
+}
+
+void BlameContext::TakeSnapshot() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(WasInitialized());
+ if (!*category_group_enabled_)
+ return;
+ std::unique_ptr<trace_event::TracedValue> snapshot(
+ new trace_event::TracedValue);
+ AsValueInto(snapshot.get());
+ static const char* const kArgName = "snapshot";
+ const int kNumArgs = 1;
+ unsigned char arg_types[1] = {TRACE_VALUE_TYPE_CONVERTABLE};
+ std::unique_ptr<trace_event::ConvertableToTraceFormat> arg_values[1] = {
+ std::move(snapshot)};
+ TRACE_EVENT_API_ADD_TRACE_EVENT(TRACE_EVENT_PHASE_SNAPSHOT_OBJECT,
+ category_group_enabled_, type_, scope_, id_,
+ kNumArgs, &kArgName, arg_types, nullptr,
+ arg_values, TRACE_EVENT_FLAG_HAS_ID);
+}
+
+void BlameContext::OnTraceLogEnabled() {
+ DCHECK(WasInitialized());
+ TakeSnapshot();
+}
+
+void BlameContext::OnTraceLogDisabled() {}
+
+void BlameContext::AsValueInto(trace_event::TracedValue* state) {
+ DCHECK(WasInitialized());
+ if (!parent_id_)
+ return;
+ state->BeginDictionary("parent");
+ state->SetString("id_ref", StringPrintf("0x%" PRIx64, parent_id_));
+ state->SetString("scope", parent_scope_);
+ state->EndDictionary();
+}
+
+void BlameContext::Initialize() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ category_group_enabled_ =
+ TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_);
+ TRACE_EVENT_API_ADD_TRACE_EVENT(
+ TRACE_EVENT_PHASE_CREATE_OBJECT, category_group_enabled_, type_, scope_,
+ id_, 0, nullptr, nullptr, nullptr, nullptr, TRACE_EVENT_FLAG_HAS_ID);
+ trace_event::TraceLog::GetInstance()->AddAsyncEnabledStateObserver(
+ weak_factory_.GetWeakPtr());
+ TakeSnapshot();
+}
+
+bool BlameContext::WasInitialized() const {
+ return category_group_enabled_ != nullptr;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/blame_context.h b/base/trace_event/blame_context.h
new file mode 100644
index 0000000000..a973a28fd2
--- /dev/null
+++ b/base/trace_event/blame_context.h
@@ -0,0 +1,138 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_BLAME_CONTEXT_H_
+#define BASE_TRACE_EVENT_BLAME_CONTEXT_H_
+
+#include <inttypes.h>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "base/trace_event/trace_log.h"
+
+namespace base {
+namespace trace_event {
+class TracedValue;
+}
+
+namespace trace_event {
+
+// A blame context represents a logical unit to which we want to attribute
+// different costs (e.g., CPU, network, or memory usage). An example of a blame
+// context is an <iframe> element on a web page. Different subsystems can
+// "enter" and "leave" blame contexts to indicate that they are doing work which
+// should be accounted against this blame context.
+//
+// A blame context can optionally have a parent context, forming a blame context
+// tree. When work is attributed to a particular blame context, it is considered
+// to count against all of that context's children too. This is useful when work
+// cannot be exactly attributed into a more specific context. For example,
+// Javascript garbage collection generally needs to inspect all objects on a
+// page instead looking at each <iframe> individually. In this case the work
+// should be attributed to a blame context which is the parent of all <iframe>
+// blame contexts.
+class BASE_EXPORT BlameContext
+ : public trace_event::TraceLog::AsyncEnabledStateObserver {
+ public:
+ // Construct a blame context belonging to the blame context tree |name|, using
+ // the tracing category |category|, identified by |id| from the |scope|
+ // namespace. |type| identifies the type of this object snapshot in the blame
+ // context tree. |parent_context| is the parent of this blame context or
+ // null. Note that all strings must have application lifetime.
+ //
+ // For example, a blame context which represents a specific <iframe> in a
+ // browser frame tree could be specified with:
+ //
+ // category="blink",
+ // name="FrameTree",
+ // type="IFrame",
+ // scope="IFrameIdentifier",
+ // id=1234.
+ //
+ // Each <iframe> blame context could have another <iframe> context as a
+ // parent, or a top-level context which represents the entire browser:
+ //
+ // category="blink",
+ // name="FrameTree",
+ // type="Browser",
+ // scope="BrowserIdentifier",
+ // id=1.
+ //
+ // Note that the |name| property is identical, signifying that both context
+ // types are part of the same tree.
+ //
+ BlameContext(const char* category,
+ const char* name,
+ const char* type,
+ const char* scope,
+ int64_t id,
+ const BlameContext* parent_context);
+ ~BlameContext() override;
+
+ // Initialize the blame context, automatically taking a snapshot if tracing is
+ // enabled. Must be called before any other methods on this class.
+ void Initialize();
+
+ // Indicate that the current thread is now doing work which should count
+ // against this blame context. This function is allowed to be called in a
+ // thread different from where the blame context was created; However, any
+ // client doing that must be fully responsible for ensuring thready safety.
+ void Enter();
+
+ // Leave and stop doing work for a previously entered blame context. If
+ // another blame context belonging to the same tree was entered prior to this
+ // one, it becomes the active blame context for this thread again. Similar
+ // to Enter(), this function can be called in a thread different from where
+ // the blame context was created, and the same requirement on thread safety
+ // must be satisfied.
+ void Leave();
+
+ // Record a snapshot of the blame context. This is normally only needed if a
+ // blame context subclass defines custom properties (see AsValueInto) and one
+ // or more of those properties have changed.
+ void TakeSnapshot();
+
+ const char* category() const { return category_; }
+ const char* name() const { return name_; }
+ const char* type() const { return type_; }
+ const char* scope() const { return scope_; }
+ int64_t id() const { return id_; }
+
+ // trace_event::TraceLog::EnabledStateObserver implementation:
+ void OnTraceLogEnabled() override;
+ void OnTraceLogDisabled() override;
+
+ protected:
+ // Serialize the properties of this blame context into |state|. Subclasses can
+ // override this method to record additional properties (e.g, the URL for an
+ // <iframe> blame context). Note that an overridden implementation must still
+ // call this base method.
+ virtual void AsValueInto(trace_event::TracedValue* state);
+
+ private:
+ bool WasInitialized() const;
+
+ // The following string pointers have application lifetime.
+ const char* category_;
+ const char* name_;
+ const char* type_;
+ const char* scope_;
+ const int64_t id_;
+
+ const char* parent_scope_;
+ const int64_t parent_id_;
+
+ const unsigned char* category_group_enabled_;
+
+ ThreadChecker thread_checker_;
+ WeakPtrFactory<BlameContext> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlameContext);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_BLAME_CONTEXT_H_
diff --git a/base/trace_event/blame_context_unittest.cc b/base/trace_event/blame_context_unittest.cc
new file mode 100644
index 0000000000..12e7857bdf
--- /dev/null
+++ b/base/trace_event/blame_context_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/blame_context.h"
+
+#include "base/json/json_writer.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/trace_event_analyzer.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+namespace {
+
+const char kTestBlameContextCategory[] = "test";
+const char kDisabledTestBlameContextCategory[] = "disabled-by-default-test";
+const char kTestBlameContextName[] = "TestBlameContext";
+const char kTestBlameContextType[] = "TestBlameContextType";
+const char kTestBlameContextScope[] = "TestBlameContextScope";
+
+class TestBlameContext : public BlameContext {
+ public:
+ explicit TestBlameContext(int id)
+ : BlameContext(kTestBlameContextCategory,
+ kTestBlameContextName,
+ kTestBlameContextType,
+ kTestBlameContextScope,
+ id,
+ nullptr) {}
+
+ TestBlameContext(int id, const TestBlameContext& parent)
+ : BlameContext(kTestBlameContextCategory,
+ kTestBlameContextName,
+ kTestBlameContextType,
+ kTestBlameContextScope,
+ id,
+ &parent) {}
+
+ protected:
+ void AsValueInto(trace_event::TracedValue* state) override {
+ BlameContext::AsValueInto(state);
+ state->SetBoolean("crossStreams", false);
+ }
+};
+
+class DisabledTestBlameContext : public BlameContext {
+ public:
+ explicit DisabledTestBlameContext(int id)
+ : BlameContext(kDisabledTestBlameContextCategory,
+ kTestBlameContextName,
+ kTestBlameContextType,
+ kTestBlameContextScope,
+ id,
+ nullptr) {}
+};
+
+class BlameContextTest : public testing::Test {
+ protected:
+ MessageLoop loop_;
+};
+
+TEST_F(BlameContextTest, EnterAndLeave) {
+ using trace_analyzer::Query;
+ trace_analyzer::Start("*");
+ {
+ TestBlameContext blame_context(0x1234);
+ blame_context.Initialize();
+ blame_context.Enter();
+ blame_context.Leave();
+ }
+ auto analyzer = trace_analyzer::Stop();
+
+ trace_analyzer::TraceEventVector events;
+ Query q = Query::EventPhaseIs(TRACE_EVENT_PHASE_ENTER_CONTEXT) ||
+ Query::EventPhaseIs(TRACE_EVENT_PHASE_LEAVE_CONTEXT);
+ analyzer->FindEvents(q, &events);
+
+ EXPECT_EQ(2u, events.size());
+ EXPECT_EQ(TRACE_EVENT_PHASE_ENTER_CONTEXT, events[0]->phase);
+ EXPECT_EQ(kTestBlameContextCategory, events[0]->category);
+ EXPECT_EQ(kTestBlameContextName, events[0]->name);
+ EXPECT_EQ("0x1234", events[0]->id);
+ EXPECT_EQ(TRACE_EVENT_PHASE_LEAVE_CONTEXT, events[1]->phase);
+ EXPECT_EQ(kTestBlameContextCategory, events[1]->category);
+ EXPECT_EQ(kTestBlameContextName, events[1]->name);
+ EXPECT_EQ("0x1234", events[1]->id);
+}
+
+TEST_F(BlameContextTest, DifferentCategories) {
+ // Ensure there is no cross talk between blame contexts from different
+ // categories.
+ using trace_analyzer::Query;
+ trace_analyzer::Start("*");
+ {
+ TestBlameContext blame_context(0x1234);
+ DisabledTestBlameContext disabled_blame_context(0x5678);
+ blame_context.Initialize();
+ blame_context.Enter();
+ blame_context.Leave();
+ disabled_blame_context.Initialize();
+ disabled_blame_context.Enter();
+ disabled_blame_context.Leave();
+ }
+ auto analyzer = trace_analyzer::Stop();
+
+ trace_analyzer::TraceEventVector events;
+ Query q = Query::EventPhaseIs(TRACE_EVENT_PHASE_ENTER_CONTEXT) ||
+ Query::EventPhaseIs(TRACE_EVENT_PHASE_LEAVE_CONTEXT);
+ analyzer->FindEvents(q, &events);
+
+ // None of the events from the disabled-by-default category should show up.
+ EXPECT_EQ(2u, events.size());
+ EXPECT_EQ(TRACE_EVENT_PHASE_ENTER_CONTEXT, events[0]->phase);
+ EXPECT_EQ(kTestBlameContextCategory, events[0]->category);
+ EXPECT_EQ(kTestBlameContextName, events[0]->name);
+ EXPECT_EQ("0x1234", events[0]->id);
+ EXPECT_EQ(TRACE_EVENT_PHASE_LEAVE_CONTEXT, events[1]->phase);
+ EXPECT_EQ(kTestBlameContextCategory, events[1]->category);
+ EXPECT_EQ(kTestBlameContextName, events[1]->name);
+ EXPECT_EQ("0x1234", events[1]->id);
+}
+
+TEST_F(BlameContextTest, TakeSnapshot) {
+ using trace_analyzer::Query;
+ trace_analyzer::Start("*");
+ {
+ TestBlameContext parent_blame_context(0x5678);
+ TestBlameContext blame_context(0x1234, parent_blame_context);
+ parent_blame_context.Initialize();
+ blame_context.Initialize();
+ blame_context.TakeSnapshot();
+ }
+ auto analyzer = trace_analyzer::Stop();
+
+ trace_analyzer::TraceEventVector events;
+ Query q = Query::EventPhaseIs(TRACE_EVENT_PHASE_SNAPSHOT_OBJECT);
+ analyzer->FindEvents(q, &events);
+
+ // We should have 3 snapshots: one for both calls to Initialize() and one from
+ // the explicit call to TakeSnapshot().
+ EXPECT_EQ(3u, events.size());
+ EXPECT_EQ(kTestBlameContextCategory, events[0]->category);
+ EXPECT_EQ(kTestBlameContextType, events[0]->name);
+ EXPECT_EQ("0x5678", events[0]->id);
+ EXPECT_TRUE(events[0]->HasArg("snapshot"));
+
+ EXPECT_EQ(kTestBlameContextCategory, events[1]->category);
+ EXPECT_EQ(kTestBlameContextType, events[1]->name);
+ EXPECT_EQ("0x1234", events[1]->id);
+ EXPECT_TRUE(events[0]->HasArg("snapshot"));
+
+ EXPECT_EQ(kTestBlameContextCategory, events[2]->category);
+ EXPECT_EQ(kTestBlameContextType, events[2]->name);
+ EXPECT_EQ("0x1234", events[2]->id);
+ EXPECT_TRUE(events[0]->HasArg("snapshot"));
+
+ const char kExpectedSnapshotJson[] =
+ "{"
+ "\"crossStreams\":false,"
+ "\"parent\":{"
+ "\"id_ref\":\"0x5678\","
+ "\"scope\":\"TestBlameContextScope\""
+ "}"
+ "}";
+
+ std::string snapshot_json;
+ JSONWriter::Write(*events[2]->GetKnownArgAsValue("snapshot"), &snapshot_json);
+ EXPECT_EQ(kExpectedSnapshotJson, snapshot_json);
+}
+
+} // namepace
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/category_registry.cc b/base/trace_event/category_registry.cc
new file mode 100644
index 0000000000..e7c14606d6
--- /dev/null
+++ b/base/trace_event/category_registry.cc
@@ -0,0 +1,156 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/category_registry.h"
+
+#include <string.h>
+
+#include <type_traits>
+
+#include "base/atomicops.h"
+#include "base/debug/leak_annotations.h"
+#include "base/logging.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/trace_event/trace_category.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+constexpr size_t kMaxCategories = 200;
+const int kNumBuiltinCategories = 4;
+
+// |g_categories| might end up causing creating dynamic initializers if not POD.
+static_assert(std::is_pod<TraceCategory>::value, "TraceCategory must be POD");
+
+// These entries must be kept consistent with the kCategory* consts below.
+TraceCategory g_categories[kMaxCategories] = {
+ {0, 0, "tracing categories exhausted; must increase kMaxCategories"},
+ {0, 0, "tracing already shutdown"}, // See kCategoryAlreadyShutdown below.
+ {0, 0, "__metadata"}, // See kCategoryMetadata below.
+ {0, 0, "toplevel"}, // Warmup the toplevel category.
+};
+
+base::subtle::AtomicWord g_category_index = kNumBuiltinCategories;
+
+bool IsValidCategoryPtr(const TraceCategory* category) {
+ // If any of these are hit, something has cached a corrupt category pointer.
+ uintptr_t ptr = reinterpret_cast<uintptr_t>(category);
+ return ptr % sizeof(void*) == 0 &&
+ ptr >= reinterpret_cast<uintptr_t>(&g_categories[0]) &&
+ ptr <= reinterpret_cast<uintptr_t>(&g_categories[kMaxCategories - 1]);
+}
+
+} // namespace
+
+// static
+TraceCategory* const CategoryRegistry::kCategoryExhausted = &g_categories[0];
+TraceCategory* const CategoryRegistry::kCategoryAlreadyShutdown =
+ &g_categories[1];
+TraceCategory* const CategoryRegistry::kCategoryMetadata = &g_categories[2];
+
+// static
+void CategoryRegistry::Initialize() {
+ // Trace is enabled or disabled on one thread while other threads are
+ // accessing the enabled flag. We don't care whether edge-case events are
+ // traced or not, so we allow races on the enabled flag to keep the trace
+ // macros fast.
+ for (size_t i = 0; i < kMaxCategories; ++i) {
+ ANNOTATE_BENIGN_RACE(g_categories[i].state_ptr(),
+ "trace_event category enabled");
+ // If this DCHECK is hit in a test it means that ResetForTesting() is not
+ // called and the categories state leaks between test fixtures.
+ DCHECK(!g_categories[i].is_enabled());
+ }
+}
+
+// static
+void CategoryRegistry::ResetForTesting() {
+ // reset_for_testing clears up only the enabled state and filters. The
+ // categories themselves cannot be cleared up because the static pointers
+ // injected by the macros still point to them and cannot be reset.
+ for (size_t i = 0; i < kMaxCategories; ++i)
+ g_categories[i].reset_for_testing();
+}
+
+// static
+TraceCategory* CategoryRegistry::GetCategoryByName(const char* category_name) {
+ DCHECK(!strchr(category_name, '"'))
+ << "Category names may not contain double quote";
+
+ // The g_categories is append only, avoid using a lock for the fast path.
+ size_t category_index = base::subtle::Acquire_Load(&g_category_index);
+
+ // Search for pre-existing category group.
+ for (size_t i = 0; i < category_index; ++i) {
+ if (strcmp(g_categories[i].name(), category_name) == 0) {
+ return &g_categories[i];
+ }
+ }
+ return nullptr;
+}
+
+bool CategoryRegistry::GetOrCreateCategoryLocked(
+ const char* category_name,
+ CategoryInitializerFn category_initializer_fn,
+ TraceCategory** category) {
+ // This is the slow path: the lock is not held in the fastpath
+ // (GetCategoryByName), so more than one thread could have reached here trying
+ // to add the same category.
+ *category = GetCategoryByName(category_name);
+ if (*category)
+ return false;
+
+ // Create a new category.
+ size_t category_index = base::subtle::Acquire_Load(&g_category_index);
+ if (category_index >= kMaxCategories) {
+ NOTREACHED() << "must increase kMaxCategories";
+ *category = kCategoryExhausted;
+ return false;
+ }
+
+ // TODO(primiano): this strdup should be removed. The only documented reason
+ // for it was TraceWatchEvent, which is gone. However, something might have
+ // ended up relying on this. Needs some auditing before removal.
+ const char* category_name_copy = strdup(category_name);
+ ANNOTATE_LEAKING_OBJECT_PTR(category_name_copy);
+
+ *category = &g_categories[category_index];
+ DCHECK(!(*category)->is_valid());
+ DCHECK(!(*category)->is_enabled());
+ (*category)->set_name(category_name_copy);
+ category_initializer_fn(*category);
+
+ // Update the max index now.
+ base::subtle::Release_Store(&g_category_index, category_index + 1);
+ return true;
+}
+
+// static
+const TraceCategory* CategoryRegistry::GetCategoryByStatePtr(
+ const uint8_t* category_state) {
+ const TraceCategory* category = TraceCategory::FromStatePtr(category_state);
+ DCHECK(IsValidCategoryPtr(category));
+ return category;
+}
+
+// static
+bool CategoryRegistry::IsBuiltinCategory(const TraceCategory* category) {
+ DCHECK(IsValidCategoryPtr(category));
+ return category < &g_categories[kNumBuiltinCategories];
+}
+
+// static
+CategoryRegistry::Range CategoryRegistry::GetAllCategories() {
+ // The |g_categories| array is append only. We have to only guarantee to
+ // not return an index to a category which is being initialized by
+ // GetOrCreateCategoryByName().
+ size_t category_index = base::subtle::Acquire_Load(&g_category_index);
+ return CategoryRegistry::Range(&g_categories[0],
+ &g_categories[category_index]);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/category_registry.h b/base/trace_event/category_registry.h
new file mode 100644
index 0000000000..9c08efa3e1
--- /dev/null
+++ b/base/trace_event/category_registry.h
@@ -0,0 +1,93 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_CATEGORY_REGISTRY_H_
+#define BASE_TRACE_EVENT_CATEGORY_REGISTRY_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/base_export.h"
+#include "base/logging.h"
+
+namespace base {
+namespace trace_event {
+
+struct TraceCategory;
+class TraceCategoryTest;
+class TraceLog;
+
+// Allows fast and thread-safe acces to the state of all tracing categories.
+// All the methods in this class can be concurrently called on multiple threads,
+// unless otherwise noted (e.g., GetOrCreateCategoryLocked).
+// The reason why this is a fully static class with global state is to allow to
+// statically define known categories as global linker-initialized structs,
+// without requiring static initializers.
+class BASE_EXPORT CategoryRegistry {
+ public:
+ // Allows for-each iterations over a slice of the categories array.
+ class Range {
+ public:
+ Range(TraceCategory* begin, TraceCategory* end) : begin_(begin), end_(end) {
+ DCHECK_LE(begin, end);
+ }
+ TraceCategory* begin() const { return begin_; }
+ TraceCategory* end() const { return end_; }
+
+ private:
+ TraceCategory* const begin_;
+ TraceCategory* const end_;
+ };
+
+ // Known categories.
+ static TraceCategory* const kCategoryExhausted;
+ static TraceCategory* const kCategoryMetadata;
+ static TraceCategory* const kCategoryAlreadyShutdown;
+
+ // Returns a category entry from the Category.state_ptr() pointer.
+ // TODO(primiano): trace macros should just keep a pointer to the entire
+ // TraceCategory, not just the enabled state pointer. That would remove the
+ // need for this function and make everything cleaner at no extra cost (as
+ // long as the |state_| is the first field of the struct, which can be
+ // guaranteed via static_assert, see TraceCategory ctor).
+ static const TraceCategory* GetCategoryByStatePtr(
+ const uint8_t* category_state);
+
+ // Returns a category from its name or nullptr if not found.
+ // The output |category| argument is an undefinitely lived pointer to the
+ // TraceCategory owned by the registry. TRACE_EVENTx macros will cache this
+ // pointer and use it for checks in their fast-paths.
+ static TraceCategory* GetCategoryByName(const char* category_name);
+
+ static bool IsBuiltinCategory(const TraceCategory*);
+
+ private:
+ friend class TraceCategoryTest;
+ friend class TraceLog;
+ using CategoryInitializerFn = void (*)(TraceCategory*);
+
+ // Only for debugging/testing purposes, is a no-op on release builds.
+ static void Initialize();
+
+ // Resets the state of all categories, to clear up the state between tests.
+ static void ResetForTesting();
+
+ // Used to get/create a category in the slow-path. If the category exists
+ // already, this has the same effect of GetCategoryByName and returns false.
+ // If not, a new category is created and the CategoryInitializerFn is invoked
+ // before retuning true. The caller must guarantee serialization: either call
+ // this method from a single thread or hold a lock when calling this.
+ static bool GetOrCreateCategoryLocked(const char* category_name,
+ CategoryInitializerFn,
+ TraceCategory**);
+
+ // Allows to iterate over the valid categories in a for-each loop.
+ // This includes builtin categories such as __metadata.
+ static Range GetAllCategories();
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_CATEGORY_REGISTRY_H_
diff --git a/base/trace_event/cfi_backtrace_android.cc b/base/trace_event/cfi_backtrace_android.cc
new file mode 100644
index 0000000000..8fd8b955dc
--- /dev/null
+++ b/base/trace_event/cfi_backtrace_android.cc
@@ -0,0 +1,314 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/cfi_backtrace_android.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include "base/android/apk_assets.h"
+
+#if !defined(ARCH_CPU_ARMEL)
+#error This file should not be built for this architecture.
+#endif
+
+/*
+Basics of unwinding:
+For each instruction in a function we need to know what is the offset of SP
+(Stack Pointer) to reach the previous function's stack frame. To know which
+function is being invoked, we need the return address of the next function. The
+CFI information for an instruction is made up of 2 offsets, CFA (Call Frame
+Address) offset and RA (Return Address) offset. The CFA offset is the change in
+SP made by the function till the current instruction. This depends on amount of
+memory allocated on stack by the function plus some registers that the function
+stores that needs to be restored at the end of function. So, at each instruction
+the CFA offset tells the offset from original SP before the function call. The
+RA offset tells us the offset from the previous SP into the current function
+where the return address is stored.
+
+The unwind table file has 2 tables UNW_INDEX and UNW_DATA, inspired from ARM
+EHABI format. The first table contains function addresses and an index into the
+UNW_DATA table. The second table contains one or more rows for the function
+unwind information.
+
+UNW_INDEX contains two columns of N rows each, where N is the number of
+functions.
+ 1. First column 4 byte rows of all the function start address as offset from
+ start of the binary, in sorted order.
+ 2. For each function addr, the second column contains 2 byte indices in order.
+ The indices are offsets (in count of 2 bytes) of the CFI data from start of
+ UNW_DATA.
+The last entry in the table always contains CANT_UNWIND index to specify the
+end address of the last function.
+
+UNW_DATA contains data of all the functions. Each function data contains N rows.
+The data found at the address pointed from UNW_INDEX will be:
+ 2 bytes: N - number of rows that belong to current function.
+ N * 4 bytes: N rows of data. 16 bits : Address offset from function start.
+ 14 bits : CFA offset / 4.
+ 2 bits : RA offset / 4.
+If the RA offset of a row is 0, then use the offset of the previous rows in the
+same function.
+TODO(ssid): Make sure RA offset is always present.
+
+See extract_unwind_tables.py for details about how this data is extracted from
+breakpad symbol files.
+*/
+
+extern "C" {
+extern char __executable_start;
+extern char _etext;
+}
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+// The value of index when the function does not have unwind information.
+constexpr uint32_t kCantUnwind = 0xFFFF;
+
+// The mask on the CFI row data that is used to get the high 14 bits and
+// multiply it by 4 to get CFA offset. Since the last 2 bits are masked out, a
+// shift is not necessary.
+constexpr uint16_t kCFAMask = 0xfffc;
+
+// The mask on the CFI row data that is used to get the low 2 bits and multiply
+// it by 4 to get the RA offset.
+constexpr uint16_t kRAMask = 0x3;
+constexpr uint16_t kRAShift = 2;
+
+// The code in this file assumes we are running in 32-bit builds since all the
+// addresses in the unwind table are specified in 32 bits.
+static_assert(sizeof(uintptr_t) == 4,
+ "The unwind table format is only valid for 32 bit builds.");
+
+// The CFI data in UNW_DATA table starts with number of rows (N) and then
+// followed by N rows of 4 bytes long. The CFIUnwindDataRow represents a single
+// row of CFI data of a function in the table. Since we cast the memory at the
+// address after the address of number of rows, into an array of
+// CFIUnwindDataRow, the size of the struct should be 4 bytes and the order of
+// the members is fixed according to the given format. The first 2 bytes tell
+// the address of function and last 2 bytes give the CFI data for the offset.
+struct CFIUnwindDataRow {
+ // The address of the instruction in terms of offset from the start of the
+ // function.
+ uint16_t addr_offset;
+ // Represents the CFA and RA offsets to get information about next stack
+ // frame. This is the CFI data at the point before executing the instruction
+ // at |addr_offset| from the start of the function.
+ uint16_t cfi_data;
+
+ // Return the RA offset for the current unwind row.
+ size_t ra_offset() const { return (cfi_data & kRAMask) << kRAShift; }
+
+ // Returns the CFA offset for the current unwind row.
+ size_t cfa_offset() const { return cfi_data & kCFAMask; }
+};
+
+static_assert(
+ sizeof(CFIUnwindDataRow) == 4,
+ "The CFIUnwindDataRow struct must be exactly 4 bytes for searching.");
+
+} // namespace
+
+// static
+CFIBacktraceAndroid* CFIBacktraceAndroid::GetInitializedInstance() {
+ static CFIBacktraceAndroid* instance = new CFIBacktraceAndroid();
+ return instance;
+}
+
+CFIBacktraceAndroid::CFIBacktraceAndroid()
+ : thread_local_cfi_cache_(
+ [](void* ptr) { delete static_cast<CFICache*>(ptr); }) {
+ Initialize();
+}
+
+CFIBacktraceAndroid::~CFIBacktraceAndroid() {}
+
+void CFIBacktraceAndroid::Initialize() {
+ // The address |_etext| gives the end of the .text section in the binary. This
+ // value is more accurate than parsing the memory map since the mapped
+ // regions are usualy larger than the .text section.
+ executable_end_addr_ = reinterpret_cast<uintptr_t>(&_etext);
+ // The address of |__executable_start| gives the start address of the
+ // executable. This value is used to find the offset address of the
+ // instruction in binary from PC.
+ executable_start_addr_ = reinterpret_cast<uintptr_t>(&__executable_start);
+
+ // This file name is defined by extract_unwind_tables.gni.
+ static constexpr char kCfiFileName[] = "assets/unwind_cfi_32";
+ MemoryMappedFile::Region cfi_region;
+ int fd = base::android::OpenApkAsset(kCfiFileName, &cfi_region);
+ if (fd < 0)
+ return;
+ cfi_mmap_ = std::make_unique<MemoryMappedFile>();
+ // The CFI region starts at |cfi_region.offset|.
+ if (!cfi_mmap_->Initialize(base::File(fd), cfi_region))
+ return;
+
+ ParseCFITables();
+ can_unwind_stack_frames_ = true;
+}
+
+void CFIBacktraceAndroid::ParseCFITables() {
+ // The first 4 bytes in the file is the size of UNW_INDEX table.
+ static constexpr size_t kUnwIndexRowSize =
+ sizeof(*unw_index_function_col_) + sizeof(*unw_index_indices_col_);
+ size_t unw_index_size = 0;
+ memcpy(&unw_index_size, cfi_mmap_->data(), sizeof(unw_index_size));
+ DCHECK_EQ(0u, unw_index_size % kUnwIndexRowSize);
+ // UNW_INDEX table starts after 4 bytes.
+ unw_index_function_col_ =
+ reinterpret_cast<const uintptr_t*>(cfi_mmap_->data()) + 1;
+ unw_index_row_count_ = unw_index_size / kUnwIndexRowSize;
+ unw_index_indices_col_ = reinterpret_cast<const uint16_t*>(
+ unw_index_function_col_ + unw_index_row_count_);
+
+ // The UNW_DATA table data is right after the end of UNW_INDEX table.
+ // Interpret the UNW_DATA table as an array of 2 byte numbers since the
+ // indexes we have from the UNW_INDEX table are in terms of 2 bytes.
+ unw_data_start_addr_ = unw_index_indices_col_ + unw_index_row_count_;
+}
+
+size_t CFIBacktraceAndroid::Unwind(const void** out_trace, size_t max_depth) {
+ // This function walks the stack using the call frame information to find the
+ // return addresses of all the functions that belong to current binary in call
+ // stack. For each function the CFI table defines the offset of the previous
+ // call frame and offset where the return address is stored.
+ if (!can_unwind_stack_frames())
+ return 0;
+
+ // Get the current register state. This register state can be taken at any
+ // point in the function and the unwind information would be for this point.
+ // Define local variables before trying to get the current PC and SP to make
+ // sure the register state obtained is consistent with each other.
+ uintptr_t pc = 0, sp = 0;
+ asm volatile("mov %0, pc" : "=r"(pc));
+ asm volatile("mov %0, sp" : "=r"(sp));
+
+ // We can only unwind as long as the pc is within the chrome.so.
+ size_t depth = 0;
+ while (pc > executable_start_addr_ && pc <= executable_end_addr_ &&
+ depth < max_depth) {
+ out_trace[depth++] = reinterpret_cast<void*>(pc);
+ // The offset of function from the start of the chrome.so binary:
+ uintptr_t func_addr = pc - executable_start_addr_;
+ CFIRow cfi{};
+ if (!FindCFIRowForPC(func_addr, &cfi))
+ break;
+
+ // The rules for unwinding using the CFI information are:
+ // SP_prev = SP_cur + cfa_offset and
+ // PC_prev = * (SP_prev - ra_offset).
+ sp = sp + cfi.cfa_offset;
+ memcpy(&pc, reinterpret_cast<uintptr_t*>(sp - cfi.ra_offset),
+ sizeof(uintptr_t));
+ }
+ return depth;
+}
+
+bool CFIBacktraceAndroid::FindCFIRowForPC(uintptr_t func_addr,
+ CFIBacktraceAndroid::CFIRow* cfi) {
+ auto* cache = GetThreadLocalCFICache();
+ *cfi = {0};
+ if (cache->Find(func_addr, cfi))
+ return true;
+
+ // Consider each column of UNW_INDEX table as arrays of uintptr_t (function
+ // addresses) and uint16_t (indices). Define start and end iterator on the
+ // first column array (addresses) and use std::lower_bound() to binary search
+ // on this array to find the required function address.
+ static const uintptr_t* const unw_index_fn_end =
+ unw_index_function_col_ + unw_index_row_count_;
+ const uintptr_t* found =
+ std::lower_bound(unw_index_function_col_, unw_index_fn_end, func_addr);
+
+ // If found is start, then the given function is not in the table. If the
+ // given pc is start of a function then we cannot unwind.
+ if (found == unw_index_function_col_ || *found == func_addr)
+ return false;
+
+ // std::lower_bound() returns the iter that corresponds to the first address
+ // that is greater than the given address. So, the required iter is always one
+ // less than the value returned by std::lower_bound().
+ --found;
+ uintptr_t func_start_addr = *found;
+ size_t row_num = found - unw_index_function_col_;
+ uint16_t index = unw_index_indices_col_[row_num];
+ DCHECK_LE(func_start_addr, func_addr);
+ // If the index is CANT_UNWIND then we do not have unwind infomation for the
+ // function.
+ if (index == kCantUnwind)
+ return false;
+
+ // The unwind data for the current function is at an offsset of the index
+ // found in UNW_INDEX table.
+ const uint16_t* unwind_data = unw_data_start_addr_ + index;
+ // The value of first 2 bytes is the CFI data row count for the function.
+ uint16_t row_count = 0;
+ memcpy(&row_count, unwind_data, sizeof(row_count));
+ // And the actual CFI rows start after 2 bytes from the |unwind_data|. Cast
+ // the data into an array of CFIUnwindDataRow since the struct is designed to
+ // represent each row. We should be careful to read only |row_count| number of
+ // elements in the array.
+ const CFIUnwindDataRow* function_data =
+ reinterpret_cast<const CFIUnwindDataRow*>(unwind_data + 1);
+
+ // Iterate through the CFI rows of the function to find the row that gives
+ // offset for the given instruction address.
+ CFIUnwindDataRow cfi_row = {0, 0};
+ uint16_t ra_offset = 0;
+ for (uint16_t i = 0; i < row_count; ++i) {
+ CFIUnwindDataRow row;
+ memcpy(&row, function_data + i, sizeof(CFIUnwindDataRow));
+ // The return address of the function is the instruction that is not yet
+ // been executed. The cfi row specifies the unwind info before executing the
+ // given instruction. If the given address is equal to the instruction
+ // offset, then use the current row. Or use the row with highest address
+ // less than the given address.
+ if (row.addr_offset + func_start_addr > func_addr)
+ break;
+
+ cfi_row = row;
+ // The ra offset of the last specified row should be used, if unspecified.
+ // So, keep updating the RA offset till we reach the correct CFI row.
+ // TODO(ssid): This should be fixed in the format and we should always
+ // output ra offset.
+ if (cfi_row.ra_offset())
+ ra_offset = cfi_row.ra_offset();
+ }
+ DCHECK_NE(0u, cfi_row.addr_offset);
+ *cfi = {cfi_row.cfa_offset(), ra_offset};
+ DCHECK(cfi->cfa_offset);
+ DCHECK(cfi->ra_offset);
+
+ // safe to update since the cache is thread local.
+ cache->Add(func_addr, *cfi);
+ return true;
+}
+
+CFIBacktraceAndroid::CFICache* CFIBacktraceAndroid::GetThreadLocalCFICache() {
+ auto* cache = static_cast<CFICache*>(thread_local_cfi_cache_.Get());
+ if (!cache) {
+ cache = new CFICache();
+ thread_local_cfi_cache_.Set(cache);
+ }
+ return cache;
+}
+
+void CFIBacktraceAndroid::CFICache::Add(uintptr_t address, CFIRow cfi) {
+ cache_[address % kLimit] = {address, cfi};
+}
+
+bool CFIBacktraceAndroid::CFICache::Find(uintptr_t address, CFIRow* cfi) {
+ if (cache_[address % kLimit].address == address) {
+ *cfi = cache_[address % kLimit].cfi;
+ return true;
+ }
+ return false;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/cfi_backtrace_android.h b/base/trace_event/cfi_backtrace_android.h
new file mode 100644
index 0000000000..0c513321c8
--- /dev/null
+++ b/base/trace_event/cfi_backtrace_android.h
@@ -0,0 +1,157 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_CFI_BACKTRACE_ANDROID_H_
+#define BASE_TRACE_EVENT_CFI_BACKTRACE_ANDROID_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/base_export.h"
+#include "base/debug/debugging_buildflags.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/gtest_prod_util.h"
+#include "base/threading/thread_local_storage.h"
+
+namespace base {
+namespace trace_event {
+
+// This class is used to unwind stack frames in the current thread. The unwind
+// information (dwarf debug info) is stripped from the chrome binary and we do
+// not build with exception tables (ARM EHABI) in release builds. So, we use a
+// custom unwind table which is generated and added to specific android builds,
+// when add_unwind_tables_in_apk build option is specified. This unwind table
+// contains information for unwinding stack frames when the functions calls are
+// from lib[mono]chrome.so. The file is added as an asset to the apk and the
+// table is used to unwind stack frames for profiling. This class implements
+// methods to read and parse the unwind table and unwind stack frames using this
+// data.
+class BASE_EXPORT CFIBacktraceAndroid {
+ public:
+ // Creates and initializes by memory mapping the unwind tables from apk assets
+ // on first call.
+ static CFIBacktraceAndroid* GetInitializedInstance();
+
+ // Returns true if stack unwinding is possible using CFI unwind tables in apk.
+ // There is no need to check this before each unwind call. Will always return
+ // the same value based on CFI tables being present in the binary.
+ bool can_unwind_stack_frames() const { return can_unwind_stack_frames_; }
+
+ // Returns the program counters by unwinding stack in the current thread in
+ // order of latest call frame first. Unwinding works only if
+ // can_unwind_stack_frames() returns true. This function allocates memory from
+ // heap for caches. For each stack frame, this method searches through the
+ // unwind table mapped in memory to find the unwind information for function
+ // and walks the stack to find all the return address. This only works until
+ // the last function call from the chrome.so. We do not have unwind
+ // information to unwind beyond any frame outside of chrome.so. Calls to
+ // Unwind() are thread safe and lock free, once Initialize() returns success.
+ size_t Unwind(const void** out_trace, size_t max_depth);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(CFIBacktraceAndroidTest, TestCFICache);
+ FRIEND_TEST_ALL_PREFIXES(CFIBacktraceAndroidTest, TestFindCFIRow);
+ FRIEND_TEST_ALL_PREFIXES(CFIBacktraceAndroidTest, TestUnwinding);
+
+ // The CFI information that correspond to an instruction.
+ struct CFIRow {
+ bool operator==(const CFIBacktraceAndroid::CFIRow& o) const {
+ return cfa_offset == o.cfa_offset && ra_offset == o.ra_offset;
+ }
+
+ // The offset of the call frame address of previous function from the
+ // current stack pointer. Rule for unwinding SP: SP_prev = SP_cur +
+ // cfa_offset.
+ uint16_t cfa_offset = 0;
+ // The offset of location of return address from the previous call frame
+ // address. Rule for unwinding PC: PC_prev = * (SP_prev - ra_offset).
+ uint16_t ra_offset = 0;
+ };
+
+ // A simple cache that stores entries in table using prime modulo hashing.
+ // This cache with 500 entries already gives us 95% hit rate, and fits in a
+ // single system page (usually 4KiB). Using a thread local cache for each
+ // thread gives us 30% improvements on performance of heap profiling.
+ class CFICache {
+ public:
+ // Add new item to the cache. It replaces an existing item with same hash.
+ // Constant time operation.
+ void Add(uintptr_t address, CFIRow cfi);
+
+ // Finds the given address and fills |cfi| with the info for the address.
+ // returns true if found, otherwise false. Assumes |address| is never 0.
+ bool Find(uintptr_t address, CFIRow* cfi);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(CFIBacktraceAndroidTest, TestCFICache);
+
+ // Size is the highest prime which fits the cache in a single system page,
+ // usually 4KiB. A prime is chosen to make sure addresses are hashed evenly.
+ static const int kLimit = 509;
+
+ struct AddrAndCFI {
+ uintptr_t address;
+ CFIRow cfi;
+ };
+ AddrAndCFI cache_[kLimit] = {};
+ };
+
+ static_assert(sizeof(CFIBacktraceAndroid::CFICache) < 4096,
+ "The cache does not fit in a single page.");
+
+ CFIBacktraceAndroid();
+ ~CFIBacktraceAndroid();
+
+ // Initializes unwind tables using the CFI asset file in the apk if present.
+ // Also stores the limits of mapped region of the lib[mono]chrome.so binary,
+ // since the unwind is only feasible for addresses within the .so file. Once
+ // initialized, the memory map of the unwind table is never cleared since we
+ // cannot guarantee that all the threads are done using the memory map when
+ // heap profiling is turned off. But since we keep the memory map is clean,
+ // the system can choose to evict the unused pages when needed. This would
+ // still reduce the total amount of address space available in process.
+ void Initialize();
+
+ // Finds the UNW_INDEX and UNW_DATA tables in from the CFI file memory map.
+ void ParseCFITables();
+
+ // Finds the CFI row for the given |func_addr| in terms of offset from
+ // the start of the current binary.
+ bool FindCFIRowForPC(uintptr_t func_addr, CFIRow* out);
+
+ CFICache* GetThreadLocalCFICache();
+
+ // Details about the memory mapped region which contains the libchrome.so
+ // library file.
+ uintptr_t executable_start_addr_ = 0;
+ uintptr_t executable_end_addr_ = 0;
+
+ // The start address of the memory mapped unwind table asset file. Unique ptr
+ // because it is replaced in tests.
+ std::unique_ptr<MemoryMappedFile> cfi_mmap_;
+
+ // The UNW_INDEX table: Start address of the function address column. The
+ // memory segment corresponding to this column is treated as an array of
+ // uintptr_t.
+ const uintptr_t* unw_index_function_col_ = nullptr;
+ // The UNW_INDEX table: Start address of the index column. The memory segment
+ // corresponding to this column is treated as an array of uint16_t.
+ const uint16_t* unw_index_indices_col_ = nullptr;
+ // The number of rows in UNW_INDEX table.
+ size_t unw_index_row_count_ = 0;
+
+ // The start address of UNW_DATA table.
+ const uint16_t* unw_data_start_addr_ = nullptr;
+
+ bool can_unwind_stack_frames_ = false;
+
+ ThreadLocalStorage::Slot thread_local_cfi_cache_;
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_CFI_BACKTRACE_ANDROID_H_
diff --git a/base/trace_event/cfi_backtrace_android_unittest.cc b/base/trace_event/cfi_backtrace_android_unittest.cc
new file mode 100644
index 0000000000..3ad3d33042
--- /dev/null
+++ b/base/trace_event/cfi_backtrace_android_unittest.cc
@@ -0,0 +1,197 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/cfi_backtrace_android.h"
+
+#include "base/files/file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+void* GetPC() {
+ return __builtin_return_address(0);
+}
+
+} // namespace
+
+TEST(CFIBacktraceAndroidTest, TestUnwinding) {
+ auto* unwinder = CFIBacktraceAndroid::GetInitializedInstance();
+ EXPECT_TRUE(unwinder->can_unwind_stack_frames());
+ EXPECT_GT(unwinder->executable_start_addr_, 0u);
+ EXPECT_GT(unwinder->executable_end_addr_, unwinder->executable_start_addr_);
+ EXPECT_GT(unwinder->cfi_mmap_->length(), 0u);
+
+ const size_t kMaxFrames = 100;
+ const void* frames[kMaxFrames];
+ size_t unwind_count = unwinder->Unwind(frames, kMaxFrames);
+ // Expect at least 2 frames in the result.
+ ASSERT_GT(unwind_count, 2u);
+ EXPECT_LE(unwind_count, kMaxFrames);
+
+ const size_t kMaxCurrentFuncCodeSize = 50;
+ const uintptr_t current_pc = reinterpret_cast<uintptr_t>(GetPC());
+ const uintptr_t actual_frame = reinterpret_cast<uintptr_t>(frames[2]);
+ EXPECT_NEAR(current_pc, actual_frame, kMaxCurrentFuncCodeSize);
+
+ for (size_t i = 0; i < unwind_count; ++i) {
+ EXPECT_GT(reinterpret_cast<uintptr_t>(frames[i]),
+ unwinder->executable_start_addr_);
+ EXPECT_LT(reinterpret_cast<uintptr_t>(frames[i]),
+ unwinder->executable_end_addr_);
+ }
+}
+
+// Flaky: https://bugs.chromium.org/p/chromium/issues/detail?id=829555
+TEST(CFIBacktraceAndroidTest, DISABLED_TestFindCFIRow) {
+ auto* unwinder = CFIBacktraceAndroid::GetInitializedInstance();
+ /* Input is generated from the CFI file:
+ STACK CFI INIT 1000 500
+ STACK CFI 1002 .cfa: sp 272 + .ra: .cfa -4 + ^ r4: .cfa -16 +
+ STACK CFI 1008 .cfa: sp 544 + .r1: .cfa -0 + ^ r4: .cfa -16 + ^
+ STACK CFI 1040 .cfa: sp 816 + .r1: .cfa -0 + ^ r4: .cfa -16 + ^
+ STACK CFI 1050 .cfa: sp 816 + .ra: .cfa -8 + ^ r4: .cfa -16 + ^
+ STACK CFI 1080 .cfa: sp 544 + .r1: .cfa -0 + ^ r4: .cfa -16 + ^
+
+ STACK CFI INIT 2000 22
+ STACK CFI 2004 .cfa: sp 16 + .ra: .cfa -12 + ^ r4: .cfa -16 + ^
+ STACK CFI 2008 .cfa: sp 16 + .ra: .cfa -12 + ^ r4: .cfa -16 + ^
+
+ STACK CFI INIT 2024 100
+ STACK CFI 2030 .cfa: sp 48 + .ra: .cfa -12 + ^ r4: .cfa -16 + ^
+ STACK CFI 2100 .cfa: sp 64 + .r1: .cfa -0 + ^ r4: .cfa -16 + ^
+
+ STACK CFI INIT 2200 10
+ STACK CFI 2204 .cfa: sp 44 + .ra: .cfa -8 + ^ r4: .cfa -16 + ^
+ */
+ uint16_t input[] = {// UNW_INDEX size
+ 0x2A,
+
+ // UNW_INDEX address column (4 byte rows).
+ 0x0, 0x1000, 0x0, 0x1502, 0x0, 0x2000, 0x0, 0x2024, 0x0,
+ 0x2126, 0x0, 0x2200, 0x0, 0x2212, 0x0,
+
+ // UNW_INDEX index column (2 byte rows).
+ 0x0, 0xffff, 0xb, 0x10, 0xffff, 0x15, 0xffff,
+
+ // UNW_DATA table.
+ 0x5, 0x2, 0x111, 0x8, 0x220, 0x40, 0x330, 0x50, 0x332,
+ 0x80, 0x220, 0x2, 0x4, 0x13, 0x8, 0x13, 0x2, 0xc, 0x33,
+ 0xdc, 0x40, 0x1, 0x4, 0x2e};
+ FilePath temp_path;
+ CreateTemporaryFile(&temp_path);
+ EXPECT_EQ(
+ static_cast<int>(sizeof(input)),
+ WriteFile(temp_path, reinterpret_cast<char*>(input), sizeof(input)));
+
+ unwinder->cfi_mmap_.reset(new MemoryMappedFile());
+ unwinder->cfi_mmap_->Initialize(temp_path);
+ unwinder->ParseCFITables();
+
+ CFIBacktraceAndroid::CFIRow cfi_row = {0};
+ EXPECT_FALSE(unwinder->FindCFIRowForPC(0x01, &cfi_row));
+ EXPECT_FALSE(unwinder->FindCFIRowForPC(0x100, &cfi_row));
+ EXPECT_FALSE(unwinder->FindCFIRowForPC(0x1502, &cfi_row));
+ EXPECT_FALSE(unwinder->FindCFIRowForPC(0x3000, &cfi_row));
+ EXPECT_FALSE(unwinder->FindCFIRowForPC(0x2024, &cfi_row));
+ EXPECT_FALSE(unwinder->FindCFIRowForPC(0x2212, &cfi_row));
+
+ const CFIBacktraceAndroid::CFIRow kRow1 = {0x110, 0x4};
+ const CFIBacktraceAndroid::CFIRow kRow2 = {0x220, 0x4};
+ const CFIBacktraceAndroid::CFIRow kRow3 = {0x220, 0x8};
+ const CFIBacktraceAndroid::CFIRow kRow4 = {0x30, 0xc};
+ const CFIBacktraceAndroid::CFIRow kRow5 = {0x2c, 0x8};
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1002, &cfi_row));
+ EXPECT_EQ(kRow1, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1003, &cfi_row));
+ EXPECT_EQ(kRow1, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1008, &cfi_row));
+ EXPECT_EQ(kRow2, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1009, &cfi_row));
+ EXPECT_EQ(kRow2, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1039, &cfi_row));
+ EXPECT_EQ(kRow2, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1080, &cfi_row));
+ EXPECT_EQ(kRow3, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1100, &cfi_row));
+ EXPECT_EQ(kRow3, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x2050, &cfi_row));
+ EXPECT_EQ(kRow4, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x2208, &cfi_row));
+ EXPECT_EQ(kRow5, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x2210, &cfi_row));
+ EXPECT_EQ(kRow5, cfi_row);
+
+ // Test if cache is used on the future calls to Find, all addresses should
+ // have different hash. Resetting the memory map to make sure it is never
+ // accessed in Find().
+ unwinder->cfi_mmap_.reset(new MemoryMappedFile());
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1002, &cfi_row));
+ EXPECT_EQ(kRow1, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1003, &cfi_row));
+ EXPECT_EQ(kRow1, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1008, &cfi_row));
+ EXPECT_EQ(kRow2, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1009, &cfi_row));
+ EXPECT_EQ(kRow2, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1039, &cfi_row));
+ EXPECT_EQ(kRow2, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1080, &cfi_row));
+ EXPECT_EQ(kRow3, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x1100, &cfi_row));
+ EXPECT_EQ(kRow3, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x2050, &cfi_row));
+ EXPECT_EQ(kRow4, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x2208, &cfi_row));
+ EXPECT_EQ(kRow5, cfi_row);
+ EXPECT_TRUE(unwinder->FindCFIRowForPC(0x2210, &cfi_row));
+ EXPECT_EQ(kRow5, cfi_row);
+}
+
+TEST(CFIBacktraceAndroidTest, TestCFICache) {
+ // Use ASSERT macros in this function since they are in loop and using EXPECT
+ // prints too many failures.
+ CFIBacktraceAndroid::CFICache cache;
+ CFIBacktraceAndroid::CFIRow cfi;
+
+ // Empty cache should not find anything.
+ EXPECT_FALSE(cache.Find(1, &cfi));
+
+ // Insert 1 - 2*kLimit
+ for (size_t i = 1; i <= 2 * cache.kLimit; ++i) {
+ CFIBacktraceAndroid::CFIRow val = {4 * i, 2 * i};
+ cache.Add(i, val);
+ ASSERT_TRUE(cache.Find(i, &cfi));
+ ASSERT_EQ(cfi, val);
+
+ // Inserting more than kLimit items evicts |i - cache.kLimit| from cache.
+ if (i >= cache.kLimit)
+ ASSERT_FALSE(cache.Find(i - cache.kLimit, &cfi));
+ }
+ // Cache contains kLimit+1 - 2*kLimit.
+
+ // Check that 1 - kLimit cannot be found.
+ for (size_t i = 1; i <= cache.kLimit; ++i) {
+ ASSERT_FALSE(cache.Find(i, &cfi));
+ }
+
+ // Check if kLimit+1 - 2*kLimit still exists in cache.
+ for (size_t i = cache.kLimit + 1; i <= 2 * cache.kLimit; ++i) {
+ CFIBacktraceAndroid::CFIRow val = {4 * i, 2 * i};
+ ASSERT_TRUE(cache.Find(i, &cfi));
+ ASSERT_EQ(cfi, val);
+ }
+
+ // Insert 2*kLimit+1, will evict kLimit.
+ cfi = {1, 1};
+ cache.Add(2 * cache.kLimit + 1, cfi);
+ EXPECT_TRUE(cache.Find(2 * cache.kLimit + 1, &cfi));
+ EXPECT_FALSE(cache.Find(cache.kLimit + 1, &cfi));
+ // Cache contains kLimit+1 - 2*kLimit.
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/event_name_filter.cc b/base/trace_event/event_name_filter.cc
new file mode 100644
index 0000000000..7bf932e040
--- /dev/null
+++ b/base/trace_event/event_name_filter.cc
@@ -0,0 +1,26 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/event_name_filter.h"
+
+#include "base/trace_event/trace_event_impl.h"
+
+namespace base {
+namespace trace_event {
+
+// static
+const char EventNameFilter::kName[] = "event_whitelist_predicate";
+
+EventNameFilter::EventNameFilter(
+ std::unique_ptr<EventNamesWhitelist> event_names_whitelist)
+ : event_names_whitelist_(std::move(event_names_whitelist)) {}
+
+EventNameFilter::~EventNameFilter() = default;
+
+bool EventNameFilter::FilterTraceEvent(const TraceEvent& trace_event) const {
+ return event_names_whitelist_->count(trace_event.name()) != 0;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/event_name_filter.h b/base/trace_event/event_name_filter.h
new file mode 100644
index 0000000000..19333b3e03
--- /dev/null
+++ b/base/trace_event/event_name_filter.h
@@ -0,0 +1,46 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_
+#define BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/trace_event/trace_event_filter.h"
+
+namespace base {
+namespace trace_event {
+
+class TraceEvent;
+
+// Filters trace events by checking the full name against a whitelist.
+// The current implementation is quite simple and dumb and just uses a
+// hashtable which requires char* to std::string conversion. It could be smarter
+// and use a bloom filter trie. However, today this is used too rarely to
+// justify that cost.
+class BASE_EXPORT EventNameFilter : public TraceEventFilter {
+ public:
+ using EventNamesWhitelist = std::unordered_set<std::string>;
+ static const char kName[];
+
+ EventNameFilter(std::unique_ptr<EventNamesWhitelist>);
+ ~EventNameFilter() override;
+
+ // TraceEventFilter implementation.
+ bool FilterTraceEvent(const TraceEvent&) const override;
+
+ private:
+ std::unique_ptr<const EventNamesWhitelist> event_names_whitelist_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventNameFilter);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_EVENT_NAME_FILTER_H_
diff --git a/base/trace_event/event_name_filter_unittest.cc b/base/trace_event/event_name_filter_unittest.cc
new file mode 100644
index 0000000000..134be0df33
--- /dev/null
+++ b/base/trace_event/event_name_filter_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/event_name_filter.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/trace_event/trace_event_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+const TraceEvent& MakeTraceEvent(const char* name) {
+ static TraceEvent event;
+ event.Reset();
+ event.Initialize(0, TimeTicks(), ThreadTicks(), 'b', nullptr, name, "", 0, 0,
+ 0, nullptr, nullptr, nullptr, nullptr, 0);
+ return event;
+}
+
+TEST(TraceEventNameFilterTest, Whitelist) {
+ auto empty_whitelist =
+ std::make_unique<EventNameFilter::EventNamesWhitelist>();
+ auto filter = std::make_unique<EventNameFilter>(std::move(empty_whitelist));
+
+ // No events should be filtered if the whitelist is empty.
+ EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("foo")));
+
+ auto whitelist = std::make_unique<EventNameFilter::EventNamesWhitelist>();
+ whitelist->insert("foo");
+ whitelist->insert("bar");
+ filter = std::make_unique<EventNameFilter>(std::move(whitelist));
+ EXPECT_TRUE(filter->FilterTraceEvent(MakeTraceEvent("foo")));
+ EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("fooz")));
+ EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("afoo")));
+ EXPECT_TRUE(filter->FilterTraceEvent(MakeTraceEvent("bar")));
+ EXPECT_FALSE(filter->FilterTraceEvent(MakeTraceEvent("foobar")));
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/heap_profiler_allocation_context.cc b/base/trace_event/heap_profiler_allocation_context.cc
new file mode 100644
index 0000000000..bdc3c58bd4
--- /dev/null
+++ b/base/trace_event/heap_profiler_allocation_context.cc
@@ -0,0 +1,88 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/heap_profiler_allocation_context.h"
+
+#include <cstring>
+
+#include "base/hash.h"
+#include "base/macros.h"
+
+namespace base {
+namespace trace_event {
+
+bool operator < (const StackFrame& lhs, const StackFrame& rhs) {
+ return lhs.value < rhs.value;
+}
+
+bool operator == (const StackFrame& lhs, const StackFrame& rhs) {
+ return lhs.value == rhs.value;
+}
+
+bool operator != (const StackFrame& lhs, const StackFrame& rhs) {
+ return !(lhs.value == rhs.value);
+}
+
+Backtrace::Backtrace() = default;
+
+bool operator==(const Backtrace& lhs, const Backtrace& rhs) {
+ if (lhs.frame_count != rhs.frame_count) return false;
+ return std::equal(lhs.frames, lhs.frames + lhs.frame_count, rhs.frames);
+}
+
+bool operator!=(const Backtrace& lhs, const Backtrace& rhs) {
+ return !(lhs == rhs);
+}
+
+AllocationContext::AllocationContext(): type_name(nullptr) {}
+
+AllocationContext::AllocationContext(const Backtrace& backtrace,
+ const char* type_name)
+ : backtrace(backtrace), type_name(type_name) {}
+
+bool operator==(const AllocationContext& lhs, const AllocationContext& rhs) {
+ return (lhs.backtrace == rhs.backtrace) && (lhs.type_name == rhs.type_name);
+}
+
+bool operator!=(const AllocationContext& lhs, const AllocationContext& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace trace_event
+} // namespace base
+
+namespace std {
+
+using base::trace_event::AllocationContext;
+using base::trace_event::Backtrace;
+using base::trace_event::StackFrame;
+
+size_t hash<StackFrame>::operator()(const StackFrame& frame) const {
+ return hash<const void*>()(frame.value);
+}
+
+size_t hash<Backtrace>::operator()(const Backtrace& backtrace) const {
+ const void* values[Backtrace::kMaxFrameCount];
+ for (size_t i = 0; i != backtrace.frame_count; ++i) {
+ values[i] = backtrace.frames[i].value;
+ }
+ return base::PersistentHash(values, backtrace.frame_count * sizeof(*values));
+}
+
+size_t hash<AllocationContext>::operator()(const AllocationContext& ctx) const {
+ size_t backtrace_hash = hash<Backtrace>()(ctx.backtrace);
+
+ // Multiplicative hash from [Knuth 1998]. Works best if |size_t| is 32 bits,
+ // because the magic number is a prime very close to 2^32 / golden ratio, but
+ // will still redistribute keys bijectively on 64-bit architectures because
+ // the magic number is coprime to 2^64.
+ size_t type_hash = reinterpret_cast<size_t>(ctx.type_name) * 2654435761;
+
+ // Multiply one side to break the commutativity of +. Multiplication with a
+ // number coprime to |numeric_limits<size_t>::max() + 1| is bijective so
+ // randomness is preserved.
+ return (backtrace_hash * 3) + type_hash;
+}
+
+} // namespace std
diff --git a/base/trace_event/heap_profiler_allocation_context.h b/base/trace_event/heap_profiler_allocation_context.h
new file mode 100644
index 0000000000..c35663fb10
--- /dev/null
+++ b/base/trace_event/heap_profiler_allocation_context.h
@@ -0,0 +1,132 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_
+#define BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <functional>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace trace_event {
+
+// When heap profiling is enabled, tracing keeps track of the allocation
+// context for each allocation intercepted. It is generated by the
+// |AllocationContextTracker| which keeps stacks of context in TLS.
+// The tracker is initialized lazily.
+
+// The backtrace in the allocation context is a snapshot of the stack. For now,
+// this is the pseudo stack where frames are created by trace event macros. In
+// the future, we might add the option to use the native call stack. In that
+// case, |Backtrace| and |AllocationContextTracker::GetContextSnapshot| might
+// have different implementations that can be selected by a compile time flag.
+
+// The number of stack frames stored in the backtrace is a trade off between
+// memory used for tracing and accuracy. Measurements done on a prototype
+// revealed that:
+//
+// - In 60 percent of the cases, pseudo stack depth <= 7.
+// - In 87 percent of the cases, pseudo stack depth <= 9.
+// - In 95 percent of the cases, pseudo stack depth <= 11.
+//
+// See the design doc (https://goo.gl/4s7v7b) for more details.
+
+// Represents (pseudo) stack frame. Used in Backtrace class below.
+//
+// Conceptually stack frame is identified by its value, and type is used
+// mostly to properly format the value. Value is expected to be a valid
+// pointer from process' address space.
+struct BASE_EXPORT StackFrame {
+ enum class Type {
+ TRACE_EVENT_NAME, // const char* string
+ THREAD_NAME, // const char* thread name
+ PROGRAM_COUNTER, // as returned by stack tracing (e.g. by StackTrace)
+ };
+
+ static StackFrame FromTraceEventName(const char* name) {
+ return {Type::TRACE_EVENT_NAME, name};
+ }
+ static StackFrame FromThreadName(const char* name) {
+ return {Type::THREAD_NAME, name};
+ }
+ static StackFrame FromProgramCounter(const void* pc) {
+ return {Type::PROGRAM_COUNTER, pc};
+ }
+
+ Type type;
+ const void* value;
+};
+
+bool BASE_EXPORT operator < (const StackFrame& lhs, const StackFrame& rhs);
+bool BASE_EXPORT operator == (const StackFrame& lhs, const StackFrame& rhs);
+bool BASE_EXPORT operator != (const StackFrame& lhs, const StackFrame& rhs);
+
+struct BASE_EXPORT Backtrace {
+ Backtrace();
+
+ // If the stack is higher than what can be stored here, the top frames
+ // (the ones further from main()) are stored. Depth of 12 is enough for most
+ // pseudo traces (see above), but not for native traces, where we need more.
+ enum { kMaxFrameCount = 48 };
+ StackFrame frames[kMaxFrameCount];
+ size_t frame_count = 0;
+};
+
+bool BASE_EXPORT operator==(const Backtrace& lhs, const Backtrace& rhs);
+bool BASE_EXPORT operator!=(const Backtrace& lhs, const Backtrace& rhs);
+
+// The |AllocationContext| is context metadata that is kept for every allocation
+// when heap profiling is enabled. To simplify memory management for book-
+// keeping, this struct has a fixed size.
+struct BASE_EXPORT AllocationContext {
+ AllocationContext();
+ AllocationContext(const Backtrace& backtrace, const char* type_name);
+
+ Backtrace backtrace;
+
+ // Type name of the type stored in the allocated memory. A null pointer
+ // indicates "unknown type". Grouping is done by comparing pointers, not by
+ // deep string comparison. In a component build, where a type name can have a
+ // string literal in several dynamic libraries, this may distort grouping.
+ const char* type_name;
+};
+
+bool BASE_EXPORT operator==(const AllocationContext& lhs,
+ const AllocationContext& rhs);
+bool BASE_EXPORT operator!=(const AllocationContext& lhs,
+ const AllocationContext& rhs);
+
+// Struct to store the size and count of the allocations.
+struct AllocationMetrics {
+ size_t size;
+ size_t count;
+};
+
+} // namespace trace_event
+} // namespace base
+
+namespace std {
+
+template <>
+struct BASE_EXPORT hash<base::trace_event::StackFrame> {
+ size_t operator()(const base::trace_event::StackFrame& frame) const;
+};
+
+template <>
+struct BASE_EXPORT hash<base::trace_event::Backtrace> {
+ size_t operator()(const base::trace_event::Backtrace& backtrace) const;
+};
+
+template <>
+struct BASE_EXPORT hash<base::trace_event::AllocationContext> {
+ size_t operator()(const base::trace_event::AllocationContext& context) const;
+};
+
+} // namespace std
+
+#endif // BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_
diff --git a/base/trace_event/heap_profiler_allocation_context_tracker.cc b/base/trace_event/heap_profiler_allocation_context_tracker.cc
new file mode 100644
index 0000000000..556719e9ae
--- /dev/null
+++ b/base/trace_event/heap_profiler_allocation_context_tracker.cc
@@ -0,0 +1,274 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/atomicops.h"
+#include "base/debug/debugging_buildflags.h"
+#include "base/debug/leak_annotations.h"
+#include "base/debug/stack_trace.h"
+#include "base/no_destructor.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_local_storage.h"
+#include "base/trace_event/heap_profiler_allocation_context.h"
+#include "build/build_config.h"
+
+#if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE)
+#include "base/trace_event/cfi_backtrace_android.h"
+#endif
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+#include <sys/prctl.h>
+#endif
+
+namespace base {
+namespace trace_event {
+
+subtle::Atomic32 AllocationContextTracker::capture_mode_ =
+ static_cast<int32_t>(AllocationContextTracker::CaptureMode::DISABLED);
+
+namespace {
+
+const size_t kMaxStackDepth = 128u;
+const size_t kMaxTaskDepth = 16u;
+AllocationContextTracker* const kInitializingSentinel =
+ reinterpret_cast<AllocationContextTracker*>(-1);
+
+// This function is added to the TLS slot to clean up the instance when the
+// thread exits.
+void DestructAllocationContextTracker(void* alloc_ctx_tracker) {
+ delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker);
+}
+
+ThreadLocalStorage::Slot& AllocationContextTrackerTLS() {
+ static NoDestructor<ThreadLocalStorage::Slot> tls_alloc_ctx_tracker(
+ &DestructAllocationContextTracker);
+ return *tls_alloc_ctx_tracker;
+}
+
+// Cannot call ThreadIdNameManager::GetName because it holds a lock and causes
+// deadlock when lock is already held by ThreadIdNameManager before the current
+// allocation. Gets the thread name from kernel if available or returns a string
+// with id. This function intentionally leaks the allocated strings since they
+// are used to tag allocations even after the thread dies.
+const char* GetAndLeakThreadName() {
+ char name[16];
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ // If the thread name is not set, try to get it from prctl. Thread name might
+ // not be set in cases where the thread started before heap profiling was
+ // enabled.
+ int err = prctl(PR_GET_NAME, name);
+ if (!err) {
+ return strdup(name);
+ }
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+ // Use tid if we don't have a thread name.
+ snprintf(name, sizeof(name), "%lu",
+ static_cast<unsigned long>(PlatformThread::CurrentId()));
+ return strdup(name);
+}
+
+} // namespace
+
+// static
+AllocationContextTracker*
+AllocationContextTracker::GetInstanceForCurrentThread() {
+ AllocationContextTracker* tracker = static_cast<AllocationContextTracker*>(
+ AllocationContextTrackerTLS().Get());
+ if (tracker == kInitializingSentinel)
+ return nullptr; // Re-entrancy case.
+
+ if (!tracker) {
+ AllocationContextTrackerTLS().Set(kInitializingSentinel);
+ tracker = new AllocationContextTracker();
+ AllocationContextTrackerTLS().Set(tracker);
+ }
+
+ return tracker;
+}
+
+AllocationContextTracker::AllocationContextTracker()
+ : thread_name_(nullptr), ignore_scope_depth_(0) {
+ tracked_stack_.reserve(kMaxStackDepth);
+ task_contexts_.reserve(kMaxTaskDepth);
+}
+AllocationContextTracker::~AllocationContextTracker() = default;
+
+// static
+void AllocationContextTracker::SetCurrentThreadName(const char* name) {
+ if (name && capture_mode() != CaptureMode::DISABLED) {
+ GetInstanceForCurrentThread()->thread_name_ = name;
+ }
+}
+
+// static
+void AllocationContextTracker::SetCaptureMode(CaptureMode mode) {
+ // Release ordering ensures that when a thread observes |capture_mode_| to
+ // be true through an acquire load, the TLS slot has been initialized.
+ subtle::Release_Store(&capture_mode_, static_cast<int32_t>(mode));
+}
+
+void AllocationContextTracker::PushPseudoStackFrame(
+ AllocationContextTracker::PseudoStackFrame stack_frame) {
+ // Impose a limit on the height to verify that every push is popped, because
+ // in practice the pseudo stack never grows higher than ~20 frames.
+ if (tracked_stack_.size() < kMaxStackDepth) {
+ tracked_stack_.push_back(
+ StackFrame::FromTraceEventName(stack_frame.trace_event_name));
+ } else {
+ NOTREACHED();
+ }
+}
+
+void AllocationContextTracker::PopPseudoStackFrame(
+ AllocationContextTracker::PseudoStackFrame stack_frame) {
+ // Guard for stack underflow. If tracing was started with a TRACE_EVENT in
+ // scope, the frame was never pushed, so it is possible that pop is called
+ // on an empty stack.
+ if (tracked_stack_.empty())
+ return;
+
+ tracked_stack_.pop_back();
+}
+
+void AllocationContextTracker::PushNativeStackFrame(const void* pc) {
+ if (tracked_stack_.size() < kMaxStackDepth)
+ tracked_stack_.push_back(StackFrame::FromProgramCounter(pc));
+ else
+ NOTREACHED();
+}
+
+void AllocationContextTracker::PopNativeStackFrame(const void* pc) {
+ if (tracked_stack_.empty())
+ return;
+
+ DCHECK_EQ(pc, tracked_stack_.back().value);
+ tracked_stack_.pop_back();
+}
+
+void AllocationContextTracker::PushCurrentTaskContext(const char* context) {
+ DCHECK(context);
+ if (task_contexts_.size() < kMaxTaskDepth)
+ task_contexts_.push_back(context);
+ else
+ NOTREACHED();
+}
+
+void AllocationContextTracker::PopCurrentTaskContext(const char* context) {
+ // Guard for stack underflow. If tracing was started with a TRACE_EVENT in
+ // scope, the context was never pushed, so it is possible that pop is called
+ // on an empty stack.
+ if (task_contexts_.empty())
+ return;
+
+ DCHECK_EQ(context, task_contexts_.back())
+ << "Encountered an unmatched context end";
+ task_contexts_.pop_back();
+}
+
+bool AllocationContextTracker::GetContextSnapshot(AllocationContext* ctx) {
+ if (ignore_scope_depth_)
+ return false;
+
+ CaptureMode mode = static_cast<CaptureMode>(
+ subtle::NoBarrier_Load(&capture_mode_));
+
+ auto* backtrace = std::begin(ctx->backtrace.frames);
+ auto* backtrace_end = std::end(ctx->backtrace.frames);
+
+ if (!thread_name_) {
+ // Ignore the string allocation made by GetAndLeakThreadName to avoid
+ // reentrancy.
+ ignore_scope_depth_++;
+ thread_name_ = GetAndLeakThreadName();
+ ANNOTATE_LEAKING_OBJECT_PTR(thread_name_);
+ DCHECK(thread_name_);
+ ignore_scope_depth_--;
+ }
+
+ // Add the thread name as the first entry in pseudo stack.
+ if (thread_name_) {
+ *backtrace++ = StackFrame::FromThreadName(thread_name_);
+ }
+
+ switch (mode) {
+ case CaptureMode::DISABLED:
+ {
+ break;
+ }
+ case CaptureMode::PSEUDO_STACK:
+ case CaptureMode::MIXED_STACK:
+ {
+ for (const StackFrame& stack_frame : tracked_stack_) {
+ if (backtrace == backtrace_end)
+ break;
+ *backtrace++ = stack_frame;
+ }
+ break;
+ }
+ case CaptureMode::NATIVE_STACK:
+ {
+// Backtrace contract requires us to return bottom frames, i.e.
+// from main() and up. Stack unwinding produces top frames, i.e.
+// from this point and up until main(). We intentionally request
+// kMaxFrameCount + 1 frames, so that we know if there are more frames
+// than our backtrace capacity.
+#if !defined(OS_NACL) // We don't build base/debug/stack_trace.cc for NaCl.
+#if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE)
+ const void* frames[Backtrace::kMaxFrameCount + 1];
+ static_assert(arraysize(frames) >= Backtrace::kMaxFrameCount,
+ "not requesting enough frames to fill Backtrace");
+ size_t frame_count =
+ CFIBacktraceAndroid::GetInitializedInstance()->Unwind(
+ frames, arraysize(frames));
+#elif BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
+ const void* frames[Backtrace::kMaxFrameCount + 1];
+ static_assert(arraysize(frames) >= Backtrace::kMaxFrameCount,
+ "not requesting enough frames to fill Backtrace");
+ size_t frame_count = debug::TraceStackFramePointers(
+ frames, arraysize(frames),
+ 1 /* exclude this function from the trace */);
+#else
+ // Fall-back to capturing the stack with base::debug::StackTrace,
+ // which is likely slower, but more reliable.
+ base::debug::StackTrace stack_trace(Backtrace::kMaxFrameCount + 1);
+ size_t frame_count = 0u;
+ const void* const* frames = stack_trace.Addresses(&frame_count);
+#endif
+
+ // If there are too many frames, keep the ones furthest from main().
+ size_t backtrace_capacity = backtrace_end - backtrace;
+ int32_t starting_frame_index = frame_count;
+ if (frame_count > backtrace_capacity) {
+ starting_frame_index = backtrace_capacity - 1;
+ *backtrace++ = StackFrame::FromTraceEventName("<truncated>");
+ }
+ for (int32_t i = starting_frame_index - 1; i >= 0; --i) {
+ const void* frame = frames[i];
+ *backtrace++ = StackFrame::FromProgramCounter(frame);
+ }
+#endif // !defined(OS_NACL)
+ break;
+ }
+ }
+
+ ctx->backtrace.frame_count = backtrace - std::begin(ctx->backtrace.frames);
+
+ // TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension
+ // (component name) in the heap profiler and not piggy back on the type name.
+ if (!task_contexts_.empty()) {
+ ctx->type_name = task_contexts_.back();
+ } else {
+ ctx->type_name = nullptr;
+ }
+
+ return true;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/heap_profiler_allocation_context_tracker.h b/base/trace_event/heap_profiler_allocation_context_tracker.h
new file mode 100644
index 0000000000..da03b7f6d6
--- /dev/null
+++ b/base/trace_event/heap_profiler_allocation_context_tracker.h
@@ -0,0 +1,140 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_
+#define BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_
+
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/trace_event/heap_profiler_allocation_context.h"
+
+namespace base {
+namespace trace_event {
+
+// AllocationContextTracker is a thread-local object. Its main purpose is to
+// keep track of a pseudo stack of trace events. Chrome has been instrumented
+// with lots of `TRACE_EVENT` macros. These trace events push their name to a
+// thread-local stack when they go into scope, and pop when they go out of
+// scope, if all of the following conditions have been met:
+//
+// * A trace is being recorded.
+// * The category of the event is enabled in the trace config.
+// * Heap profiling is enabled (with the `--enable-heap-profiling` flag).
+//
+// This means that allocations that occur before tracing is started will not
+// have backtrace information in their context.
+//
+// AllocationContextTracker also keeps track of some thread state not related to
+// trace events. See |AllocationContext|.
+//
+// A thread-local instance of the context tracker is initialized lazily when it
+// is first accessed. This might be because a trace event pushed or popped, or
+// because `GetContextSnapshot()` was called when an allocation occurred
+class BASE_EXPORT AllocationContextTracker {
+ public:
+ enum class CaptureMode : int32_t {
+ DISABLED, // Don't capture anything
+ PSEUDO_STACK, // Backtrace has trace events
+ MIXED_STACK, // Backtrace has trace events + from
+ // HeapProfilerScopedStackFrame
+ NATIVE_STACK, // Backtrace has full native backtraces from stack unwinding
+ };
+
+ // Stack frame constructed from trace events in codebase.
+ struct BASE_EXPORT PseudoStackFrame {
+ const char* trace_event_category;
+ const char* trace_event_name;
+
+ bool operator==(const PseudoStackFrame& other) const {
+ return trace_event_category == other.trace_event_category &&
+ trace_event_name == other.trace_event_name;
+ }
+ };
+
+ // Globally sets capturing mode.
+ // TODO(primiano): How to guard against *_STACK -> DISABLED -> *_STACK?
+ static void SetCaptureMode(CaptureMode mode);
+
+ // Returns global capturing mode.
+ inline static CaptureMode capture_mode() {
+ // A little lag after heap profiling is enabled or disabled is fine, it is
+ // more important that the check is as cheap as possible when capturing is
+ // not enabled, so do not issue a memory barrier in the fast path.
+ if (subtle::NoBarrier_Load(&capture_mode_) ==
+ static_cast<int32_t>(CaptureMode::DISABLED))
+ return CaptureMode::DISABLED;
+
+ // In the slow path, an acquire load is required to pair with the release
+ // store in |SetCaptureMode|. This is to ensure that the TLS slot for
+ // the thread-local allocation context tracker has been initialized if
+ // |capture_mode| returns something other than DISABLED.
+ return static_cast<CaptureMode>(subtle::Acquire_Load(&capture_mode_));
+ }
+
+ // Returns the thread-local instance, creating one if necessary. Returns
+ // always a valid instance, unless it is called re-entrantly, in which case
+ // returns nullptr in the nested calls.
+ static AllocationContextTracker* GetInstanceForCurrentThread();
+
+ // Set the thread name in the AllocationContextTracker of the current thread
+ // if capture is enabled.
+ static void SetCurrentThreadName(const char* name);
+
+ // Starts and ends a new ignore scope between which the allocations are
+ // ignored by the heap profiler. GetContextSnapshot() returns false when
+ // allocations are ignored.
+ void begin_ignore_scope() { ignore_scope_depth_++; }
+ void end_ignore_scope() {
+ if (ignore_scope_depth_)
+ ignore_scope_depth_--;
+ }
+
+ // Pushes and pops a frame onto the thread-local pseudo stack.
+ // TODO(ssid): Change PseudoStackFrame to const char*. Only event name is
+ // used.
+ void PushPseudoStackFrame(PseudoStackFrame stack_frame);
+ void PopPseudoStackFrame(PseudoStackFrame stack_frame);
+
+ // Pushes and pops a native stack frame onto thread local tracked stack.
+ void PushNativeStackFrame(const void* pc);
+ void PopNativeStackFrame(const void* pc);
+
+ // Push and pop current task's context. A stack is used to support nested
+ // tasks and the top of the stack will be used in allocation context.
+ void PushCurrentTaskContext(const char* context);
+ void PopCurrentTaskContext(const char* context);
+
+ // Fills a snapshot of the current thread-local context. Doesn't fill and
+ // returns false if allocations are being ignored.
+ bool GetContextSnapshot(AllocationContext* snapshot);
+
+ ~AllocationContextTracker();
+
+ private:
+ AllocationContextTracker();
+
+ static subtle::Atomic32 capture_mode_;
+
+ // The pseudo stack where frames are |TRACE_EVENT| names or inserted PCs.
+ std::vector<StackFrame> tracked_stack_;
+
+ // The thread name is used as the first entry in the pseudo stack.
+ const char* thread_name_;
+
+ // Stack of tasks' contexts. Context serves as a different dimension than
+ // pseudo stack to cluster allocations.
+ std::vector<const char*> task_contexts_;
+
+ uint32_t ignore_scope_depth_;
+
+ DISALLOW_COPY_AND_ASSIGN(AllocationContextTracker);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_
diff --git a/base/trace_event/heap_profiler_allocation_context_tracker_unittest.cc b/base/trace_event/heap_profiler_allocation_context_tracker_unittest.cc
new file mode 100644
index 0000000000..c26149efaa
--- /dev/null
+++ b/base/trace_event/heap_profiler_allocation_context_tracker_unittest.cc
@@ -0,0 +1,350 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <iterator>
+
+#include "base/memory/ref_counted.h"
+#include "base/pending_task.h"
+#include "base/trace_event/heap_profiler.h"
+#include "base/trace_event/heap_profiler_allocation_context.h"
+#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/trace_event.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+// Define all strings once, because the pseudo stack requires pointer equality,
+// and string interning is unreliable.
+const char kThreadName[] = "TestThread";
+const char kCupcake[] = "Cupcake";
+const char kDonut[] = "Donut";
+const char kEclair[] = "Eclair";
+const char kFroyo[] = "Froyo";
+const char kGingerbread[] = "Gingerbread";
+
+const char kFilteringTraceConfig[] =
+ "{"
+ " \"event_filters\": ["
+ " {"
+ " \"excluded_categories\": [],"
+ " \"filter_args\": {},"
+ " \"filter_predicate\": \"heap_profiler_predicate\","
+ " \"included_categories\": ["
+ " \"*\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("Testing") "\"]"
+ " }"
+ " ]"
+ "}";
+
+// Asserts that the fixed-size array |expected_backtrace| matches the backtrace
+// in |AllocationContextTracker::GetContextSnapshot|.
+template <size_t N>
+void AssertBacktraceEquals(const StackFrame(&expected_backtrace)[N]) {
+ AllocationContext ctx;
+ ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
+ ->GetContextSnapshot(&ctx));
+
+ auto* actual = std::begin(ctx.backtrace.frames);
+ auto* actual_bottom = actual + ctx.backtrace.frame_count;
+ auto expected = std::begin(expected_backtrace);
+ auto expected_bottom = std::end(expected_backtrace);
+
+ // Note that this requires the pointers to be equal, this is not doing a deep
+ // string comparison.
+ for (; actual != actual_bottom && expected != expected_bottom;
+ actual++, expected++)
+ ASSERT_EQ(*expected, *actual);
+
+ // Ensure that the height of the stacks is the same.
+ ASSERT_EQ(actual, actual_bottom);
+ ASSERT_EQ(expected, expected_bottom);
+}
+
+void AssertBacktraceContainsOnlyThreadName() {
+ StackFrame t = StackFrame::FromThreadName(kThreadName);
+ AllocationContext ctx;
+ ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
+ ->GetContextSnapshot(&ctx));
+
+ ASSERT_EQ(1u, ctx.backtrace.frame_count);
+ ASSERT_EQ(t, ctx.backtrace.frames[0]);
+}
+
+class AllocationContextTrackerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ AllocationContextTracker::SetCaptureMode(
+ AllocationContextTracker::CaptureMode::PSEUDO_STACK);
+ // Enabling memory-infra category sets default memory dump config which
+ // includes filters for capturing pseudo stack.
+ TraceConfig config(kFilteringTraceConfig);
+ TraceLog::GetInstance()->SetEnabled(config, TraceLog::FILTERING_MODE);
+ AllocationContextTracker::SetCurrentThreadName(kThreadName);
+ }
+
+ void TearDown() override {
+ AllocationContextTracker::SetCaptureMode(
+ AllocationContextTracker::CaptureMode::DISABLED);
+ TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE);
+ }
+};
+
+// Check that |TRACE_EVENT| macros push and pop to the pseudo stack correctly.
+TEST_F(AllocationContextTrackerTest, PseudoStackScopedTrace) {
+ StackFrame t = StackFrame::FromThreadName(kThreadName);
+ StackFrame c = StackFrame::FromTraceEventName(kCupcake);
+ StackFrame d = StackFrame::FromTraceEventName(kDonut);
+ StackFrame e = StackFrame::FromTraceEventName(kEclair);
+ StackFrame f = StackFrame::FromTraceEventName(kFroyo);
+
+ AssertBacktraceContainsOnlyThreadName();
+
+ {
+ TRACE_EVENT0("Testing", kCupcake);
+ StackFrame frame_c[] = {t, c};
+ AssertBacktraceEquals(frame_c);
+
+ {
+ TRACE_EVENT0("Testing", kDonut);
+ StackFrame frame_cd[] = {t, c, d};
+ AssertBacktraceEquals(frame_cd);
+ }
+
+ AssertBacktraceEquals(frame_c);
+
+ {
+ TRACE_EVENT0("Testing", kEclair);
+ StackFrame frame_ce[] = {t, c, e};
+ AssertBacktraceEquals(frame_ce);
+ }
+
+ {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("NotTesting"), kDonut);
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("Testing"), kCupcake);
+ StackFrame frame_cc[] = {t, c, c};
+ AssertBacktraceEquals(frame_cc);
+ }
+
+ AssertBacktraceEquals(frame_c);
+ }
+
+ AssertBacktraceContainsOnlyThreadName();
+
+ {
+ TRACE_EVENT0("Testing", kFroyo);
+ StackFrame frame_f[] = {t, f};
+ AssertBacktraceEquals(frame_f);
+ }
+
+ AssertBacktraceContainsOnlyThreadName();
+}
+
+// Same as |PseudoStackScopedTrace|, but now test the |TRACE_EVENT_BEGIN| and
+// |TRACE_EVENT_END| macros.
+TEST_F(AllocationContextTrackerTest, PseudoStackBeginEndTrace) {
+ StackFrame t = StackFrame::FromThreadName(kThreadName);
+ StackFrame c = StackFrame::FromTraceEventName(kCupcake);
+ StackFrame d = StackFrame::FromTraceEventName(kDonut);
+ StackFrame e = StackFrame::FromTraceEventName(kEclair);
+ StackFrame f = StackFrame::FromTraceEventName(kFroyo);
+
+ StackFrame frame_c[] = {t, c};
+ StackFrame frame_cd[] = {t, c, d};
+ StackFrame frame_ce[] = {t, c, e};
+ StackFrame frame_f[] = {t, f};
+
+ AssertBacktraceContainsOnlyThreadName();
+
+ TRACE_EVENT_BEGIN0("Testing", kCupcake);
+ AssertBacktraceEquals(frame_c);
+
+ TRACE_EVENT_BEGIN0("Testing", kDonut);
+ AssertBacktraceEquals(frame_cd);
+ TRACE_EVENT_END0("Testing", kDonut);
+
+ AssertBacktraceEquals(frame_c);
+
+ TRACE_EVENT_BEGIN0("Testing", kEclair);
+ AssertBacktraceEquals(frame_ce);
+ TRACE_EVENT_END0("Testing", kEclair);
+
+ AssertBacktraceEquals(frame_c);
+ TRACE_EVENT_END0("Testing", kCupcake);
+
+ AssertBacktraceContainsOnlyThreadName();
+
+ TRACE_EVENT_BEGIN0("Testing", kFroyo);
+ AssertBacktraceEquals(frame_f);
+ TRACE_EVENT_END0("Testing", kFroyo);
+
+ AssertBacktraceContainsOnlyThreadName();
+}
+
+TEST_F(AllocationContextTrackerTest, PseudoStackMixedTrace) {
+ StackFrame t = StackFrame::FromThreadName(kThreadName);
+ StackFrame c = StackFrame::FromTraceEventName(kCupcake);
+ StackFrame d = StackFrame::FromTraceEventName(kDonut);
+ StackFrame e = StackFrame::FromTraceEventName(kEclair);
+ StackFrame f = StackFrame::FromTraceEventName(kFroyo);
+
+ StackFrame frame_c[] = {t, c};
+ StackFrame frame_cd[] = {t, c, d};
+ StackFrame frame_e[] = {t, e};
+ StackFrame frame_ef[] = {t, e, f};
+
+ AssertBacktraceContainsOnlyThreadName();
+
+ TRACE_EVENT_BEGIN0("Testing", kCupcake);
+ AssertBacktraceEquals(frame_c);
+
+ {
+ TRACE_EVENT0("Testing", kDonut);
+ AssertBacktraceEquals(frame_cd);
+ }
+
+ AssertBacktraceEquals(frame_c);
+ TRACE_EVENT_END0("Testing", kCupcake);
+ AssertBacktraceContainsOnlyThreadName();
+
+ {
+ TRACE_EVENT0("Testing", kEclair);
+ AssertBacktraceEquals(frame_e);
+
+ TRACE_EVENT_BEGIN0("Testing", kFroyo);
+ AssertBacktraceEquals(frame_ef);
+ TRACE_EVENT_END0("Testing", kFroyo);
+ AssertBacktraceEquals(frame_e);
+ }
+
+ AssertBacktraceContainsOnlyThreadName();
+}
+
+TEST_F(AllocationContextTrackerTest, MixedStackWithProgramCounter) {
+ StackFrame t = StackFrame::FromThreadName(kThreadName);
+ StackFrame c = StackFrame::FromTraceEventName(kCupcake);
+ StackFrame f = StackFrame::FromTraceEventName(kFroyo);
+ const void* pc1 = reinterpret_cast<void*>(0x1000);
+ const void* pc2 = reinterpret_cast<void*>(0x2000);
+ StackFrame n1 = StackFrame::FromProgramCounter(pc1);
+ StackFrame n2 = StackFrame::FromProgramCounter(pc2);
+
+ StackFrame frame_c[] = {t, c};
+ StackFrame frame_cd[] = {t, c, n1};
+ StackFrame frame_e[] = {t, n2, n1};
+ StackFrame frame_ef[] = {t, n2, n1, f};
+
+ AssertBacktraceContainsOnlyThreadName();
+
+ AllocationContextTracker::SetCaptureMode(
+ AllocationContextTracker::CaptureMode::MIXED_STACK);
+
+ TRACE_EVENT_BEGIN0("Testing", kCupcake);
+ AssertBacktraceEquals(frame_c);
+
+ {
+ TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER e1(pc1);
+ AssertBacktraceEquals(frame_cd);
+ }
+
+ AssertBacktraceEquals(frame_c);
+ TRACE_EVENT_END0("Testing", kCupcake);
+ AssertBacktraceContainsOnlyThreadName();
+
+ {
+ TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER e1(pc2);
+ TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER e2(pc1);
+ AssertBacktraceEquals(frame_e);
+
+ TRACE_EVENT0("Testing", kFroyo);
+ AssertBacktraceEquals(frame_ef);
+ }
+
+ AssertBacktraceContainsOnlyThreadName();
+ AllocationContextTracker::SetCaptureMode(
+ AllocationContextTracker::CaptureMode::DISABLED);
+}
+
+TEST_F(AllocationContextTrackerTest, BacktraceTakesTop) {
+ StackFrame t = StackFrame::FromThreadName(kThreadName);
+ StackFrame c = StackFrame::FromTraceEventName(kCupcake);
+ StackFrame f = StackFrame::FromTraceEventName(kFroyo);
+
+ // Push 11 events onto the pseudo stack.
+ TRACE_EVENT0("Testing", kCupcake);
+ TRACE_EVENT0("Testing", kCupcake);
+ TRACE_EVENT0("Testing", kCupcake);
+
+ TRACE_EVENT0("Testing", kCupcake);
+ TRACE_EVENT0("Testing", kCupcake);
+ TRACE_EVENT0("Testing", kCupcake);
+ TRACE_EVENT0("Testing", kCupcake);
+
+ TRACE_EVENT0("Testing", kCupcake);
+ TRACE_EVENT0("Testing", kDonut);
+ TRACE_EVENT0("Testing", kEclair);
+ TRACE_EVENT0("Testing", kFroyo);
+
+ {
+ TRACE_EVENT0("Testing", kGingerbread);
+ AllocationContext ctx;
+ ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
+ ->GetContextSnapshot(&ctx));
+
+ // The pseudo stack relies on pointer equality, not deep string comparisons.
+ ASSERT_EQ(t, ctx.backtrace.frames[0]);
+ ASSERT_EQ(c, ctx.backtrace.frames[1]);
+ ASSERT_EQ(f, ctx.backtrace.frames[11]);
+ }
+
+ {
+ AllocationContext ctx;
+ ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
+ ->GetContextSnapshot(&ctx));
+ ASSERT_EQ(t, ctx.backtrace.frames[0]);
+ ASSERT_EQ(c, ctx.backtrace.frames[1]);
+ ASSERT_EQ(f, ctx.backtrace.frames[11]);
+ }
+}
+
+TEST_F(AllocationContextTrackerTest, TrackCategoryName) {
+ const char kContext1[] = "context1";
+ const char kContext2[] = "context2";
+ {
+ // The context from the scoped task event should be used as type name.
+ TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event1(kContext1);
+ AllocationContext ctx1;
+ ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
+ ->GetContextSnapshot(&ctx1));
+ ASSERT_EQ(kContext1, ctx1.type_name);
+
+ // In case of nested events, the last event's context should be used.
+ TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event2(kContext2);
+ AllocationContext ctx2;
+ ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
+ ->GetContextSnapshot(&ctx2));
+ ASSERT_EQ(kContext2, ctx2.type_name);
+ }
+
+ // Type should be nullptr without task event.
+ AllocationContext ctx;
+ ASSERT_TRUE(AllocationContextTracker::GetInstanceForCurrentThread()
+ ->GetContextSnapshot(&ctx));
+ ASSERT_FALSE(ctx.type_name);
+}
+
+TEST_F(AllocationContextTrackerTest, IgnoreAllocationTest) {
+ TRACE_EVENT0("Testing", kCupcake);
+ TRACE_EVENT0("Testing", kDonut);
+ HEAP_PROFILER_SCOPED_IGNORE;
+ AllocationContext ctx;
+ ASSERT_FALSE(AllocationContextTracker::GetInstanceForCurrentThread()
+ ->GetContextSnapshot(&ctx));
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/heap_profiler_event_filter.cc b/base/trace_event/heap_profiler_event_filter.cc
new file mode 100644
index 0000000000..937072ca7b
--- /dev/null
+++ b/base/trace_event/heap_profiler_event_filter.cc
@@ -0,0 +1,70 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/heap_profiler_event_filter.h"
+
+#include "base/trace_event/category_registry.h"
+#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
+#include "base/trace_event/trace_category.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_impl.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+inline bool IsPseudoStackEnabled() {
+ // Only PSEUDO_STACK and MIXED_STACK modes require trace events.
+ return AllocationContextTracker::capture_mode() ==
+ AllocationContextTracker::CaptureMode::PSEUDO_STACK ||
+ AllocationContextTracker::capture_mode() ==
+ AllocationContextTracker::CaptureMode::MIXED_STACK;
+}
+
+inline AllocationContextTracker* GetThreadLocalTracker() {
+ return AllocationContextTracker::GetInstanceForCurrentThread();
+}
+
+} // namespace
+
+// static
+const char HeapProfilerEventFilter::kName[] = "heap_profiler_predicate";
+
+HeapProfilerEventFilter::HeapProfilerEventFilter() = default;
+HeapProfilerEventFilter::~HeapProfilerEventFilter() = default;
+
+bool HeapProfilerEventFilter::FilterTraceEvent(
+ const TraceEvent& trace_event) const {
+ if (!IsPseudoStackEnabled())
+ return true;
+
+ // TODO(primiano): Add support for events with copied name crbug.com/581079.
+ if (trace_event.flags() & TRACE_EVENT_FLAG_COPY)
+ return true;
+
+ const auto* category = CategoryRegistry::GetCategoryByStatePtr(
+ trace_event.category_group_enabled());
+ AllocationContextTracker::PseudoStackFrame frame = {category->name(),
+ trace_event.name()};
+ if (trace_event.phase() == TRACE_EVENT_PHASE_BEGIN ||
+ trace_event.phase() == TRACE_EVENT_PHASE_COMPLETE) {
+ GetThreadLocalTracker()->PushPseudoStackFrame(frame);
+ } else if (trace_event.phase() == TRACE_EVENT_PHASE_END) {
+ // The pop for |TRACE_EVENT_PHASE_COMPLETE| events is in |EndEvent|.
+ GetThreadLocalTracker()->PopPseudoStackFrame(frame);
+ }
+ // Do not filter-out any events and always return true. TraceLog adds the
+ // event only if it is enabled for recording.
+ return true;
+}
+
+void HeapProfilerEventFilter::EndEvent(const char* category_name,
+ const char* event_name) const {
+ if (IsPseudoStackEnabled())
+ GetThreadLocalTracker()->PopPseudoStackFrame({category_name, event_name});
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/heap_profiler_event_filter.h b/base/trace_event/heap_profiler_event_filter.h
new file mode 100644
index 0000000000..47368a1b07
--- /dev/null
+++ b/base/trace_event/heap_profiler_event_filter.h
@@ -0,0 +1,40 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_EVENT_FILTER_H_
+#define BASE_TRACE_EVENT_HEAP_PROFILER_EVENT_FILTER_H_
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/trace_event/trace_event_filter.h"
+
+namespace base {
+namespace trace_event {
+
+class TraceEvent;
+
+// This filter unconditionally accepts all events and pushes/pops them from the
+// thread-local AllocationContextTracker instance as they are seen.
+// This is used to cheaply construct the heap profiler pseudo stack without
+// having to actually record all events.
+class BASE_EXPORT HeapProfilerEventFilter : public TraceEventFilter {
+ public:
+ static const char kName[];
+
+ HeapProfilerEventFilter();
+ ~HeapProfilerEventFilter() override;
+
+ // TraceEventFilter implementation.
+ bool FilterTraceEvent(const TraceEvent& trace_event) const override;
+ void EndEvent(const char* category_name,
+ const char* event_name) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HeapProfilerEventFilter);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_HEAP_PROFILER_EVENT_FILTER_H_
diff --git a/base/trace_event/java_heap_dump_provider_android.cc b/base/trace_event/java_heap_dump_provider_android.cc
new file mode 100644
index 0000000000..684f7301cf
--- /dev/null
+++ b/base/trace_event/java_heap_dump_provider_android.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/java_heap_dump_provider_android.h"
+
+#include "base/android/java_runtime.h"
+#include "base/trace_event/process_memory_dump.h"
+
+namespace base {
+namespace trace_event {
+
+// static
+JavaHeapDumpProvider* JavaHeapDumpProvider::GetInstance() {
+ return Singleton<JavaHeapDumpProvider,
+ LeakySingletonTraits<JavaHeapDumpProvider>>::get();
+}
+
+JavaHeapDumpProvider::JavaHeapDumpProvider() {
+}
+
+JavaHeapDumpProvider::~JavaHeapDumpProvider() {
+}
+
+// Called at trace dump point time. Creates a snapshot with the memory counters
+// for the current process.
+bool JavaHeapDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) {
+ // These numbers come from java.lang.Runtime stats.
+ long total_heap_size = 0;
+ long free_heap_size = 0;
+ android::JavaRuntime::GetMemoryUsage(&total_heap_size, &free_heap_size);
+
+ MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("java_heap");
+ outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes, total_heap_size);
+
+ MemoryAllocatorDump* inner_dump =
+ pmd->CreateAllocatorDump("java_heap/allocated_objects");
+ inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes,
+ total_heap_size - free_heap_size);
+ return true;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/java_heap_dump_provider_android.h b/base/trace_event/java_heap_dump_provider_android.h
new file mode 100644
index 0000000000..b9f2333089
--- /dev/null
+++ b/base/trace_event/java_heap_dump_provider_android.h
@@ -0,0 +1,36 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_JAVA_HEAP_DUMP_PROVIDER_ANDROID_H_
+#define BASE_TRACE_EVENT_JAVA_HEAP_DUMP_PROVIDER_ANDROID_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/trace_event/memory_dump_provider.h"
+
+namespace base {
+namespace trace_event {
+
+// Dump provider which collects process-wide memory stats.
+class BASE_EXPORT JavaHeapDumpProvider : public MemoryDumpProvider {
+ public:
+ static JavaHeapDumpProvider* GetInstance();
+
+ // MemoryDumpProvider implementation.
+ bool OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) override;
+
+ private:
+ friend struct DefaultSingletonTraits<JavaHeapDumpProvider>;
+
+ JavaHeapDumpProvider();
+ ~JavaHeapDumpProvider() override;
+
+ DISALLOW_COPY_AND_ASSIGN(JavaHeapDumpProvider);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_JAVA_HEAP_DUMP_PROVIDER_ANDROID_H_
diff --git a/base/trace_event/java_heap_dump_provider_android_unittest.cc b/base/trace_event/java_heap_dump_provider_android_unittest.cc
new file mode 100644
index 0000000000..4deaf839ec
--- /dev/null
+++ b/base/trace_event/java_heap_dump_provider_android_unittest.cc
@@ -0,0 +1,22 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/java_heap_dump_provider_android.h"
+
+#include "base/trace_event/process_memory_dump.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+TEST(JavaHeapDumpProviderTest, JavaHeapDump) {
+ auto* jhdp = JavaHeapDumpProvider::GetInstance();
+ MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED};
+ std::unique_ptr<ProcessMemoryDump> pmd(new ProcessMemoryDump(dump_args));
+
+ jhdp->OnMemoryDump(dump_args, pmd.get());
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/malloc_dump_provider.cc b/base/trace_event/malloc_dump_provider.cc
new file mode 100644
index 0000000000..46fdb3e214
--- /dev/null
+++ b/base/trace_event/malloc_dump_provider.cc
@@ -0,0 +1,189 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/malloc_dump_provider.h"
+
+#include <stddef.h>
+
+#include <unordered_map>
+
+#include "base/allocator/allocator_extension.h"
+#include "base/allocator/buildflags.h"
+#include "base/debug/profiler.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX)
+#include <malloc/malloc.h>
+#else
+#include <malloc.h>
+#endif
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace base {
+namespace trace_event {
+
+namespace {
+#if defined(OS_WIN)
+// A structure containing some information about a given heap.
+struct WinHeapInfo {
+ size_t committed_size;
+ size_t uncommitted_size;
+ size_t allocated_size;
+ size_t block_count;
+};
+
+// NOTE: crbug.com/665516
+// Unfortunately, there is no safe way to collect information from secondary
+// heaps due to limitations and racy nature of this piece of WinAPI.
+void WinHeapMemoryDumpImpl(WinHeapInfo* crt_heap_info) {
+ // Iterate through whichever heap our CRT is using.
+ HANDLE crt_heap = reinterpret_cast<HANDLE>(_get_heap_handle());
+ ::HeapLock(crt_heap);
+ PROCESS_HEAP_ENTRY heap_entry;
+ heap_entry.lpData = nullptr;
+ // Walk over all the entries in the main heap.
+ while (::HeapWalk(crt_heap, &heap_entry) != FALSE) {
+ if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
+ crt_heap_info->allocated_size += heap_entry.cbData;
+ crt_heap_info->block_count++;
+ } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) {
+ crt_heap_info->committed_size += heap_entry.Region.dwCommittedSize;
+ crt_heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize;
+ }
+ }
+ CHECK(::HeapUnlock(crt_heap) == TRUE);
+}
+#endif // defined(OS_WIN)
+} // namespace
+
+// static
+const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects";
+
+// static
+MallocDumpProvider* MallocDumpProvider::GetInstance() {
+ return Singleton<MallocDumpProvider,
+ LeakySingletonTraits<MallocDumpProvider>>::get();
+}
+
+MallocDumpProvider::MallocDumpProvider() = default;
+MallocDumpProvider::~MallocDumpProvider() = default;
+
+// Called at trace dump point time. Creates a snapshot the memory counters for
+// the current process.
+bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) {
+ {
+ base::AutoLock auto_lock(emit_metrics_on_memory_dump_lock_);
+ if (!emit_metrics_on_memory_dump_)
+ return true;
+ }
+
+ size_t total_virtual_size = 0;
+ size_t resident_size = 0;
+ size_t allocated_objects_size = 0;
+ size_t allocated_objects_count = 0;
+#if defined(USE_TCMALLOC)
+ bool res =
+ allocator::GetNumericProperty("generic.heap_size", &total_virtual_size);
+ DCHECK(res);
+ res = allocator::GetNumericProperty("generic.total_physical_bytes",
+ &resident_size);
+ DCHECK(res);
+ res = allocator::GetNumericProperty("generic.current_allocated_bytes",
+ &allocated_objects_size);
+ DCHECK(res);
+#elif defined(OS_MACOSX) || defined(OS_IOS)
+ malloc_statistics_t stats = {0};
+ malloc_zone_statistics(nullptr, &stats);
+ total_virtual_size = stats.size_allocated;
+ allocated_objects_size = stats.size_in_use;
+
+ // Resident size is approximated pretty well by stats.max_size_in_use.
+ // However, on macOS, freed blocks are both resident and reusable, which is
+ // semantically equivalent to deallocated. The implementation of libmalloc
+ // will also only hold a fixed number of freed regions before actually
+ // starting to deallocate them, so stats.max_size_in_use is also not
+ // representative of the peak size. As a result, stats.max_size_in_use is
+ // typically somewhere between actually resident [non-reusable] pages, and
+ // peak size. This is not very useful, so we just use stats.size_in_use for
+ // resident_size, even though it's an underestimate and fails to account for
+ // fragmentation. See
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=695263#c1.
+ resident_size = stats.size_in_use;
+#elif defined(OS_WIN)
+ // This is too expensive on Windows, crbug.com/780735.
+ if (args.level_of_detail == MemoryDumpLevelOfDetail::DETAILED) {
+ WinHeapInfo main_heap_info = {};
+ WinHeapMemoryDumpImpl(&main_heap_info);
+ total_virtual_size =
+ main_heap_info.committed_size + main_heap_info.uncommitted_size;
+ // Resident size is approximated with committed heap size. Note that it is
+ // possible to do this with better accuracy on windows by intersecting the
+ // working set with the virtual memory ranges occuipied by the heap. It's
+ // not clear that this is worth it, as it's fairly expensive to do.
+ resident_size = main_heap_info.committed_size;
+ allocated_objects_size = main_heap_info.allocated_size;
+ allocated_objects_count = main_heap_info.block_count;
+ }
+#elif defined(OS_FUCHSIA)
+// TODO(fuchsia): Port, see https://crbug.com/706592.
+#else
+ struct mallinfo info = mallinfo();
+ DCHECK_GE(info.arena + info.hblkhd, info.uordblks);
+
+ // In case of Android's jemalloc |arena| is 0 and the outer pages size is
+ // reported by |hblkhd|. In case of dlmalloc the total is given by
+ // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF.
+ total_virtual_size = info.arena + info.hblkhd;
+ resident_size = info.uordblks;
+
+ // Total allocated space is given by |uordblks|.
+ allocated_objects_size = info.uordblks;
+#endif
+
+ MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc");
+ outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
+ total_virtual_size);
+ outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes, resident_size);
+
+ MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects);
+ inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes,
+ allocated_objects_size);
+ if (allocated_objects_count != 0) {
+ inner_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
+ MemoryAllocatorDump::kUnitsObjects,
+ allocated_objects_count);
+ }
+
+ if (resident_size > allocated_objects_size) {
+ // Explicitly specify why is extra memory resident. In tcmalloc it accounts
+ // for free lists and caches. In mac and ios it accounts for the
+ // fragmentation and metadata.
+ MemoryAllocatorDump* other_dump =
+ pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches");
+ other_dump->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes,
+ resident_size - allocated_objects_size);
+ }
+ return true;
+}
+
+void MallocDumpProvider::EnableMetrics() {
+ base::AutoLock auto_lock(emit_metrics_on_memory_dump_lock_);
+ emit_metrics_on_memory_dump_ = true;
+}
+
+void MallocDumpProvider::DisableMetrics() {
+ base::AutoLock auto_lock(emit_metrics_on_memory_dump_lock_);
+ emit_metrics_on_memory_dump_ = false;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/malloc_dump_provider.h b/base/trace_event/malloc_dump_provider.h
new file mode 100644
index 0000000000..e02eb9d1a9
--- /dev/null
+++ b/base/trace_event/malloc_dump_provider.h
@@ -0,0 +1,56 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MALLOC_DUMP_PROVIDER_H_
+#define BASE_TRACE_EVENT_MALLOC_DUMP_PROVIDER_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "build/build_config.h"
+
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_WIN) || \
+ (defined(OS_MACOSX) && !defined(OS_IOS))
+#define MALLOC_MEMORY_TRACING_SUPPORTED
+#endif
+
+namespace base {
+namespace trace_event {
+
+// Dump provider which collects process-wide memory stats.
+class BASE_EXPORT MallocDumpProvider : public MemoryDumpProvider {
+ public:
+ // Name of the allocated_objects dump. Use this to declare suballocator dumps
+ // from other dump providers.
+ static const char kAllocatedObjects[];
+
+ static MallocDumpProvider* GetInstance();
+
+ // MemoryDumpProvider implementation.
+ bool OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) override;
+
+ // Used by out-of-process heap-profiling. When malloc is profiled by an
+ // external process, that process will be responsible for emitting metrics on
+ // behalf of this one. Thus, MallocDumpProvider should not do anything.
+ void EnableMetrics();
+ void DisableMetrics();
+
+ private:
+ friend struct DefaultSingletonTraits<MallocDumpProvider>;
+
+ MallocDumpProvider();
+ ~MallocDumpProvider() override;
+
+ bool emit_metrics_on_memory_dump_ = true;
+ base::Lock emit_metrics_on_memory_dump_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(MallocDumpProvider);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MALLOC_DUMP_PROVIDER_H_
diff --git a/base/trace_event/memory_allocator_dump.cc b/base/trace_event/memory_allocator_dump.cc
new file mode 100644
index 0000000000..5260a734db
--- /dev/null
+++ b/base/trace_event/memory_allocator_dump.cc
@@ -0,0 +1,148 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_allocator_dump.h"
+
+#include <string.h>
+
+#include "base/format_macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "base/values.h"
+
+namespace base {
+namespace trace_event {
+
+const char MemoryAllocatorDump::kNameSize[] = "size";
+const char MemoryAllocatorDump::kNameObjectCount[] = "object_count";
+const char MemoryAllocatorDump::kTypeScalar[] = "scalar";
+const char MemoryAllocatorDump::kTypeString[] = "string";
+const char MemoryAllocatorDump::kUnitsBytes[] = "bytes";
+const char MemoryAllocatorDump::kUnitsObjects[] = "objects";
+
+MemoryAllocatorDump::MemoryAllocatorDump(
+ const std::string& absolute_name,
+ MemoryDumpLevelOfDetail level_of_detail,
+ const MemoryAllocatorDumpGuid& guid)
+ : absolute_name_(absolute_name),
+ guid_(guid),
+ level_of_detail_(level_of_detail),
+ flags_(Flags::DEFAULT) {
+ // The |absolute_name| cannot be empty.
+ DCHECK(!absolute_name.empty());
+
+ // The |absolute_name| can contain slash separator, but not leading or
+ // trailing ones.
+ DCHECK(absolute_name[0] != '/' && *absolute_name.rbegin() != '/');
+}
+
+MemoryAllocatorDump::~MemoryAllocatorDump() = default;
+
+void MemoryAllocatorDump::AddScalar(const char* name,
+ const char* units,
+ uint64_t value) {
+ entries_.emplace_back(name, units, value);
+}
+
+void MemoryAllocatorDump::AddString(const char* name,
+ const char* units,
+ const std::string& value) {
+ // String attributes are disabled in background mode.
+ if (level_of_detail_ == MemoryDumpLevelOfDetail::BACKGROUND) {
+ NOTREACHED();
+ return;
+ }
+ entries_.emplace_back(name, units, value);
+}
+
+void MemoryAllocatorDump::AsValueInto(TracedValue* value) const {
+ std::string string_conversion_buffer;
+ value->BeginDictionaryWithCopiedName(absolute_name_);
+ value->SetString("guid", guid_.ToString());
+ value->BeginDictionary("attrs");
+
+ for (const Entry& entry : entries_) {
+ value->BeginDictionaryWithCopiedName(entry.name);
+ switch (entry.entry_type) {
+ case Entry::kUint64:
+ SStringPrintf(&string_conversion_buffer, "%" PRIx64,
+ entry.value_uint64);
+ value->SetString("type", kTypeScalar);
+ value->SetString("units", entry.units);
+ value->SetString("value", string_conversion_buffer);
+ break;
+ case Entry::kString:
+ value->SetString("type", kTypeString);
+ value->SetString("units", entry.units);
+ value->SetString("value", entry.value_string);
+ break;
+ }
+ value->EndDictionary();
+ }
+ value->EndDictionary(); // "attrs": { ... }
+ if (flags_)
+ value->SetInteger("flags", flags_);
+ value->EndDictionary(); // "allocator_name/heap_subheap": { ... }
+}
+
+uint64_t MemoryAllocatorDump::GetSizeInternal() const {
+ if (cached_size_.has_value())
+ return *cached_size_;
+ for (const auto& entry : entries_) {
+ if (entry.entry_type == Entry::kUint64 && entry.units == kUnitsBytes &&
+ strcmp(entry.name.c_str(), kNameSize) == 0) {
+ cached_size_ = entry.value_uint64;
+ return entry.value_uint64;
+ }
+ }
+ return 0;
+};
+
+MemoryAllocatorDump::Entry::Entry() : entry_type(kString), value_uint64() {}
+MemoryAllocatorDump::Entry::Entry(MemoryAllocatorDump::Entry&&) noexcept =
+ default;
+MemoryAllocatorDump::Entry& MemoryAllocatorDump::Entry::operator=(
+ MemoryAllocatorDump::Entry&&) = default;
+MemoryAllocatorDump::Entry::Entry(std::string name,
+ std::string units,
+ uint64_t value)
+ : name(name), units(units), entry_type(kUint64), value_uint64(value) {}
+MemoryAllocatorDump::Entry::Entry(std::string name,
+ std::string units,
+ std::string value)
+ : name(name), units(units), entry_type(kString), value_string(value) {}
+
+bool MemoryAllocatorDump::Entry::operator==(const Entry& rhs) const {
+ if (!(name == rhs.name && units == rhs.units && entry_type == rhs.entry_type))
+ return false;
+ switch (entry_type) {
+ case EntryType::kUint64:
+ return value_uint64 == rhs.value_uint64;
+ case EntryType::kString:
+ return value_string == rhs.value_string;
+ }
+ NOTREACHED();
+ return false;
+}
+
+void PrintTo(const MemoryAllocatorDump::Entry& entry, std::ostream* out) {
+ switch (entry.entry_type) {
+ case MemoryAllocatorDump::Entry::EntryType::kUint64:
+ *out << "<Entry(\"" << entry.name << "\", \"" << entry.units << "\", "
+ << entry.value_uint64 << ")>";
+ return;
+ case MemoryAllocatorDump::Entry::EntryType::kString:
+ *out << "<Entry(\"" << entry.name << "\", \"" << entry.units << "\", \""
+ << entry.value_string << "\")>";
+ return;
+ }
+ NOTREACHED();
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_allocator_dump.h b/base/trace_event/memory_allocator_dump.h
new file mode 100644
index 0000000000..de38afd9e7
--- /dev/null
+++ b/base/trace_event/memory_allocator_dump.h
@@ -0,0 +1,153 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_H_
+#define BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "base/base_export.h"
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/trace_event/memory_allocator_dump_guid.h"
+#include "base/trace_event/memory_dump_request_args.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "base/unguessable_token.h"
+#include "base/values.h"
+
+namespace base {
+namespace trace_event {
+
+class ProcessMemoryDump;
+class TracedValue;
+
+// Data model for user-land memory allocator dumps.
+class BASE_EXPORT MemoryAllocatorDump {
+ public:
+ enum Flags {
+ DEFAULT = 0,
+
+ // A dump marked weak will be discarded by TraceViewer.
+ WEAK = 1 << 0,
+ };
+
+ // In the TraceViewer UI table each MemoryAllocatorDump becomes
+ // a row and each Entry generates a column (if it doesn't already
+ // exist).
+ struct BASE_EXPORT Entry {
+ enum EntryType {
+ kUint64,
+ kString,
+ };
+
+ // By design name, units and value_string are always coming from
+ // indefinitely lived const char* strings, the only reason we copy
+ // them into a std::string is to handle Mojo (de)serialization.
+ // TODO(hjd): Investigate optimization (e.g. using StringPiece).
+ Entry(); // Only for deserialization.
+ Entry(std::string name, std::string units, uint64_t value);
+ Entry(std::string name, std::string units, std::string value);
+ Entry(Entry&& other) noexcept;
+ Entry& operator=(Entry&& other);
+ bool operator==(const Entry& rhs) const;
+
+ std::string name;
+ std::string units;
+
+ EntryType entry_type;
+
+ uint64_t value_uint64;
+ std::string value_string;
+
+ DISALLOW_COPY_AND_ASSIGN(Entry);
+ };
+
+ MemoryAllocatorDump(const std::string& absolute_name,
+ MemoryDumpLevelOfDetail,
+ const MemoryAllocatorDumpGuid&);
+ ~MemoryAllocatorDump();
+
+ // Standard attribute |name|s for the AddScalar and AddString() methods.
+ static const char kNameSize[]; // To represent allocated space.
+ static const char kNameObjectCount[]; // To represent number of objects.
+
+ // Standard attribute |unit|s for the AddScalar and AddString() methods.
+ static const char kUnitsBytes[]; // Unit name to represent bytes.
+ static const char kUnitsObjects[]; // Unit name to represent #objects.
+
+ // Constants used only internally and by tests.
+ static const char kTypeScalar[]; // Type name for scalar attributes.
+ static const char kTypeString[]; // Type name for string attributes.
+
+ // Setters for scalar attributes. Some examples:
+ // - "size" column (all dumps are expected to have at least this one):
+ // AddScalar(kNameSize, kUnitsBytes, 1234);
+ // - Some extra-column reporting internal details of the subsystem:
+ // AddScalar("number_of_freelist_entries", kUnitsObjects, 42)
+ // - Other informational column:
+ // AddString("kitten", "name", "shadow");
+ void AddScalar(const char* name, const char* units, uint64_t value);
+ void AddString(const char* name, const char* units, const std::string& value);
+
+ // Absolute name, unique within the scope of an entire ProcessMemoryDump.
+ const std::string& absolute_name() const { return absolute_name_; }
+
+ // Called at trace generation time to populate the TracedValue.
+ void AsValueInto(TracedValue* value) const;
+
+ // Get the size for this dump.
+ // The size is the value set with AddScalar(kNameSize, kUnitsBytes, size);
+ // TODO(hjd): this should return an Optional<uint64_t>.
+ uint64_t GetSizeInternal() const;
+
+ MemoryDumpLevelOfDetail level_of_detail() const { return level_of_detail_; }
+
+ // Use enum Flags to set values.
+ void set_flags(int flags) { flags_ |= flags; }
+ void clear_flags(int flags) { flags_ &= ~flags; }
+ int flags() const { return flags_; }
+
+ // |guid| is an optional global dump identifier, unique across all processes
+ // within the scope of a global dump. It is only required when using the
+ // graph APIs (see TODO_method_name) to express retention / suballocation or
+ // cross process sharing. See crbug.com/492102 for design docs.
+ // Subsequent MemoryAllocatorDump(s) with the same |absolute_name| are
+ // expected to have the same guid.
+ const MemoryAllocatorDumpGuid& guid() const { return guid_; }
+
+ const std::vector<Entry>& entries() const { return entries_; }
+
+ // Only for mojo serialization, which can mutate the collection.
+ std::vector<Entry>* mutable_entries_for_serialization() const {
+ cached_size_.reset(); // The caller can mutate the collection.
+
+ // Mojo takes a const input argument even for move-only types that can be
+ // mutate while serializing (like this one). Hence the const_cast.
+ return const_cast<std::vector<Entry>*>(&entries_);
+ }
+
+ private:
+ const std::string absolute_name_;
+ MemoryAllocatorDumpGuid guid_;
+ MemoryDumpLevelOfDetail level_of_detail_;
+ int flags_; // See enum Flags.
+ mutable Optional<uint64_t> cached_size_; // Lazy, for GetSizeInternal().
+ std::vector<Entry> entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryAllocatorDump);
+};
+
+// This is required by gtest to print a readable output on test failures.
+void BASE_EXPORT PrintTo(const MemoryAllocatorDump::Entry&, std::ostream*);
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_H_
diff --git a/base/trace_event/memory_allocator_dump_guid.cc b/base/trace_event/memory_allocator_dump_guid.cc
new file mode 100644
index 0000000000..08ac677cfe
--- /dev/null
+++ b/base/trace_event/memory_allocator_dump_guid.cc
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_allocator_dump_guid.h"
+
+#include "base/format_macros.h"
+#include "base/sha1.h"
+#include "base/strings/stringprintf.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+uint64_t HashString(const std::string& str) {
+ uint64_t hash[(kSHA1Length + sizeof(uint64_t) - 1) / sizeof(uint64_t)] = {0};
+ SHA1HashBytes(reinterpret_cast<const unsigned char*>(str.data()), str.size(),
+ reinterpret_cast<unsigned char*>(hash));
+ return hash[0];
+}
+
+} // namespace
+
+MemoryAllocatorDumpGuid::MemoryAllocatorDumpGuid(uint64_t guid) : guid_(guid) {}
+
+MemoryAllocatorDumpGuid::MemoryAllocatorDumpGuid()
+ : MemoryAllocatorDumpGuid(0u) {
+}
+
+MemoryAllocatorDumpGuid::MemoryAllocatorDumpGuid(const std::string& guid_str)
+ : MemoryAllocatorDumpGuid(HashString(guid_str)) {
+}
+
+std::string MemoryAllocatorDumpGuid::ToString() const {
+ return StringPrintf("%" PRIx64, guid_);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_allocator_dump_guid.h b/base/trace_event/memory_allocator_dump_guid.h
new file mode 100644
index 0000000000..2a420a2af7
--- /dev/null
+++ b/base/trace_event/memory_allocator_dump_guid.h
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_GUID_H_
+#define BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_GUID_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace trace_event {
+
+class BASE_EXPORT MemoryAllocatorDumpGuid {
+ public:
+ MemoryAllocatorDumpGuid();
+ explicit MemoryAllocatorDumpGuid(uint64_t guid);
+
+ // Utility ctor to hash a GUID if the caller prefers a string. The caller
+ // still has to ensure that |guid_str| is unique, per snapshot, within the
+ // global scope of all the traced processes.
+ explicit MemoryAllocatorDumpGuid(const std::string& guid_str);
+
+ uint64_t ToUint64() const { return guid_; }
+
+ // Returns a (hex-encoded) string representation of the guid.
+ std::string ToString() const;
+
+ bool empty() const { return guid_ == 0u; }
+
+ bool operator==(const MemoryAllocatorDumpGuid& other) const {
+ return guid_ == other.guid_;
+ }
+
+ bool operator!=(const MemoryAllocatorDumpGuid& other) const {
+ return !(*this == other);
+ }
+
+ bool operator<(const MemoryAllocatorDumpGuid& other) const {
+ return guid_ < other.guid_;
+ }
+
+ private:
+ uint64_t guid_;
+
+ // Deliberately copy-able.
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_ALLOCATOR_DUMP_GUID_H_
diff --git a/base/trace_event/memory_allocator_dump_unittest.cc b/base/trace_event/memory_allocator_dump_unittest.cc
new file mode 100644
index 0000000000..78a545ff84
--- /dev/null
+++ b/base/trace_event/memory_allocator_dump_unittest.cc
@@ -0,0 +1,177 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_allocator_dump.h"
+
+#include <stdint.h>
+
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/memory_allocator_dump_guid.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::ElementsAre;
+using testing::Eq;
+using testing::ByRef;
+using testing::IsEmpty;
+using testing::Contains;
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+class FakeMemoryAllocatorDumpProvider : public MemoryDumpProvider {
+ public:
+ bool OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) override {
+ MemoryAllocatorDump* root_heap =
+ pmd->CreateAllocatorDump("foobar_allocator");
+
+ root_heap->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes, 4096);
+ root_heap->AddScalar(MemoryAllocatorDump::kNameObjectCount,
+ MemoryAllocatorDump::kUnitsObjects, 42);
+ root_heap->AddScalar("attr1", "units1", 1234);
+ root_heap->AddString("attr2", "units2", "string_value");
+
+ MemoryAllocatorDump* sub_heap =
+ pmd->CreateAllocatorDump("foobar_allocator/sub_heap");
+ sub_heap->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes, 1);
+ sub_heap->AddScalar(MemoryAllocatorDump::kNameObjectCount,
+ MemoryAllocatorDump::kUnitsObjects, 3);
+
+ pmd->CreateAllocatorDump("foobar_allocator/sub_heap/empty");
+ // Leave the rest of sub heap deliberately uninitialized, to check that
+ // CreateAllocatorDump returns a properly zero-initialized object.
+
+ return true;
+ }
+};
+
+void CheckString(const MemoryAllocatorDump* dump,
+ const std::string& name,
+ const char* expected_units,
+ const std::string& expected_value) {
+ MemoryAllocatorDump::Entry expected(name, expected_units, expected_value);
+ EXPECT_THAT(dump->entries(), Contains(Eq(ByRef(expected))));
+}
+
+void CheckScalar(const MemoryAllocatorDump* dump,
+ const std::string& name,
+ const char* expected_units,
+ uint64_t expected_value) {
+ MemoryAllocatorDump::Entry expected(name, expected_units, expected_value);
+ EXPECT_THAT(dump->entries(), Contains(Eq(ByRef(expected))));
+}
+
+} // namespace
+
+TEST(MemoryAllocatorDumpTest, GuidGeneration) {
+ std::unique_ptr<MemoryAllocatorDump> mad(new MemoryAllocatorDump(
+ "foo", MemoryDumpLevelOfDetail::FIRST, MemoryAllocatorDumpGuid(0x42u)));
+ ASSERT_EQ("42", mad->guid().ToString());
+}
+
+TEST(MemoryAllocatorDumpTest, DumpIntoProcessMemoryDump) {
+ FakeMemoryAllocatorDumpProvider fmadp;
+ MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED};
+ ProcessMemoryDump pmd(dump_args);
+
+ fmadp.OnMemoryDump(dump_args, &pmd);
+
+ ASSERT_EQ(3u, pmd.allocator_dumps().size());
+
+ const MemoryAllocatorDump* root_heap =
+ pmd.GetAllocatorDump("foobar_allocator");
+ ASSERT_NE(nullptr, root_heap);
+ EXPECT_EQ("foobar_allocator", root_heap->absolute_name());
+ CheckScalar(root_heap, MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes, 4096);
+ CheckScalar(root_heap, MemoryAllocatorDump::kNameObjectCount,
+ MemoryAllocatorDump::kUnitsObjects, 42);
+ CheckScalar(root_heap, "attr1", "units1", 1234);
+ CheckString(root_heap, "attr2", "units2", "string_value");
+
+ const MemoryAllocatorDump* sub_heap =
+ pmd.GetAllocatorDump("foobar_allocator/sub_heap");
+ ASSERT_NE(nullptr, sub_heap);
+ EXPECT_EQ("foobar_allocator/sub_heap", sub_heap->absolute_name());
+ CheckScalar(sub_heap, MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes, 1);
+ CheckScalar(sub_heap, MemoryAllocatorDump::kNameObjectCount,
+ MemoryAllocatorDump::kUnitsObjects, 3);
+ const MemoryAllocatorDump* empty_sub_heap =
+ pmd.GetAllocatorDump("foobar_allocator/sub_heap/empty");
+ ASSERT_NE(nullptr, empty_sub_heap);
+ EXPECT_EQ("foobar_allocator/sub_heap/empty", empty_sub_heap->absolute_name());
+
+ EXPECT_THAT(empty_sub_heap->entries(), IsEmpty());
+
+ // Check that calling serialization routines doesn't cause a crash.
+ std::unique_ptr<TracedValue> traced_value(new TracedValue);
+ pmd.SerializeAllocatorDumpsInto(traced_value.get());
+}
+
+TEST(MemoryAllocatorDumpTest, GetSize) {
+ MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED};
+ ProcessMemoryDump pmd(dump_args);
+ MemoryAllocatorDump* dump = pmd.CreateAllocatorDump("allocator_for_size");
+ dump->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes, 1);
+ dump->AddScalar("foo", MemoryAllocatorDump::kUnitsBytes, 2);
+ EXPECT_EQ(1u, dump->GetSizeInternal());
+}
+
+TEST(MemoryAllocatorDumpTest, ReadValues) {
+ MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED};
+ ProcessMemoryDump pmd(dump_args);
+ MemoryAllocatorDump* dump = pmd.CreateAllocatorDump("allocator_for_size");
+ dump->AddScalar("one", "byte", 1);
+ dump->AddString("one", "object", "one");
+
+ MemoryAllocatorDump::Entry expected_scalar("one", "byte", 1);
+ MemoryAllocatorDump::Entry expected_string("one", "object", "one");
+ EXPECT_THAT(dump->entries(), ElementsAre(Eq(ByRef(expected_scalar)),
+ Eq(ByRef(expected_string))));
+}
+
+TEST(MemoryAllocatorDumpTest, MovingAnEntry) {
+ MemoryAllocatorDump::Entry expected_entry("one", "byte", 1);
+ MemoryAllocatorDump::Entry from_entry("one", "byte", 1);
+ MemoryAllocatorDump::Entry to_entry = std::move(from_entry);
+ EXPECT_EQ(expected_entry, to_entry);
+}
+
+// DEATH tests are not supported in Android/iOS/Fuchsia.
+#if !defined(NDEBUG) && !defined(OS_ANDROID) && !defined(OS_IOS) && \
+ !defined(OS_FUCHSIA)
+TEST(MemoryAllocatorDumpTest, ForbidDuplicatesDeathTest) {
+ FakeMemoryAllocatorDumpProvider fmadp;
+ MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED};
+ ProcessMemoryDump pmd(dump_args);
+ pmd.CreateAllocatorDump("foo_allocator");
+ pmd.CreateAllocatorDump("bar_allocator/heap");
+ ASSERT_DEATH(pmd.CreateAllocatorDump("foo_allocator"), "");
+ ASSERT_DEATH(pmd.CreateAllocatorDump("bar_allocator/heap"), "");
+ ASSERT_DEATH(pmd.CreateAllocatorDump(""), "");
+}
+
+TEST(MemoryAllocatorDumpTest, ForbidStringsInBackgroundModeDeathTest) {
+ MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::BACKGROUND};
+ ProcessMemoryDump pmd(dump_args);
+ MemoryAllocatorDump* dump = pmd.CreateAllocatorDump("malloc");
+ ASSERT_DEATH(dump->AddString("foo", "bar", "baz"), "");
+}
+#endif
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_dump_manager.cc b/base/trace_event/memory_dump_manager.cc
new file mode 100644
index 0000000000..d61528af8e
--- /dev/null
+++ b/base/trace_event/memory_dump_manager.cc
@@ -0,0 +1,545 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_dump_manager.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/allocator/buildflags.h"
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/debug/alias.h"
+#include "base/debug/stack_trace.h"
+#include "base/debug/thread_heap_usage_tracker.h"
+#include "base/memory/ptr_util.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/heap_profiler.h"
+#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
+#include "base/trace_event/heap_profiler_event_filter.h"
+#include "base/trace_event/malloc_dump_provider.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "base/trace_event/memory_dump_scheduler.h"
+#include "base/trace_event/memory_infra_background_whitelist.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "build/build_config.h"
+
+#if defined(OS_ANDROID)
+#include "base/trace_event/java_heap_dump_provider_android.h"
+
+#if BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE)
+#include "base/trace_event/cfi_backtrace_android.h"
+#endif
+
+#endif // defined(OS_ANDROID)
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+MemoryDumpManager* g_memory_dump_manager_for_testing = nullptr;
+
+// Temporary (until scheduler is moved outside of here)
+// trampoline function to match the |request_dump_function| passed to Initialize
+// to the callback expected by MemoryDumpScheduler.
+// TODO(primiano): remove this.
+void DoGlobalDumpWithoutCallback(
+ MemoryDumpManager::RequestGlobalDumpFunction global_dump_fn,
+ MemoryDumpType dump_type,
+ MemoryDumpLevelOfDetail level_of_detail) {
+ global_dump_fn.Run(dump_type, level_of_detail);
+}
+
+} // namespace
+
+// static
+const char* const MemoryDumpManager::kTraceCategory =
+ TRACE_DISABLED_BY_DEFAULT("memory-infra");
+
+// static
+const int MemoryDumpManager::kMaxConsecutiveFailuresCount = 3;
+
+// static
+const uint64_t MemoryDumpManager::kInvalidTracingProcessId = 0;
+
+// static
+const char* const MemoryDumpManager::kSystemAllocatorPoolName =
+#if defined(MALLOC_MEMORY_TRACING_SUPPORTED)
+ MallocDumpProvider::kAllocatedObjects;
+#else
+ nullptr;
+#endif
+
+// static
+MemoryDumpManager* MemoryDumpManager::GetInstance() {
+ if (g_memory_dump_manager_for_testing)
+ return g_memory_dump_manager_for_testing;
+
+ return Singleton<MemoryDumpManager,
+ LeakySingletonTraits<MemoryDumpManager>>::get();
+}
+
+// static
+std::unique_ptr<MemoryDumpManager>
+MemoryDumpManager::CreateInstanceForTesting() {
+ DCHECK(!g_memory_dump_manager_for_testing);
+ std::unique_ptr<MemoryDumpManager> instance(new MemoryDumpManager());
+ g_memory_dump_manager_for_testing = instance.get();
+ return instance;
+}
+
+MemoryDumpManager::MemoryDumpManager()
+ : is_coordinator_(false),
+ tracing_process_id_(kInvalidTracingProcessId),
+ dumper_registrations_ignored_for_testing_(false) {}
+
+MemoryDumpManager::~MemoryDumpManager() {
+ Thread* dump_thread = nullptr;
+ {
+ AutoLock lock(lock_);
+ if (dump_thread_) {
+ dump_thread = dump_thread_.get();
+ }
+ }
+ if (dump_thread) {
+ dump_thread->Stop();
+ }
+ AutoLock lock(lock_);
+ dump_thread_.reset();
+ g_memory_dump_manager_for_testing = nullptr;
+}
+
+void MemoryDumpManager::Initialize(
+ RequestGlobalDumpFunction request_dump_function,
+ bool is_coordinator) {
+ {
+ AutoLock lock(lock_);
+ DCHECK(!request_dump_function.is_null());
+ DCHECK(!can_request_global_dumps());
+ request_dump_function_ = request_dump_function;
+ is_coordinator_ = is_coordinator;
+ }
+
+// Enable the core dump providers.
+#if defined(MALLOC_MEMORY_TRACING_SUPPORTED)
+ RegisterDumpProvider(MallocDumpProvider::GetInstance(), "Malloc", nullptr);
+#endif
+
+#if defined(OS_ANDROID)
+ RegisterDumpProvider(JavaHeapDumpProvider::GetInstance(), "JavaHeap",
+ nullptr);
+#endif
+
+ TRACE_EVENT_WARMUP_CATEGORY(kTraceCategory);
+}
+
+void MemoryDumpManager::RegisterDumpProvider(
+ MemoryDumpProvider* mdp,
+ const char* name,
+ scoped_refptr<SingleThreadTaskRunner> task_runner,
+ MemoryDumpProvider::Options options) {
+ options.dumps_on_single_thread_task_runner = true;
+ RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options);
+}
+
+void MemoryDumpManager::RegisterDumpProvider(
+ MemoryDumpProvider* mdp,
+ const char* name,
+ scoped_refptr<SingleThreadTaskRunner> task_runner) {
+ // Set |dumps_on_single_thread_task_runner| to true because all providers
+ // without task runner are run on dump thread.
+ MemoryDumpProvider::Options options;
+ options.dumps_on_single_thread_task_runner = true;
+ RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options);
+}
+
+void MemoryDumpManager::RegisterDumpProviderWithSequencedTaskRunner(
+ MemoryDumpProvider* mdp,
+ const char* name,
+ scoped_refptr<SequencedTaskRunner> task_runner,
+ MemoryDumpProvider::Options options) {
+ DCHECK(task_runner);
+ options.dumps_on_single_thread_task_runner = false;
+ RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options);
+}
+
+void MemoryDumpManager::RegisterDumpProviderInternal(
+ MemoryDumpProvider* mdp,
+ const char* name,
+ scoped_refptr<SequencedTaskRunner> task_runner,
+ const MemoryDumpProvider::Options& options) {
+ if (dumper_registrations_ignored_for_testing_)
+ return;
+
+ // Only a handful of MDPs are required to compute the memory metrics. These
+ // have small enough performance overhead that it is resonable to run them
+ // in the background while the user is doing other things. Those MDPs are
+ // 'whitelisted for background mode'.
+ bool whitelisted_for_background_mode = IsMemoryDumpProviderWhitelisted(name);
+
+ scoped_refptr<MemoryDumpProviderInfo> mdpinfo =
+ new MemoryDumpProviderInfo(mdp, name, std::move(task_runner), options,
+ whitelisted_for_background_mode);
+
+ {
+ AutoLock lock(lock_);
+ bool already_registered = !dump_providers_.insert(mdpinfo).second;
+ // This actually happens in some tests which don't have a clean tear-down
+ // path for RenderThreadImpl::Init().
+ if (already_registered)
+ return;
+ }
+}
+
+void MemoryDumpManager::UnregisterDumpProvider(MemoryDumpProvider* mdp) {
+ UnregisterDumpProviderInternal(mdp, false /* delete_async */);
+}
+
+void MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon(
+ std::unique_ptr<MemoryDumpProvider> mdp) {
+ UnregisterDumpProviderInternal(mdp.release(), true /* delete_async */);
+}
+
+void MemoryDumpManager::UnregisterDumpProviderInternal(
+ MemoryDumpProvider* mdp,
+ bool take_mdp_ownership_and_delete_async) {
+ std::unique_ptr<MemoryDumpProvider> owned_mdp;
+ if (take_mdp_ownership_and_delete_async)
+ owned_mdp.reset(mdp);
+
+ AutoLock lock(lock_);
+
+ auto mdp_iter = dump_providers_.begin();
+ for (; mdp_iter != dump_providers_.end(); ++mdp_iter) {
+ if ((*mdp_iter)->dump_provider == mdp)
+ break;
+ }
+
+ if (mdp_iter == dump_providers_.end())
+ return; // Not registered / already unregistered.
+
+ if (take_mdp_ownership_and_delete_async) {
+ // The MDP will be deleted whenever the MDPInfo struct will, that is either:
+ // - At the end of this function, if no dump is in progress.
+ // - In ContinueAsyncProcessDump() when MDPInfo is removed from
+ // |pending_dump_providers|.
+ DCHECK(!(*mdp_iter)->owned_dump_provider);
+ (*mdp_iter)->owned_dump_provider = std::move(owned_mdp);
+ } else {
+ // If you hit this DCHECK, your dump provider has a bug.
+ // Unregistration of a MemoryDumpProvider is safe only if:
+ // - The MDP has specified a sequenced task runner affinity AND the
+ // unregistration happens on the same task runner. So that the MDP cannot
+ // unregister and be in the middle of a OnMemoryDump() at the same time.
+ // - The MDP has NOT specified a task runner affinity and its ownership is
+ // transferred via UnregisterAndDeleteDumpProviderSoon().
+ // In all the other cases, it is not possible to guarantee that the
+ // unregistration will not race with OnMemoryDump() calls.
+ DCHECK((*mdp_iter)->task_runner &&
+ (*mdp_iter)->task_runner->RunsTasksInCurrentSequence())
+ << "MemoryDumpProvider \"" << (*mdp_iter)->name << "\" attempted to "
+ << "unregister itself in a racy way. Please file a crbug.";
+ }
+
+ // The MDPInfo instance can still be referenced by the
+ // |ProcessMemoryDumpAsyncState.pending_dump_providers|. For this reason
+ // the MDPInfo is flagged as disabled. It will cause InvokeOnMemoryDump()
+ // to just skip it, without actually invoking the |mdp|, which might be
+ // destroyed by the caller soon after this method returns.
+ (*mdp_iter)->disabled = true;
+ dump_providers_.erase(mdp_iter);
+}
+
+bool MemoryDumpManager::IsDumpProviderRegisteredForTesting(
+ MemoryDumpProvider* provider) {
+ AutoLock lock(lock_);
+
+ for (const auto& info : dump_providers_) {
+ if (info->dump_provider == provider)
+ return true;
+ }
+ return false;
+}
+
+scoped_refptr<base::SequencedTaskRunner>
+MemoryDumpManager::GetOrCreateBgTaskRunnerLocked() {
+ lock_.AssertAcquired();
+
+ if (dump_thread_)
+ return dump_thread_->task_runner();
+
+ dump_thread_ = std::make_unique<Thread>("MemoryInfra");
+ bool started = dump_thread_->Start();
+ CHECK(started);
+
+ return dump_thread_->task_runner();
+}
+
+void MemoryDumpManager::CreateProcessDump(
+ const MemoryDumpRequestArgs& args,
+ const ProcessMemoryDumpCallback& callback) {
+ char guid_str[20];
+ sprintf(guid_str, "0x%" PRIx64, args.dump_guid);
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(kTraceCategory, "ProcessMemoryDump",
+ TRACE_ID_LOCAL(args.dump_guid), "dump_guid",
+ TRACE_STR_COPY(guid_str));
+
+ // If argument filter is enabled then only background mode dumps should be
+ // allowed. In case the trace config passed for background tracing session
+ // missed the allowed modes argument, it crashes here instead of creating
+ // unexpected dumps.
+ if (TraceLog::GetInstance()
+ ->GetCurrentTraceConfig()
+ .IsArgumentFilterEnabled()) {
+ CHECK_EQ(MemoryDumpLevelOfDetail::BACKGROUND, args.level_of_detail);
+ }
+
+ std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state;
+ {
+ AutoLock lock(lock_);
+
+ pmd_async_state.reset(new ProcessMemoryDumpAsyncState(
+ args, dump_providers_, callback, GetOrCreateBgTaskRunnerLocked()));
+ }
+
+ // Start the process dump. This involves task runner hops as specified by the
+ // MemoryDumpProvider(s) in RegisterDumpProvider()).
+ ContinueAsyncProcessDump(pmd_async_state.release());
+}
+
+// Invokes OnMemoryDump() on all MDPs that are next in the pending list and run
+// on the current sequenced task runner. If the next MDP does not run in current
+// sequenced task runner, then switches to that task runner and continues. All
+// OnMemoryDump() invocations are linearized. |lock_| is used in these functions
+// purely to ensure consistency w.r.t. (un)registrations of |dump_providers_|.
+void MemoryDumpManager::ContinueAsyncProcessDump(
+ ProcessMemoryDumpAsyncState* owned_pmd_async_state) {
+ HEAP_PROFILER_SCOPED_IGNORE;
+ // Initalizes the ThreadLocalEventBuffer to guarantee that the TRACE_EVENTs
+ // in the PostTask below don't end up registering their own dump providers
+ // (for discounting trace memory overhead) while holding the |lock_|.
+ TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported();
+
+ // In theory |owned_pmd_async_state| should be a unique_ptr. The only reason
+ // why it isn't is because of the corner case logic of |did_post_task|
+ // above, which needs to take back the ownership of the |pmd_async_state| when
+ // the PostTask() fails.
+ // Unfortunately, PostTask() destroys the unique_ptr arguments upon failure
+ // to prevent accidental leaks. Using a unique_ptr would prevent us to to
+ // skip the hop and move on. Hence the manual naked -> unique ptr juggling.
+ auto pmd_async_state = WrapUnique(owned_pmd_async_state);
+ owned_pmd_async_state = nullptr;
+
+ while (!pmd_async_state->pending_dump_providers.empty()) {
+ // Read MemoryDumpProviderInfo thread safety considerations in
+ // memory_dump_manager.h when accessing |mdpinfo| fields.
+ MemoryDumpProviderInfo* mdpinfo =
+ pmd_async_state->pending_dump_providers.back().get();
+
+ // If we are in background mode, we should invoke only the whitelisted
+ // providers. Ignore other providers and continue.
+ if (pmd_async_state->req_args.level_of_detail ==
+ MemoryDumpLevelOfDetail::BACKGROUND &&
+ !mdpinfo->whitelisted_for_background_mode) {
+ pmd_async_state->pending_dump_providers.pop_back();
+ continue;
+ }
+
+ // If the dump provider did not specify a task runner affinity, dump on
+ // |dump_thread_|.
+ scoped_refptr<SequencedTaskRunner> task_runner = mdpinfo->task_runner;
+ if (!task_runner) {
+ DCHECK(mdpinfo->options.dumps_on_single_thread_task_runner);
+ task_runner = pmd_async_state->dump_thread_task_runner;
+ DCHECK(task_runner);
+ }
+
+ // If |RunsTasksInCurrentSequence()| is true then no PostTask is
+ // required since we are on the right SequencedTaskRunner.
+ if (task_runner->RunsTasksInCurrentSequence()) {
+ InvokeOnMemoryDump(mdpinfo, pmd_async_state->process_memory_dump.get());
+ pmd_async_state->pending_dump_providers.pop_back();
+ continue;
+ }
+
+ bool did_post_task = task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(&MemoryDumpManager::ContinueAsyncProcessDump, Unretained(this),
+ Unretained(pmd_async_state.get())));
+
+ if (did_post_task) {
+ // Ownership is tranferred to the posted task.
+ ignore_result(pmd_async_state.release());
+ return;
+ }
+
+ // PostTask usually fails only if the process or thread is shut down. So,
+ // the dump provider is disabled here. But, don't disable unbound dump
+ // providers, since the |dump_thread_| is controlled by MDM.
+ if (mdpinfo->task_runner) {
+ // A locked access is required to R/W |disabled| (for the
+ // UnregisterAndDeleteDumpProviderSoon() case).
+ AutoLock lock(lock_);
+ mdpinfo->disabled = true;
+ }
+
+ // PostTask failed. Ignore the dump provider and continue.
+ pmd_async_state->pending_dump_providers.pop_back();
+ }
+
+ FinishAsyncProcessDump(std::move(pmd_async_state));
+}
+
+// This function is called on the right task runner for current MDP. It is
+// either the task runner specified by MDP or |dump_thread_task_runner| if the
+// MDP did not specify task runner. Invokes the dump provider's OnMemoryDump()
+// (unless disabled).
+void MemoryDumpManager::InvokeOnMemoryDump(MemoryDumpProviderInfo* mdpinfo,
+ ProcessMemoryDump* pmd) {
+ HEAP_PROFILER_SCOPED_IGNORE;
+ DCHECK(!mdpinfo->task_runner ||
+ mdpinfo->task_runner->RunsTasksInCurrentSequence());
+
+ TRACE_EVENT1(kTraceCategory, "MemoryDumpManager::InvokeOnMemoryDump",
+ "dump_provider.name", mdpinfo->name);
+
+ // Do not add any other TRACE_EVENT macro (or function that might have them)
+ // below this point. Under some rare circunstances, they can re-initialize
+ // and invalide the current ThreadLocalEventBuffer MDP, making the
+ // |should_dump| check below susceptible to TOCTTOU bugs
+ // (https://crbug.com/763365).
+
+ bool is_thread_bound;
+ {
+ // A locked access is required to R/W |disabled| (for the
+ // UnregisterAndDeleteDumpProviderSoon() case).
+ AutoLock lock(lock_);
+
+ // Unregister the dump provider if it failed too many times consecutively.
+ if (!mdpinfo->disabled &&
+ mdpinfo->consecutive_failures >= kMaxConsecutiveFailuresCount) {
+ mdpinfo->disabled = true;
+ DLOG(ERROR) << "Disabling MemoryDumpProvider \"" << mdpinfo->name
+ << "\". Dump failed multiple times consecutively.";
+ }
+ if (mdpinfo->disabled)
+ return;
+
+ is_thread_bound = mdpinfo->task_runner != nullptr;
+ } // AutoLock lock(lock_);
+
+ // Invoke the dump provider.
+
+ // A stack allocated string with dump provider name is useful to debug
+ // crashes while invoking dump after a |dump_provider| is not unregistered
+ // in safe way.
+ char provider_name_for_debugging[16];
+ strncpy(provider_name_for_debugging, mdpinfo->name,
+ sizeof(provider_name_for_debugging) - 1);
+ provider_name_for_debugging[sizeof(provider_name_for_debugging) - 1] = '\0';
+ base::debug::Alias(provider_name_for_debugging);
+
+ ANNOTATE_BENIGN_RACE(&mdpinfo->disabled, "best-effort race detection");
+ CHECK(!is_thread_bound ||
+ !*(static_cast<volatile bool*>(&mdpinfo->disabled)));
+ bool dump_successful =
+ mdpinfo->dump_provider->OnMemoryDump(pmd->dump_args(), pmd);
+ mdpinfo->consecutive_failures =
+ dump_successful ? 0 : mdpinfo->consecutive_failures + 1;
+}
+
+void MemoryDumpManager::FinishAsyncProcessDump(
+ std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state) {
+ HEAP_PROFILER_SCOPED_IGNORE;
+ DCHECK(pmd_async_state->pending_dump_providers.empty());
+ const uint64_t dump_guid = pmd_async_state->req_args.dump_guid;
+ if (!pmd_async_state->callback_task_runner->BelongsToCurrentThread()) {
+ scoped_refptr<SingleThreadTaskRunner> callback_task_runner =
+ pmd_async_state->callback_task_runner;
+ callback_task_runner->PostTask(
+ FROM_HERE, BindOnce(&MemoryDumpManager::FinishAsyncProcessDump,
+ Unretained(this), std::move(pmd_async_state)));
+ return;
+ }
+
+ TRACE_EVENT0(kTraceCategory, "MemoryDumpManager::FinishAsyncProcessDump");
+
+ if (!pmd_async_state->callback.is_null()) {
+ pmd_async_state->callback.Run(
+ true /* success */, dump_guid,
+ std::move(pmd_async_state->process_memory_dump));
+ pmd_async_state->callback.Reset();
+ }
+
+ TRACE_EVENT_NESTABLE_ASYNC_END0(kTraceCategory, "ProcessMemoryDump",
+ TRACE_ID_LOCAL(dump_guid));
+}
+
+void MemoryDumpManager::SetupForTracing(
+ const TraceConfig::MemoryDumpConfig& memory_dump_config) {
+ AutoLock lock(lock_);
+
+ // At this point we must have the ability to request global dumps.
+ DCHECK(can_request_global_dumps());
+
+ MemoryDumpScheduler::Config periodic_config;
+ for (const auto& trigger : memory_dump_config.triggers) {
+ if (trigger.trigger_type == MemoryDumpType::PERIODIC_INTERVAL) {
+ if (periodic_config.triggers.empty()) {
+ periodic_config.callback =
+ BindRepeating(&DoGlobalDumpWithoutCallback, request_dump_function_,
+ MemoryDumpType::PERIODIC_INTERVAL);
+ }
+ periodic_config.triggers.push_back(
+ {trigger.level_of_detail, trigger.min_time_between_dumps_ms});
+ }
+ }
+
+ // Only coordinator process triggers periodic memory dumps.
+ if (is_coordinator_ && !periodic_config.triggers.empty()) {
+ MemoryDumpScheduler::GetInstance()->Start(periodic_config,
+ GetOrCreateBgTaskRunnerLocked());
+ }
+}
+
+void MemoryDumpManager::TeardownForTracing() {
+ // There might be a memory dump in progress while this happens. Therefore,
+ // ensure that the MDM state which depends on the tracing enabled / disabled
+ // state is always accessed by the dumping methods holding the |lock_|.
+ AutoLock lock(lock_);
+
+ MemoryDumpScheduler::GetInstance()->Stop();
+}
+
+MemoryDumpManager::ProcessMemoryDumpAsyncState::ProcessMemoryDumpAsyncState(
+ MemoryDumpRequestArgs req_args,
+ const MemoryDumpProviderInfo::OrderedSet& dump_providers,
+ ProcessMemoryDumpCallback callback,
+ scoped_refptr<SequencedTaskRunner> dump_thread_task_runner)
+ : req_args(req_args),
+ callback(callback),
+ callback_task_runner(ThreadTaskRunnerHandle::Get()),
+ dump_thread_task_runner(std::move(dump_thread_task_runner)) {
+ pending_dump_providers.reserve(dump_providers.size());
+ pending_dump_providers.assign(dump_providers.rbegin(), dump_providers.rend());
+ MemoryDumpArgs args = {req_args.level_of_detail, req_args.dump_guid};
+ process_memory_dump = std::make_unique<ProcessMemoryDump>(args);
+}
+
+MemoryDumpManager::ProcessMemoryDumpAsyncState::~ProcessMemoryDumpAsyncState() =
+ default;
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_dump_manager.h b/base/trace_event/memory_dump_manager.h
new file mode 100644
index 0000000000..6033cfbb51
--- /dev/null
+++ b/base/trace_event/memory_dump_manager.h
@@ -0,0 +1,267 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_H_
+#define BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <unordered_set>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "base/trace_event/memory_allocator_dump.h"
+#include "base/trace_event/memory_dump_provider_info.h"
+#include "base/trace_event/memory_dump_request_args.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+
+class SequencedTaskRunner;
+class SingleThreadTaskRunner;
+class Thread;
+
+namespace trace_event {
+
+class MemoryDumpProvider;
+
+// This is the interface exposed to the rest of the codebase to deal with
+// memory tracing. The main entry point for clients is represented by
+// RequestDumpPoint(). The extension by Un(RegisterDumpProvider).
+class BASE_EXPORT MemoryDumpManager {
+ public:
+ using RequestGlobalDumpFunction =
+ RepeatingCallback<void(MemoryDumpType, MemoryDumpLevelOfDetail)>;
+
+ static const char* const kTraceCategory;
+
+ // This value is returned as the tracing id of the child processes by
+ // GetTracingProcessId() when tracing is not enabled.
+ static const uint64_t kInvalidTracingProcessId;
+
+ static MemoryDumpManager* GetInstance();
+ static std::unique_ptr<MemoryDumpManager> CreateInstanceForTesting();
+
+ // Invoked once per process to listen to trace begin / end events.
+ // Initialization can happen after (Un)RegisterMemoryDumpProvider() calls
+ // and the MemoryDumpManager guarantees to support this.
+ // On the other side, the MemoryDumpManager will not be fully operational
+ // (any CreateProcessDump() will return a failure) until initialized.
+ // Arguments:
+ // is_coordinator: True when current process coordinates the periodic dump
+ // triggering.
+ // request_dump_function: Function to invoke a global dump. Global dump
+ // involves embedder-specific behaviors like multiprocess handshaking.
+ // TODO(primiano): this is only required to trigger global dumps from
+ // the scheduler. Should be removed once they are both moved out of base.
+ void Initialize(RequestGlobalDumpFunction request_dump_function,
+ bool is_coordinator);
+
+ // (Un)Registers a MemoryDumpProvider instance.
+ // Args:
+ // - mdp: the MemoryDumpProvider instance to be registered. MemoryDumpManager
+ // does NOT take memory ownership of |mdp|, which is expected to either
+ // be a singleton or unregister itself.
+ // - name: a friendly name (duplicates allowed). Used for debugging and
+ // run-time profiling of memory-infra internals. Must be a long-lived
+ // C string.
+ // - task_runner: either a SingleThreadTaskRunner or SequencedTaskRunner. All
+ // the calls to |mdp| will be run on the given |task_runner|. If passed
+ // null |mdp| should be able to handle calls on arbitrary threads.
+ // - options: extra optional arguments. See memory_dump_provider.h.
+ void RegisterDumpProvider(MemoryDumpProvider* mdp,
+ const char* name,
+ scoped_refptr<SingleThreadTaskRunner> task_runner);
+ void RegisterDumpProvider(MemoryDumpProvider* mdp,
+ const char* name,
+ scoped_refptr<SingleThreadTaskRunner> task_runner,
+ MemoryDumpProvider::Options options);
+ void RegisterDumpProviderWithSequencedTaskRunner(
+ MemoryDumpProvider* mdp,
+ const char* name,
+ scoped_refptr<SequencedTaskRunner> task_runner,
+ MemoryDumpProvider::Options options);
+ void UnregisterDumpProvider(MemoryDumpProvider* mdp);
+
+ // Unregisters an unbound dump provider and takes care about its deletion
+ // asynchronously. Can be used only for for dump providers with no
+ // task-runner affinity.
+ // This method takes ownership of the dump provider and guarantees that:
+ // - The |mdp| will be deleted at some point in the near future.
+ // - Its deletion will not happen concurrently with the OnMemoryDump() call.
+ // Note that OnMemoryDump() calls can still happen after this method returns.
+ void UnregisterAndDeleteDumpProviderSoon(
+ std::unique_ptr<MemoryDumpProvider> mdp);
+
+ // Prepare MemoryDumpManager for CreateProcessDump() calls for tracing-related
+ // modes (i.e. |level_of_detail| != SUMMARY_ONLY).
+ // Also initializes the scheduler with the given config.
+ void SetupForTracing(const TraceConfig::MemoryDumpConfig&);
+
+ // Tear-down tracing related state.
+ // Non-tracing modes (e.g. SUMMARY_ONLY) will continue to work.
+ void TeardownForTracing();
+
+ // Creates a memory dump for the current process and appends it to the trace.
+ // |callback| will be invoked asynchronously upon completion on the same
+ // thread on which CreateProcessDump() was called. This method should only be
+ // used by the memory-infra service while creating a global memory dump.
+ void CreateProcessDump(const MemoryDumpRequestArgs& args,
+ const ProcessMemoryDumpCallback& callback);
+
+ // Lets tests see if a dump provider is registered.
+ bool IsDumpProviderRegisteredForTesting(MemoryDumpProvider*);
+
+ // Returns a unique id for identifying the processes. The id can be
+ // retrieved by child processes only when tracing is enabled. This is
+ // intended to express cross-process sharing of memory dumps on the
+ // child-process side, without having to know its own child process id.
+ uint64_t GetTracingProcessId() const { return tracing_process_id_; }
+ void set_tracing_process_id(uint64_t tracing_process_id) {
+ tracing_process_id_ = tracing_process_id;
+ }
+
+ // Returns the name for a the allocated_objects dump. Use this to declare
+ // suballocator dumps from other dump providers.
+ // It will return nullptr if there is no dump provider for the system
+ // allocator registered (which is currently the case for Mac OS).
+ const char* system_allocator_pool_name() const {
+ return kSystemAllocatorPoolName;
+ };
+
+ // When set to true, calling |RegisterMemoryDumpProvider| is a no-op.
+ void set_dumper_registrations_ignored_for_testing(bool ignored) {
+ dumper_registrations_ignored_for_testing_ = ignored;
+ }
+
+ private:
+ friend std::default_delete<MemoryDumpManager>; // For the testing instance.
+ friend struct DefaultSingletonTraits<MemoryDumpManager>;
+ friend class MemoryDumpManagerTest;
+ FRIEND_TEST_ALL_PREFIXES(MemoryDumpManagerTest,
+ NoStackOverflowWithTooManyMDPs);
+
+ // Holds the state of a process memory dump that needs to be carried over
+ // across task runners in order to fulfill an asynchronous CreateProcessDump()
+ // request. At any time exactly one task runner owns a
+ // ProcessMemoryDumpAsyncState.
+ struct ProcessMemoryDumpAsyncState {
+ ProcessMemoryDumpAsyncState(
+ MemoryDumpRequestArgs req_args,
+ const MemoryDumpProviderInfo::OrderedSet& dump_providers,
+ ProcessMemoryDumpCallback callback,
+ scoped_refptr<SequencedTaskRunner> dump_thread_task_runner);
+ ~ProcessMemoryDumpAsyncState();
+
+ // A ProcessMemoryDump to collect data from MemoryDumpProviders.
+ std::unique_ptr<ProcessMemoryDump> process_memory_dump;
+
+ // The arguments passed to the initial CreateProcessDump() request.
+ const MemoryDumpRequestArgs req_args;
+
+ // An ordered sequence of dump providers that have to be invoked to complete
+ // the dump. This is a copy of |dump_providers_| at the beginning of a dump
+ // and becomes empty at the end, when all dump providers have been invoked.
+ std::vector<scoped_refptr<MemoryDumpProviderInfo>> pending_dump_providers;
+
+ // Callback passed to the initial call to CreateProcessDump().
+ ProcessMemoryDumpCallback callback;
+
+ // The thread on which FinishAsyncProcessDump() (and hence |callback|)
+ // should be invoked. This is the thread on which the initial
+ // CreateProcessDump() request was called.
+ const scoped_refptr<SingleThreadTaskRunner> callback_task_runner;
+
+ // The thread on which unbound dump providers should be invoked.
+ // This is essentially |dump_thread_|.task_runner() but needs to be kept
+ // as a separate variable as it needs to be accessed by arbitrary dumpers'
+ // threads outside of the lock_ to avoid races when disabling tracing.
+ // It is immutable for all the duration of a tracing session.
+ const scoped_refptr<SequencedTaskRunner> dump_thread_task_runner;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProcessMemoryDumpAsyncState);
+ };
+
+ static const int kMaxConsecutiveFailuresCount;
+ static const char* const kSystemAllocatorPoolName;
+
+ MemoryDumpManager();
+ virtual ~MemoryDumpManager();
+
+ static void SetInstanceForTesting(MemoryDumpManager* instance);
+
+ // Lazily initializes dump_thread_ and returns its TaskRunner.
+ scoped_refptr<base::SequencedTaskRunner> GetOrCreateBgTaskRunnerLocked();
+
+ // Calls InvokeOnMemoryDump() for the each MDP that belongs to the current
+ // task runner and switches to the task runner of the next MDP. Handles
+ // failures in MDP and thread hops, and always calls FinishAsyncProcessDump()
+ // at the end.
+ void ContinueAsyncProcessDump(
+ ProcessMemoryDumpAsyncState* owned_pmd_async_state);
+
+ // Invokes OnMemoryDump() of the given MDP. Should be called on the MDP task
+ // runner.
+ void InvokeOnMemoryDump(MemoryDumpProviderInfo* mdpinfo,
+ ProcessMemoryDump* pmd);
+
+ void FinishAsyncProcessDump(
+ std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state);
+
+ // Helper for RegierDumpProvider* functions.
+ void RegisterDumpProviderInternal(
+ MemoryDumpProvider* mdp,
+ const char* name,
+ scoped_refptr<SequencedTaskRunner> task_runner,
+ const MemoryDumpProvider::Options& options);
+
+ // Helper for the public UnregisterDumpProvider* functions.
+ void UnregisterDumpProviderInternal(MemoryDumpProvider* mdp,
+ bool take_mdp_ownership_and_delete_async);
+
+ bool can_request_global_dumps() const {
+ return !request_dump_function_.is_null();
+ }
+
+ // An ordered set of registered MemoryDumpProviderInfo(s), sorted by task
+ // runner affinity (MDPs belonging to the same task runners are adjacent).
+ MemoryDumpProviderInfo::OrderedSet dump_providers_;
+
+ // Function provided by the embedder to handle global dump requests.
+ RequestGlobalDumpFunction request_dump_function_;
+
+ // True when current process coordinates the periodic dump triggering.
+ bool is_coordinator_;
+
+ // Protects from concurrent accesses to the local state, eg: to guard against
+ // disabling logging while dumping on another thread.
+ Lock lock_;
+
+ // Thread used for MemoryDumpProviders which don't specify a task runner
+ // affinity.
+ std::unique_ptr<Thread> dump_thread_;
+
+ // The unique id of the child process. This is created only for tracing and is
+ // expected to be valid only when tracing is enabled.
+ uint64_t tracing_process_id_;
+
+ // When true, calling |RegisterMemoryDumpProvider| is a no-op.
+ bool dumper_registrations_ignored_for_testing_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryDumpManager);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_H_
diff --git a/base/trace_event/memory_dump_manager_test_utils.h b/base/trace_event/memory_dump_manager_test_utils.h
new file mode 100644
index 0000000000..413017f6c0
--- /dev/null
+++ b/base/trace_event/memory_dump_manager_test_utils.h
@@ -0,0 +1,38 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_TEST_UTILS_H_
+#define BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_TEST_UTILS_H_
+
+#include "base/bind.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/memory_dump_request_args.h"
+
+namespace base {
+namespace trace_event {
+
+void RequestGlobalDumpForInProcessTesting(
+ base::trace_event::MemoryDumpType dump_type,
+ base::trace_event::MemoryDumpLevelOfDetail level_of_detail) {
+ MemoryDumpRequestArgs local_args = {0 /* dump_guid */, dump_type,
+ level_of_detail};
+ MemoryDumpManager::GetInstance()->CreateProcessDump(
+ local_args, ProcessMemoryDumpCallback());
+};
+
+// Short circuits the RequestGlobalDumpFunction() to CreateProcessDump(),
+// effectively allowing to use both in unittests with the same behavior.
+// Unittests are in-process only and don't require all the multi-process
+// dump handshaking (which would require bits outside of base).
+void InitializeMemoryDumpManagerForInProcessTesting(bool is_coordinator) {
+ MemoryDumpManager* instance = MemoryDumpManager::GetInstance();
+ instance->set_dumper_registrations_ignored_for_testing(true);
+ instance->Initialize(BindRepeating(&RequestGlobalDumpForInProcessTesting),
+ is_coordinator);
+}
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_DUMP_MANAGER_TEST_UTILS_H_
diff --git a/base/trace_event/memory_dump_manager_unittest.cc b/base/trace_event/memory_dump_manager_unittest.cc
new file mode 100644
index 0000000000..706df2dafe
--- /dev/null
+++ b/base/trace_event/memory_dump_manager_unittest.cc
@@ -0,0 +1,840 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_dump_manager.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/allocator/buildflags.h"
+#include "base/base_switches.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/debug/thread_heap_usage_tracker.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_io_thread.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/memory_dump_manager_test_utils.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "base/trace_event/memory_dump_request_args.h"
+#include "base/trace_event/memory_dump_scheduler.h"
+#include "base/trace_event/memory_infra_background_whitelist.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::AtMost;
+using testing::Between;
+using testing::Invoke;
+using testing::Return;
+
+namespace base {
+namespace trace_event {
+
+// GTest matchers for MemoryDumpRequestArgs arguments.
+MATCHER(IsDetailedDump, "") {
+ return arg.level_of_detail == MemoryDumpLevelOfDetail::DETAILED;
+}
+
+MATCHER(IsLightDump, "") {
+ return arg.level_of_detail == MemoryDumpLevelOfDetail::LIGHT;
+}
+
+namespace {
+
+const char* kMDPName = "TestDumpProvider";
+const char* kWhitelistedMDPName = "WhitelistedTestDumpProvider";
+const char* const kTestMDPWhitelist[] = {kWhitelistedMDPName, nullptr};
+
+void RegisterDumpProvider(
+ MemoryDumpProvider* mdp,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ const MemoryDumpProvider::Options& options,
+ const char* name = kMDPName) {
+ MemoryDumpManager* mdm = MemoryDumpManager::GetInstance();
+ mdm->set_dumper_registrations_ignored_for_testing(false);
+ mdm->RegisterDumpProvider(mdp, name, std::move(task_runner), options);
+ mdm->set_dumper_registrations_ignored_for_testing(true);
+}
+
+void RegisterDumpProvider(
+ MemoryDumpProvider* mdp,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ RegisterDumpProvider(mdp, task_runner, MemoryDumpProvider::Options());
+}
+
+void RegisterDumpProviderWithSequencedTaskRunner(
+ MemoryDumpProvider* mdp,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const MemoryDumpProvider::Options& options) {
+ MemoryDumpManager* mdm = MemoryDumpManager::GetInstance();
+ mdm->set_dumper_registrations_ignored_for_testing(false);
+ mdm->RegisterDumpProviderWithSequencedTaskRunner(mdp, kMDPName, task_runner,
+ options);
+ mdm->set_dumper_registrations_ignored_for_testing(true);
+}
+
+// Posts |task| to |task_runner| and blocks until it is executed.
+void PostTaskAndWait(const Location& from_here,
+ SequencedTaskRunner* task_runner,
+ base::OnceClosure task) {
+ base::WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ task_runner->PostTask(from_here, std::move(task));
+ task_runner->PostTask(FROM_HERE, base::BindOnce(&WaitableEvent::Signal,
+ base::Unretained(&event)));
+ // The SequencedTaskRunner guarantees that |event| will only be signaled after
+ // |task| is executed.
+ event.Wait();
+}
+
+class MockMemoryDumpProvider : public MemoryDumpProvider {
+ public:
+ MOCK_METHOD0(Destructor, void());
+ MOCK_METHOD2(OnMemoryDump,
+ bool(const MemoryDumpArgs& args, ProcessMemoryDump* pmd));
+
+ MockMemoryDumpProvider() : enable_mock_destructor(false) {
+ ON_CALL(*this, OnMemoryDump(_, _))
+ .WillByDefault(
+ Invoke([](const MemoryDumpArgs&, ProcessMemoryDump* pmd) -> bool {
+ return true;
+ }));
+ }
+ ~MockMemoryDumpProvider() override {
+ if (enable_mock_destructor)
+ Destructor();
+ }
+
+ bool enable_mock_destructor;
+};
+
+class TestSequencedTaskRunner : public SequencedTaskRunner {
+ public:
+ TestSequencedTaskRunner() = default;
+
+ void set_enabled(bool value) { enabled_ = value; }
+ unsigned no_of_post_tasks() const { return num_of_post_tasks_; }
+
+ bool PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override {
+ NOTREACHED();
+ return false;
+ }
+
+ bool PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override {
+ num_of_post_tasks_++;
+ if (enabled_) {
+ return task_runner_->PostDelayedTask(from_here, std::move(task), delay);
+ }
+ return false;
+ }
+
+ bool RunsTasksInCurrentSequence() const override {
+ return task_runner_->RunsTasksInCurrentSequence();
+ }
+
+ private:
+ ~TestSequencedTaskRunner() override = default;
+
+ const scoped_refptr<SequencedTaskRunner> task_runner_ =
+ CreateSequencedTaskRunnerWithTraits({});
+ bool enabled_ = true;
+ unsigned num_of_post_tasks_ = 0;
+};
+
+class TestingThreadHeapUsageTracker : public debug::ThreadHeapUsageTracker {
+ public:
+ using ThreadHeapUsageTracker::DisableHeapTrackingForTesting;
+};
+
+} // namespace
+
+class MemoryDumpManagerTest : public testing::Test {
+ public:
+ MemoryDumpManagerTest(bool is_coordinator = false)
+ : is_coordinator_(is_coordinator) {}
+
+ void SetUp() override {
+ // Bring up and initialize MemoryDumpManager while single-threaded (before
+ // instantiating ScopedTaskEnvironment) to avoid data races if worker
+ // threads use tracing globals early.
+ mdm_ = MemoryDumpManager::CreateInstanceForTesting();
+ ASSERT_EQ(mdm_.get(), MemoryDumpManager::GetInstance());
+
+ InitializeMemoryDumpManagerForInProcessTesting(is_coordinator_);
+
+ scoped_task_environment_ = std::make_unique<test::ScopedTaskEnvironment>();
+ }
+
+ void TearDown() override {
+ scoped_task_environment_.reset();
+
+ // Tear down the MemoryDumpManager while single-threaded to mirror logic in
+ // SetUp().
+ mdm_.reset();
+ TraceLog::ResetForTesting();
+ }
+
+ protected:
+ // Blocks the current thread (spinning a nested message loop) until the
+ // memory dump is complete. Returns:
+ // - return value: the |success| from the CreateProcessDump() callback.
+ bool RequestProcessDumpAndWait(MemoryDumpType dump_type,
+ MemoryDumpLevelOfDetail level_of_detail) {
+ RunLoop run_loop;
+ bool success = false;
+ static uint64_t test_guid = 1;
+ test_guid++;
+ MemoryDumpRequestArgs request_args{test_guid, dump_type, level_of_detail};
+
+ // The signature of the callback delivered by MemoryDumpManager is:
+ // void ProcessMemoryDumpCallback(
+ // uint64_t dump_guid,
+ // bool success,
+ // std::unique_ptr<ProcessMemoryDump> pmd)
+ // The extra arguments prepended to the |callback| below (the ones with the
+ // "curried_" prefix) are just passed from the Bind(). This is just to get
+ // around the limitation of Bind() in supporting only capture-less lambdas.
+ ProcessMemoryDumpCallback callback = Bind(
+ [](bool* curried_success, Closure curried_quit_closure,
+ uint64_t curried_expected_guid, bool success, uint64_t dump_guid,
+ std::unique_ptr<ProcessMemoryDump> pmd) {
+ *curried_success = success;
+ EXPECT_EQ(curried_expected_guid, dump_guid);
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ curried_quit_closure);
+ },
+ Unretained(&success), run_loop.QuitClosure(), test_guid);
+
+ mdm_->CreateProcessDump(request_args, callback);
+ run_loop.Run();
+ return success;
+ }
+
+ void EnableForTracing() {
+ mdm_->SetupForTracing(TraceConfig::MemoryDumpConfig());
+ }
+
+ void EnableForTracingWithTraceConfig(const std::string trace_config_string) {
+ TraceConfig trace_config(trace_config_string);
+ mdm_->SetupForTracing(trace_config.memory_dump_config());
+ }
+
+ void DisableTracing() { mdm_->TeardownForTracing(); }
+
+ int GetMaxConsecutiveFailuresCount() const {
+ return MemoryDumpManager::kMaxConsecutiveFailuresCount;
+ }
+
+ const MemoryDumpProvider::Options kDefaultOptions;
+ std::unique_ptr<MemoryDumpManager> mdm_;
+
+ private:
+ // To tear down the singleton instance after each test.
+ ShadowingAtExitManager at_exit_manager_;
+
+ std::unique_ptr<test::ScopedTaskEnvironment> scoped_task_environment_;
+
+ // Whether the test MemoryDumpManager should be initialized as the
+ // coordinator.
+ const bool is_coordinator_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryDumpManagerTest);
+};
+
+class MemoryDumpManagerTestAsCoordinator : public MemoryDumpManagerTest {
+ public:
+ MemoryDumpManagerTestAsCoordinator() : MemoryDumpManagerTest(true) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MemoryDumpManagerTestAsCoordinator);
+};
+
+// Basic sanity checks. Registers a memory dump provider and checks that it is
+// called.
+TEST_F(MemoryDumpManagerTest, SingleDumper) {
+ MockMemoryDumpProvider mdp;
+ RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+
+ // Now repeat enabling the memory category and check that the dumper is
+ // invoked this time.
+ EnableForTracing();
+ EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(3);
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ }
+ DisableTracing();
+
+ mdm_->UnregisterDumpProvider(&mdp);
+
+ // Finally check the unregister logic: the global dump handler will be invoked
+ // but not the dump provider, as it has been unregistered.
+ EnableForTracing();
+ EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0);
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ }
+ DisableTracing();
+}
+
+// Checks that requesting dumps with high level of detail actually propagates
+// the level of the detail properly to OnMemoryDump() call on dump providers.
+TEST_F(MemoryDumpManagerTest, CheckMemoryDumpArgs) {
+ MockMemoryDumpProvider mdp;
+
+ RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+ EnableForTracing();
+ EXPECT_CALL(mdp, OnMemoryDump(IsDetailedDump(), _));
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ DisableTracing();
+ mdm_->UnregisterDumpProvider(&mdp);
+
+ // Check that requesting dumps with low level of detail actually propagates to
+ // OnMemoryDump() call on dump providers.
+ RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+ EnableForTracing();
+ EXPECT_CALL(mdp, OnMemoryDump(IsLightDump(), _));
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::LIGHT));
+ DisableTracing();
+ mdm_->UnregisterDumpProvider(&mdp);
+}
+
+// Checks that the (Un)RegisterDumpProvider logic behaves sanely.
+TEST_F(MemoryDumpManagerTest, MultipleDumpers) {
+ MockMemoryDumpProvider mdp1;
+ MockMemoryDumpProvider mdp2;
+
+ // Enable only mdp1.
+ RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get());
+ EnableForTracing();
+ EXPECT_CALL(mdp1, OnMemoryDump(_, _));
+ EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(0);
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ DisableTracing();
+
+ // Invert: enable mdp2 and disable mdp1.
+ mdm_->UnregisterDumpProvider(&mdp1);
+ RegisterDumpProvider(&mdp2, nullptr);
+ EnableForTracing();
+ EXPECT_CALL(mdp1, OnMemoryDump(_, _)).Times(0);
+ EXPECT_CALL(mdp2, OnMemoryDump(_, _));
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ DisableTracing();
+
+ // Enable both mdp1 and mdp2.
+ RegisterDumpProvider(&mdp1, nullptr);
+ EnableForTracing();
+ EXPECT_CALL(mdp1, OnMemoryDump(_, _));
+ EXPECT_CALL(mdp2, OnMemoryDump(_, _));
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ DisableTracing();
+}
+
+// Checks that the dump provider invocations depend only on the current
+// registration state and not on previous registrations and dumps.
+// Flaky on iOS, see crbug.com/706874
+#if defined(OS_IOS)
+#define MAYBE_RegistrationConsistency DISABLED_RegistrationConsistency
+#else
+#define MAYBE_RegistrationConsistency RegistrationConsistency
+#endif
+TEST_F(MemoryDumpManagerTest, MAYBE_RegistrationConsistency) {
+ MockMemoryDumpProvider mdp;
+
+ RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+
+ {
+ EXPECT_CALL(mdp, OnMemoryDump(_, _));
+ EnableForTracing();
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ DisableTracing();
+ }
+
+ mdm_->UnregisterDumpProvider(&mdp);
+
+ {
+ EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0);
+ EnableForTracing();
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ DisableTracing();
+ }
+
+ RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+ mdm_->UnregisterDumpProvider(&mdp);
+
+ {
+ EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0);
+ EnableForTracing();
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ DisableTracing();
+ }
+
+ RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+ mdm_->UnregisterDumpProvider(&mdp);
+ RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+
+ {
+ EXPECT_CALL(mdp, OnMemoryDump(_, _));
+ EnableForTracing();
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ DisableTracing();
+ }
+}
+
+// Checks that the MemoryDumpManager respects the thread affinity when a
+// MemoryDumpProvider specifies a task_runner(). The test starts creating 8
+// threads and registering a MemoryDumpProvider on each of them. At each
+// iteration, one thread is removed, to check the live unregistration logic.
+TEST_F(MemoryDumpManagerTest, RespectTaskRunnerAffinity) {
+ const uint32_t kNumInitialThreads = 8;
+
+ std::vector<std::unique_ptr<Thread>> threads;
+ std::vector<std::unique_ptr<MockMemoryDumpProvider>> mdps;
+
+ // Create the threads and setup the expectations. Given that at each iteration
+ // we will pop out one thread/MemoryDumpProvider, each MDP is supposed to be
+ // invoked a number of times equal to its index.
+ for (uint32_t i = kNumInitialThreads; i > 0; --i) {
+ threads.push_back(WrapUnique(new Thread("test thread")));
+ auto* thread = threads.back().get();
+ thread->Start();
+ scoped_refptr<SingleThreadTaskRunner> task_runner = thread->task_runner();
+ mdps.push_back(WrapUnique(new MockMemoryDumpProvider()));
+ auto* mdp = mdps.back().get();
+ RegisterDumpProvider(mdp, task_runner, kDefaultOptions);
+ EXPECT_CALL(*mdp, OnMemoryDump(_, _))
+ .Times(i)
+ .WillRepeatedly(Invoke(
+ [task_runner](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool {
+ EXPECT_TRUE(task_runner->RunsTasksInCurrentSequence());
+ return true;
+ }));
+ }
+ EnableForTracing();
+
+ while (!threads.empty()) {
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+
+ // Unregister a MDP and destroy one thread at each iteration to check the
+ // live unregistration logic. The unregistration needs to happen on the same
+ // thread the MDP belongs to.
+ {
+ RunLoop run_loop;
+ Closure unregistration =
+ Bind(&MemoryDumpManager::UnregisterDumpProvider,
+ Unretained(mdm_.get()), Unretained(mdps.back().get()));
+ threads.back()->task_runner()->PostTaskAndReply(FROM_HERE, unregistration,
+ run_loop.QuitClosure());
+ run_loop.Run();
+ }
+ mdps.pop_back();
+ threads.back()->Stop();
+ threads.pop_back();
+ }
+
+ DisableTracing();
+}
+
+// Check that the memory dump calls are always posted on task runner for
+// SequencedTaskRunner case and that the dump provider gets disabled when
+// PostTask fails, but the dump still succeeds.
+TEST_F(MemoryDumpManagerTest, PostTaskForSequencedTaskRunner) {
+ std::vector<MockMemoryDumpProvider> mdps(3);
+ scoped_refptr<TestSequencedTaskRunner> task_runner1(
+ MakeRefCounted<TestSequencedTaskRunner>());
+ scoped_refptr<TestSequencedTaskRunner> task_runner2(
+ MakeRefCounted<TestSequencedTaskRunner>());
+ RegisterDumpProviderWithSequencedTaskRunner(&mdps[0], task_runner1,
+ kDefaultOptions);
+ RegisterDumpProviderWithSequencedTaskRunner(&mdps[1], task_runner2,
+ kDefaultOptions);
+ RegisterDumpProviderWithSequencedTaskRunner(&mdps[2], task_runner2,
+ kDefaultOptions);
+ // |mdps[0]| should be disabled permanently after first dump.
+ EXPECT_CALL(mdps[0], OnMemoryDump(_, _)).Times(0);
+ EXPECT_CALL(mdps[1], OnMemoryDump(_, _)).Times(2);
+ EXPECT_CALL(mdps[2], OnMemoryDump(_, _)).Times(2);
+
+ EnableForTracing();
+
+ task_runner1->set_enabled(false);
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ EXPECT_EQ(1u, task_runner1->no_of_post_tasks());
+ EXPECT_EQ(1u, task_runner2->no_of_post_tasks());
+
+ task_runner1->set_enabled(true);
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ EXPECT_EQ(2u, task_runner1->no_of_post_tasks());
+ EXPECT_EQ(2u, task_runner2->no_of_post_tasks());
+ DisableTracing();
+}
+
+// Checks that providers get disabled after 3 consecutive failures, but not
+// otherwise (e.g., if interleaved).
+TEST_F(MemoryDumpManagerTest, DisableFailingDumpers) {
+ MockMemoryDumpProvider mdp1;
+ MockMemoryDumpProvider mdp2;
+
+ RegisterDumpProvider(&mdp1, nullptr);
+ RegisterDumpProvider(&mdp2, nullptr);
+ EnableForTracing();
+
+ EXPECT_CALL(mdp1, OnMemoryDump(_, _))
+ .Times(GetMaxConsecutiveFailuresCount())
+ .WillRepeatedly(Return(false));
+
+ EXPECT_CALL(mdp2, OnMemoryDump(_, _))
+ .WillOnce(Return(false))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+
+ const int kNumDumps = 2 * GetMaxConsecutiveFailuresCount();
+ for (int i = 0; i < kNumDumps; i++) {
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ }
+
+ DisableTracing();
+}
+
+// Sneakily registers an extra memory dump provider while an existing one is
+// dumping and expect it to take part in the already active tracing session.
+TEST_F(MemoryDumpManagerTest, RegisterDumperWhileDumping) {
+ MockMemoryDumpProvider mdp1;
+ MockMemoryDumpProvider mdp2;
+
+ RegisterDumpProvider(&mdp1, nullptr);
+ EnableForTracing();
+
+ EXPECT_CALL(mdp1, OnMemoryDump(_, _))
+ .Times(4)
+ .WillOnce(Return(true))
+ .WillOnce(
+ Invoke([&mdp2](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool {
+ RegisterDumpProvider(&mdp2, nullptr);
+ return true;
+ }))
+ .WillRepeatedly(Return(true));
+
+ // Depending on the insertion order (before or after mdp1), mdp2 might be
+ // called also immediately after it gets registered.
+ EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(Between(2, 3));
+
+ for (int i = 0; i < 4; i++) {
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ }
+
+ DisableTracing();
+}
+
+// Like RegisterDumperWhileDumping, but unregister the dump provider instead.
+TEST_F(MemoryDumpManagerTest, UnregisterDumperWhileDumping) {
+ MockMemoryDumpProvider mdp1;
+ MockMemoryDumpProvider mdp2;
+
+ RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get(), kDefaultOptions);
+ RegisterDumpProvider(&mdp2, ThreadTaskRunnerHandle::Get(), kDefaultOptions);
+ EnableForTracing();
+
+ EXPECT_CALL(mdp1, OnMemoryDump(_, _))
+ .Times(4)
+ .WillOnce(Return(true))
+ .WillOnce(
+ Invoke([&mdp2](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool {
+ MemoryDumpManager::GetInstance()->UnregisterDumpProvider(&mdp2);
+ return true;
+ }))
+ .WillRepeatedly(Return(true));
+
+ // Depending on the insertion order (before or after mdp1), mdp2 might have
+ // been already called when UnregisterDumpProvider happens.
+ EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(Between(1, 2));
+
+ for (int i = 0; i < 4; i++) {
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ }
+
+ DisableTracing();
+}
+
+// Checks that the dump does not abort when unregistering a provider while
+// dumping from a different thread than the dumping thread.
+TEST_F(MemoryDumpManagerTest, UnregisterDumperFromThreadWhileDumping) {
+ std::vector<std::unique_ptr<TestIOThread>> threads;
+ std::vector<std::unique_ptr<MockMemoryDumpProvider>> mdps;
+
+ for (int i = 0; i < 2; i++) {
+ threads.push_back(
+ WrapUnique(new TestIOThread(TestIOThread::kAutoStart)));
+ mdps.push_back(WrapUnique(new MockMemoryDumpProvider()));
+ RegisterDumpProvider(mdps.back().get(), threads.back()->task_runner(),
+ kDefaultOptions);
+ }
+
+ int on_memory_dump_call_count = 0;
+
+ // When OnMemoryDump is called on either of the dump providers, it will
+ // unregister the other one.
+ for (const std::unique_ptr<MockMemoryDumpProvider>& mdp : mdps) {
+ int other_idx = (mdps.front() == mdp);
+ // TestIOThread's task runner must be obtained from the main thread but can
+ // then be used from other threads.
+ scoped_refptr<SingleThreadTaskRunner> other_runner =
+ threads[other_idx]->task_runner();
+ MockMemoryDumpProvider* other_mdp = mdps[other_idx].get();
+ auto on_dump = [this, other_runner, other_mdp, &on_memory_dump_call_count](
+ const MemoryDumpArgs& args, ProcessMemoryDump* pmd) {
+ PostTaskAndWait(FROM_HERE, other_runner.get(),
+ base::BindOnce(&MemoryDumpManager::UnregisterDumpProvider,
+ base::Unretained(&*mdm_), other_mdp));
+ on_memory_dump_call_count++;
+ return true;
+ };
+
+ // OnMemoryDump is called once for the provider that dumps first, and zero
+ // times for the other provider.
+ EXPECT_CALL(*mdp, OnMemoryDump(_, _))
+ .Times(AtMost(1))
+ .WillOnce(Invoke(on_dump));
+ }
+
+ EnableForTracing();
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ ASSERT_EQ(1, on_memory_dump_call_count);
+
+ DisableTracing();
+}
+
+// If a thread (with a dump provider living on it) is torn down during a dump
+// its dump provider should be skipped but the dump itself should succeed.
+TEST_F(MemoryDumpManagerTest, TearDownThreadWhileDumping) {
+ std::vector<std::unique_ptr<TestIOThread>> threads;
+ std::vector<std::unique_ptr<MockMemoryDumpProvider>> mdps;
+
+ for (int i = 0; i < 2; i++) {
+ threads.push_back(
+ WrapUnique(new TestIOThread(TestIOThread::kAutoStart)));
+ mdps.push_back(WrapUnique(new MockMemoryDumpProvider()));
+ RegisterDumpProvider(mdps.back().get(), threads.back()->task_runner(),
+ kDefaultOptions);
+ }
+
+ int on_memory_dump_call_count = 0;
+
+ // When OnMemoryDump is called on either of the dump providers, it will
+ // tear down the thread of the other one.
+ for (const std::unique_ptr<MockMemoryDumpProvider>& mdp : mdps) {
+ int other_idx = (mdps.front() == mdp);
+ TestIOThread* other_thread = threads[other_idx].get();
+ // TestIOThread isn't thread-safe and must be stopped on the |main_runner|.
+ scoped_refptr<SequencedTaskRunner> main_runner =
+ SequencedTaskRunnerHandle::Get();
+ auto on_dump = [other_thread, main_runner, &on_memory_dump_call_count](
+ const MemoryDumpArgs& args, ProcessMemoryDump* pmd) {
+ PostTaskAndWait(
+ FROM_HERE, main_runner.get(),
+ base::BindOnce(&TestIOThread::Stop, base::Unretained(other_thread)));
+ on_memory_dump_call_count++;
+ return true;
+ };
+
+ // OnMemoryDump is called once for the provider that dumps first, and zero
+ // times for the other provider.
+ EXPECT_CALL(*mdp, OnMemoryDump(_, _))
+ .Times(AtMost(1))
+ .WillOnce(Invoke(on_dump));
+ }
+
+ EnableForTracing();
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ ASSERT_EQ(1, on_memory_dump_call_count);
+
+ DisableTracing();
+}
+
+// Checks that the callback is invoked if CreateProcessDump() is called when
+// tracing is not enabled.
+TEST_F(MemoryDumpManagerTest, TriggerDumpWithoutTracing) {
+ MockMemoryDumpProvider mdp;
+ RegisterDumpProvider(&mdp, nullptr);
+ EXPECT_CALL(mdp, OnMemoryDump(_, _));
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+}
+
+TEST_F(MemoryDumpManagerTest, BackgroundWhitelisting) {
+ SetDumpProviderWhitelistForTesting(kTestMDPWhitelist);
+
+ // Standard provider with default options (create dump for current process).
+ MockMemoryDumpProvider backgroundMdp;
+ RegisterDumpProvider(&backgroundMdp, nullptr, kDefaultOptions,
+ kWhitelistedMDPName);
+
+ EnableForTracing();
+
+ EXPECT_CALL(backgroundMdp, OnMemoryDump(_, _)).Times(1);
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::SUMMARY_ONLY,
+ MemoryDumpLevelOfDetail::BACKGROUND));
+ DisableTracing();
+}
+
+// Tests the basics of the UnregisterAndDeleteDumpProviderSoon(): the
+// unregistration should actually delete the providers and not leak them.
+TEST_F(MemoryDumpManagerTest, UnregisterAndDeleteDumpProviderSoon) {
+ static const int kNumProviders = 3;
+ int dtor_count = 0;
+ std::vector<std::unique_ptr<MemoryDumpProvider>> mdps;
+ for (int i = 0; i < kNumProviders; ++i) {
+ std::unique_ptr<MockMemoryDumpProvider> mdp(new MockMemoryDumpProvider);
+ mdp->enable_mock_destructor = true;
+ EXPECT_CALL(*mdp, Destructor())
+ .WillOnce(Invoke([&dtor_count]() { dtor_count++; }));
+ RegisterDumpProvider(mdp.get(), nullptr, kDefaultOptions);
+ mdps.push_back(std::move(mdp));
+ }
+
+ while (!mdps.empty()) {
+ mdm_->UnregisterAndDeleteDumpProviderSoon(std::move(mdps.back()));
+ mdps.pop_back();
+ }
+
+ ASSERT_EQ(kNumProviders, dtor_count);
+}
+
+// This test checks against races when unregistering an unbound dump provider
+// from another thread while dumping. It registers one MDP and, when
+// OnMemoryDump() is called, it invokes UnregisterAndDeleteDumpProviderSoon()
+// from another thread. The OnMemoryDump() and the dtor call are expected to
+// happen on the same thread (the MemoryDumpManager utility thread).
+TEST_F(MemoryDumpManagerTest, UnregisterAndDeleteDumpProviderSoonDuringDump) {
+ std::unique_ptr<MockMemoryDumpProvider> mdp(new MockMemoryDumpProvider);
+ mdp->enable_mock_destructor = true;
+ RegisterDumpProvider(mdp.get(), nullptr, kDefaultOptions);
+
+ base::PlatformThreadRef thread_ref;
+ auto self_unregister_from_another_thread = [&mdp, &thread_ref](
+ const MemoryDumpArgs&, ProcessMemoryDump*) -> bool {
+ thread_ref = PlatformThread::CurrentRef();
+ TestIOThread thread_for_unregistration(TestIOThread::kAutoStart);
+ PostTaskAndWait(
+ FROM_HERE, thread_for_unregistration.task_runner().get(),
+ base::BindOnce(&MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon,
+ base::Unretained(MemoryDumpManager::GetInstance()),
+ std::move(mdp)));
+ thread_for_unregistration.Stop();
+ return true;
+ };
+ EXPECT_CALL(*mdp, OnMemoryDump(_, _))
+ .Times(1)
+ .WillOnce(Invoke(self_unregister_from_another_thread));
+ EXPECT_CALL(*mdp, Destructor())
+ .Times(1)
+ .WillOnce(Invoke([&thread_ref]() {
+ EXPECT_EQ(thread_ref, PlatformThread::CurrentRef());
+ }));
+
+ EnableForTracing();
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ }
+ DisableTracing();
+}
+
+// Mock MDP class that tests if the number of OnMemoryDump() calls are expected.
+// It is implemented without gmocks since EXPECT_CALL implementation is slow
+// when there are 1000s of instances, as required in
+// NoStackOverflowWithTooManyMDPs test.
+class SimpleMockMemoryDumpProvider : public MemoryDumpProvider {
+ public:
+ SimpleMockMemoryDumpProvider(int expected_num_dump_calls)
+ : expected_num_dump_calls_(expected_num_dump_calls), num_dump_calls_(0) {}
+
+ ~SimpleMockMemoryDumpProvider() override {
+ EXPECT_EQ(expected_num_dump_calls_, num_dump_calls_);
+ }
+
+ bool OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) override {
+ ++num_dump_calls_;
+ return true;
+ }
+
+ private:
+ int expected_num_dump_calls_;
+ int num_dump_calls_;
+};
+
+TEST_F(MemoryDumpManagerTest, NoStackOverflowWithTooManyMDPs) {
+ SetDumpProviderWhitelistForTesting(kTestMDPWhitelist);
+
+ int kMDPCount = 1000;
+ std::vector<std::unique_ptr<SimpleMockMemoryDumpProvider>> mdps;
+ for (int i = 0; i < kMDPCount; ++i) {
+ mdps.push_back(std::make_unique<SimpleMockMemoryDumpProvider>(1));
+ RegisterDumpProvider(mdps.back().get(), nullptr);
+ }
+ for (int i = 0; i < kMDPCount; ++i) {
+ mdps.push_back(std::make_unique<SimpleMockMemoryDumpProvider>(3));
+ RegisterDumpProvider(mdps.back().get(), nullptr, kDefaultOptions,
+ kWhitelistedMDPName);
+ }
+ std::unique_ptr<Thread> stopped_thread(new Thread("test thread"));
+ stopped_thread->Start();
+ for (int i = 0; i < kMDPCount; ++i) {
+ mdps.push_back(std::make_unique<SimpleMockMemoryDumpProvider>(0));
+ RegisterDumpProvider(mdps.back().get(), stopped_thread->task_runner(),
+ kDefaultOptions, kWhitelistedMDPName);
+ }
+ stopped_thread->Stop();
+
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::DETAILED));
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ MemoryDumpLevelOfDetail::BACKGROUND));
+ EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::SUMMARY_ONLY,
+ MemoryDumpLevelOfDetail::BACKGROUND));
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_dump_provider.h b/base/trace_event/memory_dump_provider.h
new file mode 100644
index 0000000000..f55e2cf471
--- /dev/null
+++ b/base/trace_event/memory_dump_provider.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_H_
+#define BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_H_
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+#include "base/trace_event/memory_dump_request_args.h"
+
+namespace base {
+namespace trace_event {
+
+class ProcessMemoryDump;
+
+// The contract interface that memory dump providers must implement.
+class BASE_EXPORT MemoryDumpProvider {
+ public:
+ // Optional arguments for MemoryDumpManager::RegisterDumpProvider().
+ struct Options {
+ Options() : dumps_on_single_thread_task_runner(false) {}
+
+ // |dumps_on_single_thread_task_runner| is true if the dump provider runs on
+ // a SingleThreadTaskRunner, which is usually the case. It is faster to run
+ // all providers that run on the same thread together without thread hops.
+ bool dumps_on_single_thread_task_runner;
+ };
+
+ virtual ~MemoryDumpProvider() = default;
+
+ // Called by the MemoryDumpManager when generating memory dumps.
+ // The |args| specify if the embedder should generate light/heavy dumps on
+ // dump requests. The embedder should return true if the |pmd| was
+ // successfully populated, false if something went wrong and the dump should
+ // be considered invalid.
+ // (Note, the MemoryDumpManager has a fail-safe logic which will disable the
+ // MemoryDumpProvider for the entire trace session if it fails consistently).
+ virtual bool OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) = 0;
+
+ protected:
+ MemoryDumpProvider() = default;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryDumpProvider);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_H_
diff --git a/base/trace_event/memory_dump_provider_info.cc b/base/trace_event/memory_dump_provider_info.cc
new file mode 100644
index 0000000000..3220476cf9
--- /dev/null
+++ b/base/trace_event/memory_dump_provider_info.cc
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_dump_provider_info.h"
+
+#include <tuple>
+
+#include "base/sequenced_task_runner.h"
+
+namespace base {
+namespace trace_event {
+
+MemoryDumpProviderInfo::MemoryDumpProviderInfo(
+ MemoryDumpProvider* dump_provider,
+ const char* name,
+ scoped_refptr<SequencedTaskRunner> task_runner,
+ const MemoryDumpProvider::Options& options,
+ bool whitelisted_for_background_mode)
+ : dump_provider(dump_provider),
+ options(options),
+ name(name),
+ task_runner(std::move(task_runner)),
+ whitelisted_for_background_mode(whitelisted_for_background_mode),
+ consecutive_failures(0),
+ disabled(false) {}
+
+MemoryDumpProviderInfo::~MemoryDumpProviderInfo() = default;
+
+bool MemoryDumpProviderInfo::Comparator::operator()(
+ const scoped_refptr<MemoryDumpProviderInfo>& a,
+ const scoped_refptr<MemoryDumpProviderInfo>& b) const {
+ if (!a || !b)
+ return a.get() < b.get();
+ // Ensure that unbound providers (task_runner == nullptr) always run last.
+ // Rationale: some unbound dump providers are known to be slow, keep them last
+ // to avoid skewing timings of the other dump providers.
+ return std::tie(a->task_runner, a->dump_provider) >
+ std::tie(b->task_runner, b->dump_provider);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_dump_provider_info.h b/base/trace_event/memory_dump_provider_info.h
new file mode 100644
index 0000000000..f0ea1e6bbc
--- /dev/null
+++ b/base/trace_event/memory_dump_provider_info.h
@@ -0,0 +1,108 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_INFO_H_
+#define BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_INFO_H_
+
+#include <memory>
+#include <set>
+
+#include "base/base_export.h"
+#include "base/memory/ref_counted.h"
+#include "base/trace_event/memory_dump_provider.h"
+
+namespace base {
+
+class SequencedTaskRunner;
+
+namespace trace_event {
+
+// Wraps a MemoryDumpProvider (MDP), which is registered via
+// MemoryDumpManager(MDM)::RegisterDumpProvider(), holding the extra information
+// required to deal with it (which task runner it should be invoked onto,
+// whether it has been disabled, etc.)
+// More importantly, having a refptr to this object guarantees that a MDP that
+// is not thread-bound (hence which can only be unregistered via
+// MDM::UnregisterAndDeleteDumpProviderSoon()) will stay alive as long as the
+// refptr is held.
+//
+// Lifetime:
+// At any time, there is at most one instance of this class for each instance
+// of a given MemoryDumpProvider, but there might be several scoped_refptr
+// holding onto each of this. Specifically:
+// - In nominal conditions, there is a refptr for each registered MDP in the
+// MDM's |dump_providers_| list.
+// - In most cases, the only refptr (in the |dump_providers_| list) is destroyed
+// by MDM::UnregisterDumpProvider().
+// - However, when MDM starts a dump, the list of refptrs is copied into the
+// ProcessMemoryDumpAsyncState. That list is pruned as MDP(s) are invoked.
+// - If UnregisterDumpProvider() is called on a non-thread-bound MDP while a
+// dump is in progress, the extar extra of the handle is destroyed in
+// MDM::SetupNextMemoryDump() or MDM::InvokeOnMemoryDump(), when the copy
+// inside ProcessMemoryDumpAsyncState is erase()-d.
+// - The PeakDetector can keep extra refptrs when enabled.
+struct BASE_EXPORT MemoryDumpProviderInfo
+ : public RefCountedThreadSafe<MemoryDumpProviderInfo> {
+ public:
+ // Define a total order based on the |task_runner| affinity, so that MDPs
+ // belonging to the same SequencedTaskRunner are adjacent in the set.
+ struct Comparator {
+ bool operator()(const scoped_refptr<MemoryDumpProviderInfo>& a,
+ const scoped_refptr<MemoryDumpProviderInfo>& b) const;
+ };
+ using OrderedSet =
+ std::set<scoped_refptr<MemoryDumpProviderInfo>, Comparator>;
+
+ MemoryDumpProviderInfo(MemoryDumpProvider* dump_provider,
+ const char* name,
+ scoped_refptr<SequencedTaskRunner> task_runner,
+ const MemoryDumpProvider::Options& options,
+ bool whitelisted_for_background_mode);
+
+ // It is safe to access the const fields below from any thread as they are
+ // never mutated.
+
+ MemoryDumpProvider* const dump_provider;
+
+ // The |options| arg passed to MDM::RegisterDumpProvider().
+ const MemoryDumpProvider::Options options;
+
+ // Human readable name, not unique (distinct MDP instances might have the same
+ // name). Used for debugging, testing and whitelisting for BACKGROUND mode.
+ const char* const name;
+
+ // The task runner on which the MDP::OnMemoryDump call should be posted onto.
+ // Can be nullptr, in which case the MDP will be invoked on a background
+ // thread handled by MDM.
+ const scoped_refptr<SequencedTaskRunner> task_runner;
+
+ // True if the dump provider is whitelisted for background mode.
+ const bool whitelisted_for_background_mode;
+
+ // These fields below, instead, are not thread safe and can be mutated only:
+ // - On the |task_runner|, when not null (i.e. for thread-bound MDPS).
+ // - By the MDM's background thread (or in any other way that guarantees
+ // sequencing) for non-thread-bound MDPs.
+
+ // Used to transfer ownership for UnregisterAndDeleteDumpProviderSoon().
+ // nullptr in all other cases.
+ std::unique_ptr<MemoryDumpProvider> owned_dump_provider;
+
+ // For fail-safe logic (auto-disable failing MDPs).
+ int consecutive_failures;
+
+ // Flagged either by the auto-disable logic or during unregistration.
+ bool disabled;
+
+ private:
+ friend class base::RefCountedThreadSafe<MemoryDumpProviderInfo>;
+ ~MemoryDumpProviderInfo();
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryDumpProviderInfo);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_DUMP_PROVIDER_INFO_H_
diff --git a/base/trace_event/memory_dump_request_args.cc b/base/trace_event/memory_dump_request_args.cc
new file mode 100644
index 0000000000..8be3c32404
--- /dev/null
+++ b/base/trace_event/memory_dump_request_args.cc
@@ -0,0 +1,64 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_dump_request_args.h"
+
+#include "base/logging.h"
+
+namespace base {
+namespace trace_event {
+
+// static
+const char* MemoryDumpTypeToString(const MemoryDumpType& dump_type) {
+ switch (dump_type) {
+ case MemoryDumpType::PERIODIC_INTERVAL:
+ return "periodic_interval";
+ case MemoryDumpType::EXPLICITLY_TRIGGERED:
+ return "explicitly_triggered";
+ case MemoryDumpType::SUMMARY_ONLY:
+ return "summary_only";
+ }
+ NOTREACHED();
+ return "unknown";
+}
+
+MemoryDumpType StringToMemoryDumpType(const std::string& str) {
+ if (str == "periodic_interval")
+ return MemoryDumpType::PERIODIC_INTERVAL;
+ if (str == "explicitly_triggered")
+ return MemoryDumpType::EXPLICITLY_TRIGGERED;
+ if (str == "summary_only")
+ return MemoryDumpType::SUMMARY_ONLY;
+ NOTREACHED();
+ return MemoryDumpType::LAST;
+}
+
+const char* MemoryDumpLevelOfDetailToString(
+ const MemoryDumpLevelOfDetail& level_of_detail) {
+ switch (level_of_detail) {
+ case MemoryDumpLevelOfDetail::BACKGROUND:
+ return "background";
+ case MemoryDumpLevelOfDetail::LIGHT:
+ return "light";
+ case MemoryDumpLevelOfDetail::DETAILED:
+ return "detailed";
+ }
+ NOTREACHED();
+ return "unknown";
+}
+
+MemoryDumpLevelOfDetail StringToMemoryDumpLevelOfDetail(
+ const std::string& str) {
+ if (str == "background")
+ return MemoryDumpLevelOfDetail::BACKGROUND;
+ if (str == "light")
+ return MemoryDumpLevelOfDetail::LIGHT;
+ if (str == "detailed")
+ return MemoryDumpLevelOfDetail::DETAILED;
+ NOTREACHED();
+ return MemoryDumpLevelOfDetail::LAST;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_dump_request_args.h b/base/trace_event/memory_dump_request_args.h
new file mode 100644
index 0000000000..f854a4b373
--- /dev/null
+++ b/base/trace_event/memory_dump_request_args.h
@@ -0,0 +1,101 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_REQUEST_ARGS_H_
+#define BASE_TRACE_EVENT_MEMORY_DUMP_REQUEST_ARGS_H_
+
+// This file defines the types and structs used to issue memory dump requests.
+// These are also used in the IPCs for coordinating inter-process memory dumps.
+
+#include <stdint.h>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/optional.h"
+#include "base/process/process_handle.h"
+
+namespace base {
+namespace trace_event {
+
+class ProcessMemoryDump;
+
+// Captures the reason why a memory dump is being requested. This is to allow
+// selective enabling of dumps, filtering and post-processing. Keep this
+// consistent with memory_instrumentation.mojo and
+// memory_instrumentation_struct_traits.{h,cc}
+enum class MemoryDumpType {
+ PERIODIC_INTERVAL, // Dumping memory at periodic intervals.
+ EXPLICITLY_TRIGGERED, // Non maskable dump request.
+ SUMMARY_ONLY, // Calculate just the summary & don't add to the trace.
+ LAST = SUMMARY_ONLY
+};
+
+// Tells the MemoryDumpProvider(s) how much detailed their dumps should be.
+// Keep this consistent with memory_instrumentation.mojo and
+// memory_instrumentation_struct_traits.{h,cc}
+enum class MemoryDumpLevelOfDetail : uint32_t {
+ FIRST,
+
+ // For background tracing mode. The dump time is quick, and typically just the
+ // totals are expected. Suballocations need not be specified. Dump name must
+ // contain only pre-defined strings and string arguments cannot be added.
+ BACKGROUND = FIRST,
+
+ // For the levels below, MemoryDumpProvider instances must guarantee that the
+ // total size reported in the root node is consistent. Only the granularity of
+ // the child MemoryAllocatorDump(s) differs with the levels.
+
+ // Few entries, typically a fixed number, per dump.
+ LIGHT,
+
+ // Unrestricted amount of entries per dump.
+ DETAILED,
+
+ LAST = DETAILED
+};
+
+// Keep this consistent with memory_instrumentation.mojo and
+// memory_instrumentation_struct_traits.{h,cc}
+struct BASE_EXPORT MemoryDumpRequestArgs {
+ // Globally unique identifier. In multi-process dumps, all processes issue a
+ // local dump with the same guid. This allows the trace importers to
+ // reconstruct the global dump.
+ uint64_t dump_guid;
+
+ MemoryDumpType dump_type;
+ MemoryDumpLevelOfDetail level_of_detail;
+};
+
+// Args for ProcessMemoryDump and passed to OnMemoryDump calls for memory dump
+// providers. Dump providers are expected to read the args for creating dumps.
+struct MemoryDumpArgs {
+ // Specifies how detailed the dumps should be.
+ MemoryDumpLevelOfDetail level_of_detail;
+
+ // Globally unique identifier. In multi-process dumps, all processes issue a
+ // local dump with the same guid. This allows the trace importers to
+ // reconstruct the global dump.
+ uint64_t dump_guid;
+};
+
+using ProcessMemoryDumpCallback = Callback<
+ void(bool success, uint64_t dump_guid, std::unique_ptr<ProcessMemoryDump>)>;
+
+BASE_EXPORT const char* MemoryDumpTypeToString(const MemoryDumpType& dump_type);
+
+BASE_EXPORT MemoryDumpType StringToMemoryDumpType(const std::string& str);
+
+BASE_EXPORT const char* MemoryDumpLevelOfDetailToString(
+ const MemoryDumpLevelOfDetail& level_of_detail);
+
+BASE_EXPORT MemoryDumpLevelOfDetail
+StringToMemoryDumpLevelOfDetail(const std::string& str);
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_DUMP_REQUEST_ARGS_H_
diff --git a/base/trace_event/memory_dump_scheduler.cc b/base/trace_event/memory_dump_scheduler.cc
new file mode 100644
index 0000000000..8b03f5c90b
--- /dev/null
+++ b/base/trace_event/memory_dump_scheduler.cc
@@ -0,0 +1,118 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_dump_scheduler.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+
+namespace base {
+namespace trace_event {
+
+// static
+MemoryDumpScheduler* MemoryDumpScheduler::GetInstance() {
+ static MemoryDumpScheduler* instance = new MemoryDumpScheduler();
+ return instance;
+}
+
+MemoryDumpScheduler::MemoryDumpScheduler() : period_ms_(0), generation_(0) {}
+MemoryDumpScheduler::~MemoryDumpScheduler() {
+ // Hit only in tests. Check that tests don't leave without stopping.
+ DCHECK(!is_enabled_for_testing());
+}
+
+void MemoryDumpScheduler::Start(
+ MemoryDumpScheduler::Config config,
+ scoped_refptr<SequencedTaskRunner> task_runner) {
+ DCHECK(!task_runner_);
+ task_runner_ = task_runner;
+ task_runner->PostTask(FROM_HERE, BindOnce(&MemoryDumpScheduler::StartInternal,
+ Unretained(this), config));
+}
+
+void MemoryDumpScheduler::Stop() {
+ if (!task_runner_)
+ return;
+ task_runner_->PostTask(FROM_HERE, BindOnce(&MemoryDumpScheduler::StopInternal,
+ Unretained(this)));
+ task_runner_ = nullptr;
+}
+
+void MemoryDumpScheduler::StartInternal(MemoryDumpScheduler::Config config) {
+ uint32_t light_dump_period_ms = 0;
+ uint32_t heavy_dump_period_ms = 0;
+ uint32_t min_period_ms = std::numeric_limits<uint32_t>::max();
+ for (const Config::Trigger& trigger : config.triggers) {
+ DCHECK_GT(trigger.period_ms, 0u);
+ switch (trigger.level_of_detail) {
+ case MemoryDumpLevelOfDetail::BACKGROUND:
+ break;
+ case MemoryDumpLevelOfDetail::LIGHT:
+ DCHECK_EQ(0u, light_dump_period_ms);
+ light_dump_period_ms = trigger.period_ms;
+ break;
+ case MemoryDumpLevelOfDetail::DETAILED:
+ DCHECK_EQ(0u, heavy_dump_period_ms);
+ heavy_dump_period_ms = trigger.period_ms;
+ break;
+ }
+ min_period_ms = std::min(min_period_ms, trigger.period_ms);
+ }
+
+ DCHECK_EQ(0u, light_dump_period_ms % min_period_ms);
+ DCHECK_EQ(0u, heavy_dump_period_ms % min_period_ms);
+ DCHECK(!config.callback.is_null());
+ callback_ = config.callback;
+ period_ms_ = min_period_ms;
+ tick_count_ = 0;
+ light_dump_rate_ = light_dump_period_ms / min_period_ms;
+ heavy_dump_rate_ = heavy_dump_period_ms / min_period_ms;
+
+ // Trigger the first dump after 200ms.
+ // TODO(lalitm): this is a tempoarary hack to delay the first scheduled dump
+ // so that the child processes get tracing enabled notification via IPC.
+ // See crbug.com/770151.
+ SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&MemoryDumpScheduler::Tick, Unretained(this), ++generation_),
+ TimeDelta::FromMilliseconds(200));
+}
+
+void MemoryDumpScheduler::StopInternal() {
+ period_ms_ = 0;
+ generation_++;
+ callback_.Reset();
+}
+
+void MemoryDumpScheduler::Tick(uint32_t expected_generation) {
+ if (period_ms_ == 0 || generation_ != expected_generation)
+ return;
+
+ MemoryDumpLevelOfDetail level_of_detail = MemoryDumpLevelOfDetail::BACKGROUND;
+ if (light_dump_rate_ > 0 && tick_count_ % light_dump_rate_ == 0)
+ level_of_detail = MemoryDumpLevelOfDetail::LIGHT;
+ if (heavy_dump_rate_ > 0 && tick_count_ % heavy_dump_rate_ == 0)
+ level_of_detail = MemoryDumpLevelOfDetail::DETAILED;
+ tick_count_++;
+
+ callback_.Run(level_of_detail);
+
+ SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&MemoryDumpScheduler::Tick, Unretained(this),
+ expected_generation),
+ TimeDelta::FromMilliseconds(period_ms_));
+}
+
+MemoryDumpScheduler::Config::Config() = default;
+MemoryDumpScheduler::Config::~Config() = default;
+MemoryDumpScheduler::Config::Config(const MemoryDumpScheduler::Config&) =
+ default;
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_dump_scheduler.h b/base/trace_event/memory_dump_scheduler.h
new file mode 100644
index 0000000000..21334f0edd
--- /dev/null
+++ b/base/trace_event/memory_dump_scheduler.h
@@ -0,0 +1,76 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_DUMP_SCHEDULER_H
+#define BASE_TRACE_EVENT_MEMORY_DUMP_SCHEDULER_H
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/trace_event/memory_dump_request_args.h"
+
+namespace base {
+class SequencedTaskRunner;
+
+namespace trace_event {
+
+// Schedules global dump requests based on the triggers added. The methods of
+// this class are NOT thread safe and the client has to take care of invoking
+// all the methods of the class safely.
+class BASE_EXPORT MemoryDumpScheduler {
+ public:
+ using PeriodicCallback = RepeatingCallback<void(MemoryDumpLevelOfDetail)>;
+
+ // Passed to Start().
+ struct BASE_EXPORT Config {
+ struct Trigger {
+ MemoryDumpLevelOfDetail level_of_detail;
+ uint32_t period_ms;
+ };
+
+ Config();
+ Config(const Config&);
+ ~Config();
+
+ std::vector<Trigger> triggers;
+ PeriodicCallback callback;
+ };
+
+ static MemoryDumpScheduler* GetInstance();
+
+ void Start(Config, scoped_refptr<SequencedTaskRunner> task_runner);
+ void Stop();
+ bool is_enabled_for_testing() const { return bool(task_runner_); }
+
+ private:
+ friend class MemoryDumpSchedulerTest;
+ MemoryDumpScheduler();
+ ~MemoryDumpScheduler();
+
+ void StartInternal(Config);
+ void StopInternal();
+ void Tick(uint32_t expected_generation);
+
+ // Accessed only by the public methods (never from the task runner itself).
+ scoped_refptr<SequencedTaskRunner> task_runner_;
+
+ // These fields instead are only accessed from within the task runner.
+ uint32_t period_ms_; // 0 == disabled.
+ uint32_t generation_; // Used to invalidate outstanding tasks after Stop().
+ uint32_t tick_count_;
+ uint32_t light_dump_rate_;
+ uint32_t heavy_dump_rate_;
+ PeriodicCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryDumpScheduler);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_DUMP_SCHEDULER_H
diff --git a/base/trace_event/memory_dump_scheduler_unittest.cc b/base/trace_event/memory_dump_scheduler_unittest.cc
new file mode 100644
index 0000000000..d5993b6fca
--- /dev/null
+++ b/base/trace_event/memory_dump_scheduler_unittest.cc
@@ -0,0 +1,200 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_dump_scheduler.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AtMost;
+using ::testing::Invoke;
+using ::testing::_;
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+// Wrapper to use gmock on a callback.
+struct CallbackWrapper {
+ MOCK_METHOD1(OnTick, void(MemoryDumpLevelOfDetail));
+};
+
+} // namespace
+
+class MemoryDumpSchedulerTest : public testing::Test {
+ public:
+ MemoryDumpSchedulerTest()
+ : testing::Test(),
+ evt_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ bg_thread_("MemoryDumpSchedulerTest Thread") {
+ bg_thread_.Start();
+ }
+
+ protected:
+ MemoryDumpScheduler scheduler_;
+ WaitableEvent evt_;
+ CallbackWrapper on_tick_;
+ Thread bg_thread_;
+};
+
+TEST_F(MemoryDumpSchedulerTest, SingleTrigger) {
+ const uint32_t kPeriodMs = 1;
+ const auto kLevelOfDetail = MemoryDumpLevelOfDetail::DETAILED;
+ const uint32_t kTicks = 5;
+ MemoryDumpScheduler::Config config;
+ config.triggers.push_back({kLevelOfDetail, kPeriodMs});
+ config.callback = Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
+
+ testing::InSequence sequence;
+ EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1);
+ EXPECT_CALL(on_tick_, OnTick(_))
+ .WillRepeatedly(Invoke(
+ [this, kLevelOfDetail](MemoryDumpLevelOfDetail level_of_detail) {
+ EXPECT_EQ(kLevelOfDetail, level_of_detail);
+ this->evt_.Signal();
+ }));
+
+ // Check that Stop() before Start() doesn't cause any error.
+ scheduler_.Stop();
+
+ const TimeTicks tstart = TimeTicks::Now();
+ scheduler_.Start(config, bg_thread_.task_runner());
+ evt_.Wait();
+ const double time_ms = (TimeTicks::Now() - tstart).InMillisecondsF();
+
+ // It takes N-1 ms to perform N ticks of 1ms each.
+ EXPECT_GE(time_ms, kPeriodMs * (kTicks - 1));
+
+ // Check that stopping twice doesn't cause any problems.
+ scheduler_.Stop();
+ scheduler_.Stop();
+}
+
+TEST_F(MemoryDumpSchedulerTest, MultipleTriggers) {
+ const uint32_t kPeriodLightMs = 3;
+ const uint32_t kPeriodDetailedMs = 9;
+ MemoryDumpScheduler::Config config;
+ const MemoryDumpLevelOfDetail kLight = MemoryDumpLevelOfDetail::LIGHT;
+ const MemoryDumpLevelOfDetail kDetailed = MemoryDumpLevelOfDetail::DETAILED;
+ config.triggers.push_back({kLight, kPeriodLightMs});
+ config.triggers.push_back({kDetailed, kPeriodDetailedMs});
+ config.callback = Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
+
+ TimeTicks t1, t2, t3;
+
+ testing::InSequence sequence;
+ EXPECT_CALL(on_tick_, OnTick(kDetailed))
+ .WillOnce(
+ Invoke([&t1](MemoryDumpLevelOfDetail) { t1 = TimeTicks::Now(); }));
+ EXPECT_CALL(on_tick_, OnTick(kLight)).Times(1);
+ EXPECT_CALL(on_tick_, OnTick(kLight)).Times(1);
+ EXPECT_CALL(on_tick_, OnTick(kDetailed))
+ .WillOnce(
+ Invoke([&t2](MemoryDumpLevelOfDetail) { t2 = TimeTicks::Now(); }));
+ EXPECT_CALL(on_tick_, OnTick(kLight))
+ .WillOnce(
+ Invoke([&t3](MemoryDumpLevelOfDetail) { t3 = TimeTicks::Now(); }));
+
+ // Rationale for WillRepeatedly and not just WillOnce: Extra ticks might
+ // happen if the Stop() takes time. Not an interesting case, but we need to
+ // avoid gmock to shout in that case.
+ EXPECT_CALL(on_tick_, OnTick(_))
+ .WillRepeatedly(
+ Invoke([this](MemoryDumpLevelOfDetail) { this->evt_.Signal(); }));
+
+ scheduler_.Start(config, bg_thread_.task_runner());
+ evt_.Wait();
+ scheduler_.Stop();
+ EXPECT_GE((t2 - t1).InMillisecondsF(), kPeriodDetailedMs);
+ EXPECT_GE((t3 - t2).InMillisecondsF(), kPeriodLightMs);
+}
+
+TEST_F(MemoryDumpSchedulerTest, StartStopQuickly) {
+ const uint32_t kPeriodMs = 3;
+ const uint32_t kQuickIterations = 5;
+ const uint32_t kDetailedTicks = 10;
+
+ MemoryDumpScheduler::Config light_config;
+ light_config.triggers.push_back({MemoryDumpLevelOfDetail::LIGHT, kPeriodMs});
+ light_config.callback = Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
+
+ MemoryDumpScheduler::Config detailed_config;
+ detailed_config.triggers.push_back(
+ {MemoryDumpLevelOfDetail::DETAILED, kPeriodMs});
+ detailed_config.callback =
+ Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
+
+ testing::InSequence sequence;
+ EXPECT_CALL(on_tick_, OnTick(MemoryDumpLevelOfDetail::LIGHT))
+ .Times(AtMost(kQuickIterations));
+ EXPECT_CALL(on_tick_, OnTick(MemoryDumpLevelOfDetail::DETAILED))
+ .Times(kDetailedTicks - 1);
+ EXPECT_CALL(on_tick_, OnTick(MemoryDumpLevelOfDetail::DETAILED))
+ .WillRepeatedly(
+ Invoke([this](MemoryDumpLevelOfDetail) { this->evt_.Signal(); }));
+
+ const TimeTicks tstart = TimeTicks::Now();
+ for (unsigned int i = 0; i < kQuickIterations; i++) {
+ scheduler_.Start(light_config, bg_thread_.task_runner());
+ scheduler_.Stop();
+ }
+
+ scheduler_.Start(detailed_config, bg_thread_.task_runner());
+
+ evt_.Wait();
+ const double time_ms = (TimeTicks::Now() - tstart).InMillisecondsF();
+ scheduler_.Stop();
+
+ // It takes N-1 ms to perform N ticks of 1ms each.
+ EXPECT_GE(time_ms, kPeriodMs * (kDetailedTicks - 1));
+}
+
+TEST_F(MemoryDumpSchedulerTest, StopAndStartOnAnotherThread) {
+ const uint32_t kPeriodMs = 1;
+ const uint32_t kTicks = 3;
+ MemoryDumpScheduler::Config config;
+ config.triggers.push_back({MemoryDumpLevelOfDetail::DETAILED, kPeriodMs});
+ config.callback = Bind(&CallbackWrapper::OnTick, Unretained(&on_tick_));
+
+ scoped_refptr<TaskRunner> expected_task_runner = bg_thread_.task_runner();
+ testing::InSequence sequence;
+ EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1);
+ EXPECT_CALL(on_tick_, OnTick(_))
+ .WillRepeatedly(
+ Invoke([this, expected_task_runner](MemoryDumpLevelOfDetail) {
+ EXPECT_TRUE(expected_task_runner->RunsTasksInCurrentSequence());
+ this->evt_.Signal();
+ }));
+
+ scheduler_.Start(config, bg_thread_.task_runner());
+ evt_.Wait();
+ scheduler_.Stop();
+ bg_thread_.Stop();
+
+ Thread bg_thread_2("MemoryDumpSchedulerTest Thread 2");
+ bg_thread_2.Start();
+ evt_.Reset();
+ expected_task_runner = bg_thread_2.task_runner();
+ EXPECT_CALL(on_tick_, OnTick(_)).Times(kTicks - 1);
+ EXPECT_CALL(on_tick_, OnTick(_))
+ .WillRepeatedly(
+ Invoke([this, expected_task_runner](MemoryDumpLevelOfDetail) {
+ EXPECT_TRUE(expected_task_runner->RunsTasksInCurrentSequence());
+ this->evt_.Signal();
+ }));
+ scheduler_.Start(config, bg_thread_2.task_runner());
+ evt_.Wait();
+ scheduler_.Stop();
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_infra_background_whitelist.cc b/base/trace_event/memory_infra_background_whitelist.cc
new file mode 100644
index 0000000000..40f5ac8ccc
--- /dev/null
+++ b/base/trace_event/memory_infra_background_whitelist.cc
@@ -0,0 +1,392 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_infra_background_whitelist.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include <string>
+
+#include "base/strings/string_util.h"
+
+namespace base {
+namespace trace_event {
+namespace {
+
+// The names of dump providers whitelisted for background tracing. Dump
+// providers can be added here only if the background mode dump has very
+// little processor and memory overhead.
+// TODO(ssid): Some dump providers do not create ownership edges on background
+// dump. So, the effective size will not be correct.
+const char* const kDumpProviderWhitelist[] = {
+ "android::ResourceManagerImpl",
+ "AutocompleteController",
+ "BlinkGC",
+ "BlinkObjectCounters",
+ "BlobStorageContext",
+ "ClientDiscardableSharedMemoryManager",
+ "DOMStorage",
+ "DownloadService",
+ "DiscardableSharedMemoryManager",
+ "gpu::BufferManager",
+ "gpu::RenderbufferManager",
+ "gpu::TextureManager",
+ "FontCaches",
+ "HistoryReport",
+ "IPCChannel",
+ "IndexedDBBackingStore",
+ "InMemoryURLIndex",
+ "JavaHeap",
+ "LevelDB",
+ "LeveldbValueStore",
+ "LocalStorage",
+ "Malloc",
+ "MemoryCache",
+ "MojoHandleTable",
+ "MojoLevelDB",
+ "MojoMessages",
+ "PartitionAlloc",
+ "ProcessMemoryMetrics",
+ "RenderProcessHost",
+ "SharedMemoryTracker",
+ "Skia",
+ "Sql",
+ "URLRequestContext",
+ "V8Isolate",
+ "SyncDirectory",
+ "TabRestoreServiceHelper",
+ nullptr // End of list marker.
+};
+
+// A list of string names that are allowed for the memory allocator dumps in
+// background mode.
+const char* const kAllocatorDumpNameWhitelist[] = {
+ "blink_gc",
+ "blink_gc/allocated_objects",
+ "blink_objects/AdSubframe",
+ "blink_objects/AudioHandler",
+ "blink_objects/DetachedScriptState",
+ "blink_objects/Document",
+ "blink_objects/Frame",
+ "blink_objects/JSEventListener",
+ "blink_objects/LayoutObject",
+ "blink_objects/MediaKeySession",
+ "blink_objects/MediaKeys",
+ "blink_objects/Node",
+ "blink_objects/Resource",
+ "blink_objects/RTCPeerConnection",
+ "blink_objects/ScriptPromise",
+ "blink_objects/PausableObject",
+ "blink_objects/V8PerContextData",
+ "blink_objects/WorkerGlobalScope",
+ "blink_objects/UACSSResource",
+ "blink_objects/ResourceFetcher",
+ "components/download/controller_0x?",
+ "discardable",
+ "discardable/child_0x?",
+ "extensions/value_store/Extensions.Database.Open.Settings/0x?",
+ "extensions/value_store/Extensions.Database.Open.Rules/0x?",
+ "extensions/value_store/Extensions.Database.Open.State/0x?",
+ "extensions/value_store/Extensions.Database.Open/0x?",
+ "extensions/value_store/Extensions.Database.Restore/0x?",
+ "extensions/value_store/Extensions.Database.Value.Restore/0x?",
+ "font_caches/font_platform_data_cache",
+ "font_caches/shape_caches",
+ "gpu/gl/buffers/share_group_0x?",
+ "gpu/gl/renderbuffers/share_group_0x?",
+ "gpu/gl/textures/share_group_0x?",
+ "history/delta_file_service/leveldb_0x?",
+ "history/usage_reports_buffer/leveldb_0x?",
+ "java_heap",
+ "java_heap/allocated_objects",
+ "leveldatabase",
+ "leveldatabase/block_cache/browser",
+ "leveldatabase/block_cache/in_memory",
+ "leveldatabase/block_cache/unified",
+ "leveldatabase/block_cache/web",
+ "leveldatabase/db_0x?",
+ "leveldatabase/db_0x?/block_cache",
+ "leveldatabase/memenv_0x?",
+ "malloc",
+ "malloc/allocated_objects",
+ "malloc/metadata_fragmentation_caches",
+ "mojo",
+ "mojo/data_pipe_consumer",
+ "mojo/data_pipe_producer",
+ "mojo/invitation",
+ "mojo/messages",
+ "mojo/message_pipe",
+ "mojo/platform_handle",
+ "mojo/queued_ipc_channel_message/0x?",
+ "mojo/render_process_host/0x?",
+ "mojo/shared_buffer",
+ "mojo/unknown",
+ "mojo/watcher",
+ "net/http_network_session_0x?",
+ "net/http_network_session_0x?/quic_stream_factory",
+ "net/http_network_session_0x?/socket_pool",
+ "net/http_network_session_0x?/spdy_session_pool",
+ "net/http_network_session_0x?/stream_factory",
+ "net/ssl_session_cache",
+ "net/url_request_context",
+ "net/url_request_context/app_request",
+ "net/url_request_context/app_request/0x?",
+ "net/url_request_context/app_request/0x?/cookie_monster",
+ "net/url_request_context/app_request/0x?/cookie_monster/cookies",
+ "net/url_request_context/app_request/0x?/cookie_monster/"
+ "tasks_pending_global",
+ "net/url_request_context/app_request/0x?/cookie_monster/"
+ "tasks_pending_for_key",
+ "net/url_request_context/app_request/0x?/http_cache",
+ "net/url_request_context/app_request/0x?/http_cache/memory_backend",
+ "net/url_request_context/app_request/0x?/http_cache/simple_backend",
+ "net/url_request_context/app_request/0x?/http_network_session",
+ "net/url_request_context/extensions",
+ "net/url_request_context/extensions/0x?",
+ "net/url_request_context/extensions/0x?/cookie_monster",
+ "net/url_request_context/extensions/0x?/cookie_monster/cookies",
+ "net/url_request_context/extensions/0x?/cookie_monster/"
+ "tasks_pending_global",
+ "net/url_request_context/extensions/0x?/cookie_monster/"
+ "tasks_pending_for_key",
+ "net/url_request_context/extensions/0x?/http_cache",
+ "net/url_request_context/extensions/0x?/http_cache/memory_backend",
+ "net/url_request_context/extensions/0x?/http_cache/simple_backend",
+ "net/url_request_context/extensions/0x?/http_network_session",
+ "net/url_request_context/isolated_media",
+ "net/url_request_context/isolated_media/0x?",
+ "net/url_request_context/isolated_media/0x?/cookie_monster",
+ "net/url_request_context/isolated_media/0x?/cookie_monster/cookies",
+ "net/url_request_context/isolated_media/0x?/cookie_monster/"
+ "tasks_pending_global",
+ "net/url_request_context/isolated_media/0x?/cookie_monster/"
+ "tasks_pending_for_key",
+ "net/url_request_context/isolated_media/0x?/http_cache",
+ "net/url_request_context/isolated_media/0x?/http_cache/memory_backend",
+ "net/url_request_context/isolated_media/0x?/http_cache/simple_backend",
+ "net/url_request_context/isolated_media/0x?/http_network_session",
+ "net/url_request_context/main",
+ "net/url_request_context/main/0x?",
+ "net/url_request_context/main/0x?/cookie_monster",
+ "net/url_request_context/main/0x?/cookie_monster/cookies",
+ "net/url_request_context/main/0x?/cookie_monster/tasks_pending_global",
+ "net/url_request_context/main/0x?/cookie_monster/tasks_pending_for_key",
+ "net/url_request_context/main/0x?/http_cache",
+ "net/url_request_context/main/0x?/http_cache/memory_backend",
+ "net/url_request_context/main/0x?/http_cache/simple_backend",
+ "net/url_request_context/main/0x?/http_network_session",
+ "net/url_request_context/main_media",
+ "net/url_request_context/main_media/0x?",
+ "net/url_request_context/main_media/0x?/cookie_monster",
+ "net/url_request_context/main_media/0x?/cookie_monster/cookies",
+ "net/url_request_context/main_media/0x?/cookie_monster/"
+ "tasks_pending_global",
+ "net/url_request_context/main_media/0x?/cookie_monster/"
+ "tasks_pending_for_key",
+ "net/url_request_context/main_media/0x?/http_cache",
+ "net/url_request_context/main_media/0x?/http_cache/memory_backend",
+ "net/url_request_context/main_media/0x?/http_cache/simple_backend",
+ "net/url_request_context/main_media/0x?/http_network_session",
+ "net/url_request_context/proxy",
+ "net/url_request_context/proxy/0x?",
+ "net/url_request_context/proxy/0x?/cookie_monster",
+ "net/url_request_context/proxy/0x?/cookie_monster/cookies",
+ "net/url_request_context/proxy/0x?/cookie_monster/tasks_pending_global",
+ "net/url_request_context/proxy/0x?/cookie_monster/tasks_pending_for_key",
+ "net/url_request_context/proxy/0x?/http_cache",
+ "net/url_request_context/proxy/0x?/http_cache/memory_backend",
+ "net/url_request_context/proxy/0x?/http_cache/simple_backend",
+ "net/url_request_context/proxy/0x?/http_network_session",
+ "net/url_request_context/safe_browsing",
+ "net/url_request_context/safe_browsing/0x?",
+ "net/url_request_context/safe_browsing/0x?/cookie_monster",
+ "net/url_request_context/safe_browsing/0x?/cookie_monster/cookies",
+ "net/url_request_context/safe_browsing/0x?/cookie_monster/"
+ "tasks_pending_global",
+ "net/url_request_context/safe_browsing/0x?/cookie_monster/"
+ "tasks_pending_for_key",
+ "net/url_request_context/safe_browsing/0x?/http_cache",
+ "net/url_request_context/safe_browsing/0x?/http_cache/memory_backend",
+ "net/url_request_context/safe_browsing/0x?/http_cache/simple_backend",
+ "net/url_request_context/safe_browsing/0x?/http_network_session",
+ "net/url_request_context/system",
+ "net/url_request_context/system/0x?",
+ "net/url_request_context/system/0x?/cookie_monster",
+ "net/url_request_context/system/0x?/cookie_monster/cookies",
+ "net/url_request_context/system/0x?/cookie_monster/tasks_pending_global",
+ "net/url_request_context/system/0x?/cookie_monster/tasks_pending_for_key",
+ "net/url_request_context/system/0x?/http_cache",
+ "net/url_request_context/system/0x?/http_cache/memory_backend",
+ "net/url_request_context/system/0x?/http_cache/simple_backend",
+ "net/url_request_context/system/0x?/http_network_session",
+ "net/url_request_context/unknown",
+ "net/url_request_context/unknown/0x?",
+ "net/url_request_context/unknown/0x?/cookie_monster",
+ "net/url_request_context/unknown/0x?/cookie_monster/cookies",
+ "net/url_request_context/unknown/0x?/cookie_monster/tasks_pending_global",
+ "net/url_request_context/unknown/0x?/cookie_monster/tasks_pending_for_key",
+ "net/url_request_context/unknown/0x?/http_cache",
+ "net/url_request_context/unknown/0x?/http_cache/memory_backend",
+ "net/url_request_context/unknown/0x?/http_cache/simple_backend",
+ "net/url_request_context/unknown/0x?/http_network_session",
+ "omnibox/autocomplete_controller/0x?",
+ "omnibox/in_memory_url_index/0x?",
+ "web_cache/Image_resources",
+ "web_cache/CSS stylesheet_resources",
+ "web_cache/Script_resources",
+ "web_cache/XSL stylesheet_resources",
+ "web_cache/Font_resources",
+ "web_cache/Other_resources",
+ "partition_alloc/allocated_objects",
+ "partition_alloc/partitions",
+ "partition_alloc/partitions/array_buffer",
+ "partition_alloc/partitions/buffer",
+ "partition_alloc/partitions/fast_malloc",
+ "partition_alloc/partitions/layout",
+ "skia/sk_glyph_cache",
+ "skia/sk_resource_cache",
+ "sqlite",
+ "ui/resource_manager_0x?/default_resource/0x?",
+ "ui/resource_manager_0x?/tinted_resource",
+ "v8/isolate_0x?/contexts/detached_context",
+ "v8/isolate_0x?/contexts/native_context",
+ "v8/isolate_0x?/heap_spaces",
+ "v8/isolate_0x?/heap_spaces/code_space",
+ "v8/isolate_0x?/heap_spaces/large_object_space",
+ "v8/isolate_0x?/heap_spaces/map_space",
+ "v8/isolate_0x?/heap_spaces/new_space",
+ "v8/isolate_0x?/heap_spaces/new_large_object_space",
+ "v8/isolate_0x?/heap_spaces/old_space",
+ "v8/isolate_0x?/heap_spaces/read_only_space",
+ "v8/isolate_0x?/malloc",
+ "v8/isolate_0x?/zapped_for_debug",
+ "site_storage/blob_storage/0x?",
+ "site_storage/index_db/db_0x?",
+ "site_storage/index_db/memenv_0x?",
+ "site_storage/localstorage/0x?/cache_size",
+ "site_storage/localstorage/0x?/leveldb",
+ "site_storage/session_storage/0x?",
+ "site_storage/session_storage/0x?/cache_size",
+ "sync/0x?/kernel",
+ "sync/0x?/store",
+ "sync/0x?/model_type/APP",
+ "sync/0x?/model_type/APP_LIST",
+ "sync/0x?/model_type/APP_NOTIFICATION",
+ "sync/0x?/model_type/APP_SETTING",
+ "sync/0x?/model_type/ARC_PACKAGE",
+ "sync/0x?/model_type/ARTICLE",
+ "sync/0x?/model_type/AUTOFILL",
+ "sync/0x?/model_type/AUTOFILL_PROFILE",
+ "sync/0x?/model_type/AUTOFILL_WALLET",
+ "sync/0x?/model_type/BOOKMARK",
+ "sync/0x?/model_type/DEVICE_INFO",
+ "sync/0x?/model_type/DICTIONARY",
+ "sync/0x?/model_type/EXPERIMENTS",
+ "sync/0x?/model_type/EXTENSION",
+ "sync/0x?/model_type/EXTENSION_SETTING",
+ "sync/0x?/model_type/FAVICON_IMAGE",
+ "sync/0x?/model_type/FAVICON_TRACKING",
+ "sync/0x?/model_type/HISTORY_DELETE_DIRECTIVE",
+ "sync/0x?/model_type/MANAGED_USER",
+ "sync/0x?/model_type/MANAGED_USER_SETTING",
+ "sync/0x?/model_type/MANAGED_USER_SHARED_SETTING",
+ "sync/0x?/model_type/MANAGED_USER_WHITELIST",
+ "sync/0x?/model_type/MOUNTAIN_SHARE",
+ "sync/0x?/model_type/NIGORI",
+ "sync/0x?/model_type/PASSWORD",
+ "sync/0x?/model_type/PREFERENCE",
+ "sync/0x?/model_type/PRINTER",
+ "sync/0x?/model_type/PRIORITY_PREFERENCE",
+ "sync/0x?/model_type/READING_LIST",
+ "sync/0x?/model_type/SEARCH_ENGINE",
+ "sync/0x?/model_type/SESSION",
+ "sync/0x?/model_type/SYNCED_NOTIFICATION",
+ "sync/0x?/model_type/SYNCED_NOTIFICATION_APP_INFO",
+ "sync/0x?/model_type/THEME",
+ "sync/0x?/model_type/TYPED_URL",
+ "sync/0x?/model_type/USER_CONSENT",
+ "sync/0x?/model_type/USER_EVENT",
+ "sync/0x?/model_type/WALLET_METADATA",
+ "sync/0x?/model_type/WIFI_CREDENTIAL",
+ "tab_restore/service_helper_0x?/entries",
+ "tab_restore/service_helper_0x?/entries/tab_0x?",
+ "tab_restore/service_helper_0x?/entries/window_0x?",
+ "tracing/heap_profiler_blink_gc/AllocationRegister",
+ "tracing/heap_profiler_malloc/AllocationRegister",
+ "tracing/heap_profiler_partition_alloc/AllocationRegister",
+ nullptr // End of list marker.
+};
+
+const char* const* g_dump_provider_whitelist = kDumpProviderWhitelist;
+const char* const* g_allocator_dump_name_whitelist =
+ kAllocatorDumpNameWhitelist;
+
+bool IsMemoryDumpProviderInList(const char* mdp_name, const char* const* list) {
+ for (size_t i = 0; list[i] != nullptr; ++i) {
+ if (strcmp(mdp_name, list[i]) == 0)
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+bool IsMemoryDumpProviderWhitelisted(const char* mdp_name) {
+ return IsMemoryDumpProviderInList(mdp_name, g_dump_provider_whitelist);
+}
+
+bool IsMemoryAllocatorDumpNameWhitelisted(const std::string& name) {
+ // Global dumps are explicitly whitelisted for background use.
+ if (base::StartsWith(name, "global/", CompareCase::SENSITIVE)) {
+ for (size_t i = strlen("global/"); i < name.size(); i++)
+ if (!base::IsHexDigit(name[i]))
+ return false;
+ return true;
+ }
+
+ if (base::StartsWith(name, "shared_memory/", CompareCase::SENSITIVE)) {
+ for (size_t i = strlen("shared_memory/"); i < name.size(); i++)
+ if (!base::IsHexDigit(name[i]))
+ return false;
+ return true;
+ }
+
+ // Remove special characters, numbers (including hexadecimal which are marked
+ // by '0x') from the given string.
+ const size_t length = name.size();
+ std::string stripped_str;
+ stripped_str.reserve(length);
+ bool parsing_hex = false;
+ for (size_t i = 0; i < length; ++i) {
+ if (parsing_hex && isxdigit(name[i]))
+ continue;
+ parsing_hex = false;
+ if (i + 1 < length && name[i] == '0' && name[i + 1] == 'x') {
+ parsing_hex = true;
+ stripped_str.append("0x?");
+ ++i;
+ } else {
+ stripped_str.push_back(name[i]);
+ }
+ }
+
+ for (size_t i = 0; g_allocator_dump_name_whitelist[i] != nullptr; ++i) {
+ if (stripped_str == g_allocator_dump_name_whitelist[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void SetDumpProviderWhitelistForTesting(const char* const* list) {
+ g_dump_provider_whitelist = list;
+}
+
+void SetAllocatorDumpNameWhitelistForTesting(const char* const* list) {
+ g_allocator_dump_name_whitelist = list;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_infra_background_whitelist.h b/base/trace_event/memory_infra_background_whitelist.h
new file mode 100644
index 0000000000..b8d704ae24
--- /dev/null
+++ b/base/trace_event/memory_infra_background_whitelist.h
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_INFRA_BACKGROUND_WHITELIST_H_
+#define BASE_TRACE_EVENT_MEMORY_INFRA_BACKGROUND_WHITELIST_H_
+
+// This file contains the whitelists for background mode to limit the tracing
+// overhead and remove sensitive information from traces.
+
+#include <string>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace trace_event {
+
+// Checks if the given |mdp_name| is in the whitelist.
+bool BASE_EXPORT IsMemoryDumpProviderWhitelisted(const char* mdp_name);
+
+// Checks if the given |name| matches any of the whitelisted patterns.
+bool BASE_EXPORT IsMemoryAllocatorDumpNameWhitelisted(const std::string& name);
+
+// The whitelist is replaced with the given list for tests. The last element of
+// the list must be nullptr.
+void BASE_EXPORT SetDumpProviderWhitelistForTesting(const char* const* list);
+void BASE_EXPORT
+SetAllocatorDumpNameWhitelistForTesting(const char* const* list);
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_INFRA_BACKGROUND_WHITELIST_H_
diff --git a/base/trace_event/memory_infra_background_whitelist_unittest.cc b/base/trace_event/memory_infra_background_whitelist_unittest.cc
new file mode 100644
index 0000000000..3037eb1da7
--- /dev/null
+++ b/base/trace_event/memory_infra_background_whitelist_unittest.cc
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_infra_background_whitelist.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace trace_event {
+
+TEST(MemoryInfraBackgroundWhitelist, Whitelist) {
+ // Global dumps that are of hex digits are all whitelisted for background use.
+ EXPECT_TRUE(IsMemoryAllocatorDumpNameWhitelisted("global/01234ABCDEF"));
+ EXPECT_TRUE(
+ IsMemoryAllocatorDumpNameWhitelisted("shared_memory/01234ABCDEF"));
+
+ // Global dumps that contain non-hex digits are not whitelisted.
+ EXPECT_FALSE(IsMemoryAllocatorDumpNameWhitelisted("global/GHIJK"));
+ EXPECT_FALSE(IsMemoryAllocatorDumpNameWhitelisted("shared_memory/GHIJK"));
+
+ // Test a couple that contain pointer values.
+ EXPECT_TRUE(IsMemoryAllocatorDumpNameWhitelisted("net/url_request_context"));
+ EXPECT_TRUE(IsMemoryAllocatorDumpNameWhitelisted(
+ "net/url_request_context/app_request/0x123/cookie_monster"));
+ EXPECT_TRUE(
+ IsMemoryAllocatorDumpNameWhitelisted("net/http_network_session_0x123"));
+ EXPECT_FALSE(
+ IsMemoryAllocatorDumpNameWhitelisted("net/http_network_session/0x123"));
+ EXPECT_TRUE(IsMemoryAllocatorDumpNameWhitelisted(
+ "net/http_network_session_0x123/quic_stream_factory"));
+}
+
+} // namespace trace_event
+
+} // namespace base
diff --git a/base/trace_event/memory_usage_estimator.cc b/base/trace_event/memory_usage_estimator.cc
new file mode 100644
index 0000000000..c769d5b6f1
--- /dev/null
+++ b/base/trace_event/memory_usage_estimator.cc
@@ -0,0 +1,14 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_usage_estimator.h"
+
+namespace base {
+namespace trace_event {
+
+template size_t EstimateMemoryUsage(const std::string&);
+template size_t EstimateMemoryUsage(const string16&);
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/memory_usage_estimator.h b/base/trace_event/memory_usage_estimator.h
new file mode 100644
index 0000000000..214c64a905
--- /dev/null
+++ b/base/trace_event/memory_usage_estimator.h
@@ -0,0 +1,654 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_
+#define BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_
+
+#include <stdint.h>
+
+#include <array>
+#include <deque>
+#include <list>
+#include <map>
+#include <memory>
+#include <queue>
+#include <set>
+#include <stack>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/containers/circular_deque.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "base/containers/linked_list.h"
+#include "base/containers/mru_cache.h"
+#include "base/containers/queue.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/template_util.h"
+
+// Composable memory usage estimators.
+//
+// This file defines set of EstimateMemoryUsage(object) functions that return
+// approximate memory usage of their argument.
+//
+// The ultimate goal is to make memory usage estimation for a class simply a
+// matter of aggregating EstimateMemoryUsage() results over all fields.
+//
+// That is achieved via composability: if EstimateMemoryUsage() is defined
+// for T then EstimateMemoryUsage() is also defined for any combination of
+// containers holding T (e.g. std::map<int, std::vector<T>>).
+//
+// There are two ways of defining EstimateMemoryUsage() for a type:
+//
+// 1. As a global function 'size_t EstimateMemoryUsage(T)' in
+// in base::trace_event namespace.
+//
+// 2. As 'size_t T::EstimateMemoryUsage() const' method. In this case
+// EstimateMemoryUsage(T) function in base::trace_event namespace is
+// provided automatically.
+//
+// Here is an example implementation:
+//
+// size_t foo::bar::MyClass::EstimateMemoryUsage() const {
+// return base::trace_event::EstimateMemoryUsage(name_) +
+// base::trace_event::EstimateMemoryUsage(id_) +
+// base::trace_event::EstimateMemoryUsage(items_);
+// }
+//
+// The approach is simple: first call EstimateMemoryUsage() on all members,
+// then recursively fix compilation errors that are caused by types not
+// implementing EstimateMemoryUsage().
+
+namespace base {
+namespace trace_event {
+
+// Declarations
+
+// If T declares 'EstimateMemoryUsage() const' member function, then
+// global function EstimateMemoryUsage(T) is available, and just calls
+// the member function.
+template <class T>
+auto EstimateMemoryUsage(const T& object)
+ -> decltype(object.EstimateMemoryUsage());
+
+// String
+
+template <class C, class T, class A>
+size_t EstimateMemoryUsage(const std::basic_string<C, T, A>& string);
+
+// Arrays
+
+template <class T, size_t N>
+size_t EstimateMemoryUsage(const std::array<T, N>& array);
+
+template <class T, size_t N>
+size_t EstimateMemoryUsage(T (&array)[N]);
+
+template <class T>
+size_t EstimateMemoryUsage(const T* array, size_t array_length);
+
+// std::unique_ptr
+
+template <class T, class D>
+size_t EstimateMemoryUsage(const std::unique_ptr<T, D>& ptr);
+
+template <class T, class D>
+size_t EstimateMemoryUsage(const std::unique_ptr<T[], D>& array,
+ size_t array_length);
+
+// std::shared_ptr
+
+template <class T>
+size_t EstimateMemoryUsage(const std::shared_ptr<T>& ptr);
+
+// Containers
+
+template <class F, class S>
+size_t EstimateMemoryUsage(const std::pair<F, S>& pair);
+
+template <class T, class A>
+size_t EstimateMemoryUsage(const std::vector<T, A>& vector);
+
+template <class T, class A>
+size_t EstimateMemoryUsage(const std::list<T, A>& list);
+
+template <class T>
+size_t EstimateMemoryUsage(const base::LinkedList<T>& list);
+
+template <class T, class C, class A>
+size_t EstimateMemoryUsage(const std::set<T, C, A>& set);
+
+template <class T, class C, class A>
+size_t EstimateMemoryUsage(const std::multiset<T, C, A>& set);
+
+template <class K, class V, class C, class A>
+size_t EstimateMemoryUsage(const std::map<K, V, C, A>& map);
+
+template <class K, class V, class C, class A>
+size_t EstimateMemoryUsage(const std::multimap<K, V, C, A>& map);
+
+template <class T, class H, class KE, class A>
+size_t EstimateMemoryUsage(const std::unordered_set<T, H, KE, A>& set);
+
+template <class T, class H, class KE, class A>
+size_t EstimateMemoryUsage(const std::unordered_multiset<T, H, KE, A>& set);
+
+template <class K, class V, class H, class KE, class A>
+size_t EstimateMemoryUsage(const std::unordered_map<K, V, H, KE, A>& map);
+
+template <class K, class V, class H, class KE, class A>
+size_t EstimateMemoryUsage(const std::unordered_multimap<K, V, H, KE, A>& map);
+
+template <class T, class A>
+size_t EstimateMemoryUsage(const std::deque<T, A>& deque);
+
+template <class T, class C>
+size_t EstimateMemoryUsage(const std::queue<T, C>& queue);
+
+template <class T, class C>
+size_t EstimateMemoryUsage(const std::priority_queue<T, C>& queue);
+
+template <class T, class C>
+size_t EstimateMemoryUsage(const std::stack<T, C>& stack);
+
+template <class T>
+size_t EstimateMemoryUsage(const base::circular_deque<T>& deque);
+
+template <class T, class C>
+size_t EstimateMemoryUsage(const base::flat_set<T, C>& set);
+
+template <class K, class V, class C>
+size_t EstimateMemoryUsage(const base::flat_map<K, V, C>& map);
+
+template <class Key,
+ class Payload,
+ class HashOrComp,
+ template <typename, typename, typename> class Map>
+size_t EstimateMemoryUsage(const MRUCacheBase<Key, Payload, HashOrComp, Map>&);
+
+// TODO(dskiba):
+// std::forward_list
+
+// Definitions
+
+namespace internal {
+
+// HasEMU<T>::value is true iff EstimateMemoryUsage(T) is available.
+// (This is the default version, which is false.)
+template <class T, class X = void>
+struct HasEMU : std::false_type {};
+
+// This HasEMU specialization is only picked up if there exists function
+// EstimateMemoryUsage(const T&) that returns size_t. Simpler ways to
+// achieve this don't work on MSVC.
+template <class T>
+struct HasEMU<
+ T,
+ typename std::enable_if<std::is_same<
+ size_t,
+ decltype(EstimateMemoryUsage(std::declval<const T&>()))>::value>::type>
+ : std::true_type {};
+
+// EMUCaller<T> does three things:
+// 1. Defines Call() method that calls EstimateMemoryUsage(T) if it's
+// available.
+// 2. If EstimateMemoryUsage(T) is not available, but T has trivial dtor
+// (i.e. it's POD, integer, pointer, enum, etc.) then it defines Call()
+// method that returns 0. This is useful for containers, which allocate
+// memory regardless of T (also for cases like std::map<int, MyClass>).
+// 3. Finally, if EstimateMemoryUsage(T) is not available, then it triggers
+// a static_assert with a helpful message. That cuts numbers of errors
+// considerably - if you just call EstimateMemoryUsage(T) but it's not
+// available for T, then compiler will helpfully list *all* possible
+// variants of it, with an explanation for each.
+template <class T, class X = void>
+struct EMUCaller {
+ // std::is_same<> below makes static_assert depend on T, in order to
+ // prevent it from asserting regardless instantiation.
+ static_assert(std::is_same<T, std::false_type>::value,
+ "Neither global function 'size_t EstimateMemoryUsage(T)' "
+ "nor member function 'size_t T::EstimateMemoryUsage() const' "
+ "is defined for the type.");
+
+ static size_t Call(const T&) { return 0; }
+};
+
+template <class T>
+struct EMUCaller<T, typename std::enable_if<HasEMU<T>::value>::type> {
+ static size_t Call(const T& value) { return EstimateMemoryUsage(value); }
+};
+
+template <template <class...> class Container, class I, class = void>
+struct IsComplexIteratorForContainer : std::false_type {};
+
+template <template <class...> class Container, class I>
+struct IsComplexIteratorForContainer<
+ Container,
+ I,
+ std::enable_if_t<!std::is_pointer<I>::value &&
+ base::internal::is_iterator<I>::value>> {
+ using value_type = typename std::iterator_traits<I>::value_type;
+ using container_type = Container<value_type>;
+
+ // We use enum instead of static constexpr bool, beause we don't have inline
+ // variables until c++17.
+ //
+ // The downside is - value is not of type bool.
+ enum : bool {
+ value =
+ std::is_same<typename container_type::iterator, I>::value ||
+ std::is_same<typename container_type::const_iterator, I>::value ||
+ std::is_same<typename container_type::reverse_iterator, I>::value ||
+ std::is_same<typename container_type::const_reverse_iterator, I>::value,
+ };
+};
+
+template <class I, template <class...> class... Containers>
+constexpr bool OneOfContainersComplexIterators() {
+ // We are forced to create a temporary variable to workaround a compilation
+ // error in msvs.
+ const bool all_tests[] = {
+ IsComplexIteratorForContainer<Containers, I>::value...};
+ for (bool test : all_tests)
+ if (test)
+ return true;
+ return false;
+}
+
+// std::array has an extra required template argument. We curry it.
+template <class T>
+using array_test_helper = std::array<T, 1>;
+
+template <class I>
+constexpr bool IsStandardContainerComplexIterator() {
+ // TODO(dyaroshev): deal with maps iterators if there is a need.
+ // It requires to parse pairs into keys and values.
+ // TODO(dyaroshev): deal with unordered containers: they do not have reverse
+ // iterators.
+ return OneOfContainersComplexIterators<
+ I, array_test_helper, std::vector, std::deque,
+ /*std::forward_list,*/ std::list, std::set, std::multiset>();
+}
+
+// Work around MSVS bug. For some reason constexpr function doesn't work.
+// However variable template does.
+template <typename T>
+constexpr bool IsKnownNonAllocatingType_v =
+ std::is_trivially_destructible<T>::value ||
+ IsStandardContainerComplexIterator<T>();
+
+template <class T>
+struct EMUCaller<
+ T,
+ std::enable_if_t<!HasEMU<T>::value && IsKnownNonAllocatingType_v<T>>> {
+ static size_t Call(const T& value) { return 0; }
+};
+
+} // namespace internal
+
+// Proxy that deducts T and calls EMUCaller<T>.
+// To be used by EstimateMemoryUsage() implementations for containers.
+template <class T>
+size_t EstimateItemMemoryUsage(const T& value) {
+ return internal::EMUCaller<T>::Call(value);
+}
+
+template <class I>
+size_t EstimateIterableMemoryUsage(const I& iterable) {
+ size_t memory_usage = 0;
+ for (const auto& item : iterable) {
+ memory_usage += EstimateItemMemoryUsage(item);
+ }
+ return memory_usage;
+}
+
+// Global EstimateMemoryUsage(T) that just calls T::EstimateMemoryUsage().
+template <class T>
+auto EstimateMemoryUsage(const T& object)
+ -> decltype(object.EstimateMemoryUsage()) {
+ static_assert(
+ std::is_same<decltype(object.EstimateMemoryUsage()), size_t>::value,
+ "'T::EstimateMemoryUsage() const' must return size_t.");
+ return object.EstimateMemoryUsage();
+}
+
+// String
+
+template <class C, class T, class A>
+size_t EstimateMemoryUsage(const std::basic_string<C, T, A>& string) {
+ using string_type = std::basic_string<C, T, A>;
+ using value_type = typename string_type::value_type;
+ // C++11 doesn't leave much room for implementors - std::string can
+ // use short string optimization, but that's about it. We detect SSO
+ // by checking that c_str() points inside |string|.
+ const uint8_t* cstr = reinterpret_cast<const uint8_t*>(string.c_str());
+ const uint8_t* inline_cstr = reinterpret_cast<const uint8_t*>(&string);
+ if (cstr >= inline_cstr && cstr < inline_cstr + sizeof(string)) {
+ // SSO string
+ return 0;
+ }
+ return (string.capacity() + 1) * sizeof(value_type);
+}
+
+// Use explicit instantiations from the .cc file (reduces bloat).
+extern template BASE_EXPORT size_t EstimateMemoryUsage(const std::string&);
+extern template BASE_EXPORT size_t EstimateMemoryUsage(const string16&);
+
+// Arrays
+
+template <class T, size_t N>
+size_t EstimateMemoryUsage(const std::array<T, N>& array) {
+ return EstimateIterableMemoryUsage(array);
+}
+
+template <class T, size_t N>
+size_t EstimateMemoryUsage(T (&array)[N]) {
+ return EstimateIterableMemoryUsage(array);
+}
+
+template <class T>
+size_t EstimateMemoryUsage(const T* array, size_t array_length) {
+ size_t memory_usage = sizeof(T) * array_length;
+ for (size_t i = 0; i != array_length; ++i) {
+ memory_usage += EstimateItemMemoryUsage(array[i]);
+ }
+ return memory_usage;
+}
+
+// std::unique_ptr
+
+template <class T, class D>
+size_t EstimateMemoryUsage(const std::unique_ptr<T, D>& ptr) {
+ return ptr ? (sizeof(T) + EstimateItemMemoryUsage(*ptr)) : 0;
+}
+
+template <class T, class D>
+size_t EstimateMemoryUsage(const std::unique_ptr<T[], D>& array,
+ size_t array_length) {
+ return EstimateMemoryUsage(array.get(), array_length);
+}
+
+// std::shared_ptr
+
+template <class T>
+size_t EstimateMemoryUsage(const std::shared_ptr<T>& ptr) {
+ auto use_count = ptr.use_count();
+ if (use_count == 0) {
+ return 0;
+ }
+ // Model shared_ptr after libc++,
+ // see __shared_ptr_pointer from include/memory
+ struct SharedPointer {
+ void* vtbl;
+ long shared_owners;
+ long shared_weak_owners;
+ T* value;
+ };
+ // If object of size S shared N > S times we prefer to (potentially)
+ // overestimate than to return 0.
+ return sizeof(SharedPointer) +
+ (EstimateItemMemoryUsage(*ptr) + (use_count - 1)) / use_count;
+}
+
+// std::pair
+
+template <class F, class S>
+size_t EstimateMemoryUsage(const std::pair<F, S>& pair) {
+ return EstimateItemMemoryUsage(pair.first) +
+ EstimateItemMemoryUsage(pair.second);
+}
+
+// std::vector
+
+template <class T, class A>
+size_t EstimateMemoryUsage(const std::vector<T, A>& vector) {
+ return sizeof(T) * vector.capacity() + EstimateIterableMemoryUsage(vector);
+}
+
+// std::list
+
+template <class T, class A>
+size_t EstimateMemoryUsage(const std::list<T, A>& list) {
+ using value_type = typename std::list<T, A>::value_type;
+ struct Node {
+ Node* prev;
+ Node* next;
+ value_type value;
+ };
+ return sizeof(Node) * list.size() +
+ EstimateIterableMemoryUsage(list);
+}
+
+template <class T>
+size_t EstimateMemoryUsage(const base::LinkedList<T>& list) {
+ size_t memory_usage = 0u;
+ for (base::LinkNode<T>* node = list.head(); node != list.end();
+ node = node->next()) {
+ // Since we increment by calling node = node->next() we know that node
+ // isn't nullptr.
+ memory_usage += EstimateMemoryUsage(*node->value()) + sizeof(T);
+ }
+ return memory_usage;
+}
+
+// Tree containers
+
+template <class V>
+size_t EstimateTreeMemoryUsage(size_t size) {
+ // Tree containers are modeled after libc++
+ // (__tree_node from include/__tree)
+ struct Node {
+ Node* left;
+ Node* right;
+ Node* parent;
+ bool is_black;
+ V value;
+ };
+ return sizeof(Node) * size;
+}
+
+template <class T, class C, class A>
+size_t EstimateMemoryUsage(const std::set<T, C, A>& set) {
+ using value_type = typename std::set<T, C, A>::value_type;
+ return EstimateTreeMemoryUsage<value_type>(set.size()) +
+ EstimateIterableMemoryUsage(set);
+}
+
+template <class T, class C, class A>
+size_t EstimateMemoryUsage(const std::multiset<T, C, A>& set) {
+ using value_type = typename std::multiset<T, C, A>::value_type;
+ return EstimateTreeMemoryUsage<value_type>(set.size()) +
+ EstimateIterableMemoryUsage(set);
+}
+
+template <class K, class V, class C, class A>
+size_t EstimateMemoryUsage(const std::map<K, V, C, A>& map) {
+ using value_type = typename std::map<K, V, C, A>::value_type;
+ return EstimateTreeMemoryUsage<value_type>(map.size()) +
+ EstimateIterableMemoryUsage(map);
+}
+
+template <class K, class V, class C, class A>
+size_t EstimateMemoryUsage(const std::multimap<K, V, C, A>& map) {
+ using value_type = typename std::multimap<K, V, C, A>::value_type;
+ return EstimateTreeMemoryUsage<value_type>(map.size()) +
+ EstimateIterableMemoryUsage(map);
+}
+
+// HashMap containers
+
+namespace internal {
+
+// While hashtable containers model doesn't depend on STL implementation, one
+// detail still crept in: bucket_count. It's used in size estimation, but its
+// value after inserting N items is not predictable.
+// This function is specialized by unittests to return constant value, thus
+// excluding bucket_count from testing.
+template <class V>
+size_t HashMapBucketCountForTesting(size_t bucket_count) {
+ return bucket_count;
+}
+
+template <class MruCacheType>
+size_t DoEstimateMemoryUsageForMruCache(const MruCacheType& mru_cache) {
+ return EstimateMemoryUsage(mru_cache.ordering_) +
+ EstimateMemoryUsage(mru_cache.index_);
+}
+
+} // namespace internal
+
+template <class V>
+size_t EstimateHashMapMemoryUsage(size_t bucket_count, size_t size) {
+ // Hashtable containers are modeled after libc++
+ // (__hash_node from include/__hash_table)
+ struct Node {
+ void* next;
+ size_t hash;
+ V value;
+ };
+ using Bucket = void*;
+ bucket_count = internal::HashMapBucketCountForTesting<V>(bucket_count);
+ return sizeof(Bucket) * bucket_count + sizeof(Node) * size;
+}
+
+template <class K, class H, class KE, class A>
+size_t EstimateMemoryUsage(const std::unordered_set<K, H, KE, A>& set) {
+ using value_type = typename std::unordered_set<K, H, KE, A>::value_type;
+ return EstimateHashMapMemoryUsage<value_type>(set.bucket_count(),
+ set.size()) +
+ EstimateIterableMemoryUsage(set);
+}
+
+template <class K, class H, class KE, class A>
+size_t EstimateMemoryUsage(const std::unordered_multiset<K, H, KE, A>& set) {
+ using value_type = typename std::unordered_multiset<K, H, KE, A>::value_type;
+ return EstimateHashMapMemoryUsage<value_type>(set.bucket_count(),
+ set.size()) +
+ EstimateIterableMemoryUsage(set);
+}
+
+template <class K, class V, class H, class KE, class A>
+size_t EstimateMemoryUsage(const std::unordered_map<K, V, H, KE, A>& map) {
+ using value_type = typename std::unordered_map<K, V, H, KE, A>::value_type;
+ return EstimateHashMapMemoryUsage<value_type>(map.bucket_count(),
+ map.size()) +
+ EstimateIterableMemoryUsage(map);
+}
+
+template <class K, class V, class H, class KE, class A>
+size_t EstimateMemoryUsage(const std::unordered_multimap<K, V, H, KE, A>& map) {
+ using value_type =
+ typename std::unordered_multimap<K, V, H, KE, A>::value_type;
+ return EstimateHashMapMemoryUsage<value_type>(map.bucket_count(),
+ map.size()) +
+ EstimateIterableMemoryUsage(map);
+}
+
+// std::deque
+
+template <class T, class A>
+size_t EstimateMemoryUsage(const std::deque<T, A>& deque) {
+// Since std::deque implementations are wildly different
+// (see crbug.com/674287), we can't have one "good enough"
+// way to estimate.
+
+// kBlockSize - minimum size of a block, in bytes
+// kMinBlockLength - number of elements in a block
+// if sizeof(T) > kBlockSize
+#if defined(_LIBCPP_VERSION)
+ size_t kBlockSize = 4096;
+ size_t kMinBlockLength = 16;
+#elif defined(__GLIBCXX__)
+ size_t kBlockSize = 512;
+ size_t kMinBlockLength = 1;
+#elif defined(_MSC_VER)
+ size_t kBlockSize = 16;
+ size_t kMinBlockLength = 1;
+#else
+ size_t kBlockSize = 0;
+ size_t kMinBlockLength = 1;
+#endif
+
+ size_t block_length =
+ (sizeof(T) > kBlockSize) ? kMinBlockLength : kBlockSize / sizeof(T);
+
+ size_t blocks = (deque.size() + block_length - 1) / block_length;
+
+#if defined(__GLIBCXX__)
+ // libstdc++: deque always has at least one block
+ if (!blocks)
+ blocks = 1;
+#endif
+
+#if defined(_LIBCPP_VERSION)
+ // libc++: deque keeps at most two blocks when it shrinks,
+ // so even if the size is zero, deque might be holding up
+ // to 4096 * 2 bytes. One way to know whether deque has
+ // ever allocated (and hence has 1 or 2 blocks) is to check
+ // iterator's pointer. Non-zero value means that deque has
+ // at least one block.
+ if (!blocks && deque.begin().operator->())
+ blocks = 1;
+#endif
+
+ return (blocks * block_length * sizeof(T)) +
+ EstimateIterableMemoryUsage(deque);
+}
+
+// Container adapters
+
+template <class T, class C>
+size_t EstimateMemoryUsage(const std::queue<T, C>& queue) {
+ return EstimateMemoryUsage(GetUnderlyingContainer(queue));
+}
+
+template <class T, class C>
+size_t EstimateMemoryUsage(const std::priority_queue<T, C>& queue) {
+ return EstimateMemoryUsage(GetUnderlyingContainer(queue));
+}
+
+template <class T, class C>
+size_t EstimateMemoryUsage(const std::stack<T, C>& stack) {
+ return EstimateMemoryUsage(GetUnderlyingContainer(stack));
+}
+
+// base::circular_deque
+
+template <class T>
+size_t EstimateMemoryUsage(const base::circular_deque<T>& deque) {
+ return sizeof(T) * deque.capacity() + EstimateIterableMemoryUsage(deque);
+}
+
+// Flat containers
+
+template <class T, class C>
+size_t EstimateMemoryUsage(const base::flat_set<T, C>& set) {
+ using value_type = typename base::flat_set<T, C>::value_type;
+ return sizeof(value_type) * set.capacity() + EstimateIterableMemoryUsage(set);
+}
+
+template <class K, class V, class C>
+size_t EstimateMemoryUsage(const base::flat_map<K, V, C>& map) {
+ using value_type = typename base::flat_map<K, V, C>::value_type;
+ return sizeof(value_type) * map.capacity() + EstimateIterableMemoryUsage(map);
+}
+
+template <class Key,
+ class Payload,
+ class HashOrComp,
+ template <typename, typename, typename> class Map>
+size_t EstimateMemoryUsage(
+ const MRUCacheBase<Key, Payload, HashOrComp, Map>& mru_cache) {
+ return internal::DoEstimateMemoryUsageForMruCache(mru_cache);
+}
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_
diff --git a/base/trace_event/memory_usage_estimator_unittest.cc b/base/trace_event/memory_usage_estimator_unittest.cc
new file mode 100644
index 0000000000..b525990257
--- /dev/null
+++ b/base/trace_event/memory_usage_estimator_unittest.cc
@@ -0,0 +1,265 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/memory_usage_estimator.h"
+
+#include <stdlib.h>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(ARCH_CPU_64_BITS)
+#define EXPECT_EQ_32_64(_, e, a) EXPECT_EQ(e, a)
+#else
+#define EXPECT_EQ_32_64(e, _, a) EXPECT_EQ(e, a)
+#endif
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+// Test class with predictable memory usage.
+class Data {
+ public:
+ explicit Data(size_t size = 17): size_(size) {
+ }
+
+ size_t size() const { return size_; }
+
+ size_t EstimateMemoryUsage() const {
+ return size_;
+ }
+
+ bool operator < (const Data& other) const {
+ return size_ < other.size_;
+ }
+ bool operator == (const Data& other) const {
+ return size_ == other.size_;
+ }
+
+ struct Hasher {
+ size_t operator () (const Data& data) const {
+ return data.size();
+ }
+ };
+
+ private:
+ size_t size_;
+};
+
+} // namespace
+
+namespace internal {
+
+// This kills variance of bucket_count across STL implementations.
+template <>
+size_t HashMapBucketCountForTesting<Data>(size_t) {
+ return 10;
+}
+template <>
+size_t HashMapBucketCountForTesting<std::pair<const Data, short>>(size_t) {
+ return 10;
+}
+
+} // namespace internal
+
+TEST(EstimateMemoryUsageTest, String) {
+ std::string string(777, 'a');
+ EXPECT_EQ(string.capacity() + 1, EstimateMemoryUsage(string));
+}
+
+TEST(EstimateMemoryUsageTest, String16) {
+ string16 string(777, 'a');
+ EXPECT_EQ(sizeof(char16) * (string.capacity() + 1),
+ EstimateMemoryUsage(string));
+}
+
+TEST(EstimateMemoryUsageTest, Arrays) {
+ // std::array
+ {
+ std::array<Data, 10> array;
+ EXPECT_EQ(170u, EstimateMemoryUsage(array));
+ }
+
+ // T[N]
+ {
+ Data array[10];
+ EXPECT_EQ(170u, EstimateMemoryUsage(array));
+ }
+
+ // C array
+ {
+ struct Item {
+ char payload[10];
+ };
+ Item* array = new Item[7];
+ EXPECT_EQ(70u, EstimateMemoryUsage(array, 7));
+ delete[] array;
+ }
+}
+
+TEST(EstimateMemoryUsageTest, UniquePtr) {
+ // Empty
+ {
+ std::unique_ptr<Data> ptr;
+ EXPECT_EQ(0u, EstimateMemoryUsage(ptr));
+ }
+
+ // Not empty
+ {
+ std::unique_ptr<Data> ptr(new Data());
+ EXPECT_EQ_32_64(21u, 25u, EstimateMemoryUsage(ptr));
+ }
+
+ // With a pointer
+ {
+ std::unique_ptr<Data*> ptr(new Data*());
+ EXPECT_EQ(sizeof(void*), EstimateMemoryUsage(ptr));
+ }
+
+ // With an array
+ {
+ struct Item {
+ uint32_t payload[10];
+ };
+ std::unique_ptr<Item[]> ptr(new Item[7]);
+ EXPECT_EQ(280u, EstimateMemoryUsage(ptr, 7));
+ }
+}
+
+TEST(EstimateMemoryUsageTest, Vector) {
+ std::vector<Data> vector;
+ vector.reserve(1000);
+
+ // For an empty vector we should return memory usage of its buffer
+ size_t capacity = vector.capacity();
+ size_t expected_size = capacity * sizeof(Data);
+ EXPECT_EQ(expected_size, EstimateMemoryUsage(vector));
+
+ // If vector is not empty, its size should also include memory usages
+ // of all elements.
+ for (size_t i = 0; i != capacity / 2; ++i) {
+ vector.push_back(Data(i));
+ expected_size += EstimateMemoryUsage(vector.back());
+ }
+ EXPECT_EQ(expected_size, EstimateMemoryUsage(vector));
+}
+
+TEST(EstimateMemoryUsageTest, List) {
+ struct POD {
+ short data;
+ };
+ std::list<POD> list;
+ for (int i = 0; i != 1000; ++i) {
+ list.push_back(POD());
+ }
+ EXPECT_EQ_32_64(12000u, 24000u, EstimateMemoryUsage(list));
+}
+
+TEST(EstimateMemoryUsageTest, Set) {
+ std::set<std::pair<int, Data>> set;
+ for (int i = 0; i != 1000; ++i) {
+ set.insert({i, Data(i)});
+ }
+ EXPECT_EQ_32_64(523500u, 547500u, EstimateMemoryUsage(set));
+}
+
+TEST(EstimateMemoryUsageTest, MultiSet) {
+ std::multiset<bool> set;
+ for (int i = 0; i != 1000; ++i) {
+ set.insert((i & 1) != 0);
+ }
+ EXPECT_EQ_32_64(16000u, 32000u, EstimateMemoryUsage(set));
+}
+
+TEST(EstimateMemoryUsageTest, Map) {
+ std::map<Data, int> map;
+ for (int i = 0; i != 1000; ++i) {
+ map.insert({Data(i), i});
+ }
+ EXPECT_EQ_32_64(523500u, 547500u, EstimateMemoryUsage(map));
+}
+
+TEST(EstimateMemoryUsageTest, MultiMap) {
+ std::multimap<char, Data> map;
+ for (int i = 0; i != 1000; ++i) {
+ map.insert({static_cast<char>(i), Data(i)});
+ }
+ EXPECT_EQ_32_64(523500u, 547500u, EstimateMemoryUsage(map));
+}
+
+TEST(EstimateMemoryUsageTest, UnorderedSet) {
+ std::unordered_set<Data, Data::Hasher> set;
+ for (int i = 0; i != 1000; ++i) {
+ set.insert(Data(i));
+ }
+ EXPECT_EQ_32_64(511540u, 523580u, EstimateMemoryUsage(set));
+}
+
+TEST(EstimateMemoryUsageTest, UnorderedMultiSet) {
+ std::unordered_multiset<Data, Data::Hasher> set;
+ for (int i = 0; i != 500; ++i) {
+ set.insert(Data(i));
+ set.insert(Data(i));
+ }
+ EXPECT_EQ_32_64(261540u, 273580u, EstimateMemoryUsage(set));
+}
+
+TEST(EstimateMemoryUsageTest, UnorderedMap) {
+ std::unordered_map<Data, short, Data::Hasher> map;
+ for (int i = 0; i != 1000; ++i) {
+ map.insert({Data(i), static_cast<short>(i)});
+ }
+ EXPECT_EQ_32_64(515540u, 531580u, EstimateMemoryUsage(map));
+}
+
+TEST(EstimateMemoryUsageTest, UnorderedMultiMap) {
+ std::unordered_multimap<Data, short, Data::Hasher> map;
+ for (int i = 0; i != 1000; ++i) {
+ map.insert({Data(i), static_cast<short>(i)});
+ }
+ EXPECT_EQ_32_64(515540u, 531580u, EstimateMemoryUsage(map));
+}
+
+TEST(EstimateMemoryUsageTest, Deque) {
+ std::deque<Data> deque;
+
+ // Pick a large value so that platform-specific accounting
+ // for deque's blocks is small compared to usage of all items.
+ constexpr size_t kDataSize = 100000;
+ for (int i = 0; i != 1500; ++i) {
+ deque.push_back(Data(kDataSize));
+ }
+
+ // Compare against a reasonable minimum (i.e. no overhead).
+ size_t min_expected_usage = deque.size() * (sizeof(Data) + kDataSize);
+ EXPECT_LE(min_expected_usage, EstimateMemoryUsage(deque));
+}
+
+TEST(EstimateMemoryUsageTest, IsStandardContainerComplexIteratorTest) {
+ struct abstract {
+ virtual void method() = 0;
+ };
+
+ static_assert(
+ internal::IsStandardContainerComplexIterator<std::list<int>::iterator>(),
+ "");
+ static_assert(internal::IsStandardContainerComplexIterator<
+ std::list<int>::const_iterator>(),
+ "");
+ static_assert(internal::IsStandardContainerComplexIterator<
+ std::list<int>::reverse_iterator>(),
+ "");
+ static_assert(internal::IsStandardContainerComplexIterator<
+ std::list<int>::const_reverse_iterator>(),
+ "");
+ static_assert(!internal::IsStandardContainerComplexIterator<int>(), "");
+ static_assert(!internal::IsStandardContainerComplexIterator<abstract*>(), "");
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/process_memory_dump.cc b/base/trace_event/process_memory_dump.cc
new file mode 100644
index 0000000000..362641c400
--- /dev/null
+++ b/base/trace_event/process_memory_dump.cc
@@ -0,0 +1,511 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/process_memory_dump.h"
+
+#include <errno.h>
+
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/shared_memory_tracker.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/memory_infra_background_whitelist.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "base/unguessable_token.h"
+#include "build/build_config.h"
+
+#if defined(OS_IOS)
+#include <mach/vm_page_size.h>
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <sys/mman.h>
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h> // Must be in front of other Windows header files
+
+#include <Psapi.h>
+#endif
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+const char kEdgeTypeOwnership[] = "ownership";
+
+std::string GetSharedGlobalAllocatorDumpName(
+ const MemoryAllocatorDumpGuid& guid) {
+ return "global/" + guid.ToString();
+}
+
+#if defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+size_t GetSystemPageCount(size_t mapped_size, size_t page_size) {
+ return (mapped_size + page_size - 1) / page_size;
+}
+#endif
+
+UnguessableToken GetTokenForCurrentProcess() {
+ static UnguessableToken instance = UnguessableToken::Create();
+ return instance;
+}
+
+} // namespace
+
+// static
+bool ProcessMemoryDump::is_black_hole_non_fatal_for_testing_ = false;
+
+#if defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+// static
+size_t ProcessMemoryDump::GetSystemPageSize() {
+#if defined(OS_IOS)
+ // On iOS, getpagesize() returns the user page sizes, but for allocating
+ // arrays for mincore(), kernel page sizes is needed. Use vm_kernel_page_size
+ // as recommended by Apple, https://forums.developer.apple.com/thread/47532/.
+ // Refer to http://crbug.com/542671 and Apple rdar://23651782
+ return vm_kernel_page_size;
+#else
+ return base::GetPageSize();
+#endif // defined(OS_IOS)
+}
+
+// static
+size_t ProcessMemoryDump::CountResidentBytes(void* start_address,
+ size_t mapped_size) {
+ const size_t page_size = GetSystemPageSize();
+ const uintptr_t start_pointer = reinterpret_cast<uintptr_t>(start_address);
+ DCHECK_EQ(0u, start_pointer % page_size);
+
+ size_t offset = 0;
+ size_t total_resident_pages = 0;
+ bool failure = false;
+
+ // An array as large as number of pages in memory segment needs to be passed
+ // to the query function. To avoid allocating a large array, the given block
+ // of memory is split into chunks of size |kMaxChunkSize|.
+ const size_t kMaxChunkSize = 8 * 1024 * 1024;
+ size_t max_vec_size =
+ GetSystemPageCount(std::min(mapped_size, kMaxChunkSize), page_size);
+#if defined(OS_WIN)
+ std::unique_ptr<PSAPI_WORKING_SET_EX_INFORMATION[]> vec(
+ new PSAPI_WORKING_SET_EX_INFORMATION[max_vec_size]);
+#elif defined(OS_MACOSX)
+ std::unique_ptr<char[]> vec(new char[max_vec_size]);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ std::unique_ptr<unsigned char[]> vec(new unsigned char[max_vec_size]);
+#endif
+
+ while (offset < mapped_size) {
+ uintptr_t chunk_start = (start_pointer + offset);
+ const size_t chunk_size = std::min(mapped_size - offset, kMaxChunkSize);
+ const size_t page_count = GetSystemPageCount(chunk_size, page_size);
+ size_t resident_page_count = 0;
+#if defined(OS_WIN)
+ for (size_t i = 0; i < page_count; i++) {
+ vec[i].VirtualAddress =
+ reinterpret_cast<void*>(chunk_start + i * page_size);
+ }
+ DWORD vec_size = static_cast<DWORD>(
+ page_count * sizeof(PSAPI_WORKING_SET_EX_INFORMATION));
+ failure = !QueryWorkingSetEx(GetCurrentProcess(), vec.get(), vec_size);
+
+ for (size_t i = 0; i < page_count; i++)
+ resident_page_count += vec[i].VirtualAttributes.Valid;
+#elif defined(OS_FUCHSIA)
+ // TODO(fuchsia): Port, see https://crbug.com/706592.
+ ALLOW_UNUSED_LOCAL(chunk_start);
+ ALLOW_UNUSED_LOCAL(page_count);
+#elif defined(OS_MACOSX)
+ // mincore in MAC does not fail with EAGAIN.
+ failure =
+ !!mincore(reinterpret_cast<void*>(chunk_start), chunk_size, vec.get());
+ for (size_t i = 0; i < page_count; i++)
+ resident_page_count += vec[i] & MINCORE_INCORE ? 1 : 0;
+#elif defined(OS_POSIX)
+ int error_counter = 0;
+ int result = 0;
+ // HANDLE_EINTR tries for 100 times. So following the same pattern.
+ do {
+ result =
+#if defined(OS_AIX)
+ mincore(reinterpret_cast<char*>(chunk_start), chunk_size,
+ reinterpret_cast<char*>(vec.get()));
+#else
+ mincore(reinterpret_cast<void*>(chunk_start), chunk_size, vec.get());
+#endif
+ } while (result == -1 && errno == EAGAIN && error_counter++ < 100);
+ failure = !!result;
+
+ for (size_t i = 0; i < page_count; i++)
+ resident_page_count += vec[i] & 1;
+#endif
+
+ if (failure)
+ break;
+
+ total_resident_pages += resident_page_count * page_size;
+ offset += kMaxChunkSize;
+ }
+
+ DCHECK(!failure);
+ if (failure) {
+ total_resident_pages = 0;
+ LOG(ERROR) << "CountResidentBytes failed. The resident size is invalid";
+ }
+ return total_resident_pages;
+}
+
+// static
+base::Optional<size_t> ProcessMemoryDump::CountResidentBytesInSharedMemory(
+ void* start_address,
+ size_t mapped_size) {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // On macOS, use mach_vm_region instead of mincore for performance
+ // (crbug.com/742042).
+ mach_vm_size_t dummy_size = 0;
+ mach_vm_address_t address =
+ reinterpret_cast<mach_vm_address_t>(start_address);
+ vm_region_top_info_data_t info;
+ MachVMRegionResult result =
+ GetTopInfo(mach_task_self(), &dummy_size, &address, &info);
+ if (result == MachVMRegionResult::Error) {
+ LOG(ERROR) << "CountResidentBytesInSharedMemory failed. The resident size "
+ "is invalid";
+ return base::Optional<size_t>();
+ }
+
+ size_t resident_pages =
+ info.private_pages_resident + info.shared_pages_resident;
+
+ // On macOS, measurements for private memory footprint overcount by
+ // faulted pages in anonymous shared memory. To discount for this, we touch
+ // all the resident pages in anonymous shared memory here, thus making them
+ // faulted as well. This relies on two assumptions:
+ //
+ // 1) Consumers use shared memory from front to back. Thus, if there are
+ // (N) resident pages, those pages represent the first N * PAGE_SIZE bytes in
+ // the shared memory region.
+ //
+ // 2) This logic is run shortly before the logic that calculates
+ // phys_footprint, thus ensuring that the discrepancy between faulted and
+ // resident pages is minimal.
+ //
+ // The performance penalty is expected to be small.
+ //
+ // * Most of the time, we expect the pages to already be resident and faulted,
+ // thus incurring a cache penalty read hit [since we read from each resident
+ // page].
+ //
+ // * Rarely, we expect the pages to be resident but not faulted, resulting in
+ // soft faults + cache penalty.
+ //
+ // * If assumption (1) is invalid, this will potentially fault some
+ // previously non-resident pages, thus increasing memory usage, without fixing
+ // the accounting.
+ //
+ // Sanity check in case the mapped size is less than the total size of the
+ // region.
+ size_t pages_to_fault =
+ std::min(resident_pages, (mapped_size + PAGE_SIZE - 1) / PAGE_SIZE);
+
+ volatile char* base_address = static_cast<char*>(start_address);
+ for (size_t i = 0; i < pages_to_fault; ++i) {
+ // Reading from a volatile is a visible side-effect for the purposes of
+ // optimization. This guarantees that the optimizer will not kill this line.
+ base_address[i * PAGE_SIZE];
+ }
+
+ return resident_pages * PAGE_SIZE;
+#else
+ return CountResidentBytes(start_address, mapped_size);
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+}
+
+#endif // defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+
+ProcessMemoryDump::ProcessMemoryDump(
+ const MemoryDumpArgs& dump_args)
+ : process_token_(GetTokenForCurrentProcess()),
+ dump_args_(dump_args) {}
+
+ProcessMemoryDump::~ProcessMemoryDump() = default;
+ProcessMemoryDump::ProcessMemoryDump(ProcessMemoryDump&& other) = default;
+ProcessMemoryDump& ProcessMemoryDump::operator=(ProcessMemoryDump&& other) =
+ default;
+
+MemoryAllocatorDump* ProcessMemoryDump::CreateAllocatorDump(
+ const std::string& absolute_name) {
+ return AddAllocatorDumpInternal(std::make_unique<MemoryAllocatorDump>(
+ absolute_name, dump_args_.level_of_detail, GetDumpId(absolute_name)));
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::CreateAllocatorDump(
+ const std::string& absolute_name,
+ const MemoryAllocatorDumpGuid& guid) {
+ return AddAllocatorDumpInternal(std::make_unique<MemoryAllocatorDump>(
+ absolute_name, dump_args_.level_of_detail, guid));
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::AddAllocatorDumpInternal(
+ std::unique_ptr<MemoryAllocatorDump> mad) {
+ // In background mode return the black hole dump, if invalid dump name is
+ // given.
+ if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND &&
+ !IsMemoryAllocatorDumpNameWhitelisted(mad->absolute_name())) {
+ return GetBlackHoleMad();
+ }
+
+ auto insertion_result = allocator_dumps_.insert(
+ std::make_pair(mad->absolute_name(), std::move(mad)));
+ MemoryAllocatorDump* inserted_mad = insertion_result.first->second.get();
+ DCHECK(insertion_result.second) << "Duplicate name: "
+ << inserted_mad->absolute_name();
+ return inserted_mad;
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::GetAllocatorDump(
+ const std::string& absolute_name) const {
+ auto it = allocator_dumps_.find(absolute_name);
+ if (it != allocator_dumps_.end())
+ return it->second.get();
+ return nullptr;
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::GetOrCreateAllocatorDump(
+ const std::string& absolute_name) {
+ MemoryAllocatorDump* mad = GetAllocatorDump(absolute_name);
+ return mad ? mad : CreateAllocatorDump(absolute_name);
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::CreateSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid) {
+ // A shared allocator dump can be shared within a process and the guid could
+ // have been created already.
+ MemoryAllocatorDump* mad = GetSharedGlobalAllocatorDump(guid);
+ if (mad && mad != black_hole_mad_.get()) {
+ // The weak flag is cleared because this method should create a non-weak
+ // dump.
+ mad->clear_flags(MemoryAllocatorDump::Flags::WEAK);
+ return mad;
+ }
+ return CreateAllocatorDump(GetSharedGlobalAllocatorDumpName(guid), guid);
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::CreateWeakSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid) {
+ MemoryAllocatorDump* mad = GetSharedGlobalAllocatorDump(guid);
+ if (mad && mad != black_hole_mad_.get())
+ return mad;
+ mad = CreateAllocatorDump(GetSharedGlobalAllocatorDumpName(guid), guid);
+ mad->set_flags(MemoryAllocatorDump::Flags::WEAK);
+ return mad;
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::GetSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid) const {
+ return GetAllocatorDump(GetSharedGlobalAllocatorDumpName(guid));
+}
+
+void ProcessMemoryDump::DumpHeapUsage(
+ const std::unordered_map<base::trace_event::AllocationContext,
+ base::trace_event::AllocationMetrics>&
+ metrics_by_context,
+ base::trace_event::TraceEventMemoryOverhead& overhead,
+ const char* allocator_name) {
+ std::string base_name = base::StringPrintf("tracing/heap_profiler_%s",
+ allocator_name);
+ overhead.DumpInto(base_name.c_str(), this);
+}
+
+void ProcessMemoryDump::SetAllocatorDumpsForSerialization(
+ std::vector<std::unique_ptr<MemoryAllocatorDump>> dumps) {
+ DCHECK(allocator_dumps_.empty());
+ for (std::unique_ptr<MemoryAllocatorDump>& dump : dumps)
+ AddAllocatorDumpInternal(std::move(dump));
+}
+
+std::vector<ProcessMemoryDump::MemoryAllocatorDumpEdge>
+ProcessMemoryDump::GetAllEdgesForSerialization() const {
+ std::vector<MemoryAllocatorDumpEdge> edges;
+ edges.reserve(allocator_dumps_edges_.size());
+ for (const auto& it : allocator_dumps_edges_)
+ edges.push_back(it.second);
+ return edges;
+}
+
+void ProcessMemoryDump::SetAllEdgesForSerialization(
+ const std::vector<ProcessMemoryDump::MemoryAllocatorDumpEdge>& edges) {
+ DCHECK(allocator_dumps_edges_.empty());
+ for (const MemoryAllocatorDumpEdge& edge : edges) {
+ auto it_and_inserted = allocator_dumps_edges_.emplace(edge.source, edge);
+ DCHECK(it_and_inserted.second);
+ }
+}
+
+void ProcessMemoryDump::Clear() {
+ allocator_dumps_.clear();
+ allocator_dumps_edges_.clear();
+}
+
+void ProcessMemoryDump::TakeAllDumpsFrom(ProcessMemoryDump* other) {
+ // Moves the ownership of all MemoryAllocatorDump(s) contained in |other|
+ // into this ProcessMemoryDump, checking for duplicates.
+ for (auto& it : other->allocator_dumps_)
+ AddAllocatorDumpInternal(std::move(it.second));
+ other->allocator_dumps_.clear();
+
+ // Move all the edges.
+ allocator_dumps_edges_.insert(other->allocator_dumps_edges_.begin(),
+ other->allocator_dumps_edges_.end());
+ other->allocator_dumps_edges_.clear();
+}
+
+void ProcessMemoryDump::SerializeAllocatorDumpsInto(TracedValue* value) const {
+ if (allocator_dumps_.size() > 0) {
+ value->BeginDictionary("allocators");
+ for (const auto& allocator_dump_it : allocator_dumps_)
+ allocator_dump_it.second->AsValueInto(value);
+ value->EndDictionary();
+ }
+
+ value->BeginArray("allocators_graph");
+ for (const auto& it : allocator_dumps_edges_) {
+ const MemoryAllocatorDumpEdge& edge = it.second;
+ value->BeginDictionary();
+ value->SetString("source", edge.source.ToString());
+ value->SetString("target", edge.target.ToString());
+ value->SetInteger("importance", edge.importance);
+ value->SetString("type", kEdgeTypeOwnership);
+ value->EndDictionary();
+ }
+ value->EndArray();
+}
+
+void ProcessMemoryDump::AddOwnershipEdge(const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target,
+ int importance) {
+ // This will either override an existing edge or create a new one.
+ auto it = allocator_dumps_edges_.find(source);
+ int max_importance = importance;
+ if (it != allocator_dumps_edges_.end()) {
+ DCHECK_EQ(target.ToUint64(), it->second.target.ToUint64());
+ max_importance = std::max(importance, it->second.importance);
+ }
+ allocator_dumps_edges_[source] = {source, target, max_importance,
+ false /* overridable */};
+}
+
+void ProcessMemoryDump::AddOwnershipEdge(
+ const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target) {
+ AddOwnershipEdge(source, target, 0 /* importance */);
+}
+
+void ProcessMemoryDump::AddOverridableOwnershipEdge(
+ const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target,
+ int importance) {
+ if (allocator_dumps_edges_.count(source) == 0) {
+ allocator_dumps_edges_[source] = {source, target, importance,
+ true /* overridable */};
+ } else {
+ // An edge between the source and target already exits. So, do nothing here
+ // since the new overridable edge is implicitly overridden by a strong edge
+ // which was created earlier.
+ DCHECK(!allocator_dumps_edges_[source].overridable);
+ }
+}
+
+void ProcessMemoryDump::CreateSharedMemoryOwnershipEdge(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance) {
+ CreateSharedMemoryOwnershipEdgeInternal(client_local_dump_guid,
+ shared_memory_guid, importance,
+ false /*is_weak*/);
+}
+
+void ProcessMemoryDump::CreateWeakSharedMemoryOwnershipEdge(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance) {
+ CreateSharedMemoryOwnershipEdgeInternal(
+ client_local_dump_guid, shared_memory_guid, importance, true /*is_weak*/);
+}
+
+void ProcessMemoryDump::CreateSharedMemoryOwnershipEdgeInternal(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance,
+ bool is_weak) {
+ DCHECK(!shared_memory_guid.is_empty());
+ // New model where the global dumps created by SharedMemoryTracker are used
+ // for the clients.
+
+ // The guid of the local dump created by SharedMemoryTracker for the memory
+ // segment.
+ auto local_shm_guid =
+ GetDumpId(SharedMemoryTracker::GetDumpNameForTracing(shared_memory_guid));
+
+ // The dump guid of the global dump created by the tracker for the memory
+ // segment.
+ auto global_shm_guid =
+ SharedMemoryTracker::GetGlobalDumpIdForTracing(shared_memory_guid);
+
+ // Create an edge between local dump of the client and the local dump of the
+ // SharedMemoryTracker. Do not need to create the dumps here since the tracker
+ // would create them. The importance is also required here for the case of
+ // single process mode.
+ AddOwnershipEdge(client_local_dump_guid, local_shm_guid, importance);
+
+ // TODO(ssid): Handle the case of weak dumps here. This needs a new function
+ // GetOrCreaetGlobalDump() in PMD since we need to change the behavior of the
+ // created global dump.
+ // Create an edge that overrides the edge created by SharedMemoryTracker.
+ AddOwnershipEdge(local_shm_guid, global_shm_guid, importance);
+}
+
+void ProcessMemoryDump::AddSuballocation(const MemoryAllocatorDumpGuid& source,
+ const std::string& target_node_name) {
+ // Do not create new dumps for suballocations in background mode.
+ if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND)
+ return;
+
+ std::string child_mad_name = target_node_name + "/__" + source.ToString();
+ MemoryAllocatorDump* target_child_mad = CreateAllocatorDump(child_mad_name);
+ AddOwnershipEdge(source, target_child_mad->guid());
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::GetBlackHoleMad() {
+ DCHECK(is_black_hole_non_fatal_for_testing_);
+ if (!black_hole_mad_) {
+ std::string name = "discarded";
+ black_hole_mad_.reset(new MemoryAllocatorDump(
+ name, dump_args_.level_of_detail, GetDumpId(name)));
+ }
+ return black_hole_mad_.get();
+}
+
+MemoryAllocatorDumpGuid ProcessMemoryDump::GetDumpId(
+ const std::string& absolute_name) {
+ return MemoryAllocatorDumpGuid(StringPrintf(
+ "%s:%s", process_token().ToString().c_str(), absolute_name.c_str()));
+}
+
+bool ProcessMemoryDump::MemoryAllocatorDumpEdge::operator==(
+ const MemoryAllocatorDumpEdge& other) const {
+ return source == other.source && target == other.target &&
+ importance == other.importance && overridable == other.overridable;
+}
+
+bool ProcessMemoryDump::MemoryAllocatorDumpEdge::operator!=(
+ const MemoryAllocatorDumpEdge& other) const {
+ return !(*this == other);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/process_memory_dump.h b/base/trace_event/process_memory_dump.h
new file mode 100644
index 0000000000..e2457b7389
--- /dev/null
+++ b/base/trace_event/process_memory_dump.h
@@ -0,0 +1,284 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_PROCESS_MEMORY_DUMP_H_
+#define BASE_TRACE_EVENT_PROCESS_MEMORY_DUMP_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/trace_event/heap_profiler_allocation_context.h"
+#include "base/trace_event/memory_allocator_dump.h"
+#include "base/trace_event/memory_allocator_dump_guid.h"
+#include "base/trace_event/memory_dump_request_args.h"
+#include "build/build_config.h"
+
+// Define COUNT_RESIDENT_BYTES_SUPPORTED if platform supports counting of the
+// resident memory.
+#if !defined(OS_NACL)
+#define COUNT_RESIDENT_BYTES_SUPPORTED
+#endif
+
+namespace base {
+
+class SharedMemory;
+class UnguessableToken;
+
+namespace trace_event {
+
+class TracedValue;
+
+// ProcessMemoryDump is as a strongly typed container which holds the dumps
+// produced by the MemoryDumpProvider(s) for a specific process.
+class BASE_EXPORT ProcessMemoryDump {
+ public:
+ struct BASE_EXPORT MemoryAllocatorDumpEdge {
+ bool operator==(const MemoryAllocatorDumpEdge&) const;
+ bool operator!=(const MemoryAllocatorDumpEdge&) const;
+
+ MemoryAllocatorDumpGuid source;
+ MemoryAllocatorDumpGuid target;
+ int importance = 0;
+ bool overridable = false;
+ };
+
+ // Maps allocator dumps absolute names (allocator_name/heap/subheap) to
+ // MemoryAllocatorDump instances.
+ using AllocatorDumpsMap =
+ std::map<std::string, std::unique_ptr<MemoryAllocatorDump>>;
+
+ // Stores allocator dump edges indexed by source allocator dump GUID.
+ using AllocatorDumpEdgesMap =
+ std::map<MemoryAllocatorDumpGuid, MemoryAllocatorDumpEdge>;
+
+#if defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+ // Returns the number of bytes in a kernel memory page. Some platforms may
+ // have a different value for kernel page sizes from user page sizes. It is
+ // important to use kernel memory page sizes for resident bytes calculation.
+ // In most cases, the two are the same.
+ static size_t GetSystemPageSize();
+
+ // Returns the total bytes resident for a virtual address range, with given
+ // |start_address| and |mapped_size|. |mapped_size| is specified in bytes. The
+ // value returned is valid only if the given range is currently mmapped by the
+ // process. The |start_address| must be page-aligned.
+ static size_t CountResidentBytes(void* start_address, size_t mapped_size);
+
+ // The same as above, but the given mapped range should belong to the
+ // shared_memory's mapped region.
+ static base::Optional<size_t> CountResidentBytesInSharedMemory(
+ void* start_address,
+ size_t mapped_size);
+#endif
+
+ explicit ProcessMemoryDump(const MemoryDumpArgs& dump_args);
+ ProcessMemoryDump(ProcessMemoryDump&&);
+ ~ProcessMemoryDump();
+
+ ProcessMemoryDump& operator=(ProcessMemoryDump&&);
+
+ // Creates a new MemoryAllocatorDump with the given name and returns the
+ // empty object back to the caller.
+ // Arguments:
+ // absolute_name: a name that uniquely identifies allocator dumps produced
+ // by this provider. It is possible to specify nesting by using a
+ // path-like string (e.g., v8/isolate1/heap1, v8/isolate1/heap2).
+ // Leading or trailing slashes are not allowed.
+ // guid: an optional identifier, unique among all processes within the
+ // scope of a global dump. This is only relevant when using
+ // AddOwnershipEdge() to express memory sharing. If omitted,
+ // it will be automatically generated.
+ // ProcessMemoryDump handles the memory ownership of its MemoryAllocatorDumps.
+ MemoryAllocatorDump* CreateAllocatorDump(const std::string& absolute_name);
+ MemoryAllocatorDump* CreateAllocatorDump(const std::string& absolute_name,
+ const MemoryAllocatorDumpGuid& guid);
+
+ // Looks up a MemoryAllocatorDump given its allocator and heap names, or
+ // nullptr if not found.
+ MemoryAllocatorDump* GetAllocatorDump(const std::string& absolute_name) const;
+
+ // Do NOT use this method. All dump providers should use
+ // CreateAllocatorDump(). Tries to create a new MemoryAllocatorDump only if it
+ // doesn't already exist. Creating multiple dumps with same name using
+ // GetOrCreateAllocatorDump() would override the existing scalars in MAD and
+ // cause misreporting. This method is used only in rare cases multiple
+ // components create allocator dumps with same name and only one of them adds
+ // size.
+ MemoryAllocatorDump* GetOrCreateAllocatorDump(
+ const std::string& absolute_name);
+
+ // Creates a shared MemoryAllocatorDump, to express cross-process sharing.
+ // Shared allocator dumps are allowed to have duplicate guids within the
+ // global scope, in order to reference the same dump from multiple processes.
+ // See the design doc goo.gl/keU6Bf for reference usage patterns.
+ MemoryAllocatorDump* CreateSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid);
+
+ // Creates a shared MemoryAllocatorDump as CreateSharedGlobalAllocatorDump,
+ // but with a WEAK flag. A weak dump will be discarded unless a non-weak dump
+ // is created using CreateSharedGlobalAllocatorDump by at least one process.
+ // The WEAK flag does not apply if a non-weak dump with the same GUID already
+ // exists or is created later. All owners and children of the discarded dump
+ // will also be discarded transitively.
+ MemoryAllocatorDump* CreateWeakSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid);
+
+ // Looks up a shared MemoryAllocatorDump given its guid.
+ MemoryAllocatorDump* GetSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid) const;
+
+ // Returns the map of the MemoryAllocatorDumps added to this dump.
+ const AllocatorDumpsMap& allocator_dumps() const { return allocator_dumps_; }
+
+ AllocatorDumpsMap* mutable_allocator_dumps_for_serialization() const {
+ // Mojo takes a const input argument even for move-only types that can be
+ // mutate while serializing (like this one). Hence the const_cast.
+ return const_cast<AllocatorDumpsMap*>(&allocator_dumps_);
+ }
+ void SetAllocatorDumpsForSerialization(
+ std::vector<std::unique_ptr<MemoryAllocatorDump>>);
+
+ // Only for mojo serialization.
+ std::vector<MemoryAllocatorDumpEdge> GetAllEdgesForSerialization() const;
+ void SetAllEdgesForSerialization(const std::vector<MemoryAllocatorDumpEdge>&);
+
+ // Dumps heap usage with |allocator_name|.
+ void DumpHeapUsage(
+ const std::unordered_map<base::trace_event::AllocationContext,
+ base::trace_event::AllocationMetrics>&
+ metrics_by_context,
+ base::trace_event::TraceEventMemoryOverhead& overhead,
+ const char* allocator_name);
+
+ // Adds an ownership relationship between two MemoryAllocatorDump(s) with the
+ // semantics: |source| owns |target|, and has the effect of attributing
+ // the memory usage of |target| to |source|. |importance| is optional and
+ // relevant only for the cases of co-ownership, where it acts as a z-index:
+ // the owner with the highest importance will be attributed |target|'s memory.
+ // If an edge is present, its importance will not be updated unless
+ // |importance| is larger.
+ void AddOwnershipEdge(const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target,
+ int importance);
+ void AddOwnershipEdge(const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target);
+
+ // Adds edges that can be overriden by a later or earlier call to
+ // AddOwnershipEdge() with the same source and target with a different
+ // |importance| value.
+ void AddOverridableOwnershipEdge(const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target,
+ int importance);
+
+ // Creates ownership edges for memory backed by base::SharedMemory. Handles
+ // the case of cross process sharing and importnace of ownership for the case
+ // with and without the base::SharedMemory dump provider. The new version
+ // should just use global dumps created by SharedMemoryTracker and this
+ // function handles the transition until we get SharedMemory IDs through mojo
+ // channel crbug.com/713763. The weak version creates a weak global dump.
+ // |client_local_dump_guid| The guid of the local dump created by the client
+ // of base::SharedMemory.
+ // |shared_memory_guid| The ID of the base::SharedMemory that is assigned
+ // globally, used to create global dump edges in the new model.
+ // |importance| Importance of the global dump edges to say if the current
+ // process owns the memory segment.
+ void CreateSharedMemoryOwnershipEdge(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance);
+ void CreateWeakSharedMemoryOwnershipEdge(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance);
+
+ const AllocatorDumpEdgesMap& allocator_dumps_edges() const {
+ return allocator_dumps_edges_;
+ }
+
+ // Utility method to add a suballocation relationship with the following
+ // semantics: |source| is suballocated from |target_node_name|.
+ // This creates a child node of |target_node_name| and adds an ownership edge
+ // between |source| and the new child node. As a result, the UI will not
+ // account the memory of |source| in the target node.
+ void AddSuballocation(const MemoryAllocatorDumpGuid& source,
+ const std::string& target_node_name);
+
+ // Removes all the MemoryAllocatorDump(s) contained in this instance. This
+ // ProcessMemoryDump can be safely reused as if it was new once this returns.
+ void Clear();
+
+ // Merges all MemoryAllocatorDump(s) contained in |other| inside this
+ // ProcessMemoryDump, transferring their ownership to this instance.
+ // |other| will be an empty ProcessMemoryDump after this method returns.
+ // This is to allow dump providers to pre-populate ProcessMemoryDump instances
+ // and later move their contents into the ProcessMemoryDump passed as argument
+ // of the MemoryDumpProvider::OnMemoryDump(ProcessMemoryDump*) callback.
+ void TakeAllDumpsFrom(ProcessMemoryDump* other);
+
+ // Populate the traced value with information about the memory allocator
+ // dumps.
+ void SerializeAllocatorDumpsInto(TracedValue* value) const;
+
+ const MemoryDumpArgs& dump_args() const { return dump_args_; }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ProcessMemoryDumpTest, BackgroundModeTest);
+ FRIEND_TEST_ALL_PREFIXES(ProcessMemoryDumpTest, SharedMemoryOwnershipTest);
+ FRIEND_TEST_ALL_PREFIXES(ProcessMemoryDumpTest, GuidsTest);
+
+ MemoryAllocatorDump* AddAllocatorDumpInternal(
+ std::unique_ptr<MemoryAllocatorDump> mad);
+
+ // A per-process token, valid throughout all the lifetime of the current
+ // process, used to disambiguate dumps with the same name generated in
+ // different processes.
+ const UnguessableToken& process_token() const { return process_token_; }
+ void set_process_token_for_testing(UnguessableToken token) {
+ process_token_ = token;
+ };
+
+ // Returns the Guid of the dump for the given |absolute_name| for
+ // for the given process' token. |process_token| is used to disambiguate GUIDs
+ // derived from the same name under different processes.
+ MemoryAllocatorDumpGuid GetDumpId(const std::string& absolute_name);
+
+ void CreateSharedMemoryOwnershipEdgeInternal(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance,
+ bool is_weak);
+
+ MemoryAllocatorDump* GetBlackHoleMad();
+
+ UnguessableToken process_token_;
+ AllocatorDumpsMap allocator_dumps_;
+
+ // Keeps track of relationships between MemoryAllocatorDump(s).
+ AllocatorDumpEdgesMap allocator_dumps_edges_;
+
+ // Level of detail of the current dump.
+ MemoryDumpArgs dump_args_;
+
+ // This allocator dump is returned when an invalid dump is created in
+ // background mode. The attributes of the dump are ignored and not added to
+ // the trace.
+ std::unique_ptr<MemoryAllocatorDump> black_hole_mad_;
+
+ // When set to true, the DCHECK(s) for invalid dump creations on the
+ // background mode are disabled for testing.
+ static bool is_black_hole_non_fatal_for_testing_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessMemoryDump);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_PROCESS_MEMORY_DUMP_H_
diff --git a/base/trace_event/process_memory_dump_unittest.cc b/base/trace_event/process_memory_dump_unittest.cc
new file mode 100644
index 0000000000..d5f771d81b
--- /dev/null
+++ b/base/trace_event/process_memory_dump_unittest.cc
@@ -0,0 +1,565 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/process_memory_dump.h"
+
+#include <stddef.h>
+
+#include "base/memory/aligned_memory.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/shared_memory_tracker.h"
+#include "base/process/process_metrics.h"
+#include "base/trace_event/memory_allocator_dump_guid.h"
+#include "base/trace_event/memory_infra_background_whitelist.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "base/trace_event/trace_log.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include "winbase.h"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <sys/mman.h>
+#endif
+
+#if defined(OS_IOS)
+#include "base/ios/ios_util.h"
+#endif
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+const MemoryDumpArgs kDetailedDumpArgs = {MemoryDumpLevelOfDetail::DETAILED};
+const char* const kTestDumpNameWhitelist[] = {
+ "Whitelisted/TestName", "Whitelisted/TestName_0x?",
+ "Whitelisted/0x?/TestName", "Whitelisted/0x?", nullptr};
+
+void* Map(size_t size) {
+#if defined(OS_WIN)
+ return ::VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT,
+ PAGE_READWRITE);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ return ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
+ 0, 0);
+#endif
+}
+
+void Unmap(void* addr, size_t size) {
+#if defined(OS_WIN)
+ ::VirtualFree(addr, 0, MEM_DECOMMIT);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ ::munmap(addr, size);
+#else
+#error This architecture is not (yet) supported.
+#endif
+}
+
+} // namespace
+
+TEST(ProcessMemoryDumpTest, MoveConstructor) {
+ ProcessMemoryDump pmd1 = ProcessMemoryDump(kDetailedDumpArgs);
+ pmd1.CreateAllocatorDump("mad1");
+ pmd1.CreateAllocatorDump("mad2");
+ pmd1.AddOwnershipEdge(MemoryAllocatorDumpGuid(42),
+ MemoryAllocatorDumpGuid(4242));
+
+ ProcessMemoryDump pmd2(std::move(pmd1));
+
+ EXPECT_EQ(1u, pmd2.allocator_dumps().count("mad1"));
+ EXPECT_EQ(1u, pmd2.allocator_dumps().count("mad2"));
+ EXPECT_EQ(MemoryDumpLevelOfDetail::DETAILED,
+ pmd2.dump_args().level_of_detail);
+ EXPECT_EQ(1u, pmd2.allocator_dumps_edges().size());
+
+ // Check that calling serialization routines doesn't cause a crash.
+ auto traced_value = std::make_unique<TracedValue>();
+ pmd2.SerializeAllocatorDumpsInto(traced_value.get());
+}
+
+TEST(ProcessMemoryDumpTest, MoveAssignment) {
+ ProcessMemoryDump pmd1 = ProcessMemoryDump(kDetailedDumpArgs);
+ pmd1.CreateAllocatorDump("mad1");
+ pmd1.CreateAllocatorDump("mad2");
+ pmd1.AddOwnershipEdge(MemoryAllocatorDumpGuid(42),
+ MemoryAllocatorDumpGuid(4242));
+
+ ProcessMemoryDump pmd2({MemoryDumpLevelOfDetail::BACKGROUND});
+ pmd2.CreateAllocatorDump("malloc");
+
+ pmd2 = std::move(pmd1);
+ EXPECT_EQ(1u, pmd2.allocator_dumps().count("mad1"));
+ EXPECT_EQ(1u, pmd2.allocator_dumps().count("mad2"));
+ EXPECT_EQ(0u, pmd2.allocator_dumps().count("mad3"));
+ EXPECT_EQ(MemoryDumpLevelOfDetail::DETAILED,
+ pmd2.dump_args().level_of_detail);
+ EXPECT_EQ(1u, pmd2.allocator_dumps_edges().size());
+
+ // Check that calling serialization routines doesn't cause a crash.
+ auto traced_value = std::make_unique<TracedValue>();
+ pmd2.SerializeAllocatorDumpsInto(traced_value.get());
+}
+
+TEST(ProcessMemoryDumpTest, Clear) {
+ std::unique_ptr<ProcessMemoryDump> pmd1(
+ new ProcessMemoryDump(kDetailedDumpArgs));
+ pmd1->CreateAllocatorDump("mad1");
+ pmd1->CreateAllocatorDump("mad2");
+ ASSERT_FALSE(pmd1->allocator_dumps().empty());
+
+ pmd1->AddOwnershipEdge(MemoryAllocatorDumpGuid(42),
+ MemoryAllocatorDumpGuid(4242));
+
+ MemoryAllocatorDumpGuid shared_mad_guid1(1);
+ MemoryAllocatorDumpGuid shared_mad_guid2(2);
+ pmd1->CreateSharedGlobalAllocatorDump(shared_mad_guid1);
+ pmd1->CreateSharedGlobalAllocatorDump(shared_mad_guid2);
+
+ pmd1->Clear();
+ ASSERT_TRUE(pmd1->allocator_dumps().empty());
+ ASSERT_TRUE(pmd1->allocator_dumps_edges().empty());
+ ASSERT_EQ(nullptr, pmd1->GetAllocatorDump("mad1"));
+ ASSERT_EQ(nullptr, pmd1->GetAllocatorDump("mad2"));
+ ASSERT_EQ(nullptr, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid1));
+ ASSERT_EQ(nullptr, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid2));
+
+ // Check that calling serialization routines doesn't cause a crash.
+ auto traced_value = std::make_unique<TracedValue>();
+ pmd1->SerializeAllocatorDumpsInto(traced_value.get());
+
+ // Check that the pmd can be reused and behaves as expected.
+ auto* mad1 = pmd1->CreateAllocatorDump("mad1");
+ auto* mad3 = pmd1->CreateAllocatorDump("mad3");
+ auto* shared_mad1 = pmd1->CreateSharedGlobalAllocatorDump(shared_mad_guid1);
+ auto* shared_mad2 =
+ pmd1->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid2);
+ ASSERT_EQ(4u, pmd1->allocator_dumps().size());
+ ASSERT_EQ(mad1, pmd1->GetAllocatorDump("mad1"));
+ ASSERT_EQ(nullptr, pmd1->GetAllocatorDump("mad2"));
+ ASSERT_EQ(mad3, pmd1->GetAllocatorDump("mad3"));
+ ASSERT_EQ(shared_mad1, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid1));
+ ASSERT_EQ(MemoryAllocatorDump::Flags::DEFAULT, shared_mad1->flags());
+ ASSERT_EQ(shared_mad2, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid2));
+ ASSERT_EQ(MemoryAllocatorDump::Flags::WEAK, shared_mad2->flags());
+
+ traced_value.reset(new TracedValue);
+ pmd1->SerializeAllocatorDumpsInto(traced_value.get());
+
+ pmd1.reset();
+}
+
+TEST(ProcessMemoryDumpTest, TakeAllDumpsFrom) {
+ std::unique_ptr<TracedValue> traced_value(new TracedValue);
+ std::unordered_map<AllocationContext, AllocationMetrics> metrics_by_context;
+ metrics_by_context[AllocationContext()] = {1, 1};
+ TraceEventMemoryOverhead overhead;
+
+ std::unique_ptr<ProcessMemoryDump> pmd1(
+ new ProcessMemoryDump(kDetailedDumpArgs));
+ auto* mad1_1 = pmd1->CreateAllocatorDump("pmd1/mad1");
+ auto* mad1_2 = pmd1->CreateAllocatorDump("pmd1/mad2");
+ pmd1->AddOwnershipEdge(mad1_1->guid(), mad1_2->guid());
+ pmd1->DumpHeapUsage(metrics_by_context, overhead, "pmd1/heap_dump1");
+ pmd1->DumpHeapUsage(metrics_by_context, overhead, "pmd1/heap_dump2");
+
+ std::unique_ptr<ProcessMemoryDump> pmd2(
+ new ProcessMemoryDump(kDetailedDumpArgs));
+ auto* mad2_1 = pmd2->CreateAllocatorDump("pmd2/mad1");
+ auto* mad2_2 = pmd2->CreateAllocatorDump("pmd2/mad2");
+ pmd2->AddOwnershipEdge(mad2_1->guid(), mad2_2->guid());
+ pmd2->DumpHeapUsage(metrics_by_context, overhead, "pmd2/heap_dump1");
+ pmd2->DumpHeapUsage(metrics_by_context, overhead, "pmd2/heap_dump2");
+
+ MemoryAllocatorDumpGuid shared_mad_guid1(1);
+ MemoryAllocatorDumpGuid shared_mad_guid2(2);
+ auto* shared_mad1 = pmd2->CreateSharedGlobalAllocatorDump(shared_mad_guid1);
+ auto* shared_mad2 =
+ pmd2->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid2);
+
+ pmd1->TakeAllDumpsFrom(pmd2.get());
+
+ // Make sure that pmd2 is empty but still usable after it has been emptied.
+ ASSERT_TRUE(pmd2->allocator_dumps().empty());
+ ASSERT_TRUE(pmd2->allocator_dumps_edges().empty());
+ pmd2->CreateAllocatorDump("pmd2/this_mad_stays_with_pmd2");
+ ASSERT_EQ(1u, pmd2->allocator_dumps().size());
+ ASSERT_EQ(1u, pmd2->allocator_dumps().count("pmd2/this_mad_stays_with_pmd2"));
+ pmd2->AddOwnershipEdge(MemoryAllocatorDumpGuid(42),
+ MemoryAllocatorDumpGuid(4242));
+
+ // Check that calling serialization routines doesn't cause a crash.
+ pmd2->SerializeAllocatorDumpsInto(traced_value.get());
+
+ // Free the |pmd2| to check that the memory ownership of the two MAD(s)
+ // has been transferred to |pmd1|.
+ pmd2.reset();
+
+ // Now check that |pmd1| has been effectively merged.
+ ASSERT_EQ(6u, pmd1->allocator_dumps().size());
+ ASSERT_EQ(1u, pmd1->allocator_dumps().count("pmd1/mad1"));
+ ASSERT_EQ(1u, pmd1->allocator_dumps().count("pmd1/mad2"));
+ ASSERT_EQ(1u, pmd1->allocator_dumps().count("pmd2/mad1"));
+ ASSERT_EQ(1u, pmd1->allocator_dumps().count("pmd1/mad2"));
+ ASSERT_EQ(2u, pmd1->allocator_dumps_edges().size());
+ ASSERT_EQ(shared_mad1, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid1));
+ ASSERT_EQ(shared_mad2, pmd1->GetSharedGlobalAllocatorDump(shared_mad_guid2));
+ ASSERT_TRUE(MemoryAllocatorDump::Flags::WEAK & shared_mad2->flags());
+
+ // Check that calling serialization routines doesn't cause a crash.
+ traced_value.reset(new TracedValue);
+ pmd1->SerializeAllocatorDumpsInto(traced_value.get());
+
+ pmd1.reset();
+}
+
+TEST(ProcessMemoryDumpTest, OverrideOwnershipEdge) {
+ std::unique_ptr<ProcessMemoryDump> pmd(
+ new ProcessMemoryDump(kDetailedDumpArgs));
+
+ auto* shm_dump1 = pmd->CreateAllocatorDump("shared_mem/seg1");
+ auto* shm_dump2 = pmd->CreateAllocatorDump("shared_mem/seg2");
+ auto* shm_dump3 = pmd->CreateAllocatorDump("shared_mem/seg3");
+ auto* shm_dump4 = pmd->CreateAllocatorDump("shared_mem/seg4");
+
+ // Create one allocation with an auto-assigned guid and mark it as a
+ // suballocation of "fakealloc/allocated_objects".
+ auto* child1_dump = pmd->CreateAllocatorDump("shared_mem/child/seg1");
+ pmd->AddOverridableOwnershipEdge(child1_dump->guid(), shm_dump1->guid(),
+ 0 /* importance */);
+ auto* child2_dump = pmd->CreateAllocatorDump("shared_mem/child/seg2");
+ pmd->AddOwnershipEdge(child2_dump->guid(), shm_dump2->guid(),
+ 3 /* importance */);
+ MemoryAllocatorDumpGuid shared_mad_guid(1);
+ pmd->CreateSharedGlobalAllocatorDump(shared_mad_guid);
+ pmd->AddOverridableOwnershipEdge(shm_dump3->guid(), shared_mad_guid,
+ 0 /* importance */);
+ auto* child4_dump = pmd->CreateAllocatorDump("shared_mem/child/seg4");
+ pmd->AddOverridableOwnershipEdge(child4_dump->guid(), shm_dump4->guid(),
+ 4 /* importance */);
+
+ const ProcessMemoryDump::AllocatorDumpEdgesMap& edges =
+ pmd->allocator_dumps_edges();
+ EXPECT_EQ(4u, edges.size());
+ EXPECT_EQ(shm_dump1->guid(), edges.find(child1_dump->guid())->second.target);
+ EXPECT_EQ(0, edges.find(child1_dump->guid())->second.importance);
+ EXPECT_TRUE(edges.find(child1_dump->guid())->second.overridable);
+ EXPECT_EQ(shm_dump2->guid(), edges.find(child2_dump->guid())->second.target);
+ EXPECT_EQ(3, edges.find(child2_dump->guid())->second.importance);
+ EXPECT_FALSE(edges.find(child2_dump->guid())->second.overridable);
+ EXPECT_EQ(shared_mad_guid, edges.find(shm_dump3->guid())->second.target);
+ EXPECT_EQ(0, edges.find(shm_dump3->guid())->second.importance);
+ EXPECT_TRUE(edges.find(shm_dump3->guid())->second.overridable);
+ EXPECT_EQ(shm_dump4->guid(), edges.find(child4_dump->guid())->second.target);
+ EXPECT_EQ(4, edges.find(child4_dump->guid())->second.importance);
+ EXPECT_TRUE(edges.find(child4_dump->guid())->second.overridable);
+
+ // These should override old edges:
+ pmd->AddOwnershipEdge(child1_dump->guid(), shm_dump1->guid(),
+ 1 /* importance */);
+ pmd->AddOwnershipEdge(shm_dump3->guid(), shared_mad_guid, 2 /* importance */);
+ // This should not change the old edges.
+ pmd->AddOverridableOwnershipEdge(child2_dump->guid(), shm_dump2->guid(),
+ 0 /* importance */);
+ pmd->AddOwnershipEdge(child4_dump->guid(), shm_dump4->guid(),
+ 0 /* importance */);
+
+ EXPECT_EQ(4u, edges.size());
+ EXPECT_EQ(shm_dump1->guid(), edges.find(child1_dump->guid())->second.target);
+ EXPECT_EQ(1, edges.find(child1_dump->guid())->second.importance);
+ EXPECT_FALSE(edges.find(child1_dump->guid())->second.overridable);
+ EXPECT_EQ(shm_dump2->guid(), edges.find(child2_dump->guid())->second.target);
+ EXPECT_EQ(3, edges.find(child2_dump->guid())->second.importance);
+ EXPECT_FALSE(edges.find(child2_dump->guid())->second.overridable);
+ EXPECT_EQ(shared_mad_guid, edges.find(shm_dump3->guid())->second.target);
+ EXPECT_EQ(2, edges.find(shm_dump3->guid())->second.importance);
+ EXPECT_FALSE(edges.find(shm_dump3->guid())->second.overridable);
+ EXPECT_EQ(shm_dump4->guid(), edges.find(child4_dump->guid())->second.target);
+ EXPECT_EQ(4, edges.find(child4_dump->guid())->second.importance);
+ EXPECT_FALSE(edges.find(child4_dump->guid())->second.overridable);
+}
+
+TEST(ProcessMemoryDumpTest, Suballocations) {
+ std::unique_ptr<ProcessMemoryDump> pmd(
+ new ProcessMemoryDump(kDetailedDumpArgs));
+ const std::string allocator_dump_name = "fakealloc/allocated_objects";
+ pmd->CreateAllocatorDump(allocator_dump_name);
+
+ // Create one allocation with an auto-assigned guid and mark it as a
+ // suballocation of "fakealloc/allocated_objects".
+ auto* pic1_dump = pmd->CreateAllocatorDump("picturemanager/picture1");
+ pmd->AddSuballocation(pic1_dump->guid(), allocator_dump_name);
+
+ // Same here, but this time create an allocation with an explicit guid.
+ auto* pic2_dump = pmd->CreateAllocatorDump("picturemanager/picture2",
+ MemoryAllocatorDumpGuid(0x42));
+ pmd->AddSuballocation(pic2_dump->guid(), allocator_dump_name);
+
+ // Now check that AddSuballocation() has created anonymous child dumps under
+ // "fakealloc/allocated_objects".
+ auto anon_node_1_it = pmd->allocator_dumps().find(
+ allocator_dump_name + "/__" + pic1_dump->guid().ToString());
+ ASSERT_NE(pmd->allocator_dumps().end(), anon_node_1_it);
+
+ auto anon_node_2_it =
+ pmd->allocator_dumps().find(allocator_dump_name + "/__42");
+ ASSERT_NE(pmd->allocator_dumps().end(), anon_node_2_it);
+
+ // Finally check that AddSuballocation() has created also the
+ // edges between the pictures and the anonymous allocator child dumps.
+ bool found_edge[2]{false, false};
+ for (const auto& e : pmd->allocator_dumps_edges()) {
+ found_edge[0] |= (e.first == pic1_dump->guid() &&
+ e.second.target == anon_node_1_it->second->guid());
+ found_edge[1] |= (e.first == pic2_dump->guid() &&
+ e.second.target == anon_node_2_it->second->guid());
+ }
+ ASSERT_TRUE(found_edge[0]);
+ ASSERT_TRUE(found_edge[1]);
+
+ // Check that calling serialization routines doesn't cause a crash.
+ std::unique_ptr<TracedValue> traced_value(new TracedValue);
+ pmd->SerializeAllocatorDumpsInto(traced_value.get());
+
+ pmd.reset();
+}
+
+TEST(ProcessMemoryDumpTest, GlobalAllocatorDumpTest) {
+ std::unique_ptr<ProcessMemoryDump> pmd(
+ new ProcessMemoryDump(kDetailedDumpArgs));
+ MemoryAllocatorDumpGuid shared_mad_guid(1);
+ auto* shared_mad1 = pmd->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid);
+ ASSERT_EQ(shared_mad_guid, shared_mad1->guid());
+ ASSERT_EQ(MemoryAllocatorDump::Flags::WEAK, shared_mad1->flags());
+
+ auto* shared_mad2 = pmd->GetSharedGlobalAllocatorDump(shared_mad_guid);
+ ASSERT_EQ(shared_mad1, shared_mad2);
+ ASSERT_EQ(MemoryAllocatorDump::Flags::WEAK, shared_mad1->flags());
+
+ auto* shared_mad3 = pmd->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid);
+ ASSERT_EQ(shared_mad1, shared_mad3);
+ ASSERT_EQ(MemoryAllocatorDump::Flags::WEAK, shared_mad1->flags());
+
+ auto* shared_mad4 = pmd->CreateSharedGlobalAllocatorDump(shared_mad_guid);
+ ASSERT_EQ(shared_mad1, shared_mad4);
+ ASSERT_EQ(MemoryAllocatorDump::Flags::DEFAULT, shared_mad1->flags());
+
+ auto* shared_mad5 = pmd->CreateWeakSharedGlobalAllocatorDump(shared_mad_guid);
+ ASSERT_EQ(shared_mad1, shared_mad5);
+ ASSERT_EQ(MemoryAllocatorDump::Flags::DEFAULT, shared_mad1->flags());
+}
+
+TEST(ProcessMemoryDumpTest, SharedMemoryOwnershipTest) {
+ std::unique_ptr<ProcessMemoryDump> pmd(
+ new ProcessMemoryDump(kDetailedDumpArgs));
+ const ProcessMemoryDump::AllocatorDumpEdgesMap& edges =
+ pmd->allocator_dumps_edges();
+
+ auto* client_dump2 = pmd->CreateAllocatorDump("discardable/segment2");
+ auto shm_token2 = UnguessableToken::Create();
+ MemoryAllocatorDumpGuid shm_local_guid2 =
+ pmd->GetDumpId(SharedMemoryTracker::GetDumpNameForTracing(shm_token2));
+ MemoryAllocatorDumpGuid shm_global_guid2 =
+ SharedMemoryTracker::GetGlobalDumpIdForTracing(shm_token2);
+ pmd->AddOverridableOwnershipEdge(shm_local_guid2, shm_global_guid2,
+ 0 /* importance */);
+
+ pmd->CreateSharedMemoryOwnershipEdge(client_dump2->guid(), shm_token2,
+ 1 /* importance */);
+ EXPECT_EQ(2u, edges.size());
+
+ EXPECT_EQ(shm_global_guid2, edges.find(shm_local_guid2)->second.target);
+ EXPECT_EQ(1, edges.find(shm_local_guid2)->second.importance);
+ EXPECT_FALSE(edges.find(shm_local_guid2)->second.overridable);
+ EXPECT_EQ(shm_local_guid2, edges.find(client_dump2->guid())->second.target);
+ EXPECT_EQ(1, edges.find(client_dump2->guid())->second.importance);
+ EXPECT_FALSE(edges.find(client_dump2->guid())->second.overridable);
+}
+
+TEST(ProcessMemoryDumpTest, BackgroundModeTest) {
+ MemoryDumpArgs background_args = {MemoryDumpLevelOfDetail::BACKGROUND};
+ std::unique_ptr<ProcessMemoryDump> pmd(
+ new ProcessMemoryDump(background_args));
+ ProcessMemoryDump::is_black_hole_non_fatal_for_testing_ = true;
+ SetAllocatorDumpNameWhitelistForTesting(kTestDumpNameWhitelist);
+ MemoryAllocatorDump* black_hole_mad = pmd->GetBlackHoleMad();
+
+ // GetAllocatorDump works for uncreated dumps.
+ EXPECT_EQ(nullptr, pmd->GetAllocatorDump("NotWhitelisted/TestName"));
+ EXPECT_EQ(nullptr, pmd->GetAllocatorDump("Whitelisted/TestName"));
+
+ // Invalid dump names.
+ EXPECT_EQ(black_hole_mad,
+ pmd->CreateAllocatorDump("NotWhitelisted/TestName"));
+ EXPECT_EQ(black_hole_mad, pmd->CreateAllocatorDump("TestName"));
+ EXPECT_EQ(black_hole_mad, pmd->CreateAllocatorDump("Whitelisted/Test"));
+ EXPECT_EQ(black_hole_mad,
+ pmd->CreateAllocatorDump("Not/Whitelisted/TestName"));
+ EXPECT_EQ(black_hole_mad,
+ pmd->CreateAllocatorDump("Whitelisted/TestName/Google"));
+ EXPECT_EQ(black_hole_mad,
+ pmd->CreateAllocatorDump("Whitelisted/TestName/0x1a2Google"));
+ EXPECT_EQ(black_hole_mad,
+ pmd->CreateAllocatorDump("Whitelisted/TestName/__12/Google"));
+
+ // Suballocations.
+ MemoryAllocatorDumpGuid guid(1);
+ pmd->AddSuballocation(guid, "malloc/allocated_objects");
+ EXPECT_EQ(0u, pmd->allocator_dumps_edges_.size());
+ EXPECT_EQ(0u, pmd->allocator_dumps_.size());
+
+ // Global dumps.
+ EXPECT_NE(black_hole_mad, pmd->CreateSharedGlobalAllocatorDump(guid));
+ EXPECT_NE(black_hole_mad, pmd->CreateWeakSharedGlobalAllocatorDump(guid));
+ EXPECT_NE(black_hole_mad, pmd->GetSharedGlobalAllocatorDump(guid));
+
+ // Valid dump names.
+ EXPECT_NE(black_hole_mad, pmd->CreateAllocatorDump("Whitelisted/TestName"));
+ EXPECT_NE(black_hole_mad,
+ pmd->CreateAllocatorDump("Whitelisted/TestName_0xA1b2"));
+ EXPECT_NE(black_hole_mad,
+ pmd->CreateAllocatorDump("Whitelisted/0xaB/TestName"));
+
+ // GetAllocatorDump is consistent.
+ EXPECT_EQ(nullptr, pmd->GetAllocatorDump("NotWhitelisted/TestName"));
+ EXPECT_NE(black_hole_mad, pmd->GetAllocatorDump("Whitelisted/TestName"));
+
+ // Test whitelisted entries.
+ ASSERT_TRUE(IsMemoryAllocatorDumpNameWhitelisted("Whitelisted/TestName"));
+
+ // Global dumps should be whitelisted.
+ ASSERT_TRUE(IsMemoryAllocatorDumpNameWhitelisted("global/13456"));
+
+ // Global dumps with non-guids should not be.
+ ASSERT_FALSE(IsMemoryAllocatorDumpNameWhitelisted("global/random"));
+
+ // Random names should not.
+ ASSERT_FALSE(IsMemoryAllocatorDumpNameWhitelisted("NotWhitelisted/TestName"));
+
+ // Check hex processing.
+ ASSERT_TRUE(IsMemoryAllocatorDumpNameWhitelisted("Whitelisted/0xA1b2"));
+}
+
+TEST(ProcessMemoryDumpTest, GuidsTest) {
+ MemoryDumpArgs dump_args = {MemoryDumpLevelOfDetail::DETAILED};
+
+ const auto process_token_one = UnguessableToken::Create();
+ const auto process_token_two = UnguessableToken::Create();
+
+ ProcessMemoryDump pmd1(dump_args);
+ pmd1.set_process_token_for_testing(process_token_one);
+ MemoryAllocatorDump* mad1 = pmd1.CreateAllocatorDump("foo");
+
+ ProcessMemoryDump pmd2(dump_args);
+ pmd2.set_process_token_for_testing(process_token_one);
+ MemoryAllocatorDump* mad2 = pmd2.CreateAllocatorDump("foo");
+
+ // If we don't pass the argument we get a random PMD:
+ ProcessMemoryDump pmd3(dump_args);
+ MemoryAllocatorDump* mad3 = pmd3.CreateAllocatorDump("foo");
+
+ // PMD's for different processes produce different GUIDs even for the same
+ // names:
+ ProcessMemoryDump pmd4(dump_args);
+ pmd4.set_process_token_for_testing(process_token_two);
+ MemoryAllocatorDump* mad4 = pmd4.CreateAllocatorDump("foo");
+
+ ASSERT_EQ(mad1->guid(), mad2->guid());
+
+ ASSERT_NE(mad2->guid(), mad3->guid());
+ ASSERT_NE(mad3->guid(), mad4->guid());
+ ASSERT_NE(mad4->guid(), mad2->guid());
+
+ ASSERT_EQ(mad1->guid(), pmd1.GetDumpId("foo"));
+}
+
+#if defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/851760): Counting resident bytes is not supported on Fuchsia.
+#define MAYBE_CountResidentBytes DISABLED_CountResidentBytes
+#else
+#define MAYBE_CountResidentBytes CountResidentBytes
+#endif
+TEST(ProcessMemoryDumpTest, MAYBE_CountResidentBytes) {
+ const size_t page_size = ProcessMemoryDump::GetSystemPageSize();
+
+ // Allocate few page of dirty memory and check if it is resident.
+ const size_t size1 = 5 * page_size;
+ void* memory1 = Map(size1);
+ memset(memory1, 0, size1);
+ size_t res1 = ProcessMemoryDump::CountResidentBytes(memory1, size1);
+ ASSERT_EQ(res1, size1);
+ Unmap(memory1, size1);
+
+ // Allocate a large memory segment (> 8Mib).
+ const size_t kVeryLargeMemorySize = 15 * 1024 * 1024;
+ void* memory2 = Map(kVeryLargeMemorySize);
+ memset(memory2, 0, kVeryLargeMemorySize);
+ size_t res2 =
+ ProcessMemoryDump::CountResidentBytes(memory2, kVeryLargeMemorySize);
+ ASSERT_EQ(res2, kVeryLargeMemorySize);
+ Unmap(memory2, kVeryLargeMemorySize);
+}
+
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/851760): Counting resident bytes is not supported on Fuchsia.
+#define MAYBE_CountResidentBytesInSharedMemory \
+ DISABLED_CountResidentBytesInSharedMemory
+#else
+#define MAYBE_CountResidentBytesInSharedMemory CountResidentBytesInSharedMemory
+#endif
+TEST(ProcessMemoryDumpTest, MAYBE_CountResidentBytesInSharedMemory) {
+#if defined(OS_IOS)
+ // TODO(crbug.com/748410): Reenable this test.
+ if (!base::ios::IsRunningOnIOS10OrLater()) {
+ return;
+ }
+#endif
+
+ const size_t page_size = ProcessMemoryDump::GetSystemPageSize();
+
+ // Allocate few page of dirty memory and check if it is resident.
+ const size_t size1 = 5 * page_size;
+ SharedMemory shared_memory1;
+ shared_memory1.CreateAndMapAnonymous(size1);
+ memset(shared_memory1.memory(), 0, size1);
+ base::Optional<size_t> res1 =
+ ProcessMemoryDump::CountResidentBytesInSharedMemory(
+ shared_memory1.memory(), shared_memory1.mapped_size());
+ ASSERT_TRUE(res1.has_value());
+ ASSERT_EQ(res1.value(), size1);
+ shared_memory1.Unmap();
+ shared_memory1.Close();
+
+ // Allocate a large memory segment (> 8Mib).
+ const size_t kVeryLargeMemorySize = 15 * 1024 * 1024;
+ SharedMemory shared_memory2;
+ shared_memory2.CreateAndMapAnonymous(kVeryLargeMemorySize);
+ memset(shared_memory2.memory(), 0, kVeryLargeMemorySize);
+ base::Optional<size_t> res2 =
+ ProcessMemoryDump::CountResidentBytesInSharedMemory(
+ shared_memory2.memory(), shared_memory2.mapped_size());
+ ASSERT_TRUE(res2.has_value());
+ ASSERT_EQ(res2.value(), kVeryLargeMemorySize);
+ shared_memory2.Unmap();
+ shared_memory2.Close();
+
+ // Allocate a large memory segment, but touch about half of all pages.
+ const size_t kTouchedMemorySize = 7 * 1024 * 1024;
+ SharedMemory shared_memory3;
+ shared_memory3.CreateAndMapAnonymous(kVeryLargeMemorySize);
+ memset(shared_memory3.memory(), 0, kTouchedMemorySize);
+ base::Optional<size_t> res3 =
+ ProcessMemoryDump::CountResidentBytesInSharedMemory(
+ shared_memory3.memory(), shared_memory3.mapped_size());
+ ASSERT_TRUE(res3.has_value());
+ ASSERT_EQ(res3.value(), kTouchedMemorySize);
+ shared_memory3.Unmap();
+ shared_memory3.Close();
+}
+#endif // defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_buffer.cc b/base/trace_event/trace_buffer.cc
new file mode 100644
index 0000000000..8de470f0fd
--- /dev/null
+++ b/base/trace_event/trace_buffer.cc
@@ -0,0 +1,347 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_buffer.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/trace_event/heap_profiler.h"
+#include "base/trace_event/trace_event_impl.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+class TraceBufferRingBuffer : public TraceBuffer {
+ public:
+ TraceBufferRingBuffer(size_t max_chunks)
+ : max_chunks_(max_chunks),
+ recyclable_chunks_queue_(new size_t[queue_capacity()]),
+ queue_head_(0),
+ queue_tail_(max_chunks),
+ current_iteration_index_(0),
+ current_chunk_seq_(1) {
+ chunks_.reserve(max_chunks);
+ for (size_t i = 0; i < max_chunks; ++i)
+ recyclable_chunks_queue_[i] = i;
+ }
+
+ std::unique_ptr<TraceBufferChunk> GetChunk(size_t* index) override {
+ HEAP_PROFILER_SCOPED_IGNORE;
+
+ // Because the number of threads is much less than the number of chunks,
+ // the queue should never be empty.
+ DCHECK(!QueueIsEmpty());
+
+ *index = recyclable_chunks_queue_[queue_head_];
+ queue_head_ = NextQueueIndex(queue_head_);
+ current_iteration_index_ = queue_head_;
+
+ if (*index >= chunks_.size())
+ chunks_.resize(*index + 1);
+
+ TraceBufferChunk* chunk = chunks_[*index].release();
+ chunks_[*index] = nullptr; // Put nullptr in the slot of a in-flight chunk.
+ if (chunk)
+ chunk->Reset(current_chunk_seq_++);
+ else
+ chunk = new TraceBufferChunk(current_chunk_seq_++);
+
+ return std::unique_ptr<TraceBufferChunk>(chunk);
+ }
+
+ void ReturnChunk(size_t index,
+ std::unique_ptr<TraceBufferChunk> chunk) override {
+ // When this method is called, the queue should not be full because it
+ // can contain all chunks including the one to be returned.
+ DCHECK(!QueueIsFull());
+ DCHECK(chunk);
+ DCHECK_LT(index, chunks_.size());
+ DCHECK(!chunks_[index]);
+ chunks_[index] = std::move(chunk);
+ recyclable_chunks_queue_[queue_tail_] = index;
+ queue_tail_ = NextQueueIndex(queue_tail_);
+ }
+
+ bool IsFull() const override { return false; }
+
+ size_t Size() const override {
+ // This is approximate because not all of the chunks are full.
+ return chunks_.size() * TraceBufferChunk::kTraceBufferChunkSize;
+ }
+
+ size_t Capacity() const override {
+ return max_chunks_ * TraceBufferChunk::kTraceBufferChunkSize;
+ }
+
+ TraceEvent* GetEventByHandle(TraceEventHandle handle) override {
+ if (handle.chunk_index >= chunks_.size())
+ return nullptr;
+ TraceBufferChunk* chunk = chunks_[handle.chunk_index].get();
+ if (!chunk || chunk->seq() != handle.chunk_seq)
+ return nullptr;
+ return chunk->GetEventAt(handle.event_index);
+ }
+
+ const TraceBufferChunk* NextChunk() override {
+ if (chunks_.empty())
+ return nullptr;
+
+ while (current_iteration_index_ != queue_tail_) {
+ size_t chunk_index = recyclable_chunks_queue_[current_iteration_index_];
+ current_iteration_index_ = NextQueueIndex(current_iteration_index_);
+ if (chunk_index >= chunks_.size()) // Skip uninitialized chunks.
+ continue;
+ DCHECK(chunks_[chunk_index]);
+ return chunks_[chunk_index].get();
+ }
+ return nullptr;
+ }
+
+ void EstimateTraceMemoryOverhead(
+ TraceEventMemoryOverhead* overhead) override {
+ overhead->Add(TraceEventMemoryOverhead::kTraceBuffer, sizeof(*this));
+ for (size_t queue_index = queue_head_; queue_index != queue_tail_;
+ queue_index = NextQueueIndex(queue_index)) {
+ size_t chunk_index = recyclable_chunks_queue_[queue_index];
+ if (chunk_index >= chunks_.size()) // Skip uninitialized chunks.
+ continue;
+ chunks_[chunk_index]->EstimateTraceMemoryOverhead(overhead);
+ }
+ }
+
+ private:
+ bool QueueIsEmpty() const { return queue_head_ == queue_tail_; }
+
+ size_t QueueSize() const {
+ return queue_tail_ > queue_head_
+ ? queue_tail_ - queue_head_
+ : queue_tail_ + queue_capacity() - queue_head_;
+ }
+
+ bool QueueIsFull() const { return QueueSize() == queue_capacity() - 1; }
+
+ size_t queue_capacity() const {
+ // One extra space to help distinguish full state and empty state.
+ return max_chunks_ + 1;
+ }
+
+ size_t NextQueueIndex(size_t index) const {
+ index++;
+ if (index >= queue_capacity())
+ index = 0;
+ return index;
+ }
+
+ size_t max_chunks_;
+ std::vector<std::unique_ptr<TraceBufferChunk>> chunks_;
+
+ std::unique_ptr<size_t[]> recyclable_chunks_queue_;
+ size_t queue_head_;
+ size_t queue_tail_;
+
+ size_t current_iteration_index_;
+ uint32_t current_chunk_seq_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceBufferRingBuffer);
+};
+
+class TraceBufferVector : public TraceBuffer {
+ public:
+ TraceBufferVector(size_t max_chunks)
+ : in_flight_chunk_count_(0),
+ current_iteration_index_(0),
+ max_chunks_(max_chunks) {
+ chunks_.reserve(max_chunks_);
+ }
+
+ std::unique_ptr<TraceBufferChunk> GetChunk(size_t* index) override {
+ HEAP_PROFILER_SCOPED_IGNORE;
+
+ // This function may be called when adding normal events or indirectly from
+ // AddMetadataEventsWhileLocked(). We can not DECHECK(!IsFull()) because we
+ // have to add the metadata events and flush thread-local buffers even if
+ // the buffer is full.
+ *index = chunks_.size();
+ // Put nullptr in the slot of a in-flight chunk.
+ chunks_.push_back(nullptr);
+ ++in_flight_chunk_count_;
+ // + 1 because zero chunk_seq is not allowed.
+ return std::unique_ptr<TraceBufferChunk>(
+ new TraceBufferChunk(static_cast<uint32_t>(*index) + 1));
+ }
+
+ void ReturnChunk(size_t index,
+ std::unique_ptr<TraceBufferChunk> chunk) override {
+ DCHECK_GT(in_flight_chunk_count_, 0u);
+ DCHECK_LT(index, chunks_.size());
+ DCHECK(!chunks_[index]);
+ --in_flight_chunk_count_;
+ chunks_[index] = std::move(chunk);
+ }
+
+ bool IsFull() const override { return chunks_.size() >= max_chunks_; }
+
+ size_t Size() const override {
+ // This is approximate because not all of the chunks are full.
+ return chunks_.size() * TraceBufferChunk::kTraceBufferChunkSize;
+ }
+
+ size_t Capacity() const override {
+ return max_chunks_ * TraceBufferChunk::kTraceBufferChunkSize;
+ }
+
+ TraceEvent* GetEventByHandle(TraceEventHandle handle) override {
+ if (handle.chunk_index >= chunks_.size())
+ return nullptr;
+ TraceBufferChunk* chunk = chunks_[handle.chunk_index].get();
+ if (!chunk || chunk->seq() != handle.chunk_seq)
+ return nullptr;
+ return chunk->GetEventAt(handle.event_index);
+ }
+
+ const TraceBufferChunk* NextChunk() override {
+ while (current_iteration_index_ < chunks_.size()) {
+ // Skip in-flight chunks.
+ const TraceBufferChunk* chunk = chunks_[current_iteration_index_++].get();
+ if (chunk)
+ return chunk;
+ }
+ return nullptr;
+ }
+
+ void EstimateTraceMemoryOverhead(
+ TraceEventMemoryOverhead* overhead) override {
+ const size_t chunks_ptr_vector_allocated_size =
+ sizeof(*this) + max_chunks_ * sizeof(decltype(chunks_)::value_type);
+ const size_t chunks_ptr_vector_resident_size =
+ sizeof(*this) + chunks_.size() * sizeof(decltype(chunks_)::value_type);
+ overhead->Add(TraceEventMemoryOverhead::kTraceBuffer,
+ chunks_ptr_vector_allocated_size,
+ chunks_ptr_vector_resident_size);
+ for (size_t i = 0; i < chunks_.size(); ++i) {
+ TraceBufferChunk* chunk = chunks_[i].get();
+ // Skip the in-flight (nullptr) chunks. They will be accounted by the
+ // per-thread-local dumpers, see ThreadLocalEventBuffer::OnMemoryDump.
+ if (chunk)
+ chunk->EstimateTraceMemoryOverhead(overhead);
+ }
+ }
+
+ private:
+ size_t in_flight_chunk_count_;
+ size_t current_iteration_index_;
+ size_t max_chunks_;
+ std::vector<std::unique_ptr<TraceBufferChunk>> chunks_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceBufferVector);
+};
+
+} // namespace
+
+TraceBufferChunk::TraceBufferChunk(uint32_t seq) : next_free_(0), seq_(seq) {}
+
+TraceBufferChunk::~TraceBufferChunk() = default;
+
+void TraceBufferChunk::Reset(uint32_t new_seq) {
+ for (size_t i = 0; i < next_free_; ++i)
+ chunk_[i].Reset();
+ next_free_ = 0;
+ seq_ = new_seq;
+ cached_overhead_estimate_.reset();
+}
+
+TraceEvent* TraceBufferChunk::AddTraceEvent(size_t* event_index) {
+ DCHECK(!IsFull());
+ *event_index = next_free_++;
+ return &chunk_[*event_index];
+}
+
+void TraceBufferChunk::EstimateTraceMemoryOverhead(
+ TraceEventMemoryOverhead* overhead) {
+ if (!cached_overhead_estimate_) {
+ cached_overhead_estimate_.reset(new TraceEventMemoryOverhead);
+
+ // When estimating the size of TraceBufferChunk, exclude the array of trace
+ // events, as they are computed individually below.
+ cached_overhead_estimate_->Add(TraceEventMemoryOverhead::kTraceBufferChunk,
+ sizeof(*this) - sizeof(chunk_));
+ }
+
+ const size_t num_cached_estimated_events =
+ cached_overhead_estimate_->GetCount(
+ TraceEventMemoryOverhead::kTraceEvent);
+ DCHECK_LE(num_cached_estimated_events, size());
+
+ if (IsFull() && num_cached_estimated_events == size()) {
+ overhead->Update(*cached_overhead_estimate_);
+ return;
+ }
+
+ for (size_t i = num_cached_estimated_events; i < size(); ++i)
+ chunk_[i].EstimateTraceMemoryOverhead(cached_overhead_estimate_.get());
+
+ if (IsFull()) {
+ cached_overhead_estimate_->AddSelf();
+ } else {
+ // The unused TraceEvents in |chunks_| are not cached. They will keep
+ // changing as new TraceEvents are added to this chunk, so they are
+ // computed on the fly.
+ const size_t num_unused_trace_events = capacity() - size();
+ overhead->Add(TraceEventMemoryOverhead::kUnusedTraceEvent,
+ num_unused_trace_events * sizeof(TraceEvent));
+ }
+
+ overhead->Update(*cached_overhead_estimate_);
+}
+
+TraceResultBuffer::OutputCallback
+TraceResultBuffer::SimpleOutput::GetCallback() {
+ return Bind(&SimpleOutput::Append, Unretained(this));
+}
+
+void TraceResultBuffer::SimpleOutput::Append(
+ const std::string& json_trace_output) {
+ json_output += json_trace_output;
+}
+
+TraceResultBuffer::TraceResultBuffer() : append_comma_(false) {}
+
+TraceResultBuffer::~TraceResultBuffer() = default;
+
+void TraceResultBuffer::SetOutputCallback(
+ const OutputCallback& json_chunk_callback) {
+ output_callback_ = json_chunk_callback;
+}
+
+void TraceResultBuffer::Start() {
+ append_comma_ = false;
+ output_callback_.Run("[");
+}
+
+void TraceResultBuffer::AddFragment(const std::string& trace_fragment) {
+ if (append_comma_)
+ output_callback_.Run(",");
+ append_comma_ = true;
+ output_callback_.Run(trace_fragment);
+}
+
+void TraceResultBuffer::Finish() {
+ output_callback_.Run("]");
+}
+
+TraceBuffer* TraceBuffer::CreateTraceBufferRingBuffer(size_t max_chunks) {
+ return new TraceBufferRingBuffer(max_chunks);
+}
+
+TraceBuffer* TraceBuffer::CreateTraceBufferVectorOfSize(size_t max_chunks) {
+ return new TraceBufferVector(max_chunks);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_buffer.h b/base/trace_event/trace_buffer.h
new file mode 100644
index 0000000000..3d6465fdc3
--- /dev/null
+++ b/base/trace_event/trace_buffer.h
@@ -0,0 +1,130 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_BUFFER_H_
+#define BASE_TRACE_EVENT_TRACE_BUFFER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/base_export.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_impl.h"
+
+namespace base {
+
+namespace trace_event {
+
+// TraceBufferChunk is the basic unit of TraceBuffer.
+class BASE_EXPORT TraceBufferChunk {
+ public:
+ explicit TraceBufferChunk(uint32_t seq);
+ ~TraceBufferChunk();
+
+ void Reset(uint32_t new_seq);
+ TraceEvent* AddTraceEvent(size_t* event_index);
+ bool IsFull() const { return next_free_ == kTraceBufferChunkSize; }
+
+ uint32_t seq() const { return seq_; }
+ size_t capacity() const { return kTraceBufferChunkSize; }
+ size_t size() const { return next_free_; }
+
+ TraceEvent* GetEventAt(size_t index) {
+ DCHECK(index < size());
+ return &chunk_[index];
+ }
+ const TraceEvent* GetEventAt(size_t index) const {
+ DCHECK(index < size());
+ return &chunk_[index];
+ }
+
+ void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead);
+
+ // These values must be kept consistent with the numbers of bits of
+ // chunk_index and event_index fields in TraceEventHandle
+ // (in trace_event_impl.h).
+ static const size_t kMaxChunkIndex = (1u << 26) - 1;
+ static const size_t kTraceBufferChunkSize = 64;
+
+ private:
+ size_t next_free_;
+ std::unique_ptr<TraceEventMemoryOverhead> cached_overhead_estimate_;
+ TraceEvent chunk_[kTraceBufferChunkSize];
+ uint32_t seq_;
+};
+
+// TraceBuffer holds the events as they are collected.
+class BASE_EXPORT TraceBuffer {
+ public:
+ virtual ~TraceBuffer() = default;
+
+ virtual std::unique_ptr<TraceBufferChunk> GetChunk(size_t* index) = 0;
+ virtual void ReturnChunk(size_t index,
+ std::unique_ptr<TraceBufferChunk> chunk) = 0;
+
+ virtual bool IsFull() const = 0;
+ virtual size_t Size() const = 0;
+ virtual size_t Capacity() const = 0;
+ virtual TraceEvent* GetEventByHandle(TraceEventHandle handle) = 0;
+
+ // For iteration. Each TraceBuffer can only be iterated once.
+ virtual const TraceBufferChunk* NextChunk() = 0;
+
+
+ // Computes an estimate of the size of the buffer, including all the retained
+ // objects.
+ virtual void EstimateTraceMemoryOverhead(
+ TraceEventMemoryOverhead* overhead) = 0;
+
+ static TraceBuffer* CreateTraceBufferRingBuffer(size_t max_chunks);
+ static TraceBuffer* CreateTraceBufferVectorOfSize(size_t max_chunks);
+};
+
+// TraceResultBuffer collects and converts trace fragments returned by TraceLog
+// to JSON output.
+class BASE_EXPORT TraceResultBuffer {
+ public:
+ typedef base::Callback<void(const std::string&)> OutputCallback;
+
+ // If you don't need to stream JSON chunks out efficiently, and just want to
+ // get a complete JSON string after calling Finish, use this struct to collect
+ // JSON trace output.
+ struct BASE_EXPORT SimpleOutput {
+ OutputCallback GetCallback();
+ void Append(const std::string& json_string);
+
+ // Do what you want with the json_output_ string after calling
+ // TraceResultBuffer::Finish.
+ std::string json_output;
+ };
+
+ TraceResultBuffer();
+ ~TraceResultBuffer();
+
+ // Set callback. The callback will be called during Start with the initial
+ // JSON output and during AddFragment and Finish with following JSON output
+ // chunks. The callback target must live past the last calls to
+ // TraceResultBuffer::Start/AddFragment/Finish.
+ void SetOutputCallback(const OutputCallback& json_chunk_callback);
+
+ // Start JSON output. This resets all internal state, so you can reuse
+ // the TraceResultBuffer by calling Start.
+ void Start();
+
+ // Call AddFragment 0 or more times to add trace fragments from TraceLog.
+ void AddFragment(const std::string& trace_fragment);
+
+ // When all fragments have been added, call Finish to complete the JSON
+ // formatted output.
+ void Finish();
+
+ private:
+ OutputCallback output_callback_;
+ bool append_comma_;
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_BUFFER_H_
diff --git a/base/trace_event/trace_category.h b/base/trace_event/trace_category.h
new file mode 100644
index 0000000000..792bc5e53e
--- /dev/null
+++ b/base/trace_event/trace_category.h
@@ -0,0 +1,109 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_CATEGORY_H_
+#define BASE_TRACE_EVENT_TRACE_CATEGORY_H_
+
+#include <stdint.h>
+
+namespace base {
+namespace trace_event {
+
+// Captures the state of an invidivual trace category. Nothing except tracing
+// internals (e.g., TraceLog) is supposed to have non-const Category pointers.
+struct TraceCategory {
+ // The TRACE_EVENT macros should only use this value as a bool.
+ // These enum values are effectively a public API and third_party projects
+ // depend on their value. Hence, never remove or recycle existing bits, unless
+ // you are sure that all the third-party projects that depend on this have
+ // been updated.
+ enum StateFlags : uint8_t {
+ ENABLED_FOR_RECORDING = 1 << 0,
+
+ // Not used anymore.
+ DEPRECATED_ENABLED_FOR_MONITORING = 1 << 1,
+ DEPRECATED_ENABLED_FOR_EVENT_CALLBACK = 1 << 2,
+
+ ENABLED_FOR_ETW_EXPORT = 1 << 3,
+ ENABLED_FOR_FILTERING = 1 << 4
+ };
+
+ static const TraceCategory* FromStatePtr(const uint8_t* state_ptr) {
+ static_assert(
+ offsetof(TraceCategory, state_) == 0,
+ "|state_| must be the first field of the TraceCategory class.");
+ return reinterpret_cast<const TraceCategory*>(state_ptr);
+ }
+
+ bool is_valid() const { return name_ != nullptr; }
+ void set_name(const char* name) { name_ = name; }
+ const char* name() const {
+ DCHECK(is_valid());
+ return name_;
+ }
+
+ // TODO(primiano): This is an intermediate solution to deal with the fact that
+ // today TRACE_EVENT* macros cache the state ptr. They should just cache the
+ // full TraceCategory ptr, which is immutable, and use these helper function
+ // here. This will get rid of the need of this awkward ptr getter completely.
+ const uint8_t* state_ptr() const {
+ return const_cast<const uint8_t*>(&state_);
+ }
+
+ uint8_t state() const {
+ return *const_cast<volatile const uint8_t*>(&state_);
+ }
+
+ bool is_enabled() const { return state() != 0; }
+
+ void set_state(uint8_t state) {
+ *const_cast<volatile uint8_t*>(&state_) = state;
+ }
+
+ void clear_state_flag(StateFlags flag) { set_state(state() & (~flag)); }
+ void set_state_flag(StateFlags flag) { set_state(state() | flag); }
+
+ uint32_t enabled_filters() const {
+ return *const_cast<volatile const uint32_t*>(&enabled_filters_);
+ }
+
+ bool is_filter_enabled(size_t index) const {
+ DCHECK(index < sizeof(enabled_filters_) * 8);
+ return (enabled_filters() & (1 << index)) != 0;
+ }
+
+ void set_enabled_filters(uint32_t enabled_filters) {
+ *const_cast<volatile uint32_t*>(&enabled_filters_) = enabled_filters;
+ }
+
+ void reset_for_testing() {
+ set_state(0);
+ set_enabled_filters(0);
+ }
+
+ // These fields should not be accessed directly, not even by tracing code.
+ // The only reason why these are not private is because it makes it impossible
+ // to have a global array of TraceCategory in category_registry.cc without
+ // creating initializers. See discussion on goo.gl/qhZN94 and
+ // crbug.com/{660967,660828}.
+
+ // The enabled state. TRACE_EVENT* macros will capture events if any of the
+ // flags here are set. Since TRACE_EVENTx macros are used in a lot of
+ // fast-paths, accesses to this field are non-barriered and racy by design.
+ // This field is mutated when starting/stopping tracing and we don't care
+ // about missing some events.
+ uint8_t state_;
+
+ // When ENABLED_FOR_FILTERING is set, this contains a bitmap to the
+ // corresponding filter (see event_filters.h).
+ uint32_t enabled_filters_;
+
+ // TraceCategory group names are long lived static strings.
+ const char* name_;
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_CATEGORY_H_
diff --git a/base/trace_event/trace_category_unittest.cc b/base/trace_event/trace_category_unittest.cc
new file mode 100644
index 0000000000..1370f5e906
--- /dev/null
+++ b/base/trace_event/trace_category_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/trace_event/category_registry.h"
+#include "base/trace_event/trace_category.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+// Static initializers are generally forbidden. However, in the past we ran in
+// the case of some test using tracing in a static initializer. This test checks
+// That the category registry doesn't rely on static initializers itself and is
+// functional even if called from another static initializer.
+bool Initializer() {
+ return CategoryRegistry::kCategoryMetadata &&
+ CategoryRegistry::kCategoryMetadata->is_valid();
+}
+bool g_initializer_check = Initializer();
+
+class TraceCategoryTest : public testing::Test {
+ public:
+ void SetUp() override { CategoryRegistry::Initialize(); }
+
+ void TearDown() override { CategoryRegistry::ResetForTesting(); }
+
+ static bool GetOrCreateCategoryByName(const char* name, TraceCategory** cat) {
+ static LazyInstance<Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER;
+ bool is_new_cat = false;
+ *cat = CategoryRegistry::GetCategoryByName(name);
+ if (!*cat) {
+ AutoLock lock(g_lock.Get());
+ is_new_cat = CategoryRegistry::GetOrCreateCategoryLocked(
+ name, [](TraceCategory*) {}, cat);
+ }
+ return is_new_cat;
+ };
+
+ static CategoryRegistry::Range GetAllCategories() {
+ return CategoryRegistry::GetAllCategories();
+ }
+
+ static void TestRaceThreadMain(WaitableEvent* event) {
+ TraceCategory* cat = nullptr;
+ event->Wait();
+ GetOrCreateCategoryByName("__test_race", &cat);
+ EXPECT_NE(nullptr, cat);
+ }
+};
+
+TEST_F(TraceCategoryTest, Basic) {
+ ASSERT_NE(nullptr, CategoryRegistry::kCategoryMetadata);
+ ASSERT_TRUE(CategoryRegistry::kCategoryMetadata->is_valid());
+ ASSERT_FALSE(CategoryRegistry::kCategoryMetadata->is_enabled());
+
+ // Metadata category is built-in and should create a new category.
+ TraceCategory* cat_meta = nullptr;
+ const char* kMetadataName = CategoryRegistry::kCategoryMetadata->name();
+ ASSERT_FALSE(GetOrCreateCategoryByName(kMetadataName, &cat_meta));
+ ASSERT_EQ(CategoryRegistry::kCategoryMetadata, cat_meta);
+
+ TraceCategory* cat_1 = nullptr;
+ ASSERT_TRUE(GetOrCreateCategoryByName("__test_basic_ab", &cat_1));
+ ASSERT_FALSE(cat_1->is_enabled());
+ ASSERT_EQ(0u, cat_1->enabled_filters());
+ cat_1->set_state_flag(TraceCategory::ENABLED_FOR_RECORDING);
+ cat_1->set_state_flag(TraceCategory::ENABLED_FOR_FILTERING);
+ ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING |
+ TraceCategory::ENABLED_FOR_FILTERING,
+ cat_1->state());
+
+ cat_1->set_enabled_filters(129);
+ ASSERT_EQ(129u, cat_1->enabled_filters());
+ ASSERT_EQ(cat_1, CategoryRegistry::GetCategoryByStatePtr(cat_1->state_ptr()));
+
+ cat_1->clear_state_flag(TraceCategory::ENABLED_FOR_FILTERING);
+ ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING, cat_1->state());
+ ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING, *cat_1->state_ptr());
+ ASSERT_TRUE(cat_1->is_enabled());
+
+ TraceCategory* cat_2 = nullptr;
+ ASSERT_TRUE(GetOrCreateCategoryByName("__test_basic_a", &cat_2));
+ ASSERT_FALSE(cat_2->is_enabled());
+ cat_2->set_state_flag(TraceCategory::ENABLED_FOR_RECORDING);
+
+ TraceCategory* cat_2_copy = nullptr;
+ ASSERT_FALSE(GetOrCreateCategoryByName("__test_basic_a", &cat_2_copy));
+ ASSERT_EQ(cat_2, cat_2_copy);
+
+ TraceCategory* cat_3 = nullptr;
+ ASSERT_TRUE(
+ GetOrCreateCategoryByName("__test_basic_ab,__test_basic_a", &cat_3));
+ ASSERT_FALSE(cat_3->is_enabled());
+ ASSERT_EQ(0u, cat_3->enabled_filters());
+
+ int num_test_categories_seen = 0;
+ for (const TraceCategory& cat : GetAllCategories()) {
+ if (strcmp(cat.name(), kMetadataName) == 0)
+ ASSERT_TRUE(CategoryRegistry::IsBuiltinCategory(&cat));
+
+ if (strncmp(cat.name(), "__test_basic_", 13) == 0) {
+ ASSERT_FALSE(CategoryRegistry::IsBuiltinCategory(&cat));
+ num_test_categories_seen++;
+ }
+ }
+ ASSERT_EQ(3, num_test_categories_seen);
+ ASSERT_TRUE(g_initializer_check);
+}
+
+// Tries to cover the case of multiple threads creating the same category
+// simultaneously. Should never end up with distinct entries with the same name.
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/738275): This is flaky on Fuchsia.
+#define MAYBE_ThreadRaces DISABLED_ThreadRaces
+#else
+#define MAYBE_ThreadRaces ThreadRaces
+#endif
+TEST_F(TraceCategoryTest, MAYBE_ThreadRaces) {
+ const int kNumThreads = 32;
+ std::unique_ptr<Thread> threads[kNumThreads];
+ for (int i = 0; i < kNumThreads; i++) {
+ threads[i].reset(new Thread("test thread"));
+ threads[i]->Start();
+ }
+ WaitableEvent sync_event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ for (int i = 0; i < kNumThreads; i++) {
+ threads[i]->task_runner()->PostTask(
+ FROM_HERE, BindOnce(&TestRaceThreadMain, Unretained(&sync_event)));
+ }
+ sync_event.Signal();
+ for (int i = 0; i < kNumThreads; i++)
+ threads[i]->Stop();
+
+ int num_times_seen = 0;
+ for (const TraceCategory& cat : GetAllCategories()) {
+ if (strcmp(cat.name(), "__test_race") == 0)
+ num_times_seen++;
+ }
+ ASSERT_EQ(1, num_times_seen);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_config.cc b/base/trace_event/trace_config.cc
new file mode 100644
index 0000000000..9d6a9d4874
--- /dev/null
+++ b/base/trace_event/trace_config.cc
@@ -0,0 +1,618 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_config.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_split.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/memory_dump_request_args.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+// String options that can be used to initialize TraceOptions.
+const char kRecordUntilFull[] = "record-until-full";
+const char kRecordContinuously[] = "record-continuously";
+const char kRecordAsMuchAsPossible[] = "record-as-much-as-possible";
+const char kTraceToConsole[] = "trace-to-console";
+const char kEnableSystrace[] = "enable-systrace";
+const char kEnableArgumentFilter[] = "enable-argument-filter";
+
+// String parameters that can be used to parse the trace config string.
+const char kRecordModeParam[] = "record_mode";
+const char kEnableSystraceParam[] = "enable_systrace";
+const char kEnableArgumentFilterParam[] = "enable_argument_filter";
+
+// String parameters that is used to parse memory dump config in trace config
+// string.
+const char kMemoryDumpConfigParam[] = "memory_dump_config";
+const char kAllowedDumpModesParam[] = "allowed_dump_modes";
+const char kTriggersParam[] = "triggers";
+const char kTriggerModeParam[] = "mode";
+const char kMinTimeBetweenDumps[] = "min_time_between_dumps_ms";
+const char kTriggerTypeParam[] = "type";
+const char kPeriodicIntervalLegacyParam[] = "periodic_interval_ms";
+const char kHeapProfilerOptions[] = "heap_profiler_options";
+const char kBreakdownThresholdBytes[] = "breakdown_threshold_bytes";
+
+// String parameters used to parse category event filters.
+const char kEventFiltersParam[] = "event_filters";
+const char kFilterPredicateParam[] = "filter_predicate";
+const char kFilterArgsParam[] = "filter_args";
+
+// String parameter used to parse process filter.
+const char kIncludedProcessesParam[] = "included_process_ids";
+
+class ConvertableTraceConfigToTraceFormat
+ : public base::trace_event::ConvertableToTraceFormat {
+ public:
+ explicit ConvertableTraceConfigToTraceFormat(const TraceConfig& trace_config)
+ : trace_config_(trace_config) {}
+
+ ~ConvertableTraceConfigToTraceFormat() override = default;
+
+ void AppendAsTraceFormat(std::string* out) const override {
+ out->append(trace_config_.ToString());
+ }
+
+ private:
+ const TraceConfig trace_config_;
+};
+
+std::set<MemoryDumpLevelOfDetail> GetDefaultAllowedMemoryDumpModes() {
+ std::set<MemoryDumpLevelOfDetail> all_modes;
+ for (uint32_t mode = static_cast<uint32_t>(MemoryDumpLevelOfDetail::FIRST);
+ mode <= static_cast<uint32_t>(MemoryDumpLevelOfDetail::LAST); mode++) {
+ all_modes.insert(static_cast<MemoryDumpLevelOfDetail>(mode));
+ }
+ return all_modes;
+}
+
+} // namespace
+
+TraceConfig::MemoryDumpConfig::HeapProfiler::HeapProfiler()
+ : breakdown_threshold_bytes(kDefaultBreakdownThresholdBytes) {}
+
+void TraceConfig::MemoryDumpConfig::HeapProfiler::Clear() {
+ breakdown_threshold_bytes = kDefaultBreakdownThresholdBytes;
+}
+
+void TraceConfig::ResetMemoryDumpConfig(
+ const TraceConfig::MemoryDumpConfig& memory_dump_config) {
+ memory_dump_config_.Clear();
+ memory_dump_config_ = memory_dump_config;
+}
+
+TraceConfig::MemoryDumpConfig::MemoryDumpConfig() = default;
+
+TraceConfig::MemoryDumpConfig::MemoryDumpConfig(
+ const MemoryDumpConfig& other) = default;
+
+TraceConfig::MemoryDumpConfig::~MemoryDumpConfig() = default;
+
+void TraceConfig::MemoryDumpConfig::Clear() {
+ allowed_dump_modes.clear();
+ triggers.clear();
+ heap_profiler_options.Clear();
+}
+
+void TraceConfig::MemoryDumpConfig::Merge(
+ const TraceConfig::MemoryDumpConfig& config) {
+ triggers.insert(triggers.end(), config.triggers.begin(),
+ config.triggers.end());
+ allowed_dump_modes.insert(config.allowed_dump_modes.begin(),
+ config.allowed_dump_modes.end());
+ heap_profiler_options.breakdown_threshold_bytes =
+ std::min(heap_profiler_options.breakdown_threshold_bytes,
+ config.heap_profiler_options.breakdown_threshold_bytes);
+}
+
+TraceConfig::ProcessFilterConfig::ProcessFilterConfig() = default;
+
+TraceConfig::ProcessFilterConfig::ProcessFilterConfig(
+ const ProcessFilterConfig& other) = default;
+
+TraceConfig::ProcessFilterConfig::ProcessFilterConfig(
+ const std::unordered_set<base::ProcessId>& included_process_ids)
+ : included_process_ids_(included_process_ids) {}
+
+TraceConfig::ProcessFilterConfig::~ProcessFilterConfig() = default;
+
+void TraceConfig::ProcessFilterConfig::Clear() {
+ included_process_ids_.clear();
+}
+
+void TraceConfig::ProcessFilterConfig::Merge(
+ const ProcessFilterConfig& config) {
+ included_process_ids_.insert(config.included_process_ids_.begin(),
+ config.included_process_ids_.end());
+}
+
+void TraceConfig::ProcessFilterConfig::InitializeFromConfigDict(
+ const base::DictionaryValue& dict) {
+ included_process_ids_.clear();
+ const Value* value =
+ dict.FindKeyOfType(kIncludedProcessesParam, Value::Type::LIST);
+ if (!value)
+ return;
+ for (auto& pid_value : value->GetList()) {
+ if (pid_value.is_int())
+ included_process_ids_.insert(pid_value.GetInt());
+ }
+}
+
+void TraceConfig::ProcessFilterConfig::ToDict(DictionaryValue* dict) const {
+ if (included_process_ids_.empty())
+ return;
+ Value* list = dict->SetKey(kIncludedProcessesParam, Value(Value::Type::LIST));
+ std::set<base::ProcessId> ordered_set(included_process_ids_.begin(),
+ included_process_ids_.end());
+ for (auto process_id : ordered_set)
+ list->GetList().emplace_back(static_cast<int>(process_id));
+}
+
+bool TraceConfig::ProcessFilterConfig::IsEnabled(
+ base::ProcessId process_id) const {
+ return included_process_ids_.empty() ||
+ included_process_ids_.count(process_id);
+}
+
+TraceConfig::EventFilterConfig::EventFilterConfig(
+ const std::string& predicate_name)
+ : predicate_name_(predicate_name) {}
+
+TraceConfig::EventFilterConfig::~EventFilterConfig() = default;
+
+TraceConfig::EventFilterConfig::EventFilterConfig(const EventFilterConfig& tc) {
+ *this = tc;
+}
+
+TraceConfig::EventFilterConfig& TraceConfig::EventFilterConfig::operator=(
+ const TraceConfig::EventFilterConfig& rhs) {
+ if (this == &rhs)
+ return *this;
+
+ predicate_name_ = rhs.predicate_name_;
+ category_filter_ = rhs.category_filter_;
+
+ if (rhs.args_)
+ args_ = rhs.args_->CreateDeepCopy();
+
+ return *this;
+}
+
+void TraceConfig::EventFilterConfig::InitializeFromConfigDict(
+ const base::DictionaryValue* event_filter) {
+ category_filter_.InitializeFromConfigDict(*event_filter);
+
+ const base::DictionaryValue* args_dict = nullptr;
+ if (event_filter->GetDictionary(kFilterArgsParam, &args_dict))
+ args_ = args_dict->CreateDeepCopy();
+}
+
+void TraceConfig::EventFilterConfig::SetCategoryFilter(
+ const TraceConfigCategoryFilter& category_filter) {
+ category_filter_ = category_filter;
+}
+
+void TraceConfig::EventFilterConfig::ToDict(
+ DictionaryValue* filter_dict) const {
+ filter_dict->SetString(kFilterPredicateParam, predicate_name());
+
+ category_filter_.ToDict(filter_dict);
+
+ if (args_)
+ filter_dict->Set(kFilterArgsParam, args_->CreateDeepCopy());
+}
+
+bool TraceConfig::EventFilterConfig::GetArgAsSet(
+ const char* key,
+ std::unordered_set<std::string>* out_set) const {
+ const ListValue* list = nullptr;
+ if (!args_->GetList(key, &list))
+ return false;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ std::string value;
+ if (list->GetString(i, &value))
+ out_set->insert(value);
+ }
+ return true;
+}
+
+bool TraceConfig::EventFilterConfig::IsCategoryGroupEnabled(
+ const StringPiece& category_group_name) const {
+ return category_filter_.IsCategoryGroupEnabled(category_group_name);
+}
+
+// static
+std::string TraceConfig::TraceRecordModeToStr(TraceRecordMode record_mode) {
+ switch (record_mode) {
+ case RECORD_UNTIL_FULL:
+ return kRecordUntilFull;
+ case RECORD_CONTINUOUSLY:
+ return kRecordContinuously;
+ case RECORD_AS_MUCH_AS_POSSIBLE:
+ return kRecordAsMuchAsPossible;
+ case ECHO_TO_CONSOLE:
+ return kTraceToConsole;
+ default:
+ NOTREACHED();
+ }
+ return kRecordUntilFull;
+}
+
+TraceConfig::TraceConfig() {
+ InitializeDefault();
+}
+
+TraceConfig::TraceConfig(StringPiece category_filter_string,
+ StringPiece trace_options_string) {
+ InitializeFromStrings(category_filter_string, trace_options_string);
+}
+
+TraceConfig::TraceConfig(StringPiece category_filter_string,
+ TraceRecordMode record_mode) {
+ InitializeFromStrings(category_filter_string,
+ TraceConfig::TraceRecordModeToStr(record_mode));
+}
+
+TraceConfig::TraceConfig(const DictionaryValue& config) {
+ InitializeFromConfigDict(config);
+}
+
+TraceConfig::TraceConfig(StringPiece config_string) {
+ if (!config_string.empty())
+ InitializeFromConfigString(config_string);
+ else
+ InitializeDefault();
+}
+
+TraceConfig::TraceConfig(const TraceConfig& tc) = default;
+
+TraceConfig::~TraceConfig() = default;
+
+TraceConfig& TraceConfig::operator=(const TraceConfig& rhs) {
+ if (this == &rhs)
+ return *this;
+
+ record_mode_ = rhs.record_mode_;
+ enable_systrace_ = rhs.enable_systrace_;
+ enable_argument_filter_ = rhs.enable_argument_filter_;
+ category_filter_ = rhs.category_filter_;
+ process_filter_config_ = rhs.process_filter_config_;
+ memory_dump_config_ = rhs.memory_dump_config_;
+ event_filters_ = rhs.event_filters_;
+ return *this;
+}
+
+std::string TraceConfig::ToString() const {
+ std::unique_ptr<DictionaryValue> dict = ToDict();
+ std::string json;
+ JSONWriter::Write(*dict, &json);
+ return json;
+}
+
+std::unique_ptr<ConvertableToTraceFormat>
+TraceConfig::AsConvertableToTraceFormat() const {
+ return std::make_unique<ConvertableTraceConfigToTraceFormat>(*this);
+}
+
+std::string TraceConfig::ToCategoryFilterString() const {
+ return category_filter_.ToFilterString();
+}
+
+bool TraceConfig::IsCategoryGroupEnabled(
+ const StringPiece& category_group_name) const {
+ // TraceLog should call this method only as part of enabling/disabling
+ // categories.
+ return category_filter_.IsCategoryGroupEnabled(category_group_name);
+}
+
+void TraceConfig::Merge(const TraceConfig& config) {
+ if (record_mode_ != config.record_mode_
+ || enable_systrace_ != config.enable_systrace_
+ || enable_argument_filter_ != config.enable_argument_filter_) {
+ DLOG(ERROR) << "Attempting to merge trace config with a different "
+ << "set of options.";
+ }
+
+ category_filter_.Merge(config.category_filter_);
+ memory_dump_config_.Merge(config.memory_dump_config_);
+ process_filter_config_.Merge(config.process_filter_config_);
+
+ event_filters_.insert(event_filters_.end(), config.event_filters().begin(),
+ config.event_filters().end());
+}
+
+void TraceConfig::Clear() {
+ record_mode_ = RECORD_UNTIL_FULL;
+ enable_systrace_ = false;
+ enable_argument_filter_ = false;
+ category_filter_.Clear();
+ memory_dump_config_.Clear();
+ process_filter_config_.Clear();
+ event_filters_.clear();
+}
+
+void TraceConfig::InitializeDefault() {
+ record_mode_ = RECORD_UNTIL_FULL;
+ enable_systrace_ = false;
+ enable_argument_filter_ = false;
+}
+
+void TraceConfig::InitializeFromConfigDict(const DictionaryValue& dict) {
+ record_mode_ = RECORD_UNTIL_FULL;
+ std::string record_mode;
+ if (dict.GetString(kRecordModeParam, &record_mode)) {
+ if (record_mode == kRecordUntilFull) {
+ record_mode_ = RECORD_UNTIL_FULL;
+ } else if (record_mode == kRecordContinuously) {
+ record_mode_ = RECORD_CONTINUOUSLY;
+ } else if (record_mode == kTraceToConsole) {
+ record_mode_ = ECHO_TO_CONSOLE;
+ } else if (record_mode == kRecordAsMuchAsPossible) {
+ record_mode_ = RECORD_AS_MUCH_AS_POSSIBLE;
+ }
+ }
+
+ bool val;
+ enable_systrace_ = dict.GetBoolean(kEnableSystraceParam, &val) ? val : false;
+ enable_argument_filter_ =
+ dict.GetBoolean(kEnableArgumentFilterParam, &val) ? val : false;
+
+ category_filter_.InitializeFromConfigDict(dict);
+ process_filter_config_.InitializeFromConfigDict(dict);
+
+ const base::ListValue* category_event_filters = nullptr;
+ if (dict.GetList(kEventFiltersParam, &category_event_filters))
+ SetEventFiltersFromConfigList(*category_event_filters);
+
+ if (category_filter_.IsCategoryEnabled(MemoryDumpManager::kTraceCategory)) {
+ // If dump triggers not set, the client is using the legacy with just
+ // category enabled. So, use the default periodic dump config.
+ const DictionaryValue* memory_dump_config = nullptr;
+ if (dict.GetDictionary(kMemoryDumpConfigParam, &memory_dump_config))
+ SetMemoryDumpConfigFromConfigDict(*memory_dump_config);
+ else
+ SetDefaultMemoryDumpConfig();
+ }
+}
+
+void TraceConfig::InitializeFromConfigString(StringPiece config_string) {
+ auto dict = DictionaryValue::From(JSONReader::Read(config_string));
+ if (dict)
+ InitializeFromConfigDict(*dict);
+ else
+ InitializeDefault();
+}
+
+void TraceConfig::InitializeFromStrings(StringPiece category_filter_string,
+ StringPiece trace_options_string) {
+ if (!category_filter_string.empty())
+ category_filter_.InitializeFromString(category_filter_string);
+
+ record_mode_ = RECORD_UNTIL_FULL;
+ enable_systrace_ = false;
+ enable_argument_filter_ = false;
+ if (!trace_options_string.empty()) {
+ std::vector<std::string> split =
+ SplitString(trace_options_string, ",", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+ for (const std::string& token : split) {
+ if (token == kRecordUntilFull) {
+ record_mode_ = RECORD_UNTIL_FULL;
+ } else if (token == kRecordContinuously) {
+ record_mode_ = RECORD_CONTINUOUSLY;
+ } else if (token == kTraceToConsole) {
+ record_mode_ = ECHO_TO_CONSOLE;
+ } else if (token == kRecordAsMuchAsPossible) {
+ record_mode_ = RECORD_AS_MUCH_AS_POSSIBLE;
+ } else if (token == kEnableSystrace) {
+ enable_systrace_ = true;
+ } else if (token == kEnableArgumentFilter) {
+ enable_argument_filter_ = true;
+ }
+ }
+ }
+
+ if (category_filter_.IsCategoryEnabled(MemoryDumpManager::kTraceCategory)) {
+ SetDefaultMemoryDumpConfig();
+ }
+}
+
+void TraceConfig::SetMemoryDumpConfigFromConfigDict(
+ const DictionaryValue& memory_dump_config) {
+ // Set allowed dump modes.
+ memory_dump_config_.allowed_dump_modes.clear();
+ const ListValue* allowed_modes_list;
+ if (memory_dump_config.GetList(kAllowedDumpModesParam, &allowed_modes_list)) {
+ for (size_t i = 0; i < allowed_modes_list->GetSize(); ++i) {
+ std::string level_of_detail_str;
+ allowed_modes_list->GetString(i, &level_of_detail_str);
+ memory_dump_config_.allowed_dump_modes.insert(
+ StringToMemoryDumpLevelOfDetail(level_of_detail_str));
+ }
+ } else {
+ // If allowed modes param is not given then allow all modes by default.
+ memory_dump_config_.allowed_dump_modes = GetDefaultAllowedMemoryDumpModes();
+ }
+
+ // Set triggers
+ memory_dump_config_.triggers.clear();
+ const ListValue* trigger_list = nullptr;
+ if (memory_dump_config.GetList(kTriggersParam, &trigger_list) &&
+ trigger_list->GetSize() > 0) {
+ for (size_t i = 0; i < trigger_list->GetSize(); ++i) {
+ const DictionaryValue* trigger = nullptr;
+ if (!trigger_list->GetDictionary(i, &trigger))
+ continue;
+
+ MemoryDumpConfig::Trigger dump_config;
+ int interval = 0;
+ if (!trigger->GetInteger(kMinTimeBetweenDumps, &interval)) {
+ // If "min_time_between_dumps_ms" param was not given, then the trace
+ // config uses old format where only periodic dumps are supported.
+ trigger->GetInteger(kPeriodicIntervalLegacyParam, &interval);
+ dump_config.trigger_type = MemoryDumpType::PERIODIC_INTERVAL;
+ } else {
+ std::string trigger_type_str;
+ trigger->GetString(kTriggerTypeParam, &trigger_type_str);
+ dump_config.trigger_type = StringToMemoryDumpType(trigger_type_str);
+ }
+ DCHECK_GT(interval, 0);
+ dump_config.min_time_between_dumps_ms = static_cast<uint32_t>(interval);
+
+ std::string level_of_detail_str;
+ trigger->GetString(kTriggerModeParam, &level_of_detail_str);
+ dump_config.level_of_detail =
+ StringToMemoryDumpLevelOfDetail(level_of_detail_str);
+
+ memory_dump_config_.triggers.push_back(dump_config);
+ }
+ }
+
+ // Set heap profiler options
+ const DictionaryValue* heap_profiler_options = nullptr;
+ if (memory_dump_config.GetDictionary(kHeapProfilerOptions,
+ &heap_profiler_options)) {
+ int min_size_bytes = 0;
+ if (heap_profiler_options->GetInteger(kBreakdownThresholdBytes,
+ &min_size_bytes)
+ && min_size_bytes >= 0) {
+ memory_dump_config_.heap_profiler_options.breakdown_threshold_bytes =
+ static_cast<size_t>(min_size_bytes);
+ } else {
+ memory_dump_config_.heap_profiler_options.breakdown_threshold_bytes =
+ MemoryDumpConfig::HeapProfiler::kDefaultBreakdownThresholdBytes;
+ }
+ }
+}
+
+void TraceConfig::SetDefaultMemoryDumpConfig() {
+ memory_dump_config_.Clear();
+ memory_dump_config_.allowed_dump_modes = GetDefaultAllowedMemoryDumpModes();
+}
+
+void TraceConfig::SetProcessFilterConfig(const ProcessFilterConfig& config) {
+ process_filter_config_ = config;
+}
+
+void TraceConfig::SetEventFiltersFromConfigList(
+ const base::ListValue& category_event_filters) {
+ event_filters_.clear();
+
+ for (size_t event_filter_index = 0;
+ event_filter_index < category_event_filters.GetSize();
+ ++event_filter_index) {
+ const base::DictionaryValue* event_filter = nullptr;
+ if (!category_event_filters.GetDictionary(event_filter_index,
+ &event_filter))
+ continue;
+
+ std::string predicate_name;
+ CHECK(event_filter->GetString(kFilterPredicateParam, &predicate_name))
+ << "Invalid predicate name in category event filter.";
+
+ EventFilterConfig new_config(predicate_name);
+ new_config.InitializeFromConfigDict(event_filter);
+ event_filters_.push_back(new_config);
+ }
+}
+
+std::unique_ptr<DictionaryValue> TraceConfig::ToDict() const {
+ auto dict = std::make_unique<DictionaryValue>();
+ dict->SetString(kRecordModeParam,
+ TraceConfig::TraceRecordModeToStr(record_mode_));
+ dict->SetBoolean(kEnableSystraceParam, enable_systrace_);
+ dict->SetBoolean(kEnableArgumentFilterParam, enable_argument_filter_);
+
+ category_filter_.ToDict(dict.get());
+ process_filter_config_.ToDict(dict.get());
+
+ if (!event_filters_.empty()) {
+ std::unique_ptr<base::ListValue> filter_list(new base::ListValue());
+ for (const EventFilterConfig& filter : event_filters_) {
+ std::unique_ptr<base::DictionaryValue> filter_dict(
+ new base::DictionaryValue());
+ filter.ToDict(filter_dict.get());
+ filter_list->Append(std::move(filter_dict));
+ }
+ dict->Set(kEventFiltersParam, std::move(filter_list));
+ }
+
+ if (category_filter_.IsCategoryEnabled(MemoryDumpManager::kTraceCategory)) {
+ auto allowed_modes = std::make_unique<ListValue>();
+ for (auto dump_mode : memory_dump_config_.allowed_dump_modes)
+ allowed_modes->AppendString(MemoryDumpLevelOfDetailToString(dump_mode));
+
+ auto memory_dump_config = std::make_unique<DictionaryValue>();
+ memory_dump_config->Set(kAllowedDumpModesParam, std::move(allowed_modes));
+
+ auto triggers_list = std::make_unique<ListValue>();
+ for (const auto& config : memory_dump_config_.triggers) {
+ auto trigger_dict = std::make_unique<DictionaryValue>();
+ trigger_dict->SetString(kTriggerTypeParam,
+ MemoryDumpTypeToString(config.trigger_type));
+ trigger_dict->SetInteger(
+ kMinTimeBetweenDumps,
+ static_cast<int>(config.min_time_between_dumps_ms));
+ trigger_dict->SetString(
+ kTriggerModeParam,
+ MemoryDumpLevelOfDetailToString(config.level_of_detail));
+ triggers_list->Append(std::move(trigger_dict));
+ }
+
+ // Empty triggers will still be specified explicitly since it means that
+ // the periodic dumps are not enabled.
+ memory_dump_config->Set(kTriggersParam, std::move(triggers_list));
+
+ if (memory_dump_config_.heap_profiler_options.breakdown_threshold_bytes !=
+ MemoryDumpConfig::HeapProfiler::kDefaultBreakdownThresholdBytes) {
+ auto options = std::make_unique<DictionaryValue>();
+ options->SetInteger(
+ kBreakdownThresholdBytes,
+ memory_dump_config_.heap_profiler_options.breakdown_threshold_bytes);
+ memory_dump_config->Set(kHeapProfilerOptions, std::move(options));
+ }
+ dict->Set(kMemoryDumpConfigParam, std::move(memory_dump_config));
+ }
+ return dict;
+}
+
+std::string TraceConfig::ToTraceOptionsString() const {
+ std::string ret;
+ switch (record_mode_) {
+ case RECORD_UNTIL_FULL:
+ ret = kRecordUntilFull;
+ break;
+ case RECORD_CONTINUOUSLY:
+ ret = kRecordContinuously;
+ break;
+ case RECORD_AS_MUCH_AS_POSSIBLE:
+ ret = kRecordAsMuchAsPossible;
+ break;
+ case ECHO_TO_CONSOLE:
+ ret = kTraceToConsole;
+ break;
+ default:
+ NOTREACHED();
+ }
+ if (enable_systrace_)
+ ret = ret + "," + kEnableSystrace;
+ if (enable_argument_filter_)
+ ret = ret + "," + kEnableArgumentFilter;
+ return ret;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_config.h b/base/trace_event/trace_config.h
new file mode 100644
index 0000000000..b1d809b58b
--- /dev/null
+++ b/base/trace_event/trace_config.h
@@ -0,0 +1,321 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_CONFIG_H_
+#define BASE_TRACE_EVENT_TRACE_CONFIG_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/gtest_prod_util.h"
+#include "base/strings/string_piece.h"
+#include "base/trace_event/memory_dump_request_args.h"
+#include "base/trace_event/trace_config_category_filter.h"
+#include "base/values.h"
+
+namespace base {
+namespace trace_event {
+
+class ConvertableToTraceFormat;
+
+// Options determines how the trace buffer stores data.
+// A Java counterpart will be generated for this enum.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base
+enum TraceRecordMode {
+ // Record until the trace buffer is full.
+ RECORD_UNTIL_FULL,
+
+ // Record until the user ends the trace. The trace buffer is a fixed size
+ // and we use it as a ring buffer during recording.
+ RECORD_CONTINUOUSLY,
+
+ // Record until the trace buffer is full, but with a huge buffer size.
+ RECORD_AS_MUCH_AS_POSSIBLE,
+
+ // Echo to console. Events are discarded.
+ ECHO_TO_CONSOLE,
+};
+
+class BASE_EXPORT TraceConfig {
+ public:
+ using StringList = std::vector<std::string>;
+
+ // Specifies the memory dump config for tracing.
+ // Used only when "memory-infra" category is enabled.
+ struct BASE_EXPORT MemoryDumpConfig {
+ MemoryDumpConfig();
+ MemoryDumpConfig(const MemoryDumpConfig& other);
+ ~MemoryDumpConfig();
+
+ // Specifies the triggers in the memory dump config.
+ struct Trigger {
+ uint32_t min_time_between_dumps_ms;
+ MemoryDumpLevelOfDetail level_of_detail;
+ MemoryDumpType trigger_type;
+ };
+
+ // Specifies the configuration options for the heap profiler.
+ struct HeapProfiler {
+ // Default value for |breakdown_threshold_bytes|.
+ enum { kDefaultBreakdownThresholdBytes = 1024 };
+
+ HeapProfiler();
+
+ // Reset the options to default.
+ void Clear();
+
+ uint32_t breakdown_threshold_bytes;
+ };
+
+ // Reset the values in the config.
+ void Clear();
+
+ void Merge(const MemoryDumpConfig& config);
+
+ // Set of memory dump modes allowed for the tracing session. The explicitly
+ // triggered dumps will be successful only if the dump mode is allowed in
+ // the config.
+ std::set<MemoryDumpLevelOfDetail> allowed_dump_modes;
+
+ std::vector<Trigger> triggers;
+ HeapProfiler heap_profiler_options;
+ };
+
+ class BASE_EXPORT ProcessFilterConfig {
+ public:
+ ProcessFilterConfig();
+ explicit ProcessFilterConfig(
+ const std::unordered_set<base::ProcessId>& included_process_ids);
+ ProcessFilterConfig(const ProcessFilterConfig&);
+ ~ProcessFilterConfig();
+
+ bool empty() const { return included_process_ids_.empty(); }
+
+ void Clear();
+ void Merge(const ProcessFilterConfig&);
+
+ void InitializeFromConfigDict(const base::DictionaryValue&);
+ void ToDict(DictionaryValue*) const;
+
+ bool IsEnabled(base::ProcessId) const;
+
+ bool operator==(const ProcessFilterConfig& other) const {
+ return included_process_ids_ == other.included_process_ids_;
+ }
+
+ private:
+ std::unordered_set<base::ProcessId> included_process_ids_;
+ };
+
+ class BASE_EXPORT EventFilterConfig {
+ public:
+ EventFilterConfig(const std::string& predicate_name);
+ EventFilterConfig(const EventFilterConfig& tc);
+
+ ~EventFilterConfig();
+
+ EventFilterConfig& operator=(const EventFilterConfig& rhs);
+
+ void InitializeFromConfigDict(const base::DictionaryValue* event_filter);
+
+ void SetCategoryFilter(const TraceConfigCategoryFilter& category_filter);
+
+ void ToDict(DictionaryValue* filter_dict) const;
+
+ bool GetArgAsSet(const char* key, std::unordered_set<std::string>*) const;
+
+ bool IsCategoryGroupEnabled(const StringPiece& category_group_name) const;
+
+ const std::string& predicate_name() const { return predicate_name_; }
+ base::DictionaryValue* filter_args() const { return args_.get(); }
+ const TraceConfigCategoryFilter& category_filter() const {
+ return category_filter_;
+ }
+
+ private:
+ std::string predicate_name_;
+ TraceConfigCategoryFilter category_filter_;
+ std::unique_ptr<base::DictionaryValue> args_;
+ };
+ typedef std::vector<EventFilterConfig> EventFilters;
+
+ static std::string TraceRecordModeToStr(TraceRecordMode record_mode);
+
+ TraceConfig();
+
+ // Create TraceConfig object from category filter and trace options strings.
+ //
+ // |category_filter_string| is a comma-delimited list of category wildcards.
+ // A category can have an optional '-' prefix to make it an excluded category.
+ // All the same rules apply above, so for example, having both included and
+ // excluded categories in the same list would not be supported.
+ //
+ // |trace_options_string| is a comma-delimited list of trace options.
+ // Possible options are: "record-until-full", "record-continuously",
+ // "record-as-much-as-possible", "trace-to-console", "enable-systrace" and
+ // "enable-argument-filter".
+ // The first 4 options are trace recoding modes and hence
+ // mutually exclusive. If more than one trace recording modes appear in the
+ // options_string, the last one takes precedence. If none of the trace
+ // recording mode is specified, recording mode is RECORD_UNTIL_FULL.
+ //
+ // The trace option will first be reset to the default option
+ // (record_mode set to RECORD_UNTIL_FULL, enable_systrace and
+ // enable_argument_filter set to false) before options parsed from
+ // |trace_options_string| are applied on it. If |trace_options_string| is
+ // invalid, the final state of trace options is undefined.
+ //
+ // Example: TraceConfig("test_MyTest*", "record-until-full");
+ // Example: TraceConfig("test_MyTest*,test_OtherStuff",
+ // "record-continuously");
+ // Example: TraceConfig("-excluded_category1,-excluded_category2",
+ // "record-until-full, trace-to-console");
+ // would set ECHO_TO_CONSOLE as the recording mode.
+ // Example: TraceConfig("-*,webkit", "");
+ // would disable everything but webkit; and use default options.
+ // Example: TraceConfig("-webkit", "");
+ // would enable everything but webkit; and use default options.
+ TraceConfig(StringPiece category_filter_string,
+ StringPiece trace_options_string);
+
+ TraceConfig(StringPiece category_filter_string, TraceRecordMode record_mode);
+
+ // Create TraceConfig object from the trace config string.
+ //
+ // |config_string| is a dictionary formatted as a JSON string, containing both
+ // category filters and trace options.
+ //
+ // Example:
+ // {
+ // "record_mode": "record-continuously",
+ // "enable_systrace": true,
+ // "enable_argument_filter": true,
+ // "included_categories": ["included",
+ // "inc_pattern*",
+ // "disabled-by-default-memory-infra"],
+ // "excluded_categories": ["excluded", "exc_pattern*"],
+ // "memory_dump_config": {
+ // "triggers": [
+ // {
+ // "mode": "detailed",
+ // "periodic_interval_ms": 2000
+ // }
+ // ]
+ // }
+ // }
+ //
+ // Note: memory_dump_config can be specified only if
+ // disabled-by-default-memory-infra category is enabled.
+ explicit TraceConfig(StringPiece config_string);
+
+ // Functionally identical to the above, but takes a parsed dictionary as input
+ // instead of its JSON serialization.
+ explicit TraceConfig(const DictionaryValue& config);
+
+ TraceConfig(const TraceConfig& tc);
+
+ ~TraceConfig();
+
+ TraceConfig& operator=(const TraceConfig& rhs);
+
+ TraceRecordMode GetTraceRecordMode() const { return record_mode_; }
+ bool IsSystraceEnabled() const { return enable_systrace_; }
+ bool IsArgumentFilterEnabled() const { return enable_argument_filter_; }
+
+ void SetTraceRecordMode(TraceRecordMode mode) { record_mode_ = mode; }
+ void EnableSystrace() { enable_systrace_ = true; }
+ void EnableArgumentFilter() { enable_argument_filter_ = true; }
+
+ // Writes the string representation of the TraceConfig. The string is JSON
+ // formatted.
+ std::string ToString() const;
+
+ // Returns a copy of the TraceConfig wrapped in a ConvertableToTraceFormat
+ std::unique_ptr<ConvertableToTraceFormat> AsConvertableToTraceFormat() const;
+
+ // Write the string representation of the CategoryFilter part.
+ std::string ToCategoryFilterString() const;
+
+ // Returns true if at least one category in the list is enabled by this
+ // trace config. This is used to determine if the category filters are
+ // enabled in the TRACE_* macros.
+ bool IsCategoryGroupEnabled(const StringPiece& category_group_name) const;
+
+ // Merges config with the current TraceConfig
+ void Merge(const TraceConfig& config);
+
+ void Clear();
+
+ // Clears and resets the memory dump config.
+ void ResetMemoryDumpConfig(const MemoryDumpConfig& memory_dump_config);
+
+ const TraceConfigCategoryFilter& category_filter() const {
+ return category_filter_;
+ }
+
+ const MemoryDumpConfig& memory_dump_config() const {
+ return memory_dump_config_;
+ }
+
+ const ProcessFilterConfig& process_filter_config() const {
+ return process_filter_config_;
+ }
+ void SetProcessFilterConfig(const ProcessFilterConfig&);
+
+ const EventFilters& event_filters() const { return event_filters_; }
+ void SetEventFilters(const EventFilters& filter_configs) {
+ event_filters_ = filter_configs;
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(TraceConfigTest, TraceConfigFromValidLegacyFormat);
+ FRIEND_TEST_ALL_PREFIXES(TraceConfigTest,
+ TraceConfigFromInvalidLegacyStrings);
+
+ // The default trace config, used when none is provided.
+ // Allows all non-disabled-by-default categories through, except if they end
+ // in the suffix 'Debug' or 'Test'.
+ void InitializeDefault();
+
+ // Initialize from a config dictionary.
+ void InitializeFromConfigDict(const DictionaryValue& dict);
+
+ // Initialize from a config string.
+ void InitializeFromConfigString(StringPiece config_string);
+
+ // Initialize from category filter and trace options strings
+ void InitializeFromStrings(StringPiece category_filter_string,
+ StringPiece trace_options_string);
+
+ void SetMemoryDumpConfigFromConfigDict(
+ const DictionaryValue& memory_dump_config);
+ void SetDefaultMemoryDumpConfig();
+
+ void SetEventFiltersFromConfigList(const base::ListValue& event_filters);
+ std::unique_ptr<DictionaryValue> ToDict() const;
+
+ std::string ToTraceOptionsString() const;
+
+ TraceRecordMode record_mode_;
+ bool enable_systrace_ : 1;
+ bool enable_argument_filter_ : 1;
+
+ TraceConfigCategoryFilter category_filter_;
+
+ MemoryDumpConfig memory_dump_config_;
+ ProcessFilterConfig process_filter_config_;
+
+ EventFilters event_filters_;
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_CONFIG_H_
diff --git a/base/trace_event/trace_config_category_filter.cc b/base/trace_event/trace_config_category_filter.cc
new file mode 100644
index 0000000000..d1884307a9
--- /dev/null
+++ b/base/trace_event/trace_config_category_filter.cc
@@ -0,0 +1,235 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_config_category_filter.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+const char kIncludedCategoriesParam[] = "included_categories";
+const char kExcludedCategoriesParam[] = "excluded_categories";
+}
+
+TraceConfigCategoryFilter::TraceConfigCategoryFilter() = default;
+
+TraceConfigCategoryFilter::TraceConfigCategoryFilter(
+ const TraceConfigCategoryFilter& other) = default;
+
+TraceConfigCategoryFilter::~TraceConfigCategoryFilter() = default;
+
+TraceConfigCategoryFilter& TraceConfigCategoryFilter::operator=(
+ const TraceConfigCategoryFilter& rhs) = default;
+
+void TraceConfigCategoryFilter::InitializeFromString(
+ const StringPiece& category_filter_string) {
+ std::vector<StringPiece> split = SplitStringPiece(
+ category_filter_string, ",", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+ for (const StringPiece& category : split) {
+ // Ignore empty categories.
+ if (category.empty())
+ continue;
+ if (category.front() == '-') {
+ // Excluded categories start with '-'.
+ // Remove '-' from category string.
+ excluded_categories_.push_back(category.substr(1).as_string());
+ } else if (category.starts_with(TRACE_DISABLED_BY_DEFAULT(""))) {
+ disabled_categories_.push_back(category.as_string());
+ } else {
+ included_categories_.push_back(category.as_string());
+ }
+ }
+}
+
+void TraceConfigCategoryFilter::InitializeFromConfigDict(
+ const DictionaryValue& dict) {
+ const ListValue* category_list = nullptr;
+ if (dict.GetList(kIncludedCategoriesParam, &category_list))
+ SetCategoriesFromIncludedList(*category_list);
+ if (dict.GetList(kExcludedCategoriesParam, &category_list))
+ SetCategoriesFromExcludedList(*category_list);
+}
+
+bool TraceConfigCategoryFilter::IsCategoryGroupEnabled(
+ const StringPiece& category_group_name) const {
+ bool had_enabled_by_default = false;
+ DCHECK(!category_group_name.empty());
+ CStringTokenizer category_group_tokens(category_group_name.begin(),
+ category_group_name.end(), ",");
+ while (category_group_tokens.GetNext()) {
+ StringPiece category_group_token = category_group_tokens.token_piece();
+ // Don't allow empty tokens, nor tokens with leading or trailing space.
+ DCHECK(IsCategoryNameAllowed(category_group_token))
+ << "Disallowed category string";
+ if (IsCategoryEnabled(category_group_token))
+ return true;
+
+ if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
+ had_enabled_by_default = true;
+ }
+ // Do a second pass to check for explicitly disabled categories
+ // (those explicitly enabled have priority due to first pass).
+ category_group_tokens.Reset();
+ bool category_group_disabled = false;
+ while (category_group_tokens.GetNext()) {
+ StringPiece category_group_token = category_group_tokens.token_piece();
+ for (const std::string& category : excluded_categories_) {
+ if (MatchPattern(category_group_token, category)) {
+ // Current token of category_group_name is present in excluded_list.
+ // Flag the exclusion and proceed further to check if any of the
+ // remaining categories of category_group_name is not present in the
+ // excluded_ list.
+ category_group_disabled = true;
+ break;
+ }
+ // One of the category of category_group_name is not present in
+ // excluded_ list. So, if it's not a disabled-by-default category,
+ // it has to be included_ list. Enable the category_group_name
+ // for recording.
+ if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
+ category_group_disabled = false;
+ }
+ // One of the categories present in category_group_name is not present in
+ // excluded_ list. Implies this category_group_name group can be enabled
+ // for recording, since one of its groups is enabled for recording.
+ if (!category_group_disabled)
+ break;
+ }
+ // If the category group is not excluded, and there are no included patterns
+ // we consider this category group enabled, as long as it had categories
+ // other than disabled-by-default.
+ return !category_group_disabled && had_enabled_by_default &&
+ included_categories_.empty();
+}
+
+bool TraceConfigCategoryFilter::IsCategoryEnabled(
+ const StringPiece& category_name) const {
+ // Check the disabled- filters and the disabled-* wildcard first so that a
+ // "*" filter does not include the disabled.
+ for (const std::string& category : disabled_categories_) {
+ if (MatchPattern(category_name, category))
+ return true;
+ }
+
+ if (MatchPattern(category_name, TRACE_DISABLED_BY_DEFAULT("*")))
+ return false;
+
+ for (const std::string& category : included_categories_) {
+ if (MatchPattern(category_name, category))
+ return true;
+ }
+
+ return false;
+}
+
+void TraceConfigCategoryFilter::Merge(const TraceConfigCategoryFilter& config) {
+ // Keep included patterns only if both filters have an included entry.
+ // Otherwise, one of the filter was specifying "*" and we want to honor the
+ // broadest filter.
+ if (!included_categories_.empty() && !config.included_categories_.empty()) {
+ included_categories_.insert(included_categories_.end(),
+ config.included_categories_.begin(),
+ config.included_categories_.end());
+ } else {
+ included_categories_.clear();
+ }
+
+ disabled_categories_.insert(disabled_categories_.end(),
+ config.disabled_categories_.begin(),
+ config.disabled_categories_.end());
+ excluded_categories_.insert(excluded_categories_.end(),
+ config.excluded_categories_.begin(),
+ config.excluded_categories_.end());
+}
+
+void TraceConfigCategoryFilter::Clear() {
+ included_categories_.clear();
+ disabled_categories_.clear();
+ excluded_categories_.clear();
+}
+
+void TraceConfigCategoryFilter::ToDict(DictionaryValue* dict) const {
+ StringList categories(included_categories_);
+ categories.insert(categories.end(), disabled_categories_.begin(),
+ disabled_categories_.end());
+ AddCategoriesToDict(categories, kIncludedCategoriesParam, dict);
+ AddCategoriesToDict(excluded_categories_, kExcludedCategoriesParam, dict);
+}
+
+std::string TraceConfigCategoryFilter::ToFilterString() const {
+ std::string filter_string;
+ WriteCategoryFilterString(included_categories_, &filter_string, true);
+ WriteCategoryFilterString(disabled_categories_, &filter_string, true);
+ WriteCategoryFilterString(excluded_categories_, &filter_string, false);
+ return filter_string;
+}
+
+void TraceConfigCategoryFilter::SetCategoriesFromIncludedList(
+ const ListValue& included_list) {
+ included_categories_.clear();
+ for (size_t i = 0; i < included_list.GetSize(); ++i) {
+ std::string category;
+ if (!included_list.GetString(i, &category))
+ continue;
+ if (category.compare(0, strlen(TRACE_DISABLED_BY_DEFAULT("")),
+ TRACE_DISABLED_BY_DEFAULT("")) == 0) {
+ disabled_categories_.push_back(category);
+ } else {
+ included_categories_.push_back(category);
+ }
+ }
+}
+
+void TraceConfigCategoryFilter::SetCategoriesFromExcludedList(
+ const ListValue& excluded_list) {
+ excluded_categories_.clear();
+ for (size_t i = 0; i < excluded_list.GetSize(); ++i) {
+ std::string category;
+ if (excluded_list.GetString(i, &category))
+ excluded_categories_.push_back(category);
+ }
+}
+
+void TraceConfigCategoryFilter::AddCategoriesToDict(
+ const StringList& categories,
+ const char* param,
+ DictionaryValue* dict) const {
+ if (categories.empty())
+ return;
+
+ auto list = std::make_unique<ListValue>();
+ for (const std::string& category : categories)
+ list->AppendString(category);
+ dict->Set(param, std::move(list));
+}
+
+void TraceConfigCategoryFilter::WriteCategoryFilterString(
+ const StringList& values,
+ std::string* out,
+ bool included) const {
+ bool prepend_comma = !out->empty();
+ int token_cnt = 0;
+ for (const std::string& category : values) {
+ if (token_cnt > 0 || prepend_comma)
+ StringAppendF(out, ",");
+ StringAppendF(out, "%s%s", (included ? "" : "-"), category.c_str());
+ ++token_cnt;
+ }
+}
+
+// static
+bool TraceConfigCategoryFilter::IsCategoryNameAllowed(StringPiece str) {
+ return !str.empty() && str.front() != ' ' && str.back() != ' ';
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_config_category_filter.h b/base/trace_event/trace_config_category_filter.h
new file mode 100644
index 0000000000..0140c1d126
--- /dev/null
+++ b/base/trace_event/trace_config_category_filter.h
@@ -0,0 +1,81 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_CONFIG_CATEGORY_FILTER_H_
+#define BASE_TRACE_EVENT_TRACE_CONFIG_CATEGORY_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+
+namespace base {
+namespace trace_event {
+
+// Configuration of categories enabled and disabled in TraceConfig.
+class BASE_EXPORT TraceConfigCategoryFilter {
+ public:
+ using StringList = std::vector<std::string>;
+
+ TraceConfigCategoryFilter();
+ TraceConfigCategoryFilter(const TraceConfigCategoryFilter& other);
+ ~TraceConfigCategoryFilter();
+
+ TraceConfigCategoryFilter& operator=(const TraceConfigCategoryFilter& rhs);
+
+ // Initializes from category filter string. See TraceConfig constructor for
+ // description of how to write category filter string.
+ void InitializeFromString(const StringPiece& category_filter_string);
+
+ // Initializes TraceConfigCategoryFilter object from the config dictionary.
+ void InitializeFromConfigDict(const DictionaryValue& dict);
+
+ // Merges this with category filter config.
+ void Merge(const TraceConfigCategoryFilter& config);
+ void Clear();
+
+ // Returns true if at least one category in the list is enabled by this
+ // trace config. This is used to determine if the category filters are
+ // enabled in the TRACE_* macros.
+ bool IsCategoryGroupEnabled(const StringPiece& category_group_name) const;
+
+ // Returns true if the category is enabled according to this trace config.
+ // This tells whether a category is enabled from the TraceConfig's
+ // perspective. Please refer to IsCategoryGroupEnabled() to determine if a
+ // category is enabled from the tracing runtime's perspective.
+ bool IsCategoryEnabled(const StringPiece& category_name) const;
+
+ void ToDict(DictionaryValue* dict) const;
+
+ std::string ToFilterString() const;
+
+ // Returns true if category name is a valid string.
+ static bool IsCategoryNameAllowed(StringPiece str);
+
+ const StringList& included_categories() const { return included_categories_; }
+ const StringList& excluded_categories() const { return excluded_categories_; }
+
+ private:
+ void SetCategoriesFromIncludedList(const ListValue& included_list);
+ void SetCategoriesFromExcludedList(const ListValue& excluded_list);
+
+ void AddCategoriesToDict(const StringList& categories,
+ const char* param,
+ DictionaryValue* dict) const;
+
+ void WriteCategoryFilterString(const StringList& values,
+ std::string* out,
+ bool included) const;
+
+ StringList included_categories_;
+ StringList disabled_categories_;
+ StringList excluded_categories_;
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_CONFIG_CATEGORY_FILTER_H_
diff --git a/base/trace_event/trace_config_memory_test_util.h b/base/trace_event/trace_config_memory_test_util.h
new file mode 100644
index 0000000000..cc49a65c2b
--- /dev/null
+++ b/base/trace_event/trace_config_memory_test_util.h
@@ -0,0 +1,152 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_CONFIG_MEMORY_TEST_UTIL_H_
+#define BASE_TRACE_EVENT_TRACE_CONFIG_MEMORY_TEST_UTIL_H_
+
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/memory_dump_manager.h"
+
+namespace base {
+namespace trace_event {
+
+class TraceConfigMemoryTestUtil {
+ public:
+ static std::string GetTraceConfig_LegacyPeriodicTriggers(int light_period,
+ int heavy_period) {
+ return StringPrintf(
+ "{"
+ "\"enable_argument_filter\":false,"
+ "\"enable_systrace\":false,"
+ "\"excluded_categories\":["
+ "\"*\""
+ "],"
+ "\"included_categories\":["
+ "\"%s\""
+ "],"
+ "\"memory_dump_config\":{"
+ "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"],"
+ "\"heap_profiler_options\":{"
+ "\"breakdown_threshold_bytes\":2048"
+ "},"
+ "\"triggers\":["
+ "{"
+ "\"mode\":\"light\","
+ "\"periodic_interval_ms\":%d"
+ "},"
+ "{"
+ "\"mode\":\"detailed\","
+ "\"periodic_interval_ms\":%d"
+ "}"
+ "]"
+ "},"
+ "\"record_mode\":\"record-until-full\""
+ "}",
+ MemoryDumpManager::kTraceCategory, light_period, heavy_period);
+ ;
+ }
+
+ static std::string GetTraceConfig_PeriodicTriggers(int light_period,
+ int heavy_period) {
+ return StringPrintf(
+ "{"
+ "\"enable_argument_filter\":false,"
+ "\"enable_systrace\":false,"
+ "\"excluded_categories\":["
+ "\"*\""
+ "],"
+ "\"included_categories\":["
+ "\"%s\""
+ "],"
+ "\"memory_dump_config\":{"
+ "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"],"
+ "\"heap_profiler_options\":{"
+ "\"breakdown_threshold_bytes\":2048"
+ "},"
+ "\"triggers\":["
+ "{"
+ "\"min_time_between_dumps_ms\":%d,"
+ "\"mode\":\"light\","
+ "\"type\":\"periodic_interval\""
+ "},"
+ "{"
+ "\"min_time_between_dumps_ms\":%d,"
+ "\"mode\":\"detailed\","
+ "\"type\":\"periodic_interval\""
+ "}"
+ "]"
+ "},"
+ "\"record_mode\":\"record-until-full\""
+ "}",
+ MemoryDumpManager::kTraceCategory, light_period, heavy_period);
+ }
+
+ static std::string GetTraceConfig_EmptyTriggers() {
+ return StringPrintf(
+ "{"
+ "\"enable_argument_filter\":false,"
+ "\"enable_systrace\":false,"
+ "\"excluded_categories\":["
+ "\"*\""
+ "],"
+ "\"included_categories\":["
+ "\"%s\""
+ "],"
+ "\"memory_dump_config\":{"
+ "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"],"
+ "\"triggers\":["
+ "]"
+ "},"
+ "\"record_mode\":\"record-until-full\""
+ "}",
+ MemoryDumpManager::kTraceCategory);
+ }
+
+ static std::string GetTraceConfig_NoTriggers() {
+ return StringPrintf(
+ "{"
+ "\"enable_argument_filter\":false,"
+ "\"enable_systrace\":false,"
+ "\"excluded_categories\":["
+ "\"*\""
+ "],"
+ "\"included_categories\":["
+ "\"%s\""
+ "],"
+ "\"record_mode\":\"record-until-full\""
+ "}",
+ MemoryDumpManager::kTraceCategory);
+ }
+
+ static std::string GetTraceConfig_BackgroundTrigger(int period_ms) {
+ return StringPrintf(
+ "{"
+ "\"enable_argument_filter\":false,"
+ "\"enable_systrace\":false,"
+ "\"excluded_categories\":["
+ "\"*\""
+ "],"
+ "\"included_categories\":["
+ "\"%s\""
+ "],"
+ "\"memory_dump_config\":{"
+ "\"allowed_dump_modes\":[\"background\"],"
+ "\"triggers\":["
+ "{"
+ "\"min_time_between_dumps_ms\":%d,"
+ "\"mode\":\"background\","
+ "\"type\":\"periodic_interval\""
+ "}"
+ "]"
+ "},"
+ "\"record_mode\":\"record-until-full\""
+ "}",
+ MemoryDumpManager::kTraceCategory, period_ms);
+ }
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_CONFIG_MEMORY_TEST_UTIL_H_
diff --git a/base/trace_event/trace_config_unittest.cc b/base/trace_event/trace_config_unittest.cc
new file mode 100644
index 0000000000..efdbffb6ef
--- /dev/null
+++ b/base/trace_event/trace_config_unittest.cc
@@ -0,0 +1,663 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/macros.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/trace_config.h"
+#include "base/trace_event/trace_config_memory_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+const char kDefaultTraceConfigString[] =
+ "{"
+ "\"enable_argument_filter\":false,"
+ "\"enable_systrace\":false,"
+ "\"record_mode\":\"record-until-full\""
+ "}";
+
+const char kCustomTraceConfigString[] =
+ "{"
+ "\"enable_argument_filter\":true,"
+ "\"enable_systrace\":true,"
+ "\"event_filters\":["
+ "{"
+ "\"excluded_categories\":[\"unfiltered_cat\"],"
+ "\"filter_args\":{\"event_name_whitelist\":[\"a snake\",\"a dog\"]},"
+ "\"filter_predicate\":\"event_whitelist_predicate\","
+ "\"included_categories\":[\"*\"]"
+ "}"
+ "],"
+ "\"excluded_categories\":[\"excluded\",\"exc_pattern*\"],"
+ "\"included_categories\":["
+ "\"included\","
+ "\"inc_pattern*\","
+ "\"disabled-by-default-cc\","
+ "\"disabled-by-default-memory-infra\"],"
+ "\"memory_dump_config\":{"
+ "\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"],"
+ "\"heap_profiler_options\":{"
+ "\"breakdown_threshold_bytes\":10240"
+ "},"
+ "\"triggers\":["
+ "{"
+ "\"min_time_between_dumps_ms\":50,"
+ "\"mode\":\"light\","
+ "\"type\":\"periodic_interval\""
+ "},"
+ "{"
+ "\"min_time_between_dumps_ms\":1000,"
+ "\"mode\":\"detailed\","
+ "\"type\":\"periodic_interval\""
+ "}"
+ "]"
+ "},"
+ "\"record_mode\":\"record-continuously\""
+ "}";
+
+void CheckDefaultTraceConfigBehavior(const TraceConfig& tc) {
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+
+ // Default trace config enables every category filter except the
+ // disabled-by-default-* ones.
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("Category1"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("not-excluded-category"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("disabled-by-default-cc"));
+
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("Category1,not-excluded-category"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("Category1,disabled-by-default-cc"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled(
+ "disabled-by-default-cc,disabled-by-default-cc2"));
+}
+
+} // namespace
+
+TEST(TraceConfigTest, TraceConfigFromValidLegacyFormat) {
+ // From trace options strings
+ TraceConfig config("", "record-until-full");
+ EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("record-until-full", config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("", "record-continuously");
+ EXPECT_EQ(RECORD_CONTINUOUSLY, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("record-continuously", config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("", "trace-to-console");
+ EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("trace-to-console", config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("", "record-as-much-as-possible");
+ EXPECT_EQ(RECORD_AS_MUCH_AS_POSSIBLE, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("record-as-much-as-possible",
+ config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("", "enable-systrace, record-continuously");
+ EXPECT_EQ(RECORD_CONTINUOUSLY, config.GetTraceRecordMode());
+ EXPECT_TRUE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("record-continuously,enable-systrace",
+ config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("", "enable-argument-filter,record-as-much-as-possible");
+ EXPECT_EQ(RECORD_AS_MUCH_AS_POSSIBLE, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_TRUE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("record-as-much-as-possible,enable-argument-filter",
+ config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig(
+ "",
+ "enable-systrace,trace-to-console,enable-argument-filter");
+ EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode());
+ EXPECT_TRUE(config.IsSystraceEnabled());
+ EXPECT_TRUE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ(
+ "trace-to-console,enable-systrace,enable-argument-filter",
+ config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig(
+ "", "record-continuously, record-until-full, trace-to-console");
+ EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("trace-to-console", config.ToTraceOptionsString().c_str());
+
+ // From TraceRecordMode
+ config = TraceConfig("", RECORD_UNTIL_FULL);
+ EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("record-until-full", config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("", RECORD_CONTINUOUSLY);
+ EXPECT_EQ(RECORD_CONTINUOUSLY, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("record-continuously", config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("", ECHO_TO_CONSOLE);
+ EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("trace-to-console", config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("", RECORD_AS_MUCH_AS_POSSIBLE);
+ EXPECT_EQ(RECORD_AS_MUCH_AS_POSSIBLE, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("record-as-much-as-possible",
+ config.ToTraceOptionsString().c_str());
+
+ // From category filter strings
+ config = TraceConfig("included,-excluded,inc_pattern*,-exc_pattern*", "");
+ EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*",
+ config.ToCategoryFilterString().c_str());
+
+ config = TraceConfig("only_inc_cat", "");
+ EXPECT_STREQ("only_inc_cat", config.ToCategoryFilterString().c_str());
+
+ config = TraceConfig("-only_exc_cat", "");
+ EXPECT_STREQ("-only_exc_cat", config.ToCategoryFilterString().c_str());
+
+ config = TraceConfig("disabled-by-default-cc,-excluded", "");
+ EXPECT_STREQ("disabled-by-default-cc,-excluded",
+ config.ToCategoryFilterString().c_str());
+
+ config = TraceConfig("disabled-by-default-cc,included", "");
+ EXPECT_STREQ("included,disabled-by-default-cc",
+ config.ToCategoryFilterString().c_str());
+
+ // From both trace options and category filter strings
+ config = TraceConfig("", "");
+ EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", config.ToCategoryFilterString().c_str());
+ EXPECT_STREQ("record-until-full", config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("included,-excluded,inc_pattern*,-exc_pattern*",
+ "enable-systrace, trace-to-console");
+ EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode());
+ EXPECT_TRUE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*",
+ config.ToCategoryFilterString().c_str());
+ EXPECT_STREQ("trace-to-console,enable-systrace",
+ config.ToTraceOptionsString().c_str());
+
+ // From both trace options and category filter strings with spaces.
+ config = TraceConfig(" included , -excluded, inc_pattern*, ,-exc_pattern* ",
+ "enable-systrace, ,trace-to-console ");
+ EXPECT_EQ(ECHO_TO_CONSOLE, config.GetTraceRecordMode());
+ EXPECT_TRUE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*",
+ config.ToCategoryFilterString().c_str());
+ EXPECT_STREQ("trace-to-console,enable-systrace",
+ config.ToTraceOptionsString().c_str());
+
+ // From category filter string and TraceRecordMode
+ config = TraceConfig("included,-excluded,inc_pattern*,-exc_pattern*",
+ RECORD_CONTINUOUSLY);
+ EXPECT_EQ(RECORD_CONTINUOUSLY, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("included,inc_pattern*,-excluded,-exc_pattern*",
+ config.ToCategoryFilterString().c_str());
+ EXPECT_STREQ("record-continuously", config.ToTraceOptionsString().c_str());
+}
+
+TEST(TraceConfigTest, TraceConfigFromInvalidLegacyStrings) {
+ TraceConfig config("", "foo-bar-baz");
+ EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode());
+ EXPECT_FALSE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", config.ToCategoryFilterString().c_str());
+ EXPECT_STREQ("record-until-full", config.ToTraceOptionsString().c_str());
+
+ config = TraceConfig("arbitrary-category", "foo-bar-baz, enable-systrace");
+ EXPECT_EQ(RECORD_UNTIL_FULL, config.GetTraceRecordMode());
+ EXPECT_TRUE(config.IsSystraceEnabled());
+ EXPECT_FALSE(config.IsArgumentFilterEnabled());
+ EXPECT_STREQ("arbitrary-category", config.ToCategoryFilterString().c_str());
+ EXPECT_STREQ("record-until-full,enable-systrace",
+ config.ToTraceOptionsString().c_str());
+}
+
+TEST(TraceConfigTest, ConstructDefaultTraceConfig) {
+ TraceConfig tc;
+ EXPECT_STREQ("", tc.ToCategoryFilterString().c_str());
+ EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str());
+ CheckDefaultTraceConfigBehavior(tc);
+
+ // Constructors from category filter string and trace option string.
+ TraceConfig tc_asterisk("*", "");
+ EXPECT_STREQ("*", tc_asterisk.ToCategoryFilterString().c_str());
+ CheckDefaultTraceConfigBehavior(tc_asterisk);
+
+ TraceConfig tc_empty_category_filter("", "");
+ EXPECT_STREQ("", tc_empty_category_filter.ToCategoryFilterString().c_str());
+ EXPECT_STREQ(kDefaultTraceConfigString,
+ tc_empty_category_filter.ToString().c_str());
+ CheckDefaultTraceConfigBehavior(tc_empty_category_filter);
+
+ // Constructor from JSON formated config string.
+ TraceConfig tc_empty_json_string("");
+ EXPECT_STREQ("", tc_empty_json_string.ToCategoryFilterString().c_str());
+ EXPECT_STREQ(kDefaultTraceConfigString,
+ tc_empty_json_string.ToString().c_str());
+ CheckDefaultTraceConfigBehavior(tc_empty_json_string);
+
+ // Constructor from dictionary value.
+ DictionaryValue dict;
+ TraceConfig tc_dict(dict);
+ EXPECT_STREQ("", tc_dict.ToCategoryFilterString().c_str());
+ EXPECT_STREQ(kDefaultTraceConfigString, tc_dict.ToString().c_str());
+ CheckDefaultTraceConfigBehavior(tc_dict);
+}
+
+TEST(TraceConfigTest, EmptyAndAsteriskCategoryFilterString) {
+ TraceConfig tc_empty("", "");
+ TraceConfig tc_asterisk("*", "");
+
+ EXPECT_STREQ("", tc_empty.ToCategoryFilterString().c_str());
+ EXPECT_STREQ("*", tc_asterisk.ToCategoryFilterString().c_str());
+
+ // Both fall back to default config.
+ CheckDefaultTraceConfigBehavior(tc_empty);
+ CheckDefaultTraceConfigBehavior(tc_asterisk);
+
+ // They differ only for internal checking.
+ EXPECT_FALSE(tc_empty.category_filter().IsCategoryEnabled("Category1"));
+ EXPECT_FALSE(
+ tc_empty.category_filter().IsCategoryEnabled("not-excluded-category"));
+ EXPECT_TRUE(tc_asterisk.category_filter().IsCategoryEnabled("Category1"));
+ EXPECT_TRUE(
+ tc_asterisk.category_filter().IsCategoryEnabled("not-excluded-category"));
+}
+
+TEST(TraceConfigTest, DisabledByDefaultCategoryFilterString) {
+ TraceConfig tc("foo,disabled-by-default-foo", "");
+ EXPECT_STREQ("foo,disabled-by-default-foo",
+ tc.ToCategoryFilterString().c_str());
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("foo"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-foo"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("bar"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("disabled-by-default-bar"));
+
+ EXPECT_TRUE(tc.event_filters().empty());
+ // Enabling only the disabled-by-default-* category means the default ones
+ // are also enabled.
+ tc = TraceConfig("disabled-by-default-foo", "");
+ EXPECT_STREQ("disabled-by-default-foo", tc.ToCategoryFilterString().c_str());
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-foo"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("foo"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("bar"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("disabled-by-default-bar"));
+}
+
+TEST(TraceConfigTest, TraceConfigFromDict) {
+ // Passing in empty dictionary will result in default trace config.
+ DictionaryValue dict;
+ TraceConfig tc(dict);
+ EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str());
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", tc.ToCategoryFilterString().c_str());
+
+ std::unique_ptr<Value> default_value(
+ JSONReader::Read(kDefaultTraceConfigString));
+ DCHECK(default_value);
+ const DictionaryValue* default_dict = nullptr;
+ bool is_dict = default_value->GetAsDictionary(&default_dict);
+ DCHECK(is_dict);
+ TraceConfig default_tc(*default_dict);
+ EXPECT_STREQ(kDefaultTraceConfigString, default_tc.ToString().c_str());
+ EXPECT_EQ(RECORD_UNTIL_FULL, default_tc.GetTraceRecordMode());
+ EXPECT_FALSE(default_tc.IsSystraceEnabled());
+ EXPECT_FALSE(default_tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", default_tc.ToCategoryFilterString().c_str());
+
+ std::unique_ptr<Value> custom_value(
+ JSONReader::Read(kCustomTraceConfigString));
+ DCHECK(custom_value);
+ const DictionaryValue* custom_dict = nullptr;
+ is_dict = custom_value->GetAsDictionary(&custom_dict);
+ DCHECK(is_dict);
+ TraceConfig custom_tc(*custom_dict);
+ EXPECT_STREQ(kCustomTraceConfigString, custom_tc.ToString().c_str());
+ EXPECT_EQ(RECORD_CONTINUOUSLY, custom_tc.GetTraceRecordMode());
+ EXPECT_TRUE(custom_tc.IsSystraceEnabled());
+ EXPECT_TRUE(custom_tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ(
+ "included,inc_pattern*,"
+ "disabled-by-default-cc,disabled-by-default-memory-infra,"
+ "-excluded,-exc_pattern*",
+ custom_tc.ToCategoryFilterString().c_str());
+}
+
+TEST(TraceConfigTest, TraceConfigFromValidString) {
+ // Using some non-empty config string.
+ const char config_string[] =
+ "{"
+ "\"enable_argument_filter\":true,"
+ "\"enable_systrace\":true,"
+ "\"event_filters\":["
+ "{"
+ "\"excluded_categories\":[\"unfiltered_cat\"],"
+ "\"filter_args\":{\"event_name_whitelist\":[\"a snake\",\"a dog\"]},"
+ "\"filter_predicate\":\"event_whitelist_predicate\","
+ "\"included_categories\":[\"*\"]"
+ "}"
+ "],"
+ "\"excluded_categories\":[\"excluded\",\"exc_pattern*\"],"
+ "\"included_categories\":[\"included\","
+ "\"inc_pattern*\","
+ "\"disabled-by-default-cc\"],"
+ "\"record_mode\":\"record-continuously\""
+ "}";
+ TraceConfig tc(config_string);
+
+ EXPECT_STREQ(config_string, tc.ToString().c_str());
+ EXPECT_EQ(RECORD_CONTINUOUSLY, tc.GetTraceRecordMode());
+ EXPECT_TRUE(tc.IsSystraceEnabled());
+ EXPECT_TRUE(tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ(
+ "included,inc_pattern*,disabled-by-default-cc,-excluded,"
+ "-exc_pattern*",
+ tc.ToCategoryFilterString().c_str());
+
+ EXPECT_TRUE(tc.category_filter().IsCategoryEnabled("included"));
+ EXPECT_TRUE(tc.category_filter().IsCategoryEnabled("inc_pattern_category"));
+ EXPECT_TRUE(tc.category_filter().IsCategoryEnabled("disabled-by-default-cc"));
+ EXPECT_FALSE(tc.category_filter().IsCategoryEnabled("excluded"));
+ EXPECT_FALSE(tc.category_filter().IsCategoryEnabled("exc_pattern_category"));
+ EXPECT_FALSE(
+ tc.category_filter().IsCategoryEnabled("disabled-by-default-others"));
+ EXPECT_FALSE(
+ tc.category_filter().IsCategoryEnabled("not-excluded-nor-included"));
+
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("included"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("inc_pattern_category"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-cc"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("excluded"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("exc_pattern_category"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("disabled-by-default-others"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("not-excluded-nor-included"));
+
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("included,excluded"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("excluded,exc_pattern_category"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("included"));
+
+ EXPECT_EQ(tc.event_filters().size(), 1u);
+ const TraceConfig::EventFilterConfig& event_filter = tc.event_filters()[0];
+ EXPECT_STREQ("event_whitelist_predicate",
+ event_filter.predicate_name().c_str());
+ EXPECT_EQ(1u, event_filter.category_filter().included_categories().size());
+ EXPECT_STREQ("*",
+ event_filter.category_filter().included_categories()[0].c_str());
+ EXPECT_EQ(1u, event_filter.category_filter().excluded_categories().size());
+ EXPECT_STREQ("unfiltered_cat",
+ event_filter.category_filter().excluded_categories()[0].c_str());
+ EXPECT_TRUE(event_filter.filter_args());
+
+ std::string json_out;
+ base::JSONWriter::Write(*event_filter.filter_args(), &json_out);
+ EXPECT_STREQ(json_out.c_str(),
+ "{\"event_name_whitelist\":[\"a snake\",\"a dog\"]}");
+ std::unordered_set<std::string> filter_values;
+ EXPECT_TRUE(event_filter.GetArgAsSet("event_name_whitelist", &filter_values));
+ EXPECT_EQ(2u, filter_values.size());
+ EXPECT_EQ(1u, filter_values.count("a snake"));
+ EXPECT_EQ(1u, filter_values.count("a dog"));
+
+ const char config_string_2[] = "{\"included_categories\":[\"*\"]}";
+ TraceConfig tc2(config_string_2);
+ EXPECT_TRUE(tc2.category_filter().IsCategoryEnabled(
+ "non-disabled-by-default-pattern"));
+ EXPECT_FALSE(
+ tc2.category_filter().IsCategoryEnabled("disabled-by-default-pattern"));
+ EXPECT_TRUE(tc2.IsCategoryGroupEnabled("non-disabled-by-default-pattern"));
+ EXPECT_FALSE(tc2.IsCategoryGroupEnabled("disabled-by-default-pattern"));
+
+ // Clear
+ tc.Clear();
+ EXPECT_STREQ(tc.ToString().c_str(),
+ "{"
+ "\"enable_argument_filter\":false,"
+ "\"enable_systrace\":false,"
+ "\"record_mode\":\"record-until-full\""
+ "}");
+}
+
+TEST(TraceConfigTest, TraceConfigFromInvalidString) {
+ // The config string needs to be a dictionary correctly formatted as a JSON
+ // string. Otherwise, it will fall back to the default initialization.
+ TraceConfig tc("");
+ EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str());
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", tc.ToCategoryFilterString().c_str());
+ CheckDefaultTraceConfigBehavior(tc);
+
+ tc = TraceConfig("This is an invalid config string.");
+ EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str());
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", tc.ToCategoryFilterString().c_str());
+ CheckDefaultTraceConfigBehavior(tc);
+
+ tc = TraceConfig("[\"This\", \"is\", \"not\", \"a\", \"dictionary\"]");
+ EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str());
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", tc.ToCategoryFilterString().c_str());
+ CheckDefaultTraceConfigBehavior(tc);
+
+ tc = TraceConfig("{\"record_mode\": invalid-value-needs-double-quote}");
+ EXPECT_STREQ(kDefaultTraceConfigString, tc.ToString().c_str());
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", tc.ToCategoryFilterString().c_str());
+ CheckDefaultTraceConfigBehavior(tc);
+
+ // If the config string a dictionary formatted as a JSON string, it will
+ // initialize TraceConfig with best effort.
+ tc = TraceConfig("{}");
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", tc.ToCategoryFilterString().c_str());
+ CheckDefaultTraceConfigBehavior(tc);
+
+ tc = TraceConfig("{\"arbitrary-key\":\"arbitrary-value\"}");
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+ EXPECT_STREQ("", tc.ToCategoryFilterString().c_str());
+ CheckDefaultTraceConfigBehavior(tc);
+
+ const char invalid_config_string[] =
+ "{"
+ "\"enable_systrace\":1,"
+ "\"excluded_categories\":[\"excluded\"],"
+ "\"included_categories\":\"not a list\","
+ "\"record_mode\":\"arbitrary-mode\""
+ "}";
+ tc = TraceConfig(invalid_config_string);
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+ EXPECT_FALSE(tc.IsArgumentFilterEnabled());
+
+ const char invalid_config_string_2[] =
+ "{"
+ "\"included_categories\":[\"category\",\"disabled-by-default-pattern\"],"
+ "\"excluded_categories\":[\"category\",\"disabled-by-default-pattern\"]"
+ "}";
+ tc = TraceConfig(invalid_config_string_2);
+ EXPECT_TRUE(tc.category_filter().IsCategoryEnabled("category"));
+ EXPECT_TRUE(
+ tc.category_filter().IsCategoryEnabled("disabled-by-default-pattern"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("category"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-pattern"));
+}
+
+TEST(TraceConfigTest, MergingTraceConfigs) {
+ // Merge
+ TraceConfig tc;
+ TraceConfig tc2("included,-excluded,inc_pattern*,-exc_pattern*", "");
+ tc.Merge(tc2);
+ EXPECT_STREQ("{"
+ "\"enable_argument_filter\":false,"
+ "\"enable_systrace\":false,"
+ "\"excluded_categories\":[\"excluded\",\"exc_pattern*\"],"
+ "\"record_mode\":\"record-until-full\""
+ "}",
+ tc.ToString().c_str());
+}
+
+TEST(TraceConfigTest, IsCategoryGroupEnabled) {
+ // Enabling a disabled- category does not require all categories to be traced
+ // to be included.
+ TraceConfig tc("disabled-by-default-cc,-excluded", "");
+ EXPECT_STREQ("disabled-by-default-cc,-excluded",
+ tc.ToCategoryFilterString().c_str());
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-cc"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("some_other_group"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("excluded"));
+
+ // Enabled a disabled- category and also including makes all categories to
+ // be traced require including.
+ tc = TraceConfig("disabled-by-default-cc,included", "");
+ EXPECT_STREQ("included,disabled-by-default-cc",
+ tc.ToCategoryFilterString().c_str());
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("disabled-by-default-cc"));
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled("included"));
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("other_included"));
+
+ // Excluding categories won't enable disabled-by-default ones with the
+ // excluded category is also present in the group.
+ tc = TraceConfig("-excluded", "");
+ EXPECT_STREQ("-excluded", tc.ToCategoryFilterString().c_str());
+ EXPECT_FALSE(tc.IsCategoryGroupEnabled("excluded,disabled-by-default-cc"));
+}
+
+TEST(TraceConfigTest, IsCategoryNameAllowed) {
+ // Test that IsCategoryNameAllowed actually catches categories that are
+ // explicitly forbidden. This method is called in a DCHECK to assert that we
+ // don't have these types of strings as categories.
+ EXPECT_FALSE(
+ TraceConfigCategoryFilter::IsCategoryNameAllowed(" bad_category "));
+ EXPECT_FALSE(
+ TraceConfigCategoryFilter::IsCategoryNameAllowed(" bad_category"));
+ EXPECT_FALSE(
+ TraceConfigCategoryFilter::IsCategoryNameAllowed("bad_category "));
+ EXPECT_FALSE(
+ TraceConfigCategoryFilter::IsCategoryNameAllowed(" bad_category"));
+ EXPECT_FALSE(
+ TraceConfigCategoryFilter::IsCategoryNameAllowed("bad_category "));
+ EXPECT_FALSE(
+ TraceConfigCategoryFilter::IsCategoryNameAllowed(" bad_category "));
+ EXPECT_FALSE(TraceConfigCategoryFilter::IsCategoryNameAllowed(""));
+ EXPECT_TRUE(
+ TraceConfigCategoryFilter::IsCategoryNameAllowed("good_category"));
+}
+
+TEST(TraceConfigTest, SetTraceOptionValues) {
+ TraceConfig tc;
+ EXPECT_EQ(RECORD_UNTIL_FULL, tc.GetTraceRecordMode());
+ EXPECT_FALSE(tc.IsSystraceEnabled());
+
+ tc.SetTraceRecordMode(RECORD_AS_MUCH_AS_POSSIBLE);
+ EXPECT_EQ(RECORD_AS_MUCH_AS_POSSIBLE, tc.GetTraceRecordMode());
+
+ tc.EnableSystrace();
+ EXPECT_TRUE(tc.IsSystraceEnabled());
+}
+
+TEST(TraceConfigTest, TraceConfigFromMemoryConfigString) {
+ std::string tc_str1 =
+ TraceConfigMemoryTestUtil::GetTraceConfig_PeriodicTriggers(200, 2000);
+ TraceConfig tc1(tc_str1);
+ EXPECT_EQ(tc_str1, tc1.ToString());
+ TraceConfig tc2(
+ TraceConfigMemoryTestUtil::GetTraceConfig_LegacyPeriodicTriggers(200,
+ 2000));
+ EXPECT_EQ(tc_str1, tc2.ToString());
+
+ EXPECT_TRUE(tc1.IsCategoryGroupEnabled(MemoryDumpManager::kTraceCategory));
+ ASSERT_EQ(2u, tc1.memory_dump_config().triggers.size());
+
+ EXPECT_EQ(200u,
+ tc1.memory_dump_config().triggers[0].min_time_between_dumps_ms);
+ EXPECT_EQ(MemoryDumpLevelOfDetail::LIGHT,
+ tc1.memory_dump_config().triggers[0].level_of_detail);
+
+ EXPECT_EQ(2000u,
+ tc1.memory_dump_config().triggers[1].min_time_between_dumps_ms);
+ EXPECT_EQ(MemoryDumpLevelOfDetail::DETAILED,
+ tc1.memory_dump_config().triggers[1].level_of_detail);
+ EXPECT_EQ(
+ 2048u,
+ tc1.memory_dump_config().heap_profiler_options.breakdown_threshold_bytes);
+
+ std::string tc_str3 =
+ TraceConfigMemoryTestUtil::GetTraceConfig_BackgroundTrigger(
+ 1 /* period_ms */);
+ TraceConfig tc3(tc_str3);
+ EXPECT_EQ(tc_str3, tc3.ToString());
+ EXPECT_TRUE(tc3.IsCategoryGroupEnabled(MemoryDumpManager::kTraceCategory));
+ ASSERT_EQ(1u, tc3.memory_dump_config().triggers.size());
+ EXPECT_EQ(1u, tc3.memory_dump_config().triggers[0].min_time_between_dumps_ms);
+ EXPECT_EQ(MemoryDumpLevelOfDetail::BACKGROUND,
+ tc3.memory_dump_config().triggers[0].level_of_detail);
+}
+
+TEST(TraceConfigTest, EmptyMemoryDumpConfigTest) {
+ // Empty trigger list should also be specified when converting back to string.
+ TraceConfig tc(TraceConfigMemoryTestUtil::GetTraceConfig_EmptyTriggers());
+ EXPECT_EQ(TraceConfigMemoryTestUtil::GetTraceConfig_EmptyTriggers(),
+ tc.ToString());
+ EXPECT_EQ(0u, tc.memory_dump_config().triggers.size());
+ EXPECT_EQ(
+ static_cast<uint32_t>(TraceConfig::MemoryDumpConfig::HeapProfiler::
+ kDefaultBreakdownThresholdBytes),
+ tc.memory_dump_config().heap_profiler_options.breakdown_threshold_bytes);
+}
+
+TEST(TraceConfigTest, LegacyStringToMemoryDumpConfig) {
+ TraceConfig tc(MemoryDumpManager::kTraceCategory, "");
+ EXPECT_TRUE(tc.IsCategoryGroupEnabled(MemoryDumpManager::kTraceCategory));
+ EXPECT_NE(std::string::npos, tc.ToString().find("memory_dump_config"));
+ EXPECT_EQ(0u, tc.memory_dump_config().triggers.size());
+ EXPECT_EQ(
+ static_cast<uint32_t>(TraceConfig::MemoryDumpConfig::HeapProfiler::
+ kDefaultBreakdownThresholdBytes),
+ tc.memory_dump_config().heap_profiler_options.breakdown_threshold_bytes);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event.h b/base/trace_event/trace_event.h
index 1ce76d9d2a..016c8ea864 100644
--- a/base/trace_event/trace_event.h
+++ b/base/trace_event/trace_event.h
@@ -23,6 +23,12 @@ template <typename... Args> void Ignore(Args&&... args) {}
#define INTERNAL_TRACE_EVENT_ADD_SCOPED_WITH_FLOW(...) \
INTERNAL_IGNORE(__VA_ARGS__)
#define TRACE_ID_MANGLE(val) (val)
+#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(...) INTERNAL_IGNORE(__VA_ARGS__)
+#define INTERNAL_TRACE_EVENT_GET_CATEGORY_ENABLED(...) \
+ INTERNAL_IGNORE(__VA_ARGS__)
+#define INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE(...) \
+ false
+#define INTERNAL_TRACE_EVENT_ADD(...) INTERNAL_IGNORE(__VA_ARGS__)
namespace base {
namespace trace_event {
diff --git a/base/trace_event/trace_event_android.cc b/base/trace_event/trace_event_android.cc
new file mode 100644
index 0000000000..30d9c74a6d
--- /dev/null
+++ b/base/trace_event/trace_event_android.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_impl.h"
+
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+int g_atrace_fd = -1;
+const char kATraceMarkerFile[] = "/sys/kernel/debug/tracing/trace_marker";
+
+void WriteToATrace(int fd, const char* buffer, size_t size) {
+ size_t total_written = 0;
+ while (total_written < size) {
+ ssize_t written = HANDLE_EINTR(write(
+ fd, buffer + total_written, size - total_written));
+ if (written <= 0)
+ break;
+ total_written += written;
+ }
+ if (total_written < size) {
+ PLOG(WARNING) << "Failed to write buffer '" << std::string(buffer, size)
+ << "' to " << kATraceMarkerFile;
+ }
+}
+
+void WriteEvent(
+ char phase,
+ const char* category_group,
+ const char* name,
+ unsigned long long id,
+ const char** arg_names,
+ const unsigned char* arg_types,
+ const TraceEvent::TraceValue* arg_values,
+ const std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags) {
+ std::string out = StringPrintf("%c|%d|%s", phase, getpid(), name);
+ if (flags & TRACE_EVENT_FLAG_HAS_ID)
+ StringAppendF(&out, "-%" PRIx64, static_cast<uint64_t>(id));
+ out += '|';
+
+ for (int i = 0; i < kTraceMaxNumArgs && arg_names[i];
+ ++i) {
+ if (i)
+ out += ';';
+ out += arg_names[i];
+ out += '=';
+ std::string::size_type value_start = out.length();
+ if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE)
+ convertable_values[i]->AppendAsTraceFormat(&out);
+ else
+ TraceEvent::AppendValueAsJSON(arg_types[i], arg_values[i], &out);
+
+ // Remove the quotes which may confuse the atrace script.
+ ReplaceSubstringsAfterOffset(&out, value_start, "\\\"", "'");
+ ReplaceSubstringsAfterOffset(&out, value_start, "\"", "");
+ // Replace chars used for separators with similar chars in the value.
+ std::replace(out.begin() + value_start, out.end(), ';', ',');
+ std::replace(out.begin() + value_start, out.end(), '|', '!');
+ }
+
+ out += '|';
+ out += category_group;
+ WriteToATrace(g_atrace_fd, out.c_str(), out.size());
+}
+
+void NoOpOutputCallback(WaitableEvent* complete_event,
+ const scoped_refptr<RefCountedString>&,
+ bool has_more_events) {
+ if (!has_more_events)
+ complete_event->Signal();
+}
+
+void EndChromeTracing(TraceLog* trace_log,
+ WaitableEvent* complete_event) {
+ trace_log->SetDisabled();
+ // Delete the buffered trace events as they have been sent to atrace.
+ trace_log->Flush(Bind(&NoOpOutputCallback, complete_event));
+}
+
+} // namespace
+
+// These functions support Android systrace.py when 'webview' category is
+// traced. With the new adb_profile_chrome, we may have two phases:
+// - before WebView is ready for combined tracing, we can use adb_profile_chrome
+// to trace android categories other than 'webview' and chromium categories.
+// In this way we can avoid the conflict between StartATrace/StopATrace and
+// the intents.
+// - TODO(wangxianzhu): after WebView is ready for combined tracing, remove
+// StartATrace, StopATrace and SendToATrace, and perhaps send Java traces
+// directly to atrace in trace_event_binding.cc.
+
+void TraceLog::StartATrace() {
+ if (g_atrace_fd != -1)
+ return;
+
+ g_atrace_fd = HANDLE_EINTR(open(kATraceMarkerFile, O_WRONLY));
+ if (g_atrace_fd == -1) {
+ PLOG(WARNING) << "Couldn't open " << kATraceMarkerFile;
+ return;
+ }
+ TraceConfig trace_config;
+ trace_config.SetTraceRecordMode(RECORD_CONTINUOUSLY);
+ SetEnabled(trace_config, TraceLog::RECORDING_MODE);
+}
+
+void TraceLog::StopATrace() {
+ if (g_atrace_fd == -1)
+ return;
+
+ close(g_atrace_fd);
+ g_atrace_fd = -1;
+
+ // TraceLog::Flush() requires the current thread to have a message loop, but
+ // this thread called from Java may not have one, so flush in another thread.
+ Thread end_chrome_tracing_thread("end_chrome_tracing");
+ WaitableEvent complete_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ end_chrome_tracing_thread.Start();
+ end_chrome_tracing_thread.task_runner()->PostTask(
+ FROM_HERE, base::Bind(&EndChromeTracing, Unretained(this),
+ Unretained(&complete_event)));
+ complete_event.Wait();
+}
+
+void TraceEvent::SendToATrace() {
+ if (g_atrace_fd == -1)
+ return;
+
+ const char* category_group =
+ TraceLog::GetCategoryGroupName(category_group_enabled_);
+
+ switch (phase_) {
+ case TRACE_EVENT_PHASE_BEGIN:
+ WriteEvent('B', category_group, name_, id_,
+ arg_names_, arg_types_, arg_values_, convertable_values_,
+ flags_);
+ break;
+
+ case TRACE_EVENT_PHASE_COMPLETE:
+ WriteEvent(duration_.ToInternalValue() == -1 ? 'B' : 'E',
+ category_group, name_, id_,
+ arg_names_, arg_types_, arg_values_, convertable_values_,
+ flags_);
+ break;
+
+ case TRACE_EVENT_PHASE_END:
+ // Though a single 'E' is enough, here append pid, name and
+ // category_group etc. So that unpaired events can be found easily.
+ WriteEvent('E', category_group, name_, id_,
+ arg_names_, arg_types_, arg_values_, convertable_values_,
+ flags_);
+ break;
+
+ case TRACE_EVENT_PHASE_INSTANT:
+ // Simulate an instance event with a pair of begin/end events.
+ WriteEvent('B', category_group, name_, id_,
+ arg_names_, arg_types_, arg_values_, convertable_values_,
+ flags_);
+ WriteToATrace(g_atrace_fd, "E", 1);
+ break;
+
+ case TRACE_EVENT_PHASE_COUNTER:
+ for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) {
+ DCHECK(arg_types_[i] == TRACE_VALUE_TYPE_INT);
+ std::string out = base::StringPrintf(
+ "C|%d|%s-%s", getpid(), name_, arg_names_[i]);
+ if (flags_ & TRACE_EVENT_FLAG_HAS_ID)
+ StringAppendF(&out, "-%" PRIx64, static_cast<uint64_t>(id_));
+ StringAppendF(&out, "|%d|%s",
+ static_cast<int>(arg_values_[i].as_int), category_group);
+ WriteToATrace(g_atrace_fd, out.c_str(), out.size());
+ }
+ break;
+
+ default:
+ // Do nothing.
+ break;
+ }
+}
+
+void TraceLog::AddClockSyncMetadataEvent() {
+ int atrace_fd = HANDLE_EINTR(open(kATraceMarkerFile, O_WRONLY | O_APPEND));
+ if (atrace_fd == -1) {
+ PLOG(WARNING) << "Couldn't open " << kATraceMarkerFile;
+ return;
+ }
+
+ // Android's kernel trace system has a trace_marker feature: this is a file on
+ // debugfs that takes the written data and pushes it onto the trace
+ // buffer. So, to establish clock sync, we write our monotonic clock into that
+ // trace buffer.
+ double now_in_seconds = (TRACE_TIME_TICKS_NOW() - TimeTicks()).InSecondsF();
+ std::string marker = StringPrintf(
+ "trace_event_clock_sync: parent_ts=%f\n", now_in_seconds);
+ WriteToATrace(atrace_fd, marker.c_str(), marker.size());
+ close(atrace_fd);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_android_unittest.cc b/base/trace_event/trace_event_android_unittest.cc
new file mode 100644
index 0000000000..58bd77ed93
--- /dev/null
+++ b/base/trace_event/trace_event_android_unittest.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+TEST(TraceEventAndroidTest, WriteToATrace) {
+ // Just a smoke test to ensure no crash.
+ TraceLog* trace_log = TraceLog::GetInstance();
+ trace_log->StartATrace();
+ TRACE_EVENT0("test", "test-event");
+ trace_log->StopATrace();
+ trace_log->AddClockSyncMetadataEvent();
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_argument.cc b/base/trace_event/trace_event_argument.cc
new file mode 100644
index 0000000000..e614b272d5
--- /dev/null
+++ b/base/trace_event/trace_event_argument.cc
@@ -0,0 +1,576 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_argument.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bits.h"
+#include "base/containers/circular_deque.h"
+#include "base/json/string_escape.h"
+#include "base/memory/ptr_util.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_impl.h"
+#include "base/trace_event/trace_event_memory_overhead.h"
+#include "base/values.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+const char kTypeStartDict = '{';
+const char kTypeEndDict = '}';
+const char kTypeStartArray = '[';
+const char kTypeEndArray = ']';
+const char kTypeBool = 'b';
+const char kTypeInt = 'i';
+const char kTypeDouble = 'd';
+const char kTypeString = 's';
+const char kTypeCStr = '*'; // only used for key names
+
+#ifndef NDEBUG
+const bool kStackTypeDict = false;
+const bool kStackTypeArray = true;
+#define DCHECK_CURRENT_CONTAINER_IS(x) DCHECK_EQ(x, nesting_stack_.back())
+#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) DCHECK_EQ(x, nesting_stack_.size())
+#define DEBUG_PUSH_CONTAINER(x) nesting_stack_.push_back(x)
+#define DEBUG_POP_CONTAINER() nesting_stack_.pop_back()
+#else
+#define DCHECK_CURRENT_CONTAINER_IS(x) do {} while (0)
+#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) do {} while (0)
+#define DEBUG_PUSH_CONTAINER(x) do {} while (0)
+#define DEBUG_POP_CONTAINER() do {} while (0)
+#endif
+
+inline void WriteKeyNameAsRawPtr(Pickle& pickle, const char* ptr) {
+ pickle.WriteBytes(&kTypeCStr, 1);
+ pickle.WriteUInt64(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(ptr)));
+}
+
+inline void WriteKeyNameWithCopy(Pickle& pickle, base::StringPiece str) {
+ pickle.WriteBytes(&kTypeString, 1);
+ pickle.WriteString(str);
+}
+
+std::string ReadKeyName(PickleIterator& pickle_iterator) {
+ const char* type = nullptr;
+ bool res = pickle_iterator.ReadBytes(&type, 1);
+ std::string key_name;
+ if (res && *type == kTypeCStr) {
+ uint64_t ptr_value = 0;
+ res = pickle_iterator.ReadUInt64(&ptr_value);
+ key_name = reinterpret_cast<const char*>(static_cast<uintptr_t>(ptr_value));
+ } else if (res && *type == kTypeString) {
+ res = pickle_iterator.ReadString(&key_name);
+ }
+ DCHECK(res);
+ return key_name;
+}
+} // namespace
+
+TracedValue::TracedValue() : TracedValue(0) {
+}
+
+TracedValue::TracedValue(size_t capacity) {
+ DEBUG_PUSH_CONTAINER(kStackTypeDict);
+ if (capacity)
+ pickle_.Reserve(capacity);
+}
+
+TracedValue::~TracedValue() {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ DEBUG_POP_CONTAINER();
+ DCHECK_CONTAINER_STACK_DEPTH_EQ(0u);
+}
+
+void TracedValue::SetInteger(const char* name, int value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeInt, 1);
+ pickle_.WriteInt(value);
+ WriteKeyNameAsRawPtr(pickle_, name);
+}
+
+void TracedValue::SetIntegerWithCopiedName(base::StringPiece name, int value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeInt, 1);
+ pickle_.WriteInt(value);
+ WriteKeyNameWithCopy(pickle_, name);
+}
+
+void TracedValue::SetDouble(const char* name, double value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeDouble, 1);
+ pickle_.WriteDouble(value);
+ WriteKeyNameAsRawPtr(pickle_, name);
+}
+
+void TracedValue::SetDoubleWithCopiedName(base::StringPiece name,
+ double value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeDouble, 1);
+ pickle_.WriteDouble(value);
+ WriteKeyNameWithCopy(pickle_, name);
+}
+
+void TracedValue::SetBoolean(const char* name, bool value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeBool, 1);
+ pickle_.WriteBool(value);
+ WriteKeyNameAsRawPtr(pickle_, name);
+}
+
+void TracedValue::SetBooleanWithCopiedName(base::StringPiece name,
+ bool value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeBool, 1);
+ pickle_.WriteBool(value);
+ WriteKeyNameWithCopy(pickle_, name);
+}
+
+void TracedValue::SetString(const char* name, base::StringPiece value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeString, 1);
+ pickle_.WriteString(value);
+ WriteKeyNameAsRawPtr(pickle_, name);
+}
+
+void TracedValue::SetStringWithCopiedName(base::StringPiece name,
+ base::StringPiece value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeString, 1);
+ pickle_.WriteString(value);
+ WriteKeyNameWithCopy(pickle_, name);
+}
+
+void TracedValue::SetValue(const char* name, const TracedValue& value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ BeginDictionary(name);
+ pickle_.WriteBytes(value.pickle_.payload(),
+ static_cast<int>(value.pickle_.payload_size()));
+ EndDictionary();
+}
+
+void TracedValue::SetValueWithCopiedName(base::StringPiece name,
+ const TracedValue& value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ BeginDictionaryWithCopiedName(name);
+ pickle_.WriteBytes(value.pickle_.payload(),
+ static_cast<int>(value.pickle_.payload_size()));
+ EndDictionary();
+}
+
+void TracedValue::BeginDictionary(const char* name) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ DEBUG_PUSH_CONTAINER(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeStartDict, 1);
+ WriteKeyNameAsRawPtr(pickle_, name);
+}
+
+void TracedValue::BeginDictionaryWithCopiedName(base::StringPiece name) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ DEBUG_PUSH_CONTAINER(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeStartDict, 1);
+ WriteKeyNameWithCopy(pickle_, name);
+}
+
+void TracedValue::BeginArray(const char* name) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ DEBUG_PUSH_CONTAINER(kStackTypeArray);
+ pickle_.WriteBytes(&kTypeStartArray, 1);
+ WriteKeyNameAsRawPtr(pickle_, name);
+}
+
+void TracedValue::BeginArrayWithCopiedName(base::StringPiece name) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ DEBUG_PUSH_CONTAINER(kStackTypeArray);
+ pickle_.WriteBytes(&kTypeStartArray, 1);
+ WriteKeyNameWithCopy(pickle_, name);
+}
+
+void TracedValue::EndDictionary() {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ DEBUG_POP_CONTAINER();
+ pickle_.WriteBytes(&kTypeEndDict, 1);
+}
+
+void TracedValue::AppendInteger(int value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
+ pickle_.WriteBytes(&kTypeInt, 1);
+ pickle_.WriteInt(value);
+}
+
+void TracedValue::AppendDouble(double value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
+ pickle_.WriteBytes(&kTypeDouble, 1);
+ pickle_.WriteDouble(value);
+}
+
+void TracedValue::AppendBoolean(bool value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
+ pickle_.WriteBytes(&kTypeBool, 1);
+ pickle_.WriteBool(value);
+}
+
+void TracedValue::AppendString(base::StringPiece value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
+ pickle_.WriteBytes(&kTypeString, 1);
+ pickle_.WriteString(value);
+}
+
+void TracedValue::BeginArray() {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
+ DEBUG_PUSH_CONTAINER(kStackTypeArray);
+ pickle_.WriteBytes(&kTypeStartArray, 1);
+}
+
+void TracedValue::BeginDictionary() {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
+ DEBUG_PUSH_CONTAINER(kStackTypeDict);
+ pickle_.WriteBytes(&kTypeStartDict, 1);
+}
+
+void TracedValue::EndArray() {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
+ DEBUG_POP_CONTAINER();
+ pickle_.WriteBytes(&kTypeEndArray, 1);
+}
+
+void TracedValue::SetValue(const char* name,
+ std::unique_ptr<base::Value> value) {
+ SetBaseValueWithCopiedName(name, *value);
+}
+
+void TracedValue::SetBaseValueWithCopiedName(base::StringPiece name,
+ const base::Value& value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ switch (value.type()) {
+ case base::Value::Type::NONE:
+ case base::Value::Type::BINARY:
+ NOTREACHED();
+ break;
+
+ case base::Value::Type::BOOLEAN: {
+ bool bool_value;
+ value.GetAsBoolean(&bool_value);
+ SetBooleanWithCopiedName(name, bool_value);
+ } break;
+
+ case base::Value::Type::INTEGER: {
+ int int_value;
+ value.GetAsInteger(&int_value);
+ SetIntegerWithCopiedName(name, int_value);
+ } break;
+
+ case base::Value::Type::DOUBLE: {
+ double double_value;
+ value.GetAsDouble(&double_value);
+ SetDoubleWithCopiedName(name, double_value);
+ } break;
+
+ case base::Value::Type::STRING: {
+ const Value* string_value;
+ value.GetAsString(&string_value);
+ SetStringWithCopiedName(name, string_value->GetString());
+ } break;
+
+ case base::Value::Type::DICTIONARY: {
+ const DictionaryValue* dict_value;
+ value.GetAsDictionary(&dict_value);
+ BeginDictionaryWithCopiedName(name);
+ for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd();
+ it.Advance()) {
+ SetBaseValueWithCopiedName(it.key(), it.value());
+ }
+ EndDictionary();
+ } break;
+
+ case base::Value::Type::LIST: {
+ const ListValue* list_value;
+ value.GetAsList(&list_value);
+ BeginArrayWithCopiedName(name);
+ for (const auto& base_value : *list_value)
+ AppendBaseValue(base_value);
+ EndArray();
+ } break;
+ }
+}
+
+void TracedValue::AppendBaseValue(const base::Value& value) {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
+ switch (value.type()) {
+ case base::Value::Type::NONE:
+ case base::Value::Type::BINARY:
+ NOTREACHED();
+ break;
+
+ case base::Value::Type::BOOLEAN: {
+ bool bool_value;
+ value.GetAsBoolean(&bool_value);
+ AppendBoolean(bool_value);
+ } break;
+
+ case base::Value::Type::INTEGER: {
+ int int_value;
+ value.GetAsInteger(&int_value);
+ AppendInteger(int_value);
+ } break;
+
+ case base::Value::Type::DOUBLE: {
+ double double_value;
+ value.GetAsDouble(&double_value);
+ AppendDouble(double_value);
+ } break;
+
+ case base::Value::Type::STRING: {
+ const Value* string_value;
+ value.GetAsString(&string_value);
+ AppendString(string_value->GetString());
+ } break;
+
+ case base::Value::Type::DICTIONARY: {
+ const DictionaryValue* dict_value;
+ value.GetAsDictionary(&dict_value);
+ BeginDictionary();
+ for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd();
+ it.Advance()) {
+ SetBaseValueWithCopiedName(it.key(), it.value());
+ }
+ EndDictionary();
+ } break;
+
+ case base::Value::Type::LIST: {
+ const ListValue* list_value;
+ value.GetAsList(&list_value);
+ BeginArray();
+ for (const auto& base_value : *list_value)
+ AppendBaseValue(base_value);
+ EndArray();
+ } break;
+ }
+}
+
+std::unique_ptr<base::Value> TracedValue::ToBaseValue() const {
+ base::Value root(base::Value::Type::DICTIONARY);
+ Value* cur_dict = &root;
+ Value* cur_list = nullptr;
+ std::vector<Value*> stack;
+ PickleIterator it(pickle_);
+ const char* type;
+
+ while (it.ReadBytes(&type, 1)) {
+ DCHECK((cur_dict && !cur_list) || (cur_list && !cur_dict));
+ switch (*type) {
+ case kTypeStartDict: {
+ base::Value new_dict(base::Value::Type::DICTIONARY);
+ if (cur_dict) {
+ stack.push_back(cur_dict);
+ cur_dict = cur_dict->SetKey(ReadKeyName(it), std::move(new_dict));
+ } else {
+ cur_list->GetList().push_back(std::move(new_dict));
+ // |new_dict| is invalidated at this point, so |cur_dict| needs to be
+ // reset.
+ cur_dict = &cur_list->GetList().back();
+ stack.push_back(cur_list);
+ cur_list = nullptr;
+ }
+ } break;
+
+ case kTypeEndArray:
+ case kTypeEndDict: {
+ if (stack.back()->is_dict()) {
+ cur_dict = stack.back();
+ cur_list = nullptr;
+ } else if (stack.back()->is_list()) {
+ cur_list = stack.back();
+ cur_dict = nullptr;
+ }
+ stack.pop_back();
+ } break;
+
+ case kTypeStartArray: {
+ base::Value new_list(base::Value::Type::LIST);
+ if (cur_dict) {
+ stack.push_back(cur_dict);
+ cur_list = cur_dict->SetKey(ReadKeyName(it), std::move(new_list));
+ cur_dict = nullptr;
+ } else {
+ cur_list->GetList().push_back(std::move(new_list));
+ stack.push_back(cur_list);
+ // |cur_list| is invalidated at this point by the Append, so it needs
+ // to be reset.
+ cur_list = &cur_list->GetList().back();
+ }
+ } break;
+
+ case kTypeBool: {
+ bool value;
+ CHECK(it.ReadBool(&value));
+ base::Value new_bool(value);
+ if (cur_dict) {
+ cur_dict->SetKey(ReadKeyName(it), std::move(new_bool));
+ } else {
+ cur_list->GetList().push_back(std::move(new_bool));
+ }
+ } break;
+
+ case kTypeInt: {
+ int value;
+ CHECK(it.ReadInt(&value));
+ base::Value new_int(value);
+ if (cur_dict) {
+ cur_dict->SetKey(ReadKeyName(it), std::move(new_int));
+ } else {
+ cur_list->GetList().push_back(std::move(new_int));
+ }
+ } break;
+
+ case kTypeDouble: {
+ double value;
+ CHECK(it.ReadDouble(&value));
+ base::Value new_double(value);
+ if (cur_dict) {
+ cur_dict->SetKey(ReadKeyName(it), std::move(new_double));
+ } else {
+ cur_list->GetList().push_back(std::move(new_double));
+ }
+ } break;
+
+ case kTypeString: {
+ std::string value;
+ CHECK(it.ReadString(&value));
+ base::Value new_str(std::move(value));
+ if (cur_dict) {
+ cur_dict->SetKey(ReadKeyName(it), std::move(new_str));
+ } else {
+ cur_list->GetList().push_back(std::move(new_str));
+ }
+ } break;
+
+ default:
+ NOTREACHED();
+ }
+ }
+ DCHECK(stack.empty());
+ return base::Value::ToUniquePtrValue(std::move(root));
+}
+
+void TracedValue::AppendAsTraceFormat(std::string* out) const {
+ DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+ DCHECK_CONTAINER_STACK_DEPTH_EQ(1u);
+
+ struct State {
+ enum Type { kTypeDict, kTypeArray };
+ Type type;
+ bool needs_comma;
+ };
+
+ auto maybe_append_key_name = [](State current_state, PickleIterator* it,
+ std::string* out) {
+ if (current_state.type == State::kTypeDict) {
+ EscapeJSONString(ReadKeyName(*it), true, out);
+ out->append(":");
+ }
+ };
+
+ base::circular_deque<State> state_stack;
+
+ out->append("{");
+ state_stack.push_back({State::kTypeDict});
+
+ PickleIterator it(pickle_);
+ for (const char* type; it.ReadBytes(&type, 1);) {
+ switch (*type) {
+ case kTypeEndDict:
+ out->append("}");
+ state_stack.pop_back();
+ continue;
+
+ case kTypeEndArray:
+ out->append("]");
+ state_stack.pop_back();
+ continue;
+ }
+
+ // Use an index so it will stay valid across resizes.
+ size_t current_state_index = state_stack.size() - 1;
+ if (state_stack[current_state_index].needs_comma)
+ out->append(",");
+
+ switch (*type) {
+ case kTypeStartDict: {
+ maybe_append_key_name(state_stack[current_state_index], &it, out);
+ out->append("{");
+ state_stack.push_back({State::kTypeDict});
+ break;
+ }
+
+ case kTypeStartArray: {
+ maybe_append_key_name(state_stack[current_state_index], &it, out);
+ out->append("[");
+ state_stack.push_back({State::kTypeArray});
+ break;
+ }
+
+ case kTypeBool: {
+ TraceEvent::TraceValue json_value;
+ CHECK(it.ReadBool(&json_value.as_bool));
+ maybe_append_key_name(state_stack[current_state_index], &it, out);
+ TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_BOOL, json_value, out);
+ break;
+ }
+
+ case kTypeInt: {
+ int value;
+ CHECK(it.ReadInt(&value));
+ maybe_append_key_name(state_stack[current_state_index], &it, out);
+ TraceEvent::TraceValue json_value;
+ json_value.as_int = value;
+ TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_INT, json_value, out);
+ break;
+ }
+
+ case kTypeDouble: {
+ TraceEvent::TraceValue json_value;
+ CHECK(it.ReadDouble(&json_value.as_double));
+ maybe_append_key_name(state_stack[current_state_index], &it, out);
+ TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_DOUBLE, json_value, out);
+ break;
+ }
+
+ case kTypeString: {
+ std::string value;
+ CHECK(it.ReadString(&value));
+ maybe_append_key_name(state_stack[current_state_index], &it, out);
+ TraceEvent::TraceValue json_value;
+ json_value.as_string = value.c_str();
+ TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_STRING, json_value, out);
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ }
+
+ state_stack[current_state_index].needs_comma = true;
+ }
+
+ out->append("}");
+ state_stack.pop_back();
+
+ DCHECK(state_stack.empty());
+}
+
+void TracedValue::EstimateTraceMemoryOverhead(
+ TraceEventMemoryOverhead* overhead) {
+ overhead->Add(TraceEventMemoryOverhead::kTracedValue,
+ /* allocated size */
+ pickle_.GetTotalAllocatedSize(),
+ /* resident size */
+ pickle_.size());
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_argument.h b/base/trace_event/trace_event_argument.h
new file mode 100644
index 0000000000..81d8c0172a
--- /dev/null
+++ b/base/trace_event/trace_event_argument.h
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_EVENT_ARGUMENT_H_
+#define BASE_TRACE_EVENT_TRACE_EVENT_ARGUMENT_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/pickle.h"
+#include "base/strings/string_piece.h"
+#include "base/trace_event/trace_event_impl.h"
+
+namespace base {
+
+class Value;
+
+namespace trace_event {
+
+class BASE_EXPORT TracedValue : public ConvertableToTraceFormat {
+ public:
+ TracedValue();
+ explicit TracedValue(size_t capacity);
+ ~TracedValue() override;
+
+ void EndDictionary();
+ void EndArray();
+
+ // These methods assume that |name| is a long lived "quoted" string.
+ void SetInteger(const char* name, int value);
+ void SetDouble(const char* name, double value);
+ void SetBoolean(const char* name, bool value);
+ void SetString(const char* name, base::StringPiece value);
+ void SetValue(const char* name, const TracedValue& value);
+ void BeginDictionary(const char* name);
+ void BeginArray(const char* name);
+
+ // These, instead, can be safely passed a temporary string.
+ void SetIntegerWithCopiedName(base::StringPiece name, int value);
+ void SetDoubleWithCopiedName(base::StringPiece name, double value);
+ void SetBooleanWithCopiedName(base::StringPiece name, bool value);
+ void SetStringWithCopiedName(base::StringPiece name,
+ base::StringPiece value);
+ void SetValueWithCopiedName(base::StringPiece name,
+ const TracedValue& value);
+ void BeginDictionaryWithCopiedName(base::StringPiece name);
+ void BeginArrayWithCopiedName(base::StringPiece name);
+
+ void AppendInteger(int);
+ void AppendDouble(double);
+ void AppendBoolean(bool);
+ void AppendString(base::StringPiece);
+ void BeginArray();
+ void BeginDictionary();
+
+ // ConvertableToTraceFormat implementation.
+ void AppendAsTraceFormat(std::string* out) const override;
+
+ void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead) override;
+
+ // DEPRECATED: do not use, here only for legacy reasons. These methods causes
+ // a copy-and-translation of the base::Value into the equivalent TracedValue.
+ // TODO(primiano): migrate the (three) existing clients to the cheaper
+ // SetValue(TracedValue) API. crbug.com/495628.
+ void SetValue(const char* name, std::unique_ptr<base::Value> value);
+ void SetBaseValueWithCopiedName(base::StringPiece name,
+ const base::Value& value);
+ void AppendBaseValue(const base::Value& value);
+
+ // Public for tests only.
+ std::unique_ptr<base::Value> ToBaseValue() const;
+
+ private:
+ Pickle pickle_;
+
+#ifndef NDEBUG
+ // In debug builds checks the pairings of {Start,End}{Dictionary,Array}
+ std::vector<bool> nesting_stack_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(TracedValue);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_EVENT_ARGUMENT_H_
diff --git a/base/trace_event/trace_event_argument_unittest.cc b/base/trace_event/trace_event_argument_unittest.cc
new file mode 100644
index 0000000000..448b2d5619
--- /dev/null
+++ b/base/trace_event/trace_event_argument_unittest.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_argument.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+TEST(TraceEventArgumentTest, FlatDictionary) {
+ std::unique_ptr<TracedValue> value(new TracedValue());
+ value->SetBoolean("bool", true);
+ value->SetDouble("double", 0.0);
+ value->SetInteger("int", 2014);
+ value->SetString("string", "string");
+ std::string json = "PREFIX";
+ value->AppendAsTraceFormat(&json);
+ EXPECT_EQ(
+ "PREFIX{\"bool\":true,\"double\":0.0,\"int\":2014,\"string\":\"string\"}",
+ json);
+}
+
+TEST(TraceEventArgumentTest, NoDotPathExpansion) {
+ std::unique_ptr<TracedValue> value(new TracedValue());
+ value->SetBoolean("bo.ol", true);
+ value->SetDouble("doub.le", 0.0);
+ value->SetInteger("in.t", 2014);
+ value->SetString("str.ing", "str.ing");
+ std::string json;
+ value->AppendAsTraceFormat(&json);
+ EXPECT_EQ(
+ "{\"bo.ol\":true,\"doub.le\":0.0,\"in.t\":2014,\"str.ing\":\"str.ing\"}",
+ json);
+}
+
+TEST(TraceEventArgumentTest, Hierarchy) {
+ std::unique_ptr<TracedValue> value(new TracedValue());
+ value->BeginArray("a1");
+ value->AppendInteger(1);
+ value->AppendBoolean(true);
+ value->BeginDictionary();
+ value->SetInteger("i2", 3);
+ value->EndDictionary();
+ value->EndArray();
+ value->SetBoolean("b0", true);
+ value->SetDouble("d0", 0.0);
+ value->BeginDictionary("dict1");
+ value->BeginDictionary("dict2");
+ value->SetBoolean("b2", false);
+ value->EndDictionary();
+ value->SetInteger("i1", 2014);
+ value->SetString("s1", "foo");
+ value->EndDictionary();
+ value->SetInteger("i0", 2014);
+ value->SetString("s0", "foo");
+ std::string json;
+ value->AppendAsTraceFormat(&json);
+ EXPECT_EQ(
+ "{\"a1\":[1,true,{\"i2\":3}],\"b0\":true,\"d0\":0.0,\"dict1\":{\"dict2\":"
+ "{\"b2\":false},\"i1\":2014,\"s1\":\"foo\"},\"i0\":2014,\"s0\":"
+ "\"foo\"}",
+ json);
+}
+
+TEST(TraceEventArgumentTest, LongStrings) {
+ std::string kLongString = "supercalifragilisticexpialidocious";
+ std::string kLongString2 = "0123456789012345678901234567890123456789";
+ char kLongString3[4096];
+ for (size_t i = 0; i < sizeof(kLongString3); ++i)
+ kLongString3[i] = 'a' + (i % 25);
+ kLongString3[sizeof(kLongString3) - 1] = '\0';
+
+ std::unique_ptr<TracedValue> value(new TracedValue());
+ value->SetString("a", "short");
+ value->SetString("b", kLongString);
+ value->BeginArray("c");
+ value->AppendString(kLongString2);
+ value->AppendString("");
+ value->BeginDictionary();
+ value->SetString("a", kLongString3);
+ value->EndDictionary();
+ value->EndArray();
+
+ std::string json;
+ value->AppendAsTraceFormat(&json);
+ EXPECT_EQ("{\"a\":\"short\",\"b\":\"" + kLongString + "\",\"c\":[\"" +
+ kLongString2 + "\",\"\",{\"a\":\"" + kLongString3 + "\"}]}",
+ json);
+}
+
+TEST(TraceEventArgumentTest, PassBaseValue) {
+ Value int_value(42);
+ Value bool_value(true);
+ Value double_value(42.0f);
+
+ auto dict_value = WrapUnique(new DictionaryValue);
+ dict_value->SetBoolean("bool", true);
+ dict_value->SetInteger("int", 42);
+ dict_value->SetDouble("double", 42.0f);
+ dict_value->SetString("string", std::string("a") + "b");
+ dict_value->SetString("string", std::string("a") + "b");
+
+ auto list_value = WrapUnique(new ListValue);
+ list_value->AppendBoolean(false);
+ list_value->AppendInteger(1);
+ list_value->AppendString("in_list");
+ list_value->Append(std::move(dict_value));
+
+ std::unique_ptr<TracedValue> value(new TracedValue());
+ value->BeginDictionary("outer_dict");
+ value->SetValue("inner_list", std::move(list_value));
+ value->EndDictionary();
+
+ dict_value.reset();
+ list_value.reset();
+
+ std::string json;
+ value->AppendAsTraceFormat(&json);
+ EXPECT_EQ(
+ "{\"outer_dict\":{\"inner_list\":[false,1,\"in_list\",{\"bool\":true,"
+ "\"double\":42.0,\"int\":42,\"string\":\"ab\"}]}}",
+ json);
+}
+
+TEST(TraceEventArgumentTest, PassTracedValue) {
+ auto dict_value = std::make_unique<TracedValue>();
+ dict_value->SetInteger("a", 1);
+
+ auto nested_dict_value = std::make_unique<TracedValue>();
+ nested_dict_value->SetInteger("b", 2);
+ nested_dict_value->BeginArray("c");
+ nested_dict_value->AppendString("foo");
+ nested_dict_value->EndArray();
+
+ dict_value->SetValue("e", *nested_dict_value);
+
+ // Check the merged result.
+ std::string json;
+ dict_value->AppendAsTraceFormat(&json);
+ EXPECT_EQ("{\"a\":1,\"e\":{\"b\":2,\"c\":[\"foo\"]}}", json);
+
+ // Check that the passed nestd dict was left unouthced.
+ json = "";
+ nested_dict_value->AppendAsTraceFormat(&json);
+ EXPECT_EQ("{\"b\":2,\"c\":[\"foo\"]}", json);
+
+ // And that it is still usable.
+ nested_dict_value->SetInteger("f", 3);
+ nested_dict_value->BeginDictionary("g");
+ nested_dict_value->EndDictionary();
+ json = "";
+ nested_dict_value->AppendAsTraceFormat(&json);
+ EXPECT_EQ("{\"b\":2,\"c\":[\"foo\"],\"f\":3,\"g\":{}}", json);
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_filter.cc b/base/trace_event/trace_event_filter.cc
new file mode 100644
index 0000000000..d0b116ee04
--- /dev/null
+++ b/base/trace_event/trace_event_filter.cc
@@ -0,0 +1,17 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_filter.h"
+
+namespace base {
+namespace trace_event {
+
+TraceEventFilter::TraceEventFilter() = default;
+TraceEventFilter::~TraceEventFilter() = default;
+
+void TraceEventFilter::EndEvent(const char* category_name,
+ const char* event_name) const {}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_filter.h b/base/trace_event/trace_event_filter.h
new file mode 100644
index 0000000000..48c6711432
--- /dev/null
+++ b/base/trace_event/trace_event_filter.h
@@ -0,0 +1,51 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_
+#define BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_
+
+#include <memory>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+
+namespace base {
+namespace trace_event {
+
+class TraceEvent;
+
+// TraceEventFilter is like iptables for TRACE_EVENT macros. Filters can be
+// enabled on a per-category basis, hence a single filter instance can serve
+// more than a TraceCategory. There are two use cases for filters:
+// 1. Snooping TRACE_EVENT macros without adding them to the TraceLog. This is
+// possible by setting the ENABLED_FOR_FILTERING flag on a category w/o
+// ENABLED_FOR_RECORDING (see TraceConfig for user-facing configuration).
+// 2. Filtering TRACE_EVENT macros before they are added to the TraceLog. This
+// requires both the ENABLED_FOR_FILTERING and ENABLED_FOR_RECORDING flags
+// on the category.
+// More importantly, filters must be thread-safe. The FilterTraceEvent and
+// EndEvent methods can be called concurrently as trace macros are hit on
+// different threads.
+class BASE_EXPORT TraceEventFilter {
+ public:
+ TraceEventFilter();
+ virtual ~TraceEventFilter();
+
+ // If the category is ENABLED_FOR_RECORDING, the event is added iff all the
+ // filters enabled for the category return true. false causes the event to be
+ // discarded.
+ virtual bool FilterTraceEvent(const TraceEvent& trace_event) const = 0;
+
+ // Notifies the end of a duration event when the RAII macro goes out of scope.
+ virtual void EndEvent(const char* category_name,
+ const char* event_name) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TraceEventFilter);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_EVENT_FILTER_H_
diff --git a/base/trace_event/trace_event_filter_test_utils.cc b/base/trace_event/trace_event_filter_test_utils.cc
new file mode 100644
index 0000000000..85b4cfa276
--- /dev/null
+++ b/base/trace_event/trace_event_filter_test_utils.cc
@@ -0,0 +1,61 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_filter_test_utils.h"
+
+#include "base/logging.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+TestEventFilter::HitsCounter* g_hits_counter;
+} // namespace;
+
+// static
+const char TestEventFilter::kName[] = "testing_predicate";
+bool TestEventFilter::filter_return_value_;
+
+// static
+std::unique_ptr<TraceEventFilter> TestEventFilter::Factory(
+ const std::string& predicate_name) {
+ std::unique_ptr<TraceEventFilter> res;
+ if (predicate_name == kName)
+ res.reset(new TestEventFilter());
+ return res;
+}
+
+TestEventFilter::TestEventFilter() = default;
+TestEventFilter::~TestEventFilter() = default;
+
+bool TestEventFilter::FilterTraceEvent(const TraceEvent& trace_event) const {
+ if (g_hits_counter)
+ g_hits_counter->filter_trace_event_hit_count++;
+ return filter_return_value_;
+}
+
+void TestEventFilter::EndEvent(const char* category_name,
+ const char* name) const {
+ if (g_hits_counter)
+ g_hits_counter->end_event_hit_count++;
+}
+
+TestEventFilter::HitsCounter::HitsCounter() {
+ Reset();
+ DCHECK(!g_hits_counter);
+ g_hits_counter = this;
+}
+
+TestEventFilter::HitsCounter::~HitsCounter() {
+ DCHECK(g_hits_counter);
+ g_hits_counter = nullptr;
+}
+
+void TestEventFilter::HitsCounter::Reset() {
+ filter_trace_event_hit_count = 0;
+ end_event_hit_count = 0;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_filter_test_utils.h b/base/trace_event/trace_event_filter_test_utils.h
new file mode 100644
index 0000000000..419068b221
--- /dev/null
+++ b/base/trace_event/trace_event_filter_test_utils.h
@@ -0,0 +1,53 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_
+#define BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/trace_event/trace_event_filter.h"
+
+namespace base {
+namespace trace_event {
+
+class TestEventFilter : public TraceEventFilter {
+ public:
+ struct HitsCounter {
+ HitsCounter();
+ ~HitsCounter();
+ void Reset();
+ size_t filter_trace_event_hit_count;
+ size_t end_event_hit_count;
+ };
+
+ static const char kName[];
+
+ // Factory method for TraceLog::SetFilterFactoryForTesting().
+ static std::unique_ptr<TraceEventFilter> Factory(
+ const std::string& predicate_name);
+
+ TestEventFilter();
+ ~TestEventFilter() override;
+
+ // TraceEventFilter implementation.
+ bool FilterTraceEvent(const TraceEvent& trace_event) const override;
+ void EndEvent(const char* category_name, const char* name) const override;
+
+ static void set_filter_return_value(bool value) {
+ filter_return_value_ = value;
+ }
+
+ private:
+ static bool filter_return_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEventFilter);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_EVENT_FILTER_TEST_UTILS_H_
diff --git a/base/trace_event/trace_event_impl.cc b/base/trace_event/trace_event_impl.cc
new file mode 100644
index 0000000000..c72e1fce7d
--- /dev/null
+++ b/base/trace_event/trace_event_impl.cc
@@ -0,0 +1,489 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_impl.h"
+
+#include <stddef.h>
+
+#include "base/format_macros.h"
+#include "base/json/string_escape.h"
+#include "base/memory/ptr_util.h"
+#include "base/process/process_handle.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "base/trace_event/trace_log.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+size_t GetAllocLength(const char* str) { return str ? strlen(str) + 1 : 0; }
+
+// Copies |*member| into |*buffer|, sets |*member| to point to this new
+// location, and then advances |*buffer| by the amount written.
+void CopyTraceEventParameter(char** buffer,
+ const char** member,
+ const char* end) {
+ if (*member) {
+ size_t written = strlcpy(*buffer, *member, end - *buffer) + 1;
+ DCHECK_LE(static_cast<int>(written), end - *buffer);
+ *member = *buffer;
+ *buffer += written;
+ }
+}
+
+} // namespace
+
+TraceEvent::TraceEvent()
+ : duration_(TimeDelta::FromInternalValue(-1)),
+ scope_(trace_event_internal::kGlobalScope),
+ id_(0u),
+ category_group_enabled_(nullptr),
+ name_(nullptr),
+ thread_id_(0),
+ flags_(0),
+ phase_(TRACE_EVENT_PHASE_BEGIN) {
+ for (int i = 0; i < kTraceMaxNumArgs; ++i)
+ arg_names_[i] = nullptr;
+ memset(arg_values_, 0, sizeof(arg_values_));
+}
+
+TraceEvent::~TraceEvent() = default;
+
+void TraceEvent::MoveFrom(std::unique_ptr<TraceEvent> other) {
+ timestamp_ = other->timestamp_;
+ thread_timestamp_ = other->thread_timestamp_;
+ duration_ = other->duration_;
+ scope_ = other->scope_;
+ id_ = other->id_;
+ category_group_enabled_ = other->category_group_enabled_;
+ name_ = other->name_;
+ if (other->flags_ & TRACE_EVENT_FLAG_HAS_PROCESS_ID)
+ process_id_ = other->process_id_;
+ else
+ thread_id_ = other->thread_id_;
+ phase_ = other->phase_;
+ flags_ = other->flags_;
+ parameter_copy_storage_ = std::move(other->parameter_copy_storage_);
+
+ for (int i = 0; i < kTraceMaxNumArgs; ++i) {
+ arg_names_[i] = other->arg_names_[i];
+ arg_types_[i] = other->arg_types_[i];
+ arg_values_[i] = other->arg_values_[i];
+ convertable_values_[i] = std::move(other->convertable_values_[i]);
+ }
+}
+
+void TraceEvent::Initialize(
+ int thread_id,
+ TimeTicks timestamp,
+ ThreadTicks thread_timestamp,
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ unsigned long long bind_id,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags) {
+ timestamp_ = timestamp;
+ thread_timestamp_ = thread_timestamp;
+ duration_ = TimeDelta::FromInternalValue(-1);
+ scope_ = scope;
+ id_ = id;
+ category_group_enabled_ = category_group_enabled;
+ name_ = name;
+ thread_id_ = thread_id;
+ phase_ = phase;
+ flags_ = flags;
+ bind_id_ = bind_id;
+
+ // Clamp num_args since it may have been set by a third_party library.
+ num_args = (num_args > kTraceMaxNumArgs) ? kTraceMaxNumArgs : num_args;
+ int i = 0;
+ for (; i < num_args; ++i) {
+ arg_names_[i] = arg_names[i];
+ arg_types_[i] = arg_types[i];
+
+ if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) {
+ convertable_values_[i] = std::move(convertable_values[i]);
+ } else {
+ arg_values_[i].as_uint = arg_values[i];
+ convertable_values_[i].reset();
+ }
+ }
+ for (; i < kTraceMaxNumArgs; ++i) {
+ arg_names_[i] = nullptr;
+ arg_values_[i].as_uint = 0u;
+ convertable_values_[i].reset();
+ arg_types_[i] = TRACE_VALUE_TYPE_UINT;
+ }
+
+ bool copy = !!(flags & TRACE_EVENT_FLAG_COPY);
+ size_t alloc_size = 0;
+ if (copy) {
+ alloc_size += GetAllocLength(name) + GetAllocLength(scope);
+ for (i = 0; i < num_args; ++i) {
+ alloc_size += GetAllocLength(arg_names_[i]);
+ if (arg_types_[i] == TRACE_VALUE_TYPE_STRING)
+ arg_types_[i] = TRACE_VALUE_TYPE_COPY_STRING;
+ }
+ }
+
+ bool arg_is_copy[kTraceMaxNumArgs];
+ for (i = 0; i < num_args; ++i) {
+ // No copying of convertable types, we retain ownership.
+ if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE)
+ continue;
+
+ // We only take a copy of arg_vals if they are of type COPY_STRING.
+ arg_is_copy[i] = (arg_types_[i] == TRACE_VALUE_TYPE_COPY_STRING);
+ if (arg_is_copy[i])
+ alloc_size += GetAllocLength(arg_values_[i].as_string);
+ }
+
+ if (alloc_size) {
+ parameter_copy_storage_.reset(new std::string);
+ parameter_copy_storage_->resize(alloc_size);
+ char* ptr = base::data(*parameter_copy_storage_);
+ const char* end = ptr + alloc_size;
+ if (copy) {
+ CopyTraceEventParameter(&ptr, &name_, end);
+ CopyTraceEventParameter(&ptr, &scope_, end);
+ for (i = 0; i < num_args; ++i) {
+ CopyTraceEventParameter(&ptr, &arg_names_[i], end);
+ }
+ }
+ for (i = 0; i < num_args; ++i) {
+ if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE)
+ continue;
+ if (arg_is_copy[i])
+ CopyTraceEventParameter(&ptr, &arg_values_[i].as_string, end);
+ }
+ DCHECK_EQ(end, ptr) << "Overrun by " << ptr - end;
+ }
+}
+
+void TraceEvent::Reset() {
+ // Only reset fields that won't be initialized in Initialize(), or that may
+ // hold references to other objects.
+ duration_ = TimeDelta::FromInternalValue(-1);
+ parameter_copy_storage_.reset();
+ for (int i = 0; i < kTraceMaxNumArgs; ++i)
+ convertable_values_[i].reset();
+}
+
+void TraceEvent::UpdateDuration(const TimeTicks& now,
+ const ThreadTicks& thread_now) {
+ DCHECK_EQ(duration_.ToInternalValue(), -1);
+ duration_ = now - timestamp_;
+
+ // |thread_timestamp_| can be empty if the thread ticks clock wasn't
+ // initialized when it was recorded.
+ if (thread_timestamp_ != ThreadTicks())
+ thread_duration_ = thread_now - thread_timestamp_;
+}
+
+void TraceEvent::EstimateTraceMemoryOverhead(
+ TraceEventMemoryOverhead* overhead) {
+ overhead->Add(TraceEventMemoryOverhead::kTraceEvent, sizeof(*this));
+
+ if (parameter_copy_storage_)
+ overhead->AddString(*parameter_copy_storage_);
+
+ for (size_t i = 0; i < kTraceMaxNumArgs; ++i) {
+ if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE)
+ convertable_values_[i]->EstimateTraceMemoryOverhead(overhead);
+ }
+}
+
+// static
+void TraceEvent::AppendValueAsJSON(unsigned char type,
+ TraceEvent::TraceValue value,
+ std::string* out) {
+ switch (type) {
+ case TRACE_VALUE_TYPE_BOOL:
+ *out += value.as_bool ? "true" : "false";
+ break;
+ case TRACE_VALUE_TYPE_UINT:
+ StringAppendF(out, "%" PRIu64, static_cast<uint64_t>(value.as_uint));
+ break;
+ case TRACE_VALUE_TYPE_INT:
+ StringAppendF(out, "%" PRId64, static_cast<int64_t>(value.as_int));
+ break;
+ case TRACE_VALUE_TYPE_DOUBLE: {
+ // FIXME: base/json/json_writer.cc is using the same code,
+ // should be made into a common method.
+ std::string real;
+ double val = value.as_double;
+ if (std::isfinite(val)) {
+ real = NumberToString(val);
+ // Ensure that the number has a .0 if there's no decimal or 'e'. This
+ // makes sure that when we read the JSON back, it's interpreted as a
+ // real rather than an int.
+ if (real.find('.') == std::string::npos &&
+ real.find('e') == std::string::npos &&
+ real.find('E') == std::string::npos) {
+ real.append(".0");
+ }
+ // The JSON spec requires that non-integer values in the range (-1,1)
+ // have a zero before the decimal point - ".52" is not valid, "0.52" is.
+ if (real[0] == '.') {
+ real.insert(0, "0");
+ } else if (real.length() > 1 && real[0] == '-' && real[1] == '.') {
+ // "-.1" bad "-0.1" good
+ real.insert(1, "0");
+ }
+ } else if (std::isnan(val)){
+ // The JSON spec doesn't allow NaN and Infinity (since these are
+ // objects in EcmaScript). Use strings instead.
+ real = "\"NaN\"";
+ } else if (val < 0) {
+ real = "\"-Infinity\"";
+ } else {
+ real = "\"Infinity\"";
+ }
+ StringAppendF(out, "%s", real.c_str());
+ break;
+ }
+ case TRACE_VALUE_TYPE_POINTER:
+ // JSON only supports double and int numbers.
+ // So as not to lose bits from a 64-bit pointer, output as a hex string.
+ StringAppendF(
+ out, "\"0x%" PRIx64 "\"",
+ static_cast<uint64_t>(reinterpret_cast<uintptr_t>(value.as_pointer)));
+ break;
+ case TRACE_VALUE_TYPE_STRING:
+ case TRACE_VALUE_TYPE_COPY_STRING:
+ EscapeJSONString(value.as_string ? value.as_string : "NULL", true, out);
+ break;
+ default:
+ NOTREACHED() << "Don't know how to print this value";
+ break;
+ }
+}
+
+void TraceEvent::AppendAsJSON(
+ std::string* out,
+ const ArgumentFilterPredicate& argument_filter_predicate) const {
+ int64_t time_int64 = timestamp_.ToInternalValue();
+ int process_id;
+ int thread_id;
+ if ((flags_ & TRACE_EVENT_FLAG_HAS_PROCESS_ID) &&
+ process_id_ != kNullProcessId) {
+ process_id = process_id_;
+ thread_id = -1;
+ } else {
+ process_id = TraceLog::GetInstance()->process_id();
+ thread_id = thread_id_;
+ }
+ const char* category_group_name =
+ TraceLog::GetCategoryGroupName(category_group_enabled_);
+
+ // Category group checked at category creation time.
+ DCHECK(!strchr(name_, '"'));
+ StringAppendF(out, "{\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64
+ ",\"ph\":\"%c\",\"cat\":\"%s\",\"name\":",
+ process_id, thread_id, time_int64, phase_, category_group_name);
+ EscapeJSONString(name_, true, out);
+ *out += ",\"args\":";
+
+ // Output argument names and values, stop at first NULL argument name.
+ // TODO(oysteine): The dual predicates here is a bit ugly; if the filtering
+ // capabilities need to grow even more precise we should rethink this
+ // approach
+ ArgumentNameFilterPredicate argument_name_filter_predicate;
+ bool strip_args =
+ arg_names_[0] && !argument_filter_predicate.is_null() &&
+ !argument_filter_predicate.Run(category_group_name, name_,
+ &argument_name_filter_predicate);
+
+ if (strip_args) {
+ *out += "\"__stripped__\"";
+ } else {
+ *out += "{";
+
+ for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) {
+ if (i > 0)
+ *out += ",";
+ *out += "\"";
+ *out += arg_names_[i];
+ *out += "\":";
+
+ if (argument_name_filter_predicate.is_null() ||
+ argument_name_filter_predicate.Run(arg_names_[i])) {
+ if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE)
+ convertable_values_[i]->AppendAsTraceFormat(out);
+ else
+ AppendValueAsJSON(arg_types_[i], arg_values_[i], out);
+ } else {
+ *out += "\"__stripped__\"";
+ }
+ }
+
+ *out += "}";
+ }
+
+ if (phase_ == TRACE_EVENT_PHASE_COMPLETE) {
+ int64_t duration = duration_.ToInternalValue();
+ if (duration != -1)
+ StringAppendF(out, ",\"dur\":%" PRId64, duration);
+ if (!thread_timestamp_.is_null()) {
+ int64_t thread_duration = thread_duration_.ToInternalValue();
+ if (thread_duration != -1)
+ StringAppendF(out, ",\"tdur\":%" PRId64, thread_duration);
+ }
+ }
+
+ // Output tts if thread_timestamp is valid.
+ if (!thread_timestamp_.is_null()) {
+ int64_t thread_time_int64 = thread_timestamp_.ToInternalValue();
+ StringAppendF(out, ",\"tts\":%" PRId64, thread_time_int64);
+ }
+
+ // Output async tts marker field if flag is set.
+ if (flags_ & TRACE_EVENT_FLAG_ASYNC_TTS) {
+ StringAppendF(out, ", \"use_async_tts\":1");
+ }
+
+ // If id_ is set, print it out as a hex string so we don't loose any
+ // bits (it might be a 64-bit pointer).
+ unsigned int id_flags_ = flags_ & (TRACE_EVENT_FLAG_HAS_ID |
+ TRACE_EVENT_FLAG_HAS_LOCAL_ID |
+ TRACE_EVENT_FLAG_HAS_GLOBAL_ID);
+ if (id_flags_) {
+ if (scope_ != trace_event_internal::kGlobalScope)
+ StringAppendF(out, ",\"scope\":\"%s\"", scope_);
+
+ switch (id_flags_) {
+ case TRACE_EVENT_FLAG_HAS_ID:
+ StringAppendF(out, ",\"id\":\"0x%" PRIx64 "\"",
+ static_cast<uint64_t>(id_));
+ break;
+
+ case TRACE_EVENT_FLAG_HAS_LOCAL_ID:
+ StringAppendF(out, ",\"id2\":{\"local\":\"0x%" PRIx64 "\"}",
+ static_cast<uint64_t>(id_));
+ break;
+
+ case TRACE_EVENT_FLAG_HAS_GLOBAL_ID:
+ StringAppendF(out, ",\"id2\":{\"global\":\"0x%" PRIx64 "\"}",
+ static_cast<uint64_t>(id_));
+ break;
+
+ default:
+ NOTREACHED() << "More than one of the ID flags are set";
+ break;
+ }
+ }
+
+ if (flags_ & TRACE_EVENT_FLAG_BIND_TO_ENCLOSING)
+ StringAppendF(out, ",\"bp\":\"e\"");
+
+ if ((flags_ & TRACE_EVENT_FLAG_FLOW_OUT) ||
+ (flags_ & TRACE_EVENT_FLAG_FLOW_IN)) {
+ StringAppendF(out, ",\"bind_id\":\"0x%" PRIx64 "\"",
+ static_cast<uint64_t>(bind_id_));
+ }
+ if (flags_ & TRACE_EVENT_FLAG_FLOW_IN)
+ StringAppendF(out, ",\"flow_in\":true");
+ if (flags_ & TRACE_EVENT_FLAG_FLOW_OUT)
+ StringAppendF(out, ",\"flow_out\":true");
+
+ // Instant events also output their scope.
+ if (phase_ == TRACE_EVENT_PHASE_INSTANT) {
+ char scope = '?';
+ switch (flags_ & TRACE_EVENT_FLAG_SCOPE_MASK) {
+ case TRACE_EVENT_SCOPE_GLOBAL:
+ scope = TRACE_EVENT_SCOPE_NAME_GLOBAL;
+ break;
+
+ case TRACE_EVENT_SCOPE_PROCESS:
+ scope = TRACE_EVENT_SCOPE_NAME_PROCESS;
+ break;
+
+ case TRACE_EVENT_SCOPE_THREAD:
+ scope = TRACE_EVENT_SCOPE_NAME_THREAD;
+ break;
+ }
+ StringAppendF(out, ",\"s\":\"%c\"", scope);
+ }
+
+ *out += "}";
+}
+
+void TraceEvent::AppendPrettyPrinted(std::ostringstream* out) const {
+ *out << name_ << "[";
+ *out << TraceLog::GetCategoryGroupName(category_group_enabled_);
+ *out << "]";
+ if (arg_names_[0]) {
+ *out << ", {";
+ for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) {
+ if (i > 0)
+ *out << ", ";
+ *out << arg_names_[i] << ":";
+ std::string value_as_text;
+
+ if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE)
+ convertable_values_[i]->AppendAsTraceFormat(&value_as_text);
+ else
+ AppendValueAsJSON(arg_types_[i], arg_values_[i], &value_as_text);
+
+ *out << value_as_text;
+ }
+ *out << "}";
+ }
+}
+
+} // namespace trace_event
+} // namespace base
+
+namespace trace_event_internal {
+
+std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
+TraceID::AsConvertableToTraceFormat() const {
+ auto value = std::make_unique<base::trace_event::TracedValue>();
+
+ if (scope_ != kGlobalScope)
+ value->SetString("scope", scope_);
+
+ const char* id_field_name = "id";
+ if (id_flags_ == TRACE_EVENT_FLAG_HAS_GLOBAL_ID) {
+ id_field_name = "global";
+ value->BeginDictionary("id2");
+ } else if (id_flags_ == TRACE_EVENT_FLAG_HAS_LOCAL_ID) {
+ id_field_name = "local";
+ value->BeginDictionary("id2");
+ } else if (id_flags_ != TRACE_EVENT_FLAG_HAS_ID) {
+ NOTREACHED() << "Unrecognized ID flag";
+ }
+
+ if (has_prefix_) {
+ value->SetString(id_field_name,
+ base::StringPrintf("0x%" PRIx64 "/0x%" PRIx64,
+ static_cast<uint64_t>(prefix_),
+ static_cast<uint64_t>(raw_id_)));
+ } else {
+ value->SetString(
+ id_field_name,
+ base::StringPrintf("0x%" PRIx64, static_cast<uint64_t>(raw_id_)));
+ }
+
+ if (id_flags_ != TRACE_EVENT_FLAG_HAS_ID)
+ value->EndDictionary();
+
+ return std::move(value);
+}
+
+} // namespace trace_event_internal
diff --git a/base/trace_event/trace_event_impl.h b/base/trace_event/trace_event_impl.h
new file mode 100644
index 0000000000..4b4b88f5bd
--- /dev/null
+++ b/base/trace_event/trace_event_impl.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+#ifndef BASE_TRACE_EVENT_TRACE_EVENT_IMPL_H_
+#define BASE_TRACE_EVENT_TRACE_EVENT_IMPL_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_local.h"
+#include "base/trace_event/trace_event_memory_overhead.h"
+#include "build/build_config.h"
+
+namespace base {
+namespace trace_event {
+
+typedef base::Callback<bool(const char* arg_name)> ArgumentNameFilterPredicate;
+
+typedef base::Callback<bool(const char* category_group_name,
+ const char* event_name,
+ ArgumentNameFilterPredicate*)>
+ ArgumentFilterPredicate;
+
+// For any argument of type TRACE_VALUE_TYPE_CONVERTABLE the provided
+// class must implement this interface.
+class BASE_EXPORT ConvertableToTraceFormat {
+ public:
+ ConvertableToTraceFormat() = default;
+ virtual ~ConvertableToTraceFormat() = default;
+
+ // Append the class info to the provided |out| string. The appended
+ // data must be a valid JSON object. Strings must be properly quoted, and
+ // escaped. There is no processing applied to the content after it is
+ // appended.
+ virtual void AppendAsTraceFormat(std::string* out) const = 0;
+
+ virtual void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead);
+
+ std::string ToString() const {
+ std::string result;
+ AppendAsTraceFormat(&result);
+ return result;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConvertableToTraceFormat);
+};
+
+const int kTraceMaxNumArgs = 2;
+
+struct TraceEventHandle {
+ uint32_t chunk_seq;
+ // These numbers of bits must be kept consistent with
+ // TraceBufferChunk::kMaxTrunkIndex and
+ // TraceBufferChunk::kTraceBufferChunkSize (in trace_buffer.h).
+ unsigned chunk_index : 26;
+ unsigned event_index : 6;
+};
+
+class BASE_EXPORT TraceEvent {
+ public:
+ union TraceValue {
+ bool as_bool;
+ unsigned long long as_uint;
+ long long as_int;
+ double as_double;
+ const void* as_pointer;
+ const char* as_string;
+ };
+
+ TraceEvent();
+ ~TraceEvent();
+
+ void MoveFrom(std::unique_ptr<TraceEvent> other);
+
+ void Initialize(int thread_id,
+ TimeTicks timestamp,
+ ThreadTicks thread_timestamp,
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ unsigned long long bind_id,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags);
+
+ void Reset();
+
+ void UpdateDuration(const TimeTicks& now, const ThreadTicks& thread_now);
+
+ void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead);
+
+ // Serialize event data to JSON
+ void AppendAsJSON(
+ std::string* out,
+ const ArgumentFilterPredicate& argument_filter_predicate) const;
+ void AppendPrettyPrinted(std::ostringstream* out) const;
+
+ static void AppendValueAsJSON(unsigned char type,
+ TraceValue value,
+ std::string* out);
+
+ TimeTicks timestamp() const { return timestamp_; }
+ ThreadTicks thread_timestamp() const { return thread_timestamp_; }
+ char phase() const { return phase_; }
+ int thread_id() const { return thread_id_; }
+ TimeDelta duration() const { return duration_; }
+ TimeDelta thread_duration() const { return thread_duration_; }
+ const char* scope() const { return scope_; }
+ unsigned long long id() const { return id_; }
+ unsigned int flags() const { return flags_; }
+ unsigned long long bind_id() const { return bind_id_; }
+ // Exposed for unittesting:
+
+ const std::string* parameter_copy_storage() const {
+ return parameter_copy_storage_.get();
+ }
+
+ const unsigned char* category_group_enabled() const {
+ return category_group_enabled_;
+ }
+
+ const char* name() const { return name_; }
+
+ unsigned char arg_type(size_t index) const { return arg_types_[index]; }
+ const char* arg_name(size_t index) const { return arg_names_[index]; }
+ const TraceValue& arg_value(size_t index) const { return arg_values_[index]; }
+
+ const ConvertableToTraceFormat* arg_convertible_value(size_t index) const {
+ return convertable_values_[index].get();
+ }
+
+#if defined(OS_ANDROID)
+ void SendToATrace();
+#endif
+
+ private:
+ // Note: these are ordered by size (largest first) for optimal packing.
+ TimeTicks timestamp_;
+ ThreadTicks thread_timestamp_;
+ TimeDelta duration_;
+ TimeDelta thread_duration_;
+ // scope_ and id_ can be used to store phase-specific data.
+ const char* scope_;
+ unsigned long long id_;
+ TraceValue arg_values_[kTraceMaxNumArgs];
+ const char* arg_names_[kTraceMaxNumArgs];
+ std::unique_ptr<ConvertableToTraceFormat>
+ convertable_values_[kTraceMaxNumArgs];
+ const unsigned char* category_group_enabled_;
+ const char* name_;
+ std::unique_ptr<std::string> parameter_copy_storage_;
+ // Depending on TRACE_EVENT_FLAG_HAS_PROCESS_ID the event will have either:
+ // tid: thread_id_, pid: current_process_id (default case).
+ // tid: -1, pid: process_id_ (when flags_ & TRACE_EVENT_FLAG_HAS_PROCESS_ID).
+ union {
+ int thread_id_;
+ int process_id_;
+ };
+ unsigned int flags_;
+ unsigned long long bind_id_;
+ unsigned char arg_types_[kTraceMaxNumArgs];
+ char phase_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceEvent);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_EVENT_IMPL_H_
diff --git a/base/trace_event/trace_event_memory_overhead.cc b/base/trace_event/trace_event_memory_overhead.cc
new file mode 100644
index 0000000000..dd7b30255e
--- /dev/null
+++ b/base/trace_event/trace_event_memory_overhead.cc
@@ -0,0 +1,179 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_memory_overhead.h"
+
+#include <algorithm>
+
+#include "base/bits.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/memory_allocator_dump.h"
+#include "base/trace_event/memory_usage_estimator.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "base/values.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+const char* ObjectTypeToString(TraceEventMemoryOverhead::ObjectType type) {
+ switch (type) {
+ case TraceEventMemoryOverhead::kOther:
+ return "(Other)";
+ case TraceEventMemoryOverhead::kTraceBuffer:
+ return "TraceBuffer";
+ case TraceEventMemoryOverhead::kTraceBufferChunk:
+ return "TraceBufferChunk";
+ case TraceEventMemoryOverhead::kTraceEvent:
+ return "TraceEvent";
+ case TraceEventMemoryOverhead::kUnusedTraceEvent:
+ return "TraceEvent(Unused)";
+ case TraceEventMemoryOverhead::kTracedValue:
+ return "TracedValue";
+ case TraceEventMemoryOverhead::kConvertableToTraceFormat:
+ return "ConvertableToTraceFormat";
+ case TraceEventMemoryOverhead::kHeapProfilerAllocationRegister:
+ return "AllocationRegister";
+ case TraceEventMemoryOverhead::kHeapProfilerTypeNameDeduplicator:
+ return "TypeNameDeduplicator";
+ case TraceEventMemoryOverhead::kHeapProfilerStackFrameDeduplicator:
+ return "StackFrameDeduplicator";
+ case TraceEventMemoryOverhead::kStdString:
+ return "std::string";
+ case TraceEventMemoryOverhead::kBaseValue:
+ return "base::Value";
+ case TraceEventMemoryOverhead::kTraceEventMemoryOverhead:
+ return "TraceEventMemoryOverhead";
+ case TraceEventMemoryOverhead::kFrameMetrics:
+ return "FrameMetrics";
+ case TraceEventMemoryOverhead::kLast:
+ NOTREACHED();
+ }
+ NOTREACHED();
+ return "BUG";
+}
+
+} // namespace
+
+TraceEventMemoryOverhead::TraceEventMemoryOverhead() : allocated_objects_() {}
+
+TraceEventMemoryOverhead::~TraceEventMemoryOverhead() = default;
+
+void TraceEventMemoryOverhead::AddInternal(ObjectType object_type,
+ size_t count,
+ size_t allocated_size_in_bytes,
+ size_t resident_size_in_bytes) {
+ ObjectCountAndSize& count_and_size =
+ allocated_objects_[static_cast<uint32_t>(object_type)];
+ count_and_size.count += count;
+ count_and_size.allocated_size_in_bytes += allocated_size_in_bytes;
+ count_and_size.resident_size_in_bytes += resident_size_in_bytes;
+}
+
+void TraceEventMemoryOverhead::Add(ObjectType object_type,
+ size_t allocated_size_in_bytes) {
+ Add(object_type, allocated_size_in_bytes, allocated_size_in_bytes);
+}
+
+void TraceEventMemoryOverhead::Add(ObjectType object_type,
+ size_t allocated_size_in_bytes,
+ size_t resident_size_in_bytes) {
+ AddInternal(object_type, 1, allocated_size_in_bytes, resident_size_in_bytes);
+}
+
+void TraceEventMemoryOverhead::AddString(const std::string& str) {
+ Add(kStdString, EstimateMemoryUsage(str));
+}
+
+void TraceEventMemoryOverhead::AddRefCountedString(
+ const RefCountedString& str) {
+ Add(kOther, sizeof(RefCountedString));
+ AddString(str.data());
+}
+
+void TraceEventMemoryOverhead::AddValue(const Value& value) {
+ switch (value.type()) {
+ case Value::Type::NONE:
+ case Value::Type::BOOLEAN:
+ case Value::Type::INTEGER:
+ case Value::Type::DOUBLE:
+ Add(kBaseValue, sizeof(Value));
+ break;
+
+ case Value::Type::STRING: {
+ const Value* string_value = nullptr;
+ value.GetAsString(&string_value);
+ Add(kBaseValue, sizeof(Value));
+ AddString(string_value->GetString());
+ } break;
+
+ case Value::Type::BINARY: {
+ Add(kBaseValue, sizeof(Value) + value.GetBlob().size());
+ } break;
+
+ case Value::Type::DICTIONARY: {
+ const DictionaryValue* dictionary_value = nullptr;
+ value.GetAsDictionary(&dictionary_value);
+ Add(kBaseValue, sizeof(DictionaryValue));
+ for (DictionaryValue::Iterator it(*dictionary_value); !it.IsAtEnd();
+ it.Advance()) {
+ AddString(it.key());
+ AddValue(it.value());
+ }
+ } break;
+
+ case Value::Type::LIST: {
+ const ListValue* list_value = nullptr;
+ value.GetAsList(&list_value);
+ Add(kBaseValue, sizeof(ListValue));
+ for (const auto& v : *list_value)
+ AddValue(v);
+ } break;
+
+ default:
+ NOTREACHED();
+ }
+}
+
+void TraceEventMemoryOverhead::AddSelf() {
+ Add(kTraceEventMemoryOverhead, sizeof(*this));
+}
+
+size_t TraceEventMemoryOverhead::GetCount(ObjectType object_type) const {
+ CHECK(object_type < kLast);
+ return allocated_objects_[static_cast<uint32_t>(object_type)].count;
+}
+
+void TraceEventMemoryOverhead::Update(const TraceEventMemoryOverhead& other) {
+ for (uint32_t i = 0; i < kLast; i++) {
+ const ObjectCountAndSize& other_entry = other.allocated_objects_[i];
+ AddInternal(static_cast<ObjectType>(i), other_entry.count,
+ other_entry.allocated_size_in_bytes,
+ other_entry.resident_size_in_bytes);
+ }
+}
+
+void TraceEventMemoryOverhead::DumpInto(const char* base_name,
+ ProcessMemoryDump* pmd) const {
+ for (uint32_t i = 0; i < kLast; i++) {
+ const ObjectCountAndSize& count_and_size = allocated_objects_[i];
+ if (count_and_size.allocated_size_in_bytes == 0)
+ continue;
+ std::string dump_name = StringPrintf(
+ "%s/%s", base_name, ObjectTypeToString(static_cast<ObjectType>(i)));
+ MemoryAllocatorDump* mad = pmd->CreateAllocatorDump(dump_name);
+ mad->AddScalar(MemoryAllocatorDump::kNameSize,
+ MemoryAllocatorDump::kUnitsBytes,
+ count_and_size.allocated_size_in_bytes);
+ mad->AddScalar("resident_size", MemoryAllocatorDump::kUnitsBytes,
+ count_and_size.resident_size_in_bytes);
+ mad->AddScalar(MemoryAllocatorDump::kNameObjectCount,
+ MemoryAllocatorDump::kUnitsObjects, count_and_size.count);
+ }
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_memory_overhead.h b/base/trace_event/trace_event_memory_overhead.h
new file mode 100644
index 0000000000..69468d4640
--- /dev/null
+++ b/base/trace_event/trace_event_memory_overhead.h
@@ -0,0 +1,95 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_
+#define BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <unordered_map>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+
+namespace base {
+
+class RefCountedString;
+class Value;
+
+namespace trace_event {
+
+class ProcessMemoryDump;
+
+// Used to estimate the memory overhead of the tracing infrastructure.
+class BASE_EXPORT TraceEventMemoryOverhead {
+ public:
+ enum ObjectType : uint32_t {
+ kOther = 0,
+ kTraceBuffer,
+ kTraceBufferChunk,
+ kTraceEvent,
+ kUnusedTraceEvent,
+ kTracedValue,
+ kConvertableToTraceFormat,
+ kHeapProfilerAllocationRegister,
+ kHeapProfilerTypeNameDeduplicator,
+ kHeapProfilerStackFrameDeduplicator,
+ kStdString,
+ kBaseValue,
+ kTraceEventMemoryOverhead,
+ kFrameMetrics,
+ kLast
+ };
+
+ TraceEventMemoryOverhead();
+ ~TraceEventMemoryOverhead();
+
+ // Use this method to account the overhead of an object for which an estimate
+ // is known for both the allocated and resident memory.
+ void Add(ObjectType object_type,
+ size_t allocated_size_in_bytes,
+ size_t resident_size_in_bytes);
+
+ // Similar to Add() above, but assumes that
+ // |resident_size_in_bytes| == |allocated_size_in_bytes|.
+ void Add(ObjectType object_type, size_t allocated_size_in_bytes);
+
+ // Specialized profiling functions for commonly used object types.
+ void AddString(const std::string& str);
+ void AddValue(const Value& value);
+ void AddRefCountedString(const RefCountedString& str);
+
+ // Call this after all the Add* methods above to account the memory used by
+ // this TraceEventMemoryOverhead instance itself.
+ void AddSelf();
+
+ // Retrieves the count, that is, the count of Add*(|object_type|, ...) calls.
+ size_t GetCount(ObjectType object_type) const;
+
+ // Adds up and merges all the values from |other| to this instance.
+ void Update(const TraceEventMemoryOverhead& other);
+
+ void DumpInto(const char* base_name, ProcessMemoryDump* pmd) const;
+
+ private:
+ struct ObjectCountAndSize {
+ size_t count;
+ size_t allocated_size_in_bytes;
+ size_t resident_size_in_bytes;
+ };
+ ObjectCountAndSize allocated_objects_[ObjectType::kLast];
+
+ void AddInternal(ObjectType object_type,
+ size_t count,
+ size_t allocated_size_in_bytes,
+ size_t resident_size_in_bytes);
+
+ DISALLOW_COPY_AND_ASSIGN(TraceEventMemoryOverhead);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_EVENT_MEMORY_OVERHEAD_H_
diff --git a/base/trace_event/trace_event_system_stats_monitor.cc b/base/trace_event/trace_event_system_stats_monitor.cc
new file mode 100644
index 0000000000..7e082f348e
--- /dev/null
+++ b/base/trace_event/trace_event_system_stats_monitor.cc
@@ -0,0 +1,132 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_system_stats_monitor.h"
+
+#include <memory>
+
+#include "base/debug/leak_annotations.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_local_storage.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+/////////////////////////////////////////////////////////////////////////////
+// Holds profiled system stats until the tracing system needs to serialize it.
+class SystemStatsHolder : public base::trace_event::ConvertableToTraceFormat {
+ public:
+ SystemStatsHolder() = default;
+ ~SystemStatsHolder() override = default;
+
+ // Fills system_metrics_ with profiled system memory and disk stats.
+ // Uses the previous stats to compute rates if this is not the first profile.
+ void GetSystemProfilingStats();
+
+ // base::trace_event::ConvertableToTraceFormat overrides:
+ void AppendAsTraceFormat(std::string* out) const override {
+ AppendSystemProfileAsTraceFormat(system_stats_, out);
+ }
+
+ private:
+ SystemMetrics system_stats_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemStatsHolder);
+};
+
+void SystemStatsHolder::GetSystemProfilingStats() {
+ system_stats_ = SystemMetrics::Sample();
+}
+
+} // namespace
+
+//////////////////////////////////////////////////////////////////////////////
+
+TraceEventSystemStatsMonitor::TraceEventSystemStatsMonitor(
+ scoped_refptr<SingleThreadTaskRunner> task_runner)
+ : task_runner_(task_runner),
+ weak_factory_(this) {
+ // Force the "system_stats" category to show up in the trace viewer.
+ TraceLog::GetCategoryGroupEnabled(TRACE_DISABLED_BY_DEFAULT("system_stats"));
+
+ // Allow this to be instantiated on unsupported platforms, but don't run.
+ TraceLog::GetInstance()->AddEnabledStateObserver(this);
+}
+
+TraceEventSystemStatsMonitor::~TraceEventSystemStatsMonitor() {
+ if (dump_timer_.IsRunning())
+ StopProfiling();
+ TraceLog::GetInstance()->RemoveEnabledStateObserver(this);
+}
+
+void TraceEventSystemStatsMonitor::OnTraceLogEnabled() {
+ // Check to see if system tracing is enabled.
+ bool enabled;
+
+ TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT(
+ "system_stats"), &enabled);
+ if (!enabled)
+ return;
+ task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&TraceEventSystemStatsMonitor::StartProfiling,
+ weak_factory_.GetWeakPtr()));
+}
+
+void TraceEventSystemStatsMonitor::OnTraceLogDisabled() {
+ task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&TraceEventSystemStatsMonitor::StopProfiling,
+ weak_factory_.GetWeakPtr()));
+}
+
+void TraceEventSystemStatsMonitor::StartProfiling() {
+ // Watch for the tracing framework sending enabling more than once.
+ if (dump_timer_.IsRunning())
+ return;
+
+ dump_timer_.Start(FROM_HERE,
+ TimeDelta::FromMilliseconds(TraceEventSystemStatsMonitor::
+ kSamplingIntervalMilliseconds),
+ base::Bind(&TraceEventSystemStatsMonitor::
+ DumpSystemStats,
+ weak_factory_.GetWeakPtr()));
+}
+
+// If system tracing is enabled, dumps a profile to the tracing system.
+void TraceEventSystemStatsMonitor::DumpSystemStats() {
+ std::unique_ptr<SystemStatsHolder> dump_holder(new SystemStatsHolder());
+ dump_holder->GetSystemProfilingStats();
+
+ TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("system_stats"),
+ "base::TraceEventSystemStatsMonitor::SystemStats", this,
+ std::move(dump_holder));
+}
+
+void TraceEventSystemStatsMonitor::StopProfiling() {
+ dump_timer_.Stop();
+}
+
+bool TraceEventSystemStatsMonitor::IsTimerRunningForTest() const {
+ return dump_timer_.IsRunning();
+}
+
+void AppendSystemProfileAsTraceFormat(const SystemMetrics& system_metrics,
+ std::string* output) {
+ std::string tmp;
+ base::JSONWriter::Write(*system_metrics.ToValue(), &tmp);
+ *output += tmp;
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_system_stats_monitor.h b/base/trace_event/trace_event_system_stats_monitor.h
new file mode 100644
index 0000000000..14aa5681fe
--- /dev/null
+++ b/base/trace_event/trace_event_system_stats_monitor.h
@@ -0,0 +1,76 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_EVENT_SYSTEM_STATS_MONITOR_H_
+#define BASE_TRACE_EVENT_TRACE_EVENT_SYSTEM_STATS_MONITOR_H_
+
+#include "base/base_export.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/process/process_metrics.h"
+#include "base/timer/timer.h"
+#include "base/trace_event/trace_log.h"
+
+namespace base {
+
+class SingleThreadTaskRunner;
+
+namespace trace_event {
+
+// Watches for chrome://tracing to be enabled or disabled. When tracing is
+// enabled, also enables system events profiling. This class is the preferred
+// way to turn system tracing on and off.
+class BASE_EXPORT TraceEventSystemStatsMonitor
+ : public TraceLog::EnabledStateObserver {
+ public:
+ // Length of time interval between stat profiles.
+ static const int kSamplingIntervalMilliseconds = 2000;
+
+ // |task_runner| must be the primary thread for the client
+ // process, e.g. the UI thread in a browser.
+ explicit TraceEventSystemStatsMonitor(
+ scoped_refptr<SingleThreadTaskRunner> task_runner);
+
+ ~TraceEventSystemStatsMonitor() override;
+
+ // base::trace_event::TraceLog::EnabledStateChangedObserver overrides:
+ void OnTraceLogEnabled() override;
+ void OnTraceLogDisabled() override;
+
+ // Retrieves system profiling at the current time.
+ void DumpSystemStats();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(TraceSystemStatsMonitorTest,
+ TraceEventSystemStatsMonitor);
+
+ bool IsTimerRunningForTest() const;
+
+ void StartProfiling();
+
+ void StopProfiling();
+
+ // Ensures the observer starts and stops tracing on the primary thread.
+ scoped_refptr<SingleThreadTaskRunner> task_runner_;
+
+ // Timer to schedule system profile dumps.
+ RepeatingTimer dump_timer_;
+
+ WeakPtrFactory<TraceEventSystemStatsMonitor> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceEventSystemStatsMonitor);
+};
+
+// Converts system memory profiling stats in |input| to
+// trace event compatible JSON and appends to |output|. Visible for testing.
+BASE_EXPORT void AppendSystemProfileAsTraceFormat(const SystemMetrics&
+ system_stats,
+ std::string* output);
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_EVENT_SYSTEM_STATS_MONITOR_H_
diff --git a/base/trace_event/trace_event_system_stats_monitor_unittest.cc b/base/trace_event/trace_event_system_stats_monitor_unittest.cc
new file mode 100644
index 0000000000..52a05ba9cd
--- /dev/null
+++ b/base/trace_event/trace_event_system_stats_monitor_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event_system_stats_monitor.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/trace_event/trace_event_impl.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+#if !defined(OS_IOS)
+// Tests for the system stats monitor.
+// Exists as a class so it can be a friend of TraceEventSystemStatsMonitor.
+class TraceSystemStatsMonitorTest : public testing::Test {
+ public:
+ TraceSystemStatsMonitorTest() = default;
+ ~TraceSystemStatsMonitorTest() override = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TraceSystemStatsMonitorTest);
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TraceSystemStatsMonitorTest, TraceEventSystemStatsMonitor) {
+ MessageLoop message_loop;
+
+ // Start with no observers of the TraceLog.
+ EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest());
+
+ // Creating a system stats monitor adds it to the TraceLog observer list.
+ std::unique_ptr<TraceEventSystemStatsMonitor> system_stats_monitor(
+ new TraceEventSystemStatsMonitor(message_loop.task_runner()));
+ EXPECT_EQ(1u, TraceLog::GetInstance()->GetObserverCountForTest());
+ EXPECT_TRUE(
+ TraceLog::GetInstance()->HasEnabledStateObserver(
+ system_stats_monitor.get()));
+
+ // By default the observer isn't dumping memory profiles.
+ EXPECT_FALSE(system_stats_monitor->IsTimerRunningForTest());
+
+ // Simulate enabling tracing.
+ system_stats_monitor->StartProfiling();
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(system_stats_monitor->IsTimerRunningForTest());
+
+ // Simulate disabling tracing.
+ system_stats_monitor->StopProfiling();
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(system_stats_monitor->IsTimerRunningForTest());
+
+ // Deleting the observer removes it from the TraceLog observer list.
+ system_stats_monitor.reset();
+ EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest());
+}
+#endif // !defined(OS_IOS)
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_event_unittest.cc b/base/trace_event/trace_event_unittest.cc
new file mode 100644
index 0000000000..a413ee5f0e
--- /dev/null
+++ b/base/trace_event/trace_event_unittest.cc
@@ -0,0 +1,3169 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_event.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cstdlib>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/singleton.h"
+#include "base/process/process_handle.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/pattern.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "base/trace_event/event_name_filter.h"
+#include "base/trace_event/heap_profiler_event_filter.h"
+#include "base/trace_event/trace_buffer.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_filter.h"
+#include "base/trace_event/trace_event_filter_test_utils.h"
+#include "base/values.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+enum CompareOp {
+ IS_EQUAL,
+ IS_NOT_EQUAL,
+};
+
+struct JsonKeyValue {
+ const char* key;
+ const char* value;
+ CompareOp op;
+};
+
+const int kThreadId = 42;
+const int kAsyncId = 5;
+const char kAsyncIdStr[] = "0x5";
+const int kAsyncId2 = 6;
+const char kAsyncId2Str[] = "0x6";
+const int kFlowId = 7;
+const char kFlowIdStr[] = "0x7";
+
+const char kRecordAllCategoryFilter[] = "*";
+
+class TraceEventTestFixture : public testing::Test {
+ public:
+ void OnTraceDataCollected(
+ WaitableEvent* flush_complete_event,
+ const scoped_refptr<base::RefCountedString>& events_str,
+ bool has_more_events);
+ DictionaryValue* FindMatchingTraceEntry(const JsonKeyValue* key_values);
+ DictionaryValue* FindNamePhase(const char* name, const char* phase);
+ DictionaryValue* FindNamePhaseKeyValue(const char* name,
+ const char* phase,
+ const char* key,
+ const char* value);
+ void DropTracedMetadataRecords();
+ bool FindMatchingValue(const char* key,
+ const char* value);
+ bool FindNonMatchingValue(const char* key,
+ const char* value);
+ void Clear() {
+ trace_parsed_.Clear();
+ json_output_.json_output.clear();
+ }
+
+ void BeginTrace() {
+ BeginSpecificTrace("*");
+ }
+
+ void BeginSpecificTrace(const std::string& filter) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(filter, ""),
+ TraceLog::RECORDING_MODE);
+ }
+
+ void CancelTrace() {
+ WaitableEvent flush_complete_event(
+ WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ CancelTraceAsync(&flush_complete_event);
+ flush_complete_event.Wait();
+ }
+
+ void EndTraceAndFlush() {
+ num_flush_callbacks_ = 0;
+ WaitableEvent flush_complete_event(
+ WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ EndTraceAndFlushAsync(&flush_complete_event);
+ flush_complete_event.Wait();
+ }
+
+ // Used when testing thread-local buffers which requires the thread initiating
+ // flush to have a message loop.
+ void EndTraceAndFlushInThreadWithMessageLoop() {
+ WaitableEvent flush_complete_event(
+ WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ Thread flush_thread("flush");
+ flush_thread.Start();
+ flush_thread.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&TraceEventTestFixture::EndTraceAndFlushAsync,
+ base::Unretained(this), &flush_complete_event));
+ flush_complete_event.Wait();
+ }
+
+ void CancelTraceAsync(WaitableEvent* flush_complete_event) {
+ TraceLog::GetInstance()->CancelTracing(
+ base::Bind(&TraceEventTestFixture::OnTraceDataCollected,
+ base::Unretained(static_cast<TraceEventTestFixture*>(this)),
+ base::Unretained(flush_complete_event)));
+ }
+
+ void EndTraceAndFlushAsync(WaitableEvent* flush_complete_event) {
+ TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE |
+ TraceLog::FILTERING_MODE);
+ TraceLog::GetInstance()->Flush(
+ base::Bind(&TraceEventTestFixture::OnTraceDataCollected,
+ base::Unretained(static_cast<TraceEventTestFixture*>(this)),
+ base::Unretained(flush_complete_event)));
+ }
+
+ void SetUp() override {
+ const char* name = PlatformThread::GetName();
+ old_thread_name_ = name ? strdup(name) : nullptr;
+
+ TraceLog::ResetForTesting();
+ TraceLog* tracelog = TraceLog::GetInstance();
+ ASSERT_TRUE(tracelog);
+ ASSERT_FALSE(tracelog->IsEnabled());
+ trace_buffer_.SetOutputCallback(json_output_.GetCallback());
+ num_flush_callbacks_ = 0;
+ }
+ void TearDown() override {
+ if (TraceLog::GetInstance())
+ EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled());
+ PlatformThread::SetName(old_thread_name_ ? old_thread_name_ : "");
+ free(old_thread_name_);
+ old_thread_name_ = nullptr;
+ // We want our singleton torn down after each test.
+ TraceLog::ResetForTesting();
+ }
+
+ char* old_thread_name_;
+ ListValue trace_parsed_;
+ TraceResultBuffer trace_buffer_;
+ TraceResultBuffer::SimpleOutput json_output_;
+ size_t num_flush_callbacks_;
+
+ private:
+ // We want our singleton torn down after each test.
+ ShadowingAtExitManager at_exit_manager_;
+ Lock lock_;
+};
+
+void TraceEventTestFixture::OnTraceDataCollected(
+ WaitableEvent* flush_complete_event,
+ const scoped_refptr<base::RefCountedString>& events_str,
+ bool has_more_events) {
+ num_flush_callbacks_++;
+ if (num_flush_callbacks_ > 1) {
+ EXPECT_FALSE(events_str->data().empty());
+ }
+ AutoLock lock(lock_);
+ json_output_.json_output.clear();
+ trace_buffer_.Start();
+ trace_buffer_.AddFragment(events_str->data());
+ trace_buffer_.Finish();
+
+ std::unique_ptr<Value> root =
+ base::JSONReader::Read(json_output_.json_output, JSON_PARSE_RFC);
+
+ if (!root.get()) {
+ LOG(ERROR) << json_output_.json_output;
+ }
+
+ ListValue* root_list = nullptr;
+ ASSERT_TRUE(root.get());
+ ASSERT_TRUE(root->GetAsList(&root_list));
+
+ // Move items into our aggregate collection
+ while (root_list->GetSize()) {
+ std::unique_ptr<Value> item;
+ root_list->Remove(0, &item);
+ trace_parsed_.Append(std::move(item));
+ }
+
+ if (!has_more_events)
+ flush_complete_event->Signal();
+}
+
+static bool CompareJsonValues(const std::string& lhs,
+ const std::string& rhs,
+ CompareOp op) {
+ switch (op) {
+ case IS_EQUAL:
+ return lhs == rhs;
+ case IS_NOT_EQUAL:
+ return lhs != rhs;
+ default:
+ CHECK(0);
+ }
+ return false;
+}
+
+static bool IsKeyValueInDict(const JsonKeyValue* key_value,
+ DictionaryValue* dict) {
+ Value* value = nullptr;
+ std::string value_str;
+ if (dict->Get(key_value->key, &value) &&
+ value->GetAsString(&value_str) &&
+ CompareJsonValues(value_str, key_value->value, key_value->op))
+ return true;
+
+ // Recurse to test arguments
+ DictionaryValue* args_dict = nullptr;
+ dict->GetDictionary("args", &args_dict);
+ if (args_dict)
+ return IsKeyValueInDict(key_value, args_dict);
+
+ return false;
+}
+
+static bool IsAllKeyValueInDict(const JsonKeyValue* key_values,
+ DictionaryValue* dict) {
+ // Scan all key_values, they must all be present and equal.
+ while (key_values && key_values->key) {
+ if (!IsKeyValueInDict(key_values, dict))
+ return false;
+ ++key_values;
+ }
+ return true;
+}
+
+DictionaryValue* TraceEventTestFixture::FindMatchingTraceEntry(
+ const JsonKeyValue* key_values) {
+ // Scan all items
+ size_t trace_parsed_count = trace_parsed_.GetSize();
+ for (size_t i = 0; i < trace_parsed_count; i++) {
+ Value* value = nullptr;
+ trace_parsed_.Get(i, &value);
+ if (!value || value->type() != Value::Type::DICTIONARY)
+ continue;
+ DictionaryValue* dict = static_cast<DictionaryValue*>(value);
+
+ if (IsAllKeyValueInDict(key_values, dict))
+ return dict;
+ }
+ return nullptr;
+}
+
+void TraceEventTestFixture::DropTracedMetadataRecords() {
+ std::unique_ptr<ListValue> old_trace_parsed(trace_parsed_.CreateDeepCopy());
+ size_t old_trace_parsed_size = old_trace_parsed->GetSize();
+ trace_parsed_.Clear();
+
+ for (size_t i = 0; i < old_trace_parsed_size; i++) {
+ Value* value = nullptr;
+ old_trace_parsed->Get(i, &value);
+ if (!value || value->type() != Value::Type::DICTIONARY) {
+ trace_parsed_.Append(value->CreateDeepCopy());
+ continue;
+ }
+ DictionaryValue* dict = static_cast<DictionaryValue*>(value);
+ std::string tmp;
+ if (dict->GetString("ph", &tmp) && tmp == "M")
+ continue;
+
+ trace_parsed_.Append(value->CreateDeepCopy());
+ }
+}
+
+DictionaryValue* TraceEventTestFixture::FindNamePhase(const char* name,
+ const char* phase) {
+ JsonKeyValue key_values[] = {{"name", name, IS_EQUAL},
+ {"ph", phase, IS_EQUAL},
+ {nullptr, nullptr, IS_EQUAL}};
+ return FindMatchingTraceEntry(key_values);
+}
+
+DictionaryValue* TraceEventTestFixture::FindNamePhaseKeyValue(
+ const char* name,
+ const char* phase,
+ const char* key,
+ const char* value) {
+ JsonKeyValue key_values[] = {{"name", name, IS_EQUAL},
+ {"ph", phase, IS_EQUAL},
+ {key, value, IS_EQUAL},
+ {nullptr, nullptr, IS_EQUAL}};
+ return FindMatchingTraceEntry(key_values);
+}
+
+bool TraceEventTestFixture::FindMatchingValue(const char* key,
+ const char* value) {
+ JsonKeyValue key_values[] = {{key, value, IS_EQUAL},
+ {nullptr, nullptr, IS_EQUAL}};
+ return FindMatchingTraceEntry(key_values);
+}
+
+bool TraceEventTestFixture::FindNonMatchingValue(const char* key,
+ const char* value) {
+ JsonKeyValue key_values[] = {{key, value, IS_NOT_EQUAL},
+ {nullptr, nullptr, IS_EQUAL}};
+ return FindMatchingTraceEntry(key_values);
+}
+
+bool IsStringInDict(const char* string_to_match, const DictionaryValue* dict) {
+ for (DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
+ if (it.key().find(string_to_match) != std::string::npos)
+ return true;
+
+ std::string value_str;
+ it.value().GetAsString(&value_str);
+ if (value_str.find(string_to_match) != std::string::npos)
+ return true;
+ }
+
+ // Recurse to test arguments
+ const DictionaryValue* args_dict = nullptr;
+ dict->GetDictionary("args", &args_dict);
+ if (args_dict)
+ return IsStringInDict(string_to_match, args_dict);
+
+ return false;
+}
+
+const DictionaryValue* FindTraceEntry(
+ const ListValue& trace_parsed,
+ const char* string_to_match,
+ const DictionaryValue* match_after_this_item = nullptr) {
+ // Scan all items
+ size_t trace_parsed_count = trace_parsed.GetSize();
+ for (size_t i = 0; i < trace_parsed_count; i++) {
+ const Value* value = nullptr;
+ trace_parsed.Get(i, &value);
+ if (match_after_this_item) {
+ if (value == match_after_this_item)
+ match_after_this_item = nullptr;
+ continue;
+ }
+ if (!value || value->type() != Value::Type::DICTIONARY)
+ continue;
+ const DictionaryValue* dict = static_cast<const DictionaryValue*>(value);
+
+ if (IsStringInDict(string_to_match, dict))
+ return dict;
+ }
+ return nullptr;
+}
+
+std::vector<const DictionaryValue*> FindTraceEntries(
+ const ListValue& trace_parsed,
+ const char* string_to_match) {
+ std::vector<const DictionaryValue*> hits;
+ size_t trace_parsed_count = trace_parsed.GetSize();
+ for (size_t i = 0; i < trace_parsed_count; i++) {
+ const Value* value = nullptr;
+ trace_parsed.Get(i, &value);
+ if (!value || value->type() != Value::Type::DICTIONARY)
+ continue;
+ const DictionaryValue* dict = static_cast<const DictionaryValue*>(value);
+
+ if (IsStringInDict(string_to_match, dict))
+ hits.push_back(dict);
+ }
+ return hits;
+}
+
+const char kControlCharacters[] = "\001\002\003\n\r";
+
+void TraceWithAllMacroVariants(WaitableEvent* task_complete_event) {
+ {
+ TRACE_EVENT0("all", "TRACE_EVENT0 call");
+ TRACE_EVENT1("all", "TRACE_EVENT1 call", "name1", "value1");
+ TRACE_EVENT2("all", "TRACE_EVENT2 call",
+ "name1", "\"value1\"",
+ "name2", "value\\2");
+
+ TRACE_EVENT_INSTANT0("all", "TRACE_EVENT_INSTANT0 call",
+ TRACE_EVENT_SCOPE_GLOBAL);
+ TRACE_EVENT_INSTANT1("all", "TRACE_EVENT_INSTANT1 call",
+ TRACE_EVENT_SCOPE_PROCESS, "name1", "value1");
+ TRACE_EVENT_INSTANT2("all", "TRACE_EVENT_INSTANT2 call",
+ TRACE_EVENT_SCOPE_THREAD,
+ "name1", "value1",
+ "name2", "value2");
+
+ TRACE_EVENT_BEGIN0("all", "TRACE_EVENT_BEGIN0 call");
+ TRACE_EVENT_BEGIN1("all", "TRACE_EVENT_BEGIN1 call", "name1", "value1");
+ TRACE_EVENT_BEGIN2("all", "TRACE_EVENT_BEGIN2 call",
+ "name1", "value1",
+ "name2", "value2");
+
+ TRACE_EVENT_END0("all", "TRACE_EVENT_END0 call");
+ TRACE_EVENT_END1("all", "TRACE_EVENT_END1 call", "name1", "value1");
+ TRACE_EVENT_END2("all", "TRACE_EVENT_END2 call",
+ "name1", "value1",
+ "name2", "value2");
+
+ TRACE_EVENT_ASYNC_BEGIN0("all", "TRACE_EVENT_ASYNC_BEGIN0 call", kAsyncId);
+ TRACE_EVENT_ASYNC_BEGIN1("all", "TRACE_EVENT_ASYNC_BEGIN1 call", kAsyncId,
+ "name1", "value1");
+ TRACE_EVENT_ASYNC_BEGIN2("all", "TRACE_EVENT_ASYNC_BEGIN2 call", kAsyncId,
+ "name1", "value1",
+ "name2", "value2");
+
+ TRACE_EVENT_ASYNC_STEP_INTO0("all", "TRACE_EVENT_ASYNC_STEP_INTO0 call",
+ kAsyncId, "step_begin1");
+ TRACE_EVENT_ASYNC_STEP_INTO1("all", "TRACE_EVENT_ASYNC_STEP_INTO1 call",
+ kAsyncId, "step_begin2", "name1", "value1");
+
+ TRACE_EVENT_ASYNC_END0("all", "TRACE_EVENT_ASYNC_END0 call", kAsyncId);
+ TRACE_EVENT_ASYNC_END1("all", "TRACE_EVENT_ASYNC_END1 call", kAsyncId,
+ "name1", "value1");
+ TRACE_EVENT_ASYNC_END2("all", "TRACE_EVENT_ASYNC_END2 call", kAsyncId,
+ "name1", "value1",
+ "name2", "value2");
+
+ TRACE_EVENT_FLOW_BEGIN0("all", "TRACE_EVENT_FLOW_BEGIN0 call", kFlowId);
+ TRACE_EVENT_FLOW_STEP0("all", "TRACE_EVENT_FLOW_STEP0 call",
+ kFlowId, "step1");
+ TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0("all",
+ "TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0 call", kFlowId);
+
+ TRACE_COUNTER1("all", "TRACE_COUNTER1 call", 31415);
+ TRACE_COUNTER2("all", "TRACE_COUNTER2 call",
+ "a", 30000,
+ "b", 1415);
+
+ TRACE_COUNTER_WITH_TIMESTAMP1("all", "TRACE_COUNTER_WITH_TIMESTAMP1 call",
+ TimeTicks::FromInternalValue(42), 31415);
+ TRACE_COUNTER_WITH_TIMESTAMP2("all", "TRACE_COUNTER_WITH_TIMESTAMP2 call",
+ TimeTicks::FromInternalValue(42),
+ "a", 30000, "b", 1415);
+
+ TRACE_COUNTER_ID1("all", "TRACE_COUNTER_ID1 call", 0x319009, 31415);
+ TRACE_COUNTER_ID2("all", "TRACE_COUNTER_ID2 call", 0x319009,
+ "a", 30000, "b", 1415);
+
+ TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("all",
+ "TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call",
+ kAsyncId, kThreadId, TimeTicks::FromInternalValue(12345));
+ TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0("all",
+ "TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0 call",
+ kAsyncId, kThreadId, TimeTicks::FromInternalValue(23456));
+
+ TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("all",
+ "TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call",
+ kAsyncId2, kThreadId, TimeTicks::FromInternalValue(34567));
+ TRACE_EVENT_ASYNC_STEP_PAST0("all", "TRACE_EVENT_ASYNC_STEP_PAST0 call",
+ kAsyncId2, "step_end1");
+ TRACE_EVENT_ASYNC_STEP_PAST1("all", "TRACE_EVENT_ASYNC_STEP_PAST1 call",
+ kAsyncId2, "step_end2", "name1", "value1");
+
+ TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0("all",
+ "TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0 call",
+ kAsyncId2, kThreadId, TimeTicks::FromInternalValue(45678));
+
+ TRACE_EVENT_OBJECT_CREATED_WITH_ID("all", "tracked object 1", 0x42);
+ TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+ "all", "tracked object 1", 0x42, "hello");
+ TRACE_EVENT_OBJECT_DELETED_WITH_ID("all", "tracked object 1", 0x42);
+
+ TraceScopedTrackableObject<int> trackable("all", "tracked object 2",
+ 0x2128506);
+ trackable.snapshot("world");
+
+ TRACE_EVENT_OBJECT_CREATED_WITH_ID(
+ "all", "tracked object 3", TRACE_ID_WITH_SCOPE("scope", 0x42));
+ TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+ "all", "tracked object 3", TRACE_ID_WITH_SCOPE("scope", 0x42), "hello");
+ TRACE_EVENT_OBJECT_DELETED_WITH_ID(
+ "all", "tracked object 3", TRACE_ID_WITH_SCOPE("scope", 0x42));
+
+ TRACE_EVENT1(kControlCharacters, kControlCharacters,
+ kControlCharacters, kControlCharacters);
+
+ uint64_t context_id = 0x20151021;
+
+ TRACE_EVENT_ENTER_CONTEXT("all", "TRACE_EVENT_ENTER_CONTEXT call",
+ TRACE_ID_WITH_SCOPE("scope", context_id));
+ TRACE_EVENT_LEAVE_CONTEXT("all", "TRACE_EVENT_LEAVE_CONTEXT call",
+ TRACE_ID_WITH_SCOPE("scope", context_id));
+ TRACE_EVENT_SCOPED_CONTEXT("disabled-by-default-cat",
+ "TRACE_EVENT_SCOPED_CONTEXT disabled call",
+ context_id);
+ TRACE_EVENT_SCOPED_CONTEXT("all", "TRACE_EVENT_SCOPED_CONTEXT call",
+ context_id);
+
+ TRACE_LINK_IDS("all", "TRACE_LINK_IDS simple call", 0x1000, 0x2000);
+ TRACE_LINK_IDS("all", "TRACE_LINK_IDS scoped call",
+ TRACE_ID_WITH_SCOPE("scope 1", 0x1000),
+ TRACE_ID_WITH_SCOPE("scope 2", 0x2000));
+ TRACE_LINK_IDS("all", "TRACE_LINK_IDS to a local ID", 0x1000,
+ TRACE_ID_LOCAL(0x2000));
+ TRACE_LINK_IDS("all", "TRACE_LINK_IDS to a global ID", 0x1000,
+ TRACE_ID_GLOBAL(0x2000));
+ TRACE_LINK_IDS("all", "TRACE_LINK_IDS to a composite ID", 0x1000,
+ TRACE_ID_WITH_SCOPE("scope 1", 0x2000, 0x3000));
+
+ TRACE_EVENT_ASYNC_BEGIN0("all", "async default process scope", 0x1000);
+ TRACE_EVENT_ASYNC_BEGIN0("all", "async local id", TRACE_ID_LOCAL(0x2000));
+ TRACE_EVENT_ASYNC_BEGIN0("all", "async global id", TRACE_ID_GLOBAL(0x3000));
+ TRACE_EVENT_ASYNC_BEGIN0("all", "async global id with scope string",
+ TRACE_ID_WITH_SCOPE("scope string",
+ TRACE_ID_GLOBAL(0x4000)));
+ } // Scope close causes TRACE_EVENT0 etc to send their END events.
+
+ if (task_complete_event)
+ task_complete_event->Signal();
+}
+
+void ValidateAllTraceMacrosCreatedData(const ListValue& trace_parsed) {
+ const DictionaryValue* item = nullptr;
+
+#define EXPECT_FIND_(string) \
+ item = FindTraceEntry(trace_parsed, string); \
+ EXPECT_TRUE(item);
+#define EXPECT_NOT_FIND_(string) \
+ item = FindTraceEntry(trace_parsed, string); \
+ EXPECT_FALSE(item);
+#define EXPECT_SUB_FIND_(string) \
+ if (item) \
+ EXPECT_TRUE(IsStringInDict(string, item));
+
+ EXPECT_FIND_("TRACE_EVENT0 call");
+ {
+ std::string ph;
+ std::string ph_end;
+ EXPECT_TRUE((item = FindTraceEntry(trace_parsed, "TRACE_EVENT0 call")));
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("X", ph);
+ item = FindTraceEntry(trace_parsed, "TRACE_EVENT0 call", item);
+ EXPECT_FALSE(item);
+ }
+ EXPECT_FIND_("TRACE_EVENT1 call");
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_FIND_("TRACE_EVENT2 call");
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("\"value1\"");
+ EXPECT_SUB_FIND_("name2");
+ EXPECT_SUB_FIND_("value\\2");
+
+ EXPECT_FIND_("TRACE_EVENT_INSTANT0 call");
+ {
+ std::string scope;
+ EXPECT_TRUE((item && item->GetString("s", &scope)));
+ EXPECT_EQ("g", scope);
+ }
+ EXPECT_FIND_("TRACE_EVENT_INSTANT1 call");
+ {
+ std::string scope;
+ EXPECT_TRUE((item && item->GetString("s", &scope)));
+ EXPECT_EQ("p", scope);
+ }
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_FIND_("TRACE_EVENT_INSTANT2 call");
+ {
+ std::string scope;
+ EXPECT_TRUE((item && item->GetString("s", &scope)));
+ EXPECT_EQ("t", scope);
+ }
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_SUB_FIND_("name2");
+ EXPECT_SUB_FIND_("value2");
+
+ EXPECT_FIND_("TRACE_EVENT_BEGIN0 call");
+ EXPECT_FIND_("TRACE_EVENT_BEGIN1 call");
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_FIND_("TRACE_EVENT_BEGIN2 call");
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_SUB_FIND_("name2");
+ EXPECT_SUB_FIND_("value2");
+
+ EXPECT_FIND_("TRACE_EVENT_END0 call");
+ EXPECT_FIND_("TRACE_EVENT_END1 call");
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_FIND_("TRACE_EVENT_END2 call");
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_SUB_FIND_("name2");
+ EXPECT_SUB_FIND_("value2");
+
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN0 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncIdStr);
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN1 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncIdStr);
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_BEGIN2 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncIdStr);
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_SUB_FIND_("name2");
+ EXPECT_SUB_FIND_("value2");
+
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_INTO0 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncIdStr);
+ EXPECT_SUB_FIND_("step_begin1");
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_INTO1 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncIdStr);
+ EXPECT_SUB_FIND_("step_begin2");
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_END0 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncIdStr);
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_END1 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncIdStr);
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_END2 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncIdStr);
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ EXPECT_SUB_FIND_("name2");
+ EXPECT_SUB_FIND_("value2");
+
+ EXPECT_FIND_("TRACE_EVENT_FLOW_BEGIN0 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kFlowIdStr);
+ EXPECT_FIND_("TRACE_EVENT_FLOW_STEP0 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kFlowIdStr);
+ EXPECT_SUB_FIND_("step1");
+ EXPECT_FIND_("TRACE_EVENT_FLOW_END_BIND_TO_ENCLOSING0 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kFlowIdStr);
+
+ EXPECT_FIND_("TRACE_COUNTER1 call");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("C", ph);
+
+ int value;
+ EXPECT_TRUE((item && item->GetInteger("args.value", &value)));
+ EXPECT_EQ(31415, value);
+ }
+
+ EXPECT_FIND_("TRACE_COUNTER2 call");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("C", ph);
+
+ int value;
+ EXPECT_TRUE((item && item->GetInteger("args.a", &value)));
+ EXPECT_EQ(30000, value);
+
+ EXPECT_TRUE((item && item->GetInteger("args.b", &value)));
+ EXPECT_EQ(1415, value);
+ }
+
+ EXPECT_FIND_("TRACE_COUNTER_WITH_TIMESTAMP1 call");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("C", ph);
+
+ int value;
+ EXPECT_TRUE((item && item->GetInteger("args.value", &value)));
+ EXPECT_EQ(31415, value);
+
+ int ts;
+ EXPECT_TRUE((item && item->GetInteger("ts", &ts)));
+ EXPECT_EQ(42, ts);
+ }
+
+ EXPECT_FIND_("TRACE_COUNTER_WITH_TIMESTAMP2 call");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("C", ph);
+
+ int value;
+ EXPECT_TRUE((item && item->GetInteger("args.a", &value)));
+ EXPECT_EQ(30000, value);
+
+ EXPECT_TRUE((item && item->GetInteger("args.b", &value)));
+ EXPECT_EQ(1415, value);
+
+ int ts;
+ EXPECT_TRUE((item && item->GetInteger("ts", &ts)));
+ EXPECT_EQ(42, ts);
+ }
+
+ EXPECT_FIND_("TRACE_COUNTER_ID1 call");
+ {
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x319009", id);
+
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("C", ph);
+
+ int value;
+ EXPECT_TRUE((item && item->GetInteger("args.value", &value)));
+ EXPECT_EQ(31415, value);
+ }
+
+ EXPECT_FIND_("TRACE_COUNTER_ID2 call");
+ {
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x319009", id);
+
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("C", ph);
+
+ int value;
+ EXPECT_TRUE((item && item->GetInteger("args.a", &value)));
+ EXPECT_EQ(30000, value);
+
+ EXPECT_TRUE((item && item->GetInteger("args.b", &value)));
+ EXPECT_EQ(1415, value);
+ }
+
+ EXPECT_FIND_("TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call");
+ {
+ int val;
+ EXPECT_TRUE((item && item->GetInteger("ts", &val)));
+ EXPECT_EQ(12345, val);
+ EXPECT_TRUE((item && item->GetInteger("tid", &val)));
+ EXPECT_EQ(kThreadId, val);
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ(kAsyncIdStr, id);
+ }
+
+ EXPECT_FIND_("TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0 call");
+ {
+ int val;
+ EXPECT_TRUE((item && item->GetInteger("ts", &val)));
+ EXPECT_EQ(23456, val);
+ EXPECT_TRUE((item && item->GetInteger("tid", &val)));
+ EXPECT_EQ(kThreadId, val);
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ(kAsyncIdStr, id);
+ }
+
+ EXPECT_FIND_("TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call");
+ {
+ int val;
+ EXPECT_TRUE((item && item->GetInteger("ts", &val)));
+ EXPECT_EQ(34567, val);
+ EXPECT_TRUE((item && item->GetInteger("tid", &val)));
+ EXPECT_EQ(kThreadId, val);
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ(kAsyncId2Str, id);
+ }
+
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_PAST0 call");
+ {
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncId2Str);
+ EXPECT_SUB_FIND_("step_end1");
+ EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_PAST1 call");
+ EXPECT_SUB_FIND_("id");
+ EXPECT_SUB_FIND_(kAsyncId2Str);
+ EXPECT_SUB_FIND_("step_end2");
+ EXPECT_SUB_FIND_("name1");
+ EXPECT_SUB_FIND_("value1");
+ }
+
+ EXPECT_FIND_("TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0 call");
+ {
+ int val;
+ EXPECT_TRUE((item && item->GetInteger("ts", &val)));
+ EXPECT_EQ(45678, val);
+ EXPECT_TRUE((item && item->GetInteger("tid", &val)));
+ EXPECT_EQ(kThreadId, val);
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ(kAsyncId2Str, id);
+ }
+
+ EXPECT_FIND_("tracked object 1");
+ {
+ std::string phase;
+ std::string id;
+ std::string snapshot;
+
+ EXPECT_TRUE((item && item->GetString("ph", &phase)));
+ EXPECT_EQ("N", phase);
+ EXPECT_FALSE((item && item->HasKey("scope")));
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x42", id);
+
+ item = FindTraceEntry(trace_parsed, "tracked object 1", item);
+ EXPECT_TRUE(item);
+ EXPECT_TRUE(item && item->GetString("ph", &phase));
+ EXPECT_EQ("O", phase);
+ EXPECT_FALSE((item && item->HasKey("scope")));
+ EXPECT_TRUE(item && item->GetString("id", &id));
+ EXPECT_EQ("0x42", id);
+ EXPECT_TRUE(item && item->GetString("args.snapshot", &snapshot));
+ EXPECT_EQ("hello", snapshot);
+
+ item = FindTraceEntry(trace_parsed, "tracked object 1", item);
+ EXPECT_TRUE(item);
+ EXPECT_TRUE(item && item->GetString("ph", &phase));
+ EXPECT_EQ("D", phase);
+ EXPECT_FALSE((item && item->HasKey("scope")));
+ EXPECT_TRUE(item && item->GetString("id", &id));
+ EXPECT_EQ("0x42", id);
+ }
+
+ EXPECT_FIND_("tracked object 2");
+ {
+ std::string phase;
+ std::string id;
+ std::string snapshot;
+
+ EXPECT_TRUE(item && item->GetString("ph", &phase));
+ EXPECT_EQ("N", phase);
+ EXPECT_TRUE(item && item->GetString("id", &id));
+ EXPECT_EQ("0x2128506", id);
+
+ item = FindTraceEntry(trace_parsed, "tracked object 2", item);
+ EXPECT_TRUE(item);
+ EXPECT_TRUE(item && item->GetString("ph", &phase));
+ EXPECT_EQ("O", phase);
+ EXPECT_TRUE(item && item->GetString("id", &id));
+ EXPECT_EQ("0x2128506", id);
+ EXPECT_TRUE(item && item->GetString("args.snapshot", &snapshot));
+ EXPECT_EQ("world", snapshot);
+
+ item = FindTraceEntry(trace_parsed, "tracked object 2", item);
+ EXPECT_TRUE(item);
+ EXPECT_TRUE(item && item->GetString("ph", &phase));
+ EXPECT_EQ("D", phase);
+ EXPECT_TRUE(item && item->GetString("id", &id));
+ EXPECT_EQ("0x2128506", id);
+ }
+
+ EXPECT_FIND_("tracked object 3");
+ {
+ std::string phase;
+ std::string scope;
+ std::string id;
+ std::string snapshot;
+
+ EXPECT_TRUE((item && item->GetString("ph", &phase)));
+ EXPECT_EQ("N", phase);
+ EXPECT_TRUE((item && item->GetString("scope", &scope)));
+ EXPECT_EQ("scope", scope);
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x42", id);
+
+ item = FindTraceEntry(trace_parsed, "tracked object 3", item);
+ EXPECT_TRUE(item);
+ EXPECT_TRUE(item && item->GetString("ph", &phase));
+ EXPECT_EQ("O", phase);
+ EXPECT_TRUE((item && item->GetString("scope", &scope)));
+ EXPECT_EQ("scope", scope);
+ EXPECT_TRUE(item && item->GetString("id", &id));
+ EXPECT_EQ("0x42", id);
+ EXPECT_TRUE(item && item->GetString("args.snapshot", &snapshot));
+ EXPECT_EQ("hello", snapshot);
+
+ item = FindTraceEntry(trace_parsed, "tracked object 3", item);
+ EXPECT_TRUE(item);
+ EXPECT_TRUE(item && item->GetString("ph", &phase));
+ EXPECT_EQ("D", phase);
+ EXPECT_TRUE((item && item->GetString("scope", &scope)));
+ EXPECT_EQ("scope", scope);
+ EXPECT_TRUE(item && item->GetString("id", &id));
+ EXPECT_EQ("0x42", id);
+ }
+
+ EXPECT_FIND_(kControlCharacters);
+ EXPECT_SUB_FIND_(kControlCharacters);
+
+ EXPECT_FIND_("TRACE_EVENT_ENTER_CONTEXT call");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("(", ph);
+
+ std::string scope;
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("scope", &scope)));
+ EXPECT_EQ("scope", scope);
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x20151021", id);
+ }
+
+ EXPECT_FIND_("TRACE_EVENT_LEAVE_CONTEXT call");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ(")", ph);
+
+ std::string scope;
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("scope", &scope)));
+ EXPECT_EQ("scope", scope);
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x20151021", id);
+ }
+
+ std::vector<const DictionaryValue*> scoped_context_calls =
+ FindTraceEntries(trace_parsed, "TRACE_EVENT_SCOPED_CONTEXT call");
+ EXPECT_EQ(2u, scoped_context_calls.size());
+ {
+ item = scoped_context_calls[0];
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("(", ph);
+
+ std::string id;
+ EXPECT_FALSE((item && item->HasKey("scope")));
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x20151021", id);
+ }
+
+ {
+ item = scoped_context_calls[1];
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ(")", ph);
+
+ std::string id;
+ EXPECT_FALSE((item && item->HasKey("scope")));
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x20151021", id);
+ }
+
+ EXPECT_FIND_("TRACE_LINK_IDS simple call");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("=", ph);
+
+ EXPECT_FALSE((item && item->HasKey("scope")));
+ std::string id1;
+ EXPECT_TRUE((item && item->GetString("id", &id1)));
+ EXPECT_EQ("0x1000", id1);
+
+ EXPECT_FALSE((item && item->HasKey("args.linked_id.scope")));
+ std::string id2;
+ EXPECT_TRUE((item && item->GetString("args.linked_id.id", &id2)));
+ EXPECT_EQ("0x2000", id2);
+ }
+
+ EXPECT_FIND_("TRACE_LINK_IDS scoped call");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("=", ph);
+
+ std::string scope1;
+ EXPECT_TRUE((item && item->GetString("scope", &scope1)));
+ EXPECT_EQ("scope 1", scope1);
+ std::string id1;
+ EXPECT_TRUE((item && item->GetString("id", &id1)));
+ EXPECT_EQ("0x1000", id1);
+
+ std::string scope2;
+ EXPECT_TRUE((item && item->GetString("args.linked_id.scope", &scope2)));
+ EXPECT_EQ("scope 2", scope2);
+ std::string id2;
+ EXPECT_TRUE((item && item->GetString("args.linked_id.id", &id2)));
+ EXPECT_EQ("0x2000", id2);
+ }
+
+ EXPECT_FIND_("TRACE_LINK_IDS to a local ID");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("=", ph);
+
+ EXPECT_FALSE((item && item->HasKey("scope")));
+ std::string id1;
+ EXPECT_TRUE((item && item->GetString("id", &id1)));
+ EXPECT_EQ("0x1000", id1);
+
+ EXPECT_FALSE((item && item->HasKey("args.linked_id.scope")));
+ std::string id2;
+ EXPECT_TRUE((item && item->GetString("args.linked_id.id2.local", &id2)));
+ EXPECT_EQ("0x2000", id2);
+ }
+
+ EXPECT_FIND_("TRACE_LINK_IDS to a global ID");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("=", ph);
+
+ EXPECT_FALSE((item && item->HasKey("scope")));
+ std::string id1;
+ EXPECT_TRUE((item && item->GetString("id", &id1)));
+ EXPECT_EQ("0x1000", id1);
+
+ EXPECT_FALSE((item && item->HasKey("args.linked_id.scope")));
+ std::string id2;
+ EXPECT_TRUE((item && item->GetString("args.linked_id.id2.global", &id2)));
+ EXPECT_EQ("0x2000", id2);
+ }
+
+ EXPECT_FIND_("TRACE_LINK_IDS to a composite ID");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("=", ph);
+
+ EXPECT_FALSE(item->HasKey("scope"));
+ std::string id1;
+ EXPECT_TRUE(item->GetString("id", &id1));
+ EXPECT_EQ("0x1000", id1);
+
+ std::string scope;
+ EXPECT_TRUE(item->GetString("args.linked_id.scope", &scope));
+ EXPECT_EQ("scope 1", scope);
+ std::string id2;
+ EXPECT_TRUE(item->GetString("args.linked_id.id", &id2));
+ EXPECT_EQ(id2, "0x2000/0x3000");
+ }
+
+ EXPECT_FIND_("async default process scope");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("S", ph);
+
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id", &id)));
+ EXPECT_EQ("0x1000", id);
+ }
+
+ EXPECT_FIND_("async local id");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("S", ph);
+
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id2.local", &id)));
+ EXPECT_EQ("0x2000", id);
+ }
+
+ EXPECT_FIND_("async global id");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("S", ph);
+
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id2.global", &id)));
+ EXPECT_EQ("0x3000", id);
+ }
+
+ EXPECT_FIND_("async global id with scope string");
+ {
+ std::string ph;
+ EXPECT_TRUE((item && item->GetString("ph", &ph)));
+ EXPECT_EQ("S", ph);
+
+ std::string id;
+ EXPECT_TRUE((item && item->GetString("id2.global", &id)));
+ EXPECT_EQ("0x4000", id);
+ std::string scope;
+ EXPECT_TRUE((item && item->GetString("scope", &scope)));
+ EXPECT_EQ("scope string", scope);
+ }
+}
+
+void TraceManyInstantEvents(int thread_id, int num_events,
+ WaitableEvent* task_complete_event) {
+ for (int i = 0; i < num_events; i++) {
+ TRACE_EVENT_INSTANT2("all", "multi thread event",
+ TRACE_EVENT_SCOPE_THREAD,
+ "thread", thread_id,
+ "event", i);
+ }
+
+ if (task_complete_event)
+ task_complete_event->Signal();
+}
+
+void ValidateInstantEventPresentOnEveryThread(const ListValue& trace_parsed,
+ int num_threads,
+ int num_events) {
+ std::map<int, std::map<int, bool> > results;
+
+ size_t trace_parsed_count = trace_parsed.GetSize();
+ for (size_t i = 0; i < trace_parsed_count; i++) {
+ const Value* value = nullptr;
+ trace_parsed.Get(i, &value);
+ if (!value || value->type() != Value::Type::DICTIONARY)
+ continue;
+ const DictionaryValue* dict = static_cast<const DictionaryValue*>(value);
+ std::string name;
+ dict->GetString("name", &name);
+ if (name != "multi thread event")
+ continue;
+
+ int thread = 0;
+ int event = 0;
+ EXPECT_TRUE(dict->GetInteger("args.thread", &thread));
+ EXPECT_TRUE(dict->GetInteger("args.event", &event));
+ results[thread][event] = true;
+ }
+
+ EXPECT_FALSE(results[-1][-1]);
+ for (int thread = 0; thread < num_threads; thread++) {
+ for (int event = 0; event < num_events; event++) {
+ EXPECT_TRUE(results[thread][event]);
+ }
+ }
+}
+
+void CheckTraceDefaultCategoryFilters(const TraceLog& trace_log) {
+ // Default enables all category filters except the disabled-by-default-* ones.
+ EXPECT_TRUE(*trace_log.GetCategoryGroupEnabled("foo"));
+ EXPECT_TRUE(*trace_log.GetCategoryGroupEnabled("bar"));
+ EXPECT_TRUE(*trace_log.GetCategoryGroupEnabled("foo,bar"));
+ EXPECT_TRUE(*trace_log.GetCategoryGroupEnabled(
+ "foo,disabled-by-default-foo"));
+ EXPECT_FALSE(*trace_log.GetCategoryGroupEnabled(
+ "disabled-by-default-foo,disabled-by-default-bar"));
+}
+
+} // namespace
+
+// Simple Test for emitting data and validating it was received.
+TEST_F(TraceEventTestFixture, DataCaptured) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+
+ TraceWithAllMacroVariants(nullptr);
+
+ EndTraceAndFlush();
+
+ ValidateAllTraceMacrosCreatedData(trace_parsed_);
+}
+
+// Emit some events and validate that only empty strings are received
+// if we tell Flush() to discard events.
+TEST_F(TraceEventTestFixture, DataDiscarded) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+
+ TraceWithAllMacroVariants(nullptr);
+
+ CancelTrace();
+
+ EXPECT_TRUE(trace_parsed_.empty());
+}
+
+class MockEnabledStateChangedObserver :
+ public TraceLog::EnabledStateObserver {
+ public:
+ MOCK_METHOD0(OnTraceLogEnabled, void());
+ MOCK_METHOD0(OnTraceLogDisabled, void());
+};
+
+TEST_F(TraceEventTestFixture, EnabledObserverFiresOnEnable) {
+ MockEnabledStateChangedObserver observer;
+ TraceLog::GetInstance()->AddEnabledStateObserver(&observer);
+
+ EXPECT_CALL(observer, OnTraceLogEnabled())
+ .Times(1);
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+ testing::Mock::VerifyAndClear(&observer);
+ EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled());
+
+ // Cleanup.
+ TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer);
+ TraceLog::GetInstance()->SetDisabled();
+}
+
+TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnSecondEnable) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+
+ testing::StrictMock<MockEnabledStateChangedObserver> observer;
+ TraceLog::GetInstance()->AddEnabledStateObserver(&observer);
+
+ EXPECT_CALL(observer, OnTraceLogEnabled())
+ .Times(0);
+ EXPECT_CALL(observer, OnTraceLogDisabled())
+ .Times(0);
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+ testing::Mock::VerifyAndClear(&observer);
+ EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled());
+
+ // Cleanup.
+ TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer);
+ TraceLog::GetInstance()->SetDisabled();
+ TraceLog::GetInstance()->SetDisabled();
+}
+
+TEST_F(TraceEventTestFixture, EnabledObserverFiresOnFirstDisable) {
+ TraceConfig tc_inc_all("*", "");
+ TraceLog::GetInstance()->SetEnabled(tc_inc_all, TraceLog::RECORDING_MODE);
+ TraceLog::GetInstance()->SetEnabled(tc_inc_all, TraceLog::RECORDING_MODE);
+
+ testing::StrictMock<MockEnabledStateChangedObserver> observer;
+ TraceLog::GetInstance()->AddEnabledStateObserver(&observer);
+
+ EXPECT_CALL(observer, OnTraceLogEnabled())
+ .Times(0);
+ EXPECT_CALL(observer, OnTraceLogDisabled())
+ .Times(1);
+ TraceLog::GetInstance()->SetDisabled();
+ testing::Mock::VerifyAndClear(&observer);
+
+ // Cleanup.
+ TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer);
+ TraceLog::GetInstance()->SetDisabled();
+}
+
+TEST_F(TraceEventTestFixture, EnabledObserverFiresOnDisable) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+
+ MockEnabledStateChangedObserver observer;
+ TraceLog::GetInstance()->AddEnabledStateObserver(&observer);
+
+ EXPECT_CALL(observer, OnTraceLogDisabled())
+ .Times(1);
+ TraceLog::GetInstance()->SetDisabled();
+ testing::Mock::VerifyAndClear(&observer);
+
+ // Cleanup.
+ TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer);
+}
+
+// Tests the IsEnabled() state of TraceLog changes before callbacks.
+class AfterStateChangeEnabledStateObserver
+ : public TraceLog::EnabledStateObserver {
+ public:
+ AfterStateChangeEnabledStateObserver() = default;
+ ~AfterStateChangeEnabledStateObserver() override = default;
+
+ // TraceLog::EnabledStateObserver overrides:
+ void OnTraceLogEnabled() override {
+ EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled());
+ }
+
+ void OnTraceLogDisabled() override {
+ EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled());
+ }
+};
+
+TEST_F(TraceEventTestFixture, ObserversFireAfterStateChange) {
+ AfterStateChangeEnabledStateObserver observer;
+ TraceLog::GetInstance()->AddEnabledStateObserver(&observer);
+
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled());
+
+ TraceLog::GetInstance()->SetDisabled();
+ EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled());
+
+ TraceLog::GetInstance()->RemoveEnabledStateObserver(&observer);
+}
+
+// Tests that a state observer can remove itself during a callback.
+class SelfRemovingEnabledStateObserver
+ : public TraceLog::EnabledStateObserver {
+ public:
+ SelfRemovingEnabledStateObserver() = default;
+ ~SelfRemovingEnabledStateObserver() override = default;
+
+ // TraceLog::EnabledStateObserver overrides:
+ void OnTraceLogEnabled() override {}
+
+ void OnTraceLogDisabled() override {
+ TraceLog::GetInstance()->RemoveEnabledStateObserver(this);
+ }
+};
+
+TEST_F(TraceEventTestFixture, SelfRemovingObserver) {
+ ASSERT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest());
+
+ SelfRemovingEnabledStateObserver observer;
+ TraceLog::GetInstance()->AddEnabledStateObserver(&observer);
+ EXPECT_EQ(1u, TraceLog::GetInstance()->GetObserverCountForTest());
+
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+ TraceLog::GetInstance()->SetDisabled();
+ // The observer removed itself on disable.
+ EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest());
+}
+
+bool IsNewTrace() {
+ bool is_new_trace;
+ TRACE_EVENT_IS_NEW_TRACE(&is_new_trace);
+ return is_new_trace;
+}
+
+TEST_F(TraceEventTestFixture, NewTraceRecording) {
+ ASSERT_FALSE(IsNewTrace());
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+ // First call to IsNewTrace() should succeed. But, the second shouldn't.
+ ASSERT_TRUE(IsNewTrace());
+ ASSERT_FALSE(IsNewTrace());
+ EndTraceAndFlush();
+
+ // IsNewTrace() should definitely be false now.
+ ASSERT_FALSE(IsNewTrace());
+
+ // Start another trace. IsNewTrace() should become true again, briefly, as
+ // before.
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+ ASSERT_TRUE(IsNewTrace());
+ ASSERT_FALSE(IsNewTrace());
+
+ // Cleanup.
+ EndTraceAndFlush();
+}
+
+TEST_F(TraceEventTestFixture, TestTraceFlush) {
+ size_t min_traces = 1;
+ size_t max_traces = 1;
+ do {
+ max_traces *= 2;
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(),
+ TraceLog::RECORDING_MODE);
+ for (size_t i = 0; i < max_traces; i++) {
+ TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD);
+ }
+ EndTraceAndFlush();
+ } while (num_flush_callbacks_ < 2);
+
+ while (min_traces + 50 < max_traces) {
+ size_t traces = (min_traces + max_traces) / 2;
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(),
+ TraceLog::RECORDING_MODE);
+ for (size_t i = 0; i < traces; i++) {
+ TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD);
+ }
+ EndTraceAndFlush();
+ if (num_flush_callbacks_ < 2) {
+ min_traces = traces - 10;
+ } else {
+ max_traces = traces + 10;
+ }
+ }
+
+ for (size_t traces = min_traces; traces < max_traces; traces++) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(),
+ TraceLog::RECORDING_MODE);
+ for (size_t i = 0; i < traces; i++) {
+ TRACE_EVENT_INSTANT0("x", "y", TRACE_EVENT_SCOPE_THREAD);
+ }
+ EndTraceAndFlush();
+ }
+}
+
+TEST_F(TraceEventTestFixture, AddMetadataEvent) {
+ int num_calls = 0;
+
+ class Convertable : public ConvertableToTraceFormat {
+ public:
+ explicit Convertable(int* num_calls) : num_calls_(num_calls) {}
+ ~Convertable() override = default;
+ void AppendAsTraceFormat(std::string* out) const override {
+ (*num_calls_)++;
+ out->append("\"metadata_value\"");
+ }
+
+ private:
+ int* num_calls_;
+ };
+
+ std::unique_ptr<ConvertableToTraceFormat> conv1(new Convertable(&num_calls));
+ std::unique_ptr<Convertable> conv2(new Convertable(&num_calls));
+
+ BeginTrace();
+ TRACE_EVENT_API_ADD_METADATA_EVENT(
+ TraceLog::GetCategoryGroupEnabled("__metadata"), "metadata_event_1",
+ "metadata_arg_name", std::move(conv1));
+ TRACE_EVENT_API_ADD_METADATA_EVENT(
+ TraceLog::GetCategoryGroupEnabled("__metadata"), "metadata_event_2",
+ "metadata_arg_name", std::move(conv2));
+ // |AppendAsTraceFormat| should only be called on flush, not when the event
+ // is added.
+ ASSERT_EQ(0, num_calls);
+ EndTraceAndFlush();
+ ASSERT_EQ(2, num_calls);
+ EXPECT_TRUE(FindNamePhaseKeyValue("metadata_event_1", "M",
+ "metadata_arg_name", "metadata_value"));
+ EXPECT_TRUE(FindNamePhaseKeyValue("metadata_event_2", "M",
+ "metadata_arg_name", "metadata_value"));
+
+ // The metadata event should only be adde to the current trace. In this new
+ // trace, the event should not appear.
+ BeginTrace();
+ EndTraceAndFlush();
+ ASSERT_EQ(2, num_calls);
+}
+
+// Test that categories work.
+TEST_F(TraceEventTestFixture, Categories) {
+ // Test that categories that are used can be retrieved whether trace was
+ // enabled or disabled when the trace event was encountered.
+ TRACE_EVENT_INSTANT0("c1", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("c2", "name", TRACE_EVENT_SCOPE_THREAD);
+ BeginTrace();
+ TRACE_EVENT_INSTANT0("c3", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("c4", "name", TRACE_EVENT_SCOPE_THREAD);
+ // Category groups containing more than one category.
+ TRACE_EVENT_INSTANT0("c5,c6", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("c7,c8", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("c9"), "name",
+ TRACE_EVENT_SCOPE_THREAD);
+
+ EndTraceAndFlush();
+ std::vector<std::string> cat_groups;
+ TraceLog::GetInstance()->GetKnownCategoryGroups(&cat_groups);
+ EXPECT_TRUE(ContainsValue(cat_groups, "c1"));
+ EXPECT_TRUE(ContainsValue(cat_groups, "c2"));
+ EXPECT_TRUE(ContainsValue(cat_groups, "c3"));
+ EXPECT_TRUE(ContainsValue(cat_groups, "c4"));
+ EXPECT_TRUE(ContainsValue(cat_groups, "c5,c6"));
+ EXPECT_TRUE(ContainsValue(cat_groups, "c7,c8"));
+ EXPECT_TRUE(ContainsValue(cat_groups, "disabled-by-default-c9"));
+ // Make sure metadata isn't returned.
+ EXPECT_FALSE(ContainsValue(cat_groups, "__metadata"));
+
+ const std::vector<std::string> empty_categories;
+ std::vector<std::string> included_categories;
+ std::vector<std::string> excluded_categories;
+
+ // Test that category filtering works.
+
+ // Include nonexistent category -> no events
+ Clear();
+ included_categories.clear();
+ TraceLog::GetInstance()->SetEnabled(TraceConfig("not_found823564786", ""),
+ TraceLog::RECORDING_MODE);
+ TRACE_EVENT_INSTANT0("cat1", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat2", "name", TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+ DropTracedMetadataRecords();
+ EXPECT_TRUE(trace_parsed_.empty());
+
+ // Include existent category -> only events of that category
+ Clear();
+ included_categories.clear();
+ TraceLog::GetInstance()->SetEnabled(TraceConfig("inc", ""),
+ TraceLog::RECORDING_MODE);
+ TRACE_EVENT_INSTANT0("inc", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc2", "name", TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+ DropTracedMetadataRecords();
+ EXPECT_TRUE(FindMatchingValue("cat", "inc"));
+ EXPECT_FALSE(FindNonMatchingValue("cat", "inc"));
+
+ // Include existent wildcard -> all categories matching wildcard
+ Clear();
+ included_categories.clear();
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig("inc_wildcard_*,inc_wildchar_?_end", ""),
+ TraceLog::RECORDING_MODE);
+ TRACE_EVENT_INSTANT0("inc_wildcard_abc", "included",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc_wildcard_", "included", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc_wildchar_x_end", "included",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc_wildchar_bla_end", "not_inc",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat1", "not_inc", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat2", "not_inc", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc_wildcard_category,other_category", "included",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0(
+ "non_included_category,inc_wildcard_category", "included",
+ TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+ EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_abc"));
+ EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_"));
+ EXPECT_TRUE(FindMatchingValue("cat", "inc_wildchar_x_end"));
+ EXPECT_FALSE(FindMatchingValue("name", "not_inc"));
+ EXPECT_TRUE(FindMatchingValue("cat", "inc_wildcard_category,other_category"));
+ EXPECT_TRUE(FindMatchingValue("cat",
+ "non_included_category,inc_wildcard_category"));
+
+ included_categories.clear();
+
+ // Exclude nonexistent category -> all events
+ Clear();
+ TraceLog::GetInstance()->SetEnabled(TraceConfig("-not_found823564786", ""),
+ TraceLog::RECORDING_MODE);
+ TRACE_EVENT_INSTANT0("cat1", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat2", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("category1,category2", "name", TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+ EXPECT_TRUE(FindMatchingValue("cat", "cat1"));
+ EXPECT_TRUE(FindMatchingValue("cat", "cat2"));
+ EXPECT_TRUE(FindMatchingValue("cat", "category1,category2"));
+
+ // Exclude existent category -> only events of other categories
+ Clear();
+ TraceLog::GetInstance()->SetEnabled(TraceConfig("-inc", ""),
+ TraceLog::RECORDING_MODE);
+ TRACE_EVENT_INSTANT0("inc", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc2", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc2,inc", "name", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc,inc2", "name", TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+ EXPECT_TRUE(FindMatchingValue("cat", "inc2"));
+ EXPECT_FALSE(FindMatchingValue("cat", "inc"));
+ EXPECT_TRUE(FindMatchingValue("cat", "inc2,inc"));
+ EXPECT_TRUE(FindMatchingValue("cat", "inc,inc2"));
+
+ // Exclude existent wildcard -> all categories not matching wildcard
+ Clear();
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig("-inc_wildcard_*,-inc_wildchar_?_end", ""),
+ TraceLog::RECORDING_MODE);
+ TRACE_EVENT_INSTANT0("inc_wildcard_abc", "not_inc",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc_wildcard_", "not_inc",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc_wildchar_x_end", "not_inc",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("inc_wildchar_bla_end", "included",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat1", "included", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("cat2", "included", TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+ EXPECT_TRUE(FindMatchingValue("cat", "inc_wildchar_bla_end"));
+ EXPECT_TRUE(FindMatchingValue("cat", "cat1"));
+ EXPECT_TRUE(FindMatchingValue("cat", "cat2"));
+ EXPECT_FALSE(FindMatchingValue("name", "not_inc"));
+}
+
+
+// Test ASYNC_BEGIN/END events
+TEST_F(TraceEventTestFixture, AsyncBeginEndEvents) {
+ BeginTrace();
+
+ unsigned long long id = 0xfeedbeeffeedbeefull;
+ TRACE_EVENT_ASYNC_BEGIN0("cat", "name1", id);
+ TRACE_EVENT_ASYNC_STEP_INTO0("cat", "name1", id, "step1");
+ TRACE_EVENT_ASYNC_END0("cat", "name1", id);
+ TRACE_EVENT_BEGIN0("cat", "name2");
+ TRACE_EVENT_ASYNC_BEGIN0("cat", "name3", 0);
+ TRACE_EVENT_ASYNC_STEP_PAST0("cat", "name3", 0, "step2");
+
+ EndTraceAndFlush();
+
+ EXPECT_TRUE(FindNamePhase("name1", "S"));
+ EXPECT_TRUE(FindNamePhase("name1", "T"));
+ EXPECT_TRUE(FindNamePhase("name1", "F"));
+
+ std::string id_str;
+ StringAppendF(&id_str, "0x%llx", id);
+
+ EXPECT_TRUE(FindNamePhaseKeyValue("name1", "S", "id", id_str.c_str()));
+ EXPECT_TRUE(FindNamePhaseKeyValue("name1", "T", "id", id_str.c_str()));
+ EXPECT_TRUE(FindNamePhaseKeyValue("name1", "F", "id", id_str.c_str()));
+ EXPECT_TRUE(FindNamePhaseKeyValue("name3", "S", "id", "0x0"));
+ EXPECT_TRUE(FindNamePhaseKeyValue("name3", "p", "id", "0x0"));
+
+ // BEGIN events should not have id
+ EXPECT_FALSE(FindNamePhaseKeyValue("name2", "B", "id", "0"));
+}
+
+// Test ASYNC_BEGIN/END events
+TEST_F(TraceEventTestFixture, AsyncBeginEndPointerMangling) {
+ void* ptr = this;
+
+ TraceLog::GetInstance()->SetProcessID(100);
+ BeginTrace();
+ TRACE_EVENT_ASYNC_BEGIN0("cat", "name1", ptr);
+ TRACE_EVENT_ASYNC_BEGIN0("cat", "name2", ptr);
+ EndTraceAndFlush();
+
+ TraceLog::GetInstance()->SetProcessID(200);
+ BeginTrace();
+ TRACE_EVENT_ASYNC_END0("cat", "name1", ptr);
+ EndTraceAndFlush();
+
+ DictionaryValue* async_begin = FindNamePhase("name1", "S");
+ DictionaryValue* async_begin2 = FindNamePhase("name2", "S");
+ DictionaryValue* async_end = FindNamePhase("name1", "F");
+ EXPECT_TRUE(async_begin);
+ EXPECT_TRUE(async_begin2);
+ EXPECT_TRUE(async_end);
+
+ Value* value = nullptr;
+ std::string async_begin_id_str;
+ std::string async_begin2_id_str;
+ std::string async_end_id_str;
+ ASSERT_TRUE(async_begin->Get("id", &value));
+ ASSERT_TRUE(value->GetAsString(&async_begin_id_str));
+ ASSERT_TRUE(async_begin2->Get("id", &value));
+ ASSERT_TRUE(value->GetAsString(&async_begin2_id_str));
+ ASSERT_TRUE(async_end->Get("id", &value));
+ ASSERT_TRUE(value->GetAsString(&async_end_id_str));
+
+ EXPECT_STREQ(async_begin_id_str.c_str(), async_begin2_id_str.c_str());
+ EXPECT_STRNE(async_begin_id_str.c_str(), async_end_id_str.c_str());
+}
+
+// Test that static strings are not copied.
+TEST_F(TraceEventTestFixture, StaticStringVsString) {
+ TraceLog* tracer = TraceLog::GetInstance();
+ // Make sure old events are flushed:
+ EXPECT_EQ(0u, tracer->GetStatus().event_count);
+ const unsigned char* category_group_enabled =
+ TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("cat");
+
+ {
+ BeginTrace();
+ // Test that string arguments are copied.
+ TraceEventHandle handle1 =
+ trace_event_internal::AddTraceEvent(
+ TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name1",
+ trace_event_internal::kGlobalScope, trace_event_internal::kNoId,
+ 0, trace_event_internal::kNoId,
+ "arg1", std::string("argval"), "arg2", std::string("argval"));
+ // Test that static TRACE_STR_COPY string arguments are copied.
+ TraceEventHandle handle2 =
+ trace_event_internal::AddTraceEvent(
+ TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name2",
+ trace_event_internal::kGlobalScope, trace_event_internal::kNoId,
+ 0, trace_event_internal::kNoId,
+ "arg1", TRACE_STR_COPY("argval"),
+ "arg2", TRACE_STR_COPY("argval"));
+ EXPECT_GT(tracer->GetStatus().event_count, 1u);
+ const TraceEvent* event1 = tracer->GetEventByHandle(handle1);
+ const TraceEvent* event2 = tracer->GetEventByHandle(handle2);
+ ASSERT_TRUE(event1);
+ ASSERT_TRUE(event2);
+ EXPECT_STREQ("name1", event1->name());
+ EXPECT_STREQ("name2", event2->name());
+ EXPECT_TRUE(event1->parameter_copy_storage() != nullptr);
+ EXPECT_TRUE(event2->parameter_copy_storage() != nullptr);
+ EXPECT_GT(event1->parameter_copy_storage()->size(), 0u);
+ EXPECT_GT(event2->parameter_copy_storage()->size(), 0u);
+ EndTraceAndFlush();
+ }
+
+ {
+ BeginTrace();
+ // Test that static literal string arguments are not copied.
+ TraceEventHandle handle1 =
+ trace_event_internal::AddTraceEvent(
+ TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name1",
+ trace_event_internal::kGlobalScope, trace_event_internal::kNoId,
+ 0, trace_event_internal::kNoId,
+ "arg1", "argval", "arg2", "argval");
+ // Test that static TRACE_STR_COPY NULL string arguments are not copied.
+ const char* str1 = nullptr;
+ const char* str2 = nullptr;
+ TraceEventHandle handle2 =
+ trace_event_internal::AddTraceEvent(
+ TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name2",
+ trace_event_internal::kGlobalScope, trace_event_internal::kNoId,
+ 0, trace_event_internal::kNoId,
+ "arg1", TRACE_STR_COPY(str1),
+ "arg2", TRACE_STR_COPY(str2));
+ EXPECT_GT(tracer->GetStatus().event_count, 1u);
+ const TraceEvent* event1 = tracer->GetEventByHandle(handle1);
+ const TraceEvent* event2 = tracer->GetEventByHandle(handle2);
+ ASSERT_TRUE(event1);
+ ASSERT_TRUE(event2);
+ EXPECT_STREQ("name1", event1->name());
+ EXPECT_STREQ("name2", event2->name());
+ EXPECT_TRUE(event1->parameter_copy_storage() == nullptr);
+ EXPECT_TRUE(event2->parameter_copy_storage() == nullptr);
+ EndTraceAndFlush();
+ }
+}
+
+// Test that data sent from other threads is gathered
+TEST_F(TraceEventTestFixture, DataCapturedOnThread) {
+ BeginTrace();
+
+ Thread thread("1");
+ WaitableEvent task_complete_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ thread.Start();
+
+ thread.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&TraceWithAllMacroVariants, &task_complete_event));
+ task_complete_event.Wait();
+ thread.Stop();
+
+ EndTraceAndFlush();
+ ValidateAllTraceMacrosCreatedData(trace_parsed_);
+}
+
+// Test that data sent from multiple threads is gathered
+TEST_F(TraceEventTestFixture, DataCapturedManyThreads) {
+ BeginTrace();
+
+ const int num_threads = 4;
+ const int num_events = 4000;
+ Thread* threads[num_threads];
+ WaitableEvent* task_complete_events[num_threads];
+ for (int i = 0; i < num_threads; i++) {
+ threads[i] = new Thread(StringPrintf("Thread %d", i));
+ task_complete_events[i] =
+ new WaitableEvent(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ threads[i]->Start();
+ threads[i]->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&TraceManyInstantEvents, i, num_events,
+ task_complete_events[i]));
+ }
+
+ for (int i = 0; i < num_threads; i++) {
+ task_complete_events[i]->Wait();
+ }
+
+ // Let half of the threads end before flush.
+ for (int i = 0; i < num_threads / 2; i++) {
+ threads[i]->Stop();
+ delete threads[i];
+ delete task_complete_events[i];
+ }
+
+ EndTraceAndFlushInThreadWithMessageLoop();
+ ValidateInstantEventPresentOnEveryThread(trace_parsed_,
+ num_threads, num_events);
+
+ // Let the other half of the threads end after flush.
+ for (int i = num_threads / 2; i < num_threads; i++) {
+ threads[i]->Stop();
+ delete threads[i];
+ delete task_complete_events[i];
+ }
+}
+
+// Test that thread and process names show up in the trace
+TEST_F(TraceEventTestFixture, ThreadNames) {
+ // Create threads before we enable tracing to make sure
+ // that tracelog still captures them.
+ const int kNumThreads = 4;
+ const int kNumEvents = 10;
+ Thread* threads[kNumThreads];
+ PlatformThreadId thread_ids[kNumThreads];
+ for (int i = 0; i < kNumThreads; i++)
+ threads[i] = new Thread(StringPrintf("Thread %d", i));
+
+ // Enable tracing.
+ BeginTrace();
+
+ // Now run some trace code on these threads.
+ WaitableEvent* task_complete_events[kNumThreads];
+ for (int i = 0; i < kNumThreads; i++) {
+ task_complete_events[i] =
+ new WaitableEvent(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ threads[i]->Start();
+ thread_ids[i] = threads[i]->GetThreadId();
+ threads[i]->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&TraceManyInstantEvents, i, kNumEvents,
+ task_complete_events[i]));
+ }
+ for (int i = 0; i < kNumThreads; i++) {
+ task_complete_events[i]->Wait();
+ }
+
+ // Shut things down.
+ for (int i = 0; i < kNumThreads; i++) {
+ threads[i]->Stop();
+ delete threads[i];
+ delete task_complete_events[i];
+ }
+
+ EndTraceAndFlush();
+
+ std::string tmp;
+ int tmp_int;
+ const DictionaryValue* item;
+
+ // Make sure we get thread name metadata.
+ // Note, the test suite may have created a ton of threads.
+ // So, we'll have thread names for threads we didn't create.
+ std::vector<const DictionaryValue*> items =
+ FindTraceEntries(trace_parsed_, "thread_name");
+ for (int i = 0; i < static_cast<int>(items.size()); i++) {
+ item = items[i];
+ ASSERT_TRUE(item);
+ EXPECT_TRUE(item->GetInteger("tid", &tmp_int));
+
+ // See if this thread name is one of the threads we just created
+ for (int j = 0; j < kNumThreads; j++) {
+ if (static_cast<int>(thread_ids[j]) != tmp_int)
+ continue;
+
+ std::string expected_name = StringPrintf("Thread %d", j);
+ EXPECT_TRUE(item->GetString("ph", &tmp) && tmp == "M");
+ EXPECT_TRUE(item->GetInteger("pid", &tmp_int) &&
+ tmp_int == static_cast<int>(base::GetCurrentProcId()));
+ // If the thread name changes or the tid gets reused, the name will be
+ // a comma-separated list of thread names, so look for a substring.
+ EXPECT_TRUE(item->GetString("args.name", &tmp) &&
+ tmp.find(expected_name) != std::string::npos);
+ }
+ }
+}
+
+TEST_F(TraceEventTestFixture, ThreadNameChanges) {
+ BeginTrace();
+
+ PlatformThread::SetName("");
+ TRACE_EVENT_INSTANT0("drink", "water", TRACE_EVENT_SCOPE_THREAD);
+
+ PlatformThread::SetName("cafe");
+ TRACE_EVENT_INSTANT0("drink", "coffee", TRACE_EVENT_SCOPE_THREAD);
+
+ PlatformThread::SetName("shop");
+ // No event here, so won't appear in combined name.
+
+ PlatformThread::SetName("pub");
+ TRACE_EVENT_INSTANT0("drink", "beer", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("drink", "wine", TRACE_EVENT_SCOPE_THREAD);
+
+ PlatformThread::SetName(" bar");
+ TRACE_EVENT_INSTANT0("drink", "whisky", TRACE_EVENT_SCOPE_THREAD);
+
+ EndTraceAndFlush();
+
+ std::vector<const DictionaryValue*> items =
+ FindTraceEntries(trace_parsed_, "thread_name");
+ EXPECT_EQ(1u, items.size());
+ ASSERT_GT(items.size(), 0u);
+ const DictionaryValue* item = items[0];
+ ASSERT_TRUE(item);
+ int tid;
+ EXPECT_TRUE(item->GetInteger("tid", &tid));
+ EXPECT_EQ(PlatformThread::CurrentId(), static_cast<PlatformThreadId>(tid));
+
+ std::string expected_name = "cafe,pub, bar";
+ std::string tmp;
+ EXPECT_TRUE(item->GetString("args.name", &tmp));
+ EXPECT_EQ(expected_name, tmp);
+}
+
+// Test that the disabled trace categories are included/excluded from the
+// trace output correctly.
+TEST_F(TraceEventTestFixture, DisabledCategories) {
+ BeginTrace();
+ TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("cc"), "first",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("included", "first", TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+ {
+ const DictionaryValue* item = nullptr;
+ ListValue& trace_parsed = trace_parsed_;
+ EXPECT_NOT_FIND_("disabled-by-default-cc");
+ EXPECT_FIND_("included");
+ }
+ Clear();
+
+ BeginSpecificTrace("disabled-by-default-cc");
+ TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("cc"), "second",
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("other_included", "second", TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+
+ {
+ const DictionaryValue* item = nullptr;
+ ListValue& trace_parsed = trace_parsed_;
+ EXPECT_FIND_("disabled-by-default-cc");
+ EXPECT_FIND_("other_included");
+ }
+
+ Clear();
+
+ BeginSpecificTrace("other_included");
+ TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("cc") ",other_included",
+ "first", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_INSTANT0("other_included," TRACE_DISABLED_BY_DEFAULT("cc"),
+ "second", TRACE_EVENT_SCOPE_THREAD);
+ EndTraceAndFlush();
+
+ {
+ const DictionaryValue* item = nullptr;
+ ListValue& trace_parsed = trace_parsed_;
+ EXPECT_FIND_("disabled-by-default-cc,other_included");
+ EXPECT_FIND_("other_included,disabled-by-default-cc");
+ }
+}
+
+TEST_F(TraceEventTestFixture, NormallyNoDeepCopy) {
+ // Test that the TRACE_EVENT macros do not deep-copy their string. If they
+ // do so it may indicate a performance regression, but more-over it would
+ // make the DEEP_COPY overloads redundant.
+ std::string name_string("event name");
+
+ BeginTrace();
+ TRACE_EVENT_INSTANT0("category", name_string.c_str(),
+ TRACE_EVENT_SCOPE_THREAD);
+
+ // Modify the string in place (a wholesale reassignment may leave the old
+ // string intact on the heap).
+ name_string[0] = '@';
+
+ EndTraceAndFlush();
+
+ EXPECT_FALSE(FindTraceEntry(trace_parsed_, "event name"));
+ EXPECT_TRUE(FindTraceEntry(trace_parsed_, name_string.c_str()));
+}
+
+TEST_F(TraceEventTestFixture, DeepCopy) {
+ static const char kOriginalName1[] = "name1";
+ static const char kOriginalName2[] = "name2";
+ static const char kOriginalName3[] = "name3";
+ std::string name1(kOriginalName1);
+ std::string name2(kOriginalName2);
+ std::string name3(kOriginalName3);
+ std::string arg1("arg1");
+ std::string arg2("arg2");
+ std::string val1("val1");
+ std::string val2("val2");
+
+ BeginTrace();
+ TRACE_EVENT_COPY_INSTANT0("category", name1.c_str(),
+ TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_COPY_BEGIN1("category", name2.c_str(),
+ arg1.c_str(), 5);
+ TRACE_EVENT_COPY_END2("category", name3.c_str(),
+ arg1.c_str(), val1,
+ arg2.c_str(), val2);
+
+ // As per NormallyNoDeepCopy, modify the strings in place.
+ name1[0] = name2[0] = name3[0] = arg1[0] = arg2[0] = val1[0] = val2[0] = '@';
+
+ EndTraceAndFlush();
+
+ EXPECT_FALSE(FindTraceEntry(trace_parsed_, name1.c_str()));
+ EXPECT_FALSE(FindTraceEntry(trace_parsed_, name2.c_str()));
+ EXPECT_FALSE(FindTraceEntry(trace_parsed_, name3.c_str()));
+
+ const DictionaryValue* entry1 = FindTraceEntry(trace_parsed_, kOriginalName1);
+ const DictionaryValue* entry2 = FindTraceEntry(trace_parsed_, kOriginalName2);
+ const DictionaryValue* entry3 = FindTraceEntry(trace_parsed_, kOriginalName3);
+ ASSERT_TRUE(entry1);
+ ASSERT_TRUE(entry2);
+ ASSERT_TRUE(entry3);
+
+ int i;
+ EXPECT_FALSE(entry2->GetInteger("args.@rg1", &i));
+ EXPECT_TRUE(entry2->GetInteger("args.arg1", &i));
+ EXPECT_EQ(5, i);
+
+ std::string s;
+ EXPECT_TRUE(entry3->GetString("args.arg1", &s));
+ EXPECT_EQ("val1", s);
+ EXPECT_TRUE(entry3->GetString("args.arg2", &s));
+ EXPECT_EQ("val2", s);
+}
+
+// Test that TraceResultBuffer outputs the correct result whether it is added
+// in chunks or added all at once.
+TEST_F(TraceEventTestFixture, TraceResultBuffer) {
+ Clear();
+
+ trace_buffer_.Start();
+ trace_buffer_.AddFragment("bla1");
+ trace_buffer_.AddFragment("bla2");
+ trace_buffer_.AddFragment("bla3,bla4");
+ trace_buffer_.Finish();
+ EXPECT_STREQ(json_output_.json_output.c_str(), "[bla1,bla2,bla3,bla4]");
+
+ Clear();
+
+ trace_buffer_.Start();
+ trace_buffer_.AddFragment("bla1,bla2,bla3,bla4");
+ trace_buffer_.Finish();
+ EXPECT_STREQ(json_output_.json_output.c_str(), "[bla1,bla2,bla3,bla4]");
+}
+
+// Test that trace_event parameters are not evaluated if the tracing
+// system is disabled.
+TEST_F(TraceEventTestFixture, TracingIsLazy) {
+ BeginTrace();
+
+ int a = 0;
+ TRACE_EVENT_INSTANT1("category", "test", TRACE_EVENT_SCOPE_THREAD, "a", a++);
+ EXPECT_EQ(1, a);
+
+ TraceLog::GetInstance()->SetDisabled();
+
+ TRACE_EVENT_INSTANT1("category", "test", TRACE_EVENT_SCOPE_THREAD, "a", a++);
+ EXPECT_EQ(1, a);
+
+ EndTraceAndFlush();
+}
+
+TEST_F(TraceEventTestFixture, TraceEnableDisable) {
+ TraceLog* trace_log = TraceLog::GetInstance();
+ TraceConfig tc_inc_all("*", "");
+ trace_log->SetEnabled(tc_inc_all, TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(trace_log->IsEnabled());
+ trace_log->SetDisabled();
+ EXPECT_FALSE(trace_log->IsEnabled());
+
+ trace_log->SetEnabled(tc_inc_all, TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(trace_log->IsEnabled());
+ const std::vector<std::string> empty;
+ trace_log->SetEnabled(TraceConfig(), TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(trace_log->IsEnabled());
+ trace_log->SetDisabled();
+ EXPECT_FALSE(trace_log->IsEnabled());
+ trace_log->SetDisabled();
+ EXPECT_FALSE(trace_log->IsEnabled());
+}
+
+TEST_F(TraceEventTestFixture, TraceCategoriesAfterNestedEnable) {
+ TraceLog* trace_log = TraceLog::GetInstance();
+ trace_log->SetEnabled(TraceConfig("foo,bar", ""), TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("bar"));
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz"));
+ trace_log->SetEnabled(TraceConfig("foo2", ""), TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo2"));
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz"));
+ // The "" becomes the default catergory set when applied.
+ trace_log->SetEnabled(TraceConfig(), TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz"));
+ EXPECT_STREQ(
+ "",
+ trace_log->GetCurrentTraceConfig().ToCategoryFilterString().c_str());
+ trace_log->SetDisabled();
+ trace_log->SetDisabled();
+ trace_log->SetDisabled();
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo"));
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("baz"));
+
+ trace_log->SetEnabled(TraceConfig("-foo,-bar", ""), TraceLog::RECORDING_MODE);
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz"));
+ trace_log->SetEnabled(TraceConfig("moo", ""), TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("baz"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("moo"));
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("foo"));
+ EXPECT_STREQ(
+ "-foo,-bar",
+ trace_log->GetCurrentTraceConfig().ToCategoryFilterString().c_str());
+ trace_log->SetDisabled();
+ trace_log->SetDisabled();
+
+ // Make sure disabled categories aren't cleared if we set in the second.
+ trace_log->SetEnabled(TraceConfig("disabled-by-default-cc,foo", ""),
+ TraceLog::RECORDING_MODE);
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("bar"));
+ trace_log->SetEnabled(TraceConfig("disabled-by-default-gpu", ""),
+ TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-cc"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-gpu"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("bar"));
+ EXPECT_STREQ(
+ "disabled-by-default-cc,disabled-by-default-gpu",
+ trace_log->GetCurrentTraceConfig().ToCategoryFilterString().c_str());
+ trace_log->SetDisabled();
+ trace_log->SetDisabled();
+}
+
+TEST_F(TraceEventTestFixture, TraceWithDefaultCategoryFilters) {
+ TraceLog* trace_log = TraceLog::GetInstance();
+
+ trace_log->SetEnabled(TraceConfig(), TraceLog::RECORDING_MODE);
+ CheckTraceDefaultCategoryFilters(*trace_log);
+ trace_log->SetDisabled();
+
+ trace_log->SetEnabled(TraceConfig("", ""), TraceLog::RECORDING_MODE);
+ CheckTraceDefaultCategoryFilters(*trace_log);
+ trace_log->SetDisabled();
+
+ trace_log->SetEnabled(TraceConfig("*", ""), TraceLog::RECORDING_MODE);
+ CheckTraceDefaultCategoryFilters(*trace_log);
+ trace_log->SetDisabled();
+
+ trace_log->SetEnabled(TraceConfig(""), TraceLog::RECORDING_MODE);
+ CheckTraceDefaultCategoryFilters(*trace_log);
+ trace_log->SetDisabled();
+}
+
+TEST_F(TraceEventTestFixture, TraceWithDisabledByDefaultCategoryFilters) {
+ TraceLog* trace_log = TraceLog::GetInstance();
+
+ trace_log->SetEnabled(TraceConfig("foo,disabled-by-default-foo", ""),
+ TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-foo"));
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("bar"));
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-bar"));
+ trace_log->SetDisabled();
+
+ // Enabling only the disabled-by-default-* category means the default ones
+ // are also enabled.
+ trace_log->SetEnabled(TraceConfig("disabled-by-default-foo", ""),
+ TraceLog::RECORDING_MODE);
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-foo"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("foo"));
+ EXPECT_TRUE(*trace_log->GetCategoryGroupEnabled("bar"));
+ EXPECT_FALSE(*trace_log->GetCategoryGroupEnabled("disabled-by-default-bar"));
+ trace_log->SetDisabled();
+}
+
+class MyData : public ConvertableToTraceFormat {
+ public:
+ MyData() = default;
+ ~MyData() override = default;
+
+ void AppendAsTraceFormat(std::string* out) const override {
+ out->append("{\"foo\":1}");
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MyData);
+};
+
+TEST_F(TraceEventTestFixture, ConvertableTypes) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+
+ std::unique_ptr<ConvertableToTraceFormat> data(new MyData());
+ std::unique_ptr<ConvertableToTraceFormat> data1(new MyData());
+ std::unique_ptr<ConvertableToTraceFormat> data2(new MyData());
+ TRACE_EVENT1("foo", "bar", "data", std::move(data));
+ TRACE_EVENT2("foo", "baz", "data1", std::move(data1), "data2",
+ std::move(data2));
+
+ // Check that std::unique_ptr<DerivedClassOfConvertable> are properly treated
+ // as
+ // convertable and not accidentally casted to bool.
+ std::unique_ptr<MyData> convertData1(new MyData());
+ std::unique_ptr<MyData> convertData2(new MyData());
+ std::unique_ptr<MyData> convertData3(new MyData());
+ std::unique_ptr<MyData> convertData4(new MyData());
+ TRACE_EVENT2("foo", "string_first", "str", "string value 1", "convert",
+ std::move(convertData1));
+ TRACE_EVENT2("foo", "string_second", "convert", std::move(convertData2),
+ "str", "string value 2");
+ TRACE_EVENT2("foo", "both_conv", "convert1", std::move(convertData3),
+ "convert2", std::move(convertData4));
+ EndTraceAndFlush();
+
+ // One arg version.
+ DictionaryValue* dict = FindNamePhase("bar", "X");
+ ASSERT_TRUE(dict);
+
+ const DictionaryValue* args_dict = nullptr;
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+
+ const Value* value = nullptr;
+ const DictionaryValue* convertable_dict = nullptr;
+ EXPECT_TRUE(args_dict->Get("data", &value));
+ ASSERT_TRUE(value->GetAsDictionary(&convertable_dict));
+
+ int foo_val;
+ EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val));
+ EXPECT_EQ(1, foo_val);
+
+ // Two arg version.
+ dict = FindNamePhase("baz", "X");
+ ASSERT_TRUE(dict);
+
+ args_dict = nullptr;
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+
+ value = nullptr;
+ convertable_dict = nullptr;
+ EXPECT_TRUE(args_dict->Get("data1", &value));
+ ASSERT_TRUE(value->GetAsDictionary(&convertable_dict));
+
+ value = nullptr;
+ convertable_dict = nullptr;
+ EXPECT_TRUE(args_dict->Get("data2", &value));
+ ASSERT_TRUE(value->GetAsDictionary(&convertable_dict));
+
+ // Convertable with other types.
+ dict = FindNamePhase("string_first", "X");
+ ASSERT_TRUE(dict);
+
+ args_dict = nullptr;
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+
+ std::string str_value;
+ EXPECT_TRUE(args_dict->GetString("str", &str_value));
+ EXPECT_STREQ("string value 1", str_value.c_str());
+
+ value = nullptr;
+ convertable_dict = nullptr;
+ foo_val = 0;
+ EXPECT_TRUE(args_dict->Get("convert", &value));
+ ASSERT_TRUE(value->GetAsDictionary(&convertable_dict));
+ EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val));
+ EXPECT_EQ(1, foo_val);
+
+ dict = FindNamePhase("string_second", "X");
+ ASSERT_TRUE(dict);
+
+ args_dict = nullptr;
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+
+ EXPECT_TRUE(args_dict->GetString("str", &str_value));
+ EXPECT_STREQ("string value 2", str_value.c_str());
+
+ value = nullptr;
+ convertable_dict = nullptr;
+ foo_val = 0;
+ EXPECT_TRUE(args_dict->Get("convert", &value));
+ ASSERT_TRUE(value->GetAsDictionary(&convertable_dict));
+ EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val));
+ EXPECT_EQ(1, foo_val);
+
+ dict = FindNamePhase("both_conv", "X");
+ ASSERT_TRUE(dict);
+
+ args_dict = nullptr;
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+
+ value = nullptr;
+ convertable_dict = nullptr;
+ foo_val = 0;
+ EXPECT_TRUE(args_dict->Get("convert1", &value));
+ ASSERT_TRUE(value->GetAsDictionary(&convertable_dict));
+ EXPECT_TRUE(args_dict->Get("convert2", &value));
+ ASSERT_TRUE(value->GetAsDictionary(&convertable_dict));
+}
+
+TEST_F(TraceEventTestFixture, PrimitiveArgs) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+
+ TRACE_EVENT1("foo", "event1", "int_one", 1);
+ TRACE_EVENT1("foo", "event2", "int_neg_ten", -10);
+ TRACE_EVENT1("foo", "event3", "float_one", 1.0f);
+ TRACE_EVENT1("foo", "event4", "float_half", .5f);
+ TRACE_EVENT1("foo", "event5", "float_neghalf", -.5f);
+ TRACE_EVENT1("foo", "event6", "float_infinity",
+ std::numeric_limits<float>::infinity());
+ TRACE_EVENT1("foo", "event6b", "float_neg_infinity",
+ -std::numeric_limits<float>::infinity());
+ TRACE_EVENT1("foo", "event7", "double_nan",
+ std::numeric_limits<double>::quiet_NaN());
+ void* p = nullptr;
+ TRACE_EVENT1("foo", "event8", "pointer_null", p);
+ p = reinterpret_cast<void*>(0xbadf00d);
+ TRACE_EVENT1("foo", "event9", "pointer_badf00d", p);
+ TRACE_EVENT1("foo", "event10", "bool_true", true);
+ TRACE_EVENT1("foo", "event11", "bool_false", false);
+ TRACE_EVENT1("foo", "event12", "time_null",
+ base::Time());
+ TRACE_EVENT1("foo", "event13", "time_one",
+ base::Time::FromInternalValue(1));
+ TRACE_EVENT1("foo", "event14", "timeticks_null",
+ base::TimeTicks());
+ TRACE_EVENT1("foo", "event15", "timeticks_one",
+ base::TimeTicks::FromInternalValue(1));
+ EndTraceAndFlush();
+
+ const DictionaryValue* args_dict = nullptr;
+ DictionaryValue* dict = nullptr;
+ const Value* value = nullptr;
+ std::string str_value;
+ int int_value;
+ double double_value;
+ bool bool_value;
+
+ dict = FindNamePhase("event1", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetInteger("int_one", &int_value));
+ EXPECT_EQ(1, int_value);
+
+ dict = FindNamePhase("event2", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetInteger("int_neg_ten", &int_value));
+ EXPECT_EQ(-10, int_value);
+
+ // 1f must be serlized to JSON as "1.0" in order to be a double, not an int.
+ dict = FindNamePhase("event3", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->Get("float_one", &value));
+ EXPECT_TRUE(value->is_double());
+ EXPECT_TRUE(value->GetAsDouble(&double_value));
+ EXPECT_EQ(1, double_value);
+
+ // .5f must be serlized to JSON as "0.5".
+ dict = FindNamePhase("event4", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->Get("float_half", &value));
+ EXPECT_TRUE(value->is_double());
+ EXPECT_TRUE(value->GetAsDouble(&double_value));
+ EXPECT_EQ(0.5, double_value);
+
+ // -.5f must be serlized to JSON as "-0.5".
+ dict = FindNamePhase("event5", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->Get("float_neghalf", &value));
+ EXPECT_TRUE(value->is_double());
+ EXPECT_TRUE(value->GetAsDouble(&double_value));
+ EXPECT_EQ(-0.5, double_value);
+
+ // Infinity is serialized to JSON as a string.
+ dict = FindNamePhase("event6", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetString("float_infinity", &str_value));
+ EXPECT_STREQ("Infinity", str_value.c_str());
+ dict = FindNamePhase("event6b", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetString("float_neg_infinity", &str_value));
+ EXPECT_STREQ("-Infinity", str_value.c_str());
+
+ // NaN is serialized to JSON as a string.
+ dict = FindNamePhase("event7", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetString("double_nan", &str_value));
+ EXPECT_STREQ("NaN", str_value.c_str());
+
+ // NULL pointers should be serialized as "0x0".
+ dict = FindNamePhase("event8", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetString("pointer_null", &str_value));
+ EXPECT_STREQ("0x0", str_value.c_str());
+
+ // Other pointers should be serlized as a hex string.
+ dict = FindNamePhase("event9", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetString("pointer_badf00d", &str_value));
+ EXPECT_STREQ("0xbadf00d", str_value.c_str());
+
+ dict = FindNamePhase("event10", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetBoolean("bool_true", &bool_value));
+ EXPECT_TRUE(bool_value);
+
+ dict = FindNamePhase("event11", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetBoolean("bool_false", &bool_value));
+ EXPECT_FALSE(bool_value);
+
+ dict = FindNamePhase("event12", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetInteger("time_null", &int_value));
+ EXPECT_EQ(0, int_value);
+
+ dict = FindNamePhase("event13", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetInteger("time_one", &int_value));
+ EXPECT_EQ(1, int_value);
+
+ dict = FindNamePhase("event14", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetInteger("timeticks_null", &int_value));
+ EXPECT_EQ(0, int_value);
+
+ dict = FindNamePhase("event15", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetInteger("timeticks_one", &int_value));
+ EXPECT_EQ(1, int_value);
+}
+
+TEST_F(TraceEventTestFixture, NameIsEscaped) {
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(kRecordAllCategoryFilter, ""),
+ TraceLog::RECORDING_MODE);
+ TRACE_EVENT0("category", "name\\with\\backspaces");
+ EndTraceAndFlush();
+
+ EXPECT_TRUE(FindMatchingValue("cat", "category"));
+ EXPECT_TRUE(FindMatchingValue("name", "name\\with\\backspaces"));
+}
+
+namespace {
+
+bool IsArgNameWhitelisted(const char* arg_name) {
+ return base::MatchPattern(arg_name, "granular_arg_whitelisted");
+}
+
+bool IsTraceEventArgsWhitelisted(const char* category_group_name,
+ const char* event_name,
+ ArgumentNameFilterPredicate* arg_filter) {
+ if (base::MatchPattern(category_group_name, "toplevel") &&
+ base::MatchPattern(event_name, "*")) {
+ return true;
+ }
+
+ if (base::MatchPattern(category_group_name, "benchmark") &&
+ base::MatchPattern(event_name, "granularly_whitelisted")) {
+ *arg_filter = base::Bind(&IsArgNameWhitelisted);
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+TEST_F(TraceEventTestFixture, ArgsWhitelisting) {
+ TraceLog::GetInstance()->SetArgumentFilterPredicate(
+ base::Bind(&IsTraceEventArgsWhitelisted));
+
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig(kRecordAllCategoryFilter, "enable-argument-filter"),
+ TraceLog::RECORDING_MODE);
+
+ TRACE_EVENT1("toplevel", "event1", "int_one", 1);
+ TRACE_EVENT1("whitewashed", "event2", "int_two", 1);
+
+ TRACE_EVENT2("benchmark", "granularly_whitelisted",
+ "granular_arg_whitelisted", "whitelisted_value",
+ "granular_arg_blacklisted", "blacklisted_value");
+
+ EndTraceAndFlush();
+
+ const DictionaryValue* args_dict = nullptr;
+ DictionaryValue* dict = nullptr;
+ int int_value;
+
+ dict = FindNamePhase("event1", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_TRUE(args_dict->GetInteger("int_one", &int_value));
+ EXPECT_EQ(1, int_value);
+
+ dict = FindNamePhase("event2", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+ EXPECT_FALSE(args_dict->GetInteger("int_two", &int_value));
+
+ std::string args_string;
+ EXPECT_TRUE(dict->GetString("args", &args_string));
+ EXPECT_EQ(args_string, "__stripped__");
+
+ dict = FindNamePhase("granularly_whitelisted", "X");
+ ASSERT_TRUE(dict);
+ dict->GetDictionary("args", &args_dict);
+ ASSERT_TRUE(args_dict);
+
+ EXPECT_TRUE(args_dict->GetString("granular_arg_whitelisted", &args_string));
+ EXPECT_EQ(args_string, "whitelisted_value");
+
+ EXPECT_TRUE(args_dict->GetString("granular_arg_blacklisted", &args_string));
+ EXPECT_EQ(args_string, "__stripped__");
+}
+
+TEST_F(TraceEventTestFixture, TraceBufferVectorReportFull) {
+ TraceLog* trace_log = TraceLog::GetInstance();
+ trace_log->SetEnabled(
+ TraceConfig(kRecordAllCategoryFilter, ""), TraceLog::RECORDING_MODE);
+ trace_log->logged_events_.reset(
+ TraceBuffer::CreateTraceBufferVectorOfSize(100));
+ do {
+ TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0(
+ "all", "with_timestamp", 0, 0, TimeTicks::Now());
+ TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0(
+ "all", "with_timestamp", 0, 0, TimeTicks::Now());
+ } while (!trace_log->BufferIsFull());
+
+ EndTraceAndFlush();
+
+ const DictionaryValue* trace_full_metadata = nullptr;
+
+ trace_full_metadata = FindTraceEntry(trace_parsed_,
+ "overflowed_at_ts");
+ std::string phase;
+ double buffer_limit_reached_timestamp = 0;
+
+ EXPECT_TRUE(trace_full_metadata);
+ EXPECT_TRUE(trace_full_metadata->GetString("ph", &phase));
+ EXPECT_EQ("M", phase);
+ EXPECT_TRUE(trace_full_metadata->GetDouble(
+ "args.overflowed_at_ts", &buffer_limit_reached_timestamp));
+ EXPECT_DOUBLE_EQ(
+ static_cast<double>(
+ trace_log->buffer_limit_reached_timestamp_.ToInternalValue()),
+ buffer_limit_reached_timestamp);
+
+ // Test that buffer_limit_reached_timestamp's value is between the timestamp
+ // of the last trace event and current time.
+ DropTracedMetadataRecords();
+ const DictionaryValue* last_trace_event = nullptr;
+ double last_trace_event_timestamp = 0;
+ EXPECT_TRUE(trace_parsed_.GetDictionary(trace_parsed_.GetSize() - 1,
+ &last_trace_event));
+ EXPECT_TRUE(last_trace_event->GetDouble("ts", &last_trace_event_timestamp));
+ EXPECT_LE(last_trace_event_timestamp, buffer_limit_reached_timestamp);
+ EXPECT_LE(buffer_limit_reached_timestamp,
+ trace_log->OffsetNow().ToInternalValue());
+}
+
+TEST_F(TraceEventTestFixture, TraceBufferRingBufferGetReturnChunk) {
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig(kRecordAllCategoryFilter, RECORD_CONTINUOUSLY),
+ TraceLog::RECORDING_MODE);
+ TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer();
+ size_t capacity = buffer->Capacity();
+ size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize;
+ uint32_t last_seq = 0;
+ size_t chunk_index;
+ EXPECT_EQ(0u, buffer->Size());
+
+ std::unique_ptr<TraceBufferChunk* []> chunks(
+ new TraceBufferChunk*[num_chunks]);
+ for (size_t i = 0; i < num_chunks; ++i) {
+ chunks[i] = buffer->GetChunk(&chunk_index).release();
+ EXPECT_TRUE(chunks[i]);
+ EXPECT_EQ(i, chunk_index);
+ EXPECT_GT(chunks[i]->seq(), last_seq);
+ EXPECT_EQ((i + 1) * TraceBufferChunk::kTraceBufferChunkSize,
+ buffer->Size());
+ last_seq = chunks[i]->seq();
+ }
+
+ // Ring buffer is never full.
+ EXPECT_FALSE(buffer->IsFull());
+
+ // Return all chunks in original order.
+ for (size_t i = 0; i < num_chunks; ++i)
+ buffer->ReturnChunk(i, std::unique_ptr<TraceBufferChunk>(chunks[i]));
+
+ // Should recycle the chunks in the returned order.
+ for (size_t i = 0; i < num_chunks; ++i) {
+ chunks[i] = buffer->GetChunk(&chunk_index).release();
+ EXPECT_TRUE(chunks[i]);
+ EXPECT_EQ(i, chunk_index);
+ EXPECT_GT(chunks[i]->seq(), last_seq);
+ last_seq = chunks[i]->seq();
+ }
+
+ // Return all chunks in reverse order.
+ for (size_t i = 0; i < num_chunks; ++i) {
+ buffer->ReturnChunk(num_chunks - i - 1, std::unique_ptr<TraceBufferChunk>(
+ chunks[num_chunks - i - 1]));
+ }
+
+ // Should recycle the chunks in the returned order.
+ for (size_t i = 0; i < num_chunks; ++i) {
+ chunks[i] = buffer->GetChunk(&chunk_index).release();
+ EXPECT_TRUE(chunks[i]);
+ EXPECT_EQ(num_chunks - i - 1, chunk_index);
+ EXPECT_GT(chunks[i]->seq(), last_seq);
+ last_seq = chunks[i]->seq();
+ }
+
+ for (size_t i = 0; i < num_chunks; ++i)
+ buffer->ReturnChunk(i, std::unique_ptr<TraceBufferChunk>(chunks[i]));
+
+ TraceLog::GetInstance()->SetDisabled();
+}
+
+TEST_F(TraceEventTestFixture, TraceBufferRingBufferHalfIteration) {
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig(kRecordAllCategoryFilter, RECORD_CONTINUOUSLY),
+ TraceLog::RECORDING_MODE);
+ TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer();
+ size_t capacity = buffer->Capacity();
+ size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize;
+ size_t chunk_index;
+ EXPECT_EQ(0u, buffer->Size());
+ EXPECT_FALSE(buffer->NextChunk());
+
+ size_t half_chunks = num_chunks / 2;
+ std::unique_ptr<TraceBufferChunk* []> chunks(
+ new TraceBufferChunk*[half_chunks]);
+
+ for (size_t i = 0; i < half_chunks; ++i) {
+ chunks[i] = buffer->GetChunk(&chunk_index).release();
+ EXPECT_TRUE(chunks[i]);
+ EXPECT_EQ(i, chunk_index);
+ }
+ for (size_t i = 0; i < half_chunks; ++i)
+ buffer->ReturnChunk(i, std::unique_ptr<TraceBufferChunk>(chunks[i]));
+
+ for (size_t i = 0; i < half_chunks; ++i)
+ EXPECT_EQ(chunks[i], buffer->NextChunk());
+ EXPECT_FALSE(buffer->NextChunk());
+ TraceLog::GetInstance()->SetDisabled();
+}
+
+TEST_F(TraceEventTestFixture, TraceBufferRingBufferFullIteration) {
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig(kRecordAllCategoryFilter, RECORD_CONTINUOUSLY),
+ TraceLog::RECORDING_MODE);
+ TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer();
+ size_t capacity = buffer->Capacity();
+ size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize;
+ size_t chunk_index;
+ EXPECT_EQ(0u, buffer->Size());
+ EXPECT_FALSE(buffer->NextChunk());
+
+ std::unique_ptr<TraceBufferChunk* []> chunks(
+ new TraceBufferChunk*[num_chunks]);
+
+ for (size_t i = 0; i < num_chunks; ++i) {
+ chunks[i] = buffer->GetChunk(&chunk_index).release();
+ EXPECT_TRUE(chunks[i]);
+ EXPECT_EQ(i, chunk_index);
+ }
+ for (size_t i = 0; i < num_chunks; ++i)
+ buffer->ReturnChunk(i, std::unique_ptr<TraceBufferChunk>(chunks[i]));
+
+ for (size_t i = 0; i < num_chunks; ++i)
+ EXPECT_TRUE(chunks[i] == buffer->NextChunk());
+ EXPECT_FALSE(buffer->NextChunk());
+ TraceLog::GetInstance()->SetDisabled();
+}
+
+TEST_F(TraceEventTestFixture, TraceRecordAsMuchAsPossibleMode) {
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig(kRecordAllCategoryFilter, RECORD_AS_MUCH_AS_POSSIBLE),
+ TraceLog::RECORDING_MODE);
+ TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer();
+ EXPECT_EQ(512000000UL, buffer->Capacity());
+ TraceLog::GetInstance()->SetDisabled();
+}
+
+void BlockUntilStopped(WaitableEvent* task_start_event,
+ WaitableEvent* task_stop_event) {
+ task_start_event->Signal();
+ task_stop_event->Wait();
+}
+
+TEST_F(TraceEventTestFixture, SetCurrentThreadBlocksMessageLoopBeforeTracing) {
+ BeginTrace();
+
+ Thread thread("1");
+ WaitableEvent task_complete_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ thread.Start();
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&TraceLog::SetCurrentThreadBlocksMessageLoop,
+ Unretained(TraceLog::GetInstance())));
+
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&TraceWithAllMacroVariants, &task_complete_event));
+ task_complete_event.Wait();
+
+ WaitableEvent task_start_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent task_stop_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ thread.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(&BlockUntilStopped, &task_start_event, &task_stop_event));
+ task_start_event.Wait();
+
+ EndTraceAndFlush();
+ ValidateAllTraceMacrosCreatedData(trace_parsed_);
+
+ task_stop_event.Signal();
+ thread.Stop();
+}
+
+TEST_F(TraceEventTestFixture, ConvertTraceConfigToInternalOptions) {
+ TraceLog* trace_log = TraceLog::GetInstance();
+ EXPECT_EQ(TraceLog::kInternalRecordUntilFull,
+ trace_log->GetInternalOptionsFromTraceConfig(
+ TraceConfig(kRecordAllCategoryFilter, RECORD_UNTIL_FULL)));
+
+ EXPECT_EQ(TraceLog::kInternalRecordContinuously,
+ trace_log->GetInternalOptionsFromTraceConfig(
+ TraceConfig(kRecordAllCategoryFilter, RECORD_CONTINUOUSLY)));
+
+ EXPECT_EQ(TraceLog::kInternalEchoToConsole,
+ trace_log->GetInternalOptionsFromTraceConfig(
+ TraceConfig(kRecordAllCategoryFilter, ECHO_TO_CONSOLE)));
+
+ EXPECT_EQ(TraceLog::kInternalEchoToConsole,
+ trace_log->GetInternalOptionsFromTraceConfig(
+ TraceConfig("*", "trace-to-console,enable-systrace")));
+}
+
+void SetBlockingFlagAndBlockUntilStopped(WaitableEvent* task_start_event,
+ WaitableEvent* task_stop_event) {
+ TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop();
+ BlockUntilStopped(task_start_event, task_stop_event);
+}
+
+TEST_F(TraceEventTestFixture, SetCurrentThreadBlocksMessageLoopAfterTracing) {
+ BeginTrace();
+
+ Thread thread("1");
+ WaitableEvent task_complete_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ thread.Start();
+
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&TraceWithAllMacroVariants, &task_complete_event));
+ task_complete_event.Wait();
+
+ WaitableEvent task_start_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent task_stop_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ thread.task_runner()->PostTask(FROM_HERE,
+ BindOnce(&SetBlockingFlagAndBlockUntilStopped,
+ &task_start_event, &task_stop_event));
+ task_start_event.Wait();
+
+ EndTraceAndFlush();
+ ValidateAllTraceMacrosCreatedData(trace_parsed_);
+
+ task_stop_event.Signal();
+ thread.Stop();
+}
+
+TEST_F(TraceEventTestFixture, ThreadOnceBlocking) {
+ BeginTrace();
+
+ Thread thread("1");
+ WaitableEvent task_complete_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ thread.Start();
+
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&TraceWithAllMacroVariants, &task_complete_event));
+ task_complete_event.Wait();
+
+ WaitableEvent task_start_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent task_stop_event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ thread.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(&BlockUntilStopped, &task_start_event, &task_stop_event));
+ task_start_event.Wait();
+
+ // The thread will timeout in this flush.
+ EndTraceAndFlushInThreadWithMessageLoop();
+ Clear();
+
+ // Let the thread's message loop continue to spin.
+ task_stop_event.Signal();
+
+ // The following sequence ensures that the FlushCurrentThread task has been
+ // executed in the thread before continuing.
+ thread.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(&BlockUntilStopped, &task_start_event, &task_stop_event));
+ task_start_event.Wait();
+ task_stop_event.Signal();
+ Clear();
+
+ // TraceLog should discover the generation mismatch and recover the thread
+ // local buffer for the thread without any error.
+ BeginTrace();
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&TraceWithAllMacroVariants, &task_complete_event));
+ task_complete_event.Wait();
+ EndTraceAndFlushInThreadWithMessageLoop();
+ ValidateAllTraceMacrosCreatedData(trace_parsed_);
+}
+
+std::string* g_log_buffer = nullptr;
+bool MockLogMessageHandler(int, const char*, int, size_t,
+ const std::string& str) {
+ if (!g_log_buffer)
+ g_log_buffer = new std::string();
+ g_log_buffer->append(str);
+ return false;
+}
+
+TEST_F(TraceEventTestFixture, EchoToConsole) {
+ logging::LogMessageHandlerFunction old_log_message_handler =
+ logging::GetLogMessageHandler();
+ logging::SetLogMessageHandler(MockLogMessageHandler);
+
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig(kRecordAllCategoryFilter, ECHO_TO_CONSOLE),
+ TraceLog::RECORDING_MODE);
+ TRACE_EVENT_BEGIN0("a", "begin_end");
+ {
+ TRACE_EVENT0("b", "duration");
+ TRACE_EVENT0("b1", "duration1");
+ }
+ TRACE_EVENT_INSTANT0("c", "instant", TRACE_EVENT_SCOPE_GLOBAL);
+ TRACE_EVENT_END0("a", "begin_end");
+
+ EXPECT_NE(std::string::npos, g_log_buffer->find("begin_end[a]\x1b"));
+ EXPECT_NE(std::string::npos, g_log_buffer->find("| duration[b]\x1b"));
+ EXPECT_NE(std::string::npos, g_log_buffer->find("| | duration1[b1]\x1b"));
+ EXPECT_NE(std::string::npos, g_log_buffer->find("| | duration1[b1] ("));
+ EXPECT_NE(std::string::npos, g_log_buffer->find("| duration[b] ("));
+ EXPECT_NE(std::string::npos, g_log_buffer->find("| instant[c]\x1b"));
+ EXPECT_NE(std::string::npos, g_log_buffer->find("begin_end[a] ("));
+
+ EndTraceAndFlush();
+ delete g_log_buffer;
+ logging::SetLogMessageHandler(old_log_message_handler);
+ g_log_buffer = nullptr;
+}
+
+bool LogMessageHandlerWithTraceEvent(int, const char*, int, size_t,
+ const std::string&) {
+ TRACE_EVENT0("log", "trace_event");
+ return false;
+}
+
+TEST_F(TraceEventTestFixture, EchoToConsoleTraceEventRecursion) {
+ logging::LogMessageHandlerFunction old_log_message_handler =
+ logging::GetLogMessageHandler();
+ logging::SetLogMessageHandler(LogMessageHandlerWithTraceEvent);
+
+ TraceLog::GetInstance()->SetEnabled(
+ TraceConfig(kRecordAllCategoryFilter, ECHO_TO_CONSOLE),
+ TraceLog::RECORDING_MODE);
+ {
+ // This should not cause deadlock or infinite recursion.
+ TRACE_EVENT0("b", "duration");
+ }
+
+ EndTraceAndFlush();
+ logging::SetLogMessageHandler(old_log_message_handler);
+}
+
+TEST_F(TraceEventTestFixture, TimeOffset) {
+ BeginTrace();
+ // Let TraceLog timer start from 0.
+ TimeDelta time_offset = TimeTicks::Now() - TimeTicks();
+ TraceLog::GetInstance()->SetTimeOffset(time_offset);
+
+ {
+ TRACE_EVENT0("all", "duration1");
+ TRACE_EVENT0("all", "duration2");
+ }
+ TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0(
+ "all", "with_timestamp", 0, 0, TimeTicks::Now());
+ TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0(
+ "all", "with_timestamp", 0, 0, TimeTicks::Now());
+
+ EndTraceAndFlush();
+ DropTracedMetadataRecords();
+
+ double end_time = static_cast<double>(
+ (TimeTicks::Now() - time_offset).ToInternalValue());
+ double last_timestamp = 0;
+ for (size_t i = 0; i < trace_parsed_.GetSize(); ++i) {
+ const DictionaryValue* item;
+ EXPECT_TRUE(trace_parsed_.GetDictionary(i, &item));
+ double timestamp;
+ EXPECT_TRUE(item->GetDouble("ts", &timestamp));
+ EXPECT_GE(timestamp, last_timestamp);
+ EXPECT_LE(timestamp, end_time);
+ last_timestamp = timestamp;
+ }
+}
+
+TEST_F(TraceEventTestFixture, TraceFilteringMode) {
+ const char config_json[] =
+ "{"
+ " \"event_filters\": ["
+ " {"
+ " \"filter_predicate\": \"testing_predicate\", "
+ " \"included_categories\": [\"*\"]"
+ " }"
+ " ]"
+ "}";
+
+ // Run RECORDING_MODE within FILTERING_MODE:
+ TestEventFilter::HitsCounter filter_hits_counter;
+ TestEventFilter::set_filter_return_value(true);
+ TraceLog::GetInstance()->SetFilterFactoryForTesting(TestEventFilter::Factory);
+
+ // Only filtering mode is enabled with test filters.
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(config_json),
+ TraceLog::FILTERING_MODE);
+ EXPECT_EQ(TraceLog::FILTERING_MODE, TraceLog::GetInstance()->enabled_modes());
+ {
+ void* ptr = this;
+ TRACE_EVENT0("c0", "name0");
+ TRACE_EVENT_ASYNC_BEGIN0("c1", "name1", ptr);
+ TRACE_EVENT_INSTANT0("c0", "name0", TRACE_EVENT_SCOPE_THREAD);
+ TRACE_EVENT_ASYNC_END0("c1", "name1", ptr);
+ }
+
+ // Recording mode is enabled when filtering mode is turned on.
+ TraceLog::GetInstance()->SetEnabled(TraceConfig("", ""),
+ TraceLog::RECORDING_MODE);
+ EXPECT_EQ(TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE,
+ TraceLog::GetInstance()->enabled_modes());
+ {
+ TRACE_EVENT0("c2", "name2");
+ }
+ // Only recording mode is disabled and filtering mode will continue to run.
+ TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE);
+ EXPECT_EQ(TraceLog::FILTERING_MODE, TraceLog::GetInstance()->enabled_modes());
+
+ {
+ TRACE_EVENT0("c0", "name0");
+ }
+ // Filtering mode is disabled and no tracing mode should be enabled.
+ TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE);
+ EXPECT_EQ(0, TraceLog::GetInstance()->enabled_modes());
+
+ EndTraceAndFlush();
+ EXPECT_FALSE(FindMatchingValue("cat", "c0"));
+ EXPECT_FALSE(FindMatchingValue("cat", "c1"));
+ EXPECT_FALSE(FindMatchingValue("name", "name0"));
+ EXPECT_FALSE(FindMatchingValue("name", "name1"));
+ EXPECT_TRUE(FindMatchingValue("cat", "c2"));
+ EXPECT_TRUE(FindMatchingValue("name", "name2"));
+ EXPECT_EQ(6u, filter_hits_counter.filter_trace_event_hit_count);
+ EXPECT_EQ(3u, filter_hits_counter.end_event_hit_count);
+ Clear();
+ filter_hits_counter.Reset();
+
+ // Run FILTERING_MODE within RECORDING_MODE:
+ // Only recording mode is enabled and all events must be recorded.
+ TraceLog::GetInstance()->SetEnabled(TraceConfig("", ""),
+ TraceLog::RECORDING_MODE);
+ EXPECT_EQ(TraceLog::RECORDING_MODE, TraceLog::GetInstance()->enabled_modes());
+ {
+ TRACE_EVENT0("c0", "name0");
+ }
+
+ // Filtering mode is also enabled and all events must be filtered-out.
+ TestEventFilter::set_filter_return_value(false);
+ TraceLog::GetInstance()->SetEnabled(TraceConfig(config_json),
+ TraceLog::FILTERING_MODE);
+ EXPECT_EQ(TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE,
+ TraceLog::GetInstance()->enabled_modes());
+ {
+ TRACE_EVENT0("c1", "name1");
+ }
+ // Only filtering mode is disabled and recording mode should continue to run
+ // with all events being recorded.
+ TraceLog::GetInstance()->SetDisabled(TraceLog::FILTERING_MODE);
+ EXPECT_EQ(TraceLog::RECORDING_MODE, TraceLog::GetInstance()->enabled_modes());
+
+ {
+ TRACE_EVENT0("c2", "name2");
+ }
+ // Recording mode is disabled and no tracing mode should be enabled.
+ TraceLog::GetInstance()->SetDisabled(TraceLog::RECORDING_MODE);
+ EXPECT_EQ(0, TraceLog::GetInstance()->enabled_modes());
+
+ EndTraceAndFlush();
+ EXPECT_TRUE(FindMatchingValue("cat", "c0"));
+ EXPECT_TRUE(FindMatchingValue("cat", "c2"));
+ EXPECT_TRUE(FindMatchingValue("name", "name0"));
+ EXPECT_TRUE(FindMatchingValue("name", "name2"));
+ EXPECT_FALSE(FindMatchingValue("cat", "c1"));
+ EXPECT_FALSE(FindMatchingValue("name", "name1"));
+ EXPECT_EQ(1u, filter_hits_counter.filter_trace_event_hit_count);
+ EXPECT_EQ(1u, filter_hits_counter.end_event_hit_count);
+ Clear();
+}
+
+TEST_F(TraceEventTestFixture, EventFiltering) {
+ const char config_json[] =
+ "{"
+ " \"included_categories\": ["
+ " \"filtered_cat\","
+ " \"unfiltered_cat\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("unfiltered_cat") "\"],"
+ " \"event_filters\": ["
+ " {"
+ " \"filter_predicate\": \"testing_predicate\", "
+ " \"included_categories\": ["
+ " \"filtered_cat\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\"]"
+ " }"
+ " "
+ " ]"
+ "}";
+
+ TestEventFilter::HitsCounter filter_hits_counter;
+ TestEventFilter::set_filter_return_value(true);
+ TraceLog::GetInstance()->SetFilterFactoryForTesting(TestEventFilter::Factory);
+
+ TraceConfig trace_config(config_json);
+ TraceLog::GetInstance()->SetEnabled(
+ trace_config, TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE);
+ ASSERT_TRUE(TraceLog::GetInstance()->IsEnabled());
+
+ TRACE_EVENT0("filtered_cat", "a snake");
+ TRACE_EVENT0("filtered_cat", "a mushroom");
+ TRACE_EVENT0("unfiltered_cat", "a horse");
+
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("filtered_cat"), "a dog");
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("unfiltered_cat"), "a pony");
+
+ // This is scoped so we can test the end event being filtered.
+ { TRACE_EVENT0("filtered_cat", "another cat whoa"); }
+
+ EndTraceAndFlush();
+
+ EXPECT_EQ(4u, filter_hits_counter.filter_trace_event_hit_count);
+ EXPECT_EQ(1u, filter_hits_counter.end_event_hit_count);
+}
+
+TEST_F(TraceEventTestFixture, EventWhitelistFiltering) {
+ std::string config_json = StringPrintf(
+ "{"
+ " \"included_categories\": ["
+ " \"filtered_cat\","
+ " \"unfiltered_cat\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\"],"
+ " \"event_filters\": ["
+ " {"
+ " \"filter_predicate\": \"%s\", "
+ " \"included_categories\": ["
+ " \"filtered_cat\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("*") "\"], "
+ " \"filter_args\": {"
+ " \"event_name_whitelist\": [\"a snake\", \"a dog\"]"
+ " }"
+ " }"
+ " "
+ " ]"
+ "}",
+ EventNameFilter::kName);
+
+ TraceConfig trace_config(config_json);
+ TraceLog::GetInstance()->SetEnabled(
+ trace_config, TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE);
+ EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled());
+
+ TRACE_EVENT0("filtered_cat", "a snake");
+ TRACE_EVENT0("filtered_cat", "a mushroom");
+ TRACE_EVENT0("unfiltered_cat", "a cat");
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("filtered_cat"), "a dog");
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("filtered_cat"), "a pony");
+
+ EndTraceAndFlush();
+
+ EXPECT_TRUE(FindMatchingValue("name", "a snake"));
+ EXPECT_FALSE(FindMatchingValue("name", "a mushroom"));
+ EXPECT_TRUE(FindMatchingValue("name", "a cat"));
+ EXPECT_TRUE(FindMatchingValue("name", "a dog"));
+ EXPECT_FALSE(FindMatchingValue("name", "a pony"));
+}
+
+TEST_F(TraceEventTestFixture, HeapProfilerFiltering) {
+ std::string config_json = StringPrintf(
+ "{"
+ " \"included_categories\": ["
+ " \"filtered_cat\","
+ " \"unfiltered_cat\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("unfiltered_cat") "\"],"
+ " \"excluded_categories\": [\"excluded_cat\"],"
+ " \"event_filters\": ["
+ " {"
+ " \"filter_predicate\": \"%s\", "
+ " \"included_categories\": ["
+ " \"*\","
+ " \"" TRACE_DISABLED_BY_DEFAULT("filtered_cat") "\"]"
+ " }"
+ " ]"
+ "}",
+ HeapProfilerEventFilter::kName);
+
+ TraceConfig trace_config(config_json);
+ TraceLog::GetInstance()->SetEnabled(
+ trace_config, TraceLog::RECORDING_MODE | TraceLog::FILTERING_MODE);
+ EXPECT_TRUE(TraceLog::GetInstance()->IsEnabled());
+
+ TRACE_EVENT0("filtered_cat", "a snake");
+ TRACE_EVENT0("excluded_cat", "a mushroom");
+ TRACE_EVENT0("unfiltered_cat", "a cat");
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("filtered_cat"), "a dog");
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("unfiltered_cat"), "a pony");
+
+ EndTraceAndFlush();
+
+ // The predicate should not change behavior of the trace events.
+ EXPECT_TRUE(FindMatchingValue("name", "a snake"));
+ EXPECT_FALSE(FindMatchingValue("name", "a mushroom"));
+ EXPECT_TRUE(FindMatchingValue("name", "a cat"));
+ EXPECT_TRUE(FindMatchingValue("name", "a dog"));
+ EXPECT_TRUE(FindMatchingValue("name", "a pony"));
+}
+
+TEST_F(TraceEventTestFixture, ClockSyncEventsAreAlwaysAddedToTrace) {
+ BeginSpecificTrace("-*");
+ TRACE_EVENT_CLOCK_SYNC_RECEIVER(1);
+ EndTraceAndFlush();
+ EXPECT_TRUE(FindNamePhase("clock_sync", "c"));
+}
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
new file mode 100644
index 0000000000..4eb6958472
--- /dev/null
+++ b/base/trace_event/trace_log.cc
@@ -0,0 +1,1787 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_log.h"
+
+#include <algorithm>
+#include <cmath>
+#include <memory>
+#include <utility>
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/leak_annotations.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/no_destructor.h"
+#include "base/process/process_info.h"
+#include "base/process/process_metrics.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_id_name_manager.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "base/trace_event/category_registry.h"
+#include "base/trace_event/event_name_filter.h"
+#include "base/trace_event/heap_profiler.h"
+#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
+#include "base/trace_event/heap_profiler_event_filter.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/trace_buffer.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/trace_event/trace_event_etw_export_win.h"
+#endif
+
+#if defined(OS_ANDROID)
+// The linker assigns the virtual address of the start of current library to
+// this symbol.
+extern char __executable_start;
+#endif
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+// Controls the number of trace events we will buffer in-memory
+// before throwing them away.
+const size_t kTraceBufferChunkSize = TraceBufferChunk::kTraceBufferChunkSize;
+
+const size_t kTraceEventVectorBigBufferChunks =
+ 512000000 / kTraceBufferChunkSize;
+static_assert(
+ kTraceEventVectorBigBufferChunks <= TraceBufferChunk::kMaxChunkIndex,
+ "Too many big buffer chunks");
+const size_t kTraceEventVectorBufferChunks = 256000 / kTraceBufferChunkSize;
+static_assert(
+ kTraceEventVectorBufferChunks <= TraceBufferChunk::kMaxChunkIndex,
+ "Too many vector buffer chunks");
+const size_t kTraceEventRingBufferChunks = kTraceEventVectorBufferChunks / 4;
+
+// ECHO_TO_CONSOLE needs a small buffer to hold the unfinished COMPLETE events.
+const size_t kEchoToConsoleTraceEventBufferChunks = 256;
+
+const size_t kTraceEventBufferSizeInBytes = 100 * 1024;
+const int kThreadFlushTimeoutMs = 3000;
+
+TraceLog* g_trace_log_for_testing = nullptr;
+
+#define MAX_TRACE_EVENT_FILTERS 32
+
+// List of TraceEventFilter objects from the most recent tracing session.
+std::vector<std::unique_ptr<TraceEventFilter>>& GetCategoryGroupFilters() {
+ static auto* filters = new std::vector<std::unique_ptr<TraceEventFilter>>();
+ return *filters;
+}
+
+ThreadTicks ThreadNow() {
+ return ThreadTicks::IsSupported()
+ ? base::subtle::ThreadTicksNowIgnoringOverride()
+ : ThreadTicks();
+}
+
+template <typename T>
+void InitializeMetadataEvent(TraceEvent* trace_event,
+ int thread_id,
+ const char* metadata_name,
+ const char* arg_name,
+ const T& value) {
+ if (!trace_event)
+ return;
+
+ int num_args = 1;
+ unsigned char arg_type;
+ unsigned long long arg_value;
+ ::trace_event_internal::SetTraceValue(value, &arg_type, &arg_value);
+ trace_event->Initialize(
+ thread_id,
+ TimeTicks(),
+ ThreadTicks(),
+ TRACE_EVENT_PHASE_METADATA,
+ CategoryRegistry::kCategoryMetadata->state_ptr(),
+ metadata_name,
+ trace_event_internal::kGlobalScope, // scope
+ trace_event_internal::kNoId, // id
+ trace_event_internal::kNoId, // bind_id
+ num_args,
+ &arg_name,
+ &arg_type,
+ &arg_value,
+ nullptr,
+ TRACE_EVENT_FLAG_NONE);
+}
+
+class AutoThreadLocalBoolean {
+ public:
+ explicit AutoThreadLocalBoolean(ThreadLocalBoolean* thread_local_boolean)
+ : thread_local_boolean_(thread_local_boolean) {
+ DCHECK(!thread_local_boolean_->Get());
+ thread_local_boolean_->Set(true);
+ }
+ ~AutoThreadLocalBoolean() { thread_local_boolean_->Set(false); }
+
+ private:
+ ThreadLocalBoolean* thread_local_boolean_;
+ DISALLOW_COPY_AND_ASSIGN(AutoThreadLocalBoolean);
+};
+
+// Use this function instead of TraceEventHandle constructor to keep the
+// overhead of ScopedTracer (trace_event.h) constructor minimum.
+void MakeHandle(uint32_t chunk_seq,
+ size_t chunk_index,
+ size_t event_index,
+ TraceEventHandle* handle) {
+ DCHECK(chunk_seq);
+ DCHECK(chunk_index <= TraceBufferChunk::kMaxChunkIndex);
+ DCHECK(event_index < TraceBufferChunk::kTraceBufferChunkSize);
+ DCHECK(chunk_index <= std::numeric_limits<uint16_t>::max());
+ handle->chunk_seq = chunk_seq;
+ handle->chunk_index = static_cast<uint16_t>(chunk_index);
+ handle->event_index = static_cast<uint16_t>(event_index);
+}
+
+template <typename Function>
+void ForEachCategoryFilter(const unsigned char* category_group_enabled,
+ Function filter_fn) {
+ const TraceCategory* category =
+ CategoryRegistry::GetCategoryByStatePtr(category_group_enabled);
+ uint32_t filter_bitmap = category->enabled_filters();
+ for (int index = 0; filter_bitmap != 0; filter_bitmap >>= 1, index++) {
+ if (filter_bitmap & 1 && GetCategoryGroupFilters()[index])
+ filter_fn(GetCategoryGroupFilters()[index].get());
+ }
+}
+
+} // namespace
+
+// A helper class that allows the lock to be acquired in the middle of the scope
+// and unlocks at the end of scope if locked.
+class TraceLog::OptionalAutoLock {
+ public:
+ explicit OptionalAutoLock(Lock* lock) : lock_(lock), locked_(false) {}
+
+ ~OptionalAutoLock() {
+ if (locked_)
+ lock_->Release();
+ }
+
+ void EnsureAcquired() {
+ if (!locked_) {
+ lock_->Acquire();
+ locked_ = true;
+ }
+ }
+
+ private:
+ Lock* lock_;
+ bool locked_;
+ DISALLOW_COPY_AND_ASSIGN(OptionalAutoLock);
+};
+
+class TraceLog::ThreadLocalEventBuffer
+ : public MessageLoopCurrent::DestructionObserver,
+ public MemoryDumpProvider {
+ public:
+ explicit ThreadLocalEventBuffer(TraceLog* trace_log);
+ ~ThreadLocalEventBuffer() override;
+
+ TraceEvent* AddTraceEvent(TraceEventHandle* handle);
+
+ TraceEvent* GetEventByHandle(TraceEventHandle handle) {
+ if (!chunk_ || handle.chunk_seq != chunk_->seq() ||
+ handle.chunk_index != chunk_index_) {
+ return nullptr;
+ }
+
+ return chunk_->GetEventAt(handle.event_index);
+ }
+
+ int generation() const { return generation_; }
+
+ private:
+ // MessageLoopCurrent::DestructionObserver
+ void WillDestroyCurrentMessageLoop() override;
+
+ // MemoryDumpProvider implementation.
+ bool OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) override;
+
+ void FlushWhileLocked();
+
+ void CheckThisIsCurrentBuffer() const {
+ DCHECK(trace_log_->thread_local_event_buffer_.Get() == this);
+ }
+
+ // Since TraceLog is a leaky singleton, trace_log_ will always be valid
+ // as long as the thread exists.
+ TraceLog* trace_log_;
+ std::unique_ptr<TraceBufferChunk> chunk_;
+ size_t chunk_index_;
+ int generation_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadLocalEventBuffer);
+};
+
+TraceLog::ThreadLocalEventBuffer::ThreadLocalEventBuffer(TraceLog* trace_log)
+ : trace_log_(trace_log),
+ chunk_index_(0),
+ generation_(trace_log->generation()) {
+ // ThreadLocalEventBuffer is created only if the thread has a message loop, so
+ // the following message_loop won't be NULL.
+ MessageLoop* message_loop = MessageLoop::current();
+ message_loop->AddDestructionObserver(this);
+
+ // This is to report the local memory usage when memory-infra is enabled.
+ MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+ this, "ThreadLocalEventBuffer", ThreadTaskRunnerHandle::Get());
+
+ AutoLock lock(trace_log->lock_);
+ trace_log->thread_message_loops_.insert(message_loop);
+}
+
+TraceLog::ThreadLocalEventBuffer::~ThreadLocalEventBuffer() {
+ CheckThisIsCurrentBuffer();
+ MessageLoop::current()->RemoveDestructionObserver(this);
+ MemoryDumpManager::GetInstance()->UnregisterDumpProvider(this);
+
+ {
+ AutoLock lock(trace_log_->lock_);
+ FlushWhileLocked();
+ trace_log_->thread_message_loops_.erase(MessageLoop::current());
+ }
+ trace_log_->thread_local_event_buffer_.Set(nullptr);
+}
+
+TraceEvent* TraceLog::ThreadLocalEventBuffer::AddTraceEvent(
+ TraceEventHandle* handle) {
+ CheckThisIsCurrentBuffer();
+
+ if (chunk_ && chunk_->IsFull()) {
+ AutoLock lock(trace_log_->lock_);
+ FlushWhileLocked();
+ chunk_.reset();
+ }
+ if (!chunk_) {
+ AutoLock lock(trace_log_->lock_);
+ chunk_ = trace_log_->logged_events_->GetChunk(&chunk_index_);
+ trace_log_->CheckIfBufferIsFullWhileLocked();
+ }
+ if (!chunk_)
+ return nullptr;
+
+ size_t event_index;
+ TraceEvent* trace_event = chunk_->AddTraceEvent(&event_index);
+ if (trace_event && handle)
+ MakeHandle(chunk_->seq(), chunk_index_, event_index, handle);
+
+ return trace_event;
+}
+
+void TraceLog::ThreadLocalEventBuffer::WillDestroyCurrentMessageLoop() {
+ delete this;
+}
+
+bool TraceLog::ThreadLocalEventBuffer::OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) {
+ if (!chunk_)
+ return true;
+ std::string dump_base_name = StringPrintf(
+ "tracing/thread_%d", static_cast<int>(PlatformThread::CurrentId()));
+ TraceEventMemoryOverhead overhead;
+ chunk_->EstimateTraceMemoryOverhead(&overhead);
+ overhead.DumpInto(dump_base_name.c_str(), pmd);
+ return true;
+}
+
+void TraceLog::ThreadLocalEventBuffer::FlushWhileLocked() {
+ if (!chunk_)
+ return;
+
+ trace_log_->lock_.AssertAcquired();
+ if (trace_log_->CheckGeneration(generation_)) {
+ // Return the chunk to the buffer only if the generation matches.
+ trace_log_->logged_events_->ReturnChunk(chunk_index_, std::move(chunk_));
+ }
+ // Otherwise this method may be called from the destructor, or TraceLog will
+ // find the generation mismatch and delete this buffer soon.
+}
+
+void TraceLog::SetAddTraceEventOverride(
+ const AddTraceEventOverrideCallback& override) {
+ subtle::NoBarrier_Store(&trace_event_override_,
+ reinterpret_cast<subtle::AtomicWord>(override));
+}
+
+struct TraceLog::RegisteredAsyncObserver {
+ explicit RegisteredAsyncObserver(WeakPtr<AsyncEnabledStateObserver> observer)
+ : observer(observer), task_runner(ThreadTaskRunnerHandle::Get()) {}
+ ~RegisteredAsyncObserver() = default;
+
+ WeakPtr<AsyncEnabledStateObserver> observer;
+ scoped_refptr<SequencedTaskRunner> task_runner;
+};
+
+TraceLogStatus::TraceLogStatus() : event_capacity(0), event_count(0) {}
+
+TraceLogStatus::~TraceLogStatus() = default;
+
+// static
+TraceLog* TraceLog::GetInstance() {
+ static base::NoDestructor<TraceLog> instance;
+ return instance.get();
+}
+
+// static
+void TraceLog::ResetForTesting() {
+ if (!g_trace_log_for_testing)
+ return;
+ CategoryRegistry::ResetForTesting();
+ g_trace_log_for_testing->~TraceLog();
+ new (g_trace_log_for_testing) TraceLog;
+}
+
+TraceLog::TraceLog()
+ : enabled_modes_(0),
+ num_traces_recorded_(0),
+ dispatching_to_observer_list_(false),
+ process_sort_index_(0),
+ process_id_hash_(0),
+ process_id_(0),
+ trace_options_(kInternalRecordUntilFull),
+ trace_config_(TraceConfig()),
+ thread_shared_chunk_index_(0),
+ generation_(0),
+ use_worker_thread_(false),
+ trace_event_override_(0),
+ filter_factory_for_testing_(nullptr) {
+ CategoryRegistry::Initialize();
+
+#if defined(OS_NACL) // NaCl shouldn't expose the process id.
+ SetProcessID(0);
+#else
+ SetProcessID(static_cast<int>(GetCurrentProcId()));
+#endif
+
+// Linux renderer processes and Android O processes are not allowed to read
+// "proc/stat" file, crbug.com/788870.
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ process_creation_time_ = CurrentProcessInfo::CreationTime();
+#else
+ // Use approximate time when creation time is not available.
+ process_creation_time_ = TRACE_TIME_NOW();
+#endif
+
+ logged_events_.reset(CreateTraceBuffer());
+
+ MemoryDumpManager::GetInstance()->RegisterDumpProvider(this, "TraceLog",
+ nullptr);
+ g_trace_log_for_testing = this;
+}
+
+TraceLog::~TraceLog() = default;
+
+void TraceLog::InitializeThreadLocalEventBufferIfSupported() {
+ // A ThreadLocalEventBuffer needs the message loop
+ // - to know when the thread exits;
+ // - to handle the final flush.
+ // For a thread without a message loop or the message loop may be blocked, the
+ // trace events will be added into the main buffer directly.
+ if (thread_blocks_message_loop_.Get() || !MessageLoopCurrent::IsSet())
+ return;
+ HEAP_PROFILER_SCOPED_IGNORE;
+ auto* thread_local_event_buffer = thread_local_event_buffer_.Get();
+ if (thread_local_event_buffer &&
+ !CheckGeneration(thread_local_event_buffer->generation())) {
+ delete thread_local_event_buffer;
+ thread_local_event_buffer = nullptr;
+ }
+ if (!thread_local_event_buffer) {
+ thread_local_event_buffer = new ThreadLocalEventBuffer(this);
+ thread_local_event_buffer_.Set(thread_local_event_buffer);
+ }
+}
+
+bool TraceLog::OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) {
+ // TODO(ssid): Use MemoryDumpArgs to create light dumps when requested
+ // (crbug.com/499731).
+ TraceEventMemoryOverhead overhead;
+ overhead.Add(TraceEventMemoryOverhead::kOther, sizeof(*this));
+ {
+ AutoLock lock(lock_);
+ if (logged_events_)
+ logged_events_->EstimateTraceMemoryOverhead(&overhead);
+
+ for (auto& metadata_event : metadata_events_)
+ metadata_event->EstimateTraceMemoryOverhead(&overhead);
+ }
+ overhead.AddSelf();
+ overhead.DumpInto("tracing/main_trace_log", pmd);
+ return true;
+}
+
+const unsigned char* TraceLog::GetCategoryGroupEnabled(
+ const char* category_group) {
+ TraceLog* tracelog = GetInstance();
+ if (!tracelog) {
+ DCHECK(!CategoryRegistry::kCategoryAlreadyShutdown->is_enabled());
+ return CategoryRegistry::kCategoryAlreadyShutdown->state_ptr();
+ }
+ TraceCategory* category = CategoryRegistry::GetCategoryByName(category_group);
+ if (!category) {
+ // Slow path: in the case of a new category we have to repeat the check
+ // holding the lock, as multiple threads might have reached this point
+ // at the same time.
+ auto category_initializer = [](TraceCategory* category) {
+ TraceLog::GetInstance()->UpdateCategoryState(category);
+ };
+ AutoLock lock(tracelog->lock_);
+ CategoryRegistry::GetOrCreateCategoryLocked(
+ category_group, category_initializer, &category);
+ }
+ DCHECK(category->state_ptr());
+ return category->state_ptr();
+}
+
+const char* TraceLog::GetCategoryGroupName(
+ const unsigned char* category_group_enabled) {
+ return CategoryRegistry::GetCategoryByStatePtr(category_group_enabled)
+ ->name();
+}
+
+void TraceLog::UpdateCategoryState(TraceCategory* category) {
+ lock_.AssertAcquired();
+ DCHECK(category->is_valid());
+ unsigned char state_flags = 0;
+ if (enabled_modes_ & RECORDING_MODE &&
+ trace_config_.IsCategoryGroupEnabled(category->name())) {
+ state_flags |= TraceCategory::ENABLED_FOR_RECORDING;
+ }
+
+ // TODO(primiano): this is a temporary workaround for catapult:#2341,
+ // to guarantee that metadata events are always added even if the category
+ // filter is "-*". See crbug.com/618054 for more details and long-term fix.
+ if (enabled_modes_ & RECORDING_MODE &&
+ category == CategoryRegistry::kCategoryMetadata) {
+ state_flags |= TraceCategory::ENABLED_FOR_RECORDING;
+ }
+
+#if defined(OS_WIN)
+ if (base::trace_event::TraceEventETWExport::IsCategoryGroupEnabled(
+ category->name())) {
+ state_flags |= TraceCategory::ENABLED_FOR_ETW_EXPORT;
+ }
+#endif
+
+ uint32_t enabled_filters_bitmap = 0;
+ int index = 0;
+ for (const auto& event_filter : enabled_event_filters_) {
+ if (event_filter.IsCategoryGroupEnabled(category->name())) {
+ state_flags |= TraceCategory::ENABLED_FOR_FILTERING;
+ DCHECK(GetCategoryGroupFilters()[index]);
+ enabled_filters_bitmap |= 1 << index;
+ }
+ if (index++ >= MAX_TRACE_EVENT_FILTERS) {
+ NOTREACHED();
+ break;
+ }
+ }
+ category->set_enabled_filters(enabled_filters_bitmap);
+ category->set_state(state_flags);
+}
+
+void TraceLog::UpdateCategoryRegistry() {
+ lock_.AssertAcquired();
+ CreateFiltersForTraceConfig();
+ for (TraceCategory& category : CategoryRegistry::GetAllCategories()) {
+ UpdateCategoryState(&category);
+ }
+}
+
+void TraceLog::CreateFiltersForTraceConfig() {
+ if (!(enabled_modes_ & FILTERING_MODE))
+ return;
+
+ // Filters were already added and tracing could be enabled. Filters list
+ // cannot be changed when trace events are using them.
+ if (GetCategoryGroupFilters().size())
+ return;
+
+ for (auto& filter_config : enabled_event_filters_) {
+ if (GetCategoryGroupFilters().size() >= MAX_TRACE_EVENT_FILTERS) {
+ NOTREACHED()
+ << "Too many trace event filters installed in the current session";
+ break;
+ }
+
+ std::unique_ptr<TraceEventFilter> new_filter;
+ const std::string& predicate_name = filter_config.predicate_name();
+ if (predicate_name == EventNameFilter::kName) {
+ auto whitelist = std::make_unique<std::unordered_set<std::string>>();
+ CHECK(filter_config.GetArgAsSet("event_name_whitelist", &*whitelist));
+ new_filter = std::make_unique<EventNameFilter>(std::move(whitelist));
+ } else if (predicate_name == HeapProfilerEventFilter::kName) {
+ new_filter = std::make_unique<HeapProfilerEventFilter>();
+ } else {
+ if (filter_factory_for_testing_)
+ new_filter = filter_factory_for_testing_(predicate_name);
+ CHECK(new_filter) << "Unknown trace filter " << predicate_name;
+ }
+ GetCategoryGroupFilters().push_back(std::move(new_filter));
+ }
+}
+
+void TraceLog::GetKnownCategoryGroups(
+ std::vector<std::string>* category_groups) {
+ for (const auto& category : CategoryRegistry::GetAllCategories()) {
+ if (!CategoryRegistry::IsBuiltinCategory(&category))
+ category_groups->push_back(category.name());
+ }
+}
+
+void TraceLog::SetEnabled(const TraceConfig& trace_config,
+ uint8_t modes_to_enable) {
+ DCHECK(trace_config.process_filter_config().IsEnabled(process_id_));
+
+ std::vector<EnabledStateObserver*> observer_list;
+ std::map<AsyncEnabledStateObserver*, RegisteredAsyncObserver> observer_map;
+ {
+ AutoLock lock(lock_);
+
+ // Can't enable tracing when Flush() is in progress.
+ DCHECK(!flush_task_runner_);
+
+ InternalTraceOptions new_options =
+ GetInternalOptionsFromTraceConfig(trace_config);
+
+ InternalTraceOptions old_options = trace_options();
+
+ if (dispatching_to_observer_list_) {
+ // TODO(ssid): Change to NOTREACHED after fixing crbug.com/625170.
+ DLOG(ERROR)
+ << "Cannot manipulate TraceLog::Enabled state from an observer.";
+ return;
+ }
+
+ // Clear all filters from previous tracing session. These filters are not
+ // cleared at the end of tracing because some threads which hit trace event
+ // when disabling, could try to use the filters.
+ if (!enabled_modes_)
+ GetCategoryGroupFilters().clear();
+
+ // Update trace config for recording.
+ const bool already_recording = enabled_modes_ & RECORDING_MODE;
+ if (modes_to_enable & RECORDING_MODE) {
+ if (already_recording) {
+ // TODO(ssid): Stop suporting enabling of RECODING_MODE when already
+ // enabled crbug.com/625170.
+ DCHECK_EQ(new_options, old_options) << "Attempting to re-enable "
+ "tracing with a different set "
+ "of options.";
+ trace_config_.Merge(trace_config);
+ } else {
+ trace_config_ = trace_config;
+ }
+ }
+
+ // Update event filters only if filtering was not enabled.
+ if (modes_to_enable & FILTERING_MODE && enabled_event_filters_.empty()) {
+ DCHECK(!trace_config.event_filters().empty());
+ enabled_event_filters_ = trace_config.event_filters();
+ }
+ // Keep the |trace_config_| updated with only enabled filters in case anyone
+ // tries to read it using |GetCurrentTraceConfig| (even if filters are
+ // empty).
+ trace_config_.SetEventFilters(enabled_event_filters_);
+
+ enabled_modes_ |= modes_to_enable;
+ UpdateCategoryRegistry();
+
+ // Do not notify observers or create trace buffer if only enabled for
+ // filtering or if recording was already enabled.
+ if (!(modes_to_enable & RECORDING_MODE) || already_recording)
+ return;
+
+ if (new_options != old_options) {
+ subtle::NoBarrier_Store(&trace_options_, new_options);
+ UseNextTraceBuffer();
+ }
+
+ num_traces_recorded_++;
+
+ UpdateCategoryRegistry();
+
+ dispatching_to_observer_list_ = true;
+ observer_list = enabled_state_observer_list_;
+ observer_map = async_observers_;
+ }
+ // Notify observers outside the lock in case they trigger trace events.
+ for (EnabledStateObserver* observer : observer_list)
+ observer->OnTraceLogEnabled();
+ for (const auto& it : observer_map) {
+ it.second.task_runner->PostTask(
+ FROM_HERE, BindOnce(&AsyncEnabledStateObserver::OnTraceLogEnabled,
+ it.second.observer));
+ }
+
+ {
+ AutoLock lock(lock_);
+ dispatching_to_observer_list_ = false;
+ }
+}
+
+void TraceLog::SetArgumentFilterPredicate(
+ const ArgumentFilterPredicate& argument_filter_predicate) {
+ AutoLock lock(lock_);
+ DCHECK(!argument_filter_predicate.is_null());
+ DCHECK(argument_filter_predicate_.is_null());
+ argument_filter_predicate_ = argument_filter_predicate;
+}
+
+TraceLog::InternalTraceOptions TraceLog::GetInternalOptionsFromTraceConfig(
+ const TraceConfig& config) {
+ InternalTraceOptions ret = config.IsArgumentFilterEnabled()
+ ? kInternalEnableArgumentFilter
+ : kInternalNone;
+ switch (config.GetTraceRecordMode()) {
+ case RECORD_UNTIL_FULL:
+ return ret | kInternalRecordUntilFull;
+ case RECORD_CONTINUOUSLY:
+ return ret | kInternalRecordContinuously;
+ case ECHO_TO_CONSOLE:
+ return ret | kInternalEchoToConsole;
+ case RECORD_AS_MUCH_AS_POSSIBLE:
+ return ret | kInternalRecordAsMuchAsPossible;
+ }
+ NOTREACHED();
+ return kInternalNone;
+}
+
+TraceConfig TraceLog::GetCurrentTraceConfig() const {
+ AutoLock lock(lock_);
+ return trace_config_;
+}
+
+void TraceLog::SetDisabled() {
+ AutoLock lock(lock_);
+ SetDisabledWhileLocked(RECORDING_MODE);
+}
+
+void TraceLog::SetDisabled(uint8_t modes_to_disable) {
+ AutoLock lock(lock_);
+ SetDisabledWhileLocked(modes_to_disable);
+}
+
+void TraceLog::SetDisabledWhileLocked(uint8_t modes_to_disable) {
+ lock_.AssertAcquired();
+
+ if (!(enabled_modes_ & modes_to_disable))
+ return;
+
+ if (dispatching_to_observer_list_) {
+ // TODO(ssid): Change to NOTREACHED after fixing crbug.com/625170.
+ DLOG(ERROR)
+ << "Cannot manipulate TraceLog::Enabled state from an observer.";
+ return;
+ }
+
+ bool is_recording_mode_disabled =
+ (enabled_modes_ & RECORDING_MODE) && (modes_to_disable & RECORDING_MODE);
+ enabled_modes_ &= ~modes_to_disable;
+
+ if (modes_to_disable & FILTERING_MODE)
+ enabled_event_filters_.clear();
+
+ if (modes_to_disable & RECORDING_MODE)
+ trace_config_.Clear();
+
+ UpdateCategoryRegistry();
+
+ // Add metadata events and notify observers only if recording mode was
+ // disabled now.
+ if (!is_recording_mode_disabled)
+ return;
+
+ AddMetadataEventsWhileLocked();
+
+ // Remove metadata events so they will not get added to a subsequent trace.
+ metadata_events_.clear();
+
+ dispatching_to_observer_list_ = true;
+ std::vector<EnabledStateObserver*> observer_list =
+ enabled_state_observer_list_;
+ std::map<AsyncEnabledStateObserver*, RegisteredAsyncObserver> observer_map =
+ async_observers_;
+
+ {
+ // Dispatch to observers outside the lock in case the observer triggers a
+ // trace event.
+ AutoUnlock unlock(lock_);
+ for (EnabledStateObserver* observer : observer_list)
+ observer->OnTraceLogDisabled();
+ for (const auto& it : observer_map) {
+ it.second.task_runner->PostTask(
+ FROM_HERE, BindOnce(&AsyncEnabledStateObserver::OnTraceLogDisabled,
+ it.second.observer));
+ }
+ }
+ dispatching_to_observer_list_ = false;
+}
+
+int TraceLog::GetNumTracesRecorded() {
+ AutoLock lock(lock_);
+ if (!IsEnabled())
+ return -1;
+ return num_traces_recorded_;
+}
+
+void TraceLog::AddEnabledStateObserver(EnabledStateObserver* listener) {
+ AutoLock lock(lock_);
+ enabled_state_observer_list_.push_back(listener);
+}
+
+void TraceLog::RemoveEnabledStateObserver(EnabledStateObserver* listener) {
+ AutoLock lock(lock_);
+ std::vector<EnabledStateObserver*>::iterator it =
+ std::find(enabled_state_observer_list_.begin(),
+ enabled_state_observer_list_.end(), listener);
+ if (it != enabled_state_observer_list_.end())
+ enabled_state_observer_list_.erase(it);
+}
+
+bool TraceLog::HasEnabledStateObserver(EnabledStateObserver* listener) const {
+ AutoLock lock(lock_);
+ return ContainsValue(enabled_state_observer_list_, listener);
+}
+
+TraceLogStatus TraceLog::GetStatus() const {
+ AutoLock lock(lock_);
+ TraceLogStatus result;
+ result.event_capacity = static_cast<uint32_t>(logged_events_->Capacity());
+ result.event_count = static_cast<uint32_t>(logged_events_->Size());
+ return result;
+}
+
+bool TraceLog::BufferIsFull() const {
+ AutoLock lock(lock_);
+ return logged_events_->IsFull();
+}
+
+TraceEvent* TraceLog::AddEventToThreadSharedChunkWhileLocked(
+ TraceEventHandle* handle,
+ bool check_buffer_is_full) {
+ lock_.AssertAcquired();
+
+ if (thread_shared_chunk_ && thread_shared_chunk_->IsFull()) {
+ logged_events_->ReturnChunk(thread_shared_chunk_index_,
+ std::move(thread_shared_chunk_));
+ }
+
+ if (!thread_shared_chunk_) {
+ thread_shared_chunk_ =
+ logged_events_->GetChunk(&thread_shared_chunk_index_);
+ if (check_buffer_is_full)
+ CheckIfBufferIsFullWhileLocked();
+ }
+ if (!thread_shared_chunk_)
+ return nullptr;
+
+ size_t event_index;
+ TraceEvent* trace_event = thread_shared_chunk_->AddTraceEvent(&event_index);
+ if (trace_event && handle) {
+ MakeHandle(thread_shared_chunk_->seq(), thread_shared_chunk_index_,
+ event_index, handle);
+ }
+ return trace_event;
+}
+
+void TraceLog::CheckIfBufferIsFullWhileLocked() {
+ lock_.AssertAcquired();
+ if (logged_events_->IsFull()) {
+ if (buffer_limit_reached_timestamp_.is_null()) {
+ buffer_limit_reached_timestamp_ = OffsetNow();
+ }
+ SetDisabledWhileLocked(RECORDING_MODE);
+ }
+}
+
+// Flush() works as the following:
+// 1. Flush() is called in thread A whose task runner is saved in
+// flush_task_runner_;
+// 2. If thread_message_loops_ is not empty, thread A posts task to each message
+// loop to flush the thread local buffers; otherwise finish the flush;
+// 3. FlushCurrentThread() deletes the thread local event buffer:
+// - The last batch of events of the thread are flushed into the main buffer;
+// - The message loop will be removed from thread_message_loops_;
+// If this is the last message loop, finish the flush;
+// 4. If any thread hasn't finish its flush in time, finish the flush.
+void TraceLog::Flush(const TraceLog::OutputCallback& cb,
+ bool use_worker_thread) {
+ FlushInternal(cb, use_worker_thread, false);
+}
+
+void TraceLog::CancelTracing(const OutputCallback& cb) {
+ SetDisabled();
+ FlushInternal(cb, false, true);
+}
+
+void TraceLog::FlushInternal(const TraceLog::OutputCallback& cb,
+ bool use_worker_thread,
+ bool discard_events) {
+ use_worker_thread_ = use_worker_thread;
+ if (IsEnabled()) {
+ // Can't flush when tracing is enabled because otherwise PostTask would
+ // - generate more trace events;
+ // - deschedule the calling thread on some platforms causing inaccurate
+ // timing of the trace events.
+ scoped_refptr<RefCountedString> empty_result = new RefCountedString;
+ if (!cb.is_null())
+ cb.Run(empty_result, false);
+ LOG(WARNING) << "Ignored TraceLog::Flush called when tracing is enabled";
+ return;
+ }
+
+ int gen = generation();
+ // Copy of thread_message_loops_ to be used without locking.
+ std::vector<scoped_refptr<SingleThreadTaskRunner>>
+ thread_message_loop_task_runners;
+ {
+ AutoLock lock(lock_);
+ DCHECK(!flush_task_runner_);
+ flush_task_runner_ = SequencedTaskRunnerHandle::IsSet()
+ ? SequencedTaskRunnerHandle::Get()
+ : nullptr;
+ DCHECK(thread_message_loops_.empty() || flush_task_runner_);
+ flush_output_callback_ = cb;
+
+ if (thread_shared_chunk_) {
+ logged_events_->ReturnChunk(thread_shared_chunk_index_,
+ std::move(thread_shared_chunk_));
+ }
+
+ for (MessageLoop* loop : thread_message_loops_)
+ thread_message_loop_task_runners.push_back(loop->task_runner());
+ }
+
+ if (!thread_message_loop_task_runners.empty()) {
+ for (auto& task_runner : thread_message_loop_task_runners) {
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&TraceLog::FlushCurrentThread, Unretained(this),
+ gen, discard_events));
+ }
+ flush_task_runner_->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&TraceLog::OnFlushTimeout, Unretained(this), gen,
+ discard_events),
+ TimeDelta::FromMilliseconds(kThreadFlushTimeoutMs));
+ return;
+ }
+
+ FinishFlush(gen, discard_events);
+}
+
+// Usually it runs on a different thread.
+void TraceLog::ConvertTraceEventsToTraceFormat(
+ std::unique_ptr<TraceBuffer> logged_events,
+ const OutputCallback& flush_output_callback,
+ const ArgumentFilterPredicate& argument_filter_predicate) {
+ if (flush_output_callback.is_null())
+ return;
+
+ HEAP_PROFILER_SCOPED_IGNORE;
+ // The callback need to be called at least once even if there is no events
+ // to let the caller know the completion of flush.
+ scoped_refptr<RefCountedString> json_events_str_ptr = new RefCountedString();
+ const size_t kReserveCapacity = kTraceEventBufferSizeInBytes * 5 / 4;
+ json_events_str_ptr->data().reserve(kReserveCapacity);
+ while (const TraceBufferChunk* chunk = logged_events->NextChunk()) {
+ for (size_t j = 0; j < chunk->size(); ++j) {
+ size_t size = json_events_str_ptr->size();
+ if (size > kTraceEventBufferSizeInBytes) {
+ flush_output_callback.Run(json_events_str_ptr, true);
+ json_events_str_ptr = new RefCountedString();
+ json_events_str_ptr->data().reserve(kReserveCapacity);
+ } else if (size) {
+ json_events_str_ptr->data().append(",\n");
+ }
+ chunk->GetEventAt(j)->AppendAsJSON(&(json_events_str_ptr->data()),
+ argument_filter_predicate);
+ }
+ }
+ flush_output_callback.Run(json_events_str_ptr, false);
+}
+
+void TraceLog::FinishFlush(int generation, bool discard_events) {
+ std::unique_ptr<TraceBuffer> previous_logged_events;
+ OutputCallback flush_output_callback;
+ ArgumentFilterPredicate argument_filter_predicate;
+
+ if (!CheckGeneration(generation))
+ return;
+
+ {
+ AutoLock lock(lock_);
+
+ previous_logged_events.swap(logged_events_);
+ UseNextTraceBuffer();
+ thread_message_loops_.clear();
+
+ flush_task_runner_ = nullptr;
+ flush_output_callback = flush_output_callback_;
+ flush_output_callback_.Reset();
+
+ if (trace_options() & kInternalEnableArgumentFilter) {
+ CHECK(!argument_filter_predicate_.is_null());
+ argument_filter_predicate = argument_filter_predicate_;
+ }
+ }
+
+ if (discard_events) {
+ if (!flush_output_callback.is_null()) {
+ scoped_refptr<RefCountedString> empty_result = new RefCountedString;
+ flush_output_callback.Run(empty_result, false);
+ }
+ return;
+ }
+
+ if (use_worker_thread_) {
+ base::PostTaskWithTraits(
+ FROM_HERE,
+ {MayBlock(), TaskPriority::BACKGROUND,
+ TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ BindOnce(&TraceLog::ConvertTraceEventsToTraceFormat,
+ std::move(previous_logged_events), flush_output_callback,
+ argument_filter_predicate));
+ return;
+ }
+
+ ConvertTraceEventsToTraceFormat(std::move(previous_logged_events),
+ flush_output_callback,
+ argument_filter_predicate);
+}
+
+// Run in each thread holding a local event buffer.
+void TraceLog::FlushCurrentThread(int generation, bool discard_events) {
+ {
+ AutoLock lock(lock_);
+ if (!CheckGeneration(generation) || !flush_task_runner_) {
+ // This is late. The corresponding flush has finished.
+ return;
+ }
+ }
+
+ // This will flush the thread local buffer.
+ delete thread_local_event_buffer_.Get();
+
+ // Scheduler uses TRACE_EVENT macros when posting a task, which can lead
+ // to acquiring a tracing lock. Given that posting a task requires grabbing
+ // a scheduler lock, we need to post this task outside tracing lock to avoid
+ // deadlocks.
+ scoped_refptr<SequencedTaskRunner> cached_flush_task_runner;
+ {
+ AutoLock lock(lock_);
+ cached_flush_task_runner = flush_task_runner_;
+ if (!CheckGeneration(generation) || !flush_task_runner_ ||
+ !thread_message_loops_.empty())
+ return;
+ }
+ cached_flush_task_runner->PostTask(
+ FROM_HERE, BindOnce(&TraceLog::FinishFlush, Unretained(this), generation,
+ discard_events));
+}
+
+void TraceLog::OnFlushTimeout(int generation, bool discard_events) {
+ {
+ AutoLock lock(lock_);
+ if (!CheckGeneration(generation) || !flush_task_runner_) {
+ // Flush has finished before timeout.
+ return;
+ }
+
+ LOG(WARNING)
+ << "The following threads haven't finished flush in time. "
+ "If this happens stably for some thread, please call "
+ "TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop() from "
+ "the thread to avoid its trace events from being lost.";
+ for (hash_set<MessageLoop*>::const_iterator it =
+ thread_message_loops_.begin();
+ it != thread_message_loops_.end(); ++it) {
+ LOG(WARNING) << "Thread: " << (*it)->GetThreadName();
+ }
+ }
+ FinishFlush(generation, discard_events);
+}
+
+void TraceLog::UseNextTraceBuffer() {
+ logged_events_.reset(CreateTraceBuffer());
+ subtle::NoBarrier_AtomicIncrement(&generation_, 1);
+ thread_shared_chunk_.reset();
+ thread_shared_chunk_index_ = 0;
+}
+
+TraceEventHandle TraceLog::AddTraceEvent(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags) {
+ int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
+ return AddTraceEventWithThreadIdAndTimestamp(
+ phase,
+ category_group_enabled,
+ name,
+ scope,
+ id,
+ trace_event_internal::kNoId, // bind_id
+ thread_id,
+ now,
+ num_args,
+ arg_names,
+ arg_types,
+ arg_values,
+ convertable_values,
+ flags);
+}
+
+TraceEventHandle TraceLog::AddTraceEventWithBindId(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ unsigned long long bind_id,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags) {
+ int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
+ return AddTraceEventWithThreadIdAndTimestamp(
+ phase,
+ category_group_enabled,
+ name,
+ scope,
+ id,
+ bind_id,
+ thread_id,
+ now,
+ num_args,
+ arg_names,
+ arg_types,
+ arg_values,
+ convertable_values,
+ flags | TRACE_EVENT_FLAG_HAS_CONTEXT_ID);
+}
+
+TraceEventHandle TraceLog::AddTraceEventWithProcessId(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ int process_id,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags) {
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
+ return AddTraceEventWithThreadIdAndTimestamp(
+ phase,
+ category_group_enabled,
+ name,
+ scope,
+ id,
+ trace_event_internal::kNoId, // bind_id
+ process_id,
+ now,
+ num_args,
+ arg_names,
+ arg_types,
+ arg_values,
+ convertable_values,
+ flags | TRACE_EVENT_FLAG_HAS_PROCESS_ID);
+}
+
+// Handle legacy calls to AddTraceEventWithThreadIdAndTimestamp
+// with kNoId as bind_id
+TraceEventHandle TraceLog::AddTraceEventWithThreadIdAndTimestamp(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ int thread_id,
+ const TimeTicks& timestamp,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags) {
+ return AddTraceEventWithThreadIdAndTimestamp(
+ phase,
+ category_group_enabled,
+ name,
+ scope,
+ id,
+ trace_event_internal::kNoId, // bind_id
+ thread_id,
+ timestamp,
+ num_args,
+ arg_names,
+ arg_types,
+ arg_values,
+ convertable_values,
+ flags);
+}
+
+TraceEventHandle TraceLog::AddTraceEventWithThreadIdAndTimestamp(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ unsigned long long bind_id,
+ int thread_id,
+ const TimeTicks& timestamp,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags) {
+ TraceEventHandle handle = {0, 0, 0};
+ if (!*category_group_enabled)
+ return handle;
+
+ // Avoid re-entrance of AddTraceEvent. This may happen in GPU process when
+ // ECHO_TO_CONSOLE is enabled: AddTraceEvent -> LOG(ERROR) ->
+ // GpuProcessLogMessageHandler -> PostPendingTask -> TRACE_EVENT ...
+ if (thread_is_in_trace_event_.Get())
+ return handle;
+
+ AutoThreadLocalBoolean thread_is_in_trace_event(&thread_is_in_trace_event_);
+
+ DCHECK(name);
+ DCHECK(!timestamp.is_null());
+
+ if (flags & TRACE_EVENT_FLAG_MANGLE_ID) {
+ if ((flags & TRACE_EVENT_FLAG_FLOW_IN) ||
+ (flags & TRACE_EVENT_FLAG_FLOW_OUT))
+ bind_id = MangleEventId(bind_id);
+ id = MangleEventId(id);
+ }
+
+ TimeTicks offset_event_timestamp = OffsetTimestamp(timestamp);
+ ThreadTicks thread_now = ThreadNow();
+
+ ThreadLocalEventBuffer* thread_local_event_buffer = nullptr;
+ if (*category_group_enabled & RECORDING_MODE) {
+ // |thread_local_event_buffer_| can be null if the current thread doesn't
+ // have a message loop or the message loop is blocked.
+ InitializeThreadLocalEventBufferIfSupported();
+ thread_local_event_buffer = thread_local_event_buffer_.Get();
+ }
+
+ // Check and update the current thread name only if the event is for the
+ // current thread to avoid locks in most cases.
+ if (thread_id == static_cast<int>(PlatformThread::CurrentId())) {
+ const char* new_name =
+ ThreadIdNameManager::GetInstance()->GetName(thread_id);
+ // Check if the thread name has been set or changed since the previous
+ // call (if any), but don't bother if the new name is empty. Note this will
+ // not detect a thread name change within the same char* buffer address: we
+ // favor common case performance over corner case correctness.
+ static auto* current_thread_name = new ThreadLocalPointer<const char>();
+ if (new_name != current_thread_name->Get() && new_name && *new_name) {
+ current_thread_name->Set(new_name);
+
+ AutoLock thread_info_lock(thread_info_lock_);
+
+ auto existing_name = thread_names_.find(thread_id);
+ if (existing_name == thread_names_.end()) {
+ // This is a new thread id, and a new name.
+ thread_names_[thread_id] = new_name;
+ } else {
+ // This is a thread id that we've seen before, but potentially with a
+ // new name.
+ std::vector<StringPiece> existing_names = base::SplitStringPiece(
+ existing_name->second, ",", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ if (!ContainsValue(existing_names, new_name)) {
+ if (!existing_names.empty())
+ existing_name->second.push_back(',');
+ existing_name->second.append(new_name);
+ }
+ }
+ }
+ }
+
+#if defined(OS_WIN)
+ // This is done sooner rather than later, to avoid creating the event and
+ // acquiring the lock, which is not needed for ETW as it's already threadsafe.
+ if (*category_group_enabled & TraceCategory::ENABLED_FOR_ETW_EXPORT)
+ TraceEventETWExport::AddEvent(phase, category_group_enabled, name, id,
+ num_args, arg_names, arg_types, arg_values,
+ convertable_values);
+#endif // OS_WIN
+
+ AddTraceEventOverrideCallback trace_event_override =
+ reinterpret_cast<AddTraceEventOverrideCallback>(
+ subtle::NoBarrier_Load(&trace_event_override_));
+ if (trace_event_override) {
+ TraceEvent new_trace_event;
+ // If we have an override in place for events, rather than sending
+ // them to the tracelog, we don't have a way of going back and updating
+ // the duration of _COMPLETE events. Instead, we emit separate _BEGIN
+ // and _END events.
+ if (phase == TRACE_EVENT_PHASE_COMPLETE)
+ phase = TRACE_EVENT_PHASE_BEGIN;
+
+ new_trace_event.Initialize(thread_id, offset_event_timestamp, thread_now,
+ phase, category_group_enabled, name, scope, id,
+ bind_id, num_args, arg_names, arg_types,
+ arg_values, convertable_values, flags);
+
+ trace_event_override(new_trace_event);
+ return handle;
+ }
+
+ std::string console_message;
+ std::unique_ptr<TraceEvent> filtered_trace_event;
+ bool disabled_by_filters = false;
+ if (*category_group_enabled & TraceCategory::ENABLED_FOR_FILTERING) {
+ std::unique_ptr<TraceEvent> new_trace_event(new TraceEvent);
+ new_trace_event->Initialize(thread_id, offset_event_timestamp, thread_now,
+ phase, category_group_enabled, name, scope, id,
+ bind_id, num_args, arg_names, arg_types,
+ arg_values, convertable_values, flags);
+
+ disabled_by_filters = true;
+ ForEachCategoryFilter(
+ category_group_enabled, [&new_trace_event, &disabled_by_filters](
+ TraceEventFilter* trace_event_filter) {
+ if (trace_event_filter->FilterTraceEvent(*new_trace_event))
+ disabled_by_filters = false;
+ });
+ if (!disabled_by_filters)
+ filtered_trace_event = std::move(new_trace_event);
+ }
+
+ // If enabled for recording, the event should be added only if one of the
+ // filters indicates or category is not enabled for filtering.
+ if ((*category_group_enabled & TraceCategory::ENABLED_FOR_RECORDING) &&
+ !disabled_by_filters) {
+ OptionalAutoLock lock(&lock_);
+
+ TraceEvent* trace_event = nullptr;
+ if (thread_local_event_buffer) {
+ trace_event = thread_local_event_buffer->AddTraceEvent(&handle);
+ } else {
+ lock.EnsureAcquired();
+ trace_event = AddEventToThreadSharedChunkWhileLocked(&handle, true);
+ }
+
+ if (trace_event) {
+ if (filtered_trace_event) {
+ trace_event->MoveFrom(std::move(filtered_trace_event));
+ } else {
+ trace_event->Initialize(thread_id, offset_event_timestamp, thread_now,
+ phase, category_group_enabled, name, scope, id,
+ bind_id, num_args, arg_names, arg_types,
+ arg_values, convertable_values, flags);
+ }
+
+#if defined(OS_ANDROID)
+ trace_event->SendToATrace();
+#endif
+ }
+
+ if (trace_options() & kInternalEchoToConsole) {
+ console_message = EventToConsoleMessage(
+ phase == TRACE_EVENT_PHASE_COMPLETE ? TRACE_EVENT_PHASE_BEGIN : phase,
+ timestamp, trace_event);
+ }
+ }
+
+ if (!console_message.empty())
+ LOG(ERROR) << console_message;
+
+ return handle;
+}
+
+void TraceLog::AddMetadataEvent(
+ const unsigned char* category_group_enabled,
+ const char* name,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags) {
+ HEAP_PROFILER_SCOPED_IGNORE;
+ std::unique_ptr<TraceEvent> trace_event(new TraceEvent);
+ int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
+ ThreadTicks thread_now = ThreadNow();
+ TimeTicks now = OffsetNow();
+ AutoLock lock(lock_);
+ trace_event->Initialize(
+ thread_id, now, thread_now, TRACE_EVENT_PHASE_METADATA,
+ category_group_enabled, name,
+ trace_event_internal::kGlobalScope, // scope
+ trace_event_internal::kNoId, // id
+ trace_event_internal::kNoId, // bind_id
+ num_args, arg_names, arg_types, arg_values, convertable_values, flags);
+ metadata_events_.push_back(std::move(trace_event));
+}
+
+// May be called when a COMPELETE event ends and the unfinished event has been
+// recycled (phase == TRACE_EVENT_PHASE_END and trace_event == NULL).
+std::string TraceLog::EventToConsoleMessage(unsigned char phase,
+ const TimeTicks& timestamp,
+ TraceEvent* trace_event) {
+ HEAP_PROFILER_SCOPED_IGNORE;
+ AutoLock thread_info_lock(thread_info_lock_);
+
+ // The caller should translate TRACE_EVENT_PHASE_COMPLETE to
+ // TRACE_EVENT_PHASE_BEGIN or TRACE_EVENT_END.
+ DCHECK(phase != TRACE_EVENT_PHASE_COMPLETE);
+
+ TimeDelta duration;
+ int thread_id =
+ trace_event ? trace_event->thread_id() : PlatformThread::CurrentId();
+ if (phase == TRACE_EVENT_PHASE_END) {
+ duration = timestamp - thread_event_start_times_[thread_id].top();
+ thread_event_start_times_[thread_id].pop();
+ }
+
+ std::string thread_name = thread_names_[thread_id];
+ if (thread_colors_.find(thread_name) == thread_colors_.end()) {
+ size_t next_color = (thread_colors_.size() % 6) + 1;
+ thread_colors_[thread_name] = next_color;
+ }
+
+ std::ostringstream log;
+ log << base::StringPrintf("%s: \x1b[0;3%dm", thread_name.c_str(),
+ thread_colors_[thread_name]);
+
+ size_t depth = 0;
+ auto it = thread_event_start_times_.find(thread_id);
+ if (it != thread_event_start_times_.end())
+ depth = it->second.size();
+
+ for (size_t i = 0; i < depth; ++i)
+ log << "| ";
+
+ if (trace_event)
+ trace_event->AppendPrettyPrinted(&log);
+ if (phase == TRACE_EVENT_PHASE_END)
+ log << base::StringPrintf(" (%.3f ms)", duration.InMillisecondsF());
+
+ log << "\x1b[0;m";
+
+ if (phase == TRACE_EVENT_PHASE_BEGIN)
+ thread_event_start_times_[thread_id].push(timestamp);
+
+ return log.str();
+}
+
+void TraceLog::EndFilteredEvent(const unsigned char* category_group_enabled,
+ const char* name,
+ TraceEventHandle handle) {
+ const char* category_name = GetCategoryGroupName(category_group_enabled);
+ ForEachCategoryFilter(
+ category_group_enabled,
+ [name, category_name](TraceEventFilter* trace_event_filter) {
+ trace_event_filter->EndEvent(category_name, name);
+ });
+}
+
+void TraceLog::UpdateTraceEventDuration(
+ const unsigned char* category_group_enabled,
+ const char* name,
+ TraceEventHandle handle) {
+ char category_group_enabled_local = *category_group_enabled;
+ if (!category_group_enabled_local)
+ return;
+
+ UpdateTraceEventDurationExplicit(category_group_enabled, name, handle,
+ OffsetNow(), ThreadNow());
+}
+
+void TraceLog::UpdateTraceEventDurationExplicit(
+ const unsigned char* category_group_enabled,
+ const char* name,
+ TraceEventHandle handle,
+ const TimeTicks& now,
+ const ThreadTicks& thread_now) {
+ char category_group_enabled_local = *category_group_enabled;
+ if (!category_group_enabled_local)
+ return;
+
+ // Avoid re-entrance of AddTraceEvent. This may happen in GPU process when
+ // ECHO_TO_CONSOLE is enabled: AddTraceEvent -> LOG(ERROR) ->
+ // GpuProcessLogMessageHandler -> PostPendingTask -> TRACE_EVENT ...
+ if (thread_is_in_trace_event_.Get())
+ return;
+ AutoThreadLocalBoolean thread_is_in_trace_event(&thread_is_in_trace_event_);
+
+#if defined(OS_WIN)
+ // Generate an ETW event that marks the end of a complete event.
+ if (category_group_enabled_local & TraceCategory::ENABLED_FOR_ETW_EXPORT)
+ TraceEventETWExport::AddCompleteEndEvent(name);
+#endif // OS_WIN
+
+ std::string console_message;
+ if (category_group_enabled_local & TraceCategory::ENABLED_FOR_RECORDING) {
+ AddTraceEventOverrideCallback trace_event_override =
+ reinterpret_cast<AddTraceEventOverrideCallback>(
+ subtle::NoBarrier_Load(&trace_event_override_));
+
+ // If we send events off to an override instead of the TraceBuffer,
+ // we don't have way of updating the prior event so we'll emit a
+ // separate _END event instead.
+ if (trace_event_override) {
+ TraceEvent new_trace_event;
+ new_trace_event.Initialize(
+ static_cast<int>(base::PlatformThread::CurrentId()), now, thread_now,
+ TRACE_EVENT_PHASE_END, category_group_enabled, name,
+ trace_event_internal::kGlobalScope,
+ trace_event_internal::kNoId /* id */,
+ trace_event_internal::kNoId /* bind_id */, 0, nullptr, nullptr,
+ nullptr, nullptr, TRACE_EVENT_FLAG_NONE);
+ trace_event_override(new_trace_event);
+ return;
+ }
+
+ OptionalAutoLock lock(&lock_);
+
+ TraceEvent* trace_event = GetEventByHandleInternal(handle, &lock);
+ if (trace_event) {
+ DCHECK(trace_event->phase() == TRACE_EVENT_PHASE_COMPLETE);
+ // TEMP(oysteine) to debug crbug.com/638744
+ if (trace_event->duration().ToInternalValue() != -1) {
+ DVLOG(1) << "TraceHandle: chunk_seq " << handle.chunk_seq
+ << ", chunk_index " << handle.chunk_index << ", event_index "
+ << handle.event_index;
+
+ std::string serialized_event;
+ trace_event->AppendAsJSON(&serialized_event, ArgumentFilterPredicate());
+ DVLOG(1) << "TraceEvent: " << serialized_event;
+ lock_.AssertAcquired();
+ }
+
+ trace_event->UpdateDuration(now, thread_now);
+#if defined(OS_ANDROID)
+ trace_event->SendToATrace();
+#endif
+ }
+
+ if (trace_options() & kInternalEchoToConsole) {
+ console_message =
+ EventToConsoleMessage(TRACE_EVENT_PHASE_END, now, trace_event);
+ }
+ }
+
+ if (!console_message.empty())
+ LOG(ERROR) << console_message;
+
+ if (category_group_enabled_local & TraceCategory::ENABLED_FOR_FILTERING)
+ EndFilteredEvent(category_group_enabled, name, handle);
+}
+
+uint64_t TraceLog::MangleEventId(uint64_t id) {
+ return id ^ process_id_hash_;
+}
+
+void TraceLog::AddMetadataEventsWhileLocked() {
+ lock_.AssertAcquired();
+
+ // Move metadata added by |AddMetadataEvent| into the trace log.
+ while (!metadata_events_.empty()) {
+ TraceEvent* event = AddEventToThreadSharedChunkWhileLocked(nullptr, false);
+ event->MoveFrom(std::move(metadata_events_.back()));
+ metadata_events_.pop_back();
+ }
+
+#if !defined(OS_NACL) // NaCl shouldn't expose the process id.
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false), 0, "num_cpus",
+ "number", base::SysInfo::NumberOfProcessors());
+#endif
+
+ int current_thread_id = static_cast<int>(base::PlatformThread::CurrentId());
+ if (process_sort_index_ != 0) {
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false),
+ current_thread_id, "process_sort_index", "sort_index",
+ process_sort_index_);
+ }
+
+ if (!process_name_.empty()) {
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false),
+ current_thread_id, "process_name", "name", process_name_);
+ }
+
+ TimeDelta process_uptime = TRACE_TIME_NOW() - process_creation_time_;
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false), current_thread_id,
+ "process_uptime_seconds", "uptime", process_uptime.InSeconds());
+
+#if defined(OS_ANDROID)
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false), current_thread_id,
+ "chrome_library_address", "start_address",
+ base::StringPrintf("%p", &__executable_start));
+#endif
+
+ if (!process_labels_.empty()) {
+ std::vector<base::StringPiece> labels;
+ for (const auto& it : process_labels_)
+ labels.push_back(it.second);
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false),
+ current_thread_id, "process_labels", "labels",
+ base::JoinString(labels, ","));
+ }
+
+ // Thread sort indices.
+ for (const auto& it : thread_sort_indices_) {
+ if (it.second == 0)
+ continue;
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false), it.first,
+ "thread_sort_index", "sort_index", it.second);
+ }
+
+ // Thread names.
+ AutoLock thread_info_lock(thread_info_lock_);
+ for (const auto& it : thread_names_) {
+ if (it.second.empty())
+ continue;
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false), it.first,
+ "thread_name", "name", it.second);
+ }
+
+ // If buffer is full, add a metadata record to report this.
+ if (!buffer_limit_reached_timestamp_.is_null()) {
+ InitializeMetadataEvent(
+ AddEventToThreadSharedChunkWhileLocked(nullptr, false),
+ current_thread_id, "trace_buffer_overflowed", "overflowed_at_ts",
+ buffer_limit_reached_timestamp_);
+ }
+}
+
+TraceEvent* TraceLog::GetEventByHandle(TraceEventHandle handle) {
+ return GetEventByHandleInternal(handle, nullptr);
+}
+
+TraceEvent* TraceLog::GetEventByHandleInternal(TraceEventHandle handle,
+ OptionalAutoLock* lock) {
+ if (!handle.chunk_seq)
+ return nullptr;
+
+ DCHECK(handle.chunk_seq);
+ DCHECK(handle.chunk_index <= TraceBufferChunk::kMaxChunkIndex);
+ DCHECK(handle.event_index <= TraceBufferChunk::kTraceBufferChunkSize - 1);
+
+ if (thread_local_event_buffer_.Get()) {
+ TraceEvent* trace_event =
+ thread_local_event_buffer_.Get()->GetEventByHandle(handle);
+ if (trace_event)
+ return trace_event;
+ }
+
+ // The event has been out-of-control of the thread local buffer.
+ // Try to get the event from the main buffer with a lock.
+ if (lock)
+ lock->EnsureAcquired();
+
+ if (thread_shared_chunk_ &&
+ handle.chunk_index == thread_shared_chunk_index_) {
+ return handle.chunk_seq == thread_shared_chunk_->seq()
+ ? thread_shared_chunk_->GetEventAt(handle.event_index)
+ : nullptr;
+ }
+
+ return logged_events_->GetEventByHandle(handle);
+}
+
+void TraceLog::SetProcessID(int process_id) {
+ process_id_ = process_id;
+ // Create a FNV hash from the process ID for XORing.
+ // See http://isthe.com/chongo/tech/comp/fnv/ for algorithm details.
+ const unsigned long long kOffsetBasis = 14695981039346656037ull;
+ const unsigned long long kFnvPrime = 1099511628211ull;
+ const unsigned long long pid = static_cast<unsigned long long>(process_id_);
+ process_id_hash_ = (kOffsetBasis ^ pid) * kFnvPrime;
+}
+
+void TraceLog::SetProcessSortIndex(int sort_index) {
+ AutoLock lock(lock_);
+ process_sort_index_ = sort_index;
+}
+
+void TraceLog::UpdateProcessLabel(int label_id,
+ const std::string& current_label) {
+ if (!current_label.length())
+ return RemoveProcessLabel(label_id);
+
+ AutoLock lock(lock_);
+ process_labels_[label_id] = current_label;
+}
+
+void TraceLog::RemoveProcessLabel(int label_id) {
+ AutoLock lock(lock_);
+ process_labels_.erase(label_id);
+}
+
+void TraceLog::SetThreadSortIndex(PlatformThreadId thread_id, int sort_index) {
+ AutoLock lock(lock_);
+ thread_sort_indices_[static_cast<int>(thread_id)] = sort_index;
+}
+
+void TraceLog::SetTimeOffset(TimeDelta offset) {
+ time_offset_ = offset;
+}
+
+size_t TraceLog::GetObserverCountForTest() const {
+ return enabled_state_observer_list_.size();
+}
+
+void TraceLog::SetCurrentThreadBlocksMessageLoop() {
+ thread_blocks_message_loop_.Set(true);
+ // This will flush the thread local buffer.
+ delete thread_local_event_buffer_.Get();
+}
+
+TraceBuffer* TraceLog::CreateTraceBuffer() {
+ HEAP_PROFILER_SCOPED_IGNORE;
+ InternalTraceOptions options = trace_options();
+ if (options & kInternalRecordContinuously) {
+ return TraceBuffer::CreateTraceBufferRingBuffer(
+ kTraceEventRingBufferChunks);
+ }
+ if (options & kInternalEchoToConsole) {
+ return TraceBuffer::CreateTraceBufferRingBuffer(
+ kEchoToConsoleTraceEventBufferChunks);
+ }
+ if (options & kInternalRecordAsMuchAsPossible) {
+ return TraceBuffer::CreateTraceBufferVectorOfSize(
+ kTraceEventVectorBigBufferChunks);
+ }
+ return TraceBuffer::CreateTraceBufferVectorOfSize(
+ kTraceEventVectorBufferChunks);
+}
+
+#if defined(OS_WIN)
+void TraceLog::UpdateETWCategoryGroupEnabledFlags() {
+ // Go through each category and set/clear the ETW bit depending on whether the
+ // category is enabled.
+ for (TraceCategory& category : CategoryRegistry::GetAllCategories()) {
+ if (base::trace_event::TraceEventETWExport::IsCategoryGroupEnabled(
+ category.name())) {
+ category.set_state_flag(TraceCategory::ENABLED_FOR_ETW_EXPORT);
+ } else {
+ category.clear_state_flag(TraceCategory::ENABLED_FOR_ETW_EXPORT);
+ }
+ }
+}
+#endif // defined(OS_WIN)
+
+void TraceLog::SetTraceBufferForTesting(
+ std::unique_ptr<TraceBuffer> trace_buffer) {
+ AutoLock lock(lock_);
+ logged_events_ = std::move(trace_buffer);
+}
+
+void ConvertableToTraceFormat::EstimateTraceMemoryOverhead(
+ TraceEventMemoryOverhead* overhead) {
+ overhead->Add(TraceEventMemoryOverhead::kConvertableToTraceFormat,
+ sizeof(*this));
+}
+
+void TraceLog::AddAsyncEnabledStateObserver(
+ WeakPtr<AsyncEnabledStateObserver> listener) {
+ AutoLock lock(lock_);
+ async_observers_.insert(
+ std::make_pair(listener.get(), RegisteredAsyncObserver(listener)));
+}
+
+void TraceLog::RemoveAsyncEnabledStateObserver(
+ AsyncEnabledStateObserver* listener) {
+ AutoLock lock(lock_);
+ async_observers_.erase(listener);
+}
+
+bool TraceLog::HasAsyncEnabledStateObserver(
+ AsyncEnabledStateObserver* listener) const {
+ AutoLock lock(lock_);
+ return ContainsKey(async_observers_, listener);
+}
+
+} // namespace trace_event
+} // namespace base
+
+namespace trace_event_internal {
+
+ScopedTraceBinaryEfficient::ScopedTraceBinaryEfficient(
+ const char* category_group,
+ const char* name) {
+ // The single atom works because for now the category_group can only be "gpu".
+ DCHECK_EQ(strcmp(category_group, "gpu"), 0);
+ static TRACE_EVENT_API_ATOMIC_WORD atomic = 0;
+ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES(
+ category_group, atomic, category_group_enabled_);
+ name_ = name;
+ if (*category_group_enabled_) {
+ event_handle_ =
+ TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP(
+ TRACE_EVENT_PHASE_COMPLETE, category_group_enabled_, name,
+ trace_event_internal::kGlobalScope, // scope
+ trace_event_internal::kNoId, // id
+ static_cast<int>(base::PlatformThread::CurrentId()), // thread_id
+ TRACE_TIME_TICKS_NOW(), trace_event_internal::kZeroNumArgs, nullptr,
+ nullptr, nullptr, nullptr, TRACE_EVENT_FLAG_NONE);
+ }
+}
+
+ScopedTraceBinaryEfficient::~ScopedTraceBinaryEfficient() {
+ if (*category_group_enabled_) {
+ TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION(category_group_enabled_, name_,
+ event_handle_);
+ }
+}
+
+} // namespace trace_event_internal
diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h
new file mode 100644
index 0000000000..8fc914d724
--- /dev/null
+++ b/base/trace_event/trace_log.h
@@ -0,0 +1,528 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACE_LOG_H_
+#define BASE_TRACE_EVENT_TRACE_LOG_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/containers/stack.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/time/time_override.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "base/trace_event/trace_config.h"
+#include "base/trace_event/trace_event_impl.h"
+#include "build/build_config.h"
+
+namespace base {
+
+class MessageLoop;
+class RefCountedString;
+
+template <typename T>
+class NoDestructor;
+
+namespace trace_event {
+
+struct TraceCategory;
+class TraceBuffer;
+class TraceBufferChunk;
+class TraceEvent;
+class TraceEventFilter;
+class TraceEventMemoryOverhead;
+
+struct BASE_EXPORT TraceLogStatus {
+ TraceLogStatus();
+ ~TraceLogStatus();
+ uint32_t event_capacity;
+ uint32_t event_count;
+};
+
+class BASE_EXPORT TraceLog : public MemoryDumpProvider {
+ public:
+ // Argument passed to TraceLog::SetEnabled.
+ enum Mode : uint8_t {
+ // Enables normal tracing (recording trace events in the trace buffer).
+ RECORDING_MODE = 1 << 0,
+
+ // Trace events are enabled just for filtering but not for recording. Only
+ // event filters config of |trace_config| argument is used.
+ FILTERING_MODE = 1 << 1
+ };
+
+ static TraceLog* GetInstance();
+
+ // Get set of known category groups. This can change as new code paths are
+ // reached. The known category groups are inserted into |category_groups|.
+ void GetKnownCategoryGroups(std::vector<std::string>* category_groups);
+
+ // Retrieves a copy (for thread-safety) of the current TraceConfig.
+ TraceConfig GetCurrentTraceConfig() const;
+
+ // Initializes the thread-local event buffer, if not already initialized and
+ // if the current thread supports that (has a message loop).
+ void InitializeThreadLocalEventBufferIfSupported();
+
+ // See TraceConfig comments for details on how to control which categories
+ // will be traced. SetDisabled must be called distinctly for each mode that is
+ // enabled. If tracing has already been enabled for recording, category filter
+ // (enabled and disabled categories) will be merged into the current category
+ // filter. Enabling RECORDING_MODE does not enable filters. Trace event
+ // filters will be used only if FILTERING_MODE is set on |modes_to_enable|.
+ // Conversely to RECORDING_MODE, FILTERING_MODE doesn't support upgrading,
+ // i.e. filters can only be enabled if not previously enabled.
+ void SetEnabled(const TraceConfig& trace_config, uint8_t modes_to_enable);
+
+ // TODO(ssid): Remove the default SetEnabled and IsEnabled. They should take
+ // Mode as argument.
+
+ // Disables tracing for all categories for the specified |modes_to_disable|
+ // only. Only RECORDING_MODE is taken as default |modes_to_disable|.
+ void SetDisabled();
+ void SetDisabled(uint8_t modes_to_disable);
+
+ // Returns true if TraceLog is enabled on recording mode.
+ // Note: Returns false even if FILTERING_MODE is enabled.
+ bool IsEnabled() { return enabled_modes_ & RECORDING_MODE; }
+
+ // Returns a bitmap of enabled modes from TraceLog::Mode.
+ uint8_t enabled_modes() { return enabled_modes_; }
+
+ // The number of times we have begun recording traces. If tracing is off,
+ // returns -1. If tracing is on, then it returns the number of times we have
+ // recorded a trace. By watching for this number to increment, you can
+ // passively discover when a new trace has begun. This is then used to
+ // implement the TRACE_EVENT_IS_NEW_TRACE() primitive.
+ int GetNumTracesRecorded();
+
+#if defined(OS_ANDROID)
+ void StartATrace();
+ void StopATrace();
+ void AddClockSyncMetadataEvent();
+#endif
+
+ // Enabled state listeners give a callback when tracing is enabled or
+ // disabled. This can be used to tie into other library's tracing systems
+ // on-demand.
+ class BASE_EXPORT EnabledStateObserver {
+ public:
+ virtual ~EnabledStateObserver() = default;
+
+ // Called just after the tracing system becomes enabled, outside of the
+ // |lock_|. TraceLog::IsEnabled() is true at this point.
+ virtual void OnTraceLogEnabled() = 0;
+
+ // Called just after the tracing system disables, outside of the |lock_|.
+ // TraceLog::IsEnabled() is false at this point.
+ virtual void OnTraceLogDisabled() = 0;
+ };
+ void AddEnabledStateObserver(EnabledStateObserver* listener);
+ void RemoveEnabledStateObserver(EnabledStateObserver* listener);
+ bool HasEnabledStateObserver(EnabledStateObserver* listener) const;
+
+ // Asynchronous enabled state listeners. When tracing is enabled or disabled,
+ // for each observer, a task for invoking its appropriate callback is posted
+ // to the thread from which AddAsyncEnabledStateObserver() was called. This
+ // allows the observer to be safely destroyed, provided that it happens on the
+ // same thread that invoked AddAsyncEnabledStateObserver().
+ class BASE_EXPORT AsyncEnabledStateObserver {
+ public:
+ virtual ~AsyncEnabledStateObserver() = default;
+
+ // Posted just after the tracing system becomes enabled, outside |lock_|.
+ // TraceLog::IsEnabled() is true at this point.
+ virtual void OnTraceLogEnabled() = 0;
+
+ // Posted just after the tracing system becomes disabled, outside |lock_|.
+ // TraceLog::IsEnabled() is false at this point.
+ virtual void OnTraceLogDisabled() = 0;
+ };
+ void AddAsyncEnabledStateObserver(
+ WeakPtr<AsyncEnabledStateObserver> listener);
+ void RemoveAsyncEnabledStateObserver(AsyncEnabledStateObserver* listener);
+ bool HasAsyncEnabledStateObserver(AsyncEnabledStateObserver* listener) const;
+
+ TraceLogStatus GetStatus() const;
+ bool BufferIsFull() const;
+
+ // Computes an estimate of the size of the TraceLog including all the retained
+ // objects.
+ void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead);
+
+ void SetArgumentFilterPredicate(
+ const ArgumentFilterPredicate& argument_filter_predicate);
+
+ // Flush all collected events to the given output callback. The callback will
+ // be called one or more times either synchronously or asynchronously from
+ // the current thread with IPC-bite-size chunks. The string format is
+ // undefined. Use TraceResultBuffer to convert one or more trace strings to
+ // JSON. The callback can be null if the caller doesn't want any data.
+ // Due to the implementation of thread-local buffers, flush can't be
+ // done when tracing is enabled. If called when tracing is enabled, the
+ // callback will be called directly with (empty_string, false) to indicate
+ // the end of this unsuccessful flush. Flush does the serialization
+ // on the same thread if the caller doesn't set use_worker_thread explicitly.
+ typedef base::Callback<void(const scoped_refptr<base::RefCountedString>&,
+ bool has_more_events)> OutputCallback;
+ void Flush(const OutputCallback& cb, bool use_worker_thread = false);
+
+ // Cancels tracing and discards collected data.
+ void CancelTracing(const OutputCallback& cb);
+
+ typedef void (*AddTraceEventOverrideCallback)(const TraceEvent&);
+ // The callback will be called up until the point where the flush is
+ // finished, i.e. must be callable until OutputCallback is called with
+ // has_more_events==false.
+ void SetAddTraceEventOverride(const AddTraceEventOverrideCallback& override);
+
+ // Called by TRACE_EVENT* macros, don't call this directly.
+ // The name parameter is a category group for example:
+ // TRACE_EVENT0("renderer,webkit", "WebViewImpl::HandleInputEvent")
+ static const unsigned char* GetCategoryGroupEnabled(const char* name);
+ static const char* GetCategoryGroupName(
+ const unsigned char* category_group_enabled);
+
+ // Called by TRACE_EVENT* macros, don't call this directly.
+ // If |copy| is set, |name|, |arg_name1| and |arg_name2| will be deep copied
+ // into the event; see "Memory scoping note" and TRACE_EVENT_COPY_XXX above.
+ TraceEventHandle AddTraceEvent(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags);
+ TraceEventHandle AddTraceEventWithBindId(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ unsigned long long bind_id,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags);
+ TraceEventHandle AddTraceEventWithProcessId(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ int process_id,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags);
+ TraceEventHandle AddTraceEventWithThreadIdAndTimestamp(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ int thread_id,
+ const TimeTicks& timestamp,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags);
+ TraceEventHandle AddTraceEventWithThreadIdAndTimestamp(
+ char phase,
+ const unsigned char* category_group_enabled,
+ const char* name,
+ const char* scope,
+ unsigned long long id,
+ unsigned long long bind_id,
+ int thread_id,
+ const TimeTicks& timestamp,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags);
+
+ // Adds a metadata event that will be written when the trace log is flushed.
+ void AddMetadataEvent(
+ const unsigned char* category_group_enabled,
+ const char* name,
+ int num_args,
+ const char* const* arg_names,
+ const unsigned char* arg_types,
+ const unsigned long long* arg_values,
+ std::unique_ptr<ConvertableToTraceFormat>* convertable_values,
+ unsigned int flags);
+
+ void UpdateTraceEventDuration(const unsigned char* category_group_enabled,
+ const char* name,
+ TraceEventHandle handle);
+
+ void UpdateTraceEventDurationExplicit(
+ const unsigned char* category_group_enabled,
+ const char* name,
+ TraceEventHandle handle,
+ const TimeTicks& now,
+ const ThreadTicks& thread_now);
+
+ void EndFilteredEvent(const unsigned char* category_group_enabled,
+ const char* name,
+ TraceEventHandle handle);
+
+ int process_id() const { return process_id_; }
+
+ uint64_t MangleEventId(uint64_t id);
+
+ // Exposed for unittesting:
+
+ // Testing factory for TraceEventFilter.
+ typedef std::unique_ptr<TraceEventFilter> (*FilterFactoryForTesting)(
+ const std::string& /* predicate_name */);
+ void SetFilterFactoryForTesting(FilterFactoryForTesting factory) {
+ filter_factory_for_testing_ = factory;
+ }
+
+ // Allows clearing up our singleton instance.
+ static void ResetForTesting();
+
+ // Allow tests to inspect TraceEvents.
+ TraceEvent* GetEventByHandle(TraceEventHandle handle);
+
+ void SetProcessID(int process_id);
+
+ // Process sort indices, if set, override the order of a process will appear
+ // relative to other processes in the trace viewer. Processes are sorted first
+ // on their sort index, ascending, then by their name, and then tid.
+ void SetProcessSortIndex(int sort_index);
+
+ // Sets the name of the process.
+ void set_process_name(const std::string& process_name) {
+ AutoLock lock(lock_);
+ process_name_ = process_name;
+ }
+
+ bool IsProcessNameEmpty() const { return process_name_.empty(); }
+
+ // Processes can have labels in addition to their names. Use labels, for
+ // instance, to list out the web page titles that a process is handling.
+ void UpdateProcessLabel(int label_id, const std::string& current_label);
+ void RemoveProcessLabel(int label_id);
+
+ // Thread sort indices, if set, override the order of a thread will appear
+ // within its process in the trace viewer. Threads are sorted first on their
+ // sort index, ascending, then by their name, and then tid.
+ void SetThreadSortIndex(PlatformThreadId thread_id, int sort_index);
+
+ // Allow setting an offset between the current TimeTicks time and the time
+ // that should be reported.
+ void SetTimeOffset(TimeDelta offset);
+
+ size_t GetObserverCountForTest() const;
+
+ // Call this method if the current thread may block the message loop to
+ // prevent the thread from using the thread-local buffer because the thread
+ // may not handle the flush request in time causing lost of unflushed events.
+ void SetCurrentThreadBlocksMessageLoop();
+
+#if defined(OS_WIN)
+ // This function is called by the ETW exporting module whenever the ETW
+ // keyword (flags) changes. This keyword indicates which categories should be
+ // exported, so whenever it changes, we adjust accordingly.
+ void UpdateETWCategoryGroupEnabledFlags();
+#endif
+
+ // Replaces |logged_events_| with a new TraceBuffer for testing.
+ void SetTraceBufferForTesting(std::unique_ptr<TraceBuffer> trace_buffer);
+
+ private:
+ typedef unsigned int InternalTraceOptions;
+
+ FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture,
+ TraceBufferRingBufferGetReturnChunk);
+ FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture,
+ TraceBufferRingBufferHalfIteration);
+ FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture,
+ TraceBufferRingBufferFullIteration);
+ FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, TraceBufferVectorReportFull);
+ FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture,
+ ConvertTraceConfigToInternalOptions);
+ FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture,
+ TraceRecordAsMuchAsPossibleMode);
+
+ friend class base::NoDestructor<TraceLog>;
+
+ // MemoryDumpProvider implementation.
+ bool OnMemoryDump(const MemoryDumpArgs& args,
+ ProcessMemoryDump* pmd) override;
+
+ // Enable/disable each category group based on the current mode_,
+ // category_filter_ and event_filters_enabled_.
+ // Enable the category group in the recording mode if category_filter_ matches
+ // the category group, is not null. Enable category for filtering if any
+ // filter in event_filters_enabled_ enables it.
+ void UpdateCategoryRegistry();
+ void UpdateCategoryState(TraceCategory* category);
+
+ void CreateFiltersForTraceConfig();
+
+ InternalTraceOptions GetInternalOptionsFromTraceConfig(
+ const TraceConfig& config);
+
+ class ThreadLocalEventBuffer;
+ class OptionalAutoLock;
+ struct RegisteredAsyncObserver;
+
+ TraceLog();
+ ~TraceLog() override;
+ void AddMetadataEventsWhileLocked();
+
+ InternalTraceOptions trace_options() const {
+ return static_cast<InternalTraceOptions>(
+ subtle::NoBarrier_Load(&trace_options_));
+ }
+
+ TraceBuffer* trace_buffer() const { return logged_events_.get(); }
+ TraceBuffer* CreateTraceBuffer();
+
+ std::string EventToConsoleMessage(unsigned char phase,
+ const TimeTicks& timestamp,
+ TraceEvent* trace_event);
+
+ TraceEvent* AddEventToThreadSharedChunkWhileLocked(TraceEventHandle* handle,
+ bool check_buffer_is_full);
+ void CheckIfBufferIsFullWhileLocked();
+ void SetDisabledWhileLocked(uint8_t modes);
+
+ TraceEvent* GetEventByHandleInternal(TraceEventHandle handle,
+ OptionalAutoLock* lock);
+
+ void FlushInternal(const OutputCallback& cb,
+ bool use_worker_thread,
+ bool discard_events);
+
+ // |generation| is used in the following callbacks to check if the callback
+ // is called for the flush of the current |logged_events_|.
+ void FlushCurrentThread(int generation, bool discard_events);
+ // Usually it runs on a different thread.
+ static void ConvertTraceEventsToTraceFormat(
+ std::unique_ptr<TraceBuffer> logged_events,
+ const TraceLog::OutputCallback& flush_output_callback,
+ const ArgumentFilterPredicate& argument_filter_predicate);
+ void FinishFlush(int generation, bool discard_events);
+ void OnFlushTimeout(int generation, bool discard_events);
+
+ int generation() const {
+ return static_cast<int>(subtle::NoBarrier_Load(&generation_));
+ }
+ bool CheckGeneration(int generation) const {
+ return generation == this->generation();
+ }
+ void UseNextTraceBuffer();
+
+ TimeTicks OffsetNow() const {
+ // This should be TRACE_TIME_TICKS_NOW but include order makes that hard.
+ return OffsetTimestamp(base::subtle::TimeTicksNowIgnoringOverride());
+ }
+ TimeTicks OffsetTimestamp(const TimeTicks& timestamp) const {
+ return timestamp - time_offset_;
+ }
+
+ // Internal representation of trace options since we store the currently used
+ // trace option as an AtomicWord.
+ static const InternalTraceOptions kInternalNone;
+ static const InternalTraceOptions kInternalRecordUntilFull;
+ static const InternalTraceOptions kInternalRecordContinuously;
+ static const InternalTraceOptions kInternalEchoToConsole;
+ static const InternalTraceOptions kInternalRecordAsMuchAsPossible;
+ static const InternalTraceOptions kInternalEnableArgumentFilter;
+
+ // This lock protects TraceLog member accesses (except for members protected
+ // by thread_info_lock_) from arbitrary threads.
+ mutable Lock lock_;
+ // This lock protects accesses to thread_names_, thread_event_start_times_
+ // and thread_colors_.
+ Lock thread_info_lock_;
+ uint8_t enabled_modes_; // See TraceLog::Mode.
+ int num_traces_recorded_;
+ std::unique_ptr<TraceBuffer> logged_events_;
+ std::vector<std::unique_ptr<TraceEvent>> metadata_events_;
+ bool dispatching_to_observer_list_;
+ std::vector<EnabledStateObserver*> enabled_state_observer_list_;
+ std::map<AsyncEnabledStateObserver*, RegisteredAsyncObserver>
+ async_observers_;
+
+ std::string process_name_;
+ std::unordered_map<int, std::string> process_labels_;
+ int process_sort_index_;
+ std::unordered_map<int, int> thread_sort_indices_;
+ std::unordered_map<int, std::string> thread_names_;
+ base::Time process_creation_time_;
+
+ // The following two maps are used only when ECHO_TO_CONSOLE.
+ std::unordered_map<int, base::stack<TimeTicks>> thread_event_start_times_;
+ std::unordered_map<std::string, int> thread_colors_;
+
+ TimeTicks buffer_limit_reached_timestamp_;
+
+ // XORed with TraceID to make it unlikely to collide with other processes.
+ unsigned long long process_id_hash_;
+
+ int process_id_;
+
+ TimeDelta time_offset_;
+
+ subtle::AtomicWord /* Options */ trace_options_;
+
+ TraceConfig trace_config_;
+ TraceConfig::EventFilters enabled_event_filters_;
+
+ ThreadLocalPointer<ThreadLocalEventBuffer> thread_local_event_buffer_;
+ ThreadLocalBoolean thread_blocks_message_loop_;
+ ThreadLocalBoolean thread_is_in_trace_event_;
+
+ // Contains the message loops of threads that have had at least one event
+ // added into the local event buffer. Not using SingleThreadTaskRunner
+ // because we need to know the life time of the message loops.
+ hash_set<MessageLoop*> thread_message_loops_;
+
+ // For events which can't be added into the thread local buffer, e.g. events
+ // from threads without a message loop.
+ std::unique_ptr<TraceBufferChunk> thread_shared_chunk_;
+ size_t thread_shared_chunk_index_;
+
+ // Set when asynchronous Flush is in progress.
+ OutputCallback flush_output_callback_;
+ scoped_refptr<SequencedTaskRunner> flush_task_runner_;
+ ArgumentFilterPredicate argument_filter_predicate_;
+ subtle::AtomicWord generation_;
+ bool use_worker_thread_;
+ subtle::AtomicWord trace_event_override_;
+
+ FilterFactoryForTesting filter_factory_for_testing_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceLog);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACE_LOG_H_
diff --git a/base/trace_event/trace_log_constants.cc b/base/trace_event/trace_log_constants.cc
new file mode 100644
index 0000000000..65dca2e4d6
--- /dev/null
+++ b/base/trace_event/trace_log_constants.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/trace_log.h"
+
+namespace base {
+namespace trace_event {
+
+// Constant used by TraceLog's internal implementation of trace_option.
+const TraceLog::InternalTraceOptions
+ TraceLog::kInternalNone = 0;
+const TraceLog::InternalTraceOptions
+ TraceLog::kInternalRecordUntilFull = 1 << 0;
+const TraceLog::InternalTraceOptions
+ TraceLog::kInternalRecordContinuously = 1 << 1;
+// 1 << 2 is reserved for the DEPRECATED kInternalEnableSampling. DO NOT USE.
+const TraceLog::InternalTraceOptions
+ TraceLog::kInternalEchoToConsole = 1 << 3;
+const TraceLog::InternalTraceOptions
+ TraceLog::kInternalRecordAsMuchAsPossible = 1 << 4;
+const TraceLog::InternalTraceOptions
+ TraceLog::kInternalEnableArgumentFilter = 1 << 5;
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/tracing_agent.cc b/base/trace_event/tracing_agent.cc
new file mode 100644
index 0000000000..e48feff6fc
--- /dev/null
+++ b/base/trace_event/tracing_agent.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/trace_event/tracing_agent.h"
+
+namespace base {
+namespace trace_event {
+
+TracingAgent::~TracingAgent() = default;
+
+bool TracingAgent::SupportsExplicitClockSync() {
+ return false;
+}
+
+void TracingAgent::RecordClockSyncMarker(
+ const std::string& sync_id,
+ RecordClockSyncMarkerCallback callback) {
+ DCHECK(SupportsExplicitClockSync());
+}
+
+
+} // namespace trace_event
+} // namespace base
diff --git a/base/trace_event/tracing_agent.h b/base/trace_event/tracing_agent.h
new file mode 100644
index 0000000000..f818457c58
--- /dev/null
+++ b/base/trace_event/tracing_agent.h
@@ -0,0 +1,96 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TRACE_EVENT_TRACING_AGENT_H_
+#define BASE_TRACE_EVENT_TRACING_AGENT_H_
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/values.h"
+
+namespace base {
+
+class TimeTicks;
+
+namespace trace_event {
+
+class TraceConfig;
+
+// A tracing agent is an entity that records its own sort of trace. Each
+// tracing method that produces its own trace log should implement this
+// interface. All tracing agents must only be controlled by TracingController.
+// Some existing examples include TracingControllerImpl for Chrome trace events,
+// DebugDaemonClient for CrOs system trace, EtwTracingAgent for Windows system
+// trace and PowerTracingAgent for BattOr power trace.
+class BASE_EXPORT TracingAgent {
+ public:
+ using StartAgentTracingCallback =
+ base::OnceCallback<void(const std::string& agent_name, bool success)>;
+ // Passing a null or empty events_str_ptr indicates that no trace data is
+ // available for the specified agent.
+ using StopAgentTracingCallback = base::OnceCallback<void(
+ const std::string& agent_name,
+ const std::string& events_label,
+ const scoped_refptr<base::RefCountedString>& events_str_ptr)>;
+ using RecordClockSyncMarkerCallback =
+ base::OnceCallback<void(const std::string& sync_id,
+ const TimeTicks& issue_ts,
+ const TimeTicks& issue_end_ts)>;
+
+ virtual ~TracingAgent();
+
+ // Gets the name of the tracing agent. Each tracing agent's name should be
+ // unique.
+ virtual std::string GetTracingAgentName() = 0;
+
+ // Gets the trace event label of this tracing agent. The label will be used to
+ // label this agent's trace when all traces from different tracing agents are
+ // combined. Multiple tracing agents could have the same label. The tracing
+ // agents using the same label should not be able to run at the same time. For
+ // example, ETW on Windows and CrOS system tracing both use
+ // "systemTraceEvents" as the label. Those two agents never run at the same
+ // time because they are for different platforms.
+ virtual std::string GetTraceEventLabel() = 0;
+
+ // Starts tracing on the tracing agent with the trace configuration.
+ virtual void StartAgentTracing(const TraceConfig& trace_config,
+ StartAgentTracingCallback callback) = 0;
+
+ // Stops tracing on the tracing agent. The trace data will be passed back to
+ // the TracingController via the callback.
+ virtual void StopAgentTracing(StopAgentTracingCallback callback) = 0;
+
+ // Checks if the tracing agent supports explicit clock synchronization.
+ virtual bool SupportsExplicitClockSync();
+
+ // Records a clock sync marker issued by another tracing agent. This is only
+ // used if the tracing agent supports explicit clock synchronization.
+ //
+ // Two things need to be done:
+ // 1. The issuer asks the receiver to record the clock sync marker.
+ // 2. The issuer records how long the receiver takes to do the recording.
+ //
+ // In Chrome, the receiver thread also runs in Chrome and it will talk to the
+ // real receiver entity, e.g., power monitor or Android device system, via
+ // different communication methods, e.g., through USB or file reading/writing.
+ // The 2nd task measures that communication latency.
+ //
+ // Having a reliable timing measurement for the 2nd task requires synchronous
+ // function call without any cross-thread or cross-process activity. However,
+ // tracing agents in Chrome run in their own threads. Therefore, the issuer
+ // needs to dedicate the 2nd task to the receiver to take time measurements
+ // in the receiver thread, and the receiver thread needs to pass them back to
+ // the issuer in the callback.
+ //
+ // The assumption is that the receiver thread knows the issuer's clock, which
+ // is true in Chrome because all agent threads' clocks are Chrome clock.
+ virtual void RecordClockSyncMarker(const std::string& sync_id,
+ RecordClockSyncMarkerCallback callback);
+};
+
+} // namespace trace_event
+} // namespace base
+
+#endif // BASE_TRACE_EVENT_TRACING_AGENT_H_
diff --git a/base/unguessable_token_unittest.cc b/base/unguessable_token_unittest.cc
new file mode 100644
index 0000000000..b70cc72444
--- /dev/null
+++ b/base/unguessable_token_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/unguessable_token.h"
+
+#include <memory>
+#include <sstream>
+#include <type_traits>
+
+#include "base/value_conversions.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+void TestSmallerThanOperator(const UnguessableToken& a,
+ const UnguessableToken& b) {
+ EXPECT_TRUE(a < b);
+ EXPECT_FALSE(b < a);
+}
+
+TEST(UnguessableTokenTest, VerifyEqualityOperators) {
+ // Deserialize is used for testing purposes.
+ // Use UnguessableToken::Create() in production code instead.
+ UnguessableToken token = UnguessableToken::Deserialize(1, 2);
+ UnguessableToken same_token = UnguessableToken::Deserialize(1, 2);
+ UnguessableToken diff_token = UnguessableToken::Deserialize(1, 3);
+
+ EXPECT_TRUE(token == token);
+ EXPECT_FALSE(token != token);
+
+ EXPECT_TRUE(token == same_token);
+ EXPECT_FALSE(token != same_token);
+
+ EXPECT_FALSE(token == diff_token);
+ EXPECT_FALSE(diff_token == token);
+ EXPECT_TRUE(token != diff_token);
+ EXPECT_TRUE(diff_token != token);
+}
+
+TEST(UnguessableTokenTest, VerifyConstructors) {
+ UnguessableToken token = UnguessableToken::Create();
+ EXPECT_FALSE(token.is_empty());
+ EXPECT_TRUE(token);
+
+ UnguessableToken copied_token(token);
+ EXPECT_TRUE(copied_token);
+ EXPECT_EQ(token, copied_token);
+
+ UnguessableToken uninitialized;
+ EXPECT_TRUE(uninitialized.is_empty());
+ EXPECT_FALSE(uninitialized);
+
+ EXPECT_TRUE(UnguessableToken().is_empty());
+ EXPECT_FALSE(UnguessableToken());
+}
+
+TEST(UnguessableTokenTest, VerifySerialization) {
+ UnguessableToken token = UnguessableToken::Create();
+
+ uint64_t high = token.GetHighForSerialization();
+ uint64_t low = token.GetLowForSerialization();
+
+ EXPECT_TRUE(high);
+ EXPECT_TRUE(low);
+
+ UnguessableToken Deserialized = UnguessableToken::Deserialize(high, low);
+ EXPECT_EQ(token, Deserialized);
+}
+
+TEST(UnguessableTokenTest, VerifyValueSerialization) {
+ UnguessableToken token = UnguessableToken::Create();
+ std::unique_ptr<Value> value = CreateUnguessableTokenValue(token);
+
+ UnguessableToken deserialized;
+ EXPECT_TRUE(GetValueAsUnguessableToken(*value, &deserialized));
+ EXPECT_EQ(token, deserialized);
+}
+
+// Common case (~88% of the time) - no leading zeroes in high_ nor low_.
+TEST(UnguessableTokenTest, VerifyToString1) {
+ UnguessableToken token =
+ UnguessableToken::Deserialize(0x1234567890ABCDEF, 0xFEDCBA0987654321);
+ std::string expected = "1234567890ABCDEFFEDCBA0987654321";
+
+ EXPECT_EQ(expected, token.ToString());
+
+ std::string expected_stream = "(1234567890ABCDEFFEDCBA0987654321)";
+ std::stringstream stream;
+ stream << token;
+ EXPECT_EQ(expected_stream, stream.str());
+}
+
+// Less common case - leading zeroes in high_ or low_ (testing with both).
+TEST(UnguessableTokenTest, VerifyToString2) {
+ UnguessableToken token = UnguessableToken::Deserialize(0x123, 0xABC);
+ std::string expected = "00000000000001230000000000000ABC";
+
+ EXPECT_EQ(expected, token.ToString());
+
+ std::string expected_stream = "(00000000000001230000000000000ABC)";
+ std::stringstream stream;
+ stream << token;
+ EXPECT_EQ(expected_stream, stream.str());
+}
+
+TEST(UnguessableTokenTest, VerifyToStringUniqueness) {
+ const UnguessableToken token1 =
+ UnguessableToken::Deserialize(0x0000000012345678, 0x0000000123456789);
+ const UnguessableToken token2 =
+ UnguessableToken::Deserialize(0x0000000123456781, 0x0000000023456789);
+ EXPECT_NE(token1.ToString(), token2.ToString());
+}
+
+TEST(UnguessableTokenTest, VerifySmallerThanOperator) {
+ // Deserialize is used for testing purposes.
+ // Use UnguessableToken::Create() in production code instead.
+ {
+ SCOPED_TRACE("a.low < b.low and a.high == b.high.");
+ TestSmallerThanOperator(UnguessableToken::Deserialize(0, 1),
+ UnguessableToken::Deserialize(0, 5));
+ }
+ {
+ SCOPED_TRACE("a.low == b.low and a.high < b.high.");
+ TestSmallerThanOperator(UnguessableToken::Deserialize(1, 0),
+ UnguessableToken::Deserialize(5, 0));
+ }
+ {
+ SCOPED_TRACE("a.low < b.low and a.high < b.high.");
+ TestSmallerThanOperator(UnguessableToken::Deserialize(1, 1),
+ UnguessableToken::Deserialize(5, 5));
+ }
+ {
+ SCOPED_TRACE("a.low > b.low and a.high < b.high.");
+ TestSmallerThanOperator(UnguessableToken::Deserialize(1, 10),
+ UnguessableToken::Deserialize(10, 1));
+ }
+}
+
+TEST(UnguessableTokenTest, VerifyHash) {
+ UnguessableToken token = UnguessableToken::Create();
+
+ EXPECT_EQ(base::HashInts64(token.GetHighForSerialization(),
+ token.GetLowForSerialization()),
+ UnguessableTokenHash()(token));
+}
+
+TEST(UnguessableTokenTest, VerifyBasicUniqueness) {
+ EXPECT_NE(UnguessableToken::Create(), UnguessableToken::Create());
+
+ UnguessableToken token = UnguessableToken::Create();
+ EXPECT_NE(token.GetHighForSerialization(), token.GetLowForSerialization());
+}
+}
diff --git a/base/value_conversions.cc b/base/value_conversions.cc
new file mode 100644
index 0000000000..7e3fd94551
--- /dev/null
+++ b/base/value_conversions.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/value_conversions.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "base/unguessable_token.h"
+#include "base/values.h"
+
+namespace base {
+namespace {
+// Helper for serialize/deserialize UnguessableToken.
+union UnguessableTokenRepresentation {
+ struct Field {
+ uint64_t high;
+ uint64_t low;
+ } field;
+
+ uint8_t buffer[sizeof(Field)];
+};
+} // namespace
+
+// |Value| internally stores strings in UTF-8, so we have to convert from the
+// system native code to UTF-8 and back.
+std::unique_ptr<Value> CreateFilePathValue(const FilePath& in_value) {
+ return std::make_unique<Value>(in_value.AsUTF8Unsafe());
+}
+
+bool GetValueAsFilePath(const Value& value, FilePath* file_path) {
+ std::string str;
+ if (!value.GetAsString(&str))
+ return false;
+ if (file_path)
+ *file_path = FilePath::FromUTF8Unsafe(str);
+ return true;
+}
+
+// |Value| does not support 64-bit integers, and doubles do not have enough
+// precision, so we store the 64-bit time value as a string instead.
+std::unique_ptr<Value> CreateTimeDeltaValue(const TimeDelta& time) {
+ std::string string_value = base::Int64ToString(time.ToInternalValue());
+ return std::make_unique<Value>(string_value);
+}
+
+bool GetValueAsTimeDelta(const Value& value, TimeDelta* time) {
+ std::string str;
+ int64_t int_value;
+ if (!value.GetAsString(&str) || !base::StringToInt64(str, &int_value))
+ return false;
+ if (time)
+ *time = TimeDelta::FromInternalValue(int_value);
+ return true;
+}
+
+std::unique_ptr<Value> CreateUnguessableTokenValue(
+ const UnguessableToken& token) {
+ UnguessableTokenRepresentation representation;
+ representation.field.high = token.GetHighForSerialization();
+ representation.field.low = token.GetLowForSerialization();
+
+ return std::make_unique<Value>(
+ HexEncode(representation.buffer, sizeof(representation.buffer)));
+}
+
+bool GetValueAsUnguessableToken(const Value& value, UnguessableToken* token) {
+ if (!value.is_string()) {
+ return false;
+ }
+
+ // TODO(dcheng|yucliu): Make a function that accepts non vector variant and
+ // reads a fixed number of bytes.
+ std::vector<uint8_t> high_low_bytes;
+ if (!HexStringToBytes(value.GetString(), &high_low_bytes)) {
+ return false;
+ }
+
+ UnguessableTokenRepresentation representation;
+ if (high_low_bytes.size() != sizeof(representation.buffer)) {
+ return false;
+ }
+
+ std::copy(high_low_bytes.begin(), high_low_bytes.end(),
+ std::begin(representation.buffer));
+ *token = UnguessableToken::Deserialize(representation.field.high,
+ representation.field.low);
+ return true;
+}
+
+} // namespace base
diff --git a/base/value_conversions.h b/base/value_conversions.h
new file mode 100644
index 0000000000..bd095cdc4c
--- /dev/null
+++ b/base/value_conversions.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_VALUE_CONVERSIONS_H_
+#define BASE_VALUE_CONVERSIONS_H_
+
+// This file contains methods to convert things to a |Value| and back.
+
+#include <memory>
+#include "base/base_export.h"
+
+namespace base {
+
+class FilePath;
+class TimeDelta;
+class UnguessableToken;
+class Value;
+
+// The caller takes ownership of the returned value.
+BASE_EXPORT std::unique_ptr<Value> CreateFilePathValue(
+ const FilePath& in_value);
+BASE_EXPORT bool GetValueAsFilePath(const Value& value, FilePath* file_path);
+
+BASE_EXPORT std::unique_ptr<Value> CreateTimeDeltaValue(const TimeDelta& time);
+BASE_EXPORT bool GetValueAsTimeDelta(const Value& value, TimeDelta* time);
+
+BASE_EXPORT std::unique_ptr<Value> CreateUnguessableTokenValue(
+ const UnguessableToken& token);
+BASE_EXPORT bool GetValueAsUnguessableToken(const Value& value,
+ UnguessableToken* token);
+
+} // namespace base
+
+#endif // BASE_VALUE_CONVERSIONS_H_
diff --git a/build/android/gyp/util/build_utils_test.py b/build/android/gyp/util/build_utils_test.py
new file mode 100755
index 0000000000..bcc892f39b
--- /dev/null
+++ b/build/android/gyp/util/build_utils_test.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import collections
+import unittest
+
+import build_utils # pylint: disable=W0403
+
+_DEPS = collections.OrderedDict()
+_DEPS['a'] = []
+_DEPS['b'] = []
+_DEPS['c'] = ['a']
+_DEPS['d'] = ['a']
+_DEPS['e'] = ['f']
+_DEPS['f'] = ['a', 'd']
+_DEPS['g'] = []
+_DEPS['h'] = ['d', 'b', 'f']
+_DEPS['i'] = ['f']
+
+
+class BuildUtilsTest(unittest.TestCase):
+ def testGetSortedTransitiveDependencies_all(self):
+ TOP = _DEPS.keys()
+ EXPECTED = ['a', 'b', 'c', 'd', 'f', 'e', 'g', 'h', 'i']
+ actual = build_utils.GetSortedTransitiveDependencies(TOP, _DEPS.get)
+ self.assertEqual(EXPECTED, actual)
+
+ def testGetSortedTransitiveDependencies_leaves(self):
+ TOP = ['c', 'e', 'g', 'h', 'i']
+ EXPECTED = ['a', 'c', 'd', 'f', 'e', 'g', 'b', 'h', 'i']
+ actual = build_utils.GetSortedTransitiveDependencies(TOP, _DEPS.get)
+ self.assertEqual(EXPECTED, actual)
+
+ def testGetSortedTransitiveDependencies_leavesReverse(self):
+ TOP = ['i', 'h', 'g', 'e', 'c']
+ EXPECTED = ['a', 'd', 'f', 'i', 'b', 'h', 'g', 'e', 'c']
+ actual = build_utils.GetSortedTransitiveDependencies(TOP, _DEPS.get)
+ self.assertEqual(EXPECTED, actual)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/gyp/util/jar_info_utils.py b/build/android/gyp/util/jar_info_utils.py
new file mode 100644
index 0000000000..987ee9dcf1
--- /dev/null
+++ b/build/android/gyp/util/jar_info_utils.py
@@ -0,0 +1,52 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+
+# Utilities to read and write .jar.info files.
+#
+# A .jar.info file contains a simple mapping from fully-qualified Java class
+# names to the source file that actually defines it.
+#
+# For APKs, the .jar.info maps the class names to the .jar file that which
+# contains its .class definition instead.
+
+
+def ParseJarInfoFile(info_path):
+ """Parse a given .jar.info file as a dictionary.
+
+ Args:
+ info_path: input .jar.info file path.
+ Returns:
+ A new dictionary mapping fully-qualified Java class names to file paths.
+ """
+ info_data = dict()
+ if os.path.exists(info_path):
+ with open(info_path, 'r') as info_file:
+ for line in info_file:
+ line = line.strip()
+ if line:
+ fully_qualified_name, path = line.split(',', 1)
+ info_data[fully_qualified_name] = path
+ return info_data
+
+
+def WriteJarInfoFile(info_path, info_data, source_file_map=None):
+ """Generate a .jar.info file from a given dictionary.
+
+ Args:
+ info_path: output file path.
+ info_data: a mapping of fully qualified Java class names to filepaths.
+ source_file_map: an optional mapping from java source file paths to the
+ corresponding source .srcjar. This is because info_data may contain the
+ path of Java source files that where extracted from an .srcjar into a
+ temporary location.
+ """
+ with open(info_path, 'w') as info_file:
+ for fully_qualified_name, path in info_data.iteritems():
+ if source_file_map and path in source_file_map:
+ path = source_file_map[path]
+ assert not path.startswith('/tmp'), (
+ 'Java file path should not be in temp dir: {}'.format(path))
+ info_file.write('{},{}\n'.format(fully_qualified_name, path))
diff --git a/build/android/gyp/util/md5_check_test.py b/build/android/gyp/util/md5_check_test.py
new file mode 100755
index 0000000000..312d4a98cb
--- /dev/null
+++ b/build/android/gyp/util/md5_check_test.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import fnmatch
+import tempfile
+import unittest
+import zipfile
+
+import md5_check # pylint: disable=W0403
+
+
+def _WriteZipFile(path, entries):
+ with zipfile.ZipFile(path, 'w') as zip_file:
+ for subpath, data in entries:
+ zip_file.writestr(subpath, data)
+
+
+class TestMd5Check(unittest.TestCase):
+ def setUp(self):
+ self.called = False
+ self.changes = None
+
+ def testCallAndRecordIfStale(self):
+ input_strings = ['string1', 'string2']
+ input_file1 = tempfile.NamedTemporaryFile(suffix='.txt')
+ input_file2 = tempfile.NamedTemporaryFile(suffix='.zip')
+ file1_contents = 'input file 1'
+ input_file1.write(file1_contents)
+ input_file1.flush()
+ # Test out empty zip file to start.
+ _WriteZipFile(input_file2.name, [])
+ input_files = [input_file1.name, input_file2.name]
+
+ record_path = tempfile.NamedTemporaryFile(suffix='.stamp')
+
+ def CheckCallAndRecord(should_call, message, force=False,
+ outputs_specified=False, outputs_missing=False,
+ expected_changes=None, added_or_modified_only=None):
+ output_paths = None
+ if outputs_specified:
+ output_file1 = tempfile.NamedTemporaryFile()
+ if outputs_missing:
+ output_file1.close() # Gets deleted on close().
+ output_paths = [output_file1.name]
+
+ self.called = False
+ self.changes = None
+ if expected_changes or added_or_modified_only is not None:
+ def MarkCalled(changes):
+ self.called = True
+ self.changes = changes
+ else:
+ def MarkCalled():
+ self.called = True
+
+ md5_check.CallAndRecordIfStale(
+ MarkCalled,
+ record_path=record_path.name,
+ input_paths=input_files,
+ input_strings=input_strings,
+ output_paths=output_paths,
+ force=force,
+ pass_changes=(expected_changes or added_or_modified_only) is not None)
+ self.assertEqual(should_call, self.called, message)
+ if expected_changes:
+ description = self.changes.DescribeDifference()
+ self.assertTrue(fnmatch.fnmatch(description, expected_changes),
+ 'Expected %s to match %s' % (
+ repr(description), repr(expected_changes)))
+ if should_call and added_or_modified_only is not None:
+ self.assertEqual(added_or_modified_only,
+ self.changes.AddedOrModifiedOnly())
+
+ CheckCallAndRecord(True, 'should call when record doesn\'t exist',
+ expected_changes='Previous stamp file not found.',
+ added_or_modified_only=False)
+ CheckCallAndRecord(False, 'should not call when nothing changed')
+ CheckCallAndRecord(False, 'should not call when nothing changed #2',
+ outputs_specified=True, outputs_missing=False)
+ CheckCallAndRecord(True, 'should call when output missing',
+ outputs_specified=True, outputs_missing=True,
+ expected_changes='Outputs do not exist:*',
+ added_or_modified_only=False)
+ CheckCallAndRecord(True, force=True, message='should call when forced',
+ expected_changes='force=True',
+ added_or_modified_only=False)
+
+ input_file1.write('some more input')
+ input_file1.flush()
+ CheckCallAndRecord(True, 'changed input file should trigger call',
+ expected_changes='*Modified: %s' % input_file1.name,
+ added_or_modified_only=True)
+
+ input_files = input_files[::-1]
+ CheckCallAndRecord(False, 'reordering of inputs shouldn\'t trigger call')
+
+ input_files = input_files[:1]
+ CheckCallAndRecord(True, 'removing file should trigger call',
+ expected_changes='*Removed: %s' % input_file1.name,
+ added_or_modified_only=False)
+
+ input_files.append(input_file1.name)
+ CheckCallAndRecord(True, 'added input file should trigger call',
+ expected_changes='*Added: %s' % input_file1.name,
+ added_or_modified_only=True)
+
+ input_strings[0] = input_strings[0] + ' a bit longer'
+ CheckCallAndRecord(True, 'changed input string should trigger call',
+ expected_changes='*Input strings changed*',
+ added_or_modified_only=False)
+
+ input_strings = input_strings[::-1]
+ CheckCallAndRecord(True, 'reordering of string inputs should trigger call',
+ expected_changes='*Input strings changed*')
+
+ input_strings = input_strings[:1]
+ CheckCallAndRecord(True, 'removing a string should trigger call')
+
+ input_strings.append('a brand new string')
+ CheckCallAndRecord(True, 'added input string should trigger call')
+
+ _WriteZipFile(input_file2.name, [('path/1.txt', '1')])
+ CheckCallAndRecord(True, 'added subpath should trigger call',
+ expected_changes='*Modified: %s*Subpath added: %s' % (
+ input_file2.name, 'path/1.txt'),
+ added_or_modified_only=True)
+ _WriteZipFile(input_file2.name, [('path/1.txt', '2')])
+ CheckCallAndRecord(True, 'changed subpath should trigger call',
+ expected_changes='*Modified: %s*Subpath modified: %s' % (
+ input_file2.name, 'path/1.txt'),
+ added_or_modified_only=True)
+ CheckCallAndRecord(False, 'should not call when nothing changed')
+
+ _WriteZipFile(input_file2.name, [])
+ CheckCallAndRecord(True, 'removed subpath should trigger call',
+ expected_changes='*Modified: %s*Subpath removed: %s' % (
+ input_file2.name, 'path/1.txt'),
+ added_or_modified_only=False)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/gyp/util/proguard_util.py b/build/android/gyp/util/proguard_util.py
new file mode 100644
index 0000000000..fd657e2aa7
--- /dev/null
+++ b/build/android/gyp/util/proguard_util.py
@@ -0,0 +1,212 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import re
+from util import build_utils
+
+
+class ProguardOutputFilter(object):
+ """ProGuard outputs boring stuff to stdout (proguard version, jar path, etc)
+ as well as interesting stuff (notes, warnings, etc). If stdout is entirely
+ boring, this class suppresses the output.
+ """
+
+ IGNORE_RE = re.compile(
+ r'Pro.*version|Note:|Reading|Preparing|Printing|ProgramClass:|Searching|'
+ r'jar \[|\d+ class path entries checked')
+
+ def __init__(self):
+ self._last_line_ignored = False
+ self._ignore_next_line = False
+
+ def __call__(self, output):
+ ret = []
+ for line in output.splitlines(True):
+ if self._ignore_next_line:
+ self._ignore_next_line = False
+ continue
+
+ if '***BINARY RUN STATS***' in line:
+ self._last_line_ignored = True
+ self._ignore_next_line = True
+ elif not line.startswith(' '):
+ self._last_line_ignored = bool(self.IGNORE_RE.match(line))
+ elif 'You should check if you need to specify' in line:
+ self._last_line_ignored = True
+
+ if not self._last_line_ignored:
+ ret.append(line)
+ return ''.join(ret)
+
+
+class ProguardCmdBuilder(object):
+ def __init__(self, proguard_jar):
+ assert os.path.exists(proguard_jar)
+ self._proguard_jar_path = proguard_jar
+ self._mapping = None
+ self._libraries = None
+ self._injars = None
+ self._configs = None
+ self._config_exclusions = None
+ self._outjar = None
+ self._verbose = False
+ self._disabled_optimizations = []
+
+ def outjar(self, path):
+ assert self._outjar is None
+ self._outjar = path
+
+ def mapping(self, path):
+ assert self._mapping is None
+ assert os.path.exists(path), path
+ self._mapping = path
+
+ def libraryjars(self, paths):
+ assert self._libraries is None
+ for p in paths:
+ assert os.path.exists(p), p
+ self._libraries = paths
+
+ def injars(self, paths):
+ assert self._injars is None
+ for p in paths:
+ assert os.path.exists(p), p
+ self._injars = paths
+
+ def configs(self, paths):
+ assert self._configs is None
+ self._configs = paths
+ for p in self._configs:
+ assert os.path.exists(p), p
+
+ def config_exclusions(self, paths):
+ assert self._config_exclusions is None
+ self._config_exclusions = paths
+
+ def verbose(self, verbose):
+ self._verbose = verbose
+
+ def disable_optimizations(self, optimizations):
+ self._disabled_optimizations += optimizations
+
+ def build(self):
+ assert self._injars is not None
+ assert self._outjar is not None
+ assert self._configs is not None
+ cmd = [
+ 'java', '-jar', self._proguard_jar_path,
+ '-forceprocessing',
+ ]
+
+ if self._mapping:
+ cmd += ['-applymapping', self._mapping]
+
+ if self._libraries:
+ cmd += ['-libraryjars', ':'.join(self._libraries)]
+
+ for optimization in self._disabled_optimizations:
+ cmd += [ '-optimizations', '!' + optimization ]
+
+ # Filter to just .class files to avoid warnings about multiple inputs having
+ # the same files in META_INF/.
+ cmd += [
+ '-injars',
+ ':'.join('{}(**.class)'.format(x) for x in self._injars)
+ ]
+
+ for config_file in self.GetConfigs():
+ cmd += ['-include', config_file]
+
+ # The output jar must be specified after inputs.
+ cmd += [
+ '-outjars', self._outjar,
+ '-printseeds', self._outjar + '.seeds',
+ '-printusage', self._outjar + '.usage',
+ '-printmapping', self._outjar + '.mapping',
+ ]
+
+ if self._verbose:
+ cmd.append('-verbose')
+
+ return cmd
+
+ def GetDepfileDeps(self):
+ # The list of inputs that the GN target does not directly know about.
+ inputs = self._configs + self._injars
+ if self._libraries:
+ inputs += self._libraries
+ return inputs
+
+ def GetConfigs(self):
+ ret = list(self._configs)
+ for path in self._config_exclusions:
+ ret.remove(path)
+ return ret
+
+ def GetInputs(self):
+ inputs = self.GetDepfileDeps()
+ inputs += [self._proguard_jar_path]
+ if self._mapping:
+ inputs.append(self._mapping)
+ return inputs
+
+ def GetOutputs(self):
+ return [
+ self._outjar,
+ self._outjar + '.flags',
+ self._outjar + '.mapping',
+ self._outjar + '.seeds',
+ self._outjar + '.usage',
+ ]
+
+ def _WriteFlagsFile(self, cmd, out):
+ # Quite useful for auditing proguard flags.
+ for config in sorted(self._configs):
+ out.write('#' * 80 + '\n')
+ out.write(config + '\n')
+ out.write('#' * 80 + '\n')
+ with open(config) as config_file:
+ contents = config_file.read().rstrip()
+ # Remove numbers from generated rule comments to make file more
+ # diff'able.
+ contents = re.sub(r' #generated:\d+', '', contents)
+ out.write(contents)
+ out.write('\n\n')
+ out.write('#' * 80 + '\n')
+ out.write('Command-line\n')
+ out.write('#' * 80 + '\n')
+ out.write(' '.join(cmd) + '\n')
+
+ def CheckOutput(self):
+ cmd = self.build()
+
+ # There are a couple scenarios (.mapping files and switching from no
+ # proguard -> proguard) where GN's copy() target is used on output
+ # paths. These create hardlinks, so we explicitly unlink here to avoid
+ # updating files with multiple links.
+ for path in self.GetOutputs():
+ if os.path.exists(path):
+ os.unlink(path)
+
+ with open(self._outjar + '.flags', 'w') as out:
+ self._WriteFlagsFile(cmd, out)
+
+ # Warning: and Error: are sent to stderr, but messages and Note: are sent
+ # to stdout.
+ stdout_filter = None
+ stderr_filter = None
+ if not self._verbose:
+ stdout_filter = ProguardOutputFilter()
+ stderr_filter = ProguardOutputFilter()
+ build_utils.CheckOutput(cmd, print_stdout=True,
+ print_stderr=True,
+ stdout_filter=stdout_filter,
+ stderr_filter=stderr_filter)
+
+ # Proguard will skip writing -printseeds / -printusage / -printmapping if
+ # the files would be empty, but ninja needs all outputs to exist.
+ open(self._outjar + '.seeds', 'a').close()
+ open(self._outjar + '.usage', 'a').close()
+ open(self._outjar + '.mapping', 'a').close()
diff --git a/build/android/gyp/util/resource_utils.py b/build/android/gyp/util/resource_utils.py
new file mode 100644
index 0000000000..875fd12631
--- /dev/null
+++ b/build/android/gyp/util/resource_utils.py
@@ -0,0 +1,511 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import collections
+import contextlib
+import os
+import re
+import shutil
+import sys
+import tempfile
+from xml.etree import ElementTree
+
+import util.build_utils as build_utils
+
+_SOURCE_ROOT = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
+# Import jinja2 from third_party/jinja2
+sys.path.insert(1, os.path.join(_SOURCE_ROOT, 'third_party'))
+from jinja2 import Template # pylint: disable=F0401
+
+
+EMPTY_ANDROID_MANIFEST_PATH = os.path.join(
+ _SOURCE_ROOT, 'build', 'android', 'AndroidManifest.xml')
+
+
+# A variation of this lists also exists in:
+# //base/android/java/src/org/chromium/base/LocaleUtils.java
+# //ui/android/java/src/org/chromium/base/LocalizationUtils.java
+CHROME_TO_ANDROID_LOCALE_MAP = {
+ 'en-GB': 'en-rGB',
+ 'en-US': 'en-rUS',
+ 'es-419': 'es-rUS',
+ 'fil': 'tl',
+ 'he': 'iw',
+ 'id': 'in',
+ 'pt-PT': 'pt-rPT',
+ 'pt-BR': 'pt-rBR',
+ 'yi': 'ji',
+ 'zh-CN': 'zh-rCN',
+ 'zh-TW': 'zh-rTW',
+}
+
+# Represents a line from a R.txt file.
+_TextSymbolEntry = collections.namedtuple('RTextEntry',
+ ('java_type', 'resource_type', 'name', 'value'))
+
+
+def CreateResourceInfoFile(files_to_zip, zip_path):
+ """Given a mapping of archive paths to their source, write an info file.
+
+ The info file contains lines of '{archive_path},{source_path}' for ease of
+ parsing. Assumes that there is no comma in the file names.
+
+ Args:
+ files_to_zip: Dict mapping path in the zip archive to original source.
+ zip_path: Path where the zip file ends up, this is where the info file goes.
+ """
+ info_file_path = zip_path + '.info'
+ with open(info_file_path, 'w') as info_file:
+ for archive_path, source_path in files_to_zip.iteritems():
+ info_file.write('{},{}\n'.format(archive_path, source_path))
+
+
+def _ParseTextSymbolsFile(path, fix_package_ids=False):
+ """Given an R.txt file, returns a list of _TextSymbolEntry.
+
+ Args:
+ path: Input file path.
+ fix_package_ids: if True, all packaged IDs read from the file
+ will be fixed to 0x7f.
+ Returns:
+ A list of _TextSymbolEntry instances.
+ Raises:
+ Exception: An unexpected line was detected in the input.
+ """
+ ret = []
+ with open(path) as f:
+ for line in f:
+ m = re.match(r'(int(?:\[\])?) (\w+) (\w+) (.+)$', line)
+ if not m:
+ raise Exception('Unexpected line in R.txt: %s' % line)
+ java_type, resource_type, name, value = m.groups()
+ if fix_package_ids:
+ value = _FixPackageIds(value)
+ ret.append(_TextSymbolEntry(java_type, resource_type, name, value))
+ return ret
+
+
+def _FixPackageIds(resource_value):
+ # Resource IDs for resources belonging to regular APKs have their first byte
+ # as 0x7f (package id). However with webview, since it is not a regular apk
+ # but used as a shared library, aapt is passed the --shared-resources flag
+ # which changes some of the package ids to 0x02 and 0x00. This function just
+ # normalises all package ids to 0x7f, which the generated code in R.java
+ # changes to the correct package id at runtime.
+ # resource_value is a string with either, a single value '0x12345678', or an
+ # array of values like '{ 0xfedcba98, 0x01234567, 0x56789abc }'
+ return re.sub(r'0x(?!01)\d\d', r'0x7f', resource_value)
+
+
+def _GetRTxtResourceNames(r_txt_path):
+ """Parse an R.txt file and extract the set of resource names from it."""
+ result = set()
+ for entry in _ParseTextSymbolsFile(r_txt_path):
+ result.add(entry.name)
+ return result
+
+
+class RJavaBuildOptions:
+ """A class used to model the various ways to build an R.java file.
+
+ This is used to control which resource ID variables will be final or
+ non-final, and whether an onResourcesLoaded() method will be generated
+ to adjust the non-final ones, when the corresponding library is loaded
+ at runtime.
+
+ Note that by default, all resources are final, and there is no
+ method generated, which corresponds to calling ExportNoResources().
+ """
+ def __init__(self):
+ self.has_constant_ids = True
+ self.resources_whitelist = None
+ self.has_on_resources_loaded = False
+ self.export_const_styleable = False
+
+ def ExportNoResources(self):
+ """Make all resource IDs final, and don't generate a method."""
+ self.has_constant_ids = True
+ self.resources_whitelist = None
+ self.has_on_resources_loaded = False
+ self.export_const_styleable = False
+
+ def ExportAllResources(self):
+ """Make all resource IDs non-final in the R.java file."""
+ self.has_constant_ids = False
+ self.resources_whitelist = None
+
+ def ExportSomeResources(self, r_txt_file_path):
+ """Only select specific resource IDs to be non-final.
+
+ Args:
+ r_txt_file_path: The path to an R.txt file. All resources named
+ int it will be non-final in the generated R.java file, all others
+ will be final.
+ """
+ self.has_constant_ids = True
+ self.resources_whitelist = _GetRTxtResourceNames(r_txt_file_path)
+
+ def ExportAllStyleables(self):
+ """Make all styleable constants non-final, even non-resources ones.
+
+ Resources that are styleable but not of int[] type are not actually
+ resource IDs but constants. By default they are always final. Call this
+ method to make them non-final anyway in the final R.java file.
+ """
+ self.export_const_styleable = True
+
+ def GenerateOnResourcesLoaded(self):
+ """Generate an onResourcesLoaded() method.
+
+ This Java method will be called at runtime by the framework when
+ the corresponding library (which includes the R.java source file)
+ will be loaded at runtime. This corresponds to the --shared-resources
+ or --app-as-shared-lib flags of 'aapt package'.
+ """
+ self.has_on_resources_loaded = True
+
+ def _IsResourceFinal(self, entry):
+ """Determines whether a resource should be final or not.
+
+ Args:
+ entry: A _TextSymbolEntry instance.
+ Returns:
+ True iff the corresponding entry should be final.
+ """
+ if entry.resource_type == 'styleable' and entry.java_type != 'int[]':
+ # A styleable constant may be exported as non-final after all.
+ return not self.export_const_styleable
+ elif not self.has_constant_ids:
+ # Every resource is non-final
+ return False
+ elif not self.resources_whitelist:
+ # No whitelist means all IDs are non-final.
+ return True
+ else:
+ # Otherwise, only those in the
+ return entry.name not in self.resources_whitelist
+
+
+def CreateRJavaFiles(srcjar_dir, package, main_r_txt_file,
+ extra_res_packages, extra_r_txt_files,
+ rjava_build_options):
+ """Create all R.java files for a set of packages and R.txt files.
+
+ Args:
+ srcjar_dir: The top-level output directory for the generated files.
+ package: Top-level package name.
+ main_r_txt_file: The main R.txt file containing the valid values
+ of _all_ resource IDs.
+ extra_res_packages: A list of extra package names.
+ extra_r_txt_files: A list of extra R.txt files. One per item in
+ |extra_res_packages|. Note that all resource IDs in them will be ignored,
+ |and replaced by the values extracted from |main_r_txt_file|.
+ rjava_build_options: An RJavaBuildOptions instance that controls how
+ exactly the R.java file is generated.
+ Raises:
+ Exception if a package name appears several times in |extra_res_packages|
+ """
+ assert len(extra_res_packages) == len(extra_r_txt_files), \
+ 'Need one R.txt file per package'
+
+ packages = list(extra_res_packages)
+ r_txt_files = list(extra_r_txt_files)
+
+ if package and package not in packages:
+ # Sometimes, an apk target and a resources target share the same
+ # AndroidManifest.xml and thus |package| will already be in |packages|.
+ packages.append(package)
+ r_txt_files.append(main_r_txt_file)
+
+ # Map of (resource_type, name) -> Entry.
+ # Contains the correct values for resources.
+ all_resources = {}
+ for entry in _ParseTextSymbolsFile(main_r_txt_file, fix_package_ids=True):
+ all_resources[(entry.resource_type, entry.name)] = entry
+
+ # Map of package_name->resource_type->entry
+ resources_by_package = (
+ collections.defaultdict(lambda: collections.defaultdict(list)))
+ # Build the R.java files using each package's R.txt file, but replacing
+ # each entry's placeholder value with correct values from all_resources.
+ for package, r_txt_file in zip(packages, r_txt_files):
+ if package in resources_by_package:
+ raise Exception(('Package name "%s" appeared twice. All '
+ 'android_resources() targets must use unique package '
+ 'names, or no package name at all.') % package)
+ resources_by_type = resources_by_package[package]
+ # The sub-R.txt files have the wrong values at this point. Read them to
+ # figure out which entries belong to them, but use the values from the
+ # main R.txt file.
+ for entry in _ParseTextSymbolsFile(r_txt_file):
+ entry = all_resources.get((entry.resource_type, entry.name))
+ # For most cases missing entry here is an error. It means that some
+ # library claims to have or depend on a resource that isn't included into
+ # the APK. There is one notable exception: Google Play Services (GMS).
+ # GMS is shipped as a bunch of AARs. One of them - basement - contains
+ # R.txt with ids of all resources, but most of the resources are in the
+ # other AARs. However, all other AARs reference their resources via
+ # basement's R.java so the latter must contain all ids that are in its
+ # R.txt. Most targets depend on only a subset of GMS AARs so some
+ # resources are missing, which is okay because the code that references
+ # them is missing too. We can't get an id for a resource that isn't here
+ # so the only solution is to skip the resource entry entirely.
+ #
+ # We can verify that all entries referenced in the code were generated
+ # correctly by running Proguard on the APK: it will report missing
+ # fields.
+ if entry:
+ resources_by_type[entry.resource_type].append(entry)
+
+ for package, resources_by_type in resources_by_package.iteritems():
+ _CreateRJavaSourceFile(srcjar_dir, package, resources_by_type,
+ rjava_build_options)
+
+
+def _CreateRJavaSourceFile(srcjar_dir, package, resources_by_type,
+ rjava_build_options):
+ """Generates an R.java source file."""
+ package_r_java_dir = os.path.join(srcjar_dir, *package.split('.'))
+ build_utils.MakeDirectory(package_r_java_dir)
+ package_r_java_path = os.path.join(package_r_java_dir, 'R.java')
+ java_file_contents = _RenderRJavaSource(package, resources_by_type,
+ rjava_build_options)
+ with open(package_r_java_path, 'w') as f:
+ f.write(java_file_contents)
+
+
+# Resource IDs inside resource arrays are sorted. Application resource IDs start
+# with 0x7f but system resource IDs start with 0x01 thus system resource ids are
+# always at the start of the array. This function finds the index of the first
+# non system resource id to be used for package ID rewriting (we should not
+# rewrite system resource ids).
+def _GetNonSystemIndex(entry):
+ """Get the index of the first application resource ID within a resource
+ array."""
+ res_ids = re.findall(r'0x[0-9a-f]{8}', entry.value)
+ for i, res_id in enumerate(res_ids):
+ if res_id.startswith('0x7f'):
+ return i
+ return len(res_ids)
+
+
+def _RenderRJavaSource(package, resources_by_type, rjava_build_options):
+ """Render an R.java source file. See _CreateRJaveSourceFile for args info."""
+ final_resources_by_type = collections.defaultdict(list)
+ non_final_resources_by_type = collections.defaultdict(list)
+ for res_type, resources in resources_by_type.iteritems():
+ for entry in resources:
+ # Entries in stylable that are not int[] are not actually resource ids
+ # but constants.
+ if rjava_build_options._IsResourceFinal(entry):
+ final_resources_by_type[res_type].append(entry)
+ else:
+ non_final_resources_by_type[res_type].append(entry)
+
+ # Keep these assignments all on one line to make diffing against regular
+ # aapt-generated files easier.
+ create_id = ('{{ e.resource_type }}.{{ e.name }} ^= packageIdTransform;')
+ create_id_arr = ('{{ e.resource_type }}.{{ e.name }}[i] ^='
+ ' packageIdTransform;')
+ for_loop_condition = ('int i = {{ startIndex(e) }}; i < '
+ '{{ e.resource_type }}.{{ e.name }}.length; ++i')
+
+ # Here we diverge from what aapt does. Because we have so many
+ # resources, the onResourcesLoaded method was exceeding the 64KB limit that
+ # Java imposes. For this reason we split onResourcesLoaded into different
+ # methods for each resource type.
+ template = Template("""/* AUTO-GENERATED FILE. DO NOT MODIFY. */
+
+package {{ package }};
+
+public final class R {
+ private static boolean sResourcesDidLoad;
+ {% for resource_type in resource_types %}
+ public static final class {{ resource_type }} {
+ {% for e in final_resources[resource_type] %}
+ public static final {{ e.java_type }} {{ e.name }} = {{ e.value }};
+ {% endfor %}
+ {% for e in non_final_resources[resource_type] %}
+ public static {{ e.java_type }} {{ e.name }} = {{ e.value }};
+ {% endfor %}
+ }
+ {% endfor %}
+ {% if has_on_resources_loaded %}
+ public static void onResourcesLoaded(int packageId) {
+ assert !sResourcesDidLoad;
+ sResourcesDidLoad = true;
+ int packageIdTransform = (packageId ^ 0x7f) << 24;
+ {% for resource_type in resource_types %}
+ onResourcesLoaded{{ resource_type|title }}(packageIdTransform);
+ {% for e in non_final_resources[resource_type] %}
+ {% if e.java_type == 'int[]' %}
+ for(""" + for_loop_condition + """) {
+ """ + create_id_arr + """
+ }
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+ }
+ {% for res_type in resource_types %}
+ private static void onResourcesLoaded{{ res_type|title }} (
+ int packageIdTransform) {
+ {% for e in non_final_resources[res_type] %}
+ {% if res_type != 'styleable' and e.java_type != 'int[]' %}
+ """ + create_id + """
+ {% endif %}
+ {% endfor %}
+ }
+ {% endfor %}
+ {% endif %}
+}
+""", trim_blocks=True, lstrip_blocks=True)
+
+ return template.render(
+ package=package,
+ resource_types=sorted(resources_by_type),
+ has_on_resources_loaded=rjava_build_options.has_on_resources_loaded,
+ final_resources=final_resources_by_type,
+ non_final_resources=non_final_resources_by_type,
+ startIndex=_GetNonSystemIndex)
+
+
+def ExtractPackageFromManifest(manifest_path):
+ """Extract package name from Android manifest file."""
+ doc = ElementTree.parse(manifest_path)
+ return doc.getroot().get('package')
+
+
+def ExtractDeps(dep_zips, deps_dir):
+ """Extract a list of resource dependency zip files.
+
+ Args:
+ dep_zips: A list of zip file paths, each one will be extracted to
+ a subdirectory of |deps_dir|, named after the zip file (e.g.
+ '/some/path/foo.zip' -> '{deps_dir}/foo/').
+ deps_dir: Top-level extraction directory.
+ Returns:
+ The list of all sub-directory paths, relative to |deps_dir|.
+ Raises:
+ Exception: If a sub-directory already exists with the same name before
+ extraction.
+ """
+ dep_subdirs = []
+ for z in dep_zips:
+ subdir = os.path.join(deps_dir, os.path.basename(z))
+ if os.path.exists(subdir):
+ raise Exception('Resource zip name conflict: ' + os.path.basename(z))
+ build_utils.ExtractAll(z, path=subdir)
+ dep_subdirs.append(subdir)
+ return dep_subdirs
+
+
+class _ResourceBuildContext(object):
+ """A temporary directory for packaging and compiling Android resources."""
+ def __init__(self):
+ """Initialized the context."""
+ # The top-level temporary directory.
+ self.temp_dir = tempfile.mkdtemp()
+ # A location to store resources extracted form dependency zip files.
+ self.deps_dir = os.path.join(self.temp_dir, 'deps')
+ os.mkdir(self.deps_dir)
+ # A location to place aapt-generated files.
+ self.gen_dir = os.path.join(self.temp_dir, 'gen')
+ os.mkdir(self.gen_dir)
+ # Location of the generated R.txt file.
+ self.r_txt_path = os.path.join(self.gen_dir, 'R.txt')
+ # A location to place generated R.java files.
+ self.srcjar_dir = os.path.join(self.temp_dir, 'java')
+ os.mkdir(self.srcjar_dir)
+
+ def Close(self):
+ """Close the context and destroy all temporary files."""
+ shutil.rmtree(self.temp_dir)
+
+
+@contextlib.contextmanager
+def BuildContext():
+ """Generator for a _ResourceBuildContext instance."""
+ try:
+ context = _ResourceBuildContext()
+ yield context
+ finally:
+ context.Close()
+
+
+def ResourceArgsParser():
+ """Create an argparse.ArgumentParser instance with common argument groups.
+
+ Returns:
+ A tuple of (parser, in_group, out_group) corresponding to the parser
+ instance, and the input and output argument groups for it, respectively.
+ """
+ parser = argparse.ArgumentParser(description=__doc__)
+
+ input_opts = parser.add_argument_group('Input options')
+ output_opts = parser.add_argument_group('Output options')
+
+ build_utils.AddDepfileOption(output_opts)
+
+ input_opts.add_argument('--android-sdk-jars', required=True,
+ help='Path to the android.jar file.')
+
+ input_opts.add_argument('--aapt-path', required=True,
+ help='Path to the Android aapt tool')
+
+ input_opts.add_argument('--aapt2-path',
+ help='Path to the Android aapt2 tool. If in different'
+ ' directory from --aapt-path.')
+
+ input_opts.add_argument('--dependencies-res-zips', required=True,
+ help='Resources zip archives from dependents. Required to '
+ 'resolve @type/foo references into dependent '
+ 'libraries.')
+
+ input_opts.add_argument(
+ '--r-text-in',
+ help='Path to pre-existing R.txt. Its resource IDs override those found '
+ 'in the aapt-generated R.txt when generating R.java.')
+
+ input_opts.add_argument(
+ '--extra-res-packages',
+ help='Additional package names to generate R.java files for.')
+
+ input_opts.add_argument(
+ '--extra-r-text-files',
+ help='For each additional package, the R.txt file should contain a '
+ 'list of resources to be included in the R.java file in the format '
+ 'generated by aapt.')
+
+ return (parser, input_opts, output_opts)
+
+
+def HandleCommonOptions(options):
+ """Handle common command-line options after parsing.
+
+ Args:
+ options: the result of parse_args() on the parser returned by
+ ResourceArgsParser(). This function updates a few common fields.
+ """
+ options.android_sdk_jars = build_utils.ParseGnList(options.android_sdk_jars)
+
+ options.dependencies_res_zips = (
+ build_utils.ParseGnList(options.dependencies_res_zips))
+
+ # Don't use [] as default value since some script explicitly pass "".
+ if options.extra_res_packages:
+ options.extra_res_packages = (
+ build_utils.ParseGnList(options.extra_res_packages))
+ else:
+ options.extra_res_packages = []
+
+ if options.extra_r_text_files:
+ options.extra_r_text_files = (
+ build_utils.ParseGnList(options.extra_r_text_files))
+ else:
+ options.extra_r_text_files = []
+
+ if not options.aapt2_path:
+ options.aapt2_path = options.aapt_path + '2'
diff --git a/build/android/pylib/constants/host_paths_unittest.py b/build/android/pylib/constants/host_paths_unittest.py
new file mode 100755
index 0000000000..658ed08bd9
--- /dev/null
+++ b/build/android/pylib/constants/host_paths_unittest.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import logging
+import os
+import unittest
+
+import pylib.constants as constants
+import pylib.constants.host_paths as host_paths
+
+
+# This map corresponds to the binprefix of NDK prebuilt toolchains for various
+# target CPU architectures. Note that 'x86_64' and 'x64' are the same.
+_EXPECTED_NDK_TOOL_SUBDIR_MAP = {
+ 'arm': 'toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/' +
+ 'arm-linux-androideabi-',
+ 'arm64':
+ 'toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/' +
+ 'aarch64-linux-android-',
+ 'x86': 'toolchains/x86-4.9/prebuilt/linux-x86_64/bin/i686-linux-android-',
+ 'x86_64':
+ 'toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-',
+ 'x64':
+ 'toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-',
+ 'mips':
+ 'toolchains/mipsel-linux-android-4.9/prebuilt/linux-x86_64/bin/' +
+ 'mipsel-linux-android-'
+}
+
+
+class HostPathsTest(unittest.TestCase):
+ def setUp(self):
+ logging.getLogger().setLevel(logging.ERROR)
+
+ def test_GetAaptPath(self):
+ _EXPECTED_AAPT_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt')
+ self.assertEqual(host_paths.GetAaptPath(), _EXPECTED_AAPT_PATH)
+ self.assertEqual(host_paths.GetAaptPath(), _EXPECTED_AAPT_PATH)
+
+ def test_ToolPath(self):
+ for cpu_arch, binprefix in _EXPECTED_NDK_TOOL_SUBDIR_MAP.iteritems():
+ expected_binprefix = os.path.join(constants.ANDROID_NDK_ROOT, binprefix)
+ expected_path = expected_binprefix + 'foo'
+ self.assertEqual(host_paths.ToolPath('foo', cpu_arch), expected_path)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/android/pylib/content_settings.py b/build/android/pylib/content_settings.py
new file mode 100644
index 0000000000..3bf11bc490
--- /dev/null
+++ b/build/android/pylib/content_settings.py
@@ -0,0 +1,80 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+class ContentSettings(dict):
+
+ """A dict interface to interact with device content settings.
+
+ System properties are key/value pairs as exposed by adb shell content.
+ """
+
+ def __init__(self, table, device):
+ super(ContentSettings, self).__init__()
+ self._table = table
+ self._device = device
+
+ @staticmethod
+ def _GetTypeBinding(value):
+ if isinstance(value, bool):
+ return 'b'
+ if isinstance(value, float):
+ return 'f'
+ if isinstance(value, int):
+ return 'i'
+ if isinstance(value, long):
+ return 'l'
+ if isinstance(value, str):
+ return 's'
+ raise ValueError('Unsupported type %s' % type(value))
+
+ def iteritems(self):
+ # Example row:
+ # 'Row: 0 _id=13, name=logging_id2, value=-1fccbaa546705b05'
+ for row in self._device.RunShellCommand(
+ 'content query --uri content://%s' % self._table, as_root=True):
+ fields = row.split(', ')
+ key = None
+ value = None
+ for field in fields:
+ k, _, v = field.partition('=')
+ if k == 'name':
+ key = v
+ elif k == 'value':
+ value = v
+ if not key:
+ continue
+ if not value:
+ value = ''
+ yield key, value
+
+ def __getitem__(self, key):
+ return self._device.RunShellCommand(
+ 'content query --uri content://%s --where "name=\'%s\'" '
+ '--projection value' % (self._table, key), as_root=True).strip()
+
+ def __setitem__(self, key, value):
+ if key in self:
+ self._device.RunShellCommand(
+ 'content update --uri content://%s '
+ '--bind value:%s:%s --where "name=\'%s\'"' % (
+ self._table,
+ self._GetTypeBinding(value), value, key),
+ as_root=True)
+ else:
+ self._device.RunShellCommand(
+ 'content insert --uri content://%s '
+ '--bind name:%s:%s --bind value:%s:%s' % (
+ self._table,
+ self._GetTypeBinding(key), key,
+ self._GetTypeBinding(value), value),
+ as_root=True)
+
+ def __delitem__(self, key):
+ self._device.RunShellCommand(
+ 'content delete --uri content://%s '
+ '--bind name:%s:%s' % (
+ self._table,
+ self._GetTypeBinding(key), key),
+ as_root=True)
diff --git a/build/android/pylib/device_settings.py b/build/android/pylib/device_settings.py
new file mode 100644
index 0000000000..ab4ad1b900
--- /dev/null
+++ b/build/android/pylib/device_settings.py
@@ -0,0 +1,199 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import logging
+
+from pylib import content_settings
+
+_LOCK_SCREEN_SETTINGS_PATH = '/data/system/locksettings.db'
+_ALTERNATE_LOCK_SCREEN_SETTINGS_PATH = (
+ '/data/data/com.android.providers.settings/databases/settings.db')
+PASSWORD_QUALITY_UNSPECIFIED = '0'
+_COMPATIBLE_BUILD_TYPES = ['userdebug', 'eng']
+
+
+def ConfigureContentSettings(device, desired_settings):
+ """Configures device content setings from a list.
+
+ Many settings are documented at:
+ http://developer.android.com/reference/android/provider/Settings.Global.html
+ http://developer.android.com/reference/android/provider/Settings.Secure.html
+ http://developer.android.com/reference/android/provider/Settings.System.html
+
+ Many others are undocumented.
+
+ Args:
+ device: A DeviceUtils instance for the device to configure.
+ desired_settings: A list of (table, [(key: value), ...]) for all
+ settings to configure.
+ """
+ for table, key_value in desired_settings:
+ settings = content_settings.ContentSettings(table, device)
+ for key, value in key_value:
+ settings[key] = value
+ logging.info('\n%s %s', table, (80 - len(table)) * '-')
+ for key, value in sorted(settings.iteritems()):
+ logging.info('\t%s: %s', key, value)
+
+
+def SetLockScreenSettings(device):
+ """Sets lock screen settings on the device.
+
+ On certain device/Android configurations we need to disable the lock screen in
+ a different database. Additionally, the password type must be set to
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED.
+ Lock screen settings are stored in sqlite on the device in:
+ /data/system/locksettings.db
+
+ IMPORTANT: The first column is used as a primary key so that all rows with the
+ same value for that column are removed from the table prior to inserting the
+ new values.
+
+ Args:
+ device: A DeviceUtils instance for the device to configure.
+
+ Raises:
+ Exception if the setting was not properly set.
+ """
+ if device.build_type not in _COMPATIBLE_BUILD_TYPES:
+ logging.warning('Unable to disable lockscreen on %s builds.',
+ device.build_type)
+ return
+
+ def get_lock_settings(table):
+ return [(table, 'lockscreen.disabled', '1'),
+ (table, 'lockscreen.password_type', PASSWORD_QUALITY_UNSPECIFIED),
+ (table, 'lockscreen.password_type_alternate',
+ PASSWORD_QUALITY_UNSPECIFIED)]
+
+ if device.FileExists(_LOCK_SCREEN_SETTINGS_PATH):
+ db = _LOCK_SCREEN_SETTINGS_PATH
+ locksettings = get_lock_settings('locksettings')
+ columns = ['name', 'user', 'value']
+ generate_values = lambda k, v: [k, '0', v]
+ elif device.FileExists(_ALTERNATE_LOCK_SCREEN_SETTINGS_PATH):
+ db = _ALTERNATE_LOCK_SCREEN_SETTINGS_PATH
+ locksettings = get_lock_settings('secure') + get_lock_settings('system')
+ columns = ['name', 'value']
+ generate_values = lambda k, v: [k, v]
+ else:
+ logging.warning('Unable to find database file to set lock screen settings.')
+ return
+
+ for table, key, value in locksettings:
+ # Set the lockscreen setting for default user '0'
+ values = generate_values(key, value)
+
+ cmd = """begin transaction;
+delete from '%(table)s' where %(primary_key)s='%(primary_value)s';
+insert into '%(table)s' (%(columns)s) values (%(values)s);
+commit transaction;""" % {
+ 'table': table,
+ 'primary_key': columns[0],
+ 'primary_value': values[0],
+ 'columns': ', '.join(columns),
+ 'values': ', '.join(["'%s'" % value for value in values])
+ }
+ output_msg = device.RunShellCommand('sqlite3 %s "%s"' % (db, cmd),
+ as_root=True)
+ if output_msg:
+ logging.info(' '.join(output_msg))
+
+
+ENABLE_LOCATION_SETTINGS = [
+ # Note that setting these in this order is required in order for all of
+ # them to take and stick through a reboot.
+ ('com.google.settings/partner', [
+ ('use_location_for_services', 1),
+ ]),
+ ('settings/secure', [
+ # Ensure Geolocation is enabled and allowed for tests.
+ ('location_providers_allowed', 'gps,network'),
+ ]),
+ ('com.google.settings/partner', [
+ ('network_location_opt_in', 1),
+ ])
+]
+
+DISABLE_LOCATION_SETTINGS = [
+ ('com.google.settings/partner', [
+ ('use_location_for_services', 0),
+ ]),
+ ('settings/secure', [
+ # Ensure Geolocation is disabled.
+ ('location_providers_allowed', ''),
+ ]),
+]
+
+ENABLE_MOCK_LOCATION_SETTINGS = [
+ ('settings/secure', [
+ ('mock_location', 1),
+ ]),
+]
+
+DISABLE_MOCK_LOCATION_SETTINGS = [
+ ('settings/secure', [
+ ('mock_location', 0),
+ ]),
+]
+
+DETERMINISTIC_DEVICE_SETTINGS = [
+ ('settings/global', [
+ ('assisted_gps_enabled', 0),
+
+ # Disable "auto time" and "auto time zone" to avoid network-provided time
+ # to overwrite the device's datetime and timezone synchronized from host
+ # when running tests later. See b/6569849.
+ ('auto_time', 0),
+ ('auto_time_zone', 0),
+
+ ('development_settings_enabled', 1),
+
+ # Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
+ # on application crashes and ANRs. If this is disabled, the crash/ANR dialog
+ # will never display the "Report" button.
+ # Type: int ( 0 = disallow, 1 = allow )
+ ('send_action_app_error', 0),
+
+ ('stay_on_while_plugged_in', 3),
+
+ ('verifier_verify_adb_installs', 0),
+ ]),
+ ('settings/secure', [
+ ('allowed_geolocation_origins',
+ 'http://www.google.co.uk http://www.google.com'),
+
+ # Ensure that we never get random dialogs like "Unfortunately the process
+ # android.process.acore has stopped", which steal the focus, and make our
+ # automation fail (because the dialog steals the focus then mistakenly
+ # receives the injected user input events).
+ ('anr_show_background', 0),
+
+ ('lockscreen.disabled', 1),
+
+ ('screensaver_enabled', 0),
+
+ ('skip_first_use_hints', 1),
+ ]),
+ ('settings/system', [
+ # Don't want devices to accidentally rotate the screen as that could
+ # affect performance measurements.
+ ('accelerometer_rotation', 0),
+
+ ('lockscreen.disabled', 1),
+
+ # Turn down brightness and disable auto-adjust so that devices run cooler.
+ ('screen_brightness', 5),
+ ('screen_brightness_mode', 0),
+
+ ('user_rotation', 0),
+ ]),
+]
+
+NETWORK_DISABLED_SETTINGS = [
+ ('settings/global', [
+ ('airplane_mode_on', 1),
+ ('wifi_on', 0),
+ ]),
+]
diff --git a/build/android/pylib/pexpect.py b/build/android/pylib/pexpect.py
new file mode 100644
index 0000000000..cf59fb0f6d
--- /dev/null
+++ b/build/android/pylib/pexpect.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+from __future__ import absolute_import
+
+import os
+import sys
+
+_CHROME_SRC = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)), '..', '..', '..')
+
+_PEXPECT_PATH = os.path.join(_CHROME_SRC, 'third_party', 'pexpect')
+if _PEXPECT_PATH not in sys.path:
+ sys.path.append(_PEXPECT_PATH)
+
+# pexpect is not available on all platforms. We allow this file to be imported
+# on platforms without pexpect and only fail when pexpect is actually used.
+try:
+ from pexpect import * # pylint: disable=W0401,W0614
+except ImportError:
+ pass
diff --git a/build/android/pylib/restart_adbd.sh b/build/android/pylib/restart_adbd.sh
new file mode 100755
index 0000000000..393b2ebac0
--- /dev/null
+++ b/build/android/pylib/restart_adbd.sh
@@ -0,0 +1,20 @@
+#!/system/bin/sh
+
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Android shell script to restart adbd on the device. This has to be run
+# atomically as a shell script because stopping adbd prevents further commands
+# from running (even if called in the same adb shell).
+
+trap '' HUP
+trap '' TERM
+trap '' PIPE
+
+function restart() {
+ stop adbd
+ start adbd
+}
+
+restart &
diff --git a/build/android/pylib/valgrind_tools.py b/build/android/pylib/valgrind_tools.py
new file mode 100644
index 0000000000..3dc2488b26
--- /dev/null
+++ b/build/android/pylib/valgrind_tools.py
@@ -0,0 +1,130 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# pylint: disable=R0201
+
+import glob
+import logging
+import os.path
+import subprocess
+import sys
+
+from devil.android import device_errors
+from devil.android.valgrind_tools import base_tool
+from pylib.constants import DIR_SOURCE_ROOT
+
+
+def SetChromeTimeoutScale(device, scale):
+ """Sets the timeout scale in /data/local/tmp/chrome_timeout_scale to scale."""
+ path = '/data/local/tmp/chrome_timeout_scale'
+ if not scale or scale == 1.0:
+ # Delete if scale is None/0.0/1.0 since the default timeout scale is 1.0
+ device.RemovePath(path, force=True, as_root=True)
+ else:
+ device.WriteFile(path, '%f' % scale, as_root=True)
+
+
+
+class AddressSanitizerTool(base_tool.BaseTool):
+ """AddressSanitizer tool."""
+
+ WRAPPER_NAME = '/system/bin/asanwrapper'
+ # Disable memcmp overlap check.There are blobs (gl drivers)
+ # on some android devices that use memcmp on overlapping regions,
+ # nothing we can do about that.
+ EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1'
+
+ def __init__(self, device):
+ super(AddressSanitizerTool, self).__init__()
+ self._device = device
+
+ @classmethod
+ def CopyFiles(cls, device):
+ """Copies ASan tools to the device."""
+ libs = glob.glob(os.path.join(DIR_SOURCE_ROOT,
+ 'third_party/llvm-build/Release+Asserts/',
+ 'lib/clang/*/lib/linux/',
+ 'libclang_rt.asan-arm-android.so'))
+ assert len(libs) == 1
+ subprocess.call(
+ [os.path.join(
+ DIR_SOURCE_ROOT,
+ 'tools/android/asan/third_party/asan_device_setup.sh'),
+ '--device', str(device),
+ '--lib', libs[0],
+ '--extra-options', AddressSanitizerTool.EXTRA_OPTIONS])
+ device.WaitUntilFullyBooted()
+
+ def GetTestWrapper(self):
+ return AddressSanitizerTool.WRAPPER_NAME
+
+ def GetUtilWrapper(self):
+ """Returns the wrapper for utilities, such as forwarder.
+
+ AddressSanitizer wrapper must be added to all instrumented binaries,
+ including forwarder and the like. This can be removed if such binaries
+ were built without instrumentation. """
+ return self.GetTestWrapper()
+
+ def SetupEnvironment(self):
+ try:
+ self._device.EnableRoot()
+ except device_errors.CommandFailedError as e:
+ # Try to set the timeout scale anyway.
+ # TODO(jbudorick) Handle this exception appropriately after interface
+ # conversions are finished.
+ logging.error(str(e))
+ SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
+
+ def CleanUpEnvironment(self):
+ SetChromeTimeoutScale(self._device, None)
+
+ def GetTimeoutScale(self):
+ # Very slow startup.
+ return 20.0
+
+
+TOOL_REGISTRY = {
+ 'asan': AddressSanitizerTool,
+}
+
+
+def CreateTool(tool_name, device):
+ """Creates a tool with the specified tool name.
+
+ Args:
+ tool_name: Name of the tool to create.
+ device: A DeviceUtils instance.
+ Returns:
+ A tool for the specified tool_name.
+ """
+ if not tool_name:
+ return base_tool.BaseTool()
+
+ ctor = TOOL_REGISTRY.get(tool_name)
+ if ctor:
+ return ctor(device)
+ else:
+ print 'Unknown tool %s, available tools: %s' % (
+ tool_name, ', '.join(sorted(TOOL_REGISTRY.keys())))
+ sys.exit(1)
+
+def PushFilesForTool(tool_name, device):
+ """Pushes the files required for |tool_name| to |device|.
+
+ Args:
+ tool_name: Name of the tool to create.
+ device: A DeviceUtils instance.
+ """
+ if not tool_name:
+ return
+
+ clazz = TOOL_REGISTRY.get(tool_name)
+ if clazz:
+ clazz.CopyFiles(device)
+ else:
+ print 'Unknown tool %s, available tools: %s' % (
+ tool_name, ', '.join(sorted(TOOL_REGISTRY.keys())))
+ sys.exit(1)
+
diff --git a/build/apply_locales.py b/build/apply_locales.py
new file mode 100755
index 0000000000..6af7280fad
--- /dev/null
+++ b/build/apply_locales.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# TODO: remove this script when GYP has for loops
+
+import sys
+import optparse
+
+def main(argv):
+
+ parser = optparse.OptionParser()
+ usage = 'usage: %s [options ...] format_string locale_list'
+ parser.set_usage(usage.replace('%s', '%prog'))
+ parser.add_option('-d', dest='dash_to_underscore', action="store_true",
+ default=False,
+ help='map "en-US" to "en" and "-" to "_" in locales')
+
+ (options, arglist) = parser.parse_args(argv)
+
+ if len(arglist) < 3:
+ print 'ERROR: need string and list of locales'
+ return 1
+
+ str_template = arglist[1]
+ locales = arglist[2:]
+
+ results = []
+ for locale in locales:
+ # For Cocoa to find the locale at runtime, it needs to use '_' instead
+ # of '-' (http://crbug.com/20441). Also, 'en-US' should be represented
+ # simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
+ if options.dash_to_underscore:
+ if locale == 'en-US':
+ locale = 'en'
+ locale = locale.replace('-', '_')
+ results.append(str_template.replace('ZZLOCALE', locale))
+
+ # Quote each element so filename spaces don't mess up GYP's attempt to parse
+ # it into a list.
+ print ' '.join(["'%s'" % x for x in results])
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/build/build_config.h b/build/build_config.h
index d669ea750e..0a00240887 100644
--- a/build/build_config.h
+++ b/build/build_config.h
@@ -24,25 +24,21 @@
// within an Android checkout.
// - ANDROID is defined via -D when building code for either Android targets or
// hosts. Use __ANDROID__ and __ANDROID_HOST__ instead.
-// - OS_ANDROID is a Chrome-specific define used to build Chrome for Android
-// within the NDK.
+// - OS_ANDROID is a define used to build Chrome for Android within the NDK and
+// to build Android targets.
// Android targets and hosts don't use tcmalloc.
#if defined(__ANDROID__) || defined(__ANDROID_HOST__)
#define NO_TCMALLOC
#endif // defined(__ANDROID__) || defined(__ANDROID_HOST__)
-// Use the Chrome OS version of the code for both Android targets and Chrome OS builds.
-#if !defined(__ANDROID_HOST__)
-#define OS_CHROMEOS 1
-#endif // !defined(__ANDROID_HOST__)
-
#if defined(__ANDROID__) // Android targets
-#define __linux__ 1
+#define OS_ANDROID 1
#elif !defined(__ANDROID_HOST__) // Chrome OS
+#define OS_CHROMEOS 1
// TODO: Remove these once the GLib MessageLoopForUI isn't being used:
// https://crbug.com/361635
#define USE_GLIB 1
@@ -62,7 +58,6 @@
#else
#define OS_NACL_SFI
#endif
-// Don't set OS_ANDROID; it's only used when building Chrome for Android.
#elif defined(__APPLE__)
// only include TargetConditions after testing ANDROID as some android builds
// on mac don't have this header available and it's not needed unless the target
diff --git a/build/check_gn_headers.py b/build/check_gn_headers.py
new file mode 100755
index 0000000000..f6ae8f5cac
--- /dev/null
+++ b/build/check_gn_headers.py
@@ -0,0 +1,304 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Find header files missing in GN.
+
+This script gets all the header files from ninja_deps, which is from the true
+dependency generated by the compiler, and report if they don't exist in GN.
+"""
+
+import argparse
+import json
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+from multiprocessing import Process, Queue
+
+SRC_DIR = os.path.abspath(
+ os.path.join(os.path.abspath(os.path.dirname(__file__)), os.path.pardir))
+DEPOT_TOOLS_DIR = os.path.join(SRC_DIR, 'third_party', 'depot_tools')
+
+
+def GetHeadersFromNinja(out_dir, skip_obj, q):
+ """Return all the header files from ninja_deps"""
+
+ def NinjaSource():
+ cmd = [os.path.join(DEPOT_TOOLS_DIR, 'ninja'), '-C', out_dir, '-t', 'deps']
+ # A negative bufsize means to use the system default, which usually
+ # means fully buffered.
+ popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=-1)
+ for line in iter(popen.stdout.readline, ''):
+ yield line.rstrip()
+
+ popen.stdout.close()
+ return_code = popen.wait()
+ if return_code:
+ raise subprocess.CalledProcessError(return_code, cmd)
+
+ ans, err = set(), None
+ try:
+ ans = ParseNinjaDepsOutput(NinjaSource(), out_dir, skip_obj)
+ except Exception as e:
+ err = str(e)
+ q.put((ans, err))
+
+
+def ParseNinjaDepsOutput(ninja_out, out_dir, skip_obj):
+ """Parse ninja output and get the header files"""
+ all_headers = {}
+
+ # Ninja always uses "/", even on Windows.
+ prefix = '../../'
+
+ is_valid = False
+ obj_file = ''
+ for line in ninja_out:
+ if line.startswith(' '):
+ if not is_valid:
+ continue
+ if line.endswith('.h') or line.endswith('.hh'):
+ f = line.strip()
+ if f.startswith(prefix):
+ f = f[6:] # Remove the '../../' prefix
+ # build/ only contains build-specific files like build_config.h
+ # and buildflag.h, and system header files, so they should be
+ # skipped.
+ if f.startswith(out_dir) or f.startswith('out'):
+ continue
+ if not f.startswith('build'):
+ all_headers.setdefault(f, [])
+ if not skip_obj:
+ all_headers[f].append(obj_file)
+ else:
+ is_valid = line.endswith('(VALID)')
+ obj_file = line.split(':')[0]
+
+ return all_headers
+
+
+def GetHeadersFromGN(out_dir, q):
+ """Return all the header files from GN"""
+
+ tmp = None
+ ans, err = set(), None
+ try:
+ # Argument |dir| is needed to make sure it's on the same drive on Windows.
+ # dir='' means dir='.', but doesn't introduce an unneeded prefix.
+ tmp = tempfile.mkdtemp(dir='')
+ shutil.copy2(os.path.join(out_dir, 'args.gn'),
+ os.path.join(tmp, 'args.gn'))
+ # Do "gn gen" in a temp dir to prevent dirtying |out_dir|.
+ gn_exe = 'gn.bat' if sys.platform == 'win32' else 'gn'
+ subprocess.check_call([
+ os.path.join(DEPOT_TOOLS_DIR, gn_exe), 'gen', tmp, '--ide=json', '-q'])
+ gn_json = json.load(open(os.path.join(tmp, 'project.json')))
+ ans = ParseGNProjectJSON(gn_json, out_dir, tmp)
+ except Exception as e:
+ err = str(e)
+ finally:
+ if tmp:
+ shutil.rmtree(tmp)
+ q.put((ans, err))
+
+
+def ParseGNProjectJSON(gn, out_dir, tmp_out):
+ """Parse GN output and get the header files"""
+ all_headers = set()
+
+ for _target, properties in gn['targets'].iteritems():
+ sources = properties.get('sources', [])
+ public = properties.get('public', [])
+ # Exclude '"public": "*"'.
+ if type(public) is list:
+ sources += public
+ for f in sources:
+ if f.endswith('.h') or f.endswith('.hh'):
+ if f.startswith('//'):
+ f = f[2:] # Strip the '//' prefix.
+ if f.startswith(tmp_out):
+ f = out_dir + f[len(tmp_out):]
+ all_headers.add(f)
+
+ return all_headers
+
+
+def GetDepsPrefixes(q):
+ """Return all the folders controlled by DEPS file"""
+ prefixes, err = set(), None
+ try:
+ gclient_exe = 'gclient.bat' if sys.platform == 'win32' else 'gclient'
+ gclient_out = subprocess.check_output([
+ os.path.join(DEPOT_TOOLS_DIR, gclient_exe),
+ 'recurse', '--no-progress', '-j1',
+ 'python', '-c', 'import os;print os.environ["GCLIENT_DEP_PATH"]'],
+ universal_newlines=True)
+ for i in gclient_out.split('\n'):
+ if i.startswith('src/'):
+ i = i[4:]
+ prefixes.add(i)
+ except Exception as e:
+ err = str(e)
+ q.put((prefixes, err))
+
+
+def IsBuildClean(out_dir):
+ cmd = [os.path.join(DEPOT_TOOLS_DIR, 'ninja'), '-C', out_dir, '-n']
+ try:
+ out = subprocess.check_output(cmd)
+ return 'no work to do.' in out
+ except Exception as e:
+ print e
+ return False
+
+def ParseWhiteList(whitelist):
+ out = set()
+ for line in whitelist.split('\n'):
+ line = re.sub(r'#.*', '', line).strip()
+ if line:
+ out.add(line)
+ return out
+
+
+def FilterOutDepsedRepo(files, deps):
+ return {f for f in files if not any(f.startswith(d) for d in deps)}
+
+
+def GetNonExistingFiles(lst):
+ out = set()
+ for f in lst:
+ if not os.path.isfile(f):
+ out.add(f)
+ return out
+
+
+def main():
+
+ def DumpJson(data):
+ if args.json:
+ with open(args.json, 'w') as f:
+ json.dump(data, f)
+
+ def PrintError(msg):
+ DumpJson([])
+ parser.error(msg)
+
+ parser = argparse.ArgumentParser(description='''
+ NOTE: Use ninja to build all targets in OUT_DIR before running
+ this script.''')
+ parser.add_argument('--out-dir', metavar='OUT_DIR', default='out/Release',
+ help='output directory of the build')
+ parser.add_argument('--json',
+ help='JSON output filename for missing headers')
+ parser.add_argument('--whitelist', help='file containing whitelist')
+ parser.add_argument('--skip-dirty-check', action='store_true',
+ help='skip checking whether the build is dirty')
+ parser.add_argument('--verbose', action='store_true',
+ help='print more diagnostic info')
+
+ args, _extras = parser.parse_known_args()
+
+ if not os.path.isdir(args.out_dir):
+ parser.error('OUT_DIR "%s" does not exist.' % args.out_dir)
+
+ if not args.skip_dirty_check and not IsBuildClean(args.out_dir):
+ dirty_msg = 'OUT_DIR looks dirty. You need to build all there.'
+ if args.json:
+ # Assume running on the bots. Silently skip this step.
+ # This is possible because "analyze" step can be wrong due to
+ # underspecified header files. See crbug.com/725877
+ print dirty_msg
+ DumpJson([])
+ return 0
+ else:
+ # Assume running interactively.
+ parser.error(dirty_msg)
+
+ d_q = Queue()
+ d_p = Process(target=GetHeadersFromNinja, args=(args.out_dir, True, d_q,))
+ d_p.start()
+
+ gn_q = Queue()
+ gn_p = Process(target=GetHeadersFromGN, args=(args.out_dir, gn_q,))
+ gn_p.start()
+
+ deps_q = Queue()
+ deps_p = Process(target=GetDepsPrefixes, args=(deps_q,))
+ deps_p.start()
+
+ d, d_err = d_q.get()
+ gn, gn_err = gn_q.get()
+ missing = set(d.keys()) - gn
+ nonexisting = GetNonExistingFiles(gn)
+
+ deps, deps_err = deps_q.get()
+ missing = FilterOutDepsedRepo(missing, deps)
+ nonexisting = FilterOutDepsedRepo(nonexisting, deps)
+
+ d_p.join()
+ gn_p.join()
+ deps_p.join()
+
+ if d_err:
+ PrintError(d_err)
+ if gn_err:
+ PrintError(gn_err)
+ if deps_err:
+ PrintError(deps_err)
+ if len(GetNonExistingFiles(d)) > 0:
+ print 'Non-existing files in ninja deps:', GetNonExistingFiles(d)
+ PrintError('Found non-existing files in ninja deps. You should ' +
+ 'build all in OUT_DIR.')
+ if len(d) == 0:
+ PrintError('OUT_DIR looks empty. You should build all there.')
+ if any((('/gen/' in i) for i in nonexisting)):
+ PrintError('OUT_DIR looks wrong. You should build all there.')
+
+ if args.whitelist:
+ whitelist = ParseWhiteList(open(args.whitelist).read())
+ missing -= whitelist
+ nonexisting -= whitelist
+
+ missing = sorted(missing)
+ nonexisting = sorted(nonexisting)
+
+ DumpJson(sorted(missing + nonexisting))
+
+ if len(missing) == 0 and len(nonexisting) == 0:
+ return 0
+
+ if len(missing) > 0:
+ print '\nThe following files should be included in gn files:'
+ for i in missing:
+ print i
+
+ if len(nonexisting) > 0:
+ print '\nThe following non-existing files should be removed from gn files:'
+ for i in nonexisting:
+ print i
+
+ if args.verbose:
+ # Only get detailed obj dependency here since it is slower.
+ GetHeadersFromNinja(args.out_dir, False, d_q)
+ d, d_err = d_q.get()
+ print '\nDetailed dependency info:'
+ for f in missing:
+ print f
+ for cc in d[f]:
+ print ' ', cc
+
+ print '\nMissing headers sorted by number of affected object files:'
+ count = {k: len(v) for (k, v) in d.iteritems()}
+ for f in sorted(count, key=count.get, reverse=True):
+ if f in missing:
+ print count[f], f
+
+ return 1
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/check_gn_headers_unittest.py b/build/check_gn_headers_unittest.py
new file mode 100755
index 0000000000..20c3b13897
--- /dev/null
+++ b/build/check_gn_headers_unittest.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import logging
+import json
+import unittest
+import check_gn_headers
+
+
+ninja_input = r'''
+obj/a.o: #deps 1, deps mtime 123 (VALID)
+ ../../a.cc
+ ../../dir/path/b.h
+ ../../c.hh
+
+obj/b.o: #deps 1, deps mtime 123 (STALE)
+ ../../b.cc
+ ../../dir2/path/b.h
+ ../../c2.hh
+
+obj/c.o: #deps 1, deps mtime 123 (VALID)
+ ../../c.cc
+ ../../build/a.h
+ gen/b.h
+ ../../out/Release/gen/no.h
+ ../../dir3/path/b.h
+ ../../c3.hh
+'''
+
+
+gn_input = json.loads(r'''
+{
+ "others": [],
+ "targets": {
+ "//:All": {
+ },
+ "//:base": {
+ "public": [ "//base/p.h" ],
+ "sources": [ "//base/a.cc", "//base/a.h", "//base/b.hh" ],
+ "visibility": [ "*" ]
+ },
+ "//:star_public": {
+ "public": "*",
+ "sources": [ "//base/c.h", "//tmp/gen/a.h" ],
+ "visibility": [ "*" ]
+ }
+ }
+}
+''')
+
+
+whitelist = r'''
+ white-front.c
+a/b/c/white-end.c # comment
+ dir/white-both.c #more comment
+
+# empty line above
+a/b/c
+'''
+
+
+class CheckGnHeadersTest(unittest.TestCase):
+ def testNinja(self):
+ headers = check_gn_headers.ParseNinjaDepsOutput(
+ ninja_input.split('\n'), 'out/Release', False)
+ expected = {
+ 'dir/path/b.h': ['obj/a.o'],
+ 'c.hh': ['obj/a.o'],
+ 'dir3/path/b.h': ['obj/c.o'],
+ 'c3.hh': ['obj/c.o'],
+ }
+ self.assertEquals(headers, expected)
+
+ def testGn(self):
+ headers = check_gn_headers.ParseGNProjectJSON(gn_input,
+ 'out/Release', 'tmp')
+ expected = set([
+ 'base/a.h',
+ 'base/b.hh',
+ 'base/c.h',
+ 'base/p.h',
+ 'out/Release/gen/a.h',
+ ])
+ self.assertEquals(headers, expected)
+
+ def testWhitelist(self):
+ output = check_gn_headers.ParseWhiteList(whitelist)
+ expected = set([
+ 'white-front.c',
+ 'a/b/c/white-end.c',
+ 'dir/white-both.c',
+ 'a/b/c',
+ ])
+ self.assertEquals(output, expected)
+
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
diff --git a/build/check_return_value.py b/build/check_return_value.py
new file mode 100755
index 0000000000..c659d1e967
--- /dev/null
+++ b/build/check_return_value.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This program wraps an arbitrary command and prints "1" if the command ran
+successfully."""
+
+import os
+import subprocess
+import sys
+
+devnull = open(os.devnull, 'wb')
+if not subprocess.call(sys.argv[1:], stdout=devnull, stderr=devnull):
+ print 1
+else:
+ print 0
diff --git a/build/clobber.py b/build/clobber.py
new file mode 100755
index 0000000000..18791c28f1
--- /dev/null
+++ b/build/clobber.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This script provides methods for clobbering build directories."""
+
+import argparse
+import os
+import shutil
+import subprocess
+import sys
+
+
+def extract_gn_build_commands(build_ninja_file):
+ """Extracts from a build.ninja the commands to run GN.
+
+ The commands to run GN are the gn rule and build.ninja build step at the
+ top of the build.ninja file. We want to keep these when deleting GN builds
+ since we want to preserve the command-line flags to GN.
+
+ On error, returns the empty string."""
+ result = ""
+ with open(build_ninja_file, 'r') as f:
+ # Read until the third blank line. The first thing GN writes to the file
+ # is "ninja_required_version = x.y.z", then the "rule gn" and the third
+ # is the section for "build build.ninja", separated by blank lines.
+ num_blank_lines = 0
+ while num_blank_lines < 3:
+ line = f.readline()
+ if len(line) == 0:
+ return '' # Unexpected EOF.
+ result += line
+ if line[0] == '\n':
+ num_blank_lines = num_blank_lines + 1
+ return result
+
+
+def delete_dir(build_dir):
+ if os.path.islink(build_dir):
+ return
+ # For unknown reasons (anti-virus?) rmtree of Chromium build directories
+ # often fails on Windows.
+ if sys.platform.startswith('win'):
+ subprocess.check_call(['rmdir', '/s', '/q', build_dir], shell=True)
+ else:
+ shutil.rmtree(build_dir)
+
+
+def delete_build_dir(build_dir):
+ # GN writes a build.ninja.d file. Note that not all GN builds have args.gn.
+ build_ninja_d_file = os.path.join(build_dir, 'build.ninja.d')
+ if not os.path.exists(build_ninja_d_file):
+ delete_dir(build_dir)
+ return
+
+ # GN builds aren't automatically regenerated when you sync. To avoid
+ # messing with the GN workflow, erase everything but the args file, and
+ # write a dummy build.ninja file that will automatically rerun GN the next
+ # time Ninja is run.
+ build_ninja_file = os.path.join(build_dir, 'build.ninja')
+ build_commands = extract_gn_build_commands(build_ninja_file)
+
+ try:
+ gn_args_file = os.path.join(build_dir, 'args.gn')
+ with open(gn_args_file, 'r') as f:
+ args_contents = f.read()
+ except IOError:
+ args_contents = ''
+
+ e = None
+ try:
+ # delete_dir and os.mkdir() may fail, such as when chrome.exe is running,
+ # and we still want to restore args.gn/build.ninja/build.ninja.d, so catch
+ # the exception and rethrow it later.
+ delete_dir(build_dir)
+ os.mkdir(build_dir)
+ except Exception as e:
+ pass
+
+ # Put back the args file (if any).
+ if args_contents != '':
+ with open(gn_args_file, 'w') as f:
+ f.write(args_contents)
+
+ # Write the build.ninja file sufficiently to regenerate itself.
+ with open(os.path.join(build_dir, 'build.ninja'), 'w') as f:
+ if build_commands != '':
+ f.write(build_commands)
+ else:
+ # Couldn't parse the build.ninja file, write a default thing.
+ f.write('''rule gn
+command = gn -q gen //out/%s/
+description = Regenerating ninja files
+
+build build.ninja: gn
+generator = 1
+depfile = build.ninja.d
+''' % (os.path.split(build_dir)[1]))
+
+ # Write a .d file for the build which references a nonexistant file. This
+ # will make Ninja always mark the build as dirty.
+ with open(build_ninja_d_file, 'w') as f:
+ f.write('build.ninja: nonexistant_file.gn\n')
+
+ if e:
+ # Rethrow the exception we caught earlier.
+ raise e
+
+def clobber(out_dir):
+ """Clobber contents of build directory.
+
+ Don't delete the directory itself: some checkouts have the build directory
+ mounted."""
+ for f in os.listdir(out_dir):
+ path = os.path.join(out_dir, f)
+ if os.path.isfile(path):
+ os.unlink(path)
+ elif os.path.isdir(path):
+ delete_build_dir(path)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('out_dir', help='The output directory to clobber')
+ args = parser.parse_args()
+ clobber(args.out_dir)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/cp.py b/build/cp.py
new file mode 100755
index 0000000000..0f32536b62
--- /dev/null
+++ b/build/cp.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Copy a file.
+
+This module works much like the cp posix command - it takes 2 arguments:
+(src, dst) and copies the file with path |src| to |dst|.
+"""
+
+import os
+import shutil
+import sys
+
+
+def Main(src, dst):
+ # Use copy instead of copyfile to ensure the executable bit is copied.
+ return shutil.copy(src, os.path.normpath(dst))
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1], sys.argv[2]))
diff --git a/build/detect_host_arch.py b/build/detect_host_arch.py
new file mode 100755
index 0000000000..0e491bc8ba
--- /dev/null
+++ b/build/detect_host_arch.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Outputs host CPU architecture in format recognized by gyp."""
+
+import platform
+import re
+import sys
+
+
+def HostArch():
+ """Returns the host architecture with a predictable string."""
+ host_arch = platform.machine()
+
+ # Convert machine type to format recognized by gyp.
+ if re.match(r'i.86', host_arch) or host_arch == 'i86pc':
+ host_arch = 'ia32'
+ elif host_arch in ['x86_64', 'amd64']:
+ host_arch = 'x64'
+ elif host_arch.startswith('arm'):
+ host_arch = 'arm'
+ elif host_arch.startswith('aarch64'):
+ host_arch = 'arm64'
+ elif host_arch.startswith('mips'):
+ host_arch = 'mips'
+ elif host_arch.startswith('ppc'):
+ host_arch = 'ppc'
+ elif host_arch.startswith('s390'):
+ host_arch = 's390'
+
+
+ # platform.machine is based on running kernel. It's possible to use 64-bit
+ # kernel with 32-bit userland, e.g. to give linker slightly more memory.
+ # Distinguish between different userland bitness by querying
+ # the python binary.
+ if host_arch == 'x64' and platform.architecture()[0] == '32bit':
+ host_arch = 'ia32'
+ if host_arch == 'arm64' and platform.architecture()[0] == '32bit':
+ host_arch = 'arm'
+
+ return host_arch
+
+def DoMain(_):
+ """Hook to be called from gyp without starting a separate python
+ interpreter."""
+ return HostArch()
+
+if __name__ == '__main__':
+ print DoMain([])
diff --git a/build/dir_exists.py b/build/dir_exists.py
new file mode 100755
index 0000000000..70d367ec26
--- /dev/null
+++ b/build/dir_exists.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Writes True if the argument is a directory."""
+
+import os.path
+import sys
+
+def main():
+ sys.stdout.write(_is_dir(sys.argv[1]))
+ return 0
+
+def _is_dir(dir_name):
+ return str(os.path.isdir(dir_name))
+
+def DoMain(args):
+ """Hook to be called from gyp without starting a separate python
+ interpreter."""
+ return _is_dir(args[0])
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/download_translation_unit_tool.py b/build/download_translation_unit_tool.py
new file mode 100755
index 0000000000..b60d33a19f
--- /dev/null
+++ b/build/download_translation_unit_tool.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Script to download Clang translation_unit tool from google storage."""
+
+import find_depot_tools
+import json
+import os
+import shutil
+import subprocess
+import sys
+import tarfile
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+CHROME_SRC = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir))
+
+
+DEPOT_PATH = find_depot_tools.add_depot_tools_to_path()
+GSUTIL_PATH = os.path.join(DEPOT_PATH, 'gsutil.py')
+
+LLVM_BUILD_PATH = os.path.join(CHROME_SRC, 'third_party', 'llvm-build',
+ 'Release+Asserts')
+CLANG_UPDATE_PY = os.path.join(CHROME_SRC, 'tools', 'clang', 'scripts',
+ 'update.py')
+
+CLANG_BUCKET = 'gs://chromium-browser-clang'
+
+
+def main():
+ clang_revision = subprocess.check_output([sys.executable, CLANG_UPDATE_PY,
+ '--print-revision']).rstrip()
+ targz_name = 'translation_unit-%s.tgz' % clang_revision
+
+ if sys.platform == 'win32' or sys.platform == 'cygwin':
+ cds_full_url = CLANG_BUCKET + '/Win/' + targz_name
+ elif sys.platform == 'darwin':
+ cds_full_url = CLANG_BUCKET + '/Mac/' + targz_name
+ else:
+ assert sys.platform.startswith('linux')
+ cds_full_url = CLANG_BUCKET + '/Linux_x64/' + targz_name
+
+ os.chdir(LLVM_BUILD_PATH)
+
+ subprocess.check_call([sys.executable, GSUTIL_PATH,
+ 'cp', cds_full_url, targz_name])
+ tarfile.open(name=targz_name, mode='r:gz').extractall(path=LLVM_BUILD_PATH)
+
+ os.remove(targz_name)
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/env_dump.py b/build/env_dump.py
new file mode 100755
index 0000000000..3f8217398c
--- /dev/null
+++ b/build/env_dump.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This script can either source a file and dump the enironment changes done by
+# it, or just simply dump the current environment as JSON into a file.
+
+import json
+import optparse
+import os
+import pipes
+import subprocess
+import sys
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('-f', '--output-json',
+ help='File to dump the environment as JSON into.')
+ parser.add_option(
+ '-d', '--dump-mode', action='store_true',
+ help='Dump the environment to sys.stdout and exit immediately.')
+
+ parser.disable_interspersed_args()
+ options, args = parser.parse_args()
+ if options.dump_mode:
+ if args or options.output_json:
+ parser.error('Cannot specify args or --output-json with --dump-mode.')
+ json.dump(dict(os.environ), sys.stdout)
+ else:
+ if not options.output_json:
+ parser.error('Requires --output-json option.')
+
+ envsetup_cmd = ' '.join(map(pipes.quote, args))
+ full_cmd = [
+ 'bash', '-c',
+ '. %s > /dev/null; %s -d' % (envsetup_cmd, os.path.abspath(__file__))
+ ]
+ try:
+ output = subprocess.check_output(full_cmd)
+ except Exception as e:
+ sys.exit('Error running %s and dumping environment.' % envsetup_cmd)
+
+ env_diff = {}
+ new_env = json.loads(output)
+ for k, val in new_env.items():
+ if k == '_' or (k in os.environ and os.environ[k] == val):
+ continue
+ env_diff[k] = val
+ with open(options.output_json, 'w') as f:
+ json.dump(env_diff, f)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/extract_from_cab.py b/build/extract_from_cab.py
new file mode 100755
index 0000000000..080370ca9a
--- /dev/null
+++ b/build/extract_from_cab.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Extracts a single file from a CAB archive."""
+
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+def run_quiet(*args):
+ """Run 'expand' suppressing noisy output. Returns returncode from process."""
+ popen = subprocess.Popen(args, stdout=subprocess.PIPE)
+ out, _ = popen.communicate()
+ if popen.returncode:
+ # expand emits errors to stdout, so if we fail, then print that out.
+ print out
+ return popen.returncode
+
+def main():
+ if len(sys.argv) != 4:
+ print 'Usage: extract_from_cab.py cab_path archived_file output_dir'
+ return 1
+
+ [cab_path, archived_file, output_dir] = sys.argv[1:]
+
+ # Expand.exe does its work in a fixed-named temporary directory created within
+ # the given output directory. This is a problem for concurrent extractions, so
+ # create a unique temp dir within the desired output directory to work around
+ # this limitation.
+ temp_dir = tempfile.mkdtemp(dir=output_dir)
+
+ try:
+ # Invoke the Windows expand utility to extract the file.
+ level = run_quiet('expand', cab_path, '-F:' + archived_file, temp_dir)
+ if level == 0:
+ # Move the output file into place, preserving expand.exe's behavior of
+ # paving over any preexisting file.
+ output_file = os.path.join(output_dir, archived_file)
+ try:
+ os.remove(output_file)
+ except OSError:
+ pass
+ os.rename(os.path.join(temp_dir, archived_file), output_file)
+ finally:
+ shutil.rmtree(temp_dir, True)
+
+ if level != 0:
+ return level
+
+ # The expand utility preserves the modification date and time of the archived
+ # file. Touch the extracted file. This helps build systems that compare the
+ # modification times of input and output files to determine whether to do an
+ # action.
+ os.utime(os.path.join(output_dir, archived_file), None)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/find_depot_tools.py b/build/find_depot_tools.py
new file mode 100755
index 0000000000..5c496e7c79
--- /dev/null
+++ b/build/find_depot_tools.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Small utility function to find depot_tools and add it to the python path.
+
+Will throw an ImportError exception if depot_tools can't be found since it
+imports breakpad.
+
+This can also be used as a standalone script to print out the depot_tools
+directory location.
+"""
+
+import os
+import sys
+
+
+# Path to //src
+SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
+
+
+def IsRealDepotTools(path):
+ expanded_path = os.path.expanduser(path)
+ return os.path.isfile(os.path.join(expanded_path, 'gclient.py'))
+
+
+def add_depot_tools_to_path():
+ """Search for depot_tools and add it to sys.path."""
+ # First, check if we have a DEPS'd in "depot_tools".
+ deps_depot_tools = os.path.join(SRC, 'third_party', 'depot_tools')
+ if IsRealDepotTools(deps_depot_tools):
+ # Put the pinned version at the start of the sys.path, in case there
+ # are other non-pinned versions already on the sys.path.
+ sys.path.insert(0, deps_depot_tools)
+ return deps_depot_tools
+
+ # Then look if depot_tools is already in PYTHONPATH.
+ for i in sys.path:
+ if i.rstrip(os.sep).endswith('depot_tools') and IsRealDepotTools(i):
+ return i
+ # Then look if depot_tools is in PATH, common case.
+ for i in os.environ['PATH'].split(os.pathsep):
+ if IsRealDepotTools(i):
+ sys.path.append(i.rstrip(os.sep))
+ return i
+ # Rare case, it's not even in PATH, look upward up to root.
+ root_dir = os.path.dirname(os.path.abspath(__file__))
+ previous_dir = os.path.abspath(__file__)
+ while root_dir and root_dir != previous_dir:
+ i = os.path.join(root_dir, 'depot_tools')
+ if IsRealDepotTools(i):
+ sys.path.append(i)
+ return i
+ previous_dir = root_dir
+ root_dir = os.path.dirname(root_dir)
+ print >> sys.stderr, 'Failed to find depot_tools'
+ return None
+
+DEPOT_TOOLS_PATH = add_depot_tools_to_path()
+
+# pylint: disable=W0611
+import breakpad
+
+
+def main():
+ if DEPOT_TOOLS_PATH is None:
+ return 1
+ print DEPOT_TOOLS_PATH
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/find_isolated_tests.py b/build/find_isolated_tests.py
new file mode 100755
index 0000000000..c5b3ab77a9
--- /dev/null
+++ b/build/find_isolated_tests.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Scans build output directory for .isolated files, calculates their SHA1
+hashes, stores final list in JSON document and then removes *.isolated files
+found (to ensure no stale *.isolated stay around on the next build).
+
+Used to figure out what tests were build in isolated mode to trigger these
+tests to run on swarming.
+
+For more info see:
+https://sites.google.com/a/chromium.org/dev/developers/testing/isolated-testing
+"""
+
+import glob
+import hashlib
+import json
+import optparse
+import os
+import re
+import sys
+
+
+def hash_file(filepath):
+ """Calculates the hash of a file without reading it all in memory at once."""
+ digest = hashlib.sha1()
+ with open(filepath, 'rb') as f:
+ while True:
+ chunk = f.read(1024*1024)
+ if not chunk:
+ break
+ digest.update(chunk)
+ return digest.hexdigest()
+
+
+def main():
+ parser = optparse.OptionParser(
+ usage='%prog --build-dir <path> --output-json <path>',
+ description=sys.modules[__name__].__doc__)
+ parser.add_option(
+ '--build-dir',
+ help='Path to a directory to search for *.isolated files.')
+ parser.add_option(
+ '--output-json',
+ help='File to dump JSON results into.')
+
+ options, _ = parser.parse_args()
+ if not options.build_dir:
+ parser.error('--build-dir option is required')
+ if not options.output_json:
+ parser.error('--output-json option is required')
+
+ result = {}
+
+ # Get the file hash values and output the pair.
+ pattern = os.path.join(options.build_dir, '*.isolated')
+ for filepath in sorted(glob.glob(pattern)):
+ test_name = os.path.splitext(os.path.basename(filepath))[0]
+ if re.match(r'^.+?\.\d$', test_name):
+ # It's a split .isolated file, e.g. foo.0.isolated. Ignore these.
+ continue
+
+ # TODO(csharp): Remove deletion once the isolate tracked dependencies are
+ # inputs for the isolated files.
+ sha1_hash = hash_file(filepath)
+ os.remove(filepath)
+ result[test_name] = sha1_hash
+
+ with open(options.output_json, 'wb') as f:
+ json.dump(result, f)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/fix_gn_headers.py b/build/fix_gn_headers.py
new file mode 100755
index 0000000000..01ff764e06
--- /dev/null
+++ b/build/fix_gn_headers.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Fix header files missing in GN.
+
+This script takes the missing header files from check_gn_headers.py, and
+try to fix them by adding them to the GN files.
+Manual cleaning up is likely required afterwards.
+"""
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+
+
+def GitGrep(pattern):
+ p = subprocess.Popen(
+ ['git', 'grep', '-En', pattern, '--', '*.gn', '*.gni'],
+ stdout=subprocess.PIPE)
+ out, _ = p.communicate()
+ return out, p.returncode
+
+
+def ValidMatches(basename, cc, grep_lines):
+ """Filter out 'git grep' matches with header files already."""
+ matches = []
+ for line in grep_lines:
+ gnfile, linenr, contents = line.split(':')
+ linenr = int(linenr)
+ new = re.sub(cc, basename, contents)
+ lines = open(gnfile).read().splitlines()
+ assert contents in lines[linenr - 1]
+ # Skip if it's already there. It could be before or after the match.
+ if lines[linenr] == new:
+ continue
+ if lines[linenr - 2] == new:
+ continue
+ print ' ', gnfile, linenr, new
+ matches.append((gnfile, linenr, new))
+ return matches
+
+
+def AddHeadersNextToCC(headers, skip_ambiguous=True):
+ """Add header files next to the corresponding .cc files in GN files.
+
+ When skip_ambiguous is True, skip if multiple .cc files are found.
+ Returns unhandled headers.
+
+ Manual cleaning up is likely required, especially if not skip_ambiguous.
+ """
+ edits = {}
+ unhandled = []
+ for filename in headers:
+ filename = filename.strip()
+ if not (filename.endswith('.h') or filename.endswith('.hh')):
+ continue
+ basename = os.path.basename(filename)
+ print filename
+ cc = r'\b' + os.path.splitext(basename)[0] + r'\.(cc|cpp|mm)\b'
+ out, returncode = GitGrep('(/|")' + cc + '"')
+ if returncode != 0 or not out:
+ unhandled.append(filename)
+ continue
+
+ matches = ValidMatches(basename, cc, out.splitlines())
+
+ if len(matches) == 0:
+ continue
+ if len(matches) > 1:
+ print '\n[WARNING] Ambiguous matching for', filename
+ for i in enumerate(matches, 1):
+ print '%d: %s' % (i[0], i[1])
+ print
+ if skip_ambiguous:
+ continue
+
+ picked = raw_input('Pick the matches ("2,3" for multiple): ')
+ try:
+ matches = [matches[int(i) - 1] for i in picked.split(',')]
+ except (ValueError, IndexError):
+ continue
+
+ for match in matches:
+ gnfile, linenr, new = match
+ print ' ', gnfile, linenr, new
+ edits.setdefault(gnfile, {})[linenr] = new
+
+ for gnfile in edits:
+ lines = open(gnfile).read().splitlines()
+ for l in sorted(edits[gnfile].keys(), reverse=True):
+ lines.insert(l, edits[gnfile][l])
+ open(gnfile, 'w').write('\n'.join(lines) + '\n')
+
+ return unhandled
+
+
+def AddHeadersToSources(headers, skip_ambiguous=True):
+ """Add header files to the sources list in the first GN file.
+
+ The target GN file is the first one up the parent directories.
+ This usually does the wrong thing for _test files if the test and the main
+ target are in the same .gn file.
+ When skip_ambiguous is True, skip if multiple sources arrays are found.
+
+ "git cl format" afterwards is required. Manually cleaning up duplicated items
+ is likely required.
+ """
+ for filename in headers:
+ filename = filename.strip()
+ print filename
+ dirname = os.path.dirname(filename)
+ while not os.path.exists(os.path.join(dirname, 'BUILD.gn')):
+ dirname = os.path.dirname(dirname)
+ rel = filename[len(dirname) + 1:]
+ gnfile = os.path.join(dirname, 'BUILD.gn')
+
+ lines = open(gnfile).read().splitlines()
+ matched = [i for i, l in enumerate(lines) if ' sources = [' in l]
+ if skip_ambiguous and len(matched) > 1:
+ print '[WARNING] Multiple sources in', gnfile
+ continue
+
+ if len(matched) < 1:
+ continue
+ print ' ', gnfile, rel
+ index = matched[0]
+ lines.insert(index + 1, '"%s",' % rel)
+ open(gnfile, 'w').write('\n'.join(lines) + '\n')
+
+
+def RemoveHeader(headers, skip_ambiguous=True):
+ """Remove non-existing headers in GN files.
+
+ When skip_ambiguous is True, skip if multiple matches are found.
+ """
+ edits = {}
+ unhandled = []
+ for filename in headers:
+ filename = filename.strip()
+ if not (filename.endswith('.h') or filename.endswith('.hh')):
+ continue
+ basename = os.path.basename(filename)
+ print filename
+ out, returncode = GitGrep('(/|")' + basename + '"')
+ if returncode != 0 or not out:
+ unhandled.append(filename)
+ print ' Not found'
+ continue
+
+ grep_lines = out.splitlines()
+ matches = []
+ for line in grep_lines:
+ gnfile, linenr, contents = line.split(':')
+ print ' ', gnfile, linenr, contents
+ linenr = int(linenr)
+ lines = open(gnfile).read().splitlines()
+ assert contents in lines[linenr - 1]
+ matches.append((gnfile, linenr, contents))
+
+ if len(matches) == 0:
+ continue
+ if len(matches) > 1:
+ print '\n[WARNING] Ambiguous matching for', filename
+ for i in enumerate(matches, 1):
+ print '%d: %s' % (i[0], i[1])
+ print
+ if skip_ambiguous:
+ continue
+
+ picked = raw_input('Pick the matches ("2,3" for multiple): ')
+ try:
+ matches = [matches[int(i) - 1] for i in picked.split(',')]
+ except (ValueError, IndexError):
+ continue
+
+ for match in matches:
+ gnfile, linenr, contents = match
+ print ' ', gnfile, linenr, contents
+ edits.setdefault(gnfile, set()).add(linenr)
+
+ for gnfile in edits:
+ lines = open(gnfile).read().splitlines()
+ for l in sorted(edits[gnfile], reverse=True):
+ lines.pop(l - 1)
+ open(gnfile, 'w').write('\n'.join(lines) + '\n')
+
+ return unhandled
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('input_file', help="missing or non-existing headers, "
+ "output of check_gn_headers.py")
+ parser.add_argument('--prefix',
+ help="only handle path name with this prefix")
+ parser.add_argument('--remove', action='store_true',
+ help="treat input_file as non-existing headers")
+
+ args, _extras = parser.parse_known_args()
+
+ headers = open(args.input_file).readlines()
+
+ if args.prefix:
+ headers = [i for i in headers if i.startswith(args.prefix)]
+
+ if args.remove:
+ RemoveHeader(headers, False)
+ else:
+ unhandled = AddHeadersNextToCC(headers)
+ AddHeadersToSources(unhandled)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/get_landmines.py b/build/get_landmines.py
new file mode 100755
index 0000000000..8a655e3032
--- /dev/null
+++ b/build/get_landmines.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+This file emits the list of reasons why a particular build needs to be clobbered
+(or a list of 'landmines').
+"""
+
+import sys
+
+import landmine_utils
+
+
+host_os = landmine_utils.host_os
+
+
+def print_landmines():
+ """
+ ALL LANDMINES ARE EMITTED FROM HERE.
+ """
+ # DO NOT add landmines as part of a regular CL. Landmines are a last-effort
+ # bandaid fix if a CL that got landed has a build dependency bug and all bots
+ # need to be cleaned up. If you're writing a new CL that causes build
+ # dependency problems, fix the dependency problems instead of adding a
+ # landmine.
+ #
+ # Before adding or changing a landmine consider the consequences of doing so.
+ # Doing so will wipe out every output directory on every Chrome developer's
+ # machine. This can be particularly problematic on Windows where the directory
+ # deletion may well fail (locked files, command prompt in the directory,
+ # etc.), and generated .sln and .vcxproj files will be deleted.
+ #
+ # This output directory deletion will be repeated when going back and forth
+ # across the change that added the landmine, adding to the cost. There are
+ # usually less troublesome alternatives.
+
+ if host_os() == 'win':
+ print 'Compile on cc_unittests fails due to symbols removed in r185063.'
+ if host_os() == 'linux':
+ print 'Builders switching from make to ninja will clobber on this.'
+ if host_os() == 'mac':
+ print 'Switching from bundle to unbundled dylib (issue 14743002).'
+ if host_os() in ('win', 'mac'):
+ print ('Improper dependency for create_nmf.py broke in r240802, '
+ 'fixed in r240860.')
+ if host_os() == 'win':
+ print 'Switch to VS2015 Update 3, 14393 SDK'
+ print 'Need to clobber everything due to an IDL change in r154579 (blink)'
+ print 'Need to clobber everything due to gen file moves in r175513 (Blink)'
+ print 'Clobber to get rid of obselete test plugin after r248358'
+ print 'Clobber to rebuild GN files for V8'
+ print 'Clobber to get rid of stale generated mojom.h files'
+ print 'Need to clobber everything due to build_nexe change in nacl r13424'
+ print '[chromium-dev] PSA: clobber build needed for IDR_INSPECTOR_* compil...'
+ print 'blink_resources.grd changed: crbug.com/400860'
+ print 'ninja dependency cycle: crbug.com/408192'
+ print 'Clobber to fix missing NaCl gyp dependencies (crbug.com/427427).'
+ print 'Another clobber for missing NaCl gyp deps (crbug.com/427427).'
+ print 'Clobber to fix GN not picking up increased ID range (crbug.com/444902)'
+ print 'Remove NaCl toolchains from the output dir (crbug.com/456902)'
+ if host_os() == 'win':
+ print 'Clobber to delete stale generated files (crbug.com/510086)'
+ if host_os() == 'mac':
+ print 'Clobber to get rid of evil libsqlite3.dylib (crbug.com/526208)'
+ if host_os() == 'mac':
+ print 'Clobber to remove libsystem.dylib. See crbug.com/620075'
+ if host_os() == 'mac':
+ print 'Clobber to get past mojo gen build error (crbug.com/679607)'
+ if host_os() == 'win':
+ print 'Clobber Windows to fix strange PCH-not-rebuilt errors.'
+ print 'CLobber all to fix GN breakage (crbug.com/736215)'
+ print 'The Great Blink mv for source files (crbug.com/768828)'
+
+def main():
+ print_landmines()
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/get_syzygy_binaries.py b/build/get_syzygy_binaries.py
new file mode 100755
index 0000000000..09b1199708
--- /dev/null
+++ b/build/get_syzygy_binaries.py
@@ -0,0 +1,529 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A utility script for downloading versioned Syzygy binaries."""
+
+import hashlib
+import errno
+import json
+import logging
+import optparse
+import os
+import re
+import shutil
+import stat
+import sys
+import subprocess
+import tempfile
+import time
+import zipfile
+
+
+_LOGGER = logging.getLogger(os.path.basename(__file__))
+
+# The relative path where official builds are archived in their GS bucket.
+_SYZYGY_ARCHIVE_PATH = ('/builds/official/%(revision)s')
+
+# A JSON file containing the state of the download directory. If this file and
+# directory state do not agree, then the binaries will be downloaded and
+# installed again.
+_STATE = '.state'
+
+# This matches an integer (an SVN revision number) or a SHA1 value (a GIT hash).
+# The archive exclusively uses lowercase GIT hashes.
+_REVISION_RE = re.compile('^(?:\d+|[a-f0-9]{40})$')
+
+# This matches an MD5 hash.
+_MD5_RE = re.compile('^[a-f0-9]{32}$')
+
+# List of reources to be downloaded and installed. These are tuples with the
+# following format:
+# (basename, logging name, relative installation path, extraction filter)
+_RESOURCES = [
+ ('benchmark.zip', 'benchmark', '', None),
+ ('binaries.zip', 'binaries', 'exe', None),
+ ('symbols.zip', 'symbols', 'exe',
+ lambda x: x.filename.endswith('.dll.pdb'))]
+
+
+# Name of the MS DIA dll that we need to copy to the binaries directory.
+_DIA_DLL_NAME = "msdia140.dll"
+
+
+def _LoadState(output_dir):
+ """Loads the contents of the state file for a given |output_dir|, returning
+ None if it doesn't exist.
+ """
+ path = os.path.join(output_dir, _STATE)
+ if not os.path.exists(path):
+ _LOGGER.debug('No state file found.')
+ return None
+ with open(path, 'rb') as f:
+ _LOGGER.debug('Reading state file: %s', path)
+ try:
+ return json.load(f)
+ except ValueError:
+ _LOGGER.debug('Invalid state file.')
+ return None
+
+
+def _SaveState(output_dir, state, dry_run=False):
+ """Saves the |state| dictionary to the given |output_dir| as a JSON file."""
+ path = os.path.join(output_dir, _STATE)
+ _LOGGER.debug('Writing state file: %s', path)
+ if dry_run:
+ return
+ with open(path, 'wb') as f:
+ f.write(json.dumps(state, sort_keys=True, indent=2))
+
+
+def _Md5(path):
+ """Returns the MD5 hash of the file at |path|, which must exist."""
+ return hashlib.md5(open(path, 'rb').read()).hexdigest()
+
+
+def _StateIsValid(state):
+ """Returns true if the given state structure is valid."""
+ if not isinstance(state, dict):
+ _LOGGER.debug('State must be a dict.')
+ return False
+ r = state.get('revision', None)
+ if not isinstance(r, basestring) or not _REVISION_RE.match(r):
+ _LOGGER.debug('State contains an invalid revision.')
+ return False
+ c = state.get('contents', None)
+ if not isinstance(c, dict):
+ _LOGGER.debug('State must contain a contents dict.')
+ return False
+ for (relpath, md5) in c.iteritems():
+ if not isinstance(relpath, basestring) or len(relpath) == 0:
+ _LOGGER.debug('State contents dict contains an invalid path.')
+ return False
+ if not isinstance(md5, basestring) or not _MD5_RE.match(md5):
+ _LOGGER.debug('State contents dict contains an invalid MD5 digest.')
+ return False
+ return True
+
+
+def _BuildActualState(stored, revision, output_dir):
+ """Builds the actual state using the provided |stored| state as a template.
+ Only examines files listed in the stored state, causing the script to ignore
+ files that have been added to the directories locally. |stored| must be a
+ valid state dictionary.
+ """
+ contents = {}
+ state = { 'revision': revision, 'contents': contents }
+ for relpath, md5 in stored['contents'].iteritems():
+ abspath = os.path.abspath(os.path.join(output_dir, relpath))
+ if os.path.isfile(abspath):
+ m = _Md5(abspath)
+ contents[relpath] = m
+
+ return state
+
+
+def _StatesAreConsistent(stored, actual):
+ """Validates whether two state dictionaries are consistent. Both must be valid
+ state dictionaries. Additional entries in |actual| are ignored.
+ """
+ if stored['revision'] != actual['revision']:
+ _LOGGER.debug('Mismatched revision number.')
+ return False
+ cont_stored = stored['contents']
+ cont_actual = actual['contents']
+ for relpath, md5 in cont_stored.iteritems():
+ if relpath not in cont_actual:
+ _LOGGER.debug('Missing content: %s', relpath)
+ return False
+ if md5 != cont_actual[relpath]:
+ _LOGGER.debug('Modified content: %s', relpath)
+ return False
+ return True
+
+
+def _GetCurrentState(revision, output_dir):
+ """Loads the current state and checks to see if it is consistent. Returns
+ a tuple (state, bool). The returned state will always be valid, even if an
+ invalid state is present on disk.
+ """
+ stored = _LoadState(output_dir)
+ if not _StateIsValid(stored):
+ _LOGGER.debug('State is invalid.')
+ # Return a valid but empty state.
+ return ({'revision': '0', 'contents': {}}, False)
+ actual = _BuildActualState(stored, revision, output_dir)
+ # If the script has been modified consider the state invalid.
+ path = os.path.join(output_dir, _STATE)
+ if os.path.getmtime(__file__) > os.path.getmtime(path):
+ return (stored, False)
+ # Otherwise, explicitly validate the state.
+ if not _StatesAreConsistent(stored, actual):
+ return (stored, False)
+ return (stored, True)
+
+
+def _DirIsEmpty(path):
+ """Returns true if the given directory is empty, false otherwise."""
+ for root, dirs, files in os.walk(path):
+ return not dirs and not files
+
+
+def _RmTreeHandleReadOnly(func, path, exc):
+ """An error handling function for use with shutil.rmtree. This will
+ detect failures to remove read-only files, and will change their properties
+ prior to removing them. This is necessary on Windows as os.remove will return
+ an access error for read-only files, and git repos contain read-only
+ pack/index files.
+ """
+ excvalue = exc[1]
+ if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
+ _LOGGER.debug('Removing read-only path: %s', path)
+ os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+ func(path)
+ else:
+ raise
+
+
+def _RmTree(path):
+ """A wrapper of shutil.rmtree that handles read-only files."""
+ shutil.rmtree(path, ignore_errors=False, onerror=_RmTreeHandleReadOnly)
+
+
+def _CleanState(output_dir, state, dry_run=False):
+ """Cleans up files/directories in |output_dir| that are referenced by
+ the given |state|. Raises an error if there are local changes. Returns a
+ dictionary of files that were deleted.
+ """
+ _LOGGER.debug('Deleting files from previous installation.')
+ deleted = {}
+
+ # Generate a list of files to delete, relative to |output_dir|.
+ contents = state['contents']
+ files = sorted(contents.keys())
+
+ # Try to delete the files. Keep track of directories to delete as well.
+ dirs = {}
+ for relpath in files:
+ fullpath = os.path.join(output_dir, relpath)
+ fulldir = os.path.dirname(fullpath)
+ dirs[fulldir] = True
+ if os.path.exists(fullpath):
+ # If somehow the file has become a directory complain about it.
+ if os.path.isdir(fullpath):
+ raise Exception('Directory exists where file expected: %s' % fullpath)
+
+ # Double check that the file doesn't have local changes. If it does
+ # then refuse to delete it.
+ if relpath in contents:
+ stored_md5 = contents[relpath]
+ actual_md5 = _Md5(fullpath)
+ if actual_md5 != stored_md5:
+ raise Exception('File has local changes: %s' % fullpath)
+
+ # The file is unchanged so it can safely be deleted.
+ _LOGGER.debug('Deleting file "%s".', fullpath)
+ deleted[relpath] = True
+ if not dry_run:
+ os.unlink(fullpath)
+
+ # Sort directories from longest name to shortest. This lets us remove empty
+ # directories from the most nested paths first.
+ dirs = sorted(dirs.keys(), key=lambda x: len(x), reverse=True)
+ for p in dirs:
+ if os.path.exists(p) and _DirIsEmpty(p):
+ _LOGGER.debug('Deleting empty directory "%s".', p)
+ if not dry_run:
+ _RmTree(p)
+
+ return deleted
+
+
+def _FindGsUtil():
+ """Looks for depot_tools and returns the absolute path to gsutil.py."""
+ for path in os.environ['PATH'].split(os.pathsep):
+ path = os.path.abspath(path)
+ git_cl = os.path.join(path, 'git_cl.py')
+ gs_util = os.path.join(path, 'gsutil.py')
+ if os.path.exists(git_cl) and os.path.exists(gs_util):
+ return gs_util
+ return None
+
+
+def _GsUtil(*cmd):
+ """Runs the given command in gsutil with exponential backoff and retries."""
+ gs_util = _FindGsUtil()
+ cmd = [sys.executable, gs_util] + list(cmd)
+
+ retries = 3
+ timeout = 4 # Seconds.
+ while True:
+ _LOGGER.debug('Running %s', cmd)
+ prog = subprocess.Popen(cmd, shell=False)
+ prog.communicate()
+
+ # Stop retrying on success.
+ if prog.returncode == 0:
+ return
+
+ # Raise a permanent failure if retries have been exhausted.
+ if retries == 0:
+ raise RuntimeError('Command "%s" returned %d.' % (cmd, prog.returncode))
+
+ _LOGGER.debug('Sleeping %d seconds and trying again.', timeout)
+ time.sleep(timeout)
+ retries -= 1
+ timeout *= 2
+
+
+def _Download(resource):
+ """Downloads the given GS resource to a temporary file, returning its path."""
+ tmp = tempfile.mkstemp(suffix='syzygy_archive')
+ os.close(tmp[0])
+ tmp_file = tmp[1]
+ url = 'gs://syzygy-archive' + resource
+ if sys.platform == 'cygwin':
+ # Change temporary path to Windows path for gsutil
+ def winpath(path):
+ return subprocess.check_output(['cygpath', '-w', path]).strip()
+ tmp_file = winpath(tmp_file)
+ _GsUtil('cp', url, tmp_file)
+ return tmp[1]
+
+
+def _MaybeCopyDIABinaries(options, contents):
+ """Try to copy the DIA DLL to the binaries exe directory."""
+ toolchain_data_file = os.path.join(os.path.dirname(__file__),
+ 'win_toolchain.json')
+ if not os.path.exists(toolchain_data_file):
+ _LOGGER.debug('Toolchain JSON data file doesn\'t exist, skipping.')
+ return
+ with open(toolchain_data_file) as temp_f:
+ toolchain_data = json.load(temp_f)
+ if not os.path.isdir(toolchain_data['path']):
+ _LOGGER.error('The toolchain JSON file is invalid.')
+ return
+ dia_sdk_binaries_dir = os.path.join(toolchain_data['path'], 'DIA SDK', 'bin')
+ dia_dll = os.path.join(dia_sdk_binaries_dir, _DIA_DLL_NAME)
+ if not os.path.exists(dia_dll):
+ _LOGGER.debug('%s is missing, skipping.')
+ return
+ dia_dll_dest = os.path.join(options.output_dir, 'exe', _DIA_DLL_NAME)
+ _LOGGER.debug('Copying %s to %s.' % (dia_dll, dia_dll_dest))
+ if not options.dry_run:
+ shutil.copy(dia_dll, dia_dll_dest)
+ contents[os.path.relpath(dia_dll_dest, options.output_dir)] = (
+ _Md5(dia_dll_dest))
+
+
+def _InstallBinaries(options, deleted={}):
+ """Installs Syzygy binaries. This assumes that the output directory has
+ already been cleaned, as it will refuse to overwrite existing files."""
+ contents = {}
+ state = { 'revision': options.revision, 'contents': contents }
+ archive_path = _SYZYGY_ARCHIVE_PATH % { 'revision': options.revision }
+ if options.resources:
+ resources = [(resource, resource, '', None)
+ for resource in options.resources]
+ else:
+ resources = _RESOURCES
+ for (base, name, subdir, filt) in resources:
+ # Create the output directory if it doesn't exist.
+ fulldir = os.path.join(options.output_dir, subdir)
+ if os.path.isfile(fulldir):
+ raise Exception('File exists where a directory needs to be created: %s' %
+ fulldir)
+ if not os.path.exists(fulldir):
+ _LOGGER.debug('Creating directory: %s', fulldir)
+ if not options.dry_run:
+ os.makedirs(fulldir)
+
+ # Download and read the archive.
+ resource = archive_path + '/' + base
+ _LOGGER.debug('Retrieving %s archive at "%s".', name, resource)
+ path = _Download(resource)
+
+ _LOGGER.debug('Unzipping %s archive.', name)
+ with open(path, 'rb') as data:
+ archive = zipfile.ZipFile(data)
+ for entry in archive.infolist():
+ if not filt or filt(entry):
+ fullpath = os.path.normpath(os.path.join(fulldir, entry.filename))
+ relpath = os.path.relpath(fullpath, options.output_dir)
+ if os.path.exists(fullpath):
+ # If in a dry-run take into account the fact that the file *would*
+ # have been deleted.
+ if options.dry_run and relpath in deleted:
+ pass
+ else:
+ raise Exception('Path already exists: %s' % fullpath)
+
+ # Extract the file and update the state dictionary.
+ _LOGGER.debug('Extracting "%s".', fullpath)
+ if not options.dry_run:
+ archive.extract(entry.filename, fulldir)
+ md5 = _Md5(fullpath)
+ contents[relpath] = md5
+ if sys.platform == 'cygwin':
+ os.chmod(fullpath, os.stat(fullpath).st_mode | stat.S_IXUSR)
+
+ _LOGGER.debug('Removing temporary file "%s".', path)
+ os.remove(path)
+
+ if options.copy_dia_binaries:
+ # Try to copy the DIA binaries to the binaries directory.
+ _MaybeCopyDIABinaries(options, contents)
+
+ return state
+
+
+def _ParseCommandLine():
+ """Parses the command-line and returns an options structure."""
+ option_parser = optparse.OptionParser()
+ option_parser.add_option('--dry-run', action='store_true', default=False,
+ help='If true then will simply list actions that would be performed.')
+ option_parser.add_option('--force', action='store_true', default=False,
+ help='Force an installation even if the binaries are up to date.')
+ option_parser.add_option('--no-cleanup', action='store_true', default=False,
+ help='Allow installation on non-Windows platforms, and skip the forced '
+ 'cleanup step.')
+ option_parser.add_option('--output-dir', type='string',
+ help='The path where the binaries will be replaced. Existing binaries '
+ 'will only be overwritten if not up to date.')
+ option_parser.add_option('--overwrite', action='store_true', default=False,
+ help='If specified then the installation will happily delete and rewrite '
+ 'the entire output directory, blasting any local changes.')
+ option_parser.add_option('--revision', type='string',
+ help='The SVN revision or GIT hash associated with the required version.')
+ option_parser.add_option('--revision-file', type='string',
+ help='A text file containing an SVN revision or GIT hash.')
+ option_parser.add_option('--resource', type='string', action='append',
+ dest='resources', help='A resource to be downloaded.')
+ option_parser.add_option('--verbose', dest='log_level', action='store_const',
+ default=logging.INFO, const=logging.DEBUG,
+ help='Enables verbose logging.')
+ option_parser.add_option('--quiet', dest='log_level', action='store_const',
+ default=logging.INFO, const=logging.ERROR,
+ help='Disables all output except for errors.')
+ option_parser.add_option('--copy-dia-binaries', action='store_true',
+ default=False, help='If true then the DIA dll will get copied into the '
+ 'binaries directory if it\'s available.')
+ options, args = option_parser.parse_args()
+ if args:
+ option_parser.error('Unexpected arguments: %s' % args)
+ if not options.output_dir:
+ option_parser.error('Must specify --output-dir.')
+ if not options.revision and not options.revision_file:
+ option_parser.error('Must specify one of --revision or --revision-file.')
+ if options.revision and options.revision_file:
+ option_parser.error('Must not specify both --revision and --revision-file.')
+
+ # Configure logging.
+ logging.basicConfig(level=options.log_level)
+
+ # If a revision file has been specified then read it.
+ if options.revision_file:
+ options.revision = open(options.revision_file, 'rb').read().strip()
+ _LOGGER.debug('Parsed revision "%s" from file "%s".',
+ options.revision, options.revision_file)
+
+ # Ensure that the specified SVN revision or GIT hash is valid.
+ if not _REVISION_RE.match(options.revision):
+ option_parser.error('Must specify a valid SVN or GIT revision.')
+
+ # This just makes output prettier to read.
+ options.output_dir = os.path.normpath(options.output_dir)
+
+ return options
+
+
+def _RemoveOrphanedFiles(options):
+ """This is run on non-Windows systems to remove orphaned files that may have
+ been downloaded by a previous version of this script.
+ """
+ # Reconfigure logging to output info messages. This will allow inspection of
+ # cleanup status on non-Windows buildbots.
+ _LOGGER.setLevel(logging.INFO)
+
+ output_dir = os.path.abspath(options.output_dir)
+
+ # We only want to clean up the folder in 'src/third_party/syzygy', and we
+ # expect to be called with that as an output directory. This is an attempt to
+ # not start deleting random things if the script is run from an alternate
+ # location, or not called from the gclient hooks.
+ expected_syzygy_dir = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), '..', 'third_party', 'syzygy'))
+ expected_output_dir = os.path.join(expected_syzygy_dir, 'binaries')
+ if expected_output_dir != output_dir:
+ _LOGGER.info('Unexpected output directory, skipping cleanup.')
+ return
+
+ if not os.path.isdir(expected_syzygy_dir):
+ _LOGGER.info('Output directory does not exist, skipping cleanup.')
+ return
+
+ def OnError(function, path, excinfo):
+ """Logs error encountered by shutil.rmtree."""
+ _LOGGER.error('Error when running %s(%s)', function, path, exc_info=excinfo)
+
+ _LOGGER.info('Removing orphaned files from %s', expected_syzygy_dir)
+ if not options.dry_run:
+ shutil.rmtree(expected_syzygy_dir, True, OnError)
+
+
+def main():
+ options = _ParseCommandLine()
+
+ if options.dry_run:
+ _LOGGER.debug('Performing a dry-run.')
+
+ # We only care about Windows platforms, as the Syzygy binaries aren't used
+ # elsewhere. However, there was a short period of time where this script
+ # wasn't gated on OS types, and those OSes downloaded and installed binaries.
+ # This will cleanup orphaned files on those operating systems.
+ if sys.platform not in ('win32', 'cygwin'):
+ if options.no_cleanup:
+ _LOGGER.debug('Skipping usual cleanup for non-Windows platforms.')
+ else:
+ return _RemoveOrphanedFiles(options)
+
+ # Load the current installation state, and validate it against the
+ # requested installation.
+ state, is_consistent = _GetCurrentState(options.revision, options.output_dir)
+
+ # Decide whether or not an install is necessary.
+ if options.force:
+ _LOGGER.debug('Forcing reinstall of binaries.')
+ elif is_consistent:
+ # Avoid doing any work if the contents of the directory are consistent.
+ _LOGGER.debug('State unchanged, no reinstall necessary.')
+ return
+
+ # Under normal logging this is the only only message that will be reported.
+ _LOGGER.info('Installing revision %s Syzygy binaries.',
+ options.revision[0:12])
+
+ # Clean up the old state to begin with.
+ deleted = []
+ if options.overwrite:
+ if os.path.exists(options.output_dir):
+ # If overwrite was specified then take a heavy-handed approach.
+ _LOGGER.debug('Deleting entire installation directory.')
+ if not options.dry_run:
+ _RmTree(options.output_dir)
+ else:
+ # Otherwise only delete things that the previous installation put in place,
+ # and take care to preserve any local changes.
+ deleted = _CleanState(options.output_dir, state, options.dry_run)
+
+ # Install the new binaries. In a dry-run this will actually download the
+ # archives, but it won't write anything to disk.
+ state = _InstallBinaries(options, deleted)
+
+ # Build and save the state for the directory.
+ _SaveState(options.output_dir, state, options.dry_run)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/build/gn_helpers_unittest.py b/build/gn_helpers_unittest.py
new file mode 100644
index 0000000000..cc6018a172
--- /dev/null
+++ b/build/gn_helpers_unittest.py
@@ -0,0 +1,117 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import gn_helpers
+import unittest
+
+class UnitTest(unittest.TestCase):
+ def test_ToGNString(self):
+ self.assertEqual(
+ gn_helpers.ToGNString([1, 'two', [ '"thr$\\', True, False, [] ]]),
+ '[ 1, "two", [ "\\"thr\\$\\\\", true, false, [ ] ] ]')
+
+ def test_UnescapeGNString(self):
+ # Backslash followed by a \, $, or " means the folling character without
+ # the special meaning. Backslash followed by everything else is a literal.
+ self.assertEqual(
+ gn_helpers.UnescapeGNString('\\as\\$\\\\asd\\"'),
+ '\\as$\\asd"')
+
+ def test_FromGNString(self):
+ self.assertEqual(
+ gn_helpers.FromGNString('[1, -20, true, false,["as\\"", []]]'),
+ [ 1, -20, True, False, [ 'as"', [] ] ])
+
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('123 456')
+ parser.Parse()
+
+ def test_ParseBool(self):
+ parser = gn_helpers.GNValueParser('true')
+ self.assertEqual(parser.Parse(), True)
+
+ parser = gn_helpers.GNValueParser('false')
+ self.assertEqual(parser.Parse(), False)
+
+ def test_ParseNumber(self):
+ parser = gn_helpers.GNValueParser('123')
+ self.assertEqual(parser.ParseNumber(), 123)
+
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('')
+ parser.ParseNumber()
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('a123')
+ parser.ParseNumber()
+
+ def test_ParseString(self):
+ parser = gn_helpers.GNValueParser('"asdf"')
+ self.assertEqual(parser.ParseString(), 'asdf')
+
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('') # Empty.
+ parser.ParseString()
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('asdf') # Unquoted.
+ parser.ParseString()
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('"trailing') # Unterminated.
+ parser.ParseString()
+
+ def test_ParseList(self):
+ parser = gn_helpers.GNValueParser('[1,]') # Optional end comma OK.
+ self.assertEqual(parser.ParseList(), [ 1 ])
+
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('') # Empty.
+ parser.ParseList()
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('asdf') # No [].
+ parser.ParseList()
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('[1, 2') # Unterminated
+ parser.ParseList()
+ with self.assertRaises(gn_helpers.GNException):
+ parser = gn_helpers.GNValueParser('[1 2]') # No separating comma.
+ parser.ParseList()
+
+ def test_FromGNArgs(self):
+ # Booleans and numbers should work; whitespace is allowed works.
+ self.assertEqual(gn_helpers.FromGNArgs('foo = true\nbar = 1\n'),
+ {'foo': True, 'bar': 1})
+
+ # Whitespace is not required; strings should also work.
+ self.assertEqual(gn_helpers.FromGNArgs('foo="bar baz"'),
+ {'foo': 'bar baz'})
+
+ # Lists should work.
+ self.assertEqual(gn_helpers.FromGNArgs('foo=[1, 2, 3]'),
+ {'foo': [1, 2, 3]})
+
+ # Empty strings should return an empty dict.
+ self.assertEqual(gn_helpers.FromGNArgs(''), {})
+ self.assertEqual(gn_helpers.FromGNArgs(' \n '), {})
+
+ # Non-identifiers should raise an exception.
+ with self.assertRaises(gn_helpers.GNException):
+ gn_helpers.FromGNArgs('123 = true')
+
+ # References to other variables should raise an exception.
+ with self.assertRaises(gn_helpers.GNException):
+ gn_helpers.FromGNArgs('foo = bar')
+
+ # References to functions should raise an exception.
+ with self.assertRaises(gn_helpers.GNException):
+ gn_helpers.FromGNArgs('foo = exec_script("//build/baz.py")')
+
+ # Underscores in identifiers should work.
+ self.assertEqual(gn_helpers.FromGNArgs('_foo = true'),
+ {'_foo': True})
+ self.assertEqual(gn_helpers.FromGNArgs('foo_bar = true'),
+ {'foo_bar': True})
+ self.assertEqual(gn_helpers.FromGNArgs('foo_=true'),
+ {'foo_': True})
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/build/gn_run_binary.py b/build/gn_run_binary.py
new file mode 100644
index 0000000000..d7f7165e18
--- /dev/null
+++ b/build/gn_run_binary.py
@@ -0,0 +1,31 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Helper script for GN to run an arbitrary binary. See compiled_action.gni.
+
+Run with:
+ python gn_run_binary.py <binary_name> [args ...]
+"""
+
+import subprocess
+import sys
+
+# This script is designed to run binaries produced by the current build. We
+# always prefix it with "./" to avoid picking up system versions that might
+# also be on the path.
+path = './' + sys.argv[1]
+
+# The rest of the arguments are passed directly to the executable.
+args = [path] + sys.argv[2:]
+
+ret = subprocess.call(args)
+if ret != 0:
+ if ret <= -100:
+ # Windows error codes such as 0xC0000005 and 0xC0000409 are much easier to
+ # recognize and differentiate in hex. In order to print them as unsigned
+ # hex we need to add 4 Gig to them.
+ print '%s failed with exit code 0x%08X' % (sys.argv[1], ret + (1 << 32))
+ else:
+ print '%s failed with exit code %d' % (sys.argv[1], ret)
+sys.exit(ret)
diff --git a/build/gyp_chromium.py b/build/gyp_chromium.py
new file mode 100644
index 0000000000..ab2e470dce
--- /dev/null
+++ b/build/gyp_chromium.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This script is now only used by the closure_compilation builders."""
+
+import argparse
+import glob
+import gyp_environment
+import os
+import shlex
+import sys
+
+script_dir = os.path.dirname(os.path.realpath(__file__))
+chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
+
+sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
+import gyp
+
+
+def ProcessGypDefinesItems(items):
+ """Converts a list of strings to a list of key-value pairs."""
+ result = []
+ for item in items:
+ tokens = item.split('=', 1)
+ # Some GYP variables have hyphens, which we don't support.
+ if len(tokens) == 2:
+ result += [(tokens[0], tokens[1])]
+ else:
+ # No value supplied, treat it as a boolean and set it. Note that we
+ # use the string '1' here so we have a consistent definition whether
+ # you do 'foo=1' or 'foo'.
+ result += [(tokens[0], '1')]
+ return result
+
+
+def GetSupplementalFiles():
+ return []
+
+
+def GetGypVars(_):
+ """Returns a dictionary of all GYP vars."""
+ # GYP defines from the environment.
+ env_items = ProcessGypDefinesItems(
+ shlex.split(os.environ.get('GYP_DEFINES', '')))
+
+ # GYP defines from the command line.
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-D', dest='defines', action='append', default=[])
+ cmdline_input_items = parser.parse_known_args()[0].defines
+ cmdline_items = ProcessGypDefinesItems(cmdline_input_items)
+
+ return dict(env_items + cmdline_items)
+
+
+def main():
+ gyp_environment.SetEnvironment()
+
+ print 'Updating projects from gyp files...'
+ sys.stdout.flush()
+ sys.exit(gyp.main(sys.argv[1:] + [
+ '--check',
+ '--no-circular-check',
+ '-I', os.path.join(script_dir, 'common.gypi'),
+ '-D', 'gyp_output_dir=out']))
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/gyp_environment.py b/build/gyp_environment.py
new file mode 100644
index 0000000000..51b9136f24
--- /dev/null
+++ b/build/gyp_environment.py
@@ -0,0 +1,30 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Sets up various automatic gyp environment variables. These are used by
+gyp_chromium and landmines.py which run at different stages of runhooks. To
+make sure settings are consistent between them, all setup should happen here.
+"""
+
+import gyp_helper
+import os
+import sys
+import vs_toolchain
+
+def SetEnvironment():
+ """Sets defaults for GYP_* variables."""
+ gyp_helper.apply_chromium_gyp_env()
+
+ # Default to ninja on linux and windows, but only if no generator has
+ # explicitly been set.
+ # Also default to ninja on mac, but only when not building chrome/ios.
+ # . -f / --format has precedence over the env var, no need to check for it
+ # . set the env var only if it hasn't been set yet
+ # . chromium.gyp_env has been applied to os.environ at this point already
+ if sys.platform.startswith(('linux', 'win', 'freebsd', 'darwin')) and \
+ not os.environ.get('GYP_GENERATORS'):
+ os.environ['GYP_GENERATORS'] = 'ninja'
+
+ vs_toolchain.SetEnvironmentAndGetRuntimeDllDirs()
diff --git a/build/gyp_helper.py b/build/gyp_helper.py
new file mode 100644
index 0000000000..c840f2d6dc
--- /dev/null
+++ b/build/gyp_helper.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This file helps gyp_chromium and landmines correctly set up the gyp
+# environment from chromium.gyp_env on disk
+
+import os
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+CHROME_SRC = os.path.dirname(SCRIPT_DIR)
+
+
+def apply_gyp_environment_from_file(file_path):
+ """Reads in a *.gyp_env file and applies the valid keys to os.environ."""
+ if not os.path.exists(file_path):
+ return
+ with open(file_path, 'rU') as f:
+ file_contents = f.read()
+ try:
+ file_data = eval(file_contents, {'__builtins__': None}, None)
+ except SyntaxError, e:
+ e.filename = os.path.abspath(file_path)
+ raise
+ supported_vars = (
+ 'CC',
+ 'CC_wrapper',
+ 'CC.host_wrapper',
+ 'CHROMIUM_GYP_FILE',
+ 'CHROMIUM_GYP_SYNTAX_CHECK',
+ 'CXX',
+ 'CXX_wrapper',
+ 'CXX.host_wrapper',
+ 'GYP_DEFINES',
+ 'GYP_GENERATOR_FLAGS',
+ 'GYP_CROSSCOMPILE',
+ 'GYP_GENERATOR_OUTPUT',
+ 'GYP_GENERATORS',
+ 'GYP_INCLUDE_FIRST',
+ 'GYP_INCLUDE_LAST',
+ 'GYP_MSVS_VERSION',
+ )
+ for var in supported_vars:
+ file_val = file_data.get(var)
+ if file_val:
+ if var in os.environ:
+ behavior = 'replaces'
+ if var == 'GYP_DEFINES':
+ result = file_val + ' ' + os.environ[var]
+ behavior = 'merges with, and individual components override,'
+ else:
+ result = os.environ[var]
+ print 'INFO: Environment value for "%s" %s value in %s' % (
+ var, behavior, os.path.abspath(file_path)
+ )
+ string_padding = max(len(var), len(file_path), len('result'))
+ print ' %s: %s' % (var.rjust(string_padding), os.environ[var])
+ print ' %s: %s' % (file_path.rjust(string_padding), file_val)
+ os.environ[var] = result
+ else:
+ os.environ[var] = file_val
+
+
+def apply_chromium_gyp_env():
+ if 'SKIP_CHROMIUM_GYP_ENV' not in os.environ:
+ # Update the environment based on chromium.gyp_env
+ path = os.path.join(os.path.dirname(CHROME_SRC), 'chromium.gyp_env')
+ apply_gyp_environment_from_file(path)
diff --git a/build/gypi_to_gn.py b/build/gypi_to_gn.py
new file mode 100644
index 0000000000..2a3a72ac0f
--- /dev/null
+++ b/build/gypi_to_gn.py
@@ -0,0 +1,192 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Converts a given gypi file to a python scope and writes the result to stdout.
+
+USING THIS SCRIPT IN CHROMIUM
+
+Forking Python to run this script in the middle of GN is slow, especially on
+Windows, and it makes both the GYP and GN files harder to follow. You can't
+use "git grep" to find files in the GN build any more, and tracking everything
+in GYP down requires a level of indirection. Any calls will have to be removed
+and cleaned up once the GYP-to-GN transition is complete.
+
+As a result, we only use this script when the list of files is large and
+frequently-changing. In these cases, having one canonical list outweights the
+downsides.
+
+As of this writing, the GN build is basically complete. It's likely that all
+large and frequently changing targets where this is appropriate use this
+mechanism already. And since we hope to turn down the GYP build soon, the time
+horizon is also relatively short. As a result, it is likely that no additional
+uses of this script should every be added to the build. During this later part
+of the transition period, we should be focusing more and more on the absolute
+readability of the GN build.
+
+
+HOW TO USE
+
+It is assumed that the file contains a toplevel dictionary, and this script
+will return that dictionary as a GN "scope" (see example below). This script
+does not know anything about GYP and it will not expand variables or execute
+conditions.
+
+It will strip conditions blocks.
+
+A variables block at the top level will be flattened so that the variables
+appear in the root dictionary. This way they can be returned to the GN code.
+
+Say your_file.gypi looked like this:
+ {
+ 'sources': [ 'a.cc', 'b.cc' ],
+ 'defines': [ 'ENABLE_DOOM_MELON' ],
+ }
+
+You would call it like this:
+ gypi_values = exec_script("//build/gypi_to_gn.py",
+ [ rebase_path("your_file.gypi") ],
+ "scope",
+ [ "your_file.gypi" ])
+
+Notes:
+ - The rebase_path call converts the gypi file from being relative to the
+ current build file to being system absolute for calling the script, which
+ will have a different current directory than this file.
+
+ - The "scope" parameter tells GN to interpret the result as a series of GN
+ variable assignments.
+
+ - The last file argument to exec_script tells GN that the given file is a
+ dependency of the build so Ninja can automatically re-run GN if the file
+ changes.
+
+Read the values into a target like this:
+ component("mycomponent") {
+ sources = gypi_values.sources
+ defines = gypi_values.defines
+ }
+
+Sometimes your .gypi file will include paths relative to a different
+directory than the current .gn file. In this case, you can rebase them to
+be relative to the current directory.
+ sources = rebase_path(gypi_values.sources, ".",
+ "//path/gypi/input/values/are/relative/to")
+
+This script will tolerate a 'variables' in the toplevel dictionary or not. If
+the toplevel dictionary just contains one item called 'variables', it will be
+collapsed away and the result will be the contents of that dictinoary. Some
+.gypi files are written with or without this, depending on how they expect to
+be embedded into a .gyp file.
+
+This script also has the ability to replace certain substrings in the input.
+Generally this is used to emulate GYP variable expansion. If you passed the
+argument "--replace=<(foo)=bar" then all instances of "<(foo)" in strings in
+the input will be replaced with "bar":
+
+ gypi_values = exec_script("//build/gypi_to_gn.py",
+ [ rebase_path("your_file.gypi"),
+ "--replace=<(foo)=bar"],
+ "scope",
+ [ "your_file.gypi" ])
+
+"""
+
+import gn_helpers
+from optparse import OptionParser
+import sys
+
+def LoadPythonDictionary(path):
+ file_string = open(path).read()
+ try:
+ file_data = eval(file_string, {'__builtins__': None}, None)
+ except SyntaxError, e:
+ e.filename = path
+ raise
+ except Exception, e:
+ raise Exception("Unexpected error while reading %s: %s" % (path, str(e)))
+
+ assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path
+
+ # Flatten any variables to the top level.
+ if 'variables' in file_data:
+ file_data.update(file_data['variables'])
+ del file_data['variables']
+
+ # Strip all elements that this script can't process.
+ elements_to_strip = [
+ 'conditions',
+ 'target_conditions',
+ 'target_defaults',
+ 'targets',
+ 'includes',
+ 'actions',
+ ]
+ for element in elements_to_strip:
+ if element in file_data:
+ del file_data[element]
+
+ return file_data
+
+
+def ReplaceSubstrings(values, search_for, replace_with):
+ """Recursively replaces substrings in a value.
+
+ Replaces all substrings of the "search_for" with "repace_with" for all
+ strings occurring in "values". This is done by recursively iterating into
+ lists as well as the keys and values of dictionaries."""
+ if isinstance(values, str):
+ return values.replace(search_for, replace_with)
+
+ if isinstance(values, list):
+ return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
+
+ if isinstance(values, dict):
+ # For dictionaries, do the search for both the key and values.
+ result = {}
+ for key, value in values.items():
+ new_key = ReplaceSubstrings(key, search_for, replace_with)
+ new_value = ReplaceSubstrings(value, search_for, replace_with)
+ result[new_key] = new_value
+ return result
+
+ # Assume everything else is unchanged.
+ return values
+
+def main():
+ parser = OptionParser()
+ parser.add_option("-r", "--replace", action="append",
+ help="Replaces substrings. If passed a=b, replaces all substrs a with b.")
+ (options, args) = parser.parse_args()
+
+ if len(args) != 1:
+ raise Exception("Need one argument which is the .gypi file to read.")
+
+ data = LoadPythonDictionary(args[0])
+ if options.replace:
+ # Do replacements for all specified patterns.
+ for replace in options.replace:
+ split = replace.split('=')
+ # Allow "foo=" to replace with nothing.
+ if len(split) == 1:
+ split.append('')
+ assert len(split) == 2, "Replacement must be of the form 'key=value'."
+ data = ReplaceSubstrings(data, split[0], split[1])
+
+ # Sometimes .gypi files use the GYP syntax with percents at the end of the
+ # variable name (to indicate not to overwrite a previously-defined value):
+ # 'foo%': 'bar',
+ # Convert these to regular variables.
+ for key in data:
+ if len(key) > 1 and key[len(key) - 1] == '%':
+ data[key[:-1]] = data[key]
+ del data[key]
+
+ print gn_helpers.ToGNString(data)
+
+if __name__ == '__main__':
+ try:
+ main()
+ except Exception, e:
+ print str(e)
+ sys.exit(1)
diff --git a/build/landmine_utils.py b/build/landmine_utils.py
new file mode 100644
index 0000000000..a3f21ff1b8
--- /dev/null
+++ b/build/landmine_utils.py
@@ -0,0 +1,33 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+import sys
+
+
+def IsWindows():
+ return sys.platform in ['win32', 'cygwin']
+
+
+def IsLinux():
+ return sys.platform.startswith(('linux', 'freebsd', 'netbsd', 'openbsd'))
+
+
+def IsMac():
+ return sys.platform == 'darwin'
+
+
+def host_os():
+ """
+ Returns a string representing the host_os of the current system.
+ Possible values: 'win', 'mac', 'linux', 'unknown'.
+ """
+ if IsWindows():
+ return 'win'
+ elif IsLinux():
+ return 'linux'
+ elif IsMac():
+ return 'mac'
+ else:
+ return 'unknown'
diff --git a/build/landmines.py b/build/landmines.py
new file mode 100755
index 0000000000..d0f429809a
--- /dev/null
+++ b/build/landmines.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+This script runs every build as the first hook (See DEPS). If it detects that
+the build should be clobbered, it will delete the contents of the build
+directory.
+
+A landmine is tripped when a builder checks out a different revision, and the
+diff between the new landmines and the old ones is non-null. At this point, the
+build is clobbered.
+
+Before adding or changing a landmine consider the consequences of doing so.
+Doing so will wipe out every output directory on every Chrome developer's
+machine. This can be particularly problematic on Windows where the directory
+deletion may well fail (locked files, command prompt in the directory, etc.),
+and generated .sln and .vcxproj files will be deleted.
+
+This output directory deletion will be repated when going back and forth across
+the change that added the landmine, adding to the cost. There are usually less
+troublesome alternatives.
+"""
+
+import difflib
+import errno
+import logging
+import optparse
+import os
+import sys
+import subprocess
+import time
+
+import clobber
+import landmine_utils
+
+
+def get_build_dir(src_dir):
+ """
+ Returns output directory absolute path dependent on build and targets.
+ Examples:
+ r'c:\b\build\slave\win\build\src\out'
+ '/mnt/data/b/build/slave/linux/build/src/out'
+ '/b/build/slave/ios_rel_device/build/src/out'
+
+ Keep this function in sync with tools/build/scripts/slave/compile.py
+ """
+ if 'CHROMIUM_OUT_DIR' in os.environ:
+ output_dir = os.environ.get('CHROMIUM_OUT_DIR').strip()
+ if not output_dir:
+ raise Error('CHROMIUM_OUT_DIR environment variable is set but blank!')
+ else:
+ output_dir = 'out'
+ return os.path.abspath(os.path.join(src_dir, output_dir))
+
+
+def clobber_if_necessary(new_landmines, src_dir):
+ """Does the work of setting, planting, and triggering landmines."""
+ out_dir = get_build_dir(src_dir)
+ landmines_path = os.path.normpath(os.path.join(src_dir, '.landmines'))
+ try:
+ os.makedirs(out_dir)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ pass
+
+ if os.path.exists(landmines_path):
+ with open(landmines_path, 'r') as f:
+ old_landmines = f.readlines()
+ if old_landmines != new_landmines:
+ old_date = time.ctime(os.stat(landmines_path).st_ctime)
+ diff = difflib.unified_diff(old_landmines, new_landmines,
+ fromfile='old_landmines', tofile='new_landmines',
+ fromfiledate=old_date, tofiledate=time.ctime(), n=0)
+ sys.stdout.write('Clobbering due to:\n')
+ sys.stdout.writelines(diff)
+ sys.stdout.flush()
+
+ clobber.clobber(out_dir)
+
+ # Save current set of landmines for next time.
+ with open(landmines_path, 'w') as f:
+ f.writelines(new_landmines)
+
+
+def process_options():
+ """Returns an options object containing the configuration for this script."""
+ parser = optparse.OptionParser()
+ parser.add_option(
+ '-s', '--landmine-scripts', action='append',
+ help='Path to the script which emits landmines to stdout. The target '
+ 'is passed to this script via option -t. Note that an extra '
+ 'script can be specified via an env var EXTRA_LANDMINES_SCRIPT.')
+ parser.add_option('-d', '--src-dir',
+ help='Path of the source root dir. Overrides the default location of the '
+ 'source root dir when calculating the build directory.')
+ parser.add_option('-v', '--verbose', action='store_true',
+ default=('LANDMINES_VERBOSE' in os.environ),
+ help=('Emit some extra debugging information (default off). This option '
+ 'is also enabled by the presence of a LANDMINES_VERBOSE environment '
+ 'variable.'))
+
+ options, args = parser.parse_args()
+
+ if args:
+ parser.error('Unknown arguments %s' % args)
+
+ logging.basicConfig(
+ level=logging.DEBUG if options.verbose else logging.ERROR)
+
+ if options.src_dir:
+ if not os.path.isdir(options.src_dir):
+ parser.error('Cannot find source root dir at %s' % options.src_dir)
+ logging.debug('Overriding source root dir. Using: %s', options.src_dir)
+ else:
+ options.src_dir = \
+ os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+
+ if not options.landmine_scripts:
+ options.landmine_scripts = [os.path.join(options.src_dir, 'build',
+ 'get_landmines.py')]
+
+ extra_script = os.environ.get('EXTRA_LANDMINES_SCRIPT')
+ if extra_script:
+ options.landmine_scripts += [extra_script]
+
+ return options
+
+
+def main():
+ options = process_options()
+
+ landmines = []
+ for s in options.landmine_scripts:
+ proc = subprocess.Popen([sys.executable, s], stdout=subprocess.PIPE)
+ output, _ = proc.communicate()
+ landmines.extend([('%s\n' % l.strip()) for l in output.splitlines()])
+ clobber_if_necessary(landmines, options.src_dir)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/precompile.h b/build/precompile.h
new file mode 100644
index 0000000000..c699562c0c
--- /dev/null
+++ b/build/precompile.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is used as a precompiled header for both C and C++ files. So
+// any C++ headers must go in the __cplusplus block below.
+
+#if defined(BUILD_PRECOMPILE_H_)
+#error You shouldn't include the precompiled header file more than once.
+#endif
+
+#define BUILD_PRECOMPILE_H_
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <math.h>
+#include <memory.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#if defined(__cplusplus)
+
+#include <algorithm>
+#include <bitset>
+#include <cmath>
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <functional>
+#include <iomanip>
+#include <iosfwd>
+#include <iterator>
+#include <limits>
+#include <list>
+#include <map>
+#include <numeric>
+#include <ostream>
+#include <queue>
+#include <set>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#endif // __cplusplus
diff --git a/build/print_python_deps.py b/build/print_python_deps.py
new file mode 100755
index 0000000000..2459a28e6b
--- /dev/null
+++ b/build/print_python_deps.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env vpython
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Prints all non-system dependencies for the given module.
+
+The primary use-case for this script is to genererate the list of python modules
+required for .isolate files.
+"""
+
+import argparse
+import imp
+import os
+import pipes
+import sys
+
+# Don't use any helper modules, or else they will end up in the results.
+
+
+_SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
+
+
+def _ComputePythonDependencies():
+ """Gets the paths of imported non-system python modules.
+
+ A path is assumed to be a "system" import if it is outside of chromium's
+ src/. The paths will be relative to the current directory.
+ """
+ module_paths = (m.__file__ for m in sys.modules.values()
+ if m and hasattr(m, '__file__'))
+
+ src_paths = set()
+ for path in module_paths:
+ if path == __file__:
+ continue
+ path = os.path.abspath(path)
+ if not path.startswith(_SRC_ROOT):
+ continue
+
+ if (path.endswith('.pyc')
+ or (path.endswith('c') and not os.path.splitext(path)[1])):
+ path = path[:-1]
+ src_paths.add(path)
+
+ return src_paths
+
+
+def _NormalizeCommandLine(options):
+ """Returns a string that when run from SRC_ROOT replicates the command."""
+ args = ['build/print_python_deps.py']
+ root = os.path.relpath(options.root, _SRC_ROOT)
+ if root != '.':
+ args.extend(('--root', root))
+ if options.output:
+ args.extend(('--output', os.path.relpath(options.output, _SRC_ROOT)))
+ for whitelist in sorted(options.whitelists):
+ args.extend(('--whitelist', os.path.relpath(whitelist, _SRC_ROOT)))
+ args.append(os.path.relpath(options.module, _SRC_ROOT))
+ return ' '.join(pipes.quote(x) for x in args)
+
+
+def _FindPythonInDirectory(directory):
+ """Returns an iterable of all non-test python files in the given directory."""
+ files = []
+ for root, _dirnames, filenames in os.walk(directory):
+ for filename in filenames:
+ if filename.endswith('.py') and not filename.endswith('_test.py'):
+ yield os.path.join(root, filename)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Prints all non-system dependencies for the given module.')
+ parser.add_argument('module',
+ help='The python module to analyze.')
+ parser.add_argument('--root', default='.',
+ help='Directory to make paths relative to.')
+ parser.add_argument('--output',
+ help='Write output to a file rather than stdout.')
+ parser.add_argument('--no-header', action='store_true',
+ help='Do not write the "# Generated by" header.')
+ parser.add_argument('--gn-paths', action='store_true',
+ help='Write paths as //foo/bar/baz.py')
+ parser.add_argument('--whitelist', default=[], action='append',
+ dest='whitelists',
+ help='Recursively include all non-test python files '
+ 'within this directory. May be specified multiple times.')
+ options = parser.parse_args()
+ # Replace the path entry for print_python_deps.py with the one for the given
+ # module.
+ sys.path[0] = os.path.dirname(options.module)
+ imp.load_source('NAME', options.module)
+
+ paths_set = _ComputePythonDependencies()
+ for path in options.whitelists:
+ paths_set.update(os.path.abspath(p) for p in _FindPythonInDirectory(path))
+
+ paths = [os.path.relpath(p, options.root) for p in paths_set]
+
+ normalized_cmdline = _NormalizeCommandLine(options)
+ out = open(options.output, 'w') if options.output else sys.stdout
+ with out:
+ if not options.no_header:
+ out.write('# Generated by running:\n')
+ out.write('# %s\n' % normalized_cmdline)
+ prefix = '//' if options.gn_paths else ''
+ for path in sorted(paths):
+ out.write(prefix + path + '\n')
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/protoc_java.py b/build/protoc_java.py
new file mode 100755
index 0000000000..2addb8210f
--- /dev/null
+++ b/build/protoc_java.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generate java source files from protobuf files.
+
+This is a helper file for the genproto_java action in protoc_java.gypi.
+
+It performs the following steps:
+1. Deletes all old sources (ensures deleted classes are not part of new jars).
+2. Creates source directory.
+3. Generates Java files using protoc (output into either --java-out-dir or
+ --srcjar).
+4. Creates a new stamp file.
+"""
+
+import os
+import optparse
+import shutil
+import subprocess
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), "android", "gyp"))
+from util import build_utils
+
+def main(argv):
+ parser = optparse.OptionParser()
+ build_utils.AddDepfileOption(parser)
+ parser.add_option("--protoc", help="Path to protoc binary.")
+ parser.add_option("--proto-path", help="Path to proto directory.")
+ parser.add_option("--java-out-dir",
+ help="Path to output directory for java files.")
+ parser.add_option("--srcjar", help="Path to output srcjar.")
+ parser.add_option("--stamp", help="File to touch on success.")
+ parser.add_option("--nano",
+ help="Use to generate nano protos.", action='store_true')
+ options, args = parser.parse_args(argv)
+
+ build_utils.CheckOptions(options, parser, ['protoc', 'proto_path'])
+ if not options.java_out_dir and not options.srcjar:
+ print 'One of --java-out-dir or --srcjar must be specified.'
+ return 1
+
+ with build_utils.TempDir() as temp_dir:
+ if options.nano:
+ # Specify arguments to the generator.
+ generator_args = ['optional_field_style=reftypes',
+ 'store_unknown_fields=true']
+ out_arg = '--javanano_out=' + ','.join(generator_args) + ':' + temp_dir
+ else:
+ out_arg = '--java_out=' + temp_dir
+
+ # Check if all proto files (which are listed in the args) are opting to
+ # use the lite runtime, otherwise we'd have to include the much heavier
+ # regular proto runtime in Chrome.
+ # TODO(jkrcal): Replace this check by '--java_lite_out=' for the out_arg
+ # above once this works on the master branch of the protobuf library,
+ # expected in version 4.0 (see https://crbug.com/800281).
+ for proto_file in args:
+ if not 'LITE_RUNTIME' in open(proto_file).read():
+ raise Exception(
+ 'Chrome only supports lite protos. Please add "optimize_for = '
+ 'LITE_RUNTIME" to your proto file to enable the lite runtime.')
+ # Generate Java files using protoc.
+ build_utils.CheckOutput(
+ [options.protoc, '--proto_path', options.proto_path, out_arg]
+ + args)
+
+ if options.java_out_dir:
+ build_utils.DeleteDirectory(options.java_out_dir)
+ shutil.copytree(temp_dir, options.java_out_dir)
+ else:
+ build_utils.ZipDir(options.srcjar, temp_dir)
+
+ if options.depfile:
+ assert options.srcjar
+ deps = args + [options.protoc]
+ build_utils.WriteDepfile(options.depfile, options.srcjar, deps)
+
+ if options.stamp:
+ build_utils.Touch(options.stamp)
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/build/redirect_stdout.py b/build/redirect_stdout.py
new file mode 100644
index 0000000000..72d0732af1
--- /dev/null
+++ b/build/redirect_stdout.py
@@ -0,0 +1,19 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import subprocess
+import sys
+
+# This script executes a command and redirects the stdout to a file. This is
+# equivalent to |command... > output_file|.
+#
+# Usage: python redirect_stdout.py output_file command...
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print >> sys.stderr, "Usage: %s output_file command..." % (sys.argv[0])
+ sys.exit(1)
+
+ with open(sys.argv[1], 'w') as fp:
+ sys.exit(subprocess.check_call(sys.argv[2:], stdout=fp))
diff --git a/build/rm.py b/build/rm.py
new file mode 100755
index 0000000000..5ca642d46e
--- /dev/null
+++ b/build/rm.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# Copyright (c) 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Delete a file.
+
+This module works much like the rm posix command.
+"""
+
+import argparse
+import os
+import sys
+
+
+def Main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('files', nargs='+')
+ parser.add_argument('-f', '--force', action='store_true',
+ help="don't err on missing")
+ parser.add_argument('--stamp', required=True, help='touch this file')
+ args = parser.parse_args()
+ for f in args.files:
+ try:
+ os.remove(f)
+ except OSError:
+ if not args.force:
+ print >>sys.stderr, "'%s' does not exist" % f
+ return 1
+
+ with open(args.stamp, 'w'):
+ os.utime(args.stamp, None)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/build/run_swarming_xcode_install.py b/build/run_swarming_xcode_install.py
new file mode 100755
index 0000000000..a731c1bd6b
--- /dev/null
+++ b/build/run_swarming_xcode_install.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+This script runs swarming_xcode_install on the bots. It should be run when we
+need to upgrade all the swarming testers. It:
+ 1) Packages two python files into an isolate.
+ 2) Runs the isolate on swarming machines that satisfy certain dimensions.
+
+Example usage:
+ $ ./build/run_swarming_xcode_install.py --luci_path ~/work/luci-py \
+ --swarming-server touch-swarming.appspot.com \
+ --isolate-server touch-isolate.appspot.com
+"""
+
+import argparse
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Run swarming_xcode_install on the bots.')
+ parser.add_argument('--luci_path', required=True, type=os.path.abspath)
+ parser.add_argument('--swarming-server', required=True, type=str)
+ parser.add_argument('--isolate-server', required=True, type=str)
+ parser.add_argument('--batches', type=int, default=25,
+ help="Run xcode install in batches of size |batches|.")
+ parser.add_argument('--dimension', nargs=2, action='append')
+ args = parser.parse_args()
+
+ args.dimension = args.dimension or []
+
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ tmp_dir = tempfile.mkdtemp(prefix='swarming_xcode')
+ try:
+ print 'Making isolate.'
+ shutil.copyfile(os.path.join(script_dir, 'swarming_xcode_install.py'),
+ os.path.join(tmp_dir, 'swarming_xcode_install.py'))
+ shutil.copyfile(os.path.join(script_dir, 'mac_toolchain.py'),
+ os.path.join(tmp_dir, 'mac_toolchain.py'))
+
+ luci_client = os.path.join(args.luci_path, 'client')
+ cmd = [
+ sys.executable, os.path.join(luci_client, 'isolateserver.py'), 'archive',
+ '-I', args.isolate_server, tmp_dir,
+ ]
+ isolate_hash = subprocess.check_output(cmd).split()[0]
+
+ print 'Running swarming_xcode_install.'
+ # TODO(crbug.com/765361): The dimensions below should be updated once
+ # swarming for iOS is fleshed out, likely removing xcode_version 9 and
+ # adding different dimensions.
+ luci_tools = os.path.join(luci_client, 'tools')
+ dimensions = [['pool', 'Chrome'], ['xcode_version', '9.0']] + args.dimension
+ dim_args = []
+ for d in dimensions:
+ dim_args += ['--dimension'] + d
+ cmd = [
+ sys.executable, os.path.join(luci_tools, 'run_on_bots.py'),
+ '--swarming', args.swarming_server, '--isolate-server',
+ args.isolate_server, '--priority', '20', '--batches', str(args.batches),
+ '--tags', 'name:run_swarming_xcode_install',
+ ] + dim_args + ['--name', 'run_swarming_xcode_install', '--', isolate_hash,
+ 'python', 'swarming_xcode_install.py',
+ ]
+ subprocess.check_call(cmd)
+ print 'All tasks completed.'
+
+ finally:
+ shutil.rmtree(tmp_dir)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/swarming_xcode_install.py b/build/swarming_xcode_install.py
new file mode 100755
index 0000000000..7764aa55c7
--- /dev/null
+++ b/build/swarming_xcode_install.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Script used to install Xcode on the swarming bots.
+"""
+
+import os
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+
+import mac_toolchain
+
+VERSION = '9A235'
+URL = 'gs://chrome-mac-sdk/ios-toolchain-9A235-1.tgz'
+REMOVE_DIR = '/Applications/Xcode9.0-Beta4.app/'
+OUTPUT_DIR = '/Applications/Xcode9.0.app/'
+
+def main():
+ # Check if it's already installed.
+ if os.path.exists(OUTPUT_DIR):
+ env = os.environ.copy()
+ env['DEVELOPER_DIR'] = OUTPUT_DIR
+ cmd = ['xcodebuild', '-version']
+ found_version = \
+ subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE).communicate()[0]
+ if VERSION in found_version:
+ print "Xcode %s already installed" % VERSION
+ sys.exit(0)
+
+ # Confirm old dir is there first.
+ if not os.path.exists(REMOVE_DIR):
+ print "Failing early since %s isn't there." % REMOVE_DIR
+ sys.exit(1)
+
+ # Download Xcode.
+ with tempfile.NamedTemporaryFile() as temp:
+ env = os.environ.copy()
+ env['PATH'] += ":/b/depot_tools"
+ subprocess.check_call(['gsutil.py', 'cp', URL, temp.name], env=env)
+ if os.path.exists(OUTPUT_DIR):
+ shutil.rmtree(OUTPUT_DIR)
+ if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+ tarfile.open(mode='r:gz', name=temp.name).extractall(path=OUTPUT_DIR)
+
+ # Accept license, call runFirstLaunch.
+ mac_toolchain.FinalizeUnpack(OUTPUT_DIR, 'ios')
+
+ # Set new Xcode as default.
+ subprocess.check_call(['sudo', '/usr/bin/xcode-select', '-s', OUTPUT_DIR])
+
+ if os.path.exists(REMOVE_DIR):
+ shutil.rmtree(REMOVE_DIR)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
+
diff --git a/build/symlink.py b/build/symlink.py
new file mode 100755
index 0000000000..5a261dcad9
--- /dev/null
+++ b/build/symlink.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Make a symlink and optionally touch a file (to handle dependencies).
+
+Usage:
+ symlink.py [options] sources... target
+
+A sym link to source is created at target. If multiple sources are specfied,
+then target is assumed to be a directory, and will contain all the links to
+the sources (basenames identical to their source).
+"""
+
+import errno
+import optparse
+import os.path
+import shutil
+import sys
+
+
+def Main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('-f', '--force', action='store_true')
+ parser.add_option('--touch')
+
+ options, args = parser.parse_args(argv[1:])
+ if len(args) < 2:
+ parser.error('at least two arguments required.')
+
+ target = args[-1]
+ sources = args[:-1]
+ for s in sources:
+ t = os.path.join(target, os.path.basename(s))
+ if len(sources) == 1 and not os.path.isdir(target):
+ t = target
+ t = os.path.expanduser(t)
+ if os.path.realpath(t) == s:
+ continue
+ try:
+ os.symlink(s, t)
+ except OSError, e:
+ if e.errno == errno.EEXIST and options.force:
+ if os.path.isdir(t):
+ shutil.rmtree(t, ignore_errors=True)
+ else:
+ os.remove(t)
+ os.symlink(s, t)
+ else:
+ raise
+
+
+ if options.touch:
+ with open(options.touch, 'w') as f:
+ pass
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv))
diff --git a/build/vs_toolchain.py b/build/vs_toolchain.py
new file mode 100755
index 0000000000..83847f4566
--- /dev/null
+++ b/build/vs_toolchain.py
@@ -0,0 +1,471 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import glob
+import json
+import os
+import pipes
+import platform
+import re
+import shutil
+import stat
+import subprocess
+import sys
+from gn_helpers import ToGNString
+
+
+script_dir = os.path.dirname(os.path.realpath(__file__))
+chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
+SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
+json_data_file = os.path.join(script_dir, 'win_toolchain.json')
+
+
+# Use MSVS2017 as the default toolchain.
+CURRENT_DEFAULT_TOOLCHAIN_VERSION = '2017'
+
+
+def SetEnvironmentAndGetRuntimeDllDirs():
+ """Sets up os.environ to use the depot_tools VS toolchain with gyp, and
+ returns the location of the VS runtime DLLs so they can be copied into
+ the output directory after gyp generation.
+
+ Return value is [x64path, x86path] or None
+ """
+ vs_runtime_dll_dirs = None
+ depot_tools_win_toolchain = \
+ bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
+ # When running on a non-Windows host, only do this if the SDK has explicitly
+ # been downloaded before (in which case json_data_file will exist).
+ if ((sys.platform in ('win32', 'cygwin') or os.path.exists(json_data_file))
+ and depot_tools_win_toolchain):
+ if ShouldUpdateToolchain():
+ update_result = Update()
+ if update_result != 0:
+ raise Exception('Failed to update, error code %d.' % update_result)
+ with open(json_data_file, 'r') as tempf:
+ toolchain_data = json.load(tempf)
+
+ toolchain = toolchain_data['path']
+ version = toolchain_data['version']
+ win_sdk = toolchain_data.get('win_sdk')
+ if not win_sdk:
+ win_sdk = toolchain_data['win8sdk']
+ wdk = toolchain_data['wdk']
+ # TODO(scottmg): The order unfortunately matters in these. They should be
+ # split into separate keys for x86 and x64. (See CopyDlls call below).
+ # http://crbug.com/345992
+ vs_runtime_dll_dirs = toolchain_data['runtime_dirs']
+
+ os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain
+ os.environ['GYP_MSVS_VERSION'] = version
+
+ os.environ['WINDOWSSDKDIR'] = win_sdk
+ os.environ['WDK_DIR'] = wdk
+ # Include the VS runtime in the PATH in case it's not machine-installed.
+ runtime_path = os.path.pathsep.join(vs_runtime_dll_dirs)
+ os.environ['PATH'] = runtime_path + os.path.pathsep + os.environ['PATH']
+ elif sys.platform == 'win32' and not depot_tools_win_toolchain:
+ if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ:
+ os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath()
+ if not 'GYP_MSVS_VERSION' in os.environ:
+ os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
+
+ # When using an installed toolchain these files aren't needed in the output
+ # directory in order to run binaries locally, but they are needed in order
+ # to create isolates or the mini_installer. Copying them to the output
+ # directory ensures that they are available when needed.
+ bitness = platform.architecture()[0]
+ # When running 64-bit python the x64 DLLs will be in System32
+ x64_path = 'System32' if bitness == '64bit' else 'Sysnative'
+ x64_path = os.path.join(os.path.expandvars('%windir%'), x64_path)
+ vs_runtime_dll_dirs = [x64_path, os.path.expandvars('%windir%/SysWOW64')]
+
+ return vs_runtime_dll_dirs
+
+
+def _RegistryGetValueUsingWinReg(key, value):
+ """Use the _winreg module to obtain the value of a registry key.
+
+ Args:
+ key: The registry key.
+ value: The particular registry value to read.
+ Return:
+ contents of the registry key's value, or None on failure. Throws
+ ImportError if _winreg is unavailable.
+ """
+ import _winreg
+ try:
+ root, subkey = key.split('\\', 1)
+ assert root == 'HKLM' # Only need HKLM for now.
+ with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey:
+ return _winreg.QueryValueEx(hkey, value)[0]
+ except WindowsError:
+ return None
+
+
+def _RegistryGetValue(key, value):
+ try:
+ return _RegistryGetValueUsingWinReg(key, value)
+ except ImportError:
+ raise Exception('The python library _winreg not found.')
+
+
+def GetVisualStudioVersion():
+ """Return GYP_MSVS_VERSION of Visual Studio.
+ """
+ return os.environ.get('GYP_MSVS_VERSION', CURRENT_DEFAULT_TOOLCHAIN_VERSION)
+
+
+def DetectVisualStudioPath():
+ """Return path to the GYP_MSVS_VERSION of Visual Studio.
+ """
+
+ # Note that this code is used from
+ # build/toolchain/win/setup_toolchain.py as well.
+ version_as_year = GetVisualStudioVersion()
+ year_to_version = {
+ '2017': '15.0',
+ }
+ if version_as_year not in year_to_version:
+ raise Exception(('Visual Studio version %s (from GYP_MSVS_VERSION)'
+ ' not supported. Supported versions are: %s') % (
+ version_as_year, ', '.join(year_to_version.keys())))
+ version = year_to_version[version_as_year]
+ if version_as_year == '2017':
+ # The VC++ 2017 install location needs to be located using COM instead of
+ # the registry. For details see:
+ # https://blogs.msdn.microsoft.com/heaths/2016/09/15/changes-to-visual-studio-15-setup/
+ # For now we use a hardcoded default with an environment variable override.
+ for path in (
+ os.environ.get('vs2017_install'),
+ os.path.expandvars('%ProgramFiles(x86)%'
+ '/Microsoft Visual Studio/2017/Enterprise'),
+ os.path.expandvars('%ProgramFiles(x86)%'
+ '/Microsoft Visual Studio/2017/Professional'),
+ os.path.expandvars('%ProgramFiles(x86)%'
+ '/Microsoft Visual Studio/2017/Community')):
+ if path and os.path.exists(path):
+ return path
+
+ raise Exception(('Visual Studio Version %s (from GYP_MSVS_VERSION)'
+ ' not found.') % (version_as_year))
+
+
+def _CopyRuntimeImpl(target, source, verbose=True):
+ """Copy |source| to |target| if it doesn't already exist or if it needs to be
+ updated (comparing last modified time as an approximate float match as for
+ some reason the values tend to differ by ~1e-07 despite being copies of the
+ same file... https://crbug.com/603603).
+ """
+ if (os.path.isdir(os.path.dirname(target)) and
+ (not os.path.isfile(target) or
+ abs(os.stat(target).st_mtime - os.stat(source).st_mtime) >= 0.01)):
+ if verbose:
+ print 'Copying %s to %s...' % (source, target)
+ if os.path.exists(target):
+ # Make the file writable so that we can delete it now, and keep it
+ # readable.
+ os.chmod(target, stat.S_IWRITE | stat.S_IREAD)
+ os.unlink(target)
+ shutil.copy2(source, target)
+ # Make the file writable so that we can overwrite or delete it later,
+ # keep it readable.
+ os.chmod(target, stat.S_IWRITE | stat.S_IREAD)
+
+
+def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, dll_pattern, suffix):
+ """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't
+ exist, but the target directory does exist."""
+ for file_part in ('msvcp', 'vccorlib', 'vcruntime'):
+ dll = dll_pattern % file_part
+ target = os.path.join(target_dir, dll)
+ source = os.path.join(source_dir, dll)
+ _CopyRuntimeImpl(target, source)
+ # Copy the UCRT files from the Windows SDK. This location includes the
+ # api-ms-win-crt-*.dll files that are not found in the Windows directory.
+ # These files are needed for component builds. If WINDOWSSDKDIR is not set
+ # use the default SDK path. This will be the case when
+ # DEPOT_TOOLS_WIN_TOOLCHAIN=0 and vcvarsall.bat has not been run.
+ win_sdk_dir = os.path.normpath(
+ os.environ.get('WINDOWSSDKDIR',
+ os.path.expandvars('%ProgramFiles(x86)%'
+ '\\Windows Kits\\10')))
+ ucrt_dll_dirs = os.path.join(win_sdk_dir, 'Redist', 'ucrt', 'DLLs',
+ target_cpu)
+ ucrt_files = glob.glob(os.path.join(ucrt_dll_dirs, 'api-ms-win-*.dll'))
+ assert len(ucrt_files) > 0
+ for ucrt_src_file in ucrt_files:
+ file_part = os.path.basename(ucrt_src_file)
+ ucrt_dst_file = os.path.join(target_dir, file_part)
+ _CopyRuntimeImpl(ucrt_dst_file, ucrt_src_file, False)
+ _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix),
+ os.path.join(source_dir, 'ucrtbase' + suffix))
+
+
+def FindVCToolsRoot():
+ """In VS2017 the PGO runtime dependencies are located in
+ {toolchain_root}/VC/Tools/MSVC/{x.y.z}/bin/Host{target_cpu}/{target_cpu}/, the
+ {version_number} part is likely to change in case of a minor update of the
+ toolchain so we don't hardcode this value here (except for the major number).
+
+ This returns the '{toolchain_root}/VC/Tools/MSVC/{x.y.z}/bin/' path.
+
+ This function should only be called when using VS2017.
+ """
+ assert GetVisualStudioVersion() == '2017'
+ SetEnvironmentAndGetRuntimeDllDirs()
+ assert ('GYP_MSVS_OVERRIDE_PATH' in os.environ)
+ vc_tools_msvc_root = os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'],
+ 'VC', 'Tools', 'MSVC')
+ for directory in os.listdir(vc_tools_msvc_root):
+ if not os.path.isdir(os.path.join(vc_tools_msvc_root, directory)):
+ continue
+ if re.match('14\.\d+\.\d+', directory):
+ return os.path.join(vc_tools_msvc_root, directory, 'bin')
+ raise Exception('Unable to find the VC tools directory.')
+
+
+def _CopyPGORuntime(target_dir, target_cpu):
+ """Copy the runtime dependencies required during a PGO build.
+ """
+ env_version = GetVisualStudioVersion()
+ # These dependencies will be in a different location depending on the version
+ # of the toolchain.
+ if env_version == '2017':
+ pgo_runtime_root = FindVCToolsRoot()
+ assert pgo_runtime_root
+ # There's no version of pgosweep.exe in HostX64/x86, so we use the copy
+ # from HostX86/x86.
+ pgo_x86_runtime_dir = os.path.join(pgo_runtime_root, 'HostX86', 'x86')
+ pgo_x64_runtime_dir = os.path.join(pgo_runtime_root, 'HostX64', 'x64')
+ else:
+ raise Exception('Unexpected toolchain version: %s.' % env_version)
+
+ # We need to copy 2 runtime dependencies used during the profiling step:
+ # - pgort140.dll: runtime library required to run the instrumented image.
+ # - pgosweep.exe: executable used to collect the profiling data
+ pgo_runtimes = ['pgort140.dll', 'pgosweep.exe']
+ for runtime in pgo_runtimes:
+ if target_cpu == 'x86':
+ source = os.path.join(pgo_x86_runtime_dir, runtime)
+ elif target_cpu == 'x64':
+ source = os.path.join(pgo_x64_runtime_dir, runtime)
+ else:
+ raise NotImplementedError("Unexpected target_cpu value: " + target_cpu)
+ if not os.path.exists(source):
+ raise Exception('Unable to find %s.' % source)
+ _CopyRuntimeImpl(os.path.join(target_dir, runtime), source)
+
+
+def _CopyRuntime(target_dir, source_dir, target_cpu, debug):
+ """Copy the VS runtime DLLs, only if the target doesn't exist, but the target
+ directory does exist. Handles VS 2015 and VS 2017."""
+ suffix = "d.dll" if debug else ".dll"
+ # VS 2017 uses the same CRT DLLs as VS 2015.
+ _CopyUCRTRuntime(target_dir, source_dir, target_cpu, '%s140' + suffix,
+ suffix)
+
+
+def CopyDlls(target_dir, configuration, target_cpu):
+ """Copy the VS runtime DLLs into the requested directory as needed.
+
+ configuration is one of 'Debug' or 'Release'.
+ target_cpu is one of 'x86' or 'x64'.
+
+ The debug configuration gets both the debug and release DLLs; the
+ release config only the latter.
+ """
+ vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
+ if not vs_runtime_dll_dirs:
+ return
+
+ x64_runtime, x86_runtime = vs_runtime_dll_dirs
+ runtime_dir = x64_runtime if target_cpu == 'x64' else x86_runtime
+ _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False)
+ if configuration == 'Debug':
+ _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True)
+ else:
+ _CopyPGORuntime(target_dir, target_cpu)
+
+ _CopyDebugger(target_dir, target_cpu)
+
+
+def _CopyDebugger(target_dir, target_cpu):
+ """Copy dbghelp.dll and dbgcore.dll into the requested directory as needed.
+
+ target_cpu is one of 'x86' or 'x64'.
+
+ dbghelp.dll is used when Chrome needs to symbolize stacks. Copying this file
+ from the SDK directory avoids using the system copy of dbghelp.dll which then
+ ensures compatibility with recent debug information formats, such as VS
+ 2017 /debug:fastlink PDBs.
+
+ dbgcore.dll is needed when using some functions from dbghelp.dll (like
+ MinidumpWriteDump).
+ """
+ win_sdk_dir = SetEnvironmentAndGetSDKDir()
+ if not win_sdk_dir:
+ return
+
+ # List of debug files that should be copied, the first element of the tuple is
+ # the name of the file and the second indicates if it's optional.
+ debug_files = [('dbghelp.dll', False), ('dbgcore.dll', True)]
+ for debug_file, is_optional in debug_files:
+ full_path = os.path.join(win_sdk_dir, 'Debuggers', target_cpu, debug_file)
+ if not os.path.exists(full_path):
+ if is_optional:
+ continue
+ else:
+ # TODO(crbug.com/773476): remove version requirement.
+ raise Exception('%s not found in "%s"\r\nYou must install the '
+ '"Debugging Tools for Windows" feature from the Windows'
+ ' 10 SDK. You must use v10.0.17134.0. of the SDK'
+ % (debug_file, full_path))
+ target_path = os.path.join(target_dir, debug_file)
+ _CopyRuntimeImpl(target_path, full_path)
+
+
+def _GetDesiredVsToolchainHashes():
+ """Load a list of SHA1s corresponding to the toolchains that we want installed
+ to build with."""
+ env_version = GetVisualStudioVersion()
+ if env_version == '2017':
+ # VS 2017 Update 7.1 (15.7.1) with 10.0.17134.12 SDK, rebuilt with
+ # dbghelp.dll fix.
+ toolchain_hash = '3bc0ec615cf20ee342f3bc29bc991b5ad66d8d2c'
+ # Third parties that do not have access to the canonical toolchain can map
+ # canonical toolchain version to their own toolchain versions.
+ toolchain_hash_mapping_key = 'GYP_MSVS_HASH_%s' % toolchain_hash
+ return [os.environ.get(toolchain_hash_mapping_key, toolchain_hash)]
+ raise Exception('Unsupported VS version %s' % env_version)
+
+
+def ShouldUpdateToolchain():
+ """Check if the toolchain should be upgraded."""
+ if not os.path.exists(json_data_file):
+ return True
+ with open(json_data_file, 'r') as tempf:
+ toolchain_data = json.load(tempf)
+ version = toolchain_data['version']
+ env_version = GetVisualStudioVersion()
+ # If there's a mismatch between the version set in the environment and the one
+ # in the json file then the toolchain should be updated.
+ return version != env_version
+
+
+def Update(force=False):
+ """Requests an update of the toolchain to the specific hashes we have at
+ this revision. The update outputs a .json of the various configuration
+ information required to pass to gyp which we use in |GetToolchainDir()|.
+ """
+ if force != False and force != '--force':
+ print >>sys.stderr, 'Unknown parameter "%s"' % force
+ return 1
+ if force == '--force' or os.path.exists(json_data_file):
+ force = True
+
+ depot_tools_win_toolchain = \
+ bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
+ if ((sys.platform in ('win32', 'cygwin') or force) and
+ depot_tools_win_toolchain):
+ import find_depot_tools
+ depot_tools_path = find_depot_tools.add_depot_tools_to_path()
+
+ # On Linux, the file system is usually case-sensitive while the Windows
+ # SDK only works on case-insensitive file systems. If it doesn't already
+ # exist, set up a ciopfs fuse mount to put the SDK in a case-insensitive
+ # part of the file system.
+ toolchain_dir = os.path.join(depot_tools_path, 'win_toolchain', 'vs_files')
+ # For testing this block, unmount existing mounts with
+ # fusermount -u third_party/depot_tools/win_toolchain/vs_files
+ if sys.platform.startswith('linux') and not os.path.ismount(toolchain_dir):
+ import distutils.spawn
+ ciopfs = distutils.spawn.find_executable('ciopfs')
+ if not ciopfs:
+ # ciopfs not found in PATH; try the one downloaded from the DEPS hook.
+ ciopfs = os.path.join(script_dir, 'ciopfs')
+ if not os.path.isdir(toolchain_dir):
+ os.mkdir(toolchain_dir)
+ if not os.path.isdir(toolchain_dir + '.ciopfs'):
+ os.mkdir(toolchain_dir + '.ciopfs')
+ # Without use_ino, clang's #pragma once and Wnonportable-include-path
+ # both don't work right, see https://llvm.org/PR34931
+ # use_ino doesn't slow down builds, so it seems there's no drawback to
+ # just using it always.
+ subprocess.check_call([
+ ciopfs, '-o', 'use_ino', toolchain_dir + '.ciopfs', toolchain_dir])
+
+ # Necessary so that get_toolchain_if_necessary.py will put the VS toolkit
+ # in the correct directory.
+ os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
+ get_toolchain_args = [
+ sys.executable,
+ os.path.join(depot_tools_path,
+ 'win_toolchain',
+ 'get_toolchain_if_necessary.py'),
+ '--output-json', json_data_file,
+ ] + _GetDesiredVsToolchainHashes()
+ if force:
+ get_toolchain_args.append('--force')
+ subprocess.check_call(get_toolchain_args)
+
+ return 0
+
+
+def NormalizePath(path):
+ while path.endswith("\\"):
+ path = path[:-1]
+ return path
+
+
+def SetEnvironmentAndGetSDKDir():
+ """Gets location information about the current sdk (must have been
+ previously updated by 'update'). This is used for the GN build."""
+ SetEnvironmentAndGetRuntimeDllDirs()
+
+ # If WINDOWSSDKDIR is not set, search the default SDK path and set it.
+ if not 'WINDOWSSDKDIR' in os.environ:
+ default_sdk_path = os.path.expandvars('%ProgramFiles(x86)%'
+ '\\Windows Kits\\10')
+ if os.path.isdir(default_sdk_path):
+ os.environ['WINDOWSSDKDIR'] = default_sdk_path
+
+ return NormalizePath(os.environ['WINDOWSSDKDIR'])
+
+
+def GetToolchainDir():
+ """Gets location information about the current toolchain (must have been
+ previously updated by 'update'). This is used for the GN build."""
+ runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
+ win_sdk_dir = SetEnvironmentAndGetSDKDir()
+
+ print '''vs_path = %s
+sdk_path = %s
+vs_version = %s
+wdk_dir = %s
+runtime_dirs = %s
+''' % (
+ ToGNString(NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH'])),
+ ToGNString(win_sdk_dir),
+ ToGNString(GetVisualStudioVersion()),
+ ToGNString(NormalizePath(os.environ.get('WDK_DIR', ''))),
+ ToGNString(os.path.pathsep.join(runtime_dll_dirs or ['None'])))
+
+
+def main():
+ commands = {
+ 'update': Update,
+ 'get_toolchain_dir': GetToolchainDir,
+ 'copy_dlls': CopyDlls,
+ }
+ if len(sys.argv) < 2 or sys.argv[1] not in commands:
+ print >>sys.stderr, 'Expected one of: %s' % ', '.join(commands)
+ return 1
+ return commands[sys.argv[1]](*sys.argv[2:])
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/write_build_date_header.py b/build/write_build_date_header.py
new file mode 100755
index 0000000000..6fe514fd78
--- /dev/null
+++ b/build/write_build_date_header.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+# Copyright (c) 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Writes a file that contains a define that approximates the build date.
+
+build_type impacts the timestamp generated:
+- default: the build date is set to the most recent first Sunday of a month at
+ 5:00am. The reason is that it is a time where invalidating the build cache
+ shouldn't have major reprecussions (due to lower load).
+- official: the build date is set to the current date at 5:00am, or the day
+ before if the current time is before 5:00am.
+Either way, it is guaranteed to be in the past and always in UTC.
+
+It is also possible to explicitly set a build date to be used.
+"""
+
+import argparse
+import calendar
+import datetime
+import doctest
+import os
+import sys
+
+
+def GetFirstSundayOfMonth(year, month):
+ """Returns the first sunday of the given month of the given year.
+
+ >>> GetFirstSundayOfMonth(2016, 2)
+ 7
+ >>> GetFirstSundayOfMonth(2016, 3)
+ 6
+ >>> GetFirstSundayOfMonth(2000, 1)
+ 2
+ """
+ weeks = calendar.Calendar().monthdays2calendar(year, month)
+ # Return the first day in the first week that is a Sunday.
+ return [date_day[0] for date_day in weeks[0] if date_day[1] == 6][0]
+
+
+def GetBuildDate(build_type, utc_now):
+ """Gets the approximate build date given the specific build type.
+
+ >>> GetBuildDate('default', datetime.datetime(2016, 2, 6, 1, 2, 3))
+ 'Jan 03 2016 01:02:03'
+ >>> GetBuildDate('default', datetime.datetime(2016, 2, 7, 5))
+ 'Feb 07 2016 05:00:00'
+ >>> GetBuildDate('default', datetime.datetime(2016, 2, 8, 5))
+ 'Feb 07 2016 05:00:00'
+ """
+ day = utc_now.day
+ month = utc_now.month
+ year = utc_now.year
+ if build_type != 'official':
+ first_sunday = GetFirstSundayOfMonth(year, month)
+ # If our build is after the first Sunday, we've already refreshed our build
+ # cache on a quiet day, so just use that day.
+ # Otherwise, take the first Sunday of the previous month.
+ if day >= first_sunday:
+ day = first_sunday
+ else:
+ month -= 1
+ if month == 0:
+ month = 12
+ year -= 1
+ day = GetFirstSundayOfMonth(year, month)
+ now = datetime.datetime(
+ year, month, day, utc_now.hour, utc_now.minute, utc_now.second)
+ return '{:%b %d %Y %H:%M:%S}'.format(now)
+
+
+def main():
+ if doctest.testmod()[0]:
+ return 1
+ argument_parser = argparse.ArgumentParser(
+ description=sys.modules[__name__].__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ argument_parser.add_argument('output_file', help='The file to write to')
+ argument_parser.add_argument(
+ 'build_type', help='The type of build', choices=('official', 'default'))
+ argument_parser.add_argument(
+ 'build_date_override', nargs='?',
+ help='Optional override for the build date. Format must be '
+ '\'Mmm DD YYYY HH:MM:SS\'')
+ args = argument_parser.parse_args()
+
+ if args.build_date_override:
+ # Format is expected to be "Mmm DD YYYY HH:MM:SS".
+ build_date = args.build_date_override
+ else:
+ now = datetime.datetime.utcnow()
+ if now.hour < 5:
+ # The time is locked at 5:00 am in UTC to cause the build cache
+ # invalidation to not happen exactly at midnight. Use the same calculation
+ # as the day before.
+ # See //base/build_time.cc.
+ now = now - datetime.timedelta(days=1)
+ now = datetime.datetime(now.year, now.month, now.day, 5, 0, 0)
+ build_date = GetBuildDate(args.build_type, now)
+
+ output = ('// Generated by //build/write_build_date_header.py\n'
+ '#ifndef BUILD_DATE\n'
+ '#define BUILD_DATE "{}"\n'
+ '#endif // BUILD_DATE\n'.format(build_date))
+
+ current_contents = ''
+ if os.path.isfile(args.output_file):
+ with open(args.output_file, 'r') as current_file:
+ current_contents = current_file.read()
+
+ if current_contents != output:
+ with open(args.output_file, 'w') as output_file:
+ output_file.write(output)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/write_buildflag_header.py b/build/write_buildflag_header.py
new file mode 100755
index 0000000000..d46cfc89a9
--- /dev/null
+++ b/build/write_buildflag_header.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This writes headers for build flags. See buildflag_header.gni for usage of
+# this system as a whole.
+#
+# The parameters are passed in a response file so we don't have to worry
+# about command line lengths. The name of the response file is passed on the
+# command line.
+#
+# The format of the response file is:
+# [--flags <list of one or more flag values>]
+
+import optparse
+import os
+import shlex
+import sys
+
+
+class Options:
+ def __init__(self, output, rulename, header_guard, flags):
+ self.output = output
+ self.rulename = rulename
+ self.header_guard = header_guard
+ self.flags = flags
+
+
+def GetOptions():
+ parser = optparse.OptionParser()
+ parser.add_option('--output', help="Output header name inside --gen-dir.")
+ parser.add_option('--rulename',
+ help="Helpful name of build rule for including in the " +
+ "comment at the top of the file.")
+ parser.add_option('--gen-dir',
+ help="Path to root of generated file directory tree.")
+ parser.add_option('--definitions',
+ help="Name of the response file containing the flags.")
+ cmdline_options, cmdline_flags = parser.parse_args()
+
+ # Compute header guard by replacing some chars with _ and upper-casing.
+ header_guard = cmdline_options.output.upper()
+ header_guard = \
+ header_guard.replace('/', '_').replace('\\', '_').replace('.', '_')
+ header_guard += '_'
+
+ # The actual output file is inside the gen dir.
+ output = os.path.join(cmdline_options.gen_dir, cmdline_options.output)
+
+ # Definition file in GYP is newline separated, in GN they are shell formatted.
+ # shlex can parse both of these.
+ with open(cmdline_options.definitions, 'r') as def_file:
+ defs = shlex.split(def_file.read())
+ flags_index = defs.index('--flags')
+
+ # Everything after --flags are flags. true/false are remapped to 1/0,
+ # everything else is passed through.
+ flags = []
+ for flag in defs[flags_index + 1 :]:
+ equals_index = flag.index('=')
+ key = flag[:equals_index]
+ value = flag[equals_index + 1:]
+
+ # Canonicalize and validate the value.
+ if value == 'true':
+ value = '1'
+ elif value == 'false':
+ value = '0'
+ flags.append((key, str(value)))
+
+ return Options(output=output,
+ rulename=cmdline_options.rulename,
+ header_guard=header_guard,
+ flags=flags)
+
+
+def WriteHeader(options):
+ with open(options.output, 'w') as output_file:
+ output_file.write("// Generated by build/write_buildflag_header.py\n")
+ if options.rulename:
+ output_file.write('// From "' + options.rulename + '"\n')
+
+ output_file.write('\n#ifndef %s\n' % options.header_guard)
+ output_file.write('#define %s\n\n' % options.header_guard)
+ output_file.write('#include "build/buildflag.h"\n\n')
+
+ for pair in options.flags:
+ output_file.write('#define BUILDFLAG_INTERNAL_%s() (%s)\n' % pair)
+
+ output_file.write('\n#endif // %s\n' % options.header_guard)
+
+
+options = GetOptions()
+WriteHeader(options)
diff --git a/components/json_schema/README b/components/json_schema/README
new file mode 100644
index 0000000000..c7453db06d
--- /dev/null
+++ b/components/json_schema/README
@@ -0,0 +1,6 @@
+The //components/json_schema component provides:
+
+a) JSON schema constants, which can be used to inspect schema objects.
+
+b) The JSONSchemaValidator class, which can be used to parse and validate JSON
+schemas, and to validate JSON objects against the parsed schema.
diff --git a/components/json_schema/json_schema_validator_unittest.cc b/components/json_schema/json_schema_validator_unittest.cc
new file mode 100644
index 0000000000..b1559536d5
--- /dev/null
+++ b/components/json_schema/json_schema_validator_unittest.cc
@@ -0,0 +1,177 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/values.h"
+#include "components/json_schema/json_schema_validator.h"
+#include "components/json_schema/json_schema_validator_unittest_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class JSONSchemaValidatorCPPTest : public JSONSchemaValidatorTestBase {
+ public:
+ JSONSchemaValidatorCPPTest() {}
+
+ protected:
+ void ExpectValid(const std::string& test_source,
+ base::Value* instance,
+ base::DictionaryValue* schema,
+ base::ListValue* types) override {
+ JSONSchemaValidator validator(schema, types);
+ if (validator.Validate(instance))
+ return;
+
+ for (size_t i = 0; i < validator.errors().size(); ++i) {
+ ADD_FAILURE() << test_source << ": "
+ << validator.errors()[i].path << ": "
+ << validator.errors()[i].message;
+ }
+ }
+
+ void ExpectNotValid(const std::string& test_source,
+ base::Value* instance,
+ base::DictionaryValue* schema,
+ base::ListValue* types,
+ const std::string& expected_error_path,
+ const std::string& expected_error_message) override {
+ JSONSchemaValidator validator(schema, types);
+ if (validator.Validate(instance)) {
+ ADD_FAILURE() << test_source;
+ return;
+ }
+
+ ASSERT_EQ(1u, validator.errors().size()) << test_source;
+ EXPECT_EQ(expected_error_path, validator.errors()[0].path) << test_source;
+ EXPECT_EQ(expected_error_message, validator.errors()[0].message)
+ << test_source;
+ }
+};
+
+TEST_F(JSONSchemaValidatorCPPTest, Test) {
+ RunTests();
+}
+
+TEST(JSONSchemaValidator, IsValidSchema) {
+ std::string error;
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("\0", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("string", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("\"string\"", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("[]", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("{}", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
+ "{ \"type\": 123 }", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
+ "{ \"type\": \"invalid\" }", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": []" // Invalid properties type.
+ "}", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": \"string\","
+ " \"maxLength\": -1" // Must be >= 0.
+ "}", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": \"string\","
+ " \"enum\": [ {} ]" // "enum" dict values must contain "name".
+ "}", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": \"string\","
+ " \"enum\": [ { \"name\": {} } ]" // "enum" name must be a simple value.
+ "}", &error));
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": \"array\","
+ " \"items\": [ 123 ]," // "items" must contain a schema or schemas.
+ "}", &error));
+ EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
+ "{ \"type\": \"object\" }", &error)) << error;
+ EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
+ "{ \"type\": [\"object\", \"array\"] }", &error)) << error;
+ EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": [\"object\", \"array\"],"
+ " \"properties\": {"
+ " \"string-property\": {"
+ " \"type\": \"string\","
+ " \"minLength\": 1,"
+ " \"maxLength\": 100,"
+ " \"title\": \"The String Policy\","
+ " \"description\": \"This policy controls the String widget.\""
+ " },"
+ " \"integer-property\": {"
+ " \"type\": \"number\","
+ " \"minimum\": 1000.0,"
+ " \"maximum\": 9999.0"
+ " },"
+ " \"enum-property\": {"
+ " \"type\": \"integer\","
+ " \"enum\": [0, 1, {\"name\": 10}, 100]"
+ " },"
+ " \"items-property\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"string\""
+ " }"
+ " },"
+ " \"items-list-property\": {"
+ " \"type\": \"array\","
+ " \"items\": ["
+ " { \"type\": \"string\" },"
+ " { \"type\": \"integer\" }"
+ " ]"
+ " }"
+ " },"
+ " \"additionalProperties\": {"
+ " \"type\": \"any\""
+ " }"
+ "}", &error)) << error;
+ EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": \"object\","
+ " \"patternProperties\": {"
+ " \".\": { \"type\": \"any\" },"
+ " \"foo\": { \"type\": \"any\" },"
+ " \"^foo$\": { \"type\": \"any\" },"
+ " \"foo+\": { \"type\": \"any\" },"
+ " \"foo?\": { \"type\": \"any\" },"
+ " \"fo{2,4}\": { \"type\": \"any\" },"
+ " \"(left)|(right)\": { \"type\": \"any\" }"
+ " }"
+ "}", &error)) << error;
+ EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": \"object\","
+ " \"unknown attribute\": \"that should just be ignored\""
+ "}",
+ JSONSchemaValidator::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES,
+ &error)) << error;
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
+ "{"
+ " \"type\": \"object\","
+ " \"unknown attribute\": \"that will cause a failure\""
+ "}", 0, &error)) << error;
+
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(R"(
+ {
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": 123
+ })",
+ 0, &error))
+ << error;
+
+ EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(R"(
+ {
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": [ 123 ]
+ })",
+ 0, &error))
+ << error;
+}
diff --git a/components/json_schema/json_schema_validator_unittest_base.cc b/components/json_schema/json_schema_validator_unittest_base.cc
new file mode 100644
index 0000000000..ce220dc479
--- /dev/null
+++ b/components/json_schema/json_schema_validator_unittest_base.cc
@@ -0,0 +1,710 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/json_schema/json_schema_validator_unittest_base.h"
+
+#include <cfloat>
+#include <cmath>
+#include <limits>
+#include <memory>
+
+#include "base/base_paths.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "components/json_schema/json_schema_constants.h"
+#include "components/json_schema/json_schema_validator.h"
+
+namespace schema = json_schema_constants;
+
+namespace {
+
+#define TEST_SOURCE base::StringPrintf("%s:%i", __FILE__, __LINE__)
+
+base::Value* LoadValue(const std::string& filename) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("json_schema")
+ .AppendASCII(filename);
+ EXPECT_TRUE(base::PathExists(path));
+
+ std::string error_message;
+ JSONFileValueDeserializer deserializer(path);
+ base::Value* result =
+ deserializer.Deserialize(nullptr, &error_message).release();
+ if (!result)
+ ADD_FAILURE() << "Could not parse JSON: " << error_message;
+ return result;
+}
+
+base::Value* LoadValue(const std::string& filename, base::Value::Type type) {
+ std::unique_ptr<base::Value> result(LoadValue(filename));
+ if (!result)
+ return nullptr;
+ if (result->type() != type) {
+ ADD_FAILURE() << "Expected type " << type << ", got: " << result->type();
+ return nullptr;
+ }
+ return result.release();
+}
+
+base::ListValue* LoadList(const std::string& filename) {
+ return static_cast<base::ListValue*>(
+ LoadValue(filename, base::Value::Type::LIST));
+}
+
+base::DictionaryValue* LoadDictionary(const std::string& filename) {
+ return static_cast<base::DictionaryValue*>(
+ LoadValue(filename, base::Value::Type::DICTIONARY));
+}
+
+} // namespace
+
+
+JSONSchemaValidatorTestBase::JSONSchemaValidatorTestBase() {
+}
+
+void JSONSchemaValidatorTestBase::RunTests() {
+ TestComplex();
+ TestStringPattern();
+ TestEnum();
+ TestChoices();
+ TestExtends();
+ TestObject();
+ TestTypeReference();
+ TestArrayTuple();
+ TestArrayNonTuple();
+ TestString();
+ TestNumber();
+ TestTypeClassifier();
+ TestTypes();
+}
+
+void JSONSchemaValidatorTestBase::TestComplex() {
+ std::unique_ptr<base::DictionaryValue> schema(
+ LoadDictionary("complex_schema.json"));
+ std::unique_ptr<base::ListValue> instance(LoadList("complex_instance.json"));
+
+ ASSERT_TRUE(schema.get());
+ ASSERT_TRUE(instance.get());
+
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->Remove(instance->GetSize() - 1, nullptr);
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->Append(std::make_unique<base::DictionaryValue>());
+ ExpectNotValid(
+ TEST_SOURCE, instance.get(), schema.get(), nullptr, "1",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kNumber, schema::kObject));
+ instance->Remove(instance->GetSize() - 1, nullptr);
+
+ base::DictionaryValue* item = nullptr;
+ ASSERT_TRUE(instance->GetDictionary(0, &item));
+ item->SetString("url", "xxxxxxxxxxx");
+
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "0.url",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kStringMaxLength, "10"));
+}
+
+void JSONSchemaValidatorTestBase::TestStringPattern() {
+ std::unique_ptr<base::DictionaryValue> schema(new base::DictionaryValue());
+ schema->SetString(schema::kType, schema::kString);
+ schema->SetString(schema::kPattern, "foo+");
+
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("foo")).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("foooooo")).get(),
+ schema.get(), nullptr);
+ ExpectNotValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("bar")).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kStringPattern, "foo+"));
+}
+
+void JSONSchemaValidatorTestBase::TestEnum() {
+ std::unique_ptr<base::DictionaryValue> schema(
+ LoadDictionary("enum_schema.json"));
+
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("foo")).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(42)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(false)).get(),
+ schema.get(), nullptr);
+
+ ExpectNotValid(
+ TEST_SOURCE, std::unique_ptr<base::Value>(new base::Value("42")).get(),
+ schema.get(), nullptr, std::string(), JSONSchemaValidator::kInvalidEnum);
+ ExpectNotValid(TEST_SOURCE, std::make_unique<base::Value>().get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::kInvalidEnum);
+}
+
+void JSONSchemaValidatorTestBase::TestChoices() {
+ std::unique_ptr<base::DictionaryValue> schema(
+ LoadDictionary("choices_schema.json"));
+
+ ExpectValid(TEST_SOURCE, std::make_unique<base::Value>().get(), schema.get(),
+ nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(42)).get(),
+ schema.get(), nullptr);
+
+ std::unique_ptr<base::DictionaryValue> instance(new base::DictionaryValue());
+ instance->SetString("foo", "bar");
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ ExpectNotValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("foo")).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::kInvalidChoice);
+ ExpectNotValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::ListValue()).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::kInvalidChoice);
+
+ instance->SetInteger("foo", 42);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr,
+ std::string(), JSONSchemaValidator::kInvalidChoice);
+}
+
+void JSONSchemaValidatorTestBase::TestExtends() {
+ // TODO(aa): JS only
+}
+
+void JSONSchemaValidatorTestBase::TestObject() {
+ std::unique_ptr<base::DictionaryValue> schema(new base::DictionaryValue());
+ schema->SetString(schema::kType, schema::kObject);
+ schema->SetString("properties.foo.type", schema::kString);
+ schema->SetString("properties.bar.type", schema::kInteger);
+
+ std::unique_ptr<base::DictionaryValue> instance(new base::DictionaryValue());
+ instance->SetString("foo", "foo");
+ instance->SetInteger("bar", 42);
+
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ instance->SetBoolean("extra", true);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "extra",
+ JSONSchemaValidator::kUnexpectedProperty);
+ instance->Remove("extra", nullptr);
+
+ instance->Remove("bar", nullptr);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "bar",
+ JSONSchemaValidator::kObjectPropertyIsRequired);
+
+ instance->SetString("bar", "42");
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "bar",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kInteger,
+ schema::kString));
+ instance->SetInteger("bar", 42);
+
+ // Test "patternProperties".
+ instance->SetInteger("extra", 42);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "extra",
+ JSONSchemaValidator::kUnexpectedProperty);
+ schema->SetString("patternProperties.extra+.type",
+ schema::kInteger);
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->Remove("extra", nullptr);
+ instance->SetInteger("extraaa", 42);
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->Remove("extraaa", nullptr);
+ instance->SetInteger("extr", 42);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "extr",
+ JSONSchemaValidator::kUnexpectedProperty);
+ instance->Remove("extr", nullptr);
+ schema->Remove(schema::kPatternProperties, nullptr);
+
+ // Test "patternProperties" and "properties" schemas are both checked if
+ // applicable.
+ schema->SetString("patternProperties.fo+.type", schema::kInteger);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "foo",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kInteger,
+ schema::kString));
+ instance->SetInteger("foo", 123);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "foo",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kString,
+ schema::kInteger));
+ instance->SetString("foo", "foo");
+ schema->Remove(schema::kPatternProperties, nullptr);
+
+ // Test additional properties.
+ base::DictionaryValue* additional_properties = schema->SetDictionary(
+ schema::kAdditionalProperties, std::make_unique<base::DictionaryValue>());
+ additional_properties->SetString(schema::kType, schema::kAny);
+
+ instance->SetBoolean("extra", true);
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ instance->SetString("extra", "foo");
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ additional_properties->SetString(schema::kType, schema::kBoolean);
+ instance->SetBoolean("extra", true);
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ instance->SetString("extra", "foo");
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "extra",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kBoolean,
+ schema::kString));
+ instance->Remove("extra", nullptr);
+
+ base::DictionaryValue* properties = nullptr;
+ base::DictionaryValue* bar_property = nullptr;
+ ASSERT_TRUE(schema->GetDictionary(schema::kProperties, &properties));
+ ASSERT_TRUE(properties->GetDictionary("bar", &bar_property));
+
+ bar_property->SetBoolean(schema::kOptional, true);
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->Remove("bar", nullptr);
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->Set("bar", std::make_unique<base::Value>());
+ ExpectNotValid(
+ TEST_SOURCE, instance.get(), schema.get(), nullptr, "bar",
+ JSONSchemaValidator::FormatErrorMessage(JSONSchemaValidator::kInvalidType,
+ schema::kInteger, schema::kNull));
+ instance->SetString("bar", "42");
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "bar",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kInteger,
+ schema::kString));
+
+ // Verify that JSON parser handles dot in "patternProperties" well.
+ schema.reset(LoadDictionary("pattern_properties_dot.json"));
+ ASSERT_TRUE(schema->GetDictionary(schema::kPatternProperties, &properties));
+ ASSERT_TRUE(properties->HasKey("^.$"));
+
+ instance.reset(new base::DictionaryValue());
+ instance->SetString("a", "whatever");
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->SetString("foo", "bar");
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "foo",
+ JSONSchemaValidator::kUnexpectedProperty);
+}
+
+void JSONSchemaValidatorTestBase::TestTypeReference() {
+ std::unique_ptr<base::ListValue> types(LoadList("reference_types.json"));
+ ASSERT_TRUE(types.get());
+
+ std::unique_ptr<base::DictionaryValue> schema(new base::DictionaryValue());
+ schema->SetString(schema::kType, schema::kObject);
+ schema->SetString("properties.foo.type", schema::kString);
+ schema->SetString("properties.bar.$ref", "Max10Int");
+ schema->SetString("properties.baz.$ref", "MinLengthString");
+
+ std::unique_ptr<base::DictionaryValue> schema_inline(
+ new base::DictionaryValue());
+ schema_inline->SetString(schema::kType, schema::kObject);
+ schema_inline->SetString("properties.foo.type", schema::kString);
+ schema_inline->SetString("properties.bar.id", "NegativeInt");
+ schema_inline->SetString("properties.bar.type", schema::kInteger);
+ schema_inline->SetInteger("properties.bar.maximum", 0);
+ schema_inline->SetString("properties.baz.$ref", "NegativeInt");
+
+ std::unique_ptr<base::DictionaryValue> instance(new base::DictionaryValue());
+ instance->SetString("foo", "foo");
+ instance->SetInteger("bar", 4);
+ instance->SetString("baz", "ab");
+
+ std::unique_ptr<base::DictionaryValue> instance_inline(
+ new base::DictionaryValue());
+ instance_inline->SetString("foo", "foo");
+ instance_inline->SetInteger("bar", -4);
+ instance_inline->SetInteger("baz", -2);
+
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), types.get());
+ ExpectValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), nullptr);
+
+ // Validation failure, but successful schema reference.
+ instance->SetString("baz", "a");
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), types.get(),
+ "baz", JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kStringMinLength, "2"));
+
+ instance_inline->SetInteger("bar", 20);
+ ExpectNotValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(),
+ nullptr, "bar",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kNumberMaximum, "0"));
+
+ // Remove MinLengthString type.
+ types->Remove(types->GetSize() - 1, nullptr);
+ instance->SetString("baz", "ab");
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), types.get(),
+ "bar", JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kUnknownTypeReference,
+ "Max10Int"));
+
+ // Remove internal type "NegativeInt".
+ schema_inline->Remove("properties.bar", nullptr);
+ instance_inline->Remove("bar", nullptr);
+ ExpectNotValid(
+ TEST_SOURCE, instance_inline.get(), schema_inline.get(), nullptr, "baz",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kUnknownTypeReference, "NegativeInt"));
+}
+
+void JSONSchemaValidatorTestBase::TestArrayTuple() {
+ std::unique_ptr<base::DictionaryValue> schema(
+ LoadDictionary("array_tuple_schema.json"));
+ ASSERT_TRUE(schema.get());
+
+ std::unique_ptr<base::ListValue> instance(new base::ListValue());
+ instance->AppendString("42");
+ instance->AppendInteger(42);
+
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ instance->AppendString("anything");
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr,
+ std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kArrayMaxItems, "2"));
+
+ instance->Remove(1, nullptr);
+ instance->Remove(1, nullptr);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "1",
+ JSONSchemaValidator::kArrayItemRequired);
+
+ instance->Set(0, std::make_unique<base::Value>(42));
+ instance->AppendInteger(42);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "0",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kString,
+ schema::kInteger));
+
+ base::DictionaryValue* additional_properties = schema->SetDictionary(
+ schema::kAdditionalProperties, std::make_unique<base::DictionaryValue>());
+ additional_properties->SetString(schema::kType, schema::kAny);
+ instance->Set(0, std::make_unique<base::Value>("42"));
+ instance->AppendString("anything");
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->Set(2, std::make_unique<base::ListValue>());
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ additional_properties->SetString(schema::kType, schema::kBoolean);
+ ExpectNotValid(
+ TEST_SOURCE, instance.get(), schema.get(), nullptr, "2",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kBoolean, schema::kArray));
+ instance->Set(2, std::make_unique<base::Value>(false));
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ base::ListValue* items_schema = nullptr;
+ base::DictionaryValue* item0_schema = nullptr;
+ ASSERT_TRUE(schema->GetList(schema::kItems, &items_schema));
+ ASSERT_TRUE(items_schema->GetDictionary(0, &item0_schema));
+ item0_schema->SetBoolean(schema::kOptional, true);
+ instance->Remove(2, nullptr);
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ // TODO(aa): I think this is inconsistent with the handling of NULL+optional
+ // for objects.
+ instance->Set(0, std::make_unique<base::Value>());
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->Set(0, std::make_unique<base::Value>(42));
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "0",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kString,
+ schema::kInteger));
+}
+
+void JSONSchemaValidatorTestBase::TestArrayNonTuple() {
+ std::unique_ptr<base::DictionaryValue> schema(new base::DictionaryValue());
+ schema->SetString(schema::kType, schema::kArray);
+ schema->SetString("items.type", schema::kString);
+ schema->SetInteger(schema::kMinItems, 2);
+ schema->SetInteger(schema::kMaxItems, 3);
+
+ std::unique_ptr<base::ListValue> instance(new base::ListValue());
+ instance->AppendString("x");
+ instance->AppendString("x");
+
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+ instance->AppendString("x");
+ ExpectValid(TEST_SOURCE, instance.get(), schema.get(), nullptr);
+
+ instance->AppendString("x");
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr,
+ std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kArrayMaxItems, "3"));
+ instance->Remove(1, nullptr);
+ instance->Remove(1, nullptr);
+ instance->Remove(1, nullptr);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr,
+ std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kArrayMinItems, "2"));
+
+ instance->Remove(1, nullptr);
+ instance->AppendInteger(42);
+ ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), nullptr, "1",
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kString,
+ schema::kInteger));
+}
+
+void JSONSchemaValidatorTestBase::TestString() {
+ std::unique_ptr<base::DictionaryValue> schema(new base::DictionaryValue());
+ schema->SetString(schema::kType, schema::kString);
+ schema->SetInteger(schema::kMinLength, 1);
+ schema->SetInteger(schema::kMaxLength, 10);
+
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("x")).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("xxxxxxxxxx")).get(),
+ schema.get(), nullptr);
+
+ ExpectNotValid(
+ TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(std::string())).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kStringMinLength, "1"));
+ ExpectNotValid(
+ TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("xxxxxxxxxxx")).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kStringMaxLength, "10"));
+}
+
+void JSONSchemaValidatorTestBase::TestNumber() {
+ std::unique_ptr<base::DictionaryValue> schema(new base::DictionaryValue());
+ schema->SetString(schema::kType, schema::kNumber);
+ schema->SetInteger(schema::kMinimum, 1);
+ schema->SetInteger(schema::kMaximum, 100);
+ schema->SetInteger("maxDecimal", 2);
+
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(1)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(50)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(100)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(88.88)).get(),
+ schema.get(), nullptr);
+
+ ExpectNotValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(0.5)).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kNumberMinimum, "1"));
+ ExpectNotValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(100.1)).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kNumberMaximum, "100"));
+}
+
+void JSONSchemaValidatorTestBase::TestTypeClassifier() {
+ EXPECT_EQ(std::string(schema::kBoolean),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value(true)).get()));
+ EXPECT_EQ(std::string(schema::kBoolean),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value(false)).get()));
+
+ // It doesn't matter whether the C++ type is 'integer' or 'real'. If the
+ // number is integral and within the representable range of integers in
+ // double, it's classified as 'integer'.
+ EXPECT_EQ(std::string(schema::kInteger),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value(42)).get()));
+ EXPECT_EQ(std::string(schema::kInteger),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value(0)).get()));
+ EXPECT_EQ(std::string(schema::kInteger),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value(42)).get()));
+ EXPECT_EQ(
+ std::string(schema::kInteger),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value(pow(2.0, DBL_MANT_DIG)))
+ .get()));
+ EXPECT_EQ(
+ std::string(schema::kInteger),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value(pow(-2.0, DBL_MANT_DIG)))
+ .get()));
+
+ // "number" is only used for non-integral numbers, or numbers beyond what
+ // double can accurately represent.
+ EXPECT_EQ(std::string(schema::kNumber),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value(88.8)).get()));
+ EXPECT_EQ(std::string(schema::kNumber),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(
+ new base::Value(pow(2.0, DBL_MANT_DIG) * 2))
+ .get()));
+ EXPECT_EQ(std::string(schema::kNumber),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(
+ new base::Value(pow(-2.0, DBL_MANT_DIG) * 2))
+ .get()));
+
+ EXPECT_EQ(std::string(schema::kString),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::Value("foo")).get()));
+ EXPECT_EQ(std::string(schema::kArray),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::ListValue()).get()));
+ EXPECT_EQ(
+ std::string(schema::kObject),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::unique_ptr<base::Value>(new base::DictionaryValue()).get()));
+ EXPECT_EQ(std::string(schema::kNull),
+ JSONSchemaValidator::GetJSONSchemaType(
+ std::make_unique<base::Value>().get()));
+}
+
+void JSONSchemaValidatorTestBase::TestTypes() {
+ std::unique_ptr<base::DictionaryValue> schema(new base::DictionaryValue());
+
+ // valid
+ schema->SetString(schema::kType, schema::kObject);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::DictionaryValue()).get(),
+ schema.get(), nullptr);
+
+ schema->SetString(schema::kType, schema::kArray);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::ListValue()).get(),
+ schema.get(), nullptr);
+
+ schema->SetString(schema::kType, schema::kString);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value("foobar")).get(),
+ schema.get(), nullptr);
+
+ schema->SetString(schema::kType, schema::kNumber);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(88.8)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(42)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(42)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(0)).get(),
+ schema.get(), nullptr);
+
+ schema->SetString(schema::kType, schema::kInteger);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(42)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(42)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(0)).get(),
+ schema.get(), nullptr);
+ ExpectValid(
+ TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(pow(2.0, DBL_MANT_DIG)))
+ .get(),
+ schema.get(), nullptr);
+ ExpectValid(
+ TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(pow(-2.0, DBL_MANT_DIG)))
+ .get(),
+ schema.get(), nullptr);
+
+ schema->SetString(schema::kType, schema::kBoolean);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(false)).get(),
+ schema.get(), nullptr);
+ ExpectValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(true)).get(),
+ schema.get(), nullptr);
+
+ schema->SetString(schema::kType, schema::kNull);
+ ExpectValid(TEST_SOURCE, std::make_unique<base::Value>().get(), schema.get(),
+ nullptr);
+
+ // not valid
+ schema->SetString(schema::kType, schema::kObject);
+ ExpectNotValid(
+ TEST_SOURCE, std::unique_ptr<base::Value>(new base::ListValue()).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(JSONSchemaValidator::kInvalidType,
+ schema::kObject, schema::kArray));
+
+ schema->SetString(schema::kType, schema::kObject);
+ ExpectNotValid(
+ TEST_SOURCE, std::make_unique<base::Value>().get(), schema.get(), nullptr,
+ std::string(),
+ JSONSchemaValidator::FormatErrorMessage(JSONSchemaValidator::kInvalidType,
+ schema::kObject, schema::kNull));
+
+ schema->SetString(schema::kType, schema::kArray);
+ ExpectNotValid(
+ TEST_SOURCE, std::unique_ptr<base::Value>(new base::Value(42)).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kArray, schema::kInteger));
+
+ schema->SetString(schema::kType, schema::kString);
+ ExpectNotValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(42)).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kString,
+ schema::kInteger));
+
+ schema->SetString(schema::kType, schema::kNumber);
+ ExpectNotValid(
+ TEST_SOURCE, std::unique_ptr<base::Value>(new base::Value("42")).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kNumber, schema::kString));
+
+ schema->SetString(schema::kType, schema::kInteger);
+ ExpectNotValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(88.8)).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::kInvalidTypeIntegerNumber);
+
+ schema->SetString(schema::kType, schema::kBoolean);
+ ExpectNotValid(TEST_SOURCE,
+ std::unique_ptr<base::Value>(new base::Value(1)).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(
+ JSONSchemaValidator::kInvalidType, schema::kBoolean,
+ schema::kInteger));
+
+ schema->SetString(schema::kType, schema::kNull);
+ ExpectNotValid(
+ TEST_SOURCE, std::unique_ptr<base::Value>(new base::Value(false)).get(),
+ schema.get(), nullptr, std::string(),
+ JSONSchemaValidator::FormatErrorMessage(JSONSchemaValidator::kInvalidType,
+ schema::kNull, schema::kBoolean));
+}
diff --git a/components/json_schema/json_schema_validator_unittest_base.h b/components/json_schema/json_schema_validator_unittest_base.h
new file mode 100644
index 0000000000..ff661acb32
--- /dev/null
+++ b/components/json_schema/json_schema_validator_unittest_base.h
@@ -0,0 +1,56 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_
+#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+}
+
+// Base class for unit tests for JSONSchemaValidator. There is currently only
+// one implementation, JSONSchemaValidatorCPPTest.
+//
+// TODO(aa): Refactor extensions/test/data/json_schema_test.js into
+// JSONSchemaValidatorJSTest that inherits from this.
+class JSONSchemaValidatorTestBase : public testing::Test {
+ public:
+ JSONSchemaValidatorTestBase();
+
+ void RunTests();
+
+ protected:
+ virtual void ExpectValid(const std::string& test_source,
+ base::Value* instance,
+ base::DictionaryValue* schema,
+ base::ListValue* types) = 0;
+
+ virtual void ExpectNotValid(const std::string& test_source,
+ base::Value* instance,
+ base::DictionaryValue* schema,
+ base::ListValue* types,
+ const std::string& expected_error_path,
+ const std::string& expected_error_message) = 0;
+
+ private:
+ void TestComplex();
+ void TestStringPattern();
+ void TestEnum();
+ void TestChoices();
+ void TestExtends();
+ void TestObject();
+ void TestTypeReference();
+ void TestArrayTuple();
+ void TestArrayNonTuple();
+ void TestString();
+ void TestNumber();
+ void TestTypeClassifier();
+ void TestTypes();
+};
+
+#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_
diff --git a/components/policy/core/common/async_policy_loader.cc b/components/policy/core/common/async_policy_loader.cc
new file mode 100644
index 0000000000..b0edee2ea7
--- /dev/null
+++ b/components/policy/core/common/async_policy_loader.cc
@@ -0,0 +1,137 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/async_policy_loader.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/sequenced_task_runner.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace policy {
+
+namespace {
+
+// Amount of time to wait for the files on disk to settle before trying to load
+// them. This alleviates the problem of reading partially written files and
+// makes it possible to batch quasi-simultaneous changes.
+constexpr TimeDelta kSettleInterval = TimeDelta::FromSeconds(5);
+
+// The time interval for rechecking policy. This is the fallback in case the
+// implementation never detects changes.
+constexpr TimeDelta kReloadInterval = TimeDelta::FromMinutes(15);
+
+} // namespace
+
+AsyncPolicyLoader::AsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner)
+ : task_runner_(task_runner) {}
+
+AsyncPolicyLoader::~AsyncPolicyLoader() {}
+
+Time AsyncPolicyLoader::LastModificationTime() {
+ return Time();
+}
+
+void AsyncPolicyLoader::Reload(bool force) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ TimeDelta delay;
+ Time now = Time::Now();
+ // Check if there was a recent modification to the underlying files.
+ if (!force && !IsSafeToReload(now, &delay)) {
+ ScheduleNextReload(delay);
+ return;
+ }
+
+ std::unique_ptr<PolicyBundle> bundle(Load());
+
+ // Check if there was a modification while reading.
+ if (!force && !IsSafeToReload(now, &delay)) {
+ ScheduleNextReload(delay);
+ return;
+ }
+
+ // Filter out mismatching policies.
+ schema_map_->FilterBundle(bundle.get());
+
+ update_callback_.Run(std::move(bundle));
+ ScheduleNextReload(kReloadInterval);
+}
+
+std::unique_ptr<PolicyBundle> AsyncPolicyLoader::InitialLoad(
+ const scoped_refptr<SchemaMap>& schema_map) {
+ // This is the first load, early during startup. Use this to record the
+ // initial |last_modification_time_|, so that potential changes made before
+ // installing the watches can be detected.
+ last_modification_time_ = LastModificationTime();
+ schema_map_ = schema_map;
+ std::unique_ptr<PolicyBundle> bundle(Load());
+ // Filter out mismatching policies.
+ schema_map_->FilterBundle(bundle.get());
+ return bundle;
+}
+
+void AsyncPolicyLoader::Init(const UpdateCallback& update_callback) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(update_callback_.is_null());
+ DCHECK(!update_callback.is_null());
+ update_callback_ = update_callback;
+
+ InitOnBackgroundThread();
+
+ // There might have been changes to the underlying files since the initial
+ // load and before the watchers have been created.
+ if (LastModificationTime() != last_modification_time_)
+ Reload(false);
+
+ // Start periodic refreshes.
+ ScheduleNextReload(kReloadInterval);
+}
+
+void AsyncPolicyLoader::RefreshPolicies(scoped_refptr<SchemaMap> schema_map) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ schema_map_ = schema_map;
+ Reload(true);
+}
+
+void AsyncPolicyLoader::ScheduleNextReload(TimeDelta delay) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ weak_factory_.InvalidateWeakPtrs();
+ task_runner_->PostDelayedTask(FROM_HERE,
+ base::Bind(&AsyncPolicyLoader::Reload,
+ weak_factory_.GetWeakPtr(),
+ false /* force */),
+ delay);
+}
+
+bool AsyncPolicyLoader::IsSafeToReload(const Time& now, TimeDelta* delay) {
+ Time last_modification = LastModificationTime();
+ if (last_modification.is_null())
+ return true;
+
+ // If there was a change since the last recorded modification, wait some more.
+ if (last_modification != last_modification_time_) {
+ last_modification_time_ = last_modification;
+ last_modification_clock_ = now;
+ *delay = kSettleInterval;
+ return false;
+ }
+
+ // Check whether the settle interval has elapsed.
+ const TimeDelta age = now - last_modification_clock_;
+ if (age < kSettleInterval) {
+ *delay = kSettleInterval - age;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/async_policy_loader.h b/components/policy/core/common/async_policy_loader.h
new file mode 100644
index 0000000000..04ea60b998
--- /dev/null
+++ b/components/policy/core/common/async_policy_loader.h
@@ -0,0 +1,126 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_LOADER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_LOADER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class PolicyBundle;
+
+// Base implementation for platform-specific policy loaders. Together with the
+// AsyncPolicyProvider, this base implementation takes care of the initial load,
+// periodic reloads, watching file changes, refreshing policies and object
+// lifetime.
+//
+// All methods are invoked on the background |task_runner_|, including the
+// destructor. The only exceptions are the constructor (which may be called on
+// any thread), InitialLoad() which is called on the thread that owns the
+// provider and the calls of Load() and LastModificationTime() during the
+// initial load.
+// Also, during tests the destructor may be called on the main thread.
+class POLICY_EXPORT AsyncPolicyLoader {
+ public:
+ explicit AsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner);
+ virtual ~AsyncPolicyLoader();
+
+ // Gets a SequencedTaskRunner backed by the background thread.
+ base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); }
+
+ // Returns the currently configured policies. Load() is always invoked on
+ // the background thread, except for the initial Load() at startup which is
+ // invoked from the thread that owns the provider.
+ virtual std::unique_ptr<PolicyBundle> Load() = 0;
+
+ // Allows implementations to finalize their initialization on the background
+ // thread (e.g. setup file watchers).
+ virtual void InitOnBackgroundThread() = 0;
+
+ // Implementations should return the time of the last modification detected,
+ // or base::Time() if it doesn't apply, which is the default.
+ virtual base::Time LastModificationTime();
+
+ // Used by the AsyncPolicyProvider to do the initial Load(). The first load
+ // is also used to initialize |last_modification_time_| and
+ // |schema_map_|.
+ std::unique_ptr<PolicyBundle> InitialLoad(
+ const scoped_refptr<SchemaMap>& schemas);
+
+ // Implementations should invoke Reload() when a change is detected. This
+ // must be invoked from the background thread and will trigger a Load(),
+ // and pass the returned bundle to the provider.
+ // The load is immediate when |force| is true. Otherwise, the loader
+ // reschedules the reload until the LastModificationTime() is a couple of
+ // seconds in the past. This mitigates the problem of reading files that are
+ // currently being written to, and whose contents are incomplete.
+ // A reload is posted periodically, if it hasn't been triggered recently. This
+ // makes sure the policies are reloaded if the update events aren't triggered.
+ void Reload(bool force);
+
+ const scoped_refptr<SchemaMap>& schema_map() const { return schema_map_; }
+
+ private:
+ // Allow AsyncPolicyProvider to call Init().
+ friend class AsyncPolicyProvider;
+
+ typedef base::Callback<void(std::unique_ptr<PolicyBundle>)> UpdateCallback;
+
+ // Used by the AsyncPolicyProvider to install the |update_callback_|.
+ // Invoked on the background thread.
+ void Init(const UpdateCallback& update_callback);
+
+ // Used by the AsyncPolicyProvider to reload with an updated SchemaMap.
+ void RefreshPolicies(scoped_refptr<SchemaMap> schema_map);
+
+ // Cancels any pending periodic reload and posts one |delay| time units from
+ // now.
+ void ScheduleNextReload(base::TimeDelta delay);
+
+ // Checks if the underlying files haven't changed recently, by checking the
+ // LastModificationTime(). |delay| is updated with a suggested time to wait
+ // before retrying when this returns false.
+ bool IsSafeToReload(const base::Time& now, base::TimeDelta* delay);
+
+ // Task runner for running background jobs.
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // Callback for updates, passed in Init().
+ UpdateCallback update_callback_;
+
+ // Records last known modification timestamp.
+ base::Time last_modification_time_;
+
+ // The wall clock time at which the last modification timestamp was
+ // recorded. It's better to not assume the file notification time and the
+ // wall clock times come from the same source, just in case there is some
+ // non-local filesystem involved.
+ base::Time last_modification_clock_;
+
+ // The current policy schemas that this provider should load.
+ scoped_refptr<SchemaMap> schema_map_;
+
+ // Used to get WeakPtrs for the periodic reload task.
+ base::WeakPtrFactory<AsyncPolicyLoader> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncPolicyLoader);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_LOADER_H_
diff --git a/components/policy/core/common/async_policy_provider.cc b/components/policy/core/common/async_policy_provider.cc
new file mode 100644
index 0000000000..9fe4153204
--- /dev/null
+++ b/components/policy/core/common/async_policy_provider.cc
@@ -0,0 +1,131 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/async_policy_provider.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/location.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/schema_registry.h"
+
+namespace policy {
+
+AsyncPolicyProvider::AsyncPolicyProvider(
+ SchemaRegistry* registry,
+ std::unique_ptr<AsyncPolicyLoader> loader)
+ : loader_(std::move(loader)), weak_factory_(this) {
+ // Make an immediate synchronous load on startup.
+ OnLoaderReloaded(loader_->InitialLoad(registry->schema_map()));
+}
+
+AsyncPolicyProvider::~AsyncPolicyProvider() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void AsyncPolicyProvider::Init(SchemaRegistry* registry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ ConfigurationPolicyProvider::Init(registry);
+
+ if (!loader_)
+ return;
+
+ AsyncPolicyLoader::UpdateCallback callback =
+ base::Bind(&AsyncPolicyProvider::LoaderUpdateCallback,
+ base::ThreadTaskRunnerHandle::Get(),
+ weak_factory_.GetWeakPtr());
+ bool post = loader_->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&AsyncPolicyLoader::Init,
+ base::Unretained(loader_.get()),
+ callback));
+ DCHECK(post) << "AsyncPolicyProvider::Init() called with threads not running";
+}
+
+void AsyncPolicyProvider::Shutdown() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Note on the lifetime of |loader_|:
+ // The |loader_| lives on the background thread, and is deleted from here.
+ // This means that posting tasks on the |loader_| to the background thread
+ // from the AsyncPolicyProvider is always safe, since a potential DeleteSoon()
+ // is only posted from here. The |loader_| posts back to the
+ // AsyncPolicyProvider through the |update_callback_|, which has a WeakPtr to
+ // |this|.
+ // If threads are spinning, delete the loader on the thread it lives on. If
+ // there are no threads, kill it immediately.
+ AsyncPolicyLoader* loader_to_delete = loader_.release();
+ if (!loader_to_delete->task_runner()->DeleteSoon(FROM_HERE, loader_to_delete))
+ delete loader_to_delete;
+ ConfigurationPolicyProvider::Shutdown();
+}
+
+void AsyncPolicyProvider::RefreshPolicies() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Subtle: RefreshPolicies() has a contract that requires the next policy
+ // update notification (triggered from UpdatePolicy()) to reflect any changes
+ // made before this call. So if a caller has modified the policy settings and
+ // invoked RefreshPolicies(), then by the next notification these policies
+ // should already be provided.
+ // However, it's also possible that an asynchronous Reload() is in progress
+ // and just posted OnLoaderReloaded(). Therefore a task is posted to the
+ // background thread before posting the next Reload, to prevent a potential
+ // concurrent Reload() from triggering a notification too early. If another
+ // refresh task has been posted, it is invalidated now.
+ if (!loader_)
+ return;
+ refresh_callback_.Reset(
+ base::Bind(&AsyncPolicyProvider::ReloadAfterRefreshSync,
+ weak_factory_.GetWeakPtr()));
+ loader_->task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(),
+ refresh_callback_.callback());
+}
+
+void AsyncPolicyProvider::ReloadAfterRefreshSync() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // This task can only enter if it was posted from RefreshPolicies(), and it
+ // hasn't been cancelled meanwhile by another call to RefreshPolicies().
+ DCHECK(!refresh_callback_.IsCancelled());
+ // There can't be another refresh callback pending now, since its creation
+ // in RefreshPolicies() would have cancelled the current execution. So it's
+ // safe to cancel the |refresh_callback_| now, so that OnLoaderReloaded()
+ // sees that there is no refresh pending.
+ refresh_callback_.Cancel();
+
+ if (!loader_)
+ return;
+
+ loader_->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&AsyncPolicyLoader::RefreshPolicies,
+ base::Unretained(loader_.get()),
+ schema_map()));
+}
+
+void AsyncPolicyProvider::OnLoaderReloaded(
+ std::unique_ptr<PolicyBundle> bundle) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Only propagate policy updates if there are no pending refreshes, and if
+ // Shutdown() hasn't been called yet.
+ if (refresh_callback_.IsCancelled() && loader_)
+ UpdatePolicy(std::move(bundle));
+}
+
+// static
+void AsyncPolicyProvider::LoaderUpdateCallback(
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ base::WeakPtr<AsyncPolicyProvider> weak_this,
+ std::unique_ptr<PolicyBundle> bundle) {
+ runner->PostTask(FROM_HERE,
+ base::BindOnce(&AsyncPolicyProvider::OnLoaderReloaded,
+ weak_this, std::move(bundle)));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/async_policy_provider.h b/components/policy/core/common/async_policy_provider.h
new file mode 100644
index 0000000000..3ef4c4b0bf
--- /dev/null
+++ b/components/policy/core/common/async_policy_provider.h
@@ -0,0 +1,80 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_PROVIDER_H_
+
+#include <memory>
+
+#include "base/cancelable_callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace policy {
+
+class AsyncPolicyLoader;
+class PolicyBundle;
+class SchemaRegistry;
+
+// A policy provider that loads its policies asynchronously on a background
+// thread. Platform-specific providers are created by passing an implementation
+// of AsyncPolicyLoader to a new AsyncPolicyProvider.
+class POLICY_EXPORT AsyncPolicyProvider : public ConfigurationPolicyProvider {
+ public:
+ // The AsyncPolicyProvider does a synchronous load in its constructor, and
+ // therefore it needs the |registry| at construction time. The same |registry|
+ // should be passed later to Init().
+ AsyncPolicyProvider(SchemaRegistry* registry,
+ std::unique_ptr<AsyncPolicyLoader> loader);
+ ~AsyncPolicyProvider() override;
+
+ // ConfigurationPolicyProvider implementation.
+ void Init(SchemaRegistry* registry) override;
+ void Shutdown() override;
+ void RefreshPolicies() override;
+
+ private:
+ // Helper for RefreshPolicies().
+ void ReloadAfterRefreshSync();
+
+ // Invoked with the latest bundle loaded by the |loader_|.
+ void OnLoaderReloaded(std::unique_ptr<PolicyBundle> bundle);
+
+ // Callback passed to the loader that it uses to pass back the current policy
+ // bundle to the provider. This is invoked on the background thread and
+ // forwards to OnLoaderReloaded() on the runner that owns the provider,
+ // if |weak_this| is still valid.
+ static void LoaderUpdateCallback(
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ base::WeakPtr<AsyncPolicyProvider> weak_this,
+ std::unique_ptr<PolicyBundle> bundle);
+
+ // The |loader_| that does the platform-specific policy loading. It lives
+ // on the background thread but is owned by |this|.
+ std::unique_ptr<AsyncPolicyLoader> loader_;
+
+ // Callback used to synchronize RefreshPolicies() calls with the background
+ // thread. See the implementation for the details.
+ base::CancelableClosure refresh_callback_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Used to get a WeakPtr to |this| for the update callback given to the
+ // loader.
+ base::WeakPtrFactory<AsyncPolicyProvider> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncPolicyProvider);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_PROVIDER_H_
diff --git a/components/policy/core/common/async_policy_provider_unittest.cc b/components/policy/core/common/async_policy_provider_unittest.cc
new file mode 100644
index 0000000000..61286c6633
--- /dev/null
+++ b/components/policy/core/common/async_policy_provider_unittest.cc
@@ -0,0 +1,227 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/async_policy_provider.h"
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+using testing::Return;
+using testing::Sequence;
+
+namespace policy {
+
+namespace {
+
+// Helper to write a policy in |bundle| with less code.
+void SetPolicy(PolicyBundle* bundle,
+ const std::string& name,
+ const std::string& value) {
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, std::make_unique<base::Value>(value),
+ nullptr);
+}
+
+class MockPolicyLoader : public AsyncPolicyLoader {
+ public:
+ explicit MockPolicyLoader(
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+ ~MockPolicyLoader() override;
+
+ // Load() returns a std::unique_ptr<PolicyBundle> but it can't be mocked
+ // because std::unique_ptr is moveable but not copyable. This override
+ // forwards the call to MockLoad() which returns a PolicyBundle*, and returns
+ // a copy wrapped in a std::unique_ptr.
+ std::unique_ptr<PolicyBundle> Load() override;
+
+ MOCK_METHOD0(MockLoad, const PolicyBundle*());
+ MOCK_METHOD0(InitOnBackgroundThread, void());
+ MOCK_METHOD0(LastModificationTime, base::Time());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockPolicyLoader);
+};
+
+MockPolicyLoader::MockPolicyLoader(
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : AsyncPolicyLoader(task_runner) {}
+
+MockPolicyLoader::~MockPolicyLoader() {}
+
+std::unique_ptr<PolicyBundle> MockPolicyLoader::Load() {
+ std::unique_ptr<PolicyBundle> bundle;
+ const PolicyBundle* loaded = MockLoad();
+ if (loaded) {
+ bundle.reset(new PolicyBundle());
+ bundle->CopyFrom(*loaded);
+ }
+ return bundle;
+}
+
+} // namespace
+
+class AsyncPolicyProviderTest : public testing::Test {
+ protected:
+ AsyncPolicyProviderTest();
+ ~AsyncPolicyProviderTest() override;
+
+ void SetUp() override;
+ void TearDown() override;
+
+ base::MessageLoop loop_;
+ SchemaRegistry schema_registry_;
+ PolicyBundle initial_bundle_;
+ MockPolicyLoader* loader_;
+ std::unique_ptr<AsyncPolicyProvider> provider_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AsyncPolicyProviderTest);
+};
+
+AsyncPolicyProviderTest::AsyncPolicyProviderTest() {}
+
+AsyncPolicyProviderTest::~AsyncPolicyProviderTest() {}
+
+void AsyncPolicyProviderTest::SetUp() {
+ SetPolicy(&initial_bundle_, "policy", "initial");
+ loader_ = new MockPolicyLoader(loop_.task_runner());
+ EXPECT_CALL(*loader_, LastModificationTime())
+ .WillRepeatedly(Return(base::Time()));
+ EXPECT_CALL(*loader_, InitOnBackgroundThread()).Times(1);
+ EXPECT_CALL(*loader_, MockLoad()).WillOnce(Return(&initial_bundle_));
+
+ provider_.reset(new AsyncPolicyProvider(
+ &schema_registry_, std::unique_ptr<AsyncPolicyLoader>(loader_)));
+ provider_->Init(&schema_registry_);
+ // Verify that the initial load is done synchronously:
+ EXPECT_TRUE(provider_->policies().Equals(initial_bundle_));
+
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(loader_);
+
+ EXPECT_CALL(*loader_, LastModificationTime())
+ .WillRepeatedly(Return(base::Time()));
+}
+
+void AsyncPolicyProviderTest::TearDown() {
+ if (provider_) {
+ provider_->Shutdown();
+ provider_.reset();
+ }
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(AsyncPolicyProviderTest, RefreshPolicies) {
+ PolicyBundle refreshed_bundle;
+ SetPolicy(&refreshed_bundle, "policy", "refreshed");
+ EXPECT_CALL(*loader_, MockLoad()).WillOnce(Return(&refreshed_bundle));
+
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ provider_->RefreshPolicies();
+ base::RunLoop().RunUntilIdle();
+ // The refreshed policies are now provided.
+ EXPECT_TRUE(provider_->policies().Equals(refreshed_bundle));
+ provider_->RemoveObserver(&observer);
+}
+
+TEST_F(AsyncPolicyProviderTest, RefreshPoliciesTwice) {
+ PolicyBundle refreshed_bundle;
+ SetPolicy(&refreshed_bundle, "policy", "refreshed");
+ EXPECT_CALL(*loader_, MockLoad()).WillRepeatedly(Return(&refreshed_bundle));
+
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ provider_->RefreshPolicies();
+ // Doesn't refresh before going through the background thread.
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Doesn't refresh if another RefreshPolicies request is made.
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ provider_->RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ base::RunLoop().RunUntilIdle();
+ // The refreshed policies are now provided.
+ EXPECT_TRUE(provider_->policies().Equals(refreshed_bundle));
+ Mock::VerifyAndClearExpectations(&observer);
+ provider_->RemoveObserver(&observer);
+}
+
+TEST_F(AsyncPolicyProviderTest, RefreshPoliciesDuringReload) {
+ PolicyBundle reloaded_bundle;
+ SetPolicy(&reloaded_bundle, "policy", "reloaded");
+ PolicyBundle refreshed_bundle;
+ SetPolicy(&refreshed_bundle, "policy", "refreshed");
+
+ Sequence load_sequence;
+ // Reload.
+ EXPECT_CALL(*loader_, MockLoad()).InSequence(load_sequence)
+ .WillOnce(Return(&reloaded_bundle));
+ // RefreshPolicies.
+ EXPECT_CALL(*loader_, MockLoad()).InSequence(load_sequence)
+ .WillOnce(Return(&refreshed_bundle));
+
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+
+ // A Reload is triggered before RefreshPolicies, and it shouldn't trigger
+ // notifications.
+ loader_->Reload(true);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Doesn't refresh before going through the background thread.
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ provider_->RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ base::RunLoop().RunUntilIdle();
+ // The refreshed policies are now provided, and the |reloaded_bundle| was
+ // dropped.
+ EXPECT_TRUE(provider_->policies().Equals(refreshed_bundle));
+ Mock::VerifyAndClearExpectations(&observer);
+ provider_->RemoveObserver(&observer);
+}
+
+TEST_F(AsyncPolicyProviderTest, Shutdown) {
+ EXPECT_CALL(*loader_, MockLoad()).WillRepeatedly(Return(&initial_bundle_));
+
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+
+ // Though there is a pending Reload, the provider and the loader can be
+ // deleted at any time.
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ loader_->Reload(true);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ provider_->Shutdown();
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ provider_->RemoveObserver(&observer);
+ provider_.reset();
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/config_dir_policy_loader.cc b/components/policy/core/common/config_dir_policy_loader.cc
new file mode 100644
index 0000000000..8edf58623d
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/config_dir_policy_loader.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/stl_util.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+namespace {
+
+// Subdirectories that contain the mandatory and recommended policies.
+constexpr base::FilePath::CharType kMandatoryConfigDir[] =
+ FILE_PATH_LITERAL("managed");
+constexpr base::FilePath::CharType kRecommendedConfigDir[] =
+ FILE_PATH_LITERAL("recommended");
+
+PolicyLoadStatus JsonErrorToPolicyLoadStatus(int status) {
+ switch (status) {
+ case JSONFileValueDeserializer::JSON_ACCESS_DENIED:
+ case JSONFileValueDeserializer::JSON_CANNOT_READ_FILE:
+ case JSONFileValueDeserializer::JSON_FILE_LOCKED:
+ return POLICY_LOAD_STATUS_READ_ERROR;
+ case JSONFileValueDeserializer::JSON_NO_SUCH_FILE:
+ return POLICY_LOAD_STATUS_MISSING;
+ case base::JSONReader::JSON_INVALID_ESCAPE:
+ case base::JSONReader::JSON_SYNTAX_ERROR:
+ case base::JSONReader::JSON_UNEXPECTED_TOKEN:
+ case base::JSONReader::JSON_TRAILING_COMMA:
+ case base::JSONReader::JSON_TOO_MUCH_NESTING:
+ case base::JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT:
+ case base::JSONReader::JSON_UNSUPPORTED_ENCODING:
+ case base::JSONReader::JSON_UNQUOTED_DICTIONARY_KEY:
+ return POLICY_LOAD_STATUS_PARSE_ERROR;
+ case base::JSONReader::JSON_NO_ERROR:
+ NOTREACHED();
+ return POLICY_LOAD_STATUS_STARTED;
+ }
+ NOTREACHED() << "Invalid status " << status;
+ return POLICY_LOAD_STATUS_PARSE_ERROR;
+}
+
+} // namespace
+
+ConfigDirPolicyLoader::ConfigDirPolicyLoader(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& config_dir,
+ PolicyScope scope)
+ : AsyncPolicyLoader(task_runner),
+ task_runner_(task_runner),
+ config_dir_(config_dir),
+ scope_(scope) {}
+
+ConfigDirPolicyLoader::~ConfigDirPolicyLoader() {}
+
+void ConfigDirPolicyLoader::InitOnBackgroundThread() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::FilePathWatcher::Callback callback =
+ base::Bind(&ConfigDirPolicyLoader::OnFileUpdated, base::Unretained(this));
+ mandatory_watcher_.Watch(config_dir_.Append(kMandatoryConfigDir), false,
+ callback);
+ recommended_watcher_.Watch(config_dir_.Append(kRecommendedConfigDir), false,
+ callback);
+}
+
+std::unique_ptr<PolicyBundle> ConfigDirPolicyLoader::Load() {
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ LoadFromPath(config_dir_.Append(kMandatoryConfigDir),
+ POLICY_LEVEL_MANDATORY,
+ bundle.get());
+ LoadFromPath(config_dir_.Append(kRecommendedConfigDir),
+ POLICY_LEVEL_RECOMMENDED,
+ bundle.get());
+ return bundle;
+}
+
+base::Time ConfigDirPolicyLoader::LastModificationTime() {
+ static constexpr const base::FilePath::CharType* kConfigDirSuffixes[] = {
+ kMandatoryConfigDir, kRecommendedConfigDir,
+ };
+
+ base::Time last_modification = base::Time();
+ base::File::Info info;
+
+ for (size_t i = 0; i < arraysize(kConfigDirSuffixes); ++i) {
+ base::FilePath path(config_dir_.Append(kConfigDirSuffixes[i]));
+
+ // Skip if the file doesn't exist, or it isn't a directory.
+ if (!base::GetFileInfo(path, &info) || !info.is_directory)
+ continue;
+
+ // Enumerate the files and find the most recent modification timestamp.
+ base::FileEnumerator file_enumerator(path, false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath config_file = file_enumerator.Next();
+ !config_file.empty();
+ config_file = file_enumerator.Next()) {
+ if (base::GetFileInfo(config_file, &info) && !info.is_directory)
+ last_modification = std::max(last_modification, info.last_modified);
+ }
+ }
+
+ return last_modification;
+}
+
+void ConfigDirPolicyLoader::LoadFromPath(const base::FilePath& path,
+ PolicyLevel level,
+ PolicyBundle* bundle) {
+ // Enumerate the files and sort them lexicographically.
+ std::set<base::FilePath> files;
+ base::FileEnumerator file_enumerator(path, false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath config_file_path = file_enumerator.Next();
+ !config_file_path.empty(); config_file_path = file_enumerator.Next())
+ files.insert(config_file_path);
+
+ PolicyLoadStatusUmaReporter status;
+ if (files.empty()) {
+ status.Add(POLICY_LOAD_STATUS_NO_POLICY);
+ return;
+ }
+
+ // Start with an empty dictionary and merge the files' contents.
+ // The files are processed in reverse order because |MergeFrom| gives priority
+ // to existing keys, but the ConfigDirPolicyProvider gives priority to the
+ // last file in lexicographic order.
+ for (std::set<base::FilePath>::reverse_iterator config_file_iter =
+ files.rbegin(); config_file_iter != files.rend();
+ ++config_file_iter) {
+ JSONFileValueDeserializer deserializer(*config_file_iter,
+ base::JSON_ALLOW_TRAILING_COMMAS);
+ int error_code = 0;
+ std::string error_msg;
+ std::unique_ptr<base::Value> value =
+ deserializer.Deserialize(&error_code, &error_msg);
+ if (!value) {
+ LOG(WARNING) << "Failed to read configuration file "
+ << config_file_iter->value() << ": " << error_msg;
+ status.Add(JsonErrorToPolicyLoadStatus(error_code));
+ continue;
+ }
+ base::DictionaryValue* dictionary_value = nullptr;
+ if (!value->GetAsDictionary(&dictionary_value)) {
+ LOG(WARNING) << "Expected JSON dictionary in configuration file "
+ << config_file_iter->value();
+ status.Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+ continue;
+ }
+
+ // Detach the "3rdparty" node.
+ std::unique_ptr<base::Value> third_party;
+ if (dictionary_value->Remove("3rdparty", &third_party))
+ Merge3rdPartyPolicy(third_party.get(), level, bundle);
+
+ // Add chrome policy.
+ PolicyMap policy_map;
+ policy_map.LoadFrom(dictionary_value, level, scope_,
+ POLICY_SOURCE_PLATFORM);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .MergeFrom(policy_map);
+ }
+}
+
+void ConfigDirPolicyLoader::Merge3rdPartyPolicy(
+ const base::Value* policies,
+ PolicyLevel level,
+ PolicyBundle* bundle) {
+ // The first-level entries in |policies| are PolicyDomains. The second-level
+ // entries are component IDs, and the third-level entries are the policies
+ // for that domain/component namespace.
+
+ const base::DictionaryValue* domains_dictionary;
+ if (!policies->GetAsDictionary(&domains_dictionary)) {
+ LOG(WARNING) << "3rdparty value is not a dictionary!";
+ return;
+ }
+
+ // Helper to lookup a domain given its string name.
+ std::map<std::string, PolicyDomain> supported_domains;
+ supported_domains["extensions"] = POLICY_DOMAIN_EXTENSIONS;
+
+ for (base::DictionaryValue::Iterator domains_it(*domains_dictionary);
+ !domains_it.IsAtEnd(); domains_it.Advance()) {
+ if (!base::ContainsKey(supported_domains, domains_it.key())) {
+ LOG(WARNING) << "Unsupported 3rd party policy domain: "
+ << domains_it.key();
+ continue;
+ }
+
+ const base::DictionaryValue* components_dictionary;
+ if (!domains_it.value().GetAsDictionary(&components_dictionary)) {
+ LOG(WARNING) << "3rdparty/" << domains_it.key()
+ << " value is not a dictionary!";
+ continue;
+ }
+
+ PolicyDomain domain = supported_domains[domains_it.key()];
+ for (base::DictionaryValue::Iterator components_it(*components_dictionary);
+ !components_it.IsAtEnd(); components_it.Advance()) {
+ const base::DictionaryValue* policy_dictionary;
+ if (!components_it.value().GetAsDictionary(&policy_dictionary)) {
+ LOG(WARNING) << "3rdparty/" << domains_it.key() << "/"
+ << components_it.key() << " value is not a dictionary!";
+ continue;
+ }
+
+ PolicyMap policy;
+ policy.LoadFrom(policy_dictionary, level, scope_, POLICY_SOURCE_PLATFORM);
+ bundle->Get(PolicyNamespace(domain, components_it.key()))
+ .MergeFrom(policy);
+ }
+ }
+}
+
+void ConfigDirPolicyLoader::OnFileUpdated(const base::FilePath& path,
+ bool error) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ if (!error)
+ Reload(false);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/config_dir_policy_loader.h b/components/policy/core/common/config_dir_policy_loader.h
new file mode 100644
index 0000000000..8956353e73
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
+
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "base/macros.h"
+#include "base/sequenced_task_runner.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class Value;
+}
+
+namespace policy {
+
+// A policy loader implementation backed by a set of files in a given
+// directory. The files should contain JSON-formatted policy settings. They are
+// merged together and the result is returned in a PolicyBundle.
+// The files are consulted in lexicographic file name order, so the
+// last value read takes precedence in case of policy key collisions.
+class POLICY_EXPORT ConfigDirPolicyLoader : public AsyncPolicyLoader {
+ public:
+ ConfigDirPolicyLoader(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& config_dir,
+ PolicyScope scope);
+ ~ConfigDirPolicyLoader() override;
+
+ // AsyncPolicyLoader implementation.
+ void InitOnBackgroundThread() override;
+ std::unique_ptr<PolicyBundle> Load() override;
+ base::Time LastModificationTime() override;
+
+ private:
+ // Loads the policy files at |path| into the |bundle|, with the given |level|.
+ void LoadFromPath(const base::FilePath& path,
+ PolicyLevel level,
+ PolicyBundle* bundle);
+
+ // Merges the 3rd party |policies| into the |bundle|, with the given |level|.
+ void Merge3rdPartyPolicy(const base::Value* policies,
+ PolicyLevel level,
+ PolicyBundle* bundle);
+
+ // Callback for the FilePathWatchers.
+ void OnFileUpdated(const base::FilePath& path, bool error);
+
+ // Task runner for running background jobs.
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // The directory containing the policy files.
+ const base::FilePath config_dir_;
+
+ // Policies loaded by this provider will have this scope.
+ const PolicyScope scope_;
+
+ // Watchers for events on the mandatory and recommended subdirectories of
+ // |config_dir_|.
+ base::FilePathWatcher mandatory_watcher_;
+ base::FilePathWatcher recommended_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConfigDirPolicyLoader);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
diff --git a/components/policy/core/common/config_dir_policy_loader_unittest.cc b/components/policy/core/common/config_dir_policy_loader_unittest.cc
new file mode 100644
index 0000000000..b17add7974
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/config_dir_policy_loader.h"
+#include <memory>
+
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/common/async_policy_provider.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+namespace {
+
+// Subdirectory of the config dir that contains mandatory policies.
+const base::FilePath::CharType kMandatoryPath[] = FILE_PATH_LITERAL("managed");
+
+class TestHarness : public PolicyProviderTestHarness {
+ public:
+ TestHarness();
+ ~TestHarness() override;
+
+ void SetUp() override;
+
+ ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) override;
+
+ void InstallEmptyPolicy() override;
+ void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) override;
+ void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) override;
+ void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) override;
+ void InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) override;
+ void InstallDictionaryPolicy(
+ const std::string& policy_name,
+ const base::DictionaryValue* policy_value) override;
+ void Install3rdPartyPolicy(const base::DictionaryValue* policies) override;
+
+ const base::FilePath& test_dir() { return test_dir_.GetPath(); }
+
+ // JSON-encode a dictionary and write it to a file.
+ void WriteConfigFile(const base::DictionaryValue& dict,
+ const std::string& file_name);
+
+ // Returns a unique name for a policy file. Each subsequent call returns a new
+ // name that comes lexicographically after the previous one.
+ std::string NextConfigFileName();
+
+ static PolicyProviderTestHarness* Create();
+
+ private:
+ base::ScopedTempDir test_dir_;
+ int next_policy_file_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestHarness);
+};
+
+TestHarness::TestHarness()
+ : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM),
+ next_policy_file_index_(100) {}
+
+TestHarness::~TestHarness() {}
+
+void TestHarness::SetUp() {
+ ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
+}
+
+ConfigurationPolicyProvider* TestHarness::CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ std::unique_ptr<AsyncPolicyLoader> loader(
+ new ConfigDirPolicyLoader(task_runner, test_dir(), POLICY_SCOPE_MACHINE));
+ return new AsyncPolicyProvider(registry, std::move(loader));
+}
+
+void TestHarness::InstallEmptyPolicy() {
+ base::DictionaryValue dict;
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) {
+ base::DictionaryValue dict;
+ dict.SetString(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) {
+ base::DictionaryValue dict;
+ dict.SetInteger(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) {
+ base::DictionaryValue dict;
+ dict.SetBoolean(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) {
+ base::DictionaryValue dict;
+ dict.Set(policy_name, std::make_unique<base::Value>(policy_value->Clone()));
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallDictionaryPolicy(
+ const std::string& policy_name,
+ const base::DictionaryValue* policy_value) {
+ base::DictionaryValue dict;
+ dict.Set(policy_name, std::make_unique<base::Value>(policy_value->Clone()));
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::Install3rdPartyPolicy(const base::DictionaryValue* policies) {
+ base::DictionaryValue dict;
+ dict.SetKey("3rdparty", policies->Clone());
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::WriteConfigFile(const base::DictionaryValue& dict,
+ const std::string& file_name) {
+ std::string data;
+ JSONStringValueSerializer serializer(&data);
+ serializer.Serialize(dict);
+ const base::FilePath mandatory_dir(test_dir().Append(kMandatoryPath));
+ ASSERT_TRUE(base::CreateDirectory(mandatory_dir));
+ const base::FilePath file_path(mandatory_dir.AppendASCII(file_name));
+ ASSERT_EQ((int) data.size(),
+ base::WriteFile(file_path, data.c_str(), data.size()));
+}
+
+std::string TestHarness::NextConfigFileName() {
+ EXPECT_LE(next_policy_file_index_, 999);
+ return std::string("policy") + base::IntToString(next_policy_file_index_++);
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::Create() {
+ return new TestHarness();
+}
+
+} // namespace
+
+// Instantiate abstract test case for basic policy reading tests.
+INSTANTIATE_TEST_CASE_P(
+ ConfigDirPolicyLoaderTest,
+ ConfigurationPolicyProviderTest,
+ testing::Values(TestHarness::Create));
+
+// Instantiate abstract test case for 3rd party policy reading tests.
+INSTANTIATE_TEST_CASE_P(
+ ConfigDir3rdPartyPolicyLoaderTest,
+ Configuration3rdPartyPolicyProviderTest,
+ testing::Values(TestHarness::Create));
+
+// Some tests that exercise special functionality in ConfigDirPolicyLoader.
+class ConfigDirPolicyLoaderTest : public PolicyTestBase {
+ protected:
+ void SetUp() override {
+ PolicyTestBase::SetUp();
+ harness_.SetUp();
+ }
+
+ TestHarness harness_;
+};
+
+// The preferences dictionary is expected to be empty when there are no files to
+// load.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsEmpty) {
+ ConfigDirPolicyLoader loader(
+ scoped_task_environment_.GetMainThreadTaskRunner(), harness_.test_dir(),
+ POLICY_SCOPE_MACHINE);
+ std::unique_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(bundle->Equals(kEmptyBundle));
+}
+
+// Reading from a non-existent directory should result in an empty preferences
+// dictionary.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsNonExistentDirectory) {
+ base::FilePath non_existent_dir(
+ harness_.test_dir().Append(FILE_PATH_LITERAL("not_there")));
+ ConfigDirPolicyLoader loader(
+ scoped_task_environment_.GetMainThreadTaskRunner(), non_existent_dir,
+ POLICY_SCOPE_MACHINE);
+ std::unique_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(bundle->Equals(kEmptyBundle));
+}
+
+// Test merging values from different files.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsMergePrefs) {
+ // Write a bunch of data files in order to increase the chance to detect the
+ // provider not respecting lexicographic ordering when reading them. Since the
+ // filesystem may return files in arbitrary order, there is no way to be sure,
+ // but this is better than nothing.
+ base::DictionaryValue test_dict_bar;
+ test_dict_bar.SetString("HomepageLocation", "http://bar.com");
+ for (unsigned int i = 1; i <= 4; ++i)
+ harness_.WriteConfigFile(test_dict_bar, base::UintToString(i));
+ base::DictionaryValue test_dict_foo;
+ test_dict_foo.SetString("HomepageLocation", "http://foo.com");
+ harness_.WriteConfigFile(test_dict_foo, "9");
+ for (unsigned int i = 5; i <= 8; ++i)
+ harness_.WriteConfigFile(test_dict_bar, base::UintToString(i));
+
+ ConfigDirPolicyLoader loader(
+ scoped_task_environment_.GetMainThreadTaskRunner(), harness_.test_dir(),
+ POLICY_SCOPE_USER);
+ std::unique_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .LoadFrom(&test_dict_foo, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM);
+ EXPECT_TRUE(bundle->Equals(expected_bundle));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/configuration_policy_provider.cc b/components/policy/core/common/configuration_policy_provider.cc
new file mode 100644
index 0000000000..a886bd4bca
--- /dev/null
+++ b/components/policy/core/common/configuration_policy_provider.cc
@@ -0,0 +1,75 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/configuration_policy_provider.h"
+
+#include "base/callback.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+ConfigurationPolicyProvider::Observer::~Observer() {}
+
+ConfigurationPolicyProvider::ConfigurationPolicyProvider()
+ : initialized_(false), schema_registry_(nullptr) {}
+
+ConfigurationPolicyProvider::~ConfigurationPolicyProvider() {
+ DCHECK(!initialized_);
+}
+
+void ConfigurationPolicyProvider::Init(SchemaRegistry* registry) {
+ schema_registry_ = registry;
+ schema_registry_->AddObserver(this);
+ initialized_ = true;
+}
+
+void ConfigurationPolicyProvider::Shutdown() {
+ initialized_ = false;
+ if (schema_registry_) {
+ // Unit tests don't initialize the BrowserPolicyConnector but call
+ // shutdown; handle that.
+ schema_registry_->RemoveObserver(this);
+ schema_registry_ = nullptr;
+ }
+}
+
+bool ConfigurationPolicyProvider::IsInitializationComplete(
+ PolicyDomain domain) const {
+ return true;
+}
+
+void ConfigurationPolicyProvider::UpdatePolicy(
+ std::unique_ptr<PolicyBundle> bundle) {
+ if (bundle)
+ policy_bundle_.Swap(bundle.get());
+ else
+ policy_bundle_.Clear();
+ for (auto& observer : observer_list_)
+ observer.OnUpdatePolicy(this);
+}
+
+SchemaRegistry* ConfigurationPolicyProvider::schema_registry() const {
+ return schema_registry_;
+}
+
+const scoped_refptr<SchemaMap>&
+ConfigurationPolicyProvider::schema_map() const {
+ return schema_registry_->schema_map();
+}
+
+void ConfigurationPolicyProvider::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void ConfigurationPolicyProvider::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void ConfigurationPolicyProvider::OnSchemaRegistryUpdated(
+ bool has_new_schemas) {}
+
+void ConfigurationPolicyProvider::OnSchemaRegistryReady() {}
+
+} // namespace policy
diff --git a/components/policy/core/common/configuration_policy_provider.h b/components/policy/core/common/configuration_policy_provider.h
new file mode 100644
index 0000000000..a813c0c091
--- /dev/null
+++ b/components/policy/core/common/configuration_policy_provider.h
@@ -0,0 +1,105 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A mostly-abstract super class for platform-specific policy providers.
+// Platform-specific policy providers (Windows Group Policy, gconf,
+// etc.) should implement a subclass of this class.
+class POLICY_EXPORT ConfigurationPolicyProvider
+ : public SchemaRegistry::Observer {
+ public:
+ class POLICY_EXPORT Observer {
+ public:
+ virtual ~Observer();
+ virtual void OnUpdatePolicy(ConfigurationPolicyProvider* provider) = 0;
+ };
+
+ ConfigurationPolicyProvider();
+
+ // Policy providers can be deleted quite late during shutdown of the browser,
+ // and it's not guaranteed that the message loops will still be running when
+ // this is invoked. Override Shutdown() instead for cleanup code that needs
+ // to post to the FILE thread, for example.
+ ~ConfigurationPolicyProvider() override;
+
+ // Invoked as soon as the main message loops are spinning. Policy providers
+ // are created early during startup to provide the initial policies; the
+ // Init() call allows them to perform initialization tasks that require
+ // running message loops.
+ // The policy provider will load policy for the components registered in
+ // the |schema_registry| whose domain is supported by this provider.
+ virtual void Init(SchemaRegistry* registry);
+
+ // Must be invoked before deleting the provider. Implementations can override
+ // this method to do appropriate cleanup while threads are still running, and
+ // must also invoke ConfigurationPolicyProvider::Shutdown().
+ // The provider should keep providing the current policies after Shutdown()
+ // is invoked, it only has to stop updating.
+ virtual void Shutdown();
+
+ // Returns the current PolicyBundle.
+ const PolicyBundle& policies() const { return policy_bundle_; }
+
+ // Check whether this provider has completed initialization for the given
+ // policy |domain|. This is used to detect whether initialization is done in
+ // case implementations need to do asynchronous operations for initialization.
+ virtual bool IsInitializationComplete(PolicyDomain domain) const;
+
+ // Asks the provider to refresh its policies. All the updates caused by this
+ // call will be visible on the next call of OnUpdatePolicy on the observers,
+ // which are guaranteed to happen even if the refresh fails.
+ // It is possible that Shutdown() is called first though, and
+ // OnUpdatePolicy won't be called if that happens.
+ virtual void RefreshPolicies() = 0;
+
+ // Observers must detach themselves before the provider is deleted.
+ virtual void AddObserver(Observer* observer);
+ virtual void RemoveObserver(Observer* observer);
+
+ // SchemaRegistry::Observer:
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+ void OnSchemaRegistryReady() override;
+
+ protected:
+ // Subclasses must invoke this to update the policies currently served by
+ // this provider. UpdatePolicy() takes ownership of |policies|.
+ // The observers are notified after the policies are updated.
+ void UpdatePolicy(std::unique_ptr<PolicyBundle> bundle);
+
+ SchemaRegistry* schema_registry() const;
+
+ const scoped_refptr<SchemaMap>& schema_map() const;
+
+ private:
+ // The policies currently configured at this provider.
+ PolicyBundle policy_bundle_;
+
+ // Used to validate proper Init() and Shutdown() nesting. This flag is set by
+ // Init() and cleared by Shutdown() and needs to be false in the destructor.
+ bool initialized_;
+
+ SchemaRegistry* schema_registry_;
+
+ base::ObserverList<Observer, true> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConfigurationPolicyProvider);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_H_
diff --git a/components/policy/core/common/configuration_policy_provider_test.cc b/components/policy/core/common/configuration_policy_provider_test.cc
new file mode 100644
index 0000000000..4f9beea29c
--- /dev/null
+++ b/components/policy/core/common/configuration_policy_provider_test.cc
@@ -0,0 +1,420 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/values.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using ::testing::Mock;
+using ::testing::_;
+
+namespace policy {
+
+const char kTestChromeSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"StringPolicy\": { \"type\": \"string\" },"
+ " \"BooleanPolicy\": { \"type\": \"boolean\" },"
+ " \"IntegerPolicy\": { \"type\": \"integer\" },"
+ " \"StringListPolicy\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"DictionaryPolicy\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"string\": { \"type\": \"string\" },"
+ " \"array\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"dictionary\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"sub\": { \"type\": \"string\" },"
+ " \"sublist\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"aaa\": { \"type\": \"integer\" },"
+ " \"bbb\": { \"type\": \"integer\" },"
+ " \"ccc\": { \"type\": \"string\" },"
+ " \"ddd\": { \"type\": \"string\" }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " },"
+ " \"list\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"subdictindex\": { \"type\": \"integer\" },"
+ " \"subdict\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"string\": { \"type\": \"string\" }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " },"
+ " \"dict\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"string\": { \"type\": \"string\" },"
+ " \"list\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"subdictindex\": { \"type\": \"integer\" },"
+ " \"subdict\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"string\": { \"type\": \"string\" }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ "}";
+
+namespace test_keys {
+
+const char kKeyString[] = "StringPolicy";
+const char kKeyBoolean[] = "BooleanPolicy";
+const char kKeyInteger[] = "IntegerPolicy";
+const char kKeyStringList[] = "StringListPolicy";
+const char kKeyDictionary[] = "DictionaryPolicy";
+
+} // namespace test_keys
+
+PolicyTestBase::PolicyTestBase() {}
+
+PolicyTestBase::~PolicyTestBase() {}
+
+void PolicyTestBase::SetUp() {
+ const PolicyNamespace ns(POLICY_DOMAIN_CHROME, "");
+ ASSERT_TRUE(RegisterSchema(ns, kTestChromeSchema));
+}
+
+void PolicyTestBase::TearDown() {
+ scoped_task_environment_.RunUntilIdle();
+}
+
+bool PolicyTestBase::RegisterSchema(const PolicyNamespace& ns,
+ const std::string& schema_string) {
+ std::string error;
+ Schema schema = Schema::Parse(schema_string, &error);
+ if (schema.valid()) {
+ schema_registry_.RegisterComponent(ns, schema);
+ return true;
+ }
+ ADD_FAILURE() << error;
+ return false;
+}
+
+PolicyProviderTestHarness::PolicyProviderTestHarness(PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source)
+ : level_(level), scope_(scope), source_(source) {}
+
+PolicyProviderTestHarness::~PolicyProviderTestHarness() {}
+
+PolicyLevel PolicyProviderTestHarness::policy_level() const {
+ return level_;
+}
+
+PolicyScope PolicyProviderTestHarness::policy_scope() const {
+ return scope_;
+}
+
+PolicySource PolicyProviderTestHarness::policy_source() const {
+ return source_;
+}
+
+void PolicyProviderTestHarness::Install3rdPartyPolicy(
+ const base::DictionaryValue* policies) {
+ FAIL();
+}
+
+ConfigurationPolicyProviderTest::ConfigurationPolicyProviderTest() {}
+
+ConfigurationPolicyProviderTest::~ConfigurationPolicyProviderTest() {}
+
+void ConfigurationPolicyProviderTest::SetUp() {
+ PolicyTestBase::SetUp();
+
+ test_harness_.reset((*GetParam())());
+ ASSERT_NO_FATAL_FAILURE(test_harness_->SetUp());
+
+ const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ Schema chrome_schema = *schema_registry_.schema_map()->GetSchema(chrome_ns);
+ Schema extension_schema =
+ chrome_schema.GetKnownProperty(test_keys::kKeyDictionary);
+ ASSERT_TRUE(extension_schema.valid());
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+ extension_schema);
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
+ extension_schema);
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "cccccccccccccccccccccccccccccccc"),
+ extension_schema);
+
+ provider_.reset(test_harness_->CreateProvider(
+ &schema_registry_,
+ base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})));
+ provider_->Init(&schema_registry_);
+ // Some providers do a reload on init. Make sure any notifications generated
+ // are fired now.
+ scoped_task_environment_.RunUntilIdle();
+
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(provider_->policies().Equals(kEmptyBundle));
+}
+
+void ConfigurationPolicyProviderTest::TearDown() {
+ // Give providers the chance to clean up after themselves on the file thread.
+ provider_->Shutdown();
+ provider_.reset();
+
+ PolicyTestBase::TearDown();
+}
+
+void ConfigurationPolicyProviderTest::CheckValue(
+ const char* policy_name,
+ const base::Value& expected_value,
+ base::Closure install_value) {
+ // Install the value, reload policy and check the provider for the value.
+ install_value.Run();
+ provider_->RefreshPolicies();
+ scoped_task_environment_.RunUntilIdle();
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(policy_name, test_harness_->policy_level(),
+ test_harness_->policy_scope(), test_harness_->policy_source(),
+ expected_value.CreateDeepCopy(), nullptr);
+ EXPECT_TRUE(provider_->policies().Equals(expected_bundle));
+ // TODO(joaodasilva): set the policy in the POLICY_DOMAIN_EXTENSIONS too,
+ // and extend the |expected_bundle|, once all providers are ready.
+}
+
+TEST_P(ConfigurationPolicyProviderTest, Empty) {
+ provider_->RefreshPolicies();
+ scoped_task_environment_.RunUntilIdle();
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(provider_->policies().Equals(kEmptyBundle));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, StringValue) {
+ const char kTestString[] = "string_value";
+ base::Value expected_value(kTestString);
+ CheckValue(test_keys::kKeyString,
+ expected_value,
+ base::Bind(&PolicyProviderTestHarness::InstallStringPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyString,
+ kTestString));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, BooleanValue) {
+ base::Value expected_value(true);
+ CheckValue(test_keys::kKeyBoolean,
+ expected_value,
+ base::Bind(&PolicyProviderTestHarness::InstallBooleanPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyBoolean,
+ true));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, IntegerValue) {
+ base::Value expected_value(42);
+ CheckValue(test_keys::kKeyInteger,
+ expected_value,
+ base::Bind(&PolicyProviderTestHarness::InstallIntegerPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyInteger,
+ 42));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, StringListValue) {
+ base::ListValue expected_value;
+ expected_value.AppendString("first");
+ expected_value.AppendString("second");
+ CheckValue(test_keys::kKeyStringList,
+ expected_value,
+ base::Bind(&PolicyProviderTestHarness::InstallStringListPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyStringList,
+ &expected_value));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, DictionaryValue) {
+ base::DictionaryValue expected_value;
+ expected_value.SetBoolean("bool", true);
+ expected_value.SetDouble("double", 123.456);
+ expected_value.SetInteger("int", 123);
+ expected_value.SetString("string", "omg");
+
+ auto list = std::make_unique<base::ListValue>();
+ list->AppendString("first");
+ list->AppendString("second");
+ expected_value.Set("array", std::move(list));
+
+ auto dict = std::make_unique<base::DictionaryValue>();
+ dict->SetString("sub", "value");
+ list = std::make_unique<base::ListValue>();
+ auto sub = std::make_unique<base::DictionaryValue>();
+ sub->SetInteger("aaa", 111);
+ sub->SetInteger("bbb", 222);
+ list->Append(std::move(sub));
+ sub = std::make_unique<base::DictionaryValue>();
+ sub->SetString("ccc", "333");
+ sub->SetString("ddd", "444");
+ list->Append(std::move(sub));
+ dict->Set("sublist", std::move(list));
+ expected_value.Set("dictionary", std::move(dict));
+
+ CheckValue(test_keys::kKeyDictionary,
+ expected_value,
+ base::Bind(&PolicyProviderTestHarness::InstallDictionaryPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyDictionary,
+ &expected_value));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, RefreshPolicies) {
+ PolicyBundle bundle;
+ EXPECT_TRUE(provider_->policies().Equals(bundle));
+
+ // OnUpdatePolicy is called even when there are no changes.
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ provider_->RefreshPolicies();
+ scoped_task_environment_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_TRUE(provider_->policies().Equals(bundle));
+
+ // OnUpdatePolicy is called when there are changes.
+ test_harness_->InstallStringPolicy(test_keys::kKeyString, "value");
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ provider_->RefreshPolicies();
+ scoped_task_environment_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(test_keys::kKeyString, test_harness_->policy_level(),
+ test_harness_->policy_scope(), test_harness_->policy_source(),
+ std::make_unique<base::Value>("value"), nullptr);
+ EXPECT_TRUE(provider_->policies().Equals(bundle));
+ provider_->RemoveObserver(&observer);
+}
+
+Configuration3rdPartyPolicyProviderTest::
+ Configuration3rdPartyPolicyProviderTest() {}
+
+Configuration3rdPartyPolicyProviderTest::
+ ~Configuration3rdPartyPolicyProviderTest() {}
+
+TEST_P(Configuration3rdPartyPolicyProviderTest, Load3rdParty) {
+ base::DictionaryValue policy_dict;
+ policy_dict.SetBoolean("bool", true);
+ policy_dict.SetDouble("double", 123.456);
+ policy_dict.SetInteger("int", 789);
+ policy_dict.SetString("string", "string value");
+
+ auto list = std::make_unique<base::ListValue>();
+ for (int i = 0; i < 2; ++i) {
+ auto dict = std::make_unique<base::DictionaryValue>();
+ dict->SetInteger("subdictindex", i);
+ dict->SetKey("subdict", policy_dict.Clone());
+ list->Append(std::move(dict));
+ }
+ policy_dict.Set("list", std::move(list));
+ policy_dict.SetKey("dict", policy_dict.Clone());
+
+ // Install these policies as a Chrome policy.
+ test_harness_->InstallDictionaryPolicy(test_keys::kKeyDictionary,
+ &policy_dict);
+ // Install them as 3rd party policies too.
+ base::DictionaryValue policy_3rdparty;
+ policy_3rdparty.SetPath({"extensions", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
+ policy_dict.Clone());
+ policy_3rdparty.SetPath({"extensions", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},
+ policy_dict.Clone());
+ // Install invalid 3rd party policies that shouldn't be loaded. These also
+ // help detecting memory leaks in the code paths that detect invalid input.
+ policy_3rdparty.SetPath({"invalid-domain", "component"}, policy_dict.Clone());
+ policy_3rdparty.SetString("extensions.cccccccccccccccccccccccccccccccc",
+ "invalid-value");
+ test_harness_->Install3rdPartyPolicy(&policy_3rdparty);
+
+ provider_->RefreshPolicies();
+ scoped_task_environment_.RunUntilIdle();
+
+ PolicyMap expected_policy;
+ expected_policy.Set(test_keys::kKeyDictionary, test_harness_->policy_level(),
+ test_harness_->policy_scope(),
+ test_harness_->policy_source(),
+ policy_dict.CreateDeepCopy(), nullptr);
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(expected_policy);
+ expected_policy.Clear();
+ expected_policy.LoadFrom(&policy_dict,
+ test_harness_->policy_level(),
+ test_harness_->policy_scope(),
+ test_harness_->policy_source());
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
+ .CopyFrom(expected_policy);
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"))
+ .CopyFrom(expected_policy);
+ EXPECT_TRUE(provider_->policies().Equals(expected_bundle));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/configuration_policy_provider_test.h b/components/policy/core/common/configuration_policy_provider_test.h
new file mode 100644
index 0000000000..0d314b3f4c
--- /dev/null
+++ b/components/policy/core/common/configuration_policy_provider_test.h
@@ -0,0 +1,158 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_TEST_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_TEST_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/scoped_task_environment.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class SequencedTaskRunner;
+class Value;
+}
+
+namespace policy {
+
+class ConfigurationPolicyProvider;
+
+namespace test_keys {
+
+extern const char kKeyString[];
+extern const char kKeyBoolean[];
+extern const char kKeyInteger[];
+extern const char kKeyStringList[];
+extern const char kKeyDictionary[];
+
+} // namespace test_keys
+
+class PolicyTestBase : public testing::Test {
+ public:
+ PolicyTestBase();
+ ~PolicyTestBase() override;
+
+ // testing::Test:
+ void SetUp() override;
+ void TearDown() override;
+
+ protected:
+ bool RegisterSchema(const PolicyNamespace& ns,
+ const std::string& schema);
+
+ // Needs to be the first member
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ SchemaRegistry schema_registry_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PolicyTestBase);
+};
+
+// An interface for creating a test policy provider and creating a policy
+// provider instance for testing. Used as the parameter to the abstract
+// ConfigurationPolicyProviderTest below.
+class PolicyProviderTestHarness {
+ public:
+ // |level|, |scope| and |source| are the level, scope and source of the
+ // policies returned by the providers from CreateProvider().
+ PolicyProviderTestHarness(PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source);
+ virtual ~PolicyProviderTestHarness();
+
+ // Actions to run at gtest SetUp() time.
+ virtual void SetUp() = 0;
+
+ // Create a new policy provider.
+ virtual ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) = 0;
+
+ // Returns the policy level, scope and source set by the policy provider.
+ PolicyLevel policy_level() const;
+ PolicyScope policy_scope() const;
+ PolicySource policy_source() const;
+
+ // Helpers to configure the environment the policy provider reads from.
+ virtual void InstallEmptyPolicy() = 0;
+ virtual void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) = 0;
+ virtual void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) = 0;
+ virtual void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) = 0;
+ virtual void InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) = 0;
+ virtual void InstallDictionaryPolicy(
+ const std::string& policy_name,
+ const base::DictionaryValue* policy_value) = 0;
+
+ // Not every provider supports installing 3rd party policy. Those who do
+ // should override this method; the default just makes the test fail.
+ virtual void Install3rdPartyPolicy(const base::DictionaryValue* policies);
+
+ private:
+ PolicyLevel level_;
+ PolicyScope scope_;
+ PolicySource source_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyProviderTestHarness);
+};
+
+// A factory method for creating a test harness.
+typedef PolicyProviderTestHarness* (*CreatePolicyProviderTestHarness)();
+
+// Abstract policy provider test. This is meant to be instantiated for each
+// policy provider implementation, passing in a suitable harness factory
+// function as the test parameter.
+class ConfigurationPolicyProviderTest
+ : public PolicyTestBase,
+ public testing::WithParamInterface<CreatePolicyProviderTestHarness> {
+ protected:
+ ConfigurationPolicyProviderTest();
+ virtual ~ConfigurationPolicyProviderTest();
+
+ void SetUp() override;
+ void TearDown() override;
+
+ // Installs a valid policy and checks whether the provider returns the
+ // |expected_value|.
+ void CheckValue(const char* policy_name,
+ const base::Value& expected_value,
+ base::Closure install_value);
+
+ std::unique_ptr<PolicyProviderTestHarness> test_harness_;
+ std::unique_ptr<ConfigurationPolicyProvider> provider_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConfigurationPolicyProviderTest);
+};
+
+// An extension of ConfigurationPolicyProviderTest that also tests loading of
+// 3rd party policy. Policy provider implementations that support loading of
+// 3rd party policy should also instantiate these tests.
+class Configuration3rdPartyPolicyProviderTest
+ : public ConfigurationPolicyProviderTest {
+ protected:
+ Configuration3rdPartyPolicyProviderTest();
+ virtual ~Configuration3rdPartyPolicyProviderTest();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Configuration3rdPartyPolicyProviderTest);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_TEST_H_
diff --git a/components/policy/core/common/external_data_fetcher.cc b/components/policy/core/common/external_data_fetcher.cc
new file mode 100644
index 0000000000..1bce30a969
--- /dev/null
+++ b/components/policy/core/common/external_data_fetcher.cc
@@ -0,0 +1,45 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/external_data_fetcher.h"
+
+#include "base/callback.h"
+#include "components/policy/core/common/external_data_manager.h"
+
+namespace policy {
+
+ExternalDataFetcher::ExternalDataFetcher(
+ base::WeakPtr<ExternalDataManager> manager,
+ const std::string& policy)
+ : manager_(manager),
+ policy_(policy) {
+}
+
+ExternalDataFetcher::ExternalDataFetcher(const ExternalDataFetcher& other)
+ : manager_(other.manager_),
+ policy_(other.policy_) {
+}
+
+ExternalDataFetcher::~ExternalDataFetcher() {
+}
+
+// static
+bool ExternalDataFetcher::Equals(const ExternalDataFetcher* first,
+ const ExternalDataFetcher* second) {
+ if (!first && !second)
+ return true;
+ if (!first || !second)
+ return false;
+ return first->manager_.get() == second->manager_.get() &&
+ first->policy_ == second->policy_;
+}
+
+void ExternalDataFetcher::Fetch(const FetchCallback& callback) const {
+ if (manager_)
+ manager_->Fetch(policy_, callback);
+ else
+ callback.Run(std::unique_ptr<std::string>());
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/external_data_fetcher.h b/components/policy/core/common/external_data_fetcher.h
new file mode 100644
index 0000000000..0c014e794b
--- /dev/null
+++ b/components/policy/core/common/external_data_fetcher.h
@@ -0,0 +1,53 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_FETCHER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_FETCHER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class ExternalDataManager;
+
+// A helper that encapsulates the parameters required to retrieve the external
+// data for a policy.
+class POLICY_EXPORT ExternalDataFetcher {
+ public:
+ typedef base::Callback<void(std::unique_ptr<std::string>)> FetchCallback;
+
+ // This instance's Fetch() method will instruct the |manager| to retrieve the
+ // external data referenced by the given |policy|.
+ ExternalDataFetcher(base::WeakPtr<ExternalDataManager> manager,
+ const std::string& policy);
+ ExternalDataFetcher(const ExternalDataFetcher& other);
+
+ ~ExternalDataFetcher();
+
+ static bool Equals(const ExternalDataFetcher* first,
+ const ExternalDataFetcher* second);
+
+ // Retrieves the external data referenced by |policy_| and invokes |callback|
+ // with the result. If |policy_| does not reference any external data, the
+ // |callback| is invoked with a NULL pointer. Otherwise, the |callback| is
+ // invoked with the referenced data once it has been successfully retrieved.
+ // If retrieval is temporarily impossible (e.g. no network connectivity), the
+ // |callback| will be invoked when the temporary hindrance is resolved. If
+ // retrieval is permanently impossible (e.g. |policy_| references data that
+ // does not exist on the server), the |callback| will never be invoked.
+ void Fetch(const FetchCallback& callback) const;
+
+ private:
+ base::WeakPtr<ExternalDataManager> manager_;
+ const std::string policy_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_FETCHER_H_
diff --git a/components/policy/core/common/external_data_manager.h b/components/policy/core/common/external_data_manager.h
new file mode 100644
index 0000000000..406cf596bb
--- /dev/null
+++ b/components/policy/core/common/external_data_manager.h
@@ -0,0 +1,36 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_MANAGER_H_
+
+#include <string>
+
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Downloads, verifies, caches and retrieves external data referenced by
+// policies.
+// An implementation of this abstract interface should be provided for each
+// policy source (cloud policy, platform policy) that supports external data
+// references.
+class POLICY_EXPORT ExternalDataManager {
+ public:
+ // Retrieves the external data referenced by |policy| and invokes |callback|
+ // with the result. If |policy| does not reference any external data, the
+ // |callback| is invoked with a NULL pointer. Otherwise, the |callback| is
+ // invoked with the referenced data once it has been successfully retrieved.
+ // If retrieval is temporarily impossible (e.g. no network connectivity), the
+ // |callback| will be invoked when the temporary hindrance is resolved. If
+ // retrieval is permanently impossible (e.g. |policy| references data that
+ // does not exist on the server), the |callback| will never be invoked.
+ virtual void Fetch(const std::string& policy,
+ const ExternalDataFetcher::FetchCallback& callback) = 0;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_MANAGER_H_
diff --git a/components/policy/core/common/fake_async_policy_loader.cc b/components/policy/core/common/fake_async_policy_loader.cc
new file mode 100644
index 0000000000..efa9f7b6b3
--- /dev/null
+++ b/components/policy/core/common/fake_async_policy_loader.cc
@@ -0,0 +1,37 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/fake_async_policy_loader.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/sequenced_task_runner.h"
+
+namespace policy {
+
+FakeAsyncPolicyLoader::FakeAsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner)
+ : AsyncPolicyLoader(task_runner) {
+}
+
+std::unique_ptr<PolicyBundle> FakeAsyncPolicyLoader::Load() {
+ std::unique_ptr<PolicyBundle> result(new PolicyBundle());
+ result->CopyFrom(policy_bundle_);
+ return result;
+}
+
+void FakeAsyncPolicyLoader::InitOnBackgroundThread() {
+ // Nothing to do.
+}
+
+void FakeAsyncPolicyLoader::SetPolicies(const PolicyBundle& policy_bundle) {
+ policy_bundle_.CopyFrom(policy_bundle);
+}
+
+void FakeAsyncPolicyLoader::PostReloadOnBackgroundThread(bool force) {
+ task_runner()->PostTask(FROM_HERE, base::Bind(&AsyncPolicyLoader::Reload,
+ base::Unretained(this), force));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/fake_async_policy_loader.h b/components/policy/core/common/fake_async_policy_loader.h
new file mode 100644
index 0000000000..e1583741fe
--- /dev/null
+++ b/components/policy/core/common/fake_async_policy_loader.h
@@ -0,0 +1,54 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_FAKE_ASYNC_POLICY_LOADER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_FAKE_ASYNC_POLICY_LOADER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace policy {
+
+// Fake AsyncPolicyLoader for testing with test-controlled policies.
+//
+// Typical test code would populate the policy contents via calls to
+// ClearPolicies and AddPolicies and then notify the rest of the policy
+// subsystem of the changes by calling PostReloadOnBackgroundThread.
+class FakeAsyncPolicyLoader : public AsyncPolicyLoader {
+ public:
+ explicit FakeAsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner);
+
+ // Implementation of virtual methods from AsyncPolicyLoader base class.
+ std::unique_ptr<PolicyBundle> Load() override;
+ void InitOnBackgroundThread() override;
+
+ // Provides content for the simulated / faked policies.
+ void SetPolicies(const PolicyBundle& policy_bundle);
+
+ // Notifies the rest of the policy subsystem that policy contents have
+ // changed. This simulates / fakes a notification that normally would be
+ // triggered by a FilePathWatcher or (registry)ObjectWatcher in a real loader.
+ //
+ // See AsyncPolicyLoader::Reload method for description of the |force|
+ // parameter.
+ void PostReloadOnBackgroundThread(bool force);
+
+ private:
+ PolicyBundle policy_bundle_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeAsyncPolicyLoader);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_FAKE_ASYNC_POLICY_LOADER_H_
diff --git a/components/policy/core/common/generate_policy_source_unittest.cc b/components/policy/core/common/generate_policy_source_unittest.cc
new file mode 100644
index 0000000000..78d5cadbd0
--- /dev/null
+++ b/components/policy/core/common/generate_policy_source_unittest.cc
@@ -0,0 +1,227 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstring>
+#include <memory>
+#include <string>
+
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// This unittest tests the code generated by
+// chrome/tools/build/generate_policy_source.py.
+
+namespace policy {
+
+namespace {
+
+#if defined(OS_CHROMEOS)
+// Checks if two schemas are the same or not. Note that this function doesn't
+// consider restrictions on integers and strings nor pattern properties.
+bool IsSameSchema(Schema a, Schema b) {
+ if (a.valid() != b.valid())
+ return false;
+ if (!a.valid())
+ return true;
+ if (a.type() != b.type())
+ return false;
+ if (a.type() == base::Value::Type::LIST)
+ return IsSameSchema(a.GetItems(), b.GetItems());
+ if (a.type() != base::Value::Type::DICTIONARY)
+ return true;
+ Schema::Iterator a_it = a.GetPropertiesIterator();
+ Schema::Iterator b_it = b.GetPropertiesIterator();
+ while (!a_it.IsAtEnd()) {
+ if (b_it.IsAtEnd())
+ return false;
+ if (strcmp(a_it.key(), b_it.key()) != 0)
+ return false;
+ if (!IsSameSchema(a_it.schema(), b_it.schema()))
+ return false;
+ a_it.Advance();
+ b_it.Advance();
+ }
+ if (!b_it.IsAtEnd())
+ return false;
+ return IsSameSchema(a.GetAdditionalProperties(), b.GetAdditionalProperties());
+}
+#endif
+
+} // namespace
+
+TEST(GeneratePolicySource, ChromeSchemaData) {
+ Schema schema = Schema::Wrap(GetChromeSchemaData());
+ ASSERT_TRUE(schema.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema subschema = schema.GetAdditionalProperties();
+ EXPECT_FALSE(subschema.valid());
+
+ subschema = schema.GetProperty("no such policy exists");
+ EXPECT_FALSE(subschema.valid());
+
+ subschema = schema.GetProperty(key::kSearchSuggestEnabled);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, subschema.type());
+
+ subschema = schema.GetProperty(key::kDefaultCookiesSetting);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, subschema.type());
+
+ subschema = schema.GetProperty(key::kProxyMode);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::STRING, subschema.type());
+
+ subschema = schema.GetProperty(key::kURLBlacklist);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::LIST, subschema.type());
+ ASSERT_TRUE(subschema.GetItems().valid());
+ EXPECT_EQ(base::Value::Type::STRING, subschema.GetItems().type());
+
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+ subschema = schema.GetProperty(key::kExtensionSettings);
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, subschema.type());
+ EXPECT_FALSE(subschema.GetAdditionalProperties().valid());
+ EXPECT_FALSE(subschema.GetProperty("no such extension id exists").valid());
+ EXPECT_TRUE(subschema.GetPatternProperties("*").empty());
+ EXPECT_TRUE(subschema.GetPatternProperties("no such extension id").empty());
+ EXPECT_TRUE(subschema.GetPatternProperties("^[a-p]{32}$").empty());
+ EXPECT_TRUE(subschema.GetPatternProperties(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").empty());
+ EXPECT_TRUE(subschema.GetPatternProperties(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").empty());
+ SchemaList schema_list =
+ subschema.GetPatternProperties("abcdefghijklmnopabcdefghijklmnop");
+ ASSERT_EQ(1u, schema_list.size());
+ subschema = schema_list[0];
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, subschema.type());
+ subschema = subschema.GetProperty("installation_mode");
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::STRING, subschema.type());
+
+ subschema = schema.GetProperty(key::kExtensionSettings).GetProperty("*");
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, subschema.type());
+ subschema = subschema.GetProperty("installation_mode");
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::STRING, subschema.type());
+#endif
+
+ subschema = schema.GetProperty(key::kProxySettings);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, subschema.type());
+ EXPECT_FALSE(subschema.GetAdditionalProperties().valid());
+ EXPECT_FALSE(subschema.GetProperty("no such proxy key exists").valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyMode).valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyServer).valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyServerMode).valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyPacUrl).valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyBypassList).valid());
+
+ // Verify that all the Chrome policies are there.
+ for (Schema::Iterator it = schema.GetPropertiesIterator();
+ !it.IsAtEnd(); it.Advance()) {
+ EXPECT_TRUE(it.key());
+ EXPECT_FALSE(std::string(it.key()).empty());
+ EXPECT_TRUE(GetChromePolicyDetails(it.key()));
+ }
+
+ // The properties are iterated in order.
+ const char* kExpectedProperties[] = {
+ key::kProxyBypassList, key::kProxyMode, key::kProxyPacUrl,
+ key::kProxyServer, key::kProxyServerMode, nullptr,
+ };
+ const char** next = kExpectedProperties;
+ for (Schema::Iterator it(subschema.GetPropertiesIterator());
+ !it.IsAtEnd(); it.Advance(), ++next) {
+ ASSERT_TRUE(*next != nullptr);
+ EXPECT_STREQ(*next, it.key());
+ ASSERT_TRUE(it.schema().valid());
+ EXPECT_EQ(base::Value::Type::STRING, it.schema().type());
+ }
+ EXPECT_TRUE(*next == nullptr);
+
+#if defined(OS_CHROMEOS)
+ subschema = schema.GetKnownProperty(key::kPowerManagementIdleSettings);
+ ASSERT_TRUE(subschema.valid());
+
+ EXPECT_TRUE(IsSameSchema(subschema.GetKnownProperty("AC"),
+ subschema.GetKnownProperty("Battery")));
+
+ subschema = schema.GetKnownProperty(key::kDeviceLoginScreenPowerManagement);
+ ASSERT_TRUE(subschema.valid());
+
+ EXPECT_TRUE(IsSameSchema(subschema.GetKnownProperty("AC"),
+ subschema.GetKnownProperty("Battery")));
+#endif
+}
+
+TEST(GeneratePolicySource, PolicyDetails) {
+ EXPECT_FALSE(GetChromePolicyDetails(""));
+ EXPECT_FALSE(GetChromePolicyDetails("no such policy"));
+ EXPECT_FALSE(GetChromePolicyDetails("SearchSuggestEnable"));
+ EXPECT_FALSE(GetChromePolicyDetails("searchSuggestEnabled"));
+ EXPECT_FALSE(GetChromePolicyDetails("SSearchSuggestEnabled"));
+
+ const PolicyDetails* details =
+ GetChromePolicyDetails(key::kSearchSuggestEnabled);
+ ASSERT_TRUE(details);
+ EXPECT_FALSE(details->is_deprecated);
+ EXPECT_FALSE(details->is_device_policy);
+ EXPECT_EQ(6, details->id);
+ EXPECT_EQ(0u, details->max_external_data_size);
+
+#if !defined(OS_IOS)
+ details = GetChromePolicyDetails(key::kJavascriptEnabled);
+ ASSERT_TRUE(details);
+ EXPECT_TRUE(details->is_deprecated);
+ EXPECT_FALSE(details->is_device_policy);
+ EXPECT_EQ(9, details->id);
+ EXPECT_EQ(0u, details->max_external_data_size);
+#endif
+
+#if defined(OS_CHROMEOS)
+ details = GetChromePolicyDetails(key::kDevicePolicyRefreshRate);
+ ASSERT_TRUE(details);
+ EXPECT_FALSE(details->is_deprecated);
+ EXPECT_TRUE(details->is_device_policy);
+ EXPECT_EQ(90, details->id);
+ EXPECT_EQ(0u, details->max_external_data_size);
+#endif
+
+ // TODO(bartfab): add a test that verifies a max_external_data_size larger
+ // than 0, once a type 'external' policy is added.
+}
+
+#if defined(OS_CHROMEOS)
+TEST(GeneratePolicySource, SetEnterpriseDefaults) {
+ PolicyMap policy_map;
+
+ // If policy not configured yet, set the enterprise default.
+ SetEnterpriseUsersDefaults(&policy_map);
+
+ const base::Value* multiprof_behavior =
+ policy_map.GetValue(key::kChromeOsMultiProfileUserBehavior);
+ base::Value expected("primary-only");
+ EXPECT_TRUE(expected.Equals(multiprof_behavior));
+
+ // If policy already configured, it's not changed to enterprise defaults.
+ policy_map.Set(key::kChromeOsMultiProfileUserBehavior, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>("test_value"), nullptr);
+ SetEnterpriseUsersDefaults(&policy_map);
+ multiprof_behavior =
+ policy_map.GetValue(key::kChromeOsMultiProfileUserBehavior);
+ expected = base::Value("test_value");
+ EXPECT_TRUE(expected.Equals(multiprof_behavior));
+}
+#endif
+
+} // namespace policy
diff --git a/components/policy/core/common/mock_configuration_policy_provider.cc b/components/policy/core/common/mock_configuration_policy_provider.cc
new file mode 100644
index 0000000000..ae8458a226
--- /dev/null
+++ b/components/policy/core/common/mock_configuration_policy_provider.cc
@@ -0,0 +1,48 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/message_loop/message_loop_current.h"
+#include "base/run_loop.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+using testing::Invoke;
+
+namespace policy {
+
+MockConfigurationPolicyProvider::MockConfigurationPolicyProvider() {}
+
+MockConfigurationPolicyProvider::~MockConfigurationPolicyProvider() {}
+
+void MockConfigurationPolicyProvider::UpdateChromePolicy(
+ const PolicyMap& policy) {
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(policy);
+ UpdatePolicy(std::move(bundle));
+ if (base::MessageLoopCurrent::IsSet())
+ base::RunLoop().RunUntilIdle();
+}
+
+void MockConfigurationPolicyProvider::SetAutoRefresh() {
+ EXPECT_CALL(*this, RefreshPolicies()).WillRepeatedly(
+ Invoke(this, &MockConfigurationPolicyProvider::RefreshWithSamePolicies));
+}
+
+void MockConfigurationPolicyProvider::RefreshWithSamePolicies() {
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle);
+ bundle->CopyFrom(policies());
+ UpdatePolicy(std::move(bundle));
+}
+
+MockConfigurationPolicyObserver::MockConfigurationPolicyObserver() {}
+
+MockConfigurationPolicyObserver::~MockConfigurationPolicyObserver() {}
+
+} // namespace policy
diff --git a/components/policy/core/common/mock_configuration_policy_provider.h b/components/policy/core/common/mock_configuration_policy_provider.h
new file mode 100644
index 0000000000..84f664c71d
--- /dev/null
+++ b/components/policy/core/common/mock_configuration_policy_provider.h
@@ -0,0 +1,66 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MOCK_CONFIGURATION_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MOCK_CONFIGURATION_POLICY_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+// Mock ConfigurationPolicyProvider implementation that supplies canned
+// values for polices.
+// TODO(joaodasilva, mnissler): introduce an implementation that non-policy
+// code can use that doesn't require the usual boilerplate.
+// http://crbug.com/242087
+class MockConfigurationPolicyProvider : public ConfigurationPolicyProvider {
+ public:
+ MockConfigurationPolicyProvider();
+ ~MockConfigurationPolicyProvider() override;
+
+ MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
+ MOCK_METHOD0(RefreshPolicies, void());
+
+ // Make public for tests.
+ using ConfigurationPolicyProvider::UpdatePolicy;
+
+ // Utility method that invokes UpdatePolicy() with a PolicyBundle that maps
+ // the Chrome namespace to a copy of |policy|.
+ void UpdateChromePolicy(const PolicyMap& policy);
+
+ // Convenience method so that tests don't need to create a registry to create
+ // this mock.
+ using ConfigurationPolicyProvider::Init;
+ void Init() {
+ ConfigurationPolicyProvider::Init(&registry_);
+ }
+
+ // Convenience method that installs an expectation on RefreshPolicies that
+ // just notifies the observers and serves the same policies.
+ void SetAutoRefresh();
+
+ private:
+ void RefreshWithSamePolicies();
+
+ SchemaRegistry registry_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockConfigurationPolicyProvider);
+};
+
+class MockConfigurationPolicyObserver
+ : public ConfigurationPolicyProvider::Observer {
+ public:
+ MockConfigurationPolicyObserver();
+ ~MockConfigurationPolicyObserver() override;
+
+ MOCK_METHOD1(OnUpdatePolicy, void(ConfigurationPolicyProvider*));
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MOCK_CONFIGURATION_POLICY_PROVIDER_H_
diff --git a/components/policy/core/common/mock_policy_service.cc b/components/policy/core/common/mock_policy_service.cc
new file mode 100644
index 0000000000..6e3e0c1cbf
--- /dev/null
+++ b/components/policy/core/common/mock_policy_service.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/mock_policy_service.h"
+
+namespace policy {
+
+MockPolicyServiceObserver::MockPolicyServiceObserver() {
+}
+
+MockPolicyServiceObserver::~MockPolicyServiceObserver() {
+}
+
+MockPolicyService::MockPolicyService() {
+}
+
+MockPolicyService::~MockPolicyService() {
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/mock_policy_service.h b/components/policy/core/common/mock_policy_service.h
new file mode 100644
index 0000000000..3352860b28
--- /dev/null
+++ b/components/policy/core/common/mock_policy_service.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
+
+#include "components/policy/core/common/policy_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class MockPolicyServiceObserver : public PolicyService::Observer {
+ public:
+ MockPolicyServiceObserver();
+ ~MockPolicyServiceObserver() override;
+
+ MOCK_METHOD3(OnPolicyUpdated, void(const PolicyNamespace&,
+ const PolicyMap& previous,
+ const PolicyMap& current));
+ MOCK_METHOD1(OnPolicyServiceInitialized, void(PolicyDomain));
+};
+
+class MockPolicyService : public PolicyService {
+ public:
+ MockPolicyService();
+ ~MockPolicyService() override;
+
+ MOCK_METHOD2(AddObserver, void(PolicyDomain, Observer*));
+ MOCK_METHOD2(RemoveObserver, void(PolicyDomain, Observer*));
+
+ MOCK_CONST_METHOD1(GetPolicies, const PolicyMap&(const PolicyNamespace&));
+ MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
+ MOCK_METHOD1(RefreshPolicies, void(const base::Closure&));
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
diff --git a/components/policy/core/common/plist_writer.cc b/components/policy/core/common/plist_writer.cc
new file mode 100644
index 0000000000..68c1db5494
--- /dev/null
+++ b/components/policy/core/common/plist_writer.cc
@@ -0,0 +1,91 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/plist_writer.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "third_party/libxml/chromium/libxml_utils.h"
+
+namespace policy {
+
+namespace {
+
+// Called recursively to build the Plist xml. When completed,
+// |plist_writer| will contain the Plist. Return true on success and false on
+// failure.
+bool BuildPlistString(const base::Value& node, XmlWriter& plist_writer) {
+ switch (node.type()) {
+ case base::Value::Type::BOOLEAN: {
+ bool value = node.GetBool();
+ plist_writer.StartElement(value ? "true" : "false");
+ plist_writer.EndElement();
+ return true;
+ }
+
+ case base::Value::Type::INTEGER: {
+ int value = node.GetInt();
+ plist_writer.WriteElement("integer", base::IntToString(value));
+ return true;
+ }
+
+ case base::Value::Type::STRING: {
+ std::string value = node.GetString();
+ plist_writer.WriteElement("string", value);
+ return true;
+ }
+
+ case base::Value::Type::LIST: {
+ plist_writer.StartElement("array");
+
+ for (const auto& value : node.GetList()) {
+ if (!BuildPlistString(value, plist_writer))
+ return false;
+ }
+
+ plist_writer.EndElement();
+ return true;
+ }
+
+ case base::Value::Type::DICTIONARY: {
+ plist_writer.StartElement("dict");
+
+ const base::DictionaryValue* dict = nullptr;
+ bool result = node.GetAsDictionary(&dict);
+ DCHECK(result);
+ for (base::DictionaryValue::Iterator itr(*dict); !itr.IsAtEnd();
+ itr.Advance()) {
+ plist_writer.WriteElement("key", itr.key());
+
+ if (!BuildPlistString(itr.value(), plist_writer))
+ result = false;
+ }
+
+ plist_writer.EndElement();
+ return result;
+ }
+
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+} // namespace
+
+bool PlistWrite(const base::Value& node, std::string* plist) {
+ // Where we write Plist data as we generate it.
+ XmlWriter plist_writer;
+ plist_writer.StartWriting();
+ plist_writer.StartIndenting();
+ plist_writer.StartElement("plist");
+ bool result = BuildPlistString(node, plist_writer);
+ plist_writer.EndElement();
+ plist_writer.StopWriting();
+
+ *plist = plist_writer.GetWrittenString();
+ return result;
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/plist_writer.h b/components/policy/core/common/plist_writer.h
new file mode 100644
index 0000000000..0ad82235f7
--- /dev/null
+++ b/components/policy/core/common/plist_writer.h
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_PLIST_WRITER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_PLIST_WRITER_H_
+
+#include <stddef.h>
+#include <string>
+#include "base/values.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Given a root node, generates a Plist string and puts it into |plist|.
+// The output string is overwritten and not appended.
+// Return true on success and false on failure.
+// TODO(rodmartin): Should we generate plist if it would be invalid plist
+// (e.g., |node| is not a DictionaryValue/ListValue or if there are inf/-inf
+// float values)?
+POLICY_EXPORT bool PlistWrite(const base::Value& node, std::string* plist);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_Plist_WRITER_H_ \ No newline at end of file
diff --git a/components/policy/core/common/plist_writer_unittest.cc b/components/policy/core/common/plist_writer_unittest.cc
new file mode 100644
index 0000000000..0d89d0aa83
--- /dev/null
+++ b/components/policy/core/common/plist_writer_unittest.cc
@@ -0,0 +1,135 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/plist_writer.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/strcat.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char kPlistHeaderXML[] = "<?xml version=\"1.0\"?>";
+const char kPlistHeaderVersion[] = "<plist>";
+const char kPlistFooter[] = "</plist>\n";
+
+#define PLIST_NEWLINE "\n"
+
+} // namespace
+
+class PlistWriterTest : public testing::Test {
+ public:
+ PlistWriterTest();
+ ~PlistWriterTest() override;
+
+ void SetUp() override;
+
+ std::string header_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PlistWriterTest);
+};
+
+PlistWriterTest::PlistWriterTest() {}
+
+PlistWriterTest::~PlistWriterTest() {}
+
+void PlistWriterTest::SetUp() {
+ header_ = base::StrCat(
+ {kPlistHeaderXML, PLIST_NEWLINE, kPlistHeaderVersion, PLIST_NEWLINE});
+}
+
+TEST_F(PlistWriterTest, BasicTypes) {
+ std::string output_plist;
+
+ // Test empty dict.
+ EXPECT_TRUE(PlistWrite(base::DictionaryValue(), &output_plist));
+ EXPECT_EQ(base::StrCat({header_, " <dict/>", PLIST_NEWLINE, kPlistFooter}),
+ output_plist);
+
+ // Test empty list.
+ EXPECT_TRUE(PlistWrite(base::ListValue(), &output_plist));
+ EXPECT_EQ(base::StrCat({header_, " <array/>", PLIST_NEWLINE, kPlistFooter}),
+ output_plist);
+
+ // Test integer values.
+ EXPECT_TRUE(PlistWrite(base::Value(42), &output_plist));
+ EXPECT_EQ(base::StrCat({header_, " <integer>42</integer>", PLIST_NEWLINE,
+ kPlistFooter}),
+ output_plist);
+
+ // Test boolean values.
+ EXPECT_TRUE(PlistWrite(base::Value(true), &output_plist));
+ EXPECT_EQ(base::StrCat({header_, " <true/>", PLIST_NEWLINE, kPlistFooter}),
+ output_plist);
+
+ // Test string values.
+ EXPECT_TRUE(PlistWrite(base::Value("foo"), &output_plist));
+ EXPECT_EQ(base::StrCat({header_, " <string>foo</string>", PLIST_NEWLINE,
+ kPlistFooter}),
+ output_plist);
+}
+
+TEST_F(PlistWriterTest, NestedTypes) {
+ std::string output_plist;
+
+ // Writer unittests like empty list/dict nesting,
+ // list list nesting, etc.
+ base::DictionaryValue root_dict;
+ std::unique_ptr<base::ListValue> list(new base::ListValue());
+ std::unique_ptr<base::DictionaryValue> inner_dict(
+ new base::DictionaryValue());
+ inner_dict->SetInteger("inner int", 10);
+ list->Append(std::move(inner_dict));
+ list->Append(std::make_unique<base::ListValue>());
+ list->AppendBoolean(false);
+ root_dict.Set("list", std::move(list));
+
+ EXPECT_TRUE(PlistWrite(root_dict, &output_plist));
+ EXPECT_EQ(base::StrCat({header_, " <dict>",
+ PLIST_NEWLINE, " <key>list</key>",
+ PLIST_NEWLINE, " <array>",
+ PLIST_NEWLINE, " <dict>",
+ PLIST_NEWLINE, " <key>inner int</key>",
+ PLIST_NEWLINE, " <integer>10</integer>",
+ PLIST_NEWLINE, " </dict>",
+ PLIST_NEWLINE, " <array/>",
+ PLIST_NEWLINE, " <false/>",
+ PLIST_NEWLINE, " </array>",
+ PLIST_NEWLINE, " </dict>",
+ PLIST_NEWLINE, kPlistFooter}),
+ output_plist);
+}
+
+TEST_F(PlistWriterTest, KeysWithPeriods) {
+ std::string output_plist;
+
+ base::DictionaryValue period_dict;
+ period_dict.SetKey("a.b", base::Value(3));
+ period_dict.SetKey("c", base::Value(2));
+ std::unique_ptr<base::DictionaryValue> period_dict2(
+ new base::DictionaryValue());
+ period_dict2->SetKey("g.h.i.j", base::Value(1));
+ period_dict.SetWithoutPathExpansion("d.e.f", std::move(period_dict2));
+ EXPECT_TRUE(PlistWrite(period_dict, &output_plist));
+ EXPECT_EQ(base::StrCat({header_, " <dict>",
+ PLIST_NEWLINE, " <key>a.b</key>",
+ PLIST_NEWLINE, " <integer>3</integer>",
+ PLIST_NEWLINE, " <key>c</key>",
+ PLIST_NEWLINE, " <integer>2</integer>",
+ PLIST_NEWLINE, " <key>d.e.f</key>",
+ PLIST_NEWLINE, " <dict>",
+ PLIST_NEWLINE, " <key>g.h.i.j</key>",
+ PLIST_NEWLINE, " <integer>1</integer>",
+ PLIST_NEWLINE, " </dict>",
+ PLIST_NEWLINE, " </dict>",
+ PLIST_NEWLINE, kPlistFooter}),
+ output_plist);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_bundle.cc b/components/policy/core/common/policy_bundle.cc
new file mode 100644
index 0000000000..3d9d1584a2
--- /dev/null
+++ b/components/policy/core/common/policy_bundle.cc
@@ -0,0 +1,104 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_bundle.h"
+
+#include "base/logging.h"
+
+namespace policy {
+
+PolicyBundle::PolicyBundle() {}
+
+PolicyBundle::~PolicyBundle() {
+ Clear();
+}
+
+PolicyMap& PolicyBundle::Get(const PolicyNamespace& ns) {
+ DCHECK(ns.domain != POLICY_DOMAIN_CHROME || ns.component_id.empty());
+ std::unique_ptr<PolicyMap>& policy = policy_bundle_[ns];
+ if (!policy)
+ policy = std::make_unique<PolicyMap>();
+ return *policy;
+}
+
+const PolicyMap& PolicyBundle::Get(const PolicyNamespace& ns) const {
+ DCHECK(ns.domain != POLICY_DOMAIN_CHROME || ns.component_id.empty());
+ const_iterator it = policy_bundle_.find(ns);
+ return it == end() ? kEmpty_ : *it->second;
+}
+
+void PolicyBundle::Swap(PolicyBundle* other) {
+ policy_bundle_.swap(other->policy_bundle_);
+}
+
+void PolicyBundle::CopyFrom(const PolicyBundle& other) {
+ Clear();
+ for (auto it = other.begin(); it != other.end(); ++it) {
+ policy_bundle_[it->first] = it->second->DeepCopy();
+ }
+}
+
+void PolicyBundle::MergeFrom(const PolicyBundle& other) {
+ // Iterate over both |this| and |other| in order; skip what's extra in |this|,
+ // add what's missing, and merge the namespaces in common.
+ MapType::iterator it_this = policy_bundle_.begin();
+ MapType::iterator end_this = policy_bundle_.end();
+ const_iterator it_other = other.begin();
+ const_iterator end_other = other.end();
+
+ while (it_this != end_this && it_other != end_other) {
+ if (it_this->first == it_other->first) {
+ // Same namespace: merge existing PolicyMaps.
+ it_this->second->MergeFrom(*it_other->second);
+ ++it_this;
+ ++it_other;
+ } else if (it_this->first < it_other->first) {
+ // |this| has a PolicyMap that |other| doesn't; skip it.
+ ++it_this;
+ } else if (it_other->first < it_this->first) {
+ // |other| has a PolicyMap that |this| doesn't; copy it.
+ policy_bundle_[it_other->first] = it_other->second->DeepCopy();
+ ++it_other;
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ // Add extra PolicyMaps at the end.
+ while (it_other != end_other) {
+ policy_bundle_[it_other->first] = it_other->second->DeepCopy();
+ ++it_other;
+ }
+}
+
+bool PolicyBundle::Equals(const PolicyBundle& other) const {
+ // Equals() has the peculiarity that an entry with an empty PolicyMap equals
+ // an non-existent entry. This handles usage of non-const Get() that doesn't
+ // insert any policies.
+ const_iterator it_this = begin();
+ const_iterator it_other = other.begin();
+
+ while (true) {
+ // Skip empty PolicyMaps.
+ while (it_this != end() && it_this->second->empty())
+ ++it_this;
+ while (it_other != other.end() && it_other->second->empty())
+ ++it_other;
+ if (it_this == end() || it_other == other.end())
+ break;
+ if (it_this->first != it_other->first ||
+ !it_this->second->Equals(*it_other->second)) {
+ return false;
+ }
+ ++it_this;
+ ++it_other;
+ }
+ return it_this == end() && it_other == other.end();
+}
+
+void PolicyBundle::Clear() {
+ policy_bundle_.clear();
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_bundle.h b/components/policy/core/common/policy_bundle.h
new file mode 100644
index 0000000000..8a395323ff
--- /dev/null
+++ b/components/policy/core/common/policy_bundle.h
@@ -0,0 +1,74 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_BUNDLE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_BUNDLE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Maps policy namespaces to PolicyMaps.
+class POLICY_EXPORT PolicyBundle {
+ public:
+ using MapType = std::map<PolicyNamespace, std::unique_ptr<PolicyMap>>;
+ using iterator = MapType::iterator;
+ using const_iterator = MapType::const_iterator;
+
+ PolicyBundle();
+ virtual ~PolicyBundle();
+
+ // Returns the PolicyMap for namespace |ns|. Creates a new map if necessary.
+ PolicyMap& Get(const PolicyNamespace& ns);
+ const PolicyMap& Get(const PolicyNamespace& ns) const;
+
+ // Swaps the internal representation of |this| with |other|.
+ void Swap(PolicyBundle* other);
+
+ // |this| becomes a copy of |other|. Any existing PolicyMaps are dropped.
+ void CopyFrom(const PolicyBundle& other);
+
+ // Merges the PolicyMaps of |this| with those of |other| for each namespace
+ // in common. Also adds copies of the (namespace, PolicyMap) pairs in |other|
+ // that don't have an entry in |this|.
+ // Each policy in each PolicyMap is replaced only if the policy from |other|
+ // has a higher priority.
+ // See PolicyMap::MergeFrom for details on merging individual PolicyMaps.
+ void MergeFrom(const PolicyBundle& other);
+
+ // Returns true if |other| has the same keys and value as |this|.
+ bool Equals(const PolicyBundle& other) const;
+
+ // Returns iterators to the beginning and end of the underlying container.
+ iterator begin() { return policy_bundle_.begin(); }
+ iterator end() { return policy_bundle_.end(); }
+
+ // These can be used to iterate over and read the PolicyMaps, but not to
+ // modify them.
+ const_iterator begin() const { return policy_bundle_.begin(); }
+ const_iterator end() const { return policy_bundle_.end(); }
+
+ // Erases all the existing pairs.
+ void Clear();
+
+ private:
+ MapType policy_bundle_;
+
+ // An empty PolicyMap that is returned by const Get() for namespaces that
+ // do not exist in |policy_bundle_|.
+ const PolicyMap kEmpty_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyBundle);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_BUNDLE_H_
diff --git a/components/policy/core/common/policy_bundle_unittest.cc b/components/policy/core/common/policy_bundle_unittest.cc
new file mode 100644
index 0000000000..7ac3bd13e5
--- /dev/null
+++ b/components/policy/core/common/policy_bundle_unittest.cc
@@ -0,0 +1,263 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_bundle.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char kPolicyClashing0[] = "policy-clashing-0";
+const char kPolicyClashing1[] = "policy-clashing-1";
+const char kPolicy0[] = "policy-0";
+const char kPolicy1[] = "policy-1";
+const char kPolicy2[] = "policy-2";
+const char kExtension0[] = "extension-0";
+const char kExtension1[] = "extension-1";
+const char kExtension2[] = "extension-2";
+const char kExtension3[] = "extension-3";
+
+// Adds test policies to |policy|.
+void AddTestPolicies(PolicyMap* policy) {
+ policy->Set("mandatory-user", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(123), nullptr);
+ policy->Set("mandatory-machine", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("omg"),
+ nullptr);
+ policy->Set("recommended-user", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true),
+ nullptr);
+ std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
+ dict->SetBoolean("false", false);
+ dict->SetInteger("int", 456);
+ dict->SetString("str", "bbq");
+ policy->Set("recommended-machine", POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD, std::move(dict),
+ nullptr);
+}
+
+// Adds test policies to |policy| based on the parameters:
+// - kPolicyClashing0 mapped to |value|, user mandatory
+// - kPolicyClashing1 mapped to |value|, with |level| and |scope|
+// - |name| mapped to |value|, user mandatory
+void AddTestPoliciesWithParams(PolicyMap *policy,
+ const char* name,
+ int value,
+ PolicyLevel level,
+ PolicyScope scope) {
+ policy->Set(kPolicyClashing0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(value),
+ nullptr);
+ policy->Set(kPolicyClashing1, level, scope, POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(value), nullptr);
+ policy->Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(value),
+ nullptr);
+}
+
+// Returns true if |bundle| is empty.
+bool IsEmpty(const PolicyBundle& bundle) {
+ return bundle.begin() == bundle.end();
+}
+
+} // namespace
+
+TEST(PolicyBundleTest, Get) {
+ PolicyBundle bundle;
+ EXPECT_TRUE(IsEmpty(bundle));
+
+ AddTestPolicies(&bundle.Get(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+ EXPECT_FALSE(IsEmpty(bundle));
+
+ PolicyMap policy;
+ AddTestPolicies(&policy);
+ EXPECT_TRUE(bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(policy));
+
+ PolicyBundle::const_iterator it = bundle.begin();
+ ASSERT_TRUE(it != bundle.end());
+ EXPECT_EQ(POLICY_DOMAIN_CHROME, it->first.domain);
+ EXPECT_EQ("", it->first.component_id);
+ ASSERT_TRUE(it->second);
+ EXPECT_TRUE(it->second->Equals(policy));
+ ++it;
+ EXPECT_TRUE(it == bundle.end());
+ EXPECT_TRUE(bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).empty());
+
+ EXPECT_FALSE(IsEmpty(bundle));
+ bundle.Clear();
+ EXPECT_TRUE(IsEmpty(bundle));
+}
+
+TEST(PolicyBundleTest, SwapAndCopy) {
+ PolicyBundle bundle0;
+ PolicyBundle bundle1;
+
+ AddTestPolicies(&bundle0.Get(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+ AddTestPolicies(&bundle0.Get(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0)));
+ EXPECT_FALSE(IsEmpty(bundle0));
+ EXPECT_TRUE(IsEmpty(bundle1));
+
+ PolicyMap policy;
+ AddTestPolicies(&policy);
+ EXPECT_TRUE(bundle0.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(policy));
+ EXPECT_TRUE(bundle0.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).Equals(policy));
+
+ bundle0.Swap(&bundle1);
+ EXPECT_TRUE(IsEmpty(bundle0));
+ EXPECT_FALSE(IsEmpty(bundle1));
+
+ EXPECT_TRUE(bundle1.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(policy));
+ EXPECT_TRUE(bundle1.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).Equals(policy));
+
+ bundle0.CopyFrom(bundle1);
+ EXPECT_FALSE(IsEmpty(bundle0));
+ EXPECT_FALSE(IsEmpty(bundle1));
+ EXPECT_TRUE(bundle0.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(policy));
+ EXPECT_TRUE(bundle0.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).Equals(policy));
+}
+
+TEST(PolicyBundleTest, MergeFrom) {
+ // Each bundleN has kExtensionN. Each bundle also has policy for
+ // chrome and kExtension3.
+ // |bundle0| has the highest priority, |bundle2| the lowest.
+ PolicyBundle bundle0;
+ PolicyBundle bundle1;
+ PolicyBundle bundle2;
+
+ PolicyMap policy0;
+ AddTestPoliciesWithParams(
+ &policy0, kPolicy0, 0u, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER);
+ bundle0.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(policy0);
+ bundle0.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0))
+ .CopyFrom(policy0);
+ bundle0.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension3))
+ .CopyFrom(policy0);
+
+ PolicyMap policy1;
+ AddTestPoliciesWithParams(
+ &policy1, kPolicy1, 1u, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
+ bundle1.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(policy1);
+ bundle1.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1))
+ .CopyFrom(policy1);
+ bundle1.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension3))
+ .CopyFrom(policy1);
+
+ PolicyMap policy2;
+ AddTestPoliciesWithParams(
+ &policy2, kPolicy2, 2u, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
+ bundle2.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(policy2);
+ bundle2.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2))
+ .CopyFrom(policy2);
+ bundle2.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension3))
+ .CopyFrom(policy2);
+
+ // Merge in order of decreasing priority.
+ PolicyBundle merged;
+ merged.MergeFrom(bundle0);
+ merged.MergeFrom(bundle1);
+ merged.MergeFrom(bundle2);
+ PolicyBundle empty_bundle;
+ merged.MergeFrom(empty_bundle);
+
+ // chrome and kExtension3 policies are merged:
+ // - kPolicyClashing0 comes from bundle0, which has the highest priority;
+ // - kPolicyClashing1 comes from bundle1, which has the highest level/scope
+ // combination;
+ // - kPolicyN are merged from each bundle.
+ PolicyMap expected;
+ expected.Set(kPolicyClashing0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(0), nullptr);
+ expected.Set(kPolicyClashing1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1), nullptr);
+ expected.Set(kPolicy0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(0), nullptr);
+ expected.Set(kPolicy1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1), nullptr);
+ expected.Set(kPolicy2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(2), nullptr);
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(expected));
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension3)).Equals(expected));
+ // extension0 comes only from bundle0.
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).Equals(policy0));
+ // extension1 comes only from bundle1.
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension1)).Equals(policy1));
+ // extension2 comes only from bundle2.
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension2)).Equals(policy2));
+}
+
+TEST(PolicyBundleTest, Equals) {
+ PolicyBundle bundle;
+ AddTestPolicies(&bundle.Get(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+ AddTestPolicies(&bundle.Get(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0)));
+
+ PolicyBundle other;
+ EXPECT_FALSE(bundle.Equals(other));
+ other.CopyFrom(bundle);
+ EXPECT_TRUE(bundle.Equals(other));
+
+ AddTestPolicies(&bundle.Get(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1)));
+ EXPECT_FALSE(bundle.Equals(other));
+ other.CopyFrom(bundle);
+ EXPECT_TRUE(bundle.Equals(other));
+ AddTestPolicies(&other.Get(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2)));
+ EXPECT_FALSE(bundle.Equals(other));
+
+ other.CopyFrom(bundle);
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(kPolicy0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(123), nullptr);
+ EXPECT_FALSE(bundle.Equals(other));
+ other.CopyFrom(bundle);
+ EXPECT_TRUE(bundle.Equals(other));
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(kPolicy0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(123), nullptr);
+ EXPECT_FALSE(bundle.Equals(other));
+
+ // Test non-const Get().
+ bundle.Clear();
+ other.Clear();
+ PolicyMap& policy_map =
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ EXPECT_TRUE(bundle.Equals(other));
+ policy_map.Set(kPolicy0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(123),
+ nullptr);
+ EXPECT_FALSE(bundle.Equals(other));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_details.h b/components/policy/core/common/policy_details.h
new file mode 100644
index 0000000000..66e26d0e1a
--- /dev/null
+++ b/components/policy/core/common/policy_details.h
@@ -0,0 +1,48 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_DETAILS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_DETAILS_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/risk_tag.h"
+
+namespace policy {
+
+// Contains read-only metadata about a Chrome policy.
+struct POLICY_EXPORT PolicyDetails {
+ // True if this policy has been deprecated.
+ bool is_deprecated;
+
+ // True if this policy is a Chrome OS device policy.
+ bool is_device_policy;
+
+ // The id of the protobuf field that contains this policy,
+ // in the cloud policy protobuf.
+ int id;
+
+ // If this policy references external data then this is the maximum size
+ // allowed for that data.
+ // Otherwise this field is 0 and doesn't have any meaning.
+ size_t max_external_data_size;
+
+ // Contains tags that describe impact on a user's privacy or security.
+ RiskTag risk_tags[kMaxRiskTagCount];
+};
+
+// A typedef for functions that match the signature of
+// GetChromePolicyDetails(). This can be used to inject that
+// function into objects, so that it can be easily mocked for
+// tests.
+typedef base::Callback<const PolicyDetails*(const std::string&)>
+ GetChromePolicyDetailsCallback;
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_DETAILS_H_
diff --git a/components/policy/core/common/policy_map.cc b/components/policy/core/common/policy_map.cc
new file mode 100644
index 0000000000..50750bbd62
--- /dev/null
+++ b/components/policy/core/common/policy_map.cc
@@ -0,0 +1,234 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_map.h"
+
+#include <algorithm>
+
+#include "base/callback.h"
+#include "base/stl_util.h"
+
+namespace policy {
+
+PolicyMap::Entry::Entry() = default;
+
+PolicyMap::Entry::~Entry() = default;
+
+PolicyMap::Entry::Entry(Entry&&) noexcept = default;
+PolicyMap::Entry& PolicyMap::Entry::operator=(Entry&&) noexcept = default;
+
+PolicyMap::Entry PolicyMap::Entry::DeepCopy() const {
+ Entry copy;
+ copy.level = level;
+ copy.scope = scope;
+ copy.source = source;
+ if (value)
+ copy.value = value->CreateDeepCopy();
+ copy.error = error;
+ if (external_data_fetcher) {
+ copy.external_data_fetcher.reset(
+ new ExternalDataFetcher(*external_data_fetcher));
+ }
+ return copy;
+}
+
+bool PolicyMap::Entry::has_higher_priority_than(
+ const PolicyMap::Entry& other) const {
+ if (level != other.level)
+ return level > other.level;
+
+ if (scope != other.scope)
+ return scope > other.scope;
+
+ return source > other.source;
+}
+
+bool PolicyMap::Entry::Equals(const PolicyMap::Entry& other) const {
+ return level == other.level && scope == other.scope &&
+ source == other.source && // Necessary for PolicyUIHandler observers.
+ // They have to update when sources change.
+ error == other.error &&
+ ((!value && !other.value) ||
+ (value && other.value && *value == *other.value)) &&
+ ExternalDataFetcher::Equals(external_data_fetcher.get(),
+ other.external_data_fetcher.get());
+}
+
+PolicyMap::PolicyMap() {}
+
+PolicyMap::~PolicyMap() {
+ Clear();
+}
+
+const PolicyMap::Entry* PolicyMap::Get(const std::string& policy) const {
+ PolicyMapType::const_iterator entry = map_.find(policy);
+ return entry == map_.end() ? nullptr : &entry->second;
+}
+
+PolicyMap::Entry* PolicyMap::GetMutable(const std::string& policy) {
+ PolicyMapType::iterator entry = map_.find(policy);
+ return entry == map_.end() ? nullptr : &entry->second;
+}
+
+const base::Value* PolicyMap::GetValue(const std::string& policy) const {
+ PolicyMapType::const_iterator entry = map_.find(policy);
+ return entry == map_.end() ? nullptr : entry->second.value.get();
+}
+
+base::Value* PolicyMap::GetMutableValue(const std::string& policy) {
+ PolicyMapType::iterator entry = map_.find(policy);
+ return entry == map_.end() ? nullptr : entry->second.value.get();
+}
+
+void PolicyMap::Set(
+ const std::string& policy,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source,
+ std::unique_ptr<base::Value> value,
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher) {
+ Entry entry;
+ entry.level = level;
+ entry.scope = scope;
+ entry.source = source;
+ entry.value = std::move(value);
+ entry.external_data_fetcher = std::move(external_data_fetcher);
+ Set(policy, std::move(entry));
+}
+
+void PolicyMap::Set(const std::string& policy, Entry entry) {
+ map_[policy] = std::move(entry);
+}
+
+void PolicyMap::SetError(const std::string& policy, const std::string& error) {
+ map_[policy].error = error;
+}
+
+void PolicyMap::SetSourceForAll(PolicySource source) {
+ for (auto& it : map_) {
+ it.second.source = source;
+ }
+}
+
+void PolicyMap::Erase(const std::string& policy) {
+ map_.erase(policy);
+}
+
+void PolicyMap::EraseMatching(
+ const base::Callback<bool(const const_iterator)>& filter) {
+ FilterErase(filter, true);
+}
+
+void PolicyMap::EraseNonmatching(
+ const base::Callback<bool(const const_iterator)>& filter) {
+ FilterErase(filter, false);
+}
+
+void PolicyMap::Swap(PolicyMap* other) {
+ map_.swap(other->map_);
+}
+
+void PolicyMap::CopyFrom(const PolicyMap& other) {
+ Clear();
+ for (const auto& it : other)
+ Set(it.first, it.second.DeepCopy());
+}
+
+std::unique_ptr<PolicyMap> PolicyMap::DeepCopy() const {
+ std::unique_ptr<PolicyMap> copy(new PolicyMap());
+ copy->CopyFrom(*this);
+ return copy;
+}
+
+void PolicyMap::MergeFrom(const PolicyMap& other) {
+ for (const auto& it : other) {
+ const Entry* entry = Get(it.first);
+ if (!entry || it.second.has_higher_priority_than(*entry))
+ Set(it.first, it.second.DeepCopy());
+ }
+}
+
+void PolicyMap::LoadFrom(const base::DictionaryValue* policies,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source) {
+ for (base::DictionaryValue::Iterator it(*policies); !it.IsAtEnd();
+ it.Advance()) {
+ Set(it.key(), level, scope, source, it.value().CreateDeepCopy(), nullptr);
+ }
+}
+
+void PolicyMap::GetDifferingKeys(const PolicyMap& other,
+ std::set<std::string>* differing_keys) const {
+ // Walk over the maps in lockstep, adding everything that is different.
+ const_iterator iter_this(begin());
+ const_iterator iter_other(other.begin());
+ while (iter_this != end() && iter_other != other.end()) {
+ const int diff = iter_this->first.compare(iter_other->first);
+ if (diff == 0) {
+ if (!iter_this->second.Equals(iter_other->second))
+ differing_keys->insert(iter_this->first);
+ ++iter_this;
+ ++iter_other;
+ } else if (diff < 0) {
+ differing_keys->insert(iter_this->first);
+ ++iter_this;
+ } else {
+ differing_keys->insert(iter_other->first);
+ ++iter_other;
+ }
+ }
+
+ // Add the remaining entries.
+ for (; iter_this != end(); ++iter_this)
+ differing_keys->insert(iter_this->first);
+ for (; iter_other != other.end(); ++iter_other)
+ differing_keys->insert(iter_other->first);
+}
+
+bool PolicyMap::Equals(const PolicyMap& other) const {
+ return other.size() == size() &&
+ std::equal(begin(), end(), other.begin(), MapEntryEquals);
+}
+
+bool PolicyMap::empty() const {
+ return map_.empty();
+}
+
+size_t PolicyMap::size() const {
+ return map_.size();
+}
+
+PolicyMap::const_iterator PolicyMap::begin() const {
+ return map_.begin();
+}
+
+PolicyMap::const_iterator PolicyMap::end() const {
+ return map_.end();
+}
+
+void PolicyMap::Clear() {
+ map_.clear();
+}
+
+// static
+bool PolicyMap::MapEntryEquals(const PolicyMap::PolicyMapType::value_type& a,
+ const PolicyMap::PolicyMapType::value_type& b) {
+ return a.first == b.first && a.second.Equals(b.second);
+}
+
+void PolicyMap::FilterErase(
+ const base::Callback<bool(const const_iterator)>& filter,
+ bool deletion_value) {
+ PolicyMapType::iterator iter(map_.begin());
+ while (iter != map_.end()) {
+ if (filter.Run(iter) == deletion_value) {
+ map_.erase(iter++);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_map.h b/components/policy/core/common/policy_map.h
new file mode 100644
index 0000000000..0f0ef09634
--- /dev/null
+++ b/components/policy/core/common/policy_map.h
@@ -0,0 +1,157 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_MAP_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_MAP_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A mapping of policy names to policy values for a given policy namespace.
+class POLICY_EXPORT PolicyMap {
+ public:
+ // Each policy maps to an Entry which keeps the policy value as well as other
+ // relevant data about the policy.
+ struct POLICY_EXPORT Entry {
+ PolicyLevel level = POLICY_LEVEL_RECOMMENDED;
+ PolicyScope scope = POLICY_SCOPE_USER;
+ std::unique_ptr<base::Value> value;
+ std::string error;
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher;
+
+ // For debugging and displaying only. Set by provider delivering the policy.
+ PolicySource source = POLICY_SOURCE_ENTERPRISE_DEFAULT;
+
+ Entry();
+ ~Entry();
+
+ Entry(Entry&&) noexcept;
+ Entry& operator=(Entry&&) noexcept;
+
+ // Returns a copy of |this|.
+ Entry DeepCopy() const;
+
+ // Returns true if |this| has higher priority than |other|. The priority of
+ // the fields are |level| > |scope| > |source|.
+ bool has_higher_priority_than(const Entry& other) const;
+
+ // Returns true if |this| equals |other|.
+ bool Equals(const Entry& other) const;
+ };
+
+ typedef std::map<std::string, Entry> PolicyMapType;
+ typedef PolicyMapType::const_iterator const_iterator;
+
+ PolicyMap();
+ virtual ~PolicyMap();
+
+ // Returns a weak reference to the entry currently stored for key |policy|,
+ // or NULL if not found. Ownership is retained by the PolicyMap.
+ const Entry* Get(const std::string& policy) const;
+ Entry* GetMutable(const std::string& policy);
+
+ // Returns a weak reference to the value currently stored for key
+ // |policy|, or NULL if not found. Ownership is retained by the PolicyMap.
+ // This is equivalent to Get(policy)->value, when it doesn't return NULL.
+ const base::Value* GetValue(const std::string& policy) const;
+ base::Value* GetMutableValue(const std::string& policy);
+
+ // Overwrites any existing information stored in the map for the key |policy|.
+ // Resets the error for that policy to the empty string.
+ void Set(const std::string& policy,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source,
+ std::unique_ptr<base::Value> value,
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher);
+
+ void Set(const std::string& policy, Entry entry);
+
+ // Adds an |error| to the map for the key |policy| that should be shown to the
+ // user alongside the value in the policy UI. This is equivalent to calling
+ // |GetMutableValue(policy)->error = error|, so should only be called for
+ // policies that are already stored in this map.
+ void SetError(const std::string& policy, const std::string& error);
+
+ // For all policies, overwrite the PolicySource with |source|.
+ void SetSourceForAll(PolicySource source);
+
+ // Erase the given |policy|, if it exists in this map.
+ void Erase(const std::string& policy);
+
+ // Erase all entries for which |filter| returns true.
+ void EraseMatching(const base::Callback<bool(const const_iterator)>& filter);
+
+ // Erase all entries for which |filter| returns false.
+ void EraseNonmatching(
+ const base::Callback<bool(const const_iterator)>& filter);
+
+ // Swaps the internal representation of |this| with |other|.
+ void Swap(PolicyMap* other);
+
+ // |this| becomes a copy of |other|. Any existing policies are dropped.
+ void CopyFrom(const PolicyMap& other);
+
+ // Returns a copy of |this|.
+ std::unique_ptr<PolicyMap> DeepCopy() const;
+
+ // Merges policies from |other| into |this|. Existing policies are only
+ // overridden by those in |other| if they have a higher priority, as defined
+ // by Entry::has_higher_priority_than(). If a policy is contained in both
+ // maps with the same priority, the current value in |this| is preserved.
+ void MergeFrom(const PolicyMap& other);
+
+ // Loads the values in |policies| into this PolicyMap. All policies loaded
+ // will have |level|, |scope| and |source| in their entries. Existing entries
+ // are replaced.
+ void LoadFrom(const base::DictionaryValue* policies,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source);
+
+ // Compares this value map against |other| and stores all key names that have
+ // different values or reference different external data in |differing_keys|.
+ // This includes keys that are present only in one of the maps.
+ // |differing_keys| is not cleared before the keys are added.
+ void GetDifferingKeys(const PolicyMap& other,
+ std::set<std::string>* differing_keys) const;
+
+ bool Equals(const PolicyMap& other) const;
+ bool empty() const;
+ size_t size() const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+ void Clear();
+
+ private:
+ // Helper function for Equals().
+ static bool MapEntryEquals(const PolicyMapType::value_type& a,
+ const PolicyMapType::value_type& b);
+
+ // Erase all entries for which |filter| returns |deletion_value|.
+ void FilterErase(const base::Callback<bool(const const_iterator)>& filter,
+ bool deletion_value);
+
+ PolicyMapType map_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyMap);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_MAP_H_
diff --git a/components/policy/core/common/policy_map_unittest.cc b/components/policy/core/common/policy_map_unittest.cc
new file mode 100644
index 0000000000..691bd21762
--- /dev/null
+++ b/components/policy/core/common/policy_map_unittest.cc
@@ -0,0 +1,354 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_map.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/core/common/external_data_manager.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+// Dummy policy names.
+const char kTestPolicyName1[] = "policy.test.1";
+const char kTestPolicyName2[] = "policy.test.2";
+const char kTestPolicyName3[] = "policy.test.3";
+const char kTestPolicyName4[] = "policy.test.4";
+const char kTestPolicyName5[] = "policy.test.5";
+const char kTestPolicyName6[] = "policy.test.6";
+const char kTestPolicyName7[] = "policy.test.7";
+const char kTestPolicyName8[] = "policy.test.8";
+
+// Dummy error message.
+const char kTestError[] = "Test error message";
+
+// Utility functions for the tests.
+void SetPolicy(PolicyMap* map,
+ const char* name,
+ std::unique_ptr<base::Value> value) {
+ map->Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(value), nullptr);
+}
+
+void SetPolicy(PolicyMap* map,
+ const char* name,
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher) {
+ map->Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ nullptr, std::move(external_data_fetcher));
+}
+
+} // namespace
+
+class PolicyMapTest : public testing::Test {
+ protected:
+ std::unique_ptr<ExternalDataFetcher> CreateExternalDataFetcher(
+ const std::string& policy) const;
+};
+
+std::unique_ptr<ExternalDataFetcher> PolicyMapTest::CreateExternalDataFetcher(
+ const std::string& policy) const {
+ return std::make_unique<ExternalDataFetcher>(
+ base::WeakPtr<ExternalDataManager>(), policy);
+}
+
+TEST_F(PolicyMapTest, SetAndGet) {
+ PolicyMap map;
+ SetPolicy(&map, kTestPolicyName1, std::make_unique<base::Value>("aaa"));
+ base::Value expected("aaa");
+ EXPECT_TRUE(expected.Equals(map.GetValue(kTestPolicyName1)));
+ SetPolicy(&map, kTestPolicyName1, std::make_unique<base::Value>("bbb"));
+ base::Value expected_b("bbb");
+ EXPECT_TRUE(expected_b.Equals(map.GetValue(kTestPolicyName1)));
+ SetPolicy(&map, kTestPolicyName1, CreateExternalDataFetcher("dummy"));
+ map.SetError(kTestPolicyName1, kTestError);
+ EXPECT_FALSE(map.GetValue(kTestPolicyName1));
+ const PolicyMap::Entry* entry = map.Get(kTestPolicyName1);
+ ASSERT_TRUE(entry != nullptr);
+ EXPECT_EQ(POLICY_LEVEL_MANDATORY, entry->level);
+ EXPECT_EQ(POLICY_SCOPE_USER, entry->scope);
+ EXPECT_EQ(POLICY_SOURCE_CLOUD, entry->source);
+ EXPECT_EQ(kTestError, entry->error);
+ EXPECT_TRUE(
+ ExternalDataFetcher::Equals(entry->external_data_fetcher.get(),
+ CreateExternalDataFetcher("dummy").get()));
+ map.Set(kTestPolicyName1, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, nullptr, nullptr);
+ EXPECT_FALSE(map.GetValue(kTestPolicyName1));
+ entry = map.Get(kTestPolicyName1);
+ ASSERT_TRUE(entry != nullptr);
+ EXPECT_EQ(POLICY_LEVEL_RECOMMENDED, entry->level);
+ EXPECT_EQ(POLICY_SCOPE_MACHINE, entry->scope);
+ EXPECT_EQ(POLICY_SOURCE_ENTERPRISE_DEFAULT, entry->source);
+ EXPECT_EQ("", entry->error);
+ EXPECT_FALSE(entry->external_data_fetcher);
+}
+
+TEST_F(PolicyMapTest, Equals) {
+ PolicyMap a;
+ SetPolicy(&a, kTestPolicyName1, std::make_unique<base::Value>("aaa"));
+ PolicyMap a2;
+ SetPolicy(&a2, kTestPolicyName1, std::make_unique<base::Value>("aaa"));
+ PolicyMap b;
+ SetPolicy(&b, kTestPolicyName1, std::make_unique<base::Value>("bbb"));
+ PolicyMap c;
+ SetPolicy(&c, kTestPolicyName1, std::make_unique<base::Value>("aaa"));
+ SetPolicy(&c, kTestPolicyName2, std::make_unique<base::Value>(true));
+ PolicyMap d;
+ SetPolicy(&d, kTestPolicyName1, CreateExternalDataFetcher("ddd"));
+ PolicyMap d2;
+ SetPolicy(&d2, kTestPolicyName1, CreateExternalDataFetcher("ddd"));
+ PolicyMap e;
+ SetPolicy(&e, kTestPolicyName1, CreateExternalDataFetcher("eee"));
+ EXPECT_FALSE(a.Equals(b));
+ EXPECT_FALSE(a.Equals(c));
+ EXPECT_FALSE(a.Equals(d));
+ EXPECT_FALSE(a.Equals(e));
+ EXPECT_FALSE(b.Equals(a));
+ EXPECT_FALSE(b.Equals(c));
+ EXPECT_FALSE(b.Equals(d));
+ EXPECT_FALSE(b.Equals(e));
+ EXPECT_FALSE(c.Equals(a));
+ EXPECT_FALSE(c.Equals(b));
+ EXPECT_FALSE(c.Equals(d));
+ EXPECT_FALSE(c.Equals(e));
+ EXPECT_FALSE(d.Equals(a));
+ EXPECT_FALSE(d.Equals(b));
+ EXPECT_FALSE(d.Equals(c));
+ EXPECT_FALSE(d.Equals(e));
+ EXPECT_FALSE(e.Equals(a));
+ EXPECT_FALSE(e.Equals(b));
+ EXPECT_FALSE(e.Equals(c));
+ EXPECT_FALSE(e.Equals(d));
+ EXPECT_TRUE(a.Equals(a2));
+ EXPECT_TRUE(a2.Equals(a));
+ EXPECT_TRUE(d.Equals(d2));
+ EXPECT_TRUE(d2.Equals(d));
+ PolicyMap empty1;
+ PolicyMap empty2;
+ EXPECT_TRUE(empty1.Equals(empty2));
+ EXPECT_TRUE(empty2.Equals(empty1));
+ EXPECT_FALSE(empty1.Equals(a));
+ EXPECT_FALSE(a.Equals(empty1));
+}
+
+TEST_F(PolicyMapTest, Swap) {
+ PolicyMap a;
+ SetPolicy(&a, kTestPolicyName1, std::make_unique<base::Value>("aaa"));
+ SetPolicy(&a, kTestPolicyName2, CreateExternalDataFetcher("dummy"));
+ PolicyMap b;
+ SetPolicy(&b, kTestPolicyName1, std::make_unique<base::Value>("bbb"));
+ SetPolicy(&b, kTestPolicyName3, std::make_unique<base::Value>(true));
+
+ a.Swap(&b);
+ base::Value expected("bbb");
+ EXPECT_TRUE(expected.Equals(a.GetValue(kTestPolicyName1)));
+ base::Value expected_bool(true);
+ EXPECT_TRUE(expected_bool.Equals(a.GetValue(kTestPolicyName3)));
+ EXPECT_FALSE(a.GetValue(kTestPolicyName2));
+ EXPECT_FALSE(a.Get(kTestPolicyName2));
+ base::Value expected_a("aaa");
+ EXPECT_TRUE(expected_a.Equals(b.GetValue(kTestPolicyName1)));
+ EXPECT_FALSE(b.GetValue(kTestPolicyName3));
+ EXPECT_FALSE(a.GetValue(kTestPolicyName2));
+ const PolicyMap::Entry* entry = b.Get(kTestPolicyName2);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(
+ ExternalDataFetcher::Equals(CreateExternalDataFetcher("dummy").get(),
+ entry->external_data_fetcher.get()));
+
+ b.Clear();
+ a.Swap(&b);
+ PolicyMap empty;
+ EXPECT_TRUE(a.Equals(empty));
+ EXPECT_FALSE(b.Equals(empty));
+}
+
+TEST_F(PolicyMapTest, MergeFrom) {
+ PolicyMap a;
+ a.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("google.com"),
+ nullptr);
+ a.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+ a.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, nullptr,
+ CreateExternalDataFetcher("a"));
+ a.Set(kTestPolicyName4, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false), nullptr);
+ a.Set(kTestPolicyName5, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("google.com/q={x}"),
+ nullptr);
+ a.Set(kTestPolicyName7, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, std::make_unique<base::Value>(false),
+ nullptr);
+
+ PolicyMap b;
+ b.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("chromium.org"),
+ nullptr);
+ b.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false), nullptr);
+ b.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, nullptr,
+ CreateExternalDataFetcher("b"));
+ b.Set(kTestPolicyName4, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PUBLIC_SESSION_OVERRIDE,
+ std::make_unique<base::Value>(true), nullptr);
+ b.Set(kTestPolicyName5, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, std::make_unique<base::Value>(std::string()),
+ nullptr);
+ b.Set(kTestPolicyName6, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+ b.Set(kTestPolicyName7, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ACTIVE_DIRECTORY, std::make_unique<base::Value>(true),
+ nullptr);
+
+ a.MergeFrom(b);
+
+ PolicyMap c;
+ // POLICY_SCOPE_MACHINE over POLICY_SCOPE_USER.
+ c.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("chromium.org"),
+ nullptr);
+ // |a| has precedence over |b|.
+ c.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+ c.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, nullptr,
+ CreateExternalDataFetcher("a"));
+ // POLICY_SCOPE_MACHINE over POLICY_SCOPE_USER for POLICY_LEVEL_RECOMMENDED.
+ c.Set(kTestPolicyName4, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PUBLIC_SESSION_OVERRIDE,
+ std::make_unique<base::Value>(true), nullptr);
+ // POLICY_LEVEL_MANDATORY over POLICY_LEVEL_RECOMMENDED.
+ c.Set(kTestPolicyName5, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, std::make_unique<base::Value>(std::string()),
+ nullptr);
+ // Merge new ones.
+ c.Set(kTestPolicyName6, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+ // POLICY_SOURCE_ACTIVE_DIRECTORY over POLICY_SOURCE_ENTERPRISE_DEFAULT.
+ c.Set(kTestPolicyName7, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ACTIVE_DIRECTORY, std::make_unique<base::Value>(true),
+ nullptr);
+
+ EXPECT_TRUE(a.Equals(c));
+}
+
+TEST_F(PolicyMapTest, GetDifferingKeys) {
+ PolicyMap a;
+ a.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("google.com"),
+ nullptr);
+ a.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, nullptr, CreateExternalDataFetcher("dummy"));
+ a.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+ a.Set(kTestPolicyName4, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, nullptr, CreateExternalDataFetcher("a"));
+ a.Set(kTestPolicyName5, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false), nullptr);
+ a.Set(kTestPolicyName6, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("google.com/q={x}"),
+ nullptr);
+ a.Set(kTestPolicyName7, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+
+ PolicyMap b;
+ b.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("google.com"),
+ nullptr);
+ b.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, nullptr, CreateExternalDataFetcher("dummy"));
+ b.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false), nullptr);
+ b.Set(kTestPolicyName4, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, nullptr, CreateExternalDataFetcher("b"));
+ b.Set(kTestPolicyName5, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false), nullptr);
+ b.Set(kTestPolicyName6, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("google.com/q={x}"),
+ nullptr);
+ b.Set(kTestPolicyName8, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+
+ std::set<std::string> diff;
+ std::set<std::string> diff2;
+ a.GetDifferingKeys(b, &diff);
+ b.GetDifferingKeys(a, &diff2);
+ // Order shouldn't matter.
+ EXPECT_EQ(diff, diff2);
+ // No change.
+ EXPECT_TRUE(diff.find(kTestPolicyName1) == diff.end());
+ EXPECT_TRUE(diff.find(kTestPolicyName2) == diff.end());
+ // Different values.
+ EXPECT_TRUE(diff.find(kTestPolicyName3) != diff.end());
+ // Different external data references.
+ EXPECT_TRUE(diff.find(kTestPolicyName4) != diff.end());
+ // Different levels.
+ EXPECT_TRUE(diff.find(kTestPolicyName5) != diff.end());
+ // Different scopes.
+ EXPECT_TRUE(diff.find(kTestPolicyName6) != diff.end());
+ // Not in |a|.
+ EXPECT_TRUE(diff.find(kTestPolicyName8) != diff.end());
+ // Not in |b|.
+ EXPECT_TRUE(diff.find(kTestPolicyName7) != diff.end());
+ // No surprises.
+ EXPECT_EQ(6u, diff.size());
+}
+
+TEST_F(PolicyMapTest, LoadFromSetsLevelScopeAndSource) {
+ base::DictionaryValue policies;
+ policies.SetString("TestPolicy1", "google.com");
+ policies.SetBoolean("TestPolicy2", true);
+ policies.SetInteger("TestPolicy3", -12321);
+
+ PolicyMap loaded;
+ loaded.LoadFrom(&policies,
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM);
+
+ PolicyMap expected;
+ expected.Set("TestPolicy1", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM,
+ std::make_unique<base::Value>("google.com"), nullptr);
+ expected.Set("TestPolicy2", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, std::make_unique<base::Value>(true),
+ nullptr);
+ expected.Set("TestPolicy3", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, std::make_unique<base::Value>(-12321),
+ nullptr);
+ EXPECT_TRUE(loaded.Equals(expected));
+}
+
+bool IsMandatory(const PolicyMap::PolicyMapType::const_iterator iter) {
+ return iter->second.level == POLICY_LEVEL_MANDATORY;
+}
+
+TEST_F(PolicyMapTest, EraseNonmatching) {
+ PolicyMap a;
+ a.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("google.com"),
+ nullptr);
+ a.Set(kTestPolicyName2, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+
+ a.EraseNonmatching(base::Bind(&IsMandatory));
+
+ PolicyMap b;
+ b.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("google.com"),
+ nullptr);
+ EXPECT_TRUE(a.Equals(b));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_namespace.cc b/components/policy/core/common/policy_namespace.cc
new file mode 100644
index 0000000000..33a9992835
--- /dev/null
+++ b/components/policy/core/common/policy_namespace.cc
@@ -0,0 +1,43 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_namespace.h"
+
+#include <tuple>
+
+namespace policy {
+
+PolicyNamespace::PolicyNamespace() {}
+
+PolicyNamespace::PolicyNamespace(PolicyDomain domain,
+ const std::string& component_id)
+ : domain(domain),
+ component_id(component_id) {}
+
+PolicyNamespace::PolicyNamespace(const PolicyNamespace& other)
+ : domain(other.domain),
+ component_id(other.component_id) {}
+
+PolicyNamespace::~PolicyNamespace() {}
+
+PolicyNamespace& PolicyNamespace::operator=(const PolicyNamespace& other) {
+ domain = other.domain;
+ component_id = other.component_id;
+ return *this;
+}
+
+bool PolicyNamespace::operator<(const PolicyNamespace& other) const {
+ return std::tie(domain, component_id) <
+ std::tie(other.domain, other.component_id);
+}
+
+bool PolicyNamespace::operator==(const PolicyNamespace& other) const {
+ return domain == other.domain && component_id == other.component_id;
+}
+
+bool PolicyNamespace::operator!=(const PolicyNamespace& other) const {
+ return !(*this == other);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_namespace.h b/components/policy/core/common/policy_namespace.h
new file mode 100644
index 0000000000..780fa22f95
--- /dev/null
+++ b/components/policy/core/common/policy_namespace.h
@@ -0,0 +1,66 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_NAMESPACE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_NAMESPACE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Policies are namespaced by a (PolicyDomain, ID) pair. The meaning of the ID
+// string depends on the domain; for example, if the PolicyDomain is
+// "extensions" then the ID identifies the extension that the policies control.
+enum PolicyDomain {
+ // The component ID for chrome policies is always the empty string.
+ POLICY_DOMAIN_CHROME,
+
+ // The component ID for the extension policies is equal to the extension ID.
+ POLICY_DOMAIN_EXTENSIONS,
+
+ // The namespace that corresponds to the policies for extensions running
+ // under Chrome OS signin profile. The component ID is equal to the extension
+ // ID.
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS,
+
+ // Must be the last entry.
+ POLICY_DOMAIN_SIZE,
+};
+
+// Groups a policy domain and a component ID in a single object representing
+// a policy namespace. Objects of this class can be used as keys in std::maps.
+struct POLICY_EXPORT PolicyNamespace {
+ PolicyNamespace();
+ PolicyNamespace(PolicyDomain domain, const std::string& component_id);
+ PolicyNamespace(const PolicyNamespace& other);
+ ~PolicyNamespace();
+
+ PolicyNamespace& operator=(const PolicyNamespace& other);
+ bool operator<(const PolicyNamespace& other) const;
+ bool operator==(const PolicyNamespace& other) const;
+ bool operator!=(const PolicyNamespace& other) const;
+
+ PolicyDomain domain;
+ std::string component_id;
+};
+
+typedef std::vector<PolicyNamespace> PolicyNamespaceList;
+
+struct PolicyNamespaceHash {
+ size_t operator()(const policy::PolicyNamespace& ns) const {
+ return std::hash<std::string>()(ns.component_id) ^
+ (UINT64_C(1) << ns.domain);
+ }
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_NAMESPACE_H_
diff --git a/components/policy/core/common/policy_pref_names.cc b/components/policy/core/common/policy_pref_names.cc
new file mode 100644
index 0000000000..6daa439509
--- /dev/null
+++ b/components/policy/core/common/policy_pref_names.cc
@@ -0,0 +1,30 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_pref_names.h"
+
+namespace policy {
+namespace policy_prefs {
+
+// 64-bit serialization of the time last policy usage statistics were collected
+// by UMA_HISTOGRAM_ENUMERATION.
+const char kLastPolicyStatisticsUpdate[] = "policy.last_statistics_update";
+
+// Blocks access to the listed host patterns.
+const char kUrlBlacklist[] = "policy.url_blacklist";
+
+// Allows access to the listed host patterns, as exceptions to the blacklist.
+const char kUrlWhitelist[] = "policy.url_whitelist";
+
+// Integer that specifies the policy refresh rate for user-policy in
+// milliseconds. Not all values are meaningful, so it is clamped to a sane range
+// by the cloud policy subsystem.
+const char kUserPolicyRefreshRate[] = "policy.user_refresh_rate";
+
+// The enrollment token of machine level user cloud policy
+const char kMachineLevelUserCloudPolicyEnrollmentToken[] =
+ "policy.machine_level_user_cloud_policy_enrollment_token";
+
+} // namespace policy_prefs
+} // namespace policy
diff --git a/components/policy/core/common/policy_pref_names.h b/components/policy/core/common/policy_pref_names.h
new file mode 100644
index 0000000000..31a82119a8
--- /dev/null
+++ b/components/policy/core/common/policy_pref_names.h
@@ -0,0 +1,21 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_PREF_NAMES_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_PREF_NAMES_H_
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+namespace policy_prefs {
+
+POLICY_EXPORT extern const char kLastPolicyStatisticsUpdate[];
+POLICY_EXPORT extern const char kUrlBlacklist[];
+POLICY_EXPORT extern const char kUrlWhitelist[];
+POLICY_EXPORT extern const char kUserPolicyRefreshRate[];
+POLICY_EXPORT extern const char kMachineLevelUserCloudPolicyEnrollmentToken[];
+} // namespace policy_prefs
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_PREF_NAMES_H_
diff --git a/components/policy/core/common/policy_proto_decoders.cc b/components/policy/core/common/policy_proto_decoders.cc
new file mode 100644
index 0000000000..c5c8414fd9
--- /dev/null
+++ b/components/policy/core/common/policy_proto_decoders.cc
@@ -0,0 +1,189 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_proto_decoders.h"
+
+#include <limits>
+#include <memory>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+
+namespace policy {
+
+namespace em = enterprise_management;
+
+namespace {
+
+// Returns true and sets |level| to a PolicyLevel if the policy has been set
+// at that level. Returns false if the policy is not set, or has been set at
+// the level of PolicyOptions::UNSET.
+template <class AnyPolicyProto>
+bool GetPolicyLevel(const AnyPolicyProto& policy_proto, PolicyLevel* level) {
+ if (!policy_proto.has_value()) {
+ return false;
+ }
+ if (!policy_proto.has_policy_options()) {
+ *level = POLICY_LEVEL_MANDATORY; // Default level.
+ return true;
+ }
+ switch (policy_proto.policy_options().mode()) {
+ case em::PolicyOptions::MANDATORY:
+ *level = POLICY_LEVEL_MANDATORY;
+ return true;
+ case em::PolicyOptions::RECOMMENDED:
+ *level = POLICY_LEVEL_RECOMMENDED;
+ return true;
+ case em::PolicyOptions::UNSET:
+ return false;
+ }
+}
+
+// Convert a BooleanPolicyProto to a bool base::Value.
+std::unique_ptr<base::Value> DecodeBooleanProto(
+ const em::BooleanPolicyProto& proto) {
+ return std::make_unique<base::Value>(proto.value());
+}
+
+// Convert an IntegerPolicyProto to an int base::Value.
+std::unique_ptr<base::Value> DecodeIntegerProto(
+ const em::IntegerPolicyProto& proto,
+ std::string* error) {
+ google::protobuf::int64 value = proto.value();
+
+ if (value < std::numeric_limits<int>::min() ||
+ value > std::numeric_limits<int>::max()) {
+ LOG(WARNING) << "Integer value " << value << " out of numeric limits";
+ *error = "Number out of range - invalid int32";
+ return std::make_unique<base::Value>(std::to_string(value));
+ }
+
+ return std::make_unique<base::Value>(static_cast<int>(value));
+}
+
+// Convert a StringPolicyProto to a string base::Value.
+std::unique_ptr<base::Value> DecodeStringProto(
+ const em::StringPolicyProto& proto) {
+ return std::make_unique<base::Value>(proto.value());
+}
+
+// Convert a StringListPolicyProto to a List base::Value, where each list value
+// is of Type::STRING.
+std::unique_ptr<base::Value> DecodeStringListProto(
+ const em::StringListPolicyProto& proto) {
+ auto list_value = std::make_unique<base::ListValue>();
+ for (const auto& entry : proto.value().entries())
+ list_value->AppendString(entry);
+ return std::move(list_value);
+}
+
+// Convert a StringPolicyProto to a base::Value of any type (for example,
+// Type::DICTIONARY or Type::LIST) by parsing it as JSON.
+std::unique_ptr<base::Value> DecodeJsonProto(const em::StringPolicyProto& proto,
+ std::string* error) {
+ const std::string& json = proto.value();
+ std::unique_ptr<base::Value> parsed_value =
+ base::JSONReader::ReadAndReturnError(
+ json, base::JSON_ALLOW_TRAILING_COMMAS, nullptr, error);
+
+ if (!parsed_value) {
+ // Can't parse as JSON so return it as a string, and leave it to the handler
+ // to validate.
+ LOG(WARNING) << "Invalid JSON: " << json;
+ return std::make_unique<base::Value>(json);
+ }
+
+ // Accept any Value type that parsed as JSON, and leave it to the handler to
+ // convert and check the concrete type.
+ error->clear();
+ return parsed_value;
+}
+
+} // namespace
+
+void DecodeProtoFields(
+ const em::CloudPolicySettings& policy,
+ base::WeakPtr<CloudExternalDataManager> external_data_manager,
+ PolicySource source,
+ PolicyScope scope,
+ PolicyMap* map) {
+ PolicyLevel level;
+
+ // Access arrays are terminated by a struct that contains only nullptrs.
+ for (const BooleanPolicyAccess* access = &kBooleanPolicyAccess[0];
+ access->policy_key; access++) {
+ if (!(policy.*access->has_proto)())
+ continue;
+
+ const em::BooleanPolicyProto& proto = (policy.*access->get_proto)();
+ if (!GetPolicyLevel(proto, &level))
+ continue;
+
+ map->Set(access->policy_key, level, scope, source,
+ DecodeBooleanProto(proto), nullptr);
+ }
+
+ for (const IntegerPolicyAccess* access = &kIntegerPolicyAccess[0];
+ access->policy_key; access++) {
+ if (!(policy.*access->has_proto)())
+ continue;
+
+ const em::IntegerPolicyProto& proto = (policy.*access->get_proto)();
+ if (!GetPolicyLevel(proto, &level))
+ continue;
+
+ std::string error;
+ map->Set(access->policy_key, level, scope, source,
+ DecodeIntegerProto(proto, &error), nullptr);
+ if (!error.empty())
+ map->SetError(access->policy_key, error);
+ }
+
+ for (const StringPolicyAccess* access = &kStringPolicyAccess[0];
+ access->policy_key; access++) {
+ if (!(policy.*access->has_proto)())
+ continue;
+
+ const em::StringPolicyProto& proto = (policy.*access->get_proto)();
+ if (!GetPolicyLevel(proto, &level))
+ continue;
+
+ std::string error;
+ std::unique_ptr<base::Value> value =
+ (access->type == StringPolicyType::STRING)
+ ? DecodeStringProto(proto)
+ : DecodeJsonProto(proto, &error);
+
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher =
+ (access->type == StringPolicyType::EXTERNAL)
+ ? std::make_unique<ExternalDataFetcher>(external_data_manager,
+ access->policy_key)
+ : nullptr;
+
+ map->Set(access->policy_key, level, scope, source, std::move(value),
+ std::move(external_data_fetcher));
+ if (!error.empty())
+ map->SetError(access->policy_key, error);
+ }
+
+ for (const StringListPolicyAccess* access = &kStringListPolicyAccess[0];
+ access->policy_key; access++) {
+ if (!(policy.*access->has_proto)())
+ continue;
+
+ const em::StringListPolicyProto& proto = (policy.*access->get_proto)();
+ if (!GetPolicyLevel(proto, &level))
+ continue;
+
+ map->Set(access->policy_key, level, scope, source,
+ DecodeStringListProto(proto), nullptr);
+ }
+}
+
+} // namespace policy \ No newline at end of file
diff --git a/components/policy/core/common/policy_proto_decoders.h b/components/policy/core/common/policy_proto_decoders.h
new file mode 100644
index 0000000000..7b95851634
--- /dev/null
+++ b/components/policy/core/common/policy_proto_decoders.h
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_PROTO_DECODERS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_PROTO_DECODERS_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace enterprise_management {
+class CloudPolicySettings;
+} // namespace enterprise_management
+
+namespace policy {
+
+class CloudExternalDataManager;
+class PolicyMap;
+
+// Decode all of the fields in |policy| which are recognized (see the metadata
+// in policy_constants.cc) and store them in the given |map|, with the given
+// |source| and |scope|.
+void DecodeProtoFields(
+ const enterprise_management::CloudPolicySettings& policy,
+ base::WeakPtr<CloudExternalDataManager> external_data_manager,
+ PolicySource source,
+ PolicyScope scope,
+ PolicyMap* map);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_PROTO_DECODERS_H_
diff --git a/components/policy/core/common/policy_scheduler.cc b/components/policy/core/common/policy_scheduler.cc
new file mode 100644
index 0000000000..4c8d67db50
--- /dev/null
+++ b/components/policy/core/common/policy_scheduler.cc
@@ -0,0 +1,73 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_scheduler.h"
+
+#include "base/threading/thread_task_runner_handle.h"
+
+namespace policy {
+
+PolicyScheduler::PolicyScheduler(Task task,
+ SchedulerCallback callback,
+ base::TimeDelta interval)
+ : task_(task), callback_(callback), interval_(interval) {
+ ScheduleTaskNow();
+}
+
+PolicyScheduler::~PolicyScheduler() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void PolicyScheduler::ScheduleTaskNow() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ ScheduleDelayedTask(base::TimeDelta());
+}
+
+void PolicyScheduler::ScheduleDelayedTask(base::TimeDelta delay) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (job_) {
+ job_->Cancel();
+ }
+ job_ = std::make_unique<base::CancelableClosure>(base::Bind(
+ &PolicyScheduler::RunScheduledTask, weak_ptr_factory_.GetWeakPtr()));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
+ job_->callback(), delay);
+}
+
+void PolicyScheduler::ScheduleNextTask() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ base::TimeDelta interval = overlap_ ? base::TimeDelta() : interval_;
+ const base::TimeTicks now(base::TimeTicks::Now());
+ // Time uses saturated arithmetics thus no under/overflow possible.
+ const base::TimeDelta delay = last_task_ + interval - now;
+ // Clamping delay to non-negative values just to be on the safe side.
+ ScheduleDelayedTask(std::max(base::TimeDelta(), delay));
+}
+
+void PolicyScheduler::RunScheduledTask() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (task_in_progress_) {
+ overlap_ = true;
+ return;
+ }
+
+ overlap_ = false;
+ task_in_progress_ = true;
+ task_.Run(base::BindOnce(&PolicyScheduler::OnTaskDone,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PolicyScheduler::OnTaskDone(bool success) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ task_in_progress_ = false;
+ last_task_ = base::TimeTicks::Now();
+ callback_.Run(success);
+ ScheduleNextTask();
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_scheduler.h b/components/policy/core/common/policy_scheduler.h
new file mode 100644
index 0000000000..b31314e828
--- /dev/null
+++ b/components/policy/core/common/policy_scheduler.h
@@ -0,0 +1,98 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEDULER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEDULER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/cancelable_callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Scheduler for driving repeated asynchronous tasks such as e.g. policy
+// fetches. Subsequent tasks are guaranteed not to overlap. Tasks are posted to
+// the current thread and therefore must not block (suitable e.g. for
+// asynchronous D-Bus calls).
+// Tasks scheduling begins immediately after instantiation of the class. Upon
+// destruction, scheduled but not yet started tasks are cancelled. The result of
+// started but not finished tasks is NOT reported.
+class POLICY_EXPORT PolicyScheduler {
+ public:
+ // Callback for the task to report success or failure.
+ using TaskCallback = base::OnceCallback<void(bool success)>;
+
+ // Task to be performed at regular intervals. The task takes a |callback| to
+ // return success or failure.
+ using Task = base::RepeatingCallback<void(TaskCallback callback)>;
+
+ // Callback for PolicyScheduler to report success or failure of the tasks.
+ using SchedulerCallback = base::RepeatingCallback<void(bool success)>;
+
+ // Defines the |task| to be run every |interval| and the |callback| for the
+ // scheduler to report the result. (Intervals are computed as the time
+ // difference between the end of the previous and the start of the subsequent
+ // task.) Calling the constructor starts the loop and schedules the first task
+ // to be run without delay.
+ PolicyScheduler(Task task,
+ SchedulerCallback callback,
+ base::TimeDelta interval);
+ ~PolicyScheduler();
+
+ // Schedules a task to run immediately. Deletes any previously scheduled but
+ // not yet started tasks. In case a task is running currently, the new task is
+ // scheduled to run immediately after the end of the currently running task.
+ void ScheduleTaskNow();
+
+ base::TimeDelta interval() const { return interval_; }
+
+ private:
+ // Schedules next task to run in |delay|. Deletes any previously scheduled
+ // tasks.
+ void ScheduleDelayedTask(base::TimeDelta delay);
+
+ // Schedules next task to run in |interval_| or immediately in case of
+ // overlap. Deletes any previously scheduled tasks.
+ void ScheduleNextTask();
+
+ // Actually executes the scheduled task.
+ void RunScheduledTask();
+
+ // Reports back the |result| of the previous task and schedules the next one.
+ void OnTaskDone(bool result);
+
+ Task task_;
+ SchedulerCallback callback_;
+ // Tasks are being run every |interval_|.
+ const base::TimeDelta interval_;
+
+ // Whether a task is in progress.
+ bool task_in_progress_ = false;
+
+ // Whether there had been an overlap of tasks and thus the next task needs to
+ // be scheduled without delay.
+ bool overlap_ = false;
+
+ // End time of the previous task. Zero in case no task has ended yet.
+ base::TimeTicks last_task_;
+
+ std::unique_ptr<base::CancelableClosure> job_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Must be last member.
+ base::WeakPtrFactory<PolicyScheduler> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyScheduler);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEDULER_H_
diff --git a/components/policy/core/common/policy_scheduler_unittest.cc b/components/policy/core/common/policy_scheduler_unittest.cc
new file mode 100644
index 0000000000..2950f3f112
--- /dev/null
+++ b/components/policy/core/common/policy_scheduler_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_scheduler.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class PolicySchedulerTest : public testing::Test {
+ public:
+ void DoTask(PolicyScheduler::TaskCallback callback) {
+ do_counter_++;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), true));
+ }
+
+ void OnTaskDone(bool success) {
+ done_counter_++;
+
+ // Terminate PolicyScheduler after 5 iterations.
+ if (done_counter_ >= 5) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&PolicySchedulerTest::Terminate,
+ base::Unretained(this)));
+ }
+ }
+
+ // To simulate a slow task the callback is captured instead of running it.
+ void CaptureCallbackForSlowTask(PolicyScheduler::TaskCallback callback) {
+ do_counter_++;
+ slow_callback_ = std::move(callback);
+ }
+
+ // Runs the captured callback to simulate the end of the slow task.
+ void PostSlowTaskCallback() {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(slow_callback_), true));
+ }
+
+ void Terminate() { scheduler_.reset(); }
+
+ protected:
+ int do_counter_ = 0;
+ int done_counter_ = 0;
+ std::unique_ptr<PolicyScheduler> scheduler_;
+
+ PolicyScheduler::TaskCallback slow_callback_;
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+TEST_F(PolicySchedulerTest, Run) {
+ scheduler_ = std::make_unique<PolicyScheduler>(
+ base::BindRepeating(&PolicySchedulerTest::DoTask, base::Unretained(this)),
+ base::BindRepeating(&PolicySchedulerTest::OnTaskDone,
+ base::Unretained(this)),
+ base::TimeDelta::Max());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, done_counter_);
+}
+
+TEST_F(PolicySchedulerTest, Loop) {
+ scheduler_ = std::make_unique<PolicyScheduler>(
+ base::BindRepeating(&PolicySchedulerTest::DoTask, base::Unretained(this)),
+ base::BindRepeating(&PolicySchedulerTest::OnTaskDone,
+ base::Unretained(this)),
+ base::TimeDelta());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(5, done_counter_);
+}
+
+TEST_F(PolicySchedulerTest, Reschedule) {
+ scheduler_ = std::make_unique<PolicyScheduler>(
+ base::BindRepeating(&PolicySchedulerTest::DoTask, base::Unretained(this)),
+ base::BindRepeating(&PolicySchedulerTest::OnTaskDone,
+ base::Unretained(this)),
+ base::TimeDelta::Max());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, done_counter_);
+
+ // Delayed action is not run.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, done_counter_);
+
+ // Rescheduling with 0 delay causes it to run.
+ scheduler_->ScheduleTaskNow();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, done_counter_);
+}
+
+TEST_F(PolicySchedulerTest, OverlappingTasks) {
+ scheduler_ = std::make_unique<PolicyScheduler>(
+ base::BindRepeating(&PolicySchedulerTest::CaptureCallbackForSlowTask,
+ base::Unretained(this)),
+ base::BindRepeating(&PolicySchedulerTest::OnTaskDone,
+ base::Unretained(this)),
+ base::TimeDelta::Max());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, do_counter_);
+ EXPECT_EQ(0, done_counter_);
+
+ // Second action doesn't start while first is still pending.
+ scheduler_->ScheduleTaskNow();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, do_counter_);
+ EXPECT_EQ(0, done_counter_);
+
+ // After first action has finished, the second is started.
+ PostSlowTaskCallback();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, do_counter_);
+ EXPECT_EQ(1, done_counter_);
+
+ // Let the second action finish.
+ PostSlowTaskCallback();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, do_counter_);
+ EXPECT_EQ(2, done_counter_);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service.cc b/components/policy/core/common/policy_service.cc
new file mode 100644
index 0000000000..fe35ca5928
--- /dev/null
+++ b/components/policy/core/common/policy_service.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_service.h"
+
+#include "base/values.h"
+
+namespace policy {
+
+PolicyChangeRegistrar::PolicyChangeRegistrar(PolicyService* policy_service,
+ const PolicyNamespace& ns)
+ : policy_service_(policy_service),
+ ns_(ns) {}
+
+PolicyChangeRegistrar::~PolicyChangeRegistrar() {
+ if (!callback_map_.empty())
+ policy_service_->RemoveObserver(ns_.domain, this);
+}
+
+void PolicyChangeRegistrar::Observe(const std::string& policy_name,
+ const UpdateCallback& callback) {
+ if (callback_map_.empty())
+ policy_service_->AddObserver(ns_.domain, this);
+ callback_map_[policy_name] = callback;
+}
+
+void PolicyChangeRegistrar::OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ if (ns != ns_)
+ return;
+ for (CallbackMap::iterator it = callback_map_.begin();
+ it != callback_map_.end(); ++it) {
+ const base::Value* prev = previous.GetValue(it->first);
+ const base::Value* cur = current.GetValue(it->first);
+
+ // Check if the values pointed to by |prev| and |cur| are different.
+ if ((!prev ^ !cur) || (prev && cur && *prev != *cur))
+ it->second.Run(prev, cur);
+ }
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service.h b/components/policy/core/common/policy_service.h
new file mode 100644
index 0000000000..b9897e18a2
--- /dev/null
+++ b/components/policy/core/common/policy_service.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// The PolicyService merges policies from all available sources, taking into
+// account their priorities. Policy clients can retrieve policy for their domain
+// and register for notifications on policy updates.
+//
+// The PolicyService is available from BrowserProcess as a global singleton.
+// There is also a PolicyService for browser-wide policies available from
+// BrowserProcess as a global singleton.
+class POLICY_EXPORT PolicyService {
+ public:
+ class POLICY_EXPORT Observer {
+ public:
+ // Invoked whenever policies for the given |ns| namespace are modified.
+ // This is only invoked for changes that happen after AddObserver is called.
+ // |previous| contains the values of the policies before the update,
+ // and |current| contains the current values.
+ virtual void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) = 0;
+
+ // Invoked at most once for each |domain|, when the PolicyService becomes
+ // ready. If IsInitializationComplete() is false, then this will be invoked
+ // once all the policy providers have finished loading their policies for
+ // |domain|.
+ virtual void OnPolicyServiceInitialized(PolicyDomain domain) {}
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ virtual ~PolicyService() {}
+
+ // Observes changes to all components of the given |domain|.
+ virtual void AddObserver(PolicyDomain domain, Observer* observer) = 0;
+
+ virtual void RemoveObserver(PolicyDomain domain, Observer* observer) = 0;
+
+ virtual const PolicyMap& GetPolicies(const PolicyNamespace& ns) const = 0;
+
+ // The PolicyService loads policy from several sources, and some require
+ // asynchronous loads. IsInitializationComplete() returns true once all
+ // sources have loaded their policies for the given |domain|.
+ // It is safe to read policy from the PolicyService even if
+ // IsInitializationComplete() is false; there will be an OnPolicyUpdated()
+ // notification once new policies become available.
+ //
+ // OnPolicyServiceInitialized() is called when IsInitializationComplete()
+ // becomes true, which happens at most once for each domain.
+ // If IsInitializationComplete() is already true for |domain| when an Observer
+ // is registered, then that Observer will not receive an
+ // OnPolicyServiceInitialized() notification.
+ virtual bool IsInitializationComplete(PolicyDomain domain) const = 0;
+
+ // Asks the PolicyService to reload policy from all available policy sources.
+ // |callback| is invoked once every source has reloaded its policies, and
+ // GetPolicies() is guaranteed to return the updated values at that point.
+ virtual void RefreshPolicies(const base::Closure& callback) = 0;
+};
+
+// A registrar that only observes changes to particular policies within the
+// PolicyMap for the given policy namespace.
+class POLICY_EXPORT PolicyChangeRegistrar : public PolicyService::Observer {
+ public:
+ typedef base::Callback<void(const base::Value*,
+ const base::Value*)> UpdateCallback;
+
+ // Observes updates to the given (domain, component_id) namespace in the given
+ // |policy_service|, and notifies |observer| whenever any of the registered
+ // policy keys changes. Both the |policy_service| and the |observer| must
+ // outlive |this|.
+ PolicyChangeRegistrar(PolicyService* policy_service,
+ const PolicyNamespace& ns);
+
+ ~PolicyChangeRegistrar() override;
+
+ // Will invoke |callback| whenever |policy_name| changes its value, as long
+ // as this registrar exists.
+ // Only one callback can be registed per policy name; a second call with the
+ // same |policy_name| will overwrite the previous callback.
+ void Observe(const std::string& policy_name, const UpdateCallback& callback);
+
+ // Implementation of PolicyService::Observer:
+ void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) override;
+
+ private:
+ typedef std::map<std::string, UpdateCallback> CallbackMap;
+
+ PolicyService* policy_service_;
+ PolicyNamespace ns_;
+ CallbackMap callback_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyChangeRegistrar);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
diff --git a/components/policy/core/common/policy_service_impl.cc b/components/policy/core/common/policy_service_impl.cc
new file mode 100644
index 0000000000..dd555a1463
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_service_impl.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+
+namespace policy {
+
+namespace {
+
+const char* kProxyPolicies[] = {
+ key::kProxyMode,
+ key::kProxyServerMode,
+ key::kProxyServer,
+ key::kProxyPacUrl,
+ key::kProxyBypassList,
+};
+
+// Maps the separate policies for proxy settings into a single Dictionary
+// policy. This allows to keep the logic of merging policies from different
+// sources simple, as all separate proxy policies should be considered as a
+// single whole during merging.
+void RemapProxyPolicies(PolicyMap* policies) {
+ // The highest (level, scope) pair for an existing proxy policy is determined
+ // first, and then only policies with those exact attributes are merged.
+ PolicyMap::Entry current_priority; // Defaults to the lowest priority.
+ PolicySource inherited_source = POLICY_SOURCE_ENTERPRISE_DEFAULT;
+ std::unique_ptr<base::DictionaryValue> proxy_settings(
+ new base::DictionaryValue);
+ for (size_t i = 0; i < arraysize(kProxyPolicies); ++i) {
+ const PolicyMap::Entry* entry = policies->Get(kProxyPolicies[i]);
+ if (entry) {
+ if (entry->has_higher_priority_than(current_priority)) {
+ proxy_settings->Clear();
+ current_priority = entry->DeepCopy();
+ if (entry->source > inherited_source) // Higher priority?
+ inherited_source = entry->source;
+ }
+ if (!entry->has_higher_priority_than(current_priority) &&
+ !current_priority.has_higher_priority_than(*entry)) {
+ proxy_settings->Set(kProxyPolicies[i], entry->value->CreateDeepCopy());
+ }
+ policies->Erase(kProxyPolicies[i]);
+ }
+ }
+ // Sets the new |proxy_settings| if kProxySettings isn't set yet, or if the
+ // new priority is higher.
+ const PolicyMap::Entry* existing = policies->Get(key::kProxySettings);
+ if (!proxy_settings->empty() &&
+ (!existing || current_priority.has_higher_priority_than(*existing))) {
+ policies->Set(key::kProxySettings, current_priority.level,
+ current_priority.scope, inherited_source,
+ std::move(proxy_settings), nullptr);
+ }
+}
+
+} // namespace
+
+PolicyServiceImpl::PolicyServiceImpl(Providers providers)
+ : update_task_ptr_factory_(this) {
+ providers_ = std::move(providers);
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain)
+ initialization_complete_[domain] = true;
+ for (auto* provider : providers_) {
+ provider->AddObserver(this);
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
+ initialization_complete_[domain] &=
+ provider->IsInitializationComplete(static_cast<PolicyDomain>(domain));
+ }
+ }
+ // There are no observers yet, but calls to GetPolicies() should already get
+ // the processed policy values.
+ MergeAndTriggerUpdates();
+}
+
+PolicyServiceImpl::~PolicyServiceImpl() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ for (auto* provider : providers_)
+ provider->RemoveObserver(this);
+}
+
+void PolicyServiceImpl::AddObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::unique_ptr<Observers>& list = observers_[domain];
+ if (!list)
+ list = std::make_unique<Observers>();
+ list->AddObserver(observer);
+}
+
+void PolicyServiceImpl::RemoveObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ auto it = observers_.find(domain);
+ if (it == observers_.end()) {
+ NOTREACHED();
+ return;
+ }
+ it->second->RemoveObserver(observer);
+ if (!it->second->might_have_observers()) {
+ observers_.erase(it);
+ }
+}
+
+const PolicyMap& PolicyServiceImpl::GetPolicies(
+ const PolicyNamespace& ns) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return policy_bundle_.Get(ns);
+}
+
+bool PolicyServiceImpl::IsInitializationComplete(PolicyDomain domain) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE);
+ return initialization_complete_[domain];
+}
+
+void PolicyServiceImpl::RefreshPolicies(const base::Closure& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!callback.is_null())
+ refresh_callbacks_.push_back(callback);
+
+ if (providers_.empty()) {
+ // Refresh is immediately complete if there are no providers. See the note
+ // on OnUpdatePolicy() about why this is a posted task.
+ update_task_ptr_factory_.InvalidateWeakPtrs();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&PolicyServiceImpl::MergeAndTriggerUpdates,
+ update_task_ptr_factory_.GetWeakPtr()));
+ } else {
+ // Some providers might invoke OnUpdatePolicy synchronously while handling
+ // RefreshPolicies. Mark all as pending before refreshing.
+ for (auto* provider : providers_)
+ refresh_pending_.insert(provider);
+ for (auto* provider : providers_)
+ provider->RefreshPolicies();
+ }
+}
+
+void PolicyServiceImpl::OnUpdatePolicy(ConfigurationPolicyProvider* provider) {
+ DCHECK_EQ(1, std::count(providers_.begin(), providers_.end(), provider));
+ refresh_pending_.erase(provider);
+
+ // Note: a policy change may trigger further policy changes in some providers.
+ // For example, disabling SigninAllowed would cause the CloudPolicyManager to
+ // drop all its policies, which makes this method enter again for that
+ // provider.
+ //
+ // Therefore this update is posted asynchronously, to prevent reentrancy in
+ // MergeAndTriggerUpdates. Also, cancel a pending update if there is any,
+ // since both will produce the same PolicyBundle.
+ update_task_ptr_factory_.InvalidateWeakPtrs();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&PolicyServiceImpl::MergeAndTriggerUpdates,
+ update_task_ptr_factory_.GetWeakPtr()));
+}
+
+void PolicyServiceImpl::NotifyNamespaceUpdated(
+ const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ auto iterator = observers_.find(ns.domain);
+ if (iterator != observers_.end()) {
+ for (auto& observer : *iterator->second)
+ observer.OnPolicyUpdated(ns, previous, current);
+ }
+}
+
+void PolicyServiceImpl::MergeAndTriggerUpdates() {
+ // Merge from each provider in their order of priority.
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+ PolicyBundle bundle;
+ for (auto* provider : providers_) {
+ PolicyBundle provided_bundle;
+ provided_bundle.CopyFrom(provider->policies());
+ RemapProxyPolicies(&provided_bundle.Get(chrome_namespace));
+ bundle.MergeFrom(provided_bundle);
+ }
+
+ // Swap first, so that observers that call GetPolicies() see the current
+ // values.
+ policy_bundle_.Swap(&bundle);
+
+ // Only notify observers of namespaces that have been modified.
+ const PolicyMap kEmpty;
+ PolicyBundle::const_iterator it_new = policy_bundle_.begin();
+ PolicyBundle::const_iterator end_new = policy_bundle_.end();
+ PolicyBundle::const_iterator it_old = bundle.begin();
+ PolicyBundle::const_iterator end_old = bundle.end();
+ while (it_new != end_new && it_old != end_old) {
+ if (it_new->first < it_old->first) {
+ // A new namespace is available.
+ NotifyNamespaceUpdated(it_new->first, kEmpty, *it_new->second);
+ ++it_new;
+ } else if (it_old->first < it_new->first) {
+ // A previously available namespace is now gone.
+ NotifyNamespaceUpdated(it_old->first, *it_old->second, kEmpty);
+ ++it_old;
+ } else {
+ if (!it_new->second->Equals(*it_old->second)) {
+ // An existing namespace's policies have changed.
+ NotifyNamespaceUpdated(it_new->first, *it_old->second, *it_new->second);
+ }
+ ++it_new;
+ ++it_old;
+ }
+ }
+
+ // Send updates for the remaining new namespaces, if any.
+ for (; it_new != end_new; ++it_new)
+ NotifyNamespaceUpdated(it_new->first, kEmpty, *it_new->second);
+
+ // Sends updates for the remaining removed namespaces, if any.
+ for (; it_old != end_old; ++it_old)
+ NotifyNamespaceUpdated(it_old->first, *it_old->second, kEmpty);
+
+ CheckInitializationComplete();
+ CheckRefreshComplete();
+}
+
+void PolicyServiceImpl::CheckInitializationComplete() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Check if all the providers just became initialized for each domain; if so,
+ // notify that domain's observers.
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
+ if (initialization_complete_[domain])
+ continue;
+
+ PolicyDomain policy_domain = static_cast<PolicyDomain>(domain);
+
+ bool all_complete = true;
+ for (auto* provider : providers_) {
+ if (!provider->IsInitializationComplete(policy_domain)) {
+ all_complete = false;
+ break;
+ }
+ }
+ if (all_complete) {
+ initialization_complete_[domain] = true;
+ auto iter = observers_.find(policy_domain);
+ if (iter != observers_.end()) {
+ for (auto& observer : *iter->second)
+ observer.OnPolicyServiceInitialized(policy_domain);
+ }
+ }
+ }
+}
+
+void PolicyServiceImpl::CheckRefreshComplete() {
+ // Invoke all the callbacks if a refresh has just fully completed.
+ if (refresh_pending_.empty() && !refresh_callbacks_.empty()) {
+ std::vector<base::Closure> callbacks;
+ callbacks.swap(refresh_callbacks_);
+ std::vector<base::Closure>::iterator it;
+ for (it = callbacks.begin(); it != callbacks.end(); ++it)
+ it->Run();
+ }
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service_impl.h b/components/policy/core/common/policy_service_impl.h
new file mode 100644
index 0000000000..985b27e257
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyMap;
+
+class POLICY_EXPORT PolicyServiceImpl
+ : public PolicyService,
+ public ConfigurationPolicyProvider::Observer {
+ public:
+ using Providers = std::vector<ConfigurationPolicyProvider*>;
+
+ // Creates a new PolicyServiceImpl with the list of
+ // ConfigurationPolicyProviders, in order of decreasing priority.
+ explicit PolicyServiceImpl(Providers providers);
+ ~PolicyServiceImpl() override;
+
+ // PolicyService overrides:
+ void AddObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) override;
+ void RemoveObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) override;
+ const PolicyMap& GetPolicies(const PolicyNamespace& ns) const override;
+ bool IsInitializationComplete(PolicyDomain domain) const override;
+ void RefreshPolicies(const base::Closure& callback) override;
+
+ private:
+ using Observers = base::ObserverList<PolicyService::Observer, true>;
+
+ // ConfigurationPolicyProvider::Observer overrides:
+ void OnUpdatePolicy(ConfigurationPolicyProvider* provider) override;
+
+ // Posts a task to notify observers of |ns| that its policies have changed,
+ // passing along the |previous| and the |current| policies.
+ void NotifyNamespaceUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current);
+
+ // Combines the policies from all the providers, and notifies the observers
+ // of namespaces whose policies have been modified.
+ void MergeAndTriggerUpdates();
+
+ // Checks if all providers are initialized, and notifies the observers
+ // if the service just became initialized.
+ void CheckInitializationComplete();
+
+ // Invokes all the refresh callbacks if there are no more refreshes pending.
+ void CheckRefreshComplete();
+
+ // The providers, in order of decreasing priority.
+ Providers providers_;
+
+ // Maps each policy namespace to its current policies.
+ PolicyBundle policy_bundle_;
+
+ // Maps each policy domain to its observer list.
+ std::map<PolicyDomain, std::unique_ptr<Observers>> observers_;
+
+ // True if all the providers are initialized for the indexed policy domain.
+ bool initialization_complete_[POLICY_DOMAIN_SIZE];
+
+ // Set of providers that have a pending update that was triggered by a
+ // call to RefreshPolicies().
+ std::set<ConfigurationPolicyProvider*> refresh_pending_;
+
+ // List of callbacks to invoke once all providers refresh after a
+ // RefreshPolicies() call.
+ std::vector<base::Closure> refresh_callbacks_;
+
+ // Used to verify thread-safe usage.
+ base::ThreadChecker thread_checker_;
+
+ // Used to create tasks to delay new policy updates while we may be already
+ // processing previous policy updates.
+ base::WeakPtrFactory<PolicyServiceImpl> update_task_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyServiceImpl);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
diff --git a/components/policy/core/common/policy_service_impl_unittest.cc b/components/policy/core/common/policy_service_impl_unittest.cc
new file mode 100644
index 0000000000..bc84baa9d6
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl_unittest.cc
@@ -0,0 +1,706 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_service_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AnyNumber;
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::_;
+
+namespace policy {
+
+namespace {
+
+const char kExtension[] = "extension-id";
+const char kSameLevelPolicy[] = "policy-same-level-and-scope";
+const char kDiffLevelPolicy[] = "chrome-diff-level-and-scope";
+
+// Helper to compare the arguments to an EXPECT_CALL of OnPolicyUpdated() with
+// their expected values.
+MATCHER_P(PolicyEquals, expected, "") {
+ return arg.Equals(*expected);
+}
+
+// Helper to compare the arguments to an EXPECT_CALL of OnPolicyValueUpdated()
+// with their expected values.
+MATCHER_P(ValueEquals, expected, "") {
+ return *expected == *arg;
+}
+
+// Helper that fills |bundle| with test policies.
+void AddTestPolicies(PolicyBundle* bundle,
+ const char* value,
+ PolicyLevel level,
+ PolicyScope scope) {
+ PolicyMap* policy_map =
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ policy_map->Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ std::make_unique<base::Value>(value), nullptr);
+ policy_map->Set(kDiffLevelPolicy, level, scope, POLICY_SOURCE_PLATFORM,
+ std::make_unique<base::Value>(value), nullptr);
+ policy_map =
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension));
+ policy_map->Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ std::make_unique<base::Value>(value), nullptr);
+ policy_map->Set(kDiffLevelPolicy, level, scope, POLICY_SOURCE_PLATFORM,
+ std::make_unique<base::Value>(value), nullptr);
+}
+
+// Observer class that changes the policy in the passed provider when the
+// callback is invoked.
+class ChangePolicyObserver : public PolicyService::Observer {
+ public:
+ explicit ChangePolicyObserver(MockConfigurationPolicyProvider* provider)
+ : provider_(provider),
+ observer_invoked_(false) {}
+
+ void OnPolicyUpdated(const PolicyNamespace&,
+ const PolicyMap& previous,
+ const PolicyMap& current) override {
+ PolicyMap new_policy;
+ new_policy.Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(14),
+ nullptr);
+ provider_->UpdateChromePolicy(new_policy);
+ observer_invoked_ = true;
+ }
+
+ bool observer_invoked() const { return observer_invoked_; }
+
+ private:
+ MockConfigurationPolicyProvider* provider_;
+ bool observer_invoked_;
+};
+
+} // namespace
+
+class PolicyServiceTest : public testing::Test {
+ public:
+ PolicyServiceTest() {}
+ void SetUp() override {
+ EXPECT_CALL(provider0_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+
+ provider0_.Init();
+ provider1_.Init();
+ provider2_.Init();
+
+ policy0_.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ std::make_unique<base::Value>(13), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ policy_service_ = std::make_unique<PolicyServiceImpl>(std::move(providers));
+ }
+
+ void TearDown() override {
+ provider0_.Shutdown();
+ provider1_.Shutdown();
+ provider2_.Shutdown();
+ }
+
+ MOCK_METHOD2(OnPolicyValueUpdated, void(const base::Value*,
+ const base::Value*));
+
+ MOCK_METHOD0(OnPolicyRefresh, void());
+
+ // Returns true if the policies for namespace |ns| match |expected|.
+ bool VerifyPolicies(const PolicyNamespace& ns,
+ const PolicyMap& expected) {
+ return policy_service_->GetPolicies(ns).Equals(expected);
+ }
+
+ void RunUntilIdle() {
+ base::RunLoop loop;
+ loop.RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoop loop_;
+ MockConfigurationPolicyProvider provider0_;
+ MockConfigurationPolicyProvider provider1_;
+ MockConfigurationPolicyProvider provider2_;
+ PolicyMap policy0_;
+ PolicyMap policy1_;
+ PolicyMap policy2_;
+ std::unique_ptr<PolicyServiceImpl> policy_service_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PolicyServiceTest);
+};
+
+TEST_F(PolicyServiceTest, LoadsPoliciesBeforeProvidersRefresh) {
+ PolicyMap expected;
+ expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ std::make_unique<base::Value>(13), nullptr);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+}
+
+TEST_F(PolicyServiceTest, NotifyObservers) {
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+
+ PolicyMap expectedPrevious;
+ expectedPrevious.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ std::make_unique<base::Value>(13), nullptr);
+
+ PolicyMap expectedCurrent;
+ expectedCurrent.CopyFrom(expectedPrevious);
+ expectedCurrent.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(123),
+ nullptr);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(123),
+ nullptr);
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // No changes.
+ EXPECT_CALL(observer, OnPolicyUpdated(_, _, _)).Times(0);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expectedCurrent));
+
+ // New policy.
+ expectedPrevious.CopyFrom(expectedCurrent);
+ expectedCurrent.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(456),
+ nullptr);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(456),
+ nullptr);
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Removed policy.
+ expectedPrevious.CopyFrom(expectedCurrent);
+ expectedCurrent.Erase("bbb");
+ policy0_.Erase("bbb");
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Changed policy.
+ expectedPrevious.CopyFrom(expectedCurrent);
+ expectedCurrent.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(789),
+ nullptr);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(789),
+ nullptr);
+
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // No changes again.
+ EXPECT_CALL(observer, OnPolicyUpdated(_, _, _)).Times(0);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expectedCurrent));
+
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+}
+
+TEST_F(PolicyServiceTest, NotifyObserversInMultipleNamespaces) {
+ const std::string kExtension0("extension-0");
+ const std::string kExtension1("extension-1");
+ const std::string kExtension2("extension-2");
+ MockPolicyServiceObserver chrome_observer;
+ MockPolicyServiceObserver extension_observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &chrome_observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &extension_observer);
+
+ PolicyMap previous_policy_map;
+ previous_policy_map.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ std::make_unique<base::Value>(13), nullptr);
+ PolicyMap policy_map;
+ policy_map.CopyFrom(previous_policy_map);
+ policy_map.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value"),
+ nullptr);
+
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ // The initial setup includes a policy for chrome that is now changing.
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(policy_map);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0))
+ .CopyFrom(policy_map);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1))
+ .CopyFrom(policy_map);
+
+ const PolicyMap kEmptyPolicyMap;
+ EXPECT_CALL(
+ chrome_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ provider0_.UpdatePolicy(std::move(bundle));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&chrome_observer);
+ Mock::VerifyAndClearExpectations(&extension_observer);
+
+ // Chrome policy stays the same, kExtension0 is gone, kExtension1 changes,
+ // and kExtension2 is new.
+ previous_policy_map.CopyFrom(policy_map);
+ bundle.reset(new PolicyBundle());
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(policy_map);
+ policy_map.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>("another value"), nullptr);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1))
+ .CopyFrom(policy_map);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2))
+ .CopyFrom(policy_map);
+
+ EXPECT_CALL(chrome_observer, OnPolicyUpdated(_, _, _)).Times(0);
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&kEmptyPolicyMap)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ provider0_.UpdatePolicy(std::move(bundle));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&chrome_observer);
+ Mock::VerifyAndClearExpectations(&extension_observer);
+
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &chrome_observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS,
+ &extension_observer);
+}
+
+TEST_F(PolicyServiceTest, ObserverChangesPolicy) {
+ ChangePolicyObserver observer(&provider0_);
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(123),
+ nullptr);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1234),
+ nullptr);
+ // Should not crash.
+ provider0_.UpdateChromePolicy(policy0_);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ EXPECT_TRUE(observer.observer_invoked());
+}
+
+TEST_F(PolicyServiceTest, Priorities) {
+ PolicyMap expected;
+ expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ std::make_unique<base::Value>(13), nullptr);
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(0), nullptr);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(0), nullptr);
+ policy1_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1), nullptr);
+ policy2_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(2), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ provider1_.UpdateChromePolicy(policy1_);
+ provider2_.UpdateChromePolicy(policy2_);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1), nullptr);
+ policy0_.Erase("aaa");
+ provider0_.UpdateChromePolicy(policy0_);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(2), nullptr);
+ policy1_.Set("aaa", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1), nullptr);
+ provider1_.UpdateChromePolicy(policy1_);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+}
+
+TEST_F(PolicyServiceTest, PolicyChangeRegistrar) {
+ std::unique_ptr<PolicyChangeRegistrar> registrar(new PolicyChangeRegistrar(
+ policy_service_.get(),
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+
+ // Starting to observe existing policies doesn't trigger a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ registrar->Observe("pre", base::Bind(
+ &PolicyServiceTest::OnPolicyValueUpdated,
+ base::Unretained(this)));
+ registrar->Observe("aaa", base::Bind(
+ &PolicyServiceTest::OnPolicyValueUpdated,
+ base::Unretained(this)));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ // Changing it now triggers a notification.
+ base::Value kValue0(0);
+ EXPECT_CALL(*this, OnPolicyValueUpdated(NULL, ValueEquals(&kValue0)));
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue0.CreateDeepCopy(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Changing other values doesn't trigger a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue0.CreateDeepCopy(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Modifying the value triggers a notification.
+ base::Value kValue1(1);
+ EXPECT_CALL(*this, OnPolicyValueUpdated(ValueEquals(&kValue0),
+ ValueEquals(&kValue1)));
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue1.CreateDeepCopy(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Removing the value triggers a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(ValueEquals(&kValue1), NULL));
+ policy0_.Erase("aaa");
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // No more notifications after destroying the registrar.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ registrar.reset();
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue1.CreateDeepCopy(), nullptr);
+ policy0_.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, kValue1.CreateDeepCopy(),
+ nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+}
+
+TEST_F(PolicyServiceTest, RefreshPolicies) {
+ EXPECT_CALL(provider0_, RefreshPolicies()).Times(AnyNumber());
+ EXPECT_CALL(provider1_, RefreshPolicies()).Times(AnyNumber());
+ EXPECT_CALL(provider2_, RefreshPolicies()).Times(AnyNumber());
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy_service_->RefreshPolicies(base::Bind(
+ &PolicyServiceTest::OnPolicyRefresh,
+ base::Unretained(this)));
+ // Let any queued observer tasks run.
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ base::Value kValue0(0);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue0.CreateDeepCopy(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ base::Value kValue1(1);
+ policy1_.Set("aaa", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue1.CreateDeepCopy(), nullptr);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // A provider can refresh more than once after a RefreshPolicies call, but
+ // OnPolicyRefresh should be triggered only after all providers are
+ // refreshed.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy1_.Set("bbb", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue1.CreateDeepCopy(), nullptr);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // If another RefreshPolicies() call happens while waiting for a previous
+ // one to complete, then all providers must refresh again.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy_service_->RefreshPolicies(base::Bind(
+ &PolicyServiceTest::OnPolicyRefresh,
+ base::Unretained(this)));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy2_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue0.CreateDeepCopy(), nullptr);
+ provider2_.UpdateChromePolicy(policy2_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Providers 0 and 1 must reload again.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(2);
+ base::Value kValue2(2);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue2.CreateDeepCopy(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ const PolicyMap& policies = policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ EXPECT_EQ(kValue2, *policies.GetValue("aaa"));
+ EXPECT_EQ(kValue0, *policies.GetValue("bbb"));
+}
+
+TEST_F(PolicyServiceTest, NamespaceMerge) {
+ std::unique_ptr<PolicyBundle> bundle0(new PolicyBundle());
+ std::unique_ptr<PolicyBundle> bundle1(new PolicyBundle());
+ std::unique_ptr<PolicyBundle> bundle2(new PolicyBundle());
+
+ AddTestPolicies(bundle0.get(), "bundle0",
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER);
+ AddTestPolicies(bundle1.get(), "bundle1",
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
+ AddTestPolicies(bundle2.get(), "bundle2",
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
+
+ provider0_.UpdatePolicy(std::move(bundle0));
+ provider1_.UpdatePolicy(std::move(bundle1));
+ provider2_.UpdatePolicy(std::move(bundle2));
+ RunUntilIdle();
+
+ PolicyMap expected;
+ // For policies of the same level and scope, the first provider takes
+ // precedence, on every namespace.
+ expected.Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ std::make_unique<base::Value>("bundle0"), nullptr);
+ // For policies with different levels and scopes, the highest priority
+ // level/scope combination takes precedence, on every namespace.
+ expected.Set(kDiffLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, std::make_unique<base::Value>("bundle2"),
+ nullptr);
+ EXPECT_TRUE(policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())).Equals(expected));
+ EXPECT_TRUE(policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension)).Equals(expected));
+}
+
+TEST_F(PolicyServiceTest, IsInitializationComplete) {
+ // |provider0| has all domains initialized.
+ Mock::VerifyAndClearExpectations(&provider1_);
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(false));
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ policy_service_ = std::make_unique<PolicyServiceImpl>(std::move(providers));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // |provider2_| still doesn't have POLICY_DOMAIN_CHROME initialized, so
+ // the initialization status of that domain won't change.
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+ Mock::VerifyAndClearExpectations(&provider1_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider1_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ const PolicyMap kPolicyMap;
+ provider1_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Same if |provider1_| doesn't have POLICY_DOMAIN_EXTENSIONS initialized.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Now initialize POLICY_DOMAIN_CHROME on all the providers.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME));
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ // Other domains are still not initialized.
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Initialize the remaining domain.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_CALL(observer,
+ OnPolicyServiceInitialized(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+ Mock::VerifyAndClearExpectations(&provider1_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider1_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Cleanup.
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+}
+
+TEST_F(PolicyServiceTest, SeparateProxyPoliciesMerging) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+ const PolicyNamespace extension_namespace(POLICY_DOMAIN_EXTENSIONS, "xyz");
+
+ std::unique_ptr<PolicyBundle> policy_bundle(new PolicyBundle());
+ PolicyMap& policy_map = policy_bundle->Get(chrome_namespace);
+ // Individual proxy policy values in the Chrome namespace should be collected
+ // into a dictionary.
+ policy_map.Set(key::kProxyServerMode, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(3), nullptr);
+
+ // Both these policies should be ignored, since there's a higher priority
+ // policy available.
+ policy_map.Set(key::kProxyMode, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>("pac_script"), nullptr);
+ policy_map.Set(key::kProxyPacUrl, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>("http://example.com/wpad.dat"),
+ nullptr);
+
+ // Add a value to a non-Chrome namespace.
+ policy_bundle->Get(extension_namespace)
+ .Set(key::kProxyServerMode, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(3), nullptr);
+
+ // The resulting Chrome namespace map should have the collected policy.
+ PolicyMap expected_chrome;
+ std::unique_ptr<base::DictionaryValue> expected_value(
+ new base::DictionaryValue);
+ expected_value->SetInteger(key::kProxyServerMode, 3);
+ expected_chrome.Set(key::kProxySettings, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(expected_value), nullptr);
+
+ // The resulting Extensions namespace map shouldn't have been modified.
+ PolicyMap expected_extension;
+ expected_extension.Set(key::kProxyServerMode, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(3), nullptr);
+
+ provider0_.UpdatePolicy(std::move(policy_bundle));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+ EXPECT_TRUE(VerifyPolicies(extension_namespace, expected_extension));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service_stub.cc b/components/policy/core/common/policy_service_stub.cc
new file mode 100644
index 0000000000..025ff86e40
--- /dev/null
+++ b/components/policy/core/common/policy_service_stub.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_service_stub.h"
+
+
+namespace policy {
+
+PolicyServiceStub::PolicyServiceStub() {}
+
+PolicyServiceStub::~PolicyServiceStub() {}
+
+void PolicyServiceStub::AddObserver(PolicyDomain domain,
+ Observer* observer) {}
+
+void PolicyServiceStub::RemoveObserver(PolicyDomain domain,
+ Observer* observer) {}
+
+const PolicyMap& PolicyServiceStub::GetPolicies(
+ const PolicyNamespace& ns) const {
+ return kEmpty_;
+}
+
+bool PolicyServiceStub::IsInitializationComplete(PolicyDomain domain) const {
+ return true;
+}
+
+void PolicyServiceStub::RefreshPolicies(const base::Closure& callback) {
+ if (!callback.is_null())
+ callback.Run();
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service_stub.h b/components/policy/core/common/policy_service_stub.h
new file mode 100644
index 0000000000..8759336fe8
--- /dev/null
+++ b/components/policy/core/common/policy_service_stub.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
+
+#include "base/macros.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A stub implementation, that is used when ENABLE_CONFIGURATION_POLICY is not
+// set. This allows client code to compile without requiring #ifdefs.
+class POLICY_EXPORT PolicyServiceStub : public PolicyService {
+ public:
+ PolicyServiceStub();
+ ~PolicyServiceStub() override;
+
+ void AddObserver(PolicyDomain domain,
+ Observer* observer) override;
+
+ void RemoveObserver(PolicyDomain domain,
+ Observer* observer) override;
+
+ const PolicyMap& GetPolicies(
+ const PolicyNamespace& ns) const override;
+
+ bool IsInitializationComplete(PolicyDomain domain) const override;
+
+ void RefreshPolicies(const base::Closure& callback) override;
+ private:
+ const PolicyMap kEmpty_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyServiceStub);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
diff --git a/components/policy/core/common/policy_statistics_collector.cc b/components/policy/core/common/policy_statistics_collector.cc
new file mode 100644
index 0000000000..de3cd16ce4
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_statistics_collector.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/task_runner.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace policy {
+
+const int PolicyStatisticsCollector::kStatisticsUpdateRate =
+ 24 * 60 * 60 * 1000; // 24 hours.
+
+PolicyStatisticsCollector::PolicyStatisticsCollector(
+ const GetChromePolicyDetailsCallback& get_details,
+ const Schema& chrome_schema,
+ PolicyService* policy_service,
+ PrefService* prefs,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : get_details_(get_details),
+ chrome_schema_(chrome_schema),
+ policy_service_(policy_service),
+ prefs_(prefs),
+ task_runner_(task_runner) {
+}
+
+PolicyStatisticsCollector::~PolicyStatisticsCollector() {
+}
+
+void PolicyStatisticsCollector::Initialize() {
+ using base::Time;
+ using base::TimeDelta;
+
+ TimeDelta update_rate = TimeDelta::FromMilliseconds(kStatisticsUpdateRate);
+ Time last_update = Time::FromInternalValue(
+ prefs_->GetInt64(policy_prefs::kLastPolicyStatisticsUpdate));
+ TimeDelta delay = std::max(Time::Now() - last_update, TimeDelta::FromDays(0));
+ if (delay >= update_rate)
+ CollectStatistics();
+ else
+ ScheduleUpdate(update_rate - delay);
+}
+
+// static
+void PolicyStatisticsCollector::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterInt64Pref(policy_prefs::kLastPolicyStatisticsUpdate, 0);
+}
+
+void PolicyStatisticsCollector::RecordPolicyUse(int id) {
+ base::UmaHistogramSparse("Enterprise.Policies", id);
+}
+
+void PolicyStatisticsCollector::CollectStatistics() {
+ const PolicyMap& policies = policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+
+ // Collect statistics.
+ for (Schema::Iterator it(chrome_schema_.GetPropertiesIterator());
+ !it.IsAtEnd(); it.Advance()) {
+ if (policies.Get(it.key())) {
+ const PolicyDetails* details = get_details_.Run(it.key());
+ if (details)
+ RecordPolicyUse(details->id);
+ else
+ NOTREACHED();
+ }
+ }
+
+ // Take care of next update.
+ prefs_->SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::Now().ToInternalValue());
+ ScheduleUpdate(base::TimeDelta::FromMilliseconds(kStatisticsUpdateRate));
+}
+
+void PolicyStatisticsCollector::ScheduleUpdate(base::TimeDelta delay) {
+ update_callback_.Reset(base::Bind(
+ &PolicyStatisticsCollector::CollectStatistics,
+ base::Unretained(this)));
+ task_runner_->PostDelayedTask(FROM_HERE, update_callback_.callback(), delay);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_statistics_collector.h b/components/policy/core/common/policy_statistics_collector.h
new file mode 100644
index 0000000000..f373fd5672
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
+
+#include "base/cancelable_callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace base {
+class TaskRunner;
+}
+
+namespace policy {
+
+class PolicyService;
+
+// Manages regular updates of policy usage UMA histograms.
+class POLICY_EXPORT PolicyStatisticsCollector {
+ public:
+ // Policy usage statistics update rate, in milliseconds.
+ static const int kStatisticsUpdateRate;
+
+ // Neither |policy_service| nor |prefs| can be NULL and must stay valid
+ // throughout the lifetime of PolicyStatisticsCollector.
+ PolicyStatisticsCollector(const GetChromePolicyDetailsCallback& get_details,
+ const Schema& chrome_schema,
+ PolicyService* policy_service,
+ PrefService* prefs,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+ virtual ~PolicyStatisticsCollector();
+
+ // Completes initialization and starts periodical statistic updates.
+ void Initialize();
+
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ protected:
+ // protected virtual for mocking.
+ virtual void RecordPolicyUse(int id);
+
+ private:
+ void CollectStatistics();
+ void ScheduleUpdate(base::TimeDelta delay);
+
+ GetChromePolicyDetailsCallback get_details_;
+ Schema chrome_schema_;
+ PolicyService* policy_service_;
+ PrefService* prefs_;
+
+ base::CancelableClosure update_callback_;
+
+ const scoped_refptr<base::TaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyStatisticsCollector);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
diff --git a/components/policy/core/common/policy_statistics_collector_unittest.cc b/components/policy/core/common/policy_statistics_collector_unittest.cc
new file mode 100644
index 0000000000..80d24980f8
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector_unittest.cc
@@ -0,0 +1,189 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_statistics_collector.h"
+
+#include <cstring>
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_test_utils.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+using testing::ReturnRef;
+
+// Arbitrary policy names used for testing.
+const char kTestPolicy1[] = "Test Policy 1";
+const char kTestPolicy2[] = "Test Policy 2";
+
+const int kTestPolicy1Id = 42;
+const int kTestPolicy2Id = 123;
+
+const char kTestChromeSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"Test Policy 1\": { \"type\": \"string\" },"
+ " \"Test Policy 2\": { \"type\": \"string\" }"
+ " }"
+ "}";
+
+const PolicyDetails kTestPolicyDetails[] = {
+ // is_deprecated is_device_policy id max_external_data_size
+ { false, false, kTestPolicy1Id, 0 },
+ { false, false, kTestPolicy2Id, 0 },
+};
+
+class TestPolicyStatisticsCollector : public PolicyStatisticsCollector {
+ public:
+ TestPolicyStatisticsCollector(
+ const GetChromePolicyDetailsCallback& get_details,
+ const Schema& chrome_schema,
+ PolicyService* policy_service,
+ PrefService* prefs,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : PolicyStatisticsCollector(get_details,
+ chrome_schema,
+ policy_service,
+ prefs,
+ task_runner) {}
+
+ MOCK_METHOD1(RecordPolicyUse, void(int));
+};
+
+} // namespace
+
+class PolicyStatisticsCollectorTest : public testing::Test {
+ protected:
+ PolicyStatisticsCollectorTest()
+ : update_delay_(base::TimeDelta::FromMilliseconds(
+ PolicyStatisticsCollector::kStatisticsUpdateRate)),
+ task_runner_(new base::TestSimpleTaskRunner()) {
+ }
+
+ void SetUp() override {
+ std::string error;
+ chrome_schema_ = Schema::Parse(kTestChromeSchema, &error);
+ ASSERT_TRUE(chrome_schema_.valid()) << error;
+
+ policy_details_.SetDetails(kTestPolicy1, &kTestPolicyDetails[0]);
+ policy_details_.SetDetails(kTestPolicy2, &kTestPolicyDetails[1]);
+
+ prefs_.registry()->RegisterInt64Pref(
+ policy_prefs::kLastPolicyStatisticsUpdate, 0);
+
+ // Set up default function behaviour.
+ EXPECT_CALL(policy_service_,
+ GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())))
+ .WillRepeatedly(ReturnRef(policy_map_));
+
+ // Arbitrary negative value (so it'll be different from |update_delay_|).
+ last_delay_ = base::TimeDelta::FromDays(-1);
+ policy_map_.Clear();
+ policy_statistics_collector_.reset(new TestPolicyStatisticsCollector(
+ policy_details_.GetCallback(),
+ chrome_schema_,
+ &policy_service_,
+ &prefs_,
+ task_runner_));
+ }
+
+ void SetPolicy(const std::string& name) {
+ policy_map_.Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true),
+ nullptr);
+ }
+
+ base::TimeDelta GetFirstDelay() const {
+ if (!task_runner_->HasPendingTask()) {
+ ADD_FAILURE();
+ return base::TimeDelta();
+ }
+ return task_runner_->NextPendingTaskDelay();
+ }
+
+ const base::TimeDelta update_delay_;
+
+ base::TimeDelta last_delay_;
+
+ PolicyDetailsMap policy_details_;
+ Schema chrome_schema_;
+ TestingPrefServiceSimple prefs_;
+ MockPolicyService policy_service_;
+ PolicyMap policy_map_;
+
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ std::unique_ptr<TestPolicyStatisticsCollector> policy_statistics_collector_;
+};
+
+TEST_F(PolicyStatisticsCollectorTest, CollectPending) {
+ SetPolicy(kTestPolicy1);
+
+ prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ (base::Time::Now() - update_delay_).ToInternalValue());
+
+ EXPECT_CALL(*policy_statistics_collector_, RecordPolicyUse(kTestPolicy1Id));
+
+ policy_statistics_collector_->Initialize();
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+ EXPECT_EQ(update_delay_, GetFirstDelay());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, CollectPendingVeryOld) {
+ SetPolicy(kTestPolicy1);
+
+ // Must not be 0.0 (read comment for Time::FromDoubleT).
+ prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::FromDoubleT(1.0).ToInternalValue());
+
+ EXPECT_CALL(*policy_statistics_collector_, RecordPolicyUse(kTestPolicy1Id));
+
+ policy_statistics_collector_->Initialize();
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+ EXPECT_EQ(update_delay_, GetFirstDelay());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, CollectLater) {
+ SetPolicy(kTestPolicy1);
+
+ prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ (base::Time::Now() - update_delay_ / 2).ToInternalValue());
+
+ policy_statistics_collector_->Initialize();
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+ EXPECT_LT(GetFirstDelay(), update_delay_);
+}
+
+TEST_F(PolicyStatisticsCollectorTest, MultiplePolicies) {
+ SetPolicy(kTestPolicy1);
+ SetPolicy(kTestPolicy2);
+
+ prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ (base::Time::Now() - update_delay_).ToInternalValue());
+
+ EXPECT_CALL(*policy_statistics_collector_, RecordPolicyUse(kTestPolicy1Id));
+ EXPECT_CALL(*policy_statistics_collector_, RecordPolicyUse(kTestPolicy2Id));
+
+ policy_statistics_collector_->Initialize();
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_switches.cc b/components/policy/core/common/policy_switches.cc
new file mode 100644
index 0000000000..7bea73e2fe
--- /dev/null
+++ b/components/policy/core/common/policy_switches.cc
@@ -0,0 +1,22 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_switches.h"
+
+namespace policy {
+namespace switches {
+
+// Specifies the URL at which to fetch configuration policy from the device
+// management backend.
+const char kDeviceManagementUrl[] = "device-management-url";
+
+// Disables fetching and storing cloud policy for components.
+const char kDisableComponentCloudPolicy[] = "disable-component-cloud-policy";
+
+// Always treat user as affiliated.
+// TODO(antrim): Remove once test servers correctly produce affiliation ids.
+const char kUserAlwaysAffiliated[] = "user-always-affiliated";
+
+} // namespace switches
+} // namespace policy
diff --git a/components/policy/core/common/policy_switches.h b/components/policy/core/common/policy_switches.h
new file mode 100644
index 0000000000..08d13cb5ff
--- /dev/null
+++ b/components/policy/core/common/policy_switches.h
@@ -0,0 +1,22 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SWITCHES_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SWITCHES_H_
+
+#include "components/policy/policy_export.h"
+
+#include "build/build_config.h"
+
+namespace policy {
+namespace switches {
+
+POLICY_EXPORT extern const char kDeviceManagementUrl[];
+POLICY_EXPORT extern const char kDisableComponentCloudPolicy[];
+POLICY_EXPORT extern const char kUserAlwaysAffiliated[];
+
+} // namespace switches
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SWITCHES_H_
diff --git a/components/policy/core/common/policy_test_utils.cc b/components/policy/core/common/policy_test_utils.cc
new file mode 100644
index 0000000000..d51eea90a4
--- /dev/null
+++ b/components/policy/core/common/policy_test_utils.cc
@@ -0,0 +1,209 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_test_utils.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+#if defined(OS_IOS) || defined(OS_MACOSX)
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/mac/scoped_cftyperef.h"
+#endif
+
+namespace policy {
+
+PolicyDetailsMap::PolicyDetailsMap() {}
+
+PolicyDetailsMap::~PolicyDetailsMap() {}
+
+GetChromePolicyDetailsCallback PolicyDetailsMap::GetCallback() const {
+ return base::Bind(&PolicyDetailsMap::Lookup, base::Unretained(this));
+}
+
+void PolicyDetailsMap::SetDetails(const std::string& policy,
+ const PolicyDetails* details) {
+ map_[policy] = details;
+}
+
+const PolicyDetails* PolicyDetailsMap::Lookup(const std::string& policy) const {
+ PolicyDetailsMapping::const_iterator it = map_.find(policy);
+ return it == map_.end() ? NULL : it->second;
+}
+
+bool PolicyServiceIsEmpty(const PolicyService* service) {
+ const PolicyMap& map = service->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ if (!map.empty()) {
+ base::DictionaryValue dict;
+ for (PolicyMap::const_iterator it = map.begin(); it != map.end(); ++it)
+ dict.SetKey(it->first, it->second.value->Clone());
+ LOG(WARNING) << "There are pre-existing policies in this machine: " << dict;
+ }
+ return map.empty();
+}
+
+#if defined(OS_IOS) || defined(OS_MACOSX)
+CFPropertyListRef ValueToProperty(const base::Value& value) {
+ switch (value.type()) {
+ case base::Value::Type::NONE:
+ return kCFNull;
+
+ case base::Value::Type::BOOLEAN: {
+ bool bool_value;
+ if (value.GetAsBoolean(&bool_value))
+ return bool_value ? kCFBooleanTrue : kCFBooleanFalse;
+ break;
+ }
+
+ case base::Value::Type::INTEGER: {
+ int int_value;
+ if (value.GetAsInteger(&int_value)) {
+ return CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType,
+ &int_value);
+ }
+ break;
+ }
+
+ case base::Value::Type::DOUBLE: {
+ double double_value;
+ if (value.GetAsDouble(&double_value)) {
+ return CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType,
+ &double_value);
+ }
+ break;
+ }
+
+ case base::Value::Type::STRING: {
+ std::string string_value;
+ if (value.GetAsString(&string_value))
+ return base::SysUTF8ToCFStringRef(string_value);
+ break;
+ }
+
+ case base::Value::Type::DICTIONARY: {
+ const base::DictionaryValue* dict_value;
+ if (value.GetAsDictionary(&dict_value)) {
+ // |dict| is owned by the caller.
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, dict_value->size(),
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ for (base::DictionaryValue::Iterator iterator(*dict_value);
+ !iterator.IsAtEnd(); iterator.Advance()) {
+ // CFDictionaryAddValue() retains both |key| and |value|, so make sure
+ // the references are balanced.
+ base::ScopedCFTypeRef<CFStringRef> key(
+ base::SysUTF8ToCFStringRef(iterator.key()));
+ base::ScopedCFTypeRef<CFPropertyListRef> cf_value(
+ ValueToProperty(iterator.value()));
+ if (cf_value)
+ CFDictionaryAddValue(dict, key, cf_value);
+ }
+ return dict;
+ }
+ break;
+ }
+
+ case base::Value::Type::LIST: {
+ const base::ListValue* list;
+ if (value.GetAsList(&list)) {
+ CFMutableArrayRef array =
+ CFArrayCreateMutable(NULL, list->GetSize(), &kCFTypeArrayCallBacks);
+ for (const auto& entry : *list) {
+ // CFArrayAppendValue() retains |cf_value|, so make sure the reference
+ // created by ValueToProperty() is released.
+ base::ScopedCFTypeRef<CFPropertyListRef> cf_value(
+ ValueToProperty(entry));
+ if (cf_value)
+ CFArrayAppendValue(array, cf_value);
+ }
+ return array;
+ }
+ break;
+ }
+
+ case base::Value::Type::BINARY:
+ // This type isn't converted (though it can be represented as CFData)
+ // because there's no equivalent JSON type, and policy values can only
+ // take valid JSON values.
+ break;
+ }
+
+ return NULL;
+}
+#endif // defined(OS_IOS) || defined(OS_MACOSX)
+
+} // namespace policy
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyBundle& bundle) {
+ os << "{" << std::endl;
+ for (policy::PolicyBundle::const_iterator iter = bundle.begin();
+ iter != bundle.end(); ++iter) {
+ os << " \"" << iter->first << "\": " << *iter->second << "," << std::endl;
+ }
+ os << "}";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyScope scope) {
+ switch (scope) {
+ case policy::POLICY_SCOPE_USER:
+ return os << "POLICY_SCOPE_USER";
+ case policy::POLICY_SCOPE_MACHINE:
+ return os << "POLICY_SCOPE_MACHINE";
+ }
+ return os << "POLICY_SCOPE_UNKNOWN(" << int(scope) << ")";
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyLevel level) {
+ switch (level) {
+ case policy::POLICY_LEVEL_RECOMMENDED:
+ return os << "POLICY_LEVEL_RECOMMENDED";
+ case policy::POLICY_LEVEL_MANDATORY:
+ return os << "POLICY_LEVEL_MANDATORY";
+ }
+ return os << "POLICY_LEVEL_UNKNOWN(" << int(level) << ")";
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyDomain domain) {
+ switch (domain) {
+ case policy::POLICY_DOMAIN_CHROME:
+ return os << "POLICY_DOMAIN_CHROME";
+ case policy::POLICY_DOMAIN_EXTENSIONS:
+ return os << "POLICY_DOMAIN_EXTENSIONS";
+ case policy::POLICY_DOMAIN_SIGNIN_EXTENSIONS:
+ return os << "POLICY_DOMAIN_SIGNIN_EXTENSIONS";
+ case policy::POLICY_DOMAIN_SIZE:
+ break;
+ }
+ return os << "POLICY_DOMAIN_UNKNOWN(" << int(domain) << ")";
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap& policies) {
+ os << "{" << std::endl;
+ for (const auto& iter : policies)
+ os << " \"" << iter.first << "\": " << iter.second << "," << std::endl;
+ os << "}";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap::Entry& e) {
+ return os << "{" << std::endl
+ << " \"level\": " << e.level << "," << std::endl
+ << " \"scope\": " << e.scope << "," << std::endl
+ << " \"value\": " << *e.value << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyNamespace& ns) {
+ return os << ns.domain << "/" << ns.component_id;
+}
diff --git a/components/policy/core/common/policy_test_utils.h b/components/policy/core/common/policy_test_utils.h
new file mode 100644
index 0000000000..c76a7bb63a
--- /dev/null
+++ b/components/policy/core/common/policy_test_utils.h
@@ -0,0 +1,69 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
+
+#include <map>
+#include <ostream>
+#include <string>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+class PolicyBundle;
+struct PolicyNamespace;
+
+// A mapping of policy names to PolicyDetails that can be used to set the
+// PolicyDetails for test policies.
+class PolicyDetailsMap {
+ public:
+ PolicyDetailsMap();
+ ~PolicyDetailsMap();
+
+ // The returned callback's lifetime is tied to |this| object.
+ GetChromePolicyDetailsCallback GetCallback() const;
+
+ // Does not take ownership of |details|.
+ void SetDetails(const std::string& policy, const PolicyDetails* details);
+
+ private:
+ typedef std::map<std::string, const PolicyDetails*> PolicyDetailsMapping;
+
+ const PolicyDetails* Lookup(const std::string& policy) const;
+
+ PolicyDetailsMapping map_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyDetailsMap);
+};
+
+// Returns true if |service| is not serving any policies. Otherwise logs the
+// current policies and returns false.
+bool PolicyServiceIsEmpty(const PolicyService* service);
+
+#if defined(OS_IOS) || defined(OS_MACOSX)
+
+// Converts a base::Value to the equivalent CFPropertyListRef.
+// The returned value is owned by the caller.
+CFPropertyListRef ValueToProperty(const base::Value& value);
+
+#endif
+
+} // namespace policy
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyBundle& bundle);
+std::ostream& operator<<(std::ostream& os, policy::PolicyScope scope);
+std::ostream& operator<<(std::ostream& os, policy::PolicyLevel level);
+std::ostream& operator<<(std::ostream& os, policy::PolicyDomain domain);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap& policies);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap::Entry& e);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyNamespace& ns);
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
diff --git a/components/policy/core/common/preg_parser.cc b/components/policy/core/common/preg_parser.cc
new file mode 100644
index 0000000000..a4fada30e4
--- /dev/null
+++ b/components/policy/core/common/preg_parser.cc
@@ -0,0 +1,410 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/preg_parser.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_byteorder.h"
+#include "base/values.h"
+#include "components/policy/core/common/registry_dict.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#else
+// Registry data type constants.
+#define REG_NONE 0
+#define REG_SZ 1
+#define REG_EXPAND_SZ 2
+#define REG_BINARY 3
+#define REG_DWORD_LITTLE_ENDIAN 4
+#define REG_DWORD_BIG_ENDIAN 5
+#define REG_LINK 6
+#define REG_MULTI_SZ 7
+#define REG_RESOURCE_LIST 8
+#define REG_FULL_RESOURCE_DESCRIPTOR 9
+#define REG_RESOURCE_REQUIREMENTS_LIST 10
+#define REG_QWORD_LITTLE_ENDIAN 11
+#endif
+
+using RegistryDict = policy::RegistryDict;
+
+namespace {
+
+// Maximum PReg file size we're willing to accept.
+const int64_t kMaxPRegFileSize = 1024 * 1024 * 16;
+static_assert(kMaxPRegFileSize <= std::numeric_limits<ptrdiff_t>::max(),
+ "Max PReg file size too large.");
+
+// Maximum number of components in registry key names. This corresponds to the
+// maximum nesting level of RegistryDict trees.
+const size_t kMaxKeyNameComponents = 1024;
+
+// Constants for PReg file delimiters.
+const base::char16 kDelimBracketOpen = L'[';
+const base::char16 kDelimBracketClose = L']';
+const base::char16 kDelimSemicolon = L';';
+
+// Registry path separator.
+const base::char16 kRegistryPathSeparator[] = {L'\\', L'\0'};
+
+// Magic strings for the PReg value field to trigger special actions.
+const char kActionTriggerPrefix[] = "**";
+const char kActionTriggerDeleteValues[] = "deletevalues";
+const char kActionTriggerDel[] = "del.";
+const char kActionTriggerDelVals[] = "delvals";
+const char kActionTriggerDeleteKeys[] = "deletekeys";
+const char kActionTriggerSecureKey[] = "securekey";
+const char kActionTriggerSoft[] = "soft";
+
+// Returns the character at |cursor| and increments it, unless the end is here
+// in which case -1 is returned. The calling code must guarantee that
+// end - *cursor does not overflow ptrdiff_t.
+int NextChar(const uint8_t** cursor, const uint8_t* end) {
+ // Only read the character if a full base::char16 is available.
+ // This comparison makes sure no overflow can happen.
+ if (*cursor >= end ||
+ end - *cursor < static_cast<ptrdiff_t>(sizeof(base::char16)))
+ return -1;
+
+ int result = **cursor | (*(*cursor + 1) << 8);
+ *cursor += sizeof(base::char16);
+ return result;
+}
+
+// Reads a fixed-size field from a PReg file. The calling code must guarantee
+// that both end - *cursor and size do not overflow ptrdiff_t.
+bool ReadFieldBinary(const uint8_t** cursor,
+ const uint8_t* end,
+ uint32_t size,
+ uint8_t* data) {
+ if (size == 0)
+ return true;
+
+ // Be careful to prevent possible overflows here (don't do *cursor + size).
+ if (*cursor >= end || end - *cursor < static_cast<ptrdiff_t>(size))
+ return false;
+ const uint8_t* field_end = *cursor + size;
+ std::copy(*cursor, field_end, data);
+ *cursor = field_end;
+ return true;
+}
+
+bool ReadField32(const uint8_t** cursor, const uint8_t* end, uint32_t* data) {
+ uint32_t value = 0;
+ if (!ReadFieldBinary(cursor, end, sizeof(uint32_t),
+ reinterpret_cast<uint8_t*>(&value))) {
+ return false;
+ }
+ *data = base::ByteSwapToLE32(value);
+ return true;
+}
+
+// Reads a string field from a file.
+bool ReadFieldString(const uint8_t** cursor,
+ const uint8_t* end,
+ base::string16* str) {
+ int current = -1;
+ while ((current = NextChar(cursor, end)) > 0x0000)
+ *str += current;
+
+ return current == L'\0';
+}
+
+// Converts the UTF16 |data| to an UTF8 string |value|. Returns false if the
+// resulting UTF8 string contains invalid characters.
+bool DecodePRegStringValue(const std::vector<uint8_t>& data,
+ std::string* value) {
+ size_t len = data.size() / sizeof(base::char16);
+ if (len <= 0) {
+ value->clear();
+ return true;
+ }
+
+ const base::char16* chars =
+ reinterpret_cast<const base::char16*>(data.data());
+ base::string16 utf16_str;
+ std::transform(chars, chars + len - 1, std::back_inserter(utf16_str),
+ base::ByteSwapToLE16);
+ // Note: UTF16ToUTF8() only checks whether all chars are valid code points,
+ // but not whether they're valid characters. IsStringUTF8(), however, does.
+ *value = base::UTF16ToUTF8(utf16_str);
+ if (!base::IsStringUTF8(*value)) {
+ LOG(ERROR) << "String '" << *value << "' is not a valid UTF8 string";
+ value->clear();
+ return false;
+ }
+ return true;
+}
+
+// Decodes a value from a PReg file given as a uint8_t vector.
+bool DecodePRegValue(uint32_t type,
+ const std::vector<uint8_t>& data,
+ std::unique_ptr<base::Value>* value) {
+ std::string data_utf8;
+ switch (type) {
+ case REG_SZ:
+ case REG_EXPAND_SZ:
+ if (!DecodePRegStringValue(data, &data_utf8))
+ return false;
+ value->reset(new base::Value(data_utf8));
+ return true;
+ case REG_DWORD_LITTLE_ENDIAN:
+ case REG_DWORD_BIG_ENDIAN:
+ if (data.size() == sizeof(uint32_t)) {
+ uint32_t val = *reinterpret_cast<const uint32_t*>(data.data());
+ if (type == REG_DWORD_BIG_ENDIAN)
+ val = base::NetToHost32(val);
+ else
+ val = base::ByteSwapToLE32(val);
+ value->reset(new base::Value(static_cast<int>(val)));
+ return true;
+ } else {
+ LOG(ERROR) << "Bad data size " << data.size();
+ }
+ break;
+ case REG_NONE:
+ case REG_LINK:
+ case REG_MULTI_SZ:
+ case REG_RESOURCE_LIST:
+ case REG_FULL_RESOURCE_DESCRIPTOR:
+ case REG_RESOURCE_REQUIREMENTS_LIST:
+ case REG_QWORD_LITTLE_ENDIAN:
+ default:
+ LOG(ERROR) << "Unsupported registry data type " << type;
+ }
+
+ return false;
+}
+
+// Returns true if the registry key |key_name| belongs to the sub-tree specified
+// by the key |root|.
+bool KeyRootEquals(const base::string16& key_name, const base::string16& root) {
+ if (root.empty())
+ return true;
+
+ if (!base::StartsWith(key_name, root, base::CompareCase::INSENSITIVE_ASCII))
+ return false;
+
+ // Handle the case where |root| == "ABC" and |key_name| == "ABCDE\FG". This
+ // should not be interpreted as a match.
+ return key_name.length() == root.length() ||
+ key_name.at(root.length()) == kRegistryPathSeparator[0];
+}
+
+// Adds |value| and |data| to |dict| or an appropriate sub-dictionary indicated
+// by |key_name|. Creates sub-dictionaries if necessary. Also handles special
+// action triggers, see |kActionTrigger*|, that can, for instance, remove an
+// existing value.
+void HandleRecord(const base::string16& key_name,
+ const base::string16& value,
+ uint32_t type,
+ const std::vector<uint8_t>& data,
+ RegistryDict* dict) {
+ // Locate/create the dictionary to place the value in.
+ std::vector<base::string16> path;
+
+ std::vector<base::StringPiece16> key_name_components =
+ base::SplitStringPiece(key_name, kRegistryPathSeparator,
+ base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ if (key_name_components.size() > kMaxKeyNameComponents) {
+ LOG(ERROR) << "Encountered a key which has more than "
+ << kMaxKeyNameComponents << " components.";
+ return;
+ }
+ for (const base::StringPiece16& key_name_component : key_name_components) {
+ if (key_name_component.empty())
+ continue;
+
+ const std::string name = base::UTF16ToUTF8(key_name_component);
+ RegistryDict* subdict = dict->GetKey(name);
+ if (!subdict) {
+ subdict = new RegistryDict();
+ dict->SetKey(name, base::WrapUnique(subdict));
+ }
+ dict = subdict;
+ }
+
+ if (value.empty())
+ return;
+
+ std::string value_name(base::UTF16ToUTF8(value));
+ if (!base::StartsWith(value_name, kActionTriggerPrefix,
+ base::CompareCase::SENSITIVE)) {
+ std::unique_ptr<base::Value> value;
+ if (DecodePRegValue(type, data, &value))
+ dict->SetValue(value_name, std::move(value));
+ return;
+ }
+
+ std::string data_utf8;
+ std::string action_trigger(base::ToLowerASCII(
+ value_name.substr(arraysize(kActionTriggerPrefix) - 1)));
+ if (action_trigger == kActionTriggerDeleteValues) {
+ if (DecodePRegStringValue(data, &data_utf8)) {
+ for (const std::string& value :
+ base::SplitString(data_utf8, ";", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY))
+ dict->RemoveValue(value);
+ }
+ } else if (base::StartsWith(action_trigger, kActionTriggerDeleteKeys,
+ base::CompareCase::SENSITIVE)) {
+ if (DecodePRegStringValue(data, &data_utf8)) {
+ for (const std::string& key :
+ base::SplitString(data_utf8, ";", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY))
+ dict->RemoveKey(key);
+ }
+ } else if (base::StartsWith(action_trigger, kActionTriggerDel,
+ base::CompareCase::SENSITIVE)) {
+ dict->RemoveValue(value_name.substr(arraysize(kActionTriggerPrefix) - 1 +
+ arraysize(kActionTriggerDel) - 1));
+ } else if (base::StartsWith(action_trigger, kActionTriggerDelVals,
+ base::CompareCase::SENSITIVE)) {
+ // Delete all values.
+ dict->ClearValues();
+ } else if (base::StartsWith(action_trigger, kActionTriggerSecureKey,
+ base::CompareCase::SENSITIVE) ||
+ base::StartsWith(action_trigger, kActionTriggerSoft,
+ base::CompareCase::SENSITIVE)) {
+ // Doesn't affect values.
+ } else {
+ LOG(ERROR) << "Bad action trigger " << value_name;
+ }
+}
+
+} // namespace
+
+namespace policy {
+namespace preg_parser {
+
+const char kPRegFileHeader[8] = {'P', 'R', 'e', 'g',
+ '\x01', '\x00', '\x00', '\x00'};
+
+bool ReadFile(const base::FilePath& file_path,
+ const base::string16& root,
+ RegistryDict* dict,
+ PolicyLoadStatusSampler* status) {
+ base::MemoryMappedFile mapped_file;
+ if (!mapped_file.Initialize(file_path) || !mapped_file.IsValid()) {
+ PLOG(ERROR) << "Failed to map " << file_path.value();
+ status->Add(POLICY_LOAD_STATUS_READ_ERROR);
+ return false;
+ }
+
+ return ReadDataInternal(
+ mapped_file.data(), mapped_file.length(), root, dict, status,
+ base::StringPrintf("file '%" PRIsFP "'", file_path.value().c_str()));
+}
+
+POLICY_EXPORT bool ReadDataInternal(const uint8_t* preg_data,
+ size_t preg_data_size,
+ const base::string16& root,
+ RegistryDict* dict,
+ PolicyLoadStatusSampler* status,
+ const std::string& debug_name) {
+ DCHECK(status);
+ DCHECK(root.empty() || root.back() != kRegistryPathSeparator[0]);
+
+ // Check data size.
+ if (preg_data_size > kMaxPRegFileSize) {
+ LOG(ERROR) << "PReg " << debug_name << " too large: " << preg_data_size;
+ status->Add(POLICY_LOAD_STATUS_TOO_BIG);
+ return false;
+ }
+
+ // Check the header.
+ const int kHeaderSize = arraysize(kPRegFileHeader);
+ if (!preg_data || preg_data_size < kHeaderSize ||
+ memcmp(kPRegFileHeader, preg_data, kHeaderSize) != 0) {
+ LOG(ERROR) << "Bad PReg " << debug_name;
+ status->Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+ return false;
+ }
+
+ // Parse data, which is expected to be UCS-2 and little-endian. The latter I
+ // couldn't find documentation on, but the example I saw were all
+ // little-endian. It'd be interesting to check on big-endian hardware.
+ const uint8_t* cursor = preg_data + kHeaderSize;
+ const uint8_t* end = preg_data + preg_data_size;
+ while (true) {
+ if (cursor == end)
+ return true;
+
+ if (NextChar(&cursor, end) != kDelimBracketOpen)
+ break;
+
+ // Read the record fields.
+ base::string16 key_name;
+ base::string16 value;
+ uint32_t type = 0;
+ uint32_t size = 0;
+ std::vector<uint8_t> data;
+
+ if (!ReadFieldString(&cursor, end, &key_name))
+ break;
+
+ int current = NextChar(&cursor, end);
+ if (current == kDelimSemicolon) {
+ if (!ReadFieldString(&cursor, end, &value))
+ break;
+ current = NextChar(&cursor, end);
+ }
+
+ if (current == kDelimSemicolon) {
+ if (!ReadField32(&cursor, end, &type))
+ break;
+ current = NextChar(&cursor, end);
+ }
+
+ if (current == kDelimSemicolon) {
+ if (!ReadField32(&cursor, end, &size))
+ break;
+ current = NextChar(&cursor, end);
+ }
+
+ if (current == kDelimSemicolon) {
+ if (size > kMaxPRegFileSize)
+ break;
+ data.resize(size);
+ if (!ReadFieldBinary(&cursor, end, size, data.data()))
+ break;
+ current = NextChar(&cursor, end);
+ }
+
+ if (current != kDelimBracketClose)
+ break;
+
+ // Process the record if it is within the |root| subtree.
+ if (KeyRootEquals(key_name, root))
+ HandleRecord(key_name.substr(root.size()), value, type, data, dict);
+ }
+
+ LOG(ERROR) << "Error parsing PReg " << debug_name << " at offset "
+ << (reinterpret_cast<const uint8_t*>(cursor - 1) - preg_data);
+ status->Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+ return false;
+}
+
+} // namespace preg_parser
+} // namespace policy
diff --git a/components/policy/core/common/preg_parser.h b/components/policy/core/common/preg_parser.h
new file mode 100644
index 0000000000..cf9bb37b15
--- /dev/null
+++ b/components/policy/core/common/preg_parser.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file provides a parser for PReg files which are used for storing group
+// policy settings in the file system. The file format is documented here:
+//
+// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374407(v=vs.85).aspx
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_PREG_PARSER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_PREG_PARSER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace policy {
+
+class RegistryDict;
+
+namespace preg_parser {
+
+// The magic header in PReg files: ASCII "PReg" + version (0x0001).
+POLICY_EXPORT extern const char kPRegFileHeader[8];
+
+// Reads the PReg file at |file_path| and writes the registry data to |dict|.
+// |root| specifies the registry subtree the caller is interested in, everything
+// else gets ignored. It may be empty if all keys should be returned, but it
+// must NOT end with a backslash.
+POLICY_EXPORT bool ReadFile(const base::FilePath& file_path,
+ const base::string16& root,
+ RegistryDict* dict,
+ PolicyLoadStatusSampler* status);
+
+// Similar to ReadFile, but reads from |preg_data| of length |preg_data_size|
+// instead of a file. |debug_name| is printed out along with error messages.
+// Used internally and for testing only. All other callers should use ReadFile
+// instead.
+POLICY_EXPORT bool ReadDataInternal(const uint8_t* preg_data,
+ size_t preg_data_size,
+ const base::string16& root,
+ RegistryDict* dict,
+ PolicyLoadStatusSampler* status,
+ const std::string& debug_name);
+
+} // namespace preg_parser
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_PREG_PARSER_H_
diff --git a/components/policy/core/common/preg_parser_fuzzer.cc b/components/policy/core/common/preg_parser_fuzzer.cc
new file mode 100644
index 0000000000..003031f719
--- /dev/null
+++ b/components/policy/core/common/preg_parser_fuzzer.cc
@@ -0,0 +1,45 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <base/strings/utf_string_conversions.h>
+#include "base/strings/string16.h"
+
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/preg_parser.h"
+#include "components/policy/core/common/registry_dict.h"
+
+namespace {
+
+const char kRegistryChromePolicyKey[] = "SOFTWARE\\Policies\\Chromium";
+
+} // namespace
+
+namespace policy {
+namespace preg_parser {
+
+// Disable logging.
+struct Environment {
+ Environment() : root(base::ASCIIToUTF16(kRegistryChromePolicyKey)) {
+ logging::SetMinLogLevel(logging::LOG_FATAL);
+ }
+
+ const base::string16 root;
+};
+
+Environment* env = new Environment();
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ // Note: Don't use PolicyLoadStatusUmaReporter here, it leaks!
+ PolicyLoadStatusSampler status;
+ RegistryDict dict;
+ ReadDataInternal(data, size, env->root, &dict, &status, "data");
+ return 0;
+}
+
+} // namespace preg_parser
+} // namespace policy
diff --git a/components/policy/core/common/preg_parser_unittest.cc b/components/policy/core/common/preg_parser_unittest.cc
new file mode 100644
index 0000000000..43d84c2682
--- /dev/null
+++ b/components/policy/core/common/preg_parser_unittest.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/preg_parser.h"
+
+#include <utility>
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/registry_dict.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+namespace preg_parser {
+namespace {
+
+// Preg files are relative to |kRegistryPolBaseDir|.
+const char kRegistryPolBaseDir[] = "chrome/test/data/policy/gpo";
+const char kRegistryPolFile[] = "parser_test/registry.pol";
+const char kInvalidEncodingRegistryPolFile[] = "invalid_encoding/registry.pol";
+const char kNonExistingRegistryPolFile[] = "does_not_exist.pol";
+
+const char kRegistryKey[] = "SOFTWARE\\Policies\\Chromium";
+
+// Check whether two RegistryDicts equal each other.
+testing::AssertionResult RegistryDictEquals(const RegistryDict& a,
+ const RegistryDict& b) {
+ auto iter_key_a = a.keys().begin();
+ auto iter_key_b = b.keys().begin();
+ for (; iter_key_a != a.keys().end() && iter_key_b != b.keys().end();
+ ++iter_key_a, ++iter_key_b) {
+ if (iter_key_a->first != iter_key_b->first) {
+ return testing::AssertionFailure() << "Key mismatch " << iter_key_a->first
+ << " vs. " << iter_key_b->first;
+ }
+ testing::AssertionResult result =
+ RegistryDictEquals(*iter_key_a->second, *iter_key_b->second);
+ if (!result)
+ return result;
+ }
+ if (iter_key_a != a.keys().end())
+ return testing::AssertionFailure()
+ << "key mismatch, a has extra key " << iter_key_a->first;
+ if (iter_key_b != b.keys().end())
+ return testing::AssertionFailure()
+ << "key mismatch, b has extra key " << iter_key_b->first;
+
+ auto iter_value_a = a.values().begin();
+ auto iter_value_b = b.values().begin();
+ for (; iter_value_a != a.values().end() && iter_value_b != b.values().end();
+ ++iter_value_a, ++iter_value_b) {
+ if (iter_value_a->first != iter_value_b->first ||
+ *iter_value_a->second != *iter_value_b->second) {
+ return testing::AssertionFailure()
+ << "Value mismatch " << iter_value_a->first << "="
+ << *iter_value_a->second << " vs. " << iter_value_b->first << "="
+ << *iter_value_b->second;
+ }
+ }
+ if (iter_value_a != a.values().end())
+ return testing::AssertionFailure()
+ << "Value mismatch, a has extra value " << iter_value_a->first << "="
+ << *iter_value_a->second;
+ if (iter_value_b != b.values().end())
+ return testing::AssertionFailure()
+ << "Value mismatch, b has extra value " << iter_value_b->first << "="
+ << *iter_value_b->second;
+
+ return testing::AssertionSuccess();
+}
+
+void SetInteger(RegistryDict* dict, const std::string& name, int value) {
+ dict->SetValue(name, base::WrapUnique<base::Value>(new base::Value(value)));
+}
+
+void SetString(RegistryDict* dict,
+ const std::string& name,
+ const std::string& value) {
+ dict->SetValue(name, base::WrapUnique<base::Value>(new base::Value(value)));
+}
+
+class PRegParserTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir_));
+ test_data_dir_ = test_data_dir_.AppendASCII(kRegistryPolBaseDir);
+ }
+
+ base::FilePath test_data_dir_;
+};
+
+TEST_F(PRegParserTest, TestParseFile) {
+ // Prepare the test dictionary with some data so the test can check that the
+ // PReg action triggers work, i.e. remove these items.
+ RegistryDict dict;
+ SetInteger(&dict, "DeleteValuesTest1", 1);
+ SetString(&dict, "DeleteValuesTest2", "2");
+ dict.SetKey("DeleteKeysTest1", std::make_unique<RegistryDict>());
+ std::unique_ptr<RegistryDict> delete_keys_test(new RegistryDict());
+ SetInteger(delete_keys_test.get(), "DeleteKeysTest2Entry", 1);
+ dict.SetKey("DeleteKeysTest2", std::move(delete_keys_test));
+ SetInteger(&dict, "DelTest", 1);
+ std::unique_ptr<RegistryDict> subdict(new RegistryDict());
+ SetInteger(subdict.get(), "DelValsTest1", 1);
+ SetString(subdict.get(), "DelValsTest2", "2");
+ subdict->SetKey("DelValsTest3", std::make_unique<RegistryDict>());
+ dict.SetKey("DelValsTest", std::move(subdict));
+
+ // Run the parser.
+ base::FilePath test_file(test_data_dir_.AppendASCII(kRegistryPolFile));
+ PolicyLoadStatusUmaReporter status;
+ ASSERT_TRUE(preg_parser::ReadFile(test_file, base::ASCIIToUTF16(kRegistryKey),
+ &dict, &status));
+
+ // Build the expected output dictionary.
+ RegistryDict expected;
+ std::unique_ptr<RegistryDict> del_vals_dict(new RegistryDict());
+ del_vals_dict->SetKey("DelValsTest3", std::make_unique<RegistryDict>());
+ expected.SetKey("DelValsTest", std::move(del_vals_dict));
+ SetInteger(&expected, "HomepageIsNewTabPage", 1);
+ SetString(&expected, "HomepageLocation", "http://www.example.com");
+ SetInteger(&expected, "RestoreOnStartup", 4);
+ std::unique_ptr<RegistryDict> startup_urls(new RegistryDict());
+ SetString(startup_urls.get(), "1", "http://www.chromium.org");
+ SetString(startup_urls.get(), "2", "http://www.example.com");
+ expected.SetKey("RestoreOnStartupURLs", std::move(startup_urls));
+ SetInteger(&expected, "ShowHomeButton", 1);
+ SetString(&expected, "Snowman", "\xE2\x98\x83");
+ SetString(&expected, "Empty", "");
+
+ EXPECT_TRUE(RegistryDictEquals(dict, expected));
+}
+
+TEST_F(PRegParserTest, SubstringRootInvalid) {
+ // A root of "Aa/Bb/Cc" should not be considered a valid root for a
+ // key like "Aa/Bb/C".
+ base::FilePath test_file(test_data_dir_.AppendASCII(kRegistryPolFile));
+ RegistryDict empty;
+ PolicyLoadStatusUmaReporter status;
+
+ // No data should be loaded for partial roots ("Aa/Bb/C").
+ RegistryDict dict1;
+ ASSERT_TRUE(preg_parser::ReadFile(
+ test_file, base::ASCIIToUTF16("SOFTWARE\\Policies\\Chro"), &dict1,
+ &status));
+ EXPECT_TRUE(RegistryDictEquals(dict1, empty));
+
+ // Safety check with kRegistryKey (dict should not be empty).
+ RegistryDict dict2;
+ ASSERT_TRUE(preg_parser::ReadFile(test_file, base::ASCIIToUTF16(kRegistryKey),
+ &dict2, &status));
+ EXPECT_FALSE(RegistryDictEquals(dict2, empty));
+}
+
+TEST_F(PRegParserTest, RejectInvalidStrings) {
+ // Tests whether strings with invalid characters are rejected.
+ base::FilePath test_file(
+ test_data_dir_.AppendASCII(kInvalidEncodingRegistryPolFile));
+ PolicyLoadStatusUmaReporter status;
+ RegistryDict dict;
+ ASSERT_TRUE(preg_parser::ReadFile(test_file, base::ASCIIToUTF16(kRegistryKey),
+ &dict, &status));
+
+ RegistryDict empty;
+ EXPECT_TRUE(RegistryDictEquals(dict, empty));
+}
+
+TEST_F(PRegParserTest, LoadStatusSampling) {
+ // Tests load status sampling.
+ PolicyLoadStatusUmaReporter status;
+ RegistryDict dict;
+ base::FilePath test_file(
+ test_data_dir_.AppendASCII(kNonExistingRegistryPolFile));
+ ASSERT_FALSE(preg_parser::ReadFile(
+ test_file, base::ASCIIToUTF16(kRegistryKey), &dict, &status));
+
+ PolicyLoadStatusSampler::StatusSet expected_status_set;
+ expected_status_set[POLICY_LOAD_STATUS_STARTED] = true;
+ expected_status_set[POLICY_LOAD_STATUS_READ_ERROR] = true;
+ EXPECT_EQ(expected_status_set, status.GetStatusSet());
+}
+
+} // namespace
+} // namespace preg_parser
+} // namespace policy
diff --git a/components/policy/core/common/proxy_policy_provider.cc b/components/policy/core/common/proxy_policy_provider.cc
new file mode 100644
index 0000000000..a1af2c2bbd
--- /dev/null
+++ b/components/policy/core/common/proxy_policy_provider.cc
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/proxy_policy_provider.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/logging.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+namespace policy {
+
+ProxyPolicyProvider::ProxyPolicyProvider() : delegate_(NULL) {}
+
+ProxyPolicyProvider::~ProxyPolicyProvider() {
+ DCHECK(!delegate_);
+}
+
+void ProxyPolicyProvider::SetDelegate(ConfigurationPolicyProvider* delegate) {
+ if (delegate_)
+ delegate_->RemoveObserver(this);
+ delegate_ = delegate;
+ if (delegate_) {
+ delegate_->AddObserver(this);
+ OnUpdatePolicy(delegate_);
+ } else {
+ UpdatePolicy(std::unique_ptr<PolicyBundle>(new PolicyBundle()));
+ }
+}
+
+void ProxyPolicyProvider::Shutdown() {
+ // Note: the delegate is not owned by the proxy provider, so this call is not
+ // forwarded. The same applies for the Init() call.
+ // Just drop the delegate without propagating updates here.
+ if (delegate_) {
+ delegate_->RemoveObserver(this);
+ delegate_ = NULL;
+ }
+ ConfigurationPolicyProvider::Shutdown();
+}
+
+void ProxyPolicyProvider::RefreshPolicies() {
+ if (delegate_) {
+ delegate_->RefreshPolicies();
+ } else {
+ // Subtle: if a RefreshPolicies() call comes after Shutdown() then the
+ // current bundle should be served instead. This also does the right thing
+ // if SetDelegate() was never called before.
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ bundle->CopyFrom(policies());
+ UpdatePolicy(std::move(bundle));
+ }
+}
+
+void ProxyPolicyProvider::OnUpdatePolicy(
+ ConfigurationPolicyProvider* provider) {
+ DCHECK_EQ(delegate_, provider);
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ bundle->CopyFrom(delegate_->policies());
+ UpdatePolicy(std::move(bundle));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/proxy_policy_provider.h b/components/policy/core/common/proxy_policy_provider.h
new file mode 100644
index 0000000000..2585d145cc
--- /dev/null
+++ b/components/policy/core/common/proxy_policy_provider.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_PROXY_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_PROXY_POLICY_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A policy provider implementation that acts as a proxy for another policy
+// provider, swappable at any point.
+//
+// Note that ProxyPolicyProvider correctly forwards RefreshPolicies() calls to
+// the delegate if present. If there is no delegate, the refresh results in an
+// immediate (empty) policy update.
+//
+// Furthermore, IsInitializationComplete() is implemented trivially - it always
+// returns true. Given that the delegate may be swapped at any point, there's no
+// point in trying to carry over initialization status from the delegate.
+//
+// This policy provider implementation is used to inject browser-global policy
+// originating from the user policy configured on the primary Chrome OS user
+// (i.e. the user logging in from the login screen). This way, policy settings
+// on the primary user propagate into g_browser_process->local_state_().
+//
+// The bizarre situation of user-scoped policy settings which are implemented
+// browser-global wouldn't exist in an ideal world. However, for historic
+// and technical reasons there are policy settings that are scoped to the user
+// but are implemented to take effect for the entire browser instance. A good
+// example for this are policies that affect the Chrome network stack in areas
+// where there's no profile-specific context. The meta data in
+// policy_templates.json allows to identify the policies in this bucket; they'll
+// have per_profile set to False, supported_on including chrome_os, and
+// dynamic_refresh set to True.
+class POLICY_EXPORT ProxyPolicyProvider
+ : public ConfigurationPolicyProvider,
+ public ConfigurationPolicyProvider::Observer {
+ public:
+ ProxyPolicyProvider();
+ ~ProxyPolicyProvider() override;
+
+ // Updates the provider this proxy delegates to.
+ void SetDelegate(ConfigurationPolicyProvider* delegate);
+
+ // ConfigurationPolicyProvider:
+ void Shutdown() override;
+ void RefreshPolicies() override;
+
+ // ConfigurationPolicyProvider::Observer:
+ void OnUpdatePolicy(ConfigurationPolicyProvider* provider) override;
+
+ private:
+ ConfigurationPolicyProvider* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyPolicyProvider);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_PROXY_POLICY_PROVIDER_H_
diff --git a/components/policy/core/common/proxy_policy_provider_unittest.cc b/components/policy/core/common/proxy_policy_provider_unittest.cc
new file mode 100644
index 0000000000..fcd9317c77
--- /dev/null
+++ b/components/policy/core/common/proxy_policy_provider_unittest.cc
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/proxy_policy_provider.h"
+#include <memory>
+#include "base/callback.h"
+#include "base/macros.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+
+namespace policy {
+
+class ProxyPolicyProviderTest : public testing::Test {
+ protected:
+ ProxyPolicyProviderTest() {
+ mock_provider_.Init();
+ proxy_provider_.Init(&schema_registry_);
+ proxy_provider_.AddObserver(&observer_);
+ }
+
+ ~ProxyPolicyProviderTest() override {
+ proxy_provider_.RemoveObserver(&observer_);
+ proxy_provider_.Shutdown();
+ mock_provider_.Shutdown();
+ }
+
+ SchemaRegistry schema_registry_;
+ MockConfigurationPolicyObserver observer_;
+ MockConfigurationPolicyProvider mock_provider_;
+ ProxyPolicyProvider proxy_provider_;
+
+ static std::unique_ptr<PolicyBundle> CopyBundle(const PolicyBundle& bundle) {
+ std::unique_ptr<PolicyBundle> copy(new PolicyBundle());
+ copy->CopyFrom(bundle);
+ return copy;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProxyPolicyProviderTest);
+};
+
+TEST_F(ProxyPolicyProviderTest, Init) {
+ EXPECT_TRUE(proxy_provider_.IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(PolicyBundle().Equals(proxy_provider_.policies()));
+}
+
+TEST_F(ProxyPolicyProviderTest, Delegate) {
+ PolicyBundle bundle;
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value"),
+ nullptr);
+ mock_provider_.UpdatePolicy(CopyBundle(bundle));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ proxy_provider_.SetDelegate(&mock_provider_);
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(bundle.Equals(proxy_provider_.policies()));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("new value"),
+ nullptr);
+ mock_provider_.UpdatePolicy(CopyBundle(bundle));
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(bundle.Equals(proxy_provider_.policies()));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ proxy_provider_.SetDelegate(NULL);
+ EXPECT_TRUE(PolicyBundle().Equals(proxy_provider_.policies()));
+}
+
+TEST_F(ProxyPolicyProviderTest, RefreshPolicies) {
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ proxy_provider_.RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ proxy_provider_.SetDelegate(&mock_provider_);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_)).Times(0);
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ proxy_provider_.RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer_);
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ mock_provider_.UpdatePolicy(
+ std::unique_ptr<PolicyBundle>(new PolicyBundle()));
+ Mock::VerifyAndClearExpectations(&observer_);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_map.cc b/components/policy/core/common/schema_map.cc
new file mode 100644
index 0000000000..0779cd3dc2
--- /dev/null
+++ b/components/policy/core/common/schema_map.cc
@@ -0,0 +1,118 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_map.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+SchemaMap::SchemaMap() {}
+
+SchemaMap::SchemaMap(DomainMap& map) {
+ map_.swap(map);
+}
+
+SchemaMap::~SchemaMap() {}
+
+const DomainMap& SchemaMap::GetDomains() const {
+ return map_;
+}
+
+const ComponentMap* SchemaMap::GetComponents(PolicyDomain domain) const {
+ const auto it = map_.find(domain);
+ return it == map_.end() ? nullptr : &it->second;
+}
+
+const Schema* SchemaMap::GetSchema(const PolicyNamespace& ns) const {
+ const ComponentMap* map = GetComponents(ns.domain);
+ if (!map)
+ return nullptr;
+ const auto it = map->find(ns.component_id);
+ return it == map->end() ? nullptr : &it->second;
+}
+
+void SchemaMap::FilterBundle(PolicyBundle* bundle) const {
+ for (const auto& bundle_item : *bundle) {
+ const PolicyNamespace& ns = bundle_item.first;
+ const std::unique_ptr<PolicyMap>& policy_map = bundle_item.second;
+
+ // Chrome policies are not filtered, so that typos appear in about:policy.
+ // Everything else gets filtered, so that components only see valid policy.
+ if (ns.domain == POLICY_DOMAIN_CHROME)
+ continue;
+
+ const Schema* schema = GetSchema(ns);
+
+ if (!schema) {
+ policy_map->Clear();
+ continue;
+ }
+
+ if (!schema->valid()) {
+ // Don't serve unknown policies.
+ policy_map->Clear();
+ continue;
+ }
+
+ for (auto it_map = policy_map->begin(); it_map != policy_map->end();) {
+ const std::string& policy_name = it_map->first;
+ const base::Value* policy_value = it_map->second.value.get();
+ Schema policy_schema = schema->GetProperty(policy_name);
+ ++it_map;
+ std::string error_path;
+ std::string error;
+ if (!policy_value ||
+ !policy_schema.Validate(*policy_value,
+ SCHEMA_STRICT,
+ &error_path,
+ &error)) {
+ LOG(ERROR) << "Dropping policy " << policy_name << " of component "
+ << ns.component_id << " due to error at "
+ << (error_path.empty() ? "root" : error_path) << ": "
+ << error;
+ policy_map->Erase(policy_name);
+ }
+ }
+ }
+}
+
+bool SchemaMap::HasComponents() const {
+ for (const auto& item : map_) {
+ const PolicyDomain& domain = item.first;
+ const ComponentMap& component_map = item.second;
+ if (domain == POLICY_DOMAIN_CHROME)
+ continue;
+ if (!component_map.empty())
+ return true;
+ }
+ return false;
+}
+
+void SchemaMap::GetChanges(const scoped_refptr<SchemaMap>& older,
+ PolicyNamespaceList* removed,
+ PolicyNamespaceList* added) const {
+ GetNamespacesNotInOther(older.get(), added);
+ older->GetNamespacesNotInOther(this, removed);
+}
+
+void SchemaMap::GetNamespacesNotInOther(const SchemaMap* other,
+ PolicyNamespaceList* list) const {
+ list->clear();
+ for (const auto& item : map_) {
+ const PolicyDomain& domain = item.first;
+ const ComponentMap& component_map = item.second;
+ for (const auto& comp : component_map) {
+ const std::string& component_id = comp.first;
+ const PolicyNamespace ns(domain, component_id);
+ if (!other->GetSchema(ns))
+ list->push_back(ns);
+ }
+ }
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_map.h b/components/policy/core/common/schema_map.h
new file mode 100644
index 0000000000..835941e853
--- /dev/null
+++ b/components/policy/core/common/schema_map.h
@@ -0,0 +1,68 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyBundle;
+
+// Maps component id (e.g. extension id) to schema.
+typedef std::map<std::string, Schema> ComponentMap;
+typedef std::map<PolicyDomain, ComponentMap> DomainMap;
+
+// Contains a mapping of policy namespaces (domain + component ID) to its
+// corresponding Schema.
+// This class is thread-safe.
+class POLICY_EXPORT SchemaMap : public base::RefCountedThreadSafe<SchemaMap> {
+ public:
+ SchemaMap();
+ // Takes ownership of |map| (its contents will be swapped).
+ // TODO(emaxx): Change to use move semantics.
+ explicit SchemaMap(DomainMap& map);
+
+ const DomainMap& GetDomains() const;
+
+ const ComponentMap* GetComponents(PolicyDomain domain) const;
+
+ const Schema* GetSchema(const PolicyNamespace& ns) const;
+
+ // Removes all the policies in |bundle| that don't match the known schemas.
+ // Unknown components are also dropped.
+ void FilterBundle(PolicyBundle* bundle) const;
+
+ // Returns true if this map contains at least one component of a domain other
+ // than POLICY_DOMAIN_CHROME.
+ bool HasComponents() const;
+
+ void GetChanges(const scoped_refptr<SchemaMap>& older,
+ PolicyNamespaceList* removed,
+ PolicyNamespaceList* added) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<SchemaMap>;
+
+ void GetNamespacesNotInOther(const SchemaMap* other,
+ PolicyNamespaceList* list) const;
+
+ ~SchemaMap();
+
+ DomainMap map_;
+
+ DISALLOW_COPY_AND_ASSIGN(SchemaMap);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
diff --git a/components/policy/core/common/schema_map_unittest.cc b/components/policy/core/common/schema_map_unittest.cc
new file mode 100644
index 0000000000..96916e5cc1
--- /dev/null
+++ b/components/policy/core/common/schema_map_unittest.cc
@@ -0,0 +1,308 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_map.h"
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/external_data_manager.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char kTestSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"string\": { \"type\": \"string\" },"
+ " \"integer\": { \"type\": \"integer\" },"
+ " \"boolean\": { \"type\": \"boolean\" },"
+ " \"null\": { \"type\": \"null\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"list\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"object\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"a\": { \"type\": \"string\" },"
+ " \"b\": { \"type\": \"integer\" }"
+ " }"
+ " }"
+ " }"
+ "}";
+
+} // namespace
+
+class SchemaMapTest : public testing::Test {
+ protected:
+ Schema CreateTestSchema() {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ if (!schema.valid())
+ ADD_FAILURE() << error;
+ return schema;
+ }
+
+ scoped_refptr<SchemaMap> CreateTestMap() {
+ Schema schema = CreateTestSchema();
+ ComponentMap component_map;
+ component_map["extension-1"] = schema;
+ component_map["extension-2"] = schema;
+ component_map["legacy-extension"] = Schema();
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS] = component_map;
+
+ return new SchemaMap(domain_map);
+ }
+};
+
+TEST_F(SchemaMapTest, Empty) {
+ scoped_refptr<SchemaMap> map = new SchemaMap();
+ EXPECT_TRUE(map->GetDomains().empty());
+ EXPECT_FALSE(map->GetComponents(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(map->GetComponents(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(map->GetSchema(PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
+ EXPECT_FALSE(map->HasComponents());
+}
+
+TEST_F(SchemaMapTest, HasComponents) {
+ scoped_refptr<SchemaMap> map = new SchemaMap();
+ EXPECT_FALSE(map->HasComponents());
+
+ // The Chrome schema does not count as a component.
+ Schema schema = CreateTestSchema();
+ ComponentMap component_map;
+ component_map[""] = schema;
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_CHROME] = component_map;
+ map = new SchemaMap(domain_map);
+ EXPECT_FALSE(map->HasComponents());
+
+ // An extension schema does.
+ domain_map[POLICY_DOMAIN_EXTENSIONS] = component_map;
+ map = new SchemaMap(domain_map);
+ EXPECT_TRUE(map->HasComponents());
+}
+
+TEST_F(SchemaMapTest, Lookups) {
+ scoped_refptr<SchemaMap> map = CreateTestMap();
+ ASSERT_TRUE(map.get());
+ EXPECT_TRUE(map->HasComponents());
+
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "extension-1")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "legacy-extension")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "extension-3")));
+
+ const Schema* schema =
+ map->GetSchema(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "extension-1"));
+ ASSERT_TRUE(schema);
+ EXPECT_TRUE(schema->valid());
+
+ schema = map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "legacy-extension"));
+ ASSERT_TRUE(schema);
+ EXPECT_FALSE(schema->valid());
+}
+
+TEST_F(SchemaMapTest, FilterBundle) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["abc"] = schema;
+ scoped_refptr<SchemaMap> schema_map = new SchemaMap(domain_map);
+
+ PolicyBundle bundle;
+ schema_map->FilterBundle(&bundle);
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(bundle.Equals(empty_bundle));
+
+ // The Chrome namespace isn't filtered.
+ PolicyBundle expected_bundle;
+ PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ expected_bundle.Get(chrome_ns).Set(
+ "ChromePolicy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value"), nullptr);
+ bundle.CopyFrom(expected_bundle);
+
+ // Unknown components are filtered out.
+ PolicyNamespace another_extension_ns(POLICY_DOMAIN_EXTENSIONS, "xyz");
+ bundle.Get(another_extension_ns)
+ .Set("AnotherExtensionPolicy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value"),
+ nullptr);
+ schema_map->FilterBundle(&bundle);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+
+ PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "abc");
+ PolicyMap& map = expected_bundle.Get(extension_ns);
+ base::ListValue list;
+ list.AppendString("a");
+ list.AppendString("b");
+ map.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.CreateDeepCopy(), nullptr);
+ map.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
+ map.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1), nullptr);
+ map.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(), nullptr);
+ map.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1.2), nullptr);
+ base::DictionaryValue dict;
+ dict.SetString("a", "b");
+ dict.SetInteger("b", 2);
+ map.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, dict.CreateDeepCopy(), nullptr);
+ map.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value"), nullptr);
+
+ bundle.MergeFrom(expected_bundle);
+ bundle.Get(extension_ns)
+ .Set("Unexpected", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("to-be-removed"),
+ nullptr);
+
+ schema_map->FilterBundle(&bundle);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+
+ // Mismatched types are also removed.
+ bundle.Clear();
+ PolicyMap& badmap = bundle.Get(extension_ns);
+ badmap.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
+ nullptr);
+ badmap.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(0), nullptr);
+ badmap.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
+ nullptr);
+ badmap.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
+ nullptr);
+ badmap.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
+ nullptr);
+ badmap.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
+ nullptr);
+ badmap.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, nullptr,
+ std::make_unique<ExternalDataFetcher>(nullptr, std::string()));
+
+ schema_map->FilterBundle(&bundle);
+ EXPECT_TRUE(bundle.Equals(empty_bundle));
+}
+
+TEST_F(SchemaMapTest, LegacyComponents) {
+ std::string error;
+ Schema schema = Schema::Parse(
+ "{"
+ " \"type\":\"object\","
+ " \"properties\": {"
+ " \"String\": { \"type\": \"string\" }"
+ " }"
+ "}", &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["with-schema"] = schema;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["without-schema"] = Schema();
+ scoped_refptr<SchemaMap> schema_map = new SchemaMap(domain_map);
+
+ // |bundle| contains policies loaded by a policy provider.
+ PolicyBundle bundle;
+
+ // Known components with schemas are filtered.
+ PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "with-schema");
+ bundle.Get(extension_ns)
+ .Set("String", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value 1"),
+ nullptr);
+
+ // The Chrome namespace isn't filtered.
+ PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ bundle.Get(chrome_ns).Set("ChromePolicy", POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>("value 3"), nullptr);
+
+ PolicyBundle expected_bundle;
+ expected_bundle.MergeFrom(bundle);
+
+ // Known components without a schema are filtered out completely.
+ PolicyNamespace without_schema_ns(POLICY_DOMAIN_EXTENSIONS, "without-schema");
+ bundle.Get(without_schema_ns)
+ .Set("Schemaless", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value 2"),
+ nullptr);
+
+ // Unknown policies of known components with a schema are removed.
+ bundle.Get(extension_ns)
+ .Set("Surprise", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value 4"),
+ nullptr);
+
+ // Unknown components are removed.
+ PolicyNamespace unknown_ns(POLICY_DOMAIN_EXTENSIONS, "unknown");
+ bundle.Get(unknown_ns)
+ .Set("Surprise", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value 5"),
+ nullptr);
+
+ schema_map->FilterBundle(&bundle);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+}
+
+TEST_F(SchemaMapTest, GetChanges) {
+ DomainMap map;
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ scoped_refptr<SchemaMap> older = new SchemaMap(map);
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ scoped_refptr<SchemaMap> newer = new SchemaMap(map);
+
+ PolicyNamespaceList removed;
+ PolicyNamespaceList added;
+ newer->GetChanges(older, &removed, &added);
+ EXPECT_TRUE(removed.empty());
+ EXPECT_TRUE(added.empty());
+
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ map[POLICY_DOMAIN_EXTENSIONS]["xyz"] = Schema();
+ newer = new SchemaMap(map);
+ newer->GetChanges(older, &removed, &added);
+ EXPECT_TRUE(removed.empty());
+ ASSERT_EQ(1u, added.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), added[0]);
+
+ older = newer;
+ map[POLICY_DOMAIN_EXTENSIONS]["abc"] = Schema();
+ newer = new SchemaMap(map);
+ newer->GetChanges(older, &removed, &added);
+ ASSERT_EQ(2u, removed.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_CHROME, ""), removed[0]);
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), removed[1]);
+ ASSERT_EQ(1u, added.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), added[0]);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_registry.cc b/components/policy/core/common/schema_registry.cc
new file mode 100644
index 0000000000..e44f3d7751
--- /dev/null
+++ b/components/policy/core/common/schema_registry.cc
@@ -0,0 +1,278 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry.h"
+
+#include "base/logging.h"
+#include "extensions/buildflags/buildflags.h"
+
+namespace policy {
+
+SchemaRegistry::Observer::~Observer() {}
+
+SchemaRegistry::InternalObserver::~InternalObserver() {}
+
+SchemaRegistry::SchemaRegistry() : schema_map_(new SchemaMap) {
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i)
+ domains_ready_[i] = false;
+#if !BUILDFLAG(ENABLE_EXTENSIONS)
+ SetExtensionsDomainsReady();
+#endif
+}
+
+SchemaRegistry::~SchemaRegistry() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (auto& observer : internal_observers_)
+ observer.OnSchemaRegistryShuttingDown(this);
+}
+
+void SchemaRegistry::RegisterComponent(const PolicyNamespace& ns,
+ const Schema& schema) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ ComponentMap map;
+ map[ns.component_id] = schema;
+ RegisterComponents(ns.domain, map);
+}
+
+void SchemaRegistry::RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Don't issue notifications if nothing is being registered.
+ if (components.empty())
+ return;
+ // Assume that a schema was updated if the namespace was already registered
+ // before.
+ DomainMap map(schema_map_->GetDomains());
+ for (ComponentMap::const_iterator it = components.begin();
+ it != components.end(); ++it) {
+ map[domain][it->first] = it->second;
+ }
+ schema_map_ = new SchemaMap(map);
+ Notify(true);
+}
+
+void SchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DomainMap map(schema_map_->GetDomains());
+ if (map[ns.domain].erase(ns.component_id) != 0) {
+ schema_map_ = new SchemaMap(map);
+ Notify(false);
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool SchemaRegistry::IsReady() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i) {
+ if (!domains_ready_[i])
+ return false;
+ }
+ return true;
+}
+
+void SchemaRegistry::SetDomainReady(PolicyDomain domain) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (domains_ready_[domain])
+ return;
+ domains_ready_[domain] = true;
+ if (IsReady()) {
+ for (auto& observer : observers_)
+ observer.OnSchemaRegistryReady();
+ }
+}
+
+void SchemaRegistry::SetAllDomainsReady() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i)
+ SetDomainReady(static_cast<PolicyDomain>(i));
+}
+
+void SchemaRegistry::SetExtensionsDomainsReady() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ SetDomainReady(POLICY_DOMAIN_EXTENSIONS);
+ SetDomainReady(POLICY_DOMAIN_SIGNIN_EXTENSIONS);
+}
+
+void SchemaRegistry::AddObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.AddObserver(observer);
+}
+
+void SchemaRegistry::RemoveObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.RemoveObserver(observer);
+}
+
+void SchemaRegistry::AddInternalObserver(InternalObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ internal_observers_.AddObserver(observer);
+}
+
+void SchemaRegistry::RemoveInternalObserver(InternalObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ internal_observers_.RemoveObserver(observer);
+}
+
+void SchemaRegistry::Notify(bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (auto& observer : observers_)
+ observer.OnSchemaRegistryUpdated(has_new_schemas);
+}
+
+CombinedSchemaRegistry::CombinedSchemaRegistry()
+ : own_schema_map_(new SchemaMap) {
+ // The combined registry is always ready, since it can always start tracking
+ // another registry that is not ready yet and going from "ready" to "not
+ // ready" is not allowed.
+ SetAllDomainsReady();
+}
+
+CombinedSchemaRegistry::~CombinedSchemaRegistry() {}
+
+void CombinedSchemaRegistry::Track(SchemaRegistry* registry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ registries_.insert(registry);
+ registry->AddObserver(this);
+ registry->AddInternalObserver(this);
+ // Recombine the maps only if the |registry| has any components other than
+ // POLICY_DOMAIN_CHROME.
+ if (registry->schema_map()->HasComponents())
+ Combine(true);
+}
+
+void CombinedSchemaRegistry::RegisterComponents(
+ PolicyDomain domain,
+ const ComponentMap& components) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DomainMap map(own_schema_map_->GetDomains());
+ for (ComponentMap::const_iterator it = components.begin();
+ it != components.end(); ++it) {
+ map[domain][it->first] = it->second;
+ }
+ own_schema_map_ = new SchemaMap(map);
+ Combine(true);
+}
+
+void CombinedSchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DomainMap map(own_schema_map_->GetDomains());
+ if (map[ns.domain].erase(ns.component_id) != 0) {
+ own_schema_map_ = new SchemaMap(map);
+ Combine(false);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void CombinedSchemaRegistry::OnSchemaRegistryUpdated(bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ Combine(has_new_schemas);
+}
+
+void CombinedSchemaRegistry::OnSchemaRegistryShuttingDown(
+ SchemaRegistry* registry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ registry->RemoveObserver(this);
+ registry->RemoveInternalObserver(this);
+ if (registries_.erase(registry) != 0) {
+ if (registry->schema_map()->HasComponents())
+ Combine(false);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void CombinedSchemaRegistry::Combine(bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // If two registries publish a Schema for the same component then it's
+ // undefined which version gets in the combined registry.
+ //
+ // The common case is that both registries want policy for the same component,
+ // and the Schemas should be the same; in that case this makes no difference.
+ //
+ // But if the Schemas are different then one of the components is out of date.
+ // In that case the policy loaded will be valid only for one of them, until
+ // the outdated components are updated. This is a known limitation of the
+ // way policies are loaded currently, but isn't a problem worth fixing for
+ // the time being.
+ DomainMap map(own_schema_map_->GetDomains());
+ for (std::set<SchemaRegistry*>::const_iterator reg_it = registries_.begin();
+ reg_it != registries_.end(); ++reg_it) {
+ const DomainMap& reg_domain_map = (*reg_it)->schema_map()->GetDomains();
+ for (DomainMap::const_iterator domain_it = reg_domain_map.begin();
+ domain_it != reg_domain_map.end(); ++domain_it) {
+ const ComponentMap& reg_component_map = domain_it->second;
+ for (ComponentMap::const_iterator comp_it = reg_component_map.begin();
+ comp_it != reg_component_map.end(); ++comp_it) {
+ map[domain_it->first][comp_it->first] = comp_it->second;
+ }
+ }
+ }
+ schema_map_ = new SchemaMap(map);
+ Notify(has_new_schemas);
+}
+
+ForwardingSchemaRegistry::ForwardingSchemaRegistry(SchemaRegistry* wrapped)
+ : wrapped_(wrapped) {
+ schema_map_ = wrapped_->schema_map();
+ wrapped_->AddObserver(this);
+ wrapped_->AddInternalObserver(this);
+ UpdateReadiness();
+}
+
+ForwardingSchemaRegistry::~ForwardingSchemaRegistry() {
+ if (wrapped_) {
+ wrapped_->RemoveObserver(this);
+ wrapped_->RemoveInternalObserver(this);
+ }
+}
+
+void ForwardingSchemaRegistry::RegisterComponents(
+ PolicyDomain domain,
+ const ComponentMap& components) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // POLICY_DOMAIN_CHROME is skipped to avoid spurious updates when a new
+ // Profile is created. If the ForwardingSchemaRegistry is used outside
+ // device-level accounts then this should become configurable.
+ if (wrapped_ && domain != POLICY_DOMAIN_CHROME)
+ wrapped_->RegisterComponents(domain, components);
+ // Ignore otherwise.
+}
+
+void ForwardingSchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (wrapped_)
+ wrapped_->UnregisterComponent(ns);
+ // Ignore otherwise.
+}
+
+void ForwardingSchemaRegistry::OnSchemaRegistryUpdated(bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ schema_map_ = wrapped_->schema_map();
+ Notify(has_new_schemas);
+}
+
+void ForwardingSchemaRegistry::OnSchemaRegistryReady() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ UpdateReadiness();
+}
+
+void ForwardingSchemaRegistry::OnSchemaRegistryShuttingDown(
+ SchemaRegistry* registry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(wrapped_, registry);
+ wrapped_->RemoveObserver(this);
+ wrapped_->RemoveInternalObserver(this);
+ wrapped_ = nullptr;
+ // Keep serving the same |schema_map_|.
+}
+
+void ForwardingSchemaRegistry::UpdateReadiness() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (wrapped_->IsReady())
+ SetAllDomainsReady();
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_registry.h b/components/policy/core/common/schema_registry.h
new file mode 100644
index 0000000000..a1d26a5934
--- /dev/null
+++ b/components/policy/core/common/schema_registry.h
@@ -0,0 +1,170 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
+
+#include <set>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class SchemaMap;
+
+// Holds the main reference to the current SchemaMap, and allows a list of
+// observers to get notified whenever it is updated.
+// This object is not thread safe and must be used from the owner's thread,
+// usually UI.
+class POLICY_EXPORT SchemaRegistry {
+ public:
+ class POLICY_EXPORT Observer {
+ public:
+ // Invoked whenever schemas are registered or unregistered.
+ // |has_new_schemas| is true if a new component has been registered since
+ // the last update; this allows observers to ignore updates when
+ // components are unregistered but still get a handle to the current map
+ // (e.g. for periodic reloads).
+ virtual void OnSchemaRegistryUpdated(bool has_new_schemas) = 0;
+
+ // Invoked when all policy domains become ready.
+ virtual void OnSchemaRegistryReady() {}
+
+ protected:
+ virtual ~Observer();
+ };
+
+ // This observer is only meant to be used by subclasses.
+ class POLICY_EXPORT InternalObserver {
+ public:
+ // Invoked when |registry| is about to be destroyed.
+ virtual void OnSchemaRegistryShuttingDown(SchemaRegistry* registry) = 0;
+
+ protected:
+ virtual ~InternalObserver();
+ };
+
+ SchemaRegistry();
+ virtual ~SchemaRegistry();
+
+ const scoped_refptr<SchemaMap>& schema_map() const { return schema_map_; }
+
+ // Register a single component.
+ void RegisterComponent(const PolicyNamespace& ns,
+ const Schema& schema);
+
+ // Register a list of components for a given domain.
+ virtual void RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components);
+
+ virtual void UnregisterComponent(const PolicyNamespace& ns);
+
+ // Returns true if all domains have registered the initial components.
+ bool IsReady() const;
+
+ // This indicates that the initial components for |domain| have all been
+ // registered. It must be invoked at least once for each policy domain;
+ // subsequent calls for the same domain are ignored.
+ void SetDomainReady(PolicyDomain domain);
+ // This is equivalent to calling |SetDomainReady| with each of the policy
+ // domains.
+ void SetAllDomainsReady();
+ // This is equivalent to calling |SetDomainReady| with each of the domains
+ // that correspond to policy for extensions.
+ void SetExtensionsDomainsReady();
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ void AddInternalObserver(InternalObserver* observer);
+ void RemoveInternalObserver(InternalObserver* observer);
+
+ protected:
+ void Notify(bool has_new_schemas);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ scoped_refptr<SchemaMap> schema_map_;
+
+ private:
+ base::ObserverList<Observer, true> observers_;
+ base::ObserverList<InternalObserver, true> internal_observers_;
+ bool domains_ready_[POLICY_DOMAIN_SIZE];
+
+ DISALLOW_COPY_AND_ASSIGN(SchemaRegistry);
+};
+
+// A registry that combines the maps of other registries.
+class POLICY_EXPORT CombinedSchemaRegistry
+ : public SchemaRegistry,
+ public SchemaRegistry::Observer,
+ public SchemaRegistry::InternalObserver {
+ public:
+ CombinedSchemaRegistry();
+ ~CombinedSchemaRegistry() override;
+
+ void Track(SchemaRegistry* registry);
+
+ // SchemaRegistry:
+ void RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components) override;
+ void UnregisterComponent(const PolicyNamespace& ns) override;
+
+ // SchemaRegistry::Observer:
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+
+ // SchemaRegistry::InternalObserver:
+ void OnSchemaRegistryShuttingDown(SchemaRegistry* registry) override;
+
+ private:
+ void Combine(bool has_new_schemas);
+
+ std::set<SchemaRegistry*> registries_;
+ scoped_refptr<SchemaMap> own_schema_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(CombinedSchemaRegistry);
+};
+
+// A registry that wraps another schema registry.
+class POLICY_EXPORT ForwardingSchemaRegistry
+ : public SchemaRegistry,
+ public SchemaRegistry::Observer,
+ public SchemaRegistry::InternalObserver {
+ public:
+ // This registry will stop updating its SchemaMap when |wrapped| is
+ // destroyed.
+ explicit ForwardingSchemaRegistry(SchemaRegistry* wrapped);
+ ~ForwardingSchemaRegistry() override;
+
+ // SchemaRegistry:
+ void RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components) override;
+ void UnregisterComponent(const PolicyNamespace& ns) override;
+
+ // SchemaRegistry::Observer:
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+ void OnSchemaRegistryReady() override;
+
+ // SchemaRegistry::InternalObserver:
+ void OnSchemaRegistryShuttingDown(SchemaRegistry* registry) override;
+
+ private:
+ void UpdateReadiness();
+
+ SchemaRegistry* wrapped_;
+
+ DISALLOW_COPY_AND_ASSIGN(ForwardingSchemaRegistry);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
diff --git a/components/policy/core/common/schema_registry_tracking_policy_provider.cc b/components/policy/core/common/schema_registry_tracking_policy_provider.cc
new file mode 100644
index 0000000000..5f15b33fda
--- /dev/null
+++ b/components/policy/core/common/schema_registry_tracking_policy_provider.cc
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry_tracking_policy_provider.h"
+
+#include <utility>
+
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/core/common/schema_registry.h"
+
+namespace policy {
+
+SchemaRegistryTrackingPolicyProvider::SchemaRegistryTrackingPolicyProvider(
+ ConfigurationPolicyProvider* delegate)
+ : delegate_(delegate), state_(WAITING_FOR_REGISTRY_READY) {
+ delegate_->AddObserver(this);
+ // Serve the initial |delegate_| policies.
+ OnUpdatePolicy(delegate_);
+}
+
+SchemaRegistryTrackingPolicyProvider::~SchemaRegistryTrackingPolicyProvider() {
+ delegate_->RemoveObserver(this);
+}
+
+void SchemaRegistryTrackingPolicyProvider::Init(SchemaRegistry* registry) {
+ ConfigurationPolicyProvider::Init(registry);
+ if (registry->IsReady())
+ OnSchemaRegistryReady();
+}
+
+bool SchemaRegistryTrackingPolicyProvider::IsInitializationComplete(
+ PolicyDomain domain) const {
+ if (domain == POLICY_DOMAIN_CHROME)
+ return delegate_->IsInitializationComplete(domain);
+ // This provider keeps its own state for all the other domains.
+ return state_ == READY;
+}
+
+void SchemaRegistryTrackingPolicyProvider::RefreshPolicies() {
+ delegate_->RefreshPolicies();
+}
+
+void SchemaRegistryTrackingPolicyProvider::OnSchemaRegistryReady() {
+ DCHECK_EQ(WAITING_FOR_REGISTRY_READY, state_);
+ // This provider's registry is ready, meaning that it has all the initial
+ // components schemas; the delegate's registry should also see them now,
+ // since it's tracking the former.
+ // Asking the delegate to RefreshPolicies now means that the next
+ // OnUpdatePolicy from the delegate will have the initial policy for
+ // components.
+ if (!schema_map()->HasComponents()) {
+ // If there are no component registered for this provider then there's no
+ // need to reload.
+ state_ = READY;
+ OnUpdatePolicy(delegate_);
+ return;
+ }
+
+ state_ = WAITING_FOR_REFRESH;
+ RefreshPolicies();
+}
+
+void SchemaRegistryTrackingPolicyProvider::OnSchemaRegistryUpdated(
+ bool has_new_schemas) {
+ if (state_ != READY)
+ return;
+ if (has_new_schemas) {
+ RefreshPolicies();
+ } else {
+ // Remove the policies that were being served for the component that have
+ // been removed. This is important so that update notifications are also
+ // sent in case those component are reinstalled during the current session.
+ OnUpdatePolicy(delegate_);
+ }
+}
+
+void SchemaRegistryTrackingPolicyProvider::OnUpdatePolicy(
+ ConfigurationPolicyProvider* provider) {
+ DCHECK_EQ(delegate_, provider);
+
+ if (state_ == WAITING_FOR_REFRESH)
+ state_ = READY;
+
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ if (state_ == READY) {
+ bundle->CopyFrom(delegate_->policies());
+ schema_map()->FilterBundle(bundle.get());
+ } else {
+ // Always pass on the Chrome policy, even if the components are not ready
+ // yet.
+ const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ bundle->Get(chrome_ns).CopyFrom(delegate_->policies().Get(chrome_ns));
+ }
+
+ UpdatePolicy(std::move(bundle));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_registry_tracking_policy_provider.h b/components/policy/core/common/schema_registry_tracking_policy_provider.h
new file mode 100644
index 0000000000..1ec64c3bc9
--- /dev/null
+++ b/components/policy/core/common/schema_registry_tracking_policy_provider.h
@@ -0,0 +1,92 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_TRACKING_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_TRACKING_POLICY_PROVIDER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A policy provider that relies on a delegate provider to obtain policy
+// settings, but uses a different SchemaRegistry to determine which policy
+// namespaces to request from the delegate provider.
+//
+// This provider tracks the SchemaRegistry's state, and becomes ready after
+// making sure the delegate provider has refreshed its policies with an updated
+// view of the complete schema. It is expected that the delegate's
+// SchemaRegistry is a CombinedSchemaRegistry tracking the
+// SchemaRegistryTrackingPolicyProvider's registry.
+//
+// This policy provider implementation is used to wrap the platform policy
+// provider for use with individual profiles, which may have different
+// SchemaRegistries. The SchemaRegistryTrackingPolicyProvider ensures that
+// initialization completion is only signaled for non-Chrome PolicyDomains after
+// the SchemaRegistry is fully initialized. This is important to avoid flapping
+// on startup due to asynchronous SchemaRegistry initialization while the
+// underlying policy provider has already completed initialization.
+//
+// A concrete example of this is POLICY_DOMAIN_EXTENSIONS, which registers
+// the PolicyNamespaces for the different extensions it's interested in based
+// on what extensions are installed in a Profile. Before that happens, the
+// underlying policy providers will not load the corresponding policy, so at
+// startup there would be a window during which the policy appears to be not
+// present. This is avoided by only flagging POLICY_DOMAIN_EXTENSIONS ready
+// once the corresponding SchemaRegistry has been fully initialized with the
+// list of installed extensions.
+class POLICY_EXPORT SchemaRegistryTrackingPolicyProvider
+ : public ConfigurationPolicyProvider,
+ public ConfigurationPolicyProvider::Observer {
+ public:
+ // The |delegate| must outlive this provider.
+ explicit SchemaRegistryTrackingPolicyProvider(
+ ConfigurationPolicyProvider* delegate);
+ ~SchemaRegistryTrackingPolicyProvider() override;
+
+ // ConfigurationPolicyProvider:
+ //
+ // Note that Init() and Shutdown() are not forwarded to the |delegate_|, since
+ // this provider does not own it and its up to the |delegate_|'s owner to
+ // initialize it and shut it down.
+ //
+ // Note also that this provider may have a SchemaRegistry passed in Init()
+ // that doesn't match the |delegate_|'s; therefore OnSchemaRegistryUpdated()
+ // and OnSchemaRegistryReady() are not forwarded either. It is assumed that
+ // the |delegate_|'s SchemaRegistry contains a superset of this provider's
+ // SchemaRegistry though (i.e. it's a CombinedSchemaRegistry that contains
+ // this provider's SchemaRegistry).
+ //
+ // This provider manages its own initialization state for all policy domains
+ // except POLICY_DOMAIN_CHROME, whose status is always queried from the
+ // |delegate_|. RefreshPolicies() calls are also forwarded, since this
+ // provider doesn't have a "real" policy source of its own.
+ void Init(SchemaRegistry* registry) override;
+ bool IsInitializationComplete(PolicyDomain domain) const override;
+ void RefreshPolicies() override;
+ void OnSchemaRegistryReady() override;
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+
+ // ConfigurationPolicyProvider::Observer:
+ void OnUpdatePolicy(ConfigurationPolicyProvider* provider) override;
+
+ private:
+ enum InitializationState {
+ WAITING_FOR_REGISTRY_READY,
+ WAITING_FOR_REFRESH,
+ READY,
+ };
+
+ ConfigurationPolicyProvider* delegate_;
+ InitializationState state_;
+
+ DISALLOW_COPY_AND_ASSIGN(SchemaRegistryTrackingPolicyProvider);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_TRACKING_POLICY_PROVIDER_H_
diff --git a/components/policy/core/common/schema_registry_tracking_policy_provider_unittest.cc b/components/policy/core/common/schema_registry_tracking_policy_provider_unittest.cc
new file mode 100644
index 0000000000..5def4983f4
--- /dev/null
+++ b/components/policy/core/common/schema_registry_tracking_policy_provider_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry_tracking_policy_provider.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/values.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+using testing::Return;
+using testing::_;
+
+namespace policy {
+
+namespace {
+
+const char kTestSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"foo\": { \"type\": \"string\" }"
+ " }"
+ "}";
+
+} // namespace
+
+class SchemaRegistryTrackingPolicyProviderTest : public testing::Test {
+ protected:
+ SchemaRegistryTrackingPolicyProviderTest()
+ : schema_registry_tracking_provider_(&mock_provider_) {
+ mock_provider_.Init();
+ schema_registry_tracking_provider_.Init(&schema_registry_);
+ schema_registry_tracking_provider_.AddObserver(&observer_);
+ }
+
+ ~SchemaRegistryTrackingPolicyProviderTest() override {
+ schema_registry_tracking_provider_.RemoveObserver(&observer_);
+ schema_registry_tracking_provider_.Shutdown();
+ mock_provider_.Shutdown();
+ }
+
+ Schema CreateTestSchema() {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ if (!schema.valid())
+ ADD_FAILURE() << error;
+ return schema;
+ }
+
+ SchemaRegistry schema_registry_;
+ MockConfigurationPolicyObserver observer_;
+ MockConfigurationPolicyProvider mock_provider_;
+ SchemaRegistryTrackingPolicyProvider schema_registry_tracking_provider_;
+};
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, Empty) {
+ EXPECT_FALSE(schema_registry_.IsReady());
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ POLICY_DOMAIN_EXTENSIONS));
+
+ EXPECT_CALL(mock_provider_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillOnce(Return(false));
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ POLICY_DOMAIN_CHROME));
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(empty_bundle));
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, PassOnChromePolicy) {
+ PolicyBundle bundle;
+ const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ bundle.Get(chrome_ns).Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>("visible"), nullptr);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ std::unique_ptr<PolicyBundle> delegate_bundle(new PolicyBundle);
+ delegate_bundle->CopyFrom(bundle);
+ delegate_bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"))
+ .Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("not visible"),
+ nullptr);
+ mock_provider_.UpdatePolicy(std::move(delegate_bundle));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(schema_registry_tracking_provider_.policies().Equals(bundle));
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, RefreshPolicies) {
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ schema_registry_tracking_provider_.RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, SchemaReady) {
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ schema_registry_.SetAllDomainsReady();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_TRUE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, SchemaReadyWithComponents) {
+ PolicyMap policy_map;
+ policy_map.Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("omg"),
+ nullptr);
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, "")).CopyFrom(policy_map);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"))
+ .CopyFrom(policy_map);
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ mock_provider_.UpdatePolicy(std::move(bundle));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(mock_provider_, RefreshPolicies()).Times(0);
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), CreateTestSchema());
+ schema_registry_.SetExtensionsDomainsReady();
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ schema_registry_.SetDomainReady(POLICY_DOMAIN_CHROME);
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""))
+ .CopyFrom(policy_map);
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(expected_bundle));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ schema_registry_tracking_provider_.OnUpdatePolicy(&mock_provider_);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_TRUE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"))
+ .CopyFrom(policy_map);
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(expected_bundle));
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, DelegateUpdates) {
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), CreateTestSchema());
+ EXPECT_FALSE(schema_registry_.IsReady());
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+
+ PolicyMap policy_map;
+ policy_map.Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("omg"),
+ nullptr);
+ // Chrome policy updates are visible even if the components aren't ready.
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ mock_provider_.UpdateChromePolicy(policy_map);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ schema_registry_.SetAllDomainsReady();
+ EXPECT_TRUE(schema_registry_.IsReady());
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+
+ // The provider becomes ready after this refresh completes, and policy updates
+ // are visible after that.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ mock_provider_.UpdateChromePolicy(policy_map);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_TRUE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+
+ // Updates continue to be visible.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ mock_provider_.UpdateChromePolicy(policy_map);
+ Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, RemoveAndAddComponent) {
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "xyz");
+ schema_registry_.RegisterComponent(ns, CreateTestSchema());
+ schema_registry_.SetAllDomainsReady();
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ // Serve policy for |ns|.
+ PolicyBundle platform_policy;
+ platform_policy.Get(ns).Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>("omg"), nullptr);
+ std::unique_ptr<PolicyBundle> copy(new PolicyBundle);
+ copy->CopyFrom(platform_policy);
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ mock_provider_.UpdatePolicy(std::move(copy));
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(platform_policy));
+
+ // Now remove that component.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ schema_registry_.UnregisterComponent(ns);
+ Mock::VerifyAndClearExpectations(&observer_);
+ const PolicyBundle empty;
+ EXPECT_TRUE(schema_registry_tracking_provider_.policies().Equals(empty));
+
+ // Adding it back should serve the current policies again, even though they
+ // haven't changed on the platform provider.
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ schema_registry_.RegisterComponent(ns, CreateTestSchema());
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ copy.reset(new PolicyBundle);
+ copy->CopyFrom(platform_policy);
+ mock_provider_.UpdatePolicy(std::move(copy));
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(platform_policy));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_registry_unittest.cc b/components/policy/core/common/schema_registry_unittest.cc
new file mode 100644
index 0000000000..aea54d1cc3
--- /dev/null
+++ b/components/policy/core/common/schema_registry_unittest.cc
@@ -0,0 +1,329 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry.h"
+
+#include <memory>
+
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "extensions/buildflags/buildflags.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Mock;
+using ::testing::_;
+
+namespace policy {
+
+namespace {
+
+const char kTestSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"string\": { \"type\": \"string\" },"
+ " \"integer\": { \"type\": \"integer\" },"
+ " \"boolean\": { \"type\": \"boolean\" },"
+ " \"null\": { \"type\": \"null\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"list\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"object\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"a\": { \"type\": \"string\" },"
+ " \"b\": { \"type\": \"integer\" }"
+ " }"
+ " }"
+ " }"
+ "}";
+
+class MockSchemaRegistryObserver : public SchemaRegistry::Observer {
+ public:
+ MockSchemaRegistryObserver() {}
+ ~MockSchemaRegistryObserver() override {}
+
+ MOCK_METHOD1(OnSchemaRegistryUpdated, void(bool));
+ MOCK_METHOD0(OnSchemaRegistryReady, void());
+};
+
+bool SchemaMapEquals(const scoped_refptr<SchemaMap>& schema_map1,
+ const scoped_refptr<SchemaMap>& schema_map2) {
+ PolicyNamespaceList added;
+ PolicyNamespaceList removed;
+ schema_map1->GetChanges(schema_map2, &removed, &added);
+ return added.empty() && removed.empty();
+}
+
+} // namespace
+
+TEST(SchemaRegistryTest, Notifications) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ MockSchemaRegistryObserver observer;
+ SchemaRegistry registry;
+ registry.AddObserver(&observer);
+
+ ASSERT_TRUE(registry.schema_map().get());
+ EXPECT_FALSE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Re-register also triggers notifications, because the Schema might have
+ // changed.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_TRUE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_FALSE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ // Registering multiple components at once issues only one notification.
+ ComponentMap components;
+ components["abc"] = schema;
+ components["def"] = schema;
+ components["xyz"] = schema;
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponents(POLICY_DOMAIN_EXTENSIONS, components);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ registry.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, IsReady) {
+ SchemaRegistry registry;
+ MockSchemaRegistryObserver observer;
+ registry.AddObserver(&observer);
+
+ EXPECT_FALSE(registry.IsReady());
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ EXPECT_CALL(observer, OnSchemaRegistryReady()).Times(0);
+ registry.SetExtensionsDomainsReady();
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(registry.IsReady());
+#endif
+ EXPECT_CALL(observer, OnSchemaRegistryReady());
+ registry.SetDomainReady(POLICY_DOMAIN_CHROME);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(registry.IsReady());
+ EXPECT_CALL(observer, OnSchemaRegistryReady()).Times(0);
+ registry.SetDomainReady(POLICY_DOMAIN_CHROME);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(registry.IsReady());
+
+ CombinedSchemaRegistry combined;
+ EXPECT_TRUE(combined.IsReady());
+
+ registry.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, Combined) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ MockSchemaRegistryObserver observer;
+ std::unique_ptr<SchemaRegistry> registry1(new SchemaRegistry);
+ std::unique_ptr<SchemaRegistry> registry2(new SchemaRegistry);
+ CombinedSchemaRegistry combined;
+ combined.AddObserver(&observer);
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Starting to track a registry issues notifications when it comes with new
+ // schemas.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ combined.Track(registry1.get());
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding a new empty registry does not trigger notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ combined.Track(registry2.get());
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding the same component to the combined registry itself triggers
+ // notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ combined.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding components to the sub-registries triggers notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry2->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // If the same component is published in 2 sub-registries then the combined
+ // registry publishes one of them.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ ASSERT_EQ(1u, combined.schema_map()->GetDomains().size());
+ ASSERT_TRUE(combined.schema_map()->GetComponents(POLICY_DOMAIN_EXTENSIONS));
+ ASSERT_EQ(
+ 2u,
+ combined.schema_map()->GetComponents(POLICY_DOMAIN_EXTENSIONS)->size());
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry1->UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Still registered at the combined registry.
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ combined.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Now it's gone.
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry1->UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Still registered at registry2.
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry2->UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Now it's gone.
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)).Times(2);
+ registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_CHROME, ""),
+ schema);
+ registry2->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "hij"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Untracking |registry1| doesn't trigger an update notification, because it
+ // doesn't contain any components.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ registry1.reset();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry2.reset();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ combined.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, ForwardingSchemaRegistry) {
+ std::unique_ptr<SchemaRegistry> registry(new SchemaRegistry);
+ ForwardingSchemaRegistry forwarding(registry.get());
+ MockSchemaRegistryObserver observer;
+ forwarding.AddObserver(&observer);
+
+ EXPECT_FALSE(registry->IsReady());
+ EXPECT_FALSE(forwarding.IsReady());
+ // They always have the same SchemaMap.
+ EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map()));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ Schema());
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map()));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry->UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map()));
+
+ // No notifications expected for these calls.
+ EXPECT_FALSE(registry->IsReady());
+ EXPECT_FALSE(forwarding.IsReady());
+
+ registry->SetExtensionsDomainsReady();
+ EXPECT_FALSE(registry->IsReady());
+ EXPECT_FALSE(forwarding.IsReady());
+
+ EXPECT_CALL(observer, OnSchemaRegistryReady());
+ registry->SetDomainReady(POLICY_DOMAIN_CHROME);
+ EXPECT_TRUE(registry->IsReady());
+ EXPECT_TRUE(forwarding.IsReady());
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map()));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ forwarding.SetExtensionsDomainsReady();
+ forwarding.SetDomainReady(POLICY_DOMAIN_CHROME);
+ EXPECT_TRUE(forwarding.IsReady());
+
+ // Keep the same SchemaMap when the original registry is gone.
+ // No notifications are expected in this case either.
+ scoped_refptr<SchemaMap> schema_map = registry->schema_map();
+ registry.reset();
+ EXPECT_TRUE(SchemaMapEquals(schema_map, forwarding.schema_map()));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ forwarding.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, ForwardingSchemaRegistryReadiness) {
+ std::unique_ptr<SchemaRegistry> registry(new SchemaRegistry);
+
+ ForwardingSchemaRegistry forwarding_1(registry.get());
+ EXPECT_FALSE(registry->IsReady());
+ EXPECT_FALSE(forwarding_1.IsReady());
+
+ // Once the wrapped registry gets ready, the forwarding schema registry
+ // becomes ready too.
+ registry->SetAllDomainsReady();
+ EXPECT_TRUE(registry->IsReady());
+ EXPECT_TRUE(forwarding_1.IsReady());
+
+ // The wrapped registry was ready at the time when the forwarding registry was
+ // constructed, so the forwarding registry is immediately ready too.
+ ForwardingSchemaRegistry forwarding_2(registry.get());
+ EXPECT_TRUE(forwarding_2.IsReady());
+
+ // Destruction of the wrapped registry doesn't change the readiness of the
+ // forwarding registry.
+ registry.reset();
+ EXPECT_TRUE(forwarding_1.IsReady());
+ EXPECT_TRUE(forwarding_2.IsReady());
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_unittest.cc b/components/policy/core/common/schema_unittest.cc
new file mode 100644
index 0000000000..4e861d126c
--- /dev/null
+++ b/components/policy/core/common/schema_unittest.cc
@@ -0,0 +1,1286 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "components/policy/core/common/schema_internal.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+#define TestSchemaValidation(a, b, c, d) \
+ TestSchemaValidationHelper( \
+ base::StringPrintf("%s:%i", __FILE__, __LINE__), a, b, c, d)
+
+const char kTestSchema[] = R"({
+ "type": "object",
+ "properties": {
+ "Boolean": { "type": "boolean" },
+ "Integer": { "type": "integer" },
+ "Null": { "type": "null" },
+ "Number": { "type": "number" },
+ "String": { "type": "string" },
+ "Array": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "ArrayOfObjects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "one": { "type": "string" },
+ "two": { "type": "integer" }
+ }
+ }
+ },
+ "ArrayOfArray": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ },
+ "Object": {
+ "type": "object",
+ "properties": {
+ "one": { "type": "boolean" },
+ "two": { "type": "integer" }
+ },
+ "additionalProperties": { "type": "string" }
+ },
+ "ObjectOfObject": {
+ "type": "object",
+ "properties": {
+ "Object": {
+ "type": "object",
+ "properties": {
+ "one": { "type": "string" },
+ "two": { "type": "integer" }
+ }
+ }
+ }
+ },
+ "IntegerWithEnums": {
+ "type": "integer",
+ "enum": [1, 2, 3]
+ },
+ "IntegerWithEnumsGaps": {
+ "type": "integer",
+ "enum": [10, 20, 30]
+ },
+ "StringWithEnums": {
+ "type": "string",
+ "enum": ["one", "two", "three"]
+ },
+ "IntegerWithRange": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 3
+ },
+ "ObjectOfArray": {
+ "type": "object",
+ "properties": {
+ "List": {
+ "type": "array",
+ "items": { "type": "integer" }
+ }
+ }
+ },
+ "ArrayOfObjectOfArray": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "List": {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+ }
+ },
+ "StringWithPattern": {
+ "type": "string",
+ "pattern": "^foo+$"
+ },
+ "ObjectWithPatternProperties": {
+ "type": "object",
+ "patternProperties": {
+ "^foo+$": { "type": "integer" },
+ "^bar+$": {
+ "type": "string",
+ "enum": ["one", "two"]
+ }
+ },
+ "properties": {
+ "bar": {
+ "type": "string",
+ "enum": ["one", "three"]
+ }
+ }
+ },
+ "ObjectWithRequiredProperties": {
+ "type": "object",
+ "properties": {
+ "Integer": {
+ "type": "integer",
+ "enum": [1, 2]
+ },
+ "String": { "type": "string" },
+ "Number": { "type": "number" }
+ },
+ "patternProperties": {
+ "^Integer": {
+ "type": "integer",
+ "enum": [1, 3]
+ }
+ },
+ "required": [ "Integer", "String" ]
+ }
+ }
+})";
+
+bool ParseFails(const std::string& content) {
+ std::string error;
+ Schema schema = Schema::Parse(content, &error);
+ if (schema.valid())
+ return false;
+ EXPECT_FALSE(error.empty());
+ return true;
+}
+
+void TestSchemaValidationHelper(const std::string& source,
+ Schema schema,
+ const base::Value& value,
+ SchemaOnErrorStrategy strategy,
+ bool expected_return_value) {
+ std::string error;
+ static const char kNoErrorReturned[] = "No error returned.";
+
+ // Test that Schema::Validate() works as expected.
+ error = kNoErrorReturned;
+ bool returned = schema.Validate(value, strategy, nullptr, &error);
+ ASSERT_EQ(expected_return_value, returned) << source << ": " << error;
+
+ // Test that Schema::Normalize() will return the same value as
+ // Schema::Validate().
+ error = kNoErrorReturned;
+ std::unique_ptr<base::Value> cloned_value(value.DeepCopy());
+ bool touched = false;
+ returned =
+ schema.Normalize(cloned_value.get(), strategy, nullptr, &error, &touched);
+ EXPECT_EQ(expected_return_value, returned) << source << ": " << error;
+
+ bool strictly_valid = schema.Validate(value, SCHEMA_STRICT, nullptr, &error);
+ EXPECT_EQ(touched, !strictly_valid && returned) << source;
+
+ // Test that Schema::Normalize() have actually dropped invalid and unknown
+ // properties.
+ if (expected_return_value) {
+ EXPECT_TRUE(schema.Validate(*cloned_value, SCHEMA_STRICT, nullptr, &error))
+ << source;
+ EXPECT_TRUE(schema.Normalize(cloned_value.get(), SCHEMA_STRICT, nullptr,
+ &error, nullptr))
+ << source;
+ }
+}
+
+void TestSchemaValidationWithPath(Schema schema,
+ const base::Value& value,
+ const std::string& expected_failure_path) {
+ std::string error_path = "NOT_SET";
+ std::string error;
+
+ bool returned = schema.Validate(value, SCHEMA_STRICT, &error_path, &error);
+ ASSERT_FALSE(returned) << error_path;
+ EXPECT_EQ(error_path, expected_failure_path);
+}
+
+std::string SchemaObjectWrapper(const std::string& subschema) {
+ return "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"SomePropertyName\":" + subschema +
+ " }"
+ "}";
+}
+
+} // namespace
+
+TEST(SchemaTest, MinimalSchema) {
+ EXPECT_FALSE(ParseFails(R"({ "type": "object" })"));
+}
+
+TEST(SchemaTest, InvalidSchemas) {
+ EXPECT_TRUE(ParseFails(""));
+ EXPECT_TRUE(ParseFails("omg"));
+ EXPECT_TRUE(ParseFails("\"omg\""));
+ EXPECT_TRUE(ParseFails("123"));
+ EXPECT_TRUE(ParseFails("[]"));
+ EXPECT_TRUE(ParseFails("null"));
+ EXPECT_TRUE(ParseFails("{}"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "additionalProperties": { "type":"object" }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "patternProperties": { "a+b*": { "type": "object" } }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": { "Policy": { "type": "bogus" } }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": { "Policy": { "type": ["string", "number"] } }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": { "Policy": { "type": "any" } }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": { "Policy": 123 }
+ })"));
+
+ EXPECT_FALSE(ParseFails(R"({
+ "type": "object",
+ "unknown attribute": "is ignored"
+ })"));
+}
+
+TEST(SchemaTest, Ownership) {
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "sub": {
+ "type": "object",
+ "properties": {
+ "subsub": { "type": "string" }
+ }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ schema = schema.GetKnownProperty("sub");
+ ASSERT_TRUE(schema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ {
+ Schema::Iterator it = schema.GetPropertiesIterator();
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_STREQ("subsub", it.key());
+
+ schema = it.schema();
+ it.Advance();
+ EXPECT_TRUE(it.IsAtEnd());
+ }
+
+ ASSERT_TRUE(schema.valid());
+ EXPECT_EQ(base::Value::Type::STRING, schema.type());
+
+ // This test shouldn't leak nor use invalid memory.
+}
+
+TEST(SchemaTest, ValidSchema) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+ EXPECT_FALSE(schema.GetProperty("invalid").valid());
+
+ Schema sub = schema.GetProperty("Boolean");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, sub.type());
+
+ sub = schema.GetProperty("Integer");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, sub.type());
+
+ sub = schema.GetProperty("Null");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::NONE, sub.type());
+
+ sub = schema.GetProperty("Number");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::DOUBLE, sub.type());
+
+ sub = schema.GetProperty("String");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("Array");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ sub = sub.GetItems();
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("ArrayOfObjects");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ sub = sub.GetItems();
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, sub.type());
+ Schema subsub = sub.GetProperty("one");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, subsub.type());
+ subsub = sub.GetProperty("two");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, subsub.type());
+ subsub = sub.GetProperty("invalid");
+ EXPECT_FALSE(subsub.valid());
+
+ sub = schema.GetProperty("ArrayOfArray");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ sub = sub.GetItems();
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ sub = sub.GetItems();
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("Object");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, sub.type());
+ subsub = sub.GetProperty("one");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, subsub.type());
+ subsub = sub.GetProperty("two");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, subsub.type());
+ subsub = sub.GetProperty("undeclared");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, subsub.type());
+
+ sub = schema.GetProperty("IntegerWithEnums");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
+
+ sub = schema.GetProperty("IntegerWithEnumsGaps");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
+
+ sub = schema.GetProperty("StringWithEnums");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("IntegerWithRange");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
+
+ sub = schema.GetProperty("StringWithPattern");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("ObjectWithPatternProperties");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, sub.type());
+
+ sub = schema.GetProperty("ObjectWithRequiredProperties");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, sub.type());
+
+ struct {
+ const char* expected_key;
+ base::Value::Type expected_type;
+ } kExpectedProperties[] = {
+ { "Array", base::Value::Type::LIST },
+ { "ArrayOfArray", base::Value::Type::LIST },
+ { "ArrayOfObjectOfArray", base::Value::Type::LIST },
+ { "ArrayOfObjects", base::Value::Type::LIST },
+ { "Boolean", base::Value::Type::BOOLEAN },
+ { "Integer", base::Value::Type::INTEGER },
+ { "IntegerWithEnums", base::Value::Type::INTEGER },
+ { "IntegerWithEnumsGaps", base::Value::Type::INTEGER },
+ { "IntegerWithRange", base::Value::Type::INTEGER },
+ { "Null", base::Value::Type::NONE },
+ { "Number", base::Value::Type::DOUBLE },
+ { "Object", base::Value::Type::DICTIONARY },
+ { "ObjectOfArray", base::Value::Type::DICTIONARY },
+ { "ObjectOfObject", base::Value::Type::DICTIONARY },
+ { "ObjectWithPatternProperties", base::Value::Type::DICTIONARY },
+ { "ObjectWithRequiredProperties", base::Value::Type::DICTIONARY },
+ { "String", base::Value::Type::STRING },
+ { "StringWithEnums", base::Value::Type::STRING },
+ { "StringWithPattern", base::Value::Type::STRING },
+ };
+ Schema::Iterator it = schema.GetPropertiesIterator();
+ for (size_t i = 0; i < arraysize(kExpectedProperties); ++i) {
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_STREQ(kExpectedProperties[i].expected_key, it.key());
+ ASSERT_TRUE(it.schema().valid());
+ EXPECT_EQ(kExpectedProperties[i].expected_type, it.schema().type());
+ it.Advance();
+ }
+ EXPECT_TRUE(it.IsAtEnd());
+}
+
+TEST(SchemaTest, Lookups) {
+ std::string error;
+
+ Schema schema = Schema::Parse(R"({ "type": "object" })", &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ // This empty schema should never find named properties.
+ EXPECT_FALSE(schema.GetKnownProperty("").valid());
+ EXPECT_FALSE(schema.GetKnownProperty("xyz").valid());
+ EXPECT_TRUE(schema.GetPropertiesIterator().IsAtEnd());
+
+ schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "Boolean": { "type": "boolean" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_FALSE(schema.GetKnownProperty("").valid());
+ EXPECT_FALSE(schema.GetKnownProperty("xyz").valid());
+ EXPECT_TRUE(schema.GetKnownProperty("Boolean").valid());
+
+ schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "bb" : { "type": "null" },
+ "aa" : { "type": "boolean" },
+ "abab" : { "type": "string" },
+ "ab" : { "type": "number" },
+ "aba" : { "type": "integer" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_FALSE(schema.GetKnownProperty("").valid());
+ EXPECT_FALSE(schema.GetKnownProperty("xyz").valid());
+
+ struct {
+ const char* expected_key;
+ base::Value::Type expected_type;
+ } kExpectedKeys[] = {
+ { "aa", base::Value::Type::BOOLEAN },
+ { "ab", base::Value::Type::DOUBLE },
+ { "aba", base::Value::Type::INTEGER },
+ { "abab", base::Value::Type::STRING },
+ { "bb", base::Value::Type::NONE },
+ };
+ for (size_t i = 0; i < arraysize(kExpectedKeys); ++i) {
+ Schema sub = schema.GetKnownProperty(kExpectedKeys[i].expected_key);
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(kExpectedKeys[i].expected_type, sub.type());
+ }
+
+ schema = Schema::Parse(R"(
+ {
+ "type": "object",
+ "properties": {
+ "String": { "type": "string" },
+ "Object": {
+ "type": "object",
+ "properties": {"Integer": {"type": "integer"}},
+ "required": [ "Integer" ]
+ },
+ "Number": { "type": "number" }
+ },
+ "required": [ "String", "Object"]
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_EQ(std::vector<std::string>({"String", "Object"}),
+ schema.GetRequiredProperties());
+
+ schema = schema.GetKnownProperty("Object");
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_EQ(std::vector<std::string>({"Integer"}),
+ schema.GetRequiredProperties());
+}
+
+TEST(SchemaTest, Wrap) {
+ const internal::SchemaNode kSchemas[] = {
+ { base::Value::Type::DICTIONARY, 0 }, // 0: root node
+ { base::Value::Type::BOOLEAN, -1 }, // 1
+ { base::Value::Type::INTEGER, -1 }, // 2
+ { base::Value::Type::DOUBLE, -1 }, // 3
+ { base::Value::Type::STRING, -1 }, // 4
+ { base::Value::Type::LIST, 4 }, // 5: list of strings.
+ { base::Value::Type::LIST, 5 }, // 6: list of lists of strings.
+ { base::Value::Type::INTEGER, 0 }, // 7: integer enumerations.
+ { base::Value::Type::INTEGER, 1 }, // 8: ranged integers.
+ { base::Value::Type::STRING, 2 }, // 9: string enumerations.
+ { base::Value::Type::STRING, 3 }, // 10: string with pattern.
+ { base::Value::Type::DICTIONARY, 1 }, // 11: dictionary with required
+ // properties
+ };
+
+ const internal::PropertyNode kPropertyNodes[] = {
+ { "Boolean", 1}, // 0
+ { "DictRequired", 11}, // 1
+ { "Integer", 2}, // 2
+ { "List", 5}, // 3
+ { "Number", 3}, // 4
+ { "String", 4}, // 5
+ { "IntEnum", 7}, // 6
+ { "RangedInt", 8}, // 7
+ { "StrEnum", 9}, // 8
+ { "StrPat", 10}, // 9
+ { "bar+$", 4}, // 10
+ { "String", 4}, // 11
+ { "Number", 3}, // 12
+ };
+
+ const internal::PropertiesNode kProperties[] = {
+ // 0 to 10 (exclusive) are the known properties in kPropertyNodes, 9 is
+ // patternProperties and 6 is the additionalProperties node.
+ { 0, 10, 11, 0, 0, 6 },
+ // 11 to 13 (exclusive) are the known properties in kPropertyNodes. 0 to
+ // 1 (exclusive) are the required properties in kRequired. -1 indicates
+ // no additionalProperties.
+ { 11, 13, 13, 0, 1, -1 },
+ };
+
+ const internal::RestrictionNode kRestriction[] = {
+ {{0, 3}}, // 0: [1, 2, 3]
+ {{5, 1}}, // 1: minimum = 1, maximum = 5
+ {{0, 3}}, // 2: ["one", "two", "three"]
+ {{3, 3}}, // 3: pattern "foo+"
+ };
+
+ const char* kRequired[] = {"String"};
+
+ const int kIntEnums[] = {1, 2, 3};
+
+ const char* kStringEnums[] = {
+ "one", // 0
+ "two", // 1
+ "three", // 2
+ "foo+", // 3
+ };
+
+ const internal::SchemaData kData = {
+ kSchemas,
+ kPropertyNodes,
+ kProperties,
+ kRestriction,
+ kRequired,
+ kIntEnums,
+ kStringEnums,
+ };
+
+ Schema schema = Schema::Wrap(&kData);
+ ASSERT_TRUE(schema.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ struct {
+ const char* key;
+ base::Value::Type type;
+ } kExpectedProperties[] = {
+ { "Boolean", base::Value::Type::BOOLEAN },
+ { "DictRequired", base::Value::Type::DICTIONARY },
+ { "Integer", base::Value::Type::INTEGER },
+ { "List", base::Value::Type::LIST },
+ { "Number", base::Value::Type::DOUBLE },
+ { "String", base::Value::Type::STRING },
+ { "IntEnum", base::Value::Type::INTEGER },
+ { "RangedInt", base::Value::Type::INTEGER },
+ { "StrEnum", base::Value::Type::STRING },
+ { "StrPat", base::Value::Type::STRING },
+ };
+
+ Schema::Iterator it = schema.GetPropertiesIterator();
+ for (size_t i = 0; i < arraysize(kExpectedProperties); ++i) {
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_STREQ(kExpectedProperties[i].key, it.key());
+ Schema sub = it.schema();
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(kExpectedProperties[i].type, sub.type());
+
+ if (sub.type() == base::Value::Type::LIST) {
+ Schema items = sub.GetItems();
+ ASSERT_TRUE(items.valid());
+ EXPECT_EQ(base::Value::Type::STRING, items.type());
+ }
+
+ it.Advance();
+ }
+ EXPECT_TRUE(it.IsAtEnd());
+
+ Schema sub = schema.GetAdditionalProperties();
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ Schema subsub = sub.GetItems();
+ ASSERT_TRUE(subsub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, subsub.type());
+ Schema subsubsub = subsub.GetItems();
+ ASSERT_TRUE(subsubsub.valid());
+ ASSERT_EQ(base::Value::Type::STRING, subsubsub.type());
+
+ SchemaList schema_list = schema.GetPatternProperties("barr");
+ ASSERT_EQ(1u, schema_list.size());
+ sub = schema_list[0];
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sub.type());
+
+ EXPECT_TRUE(schema.GetPatternProperties("ba").empty());
+ EXPECT_TRUE(schema.GetPatternProperties("bar+$").empty());
+
+ Schema dict = schema.GetKnownProperty("DictRequired");
+ ASSERT_TRUE(dict.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, dict.type());
+
+ EXPECT_EQ(std::vector<std::string>({"String"}), dict.GetRequiredProperties());
+}
+
+TEST(SchemaTest, Validate) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ base::DictionaryValue bundle;
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+
+ // Wrong type, expected integer.
+ bundle.SetBoolean("Integer", true);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+
+ // Wrong type, expected list of strings.
+ {
+ bundle.Clear();
+ base::ListValue list;
+ list.AppendInteger(1);
+ bundle.SetKey("Array", std::move(list));
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ }
+
+ // Wrong type in a sub-object.
+ {
+ bundle.Clear();
+ base::DictionaryValue dict;
+ dict.SetString("one", "one");
+ bundle.SetKey("Object", std::move(dict));
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ }
+
+ // Unknown name.
+ bundle.Clear();
+ bundle.SetBoolean("Unknown", true);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+
+ // All of these will be valid.
+ bundle.Clear();
+ bundle.SetBoolean("Boolean", true);
+ bundle.SetInteger("Integer", 123);
+ bundle.Set("Null", std::make_unique<base::Value>());
+ bundle.SetDouble("Number", 3.14);
+ bundle.SetString("String", "omg");
+
+ {
+ base::ListValue list;
+ list.AppendString("a string");
+ list.AppendString("another string");
+ bundle.SetKey("Array", std::move(list));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetString("one", "string");
+ dict.SetInteger("two", 2);
+ base::ListValue list;
+ list.GetList().push_back(dict.Clone());
+ list.GetList().push_back(std::move(dict));
+ bundle.SetKey("ArrayOfObjects", std::move(list));
+ }
+
+ {
+ base::ListValue list;
+ list.AppendString("a string");
+ list.AppendString("another string");
+ base::ListValue listlist;
+ listlist.GetList().push_back(list.Clone());
+ listlist.GetList().push_back(std::move(list));
+ bundle.SetKey("ArrayOfArray", std::move(listlist));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetBoolean("one", true);
+ dict.SetInteger("two", 2);
+ dict.SetString("additionally", "a string");
+ dict.SetString("and also", "another string");
+ bundle.SetKey("Object", std::move(dict));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetInteger("Integer", 1);
+ dict.SetString("String", "a string");
+ dict.SetDouble("Number", 3.14);
+ bundle.SetKey("ObjectWithRequiredProperties", std::move(dict));
+ }
+
+ bundle.SetInteger("IntegerWithEnums", 1);
+ bundle.SetInteger("IntegerWithEnumsGaps", 20);
+ bundle.SetString("StringWithEnums", "two");
+ bundle.SetInteger("IntegerWithRange", 3);
+
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+
+ bundle.SetInteger("IntegerWithEnums", 0);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnums", 1);
+
+ bundle.SetInteger("IntegerWithEnumsGaps", 0);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 9);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 10);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+ bundle.SetInteger("IntegerWithEnumsGaps", 11);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 19);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 21);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 29);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 30);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+ bundle.SetInteger("IntegerWithEnumsGaps", 31);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 100);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 20);
+
+ bundle.SetString("StringWithEnums", "FOUR");
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetString("StringWithEnums", "two");
+
+ bundle.SetInteger("IntegerWithRange", 4);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithRange", 3);
+
+ // Unknown top level property.
+ bundle.SetString("boom", "bang");
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, true);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidationWithPath(schema, bundle, "");
+ bundle.Remove("boom", nullptr);
+
+ // Invalid top level property.
+ bundle.SetInteger("Boolean", 12345);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(schema, bundle, "Boolean");
+ bundle.SetBoolean("Boolean", true);
+
+ // Tests on ObjectOfObject.
+ {
+ Schema subschema = schema.GetProperty("ObjectOfObject");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ // Unknown property.
+ root.SetBoolean("Object.three", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "Object");
+ root.Remove("Object.three", nullptr);
+
+ // Invalid property.
+ root.SetInteger("Object.one", 12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "Object.one");
+ root.Remove("Object.one", nullptr);
+ }
+
+ // Tests on ArrayOfObjects.
+ {
+ Schema subschema = schema.GetProperty("ArrayOfObjects");
+ ASSERT_TRUE(subschema.valid());
+ base::ListValue root;
+
+ // Unknown property.
+ std::unique_ptr<base::DictionaryValue> dict_value(
+ new base::DictionaryValue());
+ dict_value->SetBoolean("three", true);
+ root.Append(std::move(dict_value));
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0]");
+ root.Remove(root.GetSize() - 1, nullptr);
+
+ // Invalid property.
+ dict_value.reset(new base::DictionaryValue());
+ dict_value->SetBoolean("two", true);
+ root.Append(std::move(dict_value));
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0].two");
+ root.Remove(root.GetSize() - 1, nullptr);
+ }
+
+ // Tests on ObjectOfArray.
+ {
+ Schema subschema = schema.GetProperty("ObjectOfArray");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ base::ListValue* list_value =
+ root.SetList("List", std::make_unique<base::ListValue>());
+
+ // Test that there are not errors here.
+ list_value->AppendInteger(12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+
+ // Invalid list item.
+ list_value->AppendString("blabla");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "List.items[1]");
+ }
+
+ // Tests on ArrayOfObjectOfArray.
+ {
+ Schema subschema = schema.GetProperty("ArrayOfObjectOfArray");
+ ASSERT_TRUE(subschema.valid());
+ base::ListValue root;
+
+ auto dict_value = std::make_unique<base::DictionaryValue>();
+ base::ListValue* list_value =
+ dict_value->SetList("List", std::make_unique<base::ListValue>());
+ root.Append(std::move(dict_value));
+
+ // Test that there are not errors here.
+ list_value->AppendString("blabla");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+
+ // Invalid list item.
+ list_value->AppendInteger(12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0].List.items[1]");
+ }
+
+ // Tests on StringWithPattern.
+ {
+ Schema subschema = schema.GetProperty("StringWithPattern");
+ ASSERT_TRUE(subschema.valid());
+
+ TestSchemaValidation(subschema, base::Value("foobar"), SCHEMA_STRICT,
+ false);
+ TestSchemaValidation(subschema, base::Value("foo"), SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, base::Value("fo"), SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, base::Value("fooo"), SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, base::Value("^foo+$"), SCHEMA_STRICT,
+ false);
+ }
+
+ // Tests on ObjectWithPatternProperties.
+ {
+ Schema subschema = schema.GetProperty("ObjectWithPatternProperties");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ ASSERT_EQ(1u, subschema.GetPatternProperties("fooo").size());
+ ASSERT_EQ(1u, subschema.GetPatternProperties("foo").size());
+ ASSERT_EQ(1u, subschema.GetPatternProperties("barr").size());
+ ASSERT_EQ(1u, subschema.GetPatternProperties("bar").size());
+ ASSERT_EQ(1u, subschema.GetMatchingProperties("fooo").size());
+ ASSERT_EQ(1u, subschema.GetMatchingProperties("foo").size());
+ ASSERT_EQ(1u, subschema.GetMatchingProperties("barr").size());
+ ASSERT_EQ(2u, subschema.GetMatchingProperties("bar").size());
+ ASSERT_TRUE(subschema.GetPatternProperties("foobar").empty());
+
+ root.SetInteger("fooo", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetBoolean("fooo", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.Remove("fooo", nullptr);
+
+ root.SetInteger("foo", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetBoolean("foo", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.Remove("foo", nullptr);
+
+ root.SetString("barr", "one");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetString("barr", "three");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.SetBoolean("barr", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.Remove("barr", nullptr);
+
+ root.SetString("bar", "one");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetString("bar", "two");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.SetString("bar", "three");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.Remove("bar", nullptr);
+
+ root.SetInteger("foobar", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ root.Remove("foobar", nullptr);
+ }
+
+ // Tests on ObjectWithRequiredProperties
+ {
+ Schema subschema = schema.GetProperty("ObjectWithRequiredProperties");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ // Required property missing.
+ root.SetInteger("Integer", 1);
+ root.SetDouble("Number", 3.14);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, false);
+
+ // Invalid required property.
+ root.SetInteger("String", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, false);
+ root.SetString("String", "a string");
+
+ // Invalid subschema of required property with multiple subschemas.
+ //
+ // The "Integer" property has two subschemas, one in "properties" and one
+ // in "patternProperties". The first test generates a valid schema for the
+ // first subschema and the second test generates a valid schema for the
+ // second subschema. In both cases validation should fail because one of the
+ // required properties is invalid.
+ root.SetInteger("Integer", 2);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, false);
+
+ root.SetInteger("Integer", 3);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, false);
+ }
+
+ // Test that integer to double promotion is allowed.
+ bundle.SetInteger("Number", 31415);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+}
+
+TEST(SchemaTest, InvalidReferences) {
+ // References to undeclared schemas fail.
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": {
+ "name": { "$ref": "undeclared" }
+ }
+ })"));
+
+ // Can't refer to self.
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": {
+ "name": {
+ "id": "self",
+ "$ref": "self"
+ }
+ }
+ })"));
+
+ // Duplicated IDs are invalid.
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": {
+ "name": {
+ "id": "x",
+ "type": "string"
+ },
+ "another": {
+ "id": "x",
+ "type": "string"
+ }
+ }
+ })"));
+
+ // Main object can't be a reference.
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "id": "main",
+ "$ref": "main"
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "$ref": "main"
+ })"));
+}
+
+TEST(SchemaTest, RecursiveReferences) {
+ // Verifies that references can go to a parent schema, to define a
+ // recursive type.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "bookmarks": {
+ "type": "array",
+ "id": "ListOfBookmarks",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "url": { "type": "string" },
+ "children": { "$ref": "ListOfBookmarks" }
+ }
+ }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema parent = schema.GetKnownProperty("bookmarks");
+ ASSERT_TRUE(parent.valid());
+ ASSERT_EQ(base::Value::Type::LIST, parent.type());
+
+ // Check the recursive type a number of times.
+ for (int i = 0; i < 10; ++i) {
+ Schema items = parent.GetItems();
+ ASSERT_TRUE(items.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, items.type());
+
+ Schema prop = items.GetKnownProperty("name");
+ ASSERT_TRUE(prop.valid());
+ ASSERT_EQ(base::Value::Type::STRING, prop.type());
+
+ prop = items.GetKnownProperty("url");
+ ASSERT_TRUE(prop.valid());
+ ASSERT_EQ(base::Value::Type::STRING, prop.type());
+
+ prop = items.GetKnownProperty("children");
+ ASSERT_TRUE(prop.valid());
+ ASSERT_EQ(base::Value::Type::LIST, prop.type());
+
+ parent = prop;
+ }
+}
+
+TEST(SchemaTest, UnorderedReferences) {
+ // Verifies that references and IDs can come in any order.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "a": { "$ref": "shared" },
+ "b": { "$ref": "shared" },
+ "c": { "$ref": "shared" },
+ "d": { "$ref": "shared" },
+ "e": {
+ "type": "boolean",
+ "id": "shared"
+ },
+ "f": { "$ref": "shared" },
+ "g": { "$ref": "shared" },
+ "h": { "$ref": "shared" },
+ "i": { "$ref": "shared" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ for (char c = 'a'; c <= 'i'; ++c) {
+ Schema sub = schema.GetKnownProperty(std::string(1, c));
+ ASSERT_TRUE(sub.valid()) << c;
+ ASSERT_EQ(base::Value::Type::BOOLEAN, sub.type()) << c;
+ }
+}
+
+TEST(SchemaTest, AdditionalPropertiesReference) {
+ // Verifies that "additionalProperties" can be a reference.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "policy": {
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "boolean",
+ "id": "FooId"
+ }
+ },
+ "additionalProperties": { "$ref": "FooId" }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema policy = schema.GetKnownProperty("policy");
+ ASSERT_TRUE(policy.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, policy.type());
+
+ Schema foo = policy.GetKnownProperty("foo");
+ ASSERT_TRUE(foo.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, foo.type());
+
+ Schema additional = policy.GetAdditionalProperties();
+ ASSERT_TRUE(additional.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, additional.type());
+
+ Schema x = policy.GetProperty("x");
+ ASSERT_TRUE(x.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, x.type());
+}
+
+TEST(SchemaTest, ItemsReference) {
+ // Verifies that "items" can be a reference.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "boolean",
+ "id": "FooId"
+ },
+ "list": {
+ "type": "array",
+ "items": { "$ref": "FooId" }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema foo = schema.GetKnownProperty("foo");
+ ASSERT_TRUE(foo.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, foo.type());
+
+ Schema list = schema.GetKnownProperty("list");
+ ASSERT_TRUE(list.valid());
+ ASSERT_EQ(base::Value::Type::LIST, list.type());
+
+ Schema items = list.GetItems();
+ ASSERT_TRUE(items.valid());
+ ASSERT_EQ(base::Value::Type::BOOLEAN, items.type());
+}
+
+TEST(SchemaTest, EnumerationRestriction) {
+ // Enum attribute is a list.
+ EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "string",
+ "enum": 12
+ })")));
+
+ // Empty enum attributes is not allowed.
+ EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "integer",
+ "enum": []
+ })")));
+
+ // Enum elements type should be same as stated.
+ EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "string",
+ "enum": [1, 2, 3]
+ })")));
+
+ EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "integer",
+ "enum": [1, 2, 3]
+ })")));
+
+ EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "string",
+ "enum": ["1", "2", "3"]
+ })")));
+}
+
+TEST(SchemaTest, RangedRestriction) {
+ EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "integer",
+ "minimum": 10,
+ "maximum": 5
+ })")));
+
+ EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "integer",
+ "minimum": 10,
+ "maximum": 20
+ })")));
+}
+
+} // namespace policy
diff --git a/components/timers/alarm_timer_unittest.cc b/components/timers/alarm_timer_unittest.cc
new file mode 100644
index 0000000000..868eb787ab
--- /dev/null
+++ b/components/timers/alarm_timer_unittest.cc
@@ -0,0 +1,372 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sys/timerfd.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_descriptor_watcher_posix.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/timers/alarm_timer_chromeos.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Most of these tests have been lifted right out of timer_unittest.cc with only
+// cosmetic changes. We want the AlarmTimer to be a drop-in replacement for the
+// regular Timer so it should pass the same tests as the Timer class.
+namespace timers {
+namespace {
+const base::TimeDelta kTenMilliseconds = base::TimeDelta::FromMilliseconds(10);
+
+class AlarmTimerTester {
+ public:
+ AlarmTimerTester(bool* did_run,
+ base::TimeDelta delay,
+ base::OnceClosure quit_closure)
+ : did_run_(did_run),
+ quit_closure_(std::move(quit_closure)),
+ delay_(delay),
+ timer_(new timers::SimpleAlarmTimer()) {}
+ void Start() {
+ timer_->Start(
+ FROM_HERE, delay_,
+ base::BindRepeating(&AlarmTimerTester::Run, base::Unretained(this)));
+ }
+
+ private:
+ void Run() {
+ *did_run_ = true;
+ if (quit_closure_)
+ std::move(quit_closure_).Run();
+ }
+
+ bool* did_run_;
+ base::OnceClosure quit_closure_;
+ const base::TimeDelta delay_;
+ std::unique_ptr<timers::SimpleAlarmTimer> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(AlarmTimerTester);
+};
+
+class SelfDeletingAlarmTimerTester {
+ public:
+ SelfDeletingAlarmTimerTester(bool* did_run,
+ base::TimeDelta delay,
+ base::OnceClosure quit_closure)
+ : did_run_(did_run),
+ quit_closure_(std::move(quit_closure)),
+ delay_(delay),
+ timer_(new timers::SimpleAlarmTimer()) {}
+ void Start() {
+ timer_->Start(FROM_HERE, delay_,
+ base::BindRepeating(&SelfDeletingAlarmTimerTester::Run,
+ base::Unretained(this)));
+ }
+
+ private:
+ void Run() {
+ *did_run_ = true;
+ timer_.reset();
+
+ if (quit_closure_)
+ std::move(quit_closure_).Run();
+ }
+
+ bool* did_run_;
+ base::OnceClosure quit_closure_;
+ const base::TimeDelta delay_;
+ std::unique_ptr<timers::SimpleAlarmTimer> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelfDeletingAlarmTimerTester);
+};
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+// Each test is run against each type of MessageLoop. That way we are sure
+// that timers work properly in all configurations.
+
+TEST(AlarmTimerTest, SimpleAlarmTimer) {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+
+ base::RunLoop run_loop;
+ bool did_run = false;
+ AlarmTimerTester f(&did_run, kTenMilliseconds,
+ run_loop.QuitWhenIdleClosure());
+ f.Start();
+
+ run_loop.Run();
+
+ EXPECT_TRUE(did_run);
+}
+
+TEST(AlarmTimerTest, SimpleAlarmTimer_Cancel) {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+
+ bool did_run_a = false;
+ AlarmTimerTester* a =
+ new AlarmTimerTester(&did_run_a, kTenMilliseconds, base::OnceClosure());
+
+ // This should run before the timer expires.
+ base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, a);
+
+ // Now start the timer.
+ a->Start();
+
+ base::RunLoop run_loop;
+ bool did_run_b = false;
+ AlarmTimerTester b(&did_run_b, kTenMilliseconds,
+ run_loop.QuitWhenIdleClosure());
+ b.Start();
+
+ run_loop.Run();
+
+ EXPECT_FALSE(did_run_a);
+ EXPECT_TRUE(did_run_b);
+}
+
+// If underlying timer does not handle this properly, we will crash or fail
+// in full page heap environment.
+TEST(AlarmTimerTest, SelfDeletingAlarmTimer) {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+
+ base::RunLoop run_loop;
+ bool did_run = false;
+ SelfDeletingAlarmTimerTester f(&did_run, kTenMilliseconds,
+ run_loop.QuitWhenIdleClosure());
+ f.Start();
+
+ run_loop.Run();
+
+ EXPECT_TRUE(did_run);
+}
+
+TEST(AlarmTimerTest, AlarmTimerZeroDelay) {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+
+ base::RunLoop run_loop;
+ bool did_run = false;
+ AlarmTimerTester f(&did_run, base::TimeDelta(),
+ run_loop.QuitWhenIdleClosure());
+ f.Start();
+
+ run_loop.Run();
+
+ EXPECT_TRUE(did_run);
+}
+
+TEST(AlarmTimerTest, AlarmTimerZeroDelay_Cancel) {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+
+ bool did_run_a = false;
+ AlarmTimerTester* a =
+ new AlarmTimerTester(&did_run_a, base::TimeDelta(), base::OnceClosure());
+
+ // This should run before the timer expires.
+ base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, a);
+
+ // Now start the timer.
+ a->Start();
+
+ base::RunLoop run_loop;
+ bool did_run_b = false;
+ AlarmTimerTester b(&did_run_b, base::TimeDelta(),
+ run_loop.QuitWhenIdleClosure());
+ b.Start();
+
+ run_loop.Run();
+
+ EXPECT_FALSE(did_run_a);
+ EXPECT_TRUE(did_run_b);
+}
+
+TEST(AlarmTimerTest, MessageLoopShutdown) {
+ // This test is designed to verify that shutdown of the
+ // message loop does not cause crashes if there were pending
+ // timers not yet fired. It may only trigger exceptions
+ // if debug heap checking is enabled.
+ bool did_run = false;
+ {
+ auto loop = std::make_unique<base::MessageLoopForIO>();
+ auto file_descriptor_watcher =
+ std::make_unique<base::FileDescriptorWatcher>(loop.get());
+ AlarmTimerTester a(&did_run, kTenMilliseconds, base::OnceClosure());
+ AlarmTimerTester b(&did_run, kTenMilliseconds, base::OnceClosure());
+ AlarmTimerTester c(&did_run, kTenMilliseconds, base::OnceClosure());
+ AlarmTimerTester d(&did_run, kTenMilliseconds, base::OnceClosure());
+
+ a.Start();
+ b.Start();
+
+ // Allow FileDescriptorWatcher to start watching the timers. Without this,
+ // tasks posted by FileDescriptorWatcher::WatchReadable() are leaked.
+ base::RunLoop().RunUntilIdle();
+
+ // MessageLoop and FileDescriptorWatcher destruct.
+ file_descriptor_watcher.reset();
+ loop.reset();
+ } // SimpleAlarmTimers destruct. SHOULD NOT CRASH, of course.
+
+ EXPECT_FALSE(did_run);
+}
+
+TEST(AlarmTimerTest, NonRepeatIsRunning) {
+ {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+ timers::SimpleAlarmTimer timer;
+ EXPECT_FALSE(timer.IsRunning());
+ timer.Start(FROM_HERE, base::TimeDelta::FromDays(1), base::DoNothing());
+
+ // Allow FileDescriptorWatcher to start watching the timer. Without this, a
+ // task posted by FileDescriptorWatcher::WatchReadable() is leaked.
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(timer.IsRunning());
+ timer.Stop();
+ EXPECT_FALSE(timer.IsRunning());
+ ASSERT_FALSE(timer.user_task().is_null());
+ timer.Reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(timer.IsRunning());
+ }
+}
+
+TEST(AlarmTimerTest, RetainNonRepeatIsRunning) {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+ timers::SimpleAlarmTimer timer;
+ EXPECT_FALSE(timer.IsRunning());
+ timer.Start(FROM_HERE, base::TimeDelta::FromDays(1), base::DoNothing());
+
+ // Allow FileDescriptorWatcher to start watching the timer. Without this, a
+ // task posted by FileDescriptorWatcher::WatchReadable() is leaked.
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(timer.IsRunning());
+ timer.Reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(timer.IsRunning());
+ timer.Stop();
+ EXPECT_FALSE(timer.IsRunning());
+ timer.Reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(timer.IsRunning());
+}
+
+namespace {
+
+bool g_callback_happened1 = false;
+bool g_callback_happened2 = false;
+
+void ClearAllCallbackHappened() {
+ g_callback_happened1 = false;
+ g_callback_happened2 = false;
+}
+
+void SetCallbackHappened1(base::OnceClosure quit_closure) {
+ g_callback_happened1 = true;
+ if (quit_closure)
+ std::move(quit_closure).Run();
+}
+
+void SetCallbackHappened2(base::OnceClosure quit_closure) {
+ g_callback_happened2 = true;
+ if (quit_closure)
+ std::move(quit_closure).Run();
+}
+
+TEST(AlarmTimerTest, ContinuationStopStart) {
+ ClearAllCallbackHappened();
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+ timers::SimpleAlarmTimer timer;
+ timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(10),
+ base::BindRepeating(&SetCallbackHappened1,
+ base::DoNothing().Repeatedly()));
+ timer.Stop();
+
+ base::RunLoop run_loop;
+ timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(40),
+ base::BindRepeating(&SetCallbackHappened2,
+ run_loop.QuitWhenIdleClosure()));
+ run_loop.Run();
+
+ EXPECT_FALSE(g_callback_happened1);
+ EXPECT_TRUE(g_callback_happened2);
+}
+
+TEST(AlarmTimerTest, ContinuationReset) {
+ ClearAllCallbackHappened();
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+
+ base::RunLoop run_loop;
+ timers::SimpleAlarmTimer timer;
+ timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(10),
+ base::BindRepeating(&SetCallbackHappened1,
+ run_loop.QuitWhenIdleClosure()));
+ timer.Reset();
+ ASSERT_FALSE(timer.user_task().is_null());
+ run_loop.Run();
+ EXPECT_TRUE(g_callback_happened1);
+}
+
+// Verify that no crash occurs if a timer is deleted while its callback is
+// running.
+TEST(AlarmTimerTest, DeleteTimerWhileCallbackIsRunning) {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+ base::RunLoop run_loop;
+
+ // Will be deleted by the callback.
+ timers::SimpleAlarmTimer* timer = new timers::SimpleAlarmTimer;
+
+ timer->Start(
+ FROM_HERE, base::TimeDelta::FromMilliseconds(10),
+ base::BindRepeating(
+ [](timers::SimpleAlarmTimer* timer, base::RunLoop* run_loop) {
+ delete timer;
+ run_loop->Quit();
+ },
+ timer, &run_loop));
+ run_loop.Run();
+}
+
+// Verify that no crash occurs if a zero-delay timer is deleted while its
+// callback is running.
+TEST(AlarmTimerTest, DeleteTimerWhileCallbackIsRunningZeroDelay) {
+ base::MessageLoopForIO loop;
+ base::FileDescriptorWatcher file_descriptor_watcher(&loop);
+ base::RunLoop run_loop;
+
+ // Will be deleted by the callback.
+ timers::SimpleAlarmTimer* timer = new timers::SimpleAlarmTimer;
+
+ timer->Start(
+ FROM_HERE, base::TimeDelta(),
+ base::BindRepeating(
+ [](timers::SimpleAlarmTimer* timer, base::RunLoop* run_loop) {
+ delete timer;
+ run_loop->Quit();
+ },
+ timer, &run_loop));
+ run_loop.Run();
+}
+
+} // namespace
+} // namespace timers
diff --git a/dbus/bus_unittest.cc b/dbus/bus_unittest.cc
new file mode 100644
index 0000000000..f00d1e9374
--- /dev/null
+++ b/dbus/bus_unittest.cc
@@ -0,0 +1,420 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/bus.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_descriptor_watcher_posix.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "dbus/exported_object.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "dbus/scoped_dbus_error.h"
+#include "dbus/test_service.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+namespace {
+
+// Test helper for BusTest.ListenForServiceOwnerChange that wraps a
+// base::RunLoop. At Run() time, the caller pass in the expected number of
+// quit calls, and at QuitIfConditionIsSatisified() time, only quit the RunLoop
+// if the expected number of quit calls have been reached.
+class RunLoopWithExpectedCount {
+ public:
+ RunLoopWithExpectedCount() : expected_quit_calls_(0), actual_quit_calls_(0) {}
+ ~RunLoopWithExpectedCount() = default;
+
+ void Run(int expected_quit_calls) {
+ DCHECK_EQ(0, expected_quit_calls_);
+ DCHECK_EQ(0, actual_quit_calls_);
+ expected_quit_calls_ = expected_quit_calls;
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+ }
+
+ void QuitIfConditionIsSatisified() {
+ if (++actual_quit_calls_ != expected_quit_calls_)
+ return;
+ run_loop_->Quit();
+ expected_quit_calls_ = 0;
+ actual_quit_calls_ = 0;
+ }
+
+ private:
+ std::unique_ptr<base::RunLoop> run_loop_;
+ int expected_quit_calls_;
+ int actual_quit_calls_;
+
+ DISALLOW_COPY_AND_ASSIGN(RunLoopWithExpectedCount);
+};
+
+// Test helper for BusTest.ListenForServiceOwnerChange.
+void OnServiceOwnerChanged(RunLoopWithExpectedCount* run_loop_state,
+ std::string* service_owner,
+ int* num_of_owner_changes,
+ const std::string& new_service_owner) {
+ *service_owner = new_service_owner;
+ ++(*num_of_owner_changes);
+ run_loop_state->QuitIfConditionIsSatisified();
+}
+
+} // namespace
+
+TEST(BusTest, GetObjectProxy) {
+ Bus::Options options;
+ scoped_refptr<Bus> bus = new Bus(options);
+
+ ObjectProxy* object_proxy1 =
+ bus->GetObjectProxy("org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(object_proxy1);
+
+ // This should return the same object.
+ ObjectProxy* object_proxy2 =
+ bus->GetObjectProxy("org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(object_proxy2);
+ EXPECT_EQ(object_proxy1, object_proxy2);
+
+ // This should not.
+ ObjectProxy* object_proxy3 =
+ bus->GetObjectProxy(
+ "org.chromium.TestService",
+ ObjectPath("/org/chromium/DifferentTestObject"));
+ ASSERT_TRUE(object_proxy3);
+ EXPECT_NE(object_proxy1, object_proxy3);
+
+ bus->ShutdownAndBlock();
+}
+
+TEST(BusTest, GetObjectProxyIgnoreUnknownService) {
+ Bus::Options options;
+ scoped_refptr<Bus> bus = new Bus(options);
+
+ ObjectProxy* object_proxy1 =
+ bus->GetObjectProxyWithOptions(
+ "org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"),
+ ObjectProxy::IGNORE_SERVICE_UNKNOWN_ERRORS);
+ ASSERT_TRUE(object_proxy1);
+
+ // This should return the same object.
+ ObjectProxy* object_proxy2 =
+ bus->GetObjectProxyWithOptions(
+ "org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"),
+ ObjectProxy::IGNORE_SERVICE_UNKNOWN_ERRORS);
+ ASSERT_TRUE(object_proxy2);
+ EXPECT_EQ(object_proxy1, object_proxy2);
+
+ // This should not.
+ ObjectProxy* object_proxy3 =
+ bus->GetObjectProxyWithOptions(
+ "org.chromium.TestService",
+ ObjectPath("/org/chromium/DifferentTestObject"),
+ ObjectProxy::IGNORE_SERVICE_UNKNOWN_ERRORS);
+ ASSERT_TRUE(object_proxy3);
+ EXPECT_NE(object_proxy1, object_proxy3);
+
+ bus->ShutdownAndBlock();
+}
+
+TEST(BusTest, RemoveObjectProxy) {
+ // Setup the current thread's MessageLoop.
+ base::MessageLoop message_loop;
+
+ // Start the D-Bus thread.
+ base::Thread::Options thread_options;
+ thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ base::Thread dbus_thread("D-Bus thread");
+ dbus_thread.StartWithOptions(thread_options);
+
+ // Create the bus.
+ Bus::Options options;
+ options.dbus_task_runner = dbus_thread.task_runner();
+ scoped_refptr<Bus> bus = new Bus(options);
+ ASSERT_FALSE(bus->shutdown_completed());
+
+ // Try to remove a non existant object proxy should return false.
+ ASSERT_FALSE(bus->RemoveObjectProxy("org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"),
+ base::DoNothing()));
+
+ ObjectProxy* object_proxy1 =
+ bus->GetObjectProxy("org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(object_proxy1);
+
+ // Increment the reference count to the object proxy to avoid destroying it
+ // while removing the object.
+ object_proxy1->AddRef();
+
+ // Remove the object from the bus. This will invalidate any other usage of
+ // object_proxy1 other than destroy it. We keep this object for a comparison
+ // at a later time.
+ ASSERT_TRUE(bus->RemoveObjectProxy("org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"),
+ base::DoNothing()));
+
+ // This should return a different object because the first object was removed
+ // from the bus, but not deleted from memory.
+ ObjectProxy* object_proxy2 =
+ bus->GetObjectProxy("org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(object_proxy2);
+
+ // Compare the new object with the first object. The first object still exists
+ // thanks to the increased reference.
+ EXPECT_NE(object_proxy1, object_proxy2);
+
+ // Release object_proxy1.
+ object_proxy1->Release();
+
+ // Shut down synchronously.
+ bus->ShutdownOnDBusThreadAndBlock();
+ EXPECT_TRUE(bus->shutdown_completed());
+ dbus_thread.Stop();
+}
+
+TEST(BusTest, GetExportedObject) {
+ Bus::Options options;
+ scoped_refptr<Bus> bus = new Bus(options);
+
+ ExportedObject* object_proxy1 =
+ bus->GetExportedObject(ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(object_proxy1);
+
+ // This should return the same object.
+ ExportedObject* object_proxy2 =
+ bus->GetExportedObject(ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(object_proxy2);
+ EXPECT_EQ(object_proxy1, object_proxy2);
+
+ // This should not.
+ ExportedObject* object_proxy3 =
+ bus->GetExportedObject(
+ ObjectPath("/org/chromium/DifferentTestObject"));
+ ASSERT_TRUE(object_proxy3);
+ EXPECT_NE(object_proxy1, object_proxy3);
+
+ bus->ShutdownAndBlock();
+}
+
+TEST(BusTest, UnregisterExportedObject) {
+ // Start the D-Bus thread.
+ base::Thread::Options thread_options;
+ thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ base::Thread dbus_thread("D-Bus thread");
+ dbus_thread.StartWithOptions(thread_options);
+
+ // Create the bus.
+ Bus::Options options;
+ options.dbus_task_runner = dbus_thread.task_runner();
+ scoped_refptr<Bus> bus = new Bus(options);
+ ASSERT_FALSE(bus->shutdown_completed());
+
+ ExportedObject* object_proxy1 =
+ bus->GetExportedObject(ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(object_proxy1);
+
+ // Increment the reference count to the object proxy to avoid destroying it
+ // calling UnregisterExportedObject. This ensures the dbus::ExportedObject is
+ // not freed from memory. See http://crbug.com/137846 for details.
+ object_proxy1->AddRef();
+
+ bus->UnregisterExportedObject(ObjectPath("/org/chromium/TestObject"));
+
+ // This should return a new object because the object_proxy1 is still in
+ // alloc'ed memory.
+ ExportedObject* object_proxy2 =
+ bus->GetExportedObject(ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(object_proxy2);
+ EXPECT_NE(object_proxy1, object_proxy2);
+
+ // Release the incremented reference.
+ object_proxy1->Release();
+
+ // Shut down synchronously.
+ bus->ShutdownOnDBusThreadAndBlock();
+ EXPECT_TRUE(bus->shutdown_completed());
+ dbus_thread.Stop();
+}
+
+TEST(BusTest, ShutdownAndBlock) {
+ Bus::Options options;
+ scoped_refptr<Bus> bus = new Bus(options);
+ ASSERT_FALSE(bus->shutdown_completed());
+
+ // Shut down synchronously.
+ bus->ShutdownAndBlock();
+ EXPECT_TRUE(bus->shutdown_completed());
+}
+
+TEST(BusTest, ShutdownAndBlockWithDBusThread) {
+ // Start the D-Bus thread.
+ base::Thread::Options thread_options;
+ thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ base::Thread dbus_thread("D-Bus thread");
+ dbus_thread.StartWithOptions(thread_options);
+
+ // Create the bus.
+ Bus::Options options;
+ options.dbus_task_runner = dbus_thread.task_runner();
+ scoped_refptr<Bus> bus = new Bus(options);
+ ASSERT_FALSE(bus->shutdown_completed());
+
+ // Shut down synchronously.
+ bus->ShutdownOnDBusThreadAndBlock();
+ EXPECT_TRUE(bus->shutdown_completed());
+ dbus_thread.Stop();
+}
+
+TEST(BusTest, DoubleAddAndRemoveMatch) {
+ Bus::Options options;
+ scoped_refptr<Bus> bus = new Bus(options);
+ ScopedDBusError error;
+
+ bus->Connect();
+
+ // Adds the same rule twice.
+ bus->AddMatch(
+ "type='signal',interface='org.chromium.TestService',path='/'",
+ error.get());
+ ASSERT_FALSE(error.is_set());
+
+ bus->AddMatch(
+ "type='signal',interface='org.chromium.TestService',path='/'",
+ error.get());
+ ASSERT_FALSE(error.is_set());
+
+ // Removes the same rule twice.
+ ASSERT_TRUE(bus->RemoveMatch(
+ "type='signal',interface='org.chromium.TestService',path='/'",
+ error.get()));
+ ASSERT_FALSE(error.is_set());
+
+ // The rule should be still in the bus since it was removed only once.
+ // A second removal shouldn't give an error.
+ ASSERT_TRUE(bus->RemoveMatch(
+ "type='signal',interface='org.chromium.TestService',path='/'",
+ error.get()));
+ ASSERT_FALSE(error.is_set());
+
+ // A third attemp to remove the same rule should fail.
+ ASSERT_FALSE(bus->RemoveMatch(
+ "type='signal',interface='org.chromium.TestService',path='/'",
+ error.get()));
+
+ bus->ShutdownAndBlock();
+}
+
+TEST(BusTest, ListenForServiceOwnerChange) {
+ base::MessageLoopForIO message_loop;
+
+ // This enables FileDescriptorWatcher, which is required by dbus::Watch.
+ base::FileDescriptorWatcher file_descriptor_watcher(&message_loop);
+
+ RunLoopWithExpectedCount run_loop_state;
+
+ // Create the bus.
+ Bus::Options bus_options;
+ scoped_refptr<Bus> bus = new Bus(bus_options);
+
+ // Add a listener.
+ std::string service_owner1;
+ int num_of_owner_changes1 = 0;
+ Bus::GetServiceOwnerCallback callback1 =
+ base::Bind(&OnServiceOwnerChanged,
+ &run_loop_state,
+ &service_owner1,
+ &num_of_owner_changes1);
+ bus->ListenForServiceOwnerChange("org.chromium.TestService", callback1);
+ // This should be a no-op.
+ bus->ListenForServiceOwnerChange("org.chromium.TestService", callback1);
+ base::RunLoop().RunUntilIdle();
+
+ // Nothing has happened yet. Check initial state.
+ EXPECT_TRUE(service_owner1.empty());
+ EXPECT_EQ(0, num_of_owner_changes1);
+
+ // Make an ownership change.
+ ASSERT_TRUE(bus->RequestOwnershipAndBlock("org.chromium.TestService",
+ Bus::REQUIRE_PRIMARY));
+ run_loop_state.Run(1);
+
+ {
+ // Get the current service owner and check to make sure the listener got
+ // the right value.
+ std::string current_service_owner =
+ bus->GetServiceOwnerAndBlock("org.chromium.TestService",
+ Bus::REPORT_ERRORS);
+ ASSERT_FALSE(current_service_owner.empty());
+
+ // Make sure the listener heard about the new owner.
+ EXPECT_EQ(current_service_owner, service_owner1);
+
+ // Test the second ListenForServiceOwnerChange() above is indeed a no-op.
+ EXPECT_EQ(1, num_of_owner_changes1);
+ }
+
+ // Add a second listener.
+ std::string service_owner2;
+ int num_of_owner_changes2 = 0;
+ Bus::GetServiceOwnerCallback callback2 =
+ base::Bind(&OnServiceOwnerChanged,
+ &run_loop_state,
+ &service_owner2,
+ &num_of_owner_changes2);
+ bus->ListenForServiceOwnerChange("org.chromium.TestService", callback2);
+ base::RunLoop().RunUntilIdle();
+
+ // Release the ownership and make sure the service owner listeners fire with
+ // the right values and the right number of times.
+ ASSERT_TRUE(bus->ReleaseOwnership("org.chromium.TestService"));
+ run_loop_state.Run(2);
+
+ EXPECT_TRUE(service_owner1.empty());
+ EXPECT_TRUE(service_owner2.empty());
+ EXPECT_EQ(2, num_of_owner_changes1);
+ EXPECT_EQ(1, num_of_owner_changes2);
+
+ // Unlisten so shutdown can proceed correctly.
+ bus->UnlistenForServiceOwnerChange("org.chromium.TestService", callback1);
+ bus->UnlistenForServiceOwnerChange("org.chromium.TestService", callback2);
+ base::RunLoop().RunUntilIdle();
+
+ // Shut down synchronously.
+ bus->ShutdownAndBlock();
+ EXPECT_TRUE(bus->shutdown_completed());
+}
+
+TEST(BusTest, GetConnectionName) {
+ Bus::Options options;
+ scoped_refptr<Bus> bus = new Bus(options);
+
+ // Connection name is empty since bus is not connected.
+ EXPECT_FALSE(bus->is_connected());
+ EXPECT_TRUE(bus->GetConnectionName().empty());
+
+ // Connect bus to D-Bus.
+ bus->Connect();
+
+ // Connection name is not empty after connection is established.
+ EXPECT_TRUE(bus->is_connected());
+ EXPECT_FALSE(bus->GetConnectionName().empty());
+
+ // Shut down synchronously.
+ bus->ShutdownAndBlock();
+ EXPECT_TRUE(bus->shutdown_completed());
+}
+
+} // namespace dbus
diff --git a/dbus/dbus_statistics_unittest.cc b/dbus/dbus_statistics_unittest.cc
new file mode 100644
index 0000000000..164d0b3521
--- /dev/null
+++ b/dbus/dbus_statistics_unittest.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/dbus_statistics.h"
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+class DBusStatisticsTest : public testing::Test {
+ public:
+ DBusStatisticsTest() = default;
+
+ void SetUp() override { statistics::Initialize(); }
+
+ void TearDown() override { statistics::Shutdown(); }
+
+ protected:
+ void AddTestMethodCalls() {
+ statistics::AddSentMethodCall(
+ "service1", "service1.interface1", "method1");
+ statistics::AddReceivedSignal(
+ "service1", "service1.interface1", "method1");
+ statistics::AddBlockingSentMethodCall(
+ "service1", "service1.interface1", "method1");
+
+ statistics::AddSentMethodCall(
+ "service1", "service1.interface1", "method2");
+ statistics::AddSentMethodCall(
+ "service1", "service1.interface1", "method2");
+ statistics::AddReceivedSignal(
+ "service1", "service1.interface1", "method2");
+
+ statistics::AddSentMethodCall(
+ "service1", "service1.interface1", "method3");
+ statistics::AddSentMethodCall(
+ "service1", "service1.interface1", "method3");
+ statistics::AddSentMethodCall(
+ "service1", "service1.interface1", "method3");
+
+ statistics::AddSentMethodCall(
+ "service1", "service1.interface2", "method1");
+
+ statistics::AddSentMethodCall(
+ "service1", "service1.interface2", "method2");
+
+ statistics::AddSentMethodCall(
+ "service2", "service2.interface1", "method1");
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DBusStatisticsTest);
+};
+
+TEST_F(DBusStatisticsTest, TestDBusStatsBasic) {
+ int sent = 0, received = 0, block = 0;
+
+ // Add a sent call
+ statistics::AddSentMethodCall("service1", "service1.interface1", "method1");
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service1", "service1.interface1", "method1", &sent, &received, &block));
+ EXPECT_EQ(1, sent);
+ EXPECT_EQ(0, received);
+ EXPECT_EQ(0, block);
+
+ // Add a received call
+ statistics::AddReceivedSignal("service1", "service1.interface1", "method1");
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service1", "service1.interface1", "method1", &sent, &received, &block));
+ EXPECT_EQ(1, sent);
+ EXPECT_EQ(1, received);
+ EXPECT_EQ(0, block);
+
+ // Add a block call
+ statistics::AddBlockingSentMethodCall(
+ "service1", "service1.interface1", "method1");
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service1", "service1.interface1", "method1", &sent, &received, &block));
+ EXPECT_EQ(1, sent);
+ EXPECT_EQ(1, received);
+ EXPECT_EQ(1, block);
+}
+
+TEST_F(DBusStatisticsTest, TestDBusStatsMulti) {
+ int sent = 0, received = 0, block = 0;
+
+ // Add some more stats to exercise accessing multiple different stats.
+ AddTestMethodCalls();
+
+ // Make sure all entries can be found in the set and their counts were
+ // incremented correctly.
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service1", "service1.interface1", "method1", &sent, &received, &block));
+ EXPECT_EQ(1, sent);
+ EXPECT_EQ(1, received);
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service1", "service1.interface1", "method2", &sent, &received, &block));
+ EXPECT_EQ(2, sent);
+ EXPECT_EQ(1, received);
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service1", "service1.interface1", "method3", &sent, &received, &block));
+ EXPECT_EQ(3, sent);
+ EXPECT_EQ(0, received);
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service1", "service1.interface2", "method1", &sent, &received, &block));
+ EXPECT_EQ(1, sent);
+ EXPECT_EQ(0, received);
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service1", "service1.interface2", "method2", &sent, &received, &block));
+ EXPECT_EQ(1, sent);
+ EXPECT_EQ(0, received);
+ ASSERT_TRUE(statistics::testing::GetCalls(
+ "service2", "service2.interface1", "method1", &sent, &received, &block));
+ EXPECT_EQ(1, sent);
+ EXPECT_EQ(0, received);
+
+ ASSERT_FALSE(statistics::testing::GetCalls(
+ "service1", "service1.interface3", "method2", &sent, &received, &block));
+}
+
+TEST_F(DBusStatisticsTest, TestGetAsString) {
+ std::string output_none = GetAsString(statistics::SHOW_SERVICE,
+ statistics::FORMAT_TOTALS);
+ EXPECT_EQ("No DBus calls.", output_none);
+
+ AddTestMethodCalls();
+
+ std::string output_service = GetAsString(statistics::SHOW_SERVICE,
+ statistics::FORMAT_TOTALS);
+ const std::string expected_output_service(
+ "service1: Sent (BLOCKING): 1 Sent: 8 Received: 2\n"
+ "service2: Sent: 1\n");
+ EXPECT_EQ(expected_output_service, output_service);
+
+ std::string output_interface = GetAsString(statistics::SHOW_INTERFACE,
+ statistics::FORMAT_TOTALS);
+ const std::string expected_output_interface(
+ "service1.interface1: Sent (BLOCKING): 1 Sent: 6 Received: 2\n"
+ "service1.interface2: Sent: 2\n"
+ "service2.interface1: Sent: 1\n");
+ EXPECT_EQ(expected_output_interface, output_interface);
+
+ std::string output_per_minute = GetAsString(statistics::SHOW_INTERFACE,
+ statistics::FORMAT_PER_MINUTE);
+ const std::string expected_output_per_minute(
+ "service1.interface1: Sent (BLOCKING): 1/min Sent: 6/min"
+ " Received: 2/min\n"
+ "service1.interface2: Sent: 2/min\n"
+ "service2.interface1: Sent: 1/min\n");
+ EXPECT_EQ(expected_output_per_minute, output_per_minute);
+
+ std::string output_all = GetAsString(statistics::SHOW_INTERFACE,
+ statistics::FORMAT_ALL);
+ const std::string expected_output_all(
+ "service1.interface1: Sent (BLOCKING): 1 (1/min) Sent: 6 (6/min)"
+ " Received: 2 (2/min)\n"
+ "service1.interface2: Sent: 2 (2/min)\n"
+ "service2.interface1: Sent: 1 (1/min)\n");
+ EXPECT_EQ(expected_output_all, output_all);
+
+
+ std::string output_method = GetAsString(statistics::SHOW_METHOD,
+ statistics::FORMAT_TOTALS);
+ const std::string expected_output_method(
+ "service1.interface1.method1: Sent (BLOCKING): 1 Sent: 1 Received: 1\n"
+ "service1.interface1.method2: Sent: 2 Received: 1\n"
+ "service1.interface1.method3: Sent: 3\n"
+ "service1.interface2.method1: Sent: 1\n"
+ "service1.interface2.method2: Sent: 1\n"
+ "service2.interface1.method1: Sent: 1\n");
+ EXPECT_EQ(expected_output_method, output_method);
+
+}
+
+} // namespace dbus
diff --git a/dbus/end_to_end_async_unittest.cc b/dbus/end_to_end_async_unittest.cc
new file mode 100644
index 0000000000..73e11c4b56
--- /dev/null
+++ b/dbus/end_to_end_async_unittest.cc
@@ -0,0 +1,645 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "dbus/test_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+namespace {
+
+// See comments in ObjectProxy::RunResponseCallback() for why the number was
+// chosen.
+const int kHugePayloadSize = 64 << 20; // 64 MB
+
+} // namespace
+
+// The end-to-end test exercises the asynchronous APIs in ObjectProxy and
+// ExportedObject.
+class EndToEndAsyncTest : public testing::Test {
+ public:
+ void SetUp() override {
+ // Make the main thread not to allow IO.
+ base::ThreadRestrictions::SetIOAllowed(false);
+
+ // Start the D-Bus thread.
+ dbus_thread_.reset(new base::Thread("D-Bus Thread"));
+ base::Thread::Options thread_options;
+ thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options));
+
+ // Start the test service, using the D-Bus thread.
+ TestService::Options options;
+ options.dbus_task_runner = dbus_thread_->task_runner();
+ test_service_.reset(new TestService(options));
+ ASSERT_TRUE(test_service_->StartService());
+ ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted());
+ ASSERT_TRUE(test_service_->HasDBusThread());
+
+ // Create the client, using the D-Bus thread.
+ Bus::Options bus_options;
+ bus_options.bus_type = Bus::SESSION;
+ bus_options.connection_type = Bus::PRIVATE;
+ bus_options.dbus_task_runner = dbus_thread_->task_runner();
+ bus_ = new Bus(bus_options);
+ object_proxy_ = bus_->GetObjectProxy(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(bus_->HasDBusThread());
+
+ // Connect to the "Test" signal of "org.chromium.TestInterface" from
+ // the remote object.
+ object_proxy_->ConnectToSignal(
+ "org.chromium.TestInterface",
+ "Test",
+ base::Bind(&EndToEndAsyncTest::OnTestSignal,
+ base::Unretained(this)),
+ base::Bind(&EndToEndAsyncTest::OnConnected,
+ base::Unretained(this)));
+ // Wait until the object proxy is connected to the signal.
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+
+ // Connect to the "Test2" signal of "org.chromium.TestInterface" from
+ // the remote object. There was a bug where we were emitting error
+ // messages like "Requested to remove an unknown match rule: ..." at
+ // the shutdown of Bus when an object proxy is connected to more than
+ // one signal of the same interface. See crosbug.com/23382 for details.
+ object_proxy_->ConnectToSignal(
+ "org.chromium.TestInterface",
+ "Test2",
+ base::Bind(&EndToEndAsyncTest::OnTest2Signal,
+ base::Unretained(this)),
+ base::Bind(&EndToEndAsyncTest::OnConnected,
+ base::Unretained(this)));
+ // Wait until the object proxy is connected to the signal.
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+
+ // Create a second object proxy for the root object.
+ root_object_proxy_ = bus_->GetObjectProxy(test_service_->service_name(),
+ ObjectPath("/"));
+ ASSERT_TRUE(bus_->HasDBusThread());
+
+ // Connect to the "Test" signal of "org.chromium.TestInterface" from
+ // the root remote object too.
+ root_object_proxy_->ConnectToSignal(
+ "org.chromium.TestInterface",
+ "Test",
+ base::Bind(&EndToEndAsyncTest::OnRootTestSignal,
+ base::Unretained(this)),
+ base::Bind(&EndToEndAsyncTest::OnConnected,
+ base::Unretained(this)));
+ // Wait until the root object proxy is connected to the signal.
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+ }
+
+ void TearDown() override {
+ bus_->ShutdownOnDBusThreadAndBlock();
+
+ // Shut down the service.
+ test_service_->ShutdownAndBlock();
+
+ // Reset to the default.
+ base::ThreadRestrictions::SetIOAllowed(true);
+
+ // Stopping a thread is considered an IO operation, so do this after
+ // allowing IO.
+ test_service_->Stop();
+ }
+
+ protected:
+ // Replaces the bus with a broken one.
+ void SetUpBrokenBus() {
+ // Shut down the existing bus.
+ bus_->ShutdownOnDBusThreadAndBlock();
+
+ // Create new bus with invalid address.
+ const char kInvalidAddress[] = "";
+ Bus::Options bus_options;
+ bus_options.bus_type = Bus::CUSTOM_ADDRESS;
+ bus_options.address = kInvalidAddress;
+ bus_options.connection_type = Bus::PRIVATE;
+ bus_options.dbus_task_runner = dbus_thread_->task_runner();
+ bus_ = new Bus(bus_options);
+ ASSERT_TRUE(bus_->HasDBusThread());
+
+ // Create new object proxy.
+ object_proxy_ = bus_->GetObjectProxy(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestObject"));
+ }
+
+ // Calls the method asynchronously. OnResponse() will be called once the
+ // response is received.
+ void CallMethod(MethodCall* method_call,
+ int timeout_ms) {
+ object_proxy_->CallMethod(method_call,
+ timeout_ms,
+ base::Bind(&EndToEndAsyncTest::OnResponse,
+ base::Unretained(this)));
+ }
+
+ // Calls the method asynchronously. OnResponse() will be called once the
+ // response is received without error, otherwise OnError() will be called.
+ void CallMethodWithErrorCallback(MethodCall* method_call,
+ int timeout_ms) {
+ object_proxy_->CallMethodWithErrorCallback(
+ method_call,
+ timeout_ms,
+ base::Bind(&EndToEndAsyncTest::OnResponse, base::Unretained(this)),
+ base::Bind(&EndToEndAsyncTest::OnError, base::Unretained(this)));
+ }
+
+ // Wait for the give number of responses.
+ void WaitForResponses(size_t num_responses) {
+ while (response_strings_.size() < num_responses) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ }
+
+ // Called when the response is received.
+ void OnResponse(Response* response) {
+ // |response| will be deleted on exit of the function. Copy the
+ // payload to |response_strings_|.
+ if (response) {
+ MessageReader reader(response);
+ std::string response_string;
+ ASSERT_TRUE(reader.PopString(&response_string));
+ response_strings_.push_back(response_string);
+ } else {
+ response_strings_.push_back(std::string());
+ }
+ run_loop_->Quit();
+ };
+
+ // Wait for the given number of errors.
+ void WaitForErrors(size_t num_errors) {
+ while (error_names_.size() < num_errors) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ }
+
+ // Called when an error is received.
+ void OnError(ErrorResponse* error) {
+ // |error| will be deleted on exit of the function. Copy the payload to
+ // |error_names_|.
+ if (error) {
+ ASSERT_NE("", error->GetErrorName());
+ error_names_.push_back(error->GetErrorName());
+ } else {
+ error_names_.push_back(std::string());
+ }
+ run_loop_->Quit();
+ }
+
+ // Called when the "Test" signal is received, in the main thread.
+ // Copy the string payload to |test_signal_string_|.
+ void OnTestSignal(Signal* signal) {
+ MessageReader reader(signal);
+ ASSERT_TRUE(reader.PopString(&test_signal_string_));
+ run_loop_->Quit();
+ }
+
+ // Called when the "Test" signal is received, in the main thread, by
+ // the root object proxy. Copy the string payload to
+ // |root_test_signal_string_|.
+ void OnRootTestSignal(Signal* signal) {
+ MessageReader reader(signal);
+ ASSERT_TRUE(reader.PopString(&root_test_signal_string_));
+ run_loop_->Quit();
+ }
+
+ // Called when the "Test2" signal is received, in the main thread.
+ void OnTest2Signal(Signal* signal) {
+ MessageReader reader(signal);
+ run_loop_->Quit();
+ }
+
+ // Called when connected to the signal.
+ void OnConnected(const std::string& interface_name,
+ const std::string& signal_name,
+ bool success) {
+ ASSERT_TRUE(success);
+ run_loop_->Quit();
+ }
+
+ // Wait for the hey signal to be received.
+ void WaitForTestSignal() {
+ // OnTestSignal() will quit the message loop.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+
+ base::MessageLoop message_loop_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ std::vector<std::string> response_strings_;
+ std::vector<std::string> error_names_;
+ std::unique_ptr<base::Thread> dbus_thread_;
+ scoped_refptr<Bus> bus_;
+ ObjectProxy* object_proxy_;
+ ObjectProxy* root_object_proxy_;
+ std::unique_ptr<TestService> test_service_;
+ // Text message from "Test" signal.
+ std::string test_signal_string_;
+ // Text message from "Test" signal delivered to root.
+ std::string root_test_signal_string_;
+};
+
+TEST_F(EndToEndAsyncTest, Echo) {
+ const char* kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethod(&method_call, timeout_ms);
+
+ // Check the response.
+ WaitForResponses(1);
+ EXPECT_EQ(kHello, response_strings_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, EchoWithErrorCallback) {
+ const char* kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethodWithErrorCallback(&method_call, timeout_ms);
+
+ // Check the response.
+ WaitForResponses(1);
+ EXPECT_EQ(kHello, response_strings_[0]);
+ EXPECT_TRUE(error_names_.empty());
+}
+
+// Call Echo method three times.
+TEST_F(EndToEndAsyncTest, EchoThreeTimes) {
+ const char* kMessages[] = { "foo", "bar", "baz" };
+
+ for (size_t i = 0; i < arraysize(kMessages); ++i) {
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kMessages[i]);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethod(&method_call, timeout_ms);
+ }
+
+ // Check the responses.
+ WaitForResponses(3);
+ // Sort as the order of the returned messages is not deterministic.
+ std::sort(response_strings_.begin(), response_strings_.end());
+ EXPECT_EQ("bar", response_strings_[0]);
+ EXPECT_EQ("baz", response_strings_[1]);
+ EXPECT_EQ("foo", response_strings_[2]);
+}
+
+TEST_F(EndToEndAsyncTest, Echo_HugePayload) {
+ const std::string kHugePayload(kHugePayloadSize, 'o');
+
+ // Create the method call with a huge payload.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHugePayload);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethod(&method_call, timeout_ms);
+
+ // This caused a DCHECK failure before. Ensure that the issue is fixed.
+ WaitForResponses(1);
+ EXPECT_EQ(kHugePayload, response_strings_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, BrokenBus) {
+ const char* kHello = "hello";
+
+ // Set up a broken bus.
+ SetUpBrokenBus();
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethod(&method_call, timeout_ms);
+ WaitForResponses(1);
+
+ // Should fail because of the broken bus.
+ ASSERT_EQ("", response_strings_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, BrokenBusWithErrorCallback) {
+ const char* kHello = "hello";
+
+ // Set up a broken bus.
+ SetUpBrokenBus();
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethodWithErrorCallback(&method_call, timeout_ms);
+ WaitForErrors(1);
+
+ // Should fail because of the broken bus.
+ ASSERT_TRUE(response_strings_.empty());
+ ASSERT_EQ("", error_names_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, Timeout) {
+ const char* kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "SlowEcho");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method with timeout of 0ms.
+ const int timeout_ms = 0;
+ CallMethod(&method_call, timeout_ms);
+ WaitForResponses(1);
+
+ // Should fail because of timeout.
+ ASSERT_EQ("", response_strings_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, TimeoutWithErrorCallback) {
+ const char* kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "SlowEcho");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method with timeout of 0ms.
+ const int timeout_ms = 0;
+ CallMethodWithErrorCallback(&method_call, timeout_ms);
+ WaitForErrors(1);
+
+ // Should fail because of timeout.
+ ASSERT_TRUE(response_strings_.empty());
+ ASSERT_EQ(DBUS_ERROR_NO_REPLY, error_names_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, CancelPendingCalls) {
+ const char* kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethod(&method_call, timeout_ms);
+
+ // Remove the object proxy before receiving the result.
+ // This results in cancelling the pending method call.
+ bus_->RemoveObjectProxy(test_service_->service_name(),
+ ObjectPath("/org/chromium/TestObject"),
+ base::DoNothing());
+
+ // We shouldn't receive any responses. Wait for a while just to make sure.
+ run_loop_.reset(new base::RunLoop);
+ message_loop_.task_runner()->PostDelayedTask(
+ FROM_HERE, run_loop_->QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop_->Run();
+ EXPECT_TRUE(response_strings_.empty());
+}
+
+// Tests calling a method that sends its reply asynchronously.
+TEST_F(EndToEndAsyncTest, AsyncEcho) {
+ const char* kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "AsyncEcho");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethod(&method_call, timeout_ms);
+
+ // Check the response.
+ WaitForResponses(1);
+ EXPECT_EQ(kHello, response_strings_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, NonexistentMethod) {
+ MethodCall method_call("org.chromium.TestInterface", "Nonexistent");
+
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethod(&method_call, timeout_ms);
+ WaitForResponses(1);
+
+ // Should fail because the method is nonexistent.
+ ASSERT_EQ("", response_strings_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, NonexistentMethodWithErrorCallback) {
+ MethodCall method_call("org.chromium.TestInterface", "Nonexistent");
+
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethodWithErrorCallback(&method_call, timeout_ms);
+ WaitForErrors(1);
+
+ // Should fail because the method is nonexistent.
+ ASSERT_TRUE(response_strings_.empty());
+ ASSERT_EQ(DBUS_ERROR_UNKNOWN_METHOD, error_names_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, BrokenMethod) {
+ MethodCall method_call("org.chromium.TestInterface", "BrokenMethod");
+
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethod(&method_call, timeout_ms);
+ WaitForResponses(1);
+
+ // Should fail because the method is broken.
+ ASSERT_EQ("", response_strings_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, BrokenMethodWithErrorCallback) {
+ MethodCall method_call("org.chromium.TestInterface", "BrokenMethod");
+
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethodWithErrorCallback(&method_call, timeout_ms);
+ WaitForErrors(1);
+
+ // Should fail because the method is broken.
+ ASSERT_TRUE(response_strings_.empty());
+ ASSERT_EQ(DBUS_ERROR_FAILED, error_names_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, InvalidServiceName) {
+ // Bus name cannot contain '/'.
+ const std::string invalid_service_name = ":1/2";
+
+ // Replace object proxy with new one.
+ object_proxy_ = bus_->GetObjectProxy(invalid_service_name,
+ ObjectPath("/org/chromium/TestObject"));
+
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ CallMethodWithErrorCallback(&method_call, timeout_ms);
+ WaitForErrors(1);
+
+ // Should fail because of the invalid bus name.
+ ASSERT_TRUE(response_strings_.empty());
+ ASSERT_EQ("", error_names_[0]);
+}
+
+TEST_F(EndToEndAsyncTest, EmptyResponseCallback) {
+ const char* kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method with an empty callback.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ object_proxy_->CallMethod(&method_call, timeout_ms, base::DoNothing());
+ // Post a delayed task to quit the message loop.
+ run_loop_.reset(new base::RunLoop);
+ message_loop_.task_runner()->PostDelayedTask(
+ FROM_HERE, run_loop_->QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop_->Run();
+ // We cannot tell if the empty callback is called, but at least we can
+ // check if the test does not crash.
+}
+
+TEST_F(EndToEndAsyncTest, TestSignal) {
+ const char kMessage[] = "hello, world";
+ // Send the test signal from the exported object.
+ test_service_->SendTestSignal(kMessage);
+ // Receive the signal with the object proxy. The signal is handled in
+ // EndToEndAsyncTest::OnTestSignal() in the main thread.
+ WaitForTestSignal();
+ ASSERT_EQ(kMessage, test_signal_string_);
+}
+
+TEST_F(EndToEndAsyncTest, TestSignalFromRoot) {
+ const char kMessage[] = "hello, world";
+ // Object proxies are tied to a particular object path, if a signal
+ // arrives from a different object path like "/" the first object proxy
+ // |object_proxy_| should not handle it, and should leave it for the root
+ // object proxy |root_object_proxy_|.
+ test_service_->SendTestSignalFromRoot(kMessage);
+ WaitForTestSignal();
+ // Verify the signal was not received by the specific proxy.
+ ASSERT_TRUE(test_signal_string_.empty());
+ // Verify the string WAS received by the root proxy.
+ ASSERT_EQ(kMessage, root_test_signal_string_);
+}
+
+TEST_F(EndToEndAsyncTest, TestHugeSignal) {
+ const std::string kHugeMessage(kHugePayloadSize, 'o');
+
+ // Send the huge signal from the exported object.
+ test_service_->SendTestSignal(kHugeMessage);
+ // This caused a DCHECK failure before. Ensure that the issue is fixed.
+ WaitForTestSignal();
+ ASSERT_EQ(kHugeMessage, test_signal_string_);
+}
+
+class SignalMultipleHandlerTest : public EndToEndAsyncTest {
+ public:
+ SignalMultipleHandlerTest() = default;
+
+ void SetUp() override {
+ // Set up base class.
+ EndToEndAsyncTest::SetUp();
+
+ // Connect the root object proxy's signal handler to a new handler
+ // so that we can verify that a second call to ConnectSignal() delivers
+ // to both our new handler and the old.
+ object_proxy_->ConnectToSignal(
+ "org.chromium.TestInterface",
+ "Test",
+ base::Bind(&SignalMultipleHandlerTest::OnAdditionalTestSignal,
+ base::Unretained(this)),
+ base::Bind(&SignalMultipleHandlerTest::OnAdditionalConnected,
+ base::Unretained(this)));
+ // Wait until the object proxy is connected to the signal.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+
+ protected:
+ // Called when the "Test" signal is received, in the main thread.
+ // Copy the string payload to |additional_test_signal_string_|.
+ void OnAdditionalTestSignal(Signal* signal) {
+ MessageReader reader(signal);
+ ASSERT_TRUE(reader.PopString(&additional_test_signal_string_));
+ run_loop_->Quit();
+ }
+
+ // Called when connected to the signal.
+ void OnAdditionalConnected(const std::string& interface_name,
+ const std::string& signal_name,
+ bool success) {
+ ASSERT_TRUE(success);
+ run_loop_->Quit();
+ }
+
+ // Text message from "Test" signal delivered to additional handler.
+ std::string additional_test_signal_string_;
+};
+
+TEST_F(SignalMultipleHandlerTest, TestMultipleHandlers) {
+ const char kMessage[] = "hello, world";
+ // Send the test signal from the exported object.
+ test_service_->SendTestSignal(kMessage);
+ // Receive the signal with the object proxy.
+ WaitForTestSignal();
+ // Verify the string WAS received by the original handler.
+ ASSERT_EQ(kMessage, test_signal_string_);
+ // Verify the signal WAS ALSO received by the additional handler.
+ ASSERT_EQ(kMessage, additional_test_signal_string_);
+}
+
+} // namespace dbus
diff --git a/dbus/end_to_end_sync_unittest.cc b/dbus/end_to_end_sync_unittest.cc
new file mode 100644
index 0000000000..0bc146fbde
--- /dev/null
+++ b/dbus/end_to_end_sync_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "dbus/test_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+// The end-to-end test exercises the synchronous APIs in ObjectProxy and
+// ExportedObject. The test will launch a thread for the service side
+// operations (i.e. ExportedObject side).
+class EndToEndSyncTest : public testing::Test {
+ public:
+ EndToEndSyncTest() = default;
+
+ void SetUp() override {
+ // Start the test service;
+ TestService::Options options;
+ test_service_.reset(new TestService(options));
+ ASSERT_TRUE(test_service_->StartService());
+ ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted());
+ ASSERT_FALSE(test_service_->HasDBusThread());
+
+ // Create the client.
+ Bus::Options client_bus_options;
+ client_bus_options.bus_type = Bus::SESSION;
+ client_bus_options.connection_type = Bus::PRIVATE;
+ client_bus_ = new Bus(client_bus_options);
+ object_proxy_ = client_bus_->GetObjectProxy(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestObject"));
+ ASSERT_FALSE(client_bus_->HasDBusThread());
+ }
+
+ void TearDown() override {
+ test_service_->ShutdownAndBlock();
+ test_service_->Stop();
+ client_bus_->ShutdownAndBlock();
+ }
+
+ protected:
+ std::unique_ptr<TestService> test_service_;
+ scoped_refptr<Bus> client_bus_;
+ ObjectProxy* object_proxy_;
+};
+
+TEST_F(EndToEndSyncTest, Echo) {
+ const std::string kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ std::unique_ptr<Response> response(
+ object_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+ ASSERT_TRUE(response.get());
+
+ // Check the response. kHello should be echoed back.
+ MessageReader reader(response.get());
+ std::string returned_message;
+ ASSERT_TRUE(reader.PopString(&returned_message));
+ EXPECT_EQ(kHello, returned_message);
+}
+
+TEST_F(EndToEndSyncTest, Timeout) {
+ const std::string kHello = "hello";
+
+ // Create the method call.
+ MethodCall method_call("org.chromium.TestInterface", "DelayedEcho");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method with timeout of 0ms.
+ const int timeout_ms = 0;
+ std::unique_ptr<Response> response(
+ object_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+ // Should fail because of timeout.
+ ASSERT_FALSE(response.get());
+}
+
+TEST_F(EndToEndSyncTest, NonexistentMethod) {
+ MethodCall method_call("org.chromium.TestInterface", "Nonexistent");
+
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ std::unique_ptr<Response> response(
+ object_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+ ASSERT_FALSE(response.get());
+}
+
+TEST_F(EndToEndSyncTest, BrokenMethod) {
+ MethodCall method_call("org.chromium.TestInterface", "BrokenMethod");
+
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ std::unique_ptr<Response> response(
+ object_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+ ASSERT_FALSE(response.get());
+}
+
+TEST_F(EndToEndSyncTest, InvalidServiceName) {
+ // Bus name cannot contain '/'.
+ const std::string invalid_service_name = ":1/2";
+
+ // Replace object proxy with new one.
+ object_proxy_ = client_bus_->GetObjectProxy(
+ invalid_service_name, ObjectPath("/org/chromium/TestObject"));
+
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+
+ const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT;
+ std::unique_ptr<Response> response(
+ object_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+ ASSERT_FALSE(response.get());
+}
+
+} // namespace dbus
diff --git a/dbus/message_unittest.cc b/dbus/message_unittest.cc
new file mode 100644
index 0000000000..c17ecf6ed8
--- /dev/null
+++ b/dbus/message_unittest.cc
@@ -0,0 +1,727 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/message.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "dbus/object_path.h"
+#include "dbus/test_proto.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+// Test that a byte can be properly written and read. We only have this
+// test for byte, as repeating this for other basic types is too redundant.
+TEST(MessageTest, AppendAndPopByte) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ writer.AppendByte(123); // The input is 123.
+
+ MessageReader reader(message.get());
+ ASSERT_TRUE(reader.HasMoreData()); // Should have data to read.
+ ASSERT_EQ(Message::BYTE, reader.GetDataType());
+ ASSERT_EQ("y", reader.GetDataSignature());
+
+ bool bool_value = false;
+ // Should fail as the type is not bool here.
+ ASSERT_FALSE(reader.PopBool(&bool_value));
+
+ uint8_t byte_value = 0;
+ ASSERT_TRUE(reader.PopByte(&byte_value));
+ EXPECT_EQ(123, byte_value); // Should match with the input.
+ ASSERT_FALSE(reader.HasMoreData()); // Should not have more data to read.
+
+ // Try to get another byte. Should fail.
+ ASSERT_FALSE(reader.PopByte(&byte_value));
+}
+
+// Check all basic types can be properly written and read.
+TEST(MessageTest, AppendAndPopBasicDataTypes) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+
+ // Append 0, 1, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path".
+ writer.AppendByte(0);
+ writer.AppendBool(true);
+ writer.AppendInt16(2);
+ writer.AppendUint16(3);
+ writer.AppendInt32(4);
+ writer.AppendUint32(5);
+ writer.AppendInt64(6);
+ writer.AppendUint64(7);
+ writer.AppendDouble(8.0);
+ writer.AppendString("string");
+ writer.AppendObjectPath(ObjectPath("/object/path"));
+
+ uint8_t byte_value = 0;
+ bool bool_value = false;
+ int16_t int16_value = 0;
+ uint16_t uint16_value = 0;
+ int32_t int32_value = 0;
+ uint32_t uint32_value = 0;
+ int64_t int64_value = 0;
+ uint64_t uint64_value = 0;
+ double double_value = 0;
+ std::string string_value;
+ ObjectPath object_path_value;
+
+ MessageReader reader(message.get());
+ ASSERT_TRUE(reader.HasMoreData());
+ ASSERT_EQ("y", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopByte(&byte_value));
+ ASSERT_EQ("b", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopBool(&bool_value));
+ ASSERT_EQ("n", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopInt16(&int16_value));
+ ASSERT_EQ("q", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopUint16(&uint16_value));
+ ASSERT_EQ("i", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopInt32(&int32_value));
+ ASSERT_EQ("u", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopUint32(&uint32_value));
+ ASSERT_EQ("x", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopInt64(&int64_value));
+ ASSERT_EQ("t", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopUint64(&uint64_value));
+ ASSERT_EQ("d", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopDouble(&double_value));
+ ASSERT_EQ("s", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopString(&string_value));
+ ASSERT_EQ("o", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopObjectPath(&object_path_value));
+ ASSERT_EQ("", reader.GetDataSignature());
+ ASSERT_FALSE(reader.HasMoreData());
+
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path" should be returned.
+ EXPECT_EQ(0, byte_value);
+ EXPECT_EQ(true, bool_value);
+ EXPECT_EQ(2, int16_value);
+ EXPECT_EQ(3U, uint16_value);
+ EXPECT_EQ(4, int32_value);
+ EXPECT_EQ(5U, uint32_value);
+ EXPECT_EQ(6, int64_value);
+ EXPECT_EQ(7U, uint64_value);
+ EXPECT_DOUBLE_EQ(8.0, double_value);
+ EXPECT_EQ("string", string_value);
+ EXPECT_EQ(ObjectPath("/object/path"), object_path_value);
+}
+
+// Check all basic types can be properly written and read.
+TEST(MessageTest, AppendAndPopFileDescriptor) {
+ if (!IsDBusTypeUnixFdSupported()) {
+ LOG(WARNING) << "FD passing is not supported";
+ return;
+ }
+
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+
+ // Append stdout.
+ const int fd_in = 1;
+ writer.AppendFileDescriptor(fd_in);
+
+ base::ScopedFD fd_out;
+
+ MessageReader reader(message.get());
+ ASSERT_TRUE(reader.HasMoreData());
+ ASSERT_EQ(Message::UNIX_FD, reader.GetDataType());
+ ASSERT_EQ("h", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopFileDescriptor(&fd_out));
+ ASSERT_FALSE(reader.HasMoreData());
+
+ // Stdout should be returned but we cannot check the descriptor
+ // value because stdout will be dup'd. Instead check st_rdev
+ // which should be identical.
+ struct stat sb_stdout;
+ int status_stdout = HANDLE_EINTR(fstat(fd_in, &sb_stdout));
+ ASSERT_GE(status_stdout, 0);
+ struct stat sb_fd;
+ int status_fd = HANDLE_EINTR(fstat(fd_out.get(), &sb_fd));
+ ASSERT_GE(status_fd, 0);
+ EXPECT_EQ(sb_stdout.st_rdev, sb_fd.st_rdev);
+}
+
+// Check all variant types can be properly written and read.
+TEST(MessageTest, AppendAndPopVariantDataTypes) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+
+ // Append 0, 1, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path".
+ writer.AppendVariantOfByte(0);
+ writer.AppendVariantOfBool(true);
+ writer.AppendVariantOfInt16(2);
+ writer.AppendVariantOfUint16(3);
+ writer.AppendVariantOfInt32(4);
+ writer.AppendVariantOfUint32(5);
+ writer.AppendVariantOfInt64(6);
+ writer.AppendVariantOfUint64(7);
+ writer.AppendVariantOfDouble(8.0);
+ writer.AppendVariantOfString("string");
+ writer.AppendVariantOfObjectPath(ObjectPath("/object/path"));
+
+ uint8_t byte_value = 0;
+ bool bool_value = false;
+ int16_t int16_value = 0;
+ uint16_t uint16_value = 0;
+ int32_t int32_value = 0;
+ uint32_t uint32_value = 0;
+ int64_t int64_value = 0;
+ uint64_t uint64_value = 0;
+ double double_value = 0;
+ std::string string_value;
+ ObjectPath object_path_value;
+
+ MessageReader reader(message.get());
+ ASSERT_TRUE(reader.HasMoreData());
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfByte(&byte_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfBool(&bool_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfInt16(&int16_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfUint16(&uint16_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfInt32(&int32_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfUint32(&uint32_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfInt64(&int64_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfUint64(&uint64_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfDouble(&double_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfString(&string_value));
+ ASSERT_EQ("v", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopVariantOfObjectPath(&object_path_value));
+ ASSERT_EQ("", reader.GetDataSignature());
+ ASSERT_FALSE(reader.HasMoreData());
+
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path" should be returned.
+ EXPECT_EQ(0, byte_value);
+ EXPECT_EQ(true, bool_value);
+ EXPECT_EQ(2, int16_value);
+ EXPECT_EQ(3U, uint16_value);
+ EXPECT_EQ(4, int32_value);
+ EXPECT_EQ(5U, uint32_value);
+ EXPECT_EQ(6, int64_value);
+ EXPECT_EQ(7U, uint64_value);
+ EXPECT_DOUBLE_EQ(8.0, double_value);
+ EXPECT_EQ("string", string_value);
+ EXPECT_EQ(ObjectPath("/object/path"), object_path_value);
+}
+
+TEST(MessageTest, ArrayOfBytes) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ std::vector<uint8_t> bytes;
+ bytes.push_back(1);
+ bytes.push_back(2);
+ bytes.push_back(3);
+ writer.AppendArrayOfBytes(bytes.data(), bytes.size());
+
+ MessageReader reader(message.get());
+ const uint8_t* output_bytes = nullptr;
+ size_t length = 0;
+ ASSERT_EQ("ay", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopArrayOfBytes(&output_bytes, &length));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(3U, length);
+ EXPECT_EQ(1, output_bytes[0]);
+ EXPECT_EQ(2, output_bytes[1]);
+ EXPECT_EQ(3, output_bytes[2]);
+}
+
+TEST(MessageTest, ArrayOfInt32s) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ std::vector<int32_t> int32s;
+ int32s.push_back(1);
+ int32s.push_back(2);
+ int32s.push_back(3);
+ writer.AppendArrayOfInt32s(int32s.data(), int32s.size());
+
+ MessageReader reader(message.get());
+ const int32_t* output_int32s = nullptr;
+ size_t length = 0;
+ ASSERT_EQ("ai", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopArrayOfInt32s(&output_int32s, &length));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(3U, length);
+ EXPECT_EQ(1, output_int32s[0]);
+ EXPECT_EQ(2, output_int32s[1]);
+ EXPECT_EQ(3, output_int32s[2]);
+}
+
+TEST(MessageTest, ArrayOfUint32s) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ std::vector<uint32_t> uint32s;
+ uint32s.push_back(1);
+ uint32s.push_back(2);
+ uint32s.push_back(3);
+ writer.AppendArrayOfUint32s(uint32s.data(), uint32s.size());
+
+ MessageReader reader(message.get());
+ const uint32_t* output_uint32s = nullptr;
+ size_t length = 0;
+ ASSERT_EQ("au", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopArrayOfUint32s(&output_uint32s, &length));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(3U, length);
+ EXPECT_EQ(1U, output_uint32s[0]);
+ EXPECT_EQ(2U, output_uint32s[1]);
+ EXPECT_EQ(3U, output_uint32s[2]);
+}
+
+TEST(MessageTest, ArrayOfDoubles) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ std::vector<double> doubles;
+ doubles.push_back(0.2);
+ doubles.push_back(0.5);
+ doubles.push_back(1);
+ writer.AppendArrayOfDoubles(doubles.data(), doubles.size());
+
+ MessageReader reader(message.get());
+ const double* output_doubles = nullptr;
+ size_t length = 0;
+ ASSERT_EQ("ad", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopArrayOfDoubles(&output_doubles, &length));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(3U, length);
+ EXPECT_EQ(0.2, output_doubles[0]);
+ EXPECT_EQ(0.5, output_doubles[1]);
+ EXPECT_EQ(1, output_doubles[2]);
+}
+
+TEST(MessageTest, ArrayOfBytes_Empty) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ std::vector<uint8_t> bytes;
+ writer.AppendArrayOfBytes(bytes.data(), bytes.size());
+
+ MessageReader reader(message.get());
+ const uint8_t* output_bytes = nullptr;
+ size_t length = 0;
+ ASSERT_EQ("ay", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopArrayOfBytes(&output_bytes, &length));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(0U, length);
+ EXPECT_EQ(nullptr, output_bytes);
+}
+
+TEST(MessageTest, ArrayOfStrings) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ std::vector<std::string> strings;
+ strings.push_back("fee");
+ strings.push_back("fie");
+ strings.push_back("foe");
+ strings.push_back("fum");
+ writer.AppendArrayOfStrings(strings);
+
+ MessageReader reader(message.get());
+ std::vector<std::string> output_strings;
+ ASSERT_EQ("as", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopArrayOfStrings(&output_strings));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(4U, output_strings.size());
+ EXPECT_EQ("fee", output_strings[0]);
+ EXPECT_EQ("fie", output_strings[1]);
+ EXPECT_EQ("foe", output_strings[2]);
+ EXPECT_EQ("fum", output_strings[3]);
+}
+
+TEST(MessageTest, ArrayOfObjectPaths) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ std::vector<ObjectPath> object_paths;
+ object_paths.push_back(ObjectPath("/object/path/1"));
+ object_paths.push_back(ObjectPath("/object/path/2"));
+ object_paths.push_back(ObjectPath("/object/path/3"));
+ writer.AppendArrayOfObjectPaths(object_paths);
+
+ MessageReader reader(message.get());
+ std::vector<ObjectPath> output_object_paths;
+ ASSERT_EQ("ao", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopArrayOfObjectPaths(&output_object_paths));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(3U, output_object_paths.size());
+ EXPECT_EQ(ObjectPath("/object/path/1"), output_object_paths[0]);
+ EXPECT_EQ(ObjectPath("/object/path/2"), output_object_paths[1]);
+ EXPECT_EQ(ObjectPath("/object/path/3"), output_object_paths[2]);
+}
+
+TEST(MessageTest, ProtoBuf) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ TestProto send_message;
+ send_message.set_text("testing");
+ send_message.set_number(123);
+ writer.AppendProtoAsArrayOfBytes(send_message);
+
+ MessageReader reader(message.get());
+ TestProto receive_message;
+ ASSERT_EQ("ay", reader.GetDataSignature());
+ ASSERT_TRUE(reader.PopArrayOfBytesAsProto(&receive_message));
+ EXPECT_EQ(receive_message.text(), send_message.text());
+ EXPECT_EQ(receive_message.number(), send_message.number());
+}
+
+// Test that an array can be properly written and read. We only have this
+// test for array, as repeating this for other container types is too
+// redundant.
+TEST(MessageTest, OpenArrayAndPopArray) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ MessageWriter array_writer(nullptr);
+ writer.OpenArray("s", &array_writer); // Open an array of strings.
+ array_writer.AppendString("foo");
+ array_writer.AppendString("bar");
+ array_writer.AppendString("baz");
+ writer.CloseContainer(&array_writer);
+
+ MessageReader reader(message.get());
+ ASSERT_EQ(Message::ARRAY, reader.GetDataType());
+ ASSERT_EQ("as", reader.GetDataSignature());
+ MessageReader array_reader(nullptr);
+ ASSERT_TRUE(reader.PopArray(&array_reader));
+ ASSERT_FALSE(reader.HasMoreData()); // Should not have more data to read.
+
+ std::string string_value;
+ ASSERT_TRUE(array_reader.PopString(&string_value));
+ EXPECT_EQ("foo", string_value);
+ ASSERT_TRUE(array_reader.PopString(&string_value));
+ EXPECT_EQ("bar", string_value);
+ ASSERT_TRUE(array_reader.PopString(&string_value));
+ EXPECT_EQ("baz", string_value);
+ // Should not have more data to read.
+ ASSERT_FALSE(array_reader.HasMoreData());
+}
+
+// Create a complex message using array, struct, variant, dict entry, and
+// make sure it can be read properly.
+TEST(MessageTest, CreateComplexMessageAndReadIt) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ {
+ MessageWriter array_writer(nullptr);
+ // Open an array of variants.
+ writer.OpenArray("v", &array_writer);
+ {
+ // The first value in the array.
+ {
+ MessageWriter variant_writer(nullptr);
+ // Open a variant of a boolean.
+ array_writer.OpenVariant("b", &variant_writer);
+ variant_writer.AppendBool(true);
+ array_writer.CloseContainer(&variant_writer);
+ }
+
+ // The second value in the array.
+ {
+ MessageWriter variant_writer(nullptr);
+ // Open a variant of a struct that contains a string and an int32_t.
+ array_writer.OpenVariant("(si)", &variant_writer);
+ {
+ MessageWriter struct_writer(nullptr);
+ variant_writer.OpenStruct(&struct_writer);
+ struct_writer.AppendString("string");
+ struct_writer.AppendInt32(123);
+ variant_writer.CloseContainer(&struct_writer);
+ }
+ array_writer.CloseContainer(&variant_writer);
+ }
+
+ // The third value in the array.
+ {
+ MessageWriter variant_writer(nullptr);
+ // Open a variant of an array of string-to-int64_t dict entries.
+ array_writer.OpenVariant("a{sx}", &variant_writer);
+ {
+ // Opens an array of string-to-int64_t dict entries.
+ MessageWriter dict_array_writer(nullptr);
+ variant_writer.OpenArray("{sx}", &dict_array_writer);
+ {
+ // Opens a string-to-int64_t dict entries.
+ MessageWriter dict_entry_writer(nullptr);
+ dict_array_writer.OpenDictEntry(&dict_entry_writer);
+ dict_entry_writer.AppendString("foo");
+ dict_entry_writer.AppendInt64(INT64_C(1234567890123456789));
+ dict_array_writer.CloseContainer(&dict_entry_writer);
+ }
+ variant_writer.CloseContainer(&dict_array_writer);
+ }
+ array_writer.CloseContainer(&variant_writer);
+ }
+ }
+ writer.CloseContainer(&array_writer);
+ }
+ // What we have created looks like this:
+ EXPECT_EQ(
+ "message_type: MESSAGE_METHOD_RETURN\n"
+ "signature: av\n"
+ "\n"
+ "array [\n"
+ " variant bool true\n"
+ " variant struct {\n"
+ " string \"string\"\n"
+ " int32_t 123\n"
+ " }\n"
+ " variant array [\n"
+ " dict entry {\n"
+ " string \"foo\"\n"
+ " int64_t 1234567890123456789\n"
+ " }\n"
+ " ]\n"
+ "]\n",
+ message->ToString());
+
+ MessageReader reader(message.get());
+ ASSERT_EQ("av", reader.GetDataSignature());
+ MessageReader array_reader(nullptr);
+ ASSERT_TRUE(reader.PopArray(&array_reader));
+
+ // The first value in the array.
+ bool bool_value = false;
+ ASSERT_EQ("v", array_reader.GetDataSignature());
+ ASSERT_TRUE(array_reader.PopVariantOfBool(&bool_value));
+ EXPECT_EQ(true, bool_value);
+
+ // The second value in the array.
+ {
+ MessageReader variant_reader(nullptr);
+ ASSERT_TRUE(array_reader.PopVariant(&variant_reader));
+ {
+ MessageReader struct_reader(nullptr);
+ ASSERT_EQ("(si)", variant_reader.GetDataSignature());
+ ASSERT_TRUE(variant_reader.PopStruct(&struct_reader));
+ std::string string_value;
+ ASSERT_TRUE(struct_reader.PopString(&string_value));
+ EXPECT_EQ("string", string_value);
+ int32_t int32_value = 0;
+ ASSERT_TRUE(struct_reader.PopInt32(&int32_value));
+ EXPECT_EQ(123, int32_value);
+ ASSERT_FALSE(struct_reader.HasMoreData());
+ }
+ ASSERT_FALSE(variant_reader.HasMoreData());
+ }
+
+ // The third value in the array.
+ {
+ MessageReader variant_reader(nullptr);
+ ASSERT_TRUE(array_reader.PopVariant(&variant_reader));
+ {
+ MessageReader dict_array_reader(nullptr);
+ ASSERT_EQ("a{sx}", variant_reader.GetDataSignature());
+ ASSERT_TRUE(variant_reader.PopArray(&dict_array_reader));
+ {
+ MessageReader dict_entry_reader(nullptr);
+ ASSERT_TRUE(dict_array_reader.PopDictEntry(&dict_entry_reader));
+ std::string string_value;
+ ASSERT_TRUE(dict_entry_reader.PopString(&string_value));
+ EXPECT_EQ("foo", string_value);
+ int64_t int64_value = 0;
+ ASSERT_TRUE(dict_entry_reader.PopInt64(&int64_value));
+ EXPECT_EQ(INT64_C(1234567890123456789), int64_value);
+ }
+ ASSERT_FALSE(dict_array_reader.HasMoreData());
+ }
+ ASSERT_FALSE(variant_reader.HasMoreData());
+ }
+ ASSERT_FALSE(array_reader.HasMoreData());
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST(MessageTest, MethodCall) {
+ MethodCall method_call("com.example.Interface", "SomeMethod");
+ EXPECT_NE(nullptr, method_call.raw_message());
+ EXPECT_EQ(Message::MESSAGE_METHOD_CALL, method_call.GetMessageType());
+ EXPECT_EQ("MESSAGE_METHOD_CALL", method_call.GetMessageTypeAsString());
+ method_call.SetDestination("com.example.Service");
+ method_call.SetPath(ObjectPath("/com/example/Object"));
+
+ MessageWriter writer(&method_call);
+ writer.AppendString("payload");
+
+ EXPECT_EQ(
+ "message_type: MESSAGE_METHOD_CALL\n"
+ "destination: com.example.Service\n"
+ "path: /com/example/Object\n"
+ "interface: com.example.Interface\n"
+ "member: SomeMethod\n"
+ "signature: s\n"
+ "\n"
+ "string \"payload\"\n",
+ method_call.ToString());
+}
+
+TEST(MessageTest, MethodCall_FromRawMessage) {
+ DBusMessage* raw_message = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
+ dbus_message_set_interface(raw_message, "com.example.Interface");
+ dbus_message_set_member(raw_message, "SomeMethod");
+
+ std::unique_ptr<MethodCall> method_call(
+ MethodCall::FromRawMessage(raw_message));
+ EXPECT_EQ("com.example.Interface", method_call->GetInterface());
+ EXPECT_EQ("SomeMethod", method_call->GetMember());
+}
+
+TEST(MessageTest, Signal) {
+ Signal signal("com.example.Interface", "SomeSignal");
+ EXPECT_NE(nullptr, signal.raw_message());
+ EXPECT_EQ(Message::MESSAGE_SIGNAL, signal.GetMessageType());
+ EXPECT_EQ("MESSAGE_SIGNAL", signal.GetMessageTypeAsString());
+ signal.SetPath(ObjectPath("/com/example/Object"));
+
+ MessageWriter writer(&signal);
+ writer.AppendString("payload");
+
+ EXPECT_EQ(
+ "message_type: MESSAGE_SIGNAL\n"
+ "path: /com/example/Object\n"
+ "interface: com.example.Interface\n"
+ "member: SomeSignal\n"
+ "signature: s\n"
+ "\n"
+ "string \"payload\"\n",
+ signal.ToString());
+}
+
+TEST(MessageTest, Signal_FromRawMessage) {
+ DBusMessage* raw_message = dbus_message_new(DBUS_MESSAGE_TYPE_SIGNAL);
+ dbus_message_set_interface(raw_message, "com.example.Interface");
+ dbus_message_set_member(raw_message, "SomeSignal");
+
+ std::unique_ptr<Signal> signal(Signal::FromRawMessage(raw_message));
+ EXPECT_EQ("com.example.Interface", signal->GetInterface());
+ EXPECT_EQ("SomeSignal", signal->GetMember());
+}
+
+TEST(MessageTest, Response) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ EXPECT_TRUE(response->raw_message());
+ EXPECT_EQ(Message::MESSAGE_METHOD_RETURN, response->GetMessageType());
+ EXPECT_EQ("MESSAGE_METHOD_RETURN", response->GetMessageTypeAsString());
+}
+
+TEST(MessageTest, Response_FromMethodCall) {
+ const uint32_t kSerial = 123;
+ MethodCall method_call("com.example.Interface", "SomeMethod");
+ method_call.SetSerial(kSerial);
+
+ std::unique_ptr<Response> response(Response::FromMethodCall(&method_call));
+ EXPECT_EQ(Message::MESSAGE_METHOD_RETURN, response->GetMessageType());
+ EXPECT_EQ("MESSAGE_METHOD_RETURN", response->GetMessageTypeAsString());
+ // The serial should be copied to the reply serial.
+ EXPECT_EQ(kSerial, response->GetReplySerial());
+}
+
+TEST(MessageTest, ErrorResponse_FromMethodCall) {
+ const uint32_t kSerial = 123;
+ const char kErrorMessage[] = "error message";
+
+ MethodCall method_call("com.example.Interface", "SomeMethod");
+ method_call.SetSerial(kSerial);
+
+ std::unique_ptr<ErrorResponse> error_response(ErrorResponse::FromMethodCall(
+ &method_call, DBUS_ERROR_FAILED, kErrorMessage));
+ EXPECT_EQ(Message::MESSAGE_ERROR, error_response->GetMessageType());
+ EXPECT_EQ("MESSAGE_ERROR", error_response->GetMessageTypeAsString());
+ // The serial should be copied to the reply serial.
+ EXPECT_EQ(kSerial, error_response->GetReplySerial());
+
+ // Error message should be added to the payload.
+ MessageReader reader(error_response.get());
+ std::string error_message;
+ ASSERT_TRUE(reader.PopString(&error_message));
+ EXPECT_EQ(kErrorMessage, error_message);
+}
+
+TEST(MessageTest, GetAndSetHeaders) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+
+ EXPECT_EQ("", message->GetDestination());
+ EXPECT_EQ(ObjectPath(std::string()), message->GetPath());
+ EXPECT_EQ("", message->GetInterface());
+ EXPECT_EQ("", message->GetMember());
+ EXPECT_EQ("", message->GetErrorName());
+ EXPECT_EQ("", message->GetSender());
+ EXPECT_EQ(0U, message->GetSerial());
+ EXPECT_EQ(0U, message->GetReplySerial());
+
+ EXPECT_TRUE(message->SetDestination("org.chromium.destination"));
+ EXPECT_TRUE(message->SetPath(ObjectPath("/org/chromium/path")));
+ EXPECT_TRUE(message->SetInterface("org.chromium.interface"));
+ EXPECT_TRUE(message->SetMember("member"));
+ EXPECT_TRUE(message->SetErrorName("org.chromium.error"));
+ EXPECT_TRUE(message->SetSender(":1.2"));
+ message->SetSerial(123);
+ message->SetReplySerial(456);
+
+ EXPECT_EQ("org.chromium.destination", message->GetDestination());
+ EXPECT_EQ(ObjectPath("/org/chromium/path"), message->GetPath());
+ EXPECT_EQ("org.chromium.interface", message->GetInterface());
+ EXPECT_EQ("member", message->GetMember());
+ EXPECT_EQ("org.chromium.error", message->GetErrorName());
+ EXPECT_EQ(":1.2", message->GetSender());
+ EXPECT_EQ(123U, message->GetSerial());
+ EXPECT_EQ(456U, message->GetReplySerial());
+}
+
+TEST(MessageTest, SetInvalidHeaders) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ EXPECT_EQ("", message->GetDestination());
+ EXPECT_EQ(ObjectPath(std::string()), message->GetPath());
+ EXPECT_EQ("", message->GetInterface());
+ EXPECT_EQ("", message->GetMember());
+ EXPECT_EQ("", message->GetErrorName());
+ EXPECT_EQ("", message->GetSender());
+
+ // Empty element between periods.
+ EXPECT_FALSE(message->SetDestination("org..chromium"));
+ // Trailing '/' is only allowed for the root path.
+ EXPECT_FALSE(message->SetPath(ObjectPath("/org/chromium/")));
+ // Interface name cannot contain '/'.
+ EXPECT_FALSE(message->SetInterface("org/chromium/interface"));
+ // Member name cannot begin with a digit.
+ EXPECT_FALSE(message->SetMember("1member"));
+ // Error name cannot begin with a period.
+ EXPECT_FALSE(message->SetErrorName(".org.chromium.error"));
+ // Disallowed characters.
+ EXPECT_FALSE(message->SetSender("?!#*"));
+
+ EXPECT_EQ("", message->GetDestination());
+ EXPECT_EQ(ObjectPath(std::string()), message->GetPath());
+ EXPECT_EQ("", message->GetInterface());
+ EXPECT_EQ("", message->GetMember());
+ EXPECT_EQ("", message->GetErrorName());
+ EXPECT_EQ("", message->GetSender());
+}
+
+TEST(MessageTest, ToString_LongString) {
+ const std::string kLongString(1000, 'o');
+
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ writer.AppendString(kLongString);
+
+ ASSERT_EQ(
+ "message_type: MESSAGE_METHOD_RETURN\n"
+ "signature: s\n\n"
+ "string \"oooooooooooooooooooooooooooooooooooooooooooooooo"
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooo... "
+ "(1000 bytes in total)\"\n",
+ message->ToString());
+}
+
+} // namespace dbus
diff --git a/dbus/mock_unittest.cc b/dbus/mock_unittest.cc
new file mode 100644
index 0000000000..aa26bc4294
--- /dev/null
+++ b/dbus/mock_unittest.cc
@@ -0,0 +1,215 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "dbus/message.h"
+#include "dbus/mock_bus.h"
+#include "dbus/mock_exported_object.h"
+#include "dbus/mock_object_proxy.h"
+#include "dbus/object_path.h"
+#include "dbus/scoped_dbus_error.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::Unused;
+
+namespace dbus {
+
+class MockTest : public testing::Test {
+ public:
+ MockTest() = default;
+
+ void SetUp() override {
+ // Create a mock bus.
+ Bus::Options options;
+ options.bus_type = Bus::SYSTEM;
+ mock_bus_ = new MockBus(options);
+
+ // Create a mock proxy.
+ mock_proxy_ = new MockObjectProxy(
+ mock_bus_.get(),
+ "org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"));
+
+ // Set an expectation so mock_proxy's CallMethodAndBlock() will use
+ // CreateMockProxyResponse() to return responses.
+ EXPECT_CALL(*mock_proxy_.get(), CallMethodAndBlock(_, _))
+ .WillRepeatedly(Invoke(this, &MockTest::CreateMockProxyResponse));
+ EXPECT_CALL(*mock_proxy_.get(),
+ CallMethodAndBlockWithErrorDetails(_, _, _))
+ .WillRepeatedly(
+ Invoke(this, &MockTest::CreateMockProxyResponseWithErrorDetails));
+
+ // Set an expectation so mock_proxy's CallMethod() will use
+ // HandleMockProxyResponseWithMessageLoop() to return responses.
+ EXPECT_CALL(*mock_proxy_.get(), DoCallMethod(_, _, _))
+ .WillRepeatedly(
+ Invoke(this, &MockTest::HandleMockProxyResponseWithMessageLoop));
+
+ // Set an expectation so mock_bus's GetObjectProxy() for the given
+ // service name and the object path will return mock_proxy_.
+ EXPECT_CALL(*mock_bus_.get(),
+ GetObjectProxy("org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject")))
+ .WillOnce(Return(mock_proxy_.get()));
+
+ // ShutdownAndBlock() will be called in TearDown().
+ EXPECT_CALL(*mock_bus_.get(), ShutdownAndBlock()).WillOnce(Return());
+ }
+
+ void TearDown() override { mock_bus_->ShutdownAndBlock(); }
+
+ // Called when the response is received.
+ void OnResponse(Response* response) {
+ // |response| will be deleted on exit of the function. Copy the
+ // payload to |response_string_|.
+ if (response) {
+ MessageReader reader(response);
+ ASSERT_TRUE(reader.PopString(&response_string_));
+ }
+ run_loop_->Quit();
+ };
+
+ protected:
+ std::string response_string_;
+ base::MessageLoop message_loop_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ scoped_refptr<MockBus> mock_bus_;
+ scoped_refptr<MockObjectProxy> mock_proxy_;
+
+ private:
+ // Returns a response for the given method call. Used to implement
+ // CallMethodAndBlock() for |mock_proxy_|.
+ std::unique_ptr<Response> CreateMockProxyResponse(MethodCall* method_call,
+ int timeout_ms) {
+ if (method_call->GetInterface() == "org.chromium.TestInterface" &&
+ method_call->GetMember() == "Echo") {
+ MessageReader reader(method_call);
+ std::string text_message;
+ if (reader.PopString(&text_message)) {
+ std::unique_ptr<Response> response = Response::CreateEmpty();
+ MessageWriter writer(response.get());
+ writer.AppendString(text_message);
+ return response;
+ }
+ }
+
+ LOG(ERROR) << "Unexpected method call: " << method_call->ToString();
+ return nullptr;
+ }
+
+ std::unique_ptr<Response> CreateMockProxyResponseWithErrorDetails(
+ MethodCall* method_call, int timeout_ms, ScopedDBusError* error) {
+ dbus_set_error(error->get(), DBUS_ERROR_NOT_SUPPORTED, "Not implemented");
+ return nullptr;
+ }
+
+ // Creates a response and runs the given response callback in the
+ // message loop with the response. Used to implement for |mock_proxy_|.
+ void HandleMockProxyResponseWithMessageLoop(
+ MethodCall* method_call,
+ int timeout_ms,
+ ObjectProxy::ResponseCallback* response_callback) {
+ std::unique_ptr<Response> response =
+ CreateMockProxyResponse(method_call, timeout_ms);
+ message_loop_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&MockTest::RunResponseCallback, base::Unretained(this),
+ std::move(*response_callback), std::move(response)));
+ }
+
+ // Runs the given response callback with the given response.
+ void RunResponseCallback(
+ ObjectProxy::ResponseCallback response_callback,
+ std::unique_ptr<Response> response) {
+ std::move(response_callback).Run(response.get());
+ }
+};
+
+// This test demonstrates how to mock a synchronous method call using the
+// mock classes.
+TEST_F(MockTest, CallMethodAndBlock) {
+ const char kHello[] = "Hello";
+ // Get an object proxy from the mock bus.
+ ObjectProxy* proxy = mock_bus_->GetObjectProxy(
+ "org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"));
+
+ // Create a method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ std::unique_ptr<Response> response(proxy->CallMethodAndBlock(
+ &method_call, ObjectProxy::TIMEOUT_USE_DEFAULT));
+
+ // Check the response.
+ ASSERT_TRUE(response.get());
+ MessageReader reader(response.get());
+ std::string text_message;
+ ASSERT_TRUE(reader.PopString(&text_message));
+ // The text message should be echo'ed back.
+ EXPECT_EQ(kHello, text_message);
+}
+
+TEST_F(MockTest, CallMethodAndBlockWithErrorDetails) {
+ // Get an object proxy from the mock bus.
+ ObjectProxy* proxy = mock_bus_->GetObjectProxy(
+ "org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"));
+
+ // Create a method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+
+ ScopedDBusError error;
+ // Call the method.
+ std::unique_ptr<Response> response(proxy->CallMethodAndBlockWithErrorDetails(
+ &method_call, ObjectProxy::TIMEOUT_USE_DEFAULT, &error));
+
+ // Check the response.
+ ASSERT_FALSE(response.get());
+ ASSERT_TRUE(error.is_set());
+ EXPECT_STREQ(DBUS_ERROR_NOT_SUPPORTED, error.name());
+ EXPECT_STREQ("Not implemented", error.message());
+}
+
+// This test demonstrates how to mock an asynchronous method call using the
+// mock classes.
+TEST_F(MockTest, CallMethod) {
+ const char kHello[] = "hello";
+
+ // Get an object proxy from the mock bus.
+ ObjectProxy* proxy = mock_bus_->GetObjectProxy(
+ "org.chromium.TestService",
+ ObjectPath("/org/chromium/TestObject"));
+
+ // Create a method call.
+ MethodCall method_call("org.chromium.TestInterface", "Echo");
+ MessageWriter writer(&method_call);
+ writer.AppendString(kHello);
+
+ // Call the method.
+ run_loop_.reset(new base::RunLoop);
+ proxy->CallMethod(&method_call,
+ ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::Bind(&MockTest::OnResponse,
+ base::Unretained(this)));
+ // Run the message loop to let OnResponse be called.
+ run_loop_->Run();
+
+ EXPECT_EQ(kHello, response_string_);
+}
+
+} // namespace dbus
diff --git a/dbus/object_manager_unittest.cc b/dbus/object_manager_unittest.cc
new file mode 100644
index 0000000000..0fb74adff9
--- /dev/null
+++ b/dbus/object_manager_unittest.cc
@@ -0,0 +1,419 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/object_manager.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "dbus/bus.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "dbus/property.h"
+#include "dbus/test_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+// The object manager test exercises the asynchronous APIs in ObjectManager,
+// and by extension PropertySet and Property<>.
+class ObjectManagerTest
+ : public testing::Test,
+ public ObjectManager::Interface {
+ public:
+ ObjectManagerTest() : timeout_expired_(false) {
+ }
+
+ struct Properties : public PropertySet {
+ Property<std::string> name;
+ Property<int16_t> version;
+ Property<std::vector<std::string>> methods;
+ Property<std::vector<ObjectPath>> objects;
+
+ Properties(ObjectProxy* object_proxy,
+ const std::string& interface_name,
+ PropertyChangedCallback property_changed_callback)
+ : PropertySet(object_proxy, interface_name, property_changed_callback) {
+ RegisterProperty("Name", &name);
+ RegisterProperty("Version", &version);
+ RegisterProperty("Methods", &methods);
+ RegisterProperty("Objects", &objects);
+ }
+ };
+
+ PropertySet* CreateProperties(ObjectProxy* object_proxy,
+ const ObjectPath& object_path,
+ const std::string& interface_name) override {
+ Properties* properties = new Properties(
+ object_proxy, interface_name,
+ base::Bind(&ObjectManagerTest::OnPropertyChanged,
+ base::Unretained(this), object_path));
+ return static_cast<PropertySet*>(properties);
+ }
+
+ void SetUp() override {
+ // Make the main thread not to allow IO.
+ base::ThreadRestrictions::SetIOAllowed(false);
+
+ // Start the D-Bus thread.
+ dbus_thread_.reset(new base::Thread("D-Bus Thread"));
+ base::Thread::Options thread_options;
+ thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options));
+
+ // Start the test service, using the D-Bus thread.
+ TestService::Options options;
+ options.dbus_task_runner = dbus_thread_->task_runner();
+ test_service_.reset(new TestService(options));
+ ASSERT_TRUE(test_service_->StartService());
+ ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted());
+ ASSERT_TRUE(test_service_->HasDBusThread());
+
+ // Create the client, using the D-Bus thread.
+ Bus::Options bus_options;
+ bus_options.bus_type = Bus::SESSION;
+ bus_options.connection_type = Bus::PRIVATE;
+ bus_options.dbus_task_runner = dbus_thread_->task_runner();
+ bus_ = new Bus(bus_options);
+ ASSERT_TRUE(bus_->HasDBusThread());
+
+ object_manager_ = bus_->GetObjectManager(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestService"));
+ object_manager_->RegisterInterface("org.chromium.TestInterface", this);
+
+ WaitForObject();
+ }
+
+ void TearDown() override {
+ bus_->ShutdownOnDBusThreadAndBlock();
+
+ // Shut down the service.
+ test_service_->ShutdownAndBlock();
+
+ // Reset to the default.
+ base::ThreadRestrictions::SetIOAllowed(true);
+
+ // Stopping a thread is considered an IO operation, so do this after
+ // allowing IO.
+ test_service_->Stop();
+
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void MethodCallback(Response* response) {
+ method_callback_called_ = true;
+ run_loop_->Quit();
+ }
+
+ // Called from the PropertiesChangedAsObjectsReceived test case. The test will
+ // not run the message loop if it receives the expected PropertiesChanged
+ // signal before the timeout. This method immediately fails the test.
+ void PropertiesChangedTestTimeout() {
+ timeout_expired_ = true;
+ run_loop_->Quit();
+
+ FAIL() << "Never received PropertiesChanged";
+ }
+
+ protected:
+ // Called when an object is added.
+ void ObjectAdded(const ObjectPath& object_path,
+ const std::string& interface_name) override {
+ added_objects_.push_back(std::make_pair(object_path, interface_name));
+ run_loop_->Quit();
+ }
+
+ // Called when an object is removed.
+ void ObjectRemoved(const ObjectPath& object_path,
+ const std::string& interface_name) override {
+ removed_objects_.push_back(std::make_pair(object_path, interface_name));
+ run_loop_->Quit();
+ }
+
+ // Called when a property value is updated.
+ void OnPropertyChanged(const ObjectPath& object_path,
+ const std::string& name) {
+ // Store the value of the "Name" property if that's the one that
+ // changed.
+ Properties* properties = static_cast<Properties*>(
+ object_manager_->GetProperties(
+ object_path,
+ "org.chromium.TestInterface"));
+ if (name == properties->name.name())
+ last_name_value_ = properties->name.value();
+
+ // Store the updated property.
+ updated_properties_.push_back(name);
+ run_loop_->Quit();
+ }
+
+ static const size_t kExpectedObjects = 1;
+ static const size_t kExpectedProperties = 4;
+
+ void WaitForObject() {
+ while (added_objects_.size() < kExpectedObjects ||
+ updated_properties_.size() < kExpectedProperties) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ for (size_t i = 0; i < kExpectedObjects; ++i)
+ added_objects_.erase(added_objects_.begin());
+ for (size_t i = 0; i < kExpectedProperties; ++i)
+ updated_properties_.erase(updated_properties_.begin());
+ }
+
+ void WaitForRemoveObject() {
+ while (removed_objects_.size() < kExpectedObjects) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ for (size_t i = 0; i < kExpectedObjects; ++i)
+ removed_objects_.erase(removed_objects_.begin());
+ }
+
+ void WaitForMethodCallback() {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ method_callback_called_ = false;
+ }
+
+ void PerformAction(const std::string& action, const ObjectPath& object_path) {
+ ObjectProxy* object_proxy = bus_->GetObjectProxy(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestObject"));
+
+ MethodCall method_call("org.chromium.TestInterface", "PerformAction");
+ MessageWriter writer(&method_call);
+ writer.AppendString(action);
+ writer.AppendObjectPath(object_path);
+
+ object_proxy->CallMethod(&method_call,
+ ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::Bind(&ObjectManagerTest::MethodCallback,
+ base::Unretained(this)));
+ WaitForMethodCallback();
+ }
+
+ base::MessageLoop message_loop_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ std::unique_ptr<base::Thread> dbus_thread_;
+ scoped_refptr<Bus> bus_;
+ ObjectManager* object_manager_;
+ std::unique_ptr<TestService> test_service_;
+
+ std::string last_name_value_;
+ bool timeout_expired_;
+
+ std::vector<std::pair<ObjectPath, std::string>> added_objects_;
+ std::vector<std::pair<ObjectPath, std::string>> removed_objects_;
+ std::vector<std::string> updated_properties_;
+
+ bool method_callback_called_;
+};
+
+
+TEST_F(ObjectManagerTest, InitialObject) {
+ ObjectProxy* object_proxy = object_manager_->GetObjectProxy(
+ ObjectPath("/org/chromium/TestObject"));
+ EXPECT_NE(nullptr, object_proxy);
+
+ Properties* properties = static_cast<Properties*>(
+ object_manager_->GetProperties(ObjectPath("/org/chromium/TestObject"),
+ "org.chromium.TestInterface"));
+ EXPECT_NE(nullptr, properties);
+
+ EXPECT_EQ("TestService", properties->name.value());
+ EXPECT_EQ(10, properties->version.value());
+
+ std::vector<std::string> methods = properties->methods.value();
+ ASSERT_EQ(4U, methods.size());
+ EXPECT_EQ("Echo", methods[0]);
+ EXPECT_EQ("SlowEcho", methods[1]);
+ EXPECT_EQ("AsyncEcho", methods[2]);
+ EXPECT_EQ("BrokenMethod", methods[3]);
+
+ std::vector<ObjectPath> objects = properties->objects.value();
+ ASSERT_EQ(1U, objects.size());
+ EXPECT_EQ(ObjectPath("/TestObjectPath"), objects[0]);
+}
+
+TEST_F(ObjectManagerTest, UnknownObjectProxy) {
+ ObjectProxy* object_proxy = object_manager_->GetObjectProxy(
+ ObjectPath("/org/chromium/UnknownObject"));
+ EXPECT_EQ(nullptr, object_proxy);
+}
+
+TEST_F(ObjectManagerTest, UnknownObjectProperties) {
+ Properties* properties = static_cast<Properties*>(
+ object_manager_->GetProperties(ObjectPath("/org/chromium/UnknownObject"),
+ "org.chromium.TestInterface"));
+ EXPECT_EQ(nullptr, properties);
+}
+
+TEST_F(ObjectManagerTest, UnknownInterfaceProperties) {
+ Properties* properties = static_cast<Properties*>(
+ object_manager_->GetProperties(ObjectPath("/org/chromium/TestObject"),
+ "org.chromium.UnknownService"));
+ EXPECT_EQ(nullptr, properties);
+}
+
+TEST_F(ObjectManagerTest, GetObjects) {
+ std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
+ ASSERT_EQ(1U, object_paths.size());
+ EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]);
+}
+
+TEST_F(ObjectManagerTest, GetObjectsWithInterface) {
+ std::vector<ObjectPath> object_paths =
+ object_manager_->GetObjectsWithInterface("org.chromium.TestInterface");
+ ASSERT_EQ(1U, object_paths.size());
+ EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]);
+}
+
+TEST_F(ObjectManagerTest, GetObjectsWithUnknownInterface) {
+ std::vector<ObjectPath> object_paths =
+ object_manager_->GetObjectsWithInterface("org.chromium.UnknownService");
+ EXPECT_EQ(0U, object_paths.size());
+}
+
+TEST_F(ObjectManagerTest, SameObject) {
+ ObjectManager* object_manager = bus_->GetObjectManager(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestService"));
+ EXPECT_EQ(object_manager_, object_manager);
+}
+
+TEST_F(ObjectManagerTest, DifferentObjectForService) {
+ ObjectManager* object_manager = bus_->GetObjectManager(
+ "org.chromium.DifferentService",
+ ObjectPath("/org/chromium/TestService"));
+ EXPECT_NE(object_manager_, object_manager);
+}
+
+TEST_F(ObjectManagerTest, DifferentObjectForPath) {
+ ObjectManager* object_manager = bus_->GetObjectManager(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/DifferentService"));
+ EXPECT_NE(object_manager_, object_manager);
+}
+
+TEST_F(ObjectManagerTest, SecondObject) {
+ PerformAction("AddObject", ObjectPath("/org/chromium/SecondObject"));
+ WaitForObject();
+
+ ObjectProxy* object_proxy = object_manager_->GetObjectProxy(
+ ObjectPath("/org/chromium/SecondObject"));
+ EXPECT_NE(nullptr, object_proxy);
+
+ Properties* properties = static_cast<Properties*>(
+ object_manager_->GetProperties(ObjectPath("/org/chromium/SecondObject"),
+ "org.chromium.TestInterface"));
+ EXPECT_NE(nullptr, properties);
+
+ std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
+ ASSERT_EQ(2U, object_paths.size());
+
+ std::sort(object_paths.begin(), object_paths.end());
+ EXPECT_EQ(ObjectPath("/org/chromium/SecondObject"), object_paths[0]);
+ EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[1]);
+
+ object_paths =
+ object_manager_->GetObjectsWithInterface("org.chromium.TestInterface");
+ ASSERT_EQ(2U, object_paths.size());
+
+ std::sort(object_paths.begin(), object_paths.end());
+ EXPECT_EQ(ObjectPath("/org/chromium/SecondObject"), object_paths[0]);
+ EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[1]);
+}
+
+TEST_F(ObjectManagerTest, RemoveSecondObject) {
+ PerformAction("AddObject", ObjectPath("/org/chromium/SecondObject"));
+ WaitForObject();
+
+ std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
+ ASSERT_EQ(2U, object_paths.size());
+
+ PerformAction("RemoveObject", ObjectPath("/org/chromium/SecondObject"));
+ WaitForRemoveObject();
+
+ ObjectProxy* object_proxy = object_manager_->GetObjectProxy(
+ ObjectPath("/org/chromium/SecondObject"));
+ EXPECT_EQ(nullptr, object_proxy);
+
+ Properties* properties = static_cast<Properties*>(
+ object_manager_->GetProperties(ObjectPath("/org/chromium/SecondObject"),
+ "org.chromium.TestInterface"));
+ EXPECT_EQ(nullptr, properties);
+
+ object_paths = object_manager_->GetObjects();
+ ASSERT_EQ(1U, object_paths.size());
+ EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]);
+
+ object_paths =
+ object_manager_->GetObjectsWithInterface("org.chromium.TestInterface");
+ ASSERT_EQ(1U, object_paths.size());
+ EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]);
+}
+
+TEST_F(ObjectManagerTest, OwnershipLost) {
+ PerformAction("ReleaseOwnership", ObjectPath("/org/chromium/TestService"));
+ WaitForRemoveObject();
+
+ std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
+ ASSERT_EQ(0U, object_paths.size());
+}
+
+TEST_F(ObjectManagerTest, OwnershipLostAndRegained) {
+ PerformAction("Ownership", ObjectPath("/org/chromium/TestService"));
+ WaitForRemoveObject();
+ WaitForObject();
+
+ std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
+ ASSERT_EQ(1U, object_paths.size());
+}
+
+TEST_F(ObjectManagerTest, PropertiesChangedAsObjectsReceived) {
+ // Remove the existing object manager.
+ object_manager_->UnregisterInterface("org.chromium.TestInterface");
+ run_loop_.reset(new base::RunLoop);
+ EXPECT_TRUE(bus_->RemoveObjectManager(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestService"),
+ run_loop_->QuitClosure()));
+ run_loop_->Run();
+
+ PerformAction("SetSendImmediatePropertiesChanged",
+ ObjectPath("/org/chromium/TestService"));
+
+ object_manager_ = bus_->GetObjectManager(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestService"));
+ object_manager_->RegisterInterface("org.chromium.TestInterface", this);
+
+ // The newly created object manager should call GetManagedObjects immediately
+ // after setting up the match rule for PropertiesChanged. We should process
+ // the PropertiesChanged event right after that. If we don't receive it within
+ // 2 seconds, then fail the test.
+ message_loop_.task_runner()->PostDelayedTask(
+ FROM_HERE, base::Bind(&ObjectManagerTest::PropertiesChangedTestTimeout,
+ base::Unretained(this)),
+ base::TimeDelta::FromSeconds(2));
+
+ while (last_name_value_ != "ChangedTestServiceName" && !timeout_expired_) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+}
+
+} // namespace dbus
diff --git a/dbus/object_proxy_unittest.cc b/dbus/object_proxy_unittest.cc
new file mode 100644
index 0000000000..1f5746faa4
--- /dev/null
+++ b/dbus/object_proxy_unittest.cc
@@ -0,0 +1,147 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/object_proxy.h"
+#include "base/bind.h"
+#include "base/files/file_descriptor_watcher_posix.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "dbus/bus.h"
+#include "dbus/test_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+namespace {
+
+class ObjectProxyTest : public testing::Test {
+ protected:
+ ObjectProxyTest() : file_descriptor_watcher_(&message_loop_) {}
+
+ void SetUp() override {
+ Bus::Options bus_options;
+ bus_options.bus_type = Bus::SESSION;
+ bus_options.connection_type = Bus::PRIVATE;
+ bus_ = new Bus(bus_options);
+ }
+
+ void TearDown() override { bus_->ShutdownAndBlock(); }
+
+ base::MessageLoopForIO message_loop_;
+
+ // This enables FileDescriptorWatcher, which is required by dbus::Watch.
+ base::FileDescriptorWatcher file_descriptor_watcher_;
+
+ scoped_refptr<Bus> bus_;
+};
+
+// Used as a WaitForServiceToBeAvailableCallback.
+void OnServiceIsAvailable(bool* dest_service_is_available,
+ int* num_calls,
+ bool src_service_is_available) {
+ *dest_service_is_available = src_service_is_available;
+ (*num_calls)++;
+}
+
+// Used as a callback for TestService::RequestOwnership().
+void OnOwnershipRequestDone(bool success) {
+ ASSERT_TRUE(success);
+}
+
+// Used as a callback for TestService::ReleaseOwnership().
+void OnOwnershipReleased() {}
+
+TEST_F(ObjectProxyTest, WaitForServiceToBeAvailableRunOnce) {
+ TestService::Options options;
+ TestService test_service(options);
+ ObjectProxy* object_proxy = bus_->GetObjectProxy(
+ test_service.service_name(), ObjectPath("/org/chromium/TestObject"));
+
+ // The callback is not yet called because the service is not available.
+ int num_calls = 0;
+ bool service_is_available = false;
+ object_proxy->WaitForServiceToBeAvailable(
+ base::Bind(&OnServiceIsAvailable, &service_is_available, &num_calls));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, num_calls);
+
+ // Start the service. The callback should be called asynchronously.
+ ASSERT_TRUE(test_service.StartService());
+ ASSERT_TRUE(test_service.WaitUntilServiceIsStarted());
+ ASSERT_TRUE(test_service.has_ownership());
+ num_calls = 0;
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, num_calls);
+ EXPECT_TRUE(service_is_available);
+
+ // Release the service's ownership of its name. The callback should not be
+ // invoked again.
+ test_service.ReleaseOwnership(base::Bind(&OnOwnershipReleased));
+ num_calls = 0;
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, num_calls);
+
+ // Take ownership of the name and check that the callback is not called.
+ test_service.RequestOwnership(base::Bind(&OnOwnershipRequestDone));
+ num_calls = 0;
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, num_calls);
+}
+
+TEST_F(ObjectProxyTest, WaitForServiceToBeAvailableAlreadyRunning) {
+ TestService::Options options;
+ TestService test_service(options);
+ ObjectProxy* object_proxy = bus_->GetObjectProxy(
+ test_service.service_name(), ObjectPath("/org/chromium/TestObject"));
+
+ ASSERT_TRUE(test_service.StartService());
+ ASSERT_TRUE(test_service.WaitUntilServiceIsStarted());
+ ASSERT_TRUE(test_service.has_ownership());
+
+ // Since the service is already running, the callback should be invoked
+ // immediately (but asynchronously, rather than the callback being invoked
+ // directly within WaitForServiceToBeAvailable()).
+ int num_calls = 0;
+ bool service_is_available = false;
+ object_proxy->WaitForServiceToBeAvailable(
+ base::Bind(&OnServiceIsAvailable, &service_is_available, &num_calls));
+ EXPECT_EQ(0, num_calls);
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, num_calls);
+ EXPECT_TRUE(service_is_available);
+}
+
+TEST_F(ObjectProxyTest, WaitForServiceToBeAvailableMultipleCallbacks) {
+ TestService::Options options;
+ TestService test_service(options);
+ ObjectProxy* object_proxy = bus_->GetObjectProxy(
+ test_service.service_name(), ObjectPath("/org/chromium/TestObject"));
+
+ // Register two callbacks.
+ int num_calls_1 = 0, num_calls_2 = 0;
+ bool service_is_available_1 = false, service_is_available_2 = false;
+ object_proxy->WaitForServiceToBeAvailable(
+ base::Bind(&OnServiceIsAvailable, &service_is_available_1, &num_calls_1));
+ object_proxy->WaitForServiceToBeAvailable(
+ base::Bind(&OnServiceIsAvailable, &service_is_available_2, &num_calls_2));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, num_calls_1);
+ EXPECT_EQ(0, num_calls_2);
+
+ // Start the service and confirm that both callbacks are invoked.
+ ASSERT_TRUE(test_service.StartService());
+ ASSERT_TRUE(test_service.WaitUntilServiceIsStarted());
+ ASSERT_TRUE(test_service.has_ownership());
+ num_calls_1 = 0;
+ num_calls_2 = 0;
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, num_calls_1);
+ EXPECT_EQ(1, num_calls_2);
+ EXPECT_TRUE(service_is_available_1);
+ EXPECT_TRUE(service_is_available_2);
+}
+
+} // namespace
+} // namespace dbus
diff --git a/dbus/property_unittest.cc b/dbus/property_unittest.cc
new file mode 100644
index 0000000000..5b26edd156
--- /dev/null
+++ b/dbus/property_unittest.cc
@@ -0,0 +1,545 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/property.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "dbus/bus.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "dbus/test_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+// The property test exerises the asynchronous APIs in PropertySet and
+// Property<>.
+class PropertyTest : public testing::Test {
+ public:
+ PropertyTest() = default;
+
+ struct Properties : public PropertySet {
+ Property<std::string> name;
+ Property<int16_t> version;
+ Property<std::vector<std::string>> methods;
+ Property<std::vector<ObjectPath>> objects;
+ Property<std::vector<uint8_t>> bytes;
+
+ Properties(ObjectProxy* object_proxy,
+ PropertyChangedCallback property_changed_callback)
+ : PropertySet(object_proxy,
+ "org.chromium.TestInterface",
+ property_changed_callback) {
+ RegisterProperty("Name", &name);
+ RegisterProperty("Version", &version);
+ RegisterProperty("Methods", &methods);
+ RegisterProperty("Objects", &objects);
+ RegisterProperty("Bytes", &bytes);
+ }
+ };
+
+ void SetUp() override {
+ // Make the main thread not to allow IO.
+ base::ThreadRestrictions::SetIOAllowed(false);
+
+ // Start the D-Bus thread.
+ dbus_thread_.reset(new base::Thread("D-Bus Thread"));
+ base::Thread::Options thread_options;
+ thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options));
+
+ // Start the test service, using the D-Bus thread.
+ TestService::Options options;
+ options.dbus_task_runner = dbus_thread_->task_runner();
+ test_service_.reset(new TestService(options));
+ ASSERT_TRUE(test_service_->StartService());
+ ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted());
+ ASSERT_TRUE(test_service_->HasDBusThread());
+
+ // Create the client, using the D-Bus thread.
+ Bus::Options bus_options;
+ bus_options.bus_type = Bus::SESSION;
+ bus_options.connection_type = Bus::PRIVATE;
+ bus_options.dbus_task_runner = dbus_thread_->task_runner();
+ bus_ = new Bus(bus_options);
+ object_proxy_ = bus_->GetObjectProxy(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(bus_->HasDBusThread());
+
+ // Create the properties structure
+ properties_.reset(new Properties(
+ object_proxy_,
+ base::Bind(&PropertyTest::OnPropertyChanged,
+ base::Unretained(this))));
+ properties_->ConnectSignals();
+ properties_->GetAll();
+ }
+
+ void TearDown() override {
+ bus_->ShutdownOnDBusThreadAndBlock();
+
+ // Shut down the service.
+ test_service_->ShutdownAndBlock();
+
+ // Reset to the default.
+ base::ThreadRestrictions::SetIOAllowed(true);
+
+ // Stopping a thread is considered an IO operation, so do this after
+ // allowing IO.
+ test_service_->Stop();
+ }
+
+ // Generic callback, bind with a string |id| for passing to
+ // WaitForCallback() to ensure the callback for the right method is
+ // waited for.
+ void PropertyCallback(const std::string& id, bool success) {
+ last_callback_ = id;
+ run_loop_->Quit();
+ }
+
+ // Generic method callback, that might be used together with
+ // WaitForMethodCallback to test wether method was succesfully called.
+ void MethodCallback(Response* response) { run_loop_->Quit(); }
+
+ protected:
+ // Called when a property value is updated.
+ void OnPropertyChanged(const std::string& name) {
+ updated_properties_.push_back(name);
+ run_loop_->Quit();
+ }
+
+ // Waits for the given number of updates.
+ void WaitForUpdates(size_t num_updates) {
+ while (updated_properties_.size() < num_updates) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ for (size_t i = 0; i < num_updates; ++i)
+ updated_properties_.erase(updated_properties_.begin());
+ }
+
+ // Name, Version, Methods, Objects
+ static const int kExpectedSignalUpdates = 5;
+
+ // Waits for initial values to be set.
+ void WaitForGetAll() {
+ WaitForUpdates(kExpectedSignalUpdates);
+ }
+
+ // Waits until MethodCallback is called.
+ void WaitForMethodCallback() {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+
+ // Waits for the callback. |id| is the string bound to the callback when
+ // the method call is made that identifies it and distinguishes from any
+ // other; you can set this to whatever you wish.
+ void WaitForCallback(const std::string& id) {
+ while (last_callback_ != id) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ }
+
+ base::MessageLoop message_loop_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ std::unique_ptr<base::Thread> dbus_thread_;
+ scoped_refptr<Bus> bus_;
+ ObjectProxy* object_proxy_;
+ std::unique_ptr<Properties> properties_;
+ std::unique_ptr<TestService> test_service_;
+ // Properties updated.
+ std::vector<std::string> updated_properties_;
+ // Last callback received.
+ std::string last_callback_;
+};
+
+TEST_F(PropertyTest, InitialValues) {
+ EXPECT_FALSE(properties_->name.is_valid());
+ EXPECT_FALSE(properties_->version.is_valid());
+
+ WaitForGetAll();
+
+ EXPECT_TRUE(properties_->name.is_valid());
+ EXPECT_EQ("TestService", properties_->name.value());
+ EXPECT_TRUE(properties_->version.is_valid());
+ EXPECT_EQ(10, properties_->version.value());
+
+ std::vector<std::string> methods = properties_->methods.value();
+ ASSERT_EQ(4U, methods.size());
+ EXPECT_EQ("Echo", methods[0]);
+ EXPECT_EQ("SlowEcho", methods[1]);
+ EXPECT_EQ("AsyncEcho", methods[2]);
+ EXPECT_EQ("BrokenMethod", methods[3]);
+
+ std::vector<ObjectPath> objects = properties_->objects.value();
+ ASSERT_EQ(1U, objects.size());
+ EXPECT_EQ(ObjectPath("/TestObjectPath"), objects[0]);
+
+ std::vector<uint8_t> bytes = properties_->bytes.value();
+ ASSERT_EQ(4U, bytes.size());
+ EXPECT_EQ('T', bytes[0]);
+ EXPECT_EQ('e', bytes[1]);
+ EXPECT_EQ('s', bytes[2]);
+ EXPECT_EQ('t', bytes[3]);
+}
+
+TEST_F(PropertyTest, UpdatedValues) {
+ WaitForGetAll();
+
+ // Update the value of the "Name" property, this value should not change.
+ properties_->name.Get(base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Name"));
+ WaitForCallback("Name");
+ WaitForUpdates(1);
+
+ EXPECT_EQ("TestService", properties_->name.value());
+
+ // Update the value of the "Version" property, this value should be changed.
+ properties_->version.Get(base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Version"));
+ WaitForCallback("Version");
+ WaitForUpdates(1);
+
+ EXPECT_EQ(20, properties_->version.value());
+
+ // Update the value of the "Methods" property, this value should not change
+ // and should not grow to contain duplicate entries.
+ properties_->methods.Get(base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Methods"));
+ WaitForCallback("Methods");
+ WaitForUpdates(1);
+
+ std::vector<std::string> methods = properties_->methods.value();
+ ASSERT_EQ(4U, methods.size());
+ EXPECT_EQ("Echo", methods[0]);
+ EXPECT_EQ("SlowEcho", methods[1]);
+ EXPECT_EQ("AsyncEcho", methods[2]);
+ EXPECT_EQ("BrokenMethod", methods[3]);
+
+ // Update the value of the "Objects" property, this value should not change
+ // and should not grow to contain duplicate entries.
+ properties_->objects.Get(base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Objects"));
+ WaitForCallback("Objects");
+ WaitForUpdates(1);
+
+ std::vector<ObjectPath> objects = properties_->objects.value();
+ ASSERT_EQ(1U, objects.size());
+ EXPECT_EQ(ObjectPath("/TestObjectPath"), objects[0]);
+
+ // Update the value of the "Bytes" property, this value should not change
+ // and should not grow to contain duplicate entries.
+ properties_->bytes.Get(base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Bytes"));
+ WaitForCallback("Bytes");
+ WaitForUpdates(1);
+
+ std::vector<uint8_t> bytes = properties_->bytes.value();
+ ASSERT_EQ(4U, bytes.size());
+ EXPECT_EQ('T', bytes[0]);
+ EXPECT_EQ('e', bytes[1]);
+ EXPECT_EQ('s', bytes[2]);
+ EXPECT_EQ('t', bytes[3]);
+}
+
+TEST_F(PropertyTest, Get) {
+ WaitForGetAll();
+
+ // Ask for the new Version property.
+ properties_->version.Get(base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Get"));
+ WaitForCallback("Get");
+
+ // Make sure we got a property update too.
+ WaitForUpdates(1);
+
+ EXPECT_EQ(20, properties_->version.value());
+}
+
+TEST_F(PropertyTest, Set) {
+ WaitForGetAll();
+
+ // Set a new name.
+ properties_->name.Set("NewService",
+ base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Set"));
+ WaitForCallback("Set");
+
+ // TestService sends a property update.
+ WaitForUpdates(1);
+
+ EXPECT_EQ("NewService", properties_->name.value());
+}
+
+TEST_F(PropertyTest, Invalidate) {
+ WaitForGetAll();
+
+ EXPECT_TRUE(properties_->name.is_valid());
+
+ // Invalidate name.
+ MethodCall method_call("org.chromium.TestInterface", "PerformAction");
+ MessageWriter writer(&method_call);
+ writer.AppendString("InvalidateProperty");
+ writer.AppendObjectPath(ObjectPath("/org/chromium/TestService"));
+ object_proxy_->CallMethod(
+ &method_call, ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::Bind(&PropertyTest::MethodCallback, base::Unretained(this)));
+ WaitForMethodCallback();
+
+ // TestService sends a property update.
+ WaitForUpdates(1);
+
+ EXPECT_FALSE(properties_->name.is_valid());
+
+ // Set name to something valid.
+ properties_->name.Set("NewService",
+ base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this), "Set"));
+ WaitForCallback("Set");
+
+ // TestService sends a property update.
+ WaitForUpdates(1);
+
+ EXPECT_TRUE(properties_->name.is_valid());
+}
+
+TEST(PropertyTestStatic, ReadWriteStringMap) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ MessageWriter variant_writer(nullptr);
+ MessageWriter variant_array_writer(nullptr);
+ MessageWriter struct_entry_writer(nullptr);
+
+ writer.OpenVariant("a{ss}", &variant_writer);
+ variant_writer.OpenArray("{ss}", &variant_array_writer);
+ const char* items[] = {"One", "Two", "Three", "Four"};
+ for (unsigned i = 0; i < arraysize(items); ++i) {
+ variant_array_writer.OpenDictEntry(&struct_entry_writer);
+ struct_entry_writer.AppendString(items[i]);
+ struct_entry_writer.AppendString(base::UintToString(i + 1));
+ variant_array_writer.CloseContainer(&struct_entry_writer);
+ }
+ variant_writer.CloseContainer(&variant_array_writer);
+ writer.CloseContainer(&variant_writer);
+
+ MessageReader reader(message.get());
+ Property<std::map<std::string, std::string>> string_map;
+ EXPECT_TRUE(string_map.PopValueFromReader(&reader));
+ ASSERT_EQ(4U, string_map.value().size());
+ EXPECT_EQ("1", string_map.value().at("One"));
+ EXPECT_EQ("2", string_map.value().at("Two"));
+ EXPECT_EQ("3", string_map.value().at("Three"));
+ EXPECT_EQ("4", string_map.value().at("Four"));
+}
+
+TEST(PropertyTestStatic, SerializeStringMap) {
+ std::map<std::string, std::string> test_map;
+ test_map["Hi"] = "There";
+ test_map["Map"] = "Test";
+ test_map["Random"] = "Text";
+
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+
+ Property<std::map<std::string, std::string>> string_map;
+ string_map.ReplaceSetValueForTesting(test_map);
+ string_map.AppendSetValueToWriter(&writer);
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(string_map.PopValueFromReader(&reader));
+ EXPECT_EQ(test_map, string_map.value());
+}
+
+TEST(PropertyTestStatic, ReadWriteNetAddressArray) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ MessageWriter variant_writer(nullptr);
+ MessageWriter variant_array_writer(nullptr);
+ MessageWriter struct_entry_writer(nullptr);
+
+ writer.OpenVariant("a(ayq)", &variant_writer);
+ variant_writer.OpenArray("(ayq)", &variant_array_writer);
+ uint8_t ip_bytes[] = {0x54, 0x65, 0x73, 0x74, 0x30};
+ for (uint16_t i = 0; i < 5; ++i) {
+ variant_array_writer.OpenStruct(&struct_entry_writer);
+ ip_bytes[4] = 0x30 + i;
+ struct_entry_writer.AppendArrayOfBytes(ip_bytes, arraysize(ip_bytes));
+ struct_entry_writer.AppendUint16(i);
+ variant_array_writer.CloseContainer(&struct_entry_writer);
+ }
+ variant_writer.CloseContainer(&variant_array_writer);
+ writer.CloseContainer(&variant_writer);
+
+ MessageReader reader(message.get());
+ Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>> ip_list;
+ EXPECT_TRUE(ip_list.PopValueFromReader(&reader));
+
+ ASSERT_EQ(5U, ip_list.value().size());
+ size_t item_index = 0;
+ for (auto& item : ip_list.value()) {
+ ASSERT_EQ(5U, item.first.size());
+ ip_bytes[4] = 0x30 + item_index;
+ EXPECT_EQ(0, memcmp(ip_bytes, item.first.data(), 5U));
+ EXPECT_EQ(item_index, item.second);
+ ++item_index;
+ }
+}
+
+TEST(PropertyTestStatic, SerializeNetAddressArray) {
+ std::vector<std::pair<std::vector<uint8_t>, uint16_t>> test_list;
+
+ uint8_t ip_bytes[] = {0x54, 0x65, 0x73, 0x74, 0x30};
+ for (uint16_t i = 0; i < 5; ++i) {
+ ip_bytes[4] = 0x30 + i;
+ std::vector<uint8_t> bytes(ip_bytes, ip_bytes + arraysize(ip_bytes));
+ test_list.push_back(make_pair(bytes, 16));
+ }
+
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+
+ Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>> ip_list;
+ ip_list.ReplaceSetValueForTesting(test_list);
+ ip_list.AppendSetValueToWriter(&writer);
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(ip_list.PopValueFromReader(&reader));
+ EXPECT_EQ(test_list, ip_list.value());
+}
+
+TEST(PropertyTestStatic, ReadWriteStringToByteVectorMap) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ MessageWriter variant_writer(nullptr);
+ MessageWriter dict_writer(nullptr);
+
+ writer.OpenVariant("a{sv}", &variant_writer);
+ variant_writer.OpenArray("{sv}", &dict_writer);
+
+ const char* keys[] = {"One", "Two", "Three", "Four"};
+ const std::vector<uint8_t> values[] = {{1}, {1, 2}, {1, 2, 3}, {1, 2, 3, 4}};
+ for (unsigned i = 0; i < arraysize(keys); ++i) {
+ MessageWriter entry_writer(nullptr);
+ dict_writer.OpenDictEntry(&entry_writer);
+
+ entry_writer.AppendString(keys[i]);
+
+ MessageWriter value_varient_writer(nullptr);
+ entry_writer.OpenVariant("ay", &value_varient_writer);
+ value_varient_writer.AppendArrayOfBytes(values[i].data(), values[i].size());
+ entry_writer.CloseContainer(&value_varient_writer);
+
+ dict_writer.CloseContainer(&entry_writer);
+ }
+
+ variant_writer.CloseContainer(&dict_writer);
+ writer.CloseContainer(&variant_writer);
+
+ MessageReader reader(message.get());
+ Property<std::map<std::string, std::vector<uint8_t>>> test_property;
+ EXPECT_TRUE(test_property.PopValueFromReader(&reader));
+
+ ASSERT_EQ(arraysize(keys), test_property.value().size());
+ for (unsigned i = 0; i < arraysize(keys); ++i)
+ EXPECT_EQ(values[i], test_property.value().at(keys[i]));
+}
+
+TEST(PropertyTestStatic, SerializeStringToByteVectorMap) {
+ std::map<std::string, std::vector<uint8_t>> test_map;
+ test_map["Hi"] = {1, 2, 3};
+ test_map["Map"] = {0xab, 0xcd};
+ test_map["Random"] = {0x0};
+
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+
+ Property<std::map<std::string, std::vector<uint8_t>>> test_property;
+ test_property.ReplaceSetValueForTesting(test_map);
+ test_property.AppendSetValueToWriter(&writer);
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(test_property.PopValueFromReader(&reader));
+ EXPECT_EQ(test_map, test_property.value());
+}
+
+TEST(PropertyTestStatic, ReadWriteUInt16ToByteVectorMap) {
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+ MessageWriter variant_writer(nullptr);
+ MessageWriter dict_writer(nullptr);
+
+ writer.OpenVariant("a{qv}", &variant_writer);
+ variant_writer.OpenArray("{qv}", &dict_writer);
+
+ const uint16_t keys[] = {11, 12, 13, 14};
+ const std::vector<uint8_t> values[] = {{1}, {1, 2}, {1, 2, 3}, {1, 2, 3, 4}};
+ for (unsigned i = 0; i < arraysize(keys); ++i) {
+ MessageWriter entry_writer(nullptr);
+ dict_writer.OpenDictEntry(&entry_writer);
+
+ entry_writer.AppendUint16(keys[i]);
+
+ MessageWriter value_varient_writer(nullptr);
+ entry_writer.OpenVariant("ay", &value_varient_writer);
+ value_varient_writer.AppendArrayOfBytes(values[i].data(), values[i].size());
+ entry_writer.CloseContainer(&value_varient_writer);
+
+ dict_writer.CloseContainer(&entry_writer);
+ }
+
+ variant_writer.CloseContainer(&dict_writer);
+ writer.CloseContainer(&variant_writer);
+
+ MessageReader reader(message.get());
+ Property<std::map<uint16_t, std::vector<uint8_t>>> test_property;
+ EXPECT_TRUE(test_property.PopValueFromReader(&reader));
+
+ ASSERT_EQ(arraysize(keys), test_property.value().size());
+ for (unsigned i = 0; i < arraysize(keys); ++i)
+ EXPECT_EQ(values[i], test_property.value().at(keys[i]));
+}
+
+TEST(PropertyTestStatic, SerializeUInt16ToByteVectorMap) {
+ std::map<uint16_t, std::vector<uint8_t>> test_map;
+ test_map[11] = {1, 2, 3};
+ test_map[12] = {0xab, 0xcd};
+ test_map[13] = {0x0};
+
+ std::unique_ptr<Response> message(Response::CreateEmpty());
+ MessageWriter writer(message.get());
+
+ Property<std::map<uint16_t, std::vector<uint8_t>>> test_property;
+ test_property.ReplaceSetValueForTesting(test_map);
+ test_property.AppendSetValueToWriter(&writer);
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(test_property.PopValueFromReader(&reader));
+ EXPECT_EQ(test_map, test_property.value());
+}
+
+} // namespace dbus
diff --git a/dbus/signal_sender_verification_unittest.cc b/dbus/signal_sender_verification_unittest.cc
new file mode 100644
index 0000000000..3cc22f926b
--- /dev/null
+++ b/dbus/signal_sender_verification_unittest.cc
@@ -0,0 +1,394 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_proxy.h"
+#include "dbus/test_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+// The test for sender verification in ObjectProxy.
+class SignalSenderVerificationTest : public testing::Test {
+ public:
+ SignalSenderVerificationTest()
+ : on_name_owner_changed_called_(false),
+ on_ownership_called_(false) {
+ }
+
+ void SetUp() override {
+ // Make the main thread not to allow IO.
+ base::ThreadRestrictions::SetIOAllowed(false);
+
+ // Start the D-Bus thread.
+ dbus_thread_.reset(new base::Thread("D-Bus Thread"));
+ base::Thread::Options thread_options;
+ thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options));
+
+ // Create the test service, using the D-Bus thread.
+ TestService::Options options;
+ options.dbus_task_runner = dbus_thread_->task_runner();
+ test_service_.reset(new TestService(options));
+
+ // Create the client, using the D-Bus thread.
+ Bus::Options bus_options;
+ bus_options.bus_type = Bus::SESSION;
+ bus_options.connection_type = Bus::PRIVATE;
+ bus_options.dbus_task_runner = dbus_thread_->task_runner();
+ bus_ = new Bus(bus_options);
+ object_proxy_ = bus_->GetObjectProxy(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(bus_->HasDBusThread());
+
+ object_proxy_->SetNameOwnerChangedCallback(
+ base::Bind(&SignalSenderVerificationTest::OnNameOwnerChanged,
+ base::Unretained(this),
+ &on_name_owner_changed_called_));
+
+ // Connect to the "Test" signal of "org.chromium.TestInterface" from
+ // the remote object.
+ object_proxy_->ConnectToSignal(
+ "org.chromium.TestInterface",
+ "Test",
+ base::Bind(&SignalSenderVerificationTest::OnTestSignal,
+ base::Unretained(this)),
+ base::Bind(&SignalSenderVerificationTest::OnConnected,
+ base::Unretained(this)));
+ // Wait until the object proxy is connected to the signal.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+
+ // Start the test service.
+ ASSERT_TRUE(test_service_->StartService());
+ ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted());
+ ASSERT_TRUE(test_service_->HasDBusThread());
+ ASSERT_TRUE(test_service_->has_ownership());
+
+ // Same setup for the second TestService. This service should not have the
+ // ownership of the name at this point.
+ options.service_name = test_service_->service_name();
+ test_service2_.reset(new TestService(options));
+ ASSERT_TRUE(test_service2_->StartService());
+ ASSERT_TRUE(test_service2_->WaitUntilServiceIsStarted());
+ ASSERT_TRUE(test_service2_->HasDBusThread());
+ ASSERT_FALSE(test_service2_->has_ownership());
+
+ // The name should be owned and known at this point.
+ if (!on_name_owner_changed_called_) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ ASSERT_FALSE(latest_name_owner_.empty());
+ }
+
+ void TearDown() override {
+ bus_->ShutdownOnDBusThreadAndBlock();
+
+ // Shut down the service.
+ test_service_->ShutdownAndBlock();
+ test_service2_->ShutdownAndBlock();
+
+ // Reset to the default.
+ base::ThreadRestrictions::SetIOAllowed(true);
+
+ // Stopping a thread is considered an IO operation, so do this after
+ // allowing IO.
+ test_service_->Stop();
+ test_service2_->Stop();
+ }
+
+ void OnOwnership(bool expected, bool success) {
+ ASSERT_EQ(expected, success);
+ // PostTask to quit the MessageLoop as this is called from D-Bus thread.
+ message_loop_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&SignalSenderVerificationTest::OnOwnershipInternal,
+ base::Unretained(this)));
+ }
+
+ void OnOwnershipInternal() {
+ on_ownership_called_ = true;
+ run_loop_->Quit();
+ }
+
+ void OnNameOwnerChanged(bool* called_flag,
+ const std::string& old_owner,
+ const std::string& new_owner) {
+ latest_name_owner_ = new_owner;
+ *called_flag = true;
+ run_loop_->Quit();
+ }
+
+ // Called when the "Test" signal is received, in the main thread.
+ // Copy the string payload to |test_signal_string_|.
+ void OnTestSignal(Signal* signal) {
+ MessageReader reader(signal);
+ ASSERT_TRUE(reader.PopString(&test_signal_string_));
+ run_loop_->Quit();
+ }
+
+ // Called when connected to the signal.
+ void OnConnected(const std::string& interface_name,
+ const std::string& signal_name,
+ bool success) {
+ ASSERT_TRUE(success);
+ run_loop_->Quit();
+ }
+
+ protected:
+ // Wait for the hey signal to be received.
+ void WaitForTestSignal() {
+ // OnTestSignal() will quit the message loop.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+
+ // Stopping a thread is considered an IO operation, so we need to fiddle with
+ // thread restrictions before and after calling Stop() on a TestService.
+ void SafeServiceStop(TestService* test_service) {
+ base::ThreadRestrictions::SetIOAllowed(true);
+ test_service->Stop();
+ base::ThreadRestrictions::SetIOAllowed(false);
+ }
+
+ base::MessageLoop message_loop_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ std::unique_ptr<base::Thread> dbus_thread_;
+ scoped_refptr<Bus> bus_;
+ ObjectProxy* object_proxy_;
+ std::unique_ptr<TestService> test_service_;
+ std::unique_ptr<TestService> test_service2_;
+ // Text message from "Test" signal.
+ std::string test_signal_string_;
+
+ // The known latest name owner of TestService. Updated in OnNameOwnerChanged.
+ std::string latest_name_owner_;
+
+ // Boolean flags to record callback calls.
+ bool on_name_owner_changed_called_;
+ bool on_ownership_called_;
+};
+
+TEST_F(SignalSenderVerificationTest, TestSignalAccepted) {
+ const char kMessage[] = "hello, world";
+ // Send the test signal from the exported object.
+ test_service_->SendTestSignal(kMessage);
+ // Receive the signal with the object proxy. The signal is handled in
+ // SignalSenderVerificationTest::OnTestSignal() in the main thread.
+ WaitForTestSignal();
+ ASSERT_EQ(kMessage, test_signal_string_);
+}
+
+TEST_F(SignalSenderVerificationTest, TestSignalRejected) {
+ const char kNewMessage[] = "hello, new world";
+ test_service2_->SendTestSignal(kNewMessage);
+
+ // This test tests that our callback is NOT called by the ObjectProxy.
+ // Sleep to have message delivered to the client via the D-Bus service.
+ base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+
+ ASSERT_EQ("", test_signal_string_);
+}
+
+// Flaky. https://crbug.com/785555
+TEST_F(SignalSenderVerificationTest, DISABLED_TestOwnerChanged) {
+ const char kMessage[] = "hello, world";
+
+ // Send the test signal from the exported object.
+ test_service_->SendTestSignal(kMessage);
+ // Receive the signal with the object proxy. The signal is handled in
+ // SignalSenderVerificationTest::OnTestSignal() in the main thread.
+ WaitForTestSignal();
+ ASSERT_EQ(kMessage, test_signal_string_);
+
+ // Release and acquire the name ownership.
+ // latest_name_owner_ should be non empty as |test_service_| owns the name.
+ ASSERT_FALSE(latest_name_owner_.empty());
+ test_service_->ShutdownAndBlock();
+ // OnNameOwnerChanged will PostTask to quit the message loop.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ // latest_name_owner_ should be empty as the owner is gone.
+ ASSERT_TRUE(latest_name_owner_.empty());
+
+ // Reset the flag as NameOwnerChanged is already received in setup.
+ on_name_owner_changed_called_ = false;
+ on_ownership_called_ = false;
+ test_service2_->RequestOwnership(
+ base::Bind(&SignalSenderVerificationTest::OnOwnership,
+ base::Unretained(this), true));
+ // Both of OnNameOwnerChanged() and OnOwnership() should quit the MessageLoop,
+ // but there's no expected order of those 2 event.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ if (!on_name_owner_changed_called_ || !on_ownership_called_) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ ASSERT_TRUE(on_name_owner_changed_called_);
+ ASSERT_TRUE(on_ownership_called_);
+
+ // latest_name_owner_ becomes non empty as the new owner appears.
+ ASSERT_FALSE(latest_name_owner_.empty());
+
+ // Now the second service owns the name.
+ const char kNewMessage[] = "hello, new world";
+
+ test_service2_->SendTestSignal(kNewMessage);
+ WaitForTestSignal();
+ ASSERT_EQ(kNewMessage, test_signal_string_);
+}
+
+// Flaky. https://crbug.com/785555
+TEST_F(SignalSenderVerificationTest, DISABLED_TestOwnerStealing) {
+ // Release and acquire the name ownership.
+ // latest_name_owner_ should be non empty as |test_service_| owns the name.
+ ASSERT_FALSE(latest_name_owner_.empty());
+ test_service_->ShutdownAndBlock();
+ // OnNameOwnerChanged will PostTask to quit the message loop.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ // latest_name_owner_ should be empty as the owner is gone.
+ ASSERT_TRUE(latest_name_owner_.empty());
+ // Reset the flag as NameOwnerChanged is already received in setup.
+ on_name_owner_changed_called_ = false;
+
+ // Start a test service that allows theft, using the D-Bus thread.
+ TestService::Options options;
+ options.dbus_task_runner = dbus_thread_->task_runner();
+ options.request_ownership_options = Bus::REQUIRE_PRIMARY_ALLOW_REPLACEMENT;
+ options.service_name = test_service_->service_name();
+ TestService stealable_test_service(options);
+ ASSERT_TRUE(stealable_test_service.StartService());
+ ASSERT_TRUE(stealable_test_service.WaitUntilServiceIsStarted());
+ ASSERT_TRUE(stealable_test_service.HasDBusThread());
+ ASSERT_TRUE(stealable_test_service.has_ownership());
+
+ // OnNameOwnerChanged will PostTask to quit the message loop.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+
+ // Send a signal to check that the service is correctly owned.
+ const char kMessage[] = "hello, world";
+
+ // Send the test signal from the exported object.
+ stealable_test_service.SendTestSignal(kMessage);
+ // Receive the signal with the object proxy. The signal is handled in
+ // SignalSenderVerificationTest::OnTestSignal() in the main thread.
+ WaitForTestSignal();
+ ASSERT_EQ(kMessage, test_signal_string_);
+
+ // Reset the flag as NameOwnerChanged was called above.
+ on_name_owner_changed_called_ = false;
+ test_service2_->RequestOwnership(
+ base::Bind(&SignalSenderVerificationTest::OnOwnership,
+ base::Unretained(this), true));
+ // Both of OnNameOwnerChanged() and OnOwnership() should quit the MessageLoop,
+ // but there's no expected order of those 2 event.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ if (!on_name_owner_changed_called_ || !on_ownership_called_) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ ASSERT_TRUE(on_name_owner_changed_called_);
+ ASSERT_TRUE(on_ownership_called_);
+
+ // Now the second service owns the name.
+ const char kNewMessage[] = "hello, new world";
+
+ test_service2_->SendTestSignal(kNewMessage);
+ WaitForTestSignal();
+ ASSERT_EQ(kNewMessage, test_signal_string_);
+
+ SafeServiceStop(&stealable_test_service);
+}
+
+// Fails on Linux ChromiumOS Tests
+TEST_F(SignalSenderVerificationTest, DISABLED_TestMultipleObjects) {
+ const char kMessage[] = "hello, world";
+
+ ObjectProxy* object_proxy2 = bus_->GetObjectProxy(
+ test_service_->service_name(),
+ ObjectPath("/org/chromium/DifferentObject"));
+
+ bool second_name_owner_changed_called = false;
+ object_proxy2->SetNameOwnerChangedCallback(
+ base::Bind(&SignalSenderVerificationTest::OnNameOwnerChanged,
+ base::Unretained(this),
+ &second_name_owner_changed_called));
+
+ // Connect to a signal on the additional remote object to trigger the
+ // name owner matching.
+ object_proxy2->ConnectToSignal(
+ "org.chromium.DifferentTestInterface",
+ "Test",
+ base::Bind(&SignalSenderVerificationTest::OnTestSignal,
+ base::Unretained(this)),
+ base::Bind(&SignalSenderVerificationTest::OnConnected,
+ base::Unretained(this)));
+ // Wait until the object proxy is connected to the signal.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+
+ // Send the test signal from the exported object.
+ test_service_->SendTestSignal(kMessage);
+ // Receive the signal with the object proxy. The signal is handled in
+ // SignalSenderVerificationTest::OnTestSignal() in the main thread.
+ WaitForTestSignal();
+ ASSERT_EQ(kMessage, test_signal_string_);
+
+ // Release and acquire the name ownership.
+ // latest_name_owner_ should be non empty as |test_service_| owns the name.
+ ASSERT_FALSE(latest_name_owner_.empty());
+ test_service_->ShutdownAndBlock();
+ // OnNameOwnerChanged will PostTask to quit the message loop.
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ // latest_name_owner_ should be empty as the owner is gone.
+ ASSERT_TRUE(latest_name_owner_.empty());
+
+ // Reset the flag as NameOwnerChanged is already received in setup.
+ on_name_owner_changed_called_ = false;
+ second_name_owner_changed_called = false;
+ test_service2_->RequestOwnership(
+ base::Bind(&SignalSenderVerificationTest::OnOwnership,
+ base::Unretained(this), true));
+ // Both of OnNameOwnerChanged() and OnOwnership() should quit the MessageLoop,
+ // but there's no expected order of those 2 event.
+ while (!on_name_owner_changed_called_ || !second_name_owner_changed_called ||
+ !on_ownership_called_) {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+ ASSERT_TRUE(on_name_owner_changed_called_);
+ ASSERT_TRUE(second_name_owner_changed_called);
+ ASSERT_TRUE(on_ownership_called_);
+
+ // latest_name_owner_ becomes non empty as the new owner appears.
+ ASSERT_FALSE(latest_name_owner_.empty());
+
+ // Now the second service owns the name.
+ const char kNewMessage[] = "hello, new world";
+
+ test_service2_->SendTestSignal(kNewMessage);
+ WaitForTestSignal();
+ ASSERT_EQ(kNewMessage, test_signal_string_);
+}
+
+} // namespace dbus
diff --git a/dbus/string_util_unittest.cc b/dbus/string_util_unittest.cc
new file mode 100644
index 0000000000..3d0ff51efd
--- /dev/null
+++ b/dbus/string_util_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+TEST(StringUtilTest, IsValidObjectPath) {
+ EXPECT_TRUE(IsValidObjectPath("/"));
+ EXPECT_TRUE(IsValidObjectPath("/foo/bar"));
+ EXPECT_TRUE(IsValidObjectPath("/hoge_fuga/piyo123"));
+ // Empty string.
+ EXPECT_FALSE(IsValidObjectPath(std::string()));
+ // Empty element.
+ EXPECT_FALSE(IsValidObjectPath("//"));
+ EXPECT_FALSE(IsValidObjectPath("/foo//bar"));
+ EXPECT_FALSE(IsValidObjectPath("/foo///bar"));
+ // Trailing '/'.
+ EXPECT_FALSE(IsValidObjectPath("/foo/"));
+ EXPECT_FALSE(IsValidObjectPath("/foo/bar/"));
+ // Not beginning with '/'.
+ EXPECT_FALSE(IsValidObjectPath("foo/bar"));
+ // Invalid characters.
+ EXPECT_FALSE(IsValidObjectPath("/foo.bar"));
+ EXPECT_FALSE(IsValidObjectPath("/foo/*"));
+ EXPECT_FALSE(IsValidObjectPath("/foo/bar(1)"));
+}
+
+} // namespace dbus
diff --git a/dbus/util_unittest.cc b/dbus/util_unittest.cc
new file mode 100644
index 0000000000..4bf6efa2ae
--- /dev/null
+++ b/dbus/util_unittest.cc
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+TEST(UtilTest, GetAbsoluteMemberName) {
+ EXPECT_EQ("InterfaceName.MemberName",
+ GetAbsoluteMemberName("InterfaceName", "MemberName"));
+ EXPECT_EQ(".", GetAbsoluteMemberName("", ""));
+}
+
+} // namespace dbus
diff --git a/dbus/values_util_unittest.cc b/dbus/values_util_unittest.cc
new file mode 100644
index 0000000000..5a0b81e9f9
--- /dev/null
+++ b/dbus/values_util_unittest.cc
@@ -0,0 +1,687 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/values_util.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cmath>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/json/json_writer.h"
+#include "base/macros.h"
+#include "base/values.h"
+#include "dbus/message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dbus {
+
+TEST(ValuesUtilTest, PopBasicTypes) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ // Append basic type values.
+ MessageWriter writer(response.get());
+ const uint8_t kByteValue = 42;
+ writer.AppendByte(kByteValue);
+ const bool kBoolValue = true;
+ writer.AppendBool(kBoolValue);
+ const int16_t kInt16Value = -43;
+ writer.AppendInt16(kInt16Value);
+ const uint16_t kUint16Value = 44;
+ writer.AppendUint16(kUint16Value);
+ const int32_t kInt32Value = -45;
+ writer.AppendInt32(kInt32Value);
+ const uint32_t kUint32Value = 46;
+ writer.AppendUint32(kUint32Value);
+ const int64_t kInt64Value = -47;
+ writer.AppendInt64(kInt64Value);
+ const uint64_t kUint64Value = 48;
+ writer.AppendUint64(kUint64Value);
+ const double kDoubleValue = 4.9;
+ writer.AppendDouble(kDoubleValue);
+ const std::string kStringValue = "fifty";
+ writer.AppendString(kStringValue);
+ const std::string kEmptyStringValue;
+ writer.AppendString(kEmptyStringValue);
+ const ObjectPath kObjectPathValue("/ObjectPath");
+ writer.AppendObjectPath(kObjectPathValue);
+
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ std::unique_ptr<base::Value> expected_value;
+ // Pop a byte.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kByteValue));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop a bool.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kBoolValue));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop an int16_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kInt16Value));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop a uint16_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kUint16Value));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop an int32_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kInt32Value));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop a uint32_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(static_cast<double>(kUint32Value)));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop an int64_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(static_cast<double>(kInt64Value)));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop a uint64_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(static_cast<double>(kUint64Value)));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop a double.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kDoubleValue));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop a string.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kStringValue));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop an empty string.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kEmptyStringValue));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop an object path.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kObjectPathValue.value()));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+}
+
+TEST(ValuesUtilTest, PopVariant) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ // Append variant values.
+ MessageWriter writer(response.get());
+ const bool kBoolValue = true;
+ writer.AppendVariantOfBool(kBoolValue);
+ const int32_t kInt32Value = -45;
+ writer.AppendVariantOfInt32(kInt32Value);
+ const double kDoubleValue = 4.9;
+ writer.AppendVariantOfDouble(kDoubleValue);
+ const std::string kStringValue = "fifty";
+ writer.AppendVariantOfString(kStringValue);
+
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ std::unique_ptr<base::Value> expected_value;
+ // Pop a bool.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kBoolValue));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop an int32_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kInt32Value));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop a double.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kDoubleValue));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ // Pop a string.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(kStringValue));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+}
+
+// Pop extremely large integers which cannot be precisely represented in
+// double.
+TEST(ValuesUtilTest, PopExtremelyLargeIntegers) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ // Append large integers.
+ MessageWriter writer(response.get());
+ const int64_t kInt64Value = -123456789012345689LL;
+ writer.AppendInt64(kInt64Value);
+ const uint64_t kUint64Value = 9876543210987654321ULL;
+ writer.AppendUint64(kUint64Value);
+
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ std::unique_ptr<base::Value> expected_value;
+ double double_value = 0;
+ // Pop an int64_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(static_cast<double>(kInt64Value)));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ ASSERT_TRUE(value->GetAsDouble(&double_value));
+ EXPECT_NE(kInt64Value, static_cast<int64_t>(double_value));
+ // Pop a uint64_t.
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ expected_value.reset(new base::Value(static_cast<double>(kUint64Value)));
+ EXPECT_TRUE(value->Equals(expected_value.get()));
+ ASSERT_TRUE(value->GetAsDouble(&double_value));
+ EXPECT_NE(kUint64Value, static_cast<uint64_t>(double_value));
+}
+
+TEST(ValuesUtilTest, PopIntArray) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ // Append an int32_t array.
+ MessageWriter writer(response.get());
+ MessageWriter sub_writer(nullptr);
+ std::vector<int32_t> data;
+ data.push_back(0);
+ data.push_back(1);
+ data.push_back(2);
+ writer.OpenArray("i", &sub_writer);
+ for (size_t i = 0; i != data.size(); ++i)
+ sub_writer.AppendInt32(data[i]);
+ writer.CloseContainer(&sub_writer);
+
+ // Create the expected value.
+ std::unique_ptr<base::ListValue> list_value(new base::ListValue);
+ for (size_t i = 0; i != data.size(); ++i)
+ list_value->AppendInteger(data[i]);
+
+ // Pop an int32_t array.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value(PopDataAsValue(&reader));
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(list_value.get()));
+}
+
+TEST(ValuesUtilTest, PopStringArray) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ // Append a string array.
+ MessageWriter writer(response.get());
+ MessageWriter sub_writer(nullptr);
+ std::vector<std::string> data;
+ data.push_back("Dreamlifter");
+ data.push_back("Beluga");
+ data.push_back("Mriya");
+ writer.AppendArrayOfStrings(data);
+
+ // Create the expected value.
+ std::unique_ptr<base::ListValue> list_value(new base::ListValue);
+ for (size_t i = 0; i != data.size(); ++i)
+ list_value->AppendString(data[i]);
+
+ // Pop a string array.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value(PopDataAsValue(&reader));
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(list_value.get()));
+}
+
+TEST(ValuesUtilTest, PopStruct) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ // Append a struct.
+ MessageWriter writer(response.get());
+ MessageWriter sub_writer(nullptr);
+ writer.OpenStruct(&sub_writer);
+ const bool kBoolValue = true;
+ sub_writer.AppendBool(kBoolValue);
+ const int32_t kInt32Value = -123;
+ sub_writer.AppendInt32(kInt32Value);
+ const double kDoubleValue = 1.23;
+ sub_writer.AppendDouble(kDoubleValue);
+ const std::string kStringValue = "one two three";
+ sub_writer.AppendString(kStringValue);
+ writer.CloseContainer(&sub_writer);
+
+ // Create the expected value.
+ base::ListValue list_value;
+ list_value.AppendBoolean(kBoolValue);
+ list_value.AppendInteger(kInt32Value);
+ list_value.AppendDouble(kDoubleValue);
+ list_value.AppendString(kStringValue);
+
+ // Pop a struct.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value(PopDataAsValue(&reader));
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&list_value));
+}
+
+TEST(ValuesUtilTest, PopStringToVariantDictionary) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ // Append a dictionary.
+ MessageWriter writer(response.get());
+ MessageWriter sub_writer(nullptr);
+ MessageWriter entry_writer(nullptr);
+ writer.OpenArray("{sv}", &sub_writer);
+ sub_writer.OpenDictEntry(&entry_writer);
+ const std::string kKey1 = "one";
+ entry_writer.AppendString(kKey1);
+ const bool kBoolValue = true;
+ entry_writer.AppendVariantOfBool(kBoolValue);
+ sub_writer.CloseContainer(&entry_writer);
+ sub_writer.OpenDictEntry(&entry_writer);
+ const std::string kKey2 = "two";
+ entry_writer.AppendString(kKey2);
+ const int32_t kInt32Value = -45;
+ entry_writer.AppendVariantOfInt32(kInt32Value);
+ sub_writer.CloseContainer(&entry_writer);
+ sub_writer.OpenDictEntry(&entry_writer);
+ const std::string kKey3 = "three";
+ entry_writer.AppendString(kKey3);
+ const double kDoubleValue = 4.9;
+ entry_writer.AppendVariantOfDouble(kDoubleValue);
+ sub_writer.CloseContainer(&entry_writer);
+ sub_writer.OpenDictEntry(&entry_writer);
+ const std::string kKey4 = "four";
+ entry_writer.AppendString(kKey4);
+ const std::string kStringValue = "fifty";
+ entry_writer.AppendVariantOfString(kStringValue);
+ sub_writer.CloseContainer(&entry_writer);
+ writer.CloseContainer(&sub_writer);
+
+ // Create the expected value.
+ base::DictionaryValue dictionary_value;
+ dictionary_value.SetBoolean(kKey1, kBoolValue);
+ dictionary_value.SetInteger(kKey2, kInt32Value);
+ dictionary_value.SetDouble(kKey3, kDoubleValue);
+ dictionary_value.SetString(kKey4, kStringValue);
+
+ // Pop a dictinoary.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value(PopDataAsValue(&reader));
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&dictionary_value));
+}
+
+TEST(ValuesUtilTest, PopDictionaryWithDottedStringKey) {
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ // Append a dictionary.
+ MessageWriter writer(response.get());
+ MessageWriter sub_writer(nullptr);
+ MessageWriter entry_writer(nullptr);
+ writer.OpenArray("{sv}", &sub_writer);
+ sub_writer.OpenDictEntry(&entry_writer);
+ const std::string kKey1 = "www.example.com"; // String including dots.
+ entry_writer.AppendString(kKey1);
+ const bool kBoolValue = true;
+ entry_writer.AppendVariantOfBool(kBoolValue);
+ sub_writer.CloseContainer(&entry_writer);
+ sub_writer.OpenDictEntry(&entry_writer);
+ const std::string kKey2 = ".example"; // String starting with a dot.
+ entry_writer.AppendString(kKey2);
+ const int32_t kInt32Value = -45;
+ entry_writer.AppendVariantOfInt32(kInt32Value);
+ sub_writer.CloseContainer(&entry_writer);
+ sub_writer.OpenDictEntry(&entry_writer);
+ const std::string kKey3 = "example."; // String ending with a dot.
+ entry_writer.AppendString(kKey3);
+ const double kDoubleValue = 4.9;
+ entry_writer.AppendVariantOfDouble(kDoubleValue);
+ sub_writer.CloseContainer(&entry_writer);
+ writer.CloseContainer(&sub_writer);
+
+ // Create the expected value.
+ base::DictionaryValue dictionary_value;
+ dictionary_value.SetKey(kKey1, base::Value(kBoolValue));
+ dictionary_value.SetKey(kKey2, base::Value(kInt32Value));
+ dictionary_value.SetKey(kKey3, base::Value(kDoubleValue));
+
+ // Pop a dictinoary.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value(PopDataAsValue(&reader));
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&dictionary_value));
+}
+
+TEST(ValuesUtilTest, PopDoubleToIntDictionary) {
+ // Create test data.
+ const int32_t kValues[] = {0, 1, 1, 2, 3, 5, 8, 13, 21};
+ const std::vector<int32_t> values(kValues, kValues + arraysize(kValues));
+ std::vector<double> keys(values.size());
+ for (size_t i = 0; i != values.size(); ++i)
+ keys[i] = std::sqrt(values[i]);
+
+ // Append a dictionary.
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ MessageWriter sub_writer(nullptr);
+ writer.OpenArray("{di}", &sub_writer);
+ for (size_t i = 0; i != values.size(); ++i) {
+ MessageWriter entry_writer(nullptr);
+ sub_writer.OpenDictEntry(&entry_writer);
+ entry_writer.AppendDouble(keys[i]);
+ entry_writer.AppendInt32(values[i]);
+ sub_writer.CloseContainer(&entry_writer);
+ }
+ writer.CloseContainer(&sub_writer);
+
+ // Create the expected value.
+ base::DictionaryValue dictionary_value;
+ for (size_t i = 0; i != values.size(); ++i) {
+ std::string key_string;
+ base::JSONWriter::Write(base::Value(keys[i]), &key_string);
+ dictionary_value.SetKey(key_string, base::Value(values[i]));
+ }
+
+ // Pop a dictionary.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value(PopDataAsValue(&reader));
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&dictionary_value));
+}
+
+TEST(ValuesUtilTest, AppendBasicTypes) {
+ const base::Value kBoolValue(false);
+ const base::Value kIntegerValue(42);
+ const base::Value kDoubleValue(4.2);
+ const base::Value kStringValue("string");
+
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ AppendBasicTypeValueData(&writer, kBoolValue);
+ AppendBasicTypeValueData(&writer, kIntegerValue);
+ AppendBasicTypeValueData(&writer, kDoubleValue);
+ AppendBasicTypeValueData(&writer, kStringValue);
+
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kBoolValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kIntegerValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kDoubleValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kStringValue));
+}
+
+TEST(ValuesUtilTest, AppendBasicTypesAsVariant) {
+ const base::Value kBoolValue(false);
+ const base::Value kIntegerValue(42);
+ const base::Value kDoubleValue(4.2);
+ const base::Value kStringValue("string");
+
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ AppendBasicTypeValueDataAsVariant(&writer, kBoolValue);
+ AppendBasicTypeValueDataAsVariant(&writer, kIntegerValue);
+ AppendBasicTypeValueDataAsVariant(&writer, kDoubleValue);
+ AppendBasicTypeValueDataAsVariant(&writer, kStringValue);
+
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kBoolValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kIntegerValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kDoubleValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kStringValue));
+}
+
+TEST(ValuesUtilTest, AppendValueDataBasicTypes) {
+ const base::Value kBoolValue(false);
+ const base::Value kIntegerValue(42);
+ const base::Value kDoubleValue(4.2);
+ const base::Value kStringValue("string");
+
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ AppendValueData(&writer, kBoolValue);
+ AppendValueData(&writer, kIntegerValue);
+ AppendValueData(&writer, kDoubleValue);
+ AppendValueData(&writer, kStringValue);
+
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kBoolValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kIntegerValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kDoubleValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kStringValue));
+}
+
+TEST(ValuesUtilTest, AppendValueDataAsVariantBasicTypes) {
+ const base::Value kBoolValue(false);
+ const base::Value kIntegerValue(42);
+ const base::Value kDoubleValue(4.2);
+ const base::Value kStringValue("string");
+
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ AppendValueDataAsVariant(&writer, kBoolValue);
+ AppendValueDataAsVariant(&writer, kIntegerValue);
+ AppendValueDataAsVariant(&writer, kDoubleValue);
+ AppendValueDataAsVariant(&writer, kStringValue);
+
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kBoolValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kIntegerValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kDoubleValue));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&kStringValue));
+}
+
+TEST(ValuesUtilTest, AppendDictionary) {
+ // Set up the input dictionary.
+ const std::string kKey1 = "one";
+ const std::string kKey2 = "two";
+ const std::string kKey3 = "three";
+ const std::string kKey4 = "four";
+ const std::string kKey5 = "five";
+ const std::string kKey6 = "six";
+
+ const bool kBoolValue = true;
+ const int32_t kInt32Value = -45;
+ const double kDoubleValue = 4.9;
+ const std::string kStringValue = "fifty";
+
+ auto list_value = std::make_unique<base::ListValue>();
+ list_value->AppendBoolean(kBoolValue);
+ list_value->AppendInteger(kInt32Value);
+
+ auto dictionary_value = std::make_unique<base::DictionaryValue>();
+ dictionary_value->SetBoolean(kKey1, kBoolValue);
+ dictionary_value->SetInteger(kKey2, kDoubleValue);
+
+ base::DictionaryValue test_dictionary;
+ test_dictionary.SetBoolean(kKey1, kBoolValue);
+ test_dictionary.SetInteger(kKey2, kInt32Value);
+ test_dictionary.SetDouble(kKey3, kDoubleValue);
+ test_dictionary.SetString(kKey4, kStringValue);
+ test_dictionary.Set(kKey5, std::move(list_value));
+ test_dictionary.Set(kKey6, std::move(dictionary_value));
+
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ AppendValueData(&writer, test_dictionary);
+ base::Value int_value(kInt32Value);
+ AppendValueData(&writer, int_value);
+
+ // Read the data.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&test_dictionary));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&int_value));
+}
+
+TEST(ValuesUtilTest, AppendDictionaryAsVariant) {
+ // Set up the input dictionary.
+ const std::string kKey1 = "one";
+ const std::string kKey2 = "two";
+ const std::string kKey3 = "three";
+ const std::string kKey4 = "four";
+ const std::string kKey5 = "five";
+ const std::string kKey6 = "six";
+
+ const bool kBoolValue = true;
+ const int32_t kInt32Value = -45;
+ const double kDoubleValue = 4.9;
+ const std::string kStringValue = "fifty";
+
+ auto list_value = std::make_unique<base::ListValue>();
+ list_value->AppendBoolean(kBoolValue);
+ list_value->AppendInteger(kInt32Value);
+
+ auto dictionary_value = std::make_unique<base::DictionaryValue>();
+ dictionary_value->SetBoolean(kKey1, kBoolValue);
+ dictionary_value->SetInteger(kKey2, kDoubleValue);
+
+ base::DictionaryValue test_dictionary;
+ test_dictionary.SetBoolean(kKey1, kBoolValue);
+ test_dictionary.SetInteger(kKey2, kInt32Value);
+ test_dictionary.SetDouble(kKey3, kDoubleValue);
+ test_dictionary.SetString(kKey4, kStringValue);
+ test_dictionary.Set(kKey5, std::move(list_value));
+ test_dictionary.Set(kKey6, std::move(dictionary_value));
+
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ AppendValueDataAsVariant(&writer, test_dictionary);
+ base::Value int_value(kInt32Value);
+ AppendValueData(&writer, int_value);
+
+ // Read the data.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&test_dictionary));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&int_value));
+}
+
+TEST(ValuesUtilTest, AppendList) {
+ // Set up the input list.
+ const std::string kKey1 = "one";
+ const std::string kKey2 = "two";
+
+ const bool kBoolValue = true;
+ const int32_t kInt32Value = -45;
+ const double kDoubleValue = 4.9;
+ const std::string kStringValue = "fifty";
+
+ std::unique_ptr<base::ListValue> list_value(new base::ListValue());
+ list_value->AppendBoolean(kBoolValue);
+ list_value->AppendInteger(kInt32Value);
+
+ std::unique_ptr<base::DictionaryValue> dictionary_value(
+ new base::DictionaryValue());
+ dictionary_value->SetBoolean(kKey1, kBoolValue);
+ dictionary_value->SetInteger(kKey2, kDoubleValue);
+
+ base::ListValue test_list;
+ test_list.AppendBoolean(kBoolValue);
+ test_list.AppendInteger(kInt32Value);
+ test_list.AppendDouble(kDoubleValue);
+ test_list.AppendString(kStringValue);
+ test_list.Append(std::move(list_value));
+ test_list.Append(std::move(dictionary_value));
+
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ AppendValueData(&writer, test_list);
+ base::Value int_value(kInt32Value);
+ AppendValueData(&writer, int_value);
+
+ // Read the data.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&test_list));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&int_value));
+}
+
+TEST(ValuesUtilTest, AppendListAsVariant) {
+ // Set up the input list.
+ const std::string kKey1 = "one";
+ const std::string kKey2 = "two";
+
+ const bool kBoolValue = true;
+ const int32_t kInt32Value = -45;
+ const double kDoubleValue = 4.9;
+ const std::string kStringValue = "fifty";
+
+ std::unique_ptr<base::ListValue> list_value(new base::ListValue());
+ list_value->AppendBoolean(kBoolValue);
+ list_value->AppendInteger(kInt32Value);
+
+ std::unique_ptr<base::DictionaryValue> dictionary_value(
+ new base::DictionaryValue());
+ dictionary_value->SetBoolean(kKey1, kBoolValue);
+ dictionary_value->SetInteger(kKey2, kDoubleValue);
+
+ base::ListValue test_list;
+ test_list.AppendBoolean(kBoolValue);
+ test_list.AppendInteger(kInt32Value);
+ test_list.AppendDouble(kDoubleValue);
+ test_list.AppendString(kStringValue);
+ test_list.Append(std::move(list_value));
+ test_list.Append(std::move(dictionary_value));
+
+ std::unique_ptr<Response> response(Response::CreateEmpty());
+ MessageWriter writer(response.get());
+ AppendValueDataAsVariant(&writer, test_list);
+ base::Value int_value(kInt32Value);
+ AppendValueData(&writer, int_value);
+
+ // Read the data.
+ MessageReader reader(response.get());
+ std::unique_ptr<base::Value> value;
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&test_list));
+ value = PopDataAsValue(&reader);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->Equals(&int_value));
+}
+
+} // namespace dbus
diff --git a/ipc/constants.mojom b/ipc/constants.mojom
new file mode 100644
index 0000000000..502187f2e2
--- /dev/null
+++ b/ipc/constants.mojom
@@ -0,0 +1,8 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module IPC.mojom;
+
+// NOTE: This MUST match the value of MSG_ROUTING_NONE in src/ipc/ipc_message.h.
+const int32 kRoutingIdNone = -2;
diff --git a/ipc/ipc_channel_nacl.cc b/ipc/ipc_channel_nacl.cc
deleted file mode 100644
index 190da45453..0000000000
--- a/ipc/ipc_channel_nacl.cc
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ipc/ipc_channel_nacl.h"
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_pump_for_io.h"
-#include "base/single_thread_task_runner.h"
-#include "base/synchronization/lock.h"
-#include "base/task_runner_util.h"
-#include "base/threading/simple_thread.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "ipc/ipc_listener.h"
-#include "ipc/ipc_logging.h"
-#include "ipc/ipc_message_attachment_set.h"
-#include "ipc/ipc_platform_file_attachment_posix.h"
-#include "native_client/src/public/imc_syscalls.h"
-#include "native_client/src/public/imc_types.h"
-
-namespace IPC {
-
-struct MessageContents {
- std::vector<char> data;
- std::vector<int> fds;
-};
-
-namespace {
-
-bool ReadDataOnReaderThread(int pipe, MessageContents* contents) {
- DCHECK(pipe >= 0);
- if (pipe < 0)
- return false;
-
- contents->data.resize(Channel::kReadBufferSize);
- contents->fds.resize(NACL_ABI_IMC_DESC_MAX);
-
- NaClAbiNaClImcMsgIoVec iov = { &contents->data[0], contents->data.size() };
- NaClAbiNaClImcMsgHdr msg = {
- &iov, 1, &contents->fds[0], contents->fds.size()
- };
-
- int bytes_read = imc_recvmsg(pipe, &msg, 0);
-
- if (bytes_read <= 0) {
- // NaClIPCAdapter::BlockingReceive returns -1 when the pipe closes (either
- // due to error or for regular shutdown).
- contents->data.clear();
- contents->fds.clear();
- return false;
- }
- DCHECK(bytes_read);
- // Resize the buffers down to the number of bytes and fds we actually read.
- contents->data.resize(bytes_read);
- contents->fds.resize(msg.desc_length);
- return true;
-}
-
-} // namespace
-
-// static
-constexpr size_t Channel::kMaximumMessageSize;
-
-class ChannelNacl::ReaderThreadRunner
- : public base::DelegateSimpleThread::Delegate {
- public:
- // |pipe|: A file descriptor from which we will read using imc_recvmsg.
- // |data_read_callback|: A callback we invoke (on the main thread) when we
- // have read data.
- // |failure_callback|: A callback we invoke when we have a failure reading
- // from |pipe|.
- // |main_message_loop|: A proxy for the main thread, where we will invoke the
- // above callbacks.
- ReaderThreadRunner(
- int pipe,
- base::Callback<void(std::unique_ptr<MessageContents>)> data_read_callback,
- base::Callback<void()> failure_callback,
- scoped_refptr<base::SingleThreadTaskRunner> main_task_runner);
-
- // DelegateSimpleThread implementation. Reads data from the pipe in a loop
- // until either we are told to quit or a read fails.
- void Run() override;
-
- private:
- int pipe_;
- base::Callback<void(std::unique_ptr<MessageContents>)> data_read_callback_;
- base::Callback<void ()> failure_callback_;
- scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
-
- DISALLOW_COPY_AND_ASSIGN(ReaderThreadRunner);
-};
-
-ChannelNacl::ReaderThreadRunner::ReaderThreadRunner(
- int pipe,
- base::Callback<void(std::unique_ptr<MessageContents>)> data_read_callback,
- base::Callback<void()> failure_callback,
- scoped_refptr<base::SingleThreadTaskRunner> main_task_runner)
- : pipe_(pipe),
- data_read_callback_(data_read_callback),
- failure_callback_(failure_callback),
- main_task_runner_(main_task_runner) {}
-
-void ChannelNacl::ReaderThreadRunner::Run() {
- while (true) {
- std::unique_ptr<MessageContents> msg_contents(new MessageContents);
- bool success = ReadDataOnReaderThread(pipe_, msg_contents.get());
- if (success) {
- main_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(data_read_callback_, base::Passed(&msg_contents)));
- } else {
- main_task_runner_->PostTask(FROM_HERE, failure_callback_);
- // Because the read failed, we know we're going to quit. Don't bother
- // trying to read again.
- return;
- }
- }
-}
-
-ChannelNacl::ChannelNacl(const IPC::ChannelHandle& channel_handle,
- Mode mode,
- Listener* listener)
- : ChannelReader(listener),
- mode_(mode),
- waiting_connect_(true),
- pipe_(-1),
- weak_ptr_factory_(this) {
- if (!CreatePipe(channel_handle)) {
- // The pipe may have been closed already.
- const char *modestr = (mode_ & MODE_SERVER_FLAG) ? "server" : "client";
- LOG(WARNING) << "Unable to create pipe in " << modestr << " mode";
- }
-}
-
-ChannelNacl::~ChannelNacl() {
- CleanUp();
- Close();
-}
-
-bool ChannelNacl::Connect() {
- WillConnect();
-
- if (pipe_ == -1) {
- DLOG(WARNING) << "Channel creation failed";
- return false;
- }
-
- // Note that Connect is called on the "Channel" thread (i.e., the same thread
- // where Channel::Send will be called, and the same thread that should receive
- // messages). The constructor might be invoked on another thread (see
- // ChannelProxy for an example of that). Therefore, we must wait until Connect
- // is called to decide which SingleThreadTaskRunner to pass to
- // ReaderThreadRunner.
- reader_thread_runner_.reset(new ReaderThreadRunner(
- pipe_,
- base::Bind(&ChannelNacl::DidRecvMsg, weak_ptr_factory_.GetWeakPtr()),
- base::Bind(&ChannelNacl::ReadDidFail, weak_ptr_factory_.GetWeakPtr()),
- base::ThreadTaskRunnerHandle::Get()));
- reader_thread_.reset(
- new base::DelegateSimpleThread(reader_thread_runner_.get(),
- "ipc_channel_nacl reader thread"));
- reader_thread_->Start();
- waiting_connect_ = false;
- // If there were any messages queued before connection, send them.
- ProcessOutgoingMessages();
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(&ChannelNacl::CallOnChannelConnected,
- weak_ptr_factory_.GetWeakPtr()));
-
- return true;
-}
-
-void ChannelNacl::Close() {
- // For now, we assume that at shutdown, the reader thread will be woken with
- // a failure (see NaClIPCAdapter::BlockingRead and CloseChannel). Or... we
- // might simply be killed with no chance to clean up anyway :-).
- // If untrusted code tries to close the channel prior to shutdown, it's likely
- // to hang.
- // TODO(dmichael): Can we do anything smarter here to make sure the reader
- // thread wakes up and quits?
- reader_thread_->Join();
- close(pipe_);
- pipe_ = -1;
- reader_thread_runner_.reset();
- reader_thread_.reset();
- read_queue_.clear();
- output_queue_.clear();
-}
-
-bool ChannelNacl::Send(Message* message) {
- DCHECK(!message->HasAttachments());
- DVLOG(2) << "sending message @" << message << " on channel @" << this
- << " with type " << message->type();
- std::unique_ptr<Message> message_ptr(message);
-
-#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
- Logging::GetInstance()->OnSendMessage(message_ptr.get());
-#endif // BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
-
- TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
- "ChannelNacl::Send",
- message->header()->flags,
- TRACE_EVENT_FLAG_FLOW_OUT);
- output_queue_.push_back(std::move(message_ptr));
- if (!waiting_connect_)
- return ProcessOutgoingMessages();
-
- return true;
-}
-
-void ChannelNacl::DidRecvMsg(std::unique_ptr<MessageContents> contents) {
- // Close sets the pipe to -1. It's possible we'll get a buffer sent to us from
- // the reader thread after Close is called. If so, we ignore it.
- if (pipe_ == -1)
- return;
-
- auto data = std::make_unique<std::vector<char>>();
- data->swap(contents->data);
- read_queue_.push_back(std::move(data));
-
- input_attachments_.reserve(contents->fds.size());
- for (int fd : contents->fds) {
- input_attachments_.push_back(
- new internal::PlatformFileAttachment(base::ScopedFD(fd)));
- }
- contents->fds.clear();
-
- // In POSIX, we would be told when there are bytes to read by implementing
- // OnFileCanReadWithoutBlocking in MessagePumpForIO::FdWatcher. In NaCl, we
- // instead know at this point because the reader thread posted some data to
- // us.
- ProcessIncomingMessages();
-}
-
-void ChannelNacl::ReadDidFail() {
- Close();
-}
-
-bool ChannelNacl::CreatePipe(
- const IPC::ChannelHandle& channel_handle) {
- DCHECK(pipe_ == -1);
-
- // There's one possible case in NaCl:
- // 1) It's a channel wrapping a pipe that is given to us.
- // We don't support these:
- // 2) It's for a named channel.
- // 3) It's for a client that we implement ourself.
- // 4) It's the initial IPC channel.
-
- if (channel_handle.socket.fd == -1) {
- NOTIMPLEMENTED();
- return false;
- }
- pipe_ = channel_handle.socket.fd;
- return true;
-}
-
-bool ChannelNacl::ProcessOutgoingMessages() {
- DCHECK(!waiting_connect_); // Why are we trying to send messages if there's
- // no connection?
- if (output_queue_.empty())
- return true;
-
- if (pipe_ == -1)
- return false;
-
- // Write out all the messages. The trusted implementation is guaranteed to not
- // block. See NaClIPCAdapter::Send for the implementation of imc_sendmsg.
- while (!output_queue_.empty()) {
- std::unique_ptr<Message> msg = std::move(output_queue_.front());
- output_queue_.pop_front();
-
- const size_t num_fds = msg->attachment_set()->size();
- DCHECK(num_fds <= MessageAttachmentSet::kMaxDescriptorsPerMessage);
- std::vector<int> fds;
- fds.reserve(num_fds);
- for (size_t i = 0; i < num_fds; i++) {
- scoped_refptr<MessageAttachment> attachment =
- msg->attachment_set()->GetAttachmentAt(i);
- DCHECK_EQ(MessageAttachment::Type::PLATFORM_FILE, attachment->GetType());
- fds.push_back(static_cast<internal::PlatformFileAttachment&>(*attachment)
- .TakePlatformFile());
- }
-
- NaClAbiNaClImcMsgIoVec iov = {
- const_cast<void*>(msg->data()), msg->size()
- };
- NaClAbiNaClImcMsgHdr msgh = {&iov, 1, fds.data(), num_fds};
- ssize_t bytes_written = imc_sendmsg(pipe_, &msgh, 0);
-
- DCHECK(bytes_written); // The trusted side shouldn't return 0.
- if (bytes_written < 0) {
- // The trusted side should only ever give us an error of EPIPE. We
- // should never be interrupted, nor should we get EAGAIN.
- DCHECK(errno == EPIPE);
- Close();
- PLOG(ERROR) << "pipe_ error on "
- << pipe_
- << " Currently writing message of size: "
- << msg->size();
- return false;
- } else {
- msg->attachment_set()->CommitAllDescriptors();
- }
-
- // Message sent OK!
- DVLOG(2) << "sent message @" << msg.get() << " with type " << msg->type()
- << " on fd " << pipe_;
- }
- return true;
-}
-
-void ChannelNacl::CallOnChannelConnected() {
- listener()->OnChannelConnected(-1);
-}
-
-ChannelNacl::ReadState ChannelNacl::ReadData(
- char* buffer,
- int buffer_len,
- int* bytes_read) {
- *bytes_read = 0;
- if (pipe_ == -1)
- return READ_FAILED;
- if (read_queue_.empty())
- return READ_PENDING;
- while (!read_queue_.empty() && *bytes_read < buffer_len) {
- std::vector<char>* vec = read_queue_.front().get();
- size_t bytes_to_read = buffer_len - *bytes_read;
- if (vec->size() <= bytes_to_read) {
- // We can read and discard the entire vector.
- std::copy(vec->begin(), vec->end(), buffer + *bytes_read);
- *bytes_read += vec->size();
- read_queue_.pop_front();
- } else {
- // Read all the bytes we can and discard them from the front of the
- // vector. (This can be slowish, since erase has to move the back of the
- // vector to the front, but it's hopefully a temporary hack and it keeps
- // the code simple).
- std::copy(vec->begin(), vec->begin() + bytes_to_read,
- buffer + *bytes_read);
- vec->erase(vec->begin(), vec->begin() + bytes_to_read);
- *bytes_read += bytes_to_read;
- }
- }
- return READ_SUCCEEDED;
-}
-
-bool ChannelNacl::ShouldDispatchInputMessage(Message* msg) {
- return true;
-}
-
-bool ChannelNacl::GetAttachments(Message* msg) {
- uint16_t header_fds = msg->header()->num_fds;
- CHECK(header_fds == input_attachments_.size());
- if (header_fds == 0)
- return true; // Nothing to do.
-
- for (auto& attachment : input_attachments_) {
- msg->attachment_set()->AddAttachment(std::move(attachment));
- }
- input_attachments_.clear();
- return true;
-}
-
-bool ChannelNacl::DidEmptyInputBuffers() {
- // When the input data buffer is empty, the attachments should be too.
- return input_attachments_.empty();
-}
-
-void ChannelNacl::HandleInternalMessage(const Message& msg) {
- // The trusted side IPC::Channel should handle the "hello" handshake; we
- // should not receive the "Hello" message.
- NOTREACHED();
-}
-
-// Channel's methods
-
-// static
-std::unique_ptr<Channel> Channel::Create(
- const IPC::ChannelHandle& channel_handle,
- Mode mode,
- Listener* listener) {
- return std::make_unique<ChannelNacl>(channel_handle, mode, listener);
-}
-
-} // namespace IPC
diff --git a/ipc/ipc_channel_nacl.h b/ipc/ipc_channel_nacl.h
deleted file mode 100644
index 06e7f6a271..0000000000
--- a/ipc/ipc_channel_nacl.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IPC_IPC_CHANNEL_NACL_H_
-#define IPC_IPC_CHANNEL_NACL_H_
-
-#include <memory>
-#include <string>
-
-#include "base/containers/circular_deque.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/process/process.h"
-#include "base/threading/simple_thread.h"
-#include "ipc/ipc_channel.h"
-#include "ipc/ipc_channel_reader.h"
-
-namespace IPC {
-
-class MessageAttachment;
-
-// Contains the results from one call to imc_recvmsg (data and file
-// descriptors).
-struct MessageContents;
-
-// Similar to the ChannelPosix but for Native Client code.
-// This is somewhat different because sendmsg/recvmsg here do not follow POSIX
-// semantics. Instead, they are implemented by a custom embedding of
-// NaClDescCustom. See NaClIPCAdapter for the trusted-side implementation.
-//
-// We don't need to worry about complicated set up and READWRITE mode for
-// sharing handles. We also currently do not support passing file descriptors or
-// named pipes, and we use background threads to emulate signaling when we can
-// read or write without blocking.
-class ChannelNacl : public Channel,
- public internal::ChannelReader {
- public:
- // Mirror methods of Channel, see ipc_channel.h for description.
- ChannelNacl(const IPC::ChannelHandle& channel_handle,
- Mode mode,
- Listener* listener);
- ~ChannelNacl() override;
-
- // Channel implementation.
- bool Connect() override;
- void Close() override;
- bool Send(Message* message) override;
-
- // Posted to the main thread by ReaderThreadRunner.
- void DidRecvMsg(std::unique_ptr<MessageContents> contents);
- void ReadDidFail();
-
- private:
- class ReaderThreadRunner;
-
- bool CreatePipe(const IPC::ChannelHandle& channel_handle);
- bool ProcessOutgoingMessages();
- void CallOnChannelConnected();
-
- // ChannelReader implementation.
- ReadState ReadData(char* buffer,
- int buffer_len,
- int* bytes_read) override;
- bool ShouldDispatchInputMessage(Message* msg) override;
- bool GetAttachments(Message* msg) override;
- bool DidEmptyInputBuffers() override;
- void HandleInternalMessage(const Message& msg) override;
-
- Mode mode_;
- bool waiting_connect_;
-
- // The pipe used for communication.
- int pipe_;
-
- // We use a thread for reading, so that we can simply block on reading and
- // post the received data back to the main thread to be properly interleaved
- // with other tasks in the MessagePump.
- //
- // imc_recvmsg supports non-blocking reads, but there's no easy way to be
- // informed when a write or read can be done without blocking (this is handled
- // by libevent in Posix).
- std::unique_ptr<ReaderThreadRunner> reader_thread_runner_;
- std::unique_ptr<base::DelegateSimpleThread> reader_thread_;
-
- // IPC::ChannelReader expects to be able to call ReadData on us to
- // synchronously read data waiting in the pipe's buffer without blocking.
- // Since we can't do that (see 1 and 2 above), the reader thread does blocking
- // reads and posts the data over to the main thread in MessageContents. Each
- // MessageContents object is the result of one call to "imc_recvmsg".
- // DidRecvMsg breaks the MessageContents out in to the data and the file
- // descriptors, and puts them on these two queues.
- // TODO(dmichael): There's probably a more efficient way to emulate this with
- // a circular buffer or something, so we don't have to do so
- // many heap allocations. But it maybe isn't worth
- // the trouble given that we probably want to implement 1 and
- // 2 above in NaCl eventually.
- // When ReadData is called, it pulls the bytes out of this queue in order.
- base::circular_deque<std::unique_ptr<std::vector<char>>> read_queue_;
- // Queue of file descriptor attachments extracted from imc_recvmsg messages.
- std::vector<scoped_refptr<MessageAttachment>> input_attachments_;
-
- // This queue is used when a message is sent prior to Connect having been
- // called. Normally after we're connected, the queue is empty.
- base::circular_deque<std::unique_ptr<Message>> output_queue_;
-
- base::WeakPtrFactory<ChannelNacl> weak_ptr_factory_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(ChannelNacl);
-};
-
-} // namespace IPC
-
-#endif // IPC_IPC_CHANNEL_NACL_H_
diff --git a/ipc/ipc_test.mojom b/ipc/ipc_test.mojom
new file mode 100644
index 0000000000..fc24e37c09
--- /dev/null
+++ b/ipc/ipc_test.mojom
@@ -0,0 +1,54 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module IPC.mojom;
+
+interface SimpleTestDriver {
+ ExpectValue(int32 value);
+
+ [Sync]
+ GetExpectedValue() => (int32 value);
+
+ [Sync]
+ RequestValue() => (int32 value);
+
+ RequestQuit() => ();
+};
+
+interface SimpleTestClient {
+ [Sync]
+ RequestValue() => (int32 value);
+};
+
+interface PingReceiver {
+ Ping() => ();
+};
+
+struct TestStruct {};
+
+interface TestStructPasser {
+ Pass(TestStruct s);
+};
+
+interface IndirectTestDriver {
+ GetPingReceiver(associated PingReceiver& request);
+};
+
+interface Reflector {
+ Ping(string value) => (string value);
+ [Sync]
+ SyncPing(string value) => (string response);
+ Quit();
+};
+
+interface AssociatedInterfaceVendor {
+ GetTestInterface(associated SimpleTestDriver& interface_reqest);
+};
+
+interface InterfacePassingTestDriver {
+ Init() => ();
+ GetPingReceiver(array<PingReceiver&> request) => ();
+ GetAssociatedPingReceiver(array<associated PingReceiver&> request) => ();
+ Quit();
+};
diff --git a/libchrome_tools/files_not_built b/libchrome_tools/files_not_built
new file mode 100755
index 0000000000..64ad444f68
--- /dev/null
+++ b/libchrome_tools/files_not_built
@@ -0,0 +1,16 @@
+#!/bin/bash
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# List cc files not in BUILD.gn, and not excluded by patterns in BUILD.IGNORE
+# This list can be run by human for sanity check that no imporant things are
+# ignored after each uprev.
+
+cd $(dirname $0)/..
+find . -name '*.cc' \
+ | sed -e 's/^\.\///g' \
+ | xargs -n 1 -P 1 bash -c \
+ 'for i in $(cat BUILD.IGNORE); do grep $i <(echo $0) >/dev/null && exit; done; echo $0' \
+ | xargs -n 1 -P 1 sh -c 'grep $0 BUILD.gn >/dev/null || echo $0'
+
diff --git a/libchrome_tools/mojom_generate_type_mappings.py b/libchrome_tools/mojom_generate_type_mappings.py
index 0c8023c40f..a532e307f5 100644..100755
--- a/libchrome_tools/mojom_generate_type_mappings.py
+++ b/libchrome_tools/mojom_generate_type_mappings.py
@@ -28,11 +28,26 @@ import os
import subprocess
import sys
-from build import gn_helpers
+try:
+ # ../build cannot be loaded if cwd is other directories
+ # e.g. when emerge libchrome
+ sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..'))
+ from build import gn_helpers
+except ImportError:
+ # gn_helpers is located at the same directory when building other packages
+ # in system directory.
+ import gn_helpers
+
+# script in libchrome repositority is used when emerge libchrome.
_GENERATE_TYPE_MAPPINGS_PATH = os.path.join(
os.path.dirname(__file__),
'../mojo/public/tools/bindings/generate_type_mappings.py')
+if not os.path.isfile(_GENERATE_TYPE_MAPPINGS_PATH):
+ # use system script in the same dir when building other packages.
+ _GENERATE_TYPE_MAPPINGS_PATH = os.path.join(
+ os.path.dirname(__file__),
+ 'generate_type_mappings.py')
def _read_typemap_config(path):
"""Reads .typemap file.
diff --git a/libchrome_tools/patches/0001-Fix-pending_broker_clients-handling.patch b/libchrome_tools/patches/0001-Fix-pending_broker_clients-handling.patch
new file mode 100644
index 0000000000..2402a1097b
--- /dev/null
+++ b/libchrome_tools/patches/0001-Fix-pending_broker_clients-handling.patch
@@ -0,0 +1,44 @@
+From 6a4ab3d6429ec13e8c875ebc611683bb26453770 Mon Sep 17 00:00:00 2001
+From: Lepton Wu <lepton@chromium.org>
+Date: Tue, 15 Sep 2020 16:58:14 +0000
+Subject: [PATCH] Fix pending_broker_clients handling
+
+We always remove invitee from pending_invitations_ before adding it to
+pending_broker_clients, so the old code actually is buggy and invitees
+in pending_broker_clients_ will never be added as a broker client. Fix
+it by checking peers_ instead of pending_invitations_.
+
+BUG=b:146518063,b:150661600,b:168250032,chromium:1121709
+TEST=manual - Keep running arc.Boot.vm with updated mojo code.
+
+Change-Id: Ib0353944e7d5b9edc04947f8bd2db1442a4ed78d
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2411560
+Commit-Queue: Ken Rockot <rockot@google.com>
+Reviewed-by: Ken Rockot <rockot@google.com>
+Auto-Submit: Lepton Wu <lepton@chromium.org>
+Cr-Commit-Position: refs/heads/master@{#807076}
+---
+ mojo/core/node_controller.cc | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc
+index 298079f4e234..029bd350b08b 100644
+--- a/mojo/core/node_controller.cc
++++ b/mojo/core/node_controller.cc
+@@ -993,11 +993,8 @@ void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node,
+ // Feed the broker any pending invitees of our own.
+ while (!pending_broker_clients.empty()) {
+ const ports::NodeName& invitee_name = pending_broker_clients.front();
+- auto it = pending_invitations_.find(invitee_name);
+- // If for any reason we don't have a pending invitation for the invitee,
+- // there's nothing left to do: we've already swapped the relevant state into
+- // the stack.
+- if (it != pending_invitations_.end()) {
++ auto it = peers_.find(invitee_name);
++ if (it != peers_.end()) {
+ broker->AddBrokerClient(invitee_name,
+ it->second->CloneRemoteProcessHandle());
+ }
+--
+2.28.0.618.gf4bc123cb7-goog
+
diff --git a/libchrome_tools/patches/Add-base-NoDestructor-T.patch b/libchrome_tools/patches/Add-base-NoDestructor-T.patch
new file mode 100644
index 0000000000..9c95d4bc0e
--- /dev/null
+++ b/libchrome_tools/patches/Add-base-NoDestructor-T.patch
@@ -0,0 +1,129 @@
+From c334673e96ce73cbf1a693c7c85b1450fcd3571c Mon Sep 17 00:00:00 2001
+From: Ben Chan <benchan@chromium.org>
+Date: Fri, 2 Nov 2018 23:07:01 -0700
+Subject: [PATCH] libchrome: add base::NoDestructor<T>
+
+CL:869351 introduces base::NoDestructor<T>, which is preferred in new
+code as a drop-in replacement for a function scoped static T* or T& that
+is dynamically initialized, and a global base::LazyInstance<T>.
+
+This CL patches libchrome to pull in base/no_destructor.h at r599267, so
+that we can migrate existing Chrome OS code to use base::NoDestructor<T>
+before the next libchrome uprev.
+
+BUG=None
+TEST=`emerge-$BOARD librchrome`
+
+Change-Id: I791a70e10da6318ea81eaaec869ba4702361289e
+---
+ base/no_destructor.h | 98 ++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 98 insertions(+)
+ create mode 100644 base/no_destructor.h
+
+diff --git base/no_destructor.h base/no_destructor.h
+new file mode 100644
+index 0000000..21cfef8
+--- /dev/null
++++ base/no_destructor.h
+@@ -0,0 +1,98 @@
++// Copyright 2018 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_NO_DESTRUCTOR_H_
++#define BASE_NO_DESTRUCTOR_H_
++
++#include <new>
++#include <utility>
++
++namespace base {
++
++// A wrapper that makes it easy to create an object of type T with static
++// storage duration that:
++// - is only constructed on first access
++// - never invokes the destructor
++// in order to satisfy the styleguide ban on global constructors and
++// destructors.
++//
++// Runtime constant example:
++// const std::string& GetLineSeparator() {
++// // Forwards to std::string(size_t, char, const Allocator&) constructor.
++// static const base::NoDestructor<std::string> s(5, '-');
++// return *s;
++// }
++//
++// More complex initialization with a lambda:
++// const std::string& GetSessionNonce() {
++// static const base::NoDestructor<std::string> nonce([] {
++// std::string s(16);
++// crypto::RandString(s.data(), s.size());
++// return s;
++// }());
++// return *nonce;
++// }
++//
++// NoDestructor<T> stores the object inline, so it also avoids a pointer
++// indirection and a malloc. Also note that since C++11 static local variable
++// initialization is thread-safe and so is this pattern. Code should prefer to
++// use NoDestructor<T> over:
++// - A function scoped static T* or T& that is dynamically initialized.
++// - A global base::LazyInstance<T>.
++//
++// Note that since the destructor is never run, this *will* leak memory if used
++// as a stack or member variable. Furthermore, a NoDestructor<T> should never
++// have global scope as that may require a static initializer.
++template <typename T>
++class NoDestructor {
++ public:
++ // Not constexpr; just write static constexpr T x = ...; if the value should
++ // be a constexpr.
++ template <typename... Args>
++ explicit NoDestructor(Args&&... args) {
++ new (storage_) T(std::forward<Args>(args)...);
++ }
++
++ // Allows copy and move construction of the contained type, to allow
++ // construction from an initializer list, e.g. for std::vector.
++ explicit NoDestructor(const T& x) { new (storage_) T(x); }
++ explicit NoDestructor(T&& x) { new (storage_) T(std::move(x)); }
++
++ NoDestructor(const NoDestructor&) = delete;
++ NoDestructor& operator=(const NoDestructor&) = delete;
++
++ ~NoDestructor() = default;
++
++ const T& operator*() const { return *get(); }
++ T& operator*() { return *get(); }
++
++ const T* operator->() const { return get(); }
++ T* operator->() { return get(); }
++
++ const T* get() const { return reinterpret_cast<const T*>(storage_); }
++ T* get() { return reinterpret_cast<T*>(storage_); }
++
++ private:
++ alignas(T) char storage_[sizeof(T)];
++
++#if defined(LEAK_SANITIZER)
++ // TODO(https://crbug.com/812277): This is a hack to work around the fact
++ // that LSan doesn't seem to treat NoDestructor as a root for reachability
++ // analysis. This means that code like this:
++ // static base::NoDestructor<std::vector<int>> v({1, 2, 3});
++ // is considered a leak. Using the standard leak sanitizer annotations to
++ // suppress leaks doesn't work: std::vector is implicitly constructed before
++ // calling the base::NoDestructor constructor.
++ //
++ // Unfortunately, I haven't been able to demonstrate this issue in simpler
++ // reproductions: until that's resolved, hold an explicit pointer to the
++ // placement-new'd object in leak sanitizer mode to help LSan realize that
++ // objects allocated by the contained type are still reachable.
++ T* storage_ptr_ = reinterpret_cast<T*>(storage_);
++#endif // defined(LEAK_SANITIZER)
++};
++
++} // namespace base
++
++#endif // BASE_NO_DESTRUCTOR_H_
+--
+2.19.1.930.g4563a0d9d0-goog
+
diff --git a/libchrome_tools/patches/Add-header-files-base-check_op-notreached-h.patch b/libchrome_tools/patches/Add-header-files-base-check_op-notreached-h.patch
new file mode 100644
index 0000000000..8b61fa4d80
--- /dev/null
+++ b/libchrome_tools/patches/Add-header-files-base-check_op-notreached-h.patch
@@ -0,0 +1,48 @@
+From b69366c4a2f08545bd03e1c0aa6a37562510a2e6 Mon Sep 17 00:00:00 2001
+From: hscham <hscham@chromium.org>
+Date: Wed, 3 Jun 2020 17:26:49 +0900
+Subject: [PATCH] Add header files base/{check_op,notreached}.h
+
+Change-Id: I5267ddeba3a63c7977f093b1a25dff760dbe167b
+---
+ base/check_op.h | 10 ++++++++++
+ base/notreached.h | 10 ++++++++++
+ 2 files changed, 20 insertions(+)
+ create mode 100644 base/check_op.h
+ create mode 100644 base/notreached.h
+
+diff --git a/base/check_op.h b/base/check_op.h
+new file mode 100644
+index 000000000..caa3c3af6
+--- /dev/null
++++ base/check_op.h
+@@ -0,0 +1,10 @@
++// Copyright 2020 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_CHECK_OP_H_
++#define BASE_CHECK_OP_H_
++
++#include "base/logging.h"
++
++#endif // BASE_CHECK_OP_H_
+diff --git a/base/notreached.h b/base/notreached.h
+new file mode 100644
+index 000000000..1eacc44e4
+--- /dev/null
++++ base/notreached.h
+@@ -0,0 +1,10 @@
++// Copyright 2020 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_NOTREACHED_H_
++#define BASE_NOTREACHED_H_
++
++#include "base/logging.h"
++
++#endif // BASE_NOTREACHED_H_
+--
+2.27.0.rc2.251.g90737beb825-goog
+
diff --git a/libchrome_tools/patches/Connect-to-NameOwnerChanged-signal-when-setting-call.patch b/libchrome_tools/patches/Connect-to-NameOwnerChanged-signal-when-setting-call.patch
new file mode 100644
index 0000000000..83d2e49e9b
--- /dev/null
+++ b/libchrome_tools/patches/Connect-to-NameOwnerChanged-signal-when-setting-call.patch
@@ -0,0 +1,163 @@
+From c2e55024c6a03dcb099270718e70139542d8b0e4 Mon Sep 17 00:00:00 2001
+From: Bing Xue <bingxue@google.com>
+Date: Thu, 1 Aug 2019 15:47:10 -0700
+Subject: [PATCH] Connect to NameOwnerChanged signal when setting callback
+
+In ObjectManager, when ConnectToNameOwnerChangedSignal is called the first time,
+we could have already missed the NameOwnerChanged signal if we get a value from
+UpdateNameOwnerAndBlock. This means NameOwnerChanged callbacks will
+never be called until that service restarts. In ObjectManager we run
+into this problem:
+
+1. ObjectManager::SetupMatchRuleAndFilter is called, calling
+bus_->GetServiceOwnerAndBlock shows empty service name owner.
+
+2. ObjectManager::OnSetupManagerRuleAndFilterComplete callback will call
+object_proxy_->ConnectToSignal, which in turn calls
+ConnectToNameOwnerChangedSignal the first time. At this point,
+UpdateNameOwnerAndBlock calling bus_->GetServiceOwnerAndBlock returns the
+current name owner of the service, this means the NameOwnerChanged signal
+is already sent on system bus.
+
+3. As a result, ObjectManager::NameOwnerChanged is never called while
+the service is already online. This in turn causes GetManagedObject to
+never be called, and the object manager interface never added.
+
+See detailed sample logs in b/138416411.
+
+This CL adds the following:
+
+1. Make SetNameOwnerChangedCallback run
+ConnectToNameOwnerChangedSignal when called. Since ObjectManager calls
+SetNameOwnerChangedCallback before posting SetupMatchRuleAndFilter (in which
+ObjectManager attempts to get the service name owner through a blocking call),
+this removes the time gap described above that causes lost signal.
+
+2. Make dbus thread the only writer to |service_name_owner_|, given that
+connecting to the NameOwnerChanged signal right away in ObjectManager
+ctor causes potential data race in writing to |service_name_owner_| in
+both NameOwnerChanged (on origin thread) and SetupMatchRuleAndFilter (on
+dbus thread).
+
+BUG=b:138416411
+TEST=Manually on device.
+
+Change-Id: Ie95a5b7b303637acadebda151cc478e52b6a1af5
+---
+ dbus/object_manager.cc | 20 +++++++++++++++++---
+ dbus/object_manager.h | 5 +++++
+ dbus/object_proxy.cc | 13 +++++++++++++
+ dbus/object_proxy.h | 3 +++
+ 4 files changed, 38 insertions(+), 3 deletions(-)
+
+diff --git a/dbus/object_manager.cc b/dbus/object_manager.cc
+index 05d4b2ddeabd..44f120864310 100644
+--- a/dbus/object_manager.cc
++++ b/dbus/object_manager.cc
+@@ -187,8 +187,12 @@ bool ObjectManager::SetupMatchRuleAndFilter() {
+ if (!bus_->Connect() || !bus_->SetUpAsyncOperations())
+ return false;
+
+- service_name_owner_ =
+- bus_->GetServiceOwnerAndBlock(service_name_, Bus::SUPPRESS_ERRORS);
++ // Try to get |service_name_owner_| from dbus if we haven't received any
++ // NameOwnerChanged signals.
++ if (service_name_owner_.empty()) {
++ service_name_owner_ =
++ bus_->GetServiceOwnerAndBlock(service_name_, Bus::SUPPRESS_ERRORS);
++ }
+
+ const std::string match_rule =
+ base::StringPrintf(
+@@ -224,6 +228,7 @@ void ObjectManager::OnSetupMatchRuleAndFilterComplete(bool success) {
+ DCHECK(bus_);
+ DCHECK(object_proxy_);
+ DCHECK(setup_success_);
++ bus_->AssertOnOriginThread();
+
+ // |object_proxy_| is no longer valid if the Bus was shut down before this
+ // call. Don't initiate any other action from the origin thread.
+@@ -505,9 +510,18 @@ void ObjectManager::RemoveInterface(const ObjectPath& object_path,
+ }
+ }
+
++void ObjectManager::UpdateServiceNameOwner(const std::string& new_owner) {
++ bus_->AssertOnDBusThread();
++ service_name_owner_ = new_owner;
++}
++
+ void ObjectManager::NameOwnerChanged(const std::string& old_owner,
+ const std::string& new_owner) {
+- service_name_owner_ = new_owner;
++ bus_->AssertOnOriginThread();
++
++ bus_->GetDBusTaskRunner()->PostTask(
++ FROM_HERE,
++ base::BindOnce(&ObjectManager::UpdateServiceNameOwner, this, new_owner));
+
+ if (!old_owner.empty()) {
+ ObjectMap::iterator iter = object_map_.begin();
+diff --git a/dbus/object_manager.h b/dbus/object_manager.h
+index 05388de8e6eb..4b5fb790412d 100644
+--- a/dbus/object_manager.h
++++ b/dbus/object_manager.h
+@@ -317,6 +317,11 @@ class CHROME_DBUS_EXPORT ObjectManager final
+ void NameOwnerChanged(const std::string& old_owner,
+ const std::string& new_owner);
+
++ // Write |new_owner| to |service_name_owner_|. This method makes sure write
++ // happens on the DBus thread, which is the sole writer to
++ // |service_name_owner_|.
++ void UpdateServiceNameOwner(const std::string& new_owner);
++
+ Bus* bus_;
+ std::string service_name_;
+ std::string service_name_owner_;
+diff --git a/dbus/object_proxy.cc b/dbus/object_proxy.cc
+index 7adf8f179471..de5785e98307 100644
+--- a/dbus/object_proxy.cc
++++ b/dbus/object_proxy.cc
+@@ -274,6 +274,10 @@ void ObjectProxy::SetNameOwnerChangedCallback(
+ bus_->AssertOnOriginThread();
+
+ name_owner_changed_callback_ = callback;
++
++ bus_->GetDBusTaskRunner()->PostTask(
++ FROM_HERE,
++ base::BindOnce(&ObjectProxy::TryConnectToNameOwnerChangedSignal, this));
+ }
+
+ void ObjectProxy::WaitForServiceToBeAvailable(
+@@ -458,6 +462,15 @@ bool ObjectProxy::ConnectToNameOwnerChangedSignal() {
+ return success;
+ }
+
++void ObjectProxy::TryConnectToNameOwnerChangedSignal() {
++ bus_->AssertOnDBusThread();
++
++ bool success = ConnectToNameOwnerChangedSignal();
++ LOG_IF(WARNING, !success)
++ << "Failed to connect to NameOwnerChanged signal for object: "
++ << object_path_.value();
++}
++
+ bool ObjectProxy::ConnectToSignalInternal(const std::string& interface_name,
+ const std::string& signal_name,
+ SignalCallback signal_callback) {
+diff --git a/dbus/object_proxy.h b/dbus/object_proxy.h
+index 22e44f1d64c0..b1bf622a12cb 100644
+--- a/dbus/object_proxy.h
++++ b/dbus/object_proxy.h
+@@ -267,6 +267,9 @@ class CHROME_DBUS_EXPORT ObjectProxy
+ // Connects to NameOwnerChanged signal.
+ bool ConnectToNameOwnerChangedSignal();
+
++ // Tries to connect to NameOwnerChanged signal, ignores any error.
++ void TryConnectToNameOwnerChangedSignal();
++
+ // Helper function for ConnectToSignal().
+ bool ConnectToSignalInternal(const std::string& interface_name,
+ const std::string& signal_name,
+--
+2.22.0.770.g0f2c4a37fd-goog
+
diff --git a/libchrome_tools/patches/Fix-TimeDelta.patch b/libchrome_tools/patches/Fix-TimeDelta.patch
new file mode 100644
index 0000000000..65ab874a03
--- /dev/null
+++ b/libchrome_tools/patches/Fix-TimeDelta.patch
@@ -0,0 +1,164 @@
+commit db5f2b6e74d6d2e2a9c6f530199067c1d6eff7b6
+Author: Max Morin <maxmorin@chromium.org>
+Date: Mon Aug 27 09:45:24 2018 +0000
+
+ Make TimeDelta TriviallyCopyable for std::atomic.
+
+ We're having some awkwardness with working around the issue with stuff
+ like std::atomic<int64_t> and conversions back and forth. It would be
+ preferable to use std::atomic<TimeDelta> directly.
+
+ Removes the workaround added for bug 635974, since that was a bug in
+ vs 2015 and we use clang now.
+
+ Also fixes a couple of lint issues in time.h.
+
+ Bug: 635974, 851959, 761570
+ Change-Id: I4683f960b0c348748c5f0aaf222da4dda40256ec
+ Reviewed-on: https://chromium-review.googlesource.com/1184781
+ Commit-Queue: Max Morin <maxmorin@chromium.org>
+ Reviewed-by: Yuri Wiitala <miu@chromium.org>
+ Reviewed-by: Bruce Dawson <brucedawson@chromium.org>
+ Cr-Commit-Position: refs/heads/master@{#586219}
+
+diff --git a/base/time/time.h b/base/time/time.h
+index f4c2f93f30b4..7d4f308545c9 100644
+--- a/base/time/time.h
++++ b/base/time/time.h
+@@ -199,11 +199,6 @@ class BASE_EXPORT TimeDelta {
+ double InMicrosecondsF() const;
+ int64_t InNanoseconds() const;
+
+- constexpr TimeDelta& operator=(TimeDelta other) {
+- delta_ = other.delta_;
+- return *this;
+- }
+-
+ // Computations with other deltas. Can easily be made constexpr with C++17 but
+ // hard to do until then per limitations around
+ // __builtin_(add|sub)_overflow in safe_math_clang_gcc_impl.h :
+@@ -283,11 +278,6 @@ class BASE_EXPORT TimeDelta {
+ return delta_ >= other.delta_;
+ }
+
+-#if defined(OS_WIN)
+- // This works around crbug.com/635974
+- constexpr TimeDelta(const TimeDelta& other) : delta_(other.delta_) {}
+-#endif
+-
+ private:
+ friend int64_t time_internal::SaturatedAdd(TimeDelta delta, int64_t value);
+ friend int64_t time_internal::SaturatedSub(TimeDelta delta, int64_t value);
+@@ -375,7 +365,7 @@ class TimeBase {
+ //
+ // DEPRECATED - Do not use in new code. For serializing Time values, prefer
+ // Time::ToDeltaSinceWindowsEpoch().InMicroseconds(). http://crbug.com/634507
+- int64_t ToInternalValue() const { return us_; }
++ constexpr int64_t ToInternalValue() const { return us_; }
+
+ // The amount of time since the origin (or "zero") point. This is a syntactic
+ // convenience to aid in code readability, mainly for debugging/testing use
+@@ -799,7 +789,7 @@ constexpr TimeDelta TimeDelta::FromDouble(double value) {
+ // static
+ constexpr TimeDelta TimeDelta::FromProduct(int64_t value,
+ int64_t positive_value) {
+- DCHECK(positive_value > 0);
++ DCHECK(positive_value > 0); // NOLINT, DCHECK_GT isn't constexpr.
+ return value > std::numeric_limits<int64_t>::max() / positive_value
+ ? Max()
+ : value < std::numeric_limits<int64_t>::min() / positive_value
+@@ -903,8 +893,8 @@ class BASE_EXPORT TimeTicks : public time_internal::TimeBase<TimeTicks> {
+ return TimeTicks(us);
+ }
+
+-#if defined(OS_WIN)
+ protected:
++#if defined(OS_WIN)
+ typedef DWORD (*TickFunctionType)(void);
+ static TickFunctionType SetMockTickFunction(TickFunctionType ticker);
+ #endif
+@@ -926,8 +916,7 @@ BASE_EXPORT std::ostream& operator<<(std::ostream& os, TimeTicks time_ticks);
+ // thread is running.
+ class BASE_EXPORT ThreadTicks : public time_internal::TimeBase<ThreadTicks> {
+ public:
+- ThreadTicks() : TimeBase(0) {
+- }
++ constexpr ThreadTicks() : TimeBase(0) {}
+
+ // Returns true if ThreadTicks::Now() is supported on this system.
+ static bool IsSupported() WARN_UNUSED_RESULT {
+diff --git a/base/time/time_unittest.cc b/base/time/time_unittest.cc
+index 5dc8888a3297..ce8cc39cbaec 100644
+--- a/base/time/time_unittest.cc
++++ b/base/time/time_unittest.cc
+@@ -1542,6 +1542,70 @@ TEST(TimeDelta, Overflows) {
+ EXPECT_EQ(kOneSecond, (ticks_now + kOneSecond) - ticks_now);
+ }
+
++constexpr TimeTicks TestTimeTicksConstexprCopyAssignment() {
++ TimeTicks a = TimeTicks::FromInternalValue(12345);
++ TimeTicks b;
++ b = a;
++ return b;
++}
++
++TEST(TimeTicks, ConstexprAndTriviallyCopiable) {
++ // "Trivially copyable" is necessary for use in std::atomic<TimeTicks>.
++ static_assert(std::is_trivially_copyable<TimeTicks>(), "");
++
++ // Copy ctor.
++ constexpr TimeTicks a = TimeTicks::FromInternalValue(12345);
++ constexpr TimeTicks b{a};
++ static_assert(a.ToInternalValue() == b.ToInternalValue(), "");
++
++ // Copy assignment.
++ static_assert(a.ToInternalValue() ==
++ TestTimeTicksConstexprCopyAssignment().ToInternalValue(),
++ "");
++}
++
++constexpr ThreadTicks TestThreadTicksConstexprCopyAssignment() {
++ ThreadTicks a = ThreadTicks::FromInternalValue(12345);
++ ThreadTicks b;
++ b = a;
++ return b;
++}
++
++TEST(ThreadTicks, ConstexprAndTriviallyCopiable) {
++ // "Trivially copyable" is necessary for use in std::atomic<ThreadTicks>.
++ static_assert(std::is_trivially_copyable<ThreadTicks>(), "");
++
++ // Copy ctor.
++ constexpr ThreadTicks a = ThreadTicks::FromInternalValue(12345);
++ constexpr ThreadTicks b{a};
++ static_assert(a.ToInternalValue() == b.ToInternalValue(), "");
++
++ // Copy assignment.
++ static_assert(a.ToInternalValue() ==
++ TestThreadTicksConstexprCopyAssignment().ToInternalValue(),
++ "");
++}
++
++constexpr TimeDelta TestTimeDeltaConstexprCopyAssignment() {
++ TimeDelta a = TimeDelta::FromSeconds(1);
++ TimeDelta b;
++ b = a;
++ return b;
++}
++
++TEST(TimeDelta, ConstexprAndTriviallyCopiable) {
++ // "Trivially copyable" is necessary for use in std::atomic<TimeDelta>.
++ static_assert(std::is_trivially_copyable<TimeDelta>(), "");
++
++ // Copy ctor.
++ constexpr TimeDelta a = TimeDelta::FromSeconds(1);
++ constexpr TimeDelta b{a};
++ static_assert(a == b, "");
++
++ // Copy assignment.
++ static_assert(a == TestTimeDeltaConstexprCopyAssignment(), "");
++}
++
+ TEST(TimeDeltaLogging, DCheckEqCompiles) {
+ DCHECK_EQ(TimeDelta(), TimeDelta());
+ }
diff --git a/libchrome_tools/patches/Fix-Wdefaulted-function-deleted-warning-in-MessageLo.patch b/libchrome_tools/patches/Fix-Wdefaulted-function-deleted-warning-in-MessageLo.patch
new file mode 100644
index 0000000000..33855143c9
--- /dev/null
+++ b/libchrome_tools/patches/Fix-Wdefaulted-function-deleted-warning-in-MessageLo.patch
@@ -0,0 +1,38 @@
+From 91fe99136cd57a8eab9c076e4e1699767bcac3fa Mon Sep 17 00:00:00 2001
+From: Hans Wennborg <hans@chromium.org>
+Date: Tue, 2 Oct 2018 16:25:34 +0000
+Subject: [PATCH] Fix -Wdefaulted-function-deleted warning in
+ MessageLoopCurrent
+
+The new Clang warning points out that the class can't be copy
+assigned because the current_ member is const. Copy or move
+constructing it is fine though, and that's all that's needed.
+
+Bug: 890307
+Change-Id: I3f4d5e69485b84166ba4dd2356cc7973a5e58da6
+Reviewed-on: https://chromium-review.googlesource.com/1255613
+Reviewed-by: Daniel Cheng <dcheng@chromium.org>
+Cr-Commit-Position: refs/heads/master@{#595867}
+---
+ base/message_loop/message_loop_current.h | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/base/message_loop/message_loop_current.h b/base/message_loop/message_loop_current.h
+index d623cbc7f7b2..403d0dcc2ddb 100644
+--- a/base/message_loop/message_loop_current.h
++++ b/base/message_loop/message_loop_current.h
+@@ -38,9 +38,9 @@ class MessageLoop;
+ class BASE_EXPORT MessageLoopCurrent {
+ public:
+ // MessageLoopCurrent is effectively just a disguised pointer and is fine to
+- // copy around.
++ // copy/move around.
+ MessageLoopCurrent(const MessageLoopCurrent& other) = default;
+- MessageLoopCurrent& operator=(const MessageLoopCurrent& other) = default;
++ MessageLoopCurrent(MessageLoopCurrent&& other) = default;
+
+ // Returns a proxy object to interact with the MessageLoop running the
+ // current thread. It must only be used on the thread it was obtained.
+--
+2.22.0.rc2.383.gf4fbbf30c2-goog
+
diff --git a/libchrome_tools/patches/Mojo-Check-if-dispatcher-is-null-in-Core-UnwrapPlatf.patch b/libchrome_tools/patches/Mojo-Check-if-dispatcher-is-null-in-Core-UnwrapPlatf.patch
new file mode 100644
index 0000000000..28ff13bb6f
--- /dev/null
+++ b/libchrome_tools/patches/Mojo-Check-if-dispatcher-is-null-in-Core-UnwrapPlatf.patch
@@ -0,0 +1,38 @@
+From 317fd980dd9504d3fe321b53469fee79e3276fed Mon Sep 17 00:00:00 2001
+From: Ryo Hashimoto <hashimoto@chromium.org>
+Date: Thu, 4 Oct 2018 05:04:22 +0000
+Subject: [PATCH] Mojo: Check if dispatcher is null in
+ Core::UnwrapPlatformHandle()
+
+The same check is done in other functions in this .cc file.
+Do the same thing for UnwrapPlatformHandle().
+
+BUG=891990
+TEST=mojo_unittests
+
+Change-Id: I05fe4bfd5edd8ec3fc67aeb9f11879c74fd71dd4
+Reviewed-on: https://chromium-review.googlesource.com/c/1260782
+Reviewed-by: Ken Rockot <rockot@chromium.org>
+Commit-Queue: Ryo Hashimoto <hashimoto@chromium.org>
+Cr-Commit-Position: refs/heads/master@{#596510}
+---
+ mojo/core/core.cc | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/mojo/core/core.cc b/mojo/core/core.cc
+index 8422ec247a40..32ecea3eae55 100644
+--- a/mojo/core/core.cc
++++ b/mojo/core/core.cc
+@@ -1017,7 +1017,8 @@ MojoResult Core::UnwrapPlatformHandle(
+ {
+ base::AutoLock lock(handles_->GetLock());
+ dispatcher = handles_->GetDispatcher(mojo_handle);
+- if (dispatcher->GetType() != Dispatcher::Type::PLATFORM_HANDLE)
++ if (!dispatcher ||
++ dispatcher->GetType() != Dispatcher::Type::PLATFORM_HANDLE)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ MojoResult result =
+--
+2.25.0.225.g125e21ebc7-goog
+
diff --git a/libchrome_tools/patches/Refactor-AlarmTimer-to-report-error-to-the-caller.patch b/libchrome_tools/patches/Refactor-AlarmTimer-to-report-error-to-the-caller.patch
new file mode 100644
index 0000000000..1967794817
--- /dev/null
+++ b/libchrome_tools/patches/Refactor-AlarmTimer-to-report-error-to-the-caller.patch
@@ -0,0 +1,180 @@
+From 07763c630b9e6ee4538a86d291bfce8357dec934 Mon Sep 17 00:00:00 2001
+From: Hidehiko Abe <hidehiko@chromium.org>
+Date: Thu, 13 Jun 2019 22:12:42 +0900
+Subject: [PATCH] Refactor AlarmTimer to report error to the caller.
+
+This is to address the comment
+https://chromium-review.googlesource.com/c/aosp/platform/external/libchrome/+/1387893/2/components/timers/alarm_timer_chromeos.h#45
+
+Instead of just upstreaming it, which exposes a method unused in Chromium,
+this CL introduces Create() factory method and drops the code to
+fallback in case of timerfd_create failure.
+
+Now, a caller should check whether Create() returns null or not.
+In case of null, to keep the previous behavior, the caller can
+create an instance of the parent class.
+
+Along with the change, in order to run unittest for the class
+without capability CAP_WAKE_ALARM, this CL also introduces CreateForTesting().
+We used to test just fall-back code paths.
+
+In addition, this CL fixes the FD leak on destruction.
+
+This patch modified the original upstream patch, by
+ - keeping the old constructor for backward-compatibility.
+ - keeping CanWakeFromSuspend, and calls of CanWakeFromSuspend from Stop
+ and Reset, for backward-compatibility, so that unittest won't fail
+ due to -1 alarm_fd_ from default constructor when there's no
+ capability to alarm.
+
+BUG=None
+TEST=Build and run components_unittests --gtest_filter=AlarmTimerTest*. Run try.
+
+Change-Id: Ieb9660335120565ccff7f192d7a8192ca1e59ebc
+Reviewed-on: https://chromium-review.googlesource.com/c/1411356
+Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
+Reviewed-by: Dmitry Titov <dimich@chromium.org>
+Reviewed-by: Dan Erat <derat@chromium.org>
+Commit-Queue: Hidehiko Abe <hidehiko@chromium.org>
+Cr-Commit-Position: refs/heads/master@{#626151}
+---
+ components/timers/alarm_timer_chromeos.cc | 51 ++++++++++++++---------
+ components/timers/alarm_timer_chromeos.h | 27 +++++++-----
+ 2 files changed, 49 insertions(+), 29 deletions(-)
+
+diff --git a/components/timers/alarm_timer_chromeos.cc b/components/timers/alarm_timer_chromeos.cc
+index 0b43134..371eb67 100644
+--- a/components/timers/alarm_timer_chromeos.cc
++++ b/components/timers/alarm_timer_chromeos.cc
+@@ -15,13 +15,43 @@
+ #include "base/debug/task_annotator.h"
+ #include "base/files/file_util.h"
+ #include "base/logging.h"
++#include "base/memory/ptr_util.h"
+ #include "base/pending_task.h"
+ #include "base/trace_event/trace_event.h"
+
+ namespace timers {
+
++// Keep for backward compatibility to uprev r576279.
+ SimpleAlarmTimer::SimpleAlarmTimer()
+ : alarm_fd_(timerfd_create(CLOCK_REALTIME_ALARM, 0)), weak_factory_(this) {}
++// static
++std::unique_ptr<SimpleAlarmTimer> SimpleAlarmTimer::Create() {
++ return CreateInternal(CLOCK_REALTIME_ALARM);
++}
++
++// static
++std::unique_ptr<SimpleAlarmTimer> SimpleAlarmTimer::CreateForTesting() {
++ // For unittest, use CLOCK_REALTIME in order to run the tests without
++ // CAP_WAKE_ALARM.
++ return CreateInternal(CLOCK_REALTIME);
++}
++
++// static
++std::unique_ptr<SimpleAlarmTimer> SimpleAlarmTimer::CreateInternal(
++ int clockid) {
++ base::ScopedFD alarm_fd(timerfd_create(clockid, TFD_CLOEXEC));
++ if (!alarm_fd.is_valid()) {
++ PLOG(ERROR) << "Failed to create timer fd";
++ return nullptr;
++ }
++
++ // Note: std::make_unique<> cannot be used because the constructor is
++ // private.
++ return base::WrapUnique(new SimpleAlarmTimer(std::move(alarm_fd)));
++}
++
++SimpleAlarmTimer::SimpleAlarmTimer(base::ScopedFD alarm_fd)
++ : alarm_fd_(std::move(alarm_fd)), weak_factory_(this) {}
+
+ SimpleAlarmTimer::~SimpleAlarmTimer() {
+ DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
+@@ -80,7 +97,7 @@ void SimpleAlarmTimer::Reset() {
+ alarm_time.it_value.tv_nsec =
+ (delay.InMicroseconds() % base::Time::kMicrosecondsPerSecond) *
+ base::Time::kNanosecondsPerMicrosecond;
+- if (timerfd_settime(alarm_fd_, 0, &alarm_time, NULL) < 0)
++ if (timerfd_settime(alarm_fd_.get(), 0, &alarm_time, NULL) < 0)
+ PLOG(ERROR) << "Error while setting alarm time. Timer will not fire";
+
+ // The timer is running.
+@@ -97,7 +114,7 @@ void SimpleAlarmTimer::Reset() {
+ base::debug::TaskAnnotator().WillQueueTask("SimpleAlarmTimer::Reset",
+ pending_task_.get());
+ alarm_fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(
+- alarm_fd_,
++ alarm_fd_.get(),
+ base::BindRepeating(&SimpleAlarmTimer::OnAlarmFdReadableWithoutBlocking,
+ weak_factory_.GetWeakPtr()));
+ }
+@@ -109,7 +126,7 @@ void SimpleAlarmTimer::OnAlarmFdReadableWithoutBlocking() {
+
+ // Read from |alarm_fd_| to ack the event.
+ char val[sizeof(uint64_t)];
+- if (!base::ReadFromFD(alarm_fd_, val, sizeof(uint64_t)))
++ if (!base::ReadFromFD(alarm_fd_.get(), val, sizeof(uint64_t)))
+ PLOG(DFATAL) << "Unable to read from timer file descriptor.";
+
+ OnTimerFired();
+diff --git a/components/timers/alarm_timer_chromeos.h b/components/timers/alarm_timer_chromeos.h
+index 1ff689e..e1301e9 100644
+--- a/components/timers/alarm_timer_chromeos.h
++++ b/components/timers/alarm_timer_chromeos.h
+@@ -8,6 +8,7 @@
+ #include <memory>
+
+ #include "base/files/file_descriptor_watcher_posix.h"
++#include "base/files/scoped_file.h"
+ #include "base/macros.h"
+ #include "base/memory/scoped_refptr.h"
+ #include "base/memory/weak_ptr.h"
+@@ -24,8 +25,7 @@ namespace timers {
+ // suspended state. For example, this is useful for running tasks that are
+ // needed for maintaining network connectivity, like sending heartbeat messages.
+ // Currently, this feature is only available on Chrome OS systems running linux
+-// version 3.11 or higher. On all other platforms, the SimpleAlarmTimer behaves
+-// exactly the same way as a regular Timer.
++// version 3.11 or higher.
+ //
+ // A SimpleAlarmTimer instance can only be used from the sequence on which it
+ // was instantiated. Start() and Stop() must be called from a thread that
+@@ -36,7 +36,16 @@ namespace timers {
+ // times but not at a regular interval.
+ class SimpleAlarmTimer : public base::RetainingOneShotTimer {
+ public:
+ SimpleAlarmTimer();
++ // Creates the SimpleAlarmTimer instance, or returns null on failure, e.g.,
++ // on a platform without timerfd_* system calls support, or missing
++ // capability (CAP_WAKE_ALARM).
++ static std::unique_ptr<SimpleAlarmTimer> Create();
++
++ // Similar to Create(), but for unittests without capability.
++ // Specifically, uses CLOCK_REALTIME instead of CLOCK_REALTIME_ALARM.
++ static std::unique_ptr<SimpleAlarmTimer> CreateForTesting();
++
+ ~SimpleAlarmTimer() override;
+
+ // Timer overrides.
+@@ -44,6 +52,11 @@ class SimpleAlarmTimer : public base::RetainingOneShotTimer {
+ void Reset() override;
+
+ private:
++ // Shared implementation of Create and CreateForTesting.
++ static std::unique_ptr<SimpleAlarmTimer> CreateInternal(int clockid);
++
++ explicit SimpleAlarmTimer(base::ScopedFD alarm_fd);
++
+ // Called when |alarm_fd_| is readable without blocking. Reads data from
+ // |alarm_fd_| and calls OnTimerFired().
+ void OnAlarmFdReadableWithoutBlocking();
+@@ -61,5 +74,5 @@ class SimpleAlarmTimer : public base::RetainingOneShotTimer {
+ // Timer file descriptor.
+- const int alarm_fd_;
++ const base::ScopedFD alarm_fd_;
+
+ // Watches |alarm_fd_|.
+ std::unique_ptr<base::FileDescriptorWatcher::Controller> alarm_fd_watcher_;
+--
+2.22.0.rc2.383.gf4fbbf30c2-goog
+
diff --git a/libchrome_tools/patches/WaitForServiceToBeAvailable.patch b/libchrome_tools/patches/WaitForServiceToBeAvailable.patch
new file mode 100644
index 0000000000..f8f2b63cc2
--- /dev/null
+++ b/libchrome_tools/patches/WaitForServiceToBeAvailable.patch
@@ -0,0 +1,45 @@
+From eadafb5815145a401cbfa6da559de01cb35d3a2b Mon Sep 17 00:00:00 2001
+From: Qijiang Fan <fqj@chromium.org>
+Date: Tue, 17 Dec 2019 17:21:54 +0900
+Subject: [PATCH 3/5] mock object proxy WaitForServiceToBeAvailable
+
+Change-Id: I01d2e49547f0e9083df60de69ef254761de2c00e
+---
+ dbus/mock_object_proxy.cc | 5 +++++
+ dbus/mock_object_proxy.h | 6 +++++++
+ 2 files changed, 11 insertions(+)
+
+diff --git a/dbus/mock_object_proxy.cc b/dbus/mock_object_proxy.cc
+index 4929486..a1f2edd 100644
+--- a/dbus/mock_object_proxy.cc
++++ b/dbus/mock_object_proxy.cc
+@@ -45,4 +45,9 @@ void MockObjectProxy::ConnectToSignal(
+ &on_connected_callback);
+ }
+
++void MockObjectProxy::WaitForServiceToBeAvailable(
++ WaitForServiceToBeAvailableCallback callback) {
++ DoWaitForServiceToBeAvailable(&callback);
++}
++
+ } // namespace dbus
+diff --git a/dbus/mock_object_proxy.h b/dbus/mock_object_proxy.h
+index 7bc2376..abc793a 100644
+--- a/dbus/mock_object_proxy.h
++++ b/dbus/mock_object_proxy.h
+@@ -89,6 +90,12 @@ class MockObjectProxy : public ObjectProxy {
+ OnConnectedCallback* on_connected_callback));
+ MOCK_METHOD1(SetNameOwnerChangedCallback,
+ void(NameOwnerChangedCallback callback));
++ // This method is not mockable because it takes a move-only argument. To work
++ // around this. WaitForServiceToBeAvailable implements here calls
++ // DoWaitForServiceToBeAvailable which is mockable.
++ void WaitForServiceToBeAvailable(WaitForServiceToBeAvailableCallback callback) override;
++ MOCK_METHOD1(DoWaitForServiceToBeAvailable,
++ void(WaitForServiceToBeAvailableCallback* callback));
+ MOCK_METHOD0(Detach, void());
+
+ protected:
+--
+2.24.1.735.g03f4e72817-goog
+
diff --git a/libchrome_tools/patches/components-timers-fix-fd-leak-in-AlarmTimer.patch b/libchrome_tools/patches/components-timers-fix-fd-leak-in-AlarmTimer.patch
new file mode 100644
index 0000000000..e98c6918dc
--- /dev/null
+++ b/libchrome_tools/patches/components-timers-fix-fd-leak-in-AlarmTimer.patch
@@ -0,0 +1,102 @@
+From e18f110107399803dd070088c601f9a5540a2a3f Mon Sep 17 00:00:00 2001
+From: Hidehiko Abe <hidehiko@chromium.org>
+Date: Thu, 3 Oct 2019 02:04:53 +0900
+Subject: [PATCH] components/timers: fix fd leak in AlarmTimer
+
+This is fixed upstream but will take a while to roll back
+into Chrome OS.
+
+BUG=chromium:984593
+TEST=enable/disable wifi repeatedly and see that there is no
+ growth of open timerfds
+
+Change-Id: If2af8f8ddf6b9dc31cda36fe5f5454ca0c5819de
+---
+ components/timers/alarm_timer_chromeos.cc | 8 ++++----
+ components/timers/alarm_timer_chromeos.h | 15 ++++++++-------
+ 2 files changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/components/timers/alarm_timer_chromeos.cc b/components/timers/alarm_timer_chromeos.cc
+index 0b43134..f14d889 100644
+--- a/components/timers/alarm_timer_chromeos.cc
++++ b/components/timers/alarm_timer_chromeos.cc
+@@ -80,7 +80,7 @@ void SimpleAlarmTimer::Reset() {
+ alarm_time.it_value.tv_nsec =
+ (delay.InMicroseconds() % base::Time::kMicrosecondsPerSecond) *
+ base::Time::kNanosecondsPerMicrosecond;
+- if (timerfd_settime(alarm_fd_, 0, &alarm_time, NULL) < 0)
++ if (timerfd_settime(alarm_fd_.get(), 0, &alarm_time, NULL) < 0)
+ PLOG(ERROR) << "Error while setting alarm time. Timer will not fire";
+
+ // The timer is running.
+@@ -97,7 +97,7 @@ void SimpleAlarmTimer::Reset() {
+ base::debug::TaskAnnotator().WillQueueTask("SimpleAlarmTimer::Reset",
+ pending_task_.get());
+ alarm_fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(
+- alarm_fd_,
++ alarm_fd_.get(),
+ base::BindRepeating(&SimpleAlarmTimer::OnAlarmFdReadableWithoutBlocking,
+ weak_factory_.GetWeakPtr()));
+ }
+@@ -109,7 +109,7 @@ void SimpleAlarmTimer::OnAlarmFdReadableWithoutBlocking() {
+
+ // Read from |alarm_fd_| to ack the event.
+ char val[sizeof(uint64_t)];
+- if (!base::ReadFromFD(alarm_fd_, val, sizeof(uint64_t)))
++ if (!base::ReadFromFD(alarm_fd_.get(), val, sizeof(uint64_t)))
+ PLOG(DFATAL) << "Unable to read from timer file descriptor.";
+
+ OnTimerFired();
+@@ -137,7 +137,7 @@ void SimpleAlarmTimer::OnTimerFired() {
+ }
+
+ bool SimpleAlarmTimer::CanWakeFromSuspend() const {
+- return alarm_fd_ != -1;
++ return alarm_fd_.is_valid();
+ }
+
+ } // namespace timers
+diff --git a/components/timers/alarm_timer_chromeos.h b/components/timers/alarm_timer_chromeos.h
+index 1ff689e..100ba81 100644
+--- a/components/timers/alarm_timer_chromeos.h
++++ b/components/timers/alarm_timer_chromeos.h
+@@ -8,6 +8,7 @@
+ #include <memory>
+
+ #include "base/files/file_descriptor_watcher_posix.h"
++#include "base/files/scoped_file.h"
+ #include "base/macros.h"
+ #include "base/memory/scoped_refptr.h"
+ #include "base/memory/weak_ptr.h"
+@@ -43,6 +44,12 @@ class SimpleAlarmTimer : public base::RetainingOneShotTimer {
+ void Stop() override;
+ void Reset() override;
+
++ // Tracks whether the timer has the ability to wake the system up from
++ // suspend. This is a runtime check because we won't know if the system
++ // supports being woken up from suspend until the constructor actually tries
++ // to set it up.
++ bool CanWakeFromSuspend() const;
++
+ private:
+ // Called when |alarm_fd_| is readable without blocking. Reads data from
+ // |alarm_fd_| and calls OnTimerFired().
+@@ -51,14 +58,8 @@ class SimpleAlarmTimer : public base::RetainingOneShotTimer {
+ // Called when the timer fires. Runs the callback.
+ void OnTimerFired();
+
+- // Tracks whether the timer has the ability to wake the system up from
+- // suspend. This is a runtime check because we won't know if the system
+- // supports being woken up from suspend until the constructor actually tries
+- // to set it up.
+- bool CanWakeFromSuspend() const;
+-
+ // Timer file descriptor.
+- const int alarm_fd_;
++ base::ScopedFD alarm_fd_;
+
+ // Watches |alarm_fd_|.
+ std::unique_ptr<base::FileDescriptorWatcher::Controller> alarm_fd_watcher_;
+--
+2.23.0.581.g78d2f28ef7-goog
+
diff --git a/libchrome_tools/patches/dbus-Add-TryRegisterFallback.patch b/libchrome_tools/patches/dbus-Add-TryRegisterFallback.patch
new file mode 100644
index 0000000000..b0ef0b8156
--- /dev/null
+++ b/libchrome_tools/patches/dbus-Add-TryRegisterFallback.patch
@@ -0,0 +1,137 @@
+From a4d8dee3905d784f40d8f480eaacae2655e68b47 Mon Sep 17 00:00:00 2001
+From: Sonny Sasaka <sonnysasaka@chromium.org>
+Date: Wed, 11 Jul 2018 20:55:58 -0700
+Subject: [PATCH] dbus: Add TryRegisterFallback
+
+The TryRegisterFallback works just like TryRegisterObjectPath,
+with addition that the registered callbacks also apply to the specified
+object path and its sub paths. This is useful to implement a "catch-all"
+handler.
+
+Currently this is needed by Bluetooth dispatcher which needs to catch
+all method calls to all objects and forward them to another D-Bus
+service.
+
+Bug:862849
+---
+ dbus/bus.cc | 27 +++++++++++++++++++++------
+ dbus/bus.h | 32 ++++++++++++++++++++++++++++++++
+ dbus/mock_bus.h | 5 +++++
+ 3 files changed, 58 insertions(+), 6 deletions(-)
+
+diff --git a/dbus/bus.cc b/dbus/bus.cc
+index e62058e0dc34..29043609e760 100644
+--- a/dbus/bus.cc
++++ b/dbus/bus.cc
+@@ -722,6 +722,25 @@ bool Bus::TryRegisterObjectPath(const ObjectPath& object_path,
+ const DBusObjectPathVTable* vtable,
+ void* user_data,
+ DBusError* error) {
++ return TryRegisterObjectPathInternal(
++ object_path, vtable, user_data, error,
++ dbus_connection_try_register_object_path);
++}
++
++bool Bus::TryRegisterFallback(const ObjectPath& object_path,
++ const DBusObjectPathVTable* vtable,
++ void* user_data,
++ DBusError* error) {
++ return TryRegisterObjectPathInternal(object_path, vtable, user_data, error,
++ dbus_connection_try_register_fallback);
++}
++
++bool Bus::TryRegisterObjectPathInternal(
++ const ObjectPath& object_path,
++ const DBusObjectPathVTable* vtable,
++ void* user_data,
++ DBusError* error,
++ TryRegisterObjectPathFunction* register_function) {
+ DCHECK(connection_);
+ AssertOnDBusThread();
+
+@@ -731,12 +750,8 @@ bool Bus::TryRegisterObjectPath(const ObjectPath& object_path,
+ return false;
+ }
+
+- const bool success = dbus_connection_try_register_object_path(
+- connection_,
+- object_path.value().c_str(),
+- vtable,
+- user_data,
+- error);
++ const bool success = register_function(
++ connection_, object_path.value().c_str(), vtable, user_data, error);
+ if (success)
+ registered_object_paths_.insert(object_path);
+ return success;
+diff --git a/dbus/bus.h b/dbus/bus.h
+index c2c2685afdc4..704a4c3a0b54 100644
+--- a/dbus/bus.h
++++ b/dbus/bus.h
+@@ -520,6 +520,24 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> {
+ void* user_data,
+ DBusError* error);
+
++ // Tries to register the object path and its sub paths.
++ // Returns true on success.
++ // Returns false if the object path is already registered.
++ //
++ // |message_function| in |vtable| will be called every time when a new
++ // message sent to the object path (or hierarchically below) arrives.
++ //
++ // The same object path must not be added more than once.
++ //
++ // See also documentation of |dbus_connection_try_register_fallback| at
++ // http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
++ //
++ // BLOCKING CALL.
++ virtual bool TryRegisterFallback(const ObjectPath& object_path,
++ const DBusObjectPathVTable* vtable,
++ void* user_data,
++ DBusError* error);
++
+ // Unregister the object path.
+ //
+ // BLOCKING CALL.
+@@ -590,8 +608,22 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> {
+ virtual ~Bus();
+
+ private:
++ using TryRegisterObjectPathFunction =
++ dbus_bool_t(DBusConnection* connection,
++ const char* object_path,
++ const DBusObjectPathVTable* vtable,
++ void* user_data,
++ DBusError* error);
++
+ friend class base::RefCountedThreadSafe<Bus>;
+
++ bool TryRegisterObjectPathInternal(
++ const ObjectPath& object_path,
++ const DBusObjectPathVTable* vtable,
++ void* user_data,
++ DBusError* error,
++ TryRegisterObjectPathFunction* register_function);
++
+ // Helper function used for RemoveObjectProxy().
+ void RemoveObjectProxyInternal(scoped_refptr<dbus::ObjectProxy> object_proxy,
+ const base::Closure& callback);
+diff --git a/dbus/mock_bus.h b/dbus/mock_bus.h
+index 216bc64bd068..6b3495db6014 100644
+--- a/dbus/mock_bus.h
++++ b/dbus/mock_bus.h
+@@ -62,6 +62,11 @@ class MockBus : public Bus {
+ const DBusObjectPathVTable* vtable,
+ void* user_data,
+ DBusError* error));
++ MOCK_METHOD4(TryRegisterFallback,
++ bool(const ObjectPath& object_path,
++ const DBusObjectPathVTable* vtable,
++ void* user_data,
++ DBusError* error));
+ MOCK_METHOD1(UnregisterObjectPath, void(const ObjectPath& object_path));
+ MOCK_METHOD0(GetDBusTaskRunner, base::TaskRunner*());
+ MOCK_METHOD0(GetOriginTaskRunner, base::TaskRunner*());
+--
+2.18.0.203.gfac676dfb9-goog
+
diff --git a/libchrome_tools/patches/dbus-Make-Bus-is_connected-mockable.patch b/libchrome_tools/patches/dbus-Make-Bus-is_connected-mockable.patch
new file mode 100644
index 0000000000..3dd2c8869e
--- /dev/null
+++ b/libchrome_tools/patches/dbus-Make-Bus-is_connected-mockable.patch
@@ -0,0 +1,93 @@
+From 59bc1c00682f45d54a05aadc2a2e0559f85e4499 Mon Sep 17 00:00:00 2001
+From: Sonny Sasaka <sonnysasaka@chromium.org>
+Date: Thu, 16 Aug 2018 05:09:20 +0000
+Subject: [PATCH] dbus: Make Bus::is_connected() mockable
+
+It's currently not possible to have a unit test that triggers
+Bus::is_connected() because it always returns false. This is currently
+needed by the Bluetooth dispatcher (btdispatch) in Chrome OS.
+
+Bug: 866704
+Change-Id: I04f7e8a22792886d421479c1c7c621eeb27d3a2a
+Reviewed-on: https://chromium-review.googlesource.com/1175216
+Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
+Commit-Queue: Sonny Sasaka <sonnysasaka@chromium.org>
+Cr-Commit-Position: refs/heads/master@{#583543}
+---
+ dbus/bus.cc | 4 ++++
+ dbus/bus.h | 2 +-
+ dbus/bus_unittest.cc | 4 ++--
+ dbus/exported_object.cc | 2 +-
+ dbus/mock_bus.h | 1 +
+ dbus/object_proxy.cc | 2 +-
+ 6 files changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/dbus/bus.cc b/dbus/bus.cc
+index 2f3db885f561..9d37656ac21c 100644
+--- a/dbus/bus.cc
++++ b/dbus/bus.cc
+@@ -997,6 +997,10 @@ std::string Bus::GetConnectionName() {
+ return dbus_bus_get_unique_name(connection_);
+ }
+
++bool Bus::IsConnected() {
++ return connection_ != nullptr;
++}
++
+ dbus_bool_t Bus::OnAddWatch(DBusWatch* raw_watch) {
+ AssertOnDBusThread();
+
+diff --git a/dbus/bus.h b/dbus/bus.h
+index 704a4c3a0b54..b082110e589b 100644
+--- a/dbus/bus.h
++++ b/dbus/bus.h
+@@ -601,7 +601,7 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> {
+ std::string GetConnectionName();
+
+ // Returns true if the bus is connected to D-Bus.
+- bool is_connected() { return connection_ != nullptr; }
++ virtual bool IsConnected();
+
+ protected:
+ // This is protected, so we can define sub classes.
+diff --git a/dbus/exported_object.cc b/dbus/exported_object.cc
+index d6c91b6d2046..5fa1b916f251 100644
+--- a/dbus/exported_object.cc
++++ b/dbus/exported_object.cc
+@@ -280,7 +280,7 @@ void ExportedObject::OnMethodCompleted(std::unique_ptr<MethodCall> method_call,
+
+ // Check if the bus is still connected. If the method takes long to
+ // complete, the bus may be shut down meanwhile.
+- if (!bus_->is_connected())
++ if (!bus_->IsConnected())
+ return;
+
+ if (!response) {
+diff --git a/dbus/mock_bus.h b/dbus/mock_bus.h
+index 6b3495db6014..22807622786a 100644
+--- a/dbus/mock_bus.h
++++ b/dbus/mock_bus.h
+@@ -73,6 +73,7 @@ class MockBus : public Bus {
+ MOCK_METHOD0(HasDBusThread, bool());
+ MOCK_METHOD0(AssertOnOriginThread, void());
+ MOCK_METHOD0(AssertOnDBusThread, void());
++ MOCK_METHOD0(IsConnected, bool());
+
+ protected:
+ ~MockBus() override;
+diff --git a/dbus/object_proxy.cc b/dbus/object_proxy.cc
+index aa5102aec792..3046dbb5f38b 100644
+--- a/dbus/object_proxy.cc
++++ b/dbus/object_proxy.cc
+@@ -288,7 +288,7 @@ void ObjectProxy::WaitForServiceToBeAvailable(
+ void ObjectProxy::Detach() {
+ bus_->AssertOnDBusThread();
+
+- if (bus_->is_connected())
++ if (bus_->IsConnected())
+ bus_->RemoveFilterFunction(&ObjectProxy::HandleMessageThunk, this);
+
+ for (const auto& match_rule : match_rules_) {
+--
+2.21.0.392.gf8f6787159e-goog
+
diff --git a/libchrome_tools/patches/dbus-Remove-LOG-ERROR-in-ObjectProxy.patch b/libchrome_tools/patches/dbus-Remove-LOG-ERROR-in-ObjectProxy.patch
new file mode 100644
index 0000000000..62bdd940e2
--- /dev/null
+++ b/libchrome_tools/patches/dbus-Remove-LOG-ERROR-in-ObjectProxy.patch
@@ -0,0 +1,58 @@
+From ad887b5aaf4f834dbbd487adfe89d9a5b3d673f2 Mon Sep 17 00:00:00 2001
+From: Sonny Sasaka <sonnysasaka@chromium.org>
+Date: Thu, 9 Aug 2018 22:39:57 +0000
+Subject: [PATCH] dbus: Remove LOG(ERROR) in ObjectProxy
+
+It is a valid use case for a daemon to have multiple ObjectProxies of
+different services with the exact same object path and interface name.
+Currently, this may cause log pollution of "rejecting a message from a
+wrong sender" because one ObjectProxy receives signals intended for
+another ObjectProxy. Since it's actually a valid case and not a bug, it
+shouldn't be logged as error but it may still be logged with VLOG.
+
+Currently this is discovered in Bluetooth daemon (btdispatch) because it
+listens to both BlueZ's and Newblue's objects which have identical
+object paths and interfaces.
+
+Bug: 866704
+Change-Id: I25b6437ec6081e244a47c635c0adedf281530967
+Reviewed-on: https://chromium-review.googlesource.com/1164474
+Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
+Commit-Queue: Sonny Sasaka <sonnysasaka@chromium.org>
+Cr-Commit-Position: refs/heads/master@{#581937}
+---
+ dbus/object_proxy.cc | 11 +++++------
+ 1 file changed, 5 insertions(+), 6 deletions(-)
+
+diff --git a/dbus/object_proxy.cc b/dbus/object_proxy.cc
+index 35835fbdc32c..aa5102aec792 100644
+--- a/dbus/object_proxy.cc
++++ b/dbus/object_proxy.cc
+@@ -519,6 +519,11 @@ DBusHandlerResult ObjectProxy::HandleMessage(
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
++ std::string sender = signal->GetSender();
++ // Ignore message from sender we are not interested in.
++ if (service_name_owner_ != sender)
++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++
+ const std::string interface = signal->GetInterface();
+ const std::string member = signal->GetMember();
+
+@@ -534,12 +539,6 @@ DBusHandlerResult ObjectProxy::HandleMessage(
+ }
+ VLOG(1) << "Signal received: " << signal->ToString();
+
+- std::string sender = signal->GetSender();
+- if (service_name_owner_ != sender) {
+- LOG(ERROR) << "Rejecting a message from a wrong sender.";
+- return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+- }
+-
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+ if (bus_->HasDBusThread()) {
+ // Post a task to run the method in the origin thread.
+--
+2.21.0.392.gf8f6787159e-goog
+
diff --git a/libchrome_tools/patches/dbus-Support-UnexportMethod-from-an-exported-object.patch b/libchrome_tools/patches/dbus-Support-UnexportMethod-from-an-exported-object.patch
new file mode 100644
index 0000000000..1a797bf2a9
--- /dev/null
+++ b/libchrome_tools/patches/dbus-Support-UnexportMethod-from-an-exported-object.patch
@@ -0,0 +1,194 @@
+From fb915ed71679feafd4ed53deb2c5ba84862a9e57 Mon Sep 17 00:00:00 2001
+From: Sonny Sasaka <sonnysasaka@chromium.org>
+Date: Mon, 10 Dec 2018 14:03:49 -0800
+Subject: [PATCH] dbus: Support UnexportMethod from an exported object.
+
+Currently there is no way to override a method handler that is already
+registered to an ExportedObject. A support to do so is required to
+correctly implement Chrome OS Bluetooth dispatcher which needs to
+add/remove an interface to an exported object dynamically. Therefore
+this CL adds methods to allow method handlers to be unexported so
+another handler can be exported afterwards.
+
+Bug: 883039
+---
+ dbus/exported_object.cc | 50 +++++++++++++++++++++++++++++++++++++
+ dbus/exported_object.h | 34 +++++++++++++++++++++++++
+ dbus/mock_exported_object.h | 7 ++++++
+ 3 files changed, 91 insertions(+)
+
+diff --git a/dbus/exported_object.cc b/dbus/exported_object.cc
+index 5fa1b916f251..727a5707b869 100644
+--- a/dbus/exported_object.cc
++++ b/dbus/exported_object.cc
+@@ -68,6 +68,22 @@ bool ExportedObject::ExportMethodAndBlock(
+ return true;
+ }
+
++bool ExportedObject::UnexportMethodAndBlock(const std::string& interface_name,
++ const std::string& method_name) {
++ bus_->AssertOnDBusThread();
++
++ const std::string absolute_method_name =
++ GetAbsoluteMemberName(interface_name, method_name);
++ if (method_table_.find(absolute_method_name) == method_table_.end()) {
++ LOG(ERROR) << absolute_method_name << " is not exported";
++ return false;
++ }
++
++ method_table_.erase(absolute_method_name);
++
++ return true;
++}
++
+ void ExportedObject::ExportMethod(const std::string& interface_name,
+ const std::string& method_name,
+ MethodCallCallback method_call_callback,
+@@ -83,6 +99,18 @@ void ExportedObject::ExportMethod(const std::string& interface_name,
+ bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, task);
+ }
+
++void ExportedObject::UnexportMethod(
++ const std::string& interface_name,
++ const std::string& method_name,
++ OnUnexportedCallback on_unexported_calback) {
++ bus_->AssertOnOriginThread();
++
++ base::Closure task =
++ base::Bind(&ExportedObject::UnexportMethodInternal, this, interface_name,
++ method_name, on_unexported_calback);
++ bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, task);
++}
++
+ void ExportedObject::SendSignal(Signal* signal) {
+ // For signals, the object path should be set to the path to the sender
+ // object, which is this exported object here.
+@@ -141,6 +169,19 @@ void ExportedObject::ExportMethodInternal(
+ success));
+ }
+
++void ExportedObject::UnexportMethodInternal(
++ const std::string& interface_name,
++ const std::string& method_name,
++ OnUnexportedCallback on_unexported_calback) {
++ bus_->AssertOnDBusThread();
++
++ const bool success = UnexportMethodAndBlock(interface_name, method_name);
++ bus_->GetOriginTaskRunner()->PostTask(
++ FROM_HERE,
++ base::Bind(&ExportedObject::OnUnexported, this, on_unexported_calback,
++ interface_name, method_name, success));
++}
++
+ void ExportedObject::OnExported(OnExportedCallback on_exported_callback,
+ const std::string& interface_name,
+ const std::string& method_name,
+@@ -150,6 +191,15 @@ void ExportedObject::OnExported(OnExportedCallback on_exported_callback,
+ on_exported_callback.Run(interface_name, method_name, success);
+ }
+
++void ExportedObject::OnUnexported(OnExportedCallback on_unexported_callback,
++ const std::string& interface_name,
++ const std::string& method_name,
++ bool success) {
++ bus_->AssertOnOriginThread();
++
++ on_unexported_callback.Run(interface_name, method_name, success);
++}
++
+ void ExportedObject::SendSignalInternal(base::TimeTicks start_time,
+ DBusMessage* signal_message) {
+ uint32_t serial = 0;
+diff --git a/dbus/exported_object.h b/dbus/exported_object.h
+index 69a63a5e075e..d314083430ef 100644
+--- a/dbus/exported_object.h
++++ b/dbus/exported_object.h
+@@ -60,6 +60,13 @@ class CHROME_DBUS_EXPORT ExportedObject
+ bool success)>
+ OnExportedCallback;
+
++ // Called when method unexporting is done.
++ // |success| indicates whether unexporting was successful or not.
++ typedef base::Callback<void(const std::string& interface_name,
++ const std::string& method_name,
++ bool success)>
++ OnUnexportedCallback;
++
+ // Exports the method specified by |interface_name| and |method_name|,
+ // and blocks until exporting is done. Returns true on success.
+ //
+@@ -81,6 +88,11 @@ class CHROME_DBUS_EXPORT ExportedObject
+ const std::string& method_name,
+ MethodCallCallback method_call_callback);
+
++ // Unexports the method specified by |interface_name| and |method_name|,
++ // and blocks until unexporting is done. Returns true on success.
++ virtual bool UnexportMethodAndBlock(const std::string& interface_name,
++ const std::string& method_name);
++
+ // Requests to export the method specified by |interface_name| and
+ // |method_name|. See Also ExportMethodAndBlock().
+ //
+@@ -93,6 +105,17 @@ class CHROME_DBUS_EXPORT ExportedObject
+ MethodCallCallback method_call_callback,
+ OnExportedCallback on_exported_callback);
+
++ // Requests to unexport the method specified by |interface_name| and
++ // |method_name|. See also UnexportMethodAndBlock().
++ //
++ // |on_unexported_callback| is called when the method is unexported or
++ // failed to be unexported, in the origin thread.
++ //
++ // Must be called in the origin thread.
++ virtual void UnexportMethod(const std::string& interface_name,
++ const std::string& method_name,
++ OnUnexportedCallback on_unexported_callback);
++
+ // Requests to send the signal from this object. The signal will be sent
+ // synchronously if this method is called from the message loop in the D-Bus
+ // thread and asynchronously otherwise.
+@@ -117,12 +140,23 @@ class CHROME_DBUS_EXPORT ExportedObject
+ MethodCallCallback method_call_callback,
+ OnExportedCallback exported_callback);
+
++ // Helper function for UnexportMethod().
++ void UnexportMethodInternal(const std::string& interface_name,
++ const std::string& method_name,
++ OnUnexportedCallback unexported_callback);
++
+ // Called when the object is exported.
+ void OnExported(OnExportedCallback on_exported_callback,
+ const std::string& interface_name,
+ const std::string& method_name,
+ bool success);
+
++ // Called when a method is unexported.
++ void OnUnexported(OnExportedCallback on_unexported_callback,
++ const std::string& interface_name,
++ const std::string& method_name,
++ bool success);
++
+ // Helper function for SendSignal().
+ void SendSignalInternal(base::TimeTicks start_time,
+ DBusMessage* signal_message);
+diff --git a/dbus/mock_exported_object.h b/dbus/mock_exported_object.h
+index 99c363f9b532..9d5b3a894179 100644
+--- a/dbus/mock_exported_object.h
++++ b/dbus/mock_exported_object.h
+@@ -28,6 +28,13 @@ class MockExportedObject : public ExportedObject {
+ const std::string& method_name,
+ MethodCallCallback method_call_callback,
+ OnExportedCallback on_exported_callback));
++ MOCK_METHOD2(UnexportMethodAndBlock,
++ bool(const std::string& interface_name,
++ const std::string& method_name));
++ MOCK_METHOD3(UnexportMethod,
++ void(const std::string& interface_name,
++ const std::string& method_name,
++ OnUnexportedCallback on_unexported_callback));
+ MOCK_METHOD1(SendSignal, void(Signal* signal));
+ MOCK_METHOD0(Unregister, void());
+
+--
+2.20.0.rc2.403.gdbc3b29805-goog
+
diff --git a/libchrome_tools/patches/enable-location-source.patch b/libchrome_tools/patches/enable-location-source.patch
new file mode 100644
index 0000000000..adcafb4c89
--- /dev/null
+++ b/libchrome_tools/patches/enable-location-source.patch
@@ -0,0 +1,26 @@
+From 8f8b73113f4e785543129c806d0e57bcf13d2314 Mon Sep 17 00:00:00 2001
+From: Qijiang Fan <fqj@chromium.org>
+Date: Wed, 18 Dec 2019 17:47:58 +0900
+Subject: [PATCH 4/4] enable location source
+
+Change-Id: I29fd847bc0fa0ace7a9637417a796b64b32485e3
+---
+ base/debug/debugging_buildflags.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/base/debug/debugging_buildflags.h b/base/debug/debugging_buildflags.h
+index 33e3dd0..a974f83 100644
+--- a/base/debug/debugging_buildflags.h
++++ b/base/debug/debugging_buildflags.h
+@@ -6,7 +6,7 @@
+ #define BUILDFLAG_INTERNAL_ENABLE_PROFILING() (0)
+ #define BUILDFLAG_INTERNAL_ENABLE_MEMORY_TASK_PROFILER() (0)
+ #define BUILDFLAG_INTERNAL_CAN_UNWIND_WITH_FRAME_POINTERS() (0)
+-#define BUILDFLAG_INTERNAL_ENABLE_LOCATION_SOURCE() (0)
++#define BUILDFLAG_INTERNAL_ENABLE_LOCATION_SOURCE() (1)
+ #define BUILDFLAG_INTERNAL_CFI_ENFORCEMENT_TRAP() (0)
+ #define BUILDFLAG_INTERNAL_ENABLE_MUTEX_PRIORITY_INHERITANCE() (0)
+ #endif // BASE_DEBUG_DEBUGGING_FLAGS_H_
+--
+2.24.1.735.g03f4e72817-goog
+
diff --git a/libchrome_tools/patches/fix-fd-watcher-leak.patch b/libchrome_tools/patches/fix-fd-watcher-leak.patch
new file mode 100644
index 0000000000..510d077a6d
--- /dev/null
+++ b/libchrome_tools/patches/fix-fd-watcher-leak.patch
@@ -0,0 +1,188 @@
+From 9ae1384af2cdd16539e9caaed69b095737e2c272 Mon Sep 17 00:00:00 2001
+From: Qijiang Fan <fqj@chromium.org>
+Date: Tue, 17 Dec 2019 17:32:35 +0900
+Subject: [PATCH] backport upstream patch to fix watcher leak.
+
+https://chromium-review.googlesource.com/c/chromium/src/+/695914
+
+Change-Id: I91928fc00e9845ff75c49c272ff774ff9810f4eb
+---
+ base/files/file_descriptor_watcher_posix.cc | 104 +++++++++++++++-----
+ base/threading/thread_restrictions.h | 4 +
+ 2 files changed, 82 insertions(+), 26 deletions(-)
+
+diff --git a/base/files/file_descriptor_watcher_posix.cc b/base/files/file_descriptor_watcher_posix.cc
+index b26bf6c..98d2cec 100644
+--- a/base/files/file_descriptor_watcher_posix.cc
++++ b/base/files/file_descriptor_watcher_posix.cc
+@@ -5,6 +5,7 @@
+ #include "base/files/file_descriptor_watcher_posix.h"
+
+ #include "base/bind.h"
++#include "base/callback_helpers.h"
+ #include "base/lazy_instance.h"
+ #include "base/logging.h"
+ #include "base/memory/ptr_util.h"
+@@ -12,6 +13,7 @@
+ #include "base/message_loop/message_pump_for_io.h"
+ #include "base/sequenced_task_runner.h"
+ #include "base/single_thread_task_runner.h"
++#include "base/synchronization/waitable_event.h"
+ #include "base/threading/sequenced_task_runner_handle.h"
+ #include "base/threading/thread_checker.h"
+ #include "base/threading/thread_local.h"
+@@ -27,21 +29,7 @@ LazyInstance<ThreadLocalPointer<MessageLoopForIO>>::Leaky
+
+ } // namespace
+
+-FileDescriptorWatcher::Controller::~Controller() {
+- DCHECK(sequence_checker_.CalledOnValidSequence());
+-
+- // Delete |watcher_| on the MessageLoopForIO.
+- //
+- // If the MessageLoopForIO is deleted before Watcher::StartWatching() runs,
+- // |watcher_| is leaked. If the MessageLoopForIO is deleted after
+- // Watcher::StartWatching() runs but before the DeleteSoon task runs,
+- // |watcher_| is deleted from Watcher::WillDestroyCurrentMessageLoop().
+- message_loop_for_io_task_runner_->DeleteSoon(FROM_HERE, watcher_.release());
+-
+- // Since WeakPtrs are invalidated by the destructor, RunCallback() won't be
+- // invoked after this returns.
+-}
+-
++// Move watcher above to prevent delete incomplete type at delete watcher later.
+ class FileDescriptorWatcher::Controller::Watcher
+ : public MessagePumpForIO::FdWatcher,
+ public MessageLoopCurrent::DestructionObserver {
+@@ -90,6 +78,58 @@ class FileDescriptorWatcher::Controller::Watcher
+ DISALLOW_COPY_AND_ASSIGN(Watcher);
+ };
+
++FileDescriptorWatcher::Controller::~Controller() {
++ DCHECK(sequence_checker_.CalledOnValidSequence());
++
++ if (message_loop_for_io_task_runner_->BelongsToCurrentThread()) {
++ // If the MessageLoopForIO and the Controller live on the same thread.
++ watcher_.reset();
++ } else {
++ // Synchronously wait until |watcher_| is deleted on the MessagePumpForIO
++ // thread. This ensures that the file descriptor is never accessed after
++ // this destructor returns.
++ //
++ // Use a ScopedClosureRunner to ensure that |done| is signaled even if the
++ // thread doesn't run any more tasks (if PostTask returns true, it means
++ // that the task was queued, but it doesn't mean that a RunLoop will run the
++ // task before the queue is deleted).
++ //
++ // We considered associating "generations" to file descriptors to avoid the
++ // synchronous wait. For example, if the IO thread gets a "cancel" for fd=6,
++ // generation=1 after getting a "start watching" for fd=6, generation=2, it
++ // can ignore the "Cancel". However, "generations" didn't solve this race:
++ //
++ // T1 (client) Start watching fd = 6 with WatchReadable()
++ // Stop watching fd = 6
++ // Close fd = 6
++ // Open a new file, fd = 6 gets reused.
++ // T2 (io) Watcher::StartWatching()
++ // Incorrectly starts watching fd = 6 which now refers to a
++ // different file than when WatchReadable() was called.
++ WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
++ WaitableEvent::InitialState::NOT_SIGNALED);
++ message_loop_for_io_task_runner_->PostTask(
++ FROM_HERE, BindOnce(
++ [](Watcher *watcher, ScopedClosureRunner closure) {
++ // Since |watcher| is a raw pointer, it isn't deleted
++ // if this callback is deleted before it gets to run.
++ delete watcher;
++ // |closure| runs at the end of this scope.
++ },
++ Unretained(watcher_.release()),
++ // TODO(fqj): change to BindOnce after some uprev.
++ ScopedClosureRunner(Bind(&WaitableEvent::Signal,
++ Unretained(&done)))));
++ // TODO(fqj): change to ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
++ // after uprev to r586297
++ base::ThreadRestrictions::ScopedAllowWait allow_wait __attribute__((unused));
++ done.Wait();
++ }
++
++ // Since WeakPtrs are invalidated by the destructor, RunCallback() won't be
++ // invoked after this returns.
++}
++
+ FileDescriptorWatcher::Controller::Watcher::Watcher(
+ WeakPtr<Controller> controller,
+ MessagePumpForIO::Mode mode,
+@@ -150,11 +190,19 @@ void FileDescriptorWatcher::Controller::Watcher::
+ WillDestroyCurrentMessageLoop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+- // A Watcher is owned by a Controller. When the Controller is deleted, it
+- // transfers ownership of the Watcher to a delete task posted to the
+- // MessageLoopForIO. If the MessageLoopForIO is deleted before the delete task
+- // runs, the following line takes care of deleting the Watcher.
+- delete this;
++ if (callback_task_runner_->RunsTasksInCurrentSequence()) {
++ // |controller_| can be accessed directly when Watcher runs on the same
++ // thread.
++ controller_->watcher_.reset();
++ } else {
++ // If the Watcher and the Controller live on different threads, delete
++ // |this| synchronously. Pending tasks bound to an unretained Watcher* will
++ // not run since this loop is dead. The associated Controller still
++ // technically owns this via unique_ptr but it never uses it directly and
++ // will ultimately send it to this thread for deletion (and that also won't
++ // run since the loop being dead).
++ delete this;
++ }
+ }
+
+ FileDescriptorWatcher::Controller::Controller(MessagePumpForIO::Mode mode,
+@@ -172,12 +220,16 @@ FileDescriptorWatcher::Controller::Controller(MessagePumpForIO::Mode mode,
+
+ void FileDescriptorWatcher::Controller::StartWatching() {
+ DCHECK(sequence_checker_.CalledOnValidSequence());
+- // It is safe to use Unretained() below because |watcher_| can only be deleted
+- // by a delete task posted to |message_loop_for_io_task_runner_| by this
+- // Controller's destructor. Since this delete task hasn't been posted yet, it
+- // can't run before the task posted below.
+- message_loop_for_io_task_runner_->PostTask(
+- FROM_HERE, BindOnce(&Watcher::StartWatching, Unretained(watcher_.get())));
++ if (message_loop_for_io_task_runner_->BelongsToCurrentThread()) {
++ watcher_->StartWatching();
++ } else {
++ // It is safe to use Unretained() below because |watcher_| can only be deleted
++ // by a delete task posted to |message_loop_for_io_task_runner_| by this
++ // Controller's destructor. Since this delete task hasn't been posted yet, it
++ // can't run before the task posted below.
++ message_loop_for_io_task_runner_->PostTask(
++ FROM_HERE, Bind(&Watcher::StartWatching, Unretained(watcher_.get())));
++ }
+ }
+
+ void FileDescriptorWatcher::Controller::RunCallback() {
+diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
+index 705ba4d..7532f85 100644
+--- a/base/threading/thread_restrictions.h
++++ b/base/threading/thread_restrictions.h
+@@ -147,6 +147,7 @@ namespace internal {
+ class TaskTracker;
+ }
+
++class FileDescriptorWatcher;
+ class GetAppOutputScopedAllowBaseSyncPrimitives;
+ class SimpleThread;
+ class StackSamplingProfiler;
+@@ -479,6 +480,9 @@ class BASE_EXPORT ThreadRestrictions {
+ friend class ui::CommandBufferLocal;
+ friend class ui::GpuState;
+
++ // Chrome OS libchrome
++ friend class base::FileDescriptorWatcher;
++
+ // END ALLOWED USAGE.
+ // BEGIN USAGE THAT NEEDS TO BE FIXED.
+ friend class ::chromeos::BlockingMethodCaller; // http://crbug.com/125360
+--
+2.24.1.735.g03f4e72817-goog
+
diff --git a/libchrome_tools/patches/libchrome-Add-EmptyResponseCallback-for-backward-com.patch b/libchrome_tools/patches/libchrome-Add-EmptyResponseCallback-for-backward-com.patch
new file mode 100644
index 0000000000..80c6dfaea9
--- /dev/null
+++ b/libchrome_tools/patches/libchrome-Add-EmptyResponseCallback-for-backward-com.patch
@@ -0,0 +1,42 @@
+From f34f528c1fc4512910b2acc586e678ddb53eaf9e Mon Sep 17 00:00:00 2001
+From: Hidehiko Abe <hidehiko@chromium.org>
+Date: Wed, 2 Oct 2019 09:53:00 +0900
+Subject: [PATCH] libchrome: Add EmptyResponseCallback for backward
+ compatiblity.
+
+BUG=chromium:909719
+TEST=Build locally.
+
+Change-Id: I4d6c75f267fd6c170b966647c30c91bb02b3ee14
+---
+ dbus/object_proxy.h | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/dbus/object_proxy.h b/dbus/object_proxy.h
+index 22e44f1..7d62eb9 100644
+--- a/dbus/object_proxy.h
++++ b/dbus/object_proxy.h
+@@ -13,6 +13,7 @@
+ #include <string>
+ #include <vector>
+
++#include "base/bind.h"
+ #include "base/callback.h"
+ #include "base/macros.h"
+ #include "base/memory/ref_counted.h"
+@@ -105,6 +106,12 @@ class CHROME_DBUS_EXPORT ObjectProxy
+ using OnConnectedCallback =
+ base::OnceCallback<void(const std::string&, const std::string&, bool)>;
+
++ // TOOD(crbug.com/909719): This is just for backward compatibility.
++ // Remove this callback after clients are migrated.
++ static ResponseCallback EmptyResponseCallback() {
++ return base::BindOnce([](Response*){});
++ }
++
+ // Calls the method of the remote object and blocks until the response
+ // is returned. Returns NULL on error with the error details specified
+ // in the |error| object.
+--
+2.23.0.581.g78d2f28ef7-goog
+
diff --git a/libchrome_tools/patches/libchrome-Introduce-stub-ConvertableToTraceFormat.patch b/libchrome_tools/patches/libchrome-Introduce-stub-ConvertableToTraceFormat.patch
new file mode 100644
index 0000000000..bd68077cc7
--- /dev/null
+++ b/libchrome_tools/patches/libchrome-Introduce-stub-ConvertableToTraceFormat.patch
@@ -0,0 +1,55 @@
+From f2d560ab2808c87cd60a2962037bf4dabd9781ef Mon Sep 17 00:00:00 2001
+From: Hidehiko Abe <hidehiko@chromium.org>
+Date: Fri, 14 Jun 2019 14:22:33 +0900
+Subject: [PATCH] libchrome: Introduce stub ConvertableToTraceFormat.
+
+BUG=chromium:909719
+TEST=Built locally.
+
+Change-Id: I5c849edc2c5e8370bff6a8b1b83a92e5ef5836c8
+---
+ base/trace_event/trace_event.h | 13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+diff --git a/base/trace_event/trace_event.h b/base/trace_event/trace_event.h
+index 1ce76d9..7385582 100644
+--- a/base/trace_event/trace_event.h
++++ b/base/trace_event/trace_event.h
+@@ -10,6 +10,9 @@
+ #include "base/trace_event/common/trace_event_common.h"
+ #include "base/trace_event/heap_profiler.h"
+
++// Indirectly included.
++#include "base/strings/string_util.h"
++
+ // To avoid -Wunused-* errors, eat expression by macro.
+ namespace libchrome_internal {
+ template <typename... Args> void Ignore(Args&&... args) {}
+@@ -18,8 +21,9 @@ template <typename... Args> void Ignore(Args&&... args) {}
+ (false ? libchrome_internal::Ignore(__VA_ARGS__) : (void) 0)
+
+ // Body is effectively empty.
++#define INTERNAL_TRACE_EVENT_ADD(...) INTERNAL_IGNORE(__VA_ARGS__)
+ #define INTERNAL_TRACE_EVENT_ADD_SCOPED(...) INTERNAL_IGNORE(__VA_ARGS__)
+-#define INTERNAL_TRACE_TASK_EXECUTION(...)
++#define INTERNAL_TRACE_TASK_EXECUTION(...) INTERNAL_IGNORE(__VA_ARGS__)
+ #define INTERNAL_TRACE_EVENT_ADD_SCOPED_WITH_FLOW(...) \
+ INTERNAL_IGNORE(__VA_ARGS__)
+ #define TRACE_ID_MANGLE(val) (val)
+@@ -38,6 +42,13 @@ class TraceLog {
+ void SetCurrentThreadBlocksMessageLoop() {}
+ };
+
++class BASE_EXPORT ConvertableToTraceFormat {
++ public:
++ ConvertableToTraceFormat() = default;
++ virtual ~ConvertableToTraceFormat() = default;
++ virtual void AppendAsTraceFormat(std::string* out) const {};
++};
++
+ } // namespace trace_event
+ } // namespace base
+ #else
+--
+2.22.0.410.gd8fdbe21b5-goog
+
diff --git a/libchrome_tools/patches/libchrome-Remove-glib-dependency.patch b/libchrome_tools/patches/libchrome-Remove-glib-dependency.patch
new file mode 100644
index 0000000000..418dba196a
--- /dev/null
+++ b/libchrome_tools/patches/libchrome-Remove-glib-dependency.patch
@@ -0,0 +1,31 @@
+[200~From 78a4103946854e4e526b0cf407f0b09610b40988 Mon Sep 17 00:00:00 2001
+From: hscham <hscham@chromium.org>
+Date: Mon, 1 Jun 2020 16:50:16 +0900
+Subject: [PATCH] libchrome: Remove glib dependency.
+
+BUG=chromium:361635
+TEST=Ran Pre-CQ.
+
+Change-Id: I8a583851d51c5eaa24bb1506d45d6566f760595c
+---
+ build/build_config.h | 4 ----
+ 1 file changed, 4 deletions(-)
+
+diff --git a/build/build_config.h b/build/build_config.h
+index 0a0024088..b0e2aa26e 100644
+--- a/build/build_config.h
++++ b/build/build_config.h
+@@ -39,10 +39,6 @@
+ #elif !defined(__ANDROID_HOST__) // Chrome OS
+
+ #define OS_CHROMEOS 1
+-// TODO: Remove these once the GLib MessageLoopForUI isn't being used:
+-// https://crbug.com/361635
+-#define USE_GLIB 1
+-#define USE_OZONE 1
+
+ #endif // defined(__ANDROID__)
+
+--
+2.27.0.rc2.251.g90737beb825-goog
+
diff --git a/libchrome_tools/patches/libchrome-Unpatch-sys.path-update.patch b/libchrome_tools/patches/libchrome-Unpatch-sys.path-update.patch
new file mode 100644
index 0000000000..89d1244792
--- /dev/null
+++ b/libchrome_tools/patches/libchrome-Unpatch-sys.path-update.patch
@@ -0,0 +1,41 @@
+From 702acfbe733e5b047759a3bfee71c8b63e5814fe Mon Sep 17 00:00:00 2001
+From: Hidehiko Abe <hidehiko@chromium.org>
+Date: Thu, 13 Jun 2019 23:02:42 +0900
+Subject: [PATCH] libchrome: Unpatch sys.path update.
+
+Unlike soong build, original sys.path change works.
+
+BUG=chromium:909719
+TEST=Built locally.
+
+Change-Id: I6c8ac8d4a1d156ddd77ec75485d42a810a9e61fe
+---
+ build/android/gyp/util/build_utils.py | 12 ++----------
+ 1 file changed, 2 insertions(+), 10 deletions(-)
+
+diff --git a/build/android/gyp/util/build_utils.py b/build/android/gyp/util/build_utils.py
+index 426de03..f1764b9 100644
+--- a/build/android/gyp/util/build_utils.py
++++ b/build/android/gyp/util/build_utils.py
+@@ -25,16 +25,8 @@ import zipfile
+ # Some clients do not add //build/android/gyp to PYTHONPATH.
+ import md5_check # pylint: disable=relative-import
+
+-# pylib conflicts with mojo/public/tools/bindings/pylib. Prioritize
+-# build/android/pylib.
+-# PYTHONPATH wouldn't help in this case, because soong put source files under
+-# temp directory for each build, so the abspath is unknown until the
+-# execution.
+-#sys.path.append(os.path.join(os.path.dirname(__file__),
+-# os.pardir, os.pardir, os.pardir))
+-sys.path.insert(0, os.path.join(os.path.dirname(__file__),
+- os.pardir, os.pardir))
+-
++sys.path.append(os.path.join(os.path.dirname(__file__),
++ os.pardir, os.pardir, os.pardir))
+ import gn_helpers
+
+ # Definition copied from pylib/constants/__init__.py to avoid adding
+--
+2.22.0.rc2.383.gf4fbbf30c2-goog
+
diff --git a/libchrome_tools/patches/libchrome-Update-crypto.patch b/libchrome_tools/patches/libchrome-Update-crypto.patch
new file mode 100644
index 0000000000..45991605cd
--- /dev/null
+++ b/libchrome_tools/patches/libchrome-Update-crypto.patch
@@ -0,0 +1,182 @@
+From 23ecc2149133fa0cf369f53b5d7d28e78815bca3 Mon Sep 17 00:00:00 2001
+From: Hidehiko Abe <hidehiko@chromium.org>
+Date: Thu, 13 Jun 2019 22:27:41 +0900
+Subject: [PATCH] libchrome: Update crypto.
+
+libchrome uses OpenSSH, instead of BoringSSH, although its support
+was dropped in Chrome already.
+To lengthen its lifetime, this patch adds minor fix.
+- Use base::data() instead of base::string_as_array().
+ The method was removed. cf) crrev.com/c/1056014.
+- Use base::PostTaskAndReply instead of base::WorkerPool.
+ The class was removed. cf) crrev.com/c/650368
+- tracked_object::Location is renamed to base::Location.
+ cf) crbug.com/763556
+- base::Location::Write was removed. Use ToString().
+ cf) crrev.com/c/707310
+- base::MakeUnique was removed. Use std::make_unique.
+ cf) crrev.com/c/944048
+
+BUG=chromium:909719
+TEST=Built locally.
+
+Change-Id: I2ba45db7592ea9addc2df230b977ffb950f0b342
+---
+ crypto/nss_util.cc | 37 ++++++++++++++-----------------------
+ crypto/openssl_util.cc | 6 ++----
+ crypto/openssl_util.h | 7 +++----
+ crypto/sha2.cc | 2 +-
+ 4 files changed, 20 insertions(+), 32 deletions(-)
+
+diff --git a/crypto/nss_util.cc b/crypto/nss_util.cc
+index a7752d3..f9c6373 100644
+--- a/crypto/nss_util.cc
++++ b/crypto/nss_util.cc
+@@ -38,14 +38,13 @@
+ #include "base/files/file_util.h"
+ #include "base/lazy_instance.h"
+ #include "base/logging.h"
+-#include "base/memory/ptr_util.h"
+ #include "base/message_loop/message_loop.h"
+ #include "base/native_library.h"
+ #include "base/stl_util.h"
+ #include "base/strings/stringprintf.h"
++#include "base/task_scheduler/post_task.h"
+ #include "base/threading/thread_checker.h"
+ #include "base/threading/thread_restrictions.h"
+-#include "base/threading/worker_pool.h"
+ #include "build/build_config.h"
+
+ #if !defined(OS_CHROMEOS)
+@@ -380,22 +379,16 @@ class NSSInitSingleton {
+ std::unique_ptr<TPMModuleAndSlot> tpm_args(
+ new TPMModuleAndSlot(chaps_module_));
+ TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
+- if (base::WorkerPool::PostTaskAndReply(
+- FROM_HERE,
+- base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread,
+- system_slot_id,
+- tpm_args_ptr),
+- base::Bind(&NSSInitSingleton::OnInitializedTPMTokenAndSystemSlot,
+- base::Unretained(this), // NSSInitSingleton is leaky
+- callback,
+- base::Passed(&tpm_args)),
+- true /* task_is_slow */
+- )) {
+- initializing_tpm_token_ = true;
+- } else {
+- base::MessageLoop::current()->task_runner()->PostTask(
+- FROM_HERE, base::Bind(callback, false));
+- }
++ base::PostTaskAndReply(
++ FROM_HERE,
++ base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread,
++ system_slot_id,
++ tpm_args_ptr),
++ base::Bind(&NSSInitSingleton::OnInitializedTPMTokenAndSystemSlot,
++ base::Unretained(this), // NSSInitSingleton is leaky
++ callback,
++ base::Passed(&tpm_args)));
++ initializing_tpm_token_ = true;
+ }
+
+ static void InitializeTPMTokenOnWorkerThread(CK_SLOT_ID token_slot_id,
+@@ -508,7 +501,7 @@ class NSSInitSingleton {
+ "%s %s", kUserNSSDatabaseName, username_hash.c_str());
+ ScopedPK11Slot public_slot(OpenPersistentNSSDBForPath(db_name, path));
+ chromeos_user_map_[username_hash] =
+- base::MakeUnique<ChromeOSUserData>(std::move(public_slot));
++ std::make_unique<ChromeOSUserData>(std::move(public_slot));
+ return true;
+ }
+
+@@ -543,7 +536,7 @@ class NSSInitSingleton {
+ std::unique_ptr<TPMModuleAndSlot> tpm_args(
+ new TPMModuleAndSlot(chaps_module_));
+ TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
+- base::WorkerPool::PostTaskAndReply(
++ base::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread,
+ slot_id,
+@@ -551,9 +544,7 @@ class NSSInitSingleton {
+ base::Bind(&NSSInitSingleton::OnInitializedTPMForChromeOSUser,
+ base::Unretained(this), // NSSInitSingleton is leaky
+ username_hash,
+- base::Passed(&tpm_args)),
+- true /* task_is_slow */
+- );
++ base::Passed(&tpm_args)));
+ }
+
+ void OnInitializedTPMForChromeOSUser(
+diff --git a/crypto/openssl_util.cc b/crypto/openssl_util.cc
+index c1b7a90..b671eab 100644
+--- a/crypto/openssl_util.cc
++++ b/crypto/openssl_util.cc
+@@ -46,15 +46,13 @@ void EnsureOpenSSLInit() {
+ #endif
+ }
+
+-void ClearOpenSSLERRStack(const tracked_objects::Location& location) {
++void ClearOpenSSLERRStack(const base::Location& location) {
+ if (DCHECK_IS_ON() && VLOG_IS_ON(1)) {
+ uint32_t error_num = ERR_peek_error();
+ if (error_num == 0)
+ return;
+
+- std::string message;
+- location.Write(true, true, &message);
+- DVLOG(1) << "OpenSSL ERR_get_error stack from " << message;
++ DVLOG(1) << "OpenSSL ERR_get_error stack from " << location.ToString();
+ ERR_print_errors_cb(&OpenSSLErrorCallback, NULL);
+ } else {
+ ERR_clear_error();
+diff --git a/crypto/openssl_util.h b/crypto/openssl_util.h
+index d608cde..c3d6cc9 100644
+--- a/crypto/openssl_util.h
++++ b/crypto/openssl_util.h
+@@ -63,8 +63,7 @@ CRYPTO_EXPORT void EnsureOpenSSLInit();
+ // Drains the OpenSSL ERR_get_error stack. On a debug build the error codes
+ // are send to VLOG(1), on a release build they are disregarded. In most
+ // cases you should pass FROM_HERE as the |location|.
+-CRYPTO_EXPORT void ClearOpenSSLERRStack(
+- const tracked_objects::Location& location);
++CRYPTO_EXPORT void ClearOpenSSLERRStack(const base::Location& location);
+
+ // Place an instance of this class on the call stack to automatically clear
+ // the OpenSSL error stack on function exit.
+@@ -73,7 +72,7 @@ class OpenSSLErrStackTracer {
+ // Pass FROM_HERE as |location|, to help track the source of OpenSSL error
+ // messages. Note any diagnostic emitted will be tagged with the location of
+ // the constructor call as it's not possible to trace a destructor's callsite.
+- explicit OpenSSLErrStackTracer(const tracked_objects::Location& location)
++ explicit OpenSSLErrStackTracer(const base::Location& location)
+ : location_(location) {
+ EnsureOpenSSLInit();
+ }
+@@ -82,7 +81,7 @@ class OpenSSLErrStackTracer {
+ }
+
+ private:
+- const tracked_objects::Location location_;
++ const base::Location location_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(OpenSSLErrStackTracer);
+ };
+diff --git a/crypto/sha2.cc b/crypto/sha2.cc
+index 1b302b3..71566a9 100644
+--- a/crypto/sha2.cc
++++ b/crypto/sha2.cc
+@@ -21,7 +21,7 @@ void SHA256HashString(const base::StringPiece& str, void* output, size_t len) {
+
+ std::string SHA256HashString(const base::StringPiece& str) {
+ std::string output(kSHA256Length, 0);
+- SHA256HashString(str, base::string_as_array(&output), output.size());
++ SHA256HashString(str, base::data(output), output.size());
+ return output;
+ }
+
+--
+2.22.0.rc2.383.gf4fbbf30c2-goog
+
diff --git a/libchrome_tools/patches/libchrome-fix-integer-overflow-if-microseconds-is-IN.patch b/libchrome_tools/patches/libchrome-fix-integer-overflow-if-microseconds-is-IN.patch
new file mode 100644
index 0000000000..a1241b11df
--- /dev/null
+++ b/libchrome_tools/patches/libchrome-fix-integer-overflow-if-microseconds-is-IN.patch
@@ -0,0 +1,27 @@
+From f7ef75a1ddea9d18e1f166b76e90f948cbfd1f77 Mon Sep 17 00:00:00 2001
+From: Qijiang Fan <fqj@chromium.org>
+Date: Tue, 31 Mar 2020 17:43:16 +0900
+Subject: [PATCH] libchrome: fix integer overflow if microseconds is INT64_MIN
+
+Change-Id: Id3641f6b625f716ae6d134002c0224ed32284939
+---
+ base/time/time_exploded_posix.cc | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/base/time/time_exploded_posix.cc b/base/time/time_exploded_posix.cc
+index 627c6b4f8735..2aef3864554e 100644
+--- a/base/time/time_exploded_posix.cc
++++ b/base/time/time_exploded_posix.cc
+@@ -141,8 +141,7 @@ void Time::Explode(bool is_local, Exploded* exploded) const {
+ millisecond = milliseconds % kMillisecondsPerSecond;
+ } else {
+ // Round these *down* (towards -infinity).
+- milliseconds = (microseconds - kMicrosecondsPerMillisecond + 1) /
+- kMicrosecondsPerMillisecond;
++ milliseconds = (microseconds + 1) / kMicrosecondsPerMillisecond - 1;
+ seconds =
+ (milliseconds - kMillisecondsPerSecond + 1) / kMillisecondsPerSecond;
+ // Make this nonnegative (and between 0 and 999 inclusive).
+--
+2.24.1
+
diff --git a/libchrome_tools/patches/patches b/libchrome_tools/patches/patches
new file mode 100644
index 0000000000..e27bc7afc7
--- /dev/null
+++ b/libchrome_tools/patches/patches
@@ -0,0 +1,75 @@
+# This file will be used by emerge in libchrome-XXXX.ebuild to determine the
+# order of applying patches.
+
+# Cherry pick CLs from upstream.
+# Remove these when the libchrome gets enough new.
+# r576565.
+dbus-Add-TryRegisterFallback.patch
+
+# r581937.
+dbus-Remove-LOG-ERROR-in-ObjectProxy.patch
+
+# r582324
+Fix-Wdefaulted-function-deleted-warning-in-MessageLo.patch
+
+# r583543.
+dbus-Make-Bus-is_connected-mockable.patch
+
+# r596510.
+Mojo-Check-if-dispatcher-is-null-in-Core-UnwrapPlatf.patch
+
+# This no_destructor.h is taken from r599267.
+Add-base-NoDestructor-T.patch
+
+# r616020.
+dbus-Support-UnexportMethod-from-an-exported-object.patch
+
+# Add support for SimpleAlarmTimer::Create{,ForTesting} to reflect changes in r626151.
+Refactor-AlarmTimer-to-report-error-to-the-caller.patch
+
+# For backward compatibility.
+# TODO(crbug.com/909719): Remove this patch after clients are updated.
+libchrome-Add-EmptyResponseCallback-for-backward-com.patch
+
+# Undo gn_helper sys.path update.
+libchrome-Unpatch-sys.path-update.patch
+
+# Introduce stub ConvertableToTraceFormat for task_scheduler.
+libchrome-Introduce-stub-ConvertableToTraceFormat.patch
+
+# Fix timing issue with dbus::ObjectManager.
+# # TODO(bingxue): Remove after libchrome uprev past r684392.
+Connect-to-NameOwnerChanged-signal-when-setting-call.patch
+
+# Remove glib dependency.
+# TODO(hidehiko): Fix the config in AOSP libchrome.
+libchrome-Remove-glib-dependency.patch
+
+# Fix FileDescriptorWatcher leak
+# TODO(fqj): Remove after libchrome past r627021.
+fix-fd-watcher-leak.patch
+
+# Misc fix to build older crypto library.
+libchrome-Update-crypto.patch
+
+# Enable location source to add function_name
+enable-location-source.patch
+
+# Add WaitForServiceToBeAvailable back for MockObjectProxy
+WaitForServiceToBeAvailable.patch
+
+# TODO(crbug.com/1044363): Remove after uprev >= r586219.
+Fix-TimeDelta.patch
+
+# TODO(crbug.com/1065504): Remove after uprev to 754979.
+libchrome-fix-integer-overflow-if-microseconds-is-IN.patch
+
+# Forward compatibility for r680000
+r680000-forward-compatibility-patch-part-1.patch
+r680000-forward-compatibility-patch-part-2.patch
+
+# Add base/{check_op,notreached}.h for cbor
+Add-header-files-base-check_op-notreached-h.patch
+
+# Remove after uprev to r807076
+0001-Fix-pending_broker_clients-handling.patch
diff --git a/libchrome_tools/patches/r680000-forward-compatibility-patch-part-1.patch b/libchrome_tools/patches/r680000-forward-compatibility-patch-part-1.patch
new file mode 100644
index 0000000000..2f820e6832
--- /dev/null
+++ b/libchrome_tools/patches/r680000-forward-compatibility-patch-part-1.patch
@@ -0,0 +1,222 @@
+From 9d210ed05abf5e527f1de0b30fff62a8a3ae548f Mon Sep 17 00:00:00 2001
+From: hscham <hscham@chromium.org>
+Date: Mon, 13 Apr 2020 10:36:11 +0900
+Subject: [PATCH] libchrome: r680000 forward compatibility patch part 1
+
+This CL includes:
+- Add header files base/hash/{hash,md5,sha1}.h and
+ base/system/sys_info.h from base/.
+- Add macro PRFilePath.
+- Add JSONReader::{Read,ReadAndReturnError,ReadToValue}Deprecated, alias
+ of the corresponding method without the Deprecated suffix.
+- Add empty class base::CheckedObserver.
+- Add bool operators == and != for base::RepeatingCallback.
+- ScopedClearLastError (up to r758621)
+
+Change-Id: I6ba15f2938c02729e4fd51e5a4a52cb94e7c2a4e
+---
+ base/callback.h | 8 ++++++++
+ base/files/file_path.h | 1 +
+ base/hash/hash.h | 8 ++++++++
+ base/hash/md5.h | 10 ++++++++++
+ base/hash/sha1.h | 10 ++++++++++
+ base/json/json_reader.h | 19 +++++++++++++++++++
+ base/observer_list_types.h | 24 ++++++++++++++++++++++++
+ base/system/sys_info.h | 10 ++++++++++
+ 8 files changed, 90 insertions(+)
+ create mode 100644 base/hash/hash.h
+ create mode 100644 base/hash/md5.h
+ create mode 100644 base/hash/sha1.h
+ create mode 100644 base/observer_list_types.h
+ create mode 100644 base/system/sys_info.h
+
+diff --git a/base/callback.h b/base/callback.h
+index bcda5af58..22a6f0e3e 100644
+--- a/base/callback.h
++++ b/base/callback.h
+@@ -123,6 +123,14 @@ class RepeatingCallback<R(Args...)> : public internal::CallbackBaseCopyable {
+ return EqualsInternal(other);
+ }
+
++ bool operator==(const RepeatingCallback& other) const {
++ return EqualsInternal(other);
++ }
++
++ bool operator!=(const RepeatingCallback& other) const {
++ return !operator==(other);
++ }
++
+ R Run(Args... args) const & {
+ PolymorphicInvoke f =
+ reinterpret_cast<PolymorphicInvoke>(this->polymorphic_invoke());
+diff --git a/base/files/file_path.h b/base/files/file_path.h
+index 2dc15f9d0..36229979d 100644
+--- a/base/files/file_path.h
++++ b/base/files/file_path.h
+@@ -131,6 +131,7 @@
+ #define PRIsFP "ls"
+ #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ #define PRIsFP "s"
++#define PRFilePath "s"
+ #endif // OS_WIN
+
+ namespace base {
+diff --git a/base/hash/hash.h b/base/hash/hash.h
+new file mode 100644
+index 000000000..b35a96fd3
+--- /dev/null
++++ b/base/hash/hash.h
+@@ -0,0 +1,8 @@
++// Copyright (c) 2011 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_HASH_HASH_H_
++#define BASE_HASH_HASH_H_
++#include "base/hash.h"
++#endif // BASE_HASH_HASH_H_
+diff --git a/base/hash/md5.h b/base/hash/md5.h
+new file mode 100644
+index 000000000..821649319
+--- /dev/null
++++ b/base/hash/md5.h
+@@ -0,0 +1,10 @@
++// Copyright (c) 2011 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_HASH_MD5_H_
++#define BASE_HASH_MD5_H_
++
++#include "base/md5.h"
++
++#endif // BASE_HASH_MD5_H_
+diff --git a/base/hash/sha1.h b/base/hash/sha1.h
+new file mode 100644
+index 000000000..7d3e18212
+--- /dev/null
++++ b/base/hash/sha1.h
+@@ -0,0 +1,10 @@
++// Copyright (c) 2011 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_HASH_SHA1_H_
++#define BASE_HASH_SHA1_H_
++
++#include "base/sha1.h"
++
++#endif // BASE_HASH_SHA1_H_
+diff --git a/base/json/json_reader.h b/base/json/json_reader.h
+index 2c6bd3e47..05de907ce 100644
+--- a/base/json/json_reader.h
++++ b/base/json/json_reader.h
+@@ -98,6 +98,12 @@ class BASE_EXPORT JSONReader {
+ static std::unique_ptr<Value> Read(StringPiece json,
+ int options = JSON_PARSE_RFC,
+ int max_depth = kStackMaxDepth);
++ inline static std::unique_ptr<Value> ReadDeprecated(
++ StringPiece json,
++ int options = JSON_PARSE_RFC,
++ int max_depth = kStackMaxDepth){
++ return Read(json, options, max_depth);
++ }
+
+ // Reads and parses |json| like Read(). |error_code_out| and |error_msg_out|
+ // are optional. If specified and nullptr is returned, they will be populated
+@@ -110,6 +116,16 @@ class BASE_EXPORT JSONReader {
+ std::string* error_msg_out,
+ int* error_line_out = nullptr,
+ int* error_column_out = nullptr);
++ inline static std::unique_ptr<Value> ReadAndReturnErrorDeprecated(
++ StringPiece json,
++ int options, // JSONParserOptions
++ int* error_code_out,
++ std::string* error_msg_out,
++ int* error_line_out = nullptr,
++ int* error_column_out = nullptr){
++ return ReadAndReturnError(json, options, error_code_out, error_msg_out,
++ error_line_out, error_column_out);
++ }
+
+ // Converts a JSON parse error code into a human readable message.
+ // Returns an empty string if error_code is JSON_NO_ERROR.
+@@ -117,6 +133,9 @@ class BASE_EXPORT JSONReader {
+
+ // Non-static version of Read() above.
+ std::unique_ptr<Value> ReadToValue(StringPiece json);
++ inline std::unique_ptr<Value> ReadToValueDeprecated(StringPiece json) {
++ return JSONReader::ReadToValue(json);
++ }
+
+ // Returns the error code if the last call to ReadToValue() failed.
+ // Returns JSON_NO_ERROR otherwise.
+diff --git a/base/observer_list_types.h b/base/observer_list_types.h
+new file mode 100644
+index 000000000..8f3889735
+--- /dev/null
++++ b/base/observer_list_types.h
+@@ -0,0 +1,24 @@
++// Copyright 2018 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_OBSERVER_LIST_TYPES_H_
++#define BASE_OBSERVER_LIST_TYPES_H_
++
++#include "base/base_export.h"
++#include "base/macros.h"
++
++namespace base {
++class BASE_EXPORT CheckedObserver {
++ public:
++ CheckedObserver() {};
++
++ protected:
++ virtual ~CheckedObserver() = default;
++
++ DISALLOW_COPY_AND_ASSIGN(CheckedObserver);
++};
++
++} // namespace base
++
++#endif // BASE_OBSERVER_LIST_TYPES_H_
+diff --git a/base/scoped_clear_last_error.h b/base/scoped_clear_last_error.h
+new file mode 100644
+index 0000000..066730d
+--- /dev/null
++++ b/base/scoped_clear_last_error.h
+@@ -0,0 +1,14 @@
++// Copyright (c) 2013 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_SCOPED_CLEAR_LAST_ERROR_H_
++#define BASE_SCOPED_CLEAR_LAST_ERROR_H_
++
++#include "base/scoped_clear_errno.h"
++
++namespace base {
++using ScopedClearLastError = base::ScopedClearErrno;
++}
++
++#endif // BASE_SCOPED_CLEAR_LAST_ERROR_H_
+diff --git a/base/system/sys_info.h b/base/system/sys_info.h
+new file mode 100644
+index 000000000..60676e0e1
+--- /dev/null
++++ b/base/system/sys_info.h
+@@ -0,0 +1,10 @@
++// Copyright (c) 2012 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef BASE_SYSTEM_SYS_INFO_H_
++#define BASE_SYSTEM_SYS_INFO_H_
++
++#include "base/sys_info.h"
++
++#endif // BASE_SYSTEM_SYS_INFO_H_
+--
+2.26.1.301.g55bc3eb7cb9-goog
+
diff --git a/libchrome_tools/patches/r680000-forward-compatibility-patch-part-2.patch b/libchrome_tools/patches/r680000-forward-compatibility-patch-part-2.patch
new file mode 100644
index 0000000000..0bb346d304
--- /dev/null
+++ b/libchrome_tools/patches/r680000-forward-compatibility-patch-part-2.patch
@@ -0,0 +1,94 @@
+From e8ce13950e6afc97ea69a36f5f234a7409269086 Mon Sep 17 00:00:00 2001
+From: hscham <hscham@chromium.org>
+Date: Fri, 17 Apr 2020 15:20:53 +0900
+Subject: [PATCH] libchrome: r680000 forward compatibility patch part 2
+
+This CL includes:
+- Rename base::LaunchOptions {,clear_}environ{=>ment}.
+
+Change-Id: I07b9b84d153e942368021be7fb89f0dd07ffebb1
+---
+ base/process/launch.h | 6 +++---
+ base/process/launch_posix.cc | 8 ++++----
+ base/process/process_util_unittest.cc | 4 ++--
+ 3 files changed, 9 insertions(+), 9 deletions(-)
+
+diff --git a/base/process/launch.h b/base/process/launch.h
+index 7a2def2..84d176e 100644
+--- a/base/process/launch.h
++++ b/base/process/launch.h
+@@ -160,11 +160,11 @@ struct BASE_EXPORT LaunchOptions {
+ // Set/unset environment variables. These are applied on top of the parent
+ // process environment. Empty (the default) means to inherit the same
+ // environment. See AlterEnvironment().
+- EnvironmentMap environ;
++ EnvironmentMap environment;
+
+ // Clear the environment for the new process before processing changes from
+- // |environ|.
+- bool clear_environ = false;
++ // |environment|.
++ bool clear_environment = false;
+
+ // Remap file descriptors according to the mapping of src_fd->dest_fd to
+ // propagate FDs into the child process.
+diff --git a/base/process/launch_posix.cc b/base/process/launch_posix.cc
+index ec58488..c61db41 100644
+--- a/base/process/launch_posix.cc
++++ b/base/process/launch_posix.cc
+@@ -324,10 +324,10 @@ Process LaunchProcess(const std::vector<std::string>& argv,
+ std::unique_ptr<char* []> new_environ;
+ char* const empty_environ = nullptr;
+ char* const* old_environ = GetEnvironment();
+- if (options.clear_environ)
++ if (options.clear_environment)
+ old_environ = &empty_environ;
+- if (!options.environ.empty())
+- new_environ = AlterEnvironment(old_environ, options.environ);
++ if (!options.environment.empty())
++ new_environ = AlterEnvironment(old_environ, options.environment);
+
+ sigset_t full_sigset;
+ sigfillset(&full_sigset);
+@@ -466,7 +466,7 @@ Process LaunchProcess(const std::vector<std::string>& argv,
+ fd_shuffle2.push_back(InjectionArc(value.first, value.second, false));
+ }
+
+- if (!options.environ.empty() || options.clear_environ)
++ if (!options.environment.empty() || options.clear_environment)
+ SetEnvironment(new_environ.get());
+
+ // fd_shuffle1 is mutated by this call because it cannot malloc.
+diff --git a/base/process/process_util_unittest.cc b/base/process/process_util_unittest.cc
+index 4e788b7..a541e48 100644
+--- a/base/process/process_util_unittest.cc
++++ b/base/process/process_util_unittest.cc
+@@ -1162,8 +1162,8 @@ std::string TestLaunchProcess(const std::vector<std::string>& args,
+
+ LaunchOptions options;
+ options.wait = true;
+- options.environ = env_changes;
+- options.clear_environ = clear_environ;
++ options.environment = env_changes;
++ options.clear_environment = clear_environ;
+ options.fds_to_remap.emplace_back(fds[1], 1);
+ #if defined(OS_LINUX)
+ options.clone_flags = clone_flags;
+--
+2.26.1.301.g55bc3eb7cb9-goog
+
+diff --git a/base/test/scoped_task_environment.h b/base/test/scoped_task_environment.h
+index f9523b3138ec..a2cc7f73d8f2 100644
+--- a/base/test/scoped_task_environment.h
++++ b/base/test/scoped_task_environment.h
+@@ -81,6 +81,10 @@ class ScopedTaskEnvironment {
+ IO,
+ };
+
++ // To introduce TimeSource::MOCK_TIME behaves same as
++ // MainThreadType::MOCK_TIME.
++ using TimeSource = MainThreadType;
++
+ enum class ExecutionMode {
+ // Tasks are queued and only executed when RunUntilIdle() is explicitly
+ // called.
diff --git a/libchrome_tools/uprev/copy_new_files.py b/libchrome_tools/uprev/copy_new_files.py
new file mode 100755
index 0000000000..67da28f4ac
--- /dev/null
+++ b/libchrome_tools/uprev/copy_new_files.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Utility to copy missing files from Chromium tree to Chromium OS libchrome tree
+based on hard coded rules.
+
+This utility is used to diff current HEAD against given commit in Chromium
+browser master branch, copy missing files after hard-coded filter rules and
+remove unnecessary files. libchrome original files in hard-coded filter rules
+will be untounched.
+"""
+
+import argparse
+import os
+import os.path
+import subprocess
+import sys
+
+import filters
+import utils
+
+def main():
+ # Init args
+ parser = argparse.ArgumentParser(
+ description='Copy file from given commits')
+ parser.add_argument(
+ 'commit_hash',
+ metavar='commit',
+ type=str,
+ nargs=1,
+ help='commit hash to copy files from')
+ parser.add_argument(
+ '--dry_run',
+ dest='dry_run',
+ action='store_const',
+ const=True,
+ default=False)
+ arg = parser.parse_args(sys.argv[1:])
+
+ # Read file list from HEAD and upstream commit.
+ upstream_files = utils.get_file_list(arg.commit_hash[0])
+ our_files = utils.get_file_list('HEAD')
+
+ # Calculate target file list
+ target_files = filters.filter_file(our_files, upstream_files)
+
+ # Calculate operations needed
+ ops = utils.gen_op(our_files, target_files)
+
+ if arg.dry_run:
+ # Print ops only on dry-run mode.
+ print('\n'.join(repr(x) for x in ops))
+ return
+ for op, f in ops:
+ # Ignore if op is REP because we only want to copy missing files, not to
+ # revert custom Chromium OS libchrome patch.
+ assert type(op) == utils.DiffOperations
+ if op == utils.DiffOperations.DEL:
+ subprocess.check_call(['git', 'rm', f.path]),
+ elif op == utils.DiffOperations.ADD:
+ # Create directory recursively if not exist.
+ os.makedirs(os.path.dirname(f.path), exist_ok=True)
+ # Read file by git cat-file with blob object id to avoid heavy git checkout.
+ with open(f.path, 'wb') as outfile:
+ subprocess.check_call(['git', 'cat-file', 'blob', f.id],
+ stdout=outfile)
+ # Add to git index
+ subprocess.check_call(['git', 'add', f.path])
+
+if __name__ == '__main__':
+ main()
diff --git a/libchrome_tools/uprev/dirty_uprev.py b/libchrome_tools/uprev/dirty_uprev.py
new file mode 100755
index 0000000000..aa8e2bd08e
--- /dev/null
+++ b/libchrome_tools/uprev/dirty_uprev.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Utility to apply diffs between given two upstream commit hashes to current
+workspace.
+
+This utility diffs files between old_commit and new_commit, with hard-coded
+filter rules, and apply the diff to current HEAD with 3-way-merge supported.
+
+This can be used to uprev a libchrome directory when this is not git history for
+git merge to work.
+"""
+
+import argparse
+import subprocess
+import sys
+
+import filters
+import utils
+
+def main():
+ # Init args
+ parser = argparse.ArgumentParser(
+ description='Copy file from given commits')
+ parser.add_argument(
+ 'old_commit', metavar='old_commit', type=str, nargs=1,
+ help='commit hash in upstream branch or browser repository '
+ 'we want to uprev from')
+ parser.add_argument(
+ 'new_commit', metavar='new_commit', type=str, nargs=1,
+ help='commit hash in upstream branch or browser repository '
+ 'we want ot uprev to')
+ parser.add_argument(
+ '--dry_run', dest='dry_run', action='store_const', const=True, default=False)
+ parser.add_argument(
+ '--is_browser', dest='is_browser', action='store_const', const=True, default=False,
+ help='is the commit hash in browser repository')
+ arg = parser.parse_args(sys.argv[1:])
+
+ # Get old and new files.
+ old_files = utils.get_file_list(arg.old_commit[0])
+ new_files = utils.get_file_list(arg.new_commit[0])
+
+ if arg.is_browser:
+ old_files = filters.filter_file([], old_files)
+ new_files = filters.filter_file([], new_files)
+ assert filters.filter_file(old_files, []) == []
+ assert filters.filter_file(new_files, []) == []
+
+ # Generate a tree object for new files.
+ old_tree = utils.git_mktree(old_files)
+ new_tree = utils.git_mktree(new_files)
+ newroot = utils.git_commit(old_tree, [])
+ squashed = utils.git_commit(new_tree, [newroot])
+
+ # Generate patch for git am
+ patch = subprocess.check_output(['git', 'format-patch', '--stdout', newroot+b'..'+squashed])
+
+ if arg.dry_run:
+ print(patch.decode('utf-8'))
+ else:
+ subprocess.run(['git', 'am', '-3'], input=patch)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/libchrome_tools/uprev/filtered_utils.py b/libchrome_tools/uprev/filtered_utils.py
new file mode 100644
index 0000000000..20639b2bee
--- /dev/null
+++ b/libchrome_tools/uprev/filtered_utils.py
@@ -0,0 +1,108 @@
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+""" Provides utilities for filtered branch handling. """
+
+import collections
+import re
+import subprocess
+
+import utils
+
+# Keyword to uniquely identify the beginning of upstream history.
+CROS_LIBCHROME_INITIAL_COMMIT = b'CrOS-Libchrome-History-Initial-Commit'
+# Keyword to identify original commit in Chromium browser repository.
+CROS_LIBCHROME_ORIGINAL_COMMIT = b'CrOS-Libchrome-Original-Commit'
+
+
+# Stores metadata required for a git commit.
+GitCommitMetadata = collections.namedtuple(
+ 'GitCommitMetadata',
+ ['parents', 'original_commits', 'tree', 'authorship', 'title', 'message', 'is_root',]
+)
+
+
+# Stores information for a commit authorship.
+GitCommitAuthorship = collections.namedtuple(
+ 'GitCommitAuthorship',
+ ['name', 'email', 'time', 'timezone',]
+)
+
+
+def get_metadata(commit_hash):
+ """Returns the metadata of the commit specified by the commit_hash.
+
+ This function parses the commit message of the commit specified by the
+ commit_hash, then returns its GitCommitMetadata instance.
+ The commit must be on the filtered branch, otherwise some metadata may be
+ omitted.
+ Returns metadata from the commit message about commit_hash on the filtered
+ branch.
+
+ Args:
+ commit_hash: the commit hash on the filtered branch.
+ """
+
+ ret = subprocess.check_output(['git', 'cat-file', 'commit',
+ commit_hash]).split(b'\n')
+ parents = []
+ tree_hash = None
+ authorship = None
+ author_re = re.compile(rb'^(.*) <(.*)> ([0-9]+) ([^ ])+$')
+ while ret:
+ line = ret[0]
+ ret = ret[1:]
+ if not line.strip():
+ # End of header. break.
+ break
+ tag, reminder = line.split(b' ', 1)
+ if tag == b'tree':
+ tree_hash = reminder
+ elif tag == b'author':
+ m = author_re.match(reminder)
+ assert m, (line, commit_hash)
+ authorship = GitCommitAuthorship(m.group(1),
+ m.group(2),
+ m.group(3),
+ m.group(4))
+ elif tag == b'parent':
+ parents.append(reminder)
+
+ title = ret[0] if ret else None
+
+ original_commits = []
+ is_root = False
+ for line in ret:
+ if line.startswith(CROS_LIBCHROME_ORIGINAL_COMMIT):
+ original_commits.append(line.split(b':')[1].strip())
+ if line == CROS_LIBCHROME_INITIAL_COMMIT:
+ is_root = True
+ msg = b'\n'.join(ret)
+ return GitCommitMetadata(parents, original_commits, tree_hash, authorship,
+ title, msg, is_root)
+
+
+def get_commits_map(commit_hash, progress_callback):
+ """Returns a map from original commit hashes to filtered commit hashes.
+
+ This function traverses the filtered branch from the commit specified by
+ commit_hash to its root, then parses each commit message and constructs the
+ map of those commits.
+
+ Args:
+ commit_hash: the commit hash on the filtered branch.
+ progress_callback: called every commit is being read. Parameters taken
+ are (idx, total_commits, current_commit)
+ """
+ commits_map = {}
+ commits_filtered_tree = utils.git_revlist(None, commit_hash)
+ for index, commit in enumerate(commits_filtered_tree, start=1):
+ if progress_callback:
+ progress_callback(index, len(commits_filtered_tree), commit[0])
+ meta = get_metadata(commit[0])
+ for original_commit in meta.original_commits:
+ commits_map[original_commit] = commit[0]
+ if meta.is_root:
+ assert 'ROOT' not in commits_map
+ commits_map['ROOT'] = commit[0]
+ return commits_map
diff --git a/libchrome_tools/uprev/filters.py b/libchrome_tools/uprev/filters.py
new file mode 100644
index 0000000000..e67a030514
--- /dev/null
+++ b/libchrome_tools/uprev/filters.py
@@ -0,0 +1,129 @@
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Provide filters for libchrome tools."""
+
+import re
+
+# Libchrome wants WANT but not WANT_EXCLUDE
+# aka files matching WANT will be copied from upstream_files
+WANT = [
+ re.compile(rb'base/((?!(allocator|third_party)/).*$)'),
+ re.compile(
+ rb'base/allocator/(allocator_shim.cc|allocator_shim_override_linker_wrapped_symbols.h|allocator_shim_override_cpp_symbols.h|allocator_shim_override_libc_symbols.h|allocator_shim_default_dispatch_to_glibc.cc|allocator_shim.h|allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc|allocator_extension.cc|allocator_extension.h|allocator_shim_internals.h)$'
+ ),
+ re.compile(rb'base/third_party/(dynamic_annotation|icu|nspr|valgrind)'),
+ re.compile(rb'build/(android/(gyp/util|pylib/([^/]*$|constants))|[^/]*\.(h|py)$)'),
+ re.compile(rb'mojo/'),
+ re.compile(rb'dbus/'),
+ re.compile(rb'ipc/.*(\.cc|\.h|\.mojom)$'),
+ re.compile(rb'ui/gfx/(gfx_export.h|geometry|range)'),
+ re.compile(rb'testing/[^/]*\.(cc|h)$'),
+ re.compile(rb'third_party/(jinja2|markupsafe|ply)'),
+ re.compile(
+ rb'components/(json_schema|policy/core/common/[^/]*$|policy/policy_export.h|timers)'
+ ),
+ re.compile(
+ rb'device/bluetooth/bluetooth_(common|advertisement|uuid|export)\.*(h|cc)'
+ ),
+ re.compile(
+ rb'device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.(h|cc)'
+ ),
+]
+
+# WANT_EXCLUDE will be excluded from WANT
+WANT_EXCLUDE = [
+ re.compile(rb'(.*/)?BUILD.gn$'),
+ re.compile(rb'(.*/)?PRESUBMIT.py$'),
+ re.compile(rb'(.*/)?OWNERS$'),
+ re.compile(rb'(.*/)?SECURITY_OWNERS$'),
+ re.compile(rb'(.*/)?DEPS$'),
+ re.compile(rb'base/(.*/)?(ios|win|fuchsia|mac|openbsd|freebsd|nacl)/.*'),
+ re.compile(rb'.*_(ios|win|mac|fuchsia|openbsd|freebsd|nacl)[_./]'),
+ re.compile(rb'.*/(ios|win|mac|fuchsia|openbsd|freebsd|nacl)_'),
+ re.compile(rb'dbus/(test_serv(er|ice)\.cc|test_service\.h)$')
+]
+
+# Files matching KEEP should not be touched.
+# aka files matching KEEP will keep its our_files version,
+# and it will be kept even it doesn't exist in upstream.
+# KEEP-KEEP_EXCLUDE must NOT intersect with WANT-WANT_EXCLUDE
+KEEP = [
+ re.compile(
+ b'(Android.bp|BUILD.gn|crypto|libchrome_tools|MODULE_LICENSE_BSD|NOTICE|OWNERS|PRESUBMIT.cfg|soong|testrunner.cc|third_party)(/.*)?$'
+ ),
+ re.compile(rb'[^/]*$'),
+ re.compile(rb'.*buildflags.h'),
+ re.compile(rb'base/android/java/src/org/chromium/base/BuildConfig.java'),
+ re.compile(rb'testing/(gmock|gtest)/'),
+ re.compile(rb'base/third_party/(libevent|symbolize)'),
+]
+
+# KEEP_EXCLUDE wil be excluded from KEEP.
+KEEP_EXCLUDE = [
+ re.compile(rb'third_party/(jinja2|markupsafe|ply)'),
+]
+
+
+def _want_file(path):
+ """Returns whether the path wants to be a new file."""
+ wanted = False
+ for want_file_regex in WANT:
+ if want_file_regex.match(path):
+ wanted = True
+ break
+ for exclude_file_regex in WANT_EXCLUDE:
+ if exclude_file_regex.match(path):
+ wanted = False
+ break
+ return wanted
+
+
+def _keep_file(path):
+ """Returns whether the path wants to be kept untouched in local files."""
+ keep = False
+ for keep_file_regex in KEEP:
+ if keep_file_regex.match(path):
+ keep = True
+ break
+ for exclude_file_regex in KEEP_EXCLUDE:
+ if exclude_file_regex.match(path):
+ keep = False
+ break
+ return keep
+
+
+def filter_file(our_files, upstream_files):
+ """Generates a list of files we want based on hard-coded rules.
+
+ File list must be a list of GitFile.
+
+ Args:
+ our_files: files in Chromium OS libchrome repository.
+ upstream_files: files in Chromium browser repository.
+ """
+
+ files = []
+ for upstream_file in upstream_files:
+ if _want_file(upstream_file.path):
+ files.append(upstream_file)
+ for our_file in our_files:
+ if _keep_file(our_file.path):
+ files.append(our_file)
+ return files
+
+
+def filter_diff(diff):
+ """Returns a subset of diff, after running filters.
+
+ Args:
+ diff: diff to filter. diff contains list of utils.GitDiffTree
+ """
+ filtered = []
+ for change in diff:
+ path = change.file.path
+ if _want_file(path):
+ assert not _keep_file(path)
+ filtered.append(change)
+ return filtered
diff --git a/libchrome_tools/uprev/generate_filtered_tree.py b/libchrome_tools/uprev/generate_filtered_tree.py
new file mode 100755
index 0000000000..63c678d9b8
--- /dev/null
+++ b/libchrome_tools/uprev/generate_filtered_tree.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import collections
+import datetime
+import os
+import subprocess
+import sys
+import time
+
+import filtered_utils
+import filters
+import lazytree
+import utils
+
+# Use avg speed of last TIMING_DISTANCE commits.
+_TIMING_DISTANCE = 100
+# Verify the tree is consistent (diff-based, and actual) when a commit is made
+# after every _VERIFY_INTEGRITY_DISTANCE in browser repository.
+# Merge commits are always verified.
+_VERIFY_INTEGRITY_DISTANCE = 1000
+
+
+def timing(timing_deque, update=True):
+ """Returns a speed (c/s), and updates timing_deque.
+
+ Args:
+ timing_deque: a deque to store the timing of past _TIMING_DISTANCE.
+ update: adds current timestamp to timing_deque if True. It needs to set
+ to False, if it wants to be called multiple times with the current
+ timestamp.
+ """
+ first = timing_deque[0]
+ now = time.time()
+ if update:
+ timing_deque.append(now)
+ if len(timing_deque) > _TIMING_DISTANCE:
+ timing_deque.popleft()
+ return _TIMING_DISTANCE / (now - first)
+
+
+def get_start_commit_of_browser_tree(parent_filtered):
+ """Returns the last commit committed by the script, and its metadata.
+
+ Args:
+ parent_filtered: the commit hash of the tip of the filtered branch.
+ """
+ current = parent_filtered
+ while True:
+ meta = filtered_utils.get_metadata(current)
+ if meta.original_commits:
+ return current, meta
+ if not meta.parents:
+ return None, None
+ # Follow main line only
+ current = meta.parents[0]
+
+
+def find_filtered_commit(commit, commits_map):
+ """Finds the corresponding parent of a browser commit in filtered branch.
+
+ If not found, the corresponding commit of its least ancestor is used.
+
+ Args:
+ commit: commit hash in browser repository.
+ commits_map: commit hash mapping from original commit to the one in the
+ filtered branch. commits_map may be altered.
+ """
+ look_for = commit
+ while look_for not in commits_map:
+ meta = filtered_utils.get_metadata(look_for)
+ assert len(meta.parents) <= 1
+ if len(meta.parents) == 1:
+ look_for = meta.parents[0]
+ else:
+ look_for = 'ROOT'
+ commits_map[commit] = commits_map[look_for]
+ return commits_map[look_for]
+
+
+def do_commit(treehash, commithash, meta, commits_map):
+ """Makes a commit with the given arguments.
+
+ This creates a commit on the filtered branch with preserving the original
+ commiter name, email, authored timestamp and the message.
+ Also, the special annotation `CrOS-Libchrome-Original-Commit:
+ <original-commit-hash>' is appended at the end of commit message.
+ The parent commits are identified by the parents of the original commit and
+ commits_map.
+
+ Args:
+ treehash: tree object id for this commit.
+ commithash: original commit hash, used to append to commit message.
+ meta: meta data of the original commit.
+ commits_map: current known commit mapping. commits_map may be altered.
+ """
+ parents_parameters = []
+ for parent in meta.parents:
+ parents_parameters.append('-p')
+ parents_parameters.append(find_filtered_commit(parent, commits_map))
+ msg = (meta.message + b'\n\n' +
+ filtered_utils.CROS_LIBCHROME_ORIGINAL_COMMIT +
+ b': ' + commithash + b'\n')
+ return subprocess.check_output(
+ ['git', 'commit-tree'] + parents_parameters + [treehash],
+ env=dict(os.environ,
+ GIT_AUTHOR_NAME=meta.authorship.name,
+ GIT_AUTHOR_EMAIL=meta.authorship.email,
+ GIT_AUTHOR_DATE=b' '.join([meta.authorship.time,
+ meta.authorship.timezone])),
+ input=msg).strip(b'\n')
+
+
+def verify_commit(original_commit, new_tree):
+ """Verifies if new_tree is exactly original_commit after filters.
+
+ Args:
+ original_commit: commit hash in Chromium browser tree.
+ new_tree: tree hash created for upstream branch commit.
+ """
+ expected_file_list = filters.filter_file([], utils.get_file_list(original_commit))
+ assert utils.git_mktree(expected_file_list) == new_tree
+
+
+def process_commits(pending_commits, commits_map, progress_callback, commit_callback):
+ """Processes new commits in browser repository.
+
+ Returns the commit hash of the last commit made.
+
+ Args:
+ pending_commits: list of tuple (commit hash, parent hashes) to process,
+ in topological order.
+ commits_map: current known commit mapping. may be altered.
+ progress_callback: callback for every commit in pending_commits. It
+ should take (idx, total, orig_commit_hash, meta) as parameters.
+ commit_callback: callback when a commit is made to filtered branch. It
+ should take (orig_commit_hash, new_commit_hash, meta) as parameters.
+ """
+ last_commit = None
+ last_verified = -1
+ for i, commit in enumerate(pending_commits, start=1):
+ meta = filtered_utils.get_metadata(commit[0])
+ if progress_callback:
+ progress_callback(i, len(pending_commits), commit[0], meta)
+ diff_with_parent = filters.filter_diff(utils.git_difftree(
+ meta.parents[0] if meta.parents else None, commit[0]))
+ git_lazytree = lazytree.LazyTree(
+ filtered_utils.get_metadata(
+ find_filtered_commit(meta.parents[0], commits_map)).tree
+ if meta.parents else None)
+ if len(meta.parents) <= 1 and len(diff_with_parent) == 0:
+ # not merge commit AND no diff
+ if len(meta.parents) == 1 and meta.parents[0] in commits_map:
+ commits_map[commit[0]] = commits_map[meta.parents[0]]
+ continue
+ for op, f in diff_with_parent:
+ if op == utils.DiffOperations.ADD or op == utils.DiffOperations.REP:
+ git_lazytree[f.path] = f
+ elif op == utils.DiffOperations.DEL:
+ del git_lazytree[f.path]
+ treehash_after_diff_applied = git_lazytree.hash()
+ filtered_commit = do_commit(treehash_after_diff_applied, commit[0],
+ meta, commits_map)
+ if commit_callback:
+ commit_callback(commit[0], filtered_commit, meta)
+ commits_map[commit[0]] = filtered_commit
+ last_commit = filtered_commit
+ if len(meta.parents) > 1 or (i - last_verified >=
+ _VERIFY_INTEGRITY_DISTANCE):
+ # merge commit OR every _VERIFY_INTEGRITY_DISTANCE
+ last_verified = i
+ verify_commit(commit[0], treehash_after_diff_applied)
+ # Verify last commit
+ verify_commit(pending_commits[-1][0], filtered_utils.get_metadata(last_commit).tree)
+ return last_commit
+
+
+def main():
+ # Init args
+ parser = argparse.ArgumentParser(
+ description='Copy file from given commits')
+ parser.add_argument(
+ 'parent_filtered', metavar='parent_filtered', type=str, nargs=1,
+ help='commit hash in filtered branch to continue from. usually HEAD of that branch.')
+ parser.add_argument(
+ 'goal_browser', metavar='goal_browser', type=str, nargs=1,
+ help='commit hash in browser master branch.')
+ parser.add_argument(
+ '--dry_run', dest='dry_run', action='store_const', const=True, default=False)
+ arg = parser.parse_args(sys.argv[1:])
+
+ # Look for last known commit made by the script in filtered branch.
+ print('Looking for last known commit from', arg.parent_filtered[0])
+ last_known, meta_last_known = get_start_commit_of_browser_tree(
+ arg.parent_filtered[0])
+ if last_known:
+ print('Continuing from', last_known, meta_last_known)
+ else:
+ print('No known last commit')
+ print('parent on filter branch', arg.parent_filtered[0])
+
+ # Get a mapping between browser repository and filtered branch for commits
+ # in filtered branch.
+ print('reading commits details for commits mapping')
+ timing_deque = collections.deque([time.time()])
+ commits_map = filtered_utils.get_commits_map(
+ arg.parent_filtered[0],
+ lambda cur_idx, tot_cnt, cur_hash:
+ (
+ print('Reading', cur_hash, '%d/%d' % (cur_idx, tot_cnt),
+ '%f c/s' % timing(timing_deque),
+ end='\r', flush=True),
+ ))
+ if not 'ROOT' in commits_map:
+ commits_map['ROOT'] =subprocess.check_output(
+ ['git', 'commit-tree', '-p', arg.parent_filtered[0],
+ utils.git_mktree([])],
+ input=filtered_utils.CROS_LIBCHROME_INITIAL_COMMIT).strip(b'\n')
+ print()
+ print('loaded commit mapping of', len(commits_map), 'commit')
+
+ # Process newer commits in browser repository from
+ # last_known.original_commits
+ print('search for commits to filter')
+ timing_deque= collections.deque([time.time()])
+ pending_commits = utils.git_revlist(
+ meta_last_known.original_commits[0] if meta_last_known else None,
+ arg.goal_browser[0])
+ print(len(pending_commits), 'commits to process')
+ new_head = process_commits(
+ pending_commits,
+ commits_map,
+ # Print progress
+ lambda cur_idx, tot_cnt, cur_hash, cur_meta: (
+ print('Processing',
+ cur_hash, '%d/%d' % (cur_idx, tot_cnt),
+ '%f c/s' % timing(timing_deque, update=False),
+ 'eta %s' % (
+ datetime.timedelta(
+ seconds=int((tot_cnt - cur_idx) / timing(timing_deque)))),
+ cur_meta.title[:50],
+ end='\r', flush=True),
+ ),
+ # Print new commits
+ lambda orig_hash, new_hash, commit_meta:
+ print(b'%s is commited as %s: %s' % (orig_hash, new_hash,
+ commit_meta.title[:50]))
+ )
+ print()
+ print('New HEAD should be', new_head.decode('ascii'))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/libchrome_tools/uprev/lazytree.py b/libchrome_tools/uprev/lazytree.py
new file mode 100644
index 0000000000..d76fa5c8c1
--- /dev/null
+++ b/libchrome_tools/uprev/lazytree.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import re
+import subprocess
+
+import utils
+
+
+GIT_LSTREE_RE_LINE = re.compile(rb'^([^ ]*) ([^ ]*) ([^ ]*)\t(.*)$')
+
+
+class LazyTree:
+ """LazyTree does git mktree lazily."""
+
+ def __init__(self, treehash=None):
+ """Initializes a LazyTree.
+
+ If treehash is not None, it initializes as the tree object.
+
+ Args:
+ treehash: tree object id. please do not use a treeish, it will fail
+ later.
+ """
+ if treehash:
+ self._treehash = treehash # tree object id of current tree
+ self._subtrees = None # map from directory name to sub LazyTree
+ self._files = None # map from file naem to utils.GitFile
+ return
+ # Initialize an empty LazyTree
+ self._treehash = None
+ self._subtrees = {}
+ self._files = {}
+
+ def _loadtree(self):
+ """Loads _treehash into _subtrees and _files."""
+ if self._files is not None: # _subtrees is also not None too here.
+ return
+ output = subprocess.check_output(['git', 'ls-tree', self._treehash]).split(b'\n')
+ self._files = {}
+ self._subtrees = {}
+ for line in output:
+ if not line:
+ continue
+ m = GIT_LSTREE_RE_LINE.match(line)
+ mode, gittype, objecthash, name = m.groups()
+ assert gittype == b'blob' or gittype == b'tree'
+ assert name not in self._files and name not in self._subtrees
+ if gittype == b'blob':
+ self._files[name] = utils.GitFile(None, mode, objecthash)
+ elif gittype == b'tree':
+ self._subtrees[name] = LazyTree(objecthash)
+
+ def _remove(self, components):
+ """Removes components from self tree.
+
+ Args:
+ components: the path to remove, relative to self. Each element means
+ one level of directory tree.
+ """
+ self._loadtree()
+ self._treehash = None
+ if len(components) == 1:
+ del self._files[components[0]]
+ return
+
+ # Remove from subdirectory
+ dirname, components = components[0], components[1:]
+ subdir = self._subtrees[dirname]
+ subdir._remove(components)
+ if subdir.is_empty():
+ del self._subtrees[dirname]
+
+ def __delitem__(self, path):
+ """Removes path from self tree.
+
+ Args:
+ path: the path to remove, relative to self.
+ """
+ components = path.split(b'/')
+ self._remove(components)
+
+ def _get(self, components):
+ """Returns a file at components in utils.GitFile from self tree.
+
+ Args:
+ components: path in list instead of separated by /.
+ """
+ self._loadtree()
+ if len(components) == 1:
+ return self._files[components[0]]
+
+ dirname, components = components[0], components[1:]
+ return self._subtrees[dirname]._get(components)
+
+ def __getitem__(self, path):
+ """Returns a file at path in utils.GitFile from tree.
+
+ Args:
+ path: path of the file to read.
+ """
+ components = path.split(b'/')
+ return self._get(components)
+
+ def _set(self, components, f):
+ """Adds or replace a file.
+
+ Args:
+ components: the path to set, relative to self. Each element means
+ one level of directory tree.
+ f: a utils.GitFile object.
+ """
+
+ self._loadtree()
+ self._treehash = None
+ if len(components) == 1:
+ self._files[components[0]] = f
+ return
+
+ # Add to subdirectory
+ dirname, components = components[0], components[1:]
+ if dirname not in self._subtrees:
+ self._subtrees[dirname] = LazyTree()
+ self._subtrees[dirname]._set(components, f)
+
+ def __setitem__(self, path, f):
+ """Adds or replaces a file.
+
+ Args:
+ path: the path to set, relative to self
+ f: a utils.GitFile object
+ """
+ assert f.path.endswith(path)
+ components = path.split(b'/')
+ self._set(components, f)
+
+ def is_empty(self):
+ """Returns if self is an empty tree."""
+ return not self._subtrees and not self._files
+
+ def hash(self):
+ """Returns the hash of current tree object.
+
+ If the object doesn't exist, create it.
+ """
+ if not self._treehash:
+ self._treehash = self._mktree()
+ return self._treehash
+
+ def _mktree(self):
+ """Recreates a tree object recursively.
+
+ Lazily if subtree is unchanged.
+ """
+ keys = list(self._files.keys()) + list(self._subtrees.keys())
+ mktree_input = []
+ for name in sorted(keys):
+ file = self._files.get(name)
+ if file:
+ mktree_input.append(b'%s blob %s\t%s' % (file.mode, file.id,
+ name))
+ else:
+ mktree_input.append(
+ b'040000 tree %s\t%s' % (self._subtrees[name].hash(), name))
+ return subprocess.check_output(
+ ['git', 'mktree'],
+ input=b'\n'.join(mktree_input)).strip(b'\n')
diff --git a/libchrome_tools/uprev/reconnect_history.py b/libchrome_tools/uprev/reconnect_history.py
new file mode 100755
index 0000000000..bb6040c694
--- /dev/null
+++ b/libchrome_tools/uprev/reconnect_history.py
@@ -0,0 +1,333 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Utility to disconnect history of files from a branch, and reconnect with base on
+a different branch.
+"""
+
+import argparse
+import collections
+import subprocess
+import sys
+
+import filtered_utils
+import lazytree
+import utils
+
+
+class CommitMetadataFactory(dict):
+ """Dict-like class to read commit metadata"""
+
+ def __missing__(self, key):
+ """Reads commit metadata if missing"""
+ value = filtered_utils.get_metadata(key)
+ self.__setitem__(key, value)
+ return value
+
+
+def disconnect(source_commit, ref_commit):
+ """Creates a commit that disconnects files from source_commit.
+
+ All files existing in ref_commit will be removed from source_commit.
+
+ Args:
+ source_commit: commit hash to disconnect from.
+ ref_commit: commit hash to be a file list reference.
+ """
+ source_files = utils.get_file_list(source_commit)
+ ref_files = utils.get_file_list(ref_commit)
+ ref_files_set = set(ref.path for ref in ref_files)
+ kept_files = [ref for ref in source_files if ref.path not in ref_files_set]
+ tree = utils.git_mktree(kept_files)
+ return utils.git_commit(
+ tree, [source_commit],
+ message=b'Disconnect history from %s' % (source_commit.encode('ascii')))
+
+
+def connect_base(current_commit, base_commit):
+ """Creates a merge commit that takes files from base_commit.
+
+ Literally it's identical to git merge base_commit in current_commit.
+
+ Args:
+ current_commit: commit hashes on where to commit to.
+ base_commit: commit hashes contains file histories.
+ """
+ current_files = utils.get_file_list(current_commit)
+ base_files = utils.get_file_list(base_commit)
+ tree = utils.git_mktree(current_files + base_files)
+ return utils.git_commit(
+ tree, [current_commit, base_commit],
+ message=b'Connect history with base %s' % (base_commit.encode('ascii')))
+
+
+def blame_files(commithash, files):
+ """Blames files on givven commithash"""
+ blames = {}
+ for path in files:
+ blames[path] = utils.git_blame(commithash, path)
+ return blames
+
+
+def search_blame_line(blames, amend_commits, target_commit_hash):
+ """Searches blames matching target_commit_hash in amend_commits
+
+ Returns a map from file path to a list of tuple, each tuple has
+ len(amend_commits) + 1 elements. 0-th element is the line in blames. and
+ 1st to n-th element are corresponding lines in amend_commits blaems.
+
+ Args:
+ blames: a dict from path to list of GitBlameLine, for files blamed on
+ target_commit_hash.
+ amend_commits: a list of commit hashes to provide actual history.
+ target_commit_hash: commit hash that blames are blaemd on.
+ """
+ blames_combined = {}
+ for blame_file_path, blame_file in blames.items():
+ blames_amend = [
+ utils.git_blame(commit, blame_file_path) for commit in amend_commits
+ ]
+ blames_combined[blame_file_path] = [
+ blame_combined for blame_combined in zip(blame_file, *blames_amend)
+ if blame_combined[0].commit == target_commit_hash
+ ]
+ return blames_combined
+
+
+def get_track_from_blames(blames_combined, virtual_goal_commit, amend_commits,
+ commit_choice_cache, commit_msg_cache):
+ """Blames diffs and locate the amend commits.
+
+ Returns a tuple containing:
+ - a set of commit hashes in amend_commits tree;
+ - a line-by-line mapping for files in diff to commit hashes in
+ amend_commits tree of diffed lines.
+
+ Args:
+ blames_combined: a map from path to a list of tuple. each tuple reflect
+ one line, and has len(amend_commits)+1 elements. See more details in
+ search_blame_line.
+ virtual_goal_commit: a commit that contains no useful history for diffs.
+ amend_commits: list of HEAD commit hashes that refers to tree that can
+ amend the diffs.
+ commit_choice_cache: caches user choice on which amend commit to use.
+ commit_msg_cache: caches commit metadata.
+ """
+ blame_untracked_lines = {}
+ commits_to_track = set()
+
+ for blame_file_path, blame_lines in blames_combined.items():
+ blame_untracked_lines[blame_file_path] = []
+ for blame_line in blame_lines:
+ original_commits = tuple(
+ blame_amend.commit for blame_amend in list(blame_line)[1:])
+ chosen = commit_choice_cache.get(original_commits)
+ if chosen is None:
+ for idx, original_commit in enumerate(original_commits):
+ print('%d: %s' % (idx,
+ commit_msg_cache[original_commit].title))
+ # No validation on user_choice since no untrusted user.
+ # Also the developer can rerun if entered wrongly by accident.
+ user_choice = int(input('Choose patch: '))
+ chosen = original_commits[user_choice]
+ commit_choice_cache[original_commits] = chosen
+ commits_to_track.add(chosen)
+ blame_untracked_lines[blame_file_path].append((blame_line[0],
+ chosen))
+
+ return commits_to_track, blame_untracked_lines
+
+
+def reconstruct_file(blame_goal, blame_base, lines_to_reconstruct,
+ virtual_goal_commit):
+ """Reconstrucs a file to reflect changes in lines_to_reconstruct.
+
+ Takes lines to blame_base, and blame_goal it belongs lines_to_reconstruct.
+ It also deletes removed lines nearby.
+
+ Returns a binary for the new file content.
+
+ Args:
+ blame_goal: a list of utils.GitBlameLine blaming the file on
+ virtual_goal_commit.
+ blame_base: a list of utils.GitBlameLine blaming the file on last
+ commited commit.
+ lines_to_reconstruct: only to reconstruct these lines, instead of
+ everything in blame_goal. It is represented in a list of
+ GitBlameLine.
+ virtual_goal_commit: commit hash where blame_goal is based on.
+ """
+ idx_base, idx_goal = 0, 0
+ reconstructed_file = []
+
+ print('Changed lines are', [line.data for line in lines_to_reconstruct])
+ line_iter = iter(lines_to_reconstruct)
+ line = next(line_iter, None)
+ while idx_base < len(blame_base) or idx_goal< len(blame_goal):
+ # Both sides are idendical. We can't compare blame_base, and line
+ # directly due to blame commit difference could end up different lineno.
+ if (idx_base < len(blame_base) and
+ blame_base[idx_base].data == blame_goal[idx_goal].data and
+ blame_base[idx_base].commit == blame_goal[idx_goal].commit):
+ # We append this line if both sides are identical.
+ reconstructed_file.append(blame_base[idx_base].data)
+ idx_base += 1
+ idx_goal += 1
+ should_skip_base = False
+ elif line and blame_goal[idx_goal] == line:
+ # We append the line from goal, if blame_goal[idx_goal] is the line
+ # we're interested in.
+ reconstructed_file.append(line.data)
+ line = next(line_iter, None)
+ idx_goal += 1
+ should_skip_base = True
+ elif blame_goal[idx_goal].commit == virtual_goal_commit:
+ # We skip the line from goal, if the change in not in the commit
+ # we're interested. Thus, changed lines in other commits will not be
+ # reflected.
+ idx_goal += 1
+ else:
+ # We should skip base if we just appended some lines from goal.
+ # This would treat modified lines and append first and skip later.
+ # If we didn't append something from goal, lines from base should be
+ # preserved because the modified lines are not in the commit we're
+ # currently interested in.
+ if not should_skip_base:
+ reconstructed_file.append(blame_base[idx_base].data)
+ idx_base += 1
+
+ return b''.join([line + b'\n' for line in reconstructed_file])
+
+
+def reconstruct_files(track_commit, blame_untracked_lines, blames,
+ current_base_commit, virtual_goal_commit):
+ """Reconstructs files to reflect changes in track_commit.
+
+ Returns a map from file path to file content for reconstructed files.
+
+ Args:
+ track_commit: commit hashes to track, and reconstruct from.
+ blame_untracked_lines: a line-by-line mapping regarding selected amend
+ commits for diffs. see get_track_from_blames for more.
+ blames: a map from filename to list of utils.GitBlameLine
+ current_base_commit: commit hashes for HEAD of base that contains base
+ history + already committed amend history.
+ virtual_goal_commit: commit hash for one giant commit that has no
+ history. virtual_goal_commit is one commit ahead of
+ current_base_commit.
+ """
+ lines_to_track = collections.defaultdict(list)
+ for file, lines in blame_untracked_lines.items():
+ for line in lines:
+ if line[1] == track_commit:
+ lines_to_track[file].append(line[0])
+ constructed_files = {}
+ for current_file, current_file_lines in lines_to_track.items():
+ print('Reconstructing', current_file, 'for', track_commit)
+ blame_base = utils.git_blame(current_base_commit, current_file)
+ constructed_files[current_file] = reconstruct_file(
+ blames[current_file], blame_base, current_file_lines,
+ virtual_goal_commit)
+ return constructed_files
+
+
+def main():
+ # Init args
+ parser = argparse.ArgumentParser(description='Reconnect git history')
+ parser.add_argument(
+ 'disconnect_from',
+ metavar='disconnect_from',
+ type=str,
+ nargs=1,
+ help='disconnect history from this commit')
+ parser.add_argument(
+ 'base_commit',
+ metavar='base_commit',
+ type=str,
+ nargs=1,
+ help='base commit to use the history')
+ parser.add_argument(
+ 'amend_commits',
+ metavar='amend_commits',
+ type=str,
+ nargs='+',
+ help='commits to amend histories from base_commit')
+
+ arg = parser.parse_args(sys.argv[1:])
+ empty_commit = disconnect(arg.disconnect_from[0], arg.base_commit[0])
+ connected_base = connect_base(empty_commit, arg.base_commit[0])
+
+ commit_msg_cache = CommitMetadataFactory()
+ commit_choice_cache = {}
+ last_commit = connected_base
+ # In each iteration of the loop, it
+ # - re-create the new goal commit, (base + committed history + (one giant)
+ # uncommited history).
+ # - blame on new goal commit and tot of amend commits. map line-by-line
+ # from uncommited to past histories.
+ # - choose one of the past commits, reconstruct files to reflect changes in
+ # that commit, and create a new commits.
+ # last_commit, commit_msg_cache, commit_choice_cache will be persistent
+ # across iteratins.
+ while True:
+ # One commit is processed per iteration.
+
+ # Create virtual target commit, and its diff.
+ virtual_goal = utils.git_commit(arg.disconnect_from[0] + '^{tree}',
+ [last_commit])
+ diffs = utils.git_difftree(None, virtual_goal)
+ if not diffs:
+ print('No diffs are found between %s and goal.' %
+ (last_commit.decode('ascii'),))
+ break
+
+ blames = blame_files(virtual_goal,
+ [diff.file.path for diff in diffs])
+ blames_combined = search_blame_line(blames, arg.amend_commits,
+ virtual_goal)
+
+ commits_to_track, blame_untracked_lines = get_track_from_blames(
+ blames_combined, virtual_goal, arg.amend_commits,
+ commit_choice_cache, commit_msg_cache)
+ if not commits_to_track:
+ print('no commits to track, stopping')
+ break
+
+ # Stablely choose one commit from commits_to_track, and reconstruct it.
+ track_commit = min(commits_to_track)
+ print('Reconstructing commit %s: %s' %
+ (track_commit, commit_msg_cache[track_commit].title))
+ constructed_files = reconstruct_files(track_commit,
+ blame_untracked_lines, blames,
+ last_commit, virtual_goal)
+
+ # Mktree and commit with re-constructed_files.
+ tree = lazytree.LazyTree(filtered_utils.get_metadata(last_commit).tree)
+ for filename, filedata in constructed_files.items():
+ blob = subprocess.check_output(
+ ['git', 'hash-object', '-w', '/dev/stdin'],
+ input=filedata).strip()
+ tree[filename] = utils.GitFile(filename, tree[filename].mode, blob)
+ meta = commit_msg_cache[track_commit]
+ last_commit = utils.git_commit(
+ tree.hash(), [last_commit],
+ (meta.message + b'\n(Reconstructed from ' + track_commit + b')\n'),
+ dict(
+ GIT_AUTHOR_NAME=meta.authorship.name,
+ GIT_AUTHOR_EMAIL=meta.authorship.email,
+ GIT_AUTHOR_DATE=b' '.join(
+ [meta.authorship.time, meta.authorship.timezone])))
+ print('Reconstructed as', last_commit)
+ # Make last commit for history reconstruction.
+ print(
+ utils.git_commit(
+ filtered_utils.get_metadata(arg.disconnect_from[0]).tree,
+ [last_commit],
+ b'Finished history reconstruction\n\nRemoving unnecessary lines\n'))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/libchrome_tools/uprev/utils.py b/libchrome_tools/uprev/utils.py
new file mode 100644
index 0000000000..cf3c2a4b58
--- /dev/null
+++ b/libchrome_tools/uprev/utils.py
@@ -0,0 +1,261 @@
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Provide some basic utility functions for libchrome tools."""
+
+import collections
+import enum
+import os
+import re
+import subprocess
+
+class DiffOperations(enum.Enum):
+ """
+ Describes operations on files
+ """
+ ADD = 1
+ DEL = 2
+ REP = 3
+
+GitFile = collections.namedtuple(
+ 'GitFile',
+ ['path', 'mode', 'id',]
+)
+
+GitDiffTree = collections.namedtuple(
+ 'GitDiffTree',
+ ['op', 'file',]
+)
+
+GitBlameLine = collections.namedtuple(
+ 'GitBlameLine',
+ ['data', 'commit', 'old_line', 'new_line',]
+)
+
+
+GIT_DIFFTREE_RE_LINE = re.compile(rb'^:([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*)\t(.*)$')
+
+
+def _reverse(files):
+ """Creates a reverse map from file path to file.
+
+ Asserts if a file path exist only once in files.
+
+ Args:
+ files: list of files.
+ """
+ files_map = {}
+ for i in files:
+ if i.path in files_map:
+ assert i.path not in files_map
+ files_map[i.path] = i
+ return files_map
+
+
+def get_file_list(commit):
+ """Gets a list of the files of the commit.
+
+ Args:
+ commit: commit hash or refs.
+ """
+
+ output = subprocess.check_output(['git', 'ls-tree', '-r',
+ commit]).split(b'\n')
+ files = []
+ # Line looks like
+ # mode<space>type<space>id<tab>file name
+ # split by tab first, and by space.
+ re_line = re.compile(rb'^([^ ]*) ([^ ]*) ([^ ]*)\t(.*)$')
+ for line in output:
+ if not line:
+ continue
+ match = re_line.match(line)
+ mode, gittype, blobhash, path = match.groups()
+ if gittype == b'commit':
+ continue
+ assert gittype == b'blob', '%s\n\n%s' % (str(output), line)
+ files.append(GitFile(path, mode, blobhash))
+ return files
+
+
+def git_difftree(treeish1, treeish2):
+ """Gets diffs between treeish1 and treeish2.
+
+ It returns a list of GitDiffTree, each GitDiffTree contains an ADD, DEL or
+ REP operation and a GitFile.
+
+ Args:
+ treeish1, treeish2: treeish to diff.
+ treeish can be tree hash or commit hash. If treeish1 is None, it
+ generate difftrees with its parent.
+ """
+ out = None
+ if treeish1 is None:
+ # Remove first line since it's tree hash printed.
+ out = subprocess.check_output(['git', 'diff-tree', '-r',
+ treeish2]).split(b'\n')[1:]
+ else:
+ out = subprocess.check_output(['git', 'diff-tree', '-r',
+ treeish1, treeish2]).split(b'\n')
+ diff = []
+ for line in out:
+ if not line:
+ continue
+ match = GIT_DIFFTREE_RE_LINE.match(line)
+ oldmode, newmode, oldhash, newhash, typeofchange, path = match.groups()
+ assert typeofchange in b'ADMT', (treeish1, treeish2, line)
+ if typeofchange == b'A':
+ diff.append(
+ GitDiffTree(DiffOperations.ADD,
+ GitFile(path, newmode, newhash)))
+ elif typeofchange == b'D':
+ diff.append(
+ GitDiffTree(DiffOperations.DEL,
+ GitFile(path, oldmode, oldhash)))
+ elif typeofchange == b'M' or typeofchange == b'T':
+ diff.append(
+ GitDiffTree(DiffOperations.REP,
+ GitFile(path, newmode, newhash)))
+ else:
+ raise Exception(b"Unsupported type: " + line)
+ return diff
+
+
+def gen_op(current_files, target_files):
+ """Returns an operation list to convert files to target_files.
+
+ Generates list of operations (add/delete/replace files) if we want to
+ convert current_files in directory to target_files
+
+ Args:
+ current_files: list of files in current directory.
+ target_files: list of files we want it to be in current directory.
+ """
+ current_file_map = _reverse(current_files)
+ target_file_map = _reverse(target_files)
+ op = []
+ for i in sorted(current_file_map):
+ if i not in target_file_map:
+ op.append((DiffOperations.DEL, current_file_map[i]))
+ for i in sorted(target_file_map):
+ if i in current_file_map and current_file_map[i] != target_file_map[i]:
+ op.append((DiffOperations.REP, target_file_map[i]))
+ elif i not in current_file_map:
+ op.append((DiffOperations.ADD, target_file_map[i]))
+ return op
+
+
+def git_mktree(files):
+ """Returns a git tree object hash after mktree recursively."""
+
+ def recursive_default_dict():
+ return collections.defaultdict(recursive_default_dict)
+
+ tree = recursive_default_dict()
+ for f in files:
+ directories = f.path.split(b'/')
+ directories, filename = directories[:-1], directories[-1]
+ cwd = tree
+ for directory in directories:
+ # If cwd is a GitFile, which means a file and a directory shares the
+ # same name.
+ assert type(cwd) == collections.defaultdict
+ cwd = cwd[directory]
+ assert filename not in cwd
+ cwd[filename] = f
+
+ def _mktree(prefix, node):
+ objects = []
+ for name, val in node.items():
+ prefix.append(name)
+ if isinstance(val, collections.defaultdict):
+ tree_hash = _mktree(prefix, val)
+ objects.append(b'\t'.join(
+ [b' '.join([b'040000', b'tree', tree_hash]), name]))
+ else:
+ path = b'/'.join(prefix)
+ assert path == val.path, '%s\n%s' % (str(path), str(val.path))
+ objects.append(b'\t'.join(
+ [b' '.join([val.mode, b'blob', val.id]), name]))
+ prefix.pop(-1)
+ return subprocess.check_output(['git', 'mktree'],
+ input=b'\n'.join(objects)).strip(b'\n')
+
+ return _mktree([], tree)
+
+
+def git_commit(tree, parents, message=b"", extra_env={}):
+ """Creates a commit.
+
+ Args:
+ tree: tree object id.
+ parents: parent commit id.
+ message: commit message.
+ extra_env: extra environment variables passed to git.
+ """
+ parent_args = []
+ for parent in parents:
+ parent_args.append('-p')
+ parent_args.append(parent)
+ return subprocess.check_output(
+ ['git', 'commit-tree', tree] + parent_args,
+ input=message,
+ env=dict(os.environ, **extra_env)).strip(b'\n')
+
+
+def git_revlist(from_commit, to_commit):
+ """Returns a list of commits and their parents.
+
+ Each item in the list is a tuple, containing two elements.
+ The first element is the commit hash; the second element is a list of parent
+ commits' hash.
+ """
+
+ commits = []
+ ret = None
+ if from_commit is None:
+ ret = subprocess.check_output(['git', 'rev-list', to_commit,
+ '--topo-order', '--parents'])
+ else:
+ # b'...'.join() later requires all variable to be binary-typed.
+ if type(from_commit) == str:
+ from_commit = from_commit.encode('ascii')
+ if type(to_commit) == str:
+ to_commit = to_commit.encode('ascii')
+ commit_range = b'...'.join([from_commit, to_commit])
+ ret = subprocess.check_output(['git', 'rev-list', commit_range,
+ '--topo-order', '--parents'])
+ ret = ret.split(b'\n')
+ for line in ret:
+ if not line:
+ continue
+ hashes = line.split(b' ')
+ commits.append((hashes[0], hashes[1:]))
+ return list(reversed(commits))
+
+
+def git_blame(commit, filepath):
+ """Returns line-by-line git blame.
+
+ Return value is represented by a list of GitBlameLine.
+
+ Args:
+ commit: commit hash to blame at.
+ filepath: file to blame.
+ """
+ output = subprocess.check_output(['git', 'blame', '-p',
+ commit, filepath])
+ commit, old_line, new_line = None, None, None
+ blames = []
+ COMMIT_LINE_PREFIX = re.compile(b'^[0-9a-f]* ')
+ for line in output.split(b'\n'):
+ if not line:
+ continue
+ if line[0] == ord(b'\t'):
+ assert commit != None
+ blames.append(GitBlameLine(line[1:], commit, old_line, new_line))
+ commit, old_line, new_line = None, None, None
+ elif COMMIT_LINE_PREFIX.match(line):
+ commit, old_line, new_line = line.split(b' ', 3)[0:3]
+ return blames
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn
deleted file mode 100644
index f6cd5b6c6c..0000000000
--- a/mojo/BUILD.gn
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/ui.gni")
-import("//testing/test.gni")
-
-group("mojo") {
- # Meta-target, don't link into production code.
- testonly = true
- deps = [
- ":tests",
- ]
-
- if (!(is_linux && current_cpu == "x86")) {
- deps += [ "//mojo/public" ]
- }
-
- if (is_android) {
- deps += [ "//mojo/public/java/system" ]
- }
-
- deps += [ "//services/service_manager:all" ]
-}
-
-group("tests") {
- testonly = true
- deps = [
- ":mojo_perftests",
- ":mojo_unittests",
- "//ipc:ipc_tests",
- "//services/service_manager/tests",
- ]
-}
-
-test("mojo_unittests") {
- deps = [
- "//mojo/core:test_sources",
- "//mojo/core/test:run_all_unittests",
- "//mojo/public/cpp/base:tests",
- "//mojo/public/cpp/bindings/tests",
- "//mojo/public/cpp/platform/tests",
- "//mojo/public/cpp/system/tests",
- ]
-}
-
-test("mojo_perftests") {
- deps = [
- "//mojo/core/test:run_all_perftests",
- "//mojo/core/test:test_support",
- "//mojo/public/c/system/tests:perftests",
- "//mojo/public/cpp/bindings/tests:perftests",
- ]
-
- if (!is_ios) {
- sources = [
- "//mojo/core/message_pipe_perftest.cc",
- ]
-
- deps += [
- "//base",
- "//base/test:test_support",
- "//mojo/core:embedder_internal",
- "//mojo/core:test_utils",
- "//mojo/core/embedder",
- "//testing/gtest",
- ]
- }
-}
diff --git a/mojo/PRESUBMIT.py b/mojo/PRESUBMIT.py
deleted file mode 100644
index 2766055a5d..0000000000
--- a/mojo/PRESUBMIT.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Presubmit script for mojo
-
-See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
-for more details about the presubmit API built into depot_tools.
-"""
-
-import os.path
-
-def CheckChangeOnUpload(input_api, output_api):
- # Additional python module paths (we're in src/mojo/); not everyone needs
- # them, but it's easiest to add them to everyone's path.
- # For ply and jinja2:
- third_party_path = os.path.join(
- input_api.PresubmitLocalPath(), "..", "third_party")
- # For the bindings generator:
- mojo_public_bindings_pylib_path = os.path.join(
- input_api.PresubmitLocalPath(), "public", "tools", "bindings", "pylib")
- # For the python bindings:
- mojo_python_bindings_path = os.path.join(
- input_api.PresubmitLocalPath(), "public", "python")
- # TODO(vtl): Don't lint these files until the (many) problems are fixed
- # (possibly by deleting/rewriting some files).
- temporary_black_list = input_api.DEFAULT_BLACK_LIST + \
- (r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]pylib[\\\/]mojom[\\\/]"
- r"generate[\\\/].+\.py$",
- r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]generators[\\\/].+\.py$",
- r".*\bspy[\\\/]ui[\\\/].+\.py$",
- r".*\btools[\\\/]pylib[\\\/]transitive_hash\.py$",
- r".*\btools[\\\/]test_runner\.py$")
-
- results = []
- pylint_extra_paths = [
- third_party_path,
- mojo_public_bindings_pylib_path,
- mojo_python_bindings_path,
- ]
- results += input_api.canned_checks.RunPylint(
- input_api, output_api, extra_paths_list=pylint_extra_paths,
- black_list=temporary_black_list)
- return results
diff --git a/mojo/core/BUILD.gn b/mojo/core/BUILD.gn
deleted file mode 100644
index 49a537bcb7..0000000000
--- a/mojo/core/BUILD.gn
+++ /dev/null
@@ -1,325 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/nacl/config.gni")
-import("//testing/test.gni")
-
-component("embedder_internal") {
- output_name = "mojo_core_embedder_internal"
- public_deps = [
- ":impl_for_embedder",
- ]
- visibility = [
- ":test_sources",
- "//mojo:*",
- "//mojo/core/embedder",
- ]
-}
-
-# Bits of the EDK library which do not depend on public API linkage. It is
-# not allowed for this target or any of its transitive dependencies to depend
-# on anything under //mojo/public beyond strict C type definitions.
-#
-# This is templated because it's consumed by both the ":embedder_internal"
-# component library as well as the ":mojo_core" shared library. In the former
-# case we want to export symbols, but in the latter case we don't. The template
-# stamps out two nearly identical targets which differ only in what symbols they
-# export.
-template("core_impl_source_set") {
- source_set(target_name) {
- if (invoker.for_shared_library) {
- visibility = [ ":shared_library" ]
- } else {
- visibility = [ ":embedder_internal" ]
- }
-
- public = [
- "channel.h",
- "configuration.h",
- "connection_params.h",
- "core.h",
- "data_pipe_consumer_dispatcher.h",
- "data_pipe_control_message.h",
- "data_pipe_producer_dispatcher.h",
- "dispatcher.h",
- "embedder/configuration.h",
- "embedder/process_error_callback.h",
- "entrypoints.h",
- "handle_signals_state.h",
- "handle_table.h",
- "invitation_dispatcher.h",
- "message_pipe_dispatcher.h",
- "node_controller.h",
- "options_validation.h",
- "platform_handle_dispatcher.h",
- "platform_handle_utils.h",
- "platform_shared_memory_mapping.h",
- "request_context.h",
- "scoped_process_handle.h",
- "shared_buffer_dispatcher.h",
- "user_message_impl.h",
- ]
-
- sources = [
- "atomic_flag.h",
- "broker.h",
- "broker_win.cc",
- "channel.cc",
- "channel_win.cc",
- "configuration.cc",
- "connection_params.cc",
- "core.cc",
- "data_pipe_consumer_dispatcher.cc",
- "data_pipe_control_message.cc",
- "data_pipe_producer_dispatcher.cc",
- "dispatcher.cc",
- "entrypoints.cc",
- "handle_table.cc",
- "invitation_dispatcher.cc",
- "message_pipe_dispatcher.cc",
- "node_channel.cc",
- "node_channel.h",
- "node_controller.cc",
- "platform_handle_dispatcher.cc",
- "platform_handle_in_transit.cc",
- "platform_handle_in_transit.h",
- "platform_handle_utils.cc",
- "platform_shared_memory_mapping.cc",
- "request_context.cc",
- "scoped_process_handle.cc",
- "shared_buffer_dispatcher.cc",
- "user_message_impl.cc",
- "watch.cc",
- "watch.h",
- "watcher_dispatcher.cc",
- "watcher_dispatcher.h",
- "watcher_set.cc",
- "watcher_set.h",
- ]
-
- public_deps = [
- "//base",
- "//mojo/core/ports",
- "//mojo/public/c/system:headers",
- "//mojo/public/cpp/platform",
- ]
-
- if (is_fuchsia) {
- sources += [ "channel_fuchsia.cc" ]
-
- public_deps += [ "//third_party/fuchsia-sdk:fdio" ]
- }
-
- if (is_posix) {
- if (!is_nacl || is_nacl_nonsfi) {
- sources += [
- "broker_posix.cc",
- "channel_posix.cc",
- ]
- }
- }
-
- if (is_mac && !is_ios) {
- sources += [
- "mach_port_relay.cc",
- "mach_port_relay.h",
- ]
- }
-
- if (!is_nacl || is_nacl_nonsfi) {
- sources += [
- "broker_host.cc",
- "broker_host.h",
- ]
- }
-
- defines = []
- if (invoker.for_shared_library) {
- defines += [ "MOJO_CORE_SHARED_LIBRARY" ]
- } else {
- defines += [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ]
- }
-
- deps = []
- if (is_android) {
- deps += [ "//third_party/ashmem" ]
- }
- if (!is_nacl) {
- deps += [ "//crypto" ]
- }
-
- if (is_win) {
- cflags = [ "/wd4324" ] # Structure was padded due to __declspec(align()),
- # which is uninteresting.
- }
-
- # Use target_os == "chromeos" instead of is_chromeos because we need to
- # build NaCl targets (i.e. IRT) for ChromeOS the same as the rest of ChromeOS.
- if (is_android || target_os == "chromeos") {
- defines += [ "MOJO_CORE_LEGACY_PROTOCOL" ]
- }
- }
-}
-
-core_impl_source_set("impl_for_embedder") {
- for_shared_library = false
-}
-
-if (is_chromeos || is_linux || is_android || is_win) {
- core_impl_source_set("impl_for_shared_library") {
- for_shared_library = true
- }
-
- shared_library("shared_library") {
- output_name = "mojo_core"
- sources = [
- "mojo_core.cc",
- ]
- defines = [ "MOJO_CORE_SHARED_LIBRARY" ]
- deps = [
- ":impl_for_shared_library",
- "//mojo/public/c/system:headers",
- ]
- if (is_win) {
- inputs = [
- "mojo_core.def",
- ]
- ldflags = [ "/DEF:" + rebase_path("mojo_core.def", root_build_dir) ]
- } else {
- configs += [ ":export_only_thunks_api" ]
- }
- }
-
- if (is_chromeos) {
- if (target_cpu == "arm" || target_cpu == "arm64") {
- android32_toolchain = "android_clang_arm"
- android64_toolchain = "android_clang_arm64"
- } else {
- android32_toolchain = "android_clang_x86"
- android64_toolchain = "android_clang_x64"
- }
-
- group("shared_libraries_for_arc") {
- deps = [
- ":shared_library_arc32",
- ":shared_library_arc64",
- ]
- }
-
- copy("shared_library_arc32") {
- sources = [
- "${root_out_dir}/${android32_toolchain}/libmojo_core.so",
- ]
- outputs = [
- "${root_out_dir}/libmojo_core_arc32.so",
- ]
- deps = [
- ":shared_library(//build/toolchain/android:${android32_toolchain})",
- ]
- }
-
- copy("shared_library_arc64") {
- sources = [
- "${root_out_dir}/${android64_toolchain}/libmojo_core.so",
- ]
- outputs = [
- "${root_out_dir}/libmojo_core_arc64.so",
- ]
- deps = [
- ":shared_library(//build/toolchain/android:${android64_toolchain})",
- ]
- }
- }
-
- config("export_only_thunks_api") {
- ldflags = [ "-Wl,--version-script=" +
- rebase_path("//mojo/core/export_only_thunks_api.lst",
- root_build_dir) ]
- }
-
- if (is_chromeos || is_linux || is_win) {
- test("mojo_core_unittests") {
- sources = [
- "mojo_core_unittest.cc",
- "run_all_core_unittests.cc",
- ]
-
- deps = [
- "//base",
- "//base/test:test_support",
- "//mojo/public/c/system",
- "//testing/gtest",
- ]
-
- data_deps = [
- ":shared_library",
- ]
- }
- }
-}
-
-source_set("test_utils") {
- testonly = true
-
- sources = [
- "test_utils.cc",
- "test_utils.h",
- ]
-
- public_deps = [
- "//mojo/public/c/system",
- "//mojo/public/cpp/system",
- ]
-
- deps = [
- "//base",
- "//base/test:test_support",
- "//mojo/core/test:test_support",
- "//testing/gtest:gtest",
- ]
-}
-
-source_set("test_sources") {
- testonly = true
- sources = [
- "channel_unittest.cc",
- "core_test_base.cc",
- "core_test_base.h",
- "core_unittest.cc",
- "embedder_unittest.cc",
- "handle_table_unittest.cc",
- "message_pipe_unittest.cc",
- "message_unittest.cc",
- "options_validation_unittest.cc",
- "platform_handle_dispatcher_unittest.cc",
- "quota_unittest.cc",
- "shared_buffer_dispatcher_unittest.cc",
- "shared_buffer_unittest.cc",
- "signals_unittest.cc",
- "trap_unittest.cc",
- ]
-
- if (!is_ios) {
- sources += [
- "data_pipe_unittest.cc",
- "invitation_unittest.cc",
- "multiprocess_message_pipe_unittest.cc",
- "platform_wrapper_unittest.cc",
- ]
- }
-
- deps = [
- ":test_utils",
- "//base",
- "//base/test:test_support",
- "//mojo/core:embedder_internal",
- "//mojo/core/embedder",
- "//mojo/core/ports:tests",
- "//mojo/core/test:run_all_unittests",
- "//mojo/core/test:test_support",
- "//mojo/public/cpp/system",
- "//testing/gmock",
- "//testing/gtest",
- ]
-}
diff --git a/mojo/core/broker_win.cc b/mojo/core/broker_win.cc
deleted file mode 100644
index 3ebc8839ce..0000000000
--- a/mojo/core/broker_win.cc
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <windows.h>
-
-#include <limits>
-#include <utility>
-
-#include "base/debug/alias.h"
-#include "base/memory/platform_shared_memory_region.h"
-#include "base/numerics/safe_conversions.h"
-#include "base/strings/string_piece.h"
-#include "mojo/core/broker.h"
-#include "mojo/core/broker_messages.h"
-#include "mojo/core/channel.h"
-#include "mojo/core/platform_handle_utils.h"
-#include "mojo/public/cpp/platform/named_platform_channel.h"
-
-namespace mojo {
-namespace core {
-
-namespace {
-
-// 256 bytes should be enough for anyone!
-const size_t kMaxBrokerMessageSize = 256;
-
-bool TakeHandlesFromBrokerMessage(Channel::Message* message,
- size_t num_handles,
- PlatformHandle* out_handles) {
- if (message->num_handles() != num_handles) {
- DLOG(ERROR) << "Received unexpected number of handles in broker message";
- return false;
- }
-
- std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
- DCHECK_EQ(handles.size(), num_handles);
- DCHECK(out_handles);
-
- for (size_t i = 0; i < num_handles; ++i)
- out_handles[i] = handles[i].TakeHandle();
- return true;
-}
-
-Channel::MessagePtr WaitForBrokerMessage(HANDLE pipe_handle,
- BrokerMessageType expected_type) {
- char buffer[kMaxBrokerMessageSize];
- DWORD bytes_read = 0;
- BOOL result = ::ReadFile(pipe_handle, buffer, kMaxBrokerMessageSize,
- &bytes_read, nullptr);
- if (!result) {
- // The pipe may be broken if the browser side has been closed, e.g. during
- // browser shutdown. In that case the ReadFile call will fail and we
- // shouldn't continue waiting.
- PLOG(ERROR) << "Error reading broker pipe";
- return nullptr;
- }
-
- Channel::MessagePtr message =
- Channel::Message::Deserialize(buffer, static_cast<size_t>(bytes_read));
- if (!message || message->payload_size() < sizeof(BrokerMessageHeader)) {
- LOG(ERROR) << "Invalid broker message";
-
- base::debug::Alias(&buffer[0]);
- base::debug::Alias(&bytes_read);
- CHECK(false);
- return nullptr;
- }
-
- const BrokerMessageHeader* header =
- reinterpret_cast<const BrokerMessageHeader*>(message->payload());
- if (header->type != expected_type) {
- LOG(ERROR) << "Unexpected broker message type";
-
- base::debug::Alias(&buffer[0]);
- base::debug::Alias(&bytes_read);
- CHECK(false);
- return nullptr;
- }
-
- return message;
-}
-
-} // namespace
-
-Broker::Broker(PlatformHandle handle) : sync_channel_(std::move(handle)) {
- CHECK(sync_channel_.is_valid());
- Channel::MessagePtr message = WaitForBrokerMessage(
- sync_channel_.GetHandle().Get(), BrokerMessageType::INIT);
-
- // If we fail to read a message (broken pipe), just return early. The inviter
- // handle will be null and callers must handle this gracefully.
- if (!message)
- return;
-
- PlatformHandle endpoint_handle;
- if (TakeHandlesFromBrokerMessage(message.get(), 1, &endpoint_handle)) {
- inviter_endpoint_ = PlatformChannelEndpoint(std::move(endpoint_handle));
- } else {
- // If the message has no handles, we expect it to carry pipe name instead.
- const BrokerMessageHeader* header =
- static_cast<const BrokerMessageHeader*>(message->payload());
- CHECK_GE(message->payload_size(),
- sizeof(BrokerMessageHeader) + sizeof(InitData));
- const InitData* data = reinterpret_cast<const InitData*>(header + 1);
- CHECK_EQ(message->payload_size(),
- sizeof(BrokerMessageHeader) + sizeof(InitData) +
- data->pipe_name_length * sizeof(base::char16));
- const base::char16* name_data =
- reinterpret_cast<const base::char16*>(data + 1);
- CHECK(data->pipe_name_length);
- inviter_endpoint_ = NamedPlatformChannel::ConnectToServer(
- base::StringPiece16(name_data, data->pipe_name_length).as_string());
- }
-}
-
-Broker::~Broker() {}
-
-PlatformChannelEndpoint Broker::GetInviterEndpoint() {
- return std::move(inviter_endpoint_);
-}
-
-base::WritableSharedMemoryRegion Broker::GetWritableSharedMemoryRegion(
- size_t num_bytes) {
- base::AutoLock lock(lock_);
- BufferRequestData* buffer_request;
- Channel::MessagePtr out_message = CreateBrokerMessage(
- BrokerMessageType::BUFFER_REQUEST, 0, 0, &buffer_request);
- buffer_request->size = base::checked_cast<uint32_t>(num_bytes);
- DWORD bytes_written = 0;
- BOOL result =
- ::WriteFile(sync_channel_.GetHandle().Get(), out_message->data(),
- static_cast<DWORD>(out_message->data_num_bytes()),
- &bytes_written, nullptr);
- if (!result ||
- static_cast<size_t>(bytes_written) != out_message->data_num_bytes()) {
- PLOG(ERROR) << "Error sending sync broker message";
- return base::WritableSharedMemoryRegion();
- }
-
- PlatformHandle handle;
- Channel::MessagePtr response = WaitForBrokerMessage(
- sync_channel_.GetHandle().Get(), BrokerMessageType::BUFFER_RESPONSE);
- if (response && TakeHandlesFromBrokerMessage(response.get(), 1, &handle)) {
- BufferResponseData* data;
- if (!GetBrokerMessageData(response.get(), &data))
- return base::WritableSharedMemoryRegion();
- return base::WritableSharedMemoryRegion::Deserialize(
- base::subtle::PlatformSharedMemoryRegion::Take(
- CreateSharedMemoryRegionHandleFromPlatformHandles(std::move(handle),
- PlatformHandle()),
- base::subtle::PlatformSharedMemoryRegion::Mode::kWritable,
- num_bytes,
- base::UnguessableToken::Deserialize(data->guid_high,
- data->guid_low)));
- }
-
- return base::WritableSharedMemoryRegion();
-}
-
-} // namespace core
-} // namespace mojo
diff --git a/mojo/core/channel_fuchsia.cc b/mojo/core/channel_fuchsia.cc
deleted file mode 100644
index 4386b200b8..0000000000
--- a/mojo/core/channel_fuchsia.cc
+++ /dev/null
@@ -1,466 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "mojo/core/channel.h"
-
-#include <lib/fdio/limits.h>
-#include <lib/fdio/util.h>
-#include <lib/zx/channel.h>
-#include <lib/zx/handle.h>
-#include <zircon/processargs.h>
-#include <zircon/status.h>
-#include <zircon/syscalls.h>
-#include <algorithm>
-
-#include "base/bind.h"
-#include "base/containers/circular_deque.h"
-#include "base/files/scoped_file.h"
-#include "base/fuchsia/fuchsia_logging.h"
-#include "base/location.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/message_loop/message_loop_current.h"
-#include "base/message_loop/message_pump_for_io.h"
-#include "base/stl_util.h"
-#include "base/synchronization/lock.h"
-#include "base/task_runner.h"
-#include "mojo/core/platform_handle_in_transit.h"
-
-namespace mojo {
-namespace core {
-
-namespace {
-
-const size_t kMaxBatchReadCapacity = 256 * 1024;
-
-bool UnwrapPlatformHandle(PlatformHandleInTransit handle,
- Channel::Message::HandleInfoEntry* info_out,
- std::vector<PlatformHandleInTransit>* handles_out) {
- DCHECK(handle.handle().is_valid());
-
- if (!handle.handle().is_valid_fd()) {
- *info_out = {0u, 0u};
- handles_out->emplace_back(std::move(handle));
- return true;
- }
-
- // Each FDIO file descriptor is implemented using one or more native resources
- // and can be un-wrapped into a set of |handle| and |info| pairs, with |info|
- // consisting of an FDIO-defined type & arguments (see zircon/processargs.h).
- //
- // We try to transfer the FD, but if that fails (for example if the file has
- // already been dup()d into another FD) we may need to clone.
- zx_handle_t handles[FDIO_MAX_HANDLES] = {};
- uint32_t info[FDIO_MAX_HANDLES] = {};
- zx_status_t result =
- fdio_transfer_fd(handle.handle().GetFD().get(), 0, handles, info);
- if (result > 0) {
- // On success, the fd in |handle| has been transferred and is no longer
- // valid. Release from the PlatformHandle to avoid close()ing an invalid
- // an invalid handle.
- handle.CompleteTransit();
- } else if (result == ZX_ERR_UNAVAILABLE) {
- // No luck, try cloning instead.
- result = fdio_clone_fd(handle.handle().GetFD().get(), 0, handles, info);
- }
-
- if (result <= 0) {
- ZX_DLOG(ERROR, result) << "fdio_transfer_fd("
- << handle.handle().GetFD().get() << ")";
- return false;
- }
- DCHECK_LE(result, FDIO_MAX_HANDLES);
-
- // We assume here that only the |PA_HND_TYPE| of the |info| really matters,
- // and that that is the same for all the underlying handles.
- *info_out = {PA_HND_TYPE(info[0]), result};
- for (int i = 0; i < result; ++i) {
- DCHECK_EQ(PA_HND_TYPE(info[0]), PA_HND_TYPE(info[i]));
- DCHECK_EQ(0u, PA_HND_SUBTYPE(info[i]));
- handles_out->emplace_back(
- PlatformHandleInTransit(PlatformHandle(zx::handle(handles[i]))));
- }
-
- return true;
-}
-
-PlatformHandle WrapPlatformHandles(Channel::Message::HandleInfoEntry info,
- base::circular_deque<zx::handle>* handles) {
- PlatformHandle out_handle;
- if (!info.type) {
- out_handle = PlatformHandle(std::move(handles->front()));
- handles->pop_front();
- } else {
- if (info.count > FDIO_MAX_HANDLES)
- return PlatformHandle();
-
- // Fetch the required number of handles from |handles| and set up type info.
- zx_handle_t fd_handles[FDIO_MAX_HANDLES] = {};
- uint32_t fd_infos[FDIO_MAX_HANDLES] = {};
- for (int i = 0; i < info.count; ++i) {
- fd_handles[i] = (*handles)[i].get();
- fd_infos[i] = PA_HND(info.type, 0);
- }
-
- // Try to wrap the handles into an FDIO file descriptor.
- base::ScopedFD out_fd;
- zx_status_t result =
- fdio_create_fd(fd_handles, fd_infos, info.count, out_fd.receive());
- if (result != ZX_OK) {
- ZX_DLOG(ERROR, result) << "fdio_create_fd";
- return PlatformHandle();
- }
-
- // The handles are owned by FDIO now, so |release()| them before removing
- // the entries from |handles|.
- for (int i = 0; i < info.count; ++i) {
- ignore_result(handles->front().release());
- handles->pop_front();
- }
-
- out_handle = PlatformHandle(std::move(out_fd));
- }
- return out_handle;
-}
-
-// A view over a Channel::Message object. The write queue uses these since
-// large messages may need to be sent in chunks.
-class MessageView {
- public:
- // Owns |message|. |offset| indexes the first unsent byte in the message.
- MessageView(Channel::MessagePtr message, size_t offset)
- : message_(std::move(message)),
- offset_(offset),
- handles_(message_->TakeHandlesForTransport()) {
- DCHECK_GT(message_->data_num_bytes(), offset_);
- }
-
- MessageView(MessageView&& other) { *this = std::move(other); }
-
- MessageView& operator=(MessageView&& other) {
- message_ = std::move(other.message_);
- offset_ = other.offset_;
- handles_ = std::move(other.handles_);
- return *this;
- }
-
- ~MessageView() {}
-
- const void* data() const {
- return static_cast<const char*>(message_->data()) + offset_;
- }
-
- size_t data_num_bytes() const { return message_->data_num_bytes() - offset_; }
-
- size_t data_offset() const { return offset_; }
- void advance_data_offset(size_t num_bytes) {
- DCHECK_GT(message_->data_num_bytes(), offset_ + num_bytes);
- offset_ += num_bytes;
- }
-
- std::vector<PlatformHandleInTransit> TakeHandles() {
- if (handles_.empty())
- return std::vector<PlatformHandleInTransit>();
-
- // We can only pass Fuchsia handles via IPC, so unwrap any FDIO file-
- // descriptors in |handles_| into the underlying handles, and serialize the
- // metadata, if any, into the extra header.
- auto* handles_info = reinterpret_cast<Channel::Message::HandleInfoEntry*>(
- message_->mutable_extra_header());
- memset(handles_info, 0, message_->extra_header_size());
-
- std::vector<PlatformHandleInTransit> in_handles = std::move(handles_);
- handles_.reserve(in_handles.size());
- for (size_t i = 0; i < in_handles.size(); i++) {
- if (!UnwrapPlatformHandle(std::move(in_handles[i]), &handles_info[i],
- &handles_))
- return std::vector<PlatformHandleInTransit>();
- }
- return std::move(handles_);
- }
-
- private:
- Channel::MessagePtr message_;
- size_t offset_;
- std::vector<PlatformHandleInTransit> handles_;
-
- DISALLOW_COPY_AND_ASSIGN(MessageView);
-};
-
-class ChannelFuchsia : public Channel,
- public base::MessageLoopCurrent::DestructionObserver,
- public base::MessagePumpForIO::ZxHandleWatcher {
- public:
- ChannelFuchsia(Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> io_task_runner)
- : Channel(delegate),
- self_(this),
- handle_(
- connection_params.TakeEndpoint().TakePlatformHandle().TakeHandle()),
- io_task_runner_(io_task_runner) {
- CHECK(handle_.is_valid());
- }
-
- void Start() override {
- if (io_task_runner_->RunsTasksInCurrentSequence()) {
- StartOnIOThread();
- } else {
- io_task_runner_->PostTask(
- FROM_HERE, base::BindOnce(&ChannelFuchsia::StartOnIOThread, this));
- }
- }
-
- void ShutDownImpl() override {
- // Always shut down asynchronously when called through the public interface.
- io_task_runner_->PostTask(
- FROM_HERE, base::BindOnce(&ChannelFuchsia::ShutDownOnIOThread, this));
- }
-
- void Write(MessagePtr message) override {
- bool write_error = false;
- {
- base::AutoLock lock(write_lock_);
- if (reject_writes_)
- return;
- if (!WriteNoLock(MessageView(std::move(message), 0)))
- reject_writes_ = write_error = true;
- }
- if (write_error) {
- // Do not synchronously invoke OnWriteError(). Write() may have been
- // called by the delegate and we don't want to re-enter it.
- io_task_runner_->PostTask(
- FROM_HERE, base::BindOnce(&ChannelFuchsia::OnWriteError, this,
- Error::kDisconnected));
- }
- }
-
- void LeakHandle() override {
- DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- leak_handle_ = true;
- }
-
- bool GetReadPlatformHandles(const void* payload,
- size_t payload_size,
- size_t num_handles,
- const void* extra_header,
- size_t extra_header_size,
- std::vector<PlatformHandle>* handles,
- bool* deferred) override {
- DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- if (num_handles > std::numeric_limits<uint16_t>::max())
- return false;
-
- // Locate the handle info and verify there is enough of it.
- if (!extra_header)
- return false;
- const auto* handles_info =
- reinterpret_cast<const Channel::Message::HandleInfoEntry*>(
- extra_header);
- size_t handles_info_size = sizeof(handles_info[0]) * num_handles;
- if (handles_info_size > extra_header_size)
- return false;
-
- // Some caller-supplied handles may be FDIO file-descriptors, which were
- // un-wrapped to more than one native platform resource handle for transfer.
- // We may therefore need to expect more than |num_handles| handles to have
- // been accumulated in |incoming_handles_|, based on the handle info.
- size_t num_raw_handles = 0u;
- for (size_t i = 0; i < num_handles; ++i)
- num_raw_handles += handles_info[i].type ? handles_info[i].count : 1;
-
- // If there are too few handles then we're not ready yet, so return true
- // indicating things are OK, but leave |handles| empty.
- if (incoming_handles_.size() < num_raw_handles)
- return true;
-
- handles->reserve(num_handles);
- for (size_t i = 0; i < num_handles; ++i) {
- handles->emplace_back(
- WrapPlatformHandles(handles_info[i], &incoming_handles_));
- }
- return true;
- }
-
- private:
- ~ChannelFuchsia() override { DCHECK(!read_watch_); }
-
- void StartOnIOThread() {
- DCHECK(!read_watch_);
-
- base::MessageLoopCurrent::Get()->AddDestructionObserver(this);
-
- read_watch_.reset(
- new base::MessagePumpForIO::ZxHandleWatchController(FROM_HERE));
- base::MessageLoopCurrentForIO::Get()->WatchZxHandle(
- handle_.get(), true /* persistent */,
- ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, read_watch_.get(), this);
- }
-
- void ShutDownOnIOThread() {
- base::MessageLoopCurrent::Get()->RemoveDestructionObserver(this);
-
- read_watch_.reset();
- if (leak_handle_)
- ignore_result(handle_.release());
- handle_.reset();
-
- // May destroy the |this| if it was the last reference.
- self_ = nullptr;
- }
-
- // base::MessageLoopCurrent::DestructionObserver:
- void WillDestroyCurrentMessageLoop() override {
- DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- if (self_)
- ShutDownOnIOThread();
- }
-
- // base::MessagePumpForIO::ZxHandleWatcher:
- void OnZxHandleSignalled(zx_handle_t handle, zx_signals_t signals) override {
- DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- CHECK_EQ(handle, handle_.get());
- DCHECK((ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED) & signals);
-
- // We always try to read message(s), even if ZX_CHANNEL_PEER_CLOSED, since
- // the peer may have closed while messages were still unread, in the pipe.
-
- bool validation_error = false;
- bool read_error = false;
- size_t next_read_size = 0;
- size_t buffer_capacity = 0;
- size_t total_bytes_read = 0;
- do {
- buffer_capacity = next_read_size;
- char* buffer = GetReadBuffer(&buffer_capacity);
- DCHECK_GT(buffer_capacity, 0u);
-
- uint32_t bytes_read = 0;
- uint32_t handles_read = 0;
- zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES] = {};
-
- zx_status_t read_result =
- handle_.read(0, buffer, buffer_capacity, &bytes_read, handles,
- base::size(handles), &handles_read);
- if (read_result == ZX_OK) {
- for (size_t i = 0; i < handles_read; ++i) {
- incoming_handles_.emplace_back(handles[i]);
- }
- total_bytes_read += bytes_read;
- if (!OnReadComplete(bytes_read, &next_read_size)) {
- read_error = true;
- validation_error = true;
- break;
- }
- } else if (read_result == ZX_ERR_BUFFER_TOO_SMALL) {
- DCHECK_LE(handles_read, base::size(handles));
- next_read_size = bytes_read;
- } else if (read_result == ZX_ERR_SHOULD_WAIT) {
- break;
- } else {
- ZX_DLOG_IF(ERROR, read_result != ZX_ERR_PEER_CLOSED, read_result)
- << "zx_channel_read";
- read_error = true;
- break;
- }
- } while (total_bytes_read < kMaxBatchReadCapacity && next_read_size > 0);
- if (read_error) {
- // Stop receiving read notifications.
- read_watch_.reset();
- if (validation_error)
- OnError(Error::kReceivedMalformedData);
- else
- OnError(Error::kDisconnected);
- }
- }
-
- // Attempts to write a message directly to the channel. If the full message
- // cannot be written, it's queued and a wait is initiated to write the message
- // ASAP on the I/O thread.
- bool WriteNoLock(MessageView message_view) {
- uint32_t write_bytes = 0;
- do {
- message_view.advance_data_offset(write_bytes);
-
- std::vector<PlatformHandleInTransit> outgoing_handles =
- message_view.TakeHandles();
- zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES] = {};
- size_t handles_count = outgoing_handles.size();
-
- DCHECK_LE(handles_count, base::size(handles));
- for (size_t i = 0; i < handles_count; ++i) {
- DCHECK(outgoing_handles[i].handle().is_valid());
- handles[i] = outgoing_handles[i].handle().GetHandle().get();
- }
-
- write_bytes = std::min(message_view.data_num_bytes(),
- static_cast<size_t>(ZX_CHANNEL_MAX_MSG_BYTES));
- zx_status_t result = handle_.write(0, message_view.data(), write_bytes,
- handles, handles_count);
- // zx_channel_write() consumes |handles| whether or not it succeeds, so
- // release() our copies now, to avoid them being double-closed.
- for (auto& outgoing_handle : outgoing_handles)
- outgoing_handle.CompleteTransit();
-
- if (result != ZX_OK) {
- // TODO(fuchsia): Handle ZX_ERR_SHOULD_WAIT flow-control errors, once
- // the platform starts generating them. See https://crbug.com/754084.
- ZX_DLOG_IF(ERROR, result != ZX_ERR_PEER_CLOSED, result)
- << "WriteNoLock(zx_channel_write)";
- return false;
- }
-
- } while (write_bytes < message_view.data_num_bytes());
-
- return true;
- }
-
- void OnWriteError(Error error) {
- DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- DCHECK(reject_writes_);
-
- if (error == Error::kDisconnected) {
- // If we can't write because the pipe is disconnected then continue
- // reading to fetch any in-flight messages, relying on end-of-stream to
- // signal the actual disconnection.
- if (read_watch_) {
- // TODO: When we add flow-control for writes, we also need to reset the
- // write-watcher here.
- return;
- }
- }
-
- OnError(error);
- }
-
- // Keeps the Channel alive at least until explicit shutdown on the IO thread.
- scoped_refptr<Channel> self_;
-
- zx::channel handle_;
- scoped_refptr<base::TaskRunner> io_task_runner_;
-
- // These members are only used on the IO thread.
- std::unique_ptr<base::MessagePumpForIO::ZxHandleWatchController> read_watch_;
- base::circular_deque<zx::handle> incoming_handles_;
- bool leak_handle_ = false;
-
- base::Lock write_lock_;
- bool reject_writes_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(ChannelFuchsia);
-};
-
-} // namespace
-
-// static
-scoped_refptr<Channel> Channel::Create(
- Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> io_task_runner) {
- return new ChannelFuchsia(delegate, std::move(connection_params),
- std::move(io_task_runner));
-}
-
-} // namespace core
-} // namespace mojo
diff --git a/mojo/core/channel_win.cc b/mojo/core/channel_win.cc
deleted file mode 100644
index 30a14867be..0000000000
--- a/mojo/core/channel_win.cc
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "mojo/core/channel.h"
-
-#include <stdint.h>
-#include <windows.h>
-
-#include <algorithm>
-#include <limits>
-#include <memory>
-
-#include "base/bind.h"
-#include "base/containers/queue.h"
-#include "base/location.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/message_loop/message_loop_current.h"
-#include "base/message_loop/message_pump_for_io.h"
-#include "base/process/process_handle.h"
-#include "base/synchronization/lock.h"
-#include "base/task_runner.h"
-#include "base/win/scoped_handle.h"
-#include "base/win/win_util.h"
-
-namespace mojo {
-namespace core {
-
-namespace {
-
-class ChannelWin : public Channel,
- public base::MessageLoopCurrent::DestructionObserver,
- public base::MessagePumpForIO::IOHandler {
- public:
- ChannelWin(Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> io_task_runner)
- : Channel(delegate), self_(this), io_task_runner_(io_task_runner) {
- if (connection_params.server_endpoint().is_valid()) {
- handle_ = connection_params.TakeServerEndpoint()
- .TakePlatformHandle()
- .TakeHandle();
- needs_connection_ = true;
- } else {
- handle_ =
- connection_params.TakeEndpoint().TakePlatformHandle().TakeHandle();
- }
-
- CHECK(handle_.IsValid());
- }
-
- void Start() override {
- io_task_runner_->PostTask(
- FROM_HERE, base::BindOnce(&ChannelWin::StartOnIOThread, this));
- }
-
- void ShutDownImpl() override {
- // Always shut down asynchronously when called through the public interface.
- io_task_runner_->PostTask(
- FROM_HERE, base::BindOnce(&ChannelWin::ShutDownOnIOThread, this));
- }
-
- void Write(MessagePtr message) override {
- if (remote_process().is_valid()) {
- // If we know the remote process handle, we transfer all outgoing handles
- // to the process now rewriting them in the message.
- std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
- for (auto& handle : handles) {
- if (handle.handle().is_valid())
- handle.TransferToProcess(remote_process().Clone());
- }
- message->SetHandles(std::move(handles));
- }
-
- bool write_error = false;
- {
- base::AutoLock lock(write_lock_);
- if (reject_writes_)
- return;
-
- bool write_now = !delay_writes_ && outgoing_messages_.empty();
- outgoing_messages_.emplace_back(std::move(message));
- if (write_now && !WriteNoLock(outgoing_messages_.front()))
- reject_writes_ = write_error = true;
- }
- if (write_error) {
- // Do not synchronously invoke OnWriteError(). Write() may have been
- // called by the delegate and we don't want to re-enter it.
- io_task_runner_->PostTask(FROM_HERE,
- base::BindOnce(&ChannelWin::OnWriteError, this,
- Error::kDisconnected));
- }
- }
-
- void LeakHandle() override {
- DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- leak_handle_ = true;
- }
-
- bool GetReadPlatformHandles(const void* payload,
- size_t payload_size,
- size_t num_handles,
- const void* extra_header,
- size_t extra_header_size,
- std::vector<PlatformHandle>* handles,
- bool* deferred) override {
- DCHECK(extra_header);
- if (num_handles > std::numeric_limits<uint16_t>::max())
- return false;
- using HandleEntry = Channel::Message::HandleEntry;
- size_t handles_size = sizeof(HandleEntry) * num_handles;
- if (handles_size > extra_header_size)
- return false;
- handles->reserve(num_handles);
- const HandleEntry* extra_header_handles =
- reinterpret_cast<const HandleEntry*>(extra_header);
- for (size_t i = 0; i < num_handles; i++) {
- HANDLE handle_value =
- base::win::Uint32ToHandle(extra_header_handles[i].handle);
- if (remote_process().is_valid()) {
- // If we know the remote process's handle, we assume it doesn't know
- // ours; that means any handle values still belong to that process, and
- // we need to transfer them to this process.
- handle_value = PlatformHandleInTransit::TakeIncomingRemoteHandle(
- handle_value, remote_process().get())
- .ReleaseHandle();
- }
- handles->emplace_back(base::win::ScopedHandle(std::move(handle_value)));
- }
- return true;
- }
-
- private:
- // May run on any thread.
- ~ChannelWin() override {}
-
- void StartOnIOThread() {
- base::MessageLoopCurrent::Get()->AddDestructionObserver(this);
- base::MessageLoopCurrentForIO::Get()->RegisterIOHandler(handle_.Get(),
- this);
-
- if (needs_connection_) {
- BOOL ok = ::ConnectNamedPipe(handle_.Get(), &connect_context_.overlapped);
- if (ok) {
- PLOG(ERROR) << "Unexpected success while waiting for pipe connection";
- OnError(Error::kConnectionFailed);
- return;
- }
-
- const DWORD err = GetLastError();
- switch (err) {
- case ERROR_PIPE_CONNECTED:
- break;
- case ERROR_IO_PENDING:
- is_connect_pending_ = true;
- AddRef();
- return;
- case ERROR_NO_DATA:
- default:
- OnError(Error::kConnectionFailed);
- return;
- }
- }
-
- // Now that we have registered our IOHandler, we can start writing.
- {
- base::AutoLock lock(write_lock_);
- if (delay_writes_) {
- delay_writes_ = false;
- WriteNextNoLock();
- }
- }
-
- // Keep this alive in case we synchronously run shutdown, via OnError(),
- // as a result of a ReadFile() failure on the channel.
- scoped_refptr<ChannelWin> keep_alive(this);
- ReadMore(0);
- }
-
- void ShutDownOnIOThread() {
- base::MessageLoopCurrent::Get()->RemoveDestructionObserver(this);
-
- // TODO(https://crbug.com/583525): This function is expected to be called
- // once, and |handle_| should be valid at this point.
- CHECK(handle_.IsValid());
- CancelIo(handle_.Get());
- if (leak_handle_)
- ignore_result(handle_.Take());
- else
- handle_.Close();
-
- // Allow |this| to be destroyed as soon as no IO is pending.
- self_ = nullptr;
- }
-
- // base::MessageLoopCurrent::DestructionObserver:
- void WillDestroyCurrentMessageLoop() override {
- DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- if (self_)
- ShutDownOnIOThread();
- }
-
- // base::MessageLoop::IOHandler:
- void OnIOCompleted(base::MessagePumpForIO::IOContext* context,
- DWORD bytes_transfered,
- DWORD error) override {
- if (error != ERROR_SUCCESS) {
- if (context == &write_context_) {
- {
- base::AutoLock lock(write_lock_);
- reject_writes_ = true;
- }
- OnWriteError(Error::kDisconnected);
- } else {
- OnError(Error::kDisconnected);
- }
- } else if (context == &connect_context_) {
- DCHECK(is_connect_pending_);
- is_connect_pending_ = false;
- ReadMore(0);
-
- base::AutoLock lock(write_lock_);
- if (delay_writes_) {
- delay_writes_ = false;
- WriteNextNoLock();
- }
- } else if (context == &read_context_) {
- OnReadDone(static_cast<size_t>(bytes_transfered));
- } else {
- CHECK(context == &write_context_);
- OnWriteDone(static_cast<size_t>(bytes_transfered));
- }
- Release();
- }
-
- void OnReadDone(size_t bytes_read) {
- DCHECK(is_read_pending_);
- is_read_pending_ = false;
-
- if (bytes_read > 0) {
- size_t next_read_size = 0;
- if (OnReadComplete(bytes_read, &next_read_size)) {
- ReadMore(next_read_size);
- } else {
- OnError(Error::kReceivedMalformedData);
- }
- } else if (bytes_read == 0) {
- OnError(Error::kDisconnected);
- }
- }
-
- void OnWriteDone(size_t bytes_written) {
- if (bytes_written == 0)
- return;
-
- bool write_error = false;
- {
- base::AutoLock lock(write_lock_);
-
- DCHECK(is_write_pending_);
- is_write_pending_ = false;
- DCHECK(!outgoing_messages_.empty());
-
- Channel::MessagePtr message = std::move(outgoing_messages_.front());
- outgoing_messages_.pop_front();
-
- // Invalidate all the scoped handles so we don't attempt to close them.
- std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
- for (auto& handle : handles)
- handle.CompleteTransit();
-
- // Overlapped WriteFile() to a pipe should always fully complete.
- if (message->data_num_bytes() != bytes_written)
- reject_writes_ = write_error = true;
- else if (!WriteNextNoLock())
- reject_writes_ = write_error = true;
- }
- if (write_error)
- OnWriteError(Error::kDisconnected);
- }
-
- void ReadMore(size_t next_read_size_hint) {
- DCHECK(!is_read_pending_);
-
- size_t buffer_capacity = next_read_size_hint;
- char* buffer = GetReadBuffer(&buffer_capacity);
- DCHECK_GT(buffer_capacity, 0u);
-
- BOOL ok =
- ::ReadFile(handle_.Get(), buffer, static_cast<DWORD>(buffer_capacity),
- NULL, &read_context_.overlapped);
- if (ok || GetLastError() == ERROR_IO_PENDING) {
- is_read_pending_ = true;
- AddRef();
- } else {
- OnError(Error::kDisconnected);
- }
- }
-
- // Attempts to write a message directly to the channel. If the full message
- // cannot be written, it's queued and a wait is initiated to write the message
- // ASAP on the I/O thread.
- bool WriteNoLock(const Channel::MessagePtr& message) {
- BOOL ok = WriteFile(handle_.Get(), message->data(),
- static_cast<DWORD>(message->data_num_bytes()), NULL,
- &write_context_.overlapped);
- if (ok || GetLastError() == ERROR_IO_PENDING) {
- is_write_pending_ = true;
- AddRef();
- return true;
- }
- return false;
- }
-
- bool WriteNextNoLock() {
- if (outgoing_messages_.empty())
- return true;
- return WriteNoLock(outgoing_messages_.front());
- }
-
- void OnWriteError(Error error) {
- DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- DCHECK(reject_writes_);
-
- if (error == Error::kDisconnected) {
- // If we can't write because the pipe is disconnected then continue
- // reading to fetch any in-flight messages, relying on end-of-stream to
- // signal the actual disconnection.
- if (is_read_pending_ || is_connect_pending_)
- return;
- }
-
- OnError(error);
- }
-
- // Keeps the Channel alive at least until explicit shutdown on the IO thread.
- scoped_refptr<Channel> self_;
-
- // The pipe handle this Channel uses for communication.
- base::win::ScopedHandle handle_;
-
- // Indicates whether |handle_| must wait for a connection.
- bool needs_connection_ = false;
-
- const scoped_refptr<base::TaskRunner> io_task_runner_;
-
- base::MessagePumpForIO::IOContext connect_context_;
- base::MessagePumpForIO::IOContext read_context_;
- bool is_connect_pending_ = false;
- bool is_read_pending_ = false;
-
- // Protects all fields potentially accessed on multiple threads via Write().
- base::Lock write_lock_;
- base::MessagePumpForIO::IOContext write_context_;
- base::circular_deque<Channel::MessagePtr> outgoing_messages_;
- bool delay_writes_ = true;
- bool reject_writes_ = false;
- bool is_write_pending_ = false;
-
- bool leak_handle_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(ChannelWin);
-};
-
-} // namespace
-
-// static
-scoped_refptr<Channel> Channel::Create(
- Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> io_task_runner) {
- return new ChannelWin(delegate, std::move(connection_params), io_task_runner);
-}
-
-} // namespace core
-} // namespace mojo
diff --git a/mojo/core/embedder/BUILD.gn b/mojo/core/embedder/BUILD.gn
deleted file mode 100644
index 47f1c39016..0000000000
--- a/mojo/core/embedder/BUILD.gn
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-component("embedder") {
- output_name = "mojo_core_embedder"
-
- public = [
- "configuration.h",
- "embedder.h",
- "scoped_ipc_support.h",
- ]
-
- sources = [
- "embedder.cc",
- "scoped_ipc_support.cc",
- ]
-
- defines = [ "IS_MOJO_CORE_EMBEDDER_IMPL" ]
-
- public_deps = [
- "//base",
- ]
-
- deps = [
- "//mojo/core:embedder_internal",
- "//mojo/public/c/system",
- ]
-}
diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc
index fc65c4d5b6..56b67464fa 100644
--- a/mojo/core/node_controller.cc
+++ b/mojo/core/node_controller.cc
@@ -933,11 +933,8 @@ void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node,
// Feed the broker any pending invitees of our own.
while (!pending_broker_clients.empty()) {
const ports::NodeName& invitee_name = pending_broker_clients.front();
- auto it = pending_invitations_.find(invitee_name);
- // If for any reason we don't have a pending invitation for the invitee,
- // there's nothing left to do: we've already swapped the relevant state into
- // the stack.
- if (it != pending_invitations_.end()) {
+ auto it = peers_.find(invitee_name);
+ if (it != peers_.end()) {
broker->AddBrokerClient(invitee_name,
it->second->CloneRemoteProcessHandle());
}
diff --git a/mojo/core/ports/BUILD.gn b/mojo/core/ports/BUILD.gn
deleted file mode 100644
index 68dce3f8eb..0000000000
--- a/mojo/core/ports/BUILD.gn
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//testing/test.gni")
-
-component("ports") {
- output_name = "mojo_core_ports"
-
- sources = [
- "event.cc",
- "event.h",
- "message_filter.h",
- "message_queue.cc",
- "message_queue.h",
- "name.cc",
- "name.h",
- "node.cc",
- "node.h",
- "node_delegate.h",
- "port.cc",
- "port.h",
- "port_locker.cc",
- "port_locker.h",
- "port_ref.cc",
- "port_ref.h",
- "user_data.h",
- "user_message.cc",
- "user_message.h",
- ]
-
- defines = [ "IS_MOJO_CORE_PORTS_IMPL" ]
-
- public_deps = [
- "//base",
- ]
-
- if (!is_nacl) {
- deps = [
- "//crypto",
- ]
- }
-}
-
-source_set("tests") {
- testonly = true
-
- sources = [
- "name_unittest.cc",
- "ports_unittest.cc",
- ]
-
- deps = [
- ":ports",
- "//base",
- "//base/test:test_support",
- "//testing/gtest",
- ]
-}
diff --git a/mojo/core/test/BUILD.gn b/mojo/core/test/BUILD.gn
deleted file mode 100644
index 6fad6fec71..0000000000
--- a/mojo/core/test/BUILD.gn
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-static_library("test_support") {
- testonly = true
- sources = [
- "mojo_test_base.cc",
- "mojo_test_base.h",
- "test_utils.h",
- "test_utils_win.cc",
- ]
-
- if (!is_ios) {
- sources += [
- "multiprocess_test_helper.cc",
- "multiprocess_test_helper.h",
- ]
- }
-
- if (is_fuchsia || is_posix) {
- sources += [ "test_utils.cc" ]
- }
-
- public_deps = [
- "//base",
- "//base/test:test_support",
- "//mojo/core/embedder",
- "//mojo/public/cpp/system",
- "//testing/gtest",
- ]
-}
-
-source_set("run_all_unittests") {
- testonly = true
- sources = [
- "run_all_unittests.cc",
- ]
-
- deps = [
- ":test_support",
- ":test_support_impl",
- "//base",
- "//base/test:test_support",
- "//mojo/core/embedder",
- "//mojo/public/c/test_support",
- "//testing/gtest",
- ]
-
- if (is_linux && !is_component_build) {
- public_configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
- }
-}
-
-source_set("run_all_perftests") {
- testonly = true
- deps = [
- ":test_support_impl",
- "//base",
- "//base/test:test_support",
- "//mojo/core/embedder",
- "//mojo/core/test:test_support",
- "//mojo/public/c/test_support",
- ]
-
- sources = [
- "run_all_perftests.cc",
- ]
-
- if (is_linux && !is_component_build) {
- public_configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
- }
-}
-
-static_library("test_support_impl") {
- testonly = true
- deps = [
- "//base",
- "//base/test:test_support",
- "//mojo/public/c/test_support",
- "//mojo/public/cpp/system",
- ]
-
- sources = [
- "test_support_impl.cc",
- "test_support_impl.h",
- ]
-}
diff --git a/mojo/core/test/test_utils_win.cc b/mojo/core/test/test_utils_win.cc
deleted file mode 100644
index 9adfb7aaa5..0000000000
--- a/mojo/core/test/test_utils_win.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "mojo/core/test/test_utils.h"
-
-#include <fcntl.h>
-#include <io.h>
-#include <stddef.h>
-#include <string.h>
-#include <windows.h>
-
-namespace mojo {
-namespace core {
-namespace test {
-
-PlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) {
- CHECK(fp);
-
- HANDLE rv = INVALID_HANDLE_VALUE;
- PCHECK(DuplicateHandle(
- GetCurrentProcess(),
- reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fp.get()))),
- GetCurrentProcess(), &rv, 0, TRUE, DUPLICATE_SAME_ACCESS))
- << "DuplicateHandle";
- return PlatformHandle(base::win::ScopedHandle(rv));
-}
-
-base::ScopedFILE FILEFromPlatformHandle(PlatformHandle h, const char* mode) {
- CHECK(h.is_valid());
- // Microsoft's documentation for |_open_osfhandle()| only discusses these
- // flags (and |_O_WTEXT|). Hmmm.
- int flags = 0;
- if (strchr(mode, 'a'))
- flags |= _O_APPEND;
- if (strchr(mode, 'r'))
- flags |= _O_RDONLY;
- if (strchr(mode, 't'))
- flags |= _O_TEXT;
- base::ScopedFILE rv(_fdopen(
- _open_osfhandle(reinterpret_cast<intptr_t>(h.ReleaseHandle()), flags),
- mode));
- PCHECK(rv) << "_fdopen";
- return rv;
-}
-
-} // namespace test
-} // namespace core
-} // namespace mojo
diff --git a/mojo/public/BUILD.gn b/mojo/public/BUILD.gn
deleted file mode 100644
index bd094c8c32..0000000000
--- a/mojo/public/BUILD.gn
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-group("public") {
- # Meta-target, don't link into production code.
- testonly = true
- deps = [
- ":sdk",
- "cpp/bindings",
- "interfaces/bindings/tests:test_interfaces",
- ]
-
- if (is_android) {
- deps += [
- "java:bindings_java",
- "java:system_java",
- ]
- }
-}
-
-group("sdk") {
- deps = [
- "c/system",
- "cpp/bindings",
- ]
-}
-
-group("fuzzers") {
- deps = [
- "tools/fuzzers",
- ]
-}
diff --git a/mojo/public/c/system/BUILD.gn b/mojo/public/c/system/BUILD.gn
deleted file mode 100644
index 6cc2b02a7a..0000000000
--- a/mojo/public/c/system/BUILD.gn
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-component("system") {
- output_name = "mojo_public_system"
-
- sources = [
- "thunks.cc",
- ]
-
- defines = [ "MOJO_SYSTEM_IMPLEMENTATION" ]
-
- public_deps = [
- ":headers",
- ]
-
- deps = [
- "//base",
- ]
-}
-
-source_set("headers") {
- public = [
- "buffer.h",
- "core.h",
- "data_pipe.h",
- "functions.h",
- "invitation.h",
- "macros.h",
- "message_pipe.h",
- "platform_handle.h",
- "system_export.h",
- "thunks.h",
- "trap.h",
- "types.h",
- ]
-}
diff --git a/mojo/public/c/system/tests/BUILD.gn b/mojo/public/c/system/tests/BUILD.gn
deleted file mode 100644
index f116cc6c91..0000000000
--- a/mojo/public/c/system/tests/BUILD.gn
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-source_set("tests") {
- testonly = true
-
- visibility = [
- "//mojo/public/cpp/system/tests:mojo_unittests",
- "//mojo/public/cpp/system/tests:tests",
- ]
-
- sources = [
- "core_api_unittest.cc",
- "core_unittest_pure_c.c",
- "macros_unittest.cc",
- ]
-
- deps = [
- "//mojo/public/c/system",
- "//mojo/public/cpp/system",
- "//testing/gtest",
- ]
-}
-
-source_set("perftests") {
- testonly = true
-
- sources = [
- "core_perftest.cc",
- ]
-
- deps = [
- "//mojo/public/cpp/system",
- "//mojo/public/cpp/test_support:test_utils",
- "//testing/gtest",
- ]
-}
diff --git a/mojo/public/c/test_support/BUILD.gn b/mojo/public/c/test_support/BUILD.gn
deleted file mode 100644
index e2abd5807e..0000000000
--- a/mojo/public/c/test_support/BUILD.gn
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-static_library("test_support") {
- output_name = "mojo_public_test_support"
-
- sources = [
- "test_support.h",
-
- # TODO(vtl): Convert this to thunks http://crbug.com/386799
- "../../tests/test_support_private.cc",
- "../../tests/test_support_private.h",
- ]
-}
diff --git a/mojo/public/cpp/base/BUILD.gn b/mojo/public/cpp/base/BUILD.gn
deleted file mode 100644
index c57ac0bf1d..0000000000
--- a/mojo/public/cpp/base/BUILD.gn
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//mojo/public/tools/bindings/mojom.gni")
-
-component("base") {
- output_name = "mojo_base_lib"
-
- sources = [
- "big_buffer.cc",
- "big_buffer.h",
- ]
-
- defines = [ "IS_MOJO_BASE_IMPL" ]
-
- public_deps = [
- "//base",
- "//mojo/public/cpp/bindings",
- "//mojo/public/cpp/system",
- ]
-}
-
-# Normally typemap traits sources should be build directly into mojom targets
-# via the typemap file. This target is for typemapped mojo_base types whose
-# traits are shared between chromium and blink variants.
-component("shared_typemap_traits") {
- output_name = "mojo_base_shared_typemap_traits"
-
- sources = [
- "big_buffer_mojom_traits.cc",
- "big_buffer_mojom_traits.h",
- "file_info_mojom_traits.cc",
- "file_info_mojom_traits.h",
- "file_path_mojom_traits.cc",
- "file_path_mojom_traits.h",
- "shared_memory_mojom_traits.cc",
- "shared_memory_mojom_traits.h",
- "time_mojom_traits.cc",
- "time_mojom_traits.h",
- "values_mojom_traits.cc",
- "values_mojom_traits.h",
- ]
-
- defines = [ "IS_MOJO_BASE_SHARED_TRAITS_IMPL" ]
-
- public_deps = [
- ":base",
- "//base:i18n",
- "//mojo/public/mojom/base:base_shared",
- ]
-}
-
-source_set("tests") {
- testonly = true
-
- sources = [
- "big_buffer_unittest.cc",
- "big_string_unittest.cc",
- "file_path_unittest.cc",
- "file_unittest.cc",
- "memory_allocator_dump_cross_process_uid_unittest.cc",
- "process_id_unittest.cc",
- "read_only_buffer_unittest.cc",
- "ref_counted_memory_unittest.cc",
- "shared_memory_unittest.cc",
- "string16_unittest.cc",
- "text_direction_unittest.cc",
- "thread_priority_unittest.cc",
- "time_unittest.cc",
- "unguessable_token_unittest.cc",
- "values_unittest.cc",
- ]
-
- public_deps = [
- ":base",
- ":shared_typemap_traits",
- "//base",
- "//base/test:test_support",
- "//mojo/public/cpp/test_support:test_utils",
- "//mojo/public/mojom/base",
- "//mojo/public/mojom/base:read_only_buffer",
- "//testing/gtest",
- ]
-}
diff --git a/mojo/public/cpp/base/logfont_win.typemap b/mojo/public/cpp/base/logfont_win.typemap
deleted file mode 100644
index daaf6fcca6..0000000000
--- a/mojo/public/cpp/base/logfont_win.typemap
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//mojo/public/mojom/base/logfont_win.mojom"
-public_headers = [
- "//base/win/windows_full.h",
- "//base/win/windows_types.h",
-]
-traits_headers = [ "//mojo/public/cpp/base/logfont_win_mojom_traits.h" ]
-public_deps = [
- "//base",
-]
-sources = [
- "//mojo/public/cpp/base/logfont_win_mojom_traits.cc",
- "//mojo/public/cpp/base/logfont_win_mojom_traits.h",
-]
-type_mappings = [ "mojo_base.mojom.LOGFONT=::LOGFONT" ]
diff --git a/mojo/public/cpp/base/logfont_win_mojom_traits.cc b/mojo/public/cpp/base/logfont_win_mojom_traits.cc
deleted file mode 100644
index 3c73701ce9..0000000000
--- a/mojo/public/cpp/base/logfont_win_mojom_traits.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "mojo/public/cpp/base/logfont_win_mojom_traits.h"
-
-#include <tchar.h>
-
-#include "base/logging.h"
-#include "base/numerics/safe_conversions.h"
-
-namespace mojo {
-
-// static
-base::span<const uint8_t>
-StructTraits<mojo_base::mojom::LOGFONTDataView, ::LOGFONT>::bytes(
- const ::LOGFONT& input) {
- return base::make_span(reinterpret_cast<const uint8_t*>(&input),
- sizeof(::LOGFONT));
-}
-
-// static
-bool StructTraits<mojo_base::mojom::LOGFONTDataView, ::LOGFONT>::Read(
- mojo_base::mojom::LOGFONTDataView data,
- ::LOGFONT* out) {
- ArrayDataView<uint8_t> bytes_view;
- data.GetBytesDataView(&bytes_view);
- if (bytes_view.size() != sizeof(::LOGFONT))
- return false;
-
- const ::LOGFONT* font = reinterpret_cast<const ::LOGFONT*>(bytes_view.data());
- if (_tcsnlen(font->lfFaceName, LF_FACESIZE) >= LF_FACESIZE)
- return false;
-
- memcpy(out, font, sizeof(::LOGFONT));
- return true;
-}
-
-} // namespace mojo
diff --git a/mojo/public/cpp/base/logfont_win_mojom_traits.h b/mojo/public/cpp/base/logfont_win_mojom_traits.h
deleted file mode 100644
index 09a9fbbeee..0000000000
--- a/mojo/public/cpp/base/logfont_win_mojom_traits.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MOJO_PUBLIC_CPP_BASE_LOGFONT_WIN_MOJOM_TRAITS_H_
-#define MOJO_PUBLIC_CPP_BASE_LOGFONT_WIN_MOJOM_TRAITS_H_
-
-#include <windows.h>
-
-#include <cstdint>
-
-#include "base/component_export.h"
-#include "base/containers/span.h"
-#include "base/macros.h"
-#include "base/win/windows_types.h"
-#include "mojo/public/cpp/bindings/struct_traits.h"
-#include "mojo/public/mojom/base/logfont_win.mojom-shared.h"
-
-namespace mojo {
-
-template <>
-struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
- StructTraits<mojo_base::mojom::LOGFONTDataView, ::LOGFONT> {
- static base::span<const uint8_t> bytes(const ::LOGFONT& input);
- static bool Read(mojo_base::mojom::LOGFONTDataView data, ::LOGFONT* out);
-};
-
-} // namespace mojo
-
-#endif // MOJO_PUBLIC_CPP_BASE_LOGFONT_WIN_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
deleted file mode 100644
index 27152835ac..0000000000
--- a/mojo/public/cpp/bindings/BUILD.gn
+++ /dev/null
@@ -1,228 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/buildflag_header.gni")
-import("//build/config/nacl/config.gni")
-import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
-
-declare_args() {
- enable_mojo_tracing = false
-}
-
-buildflag_header("mojo_buildflags") {
- header = "mojo_buildflags.h"
-
- flags = [ "MOJO_TRACE_ENABLED=$enable_mojo_tracing" ]
-}
-
-# Headers and sources which generated bindings can depend upon. No need for
-# other targets to depend on this directly: just use the "bindings" target.
-component("bindings_base") {
- sources = [
- "array_data_view.h",
- "array_traits.h",
- "array_traits_span.h",
- "array_traits_stl.h",
- "associated_group.h",
- "associated_group_controller.h",
- "clone_traits.h",
- "disconnect_reason.h",
- "enum_traits.h",
- "equals_traits.h",
- "interface_data_view.h",
- "interface_id.h",
- "lib/array_internal.cc",
- "lib/array_internal.h",
- "lib/array_serialization.h",
- "lib/associated_group.cc",
- "lib/associated_group_controller.cc",
- "lib/bindings_internal.h",
- "lib/buffer.cc",
- "lib/buffer.h",
- "lib/fixed_buffer.cc",
- "lib/fixed_buffer.h",
- "lib/handle_serialization.h",
- "lib/hash_util.h",
- "lib/map_data_internal.h",
- "lib/map_serialization.h",
- "lib/may_auto_lock.h",
- "lib/message.cc",
- "lib/message_header_validator.cc",
- "lib/message_internal.cc",
- "lib/message_internal.h",
- "lib/scoped_interface_endpoint_handle.cc",
- "lib/serialization.h",
- "lib/serialization.h",
- "lib/serialization_context.cc",
- "lib/serialization_context.h",
- "lib/serialization_forward.h",
- "lib/serialization_util.h",
- "lib/string_serialization.h",
- "lib/template_util.h",
- "lib/unserialized_message_context.cc",
- "lib/unserialized_message_context.h",
- "lib/validate_params.h",
- "lib/validation_context.cc",
- "lib/validation_context.h",
- "lib/validation_errors.cc",
- "lib/validation_errors.h",
- "lib/validation_util.cc",
- "lib/validation_util.h",
- "map.h",
- "map_data_view.h",
- "map_traits.h",
- "map_traits_flat_map.h",
- "map_traits_stl.h",
- "message.h",
- "message_header_validator.h",
- "scoped_interface_endpoint_handle.h",
- "string_data_view.h",
- "string_traits.h",
- "string_traits_stl.h",
- "string_traits_string_piece.h",
- "struct_ptr.h",
- "struct_traits.h",
- "type_converter.h",
- "union_traits.h",
- ]
-
- defines = [ "IS_MOJO_CPP_BINDINGS_BASE_IMPL" ]
-
- public_deps = [
- ":mojo_buildflags",
- "//base",
- "//mojo/public/cpp/system",
- ]
-
- if (enable_ipc_fuzzer) {
- all_dependent_configs = [ "//tools/ipc_fuzzer:ipc_fuzzer_config" ]
- }
-}
-
-component("bindings") {
- sources = [
- "associated_binding.h",
- "associated_binding_set.h",
- "associated_interface_ptr.h",
- "associated_interface_ptr_info.h",
- "associated_interface_request.h",
- "binding.h",
- "binding_set.h",
- "bindings_export.h",
- "callback_helpers.h",
- "connection_error_callback.h",
- "connector.h",
- "filter_chain.h",
- "interface_endpoint_client.h",
- "interface_endpoint_controller.h",
- "interface_ptr.h",
- "interface_ptr_info.h",
- "interface_ptr_set.h",
- "interface_request.h",
- "lib/associated_binding.cc",
- "lib/associated_interface_ptr.cc",
- "lib/associated_interface_ptr_state.cc",
- "lib/associated_interface_ptr_state.h",
- "lib/binding_state.cc",
- "lib/binding_state.h",
- "lib/connector.cc",
- "lib/control_message_handler.cc",
- "lib/control_message_handler.h",
- "lib/control_message_proxy.cc",
- "lib/control_message_proxy.h",
- "lib/filter_chain.cc",
- "lib/interface_endpoint_client.cc",
- "lib/interface_ptr_state.cc",
- "lib/interface_ptr_state.h",
- "lib/interface_serialization.h",
- "lib/multiplex_router.cc",
- "lib/multiplex_router.h",
- "lib/native_enum_data.h",
- "lib/native_enum_serialization.h",
- "lib/native_struct_serialization.cc",
- "lib/native_struct_serialization.h",
- "lib/pipe_control_message_handler.cc",
- "lib/pipe_control_message_proxy.cc",
- "lib/sequence_local_sync_event_watcher.cc",
- "lib/sync_call_restrictions.cc",
- "lib/sync_event_watcher.cc",
- "lib/sync_handle_registry.cc",
- "lib/sync_handle_watcher.cc",
- "lib/task_runner_helper.cc",
- "lib/task_runner_helper.h",
- "native_enum.h",
- "pipe_control_message_handler.h",
- "pipe_control_message_handler_delegate.h",
- "pipe_control_message_proxy.h",
- "raw_ptr_impl_ref_traits.h",
- "sequence_local_sync_event_watcher.h",
- "strong_associated_binding.h",
- "strong_binding.h",
- "strong_binding_set.h",
- "sync_call_restrictions.h",
- "sync_event_watcher.h",
- "sync_handle_registry.h",
- "sync_handle_watcher.h",
- "thread_safe_interface_ptr.h",
- "unique_ptr_impl_ref_traits.h",
- ]
-
- if (enable_ipc_fuzzer && !is_nacl_nonsfi) {
- sources += [
- "lib/message_dumper.cc",
- "message_dumper.h",
- ]
- }
-
- public_deps = [
- ":bindings_base",
- ":struct_traits",
- "//base",
- "//ipc:message_support",
- "//ipc:param_traits",
- "//mojo/public/cpp/system",
- "//mojo/public/interfaces/bindings",
- ]
-
- deps = [
- "//ipc:native_handle_type_converters",
- ]
-
- defines = [ "MOJO_CPP_BINDINGS_IMPLEMENTATION" ]
-}
-
-source_set("struct_traits") {
- sources = [
- "array_traits.h",
- "enum_traits.h",
- "lib/template_util.h",
- "map_traits.h",
- "string_traits.h",
- "struct_traits.h",
- "union_traits.h",
- ]
-}
-
-if (!is_ios) {
- # TODO(yzshen): crbug.com/617718 Consider moving this into blink.
- source_set("wtf_support") {
- sources = [
- "array_traits_wtf_vector.h",
- "lib/string_traits_wtf.cc",
- "lib/wtf_clone_equals_util.h",
- "lib/wtf_hash_util.h",
- "lib/wtf_serialization.h",
- "map_traits_wtf_hash_map.h",
- "string_traits_wtf.h",
- ]
-
- public_deps = [
- ":bindings",
- "//third_party/blink/renderer/platform:platform_export",
- "//third_party/blink/renderer/platform/wtf",
- ]
-
- public_configs = [ "//third_party/blink/renderer:config" ]
- }
-}
diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
deleted file mode 100644
index 82038a186d..0000000000
--- a/mojo/public/cpp/bindings/tests/BUILD.gn
+++ /dev/null
@@ -1,156 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-source_set("tests") {
- testonly = true
-
- sources = [
- "associated_interface_unittest.cc",
- "bind_task_runner_unittest.cc",
- "binding_callback_unittest.cc",
- "binding_set_unittest.cc",
- "binding_unittest.cc",
- "bindings_test_base.cc",
- "bindings_test_base.h",
- "buffer_unittest.cc",
- "callback_helpers_unittest.cc",
- "connector_unittest.cc",
- "constant_unittest.cc",
- "container_test_util.cc",
- "container_test_util.h",
- "data_view_unittest.cc",
- "equals_unittest.cc",
- "handle_passing_unittest.cc",
- "hash_unittest.cc",
- "interface_ptr_unittest.cc",
- "lazy_serialization_unittest.cc",
- "map_unittest.cc",
- "message_queue.cc",
- "message_queue.h",
- "multiplex_router_unittest.cc",
- "native_struct_unittest.cc",
- "report_bad_message_unittest.cc",
- "request_response_unittest.cc",
- "router_test_util.cc",
- "router_test_util.h",
- "sample_service_unittest.cc",
- "serialization_warning_unittest.cc",
- "struct_unittest.cc",
- "sync_handle_registry_unittest.cc",
- "sync_method_unittest.cc",
- "test_helpers_unittest.cc",
- "type_conversion_unittest.cc",
- "union_unittest.cc",
- "validation_context_unittest.cc",
- "validation_unittest.cc",
- "variant_test_util.h",
- ]
-
- deps = [
- ":mojo_public_bindings_test_utils",
- "//base/test:test_support",
- "//mojo/core/embedder",
- "//mojo/public/cpp/bindings",
- "//mojo/public/cpp/system",
- "//mojo/public/cpp/test_support:test_utils",
- "//mojo/public/interfaces/bindings/tests:test_associated_interfaces",
- "//mojo/public/interfaces/bindings/tests:test_export_component",
- "//mojo/public/interfaces/bindings/tests:test_export_component2",
- "//mojo/public/interfaces/bindings/tests:test_exported_import",
- "//mojo/public/interfaces/bindings/tests:test_interfaces",
- "//mojo/public/interfaces/bindings/tests:test_struct_traits_interfaces",
- "//testing/gtest",
- ]
-
- data = [
- "//mojo/public/interfaces/bindings/tests/data/validation/",
- ]
-
- if (is_ios) {
- assert_no_deps = [ "//third_party/WebKit/*" ]
- } else {
- sources += [
- "pickle_unittest.cc",
- "struct_traits_unittest.cc",
- ]
-
- deps += [
- "//mojo/public/cpp/bindings/tests:struct_with_traits_impl",
- "//mojo/public/interfaces/bindings/tests:test_interfaces_blink",
- ]
- }
-}
-
-if (!is_ios) {
- source_set("for_blink_tests") {
- testonly = true
-
- sources = [
- "container_test_util.cc",
- "container_test_util.h",
- "variant_test_util.h",
- "wtf_hash_unittest.cc",
- "wtf_map_unittest.cc",
- "wtf_types_unittest.cc",
- ]
-
- deps = [
- "//mojo/public/cpp/bindings",
- "//mojo/public/cpp/system",
- "//mojo/public/interfaces/bindings/tests:test_export_blink_component",
- "//mojo/public/interfaces/bindings/tests:test_exported_import_blink",
- "//mojo/public/interfaces/bindings/tests:test_interfaces",
- "//mojo/public/interfaces/bindings/tests:test_interfaces_blink",
- "//mojo/public/interfaces/bindings/tests:test_wtf_types",
- "//mojo/public/interfaces/bindings/tests:test_wtf_types_blink",
- "//testing/gtest",
- ]
- }
-}
-
-source_set("struct_with_traits_impl") {
- sources = [
- "struct_with_traits_impl.cc",
- "struct_with_traits_impl.h",
- ]
-
- deps = [
- "//base",
- "//mojo/public/cpp/system:system",
- ]
-}
-
-source_set("perftests") {
- testonly = true
-
- sources = [
- "bindings_perftest.cc",
- ]
-
- if (!is_ios) {
- sources += [ "e2e_perftest.cc" ]
- }
-
- deps = [
- "//base/test:test_support",
- "//mojo/core/embedder",
- "//mojo/core/test:test_support",
- "//mojo/public/cpp/bindings",
- "//mojo/public/cpp/system",
- "//mojo/public/cpp/test_support:test_utils",
- "//mojo/public/interfaces/bindings/tests:test_interfaces",
- "//testing/gtest",
- ]
-}
-
-source_set("mojo_public_bindings_test_utils") {
- sources = [
- "validation_test_input_parser.cc",
- "validation_test_input_parser.h",
- ]
-
- deps = [
- "//mojo/public/c/system",
- ]
-}
diff --git a/mojo/public/cpp/platform/BUILD.gn b/mojo/public/cpp/platform/BUILD.gn
deleted file mode 100644
index b0aa90ef37..0000000000
--- a/mojo/public/cpp/platform/BUILD.gn
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/nacl/config.gni")
-
-component("platform") {
- output_name = "mojo_cpp_platform"
-
- public = [
- "named_platform_channel.h",
- "platform_channel.h",
- "platform_channel_endpoint.h",
- "platform_channel_server_endpoint.h",
- "platform_handle.h",
- ]
-
- sources = [
- "named_platform_channel.cc",
- "named_platform_channel_win.cc",
- "platform_channel.cc",
- "platform_channel_endpoint.cc",
- "platform_channel_server_endpoint.cc",
- "platform_handle.cc",
- ]
-
- if (is_posix && (!is_nacl || is_nacl_nonsfi)) {
- public += [ "socket_utils_posix.h" ]
- sources += [ "socket_utils_posix.cc" ]
- }
-
- public_deps = [
- "//base",
- "//mojo/public/c/system:headers",
- ]
-
- if (is_posix && (!is_nacl && !is_fuchsia)) {
- sources += [ "named_platform_channel_posix.cc" ]
- }
-
- if (is_fuchsia) {
- sources += [ "named_platform_channel_fuchsia.cc" ]
- public_deps += [
- "//third_party/fuchsia-sdk:fdio",
- "//third_party/fuchsia-sdk:zx",
- ]
- }
-
- defines = [ "IS_MOJO_CPP_PLATFORM_IMPL" ]
-}
diff --git a/mojo/public/cpp/platform/named_platform_channel_fuchsia.cc b/mojo/public/cpp/platform/named_platform_channel_fuchsia.cc
deleted file mode 100644
index 44ae4af368..0000000000
--- a/mojo/public/cpp/platform/named_platform_channel_fuchsia.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "mojo/public/cpp/platform/named_platform_channel.h"
-
-namespace mojo {
-
-// static
-PlatformChannelServerEndpoint NamedPlatformChannel::CreateServerEndpoint(
- const Options& options,
- ServerName* server_name) {
- // TODO(https://crbug.com/754038): Implement, or remove dependencies.
- NOTREACHED();
- return {};
-}
-
-// static
-PlatformChannelEndpoint NamedPlatformChannel::CreateClientEndpoint(
- const ServerName& server_name) {
- // TODO(https://crbug.com/754038): Implement, or remove dependencies.
- NOTREACHED();
- return {};
-}
-
-} // namespace mojo
diff --git a/mojo/public/cpp/platform/named_platform_channel_win.cc b/mojo/public/cpp/platform/named_platform_channel_win.cc
deleted file mode 100644
index 9c329bd6d8..0000000000
--- a/mojo/public/cpp/platform/named_platform_channel_win.cc
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "mojo/public/cpp/platform/named_platform_channel.h"
-
-#include <windows.h>
-#include <memory>
-
-// NOTE: This needs to be included *after* windows.h.
-#include <sddl.h>
-
-#include "base/rand_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/win/scoped_handle.h"
-#include "base/win/windows_version.h"
-
-namespace mojo {
-
-namespace {
-
-// A DACL to grant:
-// GA = Generic All
-// access to:
-// SY = LOCAL_SYSTEM
-// BA = BUILTIN_ADMINISTRATORS
-// OW = OWNER_RIGHTS
-constexpr base::char16 kDefaultSecurityDescriptor[] =
- L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;OW)";
-
-NamedPlatformChannel::ServerName GenerateRandomServerName() {
- return base::UTF8ToUTF16(
- base::StringPrintf("%lu.%lu.%I64u", ::GetCurrentProcessId(),
- ::GetCurrentThreadId(), base::RandUint64()));
-}
-
-base::string16 GetPipeNameFromServerName(
- const NamedPlatformChannel::ServerName& server_name) {
- return L"\\\\.\\pipe\\mojo." + server_name;
-}
-
-} // namespace
-
-// static
-PlatformChannelServerEndpoint NamedPlatformChannel::CreateServerEndpoint(
- const Options& options,
- ServerName* server_name) {
- ServerName name = options.server_name;
- if (name.empty())
- name = GenerateRandomServerName();
-
- PSECURITY_DESCRIPTOR security_desc = nullptr;
- ULONG security_desc_len = 0;
- PCHECK(::ConvertStringSecurityDescriptorToSecurityDescriptor(
- options.security_descriptor.empty() ? kDefaultSecurityDescriptor
- : options.security_descriptor.c_str(),
- SDDL_REVISION_1, &security_desc, &security_desc_len));
- std::unique_ptr<void, decltype(::LocalFree)*> p(security_desc, ::LocalFree);
- SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
- security_desc, FALSE};
-
- const DWORD kOpenMode = options.enforce_uniqueness
- ? PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
- FILE_FLAG_FIRST_PIPE_INSTANCE
- : PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED;
- const DWORD kPipeMode =
- PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS;
-
- base::string16 pipe_name = GetPipeNameFromServerName(name);
- PlatformHandle handle(base::win::ScopedHandle(::CreateNamedPipeW(
- pipe_name.c_str(), kOpenMode, kPipeMode,
- options.enforce_uniqueness ? 1 : 255, // Max instances.
- 4096, // Out buffer size.
- 4096, // In buffer size.
- 5000, // Timeout in milliseconds.
- &security_attributes)));
-
- *server_name = name;
- return PlatformChannelServerEndpoint(std::move(handle));
-}
-
-// static
-PlatformChannelEndpoint NamedPlatformChannel::CreateClientEndpoint(
- const ServerName& server_name) {
- base::string16 pipe_name = GetPipeNameFromServerName(server_name);
-
- // Note: This may block.
- if (!::WaitNamedPipeW(pipe_name.c_str(), NMPWAIT_USE_DEFAULT_WAIT))
- return PlatformChannelEndpoint();
-
- const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE;
- // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate
- // the client.
- const DWORD kFlags =
- SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | FILE_FLAG_OVERLAPPED;
- PlatformHandle handle(base::win::ScopedHandle(
- ::CreateFileW(pipe_name.c_str(), kDesiredAccess, 0, nullptr,
- OPEN_EXISTING, kFlags, nullptr)));
-
- // The server may have stopped accepting a connection between the
- // WaitNamedPipe() and CreateFile(). If this occurs, an invalid handle is
- // returned.
- DPLOG_IF(ERROR, !handle.is_valid())
- << "Named pipe " << pipe_name
- << " could not be opened after WaitNamedPipe succeeded";
- return PlatformChannelEndpoint(std::move(handle));
-}
-
-} // namespace mojo
diff --git a/mojo/public/cpp/platform/tests/BUILD.gn b/mojo/public/cpp/platform/tests/BUILD.gn
deleted file mode 100644
index d90e9f6155..0000000000
--- a/mojo/public/cpp/platform/tests/BUILD.gn
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-source_set("tests") {
- testonly = true
-
- sources = [
- "platform_handle_unittest.cc",
- ]
-
- deps = [
- "//base",
- "//mojo/public/c/system",
- "//mojo/public/cpp/platform",
- "//mojo/public/cpp/system",
- "//testing/gtest",
- ]
-}
diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn
deleted file mode 100644
index 155ac14ab3..0000000000
--- a/mojo/public/cpp/system/BUILD.gn
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# Deletes libsystem.dylib from the build dir, since it shadows
-# /usr/lib/libSystem.dylib on macOS.
-# TODO(thakis): Remove this after a while.
-action("clean_up_old_dylib") {
- script = "//build/rm.py"
- stamp = "$target_gen_dir/clean_up_stamp"
- outputs = [
- stamp,
- ]
- args = [
- "--stamp",
- rebase_path(stamp, root_build_dir),
- "-f",
- "libsystem.dylib",
- ]
-}
-
-component("system") {
- output_name = "mojo_public_system_cpp"
-
- sources = [
- "buffer.cc",
- "buffer.h",
- "core.h",
- "data_pipe.h",
- "data_pipe_drainer.cc",
- "data_pipe_drainer.h",
- "data_pipe_utils.cc",
- "data_pipe_utils.h",
- "file_data_pipe_producer.cc",
- "file_data_pipe_producer.h",
- "functions.h",
- "handle.h",
- "handle_signal_tracker.cc",
- "handle_signal_tracker.h",
- "handle_signals_state.h",
- "invitation.cc",
- "invitation.h",
- "isolated_connection.cc",
- "isolated_connection.h",
- "message.h",
- "message_pipe.cc",
- "message_pipe.h",
- "platform_handle.cc",
- "platform_handle.h",
- "scope_to_message_pipe.cc",
- "scope_to_message_pipe.h",
- "simple_watcher.cc",
- "simple_watcher.h",
- "string_data_pipe_producer.cc",
- "string_data_pipe_producer.h",
- "system_export.h",
- "trap.cc",
- "trap.h",
- "wait.cc",
- "wait.h",
- "wait_set.cc",
- "wait_set.h",
- ]
-
- public_deps = [
- "//base",
- "//mojo/public/c/system",
- "//mojo/public/cpp/platform",
- ]
- deps = [
- ":clean_up_old_dylib",
- ]
-
- defines = [ "MOJO_CPP_SYSTEM_IMPLEMENTATION" ]
-}
diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn
deleted file mode 100644
index c08f3c1099..0000000000
--- a/mojo/public/cpp/system/tests/BUILD.gn
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-source_set("tests") {
- testonly = true
-
- sources = [
- "core_unittest.cc",
- "data_pipe_drainer_unittest.cc",
- "file_data_pipe_producer_unittest.cc",
- "handle_signal_tracker_unittest.cc",
- "handle_signals_state_unittest.cc",
- "scope_to_message_pipe_unittest.cc",
- "simple_watcher_unittest.cc",
- "string_data_pipe_producer_unittest.cc",
- "wait_set_unittest.cc",
- "wait_unittest.cc",
- ]
-
- if (!is_ios) {
- sources += [ "invitation_unittest.cc" ]
- }
-
- deps = [
- "//base",
- "//base/test:test_support",
- "//mojo/core/test:test_support",
- "//mojo/public/c/system/tests",
- "//mojo/public/cpp/platform",
- "//mojo/public/cpp/system",
- "//mojo/public/cpp/test_support:test_utils",
- "//testing/gtest",
- ]
-}
diff --git a/mojo/public/cpp/test_support/BUILD.gn b/mojo/public/cpp/test_support/BUILD.gn
deleted file mode 100644
index 3312a371ba..0000000000
--- a/mojo/public/cpp/test_support/BUILD.gn
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-static_library("test_utils") {
- testonly = true
-
- sources = [
- "lib/test_support.cc",
- "lib/test_utils.cc",
- "test_utils.h",
- ]
-
- deps = [
- "//mojo/public/c/test_support",
- "//mojo/public/cpp/bindings",
- "//mojo/public/cpp/system",
- "//testing/gtest",
- ]
-}
diff --git a/mojo/public/interfaces/BUILD.gn b/mojo/public/interfaces/BUILD.gn
deleted file mode 100644
index fb11ec2250..0000000000
--- a/mojo/public/interfaces/BUILD.gn
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-group("interfaces") {
- deps = [
- "bindings",
- ]
-}
diff --git a/mojo/public/interfaces/bindings/BUILD.gn b/mojo/public/interfaces/bindings/BUILD.gn
deleted file mode 100644
index eca88c6c9e..0000000000
--- a/mojo/public/interfaces/bindings/BUILD.gn
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("../../tools/bindings/mojom.gni")
-
-mojom_component("bindings") {
- output_prefix = "mojo_mojom_bindings"
- macro_prefix = "MOJO_MOJOM_BINDINGS"
-
- visibility = [
- "//mojo/public/cpp/bindings",
- "//ipc:mojom",
- ]
-
- sources = [
- "interface_control_messages.mojom",
- "native_struct.mojom",
- "pipe_control_messages.mojom",
- ]
-
- disallow_native_types = true
- disallow_interfaces = true
-}
diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn
deleted file mode 100644
index 939c837c0f..0000000000
--- a/mojo/public/interfaces/bindings/tests/BUILD.gn
+++ /dev/null
@@ -1,266 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("../../../tools/bindings/mojom.gni")
-
-group("test_data_deps") {
- testonly = true
- data_deps = [
- ":test_interfaces_js_data_deps",
- ":test_associated_interfaces_js_data_deps",
- ":validation_test_data",
- ":validation_test_data_list",
- ]
-}
-
-copy("validation_test_data") {
- testonly = true
- sources = [
- "data/validation",
- ]
- outputs = [
- "$root_gen_dir/layout_test_data/{{source_root_relative_dir}}/{{source_file_part}}",
- ]
-}
-
-action_foreach("validation_test_data_list") {
- testonly = true
- script = "//mojo/public/tools/bindings/gen_data_files_list.py"
- inputs = mojom_generator_sources
- sources = [
- "data/validation",
- ]
- outputs = [
- "$root_gen_dir/layout_test_data/{{source_root_relative_dir}}/{{source_file_part}}_index.txt",
- ]
- args = [
- "-d",
- rebase_path(sources[0], root_build_dir),
- "-o",
- rebase_path(outputs[0], root_build_dir),
- ]
-}
-
-mojom("test_interfaces") {
- testonly = true
- sources = [
- "math_calculator.mojom",
- "no_module.mojom",
- "ping_service.mojom",
- "rect.mojom",
- "regression_tests.mojom",
- "sample_factory.mojom",
- "sample_interfaces.mojom",
- "sample_service.mojom",
- "scoping.mojom",
- "serialization_test_structs.mojom",
- "test_bad_messages.mojom",
- "test_constants.mojom",
- "test_data_view.mojom",
- "test_name_generator.mojom",
- "test_native_types.mojom",
- "test_structs.mojom",
- "test_sync_methods.mojom",
- "test_unions.mojom",
- "validation_test_interfaces.mojom",
- ]
- public_deps = [
- ":echo",
- ":test_mojom_import",
- ":test_mojom_import2",
- ]
-
- # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
- use_once_callback = false
-
- support_lazy_serialization = true
-
- # Validation tests require precise message content matching, so we avoid
- # scrambling message IDs for test interfaces.
- scramble_message_ids = false
-}
-
-component("test_export_component") {
- testonly = true
- deps = [
- ":test_export",
- ]
-}
-
-if (!is_ios) {
- component("test_export_blink_component") {
- testonly = true
- deps = [
- ":test_export_blink",
- ]
- }
-}
-
-mojom("test_export") {
- testonly = true
- sources = [
- "test_export.mojom",
- ]
- export_class_attribute = "MOJO_TEST_EXPORT"
- export_define = "MOJO_TEST_IMPLEMENTATION=1"
- export_header = "mojo/public/cpp/bindings/tests/mojo_test_export.h"
- if (!is_ios) {
- export_class_attribute_blink = "MOJO_TEST_BLINK_EXPORT"
- export_define_blink = "MOJO_TEST_BLINK_IMPLEMENTATION=1"
- export_header_blink =
- "mojo/public/cpp/bindings/tests/mojo_test_blink_export.h"
- }
- visibility = [ ":test_export_component" ]
- if (!is_ios) {
- visibility_blink = [ ":test_export_blink_component" ]
- }
-}
-
-mojom("test_exported_import") {
- testonly = true
- sources = [
- "test_import.mojom",
- ]
- public_deps = [
- ":test_export",
- ]
-
- overridden_deps = [ ":test_export" ]
- component_deps = [ ":test_export_component" ]
- if (!is_ios) {
- overridden_deps_blink = [ ":test_export" ]
- component_deps_blink = [ ":test_export_blink_component" ]
- }
-}
-
-# Used to test that it is okay to call mojom::Foo::Serialize()/Deserialize()
-# even if the mojom target is linked into another component.
-#
-# We don't use |test_export_component| for this test because
-# //mojo/public/cpp/bindings/tests depends on both |test_export_component| and
-# |test_exported_import| and therefore actually get the shared cpp sources of
-# test_export.mojom from |test_exported_import|.
-component("test_export_component2") {
- testonly = true
- public_deps = [
- ":test_export2",
- ]
-}
-
-mojom("test_export2") {
- testonly = true
- sources = [
- "test_export2.mojom",
- ]
- export_class_attribute = "MOJO_TEST_EXPORT"
- export_define = "MOJO_TEST_IMPLEMENTATION=1"
- export_header = "mojo/public/cpp/bindings/tests/mojo_test_export.h"
- visibility = [ ":test_export_component2" ]
-}
-
-mojom("test_mojom_import") {
- testonly = true
- sources = [
- "sample_import.mojom",
- ]
-}
-
-mojom("test_mojom_import_wrapper") {
- testonly = true
- public_deps = [
- ":test_mojom_import",
- ]
-}
-
-mojom("test_mojom_import_wrapper_wrapper") {
- testonly = true
- public_deps = [
- ":test_mojom_import_wrapper",
- ]
-}
-
-mojom("test_mojom_import2") {
- testonly = true
- sources = [
- "sample_import2.mojom",
- ]
- public_deps = [
- ":test_mojom_import",
- ":test_mojom_import_wrapper_wrapper",
- ]
-}
-
-mojom("test_struct_traits_interfaces") {
- testonly = true
- sources = [
- "struct_with_traits.mojom",
- ]
-
- # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
- use_once_callback = false
-
- support_lazy_serialization = true
-}
-
-mojom("test_associated_interfaces") {
- # These files are not included in the test_interfaces target because
- # associated interfaces are not supported by all bindings languages yet.
- testonly = true
- sources = [
- "test_associated_interfaces.mojom",
- "validation_test_associated_interfaces.mojom",
- ]
-
- public_deps = [
- ":test_interfaces",
- ]
-
- # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
- use_once_callback = false
-
- # Validation tests require precise message content matching, so we avoid
- # scrambling message IDs for test interfaces.
- scramble_message_ids = false
-}
-
-mojom("versioning_test_service_interfaces") {
- testonly = true
- sources = [
- "versioning_test_service.mojom",
- ]
-}
-
-mojom("versioning_test_client_interfaces") {
- testonly = true
- sources = [
- "versioning_test_client.mojom",
- ]
-}
-
-mojom("test_wtf_types") {
- testonly = true
-
- sources = [
- "test_wtf_types.mojom",
- ]
-
- # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
- use_once_callback = false
-}
-
-mojom("test_no_sources") {
- testonly = true
-
- public_deps = [
- ":test_interfaces",
- ]
-}
-
-mojom("echo") {
- testonly = true
- sources = [
- "echo.mojom",
- "echo_import/echo_import.mojom",
- ]
-}
diff --git a/mojo/public/java/BUILD.gn b/mojo/public/java/BUILD.gn
deleted file mode 100644
index c6159ab2e8..0000000000
--- a/mojo/public/java/BUILD.gn
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/rules.gni")
-
-android_library("system_java") {
- java_files = [
- "system/src/org/chromium/mojo/system/Core.java",
- "system/src/org/chromium/mojo/system/DataPipe.java",
- "system/src/org/chromium/mojo/system/Flags.java",
- "system/src/org/chromium/mojo/system/Handle.java",
- "system/src/org/chromium/mojo/system/InvalidHandle.java",
- "system/src/org/chromium/mojo/system/MessagePipeHandle.java",
- "system/src/org/chromium/mojo/system/MojoException.java",
- "system/src/org/chromium/mojo/system/MojoResult.java",
- "system/src/org/chromium/mojo/system/Pair.java",
- "system/src/org/chromium/mojo/system/ResultAnd.java",
- "system/src/org/chromium/mojo/system/SharedBufferHandle.java",
- "system/src/org/chromium/mojo/system/UntypedHandle.java",
- "system/src/org/chromium/mojo/system/RunLoop.java",
- "system/src/org/chromium/mojo/system/Watcher.java",
- ]
-
- deps = [
- "//base:base_java",
- ]
-}
-
-android_library("bindings_java") {
- java_files = [
- "bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java",
- "bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java",
- "bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java",
- "bindings/src/org/chromium/mojo/bindings/BindingsHelper.java",
- "bindings/src/org/chromium/mojo/bindings/Callbacks.java",
- "bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java",
- "bindings/src/org/chromium/mojo/bindings/Connector.java",
- "bindings/src/org/chromium/mojo/bindings/DataHeader.java",
- "bindings/src/org/chromium/mojo/bindings/Decoder.java",
- "bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java",
- "bindings/src/org/chromium/mojo/bindings/DeserializationException.java",
- "bindings/src/org/chromium/mojo/bindings/Encoder.java",
- "bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java",
- "bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java",
- "bindings/src/org/chromium/mojo/bindings/HandleOwner.java",
- "bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java",
- "bindings/src/org/chromium/mojo/bindings/Interface.java",
- "bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java",
- "bindings/src/org/chromium/mojo/bindings/MessageHeader.java",
- "bindings/src/org/chromium/mojo/bindings/Message.java",
- "bindings/src/org/chromium/mojo/bindings/MessageReceiver.java",
- "bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java",
- "bindings/src/org/chromium/mojo/bindings/RouterImpl.java",
- "bindings/src/org/chromium/mojo/bindings/Router.java",
- "bindings/src/org/chromium/mojo/bindings/SerializationException.java",
- "bindings/src/org/chromium/mojo/bindings/ServiceMessage.java",
- "bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java",
- "bindings/src/org/chromium/mojo/bindings/Struct.java",
- "bindings/src/org/chromium/mojo/bindings/Union.java",
- ]
-
- deps = [
- ":system_java",
- "//base:base_java",
- ]
-
- srcjar_deps = [ "../interfaces/bindings:bindings_java_sources" ]
-}
-
-android_library("base_java") {
- java_files = [ "base/src/org/chromium/mojo_base/BigBufferUtil.java" ]
-
- deps = [
- ":system_java",
- "//mojo/public/java/system:system_impl_java",
- "//mojo/public/mojom/base:base_java",
- ]
-}
diff --git a/mojo/public/java/system/BUILD.gn b/mojo/public/java/system/BUILD.gn
deleted file mode 100644
index 0025f5588d..0000000000
--- a/mojo/public/java/system/BUILD.gn
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/rules.gni")
-
-group("system") {
- testonly = true
- deps = [
- ":mojo_javatests",
- ":mojo_test_apk",
- ":system_impl_java",
- ]
-}
-
-generate_jni("jni_headers") {
- sources = [
- "javatests/src/org/chromium/mojo/MojoTestRule.java",
- "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java",
- ]
- public_deps = [
- ":system_impl_java_jni_headers",
- ]
-
- jni_package = "mojo"
-}
-
-generate_jni("system_impl_java_jni_headers") {
- sources = [
- "src/org/chromium/mojo/system/impl/BaseRunLoop.java",
- "src/org/chromium/mojo/system/impl/CoreImpl.java",
- "src/org/chromium/mojo/system/impl/WatcherImpl.java",
- ]
-
- jni_package = "mojo"
-}
-
-source_set("native_support") {
- sources = [
- "base_run_loop.cc",
- "core_impl.cc",
- "watcher_impl.cc",
- ]
-
- deps = [
- ":system_impl_java_jni_headers",
- "//base",
- "//mojo/public/c/system",
- "//mojo/public/cpp/system",
- ]
-}
-
-android_library("system_impl_java") {
- java_files = [
- "src/org/chromium/mojo/system/impl/BaseRunLoop.java",
- "src/org/chromium/mojo/system/impl/CoreImpl.java",
- "src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java",
- "src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java",
- "src/org/chromium/mojo/system/impl/HandleBase.java",
- "src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java",
- "src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java",
- "src/org/chromium/mojo/system/impl/UntypedHandleImpl.java",
- "src/org/chromium/mojo/system/impl/WatcherImpl.java",
- ]
-
- deps = [
- "//base:base_java",
- "//mojo/public/java:system_java",
- ]
-}
-
-# Targets should also depend on :test_support for the native side.
-android_library("test_support_java") {
- testonly = true
- java_files = [ "javatests/src/org/chromium/mojo/MojoTestRule.java" ]
- deps = [
- "//base:base_java",
- "//third_party/junit",
- ]
-}
-
-source_set("test_support") {
- testonly = true
- sources = [
- "javatests/mojo_test_rule.cc",
- ]
- deps = [
- ":jni_headers",
- "//base",
- "//base/test:test_support",
- "//mojo/core/embedder",
- ]
- defines = [ "UNIT_TEST" ]
-}
-
-android_library("mojo_javatests") {
- testonly = true
- java_files = [
- "javatests/src/org/chromium/mojo/HandleMock.java",
- "javatests/src/org/chromium/mojo/TestUtils.java",
- "javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java",
- "javatests/src/org/chromium/mojo/bindings/BindingsTest.java",
- "javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java",
- "javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java",
- "javatests/src/org/chromium/mojo/bindings/CallbacksTest.java",
- "javatests/src/org/chromium/mojo/bindings/ConnectorTest.java",
- "javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java",
- "javatests/src/org/chromium/mojo/bindings/InterfacesTest.java",
- "javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java",
- "javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java",
- "javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java",
- "javatests/src/org/chromium/mojo/bindings/RouterTest.java",
- "javatests/src/org/chromium/mojo/bindings/SerializationTest.java",
- "javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java",
- "javatests/src/org/chromium/mojo/bindings/ValidationTest.java",
- "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java",
- "javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java",
- "javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java",
- "javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java",
- ]
-
- deps = [
- ":system_impl_java",
- ":test_support_java",
- "//base:base_java",
- "//base:base_java_test_support",
- "//mojo/public/interfaces/bindings/tests:test_interfaces_java",
- "//mojo/public/interfaces/bindings/tests:test_mojom_import2_java",
- "//mojo/public/interfaces/bindings/tests:test_mojom_import_java",
- "//mojo/public/java:bindings_java",
- "//mojo/public/java:system_java",
- "//third_party/android_support_test_runner:runner_java",
- "//third_party/junit",
- ]
-
- data = [
- "//mojo/public/interfaces/bindings/tests/data/validation/",
- ]
-}
-
-shared_library("mojo_java_unittests") {
- testonly = true
-
- sources = [
- "javatests/init_library.cc",
- "javatests/validation_test_util.cc",
- ]
-
- deps = [
- ":jni_headers",
- ":native_support",
- ":system_impl_java_jni_headers",
- ":test_support",
- "//base",
- "//base/test:test_support",
- "//mojo/core/embedder",
- "//mojo/public/cpp/bindings/tests:mojo_public_bindings_test_utils",
- "//mojo/public/cpp/test_support:test_utils",
- ]
- defines = [ "UNIT_TEST" ]
- configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
- configs += [ "//build/config/android:hide_all_but_jni" ]
-}
-
-instrumentation_test_apk("mojo_test_apk") {
- deps = [
- ":mojo_javatests",
- ":system_impl_java",
- "//base:base_java",
- "//mojo/public/interfaces/bindings/tests:test_interfaces",
- "//mojo/public/java:bindings_java",
- "//third_party/android_support_test_runner:runner_java",
- ]
- shared_libraries = [ ":mojo_java_unittests" ]
- apk_name = "MojoTest"
- android_manifest = "javatests/AndroidManifest.xml"
-}
diff --git a/mojo/public/js/BUILD.gn b/mojo/public/js/BUILD.gn
deleted file mode 100644
index d6b97bda6b..0000000000
--- a/mojo/public/js/BUILD.gn
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/grit/grit_rule.gni")
-
-interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings"
-
-action("bindings") {
- bindings_js_files = [
- # This must be the first file in the list, because it initializes global
- # variable |mojo| that the others need to refer to.
- "base.js",
-
- "bindings.js",
- "interface_types.js",
- "lib/buffer.js",
- "lib/codec.js",
- "lib/connector.js",
- "lib/control_message_handler.js",
- "lib/control_message_proxy.js",
- "lib/interface_endpoint_client.js",
- "lib/interface_endpoint_handle.js",
- "lib/pipe_control_message_handler.js",
- "lib/pipe_control_message_proxy.js",
- "lib/router.js",
- "lib/unicode.js",
- "lib/validator.js",
-
- # These two needs to refer to codec.js.
- "$interfaces_bindings_gen_dir/interface_control_messages.mojom.js",
- "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.js",
- ]
- compiled_file = "$target_gen_dir/mojo_bindings.js"
-
- # TODO(yzshen): Eventually we would like to use Closure Compiler to minify the
- # bindings instead of simply concatenating the files.
- script = "//v8/tools/concatenate-files.py"
-
- sources = bindings_js_files
- outputs = [
- compiled_file,
- ]
-
- args = rebase_path(bindings_js_files, root_build_dir)
- args += [ rebase_path(compiled_file, root_build_dir) ]
-
- deps = [
- "//mojo/public/interfaces/bindings:bindings_js__generator",
- ]
-}
-
-grit("resources") {
- source = "mojo_bindings_resources.grd"
-
- # The .grd contains references to generated files.
- source_is_generated = true
-
- outputs = [
- "grit/mojo_bindings_resources.h",
- "grit/mojo_bindings_resources_map.cc",
- "grit/mojo_bindings_resources_map.h",
- "mojo_bindings_resources.pak",
- ]
- grit_flags = [
- "-E",
- "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
- ]
- deps = [
- ":bindings",
- ]
-}
diff --git a/mojo/public/mojom/base/BUILD.gn b/mojo/public/mojom/base/BUILD.gn
deleted file mode 100644
index 807d4b91be..0000000000
--- a/mojo/public/mojom/base/BUILD.gn
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//mojo/public/tools/bindings/mojom.gni")
-
-mojom_component("base") {
- sources = [
- "big_buffer.mojom",
- "big_string.mojom",
- "file.mojom",
- "file_error.mojom",
- "file_info.mojom",
- "file_path.mojom",
- "memory_allocator_dump_cross_process_uid.mojom",
- "process_id.mojom",
- "ref_counted_memory.mojom",
- "shared_memory.mojom",
- "string16.mojom",
- "text_direction.mojom",
- "thread_priority.mojom",
- "time.mojom",
- "unguessable_token.mojom",
- "values.mojom",
- ]
-
- if (is_win) {
- sources += [ "logfont_win.mojom" ]
- }
- enabled_features = []
- if (is_win) {
- enabled_features += [ "file_path_is_string16" ]
- } else {
- enabled_features += [ "file_path_is_string" ]
- }
-
- output_prefix = "mojo_base_mojom"
- macro_prefix = "MOJO_BASE_MOJOM"
-}
-
-mojom("read_only_buffer") {
- sources = [
- "read_only_buffer.mojom",
- ]
-}
diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn
deleted file mode 100644
index 4979617303..0000000000
--- a/mojo/public/tools/bindings/BUILD.gn
+++ /dev/null
@@ -1,86 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//mojo/public/tools/bindings/mojom.gni")
-import("//third_party/jinja2/jinja2.gni")
-
-action("precompile_templates") {
- sources = mojom_generator_sources
- sources += [
- "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
- "$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl",
- "$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl",
- "$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl",
- "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
- "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
- "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
- "$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_data_view_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_data_view_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/struct_unserialized_message_context.tmpl",
- "$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/union_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/union_serialization_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/union_traits_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/union_traits_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/validation_macros.tmpl",
- "$mojom_generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/wrapper_class_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/wrapper_class_template_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_declaration.tmpl",
- "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_definition.tmpl",
- "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_template_definition.tmpl",
- "$mojom_generator_root/generators/java_templates/constant_definition.tmpl",
- "$mojom_generator_root/generators/java_templates/constants.java.tmpl",
- "$mojom_generator_root/generators/java_templates/data_types_definition.tmpl",
- "$mojom_generator_root/generators/java_templates/enum.java.tmpl",
- "$mojom_generator_root/generators/java_templates/enum_definition.tmpl",
- "$mojom_generator_root/generators/java_templates/header.java.tmpl",
- "$mojom_generator_root/generators/java_templates/interface.java.tmpl",
- "$mojom_generator_root/generators/java_templates/interface_definition.tmpl",
- "$mojom_generator_root/generators/java_templates/interface_internal.java.tmpl",
- "$mojom_generator_root/generators/java_templates/struct.java.tmpl",
- "$mojom_generator_root/generators/java_templates/union.java.tmpl",
- "$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
- "$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
- "$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/module.amd.tmpl",
- "$mojom_generator_root/generators/js_templates/module_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/struct_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/union_definition.tmpl",
- "$mojom_generator_root/generators/js_templates/validation_macros.tmpl",
- ]
- script = mojom_generator_script
-
- inputs = jinja2_sources
- outputs = [
- "$target_gen_dir/cpp_templates.zip",
- "$target_gen_dir/java_templates.zip",
- "$target_gen_dir/js_templates.zip",
- ]
- args = [
- "--use_bundled_pylibs",
- "precompile",
- "-o",
- rebase_path(target_gen_dir, root_build_dir),
- ]
-}
diff --git a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
index 7af57bd968..568db8eb31 100644
--- a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
@@ -100,8 +100,8 @@ if ({{variable}} != null) {
{%- else %}
{
{%- endif %}
- for (int i{{level}} = 0; i{{level}} < {{variable}}.length; ++i{{level}}) {
- {{kind.kind|java_class_for_enum}}.validate({{variable}}[i{{level}}]);
+ for (int i{{level+1}} = 0; i{{level+1}} < {{variable}}.length; ++i{{level+1}}) {
+ {{kind.kind|java_class_for_enum}}.validate({{variable}}[i{{level+1}}]);
}
}
{%- elif kind|is_enum_kind %}
diff --git a/mojo/public/tools/fuzzers/BUILD.gn b/mojo/public/tools/fuzzers/BUILD.gn
deleted file mode 100644
index 69531552db..0000000000
--- a/mojo/public/tools/fuzzers/BUILD.gn
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# Mojo fuzzing tools
-
-import("//build/config/features.gni")
-import("//mojo/public/tools/bindings/mojom.gni")
-import("//testing/libfuzzer/fuzzer_test.gni")
-import("//third_party/protobuf/proto_library.gni")
-
-# mojo/public BUILD depends on this target. Needed for package discovery
-group("fuzzers") {
-}
-
-mojom("fuzz_mojom") {
- sources = [
- "fuzz.mojom",
- ]
-}
-
-fuzzer_test("mojo_parse_message_fuzzer") {
- sources = [
- "fuzz_impl.cc",
- "mojo_parse_message_fuzzer.cc",
- ]
- deps = [
- ":fuzz_mojom",
- "//mojo/core/embedder",
- ]
- seed_corpus = "//mojo/public/tools/fuzzers/message_corpus"
-}
-
-# MessageDumper is not meant to work on Windows.
-if (!is_win) {
- executable("mojo_fuzzer_message_dump") {
- sources = [
- "fuzz_impl.cc",
- "mojo_fuzzer_message_dump.cc",
- ]
- deps = [
- ":fuzz_mojom",
- "//base",
- "//mojo/core/embedder",
- ]
- }
-}
-
-fuzzer_test("mojo_parse_message_proto_fuzzer") {
- sources = [
- "fuzz_impl.cc",
- "mojo_parse_message_proto_fuzzer.cc",
- ]
- deps = [
- ":fuzz_mojom",
- ":mojo_fuzzer_proto",
- "//mojo/core/embedder",
- "//third_party/libprotobuf-mutator",
- ]
- seed_corpus = "//mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus"
-}
-
-proto_library("mojo_fuzzer_proto") {
- sources = [
- "mojo_fuzzer.proto",
- ]
-}
diff --git a/soong/Android.bp b/soong/Android.bp
index 7230b812d2..35fa5fb47c 100644
--- a/soong/Android.bp
+++ b/soong/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_libchrome_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_libchrome_license"],
+}
+
bootstrap_go_package {
name: "soong-libchrome",
pkgPath: "android/soong/external/libchrome",
diff --git a/testing/empty_main.cc b/testing/empty_main.cc
new file mode 100644
index 0000000000..759687fa2e
--- /dev/null
+++ b/testing/empty_main.cc
@@ -0,0 +1,8 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Used by bots that want to check that compiler command lines still work.
+int main(int argc, char** argv) {
+ return 0;
+}
diff --git a/testing/gmock_mutant.h b/testing/gmock_mutant.h
new file mode 100644
index 0000000000..2a41cf54da
--- /dev/null
+++ b/testing/gmock_mutant.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_GMOCK_MUTANT_H_
+#define TESTING_GMOCK_MUTANT_H_
+
+// The intention of this file is to make possible using GMock actions in
+// all of its syntactic beauty.
+//
+//
+// Sample usage with gMock:
+//
+// struct Mock : public ObjectDelegate {
+// MOCK_METHOD2(string, OnRequest(int n, const string& request));
+// MOCK_METHOD1(void, OnQuit(int exit_code));
+// MOCK_METHOD2(void, LogMessage(int level, const string& message));
+//
+// string HandleFlowers(const string& reply, int n, const string& request) {
+// string result = SStringPrintf("In request of %d %s ", n, request);
+// for (int i = 0; i < n; ++i) result.append(reply)
+// return result;
+// }
+//
+// void DoLogMessage(int level, const string& message) {
+// }
+//
+// void QuitMessageLoop(int seconds) {
+// base::MessageLoop* loop = base::MessageLoop::current();
+// loop->PostDelayedTask(FROM_HERE,
+// base::MessageLoop::QuitWhenIdleClosure(),
+// 1000 * seconds);
+// }
+// };
+//
+// Mock mock;
+// // Will invoke mock.HandleFlowers("orchids", n, request)
+// // "orchids" is a pre-bound argument, and <n> and <request> are call-time
+// // arguments - they are not known until the OnRequest mock is invoked.
+// EXPECT_CALL(mock, OnRequest(Ge(5), base::StartsWith("flower"))
+// .Times(1)
+// .WillOnce(Invoke(CreateFunctor(
+// &Mock::HandleFlowers, base::Unretained(&mock), string("orchids"))));
+//
+//
+// // No pre-bound arguments, two call-time arguments passed
+// // directly to DoLogMessage
+// EXPECT_CALL(mock, OnLogMessage(_, _))
+// .Times(AnyNumber())
+// .WillAlways(Invoke(CreateFunctor(
+// &Mock::DoLogMessage, base::Unretained(&mock))));
+//
+//
+// // In this case we have a single pre-bound argument - 3. We ignore
+// // all of the arguments of OnQuit.
+// EXCEPT_CALL(mock, OnQuit(_))
+// .Times(1)
+// .WillOnce(InvokeWithoutArgs(CreateFunctor(
+// &Mock::QuitMessageLoop, base::Unretained(&mock), 3)));
+//
+// MessageLoop loop;
+// loop.Run();
+//
+//
+// // Here is another example of how we can set an action that invokes
+// // method of an object that is not yet created.
+// struct Mock : public ObjectDelegate {
+// MOCK_METHOD1(void, DemiurgeCreated(Demiurge*));
+// MOCK_METHOD2(void, OnRequest(int count, const string&));
+//
+// void StoreDemiurge(Demiurge* w) {
+// demiurge_ = w;
+// }
+//
+// Demiurge* demiurge;
+// }
+//
+// EXPECT_CALL(mock, DemiurgeCreated(_)).Times(1)
+// .WillOnce(Invoke(CreateFunctor(
+// &Mock::StoreDemiurge, base::Unretained(&mock))));
+//
+// EXPECT_CALL(mock, OnRequest(_, StrEq("Moby Dick")))
+// .Times(AnyNumber())
+// .WillAlways(WithArgs<0>(Invoke(CreateFunctor(
+// &Demiurge::DecreaseMonsters, base::Unretained(&mock->demiurge_)))));
+//
+
+#include "base/bind.h"
+
+namespace testing {
+
+template <typename Signature>
+class CallbackToFunctorHelper;
+
+template <typename R, typename... Args>
+class CallbackToFunctorHelper<R(Args...)> {
+ public:
+ explicit CallbackToFunctorHelper(const base::Callback<R(Args...)>& cb)
+ : cb_(cb) {}
+
+ template <typename... RunArgs>
+ R operator()(RunArgs&&... args) {
+ return cb_.Run(std::forward<RunArgs>(args)...);
+ }
+
+ private:
+ base::Callback<R(Args...)> cb_;
+};
+
+template <typename Signature>
+CallbackToFunctorHelper<Signature>
+CallbackToFunctor(const base::Callback<Signature>& cb) {
+ return CallbackToFunctorHelper<Signature>(cb);
+}
+
+template <typename Functor>
+CallbackToFunctorHelper<typename Functor::RunType> CreateFunctor(
+ Functor functor) {
+ return CallbackToFunctor(functor);
+}
+
+template <typename Functor, typename... BoundArgs>
+CallbackToFunctorHelper<base::MakeUnboundRunType<Functor, BoundArgs...>>
+CreateFunctor(Functor functor, const BoundArgs&... args) {
+ return CallbackToFunctor(base::Bind(functor, args...));
+}
+
+} // namespace testing
+
+#endif // TESTING_GMOCK_MUTANT_H_
diff --git a/third_party/ashmem/ashmem.h b/third_party/ashmem/ashmem.h
index 3ef9f73c80..7a26a18ae3 100644
--- a/third_party/ashmem/ashmem.h
+++ b/third_party/ashmem/ashmem.h
@@ -15,3 +15,18 @@
// third_party/ashmem is Android shared memory. Instead of clone it here,
// use cutils/ashmem.h directly.
#include <cutils/ashmem.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+inline int ashmem_get_prot_region(int fd) {
+ int ret = ashmem_valid(fd);
+ if (ret < 0)
+ return ret;
+ return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_PROT_MASK));
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/ui/gfx/geometry/angle_conversions.h b/ui/gfx/geometry/angle_conversions.h
new file mode 100644
index 0000000000..68e9209f72
--- /dev/null
+++ b/ui/gfx/geometry/angle_conversions.h
@@ -0,0 +1,29 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_
+#define UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_
+
+#include "base/numerics/math_constants.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+GFX_EXPORT constexpr double DegToRad(double deg) {
+ return deg * base::kPiDouble / 180.0;
+}
+GFX_EXPORT constexpr float DegToRad(float deg) {
+ return deg * base::kPiFloat / 180.0f;
+}
+
+GFX_EXPORT constexpr double RadToDeg(double rad) {
+ return rad * 180.0 / base::kPiDouble;
+}
+GFX_EXPORT constexpr float RadToDeg(float rad) {
+ return rad * 180.0f / base::kPiFloat;
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_
diff --git a/ui/gfx/geometry/axis_transform2d.cc b/ui/gfx/geometry/axis_transform2d.cc
new file mode 100644
index 0000000000..5b7d86a450
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d.cc
@@ -0,0 +1,16 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/axis_transform2d.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string AxisTransform2d::ToString() const {
+ return base::StringPrintf("[%f, %s]", scale_,
+ translation_.ToString().c_str());
+}
+
+} // namespace gfx \ No newline at end of file
diff --git a/ui/gfx/geometry/axis_transform2d.h b/ui/gfx/geometry/axis_transform2d.h
new file mode 100644
index 0000000000..1829bf60fc
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
+#define UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
+
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+// This class implements the subset of 2D linear transforms that only
+// translation and uniform scaling are allowed.
+// Internally this is stored as a scalar pre-scale factor, and a vector
+// for post-translation. The class constructor and member accessor follows
+// the same convention.
+class GFX_EXPORT AxisTransform2d {
+ public:
+ constexpr AxisTransform2d() = default;
+ constexpr AxisTransform2d(float scale, const Vector2dF& translation)
+ : scale_(scale), translation_(translation) {}
+
+ bool operator==(const AxisTransform2d& other) const {
+ return scale_ == other.scale_ && translation_ == other.translation_;
+ }
+ bool operator!=(const AxisTransform2d& other) const {
+ return !(*this == other);
+ }
+
+ void PreScale(float scale) { scale_ *= scale; }
+ void PostScale(float scale) {
+ scale_ *= scale;
+ translation_.Scale(scale);
+ }
+ void PreTranslate(const Vector2dF& translation) {
+ translation_ += ScaleVector2d(translation, scale_);
+ }
+ void PostTranslate(const Vector2dF& translation) {
+ translation_ += translation;
+ }
+
+ void PreConcat(const AxisTransform2d& pre) {
+ PreTranslate(pre.translation_);
+ PreScale(pre.scale_);
+ }
+ void PostConcat(const AxisTransform2d& post) {
+ PostScale(post.scale_);
+ PostTranslate(post.translation_);
+ }
+
+ void Invert() {
+ DCHECK(scale_);
+ scale_ = 1.f / scale_;
+ translation_.Scale(-scale_);
+ }
+
+ PointF MapPoint(const PointF& p) const {
+ return ScalePoint(p, scale_) + translation_;
+ }
+ PointF InverseMapPoint(const PointF& p) const {
+ return ScalePoint(p - translation_, 1.f / scale_);
+ }
+
+ RectF MapRect(const RectF& r) const {
+ DCHECK(scale_ >= 0.f);
+ return ScaleRect(r, scale_) + translation_;
+ }
+ RectF InverseMapRect(const RectF& r) const {
+ DCHECK(scale_ > 0.f);
+ return ScaleRect(r - translation_, 1.f / scale_);
+ }
+
+ float scale() const { return scale_; }
+ const Vector2dF& translation() const { return translation_; }
+
+ std::string ToString() const;
+
+ private:
+ // Scale is applied before translation, i.e.
+ // this->Transform(p) == scale_ * p + translation_
+ float scale_ = 1.f;
+ Vector2dF translation_;
+};
+
+static inline AxisTransform2d PreScaleAxisTransform2d(const AxisTransform2d& t,
+ float scale) {
+ AxisTransform2d result(t);
+ result.PreScale(scale);
+ return result;
+}
+
+static inline AxisTransform2d PostScaleAxisTransform2d(const AxisTransform2d& t,
+ float scale) {
+ AxisTransform2d result(t);
+ result.PostScale(scale);
+ return result;
+}
+
+static inline AxisTransform2d PreTranslateAxisTransform2d(
+ const AxisTransform2d& t,
+ const Vector2dF& translation) {
+ AxisTransform2d result(t);
+ result.PreTranslate(translation);
+ return result;
+}
+
+static inline AxisTransform2d PostTranslateAxisTransform2d(
+ const AxisTransform2d& t,
+ const Vector2dF& translation) {
+ AxisTransform2d result(t);
+ result.PostTranslate(translation);
+ return result;
+}
+
+static inline AxisTransform2d ConcatAxisTransform2d(
+ const AxisTransform2d& post,
+ const AxisTransform2d& pre) {
+ AxisTransform2d result(post);
+ result.PreConcat(pre);
+ return result;
+}
+
+static inline AxisTransform2d InvertAxisTransform2d(const AxisTransform2d& t) {
+ AxisTransform2d result = t;
+ result.Invert();
+ return result;
+}
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const AxisTransform2d&, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
diff --git a/ui/gfx/geometry/axis_transform2d_unittest.cc b/ui/gfx/geometry/axis_transform2d_unittest.cc
new file mode 100644
index 0000000000..b132c69635
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/axis_transform2d.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/test/gfx_util.h"
+
+namespace gfx {
+namespace {
+
+TEST(AxisTransform2dTest, Mapping) {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+
+ PointF p(150.f, 100.f);
+ EXPECT_EQ(PointF(191.25f, 180.f), t.MapPoint(p));
+ EXPECT_POINTF_EQ(PointF(117.f, 36.f), t.InverseMapPoint(p));
+
+ RectF r(150.f, 100.f, 22.5f, 37.5f);
+ EXPECT_EQ(RectF(191.25f, 180.f, 28.125f, 46.875f), t.MapRect(r));
+ EXPECT_RECTF_EQ(RectF(117.f, 36.f, 18.f, 30.f), t.InverseMapRect(r));
+}
+
+TEST(AxisTransform2dTest, Scaling) {
+ {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)),
+ PreScaleAxisTransform2d(t, 1.25));
+ t.PreScale(1.25);
+ EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)), t);
+ }
+
+ {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)),
+ PostScaleAxisTransform2d(t, 1.25));
+ t.PostScale(1.25);
+ EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)), t);
+ }
+}
+
+TEST(AxisTransform2dTest, Translating) {
+ Vector2dF tr(3.f, -5.f);
+ {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(7.5f, 48.75f)),
+ PreTranslateAxisTransform2d(t, tr));
+ t.PreTranslate(tr);
+ EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(7.5f, 48.75f)), t);
+ }
+
+ {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(6.75f, 50.f)),
+ PostTranslateAxisTransform2d(t, tr));
+ t.PostTranslate(tr);
+ EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(6.75f, 50.f)), t);
+ }
+}
+
+TEST(AxisTransform2dTest, Concat) {
+ AxisTransform2d pre(1.25f, Vector2dF(3.75f, 55.f));
+ AxisTransform2d post(0.5f, Vector2dF(10.f, 5.f));
+ AxisTransform2d expectation(0.625f, Vector2dF(11.875f, 32.5f));
+ EXPECT_EQ(expectation, ConcatAxisTransform2d(post, pre));
+
+ AxisTransform2d post_concat = pre;
+ post_concat.PostConcat(post);
+ EXPECT_EQ(expectation, post_concat);
+
+ AxisTransform2d pre_concat = post;
+ pre_concat.PreConcat(pre);
+ EXPECT_EQ(expectation, pre_concat);
+}
+
+TEST(AxisTransform2dTest, Inverse) {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ AxisTransform2d inv_inplace = t;
+ inv_inplace.Invert();
+ AxisTransform2d inv_out_of_place = InvertAxisTransform2d(t);
+
+ EXPECT_AXIS_TRANSFORM2D_EQ(inv_inplace, inv_out_of_place);
+ EXPECT_AXIS_TRANSFORM2D_EQ(AxisTransform2d(),
+ ConcatAxisTransform2d(t, inv_inplace));
+ EXPECT_AXIS_TRANSFORM2D_EQ(AxisTransform2d(),
+ ConcatAxisTransform2d(inv_inplace, t));
+}
+
+} // namespace
+} // namespace gfx
diff --git a/ui/gfx/geometry/box_f.cc b/ui/gfx/geometry/box_f.cc
new file mode 100644
index 0000000000..674bb509c8
--- /dev/null
+++ b/ui/gfx/geometry/box_f.cc
@@ -0,0 +1,70 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/box_f.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string BoxF::ToString() const {
+ return base::StringPrintf("%s %fx%fx%f",
+ origin().ToString().c_str(),
+ width_,
+ height_,
+ depth_);
+}
+
+bool BoxF::IsEmpty() const {
+ return (width_ == 0 && height_ == 0) ||
+ (width_ == 0 && depth_ == 0) ||
+ (height_ == 0 && depth_ == 0);
+}
+
+void BoxF::ExpandTo(const Point3F& min, const Point3F& max) {
+ DCHECK_LE(min.x(), max.x());
+ DCHECK_LE(min.y(), max.y());
+ DCHECK_LE(min.z(), max.z());
+
+ float min_x = std::min(x(), min.x());
+ float min_y = std::min(y(), min.y());
+ float min_z = std::min(z(), min.z());
+ float max_x = std::max(right(), max.x());
+ float max_y = std::max(bottom(), max.y());
+ float max_z = std::max(front(), max.z());
+
+ origin_.SetPoint(min_x, min_y, min_z);
+ width_ = max_x - min_x;
+ height_ = max_y - min_y;
+ depth_ = max_z - min_z;
+}
+
+void BoxF::Union(const BoxF& box) {
+ if (IsEmpty()) {
+ *this = box;
+ return;
+ }
+ if (box.IsEmpty())
+ return;
+ ExpandTo(box);
+}
+
+void BoxF::ExpandTo(const Point3F& point) {
+ ExpandTo(point, point);
+}
+
+void BoxF::ExpandTo(const BoxF& box) {
+ ExpandTo(box.origin(), gfx::Point3F(box.right(), box.bottom(), box.front()));
+}
+
+BoxF UnionBoxes(const BoxF& a, const BoxF& b) {
+ BoxF result = a;
+ result.Union(b);
+ return result;
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/box_f.h b/ui/gfx/geometry/box_f.h
new file mode 100644
index 0000000000..b4d9eff1c0
--- /dev/null
+++ b/ui/gfx/geometry/box_f.h
@@ -0,0 +1,160 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_BOX_F_H_
+#define UI_GFX_GEOMETRY_BOX_F_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "ui/gfx/geometry/point3_f.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+// A 3d version of gfx::RectF, with the positive z-axis pointed towards
+// the camera.
+class GFX_EXPORT BoxF {
+ public:
+ constexpr BoxF() : BoxF(0, 0, 0) {}
+ constexpr BoxF(float width, float height, float depth)
+ : BoxF(0, 0, 0, width, height, depth) {}
+ constexpr BoxF(float x,
+ float y,
+ float z,
+ float width,
+ float height,
+ float depth)
+ : BoxF(Point3F(x, y, z), width, height, depth) {}
+ constexpr BoxF(const Point3F& origin, float width, float height, float depth)
+ : origin_(origin),
+ width_(width >= 0 ? width : 0),
+ height_(height >= 0 ? height : 0),
+ depth_(depth >= 0 ? depth : 0) {}
+
+ // Scales all three axes by the given scale.
+ void Scale(float scale) {
+ Scale(scale, scale, scale);
+ }
+
+ // Scales each axis by the corresponding given scale.
+ void Scale(float x_scale, float y_scale, float z_scale) {
+ origin_.Scale(x_scale, y_scale, z_scale);
+ set_size(width_ * x_scale, height_ * y_scale, depth_ * z_scale);
+ }
+
+ // Moves the box by the specified distance in each dimension.
+ void operator+=(const Vector3dF& offset) {
+ origin_ += offset;
+ }
+
+ // Returns true if the box has no interior points.
+ bool IsEmpty() const;
+
+ // Computes the union of this box with the given box. The union is the
+ // smallest box that contains both boxes.
+ void Union(const BoxF& box);
+
+ std::string ToString() const;
+
+ constexpr float x() const { return origin_.x(); }
+ void set_x(float x) { origin_.set_x(x); }
+
+ constexpr float y() const { return origin_.y(); }
+ void set_y(float y) { origin_.set_y(y); }
+
+ constexpr float z() const { return origin_.z(); }
+ void set_z(float z) { origin_.set_z(z); }
+
+ constexpr float width() const { return width_; }
+ void set_width(float width) { width_ = width < 0 ? 0 : width; }
+
+ constexpr float height() const { return height_; }
+ void set_height(float height) { height_ = height < 0 ? 0 : height; }
+
+ constexpr float depth() const { return depth_; }
+ void set_depth(float depth) { depth_ = depth < 0 ? 0 : depth; }
+
+ constexpr float right() const { return x() + width(); }
+ constexpr float bottom() const { return y() + height(); }
+ constexpr float front() const { return z() + depth(); }
+
+ void set_size(float width, float height, float depth) {
+ width_ = width < 0 ? 0 : width;
+ height_ = height < 0 ? 0 : height;
+ depth_ = depth < 0 ? 0 : depth;
+ }
+
+ constexpr const Point3F& origin() const { return origin_; }
+ void set_origin(const Point3F& origin) { origin_ = origin; }
+
+ // Expands |this| to contain the given point, if necessary. Please note, even
+ // if |this| is empty, after the function |this| will continue to contain its
+ // |origin_|.
+ void ExpandTo(const Point3F& point);
+
+ // Expands |this| to contain the given box, if necessary. Please note, even
+ // if |this| is empty, after the function |this| will continue to contain its
+ // |origin_|.
+ void ExpandTo(const BoxF& box);
+
+ private:
+ // Expands the box to contain the two given points. It is required that each
+ // component of |min| is less than or equal to the corresponding component in
+ // |max|. Precisely, what this function does is ensure that after the function
+ // completes, |this| contains origin_, min, max, and origin_ + (width_,
+ // height_, depth_), even if the box is empty. Emptiness checks are handled in
+ // the public function Union.
+ void ExpandTo(const Point3F& min, const Point3F& max);
+
+ Point3F origin_;
+ float width_;
+ float height_;
+ float depth_;
+};
+
+GFX_EXPORT BoxF UnionBoxes(const BoxF& a, const BoxF& b);
+
+inline BoxF ScaleBox(const BoxF& b,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ return BoxF(b.x() * x_scale,
+ b.y() * y_scale,
+ b.z() * z_scale,
+ b.width() * x_scale,
+ b.height() * y_scale,
+ b.depth() * z_scale);
+}
+
+inline BoxF ScaleBox(const BoxF& b, float scale) {
+ return ScaleBox(b, scale, scale, scale);
+}
+
+inline bool operator==(const BoxF& a, const BoxF& b) {
+ return a.origin() == b.origin() && a.width() == b.width() &&
+ a.height() == b.height() && a.depth() == b.depth();
+}
+
+inline bool operator!=(const BoxF& a, const BoxF& b) {
+ return !(a == b);
+}
+
+inline BoxF operator+(const BoxF& b, const Vector3dF& v) {
+ return BoxF(b.x() + v.x(),
+ b.y() + v.y(),
+ b.z() + v.z(),
+ b.width(),
+ b.height(),
+ b.depth());
+}
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const BoxF& box, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_BOX_F_H_
diff --git a/ui/gfx/geometry/box_unittest.cc b/ui/gfx/geometry/box_unittest.cc
new file mode 100644
index 0000000000..990fccc249
--- /dev/null
+++ b/ui/gfx/geometry/box_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/box_f.h"
+
+namespace gfx {
+
+TEST(BoxTest, Constructors) {
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF().ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, -3.f, -5.f, -7.f).ToString(),
+ BoxF().ToString());
+
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 3.f, 5.f, 7.f).ToString(),
+ BoxF(3.f, 5.f, 7.f).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF(-3.f, -5.f, -7.f).ToString());
+
+ EXPECT_EQ(BoxF(2.f, 4.f, 6.f, 3.f, 5.f, 7.f).ToString(),
+ BoxF(Point3F(2.f, 4.f, 6.f), 3.f, 5.f, 7.f).ToString());
+ EXPECT_EQ(BoxF(2.f, 4.f, 6.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF(Point3F(2.f, 4.f, 6.f), -3.f, -5.f, -7.f).ToString());
+}
+
+TEST(BoxTest, IsEmpty) {
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 0.f, 0.f).IsEmpty());
+
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 2.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 2.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 2.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 2.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 2.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 0.f, 2.f).IsEmpty());
+
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 0.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 0.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 0.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 0.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 2.f, 0.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 2.f, 0.f).IsEmpty());
+
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 2.f, 2.f).IsEmpty());
+}
+
+TEST(BoxTest, Union) {
+ BoxF empty_box;
+ BoxF box1(0.f, 0.f, 0.f, 1.f, 1.f, 1.f);
+ BoxF box2(0.f, 0.f, 0.f, 4.f, 6.f, 8.f);
+ BoxF box3(3.f, 4.f, 5.f, 6.f, 4.f, 0.f);
+
+ EXPECT_EQ(empty_box.ToString(), UnionBoxes(empty_box, empty_box).ToString());
+ EXPECT_EQ(box1.ToString(), UnionBoxes(empty_box, box1).ToString());
+ EXPECT_EQ(box1.ToString(), UnionBoxes(box1, empty_box).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(empty_box, box2).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box2, empty_box).ToString());
+ EXPECT_EQ(box3.ToString(), UnionBoxes(empty_box, box3).ToString());
+ EXPECT_EQ(box3.ToString(), UnionBoxes(box3, empty_box).ToString());
+
+ // box_1 is contained in box_2
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box1, box2).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box2, box1).ToString());
+
+ // box_1 and box_3 are disjoint
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 5.f).ToString(),
+ UnionBoxes(box1, box3).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 5.f).ToString(),
+ UnionBoxes(box3, box1).ToString());
+
+ // box_2 and box_3 intersect, but neither contains the other
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 8.f).ToString(),
+ UnionBoxes(box2, box3).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 8.f).ToString(),
+ UnionBoxes(box3, box2).ToString());
+}
+
+TEST(BoxTest, ExpandTo) {
+ BoxF box1;
+ BoxF box2(0.f, 0.f, 0.f, 1.f, 1.f, 1.f);
+ BoxF box3(1.f, 1.f, 1.f, 0.f, 0.f, 0.f);
+
+ Point3F point1(0.5f, 0.5f, 0.5f);
+ Point3F point2(-0.5f, -0.5f, -0.5f);
+
+ BoxF expected1_1(0.f, 0.f, 0.f, 0.5f, 0.5f, 0.5f);
+ BoxF expected1_2(-0.5f, -0.5f, -0.5f, 1.f, 1.f, 1.f);
+
+ BoxF expected2_1 = box2;
+ BoxF expected2_2(-0.5f, -0.5f, -0.5f, 1.5f, 1.5f, 1.5f);
+
+ BoxF expected3_1(0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f);
+ BoxF expected3_2(-0.5f, -0.5f, -0.5f, 1.5f, 1.5f, 1.5f);
+
+ box1.ExpandTo(point1);
+ EXPECT_EQ(expected1_1.ToString(), box1.ToString());
+ box1.ExpandTo(point2);
+ EXPECT_EQ(expected1_2.ToString(), box1.ToString());
+
+ box2.ExpandTo(point1);
+ EXPECT_EQ(expected2_1.ToString(), box2.ToString());
+ box2.ExpandTo(point2);
+ EXPECT_EQ(expected2_2.ToString(), box2.ToString());
+
+ box3.ExpandTo(point1);
+ EXPECT_EQ(expected3_1.ToString(), box3.ToString());
+ box3.ExpandTo(point2);
+ EXPECT_EQ(expected3_2.ToString(), box3.ToString());
+}
+
+TEST(BoxTest, Scale) {
+ BoxF box1(2.f, 3.f, 4.f, 5.f, 6.f, 7.f);
+
+ EXPECT_EQ(BoxF().ToString(), ScaleBox(box1, 0.f).ToString());
+ EXPECT_EQ(box1.ToString(), ScaleBox(box1, 1.f).ToString());
+ EXPECT_EQ(BoxF(4.f, 12.f, 24.f, 10.f, 24.f, 42.f).ToString(),
+ ScaleBox(box1, 2.f, 4.f, 6.f).ToString());
+
+ BoxF box2 = box1;
+ box2.Scale(0.f);
+ EXPECT_EQ(BoxF().ToString(), box2.ToString());
+
+ box2 = box1;
+ box2.Scale(1.f);
+ EXPECT_EQ(box1.ToString(), box2.ToString());
+
+ box2.Scale(2.f, 4.f, 6.f);
+ EXPECT_EQ(BoxF(4.f, 12.f, 24.f, 10.f, 24.f, 42.f).ToString(),
+ box2.ToString());
+}
+
+TEST(BoxTest, Equals) {
+ EXPECT_TRUE(BoxF() == BoxF());
+ EXPECT_TRUE(BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f) ==
+ BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 1.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 0.f, 1.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 1.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 1.f, 0.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 1.f, 0.f, 0.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(1.f, 0.f, 0.f, 0.f, 0.f, 0.f));
+}
+
+TEST(BoxTest, NotEquals) {
+ EXPECT_FALSE(BoxF() != BoxF());
+ EXPECT_FALSE(BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f) !=
+ BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 1.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 0.f, 1.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 1.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 1.f, 0.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 1.f, 0.f, 0.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(1.f, 0.f, 0.f, 0.f, 0.f, 0.f));
+}
+
+
+TEST(BoxTest, Offset) {
+ BoxF box1(2.f, 3.f, 4.f, 5.f, 6.f, 7.f);
+
+ EXPECT_EQ(box1.ToString(), (box1 + Vector3dF(0.f, 0.f, 0.f)).ToString());
+ EXPECT_EQ(BoxF(3.f, 1.f, 0.f, 5.f, 6.f, 7.f).ToString(),
+ (box1 + Vector3dF(1.f, -2.f, -4.f)).ToString());
+
+ BoxF box2 = box1;
+ box2 += Vector3dF(0.f, 0.f, 0.f);
+ EXPECT_EQ(box1.ToString(), box2.ToString());
+
+ box2 += Vector3dF(1.f, -2.f, -4.f);
+ EXPECT_EQ(BoxF(3.f, 1.f, 0.f, 5.f, 6.f, 7.f).ToString(),
+ box2.ToString());
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/cubic_bezier.cc b/ui/gfx/geometry/cubic_bezier.cc
new file mode 100644
index 0000000000..e42d893d7f
--- /dev/null
+++ b/ui/gfx/geometry/cubic_bezier.cc
@@ -0,0 +1,213 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/cubic_bezier.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+
+namespace gfx {
+
+static const double kBezierEpsilon = 1e-7;
+
+CubicBezier::CubicBezier(double p1x, double p1y, double p2x, double p2y) {
+ InitCoefficients(p1x, p1y, p2x, p2y);
+ InitGradients(p1x, p1y, p2x, p2y);
+ InitRange(p1y, p2y);
+}
+
+CubicBezier::CubicBezier(const CubicBezier& other) = default;
+
+void CubicBezier::InitCoefficients(double p1x,
+ double p1y,
+ double p2x,
+ double p2y) {
+ // Calculate the polynomial coefficients, implicit first and last control
+ // points are (0,0) and (1,1).
+ cx_ = 3.0 * p1x;
+ bx_ = 3.0 * (p2x - p1x) - cx_;
+ ax_ = 1.0 - cx_ - bx_;
+
+ cy_ = 3.0 * p1y;
+ by_ = 3.0 * (p2y - p1y) - cy_;
+ ay_ = 1.0 - cy_ - by_;
+}
+
+void CubicBezier::InitGradients(double p1x,
+ double p1y,
+ double p2x,
+ double p2y) {
+ // End-point gradients are used to calculate timing function results
+ // outside the range [0, 1].
+ //
+ // There are three possibilities for the gradient at each end:
+ // (1) the closest control point is not horizontally coincident with regard to
+ // (0, 0) or (1, 1). In this case the line between the end point and
+ // the control point is tangent to the bezier at the end point.
+ // (2) the closest control point is coincident with the end point. In
+ // this case the line between the end point and the far control
+ // point is tangent to the bezier at the end point.
+ // (3) the closest control point is horizontally coincident with the end
+ // point, but vertically distinct. In this case the gradient at the
+ // end point is Infinite. However, this causes issues when
+ // interpolating. As a result, we break down to a simple case of
+ // 0 gradient under these conditions.
+
+ if (p1x > 0)
+ start_gradient_ = p1y / p1x;
+ else if (!p1y && p2x > 0)
+ start_gradient_ = p2y / p2x;
+ else
+ start_gradient_ = 0;
+
+ if (p2x < 1)
+ end_gradient_ = (p2y - 1) / (p2x - 1);
+ else if (p2x == 1 && p1x < 1)
+ end_gradient_ = (p1y - 1) / (p1x - 1);
+ else
+ end_gradient_ = 0;
+}
+
+// This works by taking taking the derivative of the cubic bezier, on the y
+// axis. We can then solve for where the derivative is zero to find the min
+// and max distance along the line. We the have to solve those in terms of time
+// rather than distance on the x-axis
+void CubicBezier::InitRange(double p1y, double p2y) {
+ range_min_ = 0;
+ range_max_ = 1;
+ if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1)
+ return;
+
+ const double epsilon = kBezierEpsilon;
+
+ // Represent the function's derivative in the form at^2 + bt + c
+ // as in sampleCurveDerivativeY.
+ // (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros
+ // but does not actually give the slope of the curve.)
+ const double a = 3.0 * ay_;
+ const double b = 2.0 * by_;
+ const double c = cy_;
+
+ // Check if the derivative is constant.
+ if (std::abs(a) < epsilon && std::abs(b) < epsilon)
+ return;
+
+ // Zeros of the function's derivative.
+ double t1 = 0;
+ double t2 = 0;
+
+ if (std::abs(a) < epsilon) {
+ // The function's derivative is linear.
+ t1 = -c / b;
+ } else {
+ // The function's derivative is a quadratic. We find the zeros of this
+ // quadratic using the quadratic formula.
+ double discriminant = b * b - 4 * a * c;
+ if (discriminant < 0)
+ return;
+ double discriminant_sqrt = sqrt(discriminant);
+ t1 = (-b + discriminant_sqrt) / (2 * a);
+ t2 = (-b - discriminant_sqrt) / (2 * a);
+ }
+
+ double sol1 = 0;
+ double sol2 = 0;
+
+ // If the solution is in the range [0,1] then we include it, otherwise we
+ // ignore it.
+
+ // An interesting fact about these beziers is that they are only
+ // actually evaluated in [0,1]. After that we take the tangent at that point
+ // and linearly project it out.
+ if (0 < t1 && t1 < 1)
+ sol1 = SampleCurveY(t1);
+
+ if (0 < t2 && t2 < 1)
+ sol2 = SampleCurveY(t2);
+
+ range_min_ = std::min(std::min(range_min_, sol1), sol2);
+ range_max_ = std::max(std::max(range_max_, sol1), sol2);
+}
+
+double CubicBezier::GetDefaultEpsilon() {
+ return kBezierEpsilon;
+}
+
+double CubicBezier::SolveCurveX(double x, double epsilon) const {
+ DCHECK_GE(x, 0.0);
+ DCHECK_LE(x, 1.0);
+
+ double t0;
+ double t1;
+ double t2;
+ double x2;
+ double d2;
+ int i;
+
+ // First try a few iterations of Newton's method -- normally very fast.
+ for (t2 = x, i = 0; i < 8; i++) {
+ x2 = SampleCurveX(t2) - x;
+ if (fabs(x2) < epsilon)
+ return t2;
+ d2 = SampleCurveDerivativeX(t2);
+ if (fabs(d2) < 1e-6)
+ break;
+ t2 = t2 - x2 / d2;
+ }
+
+ // Fall back to the bisection method for reliability.
+ t0 = 0.0;
+ t1 = 1.0;
+ t2 = x;
+
+ while (t0 < t1) {
+ x2 = SampleCurveX(t2);
+ if (fabs(x2 - x) < epsilon)
+ return t2;
+ if (x > x2)
+ t0 = t2;
+ else
+ t1 = t2;
+ t2 = (t1 - t0) * .5 + t0;
+ }
+
+ // Failure.
+ return t2;
+}
+
+double CubicBezier::Solve(double x) const {
+ return SolveWithEpsilon(x, kBezierEpsilon);
+}
+
+double CubicBezier::SlopeWithEpsilon(double x, double epsilon) const {
+ x = std::min(std::max(x, 0.0), 1.0);
+ double t = SolveCurveX(x, epsilon);
+ double dx = SampleCurveDerivativeX(t);
+ double dy = SampleCurveDerivativeY(t);
+ return dy / dx;
+}
+
+double CubicBezier::Slope(double x) const {
+ return SlopeWithEpsilon(x, kBezierEpsilon);
+}
+
+double CubicBezier::GetX1() const {
+ return cx_ / 3.0;
+}
+
+double CubicBezier::GetY1() const {
+ return cy_ / 3.0;
+}
+
+double CubicBezier::GetX2() const {
+ return (bx_ + cx_) / 3.0 + GetX1();
+}
+
+double CubicBezier::GetY2() const {
+ return (by_ + cy_) / 3.0 + GetY1();
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/cubic_bezier.h b/ui/gfx/geometry/cubic_bezier.h
new file mode 100644
index 0000000000..013123999c
--- /dev/null
+++ b/ui/gfx/geometry/cubic_bezier.h
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
+#define UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
+
+#include "base/macros.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+class GFX_EXPORT CubicBezier {
+ public:
+ CubicBezier(double p1x, double p1y, double p2x, double p2y);
+ CubicBezier(const CubicBezier& other);
+
+ double SampleCurveX(double t) const {
+ // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
+ return ((ax_ * t + bx_) * t + cx_) * t;
+ }
+
+ double SampleCurveY(double t) const {
+ return ((ay_ * t + by_) * t + cy_) * t;
+ }
+
+ double SampleCurveDerivativeX(double t) const {
+ return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_;
+ }
+
+ double SampleCurveDerivativeY(double t) const {
+ return (3.0 * ay_ * t + 2.0 * by_) * t + cy_;
+ }
+
+ static double GetDefaultEpsilon();
+
+ // Given an x value, find a parametric value it came from.
+ // x must be in [0, 1] range. Doesn't use gradients.
+ double SolveCurveX(double x, double epsilon) const;
+
+ // Evaluates y at the given x with default epsilon.
+ double Solve(double x) const;
+ // Evaluates y at the given x. The epsilon parameter provides a hint as to the
+ // required accuracy and is not guaranteed. Uses gradients if x is
+ // out of [0, 1] range.
+ double SolveWithEpsilon(double x, double epsilon) const {
+ if (x < 0.0)
+ return 0.0 + start_gradient_ * x;
+ if (x > 1.0)
+ return 1.0 + end_gradient_ * (x - 1.0);
+ return SampleCurveY(SolveCurveX(x, epsilon));
+ }
+
+ // Returns an approximation of dy/dx at the given x with default epsilon.
+ double Slope(double x) const;
+ // Returns an approximation of dy/dx at the given x.
+ // Clamps x to range [0, 1].
+ double SlopeWithEpsilon(double x, double epsilon) const;
+
+ // These getters are used rarely. We reverse compute them from coefficients.
+ // See CubicBezier::InitCoefficients. The speed has been traded for memory.
+ double GetX1() const;
+ double GetY1() const;
+ double GetX2() const;
+ double GetY2() const;
+
+ // Gets the bezier's minimum y value in the interval [0, 1].
+ double range_min() const { return range_min_; }
+ // Gets the bezier's maximum y value in the interval [0, 1].
+ double range_max() const { return range_max_; }
+
+ private:
+ void InitCoefficients(double p1x, double p1y, double p2x, double p2y);
+ void InitGradients(double p1x, double p1y, double p2x, double p2y);
+ void InitRange(double p1y, double p2y);
+
+ double ax_;
+ double bx_;
+ double cx_;
+
+ double ay_;
+ double by_;
+ double cy_;
+
+ double start_gradient_;
+ double end_gradient_;
+
+ double range_min_;
+ double range_max_;
+
+ DISALLOW_ASSIGN(CubicBezier);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
diff --git a/ui/gfx/geometry/cubic_bezier_unittest.cc b/ui/gfx/geometry/cubic_bezier_unittest.cc
new file mode 100644
index 0000000000..74ccb6d414
--- /dev/null
+++ b/ui/gfx/geometry/cubic_bezier_unittest.cc
@@ -0,0 +1,230 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/cubic_bezier.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+namespace {
+
+TEST(CubicBezierTest, Basic) {
+ CubicBezier function(0.25, 0.0, 0.75, 1.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function.Solve(0), 0, epsilon);
+ EXPECT_NEAR(function.Solve(0.05), 0.01136, epsilon);
+ EXPECT_NEAR(function.Solve(0.1), 0.03978, epsilon);
+ EXPECT_NEAR(function.Solve(0.15), 0.079780, epsilon);
+ EXPECT_NEAR(function.Solve(0.2), 0.12803, epsilon);
+ EXPECT_NEAR(function.Solve(0.25), 0.18235, epsilon);
+ EXPECT_NEAR(function.Solve(0.3), 0.24115, epsilon);
+ EXPECT_NEAR(function.Solve(0.35), 0.30323, epsilon);
+ EXPECT_NEAR(function.Solve(0.4), 0.36761, epsilon);
+ EXPECT_NEAR(function.Solve(0.45), 0.43345, epsilon);
+ EXPECT_NEAR(function.Solve(0.5), 0.5, epsilon);
+ EXPECT_NEAR(function.Solve(0.6), 0.63238, epsilon);
+ EXPECT_NEAR(function.Solve(0.65), 0.69676, epsilon);
+ EXPECT_NEAR(function.Solve(0.7), 0.75884, epsilon);
+ EXPECT_NEAR(function.Solve(0.75), 0.81764, epsilon);
+ EXPECT_NEAR(function.Solve(0.8), 0.87196, epsilon);
+ EXPECT_NEAR(function.Solve(0.85), 0.92021, epsilon);
+ EXPECT_NEAR(function.Solve(0.9), 0.96021, epsilon);
+ EXPECT_NEAR(function.Solve(0.95), 0.98863, epsilon);
+ EXPECT_NEAR(function.Solve(1), 1, epsilon);
+
+ CubicBezier basic_use(0.5, 1.0, 0.5, 1.0);
+ EXPECT_EQ(0.875, basic_use.Solve(0.5));
+
+ CubicBezier overshoot(0.5, 2.0, 0.5, 2.0);
+ EXPECT_EQ(1.625, overshoot.Solve(0.5));
+
+ CubicBezier undershoot(0.5, -1.0, 0.5, -1.0);
+ EXPECT_EQ(-0.625, undershoot.Solve(0.5));
+}
+
+// Tests that solving the bezier works with knots with y not in (0, 1).
+TEST(CubicBezierTest, UnclampedYValues) {
+ CubicBezier function(0.5, -1.0, 0.5, 2.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function.Solve(0.0), 0.0, epsilon);
+ EXPECT_NEAR(function.Solve(0.05), -0.08954, epsilon);
+ EXPECT_NEAR(function.Solve(0.1), -0.15613, epsilon);
+ EXPECT_NEAR(function.Solve(0.15), -0.19641, epsilon);
+ EXPECT_NEAR(function.Solve(0.2), -0.20651, epsilon);
+ EXPECT_NEAR(function.Solve(0.25), -0.18232, epsilon);
+ EXPECT_NEAR(function.Solve(0.3), -0.11992, epsilon);
+ EXPECT_NEAR(function.Solve(0.35), -0.01672, epsilon);
+ EXPECT_NEAR(function.Solve(0.4), 0.12660, epsilon);
+ EXPECT_NEAR(function.Solve(0.45), 0.30349, epsilon);
+ EXPECT_NEAR(function.Solve(0.5), 0.50000, epsilon);
+ EXPECT_NEAR(function.Solve(0.55), 0.69651, epsilon);
+ EXPECT_NEAR(function.Solve(0.6), 0.87340, epsilon);
+ EXPECT_NEAR(function.Solve(0.65), 1.01672, epsilon);
+ EXPECT_NEAR(function.Solve(0.7), 1.11992, epsilon);
+ EXPECT_NEAR(function.Solve(0.75), 1.18232, epsilon);
+ EXPECT_NEAR(function.Solve(0.8), 1.20651, epsilon);
+ EXPECT_NEAR(function.Solve(0.85), 1.19641, epsilon);
+ EXPECT_NEAR(function.Solve(0.9), 1.15613, epsilon);
+ EXPECT_NEAR(function.Solve(0.95), 1.08954, epsilon);
+ EXPECT_NEAR(function.Solve(1.0), 1.0, epsilon);
+}
+
+TEST(CubicBezierTest, Range) {
+ double epsilon = 0.00015;
+
+ // Derivative is a constant.
+ std::unique_ptr<CubicBezier> function(
+ new CubicBezier(0.25, (1.0 / 3.0), 0.75, (2.0 / 3.0)));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative is linear.
+ function.reset(new CubicBezier(0.25, -0.5, 0.75, (-1.0 / 6.0)));
+ EXPECT_NEAR(function->range_min(), -0.225, epsilon);
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has no real roots.
+ function.reset(new CubicBezier(0.25, 0.25, 0.75, 0.5));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has exactly one real root.
+ function.reset(new CubicBezier(0.0, 1.0, 1.0, 0.0));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has one root < 0 and one root > 1.
+ function.reset(new CubicBezier(0.25, 0.1, 0.75, 0.9));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has two roots in [0,1].
+ function.reset(new CubicBezier(0.25, 2.5, 0.75, 0.5));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_NEAR(function->range_max(), 1.28818, epsilon);
+ function.reset(new CubicBezier(0.25, 0.5, 0.75, -1.5));
+ EXPECT_NEAR(function->range_min(), -0.28818, epsilon);
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has one root < 0 and one root in [0,1].
+ function.reset(new CubicBezier(0.25, 0.1, 0.75, 1.5));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_NEAR(function->range_max(), 1.10755, epsilon);
+
+ // Derivative has one root in [0,1] and one root > 1.
+ function.reset(new CubicBezier(0.25, -0.5, 0.75, 0.9));
+ EXPECT_NEAR(function->range_min(), -0.10755, epsilon);
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has two roots < 0.
+ function.reset(new CubicBezier(0.25, 0.3, 0.75, 0.633));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has two roots > 1.
+ function.reset(new CubicBezier(0.25, 0.367, 0.75, 0.7));
+ EXPECT_EQ(0.f, function->range_min());
+ EXPECT_EQ(1.f, function->range_max());
+}
+
+TEST(CubicBezierTest, Slope) {
+ CubicBezier function(0.25, 0.0, 0.75, 1.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function.Slope(-0.1), 0, epsilon);
+ EXPECT_NEAR(function.Slope(0), 0, epsilon);
+ EXPECT_NEAR(function.Slope(0.05), 0.42170, epsilon);
+ EXPECT_NEAR(function.Slope(0.1), 0.69778, epsilon);
+ EXPECT_NEAR(function.Slope(0.15), 0.89121, epsilon);
+ EXPECT_NEAR(function.Slope(0.2), 1.03184, epsilon);
+ EXPECT_NEAR(function.Slope(0.25), 1.13576, epsilon);
+ EXPECT_NEAR(function.Slope(0.3), 1.21239, epsilon);
+ EXPECT_NEAR(function.Slope(0.35), 1.26751, epsilon);
+ EXPECT_NEAR(function.Slope(0.4), 1.30474, epsilon);
+ EXPECT_NEAR(function.Slope(0.45), 1.32628, epsilon);
+ EXPECT_NEAR(function.Slope(0.5), 1.33333, epsilon);
+ EXPECT_NEAR(function.Slope(0.55), 1.32628, epsilon);
+ EXPECT_NEAR(function.Slope(0.6), 1.30474, epsilon);
+ EXPECT_NEAR(function.Slope(0.65), 1.26751, epsilon);
+ EXPECT_NEAR(function.Slope(0.7), 1.21239, epsilon);
+ EXPECT_NEAR(function.Slope(0.75), 1.13576, epsilon);
+ EXPECT_NEAR(function.Slope(0.8), 1.03184, epsilon);
+ EXPECT_NEAR(function.Slope(0.85), 0.89121, epsilon);
+ EXPECT_NEAR(function.Slope(0.9), 0.69778, epsilon);
+ EXPECT_NEAR(function.Slope(0.95), 0.42170, epsilon);
+ EXPECT_NEAR(function.Slope(1), 0, epsilon);
+ EXPECT_NEAR(function.Slope(1.1), 0, epsilon);
+}
+
+TEST(CubicBezierTest, InputOutOfRange) {
+ CubicBezier simple(0.5, 1.0, 0.5, 1.0);
+ EXPECT_EQ(-2.0, simple.Solve(-1.0));
+ EXPECT_EQ(1.0, simple.Solve(2.0));
+
+ CubicBezier at_edge_of_range(0.5, 1.0, 0.5, 1.0);
+ EXPECT_EQ(0.0, at_edge_of_range.Solve(0.0));
+ EXPECT_EQ(1.0, at_edge_of_range.Solve(1.0));
+
+ CubicBezier large_epsilon(0.5, 1.0, 0.5, 1.0);
+ EXPECT_EQ(-2.0, large_epsilon.SolveWithEpsilon(-1.0, 1.0));
+ EXPECT_EQ(1.0, large_epsilon.SolveWithEpsilon(2.0, 1.0));
+
+ CubicBezier coincident_endpoints(0.0, 0.0, 1.0, 1.0);
+ EXPECT_EQ(-1.0, coincident_endpoints.Solve(-1.0));
+ EXPECT_EQ(2.0, coincident_endpoints.Solve(2.0));
+
+ CubicBezier vertical_gradient(0.0, 1.0, 1.0, 0.0);
+ EXPECT_EQ(0.0, vertical_gradient.Solve(-1.0));
+ EXPECT_EQ(1.0, vertical_gradient.Solve(2.0));
+
+ CubicBezier distinct_endpoints(0.1, 0.2, 0.8, 0.8);
+ EXPECT_EQ(-2.0, distinct_endpoints.Solve(-1.0));
+ EXPECT_EQ(2.0, distinct_endpoints.Solve(2.0));
+
+ CubicBezier coincident_endpoint(0.0, 0.0, 0.8, 0.8);
+ EXPECT_EQ(-1.0, coincident_endpoint.Solve(-1.0));
+ EXPECT_EQ(2.0, coincident_endpoint.Solve(2.0));
+
+ CubicBezier three_coincident_points(0.0, 0.0, 0.0, 0.0);
+ EXPECT_EQ(0, three_coincident_points.Solve(-1.0));
+ EXPECT_EQ(2.0, three_coincident_points.Solve(2.0));
+}
+
+TEST(CubicBezierTest, GetPoints) {
+ double epsilon = 0.00015;
+
+ CubicBezier cubic1(0.1, 0.2, 0.8, 0.9);
+ EXPECT_NEAR(0.1, cubic1.GetX1(), epsilon);
+ EXPECT_NEAR(0.2, cubic1.GetY1(), epsilon);
+ EXPECT_NEAR(0.8, cubic1.GetX2(), epsilon);
+ EXPECT_NEAR(0.9, cubic1.GetY2(), epsilon);
+
+ CubicBezier cubic_zero(0, 0, 0, 0);
+ EXPECT_NEAR(0, cubic_zero.GetX1(), epsilon);
+ EXPECT_NEAR(0, cubic_zero.GetY1(), epsilon);
+ EXPECT_NEAR(0, cubic_zero.GetX2(), epsilon);
+ EXPECT_NEAR(0, cubic_zero.GetY2(), epsilon);
+
+ CubicBezier cubic_one(1, 1, 1, 1);
+ EXPECT_NEAR(1, cubic_one.GetX1(), epsilon);
+ EXPECT_NEAR(1, cubic_one.GetY1(), epsilon);
+ EXPECT_NEAR(1, cubic_one.GetX2(), epsilon);
+ EXPECT_NEAR(1, cubic_one.GetY2(), epsilon);
+
+ CubicBezier cubic_oor(-0.5, -1.5, 1.5, -1.6);
+ EXPECT_NEAR(-0.5, cubic_oor.GetX1(), epsilon);
+ EXPECT_NEAR(-1.5, cubic_oor.GetY1(), epsilon);
+ EXPECT_NEAR(1.5, cubic_oor.GetX2(), epsilon);
+ EXPECT_NEAR(-1.6, cubic_oor.GetY2(), epsilon);
+}
+
+} // namespace
+} // namespace gfx
diff --git a/ui/gfx/geometry/dip_util.cc b/ui/gfx/geometry/dip_util.cc
new file mode 100644
index 0000000000..db8e1be5d5
--- /dev/null
+++ b/ui/gfx/geometry/dip_util.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/dip_util.h"
+
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/point_conversions.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/size_conversions.h"
+
+namespace gfx {
+
+Insets ConvertInsetsToDIP(float scale_factor,
+ const gfx::Insets& insets_in_pixel) {
+ if (scale_factor == 1.f)
+ return insets_in_pixel;
+ return insets_in_pixel.Scale(1.f / scale_factor);
+}
+
+Point ConvertPointToDIP(float scale_factor, const Point& point_in_pixel) {
+ if (scale_factor == 1.f)
+ return point_in_pixel;
+ return ScaleToFlooredPoint(point_in_pixel, 1.f / scale_factor);
+}
+
+PointF ConvertPointToDIP(float scale_factor, const PointF& point_in_pixel) {
+ if (scale_factor == 1.f)
+ return point_in_pixel;
+ return ScalePoint(point_in_pixel, 1.f / scale_factor);
+}
+
+Size ConvertSizeToDIP(float scale_factor, const Size& size_in_pixel) {
+ if (scale_factor == 1.f)
+ return size_in_pixel;
+ return ScaleToFlooredSize(size_in_pixel, 1.f / scale_factor);
+}
+
+Rect ConvertRectToDIP(float scale_factor, const Rect& rect_in_pixel) {
+ if (scale_factor == 1.f)
+ return rect_in_pixel;
+ return ToFlooredRectDeprecated(
+ ScaleRect(RectF(rect_in_pixel), 1.f / scale_factor));
+}
+
+Insets ConvertInsetsToPixel(float scale_factor,
+ const gfx::Insets& insets_in_dip) {
+ if (scale_factor == 1.f)
+ return insets_in_dip;
+ return insets_in_dip.Scale(scale_factor);
+}
+
+Point ConvertPointToPixel(float scale_factor, const Point& point_in_dip) {
+ if (scale_factor == 1.f)
+ return point_in_dip;
+ return ScaleToFlooredPoint(point_in_dip, scale_factor);
+}
+
+PointF ConvertPointToPixel(float scale_factor, const PointF& point_in_dip) {
+ if (scale_factor == 1.f)
+ return point_in_dip;
+ return ScalePoint(point_in_dip, scale_factor);
+}
+
+Size ConvertSizeToPixel(float scale_factor, const Size& size_in_dip) {
+ if (scale_factor == 1.f)
+ return size_in_dip;
+ return ScaleToFlooredSize(size_in_dip, scale_factor);
+}
+
+Rect ConvertRectToPixel(float scale_factor, const Rect& rect_in_dip) {
+ // Use ToEnclosingRect() to ensure we paint all the possible pixels
+ // touched. ToEnclosingRect() floors the origin, and ceils the max
+ // coordinate. To do otherwise (such as flooring the size) potentially
+ // results in rounding down and not drawing all the pixels that are
+ // touched.
+ if (scale_factor == 1.f)
+ return rect_in_dip;
+ return ToEnclosingRect(
+ RectF(ScalePoint(gfx::PointF(rect_in_dip.origin()), scale_factor),
+ ScaleSize(gfx::SizeF(rect_in_dip.size()), scale_factor)));
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/dip_util.h b/ui/gfx/geometry/dip_util.h
new file mode 100644
index 0000000000..e88d49b007
--- /dev/null
+++ b/ui/gfx/geometry/dip_util.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_DIP_UTIL_H_
+#define UI_GFX_GEOMETRY_DIP_UTIL_H_
+
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+class Insets;
+class Point;
+class PointF;
+class Rect;
+class Size;
+
+GFX_EXPORT gfx::Insets ConvertInsetsToDIP(float scale_factor,
+ const gfx::Insets& insets_in_pixel);
+GFX_EXPORT gfx::Point ConvertPointToDIP(float scale_factor,
+ const gfx::Point& point_in_pixel);
+GFX_EXPORT gfx::PointF ConvertPointToDIP(float scale_factor,
+ const gfx::PointF& point_in_pixel);
+GFX_EXPORT gfx::Size ConvertSizeToDIP(float scale_factor,
+ const gfx::Size& size_in_pixel);
+GFX_EXPORT gfx::Rect ConvertRectToDIP(float scale_factor,
+ const gfx::Rect& rect_in_pixel);
+
+GFX_EXPORT gfx::Insets ConvertInsetsToPixel(float scale_factor,
+ const gfx::Insets& insets_in_dip);
+GFX_EXPORT gfx::Point ConvertPointToPixel(float scale_factor,
+ const gfx::Point& point_in_dip);
+GFX_EXPORT gfx::PointF ConvertPointToPixel(float scale_factor,
+ const gfx::PointF& point_in_dip);
+GFX_EXPORT gfx::Size ConvertSizeToPixel(float scale_factor,
+ const gfx::Size& size_in_dip);
+GFX_EXPORT gfx::Rect ConvertRectToPixel(float scale_factor,
+ const gfx::Rect& rect_in_dip);
+} // gfx
+
+#endif // UI_GFX_GEOMETRY_DIP_UTIL_H_
diff --git a/ui/gfx/geometry/insets_unittest.cc b/ui/gfx/geometry/insets_unittest.cc
new file mode 100644
index 0000000000..6f607d9173
--- /dev/null
+++ b/ui/gfx/geometry/insets_unittest.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/insets.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/insets_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+
+TEST(InsetsTest, InsetsDefault) {
+ gfx::Insets insets;
+ EXPECT_EQ(0, insets.top());
+ EXPECT_EQ(0, insets.left());
+ EXPECT_EQ(0, insets.bottom());
+ EXPECT_EQ(0, insets.right());
+ EXPECT_EQ(0, insets.width());
+ EXPECT_EQ(0, insets.height());
+ EXPECT_TRUE(insets.IsEmpty());
+}
+
+TEST(InsetsTest, Insets) {
+ gfx::Insets insets(1, 2, 3, 4);
+ EXPECT_EQ(1, insets.top());
+ EXPECT_EQ(2, insets.left());
+ EXPECT_EQ(3, insets.bottom());
+ EXPECT_EQ(4, insets.right());
+ EXPECT_EQ(6, insets.width()); // Left + right.
+ EXPECT_EQ(4, insets.height()); // Top + bottom.
+ EXPECT_FALSE(insets.IsEmpty());
+}
+
+TEST(InsetsTest, Set) {
+ gfx::Insets insets;
+ insets.Set(1, 2, 3, 4);
+ EXPECT_EQ(1, insets.top());
+ EXPECT_EQ(2, insets.left());
+ EXPECT_EQ(3, insets.bottom());
+ EXPECT_EQ(4, insets.right());
+}
+
+TEST(InsetsTest, Operators) {
+ gfx::Insets insets;
+ insets.Set(1, 2, 3, 4);
+ insets += gfx::Insets(5, 6, 7, 8);
+ EXPECT_EQ(6, insets.top());
+ EXPECT_EQ(8, insets.left());
+ EXPECT_EQ(10, insets.bottom());
+ EXPECT_EQ(12, insets.right());
+
+ insets -= gfx::Insets(-1, 0, 1, 2);
+ EXPECT_EQ(7, insets.top());
+ EXPECT_EQ(8, insets.left());
+ EXPECT_EQ(9, insets.bottom());
+ EXPECT_EQ(10, insets.right());
+
+ insets = gfx::Insets(10, 10, 10, 10) + gfx::Insets(5, 5, 0, -20);
+ EXPECT_EQ(15, insets.top());
+ EXPECT_EQ(15, insets.left());
+ EXPECT_EQ(10, insets.bottom());
+ EXPECT_EQ(-10, insets.right());
+
+ insets = gfx::Insets(10, 10, 10, 10) - gfx::Insets(5, 5, 0, -20);
+ EXPECT_EQ(5, insets.top());
+ EXPECT_EQ(5, insets.left());
+ EXPECT_EQ(10, insets.bottom());
+ EXPECT_EQ(30, insets.right());
+}
+
+TEST(InsetsFTest, Operators) {
+ gfx::InsetsF insets;
+ insets.Set(1.f, 2.5f, 3.3f, 4.1f);
+ insets += gfx::InsetsF(5.8f, 6.7f, 7.6f, 8.5f);
+ EXPECT_FLOAT_EQ(6.8f, insets.top());
+ EXPECT_FLOAT_EQ(9.2f, insets.left());
+ EXPECT_FLOAT_EQ(10.9f, insets.bottom());
+ EXPECT_FLOAT_EQ(12.6f, insets.right());
+
+ insets -= gfx::InsetsF(-1.f, 0, 1.1f, 2.2f);
+ EXPECT_FLOAT_EQ(7.8f, insets.top());
+ EXPECT_FLOAT_EQ(9.2f, insets.left());
+ EXPECT_FLOAT_EQ(9.8f, insets.bottom());
+ EXPECT_FLOAT_EQ(10.4f, insets.right());
+
+ insets = gfx::InsetsF(10, 10.1f, 10.01f, 10.001f) +
+ gfx::InsetsF(5.5f, 5.f, 0, -20.2f);
+ EXPECT_FLOAT_EQ(15.5f, insets.top());
+ EXPECT_FLOAT_EQ(15.1f, insets.left());
+ EXPECT_FLOAT_EQ(10.01f, insets.bottom());
+ EXPECT_FLOAT_EQ(-10.199f, insets.right());
+
+ insets = gfx::InsetsF(10, 10.1f, 10.01f, 10.001f) -
+ gfx::InsetsF(5.5f, 5.f, 0, -20.2f);
+ EXPECT_FLOAT_EQ(4.5f, insets.top());
+ EXPECT_FLOAT_EQ(5.1f, insets.left());
+ EXPECT_FLOAT_EQ(10.01f, insets.bottom());
+ EXPECT_FLOAT_EQ(30.201f, insets.right());
+}
+
+TEST(InsetsTest, Equality) {
+ gfx::Insets insets1;
+ insets1.Set(1, 2, 3, 4);
+ gfx::Insets insets2;
+ // Test operator== and operator!=.
+ EXPECT_FALSE(insets1 == insets2);
+ EXPECT_TRUE(insets1 != insets2);
+
+ insets2.Set(1, 2, 3, 4);
+ EXPECT_TRUE(insets1 == insets2);
+ EXPECT_FALSE(insets1 != insets2);
+}
+
+TEST(InsetsTest, ToString) {
+ gfx::Insets insets(1, 2, 3, 4);
+ EXPECT_EQ("1,2,3,4", insets.ToString());
+}
+
+TEST(InsetsTest, Offset) {
+ const gfx::Insets insets(1, 2, 3, 4);
+ const gfx::Rect rect(5, 6, 7, 8);
+ const gfx::Vector2d vector(9, 10);
+
+ // Whether you inset then offset the rect, offset then inset the rect, or
+ // offset the insets then apply to the rect, the outcome should be the same.
+ gfx::Rect inset_first = rect;
+ inset_first.Inset(insets);
+ inset_first.Offset(vector);
+
+ gfx::Rect offset_first = rect;
+ offset_first.Offset(vector);
+ offset_first.Inset(insets);
+
+ gfx::Rect inset_by_offset = rect;
+ inset_by_offset.Inset(insets.Offset(vector));
+
+ EXPECT_EQ(inset_first, offset_first);
+ EXPECT_EQ(inset_by_offset, inset_first);
+}
diff --git a/ui/gfx/geometry/matrix3_f.cc b/ui/gfx/geometry/matrix3_f.cc
new file mode 100644
index 0000000000..1420ee539d
--- /dev/null
+++ b/ui/gfx/geometry/matrix3_f.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/matrix3_f.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include "base/numerics/math_constants.h"
+#include "base/strings/stringprintf.h"
+
+namespace {
+
+// This is only to make accessing indices self-explanatory.
+enum MatrixCoordinates {
+ M00,
+ M01,
+ M02,
+ M10,
+ M11,
+ M12,
+ M20,
+ M21,
+ M22,
+ M_END
+};
+
+template<typename T>
+double Determinant3x3(T data[M_END]) {
+ // This routine is separated from the Matrix3F::Determinant because in
+ // computing inverse we do want higher precision afforded by the explicit
+ // use of 'double'.
+ return
+ static_cast<double>(data[M00]) * (
+ static_cast<double>(data[M11]) * data[M22] -
+ static_cast<double>(data[M12]) * data[M21]) +
+ static_cast<double>(data[M01]) * (
+ static_cast<double>(data[M12]) * data[M20] -
+ static_cast<double>(data[M10]) * data[M22]) +
+ static_cast<double>(data[M02]) * (
+ static_cast<double>(data[M10]) * data[M21] -
+ static_cast<double>(data[M11]) * data[M20]);
+}
+
+} // namespace
+
+namespace gfx {
+
+Matrix3F::Matrix3F() {
+}
+
+Matrix3F::~Matrix3F() {
+}
+
+// static
+Matrix3F Matrix3F::Zeros() {
+ Matrix3F matrix;
+ matrix.set(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::Ones() {
+ Matrix3F matrix;
+ matrix.set(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::Identity() {
+ Matrix3F matrix;
+ matrix.set(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::FromOuterProduct(const Vector3dF& a, const Vector3dF& bt) {
+ Matrix3F matrix;
+ matrix.set(a.x() * bt.x(), a.x() * bt.y(), a.x() * bt.z(),
+ a.y() * bt.x(), a.y() * bt.y(), a.y() * bt.z(),
+ a.z() * bt.x(), a.z() * bt.y(), a.z() * bt.z());
+ return matrix;
+}
+
+bool Matrix3F::IsEqual(const Matrix3F& rhs) const {
+ return 0 == memcmp(data_, rhs.data_, sizeof(data_));
+}
+
+bool Matrix3F::IsNear(const Matrix3F& rhs, float precision) const {
+ DCHECK(precision >= 0);
+ for (int i = 0; i < M_END; ++i) {
+ if (std::abs(data_[i] - rhs.data_[i]) > precision)
+ return false;
+ }
+ return true;
+}
+
+Matrix3F Matrix3F::Add(const Matrix3F& rhs) const {
+ Matrix3F result;
+ for (int i = 0; i < M_END; ++i)
+ result.data_[i] = data_[i] + rhs.data_[i];
+ return result;
+}
+
+Matrix3F Matrix3F::Subtract(const Matrix3F& rhs) const {
+ Matrix3F result;
+ for (int i = 0; i < M_END; ++i)
+ result.data_[i] = data_[i] - rhs.data_[i];
+ return result;
+}
+
+Matrix3F Matrix3F::Inverse() const {
+ Matrix3F inverse = Matrix3F::Zeros();
+ double determinant = Determinant3x3(data_);
+ if (std::numeric_limits<float>::epsilon() > std::abs(determinant))
+ return inverse; // Singular matrix. Return Zeros().
+
+ inverse.set(
+ static_cast<float>((data_[M11] * data_[M22] - data_[M12] * data_[M21]) /
+ determinant),
+ static_cast<float>((data_[M02] * data_[M21] - data_[M01] * data_[M22]) /
+ determinant),
+ static_cast<float>((data_[M01] * data_[M12] - data_[M02] * data_[M11]) /
+ determinant),
+ static_cast<float>((data_[M12] * data_[M20] - data_[M10] * data_[M22]) /
+ determinant),
+ static_cast<float>((data_[M00] * data_[M22] - data_[M02] * data_[M20]) /
+ determinant),
+ static_cast<float>((data_[M02] * data_[M10] - data_[M00] * data_[M12]) /
+ determinant),
+ static_cast<float>((data_[M10] * data_[M21] - data_[M11] * data_[M20]) /
+ determinant),
+ static_cast<float>((data_[M01] * data_[M20] - data_[M00] * data_[M21]) /
+ determinant),
+ static_cast<float>((data_[M00] * data_[M11] - data_[M01] * data_[M10]) /
+ determinant));
+ return inverse;
+}
+
+Matrix3F Matrix3F::Transpose() const {
+ Matrix3F transpose;
+ transpose.set(data_[M00], data_[M10], data_[M20], data_[M01], data_[M11],
+ data_[M21], data_[M02], data_[M12], data_[M22]);
+ return transpose;
+}
+
+float Matrix3F::Determinant() const {
+ return static_cast<float>(Determinant3x3(data_));
+}
+
+Vector3dF Matrix3F::SolveEigenproblem(Matrix3F* eigenvectors) const {
+ // The matrix must be symmetric.
+ const float epsilon = std::numeric_limits<float>::epsilon();
+ if (std::abs(data_[M01] - data_[M10]) > epsilon ||
+ std::abs(data_[M02] - data_[M20]) > epsilon ||
+ std::abs(data_[M12] - data_[M21]) > epsilon) {
+ NOTREACHED();
+ return Vector3dF();
+ }
+
+ float eigenvalues[3];
+ float p =
+ data_[M01] * data_[M01] +
+ data_[M02] * data_[M02] +
+ data_[M12] * data_[M12];
+
+ bool diagonal = std::abs(p) < epsilon;
+ if (diagonal) {
+ eigenvalues[0] = data_[M00];
+ eigenvalues[1] = data_[M11];
+ eigenvalues[2] = data_[M22];
+ } else {
+ float q = Trace() / 3.0f;
+ p = (data_[M00] - q) * (data_[M00] - q) +
+ (data_[M11] - q) * (data_[M11] - q) +
+ (data_[M22] - q) * (data_[M22] - q) +
+ 2 * p;
+ p = std::sqrt(p / 6);
+
+ // The computation below puts B as (A - qI) / p, where A is *this.
+ Matrix3F matrix_b(*this);
+ matrix_b.data_[M00] -= q;
+ matrix_b.data_[M11] -= q;
+ matrix_b.data_[M22] -= q;
+ for (int i = 0; i < M_END; ++i)
+ matrix_b.data_[i] /= p;
+
+ double half_det_b = Determinant3x3(matrix_b.data_) / 2.0;
+ // half_det_b should be in <-1, 1>, but beware of rounding error.
+ double phi = 0.0f;
+ if (half_det_b <= -1.0)
+ phi = base::kPiDouble / 3;
+ else if (half_det_b < 1.0)
+ phi = acos(half_det_b) / 3;
+
+ eigenvalues[0] = q + 2 * p * static_cast<float>(cos(phi));
+ eigenvalues[2] =
+ q + 2 * p * static_cast<float>(cos(phi + 2.0 * base::kPiDouble / 3.0));
+ eigenvalues[1] = 3 * q - eigenvalues[0] - eigenvalues[2];
+ }
+
+ // Put eigenvalues in the descending order.
+ int indices[3] = {0, 1, 2};
+ if (eigenvalues[2] > eigenvalues[1]) {
+ std::swap(eigenvalues[2], eigenvalues[1]);
+ std::swap(indices[2], indices[1]);
+ }
+
+ if (eigenvalues[1] > eigenvalues[0]) {
+ std::swap(eigenvalues[1], eigenvalues[0]);
+ std::swap(indices[1], indices[0]);
+ }
+
+ if (eigenvalues[2] > eigenvalues[1]) {
+ std::swap(eigenvalues[2], eigenvalues[1]);
+ std::swap(indices[2], indices[1]);
+ }
+
+ if (eigenvectors != NULL && diagonal) {
+ // Eigenvectors are e-vectors, just need to be sorted accordingly.
+ *eigenvectors = Zeros();
+ for (int i = 0; i < 3; ++i)
+ eigenvectors->set(indices[i], i, 1.0f);
+ } else if (eigenvectors != NULL) {
+ // Consult the following for a detailed discussion:
+ // Joachim Kopp
+ // Numerical diagonalization of hermitian 3x3 matrices
+ // arXiv.org preprint: physics/0610206
+ // Int. J. Mod. Phys. C19 (2008) 523-548
+
+ // TODO(motek): expand to handle correctly negative and multiple
+ // eigenvalues.
+ for (int i = 0; i < 3; ++i) {
+ float l = eigenvalues[i];
+ // B = A - l * I
+ Matrix3F matrix_b(*this);
+ matrix_b.data_[M00] -= l;
+ matrix_b.data_[M11] -= l;
+ matrix_b.data_[M22] -= l;
+ Vector3dF e1 = CrossProduct(matrix_b.get_column(0),
+ matrix_b.get_column(1));
+ Vector3dF e2 = CrossProduct(matrix_b.get_column(1),
+ matrix_b.get_column(2));
+ Vector3dF e3 = CrossProduct(matrix_b.get_column(2),
+ matrix_b.get_column(0));
+
+ // e1, e2 and e3 should point in the same direction.
+ if (DotProduct(e1, e2) < 0)
+ e2 = -e2;
+
+ if (DotProduct(e1, e3) < 0)
+ e3 = -e3;
+
+ Vector3dF eigvec = e1 + e2 + e3;
+ // Normalize.
+ eigvec.Scale(1.0f / eigvec.Length());
+ eigenvectors->set_column(i, eigvec);
+ }
+ }
+
+ return Vector3dF(eigenvalues[0], eigenvalues[1], eigenvalues[2]);
+}
+
+Matrix3F MatrixProduct(const Matrix3F& lhs, const Matrix3F& rhs) {
+ Matrix3F result = Matrix3F::Zeros();
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ result.set(i, j, DotProduct(lhs.get_row(i), rhs.get_column(j)));
+ }
+ }
+ return result;
+}
+
+Vector3dF MatrixProduct(const Matrix3F& lhs, const Vector3dF& rhs) {
+ return Vector3dF(DotProduct(lhs.get_row(0), rhs),
+ DotProduct(lhs.get_row(1), rhs),
+ DotProduct(lhs.get_row(2), rhs));
+}
+
+std::string Matrix3F::ToString() const {
+ return base::StringPrintf(
+ "[[%+0.4f, %+0.4f, %+0.4f],"
+ " [%+0.4f, %+0.4f, %+0.4f],"
+ " [%+0.4f, %+0.4f, %+0.4f]]",
+ data_[M00], data_[M01], data_[M02], data_[M10], data_[M11], data_[M12],
+ data_[M20], data_[M21], data_[M22]);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/matrix3_f.h b/ui/gfx/geometry/matrix3_f.h
new file mode 100644
index 0000000000..7fab2e3e7c
--- /dev/null
+++ b/ui/gfx/geometry/matrix3_f.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_MATRIX3_F_H_
+#define UI_GFX_GEOMETRY_MATRIX3_F_H_
+
+#include "base/logging.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+class GFX_EXPORT Matrix3F {
+ public:
+ ~Matrix3F();
+
+ static Matrix3F Zeros();
+ static Matrix3F Ones();
+ static Matrix3F Identity();
+ static Matrix3F FromOuterProduct(const Vector3dF& a, const Vector3dF& bt);
+
+ bool IsEqual(const Matrix3F& rhs) const;
+
+ // Element-wise comparison with given precision.
+ bool IsNear(const Matrix3F& rhs, float precision) const;
+
+ float get(int i, int j) const {
+ return data_[MatrixToArrayCoords(i, j)];
+ }
+
+ void set(int i, int j, float v) {
+ data_[MatrixToArrayCoords(i, j)] = v;
+ }
+
+ void set(float m00, float m01, float m02,
+ float m10, float m11, float m12,
+ float m20, float m21, float m22) {
+ data_[0] = m00;
+ data_[1] = m01;
+ data_[2] = m02;
+ data_[3] = m10;
+ data_[4] = m11;
+ data_[5] = m12;
+ data_[6] = m20;
+ data_[7] = m21;
+ data_[8] = m22;
+ }
+
+ Vector3dF get_row(int i) const {
+ return Vector3dF(data_[MatrixToArrayCoords(i, 0)],
+ data_[MatrixToArrayCoords(i, 1)],
+ data_[MatrixToArrayCoords(i, 2)]);
+ }
+
+ Vector3dF get_column(int i) const {
+ return Vector3dF(
+ data_[MatrixToArrayCoords(0, i)],
+ data_[MatrixToArrayCoords(1, i)],
+ data_[MatrixToArrayCoords(2, i)]);
+ }
+
+ void set_column(int i, const Vector3dF& c) {
+ data_[MatrixToArrayCoords(0, i)] = c.x();
+ data_[MatrixToArrayCoords(1, i)] = c.y();
+ data_[MatrixToArrayCoords(2, i)] = c.z();
+ }
+
+ // Produces a new matrix by adding the elements of |rhs| to this matrix
+ Matrix3F Add(const Matrix3F& rhs) const;
+ // Produces a new matrix by subtracting elements of |rhs| from this matrix.
+ Matrix3F Subtract(const Matrix3F& rhs) const;
+
+ // Returns an inverse of this if the matrix is non-singular, zero (== Zero())
+ // otherwise.
+ Matrix3F Inverse() const;
+
+ // Returns a transpose of this matrix.
+ Matrix3F Transpose() const;
+
+ // Value of the determinant of the matrix.
+ float Determinant() const;
+
+ // Trace (sum of diagonal elements) of the matrix.
+ float Trace() const {
+ return data_[MatrixToArrayCoords(0, 0)] +
+ data_[MatrixToArrayCoords(1, 1)] +
+ data_[MatrixToArrayCoords(2, 2)];
+ }
+
+ // Compute eigenvalues and (optionally) normalized eigenvectors of
+ // a positive defnite matrix *this. Eigenvectors are computed only if
+ // non-null |eigenvectors| matrix is passed. If it is NULL, the routine
+ // will not attempt to compute eigenvectors but will still return eigenvalues
+ // if they can be computed.
+ // If eigenvalues cannot be computed (the matrix does not meet constraints)
+ // the 0-vector is returned. Note that to retrieve eigenvalues, the matrix
+ // only needs to be symmetric while eigenvectors require it to be
+ // positive-definite. Passing a non-positive definite matrix will result in
+ // NaNs in vectors which cannot be computed.
+ // Eigenvectors are placed as column in |eigenvectors| in order corresponding
+ // to eigenvalues.
+ Vector3dF SolveEigenproblem(Matrix3F* eigenvectors) const;
+
+ std::string ToString() const;
+
+ private:
+ Matrix3F(); // Uninitialized default.
+
+ static int MatrixToArrayCoords(int i, int j) {
+ DCHECK(i >= 0 && i < 3);
+ DCHECK(j >= 0 && j < 3);
+ return i * 3 + j;
+ }
+
+ float data_[9];
+};
+
+inline bool operator==(const Matrix3F& lhs, const Matrix3F& rhs) {
+ return lhs.IsEqual(rhs);
+}
+
+// Matrix addition. Produces a new matrix by adding the corresponding elements
+// together.
+inline Matrix3F operator+(const Matrix3F& lhs, const Matrix3F& rhs) {
+ return lhs.Add(rhs);
+}
+
+// Matrix subtraction. Produces a new matrix by subtracting elements of rhs
+// from corresponding elements of lhs.
+inline Matrix3F operator-(const Matrix3F& lhs, const Matrix3F& rhs) {
+ return lhs.Subtract(rhs);
+}
+
+GFX_EXPORT Matrix3F MatrixProduct(const Matrix3F& lhs, const Matrix3F& rhs);
+GFX_EXPORT Vector3dF MatrixProduct(const Matrix3F& lhs, const Vector3dF& rhs);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_MATRIX3_F_H_
diff --git a/ui/gfx/geometry/matrix3_unittest.cc b/ui/gfx/geometry/matrix3_unittest.cc
new file mode 100644
index 0000000000..27e9182f94
--- /dev/null
+++ b/ui/gfx/geometry/matrix3_unittest.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cmath>
+#include <limits>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/matrix3_f.h"
+
+namespace gfx {
+namespace {
+
+TEST(Matrix3fTest, Constructors) {
+ Matrix3F zeros = Matrix3F::Zeros();
+ Matrix3F ones = Matrix3F::Ones();
+ Matrix3F identity = Matrix3F::Identity();
+
+ Matrix3F product_ones = Matrix3F::FromOuterProduct(
+ Vector3dF(1.0f, 1.0f, 1.0f), Vector3dF(1.0f, 1.0f, 1.0f));
+ Matrix3F product_zeros = Matrix3F::FromOuterProduct(
+ Vector3dF(1.0f, 1.0f, 1.0f), Vector3dF(0.0f, 0.0f, 0.0f));
+ EXPECT_EQ(ones, product_ones);
+ EXPECT_EQ(zeros, product_zeros);
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j)
+ EXPECT_EQ(i == j ? 1.0f : 0.0f, identity.get(i, j));
+ }
+}
+
+TEST(Matrix3fTest, DataAccess) {
+ Matrix3F matrix = Matrix3F::Ones();
+ Matrix3F identity = Matrix3F::Identity();
+
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), identity.get_column(1));
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), identity.get_row(1));
+ matrix.set(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
+ EXPECT_EQ(Vector3dF(2.0f, 5.0f, 8.0f), matrix.get_column(2));
+ EXPECT_EQ(Vector3dF(6.0f, 7.0f, 8.0f), matrix.get_row(2));
+ matrix.set_column(0, Vector3dF(0.1f, 0.2f, 0.3f));
+ matrix.set_column(0, Vector3dF(0.1f, 0.2f, 0.3f));
+ EXPECT_EQ(Vector3dF(0.1f, 0.2f, 0.3f), matrix.get_column(0));
+ EXPECT_EQ(Vector3dF(0.1f, 1.0f, 2.0f), matrix.get_row(0));
+
+ EXPECT_EQ(0.1f, matrix.get(0, 0));
+ EXPECT_EQ(5.0f, matrix.get(1, 2));
+}
+
+TEST(Matrix3fTest, Determinant) {
+ EXPECT_EQ(1.0f, Matrix3F::Identity().Determinant());
+ EXPECT_EQ(0.0f, Matrix3F::Zeros().Determinant());
+ EXPECT_EQ(0.0f, Matrix3F::Ones().Determinant());
+
+ // Now for something non-trivial...
+ Matrix3F matrix = Matrix3F::Zeros();
+ matrix.set(0, 5, 6, 8, 7, 0, 1, 9, 0);
+ EXPECT_EQ(390.0f, matrix.Determinant());
+ matrix.set(2, 0, 3 * matrix.get(0, 0));
+ matrix.set(2, 1, 3 * matrix.get(0, 1));
+ matrix.set(2, 2, 3 * matrix.get(0, 2));
+ EXPECT_EQ(0, matrix.Determinant());
+
+ matrix.set(0.57f, 0.205f, 0.942f,
+ 0.314f, 0.845f, 0.826f,
+ 0.131f, 0.025f, 0.962f);
+ EXPECT_NEAR(0.3149f, matrix.Determinant(), 0.0001f);
+}
+
+TEST(Matrix3fTest, Inverse) {
+ Matrix3F identity = Matrix3F::Identity();
+ Matrix3F inv_identity = identity.Inverse();
+ EXPECT_EQ(identity, inv_identity);
+
+ Matrix3F singular = Matrix3F::Zeros();
+ singular.set(1.0f, 3.0f, 4.0f,
+ 2.0f, 11.0f, 5.0f,
+ 0.5f, 1.5f, 2.0f);
+ EXPECT_EQ(0, singular.Determinant());
+ EXPECT_EQ(Matrix3F::Zeros(), singular.Inverse());
+
+ Matrix3F regular = Matrix3F::Zeros();
+ regular.set(0.57f, 0.205f, 0.942f,
+ 0.314f, 0.845f, 0.826f,
+ 0.131f, 0.025f, 0.962f);
+ Matrix3F inv_regular = regular.Inverse();
+ regular.set(2.51540616f, -0.55138018f, -1.98968043f,
+ -0.61552266f, 1.34920184f, -0.55573636f,
+ -0.32653861f, 0.04002158f, 1.32488726f);
+ EXPECT_TRUE(regular.IsNear(inv_regular, 0.00001f));
+}
+
+TEST(Matrix3fTest, Transpose) {
+ Matrix3F matrix = Matrix3F::Zeros();
+
+ matrix.set(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
+
+ Matrix3F transpose = matrix.Transpose();
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 2.0f), transpose.get_column(0));
+ EXPECT_EQ(Vector3dF(3.0f, 4.0f, 5.0f), transpose.get_column(1));
+ EXPECT_EQ(Vector3dF(6.0f, 7.0f, 8.0f), transpose.get_column(2));
+
+ EXPECT_TRUE(matrix.IsEqual(transpose.Transpose()));
+}
+
+TEST(Matrix3fTest, EigenvectorsIdentity) {
+ // This block tests the trivial case of eigenvalues of the identity matrix.
+ Matrix3F identity = Matrix3F::Identity();
+ Vector3dF eigenvals = identity.SolveEigenproblem(NULL);
+ EXPECT_EQ(Vector3dF(1.0f, 1.0f, 1.0f), eigenvals);
+}
+
+TEST(Matrix3fTest, EigenvectorsDiagonal) {
+ // This block tests the another trivial case of eigenvalues of a diagonal
+ // matrix. Here we expect values to be sorted.
+ Matrix3F matrix = Matrix3F::Zeros();
+ matrix.set(0, 0, 1.0f);
+ matrix.set(1, 1, -2.5f);
+ matrix.set(2, 2, 3.14f);
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ EXPECT_EQ(Vector3dF(3.14f, 1.0f, -2.5f), eigenvals);
+
+ EXPECT_EQ(Vector3dF(0.0f, 0.0f, 1.0f), eigenvectors.get_column(0));
+ EXPECT_EQ(Vector3dF(1.0f, 0.0f, 0.0f), eigenvectors.get_column(1));
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), eigenvectors.get_column(2));
+}
+
+TEST(Matrix3fTest, EigenvectorsNiceNotPositive) {
+ // This block tests computation of eigenvectors of a matrix where nice
+ // round values are expected.
+ Matrix3F matrix = Matrix3F::Zeros();
+ // This is not a positive-definite matrix but eigenvalues and the first
+ // eigenvector should nonetheless be computed correctly.
+ matrix.set(3, 2, 4, 2, 0, 2, 4, 2, 3);
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ EXPECT_EQ(Vector3dF(8.0f, -1.0f, -1.0f), eigenvals);
+
+ Vector3dF expected_principal(0.66666667f, 0.33333333f, 0.66666667f);
+ EXPECT_NEAR(0.0f,
+ (expected_principal - eigenvectors.get_column(0)).Length(),
+ 0.000001f);
+}
+
+TEST(Matrix3fTest, EigenvectorsPositiveDefinite) {
+ // This block tests computation of eigenvectors of a matrix where output
+ // is not as nice as above, but it actually meets the definition.
+ Matrix3F matrix = Matrix3F::Zeros();
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Matrix3F expected_eigenvectors = Matrix3F::Zeros();
+ matrix.set(1, -1, 2, -1, 4, 5, 2, 5, 0);
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ Vector3dF expected_eigv(7.3996266f, 1.91197255f, -4.31159915f);
+ expected_eigv -= eigenvals;
+ EXPECT_NEAR(0, expected_eigv.LengthSquared(), 0.00001f);
+ expected_eigenvectors.set(0.04926317f, -0.92135662f, -0.38558414f,
+ 0.82134249f, 0.25703273f, -0.50924521f,
+ 0.56830419f, -0.2916096f, 0.76941158f);
+ EXPECT_TRUE(expected_eigenvectors.IsNear(eigenvectors, 0.00001f));
+}
+
+TEST(Matrix3fTest, Operators) {
+ Matrix3F matrix1 = Matrix3F::Zeros();
+ matrix1.set(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ EXPECT_EQ(matrix1 + Matrix3F::Zeros(), matrix1);
+
+ Matrix3F matrix2 = Matrix3F::Zeros();
+ matrix2.set(-1, -2, -3, -4, -5, -6, -7, -8, -9);
+ EXPECT_EQ(matrix1 + matrix2, Matrix3F::Zeros());
+
+ EXPECT_EQ(Matrix3F::Zeros() - matrix1, matrix2);
+
+ Matrix3F result = Matrix3F::Zeros();
+ result.set(2, 4, 6, 8, 10, 12, 14, 16, 18);
+ EXPECT_EQ(matrix1 - matrix2, result);
+ result.set(-2, -4, -6, -8, -10, -12, -14, -16, -18);
+ EXPECT_EQ(matrix2 - matrix1, result);
+}
+
+} // namespace
+} // namespace gfx
diff --git a/ui/gfx/geometry/mojo/BUILD.gn b/ui/gfx/geometry/mojo/BUILD.gn
deleted file mode 100644
index 2d0e1efbcf..0000000000
--- a/ui/gfx/geometry/mojo/BUILD.gn
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//mojo/public/tools/bindings/mojom.gni")
-
-# This target does NOT depend on skia. One can depend on this target to avoid
-# picking up a dependency on skia.
-mojom("mojo") {
- sources = [
- "geometry.mojom",
- ]
-
- check_includes_blink = false
-}
-
-mojom("test_interfaces") {
- sources = [
- "geometry_traits_test_service.mojom",
- ]
-
- public_deps = [
- ":mojo",
- ]
-}
-
-source_set("unit_test") {
- testonly = true
-
- sources = [
- "geometry_struct_traits_unittest.cc",
- ]
-
- deps = [
- ":test_interfaces",
- "//base",
- "//mojo/public/cpp/bindings",
- "//testing/gtest",
- "//ui/gfx/geometry",
- ]
-}
-
-source_set("struct_traits") {
- sources = [
- "geometry_struct_traits.h",
- ]
- public_deps = [
- ":mojo_shared_cpp_sources",
- "//ui/gfx/geometry",
- ]
-}
diff --git a/ui/gfx/geometry/point3_f.cc b/ui/gfx/geometry/point3_f.cc
new file mode 100644
index 0000000000..465376e55e
--- /dev/null
+++ b/ui/gfx/geometry/point3_f.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/point3_f.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string Point3F::ToString() const {
+ return base::StringPrintf("%f,%f,%f", x_, y_, z_);
+}
+
+Point3F operator+(const Point3F& lhs, const Vector3dF& rhs) {
+ float x = lhs.x() + rhs.x();
+ float y = lhs.y() + rhs.y();
+ float z = lhs.z() + rhs.z();
+ return Point3F(x, y, z);
+}
+
+// Subtract a vector from a point, producing a new point offset by the vector's
+// inverse.
+Point3F operator-(const Point3F& lhs, const Vector3dF& rhs) {
+ float x = lhs.x() - rhs.x();
+ float y = lhs.y() - rhs.y();
+ float z = lhs.z() - rhs.z();
+ return Point3F(x, y, z);
+}
+
+// Subtract one point from another, producing a vector that represents the
+// distances between the two points along each axis.
+Vector3dF operator-(const Point3F& lhs, const Point3F& rhs) {
+ float x = lhs.x() - rhs.x();
+ float y = lhs.y() - rhs.y();
+ float z = lhs.z() - rhs.z();
+ return Vector3dF(x, y, z);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/point3_f.h b/ui/gfx/geometry/point3_f.h
new file mode 100644
index 0000000000..15a560c497
--- /dev/null
+++ b/ui/gfx/geometry/point3_f.h
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_POINT3_F_H_
+#define UI_GFX_GEOMETRY_POINT3_F_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+// A point has an x, y and z coordinate.
+class GFX_EXPORT Point3F {
+ public:
+ constexpr Point3F() : x_(0), y_(0), z_(0) {}
+ constexpr Point3F(float x, float y, float z) : x_(x), y_(y), z_(z) {}
+
+ constexpr explicit Point3F(const PointF& point)
+ : x_(point.x()), y_(point.y()), z_(0) {}
+
+ void Scale(float scale) {
+ Scale(scale, scale, scale);
+ }
+
+ void Scale(float x_scale, float y_scale, float z_scale) {
+ SetPoint(x() * x_scale, y() * y_scale, z() * z_scale);
+ }
+
+ constexpr float x() const { return x_; }
+ constexpr float y() const { return y_; }
+ constexpr float z() const { return z_; }
+
+ void set_x(float x) { x_ = x; }
+ void set_y(float y) { y_ = y; }
+ void set_z(float z) { z_ = z; }
+
+ void SetPoint(float x, float y, float z) {
+ x_ = x;
+ y_ = y;
+ z_ = z;
+ }
+
+ // Offset the point by the given vector.
+ void operator+=(const Vector3dF& v) {
+ x_ += v.x();
+ y_ += v.y();
+ z_ += v.z();
+ }
+
+ // Offset the point by the given vector's inverse.
+ void operator-=(const Vector3dF& v) {
+ x_ -= v.x();
+ y_ -= v.y();
+ z_ -= v.z();
+ }
+
+ // Returns the squared euclidean distance between two points.
+ float SquaredDistanceTo(const Point3F& other) const {
+ float dx = x_ - other.x_;
+ float dy = y_ - other.y_;
+ float dz = z_ - other.z_;
+ return dx * dx + dy * dy + dz * dz;
+ }
+
+ PointF AsPointF() const { return PointF(x_, y_); }
+
+ // Returns a string representation of 3d point.
+ std::string ToString() const;
+
+ private:
+ float x_;
+ float y_;
+ float z_;
+
+ // copy/assign are allowed.
+};
+
+inline bool operator==(const Point3F& lhs, const Point3F& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z();
+}
+
+inline bool operator!=(const Point3F& lhs, const Point3F& rhs) {
+ return !(lhs == rhs);
+}
+
+// Add a vector to a point, producing a new point offset by the vector.
+GFX_EXPORT Point3F operator+(const Point3F& lhs, const Vector3dF& rhs);
+
+// Subtract a vector from a point, producing a new point offset by the vector's
+// inverse.
+GFX_EXPORT Point3F operator-(const Point3F& lhs, const Vector3dF& rhs);
+
+// Subtract one point from another, producing a vector that represents the
+// distances between the two points along each axis.
+GFX_EXPORT Vector3dF operator-(const Point3F& lhs, const Point3F& rhs);
+
+inline Point3F PointAtOffsetFromOrigin(const Vector3dF& offset) {
+ return Point3F(offset.x(), offset.y(), offset.z());
+}
+
+inline Point3F ScalePoint(const Point3F& p,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ return Point3F(p.x() * x_scale, p.y() * y_scale, p.z() * z_scale);
+}
+
+inline Point3F ScalePoint(const Point3F& p, const Vector3dF& v) {
+ return Point3F(p.x() * v.x(), p.y() * v.y(), p.z() * v.z());
+}
+
+inline Point3F ScalePoint(const Point3F& p, float scale) {
+ return ScalePoint(p, scale, scale, scale);
+}
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const Point3F& point, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_POINT3_F_H_
diff --git a/ui/gfx/geometry/point3_unittest.cc b/ui/gfx/geometry/point3_unittest.cc
new file mode 100644
index 0000000000..999b1f43c8
--- /dev/null
+++ b/ui/gfx/geometry/point3_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/point3_f.h"
+
+namespace gfx {
+
+TEST(Point3Test, VectorArithmetic) {
+ gfx::Point3F a(1.6f, 5.1f, 3.2f);
+ gfx::Vector3dF v1(3.1f, -3.2f, 9.3f);
+ gfx::Vector3dF v2(-8.1f, 1.2f, 3.3f);
+
+ static const struct {
+ gfx::Point3F expected;
+ gfx::Point3F actual;
+ } tests[] = {
+ { gfx::Point3F(4.7f, 1.9f, 12.5f), a + v1 },
+ { gfx::Point3F(-1.5f, 8.3f, -6.1f), a - v1 },
+ { a, a - v1 + v1 },
+ { a, a + v1 - v1 },
+ { a, a + gfx::Vector3dF() },
+ { gfx::Point3F(12.8f, 0.7f, 9.2f), a + v1 - v2 },
+ { gfx::Point3F(-9.6f, 9.5f, -2.8f), a - v1 + v2 }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i)
+ EXPECT_EQ(tests[i].expected.ToString(),
+ tests[i].actual.ToString());
+
+ a += v1;
+ EXPECT_EQ(Point3F(4.7f, 1.9f, 12.5f).ToString(), a.ToString());
+
+ a -= v2;
+ EXPECT_EQ(Point3F(12.8f, 0.7f, 9.2f).ToString(), a.ToString());
+}
+
+TEST(Point3Test, VectorFromPoints) {
+ gfx::Point3F a(1.6f, 5.2f, 3.2f);
+ gfx::Vector3dF v1(3.1f, -3.2f, 9.3f);
+
+ gfx::Point3F b(a + v1);
+ EXPECT_EQ((b - a).ToString(), v1.ToString());
+}
+
+TEST(Point3Test, Scale) {
+ EXPECT_EQ(Point3F().ToString(), ScalePoint(Point3F(), 2.f).ToString());
+ EXPECT_EQ(Point3F().ToString(),
+ ScalePoint(Point3F(), 2.f, 2.f, 2.f).ToString());
+
+ EXPECT_EQ(Point3F(2.f, -2.f, 4.f).ToString(),
+ ScalePoint(Point3F(1.f, -1.f, 2.f), 2.f).ToString());
+ EXPECT_EQ(Point3F(2.f, -3.f, 8.f).ToString(),
+ ScalePoint(Point3F(1.f, -1.f, 2.f), 2.f, 3.f, 4.f).ToString());
+
+ Point3F zero;
+ zero.Scale(2.f);
+ zero.Scale(6.f, 3.f, 1.5f);
+ EXPECT_EQ(Point3F().ToString(), zero.ToString());
+
+ Point3F point(1.f, -1.f, 2.f);
+ point.Scale(2.f);
+ point.Scale(6.f, 3.f, 1.5f);
+ EXPECT_EQ(Point3F(12.f, -6.f, 6.f).ToString(), point.ToString());
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/point_unittest.cc b/ui/gfx/geometry/point_unittest.cc
new file mode 100644
index 0000000000..b453dd7684
--- /dev/null
+++ b/ui/gfx/geometry/point_unittest.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/point_conversions.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace gfx {
+
+TEST(PointTest, ToPointF) {
+ // Check that explicit conversion from integer to float compiles.
+ Point a(10, 20);
+ PointF b = PointF(a);
+
+ EXPECT_EQ(static_cast<float>(a.x()), b.x());
+ EXPECT_EQ(static_cast<float>(a.y()), b.y());
+}
+
+TEST(PointTest, IsOrigin) {
+ EXPECT_FALSE(Point(1, 0).IsOrigin());
+ EXPECT_FALSE(Point(0, 1).IsOrigin());
+ EXPECT_FALSE(Point(1, 2).IsOrigin());
+ EXPECT_FALSE(Point(-1, 0).IsOrigin());
+ EXPECT_FALSE(Point(0, -1).IsOrigin());
+ EXPECT_FALSE(Point(-1, -2).IsOrigin());
+ EXPECT_TRUE(Point(0, 0).IsOrigin());
+
+ EXPECT_FALSE(PointF(0.1f, 0).IsOrigin());
+ EXPECT_FALSE(PointF(0, 0.1f).IsOrigin());
+ EXPECT_FALSE(PointF(0.1f, 2).IsOrigin());
+ EXPECT_FALSE(PointF(-0.1f, 0).IsOrigin());
+ EXPECT_FALSE(PointF(0, -0.1f).IsOrigin());
+ EXPECT_FALSE(PointF(-0.1f, -2).IsOrigin());
+ EXPECT_TRUE(PointF(0, 0).IsOrigin());
+}
+
+TEST(PointTest, VectorArithmetic) {
+ Point a(1, 5);
+ Vector2d v1(3, -3);
+ Vector2d v2(-8, 1);
+
+ static const struct {
+ Point expected;
+ Point actual;
+ } tests[] = {
+ { Point(4, 2), a + v1 },
+ { Point(-2, 8), a - v1 },
+ { a, a - v1 + v1 },
+ { a, a + v1 - v1 },
+ { a, a + Vector2d() },
+ { Point(12, 1), a + v1 - v2 },
+ { Point(-10, 9), a - v1 + v2 }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i)
+ EXPECT_EQ(tests[i].expected.ToString(), tests[i].actual.ToString());
+}
+
+TEST(PointTest, OffsetFromPoint) {
+ Point a(1, 5);
+ Point b(-20, 8);
+ EXPECT_EQ(Vector2d(-20 - 1, 8 - 5).ToString(), (b - a).ToString());
+}
+
+TEST(PointTest, ToRoundedPoint) {
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0, 0)));
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Point(1, 1), ToRoundedPoint(PointF(0.5f, 0.5f)));
+ EXPECT_EQ(Point(1, 1), ToRoundedPoint(PointF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10, 10)));
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Point(11, 11), ToRoundedPoint(PointF(10.5f, 10.5f)));
+ EXPECT_EQ(Point(11, 11), ToRoundedPoint(PointF(10.9999f, 10.9999f)));
+
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10, -10)));
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10.0001f, -10.0001f)));
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10.4999f, -10.4999f)));
+ EXPECT_EQ(Point(-11, -11), ToRoundedPoint(PointF(-10.5f, -10.5f)));
+ EXPECT_EQ(Point(-11, -11), ToRoundedPoint(PointF(-10.9999f, -10.9999f)));
+}
+
+TEST(PointTest, Scale) {
+ EXPECT_EQ(PointF().ToString(), ScalePoint(PointF(), 2).ToString());
+ EXPECT_EQ(PointF().ToString(), ScalePoint(PointF(), 2, 2).ToString());
+
+ EXPECT_EQ(PointF(2, -2).ToString(), ScalePoint(PointF(1, -1), 2).ToString());
+ EXPECT_EQ(PointF(2, -2).ToString(),
+ ScalePoint(PointF(1, -1), 2, 2).ToString());
+
+ PointF zero;
+ PointF one(1, -1);
+
+ zero.Scale(2);
+ zero.Scale(3, 1.5);
+
+ one.Scale(2);
+ one.Scale(3, 1.5);
+
+ EXPECT_EQ(PointF().ToString(), zero.ToString());
+ EXPECT_EQ(PointF(6, -3).ToString(), one.ToString());
+}
+
+TEST(PointTest, ClampPoint) {
+ Point a;
+
+ a = Point(3, 5);
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(2, 4));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(3, 5));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(4, 2));
+ EXPECT_EQ(Point(4, 5).ToString(), a.ToString());
+ a.SetToMax(Point(8, 10));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Point(9, 11));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+ a.SetToMin(Point(8, 10));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+ a.SetToMin(Point(11, 9));
+ EXPECT_EQ(Point(8, 9).ToString(), a.ToString());
+ a.SetToMin(Point(7, 11));
+ EXPECT_EQ(Point(7, 9).ToString(), a.ToString());
+ a.SetToMin(Point(3, 5));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+}
+
+TEST(PointTest, ClampPointF) {
+ PointF a;
+
+ a = PointF(3.5f, 5.5f);
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(2.5f, 4.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(3.5f, 5.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(4.5f, 2.5f));
+ EXPECT_EQ(PointF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(8.5f, 10.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(PointF(9.5f, 11.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(8.5f, 10.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(11.5f, 9.5f));
+ EXPECT_EQ(PointF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(7.5f, 11.5f));
+ EXPECT_EQ(PointF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(3.5f, 5.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+TEST(PointTest, Offset) {
+ Point test(3, 4);
+ test.Offset(5, -8);
+ EXPECT_EQ(test, Point(8, -4));
+}
+
+TEST(PointTest, VectorMath) {
+ Point test = Point(3, 4);
+ test += Vector2d(5, -8);
+ EXPECT_EQ(test, Point(8, -4));
+
+ Point test2 = Point(3, 4);
+ test2 -= Vector2d(5, -8);
+ EXPECT_EQ(test2, Point(-2, 12));
+}
+
+TEST(PointTest, IntegerOverflow) {
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ Point max_point(int_max, int_max);
+ Point min_point(int_min, int_min);
+ Point test;
+
+ test = Point();
+ test.Offset(int_max, int_max);
+ EXPECT_EQ(test, max_point);
+
+ test = Point();
+ test.Offset(int_min, int_min);
+ EXPECT_EQ(test, min_point);
+
+ test = Point(10, 20);
+ test.Offset(int_max, int_max);
+ EXPECT_EQ(test, max_point);
+
+ test = Point(-10, -20);
+ test.Offset(int_min, int_min);
+ EXPECT_EQ(test, min_point);
+
+ test = Point();
+ test += Vector2d(int_max, int_max);
+ EXPECT_EQ(test, max_point);
+
+ test = Point();
+ test += Vector2d(int_min, int_min);
+ EXPECT_EQ(test, min_point);
+
+ test = Point(10, 20);
+ test += Vector2d(int_max, int_max);
+ EXPECT_EQ(test, max_point);
+
+ test = Point(-10, -20);
+ test += Vector2d(int_min, int_min);
+ EXPECT_EQ(test, min_point);
+
+ test = Point();
+ test -= Vector2d(int_max, int_max);
+ EXPECT_EQ(test, Point(-int_max, -int_max));
+
+ test = Point();
+ test -= Vector2d(int_min, int_min);
+ EXPECT_EQ(test, max_point);
+
+ test = Point(10, 20);
+ test -= Vector2d(int_min, int_min);
+ EXPECT_EQ(test, max_point);
+
+ test = Point(-10, -20);
+ test -= Vector2d(int_max, int_max);
+ EXPECT_EQ(test, min_point);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/quad_f.cc b/ui/gfx/geometry/quad_f.cc
new file mode 100644
index 0000000000..8ed8b91700
--- /dev/null
+++ b/ui/gfx/geometry/quad_f.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/quad_f.h"
+
+#include <limits>
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+void QuadF::operator=(const RectF& rect) {
+ p1_ = PointF(rect.x(), rect.y());
+ p2_ = PointF(rect.right(), rect.y());
+ p3_ = PointF(rect.right(), rect.bottom());
+ p4_ = PointF(rect.x(), rect.bottom());
+}
+
+std::string QuadF::ToString() const {
+ return base::StringPrintf("%s;%s;%s;%s",
+ p1_.ToString().c_str(),
+ p2_.ToString().c_str(),
+ p3_.ToString().c_str(),
+ p4_.ToString().c_str());
+}
+
+static inline bool WithinEpsilon(float a, float b) {
+ return std::abs(a - b) < std::numeric_limits<float>::epsilon();
+}
+
+bool QuadF::IsRectilinear() const {
+ return
+ (WithinEpsilon(p1_.x(), p2_.x()) && WithinEpsilon(p2_.y(), p3_.y()) &&
+ WithinEpsilon(p3_.x(), p4_.x()) && WithinEpsilon(p4_.y(), p1_.y())) ||
+ (WithinEpsilon(p1_.y(), p2_.y()) && WithinEpsilon(p2_.x(), p3_.x()) &&
+ WithinEpsilon(p3_.y(), p4_.y()) && WithinEpsilon(p4_.x(), p1_.x()));
+}
+
+bool QuadF::IsCounterClockwise() const {
+ // This math computes the signed area of the quad. Positive area
+ // indicates the quad is clockwise; negative area indicates the quad is
+ // counter-clockwise. Note carefully: this is backwards from conventional
+ // math because our geometric space uses screen coordiantes with y-axis
+ // pointing downards.
+ // Reference: http://mathworld.wolfram.com/PolygonArea.html.
+ // The equation can be written:
+ // Signed area = determinant1 + determinant2 + determinant3 + determinant4
+ // In practise, Refactoring the computation of adding determinants so that
+ // reducing the number of operations. The equation is:
+ // Signed area = element1 + element2 - element3 - element4
+
+ float p24 = p2_.y() - p4_.y();
+ float p31 = p3_.y() - p1_.y();
+
+ // Up-cast to double so this cannot overflow.
+ double element1 = static_cast<double>(p1_.x()) * p24;
+ double element2 = static_cast<double>(p2_.x()) * p31;
+ double element3 = static_cast<double>(p3_.x()) * p24;
+ double element4 = static_cast<double>(p4_.x()) * p31;
+
+ return element1 + element2 < element3 + element4;
+}
+
+static inline bool PointIsInTriangle(const PointF& point,
+ const PointF& r1,
+ const PointF& r2,
+ const PointF& r3) {
+ // Compute the barycentric coordinates (u, v, w) of |point| relative to the
+ // triangle (r1, r2, r3) by the solving the system of equations:
+ // 1) point = u * r1 + v * r2 + w * r3
+ // 2) u + v + w = 1
+ // This algorithm comes from Christer Ericson's Real-Time Collision Detection.
+
+ Vector2dF r31 = r1 - r3;
+ Vector2dF r32 = r2 - r3;
+ Vector2dF r3p = point - r3;
+
+ // Promote to doubles so all the math below is done with doubles, because
+ // otherwise it gets incorrect results on arm64.
+ double r31x = r31.x();
+ double r31y = r31.y();
+ double r32x = r32.x();
+ double r32y = r32.y();
+
+ double denom = r32y * r31x - r32x * r31y;
+ double u = (r32y * r3p.x() - r32x * r3p.y()) / denom;
+ double v = (r31x * r3p.y() - r31y * r3p.x()) / denom;
+ double w = 1.0 - u - v;
+
+ // Use the barycentric coordinates to test if |point| is inside the
+ // triangle (r1, r2, r2).
+ return (u >= 0) && (v >= 0) && (w >= 0);
+}
+
+bool QuadF::Contains(const PointF& point) const {
+ return PointIsInTriangle(point, p1_, p2_, p3_)
+ || PointIsInTriangle(point, p1_, p3_, p4_);
+}
+
+void QuadF::Scale(float x_scale, float y_scale) {
+ p1_.Scale(x_scale, y_scale);
+ p2_.Scale(x_scale, y_scale);
+ p3_.Scale(x_scale, y_scale);
+ p4_.Scale(x_scale, y_scale);
+}
+
+void QuadF::operator+=(const Vector2dF& rhs) {
+ p1_ += rhs;
+ p2_ += rhs;
+ p3_ += rhs;
+ p4_ += rhs;
+}
+
+void QuadF::operator-=(const Vector2dF& rhs) {
+ p1_ -= rhs;
+ p2_ -= rhs;
+ p3_ -= rhs;
+ p4_ -= rhs;
+}
+
+QuadF operator+(const QuadF& lhs, const Vector2dF& rhs) {
+ QuadF result = lhs;
+ result += rhs;
+ return result;
+}
+
+QuadF operator-(const QuadF& lhs, const Vector2dF& rhs) {
+ QuadF result = lhs;
+ result -= rhs;
+ return result;
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/quad_f.h b/ui/gfx/geometry/quad_f.h
new file mode 100644
index 0000000000..8e45b3e2de
--- /dev/null
+++ b/ui/gfx/geometry/quad_f.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_QUAD_F_H_
+#define UI_GFX_GEOMETRY_QUAD_F_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cmath>
+#include <iosfwd>
+#include <string>
+
+#include "base/logging.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+// A Quad is defined by four corners, allowing it to have edges that are not
+// axis-aligned, unlike a Rect.
+class GFX_EXPORT QuadF {
+ public:
+ constexpr QuadF() = default;
+ constexpr QuadF(const PointF& p1,
+ const PointF& p2,
+ const PointF& p3,
+ const PointF& p4)
+ : p1_(p1), p2_(p2), p3_(p3), p4_(p4) {}
+
+ constexpr explicit QuadF(const RectF& rect)
+ : p1_(rect.x(), rect.y()),
+ p2_(rect.right(), rect.y()),
+ p3_(rect.right(), rect.bottom()),
+ p4_(rect.x(), rect.bottom()) {}
+
+ void operator=(const RectF& rect);
+
+ void set_p1(const PointF& p) { p1_ = p; }
+ void set_p2(const PointF& p) { p2_ = p; }
+ void set_p3(const PointF& p) { p3_ = p; }
+ void set_p4(const PointF& p) { p4_ = p; }
+
+ constexpr const PointF& p1() const { return p1_; }
+ constexpr const PointF& p2() const { return p2_; }
+ constexpr const PointF& p3() const { return p3_; }
+ constexpr const PointF& p4() const { return p4_; }
+
+ // Returns true if the quad is an axis-aligned rectangle.
+ bool IsRectilinear() const;
+
+ // Returns true if the points of the quad are in counter-clockwise order. This
+ // assumes that the quad is convex, and that no three points are collinear.
+ bool IsCounterClockwise() const;
+
+ // Returns true if the |point| is contained within the quad, or lies on on
+ // edge of the quad. This assumes that the quad is convex.
+ bool Contains(const gfx::PointF& point) const;
+
+ // Returns a rectangle that bounds the four points of the quad. The points of
+ // the quad may lie on the right/bottom edge of the resulting rectangle,
+ // rather than being strictly inside it.
+ RectF BoundingBox() const {
+ float rl = std::min(std::min(p1_.x(), p2_.x()), std::min(p3_.x(), p4_.x()));
+ float rr = std::max(std::max(p1_.x(), p2_.x()), std::max(p3_.x(), p4_.x()));
+ float rt = std::min(std::min(p1_.y(), p2_.y()), std::min(p3_.y(), p4_.y()));
+ float rb = std::max(std::max(p1_.y(), p2_.y()), std::max(p3_.y(), p4_.y()));
+ return RectF(rl, rt, rr - rl, rb - rt);
+ }
+
+ // Realigns the corners in the quad by rotating them n corners to the right.
+ void Realign(size_t times) {
+ DCHECK_LE(times, 4u);
+ for (size_t i = 0; i < times; ++i) {
+ PointF temp = p1_;
+ p1_ = p2_;
+ p2_ = p3_;
+ p3_ = p4_;
+ p4_ = temp;
+ }
+ }
+
+ // Add a vector to the quad, offseting each point in the quad by the vector.
+ void operator+=(const Vector2dF& rhs);
+ // Subtract a vector from the quad, offseting each point in the quad by the
+ // inverse of the vector.
+ void operator-=(const Vector2dF& rhs);
+
+ // Scale each point in the quad by the |scale| factor.
+ void Scale(float scale) { Scale(scale, scale); }
+
+ // Scale each point in the quad by the scale factors along each axis.
+ void Scale(float x_scale, float y_scale);
+
+ // Returns a string representation of quad.
+ std::string ToString() const;
+
+ private:
+ PointF p1_;
+ PointF p2_;
+ PointF p3_;
+ PointF p4_;
+};
+
+inline bool operator==(const QuadF& lhs, const QuadF& rhs) {
+ return
+ lhs.p1() == rhs.p1() && lhs.p2() == rhs.p2() &&
+ lhs.p3() == rhs.p3() && lhs.p4() == rhs.p4();
+}
+
+inline bool operator!=(const QuadF& lhs, const QuadF& rhs) {
+ return !(lhs == rhs);
+}
+
+// Add a vector to a quad, offseting each point in the quad by the vector.
+GFX_EXPORT QuadF operator+(const QuadF& lhs, const Vector2dF& rhs);
+// Subtract a vector from a quad, offseting each point in the quad by the
+// inverse of the vector.
+GFX_EXPORT QuadF operator-(const QuadF& lhs, const Vector2dF& rhs);
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const QuadF& quad, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_QUAD_F_H_
diff --git a/ui/gfx/geometry/quad_unittest.cc b/ui/gfx/geometry/quad_unittest.cc
new file mode 100644
index 0000000000..2731583aab
--- /dev/null
+++ b/ui/gfx/geometry/quad_unittest.cc
@@ -0,0 +1,361 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/quad_f.h"
+#include "ui/gfx/geometry/rect_f.h"
+
+namespace gfx {
+
+TEST(QuadTest, Construction) {
+ // Verify constructors.
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ PointF e;
+ QuadF q1;
+ QuadF q2(e, e, e, e);
+ QuadF q3(a, b, c, d);
+ QuadF q4(BoundingRect(a, c));
+ EXPECT_EQ(q1, q2);
+ EXPECT_EQ(q3, q4);
+
+ // Verify getters.
+ EXPECT_EQ(q3.p1(), a);
+ EXPECT_EQ(q3.p2(), b);
+ EXPECT_EQ(q3.p3(), c);
+ EXPECT_EQ(q3.p4(), d);
+
+ // Verify setters.
+ q3.set_p1(b);
+ q3.set_p2(c);
+ q3.set_p3(d);
+ q3.set_p4(a);
+ EXPECT_EQ(q3.p1(), b);
+ EXPECT_EQ(q3.p2(), c);
+ EXPECT_EQ(q3.p3(), d);
+ EXPECT_EQ(q3.p4(), a);
+
+ // Verify operator=(Rect)
+ EXPECT_NE(q1, q4);
+ q1 = BoundingRect(a, c);
+ EXPECT_EQ(q1, q4);
+
+ // Verify operator=(Quad)
+ EXPECT_NE(q1, q3);
+ q1 = q3;
+ EXPECT_EQ(q1, q3);
+}
+
+TEST(QuadTest, AddingVectors) {
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ Vector2dF v(3.5f, -2.5f);
+
+ QuadF q1(a, b, c, d);
+ QuadF added = q1 + v;
+ q1 += v;
+ QuadF expected1(PointF(4.5f, -1.5f),
+ PointF(5.5f, -1.5f),
+ PointF(5.5f, -0.5f),
+ PointF(4.5f, -0.5f));
+ EXPECT_EQ(expected1, added);
+ EXPECT_EQ(expected1, q1);
+
+ QuadF q2(a, b, c, d);
+ QuadF subtracted = q2 - v;
+ q2 -= v;
+ QuadF expected2(PointF(-2.5f, 3.5f),
+ PointF(-1.5f, 3.5f),
+ PointF(-1.5f, 4.5f),
+ PointF(-2.5f, 4.5f));
+ EXPECT_EQ(expected2, subtracted);
+ EXPECT_EQ(expected2, q2);
+
+ QuadF q3(a, b, c, d);
+ q3 += v;
+ q3 -= v;
+ EXPECT_EQ(QuadF(a, b, c, d), q3);
+ EXPECT_EQ(q3, (q3 + v - v));
+}
+
+TEST(QuadTest, IsRectilinear) {
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ Vector2dF v(3.5f, -2.5f);
+
+ EXPECT_TRUE(QuadF().IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c, d) + v).IsRectilinear());
+
+ float epsilon = std::numeric_limits<float>::epsilon();
+ PointF a2(1 + epsilon / 2, 1 + epsilon / 2);
+ PointF b2(2 + epsilon / 2, 1 + epsilon / 2);
+ PointF c2(2 + epsilon / 2, 2 + epsilon / 2);
+ PointF d2(1 + epsilon / 2, 2 + epsilon / 2);
+ EXPECT_TRUE(QuadF(a2, b, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a2, b, c, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b2, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b2, c, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c2, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c2, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c, d2).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c, d2) + v).IsRectilinear());
+
+ struct {
+ PointF a_off, b_off, c_off, d_off;
+ } tests[] = {
+ {
+ PointF(1, 1.00001f),
+ PointF(2, 1.00001f),
+ PointF(2, 2.00001f),
+ PointF(1, 2.00001f)
+ },
+ {
+ PointF(1.00001f, 1),
+ PointF(2.00001f, 1),
+ PointF(2.00001f, 2),
+ PointF(1.00001f, 2)
+ },
+ {
+ PointF(1.00001f, 1.00001f),
+ PointF(2.00001f, 1.00001f),
+ PointF(2.00001f, 2.00001f),
+ PointF(1.00001f, 2.00001f)
+ },
+ {
+ PointF(1, 0.99999f),
+ PointF(2, 0.99999f),
+ PointF(2, 1.99999f),
+ PointF(1, 1.99999f)
+ },
+ {
+ PointF(0.99999f, 1),
+ PointF(1.99999f, 1),
+ PointF(1.99999f, 2),
+ PointF(0.99999f, 2)
+ },
+ {
+ PointF(0.99999f, 0.99999f),
+ PointF(1.99999f, 0.99999f),
+ PointF(1.99999f, 1.99999f),
+ PointF(0.99999f, 1.99999f)
+ }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ PointF a_off = tests[i].a_off;
+ PointF b_off = tests[i].b_off;
+ PointF c_off = tests[i].c_off;
+ PointF d_off = tests[i].d_off;
+
+ EXPECT_FALSE(QuadF(a_off, b, c, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b, c_off, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c_off, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c_off, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c_off, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b, c_off, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c_off, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b_off, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b_off, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b_off, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b_off, c_off, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a_off, b_off, c_off, d_off).IsRectilinear());
+ EXPECT_TRUE((QuadF(a_off, b_off, c_off, d_off) + v).IsRectilinear());
+ }
+}
+
+TEST(QuadTest, IsCounterClockwise) {
+ PointF a1(1, 1);
+ PointF b1(2, 1);
+ PointF c1(2, 2);
+ PointF d1(1, 2);
+ EXPECT_FALSE(QuadF(a1, b1, c1, d1).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b1, c1, d1, a1).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a1, d1, c1, b1).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c1, b1, a1, d1).IsCounterClockwise());
+
+ // Slightly more complicated quads should work just as easily.
+ PointF a2(1.3f, 1.4f);
+ PointF b2(-0.7f, 4.9f);
+ PointF c2(1.8f, 6.2f);
+ PointF d2(2.1f, 1.6f);
+ EXPECT_TRUE(QuadF(a2, b2, c2, d2).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(b2, c2, d2, a2).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(a2, d2, c2, b2).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(c2, b2, a2, d2).IsCounterClockwise());
+
+ // Quads with 3 collinear points should work correctly, too.
+ PointF a3(0, 0);
+ PointF b3(1, 0);
+ PointF c3(2, 0);
+ PointF d3(1, 1);
+ EXPECT_FALSE(QuadF(a3, b3, c3, d3).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b3, c3, d3, a3).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a3, d3, c3, b3).IsCounterClockwise());
+ // The next expectation in particular would fail for an implementation
+ // that incorrectly uses only a cross product of the first 3 vertices.
+ EXPECT_TRUE(QuadF(c3, b3, a3, d3).IsCounterClockwise());
+
+ // Non-convex quads should work correctly, too.
+ PointF a4(0, 0);
+ PointF b4(1, 1);
+ PointF c4(2, 0);
+ PointF d4(1, 3);
+ EXPECT_FALSE(QuadF(a4, b4, c4, d4).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b4, c4, d4, a4).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a4, d4, c4, b4).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c4, b4, a4, d4).IsCounterClockwise());
+
+ // A quad with huge coordinates should not fail this check due to
+ // single-precision overflow.
+ PointF a5(1e30f, 1e30f);
+ PointF b5(1e35f, 1e30f);
+ PointF c5(1e35f, 1e35f);
+ PointF d5(1e30f, 1e35f);
+ EXPECT_FALSE(QuadF(a5, b5, c5, d5).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b5, c5, d5, a5).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a5, d5, c5, b5).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c5, b5, a5, d5).IsCounterClockwise());
+}
+
+TEST(QuadTest, BoundingBox) {
+ RectF r(3.2f, 5.4f, 7.007f, 12.01f);
+ EXPECT_EQ(r, QuadF(r).BoundingBox());
+
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.7f, 4.9f);
+ PointF c(1.8f, 6.2f);
+ PointF d(2.1f, 1.6f);
+ float left = -0.7f;
+ float top = 1.4f;
+ float right = 2.1f;
+ float bottom = 6.2f;
+ EXPECT_EQ(RectF(left, top, right - left, bottom - top),
+ QuadF(a, b, c, d).BoundingBox());
+}
+
+TEST(QuadTest, ContainsPoint) {
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.8f, 4.4f);
+ PointF c(1.8f, 6.1f);
+ PointF d(2.1f, 1.6f);
+
+ Vector2dF epsilon_x(2 * std::numeric_limits<float>::epsilon(), 0);
+ Vector2dF epsilon_y(0, 2 * std::numeric_limits<float>::epsilon());
+
+ Vector2dF ac_center = c - a;
+ ac_center.Scale(0.5f);
+ Vector2dF bd_center = d - b;
+ bd_center.Scale(0.5f);
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a + ac_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b + bd_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c - ac_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d - bd_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - ac_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - bd_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + ac_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + bd_center));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a + epsilon_x));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - epsilon_y));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c - epsilon_x));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + epsilon_y));
+
+ // Test a simple square.
+ PointF s1(-1, -1);
+ PointF s2(1, -1);
+ PointF s3(1, 1);
+ PointF s4(-1, 1);
+ // Top edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, -1.0f)));
+ // Bottom edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0.0f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, 1.0f)));
+ // Left edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.1f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 0.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.1f)));
+ // Right edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.1f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 0.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.1f)));
+ // Centered inside.
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0, 0)));
+ // Centered outside.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, 0)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, 0)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(0, -1.1f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(0, 1.1f)));
+}
+
+TEST(QuadTest, Scale) {
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.8f, 4.4f);
+ PointF c(1.8f, 6.1f);
+ PointF d(2.1f, 1.6f);
+ QuadF q1(a, b, c, d);
+ q1.Scale(1.5f);
+
+ PointF a_scaled = ScalePoint(a, 1.5f);
+ PointF b_scaled = ScalePoint(b, 1.5f);
+ PointF c_scaled = ScalePoint(c, 1.5f);
+ PointF d_scaled = ScalePoint(d, 1.5f);
+ EXPECT_EQ(q1, QuadF(a_scaled, b_scaled, c_scaled, d_scaled));
+
+ QuadF q2;
+ q2.Scale(1.5f);
+ EXPECT_EQ(q2, q2);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/quaternion.cc b/ui/gfx/geometry/quaternion.cc
new file mode 100644
index 0000000000..f2be00b857
--- /dev/null
+++ b/ui/gfx/geometry/quaternion.cc
@@ -0,0 +1,106 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/quaternion.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/numerics/math_constants.h"
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+namespace {
+
+const double kEpsilon = 1e-5;
+
+} // namespace
+
+Quaternion::Quaternion(const Vector3dF& axis, double theta) {
+ // Rotation angle is the product of |angle| and the magnitude of |axis|.
+ double length = axis.Length();
+ if (std::abs(length) < kEpsilon)
+ return;
+
+ Vector3dF normalized = axis;
+ normalized.Scale(1.0 / length);
+
+ theta *= 0.5;
+ double s = sin(theta);
+ x_ = normalized.x() * s;
+ y_ = normalized.y() * s;
+ z_ = normalized.z() * s;
+ w_ = cos(theta);
+}
+
+Quaternion::Quaternion(const Vector3dF& from, const Vector3dF& to) {
+ double dot = gfx::DotProduct(from, to);
+ double norm = sqrt(from.LengthSquared() * to.LengthSquared());
+ double real = norm + dot;
+ gfx::Vector3dF axis;
+ if (real < kEpsilon * norm) {
+ real = 0.0f;
+ axis = std::abs(from.x()) > std::abs(from.z())
+ ? gfx::Vector3dF{-from.y(), from.x(), 0.0}
+ : gfx::Vector3dF{0.0, -from.z(), from.y()};
+ } else {
+ axis = gfx::CrossProduct(from, to);
+ }
+ x_ = axis.x();
+ y_ = axis.y();
+ z_ = axis.z();
+ w_ = real;
+ *this = this->Normalized();
+}
+
+// Taken from http://www.w3.org/TR/css3-transforms/.
+Quaternion Quaternion::Slerp(const Quaternion& q, double t) const {
+ double dot = x_ * q.x_ + y_ * q.y_ + z_ * q.z_ + w_ * q.w_;
+
+ // Clamp dot to -1.0 <= dot <= 1.0.
+ dot = std::min(std::max(dot, -1.0), 1.0);
+
+ // Quaternions are facing the same direction.
+ if (std::abs(dot - 1.0) < kEpsilon || std::abs(dot + 1.0) < kEpsilon)
+ return *this;
+
+ double denom = std::sqrt(1.0 - dot * dot);
+ double theta = std::acos(dot);
+ double w = std::sin(t * theta) * (1.0 / denom);
+
+ double s1 = std::cos(t * theta) - dot * w;
+ double s2 = w;
+
+ return (s1 * *this) + (s2 * q);
+}
+
+Quaternion Quaternion::Lerp(const Quaternion& q, double t) const {
+ return (((1.0 - t) * *this) + (t * q)).Normalized();
+}
+
+double Quaternion::Length() const {
+ return x_ * x_ + y_ * y_ + z_ * z_ + w_ * w_;
+}
+
+Quaternion Quaternion::Normalized() const {
+ double length = Length();
+ if (length < kEpsilon)
+ return *this;
+ return *this / sqrt(length);
+}
+
+std::string Quaternion::ToString() const {
+ // q = (con(abs(v_theta)/2), v_theta/abs(v_theta) * sin(abs(v_theta)/2))
+ float abs_theta = acos(w_) * 2;
+ float scale = 1. / sin(abs_theta * .5);
+ gfx::Vector3dF v(x_, y_, z_);
+ v.Scale(scale);
+ return base::StringPrintf("[%f %f %f %f], v:", x_, y_, z_, w_) +
+ v.ToString() +
+ base::StringPrintf(", θ:%fπ", abs_theta / base::kPiFloat);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/quaternion.h b/ui/gfx/geometry/quaternion.h
new file mode 100644
index 0000000000..7f65b796e6
--- /dev/null
+++ b/ui/gfx/geometry/quaternion.h
@@ -0,0 +1,93 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_QUATERNION_
+#define UI_GFX_GEOMETRY_QUATERNION_
+
+#include <string>
+
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+class Vector3dF;
+
+class GFX_EXPORT Quaternion {
+ public:
+ constexpr Quaternion() = default;
+ constexpr Quaternion(double x, double y, double z, double w)
+ : x_(x), y_(y), z_(z), w_(w) {}
+ Quaternion(const Vector3dF& axis, double angle);
+
+ // Constructs a quaternion representing a rotation between |from| and |to|.
+ Quaternion(const Vector3dF& from, const Vector3dF& to);
+
+ constexpr double x() const { return x_; }
+ void set_x(double x) { x_ = x; }
+
+ constexpr double y() const { return y_; }
+ void set_y(double y) { y_ = y; }
+
+ constexpr double z() const { return z_; }
+ void set_z(double z) { z_ = z; }
+
+ constexpr double w() const { return w_; }
+ void set_w(double w) { w_ = w; }
+
+ Quaternion operator+(const Quaternion& q) const {
+ return {q.x_ + x_, q.y_ + y_, q.z_ + z_, q.w_ + w_};
+ }
+
+ Quaternion operator*(const Quaternion& q) const {
+ return {w_ * q.x_ + x_ * q.w_ + y_ * q.z_ - z_ * q.y_,
+ w_ * q.y_ - x_ * q.z_ + y_ * q.w_ + z_ * q.x_,
+ w_ * q.z_ + x_ * q.y_ - y_ * q.x_ + z_ * q.w_,
+ w_ * q.w_ - x_ * q.x_ - y_ * q.y_ - z_ * q.z_};
+ }
+
+ Quaternion inverse() const { return {-x_, -y_, -z_, w_}; }
+
+ // Blends with the given quaternion, |q|, via spherical linear interpolation.
+ // Values of |t| in the range [0, 1] will interpolate between |this| and |q|,
+ // and values outside that range will extrapolate beyond in either direction.
+ Quaternion Slerp(const Quaternion& q, double t) const;
+
+ // Blends with the given quaternion, |q|, via linear interpolation. This is
+ // rarely what you want. Use only if you know what you're doing.
+ // Values of |t| in the range [0, 1] will interpolate between |this| and |q|,
+ // and values outside that range will extrapolate beyond in either direction.
+ Quaternion Lerp(const Quaternion& q, double t) const;
+
+ double Length() const;
+
+ Quaternion Normalized() const;
+
+ std::string ToString() const;
+
+ private:
+ double x_ = 0.0;
+ double y_ = 0.0;
+ double z_ = 0.0;
+ double w_ = 1.0;
+};
+
+// |s| is an arbitrary, real constant.
+inline Quaternion operator*(const Quaternion& q, double s) {
+ return Quaternion(q.x() * s, q.y() * s, q.z() * s, q.w() * s);
+}
+
+// |s| is an arbitrary, real constant.
+inline Quaternion operator*(double s, const Quaternion& q) {
+ return Quaternion(q.x() * s, q.y() * s, q.z() * s, q.w() * s);
+}
+
+// |s| is an arbitrary, real constant.
+inline Quaternion operator/(const Quaternion& q, double s) {
+ double inv = 1.0 / s;
+ return q * inv;
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_QUATERNION_
diff --git a/ui/gfx/geometry/quaternion_unittest.cc b/ui/gfx/geometry/quaternion_unittest.cc
new file mode 100644
index 0000000000..5c8fa9b8ae
--- /dev/null
+++ b/ui/gfx/geometry/quaternion_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define _USE_MATH_DEFINES // For VC++ to get M_PI. This has to be first.
+
+#include <cmath>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/quaternion.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+namespace {
+
+const double kEpsilon = 1e-7;
+
+void CompareQuaternions(const Quaternion& a, const Quaternion& b) {
+ EXPECT_FLOAT_EQ(a.x(), b.x());
+ EXPECT_FLOAT_EQ(a.y(), b.y());
+ EXPECT_FLOAT_EQ(a.z(), b.z());
+ EXPECT_FLOAT_EQ(a.w(), b.w());
+}
+
+} // namespace
+
+TEST(QuatTest, DefaultConstruction) {
+ CompareQuaternions(Quaternion(0, 0, 0, 1), Quaternion());
+}
+
+TEST(QuatTest, AxisAngleCommon) {
+ double radians = 0.5;
+ Quaternion q(Vector3dF(1, 0, 0), radians);
+ CompareQuaternions(
+ Quaternion(std::sin(radians / 2), 0, 0, std::cos(radians / 2)), q);
+}
+
+TEST(QuatTest, VectorToVectorRotation) {
+ Quaternion q(Vector3dF(1.0f, 0.0f, 0.0f), Vector3dF(0.0f, 1.0f, 0.0f));
+ Quaternion r(Vector3dF(0.0f, 0.0f, 1.0f), M_PI_2);
+
+ EXPECT_FLOAT_EQ(r.x(), q.x());
+ EXPECT_FLOAT_EQ(r.y(), q.y());
+ EXPECT_FLOAT_EQ(r.z(), q.z());
+ EXPECT_FLOAT_EQ(r.w(), q.w());
+}
+
+TEST(QuatTest, AxisAngleWithZeroLengthAxis) {
+ Quaternion q(Vector3dF(0, 0, 0), 0.5);
+ // If the axis of zero length, we should assume the default values.
+ CompareQuaternions(q, Quaternion());
+}
+
+TEST(QuatTest, Addition) {
+ double values[] = {0, 1, 100};
+ for (size_t i = 0; i < arraysize(values); ++i) {
+ float t = values[i];
+ Quaternion a(t, 2 * t, 3 * t, 4 * t);
+ Quaternion b(5 * t, 4 * t, 3 * t, 2 * t);
+ Quaternion sum = a + b;
+ CompareQuaternions(Quaternion(t, t, t, t) * 6, sum);
+ }
+}
+
+TEST(QuatTest, Multiplication) {
+ struct {
+ Quaternion a;
+ Quaternion b;
+ Quaternion expected;
+ } cases[] = {
+ {Quaternion(1, 0, 0, 0), Quaternion(1, 0, 0, 0), Quaternion(0, 0, 0, -1)},
+ {Quaternion(0, 1, 0, 0), Quaternion(0, 1, 0, 0), Quaternion(0, 0, 0, -1)},
+ {Quaternion(0, 0, 1, 0), Quaternion(0, 0, 1, 0), Quaternion(0, 0, 0, -1)},
+ {Quaternion(0, 0, 0, 1), Quaternion(0, 0, 0, 1), Quaternion(0, 0, 0, 1)},
+ {Quaternion(1, 2, 3, 4), Quaternion(5, 6, 7, 8),
+ Quaternion(24, 48, 48, -6)},
+ {Quaternion(5, 6, 7, 8), Quaternion(1, 2, 3, 4),
+ Quaternion(32, 32, 56, -6)},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ Quaternion product = cases[i].a * cases[i].b;
+ CompareQuaternions(cases[i].expected, product);
+ }
+}
+
+TEST(QuatTest, Scaling) {
+ double values[] = {0, 10, 100};
+ for (size_t i = 0; i < arraysize(values); ++i) {
+ double s = values[i];
+ Quaternion q(1, 2, 3, 4);
+ Quaternion expected(s, 2 * s, 3 * s, 4 * s);
+ CompareQuaternions(expected, q * s);
+ CompareQuaternions(expected, s * q);
+ if (s > 0)
+ CompareQuaternions(expected, q / (1 / s));
+ }
+}
+
+TEST(QuatTest, Normalization) {
+ Quaternion q(1, -1, 1, -1);
+ EXPECT_NEAR(q.Length(), 4, kEpsilon);
+
+ q = q.Normalized();
+
+ EXPECT_NEAR(q.Length(), 1, kEpsilon);
+ EXPECT_NEAR(q.x(), 0.5, kEpsilon);
+ EXPECT_NEAR(q.y(), -0.5, kEpsilon);
+ EXPECT_NEAR(q.z(), 0.5, kEpsilon);
+ EXPECT_NEAR(q.w(), -0.5, kEpsilon);
+}
+
+TEST(QuatTest, Lerp) {
+ for (size_t i = 1; i < 100; ++i) {
+ Quaternion a(0, 0, 0, 0);
+ Quaternion b(1, 2, 3, 4);
+ float t = static_cast<float>(i) / 100.0f;
+ Quaternion interpolated = a.Lerp(b, t);
+ double s = 1.0 / sqrt(30.0);
+ CompareQuaternions(Quaternion(1, 2, 3, 4) * s, interpolated);
+ }
+
+ Quaternion a(4, 3, 2, 1);
+ Quaternion b(1, 2, 3, 4);
+ CompareQuaternions(a.Normalized(), a.Lerp(b, 0));
+ CompareQuaternions(b.Normalized(), a.Lerp(b, 1));
+ CompareQuaternions(Quaternion(1, 1, 1, 1).Normalized(), a.Lerp(b, 0.5));
+}
+
+TEST(QuatTest, Slerp) {
+ Vector3dF axis(1, 1, 1);
+ double start_radians = -0.5;
+ double stop_radians = 0.5;
+ Quaternion start(axis, start_radians);
+ Quaternion stop(axis, stop_radians);
+
+ for (size_t i = 0; i < 100; ++i) {
+ float t = static_cast<float>(i) / 100.0f;
+ double radians = (1.0 - t) * start_radians + t * stop_radians;
+ Quaternion expected(axis, radians);
+ Quaternion interpolated = start.Slerp(stop, t);
+ EXPECT_NEAR(expected.x(), interpolated.x(), kEpsilon);
+ EXPECT_NEAR(expected.y(), interpolated.y(), kEpsilon);
+ EXPECT_NEAR(expected.z(), interpolated.z(), kEpsilon);
+ EXPECT_NEAR(expected.w(), interpolated.w(), kEpsilon);
+ }
+}
+
+TEST(QuatTest, SlerpOppositeAngles) {
+ Vector3dF axis(1, 1, 1);
+ double start_radians = -M_PI_2;
+ double stop_radians = M_PI_2;
+ Quaternion start(axis, start_radians);
+ Quaternion stop(axis, stop_radians);
+
+ // When quaternions are pointed in the fully opposite direction, this is
+ // ambiguous, so we rotate as per https://www.w3.org/TR/css-transforms-1/
+ Quaternion expected(axis, 0);
+
+ Quaternion interpolated = start.Slerp(stop, 0.5f);
+ EXPECT_NEAR(expected.x(), interpolated.x(), kEpsilon);
+ EXPECT_NEAR(expected.y(), interpolated.y(), kEpsilon);
+ EXPECT_NEAR(expected.z(), interpolated.z(), kEpsilon);
+ EXPECT_NEAR(expected.w(), interpolated.w(), kEpsilon);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/rect_conversions.cc b/ui/gfx/geometry/rect_conversions.cc
new file mode 100644
index 0000000000..3a5b2dcead
--- /dev/null
+++ b/ui/gfx/geometry/rect_conversions.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/rect_conversions.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+#include "ui/gfx/geometry/safe_integer_conversions.h"
+
+namespace gfx {
+
+Rect ToEnclosingRect(const RectF& r) {
+ int left = ToFlooredInt(r.x());
+ int right = r.width() ? ToCeiledInt(r.right()) : left;
+ int top = ToFlooredInt(r.y());
+ int bottom = r.height() ? ToCeiledInt(r.bottom()) : top;
+
+ Rect result;
+ result.SetByBounds(left, top, right, bottom);
+ return result;
+}
+
+Rect ToEnclosedRect(const RectF& rect) {
+ Rect result;
+ result.SetByBounds(ToCeiledInt(rect.x()), ToCeiledInt(rect.y()),
+ ToFlooredInt(rect.right()), ToFlooredInt(rect.bottom()));
+ return result;
+}
+
+Rect ToNearestRect(const RectF& rect) {
+ float float_min_x = rect.x();
+ float float_min_y = rect.y();
+ float float_max_x = rect.right();
+ float float_max_y = rect.bottom();
+
+ int min_x = ToRoundedInt(float_min_x);
+ int min_y = ToRoundedInt(float_min_y);
+ int max_x = ToRoundedInt(float_max_x);
+ int max_y = ToRoundedInt(float_max_y);
+
+ // If these DCHECKs fail, you're using the wrong method, consider using
+ // ToEnclosingRect or ToEnclosedRect instead.
+ DCHECK(std::abs(min_x - float_min_x) < 0.01f);
+ DCHECK(std::abs(min_y - float_min_y) < 0.01f);
+ DCHECK(std::abs(max_x - float_max_x) < 0.01f);
+ DCHECK(std::abs(max_y - float_max_y) < 0.01f);
+
+ Rect result;
+ result.SetByBounds(min_x, min_y, max_x, max_y);
+
+ return result;
+}
+
+bool IsNearestRectWithinDistance(const gfx::RectF& rect, float distance) {
+ float float_min_x = rect.x();
+ float float_min_y = rect.y();
+ float float_max_x = rect.right();
+ float float_max_y = rect.bottom();
+
+ int min_x = ToRoundedInt(float_min_x);
+ int min_y = ToRoundedInt(float_min_y);
+ int max_x = ToRoundedInt(float_max_x);
+ int max_y = ToRoundedInt(float_max_y);
+
+ return
+ (std::abs(min_x - float_min_x) < distance) &&
+ (std::abs(min_y - float_min_y) < distance) &&
+ (std::abs(max_x - float_max_x) < distance) &&
+ (std::abs(max_y - float_max_y) < distance);
+}
+
+Rect ToFlooredRectDeprecated(const RectF& rect) {
+ return Rect(ToFlooredInt(rect.x()),
+ ToFlooredInt(rect.y()),
+ ToFlooredInt(rect.width()),
+ ToFlooredInt(rect.height()));
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/rect_conversions.h b/ui/gfx/geometry/rect_conversions.h
new file mode 100644
index 0000000000..617074abee
--- /dev/null
+++ b/ui/gfx/geometry/rect_conversions.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_
+#define UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_
+
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_f.h"
+
+namespace gfx {
+
+// Returns the smallest Rect that encloses the given RectF.
+GFX_EXPORT Rect ToEnclosingRect(const RectF& rect);
+
+// Returns the largest Rect that is enclosed by the given RectF.
+GFX_EXPORT Rect ToEnclosedRect(const RectF& rect);
+
+// Returns the Rect after snapping the corners of the RectF to an integer grid.
+// This should only be used when the RectF you provide is expected to be an
+// integer rect with floating point error. If it is an arbitrary RectF, then
+// you should use a different method.
+GFX_EXPORT Rect ToNearestRect(const RectF& rect);
+
+// Returns true if the Rect produced after snapping the corners of the RectF
+// to an integer grid is withing |distance|.
+GFX_EXPORT bool IsNearestRectWithinDistance(
+ const gfx::RectF& rect, float distance);
+
+// Returns a Rect obtained by flooring the values of the given RectF.
+// Please prefer the previous two functions in new code.
+GFX_EXPORT Rect ToFlooredRectDeprecated(const RectF& rect);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_
diff --git a/ui/gfx/geometry/rect_unittest.cc b/ui/gfx/geometry/rect_unittest.cc
new file mode 100644
index 0000000000..aaa533bfcd
--- /dev/null
+++ b/ui/gfx/geometry/rect_unittest.cc
@@ -0,0 +1,1141 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <limits>
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/test/gfx_util.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace gfx {
+
+TEST(RectTest, Contains) {
+ static const struct ContainsCase {
+ int rect_x;
+ int rect_y;
+ int rect_width;
+ int rect_height;
+ int point_x;
+ int point_y;
+ bool contained;
+ } contains_cases[] = {
+ {0, 0, 10, 10, 0, 0, true},
+ {0, 0, 10, 10, 5, 5, true},
+ {0, 0, 10, 10, 9, 9, true},
+ {0, 0, 10, 10, 5, 10, false},
+ {0, 0, 10, 10, 10, 5, false},
+ {0, 0, 10, 10, -1, -1, false},
+ {0, 0, 10, 10, 50, 50, false},
+ #if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+ {0, 0, -10, -10, 0, 0, false},
+ #endif
+ };
+ for (size_t i = 0; i < arraysize(contains_cases); ++i) {
+ const ContainsCase& value = contains_cases[i];
+ Rect rect(value.rect_x, value.rect_y, value.rect_width, value.rect_height);
+ EXPECT_EQ(value.contained, rect.Contains(value.point_x, value.point_y));
+ }
+}
+
+TEST(RectTest, Intersects) {
+ static const struct {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ bool intersects;
+ } tests[] = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, false },
+ { 0, 0, 0, 0, -10, -10, 20, 20, false },
+ { -10, 0, 0, 20, 0, -10, 20, 0, false },
+ { 0, 0, 10, 10, 0, 0, 10, 10, true },
+ { 0, 0, 10, 10, 10, 10, 10, 10, false },
+ { 10, 10, 10, 10, 0, 0, 10, 10, false },
+ { 10, 10, 10, 10, 5, 5, 10, 10, true },
+ { 10, 10, 10, 10, 15, 15, 10, 10, true },
+ { 10, 10, 10, 10, 20, 15, 10, 10, false },
+ { 10, 10, 10, 10, 21, 15, 10, 10, false }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ EXPECT_EQ(tests[i].intersects, r1.Intersects(r2));
+ EXPECT_EQ(tests[i].intersects, r2.Intersects(r1));
+ }
+}
+
+TEST(RectTest, Intersect) {
+ static const struct {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: the union of rects 1 and 2
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 0, 0, // zeros
+ 0, 0, 0, 0,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4, // equal
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 0, 0, 4, 4, // neighboring
+ 4, 4, 4, 4,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4, // overlapping corners
+ 2, 2, 4, 4,
+ 2, 2, 2, 2 },
+ { 0, 0, 4, 4, // T junction
+ 3, 1, 4, 2,
+ 3, 1, 1, 2 },
+ { 3, 0, 2, 2, // gap
+ 0, 0, 2, 2,
+ 0, 0, 0, 0 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect ir = IntersectRects(r1, r2);
+ EXPECT_EQ(r3.x(), ir.x());
+ EXPECT_EQ(r3.y(), ir.y());
+ EXPECT_EQ(r3.width(), ir.width());
+ EXPECT_EQ(r3.height(), ir.height());
+ }
+}
+
+TEST(RectTest, Union) {
+ static const struct Test {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: the union of rects 1 and 2
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4,
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 0, 0, 4, 4,
+ 4, 4, 4, 4,
+ 0, 0, 8, 8 },
+ { 0, 0, 4, 4,
+ 0, 5, 4, 4,
+ 0, 0, 4, 9 },
+ { 0, 0, 2, 2,
+ 3, 3, 2, 2,
+ 0, 0, 5, 5 },
+ { 3, 3, 2, 2, // reverse r1 and r2 from previous test
+ 0, 0, 2, 2,
+ 0, 0, 5, 5 },
+ { 0, 0, 0, 0, // union with empty rect
+ 2, 2, 2, 2,
+ 2, 2, 2, 2 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect u = UnionRects(r1, r2);
+ EXPECT_EQ(r3.x(), u.x());
+ EXPECT_EQ(r3.y(), u.y());
+ EXPECT_EQ(r3.width(), u.width());
+ EXPECT_EQ(r3.height(), u.height());
+ }
+}
+
+TEST(RectTest, Equals) {
+ ASSERT_TRUE(Rect(0, 0, 0, 0) == Rect(0, 0, 0, 0));
+ ASSERT_TRUE(Rect(1, 2, 3, 4) == Rect(1, 2, 3, 4));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 0, 0, 1));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 0, 1, 0));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 1, 0, 0));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(1, 0, 0, 0));
+}
+
+TEST(RectTest, AdjustToFit) {
+ static const struct Test {
+ int x1; // source
+ int y1;
+ int w1;
+ int h1;
+ int x2; // target
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: results of invoking AdjustToFit
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 2, 2,
+ 0, 0, 2, 2,
+ 0, 0, 2, 2 },
+ { 2, 2, 3, 3,
+ 0, 0, 4, 4,
+ 1, 1, 3, 3 },
+ { -1, -1, 5, 5,
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 2, 2, 4, 4,
+ 0, 0, 3, 3,
+ 0, 0, 3, 3 },
+ { 2, 2, 1, 1,
+ 0, 0, 3, 3,
+ 2, 2, 1, 1 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect u = r1;
+ u.AdjustToFit(r2);
+ EXPECT_EQ(r3.x(), u.x());
+ EXPECT_EQ(r3.y(), u.y());
+ EXPECT_EQ(r3.width(), u.width());
+ EXPECT_EQ(r3.height(), u.height());
+ }
+}
+
+TEST(RectTest, Subtract) {
+ Rect result;
+
+ // Matching
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 10, 20, 20));
+ EXPECT_EQ(Rect(0, 0, 0, 0), result);
+
+ // Contains
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 30, 30));
+ EXPECT_EQ(Rect(0, 0, 0, 0), result);
+
+ // No intersection
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(30, 30, 30, 30));
+ EXPECT_EQ(Rect(10, 10, 20, 20), result);
+
+ // Not a complete intersection in either direction
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(15, 15, 20, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 20), result);
+
+ // Complete intersection in the x-direction, top edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 15, 20, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 5), result);
+
+ // Complete intersection in the x-direction, top edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 15, 30, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 5), result);
+
+ // Complete intersection in the x-direction, bottom edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 30, 20));
+ EXPECT_EQ(Rect(10, 25, 20, 5), result);
+
+ // Complete intersection in the x-direction, none of the edges is fully
+ // covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 15, 30, 1));
+ EXPECT_EQ(Rect(10, 10, 20, 20), result);
+
+ // Complete intersection in the y-direction, left edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 10, 10, 30));
+ EXPECT_EQ(Rect(20, 10, 10, 20), result);
+
+ // Complete intersection in the y-direction, left edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 20, 30));
+ EXPECT_EQ(Rect(25, 10, 5, 20), result);
+
+ // Complete intersection in the y-direction, right edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(20, 5, 20, 30));
+ EXPECT_EQ(Rect(10, 10, 10, 20), result);
+
+ // Complete intersection in the y-direction, none of the edges is fully
+ // covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(15, 5, 1, 30));
+ EXPECT_EQ(Rect(10, 10, 20, 20), result);
+}
+
+TEST(RectTest, IsEmpty) {
+ EXPECT_TRUE(Rect(0, 0, 0, 0).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 0).size().IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 10, 0).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 10, 0).size().IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 10).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 10).size().IsEmpty());
+ EXPECT_FALSE(Rect(0, 0, 10, 10).IsEmpty());
+ EXPECT_FALSE(Rect(0, 0, 10, 10).size().IsEmpty());
+}
+
+TEST(RectTest, SplitVertically) {
+ Rect left_half, right_half;
+
+ // Splitting when origin is (0, 0).
+ Rect(0, 0, 20, 20).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(0, 0, 10, 20));
+ EXPECT_TRUE(right_half == Rect(10, 0, 10, 20));
+
+ // Splitting when origin is arbitrary.
+ Rect(10, 10, 20, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 10, 10));
+ EXPECT_TRUE(right_half == Rect(20, 10, 10, 10));
+
+ // Splitting a rectangle of zero width.
+ Rect(10, 10, 0, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 0, 10));
+ EXPECT_TRUE(right_half == Rect(10, 10, 0, 10));
+
+ // Splitting a rectangle of odd width.
+ Rect(10, 10, 5, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 2, 10));
+ EXPECT_TRUE(right_half == Rect(12, 10, 3, 10));
+}
+
+TEST(RectTest, CenterPoint) {
+ Point center;
+
+ // When origin is (0, 0).
+ center = Rect(0, 0, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(10, 10));
+
+ // When origin is even.
+ center = Rect(10, 10, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 20));
+
+ // When origin is odd.
+ center = Rect(11, 11, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(21, 21));
+
+ // When 0 width or height.
+ center = Rect(10, 10, 0, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(10, 20));
+ center = Rect(10, 10, 20, 0).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 10));
+
+ // When an odd size.
+ center = Rect(10, 10, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 20));
+
+ // When an odd size and position.
+ center = Rect(11, 11, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == Point(21, 21));
+}
+
+TEST(RectTest, CenterPointF) {
+ PointF center;
+
+ // When origin is (0, 0).
+ center = RectF(0, 0, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(10, 10));
+
+ // When origin is even.
+ center = RectF(10, 10, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(20, 20));
+
+ // When origin is odd.
+ center = RectF(11, 11, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(21, 21));
+
+ // When 0 width or height.
+ center = RectF(10, 10, 0, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(10, 20));
+ center = RectF(10, 10, 20, 0).CenterPoint();
+ EXPECT_TRUE(center == PointF(20, 10));
+
+ // When an odd size.
+ center = RectF(10, 10, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == PointF(20.5f, 20.5f));
+
+ // When an odd size and position.
+ center = RectF(11, 11, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == PointF(21.5f, 21.5f));
+}
+
+TEST(RectTest, SharesEdgeWith) {
+ Rect r(2, 3, 4, 5);
+
+ // Must be non-overlapping
+ EXPECT_FALSE(r.SharesEdgeWith(r));
+
+ Rect just_above(2, 1, 4, 2);
+ Rect just_below(2, 8, 4, 2);
+ Rect just_left(0, 3, 2, 5);
+ Rect just_right(6, 3, 2, 5);
+
+ EXPECT_TRUE(r.SharesEdgeWith(just_above));
+ EXPECT_TRUE(r.SharesEdgeWith(just_below));
+ EXPECT_TRUE(r.SharesEdgeWith(just_left));
+ EXPECT_TRUE(r.SharesEdgeWith(just_right));
+
+ // Wrong placement
+ Rect same_height_no_edge(0, 0, 1, 5);
+ Rect same_width_no_edge(0, 0, 4, 1);
+
+ EXPECT_FALSE(r.SharesEdgeWith(same_height_no_edge));
+ EXPECT_FALSE(r.SharesEdgeWith(same_width_no_edge));
+
+ Rect just_above_no_edge(2, 1, 5, 2); // too wide
+ Rect just_below_no_edge(2, 8, 3, 2); // too narrow
+ Rect just_left_no_edge(0, 3, 2, 6); // too tall
+ Rect just_right_no_edge(6, 3, 2, 4); // too short
+
+ EXPECT_FALSE(r.SharesEdgeWith(just_above_no_edge));
+ EXPECT_FALSE(r.SharesEdgeWith(just_below_no_edge));
+ EXPECT_FALSE(r.SharesEdgeWith(just_left_no_edge));
+ EXPECT_FALSE(r.SharesEdgeWith(just_right_no_edge));
+}
+
+// Similar to EXPECT_FLOAT_EQ, but lets NaN equal NaN
+#define EXPECT_FLOAT_AND_NAN_EQ(a, b) \
+ { if (a == a || b == b) { EXPECT_FLOAT_EQ(a, b); } }
+
+TEST(RectTest, ScaleRect) {
+ static const struct Test {
+ int x1; // source
+ int y1;
+ int w1;
+ int h1;
+ float scale;
+ float x2; // target
+ float y2;
+ float w2;
+ float h2;
+ } tests[] = {
+ { 3, 3, 3, 3,
+ 1.5f,
+ 4.5f, 4.5f, 4.5f, 4.5f },
+ { 3, 3, 3, 3,
+ 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f },
+ { 3, 3, 3, 3,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN() },
+ { 3, 3, 3, 3,
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max() }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ RectF r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+
+ RectF scaled = ScaleRect(r1, tests[i].scale);
+ EXPECT_FLOAT_AND_NAN_EQ(r2.x(), scaled.x());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.y(), scaled.y());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.width(), scaled.width());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.height(), scaled.height());
+ }
+}
+
+TEST(RectTest, ToEnclosedRect) {
+ static const int max_int = std::numeric_limits<int>::max();
+ static const int min_int = std::numeric_limits<int>::min();
+ static const float max_float = std::numeric_limits<float>::max();
+ static const float max_int_f = static_cast<float>(max_int);
+ static const float min_int_f = static_cast<float>(min_int);
+
+ static const struct Test {
+ struct {
+ float x;
+ float y;
+ float width;
+ float height;
+ } in;
+ struct {
+ int x;
+ int y;
+ int width;
+ int height;
+ } expected;
+ } tests[] = {
+ {{0.0f, 0.0f, 0.0f, 0.0f}, {0, 0, 0, 0}},
+ {{-1.5f, -1.5f, 3.0f, 3.0f}, {-1, -1, 2, 2}},
+ {{-1.5f, -1.5f, 3.5f, 3.5f}, {-1, -1, 3, 3}},
+ {{max_float, max_float, 2.0f, 2.0f}, {max_int, max_int, 0, 0}},
+ {{0.0f, 0.0f, max_float, max_float}, {0, 0, max_int, max_int}},
+ {{20000.5f, 20000.5f, 0.5f, 0.5f}, {20001, 20001, 0, 0}},
+ {{max_int_f, max_int_f, max_int_f, max_int_f}, {max_int, max_int, 0, 0}}};
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ RectF source(tests[i].in.x, tests[i].in.y, tests[i].in.width,
+ tests[i].in.height);
+ Rect enclosed = ToEnclosedRect(source);
+
+ EXPECT_EQ(tests[i].expected.x, enclosed.x());
+ EXPECT_EQ(tests[i].expected.y, enclosed.y());
+ EXPECT_EQ(tests[i].expected.width, enclosed.width());
+ EXPECT_EQ(tests[i].expected.height, enclosed.height());
+ }
+
+ {
+ RectF source(min_int_f, min_int_f, max_int_f * 3.f, max_int_f * 3.f);
+ Rect enclosed = ToEnclosedRect(source);
+
+ // That rect can't be represented, but it should be big.
+ EXPECT_EQ(max_int, enclosed.width());
+ EXPECT_EQ(max_int, enclosed.height());
+ // It should include some axis near the global origin.
+ EXPECT_GT(1, enclosed.x());
+ EXPECT_GT(1, enclosed.y());
+ // And it should not cause computation issues for itself.
+ EXPECT_LT(0, enclosed.right());
+ EXPECT_LT(0, enclosed.bottom());
+ }
+}
+
+TEST(RectTest, ToEnclosingRect) {
+ static const int max_int = std::numeric_limits<int>::max();
+ static const int min_int = std::numeric_limits<int>::min();
+ static const float max_float = std::numeric_limits<float>::max();
+ static const float epsilon_float = std::numeric_limits<float>::epsilon();
+ static const float max_int_f = static_cast<float>(max_int);
+ static const float min_int_f = static_cast<float>(min_int);
+ static const struct Test {
+ struct {
+ float x;
+ float y;
+ float width;
+ float height;
+ } in;
+ struct {
+ int x;
+ int y;
+ int width;
+ int height;
+ } expected;
+ } tests[] = {
+ {{0.0f, 0.0f, 0.0f, 0.0f}, {0, 0, 0, 0}},
+ {{5.5f, 5.5f, 0.0f, 0.0f}, {5, 5, 0, 0}},
+ {{3.5f, 2.5f, epsilon_float, -0.0f}, {3, 2, 0, 0}},
+ {{3.5f, 2.5f, 0.f, 0.001f}, {3, 2, 0, 1}},
+ {{-1.5f, -1.5f, 3.0f, 3.0f}, {-2, -2, 4, 4}},
+ {{-1.5f, -1.5f, 3.5f, 3.5f}, {-2, -2, 4, 4}},
+ {{max_float, max_float, 2.0f, 2.0f}, {max_int, max_int, 0, 0}},
+ {{0.0f, 0.0f, max_float, max_float}, {0, 0, max_int, max_int}},
+ {{20000.5f, 20000.5f, 0.5f, 0.5f}, {20000, 20000, 1, 1}},
+ {{max_int_f, max_int_f, max_int_f, max_int_f}, {max_int, max_int, 0, 0}},
+ {{-0.5f, -0.5f, 22777712.f, 1.f}, {-1, -1, 22777713, 2}}};
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ RectF source(tests[i].in.x, tests[i].in.y, tests[i].in.width,
+ tests[i].in.height);
+
+ Rect enclosing = ToEnclosingRect(source);
+ EXPECT_EQ(tests[i].expected.x, enclosing.x());
+ EXPECT_EQ(tests[i].expected.y, enclosing.y());
+ EXPECT_EQ(tests[i].expected.width, enclosing.width());
+ EXPECT_EQ(tests[i].expected.height, enclosing.height());
+ }
+
+ {
+ RectF source(min_int_f, min_int_f, max_int_f * 3.f, max_int_f * 3.f);
+ Rect enclosing = ToEnclosingRect(source);
+
+ // That rect can't be represented, but it should be big.
+ EXPECT_EQ(max_int, enclosing.width());
+ EXPECT_EQ(max_int, enclosing.height());
+ // It should include some axis near the global origin.
+ EXPECT_GT(1, enclosing.x());
+ EXPECT_GT(1, enclosing.y());
+ // And it should cause computation issues for itself.
+ EXPECT_LT(0, enclosing.right());
+ EXPECT_LT(0, enclosing.bottom());
+ }
+}
+
+TEST(RectTest, ToNearestRect) {
+ Rect rect;
+ EXPECT_EQ(rect, ToNearestRect(RectF(rect)));
+
+ rect = Rect(-1, -1, 3, 3);
+ EXPECT_EQ(rect, ToNearestRect(RectF(rect)));
+
+ RectF rectf(-1.00001f, -0.999999f, 3.0000001f, 2.999999f);
+ EXPECT_EQ(rect, ToNearestRect(rectf));
+}
+
+TEST(RectTest, ToFlooredRect) {
+ static const struct Test {
+ float x1; // source
+ float y1;
+ float w1;
+ float h1;
+ int x2; // target
+ int y2;
+ int w2;
+ int h2;
+ } tests [] = {
+ { 0.0f, 0.0f, 0.0f, 0.0f,
+ 0, 0, 0, 0 },
+ { -1.5f, -1.5f, 3.0f, 3.0f,
+ -2, -2, 3, 3 },
+ { -1.5f, -1.5f, 3.5f, 3.5f,
+ -2, -2, 3, 3 },
+ { 20000.5f, 20000.5f, 0.5f, 0.5f,
+ 20000, 20000, 0, 0 },
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+
+ Rect floored = ToFlooredRectDeprecated(r1);
+ EXPECT_FLOAT_EQ(r2.x(), floored.x());
+ EXPECT_FLOAT_EQ(r2.y(), floored.y());
+ EXPECT_FLOAT_EQ(r2.width(), floored.width());
+ EXPECT_FLOAT_EQ(r2.height(), floored.height());
+ }
+}
+
+TEST(RectTest, ScaleToEnclosedRect) {
+ static const struct Test {
+ Rect input_rect;
+ float input_scale;
+ Rect expected_rect;
+ } tests[] = {
+ {
+ Rect(),
+ 5.f,
+ Rect(),
+ }, {
+ Rect(1, 1, 1, 1),
+ 5.f,
+ Rect(5, 5, 5, 5),
+ }, {
+ Rect(-1, -1, 0, 0),
+ 5.f,
+ Rect(-5, -5, 0, 0),
+ }, {
+ Rect(1, -1, 0, 1),
+ 5.f,
+ Rect(5, -5, 0, 5),
+ }, {
+ Rect(-1, 1, 1, 0),
+ 5.f,
+ Rect(-5, 5, 5, 0),
+ }, {
+ Rect(1, 2, 3, 4),
+ 1.5f,
+ Rect(2, 3, 4, 6),
+ }, {
+ Rect(-1, -2, 0, 0),
+ 1.5f,
+ Rect(-1, -3, 0, 0),
+ }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect result = ScaleToEnclosedRect(tests[i].input_rect,
+ tests[i].input_scale);
+ EXPECT_EQ(tests[i].expected_rect, result);
+ }
+}
+
+TEST(RectTest, ScaleToEnclosingRect) {
+ static const struct Test {
+ Rect input_rect;
+ float input_scale;
+ Rect expected_rect;
+ } tests[] = {
+ {
+ Rect(),
+ 5.f,
+ Rect(),
+ }, {
+ Rect(1, 1, 1, 1),
+ 5.f,
+ Rect(5, 5, 5, 5),
+ }, {
+ Rect(-1, -1, 0, 0),
+ 5.f,
+ Rect(-5, -5, 0, 0),
+ }, {
+ Rect(1, -1, 0, 1),
+ 5.f,
+ Rect(5, -5, 0, 5),
+ }, {
+ Rect(-1, 1, 1, 0),
+ 5.f,
+ Rect(-5, 5, 5, 0),
+ }, {
+ Rect(1, 2, 3, 4),
+ 1.5f,
+ Rect(1, 3, 5, 6),
+ }, {
+ Rect(-1, -2, 0, 0),
+ 1.5f,
+ Rect(-2, -3, 0, 0),
+ }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect result =
+ ScaleToEnclosingRect(tests[i].input_rect, tests[i].input_scale);
+ EXPECT_EQ(tests[i].expected_rect, result);
+ Rect result_safe =
+ ScaleToEnclosingRectSafe(tests[i].input_rect, tests[i].input_scale);
+ EXPECT_EQ(tests[i].expected_rect, result_safe);
+ }
+}
+
+#if defined(OS_WIN)
+TEST(RectTest, ConstructAndAssign) {
+ const RECT rect_1 = { 0, 0, 10, 10 };
+ const RECT rect_2 = { 0, 0, -10, -10 };
+ Rect test1(rect_1);
+ Rect test2(rect_2);
+}
+#endif
+
+TEST(RectTest, ToRectF) {
+ // Check that explicit conversion from integer to float compiles.
+ Rect a(10, 20, 30, 40);
+ RectF b(10, 20, 30, 40);
+
+ RectF c = RectF(a);
+ EXPECT_EQ(b, c);
+}
+
+TEST(RectTest, BoundingRect) {
+ struct {
+ Point a;
+ Point b;
+ Rect expected;
+ } int_tests[] = {
+ // If point B dominates A, then A should be the origin.
+ { Point(4, 6), Point(4, 6), Rect(4, 6, 0, 0) },
+ { Point(4, 6), Point(8, 6), Rect(4, 6, 4, 0) },
+ { Point(4, 6), Point(4, 9), Rect(4, 6, 0, 3) },
+ { Point(4, 6), Point(8, 9), Rect(4, 6, 4, 3) },
+ // If point A dominates B, then B should be the origin.
+ { Point(4, 6), Point(4, 6), Rect(4, 6, 0, 0) },
+ { Point(8, 6), Point(4, 6), Rect(4, 6, 4, 0) },
+ { Point(4, 9), Point(4, 6), Rect(4, 6, 0, 3) },
+ { Point(8, 9), Point(4, 6), Rect(4, 6, 4, 3) },
+ // If neither point dominates, then the origin is a combination of the two.
+ { Point(4, 6), Point(6, 4), Rect(4, 4, 2, 2) },
+ { Point(-4, -6), Point(-6, -4), Rect(-6, -6, 2, 2) },
+ { Point(-4, 6), Point(6, -4), Rect(-4, -4, 10, 10) },
+ };
+
+ for (size_t i = 0; i < arraysize(int_tests); ++i) {
+ Rect actual = BoundingRect(int_tests[i].a, int_tests[i].b);
+ EXPECT_EQ(int_tests[i].expected, actual);
+ }
+
+ struct {
+ PointF a;
+ PointF b;
+ RectF expected;
+ } float_tests[] = {
+ // If point B dominates A, then A should be the origin.
+ { PointF(4.2f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 0) },
+ { PointF(4.2f, 6.8f), PointF(8.5f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 0) },
+ { PointF(4.2f, 6.8f), PointF(4.2f, 9.3f),
+ RectF(4.2f, 6.8f, 0, 2.5f) },
+ { PointF(4.2f, 6.8f), PointF(8.5f, 9.3f),
+ RectF(4.2f, 6.8f, 4.3f, 2.5f) },
+ // If point A dominates B, then B should be the origin.
+ { PointF(4.2f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 0) },
+ { PointF(8.5f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 0) },
+ { PointF(4.2f, 9.3f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 2.5f) },
+ { PointF(8.5f, 9.3f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 2.5f) },
+ // If neither point dominates, then the origin is a combination of the two.
+ { PointF(4.2f, 6.8f), PointF(6.8f, 4.2f),
+ RectF(4.2f, 4.2f, 2.6f, 2.6f) },
+ { PointF(-4.2f, -6.8f), PointF(-6.8f, -4.2f),
+ RectF(-6.8f, -6.8f, 2.6f, 2.6f) },
+ { PointF(-4.2f, 6.8f), PointF(6.8f, -4.2f),
+ RectF(-4.2f, -4.2f, 11.0f, 11.0f) }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i) {
+ RectF actual = BoundingRect(float_tests[i].a, float_tests[i].b);
+ EXPECT_RECTF_EQ(float_tests[i].expected, actual);
+ }
+}
+
+TEST(RectTest, IsExpressibleAsRect) {
+ EXPECT_TRUE(RectF().IsExpressibleAsRect());
+
+ float min = std::numeric_limits<int>::min();
+ float max = std::numeric_limits<int>::max();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ EXPECT_TRUE(RectF(
+ min + 200, min + 200, max - 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min - 200, min + 200, max + 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200 , min - 200, max + 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200, min + 200, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200, min + 200, max - 200, max + 200).IsExpressibleAsRect());
+
+ EXPECT_TRUE(RectF(0, 0, max - 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(200, 0, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 200, max - 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, max - 200, max + 200).IsExpressibleAsRect());
+
+ EXPECT_FALSE(RectF(infinity, 0, 1, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, infinity, 1, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, infinity, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, 1, infinity).IsExpressibleAsRect());
+}
+
+TEST(RectTest, Offset) {
+ Rect i(1, 2, 3, 4);
+
+ EXPECT_EQ(Rect(2, 1, 3, 4), (i + Vector2d(1, -1)));
+ EXPECT_EQ(Rect(2, 1, 3, 4), (Vector2d(1, -1) + i));
+ i += Vector2d(1, -1);
+ EXPECT_EQ(Rect(2, 1, 3, 4), i);
+ EXPECT_EQ(Rect(1, 2, 3, 4), (i - Vector2d(1, -1)));
+ i -= Vector2d(1, -1);
+ EXPECT_EQ(Rect(1, 2, 3, 4), i);
+
+ RectF f(1.1f, 2.2f, 3.3f, 4.4f);
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), (f + Vector2dF(1.1f, -1.1f)));
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), (Vector2dF(1.1f, -1.1f) + f));
+ f += Vector2dF(1.1f, -1.1f);
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), f);
+ EXPECT_EQ(RectF(1.1f, 2.2f, 3.3f, 4.4f), (f - Vector2dF(1.1f, -1.1f)));
+ f -= Vector2dF(1.1f, -1.1f);
+ EXPECT_EQ(RectF(1.1f, 2.2f, 3.3f, 4.4f), f);
+}
+
+TEST(RectTest, Corners) {
+ Rect i(1, 2, 3, 4);
+ RectF f(1.1f, 2.1f, 3.1f, 4.1f);
+
+ EXPECT_EQ(Point(1, 2), i.origin());
+ EXPECT_EQ(Point(4, 2), i.top_right());
+ EXPECT_EQ(Point(1, 6), i.bottom_left());
+ EXPECT_EQ(Point(4, 6), i.bottom_right());
+
+ EXPECT_EQ(PointF(1.1f, 2.1f), f.origin());
+ EXPECT_EQ(PointF(4.2f, 2.1f), f.top_right());
+ EXPECT_EQ(PointF(1.1f, 6.2f), f.bottom_left());
+ EXPECT_EQ(PointF(4.2f, 6.2f), f.bottom_right());
+}
+
+TEST(RectTest, ManhattanDistanceToPoint) {
+ Rect i(1, 2, 3, 4);
+ EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(1, 2)));
+ EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(4, 6)));
+ EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(2, 4)));
+ EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(0, 0)));
+ EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(2, 0)));
+ EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(5, 0)));
+ EXPECT_EQ(1, i.ManhattanDistanceToPoint(Point(5, 4)));
+ EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(5, 8)));
+ EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(3, 8)));
+ EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(0, 7)));
+ EXPECT_EQ(1, i.ManhattanDistanceToPoint(Point(0, 3)));
+
+ RectF f(1.1f, 2.1f, 3.1f, 4.1f);
+ EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(1.1f, 2.1f)));
+ EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(4.2f, 6.f)));
+ EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(2.f, 4.f)));
+ EXPECT_FLOAT_EQ(3.2f, f.ManhattanDistanceToPoint(PointF(0.f, 0.f)));
+ EXPECT_FLOAT_EQ(2.1f, f.ManhattanDistanceToPoint(PointF(2.f, 0.f)));
+ EXPECT_FLOAT_EQ(2.9f, f.ManhattanDistanceToPoint(PointF(5.f, 0.f)));
+ EXPECT_FLOAT_EQ(.8f, f.ManhattanDistanceToPoint(PointF(5.f, 4.f)));
+ EXPECT_FLOAT_EQ(2.6f, f.ManhattanDistanceToPoint(PointF(5.f, 8.f)));
+ EXPECT_FLOAT_EQ(1.8f, f.ManhattanDistanceToPoint(PointF(3.f, 8.f)));
+ EXPECT_FLOAT_EQ(1.9f, f.ManhattanDistanceToPoint(PointF(0.f, 7.f)));
+ EXPECT_FLOAT_EQ(1.1f, f.ManhattanDistanceToPoint(PointF(0.f, 3.f)));
+}
+
+TEST(RectTest, ManhattanInternalDistance) {
+ Rect i(0, 0, 400, 400);
+ EXPECT_EQ(0, i.ManhattanInternalDistance(gfx::Rect(-1, 0, 2, 1)));
+ EXPECT_EQ(1, i.ManhattanInternalDistance(gfx::Rect(400, 0, 1, 400)));
+ EXPECT_EQ(2, i.ManhattanInternalDistance(gfx::Rect(-100, -100, 100, 100)));
+ EXPECT_EQ(2, i.ManhattanInternalDistance(gfx::Rect(-101, 100, 100, 100)));
+ EXPECT_EQ(4, i.ManhattanInternalDistance(gfx::Rect(-101, -101, 100, 100)));
+ EXPECT_EQ(435, i.ManhattanInternalDistance(gfx::Rect(630, 603, 100, 100)));
+
+ RectF f(0.0f, 0.0f, 400.0f, 400.0f);
+ static const float kEpsilon = std::numeric_limits<float>::epsilon();
+
+ EXPECT_FLOAT_EQ(
+ 0.0f, f.ManhattanInternalDistance(gfx::RectF(-1.0f, 0.0f, 2.0f, 1.0f)));
+ EXPECT_FLOAT_EQ(
+ kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(400.0f, 0.0f, 1.0f, 400.0f)));
+ EXPECT_FLOAT_EQ(2.0f * kEpsilon,
+ f.ManhattanInternalDistance(
+ gfx::RectF(-100.0f, -100.0f, 100.0f, 100.0f)));
+ EXPECT_FLOAT_EQ(
+ 1.0f + kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(-101.0f, 100.0f, 100.0f, 100.0f)));
+ EXPECT_FLOAT_EQ(2.0f + 2.0f * kEpsilon,
+ f.ManhattanInternalDistance(
+ gfx::RectF(-101.0f, -101.0f, 100.0f, 100.0f)));
+ EXPECT_FLOAT_EQ(
+ 433.0f + 2.0f * kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(630.0f, 603.0f, 100.0f, 100.0f)));
+
+ EXPECT_FLOAT_EQ(
+ 0.0f, f.ManhattanInternalDistance(gfx::RectF(-1.0f, 0.0f, 1.1f, 1.0f)));
+ EXPECT_FLOAT_EQ(
+ 0.1f + kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(-1.5f, 0.0f, 1.4f, 1.0f)));
+ EXPECT_FLOAT_EQ(
+ kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(-1.5f, 0.0f, 1.5f, 1.0f)));
+}
+
+TEST(RectTest, IntegerOverflow) {
+ int limit = std::numeric_limits<int>::max();
+ int min_limit = std::numeric_limits<int>::min();
+ int expected = 10;
+ int large_number = limit - expected;
+
+ Rect height_overflow(0, large_number, 100, 100);
+ EXPECT_EQ(large_number, height_overflow.y());
+ EXPECT_EQ(expected, height_overflow.height());
+
+ Rect width_overflow(large_number, 0, 100, 100);
+ EXPECT_EQ(large_number, width_overflow.x());
+ EXPECT_EQ(expected, width_overflow.width());
+
+ Rect size_height_overflow(Point(0, large_number), Size(100, 100));
+ EXPECT_EQ(large_number, size_height_overflow.y());
+ EXPECT_EQ(expected, size_height_overflow.height());
+
+ Rect size_width_overflow(Point(large_number, 0), Size(100, 100));
+ EXPECT_EQ(large_number, size_width_overflow.x());
+ EXPECT_EQ(expected, size_width_overflow.width());
+
+ Rect set_height_overflow(0, large_number, 100, 5);
+ EXPECT_EQ(5, set_height_overflow.height());
+ set_height_overflow.set_height(100);
+ EXPECT_EQ(expected, set_height_overflow.height());
+
+ Rect set_y_overflow(100, 100, 100, 100);
+ EXPECT_EQ(100, set_y_overflow.height());
+ set_y_overflow.set_y(large_number);
+ EXPECT_EQ(expected, set_y_overflow.height());
+
+ Rect set_width_overflow(large_number, 0, 5, 100);
+ EXPECT_EQ(5, set_width_overflow.width());
+ set_width_overflow.set_width(100);
+ EXPECT_EQ(expected, set_width_overflow.width());
+
+ Rect set_x_overflow(100, 100, 100, 100);
+ EXPECT_EQ(100, set_x_overflow.width());
+ set_x_overflow.set_x(large_number);
+ EXPECT_EQ(expected, set_x_overflow.width());
+
+ Point large_offset(large_number, large_number);
+ Size size(100, 100);
+ Size expected_size(10, 10);
+
+ Rect set_origin_overflow(100, 100, 100, 100);
+ EXPECT_EQ(size, set_origin_overflow.size());
+ set_origin_overflow.set_origin(large_offset);
+ EXPECT_EQ(large_offset, set_origin_overflow.origin());
+ EXPECT_EQ(expected_size, set_origin_overflow.size());
+
+ Rect set_size_overflow(large_number, large_number, 5, 5);
+ EXPECT_EQ(Size(5, 5), set_size_overflow.size());
+ set_size_overflow.set_size(size);
+ EXPECT_EQ(large_offset, set_size_overflow.origin());
+ EXPECT_EQ(expected_size, set_size_overflow.size());
+
+ Rect set_rect_overflow;
+ set_rect_overflow.SetRect(large_number, large_number, 100, 100);
+ EXPECT_EQ(large_offset, set_rect_overflow.origin());
+ EXPECT_EQ(expected_size, set_rect_overflow.size());
+
+ // Insetting an empty rect, but the total inset (left + right) could overflow.
+ Rect inset_overflow;
+ inset_overflow.Inset(large_number, large_number, 100, 100);
+ EXPECT_EQ(large_offset, inset_overflow.origin());
+ EXPECT_EQ(gfx::Size(), inset_overflow.size());
+
+ // Insetting where the total inset (width - left - right) could overflow.
+ // Also, this insetting by the min limit in all directions cannot
+ // represent width() without overflow, so that will also clamp.
+ Rect inset_overflow2;
+ inset_overflow2.Inset(min_limit, min_limit, min_limit, min_limit);
+ EXPECT_EQ(inset_overflow2, gfx::Rect(min_limit, min_limit, limit, limit));
+
+ // Insetting where the width shouldn't change, but if the insets operations
+ // clamped in the wrong order, e.g. ((width - left) - right) vs (width - (left
+ // + right)) then this will not work properly. This is the proper order,
+ // as if left + right overflows, the width cannot be decreased by more than
+ // max int anyway. Additionally, if left + right underflows, it cannot be
+ // increased by more then max int.
+ Rect inset_overflow3(0, 0, limit, limit);
+ inset_overflow3.Inset(-100, -100, 100, 100);
+ EXPECT_EQ(inset_overflow3, gfx::Rect(-100, -100, limit, limit));
+
+ Rect inset_overflow4(-1000, -1000, limit, limit);
+ inset_overflow4.Inset(100, 100, -100, -100);
+ EXPECT_EQ(inset_overflow4, gfx::Rect(-900, -900, limit, limit));
+
+ Rect offset_overflow(0, 0, 100, 100);
+ offset_overflow.Offset(large_number, large_number);
+ EXPECT_EQ(large_offset, offset_overflow.origin());
+ EXPECT_EQ(expected_size, offset_overflow.size());
+
+ Rect operator_overflow(0, 0, 100, 100);
+ operator_overflow += Vector2d(large_number, large_number);
+ EXPECT_EQ(large_offset, operator_overflow.origin());
+ EXPECT_EQ(expected_size, operator_overflow.size());
+
+ Rect origin_maxint(limit, limit, limit, limit);
+ EXPECT_EQ(origin_maxint, Rect(gfx::Point(limit, limit), gfx::Size()));
+
+ // Expect a rect at the origin and a rect whose right/bottom is maxint
+ // create a rect that extends from 0..maxint in both extents.
+ {
+ Rect origin_small(0, 0, 100, 100);
+ Rect big_clamped(50, 50, limit, limit);
+ EXPECT_EQ(big_clamped.right(), limit);
+
+ Rect unioned = UnionRects(origin_small, big_clamped);
+ Rect rect_limit(0, 0, limit, limit);
+ EXPECT_EQ(unioned, rect_limit);
+ }
+
+ // Expect a rect that would overflow width (but not right) to be clamped
+ // and to have maxint extents after unioning.
+ {
+ Rect small(-500, -400, 100, 100);
+ Rect big(-400, -500, limit, limit);
+ // Technically, this should be limit + 100 width, but will clamp to maxint.
+ EXPECT_EQ(UnionRects(small, big), Rect(-500, -500, limit, limit));
+ }
+
+ // Expect a rect that would overflow right *and* width to be clamped.
+ {
+ Rect clamped(500, 500, limit, limit);
+ Rect positive_origin(100, 100, 500, 500);
+
+ // Ideally, this should be (100, 100, limit + 400, limit + 400).
+ // However, width overflows and would be clamped to limit, but right
+ // overflows too and so will be clamped to limit - 100.
+ Rect expected(100, 100, limit - 100, limit - 100);
+ EXPECT_EQ(UnionRects(clamped, positive_origin), expected);
+ }
+
+ // Unioning a left=minint rect with a right=maxint rect.
+ // We can't represent both ends of the spectrum in the same rect.
+ // Make sure we keep the most useful area.
+ {
+ int part_limit = min_limit / 3;
+ Rect left_minint(min_limit, min_limit, 1, 1);
+ Rect right_maxint(limit - 1, limit - 1, limit, limit);
+ Rect expected(part_limit, part_limit, 2 * part_limit, 2 * part_limit);
+ Rect result = UnionRects(left_minint, right_maxint);
+
+ // The result should be maximally big.
+ EXPECT_EQ(limit, result.height());
+ EXPECT_EQ(limit, result.width());
+
+ // The result should include the area near the origin.
+ EXPECT_GT(-part_limit, result.x());
+ EXPECT_LT(part_limit, result.right());
+ EXPECT_GT(-part_limit, result.y());
+ EXPECT_LT(part_limit, result.bottom());
+
+ // More succinctly, but harder to read in the results.
+ EXPECT_TRUE(UnionRects(left_minint, right_maxint).Contains(expected));
+ }
+}
+
+TEST(RectTest, ScaleToEnclosingRectSafe) {
+ const int max_int = std::numeric_limits<int>::max();
+ const int min_int = std::numeric_limits<int>::min();
+
+ Rect xy_underflow(-100000, -123456, 10, 20);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(xy_underflow, 100000, 100000),
+ Rect(min_int, min_int, 1000000, 2000000));
+
+ // A location overflow means that width/right and bottom/top also
+ // overflow so need to be clamped.
+ Rect xy_overflow(100000, 123456, 10, 20);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(xy_overflow, 100000, 100000),
+ Rect(max_int, max_int, 0, 0));
+
+ // In practice all rects are clamped to 0 width / 0 height so
+ // negative sizes don't matter, but try this for the sake of testing.
+ Rect size_underflow(-1, -2, 100000, 100000);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(size_underflow, -100000, -100000),
+ Rect(100000, 200000, 0, 0));
+
+ Rect size_overflow(-1, -2, 123456, 234567);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(size_overflow, 100000, 100000),
+ Rect(-100000, -200000, max_int, max_int));
+ // Verify width/right gets clamped properly too if x/y positive.
+ Rect size_overflow2(1, 2, 123456, 234567);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(size_overflow2, 100000, 100000),
+ Rect(100000, 200000, max_int - 100000, max_int - 200000));
+
+ Rect max_rect(max_int, max_int, max_int, max_int);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(max_rect, max_int, max_int),
+ Rect(max_int, max_int, 0, 0));
+
+ Rect min_rect(min_int, min_int, max_int, max_int);
+ // Min rect can't be scaled up any further in any dimension.
+ EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, 2, 3.5), min_rect);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, max_int, max_int), min_rect);
+ // Min rect scaled by min is an empty rect at (max, max)
+ EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, min_int, min_int), max_rect);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/safe_integer_conversions_unittest.cc b/ui/gfx/geometry/safe_integer_conversions_unittest.cc
new file mode 100644
index 0000000000..91bdbb8d35
--- /dev/null
+++ b/ui/gfx/geometry/safe_integer_conversions_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/safe_integer_conversions.h"
+
+#include <limits>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+
+TEST(SafeIntegerConversions, ToFlooredInt) {
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToFlooredInt(infinity));
+ EXPECT_EQ(int_max, ToFlooredInt(max));
+ EXPECT_EQ(int_max, ToFlooredInt(max + 100));
+
+ EXPECT_EQ(-101, ToFlooredInt(-100.5f));
+ EXPECT_EQ(0, ToFlooredInt(0.f));
+ EXPECT_EQ(100, ToFlooredInt(100.5f));
+
+ EXPECT_EQ(int_min, ToFlooredInt(-infinity));
+ EXPECT_EQ(int_min, ToFlooredInt(min));
+ EXPECT_EQ(int_min, ToFlooredInt(min - 100));
+}
+
+TEST(SafeIntegerConversions, ToCeiledInt) {
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToCeiledInt(infinity));
+ EXPECT_EQ(int_max, ToCeiledInt(max));
+ EXPECT_EQ(int_max, ToCeiledInt(max + 100));
+
+ EXPECT_EQ(-100, ToCeiledInt(-100.5f));
+ EXPECT_EQ(0, ToCeiledInt(0.f));
+ EXPECT_EQ(101, ToCeiledInt(100.5f));
+
+ EXPECT_EQ(int_min, ToCeiledInt(-infinity));
+ EXPECT_EQ(int_min, ToCeiledInt(min));
+ EXPECT_EQ(int_min, ToCeiledInt(min - 100));
+}
+
+TEST(SafeIntegerConversions, ToRoundedInt) {
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToRoundedInt(infinity));
+ EXPECT_EQ(int_max, ToRoundedInt(max));
+ EXPECT_EQ(int_max, ToRoundedInt(max + 100));
+
+ EXPECT_EQ(-100, ToRoundedInt(-100.1f));
+ EXPECT_EQ(-101, ToRoundedInt(-100.5f));
+ EXPECT_EQ(-101, ToRoundedInt(-100.9f));
+ EXPECT_EQ(0, ToRoundedInt(0.f));
+ EXPECT_EQ(100, ToRoundedInt(100.1f));
+ EXPECT_EQ(101, ToRoundedInt(100.5f));
+ EXPECT_EQ(101, ToRoundedInt(100.9f));
+
+ EXPECT_EQ(int_min, ToRoundedInt(-infinity));
+ EXPECT_EQ(int_min, ToRoundedInt(min));
+ EXPECT_EQ(int_min, ToRoundedInt(min - 100));
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/scroll_offset_unittest.cc b/ui/gfx/geometry/scroll_offset_unittest.cc
new file mode 100644
index 0000000000..782fdf4c10
--- /dev/null
+++ b/ui/gfx/geometry/scroll_offset_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <cmath>
+#include <limits>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/scroll_offset.h"
+
+namespace gfx {
+
+TEST(ScrollOffsetTest, IsZero) {
+ ScrollOffset zero(0, 0);
+ ScrollOffset nonzero(0.1f, -0.1f);
+
+ EXPECT_TRUE(zero.IsZero());
+ EXPECT_FALSE(nonzero.IsZero());
+}
+
+TEST(ScrollOffsetTest, Add) {
+ ScrollOffset f1(3.1f, 5.1f);
+ ScrollOffset f2(4.3f, -1.3f);
+
+ const struct {
+ ScrollOffset expected;
+ ScrollOffset actual;
+ } scroll_offset_tests[] = {
+ { ScrollOffset(3.1f, 5.1f), f1 + ScrollOffset() },
+ { ScrollOffset(3.1f + 4.3f, 5.1f - 1.3f), f1 + f2 },
+ { ScrollOffset(3.1f - 4.3f, 5.1f + 1.3f), f1 - f2 }
+ };
+
+ for (size_t i = 0; i < arraysize(scroll_offset_tests); ++i)
+ EXPECT_EQ(scroll_offset_tests[i].expected.ToString(),
+ scroll_offset_tests[i].actual.ToString());
+}
+
+TEST(ScrollOffsetTest, Negative) {
+ const struct {
+ ScrollOffset expected;
+ ScrollOffset actual;
+ } scroll_offset_tests[] = {
+ { ScrollOffset(-0.3f, -0.3f), -ScrollOffset(0.3f, 0.3f) },
+ { ScrollOffset(0.3f, 0.3f), -ScrollOffset(-0.3f, -0.3f) },
+ { ScrollOffset(-0.3f, 0.3f), -ScrollOffset(0.3f, -0.3f) },
+ { ScrollOffset(0.3f, -0.3f), -ScrollOffset(-0.3f, 0.3f) }
+ };
+
+ for (size_t i = 0; i < arraysize(scroll_offset_tests); ++i)
+ EXPECT_EQ(scroll_offset_tests[i].expected.ToString(),
+ scroll_offset_tests[i].actual.ToString());
+}
+
+TEST(ScrollOffsetTest, Scale) {
+ float float_values[][4] = {
+ { 4.5f, 1.2f, 3.3f, 5.6f },
+ { 4.5f, -1.2f, 3.3f, 5.6f },
+ { 4.5f, 1.2f, 3.3f, -5.6f },
+ { 4.5f, 1.2f, -3.3f, -5.6f },
+ { -4.5f, 1.2f, 3.3f, 5.6f },
+ { -4.5f, 1.2f, 0, 5.6f },
+ { -4.5f, 1.2f, 3.3f, 0 },
+ { 4.5f, 0, 3.3f, 5.6f },
+ { 0, 1.2f, 3.3f, 5.6f }
+ };
+
+ for (size_t i = 0; i < arraysize(float_values); ++i) {
+ ScrollOffset v(float_values[i][0], float_values[i][1]);
+ v.Scale(float_values[i][2], float_values[i][3]);
+ EXPECT_EQ(v.x(), float_values[i][0] * float_values[i][2]);
+ EXPECT_EQ(v.y(), float_values[i][1] * float_values[i][3]);
+ }
+
+ float single_values[][3] = {
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, -1.2f, 3.3f },
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, 1.2f, -3.3f },
+ { -4.5f, 1.2f, 3.3f },
+ { -4.5f, 1.2f, 0 },
+ { -4.5f, 1.2f, 3.3f },
+ { 4.5f, 0, 3.3f },
+ { 0, 1.2f, 3.3f }
+ };
+
+ for (size_t i = 0; i < arraysize(single_values); ++i) {
+ ScrollOffset v(single_values[i][0], single_values[i][1]);
+ v.Scale(single_values[i][2]);
+ EXPECT_EQ(v.x(), single_values[i][0] * single_values[i][2]);
+ EXPECT_EQ(v.y(), single_values[i][1] * single_values[i][2]);
+ }
+}
+
+TEST(ScrollOffsetTest, ClampScrollOffset) {
+ ScrollOffset a;
+
+ a = ScrollOffset(3.5, 5.5);
+ EXPECT_EQ(ScrollOffset(3.5, 5.5).ToString(), a.ToString());
+ a.SetToMax(ScrollOffset(2.5, 4.5));
+ EXPECT_EQ(ScrollOffset(3.5, 5.5).ToString(), a.ToString());
+ a.SetToMax(ScrollOffset(3.5, 5.5));
+ EXPECT_EQ(ScrollOffset(3.5, 5.5).ToString(), a.ToString());
+ a.SetToMax(ScrollOffset(4.5, 2.5));
+ EXPECT_EQ(ScrollOffset(4.5, 5.5).ToString(), a.ToString());
+ a.SetToMax(ScrollOffset(8.5, 10.5));
+ EXPECT_EQ(ScrollOffset(8.5, 10.5).ToString(), a.ToString());
+
+ a.SetToMin(ScrollOffset(9.5, 11.5));
+ EXPECT_EQ(ScrollOffset(8.5, 10.5).ToString(), a.ToString());
+ a.SetToMin(ScrollOffset(8.5, 10.5));
+ EXPECT_EQ(ScrollOffset(8.5, 10.5).ToString(), a.ToString());
+ a.SetToMin(ScrollOffset(11.5, 9.5));
+ EXPECT_EQ(ScrollOffset(8.5, 9.5).ToString(), a.ToString());
+ a.SetToMin(ScrollOffset(7.5, 11.5));
+ EXPECT_EQ(ScrollOffset(7.5, 9.5).ToString(), a.ToString());
+ a.SetToMin(ScrollOffset(3.5, 5.5));
+ EXPECT_EQ(ScrollOffset(3.5, 5.5).ToString(), a.ToString());
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/size_unittest.cc b/ui/gfx/geometry/size_unittest.cc
new file mode 100644
index 0000000000..ab4a831768
--- /dev/null
+++ b/ui/gfx/geometry/size_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/size_conversions.h"
+#include "ui/gfx/geometry/size_f.h"
+
+namespace gfx {
+
+namespace {
+
+int TestSizeF(const SizeF& s) {
+ return s.width();
+}
+
+} // namespace
+
+TEST(SizeTest, ToSizeF) {
+ // Check that explicit conversion from integer to float compiles.
+ Size a(10, 20);
+ float width = TestSizeF(gfx::SizeF(a));
+ EXPECT_EQ(width, a.width());
+
+ SizeF b(10, 20);
+
+ EXPECT_EQ(b, gfx::SizeF(a));
+}
+
+TEST(SizeTest, ToFlooredSize) {
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ToCeiledSize) {
+ EXPECT_EQ(Size(0, 0), ToCeiledSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToCeiledSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ToRoundedSize) {
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(1, 1), ToRoundedSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(1, 1), ToRoundedSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(11, 11), ToRoundedSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(11, 11), ToRoundedSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ClampSize) {
+ Size a;
+
+ a = Size(3, 5);
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(2, 4));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(3, 5));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(4, 2));
+ EXPECT_EQ(Size(4, 5).ToString(), a.ToString());
+ a.SetToMax(Size(8, 10));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Size(9, 11));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+ a.SetToMin(Size(8, 10));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+ a.SetToMin(Size(11, 9));
+ EXPECT_EQ(Size(8, 9).ToString(), a.ToString());
+ a.SetToMin(Size(7, 11));
+ EXPECT_EQ(Size(7, 9).ToString(), a.ToString());
+ a.SetToMin(Size(3, 5));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+}
+
+TEST(SizeTest, ClampSizeF) {
+ SizeF a;
+
+ a = SizeF(3.5f, 5.5f);
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(2.5f, 4.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(3.5f, 5.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(4.5f, 2.5f));
+ EXPECT_EQ(SizeF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(8.5f, 10.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(SizeF(9.5f, 11.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(8.5f, 10.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(11.5f, 9.5f));
+ EXPECT_EQ(SizeF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(7.5f, 11.5f));
+ EXPECT_EQ(SizeF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(3.5f, 5.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+TEST(SizeTest, Enlarge) {
+ Size test(3, 4);
+ test.Enlarge(5, -8);
+ EXPECT_EQ(test, Size(8, -4));
+}
+
+TEST(SizeTest, IntegerOverflow) {
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ Size max_size(int_max, int_max);
+ Size min_size(int_min, int_min);
+ Size test;
+
+ test = Size();
+ test.Enlarge(int_max, int_max);
+ EXPECT_EQ(test, max_size);
+
+ test = Size();
+ test.Enlarge(int_min, int_min);
+ EXPECT_EQ(test, min_size);
+
+ test = Size(10, 20);
+ test.Enlarge(int_max, int_max);
+ EXPECT_EQ(test, max_size);
+
+ test = Size(-10, -20);
+ test.Enlarge(int_min, int_min);
+ EXPECT_EQ(test, min_size);
+}
+
+// This checks that we set IsEmpty appropriately.
+TEST(SizeTest, TrivialDimensionTests) {
+ const float clearly_trivial = SizeF::kTrivial / 2.f;
+ const float massize_dimension = 4e13f;
+
+ // First, using the constructor.
+ EXPECT_TRUE(SizeF(clearly_trivial, 1.f).IsEmpty());
+ EXPECT_TRUE(SizeF(.01f, clearly_trivial).IsEmpty());
+ EXPECT_TRUE(SizeF(0.f, 0.f).IsEmpty());
+ EXPECT_FALSE(SizeF(.01f, .01f).IsEmpty());
+
+ // Then use the setter.
+ SizeF test(2.f, 1.f);
+ EXPECT_FALSE(test.IsEmpty());
+
+ test.SetSize(clearly_trivial, 1.f);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.SetSize(.01f, clearly_trivial);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.SetSize(0.f, 0.f);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.SetSize(.01f, .01f);
+ EXPECT_FALSE(test.IsEmpty());
+
+ // Now just one dimension at a time.
+ test.set_width(clearly_trivial);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.set_width(massize_dimension);
+ test.set_height(clearly_trivial);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.set_width(clearly_trivial);
+ test.set_height(massize_dimension);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.set_width(2.f);
+ EXPECT_FALSE(test.IsEmpty());
+}
+
+// These are the ramifications of the decision to keep the recorded size
+// at zero for trivial sizes.
+TEST(SizeTest, ClampsToZero) {
+ const float clearly_trivial = SizeF::kTrivial / 2.f;
+ const float nearly_trivial = SizeF::kTrivial * 1.5f;
+
+ SizeF test(clearly_trivial, 1.f);
+
+ EXPECT_FLOAT_EQ(0.f, test.width());
+ EXPECT_FLOAT_EQ(1.f, test.height());
+
+ test.SetSize(.01f, clearly_trivial);
+
+ EXPECT_FLOAT_EQ(.01f, test.width());
+ EXPECT_FLOAT_EQ(0.f, test.height());
+
+ test.SetSize(nearly_trivial, nearly_trivial);
+
+ EXPECT_FLOAT_EQ(nearly_trivial, test.width());
+ EXPECT_FLOAT_EQ(nearly_trivial, test.height());
+
+ test.Scale(0.5f);
+
+ EXPECT_FLOAT_EQ(0.f, test.width());
+ EXPECT_FLOAT_EQ(0.f, test.height());
+
+ test.SetSize(0.f, 0.f);
+ test.Enlarge(clearly_trivial, clearly_trivial);
+ test.Enlarge(clearly_trivial, clearly_trivial);
+ test.Enlarge(clearly_trivial, clearly_trivial);
+
+ EXPECT_EQ(SizeF(0.f, 0.f), test);
+}
+
+// These make sure the constructor and setter have the same effect on the
+// boundary case. This claims to know the boundary, but not which way it goes.
+TEST(SizeTest, ConsistentClamping) {
+ SizeF resized;
+
+ resized.SetSize(SizeF::kTrivial, 0.f);
+ EXPECT_EQ(SizeF(SizeF::kTrivial, 0.f), resized);
+
+ resized.SetSize(0.f, SizeF::kTrivial);
+ EXPECT_EQ(SizeF(0.f, SizeF::kTrivial), resized);
+}
+
+// Let's make sure we don't unexpectedly grow the struct by adding constants.
+// Also, if some platform packs floats inefficiently, it would be worth noting.
+TEST(SizeTest, StaysSmall) {
+ EXPECT_EQ(2 * sizeof(float), sizeof(SizeF));
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/test/rect_test_util.cc b/ui/gfx/geometry/test/rect_test_util.cc
new file mode 100644
index 0000000000..bcc943b7fc
--- /dev/null
+++ b/ui/gfx/geometry/test/rect_test_util.cc
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/test/rect_test_util.h"
+
+namespace gfx {
+namespace test {
+
+testing::AssertionResult RectContains(const gfx::Rect& outer_rect,
+ const gfx::Rect& inner_rect) {
+ if (outer_rect.Contains(inner_rect)) {
+ return testing::AssertionSuccess()
+ << "outer_rect (" << outer_rect.ToString()
+ << ") does contain inner_rect (" << inner_rect.ToString() << ")";
+ }
+ return testing::AssertionFailure() << "outer_rect (" << outer_rect.ToString()
+ << ") does not contain inner_rect ("
+ << inner_rect.ToString() << ")";
+}
+
+} // namespace test
+} // namespace gfx
diff --git a/ui/gfx/geometry/test/rect_test_util.h b/ui/gfx/geometry/test/rect_test_util.h
new file mode 100644
index 0000000000..857dd210b1
--- /dev/null
+++ b/ui/gfx/geometry/test/rect_test_util.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_
+#define UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+namespace test {
+
+testing::AssertionResult RectContains(const gfx::Rect& outer_rect,
+ const gfx::Rect& inner_rect);
+
+} // namespace test
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_
diff --git a/ui/gfx/geometry/vector2d_conversions.cc b/ui/gfx/geometry/vector2d_conversions.cc
new file mode 100644
index 0000000000..dceb69e2ff
--- /dev/null
+++ b/ui/gfx/geometry/vector2d_conversions.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/vector2d_conversions.h"
+
+#include "ui/gfx/geometry/safe_integer_conversions.h"
+
+namespace gfx {
+
+Vector2d ToFlooredVector2d(const Vector2dF& vector2d) {
+ int x = ToFlooredInt(vector2d.x());
+ int y = ToFlooredInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+Vector2d ToCeiledVector2d(const Vector2dF& vector2d) {
+ int x = ToCeiledInt(vector2d.x());
+ int y = ToCeiledInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+Vector2d ToRoundedVector2d(const Vector2dF& vector2d) {
+ int x = ToRoundedInt(vector2d.x());
+ int y = ToRoundedInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+} // namespace gfx
+
diff --git a/ui/gfx/geometry/vector2d_conversions.h b/ui/gfx/geometry/vector2d_conversions.h
new file mode 100644
index 0000000000..f4e16ae4be
--- /dev/null
+++ b/ui/gfx/geometry/vector2d_conversions.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_
+#define UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_
+
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace gfx {
+
+// Returns a Vector2d with each component from the input Vector2dF floored.
+GFX_EXPORT Vector2d ToFlooredVector2d(const Vector2dF& vector2d);
+
+// Returns a Vector2d with each component from the input Vector2dF ceiled.
+GFX_EXPORT Vector2d ToCeiledVector2d(const Vector2dF& vector2d);
+
+// Returns a Vector2d with each component from the input Vector2dF rounded.
+GFX_EXPORT Vector2d ToRoundedVector2d(const Vector2dF& vector2d);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_
diff --git a/ui/gfx/geometry/vector2d_unittest.cc b/ui/gfx/geometry/vector2d_unittest.cc
new file mode 100644
index 0000000000..4bdf24cba4
--- /dev/null
+++ b/ui/gfx/geometry/vector2d_unittest.cc
@@ -0,0 +1,293 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <cmath>
+#include <limits>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace gfx {
+
+TEST(Vector2dTest, ConversionToFloat) {
+ Vector2d i(3, 4);
+ Vector2dF f = i;
+ EXPECT_EQ(i, f);
+}
+
+TEST(Vector2dTest, IsZero) {
+ Vector2d int_zero(0, 0);
+ Vector2d int_nonzero(2, -2);
+ Vector2dF float_zero(0, 0);
+ Vector2dF float_nonzero(0.1f, -0.1f);
+
+ EXPECT_TRUE(int_zero.IsZero());
+ EXPECT_FALSE(int_nonzero.IsZero());
+ EXPECT_TRUE(float_zero.IsZero());
+ EXPECT_FALSE(float_nonzero.IsZero());
+}
+
+TEST(Vector2dTest, Add) {
+ Vector2d i1(3, 5);
+ Vector2d i2(4, -1);
+
+ const struct {
+ Vector2d expected;
+ Vector2d actual;
+ } int_tests[] = {
+ { Vector2d(3, 5), i1 + Vector2d() },
+ { Vector2d(3 + 4, 5 - 1), i1 + i2 },
+ { Vector2d(3 - 4, 5 + 1), i1 - i2 }
+ };
+
+ for (size_t i = 0; i < arraysize(int_tests); ++i)
+ EXPECT_EQ(int_tests[i].expected.ToString(),
+ int_tests[i].actual.ToString());
+
+ Vector2dF f1(3.1f, 5.1f);
+ Vector2dF f2(4.3f, -1.3f);
+
+ const struct {
+ Vector2dF expected;
+ Vector2dF actual;
+ } float_tests[] = {
+ { Vector2dF(3.1F, 5.1F), f1 + Vector2d() },
+ { Vector2dF(3.1F, 5.1F), f1 + Vector2dF() },
+ { Vector2dF(3.1f + 4.3f, 5.1f - 1.3f), f1 + f2 },
+ { Vector2dF(3.1f - 4.3f, 5.1f + 1.3f), f1 - f2 }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector2dTest, Negative) {
+ const struct {
+ Vector2d expected;
+ Vector2d actual;
+ } int_tests[] = {
+ { Vector2d(0, 0), -Vector2d(0, 0) },
+ { Vector2d(-3, -3), -Vector2d(3, 3) },
+ { Vector2d(3, 3), -Vector2d(-3, -3) },
+ { Vector2d(-3, 3), -Vector2d(3, -3) },
+ { Vector2d(3, -3), -Vector2d(-3, 3) }
+ };
+
+ for (size_t i = 0; i < arraysize(int_tests); ++i)
+ EXPECT_EQ(int_tests[i].expected.ToString(),
+ int_tests[i].actual.ToString());
+
+ const struct {
+ Vector2dF expected;
+ Vector2dF actual;
+ } float_tests[] = {
+ { Vector2dF(0, 0), -Vector2d(0, 0) },
+ { Vector2dF(-0.3f, -0.3f), -Vector2dF(0.3f, 0.3f) },
+ { Vector2dF(0.3f, 0.3f), -Vector2dF(-0.3f, -0.3f) },
+ { Vector2dF(-0.3f, 0.3f), -Vector2dF(0.3f, -0.3f) },
+ { Vector2dF(0.3f, -0.3f), -Vector2dF(-0.3f, 0.3f) }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector2dTest, Scale) {
+ float double_values[][4] = {
+ { 4.5f, 1.2f, 3.3f, 5.6f },
+ { 4.5f, -1.2f, 3.3f, 5.6f },
+ { 4.5f, 1.2f, 3.3f, -5.6f },
+ { 4.5f, 1.2f, -3.3f, -5.6f },
+ { -4.5f, 1.2f, 3.3f, 5.6f },
+ { -4.5f, 1.2f, 0, 5.6f },
+ { -4.5f, 1.2f, 3.3f, 0 },
+ { 4.5f, 0, 3.3f, 5.6f },
+ { 0, 1.2f, 3.3f, 5.6f }
+ };
+
+ for (size_t i = 0; i < arraysize(double_values); ++i) {
+ Vector2dF v(double_values[i][0], double_values[i][1]);
+ v.Scale(double_values[i][2], double_values[i][3]);
+ EXPECT_EQ(v.x(), double_values[i][0] * double_values[i][2]);
+ EXPECT_EQ(v.y(), double_values[i][1] * double_values[i][3]);
+
+ Vector2dF v2 = ScaleVector2d(
+ gfx::Vector2dF(double_values[i][0], double_values[i][1]),
+ double_values[i][2], double_values[i][3]);
+ EXPECT_EQ(double_values[i][0] * double_values[i][2], v2.x());
+ EXPECT_EQ(double_values[i][1] * double_values[i][3], v2.y());
+ }
+
+ float single_values[][3] = {
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, -1.2f, 3.3f },
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, 1.2f, -3.3f },
+ { -4.5f, 1.2f, 3.3f },
+ { -4.5f, 1.2f, 0 },
+ { -4.5f, 1.2f, 3.3f },
+ { 4.5f, 0, 3.3f },
+ { 0, 1.2f, 3.3f }
+ };
+
+ for (size_t i = 0; i < arraysize(single_values); ++i) {
+ Vector2dF v(single_values[i][0], single_values[i][1]);
+ v.Scale(single_values[i][2]);
+ EXPECT_EQ(v.x(), single_values[i][0] * single_values[i][2]);
+ EXPECT_EQ(v.y(), single_values[i][1] * single_values[i][2]);
+
+ Vector2dF v2 = ScaleVector2d(
+ gfx::Vector2dF(double_values[i][0], double_values[i][1]),
+ double_values[i][2]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][2], v2.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][2], v2.y());
+ }
+}
+
+TEST(Vector2dTest, Length) {
+ int int_values[][2] = {
+ { 0, 0 },
+ { 10, 20 },
+ { 20, 10 },
+ { -10, -20 },
+ { -20, 10 },
+ { 10, -20 },
+ };
+
+ for (size_t i = 0; i < arraysize(int_values); ++i) {
+ int v0 = int_values[i][0];
+ int v1 = int_values[i][1];
+ double length_squared =
+ static_cast<double>(v0) * v0 + static_cast<double>(v1) * v1;
+ double length = std::sqrt(length_squared);
+ Vector2d vector(v0, v1);
+ EXPECT_EQ(static_cast<float>(length_squared), vector.LengthSquared());
+ EXPECT_EQ(static_cast<float>(length), vector.Length());
+ }
+
+ float float_values[][2] = {
+ { 0, 0 },
+ { 10.5f, 20.5f },
+ { 20.5f, 10.5f },
+ { -10.5f, -20.5f },
+ { -20.5f, 10.5f },
+ { 10.5f, -20.5f },
+ // A large vector that fails if the Length function doesn't use
+ // double precision internally.
+ { 1236278317862780234892374893213178027.12122348904204230f,
+ 335890352589839028212313231225425134332.38123f },
+ };
+
+ for (size_t i = 0; i < arraysize(float_values); ++i) {
+ double v0 = float_values[i][0];
+ double v1 = float_values[i][1];
+ double length_squared =
+ static_cast<double>(v0) * v0 + static_cast<double>(v1) * v1;
+ double length = std::sqrt(length_squared);
+ Vector2dF vector(v0, v1);
+ EXPECT_DOUBLE_EQ(length_squared, vector.LengthSquared());
+ EXPECT_FLOAT_EQ(static_cast<float>(length), vector.Length());
+ }
+}
+
+TEST(Vector2dTest, ClampVector2d) {
+ Vector2d a;
+
+ a = Vector2d(3, 5);
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(2, 4));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(3, 5));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(4, 2));
+ EXPECT_EQ(Vector2d(4, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(8, 10));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Vector2d(9, 11));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+ a.SetToMin(Vector2d(8, 10));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+ a.SetToMin(Vector2d(11, 9));
+ EXPECT_EQ(Vector2d(8, 9).ToString(), a.ToString());
+ a.SetToMin(Vector2d(7, 11));
+ EXPECT_EQ(Vector2d(7, 9).ToString(), a.ToString());
+ a.SetToMin(Vector2d(3, 5));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+}
+
+TEST(Vector2dTest, ClampVector2dF) {
+ Vector2dF a;
+
+ a = Vector2dF(3.5f, 5.5f);
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(2.5f, 4.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(3.5f, 5.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(4.5f, 2.5f));
+ EXPECT_EQ(Vector2dF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(8.5f, 10.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(Vector2dF(9.5f, 11.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(8.5f, 10.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(11.5f, 9.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(7.5f, 11.5f));
+ EXPECT_EQ(Vector2dF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(3.5f, 5.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+TEST(Vector2dTest, IntegerOverflow) {
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ Vector2d max_vector(int_max, int_max);
+ Vector2d min_vector(int_min, int_min);
+ Vector2d test;
+
+ test = Vector2d();
+ test += Vector2d(int_max, int_max);
+ EXPECT_EQ(test, max_vector);
+
+ test = Vector2d();
+ test += Vector2d(int_min, int_min);
+ EXPECT_EQ(test, min_vector);
+
+ test = Vector2d(10, 20);
+ test += Vector2d(int_max, int_max);
+ EXPECT_EQ(test, max_vector);
+
+ test = Vector2d(-10, -20);
+ test += Vector2d(int_min, int_min);
+ EXPECT_EQ(test, min_vector);
+
+ test = Vector2d();
+ test -= Vector2d(int_max, int_max);
+ EXPECT_EQ(test, Vector2d(-int_max, -int_max));
+
+ test = Vector2d();
+ test -= Vector2d(int_min, int_min);
+ EXPECT_EQ(test, max_vector);
+
+ test = Vector2d(10, 20);
+ test -= Vector2d(int_min, int_min);
+ EXPECT_EQ(test, max_vector);
+
+ test = Vector2d(-10, -20);
+ test -= Vector2d(int_max, int_max);
+ EXPECT_EQ(test, min_vector);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/vector3d_f.cc b/ui/gfx/geometry/vector3d_f.cc
new file mode 100644
index 0000000000..749e330f27
--- /dev/null
+++ b/ui/gfx/geometry/vector3d_f.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/vector3d_f.h"
+
+#include <cmath>
+
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/geometry/angle_conversions.h"
+
+namespace {
+const double kEpsilon = 1.0e-6;
+}
+
+namespace gfx {
+
+std::string Vector3dF::ToString() const {
+ return base::StringPrintf("[%f %f %f]", x_, y_, z_);
+}
+
+bool Vector3dF::IsZero() const {
+ return x_ == 0 && y_ == 0 && z_ == 0;
+}
+
+void Vector3dF::Add(const Vector3dF& other) {
+ x_ += other.x_;
+ y_ += other.y_;
+ z_ += other.z_;
+}
+
+void Vector3dF::Subtract(const Vector3dF& other) {
+ x_ -= other.x_;
+ y_ -= other.y_;
+ z_ -= other.z_;
+}
+
+double Vector3dF::LengthSquared() const {
+ return static_cast<double>(x_) * x_ + static_cast<double>(y_) * y_ +
+ static_cast<double>(z_) * z_;
+}
+
+float Vector3dF::Length() const {
+ return static_cast<float>(std::sqrt(LengthSquared()));
+}
+
+void Vector3dF::Scale(float x_scale, float y_scale, float z_scale) {
+ x_ *= x_scale;
+ y_ *= y_scale;
+ z_ *= z_scale;
+}
+
+void Vector3dF::Cross(const Vector3dF& other) {
+ double dx = x_;
+ double dy = y_;
+ double dz = z_;
+ float x = static_cast<float>(dy * other.z() - dz * other.y());
+ float y = static_cast<float>(dz * other.x() - dx * other.z());
+ float z = static_cast<float>(dx * other.y() - dy * other.x());
+ x_ = x;
+ y_ = y;
+ z_ = z;
+}
+
+bool Vector3dF::GetNormalized(Vector3dF* out) const {
+ double length_squared = LengthSquared();
+ *out = *this;
+ if (length_squared < kEpsilon * kEpsilon)
+ return false;
+ out->Scale(1 / sqrt(length_squared));
+ return true;
+}
+
+float DotProduct(const Vector3dF& lhs, const Vector3dF& rhs) {
+ return lhs.x() * rhs.x() + lhs.y() * rhs.y() + lhs.z() * rhs.z();
+}
+
+Vector3dF ScaleVector3d(const Vector3dF& v,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ Vector3dF scaled_v(v);
+ scaled_v.Scale(x_scale, y_scale, z_scale);
+ return scaled_v;
+}
+
+float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
+ const gfx::Vector3dF& other) {
+ return gfx::RadToDeg(
+ std::acos(gfx::DotProduct(base, other) / base.Length() / other.Length()));
+}
+
+float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
+ const gfx::Vector3dF& other,
+ const gfx::Vector3dF& normal) {
+ float angle = AngleBetweenVectorsInDegrees(base, other);
+ gfx::Vector3dF cross(base);
+ cross.Cross(other);
+
+ // If the dot product of this cross product is normal, it means that the
+ // shortest angle between |base| and |other| was counterclockwise with respect
+ // to the surface represented by |normal| and this angle must be reversed.
+ if (gfx::DotProduct(cross, normal) > 0.0f)
+ angle = 360.0f - angle;
+ return angle;
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/vector3d_f.h b/ui/gfx/geometry/vector3d_f.h
new file mode 100644
index 0000000000..0e5e43713a
--- /dev/null
+++ b/ui/gfx/geometry/vector3d_f.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines a simple float vector class. This class is used to indicate a
+// distance in two dimensions between two points. Subtracting two points should
+// produce a vector, and adding a vector to a point produces the point at the
+// vector's distance from the original point.
+
+#ifndef UI_GFX_GEOMETRY_VECTOR3D_F_H_
+#define UI_GFX_GEOMETRY_VECTOR3D_F_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "ui/gfx/geometry/vector2d_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+class GFX_EXPORT Vector3dF {
+ public:
+ constexpr Vector3dF() : x_(0), y_(0), z_(0) {}
+ constexpr Vector3dF(float x, float y, float z) : x_(x), y_(y), z_(z) {}
+
+ constexpr explicit Vector3dF(const Vector2dF& other)
+ : x_(other.x()), y_(other.y()), z_(0) {}
+
+ constexpr float x() const { return x_; }
+ void set_x(float x) { x_ = x; }
+
+ constexpr float y() const { return y_; }
+ void set_y(float y) { y_ = y; }
+
+ constexpr float z() const { return z_; }
+ void set_z(float z) { z_ = z; }
+
+ // True if all components of the vector are 0.
+ bool IsZero() const;
+
+ // Add the components of the |other| vector to the current vector.
+ void Add(const Vector3dF& other);
+ // Subtract the components of the |other| vector from the current vector.
+ void Subtract(const Vector3dF& other);
+
+ void operator+=(const Vector3dF& other) { Add(other); }
+ void operator-=(const Vector3dF& other) { Subtract(other); }
+
+ void SetToMin(const Vector3dF& other) {
+ x_ = x_ <= other.x_ ? x_ : other.x_;
+ y_ = y_ <= other.y_ ? y_ : other.y_;
+ z_ = z_ <= other.z_ ? z_ : other.z_;
+ }
+
+ void SetToMax(const Vector3dF& other) {
+ x_ = x_ >= other.x_ ? x_ : other.x_;
+ y_ = y_ >= other.y_ ? y_ : other.y_;
+ z_ = z_ >= other.z_ ? z_ : other.z_;
+ }
+
+ // Gives the square of the diagonal length of the vector.
+ double LengthSquared() const;
+ // Gives the diagonal length of the vector.
+ float Length() const;
+
+ // Scale all components of the vector by |scale|.
+ void Scale(float scale) { Scale(scale, scale, scale); }
+ // Scale the each component of the vector by the given scale factors.
+ void Scale(float x_scale, float y_scale, float z_scale);
+
+ // Take the cross product of this vector with |other| and become the result.
+ void Cross(const Vector3dF& other);
+
+ // |out| is assigned a unit-length vector in the direction of |this| iff
+ // this function returns true. It can return false if |this| is too short.
+ bool GetNormalized(Vector3dF* out) const;
+
+ std::string ToString() const;
+
+ private:
+ float x_;
+ float y_;
+ float z_;
+};
+
+inline bool operator==(const Vector3dF& lhs, const Vector3dF& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z();
+}
+
+inline Vector3dF operator-(const Vector3dF& v) {
+ return Vector3dF(-v.x(), -v.y(), -v.z());
+}
+
+inline Vector3dF operator+(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Add(rhs);
+ return result;
+}
+
+inline Vector3dF operator-(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Add(-rhs);
+ return result;
+}
+
+// Return the cross product of two vectors.
+inline Vector3dF CrossProduct(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Cross(rhs);
+ return result;
+}
+
+// Return the dot product of two vectors.
+GFX_EXPORT float DotProduct(const Vector3dF& lhs, const Vector3dF& rhs);
+
+// Return a vector that is |v| scaled by the given scale factors along each
+// axis.
+GFX_EXPORT Vector3dF ScaleVector3d(const Vector3dF& v,
+ float x_scale,
+ float y_scale,
+ float z_scale);
+
+// Return a vector that is |v| scaled by the components of |s|
+inline Vector3dF ScaleVector3d(const Vector3dF& v, const Vector3dF& s) {
+ return ScaleVector3d(v, s.x(), s.y(), s.z());
+}
+
+// Return a vector that is |v| scaled by the given scale factor.
+inline Vector3dF ScaleVector3d(const Vector3dF& v, float scale) {
+ return ScaleVector3d(v, scale, scale, scale);
+}
+
+// Returns the angle between |base| and |other| in degrees.
+GFX_EXPORT float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
+ const gfx::Vector3dF& other);
+
+// Returns the clockwise angle between |base| and |other| where |normal| is the
+// normal of the virtual surface to measure clockwise according to.
+GFX_EXPORT float ClockwiseAngleBetweenVectorsInDegrees(
+ const gfx::Vector3dF& base,
+ const gfx::Vector3dF& other,
+ const gfx::Vector3dF& normal);
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const Vector3dF& vector, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_VECTOR3D_F_H_
diff --git a/ui/gfx/geometry/vector3d_unittest.cc b/ui/gfx/geometry/vector3d_unittest.cc
new file mode 100644
index 0000000000..12ee757346
--- /dev/null
+++ b/ui/gfx/geometry/vector3d_unittest.cc
@@ -0,0 +1,347 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <cmath>
+#include <limits>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+TEST(Vector3dTest, IsZero) {
+ gfx::Vector3dF float_zero(0, 0, 0);
+ gfx::Vector3dF float_nonzero(0.1f, -0.1f, 0.1f);
+
+ EXPECT_TRUE(float_zero.IsZero());
+ EXPECT_FALSE(float_nonzero.IsZero());
+}
+
+TEST(Vector3dTest, Add) {
+ gfx::Vector3dF f1(3.1f, 5.1f, 2.7f);
+ gfx::Vector3dF f2(4.3f, -1.3f, 8.1f);
+
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF actual;
+ } float_tests[] = {
+ { gfx::Vector3dF(3.1F, 5.1F, 2.7f), f1 + gfx::Vector3dF() },
+ { gfx::Vector3dF(3.1f + 4.3f, 5.1f - 1.3f, 2.7f + 8.1f), f1 + f2 },
+ { gfx::Vector3dF(3.1f - 4.3f, 5.1f + 1.3f, 2.7f - 8.1f), f1 - f2 }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector3dTest, Negative) {
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF actual;
+ } float_tests[] = {
+ { gfx::Vector3dF(-0.0f, -0.0f, -0.0f), -gfx::Vector3dF(0, 0, 0) },
+ { gfx::Vector3dF(-0.3f, -0.3f, -0.3f), -gfx::Vector3dF(0.3f, 0.3f, 0.3f) },
+ { gfx::Vector3dF(0.3f, 0.3f, 0.3f), -gfx::Vector3dF(-0.3f, -0.3f, -0.3f) },
+ { gfx::Vector3dF(-0.3f, 0.3f, -0.3f), -gfx::Vector3dF(0.3f, -0.3f, 0.3f) },
+ { gfx::Vector3dF(0.3f, -0.3f, -0.3f), -gfx::Vector3dF(-0.3f, 0.3f, 0.3f) },
+ { gfx::Vector3dF(-0.3f, -0.3f, 0.3f), -gfx::Vector3dF(0.3f, 0.3f, -0.3f) }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector3dTest, Scale) {
+ float triple_values[][6] = {
+ { 4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, -1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, -1.2f -1.8f, 3.3f, 5.6f, 4.2f },
+
+ { 4.5f, 1.2f, 1.8f, 3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, 1.8f, -3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, -1.8f, 3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, -1.8f, -3.3f, -5.6f, -4.2f },
+
+ { -4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, 1.8f, 0, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 0, 5.6f, 4.2f },
+
+ { -4.5f, 1.2f, 1.8f, 3.3f, 0, 4.2f },
+ { 4.5f, 0, 1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 0, 4.2f },
+ { 4.5f, 0, -1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 0 },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 0 },
+
+ { 0, 1.2f, 0, 3.3f, 5.6f, 4.2f },
+ { 0, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f }
+ };
+
+ for (size_t i = 0; i < arraysize(triple_values); ++i) {
+ gfx::Vector3dF v(triple_values[i][0],
+ triple_values[i][1],
+ triple_values[i][2]);
+ v.Scale(triple_values[i][3], triple_values[i][4], triple_values[i][5]);
+ EXPECT_EQ(triple_values[i][0] * triple_values[i][3], v.x());
+ EXPECT_EQ(triple_values[i][1] * triple_values[i][4], v.y());
+ EXPECT_EQ(triple_values[i][2] * triple_values[i][5], v.z());
+
+ Vector3dF v2 = ScaleVector3d(
+ gfx::Vector3dF(triple_values[i][0],
+ triple_values[i][1],
+ triple_values[i][2]),
+ triple_values[i][3], triple_values[i][4], triple_values[i][5]);
+ EXPECT_EQ(triple_values[i][0] * triple_values[i][3], v2.x());
+ EXPECT_EQ(triple_values[i][1] * triple_values[i][4], v2.y());
+ EXPECT_EQ(triple_values[i][2] * triple_values[i][5], v2.z());
+ }
+
+ float single_values[][4] = {
+ { 4.5f, 1.2f, 1.8f, 3.3f },
+ { 4.5f, -1.2f, 1.8f, 3.3f },
+ { 4.5f, 1.2f, -1.8f, 3.3f },
+ { 4.5f, -1.2f, -1.8f, 3.3f },
+ { -4.5f, 1.2f, 3.3f },
+ { -4.5f, 1.2f, 0 },
+ { -4.5f, 1.2f, 1.8f, 3.3f },
+ { -4.5f, 1.2f, 1.8f, 0 },
+ { 4.5f, 0, 1.8f, 3.3f },
+ { 0, 1.2f, 1.8f, 3.3f },
+ { 4.5f, 0, 1.8f, 3.3f },
+ { 0, 1.2f, 1.8f, 3.3f },
+ { 4.5f, 1.2f, 0, 3.3f },
+ { 4.5f, 1.2f, 0, 3.3f }
+ };
+
+ for (size_t i = 0; i < arraysize(single_values); ++i) {
+ gfx::Vector3dF v(single_values[i][0],
+ single_values[i][1],
+ single_values[i][2]);
+ v.Scale(single_values[i][3]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][3], v.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][3], v.y());
+ EXPECT_EQ(single_values[i][2] * single_values[i][3], v.z());
+
+ Vector3dF v2 = ScaleVector3d(
+ gfx::Vector3dF(single_values[i][0],
+ single_values[i][1],
+ single_values[i][2]),
+ single_values[i][3]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][3], v2.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][3], v2.y());
+ EXPECT_EQ(single_values[i][2] * single_values[i][3], v2.z());
+ }
+}
+
+TEST(Vector3dTest, Length) {
+ float float_values[][3] = {
+ { 0, 0, 0 },
+ { 10.5f, 20.5f, 8.5f },
+ { 20.5f, 10.5f, 8.5f },
+ { 8.5f, 20.5f, 10.5f },
+ { 10.5f, 8.5f, 20.5f },
+ { -10.5f, -20.5f, -8.5f },
+ { -20.5f, 10.5f, -8.5f },
+ { -8.5f, -20.5f, -10.5f },
+ { -10.5f, -8.5f, -20.5f },
+ { 10.5f, -20.5f, 8.5f },
+ { -10.5f, 20.5f, 8.5f },
+ { 10.5f, -20.5f, -8.5f },
+ { -10.5f, 20.5f, -8.5f },
+ // A large vector that fails if the Length function doesn't use
+ // double precision internally.
+ { 1236278317862780234892374893213178027.12122348904204230f,
+ 335890352589839028212313231225425134332.38123f,
+ 27861786423846742743236423478236784678.236713617231f }
+ };
+
+ for (size_t i = 0; i < arraysize(float_values); ++i) {
+ double v0 = float_values[i][0];
+ double v1 = float_values[i][1];
+ double v2 = float_values[i][2];
+ double length_squared =
+ static_cast<double>(v0) * v0 +
+ static_cast<double>(v1) * v1 +
+ static_cast<double>(v2) * v2;
+ double length = std::sqrt(length_squared);
+ gfx::Vector3dF vector(v0, v1, v2);
+ EXPECT_DOUBLE_EQ(length_squared, vector.LengthSquared());
+ EXPECT_FLOAT_EQ(static_cast<float>(length), vector.Length());
+ }
+}
+
+TEST(Vector3dTest, DotProduct) {
+ const struct {
+ float expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ { 0, gfx::Vector3dF(1, 0, 0), gfx::Vector3dF(0, 1, 1) },
+ { 0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(1, 0, 1) },
+ { 0, gfx::Vector3dF(0, 0, 1), gfx::Vector3dF(1, 1, 0) },
+
+ { 3, gfx::Vector3dF(1, 1, 1), gfx::Vector3dF(1, 1, 1) },
+
+ { 1.2f, gfx::Vector3dF(1.2f, -1.2f, 1.2f), gfx::Vector3dF(1, 1, 1) },
+ { 1.2f, gfx::Vector3dF(1, 1, 1), gfx::Vector3dF(1.2f, -1.2f, 1.2f) },
+
+ { 38.72f,
+ gfx::Vector3dF(1.1f, 2.2f, 3.3f), gfx::Vector3dF(4.4f, 5.5f, 6.6f) }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ float actual = gfx::DotProduct(tests[i].input1, tests[i].input2);
+ EXPECT_EQ(tests[i].expected, actual);
+ }
+}
+
+TEST(Vector3dTest, CrossProduct) {
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ { Vector3dF(), Vector3dF(), Vector3dF(1, 1, 1) },
+ { Vector3dF(), Vector3dF(1, 1, 1), Vector3dF() },
+ { Vector3dF(), Vector3dF(1, 1, 1), Vector3dF(1, 1, 1) },
+ { Vector3dF(),
+ Vector3dF(1.6f, 10.6f, -10.6f),
+ Vector3dF(1.6f, 10.6f, -10.6f) },
+
+ { Vector3dF(1, -1, 0), Vector3dF(1, 1, 1), Vector3dF(0, 0, 1) },
+ { Vector3dF(-1, 0, 1), Vector3dF(1, 1, 1), Vector3dF(0, 1, 0) },
+ { Vector3dF(0, 1, -1), Vector3dF(1, 1, 1), Vector3dF(1, 0, 0) },
+
+ { Vector3dF(-1, 1, 0), Vector3dF(0, 0, 1), Vector3dF(1, 1, 1) },
+ { Vector3dF(1, 0, -1), Vector3dF(0, 1, 0), Vector3dF(1, 1, 1) },
+ { Vector3dF(0, -1, 1), Vector3dF(1, 0, 0), Vector3dF(1, 1, 1) }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ SCOPED_TRACE(i);
+ Vector3dF actual = gfx::CrossProduct(tests[i].input1, tests[i].input2);
+ EXPECT_EQ(tests[i].expected.ToString(), actual.ToString());
+ }
+}
+
+TEST(Vector3dFTest, ClampVector3dF) {
+ Vector3dF a;
+
+ a = Vector3dF(3.5f, 5.5f, 7.5f);
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(2, 4.5f, 6.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 5.5f, 7.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(4.5f, 2, 6.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 6.5f, 6.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 6.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 5.5f, 8.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 6.5f, 8.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(8.5f, 10.5f, 12.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+
+ a.SetToMin(Vector3dF(9.5f, 11.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(8.5f, 10.5f, 12.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(7.5f, 11.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(9.5f, 9.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 9.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(9.5f, 11.5f, 11.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 9.5f, 11.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(3.5f, 5.5f, 7.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+}
+
+TEST(Vector3dTest, AngleBetweenVectorsInDegress) {
+ const struct {
+ float expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ {0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 1, 0)},
+ {90, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, 1)},
+ {45,
+ gfx::Vector3dF(0, 1, 0),
+ gfx::Vector3dF(0, 0.70710678188f, 0.70710678188f)},
+ {180, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, -1, 0)},
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ float actual =
+ gfx::AngleBetweenVectorsInDegrees(tests[i].input1, tests[i].input2);
+ EXPECT_FLOAT_EQ(tests[i].expected, actual);
+ actual =
+ gfx::AngleBetweenVectorsInDegrees(tests[i].input2, tests[i].input1);
+ EXPECT_FLOAT_EQ(tests[i].expected, actual);
+ }
+}
+
+TEST(Vector3dTest, ClockwiseAngleBetweenVectorsInDegress) {
+ const struct {
+ float expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ {0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 1, 0)},
+ {90, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, -1)},
+ {45,
+ gfx::Vector3dF(0, -1, 0),
+ gfx::Vector3dF(0, -0.70710678188f, 0.70710678188f)},
+ {180, gfx::Vector3dF(0, -1, 0), gfx::Vector3dF(0, 1, 0)},
+ {270, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, 1)},
+ };
+
+ const gfx::Vector3dF normal_vector(1.0f, 0.0f, 0.0f);
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ float actual = gfx::ClockwiseAngleBetweenVectorsInDegrees(
+ tests[i].input1, tests[i].input2, normal_vector);
+ EXPECT_FLOAT_EQ(tests[i].expected, actual);
+ actual = -gfx::ClockwiseAngleBetweenVectorsInDegrees(
+ tests[i].input2, tests[i].input1, normal_vector);
+ if (actual < 0.0f)
+ actual += 360.0f;
+ EXPECT_FLOAT_EQ(tests[i].expected, actual);
+ }
+}
+
+TEST(Vector3dTest, GetNormalized) {
+ const struct {
+ bool expected;
+ gfx::Vector3dF v;
+ gfx::Vector3dF normalized;
+ } tests[] = {
+ {false, gfx::Vector3dF(0, 0, 0), gfx::Vector3dF(0, 0, 0)},
+ {false,
+ gfx::Vector3dF(std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min()),
+ gfx::Vector3dF(std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min())},
+ {true, gfx::Vector3dF(1, 0, 0), gfx::Vector3dF(1, 0, 0)},
+ {true, gfx::Vector3dF(std::numeric_limits<float>::max(), 0, 0),
+ gfx::Vector3dF(1, 0, 0)},
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ gfx::Vector3dF n;
+ EXPECT_EQ(tests[i].expected, tests[i].v.GetNormalized(&n));
+ EXPECT_EQ(tests[i].normalized.ToString(), n.ToString());
+ }
+}
+
+} // namespace gfx
diff --git a/ui/gfx/range/BUILD.gn b/ui/gfx/range/BUILD.gn
deleted file mode 100644
index 2a2568a287..0000000000
--- a/ui/gfx/range/BUILD.gn
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/jumbo.gni")
-
-jumbo_component("range") {
- sources = [
- "gfx_range_export.h",
- "range.cc",
- "range.h",
- "range_f.cc",
- "range_f.h",
- "range_mac.mm",
- "range_win.cc",
- ]
-
- if (is_ios) {
- set_sources_assignment_filter([])
- sources += [ "range_mac.mm" ]
- set_sources_assignment_filter(sources_assignment_filter)
- }
-
- configs += [
- # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
- "//build/config/compiler:no_size_t_to_int_warning",
- ]
-
- defines = [ "GFX_RANGE_IMPLEMENTATION" ]
-
- deps = [
- "//base",
- "//ui/gfx:gfx_export",
- ]
-}
diff --git a/ui/gfx/range/mojo/BUILD.gn b/ui/gfx/range/mojo/BUILD.gn
deleted file mode 100644
index b6d458dbc9..0000000000
--- a/ui/gfx/range/mojo/BUILD.gn
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//mojo/public/tools/bindings/mojom.gni")
-
-# This target does NOT depend on skia. One can depend on this target to avoid
-# picking up a dependency on skia.
-mojom("mojo") {
- sources = [
- "range.mojom",
- ]
-}
-
-mojom("test_interfaces") {
- sources = [
- "range_traits_test_service.mojom",
- ]
-
- public_deps = [
- ":mojo",
- ]
-}
-
-source_set("unit_test") {
- testonly = true
-
- sources = [
- "range_struct_traits_unittest.cc",
- ]
-
- deps = [
- ":test_interfaces",
- "//base",
- "//mojo/public/cpp/bindings",
- "//testing/gtest",
- "//ui/gfx/range",
- ]
-}
-
-source_set("struct_traits") {
- sources = [
- "range_struct_traits.h",
- ]
- public_deps = [
- ":mojo_shared_cpp_sources",
- "//ui/gfx/range",
- ]
-}