@@ -630,6 +630,7 @@ impl<W: Write> Writer<W> {
630630 /// }
631631 /// ```
632632 pub fn finish ( & mut self ) -> Result < ( ) > {
633+ // Flush any remaining data in the current block
633634 impl_flush_block (
634635 & mut self . inner ,
635636 & mut self . cblock ,
@@ -639,6 +640,8 @@ impl<W: Write> Writer<W> {
639640 ) ?;
640641 self . inner . flush ( ) ?;
641642
643+ // Always write the index - this is critical for VBQ file validity
644+ // The index_written flag prevents double-writing on subsequent finish() calls
642645 if !self . index_written {
643646 self . write_index ( ) ?;
644647 self . index_written = true ;
@@ -1511,4 +1514,93 @@ mod tests {
15111514
15121515 Ok ( ( ) )
15131516 }
1517+
1518+ #[ test]
1519+ fn test_index_always_written_on_finish ( ) -> super :: Result < ( ) > {
1520+ use crate :: vbq:: index:: INDEX_END_MAGIC ;
1521+ use byteorder:: { ByteOrder , LittleEndian } ;
1522+
1523+ // Create a writer with some records
1524+ let header = FileHeaderBuilder :: new ( ) . build ( ) ;
1525+ let mut writer = WriterBuilder :: default ( ) . header ( header) . build ( Vec :: new ( ) ) ?;
1526+
1527+ // Write some records
1528+ for i in 0 ..10 {
1529+ let record = SequencingRecordBuilder :: default ( )
1530+ . s_seq ( b"ACGTACGTACGT" )
1531+ . flag ( i)
1532+ . build ( ) ?;
1533+ writer. push ( record) ?;
1534+ }
1535+
1536+ // Finish the writer
1537+ writer. finish ( ) ?;
1538+
1539+ // Get the written bytes
1540+ let bytes = & writer. inner ;
1541+
1542+ // Verify the file ends with the index magic number
1543+ assert ! ( bytes. len( ) >= 8 , "File is too short to contain index" ) ;
1544+ let magic_offset = bytes. len ( ) - 8 ;
1545+ let magic = LittleEndian :: read_u64 ( & bytes[ magic_offset..] ) ;
1546+ assert_eq ! (
1547+ magic, INDEX_END_MAGIC ,
1548+ "Index magic number not found at end of file"
1549+ ) ;
1550+
1551+ // Verify we can read the index size
1552+ assert ! ( bytes. len( ) >= 16 , "File is too short to contain index size" ) ;
1553+ let size_offset = bytes. len ( ) - 16 ;
1554+ let index_size = LittleEndian :: read_u64 ( & bytes[ size_offset..size_offset + 8 ] ) ;
1555+ assert ! ( index_size > 0 , "Index size should be greater than 0" ) ;
1556+
1557+ // Verify the index size makes sense (should be less than total file size)
1558+ assert ! (
1559+ index_size < bytes. len( ) as u64 ,
1560+ "Index size is larger than file"
1561+ ) ;
1562+
1563+ Ok ( ( ) )
1564+ }
1565+
1566+ #[ test]
1567+ fn test_finish_idempotent ( ) -> super :: Result < ( ) > {
1568+ use crate :: vbq:: index:: INDEX_END_MAGIC ;
1569+ use byteorder:: { ByteOrder , LittleEndian } ;
1570+
1571+ // Create a writer
1572+ let header = FileHeaderBuilder :: new ( ) . build ( ) ;
1573+ let mut writer = WriterBuilder :: default ( ) . header ( header) . build ( Vec :: new ( ) ) ?;
1574+
1575+ // Write some records
1576+ for i in 0 ..10 {
1577+ let record = SequencingRecordBuilder :: default ( )
1578+ . s_seq ( b"ACGTACGTACGT" )
1579+ . flag ( i)
1580+ . build ( ) ?;
1581+ writer. push ( record) ?;
1582+ }
1583+
1584+ // Call finish() multiple times
1585+ writer. finish ( ) ?;
1586+ let size_after_first_finish = writer. inner . len ( ) ;
1587+
1588+ writer. finish ( ) ?;
1589+ let size_after_second_finish = writer. inner . len ( ) ;
1590+
1591+ writer. finish ( ) ?;
1592+ let size_after_third_finish = writer. inner . len ( ) ;
1593+
1594+ // All sizes should be the same - index should only be written once
1595+ assert_eq ! ( size_after_first_finish, size_after_second_finish) ;
1596+ assert_eq ! ( size_after_second_finish, size_after_third_finish) ;
1597+
1598+ // Verify only one index magic number at the end
1599+ let bytes = & writer. inner ;
1600+ let magic_offset = bytes. len ( ) - 8 ;
1601+ let magic = LittleEndian :: read_u64 ( & bytes[ magic_offset..] ) ;
1602+ assert_eq ! ( magic, INDEX_END_MAGIC ) ;
1603+
1604+ Ok ( ( ) )
1605+ }
15141606}
0 commit comments