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

Adopt ensureNoDisposablesAreLeakedInTestSuite in unit tests #190503

Closed
Tracked by #192972
connor4312 opened this issue Aug 15, 2023 · 31 comments
Closed
Tracked by #192972

Adopt ensureNoDisposablesAreLeakedInTestSuite in unit tests #190503

connor4312 opened this issue Aug 15, 2023 · 31 comments
Assignees
Labels
debt Code quality issues engineering VS Code - Build / issue tracking / etc.
Milestone

Comments

@connor4312
Copy link
Member

connor4312 commented Aug 15, 2023

Background

Changes in the order tests ran in caused spurious failures in a test that used ensureNoDisposablesAreLeakedInTestSuite. This was almost certainly a result of leaky tests further up the chain, but it's very hard to figure out which test it is. We should seek to adopt ensureNoDisposablesAreLeakedInTestSuite in the unit tests we own.

Adoption

ensureNoDisposablesAreLeakedInTestSuite is a utility that ensures that disposables created in a test are disposed of. This is very useful to find leaks in code you own.

If you have a setup and teardown blocks, make sure the function is called before the former and after the latter, like so:

suite('myCoolTests', () => {
  teardown(() => { /* ... */ });

  ensureNoDisposablesAreLeakedInTestSuite();

  setup(() => { /* ... */ });

  // ...tests
});
  • Else, simply call the function at the beginning of the suite:
suite('myCoolTests', () => {
  ensureNoDisposablesAreLeakedInTestSuite();

  // ...tests
});

The method also returns a store: DisposableStore you can use for convenience to dispose instances within your tests, like so:

suite('myCoolTests', () => {
  const store = ensureNoDisposablesAreLeakedInTestSuite();

  test('MyEditorWorks', () => {
    const editor = store.add(new CodeEditor());

    // ...assertions
  });
});

It will then emit an error in the "after each" step if there are any dangling disposables. The error shown will have a stacktrace to where the leaked disposable was created.

Sometimes, it will not be the specific disposable that wasn't disposed of, but the thing that created it. You will want to walk up the displayed stack to find where the leak is.

⚠️ As referenced, leaks in earlier tests can your tests to fail when the full suite is run in CI. If this is the case, comment out your ensureNoDisposablesAreLeakedInTestSuite. We can do a more efficient pass later as we uncomment the leak detector.

Ask

We have a lot of tests! Please spend some time in the next debt week adding this function call to your tests, prioritizing ones that do more complex setups such as those involving code editors. It's not expected that we'll get every test in the first pass, and we can start calling out individual tests in future items.


@connor4312 connor4312 added debt Code quality issues engineering VS Code - Build / issue tracking / etc. labels Aug 15, 2023
@connor4312 connor4312 added this to the September 2023 milestone Aug 15, 2023
connor4312 added a commit that referenced this issue Aug 16, 2023
For #183449

