Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: make sure readFile[Sync] reads from the beginning #9699

Merged
merged 6 commits into from
Jan 14, 2017
Merged

fs: make sure readFile[Sync] reads from the beginning #9699

merged 6 commits into from
Jan 14, 2017

Conversation

seishun
Copy link
Contributor

@seishun seishun commented Nov 19, 2016

Checklist
  • make -j8 test (UNIX), or vcbuild test nosign (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

fs

Description of change

Currently, fs.readFile[Sync] starts reading from the current file position, which can be non-zero if the fd was previously read or written from/to. This conflicts with the documentation which states that it "reads the entire contents of a file".

Fixes: #9671

Provisional testcase for fs.readFile:

const assert = require('assert');
const fs = require('fs');

const filleLength = 2;
const path = 'test-read-file';
const fd = fs.openSync(path, 'w+');

fs.writeSync(fd, Buffer.alloc(filleLength, 1), 0, filleLength);

fs.readFile(fd, (err, buf) => {
  if (err) throw err;
  assert.strictEqual(buf.length, filleLength);
});

For fs.readFileSync:

'use strict';

const assert = require('assert');
const fs = require('fs');

const fileLength = 2;
const path = 'test-read-file';
const fd = fs.openSync(path, 'w+');

fs.writeSync(fd, Buffer.alloc(fileLength, 1), 0, fileLength);

assert.strictEqual(fs.readFileSync(fd).length, fileLength);

@nodejs-github-bot nodejs-github-bot added the fs Issues and PRs related to the fs subsystem / file system. label Nov 19, 2016
@vsemozhetbyt
Copy link
Contributor

vsemozhetbyt commented Nov 19, 2016

It seems you will be asked to add some tests. Here are the possible ones even without writing and additional files to read:

'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');

const fd = fs.openSync(__filename, 'r');
const fileLength = fs.readFileSync(fd).length;

assert.strictEqual(fileLength, fs.readFileSync(fd).length);
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');

const fd = fs.openSync(__filename, 'r');
const fileLength = fs.readFileSync(fd).length;

fs.readFile(fd, common.mustCall((err, buf) => {
  if (err) throw err;
  assert.strictEqual(fileLength, buf.length);
}));

@sam-github
Copy link
Contributor

To the two tests proposed above I would add an fs.stat() call to verify the length is the disk size of the file.

@jasnell jasnell added the semver-major PRs that contain breaking changes and should be released in the next major version. label Dec 5, 2016
@jasnell
Copy link
Member

jasnell commented Dec 5, 2016

Marking this semver-major to be cautious. Adding tests is definitely a good thing

@bnoordhuis
Copy link
Member

I'd also add a test that checks it works with file descriptors opened in 'a+' mode (append + read/write.)

@seishun
Copy link
Contributor Author

seishun commented Dec 6, 2016

Added test.

@sam-github I think the test should test just one thing, namely that doing readFile[Sync] repeatedly returns the same data every time.

@jasnell IMO it shouldn't be semver-major, it's a bug fix. I mean there's a possibility that someone out there is relying on it, but the the same could be said about any bug.

Copy link
Contributor

@cjihrig cjihrig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test looks like it contains duplicated logic. You could probably extract it into a function that is called twice.

const fileLengthR = fs.readFileSync(fdr).length;
const fileLengthA = fs.readFileSync(fda).length;

// reading again should result in the same length
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you capitalize and punctuate this comment.

assert.strictEqual(fileLengthA, fs.readFileSync(fda).length);

fs.readFile(fdr, common.mustCall((err, buf) => {
if (err) throw err;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.ifError(err);

}));

