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

fix: flushFilePath() with fix for Windows #4362

Merged
merged 8 commits into from
Aug 16, 2022

Conversation

jeffb-sfdc
Copy link
Contributor

What does this PR do?

There's a bug in VS Code, which Microsoft refuses to fix (see microsoft/vscode#152993). After a file has been renamed, if only the casing has changes (eg no characters added or removed) VS Code passes the stale path to commands. This PR adds flushFilePath() and flushFilePaths() to fix this issue.

What issues does this PR fix or reference?

##3575, @W-10273785@

Functionality Before

  1. Rename an Apex class's file name (only changing the case of the filename), and rename it's metadata file
  2. Open the file and change the name of the class to match
  3. With the file open, right-click in the editor and select "SFDX : Deploy This Source to Org"
  4. This results in an error in the output: "File name mismatch with class name"

Functionality After

1-3: the same
4. This deployment succeeds

@jeffb-sfdc jeffb-sfdc requested a review from a team as a code owner August 11, 2022 16:03
@jeffb-sfdc
Copy link
Contributor Author

jeffb-sfdc commented Aug 11, 2022

This PR was originally #4240, but needed to be backed out due to an issue running on Windows. I looked into the issue, and it was b/c under Windows, fs.realpathSync.native() returns a string with the first character (the drive letter) capitalized, and this causes code to fail further downstream.


const exampleJsonString = JSON.stringify({ name: 'exampleName', error: 'exampleError' });
const exampleJsonString = JSON.stringify({
name: 'exampleName',
Copy link
Contributor Author

@jeffb-sfdc jeffb-sfdc Aug 11, 2022

Choose a reason for hiding this comment

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

Note: there's a lot of format changes here in this PR. This was not my doing. When I save files, they are automatically being re-formatted. I'm not sure if there was a recent configuration change, but it seems like we have a lot of formatting changes in PRs lately.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've seen this also. Checked in w Gordon about it yesterday. We're going to create a ticket to look into why this is happening.

fsPath: sourceUri.fsPath
}
);
await vscode.commands.executeCommand('sfdx.launch.replay.debugger.logfile', {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

more auto-formatting changes...

import {
forceAnonApexDebug
} from './forceAnonApexExecute';
import { testOutlineProvider } from '../views/testOutlineProvider';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

more auto-formatting changes...

// causes problems further down stream. To fix this, we'll use the path
// returned from fs.realpathSync.native() and then capitalize the first
// character.
nativePath = nativePath.charAt(0).toLowerCase() + nativePath.slice(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏽

Copy link
Contributor

Choose a reason for hiding this comment

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

stupid questions b/c windows. The default is C:\ but can a user have a drive named a lower case letter? So would d:\ be valid vs D:? There may be a corner case here were a drive is actually a lower case letter (a quick google told me this does happen in some instances, like when using a usb drive). We probably need a follow on ticket here to address the corner case and having the ability to ask the filesystem the valid path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I can tell, what's displayed is always uppercase (D:\) but what the APIs/SDK uses is always lowercase (d:\). I tested this on a VM running Windows, but it's VERY slow, so I'd like a more thorough test on a real Windows machine b/f we merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(also, if I remember correctly, Windows is case-insensitive, so d:\ is the same as D:\. When one performs a FileExists("c:\\foo\\bar.txt"), it's the same as FileExists("C:\\FOO\\BAR.TXT")

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm full of dumb questions. With the case insensitivity of windows why are we required to make the above change? It's been too long I don't remember what the 'down stream' changes are that result in the issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a really good question, and I wondered about that as well, but unfortunately, I don't know why the casing is failing. When I was debugging this, I noticed that the path was "c:\bla\bla\bla.cls" but fs.realpathSync.native() was returning "C:\bla\bla\bla.cls", and when I changed the case for the first char (the drive letter), our actions (deploy, delete, etc...) worked again.


const result = flushFilePath(filePath);

expect(realpathSyncNativeStub.called).to.equal(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Since calledOnce is used below, is the expect for "called" needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wrote this a while ago... I think at the time my thought was, _"test & verify that it was called... ...AND test & verify that it was only called once"..., but I think you are right, in that it might be a bit redundant. I'll go & remove it.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 nice catch


expect(result).to.equal(filePaths);

realpathSyncNativeStub.restore();
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this restore() be better placed in an afterEach() block? Or do you prefer to keep it here inside each test case block?

Copy link
Contributor Author

@jeffb-sfdc jeffb-sfdc Aug 11, 2022

Choose a reason for hiding this comment

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

For the moment, there's only one test, so I think keeping it central to the test is better. If there were more tests, and that stub was used in every test (and the stubbed value never changed), then placing it in beforeEach would be a better place, but for here & now, probably best to keep it where it is.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1
Seems you could move the stub into a beforeEach and the restore into an afterEach FTW

Copy link
Contributor

Choose a reason for hiding this comment

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

hum... I can't tell from the formatting but the flushFilePath and flushFilePaths describe blocks both appear to have tests that create and restore the following stub

      .stub(fs.realpathSync, 'native')
      .returns(realpathSyncFilePath);
...
      realpathSyncNativeStub.restore();

should those not be consolidated? I typically lean towards following patterns in tests to make them easier to grok for both new and existing developers so I'd prefer we put the stub/restore in before/after each blocks. This could be at the top level describe if that makes sense or just in a describe wrapper for all flushFilePath* describe blocks.

Copy link
Contributor

Choose a reason for hiding this comment

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

oh..above I mean I couldn't tell if both describe blocks were wrapped in a single top level describe.

Copy link
Contributor Author

@jeffb-sfdc jeffb-sfdc Aug 11, 2022

Choose a reason for hiding this comment

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

@gbockus-sf maybe we should take this off line & discuss f2f. realpathSyncNativeStub can't be hoisted or shared, b/c the mocked value that's returned changes from test to test.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jeffb-sfdc Sure I'll ping ya in slack

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wrapped everything in a describe(), added a beforeEach() and afterEach(), and moved realpathSyncNativeStub

// but it actually it returns string | undefined. Well, regardless, since flushFilePath() takes
// a string (and guards against empty strings) using the Non-null assertion operator
// (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator)
// fixes the issue.
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏽

});
});

