@@ -121,103 +121,68 @@ describe('RENAME operation', () => {
121121    await  stop ( ) ; 
122122  } ) ; 
123123
124-   describe ( 'AppleDouble file handling ' ,  ( )  =>  { 
125-     test ( 'renames AppleDouble file when renaming main file ' ,  async  ( )  =>  { 
124+   describe ( 'change_info semantics ' ,  ( )  =>  { 
125+     test ( 'returns before < after on successful rename ' ,  async  ( )  =>  { 
126126      const  { client,  stop,  vol}  =  await  setupNfsClientServerTestbed ( ) ; 
127-       vol . writeFileSync ( '/export/test.txt' ,  'data' ) ; 
128-       vol . writeFileSync ( '/export/._test.txt' ,  'xattr-data' ) ; 
129-       const  res  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'test.txt' ,  'new.txt' ) ] ) ; 
127+       vol . writeFileSync ( '/export/old.txt' ,  'data' ) ; 
128+       const  res  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'old.txt' ,  'new.txt' ) ] ) ; 
130129      expect ( res . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
131-       expect ( vol . existsSync ( '/export/new.txt' ) ) . toBe ( true ) ; 
132-       expect ( vol . existsSync ( '/export/._new.txt' ) ) . toBe ( true ) ; 
133-       expect ( vol . existsSync ( '/export/test.txt' ) ) . toBe ( false ) ; 
134-       expect ( vol . existsSync ( '/export/._test.txt' ) ) . toBe ( false ) ; 
135-       await  stop ( ) ; 
136-     } ) ; 
137- 
138-     test ( 'succeeds even if AppleDouble file does not exist' ,  async  ( )  =>  { 
139-       const  { client,  stop,  vol}  =  await  setupNfsClientServerTestbed ( ) ; 
140-       vol . writeFileSync ( '/export/test.txt' ,  'data' ) ; 
141-       const  res  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'test.txt' ,  'new.txt' ) ] ) ; 
142-       expect ( res . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
143-       expect ( vol . existsSync ( '/export/new.txt' ) ) . toBe ( true ) ; 
144-       expect ( vol . existsSync ( '/export/._new.txt' ) ) . toBe ( false ) ; 
145-       await  stop ( ) ; 
146-     } ) ; 
147- 
148-     test ( 'renames AppleDouble file for TextEdit-style save workflow' ,  async  ( )  =>  { 
149-       const  { client,  stop,  vol}  =  await  setupNfsClientServerTestbed ( ) ; 
150-       vol . writeFileSync ( '/export/file.txt' ,  'original content' ) ; 
151-       vol . writeFileSync ( '/export/file.txt.sb-temp-u9VvVu' ,  'new content' ) ; 
152-       vol . writeFileSync ( '/export/._file.txt.sb-temp-u9VvVu' ,  'new xattr' ) ; 
153-       const  res  =  await  client . compound ( [ 
154-         nfs . PUTROOTFH ( ) , 
155-         nfs . SAVEFH ( ) , 
156-         nfs . RENAME ( 'file.txt.sb-temp-u9VvVu' ,  'file.txt' ) , 
157-       ] ) ; 
158-       expect ( res . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
159-       expect ( vol . existsSync ( '/export/file.txt' ) ) . toBe ( true ) ; 
160-       expect ( vol . existsSync ( '/export/._file.txt' ) ) . toBe ( true ) ; 
161-       expect ( vol . existsSync ( '/export/file.txt.sb-temp-u9VvVu' ) ) . toBe ( false ) ; 
162-       expect ( vol . existsSync ( '/export/._file.txt.sb-temp-u9VvVu' ) ) . toBe ( false ) ; 
163-       expect ( vol . readFileSync ( '/export/file.txt' ,  'utf8' ) ) . toBe ( 'new content' ) ; 
164-       expect ( vol . readFileSync ( '/export/._file.txt' ,  'utf8' ) ) . toBe ( 'new xattr' ) ; 
165-       await  stop ( ) ; 
166-     } ) ; 
167- 
168-     test ( 'handles AppleDouble file with long filenames (ID-type FH)' ,  async  ( )  =>  { 
169-       const  { client,  stop,  vol}  =  await  setupNfsClientServerTestbed ( ) ; 
170-       const  oldName  =  'long_filename_'  +  'x' . repeat ( 100 )  +  '.txt' ; 
171-       const  newName  =  'long_filename_'  +  'y' . repeat ( 100 )  +  '.txt' ; 
172-       vol . writeFileSync ( '/export/'  +  oldName ,  'data' ) ; 
173-       vol . writeFileSync ( '/export/._'  +  oldName ,  'xattr' ) ; 
174-       const  res  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( oldName ,  newName ) ] ) ; 
175-       expect ( res . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
176-       expect ( vol . existsSync ( '/export/'  +  newName ) ) . toBe ( true ) ; 
177-       expect ( vol . existsSync ( '/export/._'  +  newName ) ) . toBe ( true ) ; 
178-       expect ( vol . existsSync ( '/export/'  +  oldName ) ) . toBe ( false ) ; 
179-       expect ( vol . existsSync ( '/export/._'  +  oldName ) ) . toBe ( false ) ; 
180-       await  stop ( ) ; 
181-     } ) ; 
182- 
183-     test ( 'AppleDouble file handle remains valid after rename' ,  async  ( )  =>  { 
184-       const  { client,  stop,  vol}  =  await  setupNfsClientServerTestbed ( ) ; 
185-       const  oldName  =  'original_'  +  'a' . repeat ( 100 )  +  '.txt' ; 
186-       const  newName  =  'renamed_'  +  'b' . repeat ( 100 )  +  '.txt' ; 
187-       vol . writeFileSync ( '/export/'  +  oldName ,  'data' ) ; 
188-       vol . writeFileSync ( '/export/._'  +  oldName ,  'xattr' ) ; 
189-       const  lookupRes  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . LOOKUP ( '._'  +  oldName ) ,  nfs . GETFH ( ) ] ) ; 
190-       expect ( lookupRes . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
191-       const  appleDoubleFh  =  ( lookupRes . resarray [ 2 ]  as  msg . Nfsv4GetfhResponse ) . resok ! . object ; 
192-       const  renameRes  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( oldName ,  newName ) ] ) ; 
130+       const  renameRes  =  res . resarray [ 2 ]  as  msg . Nfsv4RenameResponse ; 
193131      expect ( renameRes . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
194-       const  getAttrRes  =  await  client . compound ( [ nfs . PUTFH ( appleDoubleFh ) ,  nfs . GETATTR ( [ 0x00000020 ] ) ] ) ; 
195-       expect ( getAttrRes . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
132+       if  ( renameRes . status  ===  Nfsv4Stat . NFS4_OK  &&  renameRes . resok )  { 
133+         const  sourceCinfo  =  renameRes . resok . sourceCinfo ; 
134+         const  targetCinfo  =  renameRes . resok . targetCinfo ; 
135+         expect ( sourceCinfo . atomic ) . toBe ( true ) ; 
136+         expect ( targetCinfo . atomic ) . toBe ( true ) ; 
137+         expect ( sourceCinfo . after ) . toBeGreaterThan ( sourceCinfo . before ) ; 
138+         expect ( targetCinfo . after ) . toBeGreaterThan ( targetCinfo . before ) ; 
139+         expect ( sourceCinfo . after  -  sourceCinfo . before ) . toBe ( 1n ) ; 
140+       } 
196141      await  stop ( ) ; 
197142    } ) ; 
198143
199-     test ( 'does not rename AppleDouble if it is a directory ' ,  async  ( )  =>  { 
144+     test ( 'change counter increments across multiple renames ' ,  async  ( )  =>  { 
200145      const  { client,  stop,  vol}  =  await  setupNfsClientServerTestbed ( ) ; 
201-       vol . writeFileSync ( '/export/test.txt' ,  'data' ) ; 
202-       vol . mkdirSync ( '/export/._test.txt' ) ; 
203-       const  res  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'test.txt' ,  'new.txt' ) ] ) ; 
204-       expect ( res . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
205-       expect ( vol . existsSync ( '/export/new.txt' ) ) . toBe ( true ) ; 
206-       expect ( vol . existsSync ( '/export/._test.txt' ) ) . toBe ( true ) ; 
207-       expect ( vol . statSync ( '/export/._test.txt' ) . isDirectory ( ) ) . toBe ( true ) ; 
146+       vol . writeFileSync ( '/export/file1.txt' ,  'data1' ) ; 
147+       vol . writeFileSync ( '/export/file2.txt' ,  'data2' ) ; 
148+       const  res1  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'file1.txt' ,  'renamed1.txt' ) ] ) ; 
149+       expect ( res1 . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
150+       const  renameRes1  =  res1 . resarray [ 2 ]  as  msg . Nfsv4RenameResponse ; 
151+       const  res2  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'file2.txt' ,  'renamed2.txt' ) ] ) ; 
152+       expect ( res2 . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
153+       const  renameRes2  =  res2 . resarray [ 2 ]  as  msg . Nfsv4RenameResponse ; 
154+       if  ( 
155+         renameRes1 . status  ===  Nfsv4Stat . NFS4_OK  && 
156+         renameRes1 . resok  && 
157+         renameRes2 . status  ===  Nfsv4Stat . NFS4_OK  && 
158+         renameRes2 . resok 
159+       )  { 
160+         expect ( renameRes2 . resok . sourceCinfo . after ) . toBeGreaterThan ( renameRes1 . resok . sourceCinfo . after ) ; 
161+         expect ( renameRes2 . resok . sourceCinfo . before ) . toBe ( renameRes1 . resok . sourceCinfo . after ) ; 
162+       } 
208163      await  stop ( ) ; 
209164    } ) ; 
210165
211-     test ( 'handles case where main file starts with ._ ' ,  async  ( )  =>  { 
166+     test ( 'failed rename does not increment change counter ' ,  async  ( )  =>  { 
212167      const  { client,  stop,  vol}  =  await  setupNfsClientServerTestbed ( ) ; 
213-       vol . writeFileSync ( '/export/._special.txt' ,  'data' ) ; 
214-       vol . writeFileSync ( '/export/._._special.txt' ,  'xattr' ) ; 
215-       const  res  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( '._special.txt' ,  '._renamed.txt' ) ] ) ; 
216-       expect ( res . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
217-       expect ( vol . existsSync ( '/export/._renamed.txt' ) ) . toBe ( true ) ; 
218-       expect ( vol . existsSync ( '/export/._._renamed.txt' ) ) . toBe ( true ) ; 
219-       expect ( vol . existsSync ( '/export/._special.txt' ) ) . toBe ( false ) ; 
220-       expect ( vol . existsSync ( '/export/._._special.txt' ) ) . toBe ( false ) ; 
168+       vol . writeFileSync ( '/export/existing.txt' ,  'data' ) ; 
169+       const  res1  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'existing.txt' ,  'renamed.txt' ) ] ) ; 
170+       expect ( res1 . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
171+       const  renameRes1  =  res1 . resarray [ 2 ]  as  msg . Nfsv4RenameResponse ; 
172+       const  res2  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'nonexistent.txt' ,  'fail.txt' ) ] ) ; 
173+       expect ( res2 . status ) . not . toBe ( Nfsv4Stat . NFS4_OK ) ; 
174+       vol . writeFileSync ( '/export/another.txt' ,  'data' ) ; 
175+       const  res3  =  await  client . compound ( [ nfs . PUTROOTFH ( ) ,  nfs . SAVEFH ( ) ,  nfs . RENAME ( 'another.txt' ,  'renamed3.txt' ) ] ) ; 
176+       expect ( res3 . status ) . toBe ( Nfsv4Stat . NFS4_OK ) ; 
177+       const  renameRes3  =  res3 . resarray [ 2 ]  as  msg . Nfsv4RenameResponse ; 
178+       if  ( 
179+         renameRes1 . status  ===  Nfsv4Stat . NFS4_OK  && 
180+         renameRes1 . resok  && 
181+         renameRes3 . status  ===  Nfsv4Stat . NFS4_OK  && 
182+         renameRes3 . resok 
183+       )  { 
184+         expect ( renameRes3 . resok . sourceCinfo . after  -  renameRes1 . resok . sourceCinfo . after ) . toBe ( 1n ) ; 
185+       } 
221186      await  stop ( ) ; 
222187    } ) ; 
223188  } ) ; 
0 commit comments