fs.readFile(fda, common.mustCall((err, buf) => {
if (err) throw err;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here.

@seishun
Copy link
Contributor Author

seishun commented Dec 6, 2016

@cjihrig Agreed, done. I think this way is better than a function.

@sam-github
Copy link
Contributor

@sam-github I think the test should test just one thing, namely that doing readFile[Sync] repeatedly returns the same data every time.

@seishun Is this test not intended to test the API contract of readFile? If so, the API contract is not that it returns the same data every time, but that it returns the entire file contents every time - the test as written doesn't do that. Calling stat will at least guarantee that what you read from disk is the same size as what is on disk. Right now if a bug was introduced so that only the first block of a data was returned when reading from a fd, your test looks like it would pass. To be more thorough, you could readFile, change the data on disk, and readFile again... but since that isn't a suspected regression its overkill at this moment.

@sam-github
Copy link
Contributor

I don't think this is semver-major. Its hard to see how the current behaviour would be depended on. Possible - sure, like any bug can be depended on, but not likely. Marking semver-major would block back-porting.

@seishun
Copy link
Contributor Author

seishun commented Dec 6, 2016

@sam-github Alright, I guess you're right, although now the test depends on fs.stat working correctly. What do others think?

@sam-github
Copy link
Contributor

The test is always going to depend on some other fs APIs if it needs a fixture, and this does. You avoided having to create the file to read by using your own js file, which is clever, but at the price of not knowing what the file's contents are, so you no longer know if you've read the whole file contents. I don't think its likely that readFile will read incorrect data if the size of what it read is at least identical to the size on disk, but some kind of assertion that there is at least a relationship between the data you are trying to read and the data you did read is important.

const fileLength = fs.statSync(__filename).size;

['r', 'a+'].forEach(mode => {
const fd = fs.openSync(__filename, mode);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use a file in common.tmpDir for a+ because __filename is not going to work when the file is owned by a different user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that used for new files? We need an already existing file here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just create a file upfront with fs.writeFileSync().

@seishun
Copy link
Contributor Author

seishun commented Dec 6, 2016

@bnoordhuis like this?

Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM when the style nits are fixed.

const path = require('path');

const filename = path.join(common.tmpDir, 'readfile.txt');
const dataExpected = new Array(100).join('a');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'a'.repeat(100)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm still not up to speed with ES6. In my defense, I copied from test-fs-readfilesync-pipe-large.js.

fs.writeFileSync(filename, dataExpected);
const fileLength = dataExpected.length;

['r', 'a+'].forEach(mode => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the linter complaining that it should be (mode) => {?


['r', 'a+'].forEach(mode => {
const fd = fs.openSync(filename, mode);
assert.strictEqual(fileLength, fs.readFileSync(fd).length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit: it should be assert.strictEqual(actual, expected) here and below.

@seishun
Copy link
Contributor Author

seishun commented Dec 6, 2016

@bnoordhuis nits are fixed.

@jasnell
Copy link
Member

jasnell commented Dec 6, 2016

@nodejs/ctc ... we need some discussion on whether this change is semver-major or not.

@trevnorris
Copy link
Contributor

@jasnell I'd say a patch. Current behavior contradicts official documentation, and this PR aligns the two.

@jasnell
Copy link
Member

jasnell commented Dec 9, 2016

Works for me

@seishun
Copy link
Contributor Author

seishun commented Dec 17, 2016

@jasnell so you're okay with removing semver-major?

@seishun seishun removed the semver-major PRs that contain breaking changes and should be released in the next major version. label Jan 8, 2017
@seishun
Copy link
Contributor Author

seishun commented Jan 8, 2017

@nodejs/collaborators Merging in a week from now if there are no objections.

@seishun seishun merged commit 4444e73 into nodejs:master Jan 14, 2017
italoacasas pushed a commit to italoacasas/node that referenced this pull request Jan 18, 2017
It would previously read from the current file position, which can be
non-zero if the `fd` has been read from or written to. This contradicts
the documentation which states that it "reads the entire contents of a
file".

PR-URL: nodejs#9699
Fixes: nodejs#9671
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
italoacasas pushed a commit to italoacasas/node that referenced this pull request Jan 23, 2017
It would previously read from the current file position, which can be
non-zero if the `fd` has been read from or written to. This contradicts
the documentation which states that it "reads the entire contents of a
file".

PR-URL: nodejs#9699
Fixes: nodejs#9671
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
@targos
Copy link
Member

targos commented Jan 28, 2017

Since this has been reverted, should we add all the dont-land-on labels?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fs Issues and PRs related to the fs subsystem / file system.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

readFile(fd) does not read the whole file (at least on Windows, possibly other platforms)