describe('ConfirmationAndSourcePathGatherer', () => {
const examplePath = path.join('example', 'path');
const explorerPath = { fsPath: examplePath } as vscode.Uri;
const explorerPathUri = { fsPath: examplePath } as vscode.Uri;
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏽

Copy link
Contributor

@klewis-sfdc klewis-sfdc left a comment

Choose a reason for hiding this comment

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

Looks good to me @jeffb-sfdc ! Left one question about maybe using afterEach in the tests, up to you for consideration.

Copy link
Contributor

@gbockus-sf gbockus-sf left a comment

Choose a reason for hiding this comment

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

One concerning point of discussion around windows machines that have a lower case letter drive.

@klewis-sfdc
Copy link
Contributor

I ran the same tests on M1 Mac as on the last PR, all checks out:

✅ SFDX: Deploy Source to Org works after renaming locally by only making changes to the casing in the file+class name
✅ SFDX: Deploy Source to Org works when deploying multiple files after renaming one locally by only making changes to the casing in the file+class name
✅ SFDX: Retrieve Source from Org works as before
* Note: If you rename locally, you still need to deploy the class first so that it is renamed on the server before executing a “Retrieve” operation
✅ SFDX: Delete from Project and Org works as before
* Note: Similarly, if you rename the component/class locally, you need to deploy it so that the name is aligned on the server before running a delete operation

Copy link
Contributor

@gbockus-sf gbockus-sf left a comment

Choose a reason for hiding this comment

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

changes look good 👍
✅ verified on windows

Also attempted to create a volume to check the lower case volume letter and found it wasn't possible using the out of the box disk manager for windows so I think we're good to go 👍

@jeffb-sfdc jeffb-sfdc merged commit 1c52c54 into develop Aug 16, 2022
@jeffb-sfdc jeffb-sfdc deleted the jb/flushFilePath-with-fix-for-windows branch August 16, 2022 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants