Skip to content

Commit 4cb8a48

Browse files
committed
test hot exit for scratchpads
1 parent 5a92e63 commit 4cb8a48

File tree

2 files changed

+175
-64
lines changed

2 files changed

+175
-64
lines changed

src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
114114
// Trigger backup if configured and enabled for shutdown reason
115115
let backups: IWorkingCopy[] = [];
116116
let backupError: Error | undefined = undefined;
117-
const backup = await this.shouldBackupBeforeShutdown(reason);
118-
if (backup) {
117+
const copiesToBackup = await this.shouldBackupBeforeShutdown(reason, modifiedWorkingCopies);
118+
if (copiesToBackup.length > 0) {
119119
try {
120-
const backupResult = await this.backupBeforeShutdown(modifiedWorkingCopies);
120+
const backupResult = await this.backupBeforeShutdown(copiesToBackup);
121121
backups = backupResult.backups;
122122
backupError = backupResult.error;
123123

@@ -162,12 +162,12 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
162162
}
163163
}
164164

165-
private async shouldBackupBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
166-
let backup: boolean | undefined;
165+
private async shouldBackupBeforeShutdown(reason: ShutdownReason, modifiedWorkingCopies: readonly IWorkingCopy[]): Promise<readonly IWorkingCopy[]> {
166+
167167
if (!this.filesConfigurationService.isHotExitEnabled) {
168-
backup = false; // never backup when hot exit is disabled via settings
168+
return []; // never backup when hot exit is disabled via settings
169169
} else if (this.environmentService.isExtensionDevelopment) {
170-
backup = true; // always backup closing extension development window without asking to speed up debugging
170+
return modifiedWorkingCopies; // always backup closing extension development window without asking to speed up debugging
171171
} else {
172172

173173
// When quit is requested skip the confirm callback and attempt to backup all workspaces.
@@ -178,33 +178,29 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
178178
switch (reason) {
179179
case ShutdownReason.CLOSE:
180180
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
181-
backup = true; // backup if a folder is open and onExitAndWindowClose is configured
181+
return modifiedWorkingCopies; // backup if a folder is open and onExitAndWindowClose is configured
182182
} else if (await this.nativeHostService.getWindowCount() > 1 || isMacintosh) {
183-
backup = false; // do not backup if a window is closed that does not cause quitting of the application
183+
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration !== HotExitConfiguration.OFF) {
184+
return modifiedWorkingCopies.filter(wc => wc.capabilities & WorkingCopyCapabilities.Scratchpad); // only backup scratchpad working copies
185+
}
186+
return []; // do not backup if a window is closed that does not cause quitting of the application
184187
} else {
185-
backup = true; // backup if last window is closed on win/linux where the application quits right after
188+
return modifiedWorkingCopies; // backup if last window is closed on win/linux where the application quits right after
186189
}
187-
break;
188-
189190
case ShutdownReason.QUIT:
190-
backup = true; // backup because next start we restore all backups
191-
break;
191+
return modifiedWorkingCopies; // backup because next start we restore all backups
192192

193193
case ShutdownReason.RELOAD:
194-
backup = true; // backup because after window reload, backups restore
195-
break;
194+
return modifiedWorkingCopies; // backup because after window reload, backups restore
196195

197196
case ShutdownReason.LOAD:
198197
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
199-
backup = true; // backup if a folder is open and onExitAndWindowClose is configured
198+
return modifiedWorkingCopies; // backup if a folder is open and onExitAndWindowClose is configured
200199
} else {
201-
backup = false; // do not backup because we are switching contexts
200+
return []; // do not backup because we are switching contexts
202201
}
203-
break;
204202
}
205203
}
206-
207-
return backup;
208204
}
209205

210206
private showErrorDialog(msg: string, workingCopies: readonly IWorkingCopy[], error?: Error): void {

src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupTracker.test.ts

Lines changed: 158 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -423,49 +423,6 @@ suite('WorkingCopyBackupTracker (native)', function () {
423423
await cleanup();
424424
});
425425

426-
test('onWillShutdown - scratchpads - no veto if backed up', async function () {
427-
const { accessor, cleanup } = await createTracker();
428-
429-
class TestBackupWorkingCopy extends TestWorkingCopy {
430-
431-
constructor(resource: URI) {
432-
super(resource);
433-
434-
accessor.workingCopyService.registerWorkingCopy(this);
435-
}
436-
437-
override capabilities = WorkingCopyCapabilities.Untitled | WorkingCopyCapabilities.Scratchpad;
438-
439-
override isDirty(): boolean {
440-
return false;
441-
}
442-
443-
override isModified(): boolean {
444-
return true;
445-
}
446-
}
447-
448-
accessor.filesConfigurationService.testOnFilesConfigurationChange({ files: { hotExit: HotExitConfiguration.ON_EXIT } });
449-
accessor.nativeHostService.windowCount = Promise.resolve(2);
450-
// Set cancel to force a veto if hot exit does not trigger
451-
accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL);
452-
453-
const resource = toResource.call(this, '/path/custom.txt');
454-
new TestBackupWorkingCopy(resource);
455-
456-
const event = new TestBeforeShutdownEvent();
457-
event.reason = ShutdownReason.CLOSE;
458-
accessor.lifecycleService.fireBeforeShutdown(event);
459-
460-
const veto = await event.value;
461-
assert.ok(!veto);
462-
463-
const finalVeto = await event.finalValue?.();
464-
assert.ok(!finalVeto); // assert the tracker uses the internal finalVeto API
465-
466-
await cleanup();
467-
});
468-
469426
test('onWillShutdown - pending backup operations canceled and tracker suspended/resumsed', async function () {
470427
const { accessor, tracker, cleanup } = await createTracker();
471428

@@ -608,6 +565,109 @@ suite('WorkingCopyBackupTracker (native)', function () {
608565
});
609566
});
610567

