Repository: streamich/memfs
Pull request #875 (2022-10-28 03:42:35)

fix: truncate to larger size

G-Rath: Could you give a bit more a description/story about this change? why is it a problem, how did you discover it, what does Node do? etc
toyobayashi: @G-Rath 

```js
const fs = require('fs')

const fd = fs.openSync('./test', 'w');
fs.writeFileSync(fd, '12345');
console.log(fs.readFileSync('./test', 'utf8') === '12345');
fs.truncateSync('./test', 10);
console.log(fs.readFileSync('./test', 'utf8') === '12345\0\0\0\0\0');
```

According to https://nodejs.org/dist/latest-v18.x/docs/api/fs.html#fsftruncatefd-len-callback

> If the file previously was shorter than len bytes, it is extended, and the extended part is filled with null bytes ('\0'):

toyobayashi: I'm using memfs in browser, before this change, `ftruncateSync` just throws RangeError if truncate to a larger size

![image](https://user-images.githubusercontent.com/23353576/198499225-5254ba9b-0636-4cd8-a31a-167de90e2573.png)


Patch:
diff --git a/src/node.ts b/src/node.ts
index df8f1c10..30faafaa 100644
--- a/src/node.ts
+++ b/src/node.ts
@@ -143,9 +143,10 @@ export class Node extends EventEmitter {
       if (len <= this.buf.length) {
         this.buf = this.buf.slice(0, len);
       } else {
-        const buf = bufferAllocUnsafe(0);
+        const buf = bufferAllocUnsafe(len);
         this.buf.copy(buf);
-        buf.fill(0, len);
+        buf.fill(0, this.buf.length);
+        this.buf = buf;
       }
     }
 


Test patch:
diff --git a/src/__tests__/promises.test.ts b/src/__tests__/promises.test.ts
index 39225e30..e4900279 100644
--- a/src/__tests__/promises.test.ts
+++ b/src/__tests__/promises.test.ts
@@ -193,6 +193,8 @@ describe('Promises API', () => {
         const fileHandle = await promises.open('/foo', 'r+');
         await fileHandle.truncate(5);
         expect(vol.readFileSync('/foo').toString()).toEqual('01234');
+        await fileHandle.truncate(7);
+        expect(vol.readFileSync('/foo').toString()).toEqual('01234\0\0');
         await fileHandle.close();
       });
       it('Reject when the file handle was closed', async () => {
@@ -592,6 +594,8 @@ describe('Promises API', () => {
     it('Truncate an existing file', async () => {
       await promises.truncate('/foo', 5);
       expect(vol.readFileSync('/foo').toString()).toEqual('01234');
+      await promises.truncate('/foo', 7);
+      expect(vol.readFileSync('/foo').toString()).toEqual('01234\0\0');
     });
     it('Reject when file does not exist', () => {
       return expect(promises.truncate('/bar')).rejects.toBeInstanceOf(Error);
diff --git a/src/__tests__/volume.test.ts b/src/__tests__/volume.test.ts
index 53a1f893..9950c247 100644
--- a/src/__tests__/volume.test.ts
+++ b/src/__tests__/volume.test.ts
@@ -955,6 +955,13 @@ describe('volume', () => {
         vol.truncateSync('/1', 2);
         expect(vol.readFileSync('/1', 'utf8')).toBe('12');
       });
+      it('Larger truncate', () => {
+        const fd = vol.openSync('/2', 'w');
+        vol.writeFileSync(fd, '12345');
+        expect(vol.readFileSync('/2', 'utf8')).toBe('12345');
+        vol.truncateSync('/2', 10);
+        expect(vol.readFileSync('/2', 'utf8')).toBe('12345\0\0\0\0\0');
+      });
     });
     describe('.truncate(path[, len], callback)', () => {
       xit('...', () => {});