Also adds the leak detector to debug tests and fixes some usages of it (#190503)
connor4312 added a commit that referenced this issue Aug 16, 2023
For #183449

Also adds the leak detector to debug tests and fixes some usages of it (#190503)
connor4312 added a commit that referenced this issue Aug 16, 2023
For #183449

Also adds the leak detector to debug tests and fixes some usages of it (#190503)
connor4312 added a commit that referenced this issue Sep 1, 2023
This fixes disposable leaks in the testing tests. These tests also
encompass tree views. One big source of leaks I found was `Event.chain`:
technically, every step in the chain is an IDisposable and needs to be
disposed. However, this is very noisy and unergonomic.

As an alternative, I introduce a very small `chain2` implementation
which only requires disposing the resulting event listener, like an
ordinary emitter. It doesn't support debouncing, though it could we if
want it to; there was only a single usable of the chained `debounce`
method in our codebase.

For #190503
connor4312 added a commit that referenced this issue Sep 1, 2023
This fixes disposable leaks in the testing tests. These tests also
encompass tree views. One big source of leaks I found was `Event.chain`:
technically, every step in the chain is an IDisposable and needs to be
disposed. However, this is very noisy and unergonomic.

As an alternative, I introduce a very small `chain2` implementation
which only requires disposing the resulting event listener, like an
ordinary emitter. It doesn't support debouncing, though it could we if
want it to; there was only a single usable of the chained `debounce`
method in our codebase.

For #190503
@connor4312
Copy link
Member Author

I included fixes for leaks in the ListWidget in my PR #192026 -- others are likely to hit that very quickly and may want to wait on that to get in

@bpasero
Copy link
Member

bpasero commented Sep 4, 2023

@connor4312 I might need your help in #192112. Even though I think I setup the disposables correctly, I still get an error around here, when I run the entire suite:

tracker.dispose();
tracker = disposables.add(createTracker());

Could you have a look?

@bpasero
Copy link
Member

bpasero commented Sep 4, 2023

I think I have found the issue, there were more things (services) not getting disposed.

@hediet
Copy link
Member

hediet commented Sep 5, 2023

@connor4312 we could improve the experience by having a global setup/teardown.
We could actually use the name of the suite/test as condition if leaking disposables should be checked:

let tracker: DisposableTracker | undefined;
setup(function (this: import('mocha').Context) {
	if ((this.currentTest?.parent?.title ?? '').indexOf('noLeaks') !== -1) {
		tracker = new DisposableTracker();
		setDisposableTracker(tracker);
	}
});

suite('myTest.noLeaks', () => {
   test(...); // Fails if test leaks
});

We could even invert this:

let tracker: DisposableTracker | undefined;
setup(function (this: import('mocha').Context) {
	if ((this.currentTest?.parent?.title ?? '').indexOf('.allowLeaks') === -1) {
		tracker = new DisposableTracker();
		setDisposableTracker(tracker);
	}
});

suite('myTest.allowLeaks', () => {
   test(...); // doesn't fail if disposable leak
});

suite('myTest', () => {
   test(...); // fails if disposables leak
});

What do you think?

@hediet
Copy link
Member

hediet commented Sep 5, 2023

If you find all test files that you touched but don't use the disposable tracker yet, do this in Power Shell:

$author = "Your Name"; $filesByAuthor = git log --author="$author" --name-only --pretty=format: | Sort-Object | Get-Unique; $testFiles = $filesByAuthor -match '\.test\.ts$' | Where-Object { Test-Path $_ } | Sort-Object | Get-Unique; $filteredFiles = $testFiles | Where-Object { -not (Get-Content $_ -ErrorAction SilentlyContinue | Select-String "ensureNoDisposablesAreLeakedInTestSuite") }; $filteredFiles; "Total number of items: $($filteredFiles.Count)"

@Tyriar
Copy link
Member

Tyriar commented Sep 5, 2023

@hediet running the symbol ensureNoDisposablesAreLeakedInTestSuite in the suite seems nicer than adding a string to the test/suite to me.

connor4312 added a commit that referenced this issue Sep 5, 2023
This fixes disposable leaks in the testing tests. These tests also
encompass tree views. One big source of leaks I found was `Event.chain`:
technically, every step in the chain is an IDisposable and needs to be
disposed. However, this is very noisy and unergonomic.

As an alternative, I introduce a very small `chain2` implementation
which only requires disposing the resulting event listener, like an
ordinary emitter. It doesn't support debouncing, though it could we if
want it to; there was only a single usable of the chained `debounce`
method in our codebase.

For #190503
@sandy081
Copy link
Member

@jrieken Moved markerService.test.ts to you

sandy081 added a commit that referenced this issue Sep 22, 2023
#190503 adopt ensureNoDisposablesAreLeakedInTestSuite
@Tyriar Tyriar modified the milestones: September 2023, October 2023 Sep 25, 2023
@chrmarti chrmarti removed their assignment Sep 26, 2023
@amunger amunger assigned rebornix and unassigned amunger Oct 4, 2023
@rebornix rebornix removed their assignment Oct 5, 2023
hhc87 pushed a commit to lins0621/vscode that referenced this issue Oct 8, 2023
…#192936)

More adoption of `ensureNoDisposablesAreLeakedInTestSuite` (microsoft#190503)
@bhavyaus bhavyaus removed their assignment Oct 17, 2023
@aiday-mar aiday-mar removed their assignment Oct 23, 2023
@Tyriar Tyriar modified the milestones: October 2023, November 2023 Oct 23, 2023
@Tyriar
Copy link
Member

Tyriar commented Oct 23, 2023

The bulk of this is done so we can remove it from the plan next month, @andreamah let's close this out as high priority next week though

@connor4312
Copy link
Member Author

Thanks all. I will open a followup issue for owners of specific tests who haven't adopted the disposable checker

@github-actions github-actions bot locked and limited conversation to collaborators Dec 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
debt Code quality issues engineering VS Code - Build / issue tracking / etc.
Projects
None yet
Development

No branches or pull requests