568+
suite('"onExit" setting - scratchpad', () => {
569+
test('should hot exit (reason: CLOSE, windows: single, workspace)', function () {
570+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, true, false);
571+
});
572+
test('should hot exit (reason: CLOSE, windows: single, empty workspace)', function () {
573+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, false, false);
574+
});
575+
test('should hot exit (reason: CLOSE, windows: multiple, workspace)', function () {
576+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, true, false);
577+
});
578+
test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function () {
579+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, false, true);
580+
});
581+
test('should hot exit (reason: QUIT, windows: single, workspace)', function () {
582+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, true, false);
583+
});
584+
test('should hot exit (reason: QUIT, windows: single, empty workspace)', function () {
585+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, false, false);
586+
});
587+
test('should hot exit (reason: QUIT, windows: multiple, workspace)', function () {
588+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, true, false);
589+
});
590+
test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function () {
591+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, false, false);
592+
});
593+
test('should hot exit (reason: RELOAD, windows: single, workspace)', function () {
594+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, true, false);
595+
});
596+
test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function () {
597+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, false, false);
598+
});
599+
test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function () {
600+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, true, false);
601+
});
602+
test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function () {
603+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, false, false);
604+
});
605+
test('should NOT hot exit (reason: LOAD, windows: single, workspace)', function () {
606+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, true, true);
607+
});
608+
test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function () {
609+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, false, true);
610+
});
611+
test('should NOT hot exit (reason: LOAD, windows: multiple, workspace)', function () {
612+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, true, true);
613+
});
614+
test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function () {
615+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, false, true);
616+
});
617+
});
618+
619+
suite('"onExitAndWindowClose" setting - scratchpad', () => {
620+
test('should hot exit (reason: CLOSE, windows: single, workspace)', function () {
621+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, true, false);
622+
});
623+
test('should hot exit (reason: CLOSE, windows: single, empty workspace)', function () {
624+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, false, !!isMacintosh);
625+
});
626+
test('should hot exit (reason: CLOSE, windows: multiple, workspace)', function () {
627+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, true, false);
628+
});
629+
test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function () {
630+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, false, true);
631+
});
632+
test('should hot exit (reason: QUIT, windows: single, workspace)', function () {
633+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, true, false);
634+
});
635+
test('should hot exit (reason: QUIT, windows: single, empty workspace)', function () {
636+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, false, false);
637+
});
638+
test('should hot exit (reason: QUIT, windows: multiple, workspace)', function () {
639+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, true, false);
640+
});
641+
test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function () {
642+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, false, false);
643+
});
644+
test('should hot exit (reason: RELOAD, windows: single, workspace)', function () {
645+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, true, false);
646+
});
647+
test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function () {
648+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, false, false);
649+
});
650+
test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function () {
651+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, true, false);
652+
});
653+
test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function () {
654+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, false, false);
655+
});
656+
test('should hot exit (reason: LOAD, windows: single, workspace)', function () {
657+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, true, false);
658+
});
659+
test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function () {
660+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, false, true);
661+
});
662+
test('should hot exit (reason: LOAD, windows: multiple, workspace)', function () {
663+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, true, false);
664+
});
665+
test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function () {
666+
return scratchpadHotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, false, true);
667+
});
668+
});
669+
670+
611671
async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: boolean, shouldVeto: boolean): Promise<void> {
612672
const { accessor, cleanup } = await createTracker();
613673

@@ -647,5 +707,60 @@ suite('WorkingCopyBackupTracker (native)', function () {
647707

648708
await cleanup();
649709
}
710+
711+
async function scratchpadHotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: boolean, shouldVeto: boolean): Promise<void> {
712+
const { accessor, cleanup } = await createTracker();
713+
714+
class TestBackupWorkingCopy extends TestWorkingCopy {
715+
716+
constructor(resource: URI) {
717+
super(resource);
718+
719+
accessor.workingCopyService.registerWorkingCopy(this);
720+
}
721+
722+
override capabilities = WorkingCopyCapabilities.Untitled | WorkingCopyCapabilities.Scratchpad;
723+
724+
override isDirty(): boolean {
725+
return false;
726+
}
727+
728+
override isModified(): boolean {
729+
return true;
730+
}
731+
}
732+
733+
// Set hot exit config
734+
accessor.filesConfigurationService.testOnFilesConfigurationChange({ files: { hotExit: setting } });
735+
736+
// Set empty workspace if required
737+
if (!workspace) {
738+
accessor.contextService.setWorkspace(new Workspace('empty:1508317022751'));
739+
}
740+
741+
// Set multiple windows if required
742+
if (multipleWindows) {
743+
accessor.nativeHostService.windowCount = Promise.resolve(2);
744+
}
745+
746+
// Set cancel to force a veto if hot exit does not trigger
747+
accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL);
748+
749+
const resource = toResource.call(this, '/path/custom.txt');
750+
new TestBackupWorkingCopy(resource);
751+
752+
const event = new TestBeforeShutdownEvent();
753+
event.reason = shutdownReason;
754+
accessor.lifecycleService.fireBeforeShutdown(event);
755+
756+
const veto = await event.value;
757+
assert.ok(typeof event.finalValue === 'function'); // assert the tracker uses the internal finalVeto API
758+
assert.strictEqual(accessor.workingCopyBackupService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel
759+
assert.strictEqual(veto, shouldVeto);
760+
761+
await cleanup();
762+
763+
await cleanup();
764+
}
650765
});
651766
});

0 commit comments

Comments
 (0)