|
3 | 3 | require 'stringio'
|
4 | 4 | require 'fileutils'
|
5 | 5 | require 'tempfile'
|
| 6 | +require 'active_support/concurrency/latch' |
6 | 7 |
|
7 | 8 | class LoggerTest < ActiveSupport::TestCase
|
8 | 9 | include MultibyteTestHelpers
|
@@ -113,21 +114,101 @@ def test_should_know_if_its_loglevel_is_below_a_given_level
|
113 | 114 | end
|
114 | 115 |
|
115 | 116 | def test_buffer_multibyte
|
| 117 | + @logger.level = Logger::INFO |
116 | 118 | @logger.info(UNICODE_STRING)
|
117 | 119 | @logger.info(BYTE_STRING)
|
118 | 120 | assert @output.string.include?(UNICODE_STRING)
|
119 | 121 | byte_string = @output.string.dup
|
120 | 122 | byte_string.force_encoding("ASCII-8BIT")
|
121 | 123 | assert byte_string.include?(BYTE_STRING)
|
122 | 124 | end
|
123 |
| - |
| 125 | + |
124 | 126 | def test_silencing_everything_but_errors
|
125 | 127 | @logger.silence do
|
126 | 128 | @logger.debug "NOT THERE"
|
127 | 129 | @logger.error "THIS IS HERE"
|
128 | 130 | end
|
129 |
| - |
| 131 | + |
130 | 132 | assert !@output.string.include?("NOT THERE")
|
131 | 133 | assert @output.string.include?("THIS IS HERE")
|
132 | 134 | end
|
| 135 | + |
| 136 | + def test_logger_level_per_object_thread_safety |
| 137 | + logger1 = Logger.new(StringIO.new) |
| 138 | + logger2 = Logger.new(StringIO.new) |
| 139 | + |
| 140 | + level = Logger::DEBUG |
| 141 | + assert_equal level, logger1.level, "Expected level #{level_name(level)}, got #{level_name(logger1.level)}" |
| 142 | + assert_equal level, logger2.level, "Expected level #{level_name(level)}, got #{level_name(logger2.level)}" |
| 143 | + |
| 144 | + logger1.level = Logger::ERROR |
| 145 | + assert_equal level, logger2.level, "Expected level #{level_name(level)}, got #{level_name(logger2.level)}" |
| 146 | + end |
| 147 | + |
| 148 | + def test_logger_level_main_thread_safety |
| 149 | + @logger.level = Logger::INFO |
| 150 | + assert_level(Logger::INFO) |
| 151 | + |
| 152 | + latch = ActiveSupport::Concurrency::Latch.new |
| 153 | + latch2 = ActiveSupport::Concurrency::Latch.new |
| 154 | + |
| 155 | + t = Thread.new do |
| 156 | + latch.await |
| 157 | + assert_level(Logger::INFO) |
| 158 | + latch2.release |
| 159 | + end |
| 160 | + |
| 161 | + @logger.silence(Logger::ERROR) do |
| 162 | + assert_level(Logger::ERROR) |
| 163 | + latch.release |
| 164 | + latch2.await |
| 165 | + end |
| 166 | + |
| 167 | + t.join |
| 168 | + end |
| 169 | + |
| 170 | + def test_logger_level_local_thread_safety |
| 171 | + @logger.level = Logger::INFO |
| 172 | + assert_level(Logger::INFO) |
| 173 | + |
| 174 | + thread_1_latch = ActiveSupport::Concurrency::Latch.new |
| 175 | + thread_2_latch = ActiveSupport::Concurrency::Latch.new |
| 176 | + |
| 177 | + threads = (1..2).collect do |thread_number| |
| 178 | + Thread.new do |
| 179 | + # force thread 2 to wait until thread 1 is already in @logger.silence |
| 180 | + thread_2_latch.await if thread_number == 2 |
| 181 | + |
| 182 | + @logger.silence(Logger::ERROR) do |
| 183 | + assert_level(Logger::ERROR) |
| 184 | + @logger.silence(Logger::DEBUG) do |
| 185 | + # allow thread 2 to finish but hold thread 1 |
| 186 | + if thread_number == 1 |
| 187 | + thread_2_latch.release |
| 188 | + thread_1_latch.await |
| 189 | + end |
| 190 | + assert_level(Logger::DEBUG) |
| 191 | + end |
| 192 | + end |
| 193 | + |
| 194 | + # allow thread 1 to finish |
| 195 | + assert_level(Logger::INFO) |
| 196 | + thread_1_latch.release if thread_number == 2 |
| 197 | + end |
| 198 | + end |
| 199 | + |
| 200 | + threads.each(&:join) |
| 201 | + assert_level(Logger::INFO) |
| 202 | + end |
| 203 | + |
| 204 | + private |
| 205 | + def level_name(level) |
| 206 | + ::Logger::Severity.constants.find do |severity| |
| 207 | + Logger.const_get(severity) == level |
| 208 | + end.to_s |
| 209 | + end |
| 210 | + |
| 211 | + def assert_level(level) |
| 212 | + assert_equal level, @logger.level, "Expected level #{level_name(level)}, got #{level_name(@logger.level)}" |
| 213 | + end |
133 | 214 | end
|
0 commit comments