@@ -572,3 +572,238 @@ fn prop_path_validation() {
572572 }
573573 } ) ;
574574}
575+
576+ // ============================================================
577+ // File Content Modification Properties (Phase 6 M2)
578+ // ============================================================
579+
580+ /// Property: File truncation is reversible with saved content
581+ ///
582+ /// Pending Lean theorem: `truncate_restore_reversible` (FileContentOperations.lean)
583+ /// ```lean
584+ /// theorem truncate_restore_reversible (p : Path) (fs : Filesystem) :
585+ /// let original := readFile p fs
586+ /// let truncated := writeFile p "" fs
587+ /// let restored := writeFile p original truncated
588+ /// restored = fs
589+ /// ```
590+ ///
591+ /// Validates: Output redirection (>) undo restores original content
592+ #[ test]
593+ fn prop_truncate_restore_reversible ( ) {
594+ proptest ! ( |(
595+ path in valid_path_strategy( ) ,
596+ original_content in prop:: collection:: vec( any:: <u8 >( ) , 1 ..500 )
597+ ) | {
598+ let temp = TempDir :: new( ) . unwrap( ) ;
599+ let target = temp. path( ) . join( & path) ;
600+
601+ // Create file with original content
602+ fs:: write( & target, & original_content) . unwrap( ) ;
603+
604+ // Save original for undo
605+ let saved_content = fs:: read( & target) . unwrap( ) ;
606+ prop_assert_eq!( saved_content. clone( ) , original_content. clone( ) ) ;
607+
608+ // Truncate (simulate > redirection)
609+ fs:: write( & target, b"" ) . unwrap( ) ;
610+ let truncated = fs:: read( & target) . unwrap( ) ;
611+ prop_assert_eq!( truncated. len( ) , 0 , "File should be truncated to zero" ) ;
612+
613+ // Restore original (undo)
614+ fs:: write( & target, & saved_content) . unwrap( ) ;
615+
616+ // Verify restoration
617+ let restored = fs:: read( & target) . unwrap( ) ;
618+ prop_assert_eq!(
619+ restored,
620+ original_content,
621+ "Restore should return to original state"
622+ ) ;
623+ } ) ;
624+ }
625+
626+ /// Property: File append is reversible with truncation to original size
627+ ///
628+ /// Pending Lean theorem: `append_truncate_reversible` (FileContentOperations.lean)
629+ /// ```lean
630+ /// theorem append_truncate_reversible (p : Path) (data : String) (fs : Filesystem) :
631+ /// let original_size := fileSize p fs
632+ /// let appended := appendFile p data fs
633+ /// let restored := truncateFile p original_size appended
634+ /// restored = fs
635+ /// ```
636+ ///
637+ /// Validates: Append redirection (>>) undo truncates to original size
638+ #[ test]
639+ fn prop_append_truncate_reversible ( ) {
640+ proptest ! ( |(
641+ path in valid_path_strategy( ) ,
642+ original_content in prop:: collection:: vec( any:: <u8 >( ) , 1 ..500 ) ,
643+ appended_content in prop:: collection:: vec( any:: <u8 >( ) , 1 ..500 )
644+ ) | {
645+ use std:: fs:: OpenOptions ;
646+ use std:: io:: Write ;
647+
648+ let temp = TempDir :: new( ) . unwrap( ) ;
649+ let target = temp. path( ) . join( & path) ;
650+
651+ // Create file with original content
652+ fs:: write( & target, & original_content) . unwrap( ) ;
653+
654+ // Record original size
655+ let original_size = fs:: metadata( & target) . unwrap( ) . len( ) ;
656+ prop_assert_eq!( original_size, original_content. len( ) as u64 ) ;
657+
658+ // Append (simulate >> redirection)
659+ let mut file = OpenOptions :: new( )
660+ . append( true )
661+ . open( & target)
662+ . unwrap( ) ;
663+ file. write_all( & appended_content) . unwrap( ) ;
664+ drop( file) ;
665+
666+ // Verify append happened
667+ let appended = fs:: read( & target) . unwrap( ) ;
668+ let expected_size = original_content. len( ) + appended_content. len( ) ;
669+ prop_assert_eq!( appended. len( ) , expected_size, "Content should be appended" ) ;
670+
671+ // Truncate to original size (undo)
672+ let file = OpenOptions :: new( )
673+ . write( true )
674+ . open( & target)
675+ . unwrap( ) ;
676+ file. set_len( original_size) . unwrap( ) ;
677+ drop( file) ;
678+
679+ // Verify restoration
680+ let restored = fs:: read( & target) . unwrap( ) ;
681+ prop_assert_eq!(
682+ restored,
683+ original_content,
684+ "Truncate to original size should restore original content"
685+ ) ;
686+ } ) ;
687+ }
688+
689+ /// Property: Multiple truncations are reversible in sequence
690+ ///
691+ /// Validates: Multiple > redirections can be undone in reverse order
692+ #[ test]
693+ fn prop_multiple_truncates_reversible ( ) {
694+ proptest ! ( |(
695+ path in valid_path_strategy( ) ,
696+ content1 in prop:: collection:: vec( any:: <u8 >( ) , 1 ..200 ) ,
697+ content2 in prop:: collection:: vec( any:: <u8 >( ) , 1 ..200 ) ,
698+ content3 in prop:: collection:: vec( any:: <u8 >( ) , 1 ..200 )
699+ ) | {
700+ let temp = TempDir :: new( ) . unwrap( ) ;
701+ let target = temp. path( ) . join( & path) ;
702+
703+ // Initial state
704+ fs:: write( & target, & content1) . unwrap( ) ;
705+ let state1 = fs:: read( & target) . unwrap( ) ;
706+
707+ // Truncate to content2
708+ let saved1 = fs:: read( & target) . unwrap( ) ;
709+ fs:: write( & target, & content2) . unwrap( ) ;
710+ let state2 = fs:: read( & target) . unwrap( ) ;
711+
712+ // Truncate to content3
713+ let saved2 = fs:: read( & target) . unwrap( ) ;
714+ fs:: write( & target, & content3) . unwrap( ) ;
715+ let _state3 = fs:: read( & target) . unwrap( ) ;
716+
717+ // Undo sequence (reverse order)
718+ fs:: write( & target, & saved2) . unwrap( ) ;
719+ let after_undo1 = fs:: read( & target) . unwrap( ) ;
720+ prop_assert_eq!( after_undo1, state2, "First undo should restore state2" ) ;
721+
722+ fs:: write( & target, & saved1) . unwrap( ) ;
723+ let after_undo2 = fs:: read( & target) . unwrap( ) ;
724+ prop_assert_eq!( after_undo2. clone( ) , state1, "Second undo should restore state1" ) ;
725+ prop_assert_eq!( after_undo2, content1, "Should return to initial content" ) ;
726+ } ) ;
727+ }
728+
729+ /// Property: Append-truncate-append composition
730+ ///
731+ /// Validates: Complex modification sequences are reversible
732+ #[ test]
733+ fn prop_append_truncate_append ( ) {
734+ proptest ! ( |(
735+ path in valid_path_strategy( ) ,
736+ base in prop:: collection:: vec( any:: <u8 >( ) , 10 ..100 ) ,
737+ data1 in prop:: collection:: vec( any:: <u8 >( ) , 5 ..50 ) ,
738+ data2 in prop:: collection:: vec( any:: <u8 >( ) , 5 ..50 )
739+ ) | {
740+ use std:: fs:: OpenOptions ;
741+ use std:: io:: Write ;
742+
743+ let temp = TempDir :: new( ) . unwrap( ) ;
744+ let target = temp. path( ) . join( & path) ;
745+
746+ // Base state
747+ fs:: write( & target, & base) . unwrap( ) ;
748+ let base_size = fs:: metadata( & target) . unwrap( ) . len( ) ;
749+
750+ // Append data1
751+ let mut f = OpenOptions :: new( ) . append( true ) . open( & target) . unwrap( ) ;
752+ f. write_all( & data1) . unwrap( ) ;
753+ drop( f) ;
754+
755+ // Truncate to base
756+ let f = OpenOptions :: new( ) . write( true ) . open( & target) . unwrap( ) ;
757+ f. set_len( base_size) . unwrap( ) ;
758+ drop( f) ;
759+ let after_truncate1 = fs:: read( & target) . unwrap( ) ;
760+ prop_assert_eq!( after_truncate1, base. clone( ) , "Truncate should restore base" ) ;
761+
762+ // Append data2
763+ let mut f = OpenOptions :: new( ) . append( true ) . open( & target) . unwrap( ) ;
764+ f. write_all( & data2) . unwrap( ) ;
765+ drop( f) ;
766+
767+ // Truncate to base again
768+ let f = OpenOptions :: new( ) . write( true ) . open( & target) . unwrap( ) ;
769+ f. set_len( base_size) . unwrap( ) ;
770+ drop( f) ;
771+
772+ let final_state = fs:: read( & target) . unwrap( ) ;
773+ prop_assert_eq!( final_state, base, "Should return to base after second cycle" ) ;
774+ } ) ;
775+ }
776+
777+ /// Property: Zero-byte file operations
778+ ///
779+ /// Validates: Edge case handling for empty files
780+ #[ test]
781+ fn prop_empty_file_operations ( ) {
782+ proptest ! ( |( path in valid_path_strategy( ) ) | {
783+ let temp = TempDir :: new( ) . unwrap( ) ;
784+ let target = temp. path( ) . join( & path) ;
785+
786+ // Create empty file
787+ fs:: write( & target, b"" ) . unwrap( ) ;
788+ prop_assert!( target. exists( ) ) ;
789+ prop_assert_eq!( fs:: metadata( & target) . unwrap( ) . len( ) , 0 ) ;
790+
791+ // Append to empty file
792+ use std:: fs:: OpenOptions ;
793+ use std:: io:: Write ;
794+ let mut f = OpenOptions :: new( ) . append( true ) . open( & target) . unwrap( ) ;
795+ f. write_all( b"data" ) . unwrap( ) ;
796+ drop( f) ;
797+
798+ let content = fs:: read( & target) . unwrap( ) ;
799+ prop_assert_eq!( content, b"data" ) ;
800+
801+ // Truncate back to zero
802+ let f = OpenOptions :: new( ) . write( true ) . open( & target) . unwrap( ) ;
803+ f. set_len( 0 ) . unwrap( ) ;
804+ drop( f) ;
805+
806+ let final_content = fs:: read( & target) . unwrap( ) ;
807+ prop_assert_eq!( final_content. len( ) , 0 , "Should be empty again" ) ;
808+ } ) ;
809+ }
0 commit comments