@@ -20,6 +20,30 @@ def write(chunk)
2020 # drop
2121 end
2222 end
23+
24+ class DummyErrorOutputPlugin < DummyOutputPlugin
25+ def register_write ( &block )
26+ instance_variable_set ( "@write" , block )
27+ end
28+
29+ def initialize
30+ super
31+ @should_fail_writing = true
32+ @write = nil
33+ end
34+
35+ def recover
36+ @should_fail_writing = false
37+ end
38+
39+ def write ( chunk )
40+ if @should_fail_writing
41+ raise "failed writing chunk"
42+ else
43+ @write ? @write . call ( chunk ) : nil
44+ end
45+ end
46+ end
2347end
2448
2549class FileBufferTest < Test ::Unit ::TestCase
@@ -1311,4 +1335,120 @@ def compare_log(plugin, msg)
13111335 assert { not File . exist? ( "#{ @bufdir } /backup/worker0/#{ @id_output } /#{ @d . dump_unique_id_hex ( c2id ) } .log" ) }
13121336 end
13131337 end
1338+
1339+ sub_test_case 'evacuate_chunk' do
1340+ def setup
1341+ Fluent ::Test . setup
1342+
1343+ @now = Time . local ( 2025 , 5 , 30 , 17 , 0 , 0 )
1344+ @base_dir = File . expand_path ( "../../tmp/evacuate_chunk" , __FILE__ )
1345+ @buf_dir = File . join ( @base_dir , "buffer" )
1346+ @root_dir = File . join ( @base_dir , "root" )
1347+ FileUtils . mkdir_p ( @root_dir )
1348+ @output = nil
1349+
1350+ Fluent ::SystemConfig . overwrite_system_config ( "root_dir" => @root_dir ) do
1351+ Timecop . freeze ( @now )
1352+ yield
1353+ end
1354+ ensure
1355+ Timecop . return
1356+ stop_plugin ( @output )
1357+ FileUtils . rm_rf ( @base_dir )
1358+ end
1359+
1360+ def start_plugin ( plugin )
1361+ plugin . start
1362+ plugin . after_start
1363+ end
1364+
1365+ def stop_plugin ( plugin )
1366+ plugin . stop unless plugin . stopped?
1367+ plugin . before_shutdown unless plugin . before_shutdown?
1368+ plugin . shutdown unless plugin . shutdown?
1369+ plugin . after_shutdown unless plugin . after_shutdown?
1370+ plugin . close unless plugin . closed?
1371+ plugin . terminate unless plugin . terminated?
1372+ end
1373+
1374+ def configure_output ( id , chunk_key , buffer_conf )
1375+ @output = FluentPluginFileBufferTest ::DummyErrorOutputPlugin . new
1376+ @output . configure (
1377+ config_element ( 'ROOT' , '' , { '@id' => id } , [ config_element ( 'buffer' , chunk_key , buffer_conf ) ] )
1378+ )
1379+ end
1380+
1381+ def wait ( sec : 4 )
1382+ waiting ( sec ) do
1383+ Thread . pass until yield
1384+ end
1385+ end
1386+
1387+ def emit_events ( tag , es )
1388+ @output . interrupt_flushes
1389+ @output . emit_events ( "test.1" , dummy_event_stream )
1390+ @now += 1
1391+ Timecop . freeze ( @now )
1392+ @output . enqueue_thread_wait
1393+ @output . flush_thread_wakeup
1394+ end
1395+
1396+ def dummy_event_stream
1397+ Fluent ::ArrayEventStream . new ( [
1398+ [ event_time ( "2025-05-30 10:00:00" ) , { "message" => "data1" } ] ,
1399+ [ event_time ( "2025-05-30 10:10:00" ) , { "message" => "data2" } ] ,
1400+ [ event_time ( "2025-05-30 10:20:00" ) , { "message" => "data3" } ] ,
1401+ ] )
1402+ end
1403+
1404+ def evacuate_dir ( plugin_id )
1405+ File . join ( @root_dir , "buffer" , plugin_id )
1406+ end
1407+
1408+ test 'foo' do
1409+ plugin_id = "test_output"
1410+ buffer_conf = {
1411+ "path" => @buf_dir ,
1412+ "flush_mode" => "interval" ,
1413+ "flush_interval" => "1s" ,
1414+ "retry_type" => "periodic" ,
1415+ "retry_max_times" => 0 ,
1416+ "retry_randomize" => false ,
1417+ }
1418+
1419+ configure_output ( plugin_id , "tag" , buffer_conf )
1420+ start_plugin ( @output )
1421+
1422+ emit_events ( "test.1" , dummy_event_stream )
1423+
1424+ wait { @output . write_count > 0 && @output . num_errors > 0 }
1425+ wait { Dir . empty? ( @buf_dir ) }
1426+
1427+ # Assert evacuated files
1428+ evacuated_files = Dir . children ( evacuate_dir ( plugin_id ) ) . map do |child_name |
1429+ File . join ( evacuate_dir ( plugin_id ) , child_name )
1430+ end
1431+ assert { evacuated_files . size == 2 } # .log and .log.meta
1432+
1433+ # Put back evacuated files
1434+ FileUtils . move ( evacuated_files , @buf_dir )
1435+
1436+ # Restart plugin to load
1437+ stop_plugin ( @output )
1438+ configure_output ( plugin_id , "tag" , buffer_conf )
1439+ @output . recover
1440+ written_data = [ ]
1441+ @output . register_write do |chunk |
1442+ written_data << chunk . read
1443+ end
1444+ start_plugin ( @output )
1445+
1446+ wait { not written_data . empty? }
1447+
1448+ @output . log . out . logs . each do |log |
1449+ puts log
1450+ end
1451+ p written_data
1452+ end
1453+ end
13141454end
0 commit comments