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

test(semantic): add comprehensive regression test suite #5976

Conversation

DonIsaac
Copy link
Contributor

@DonIsaac DonIsaac commented Sep 22, 2024

What This PR Does

Enhance's oxc_semantic's integration tests with a regression test suite that ensures semantic's contract guarantees hold over all test cases in typescript-eslint's scope snapshot tests. Each test case checks a separate assumption and runs independently from other test cases.

This PR sets up the code infrastructure for this test suite and adds two test cases to start us off:

  1. Reflexivity tests for IdentifierReference and Reference
  2. Symbol declaration reflexivity tests between declarations in SymbolTable and their corresponding node in the AST.

Please refer to the doc comments for each of these tests for an in-depth explanation.

Aren't our existing tests sufficient?

oxc_semantic is currently tested directly via

  1. scope snapshot tests, ported from typescript-eslint
  2. Hand-written tests using SemanticTester in tests/integration

And indirectly via

  1. Conformance test suite over Test262/TypeScript/Babel
  2. Linter snapshot tests

Shouldn't this be sufficient? I argue not, for two reasons:

1. Clarify Contract Ambiguity

When using Semantic, I often find myself asking these questions?

  • Does semantic.symbols().get_declaration(id) point to a BindingIdentifer/BindingPattern or the declaration that holds an identifier/pattern?
  • Will a Reference's node_id point me to an IdentifierReference or the expression/statement that is holding an IdentifierReference?
  • When will BindingIdentifier's symbol_id get populated? can we guarantee that after semantic analysis it will never be None?
  • What actually is the node covered by semantic.symbols().get_span(id)? This one really messed me up, and resulted in me creating Incorrect Span on BindingIdentifier  #4739.
  • What scope does Function::scope_id point to? The one where the function is declared? The one created by its body? The one created by the type annotations but before the function body? Or something else entirely?

These test cases are meant to answer such questions and guarnatee those answers as test cases. No other existing testing solution currently upholds such promises: they only tell us if code expecting one answer or another produces an unexpected result. However, those parts of the codebase could always be adjusted to conform to new Semantic behavior, meaning no contract guarantees are actually upheld.

2. Existing Tests Do Not Test The Same Behavior

I'll cover each above listed test case one-by-one:

  1. For starters, these tests only cover scopes. Additionally, they only tell us how behavior has changed, not that behavior is now incorrect.
  2. These do generally cover the same behaviors, but are not comprehensive and are difficult to maintain. These are unit tests that should be used hand-in-hand with this new test suite.
  3. The most relevant tests here are for the parser. However, these tests only tell us if a syntax/parse error was produced, and tell us nothing about the validity of Semantic.
  4. Relying on lint rule's output is a a mediiocre proxy of Semantic's behavior at best. They can tell us if changes to Semantic break assumptions made by lint rules, but they do not tell us if **those assumptions are the ones we want to uphold to external crates consuming Semantic.

Copy link

graphite-app bot commented Sep 22, 2024

Your org has enabled the Graphite merge queue for merging into main

Add the label “0-merge” to the PR and Graphite will automatically add it to the merge queue when it’s ready to merge. Or use the label “hotfix” to add to the merge queue as a hot fix.

You must have a Graphite account and log in to Graphite in order to use the merge queue. Sign up using this link.

Copy link
Contributor Author

DonIsaac commented Sep 22, 2024

This stack of pull requests is managed by Graphite. Learn more about stacking.

Join @DonIsaac and the rest of your teammates on Graphite Graphite

@github-actions github-actions bot added the A-semantic Area - Semantic label Sep 22, 2024
Copy link

codspeed-hq bot commented Sep 22, 2024

CodSpeed Performance Report

Merging #5976 will not alter performance

Comparing don/09-22-test_semantic_add_comprehensive_regression_test_suite (93575cd) with main (d60ceb4)

Summary

✅ 29 untouched benchmarks

@DonIsaac DonIsaac marked this pull request as ready for review September 22, 2024 21:04
Copy link
Member

@Boshen Boshen left a comment

Choose a reason for hiding this comment

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

I'll let @overlookmotel to decide on this one.

@overlookmotel
Copy link
Contributor

overlookmotel commented Sep 23, 2024

Thanks very much for writing up the rationale for this change so clearly. I totally agree with the need for a more structured approach to testing, for all the reasons you have given. And I too want to know what semantic.symbols().get_declaration(id) is meant to be pointing at!

What I'm not so keen on is storing conformance test results in each snapshot. I am worried that if details of test fails are hidden away there, they may never get fixed.

I can imagine a scenario like this: Someone makes a PR that breaks a test. They say "I'll fix that test in a follow-up". But they forget to do it, and because it's hidden away in one .snap file out of 290, no-one notices and the bug lives on unfixed forever.

So personally, I think it'd be preferable if all tests fails were listed in a single .snap.md file.

@DonIsaac DonIsaac force-pushed the don/09-22-test_semantic_add_comprehensive_regression_test_suite branch 2 times, most recently from 1d70aab to 87f4f94 Compare September 23, 2024 15:54
@DonIsaac
Copy link
Contributor Author

So personally, I think it'd be preferable if all tests fails were listed in a single .snap.md file.

I've looked into this, and making this change would require modifying how scope snapshot tests are run. I'd rather make changes to those tests in a separate PR. Is this a hard blocker, or can it be deferred to an up-stack PR?

@DonIsaac DonIsaac force-pushed the don/09-22-test_semantic_add_comprehensive_regression_test_suite branch 2 times, most recently from f271380 to 15b6b3b Compare September 23, 2024 16:31
@overlookmotel
Copy link
Contributor

Is this a hard blocker, or can it be deferred to an up-stack PR?

Not a blocker to merging, as far as I'm concerned. But personally I would like to see it happen!

@DonIsaac
Copy link
Contributor Author

Is this a hard blocker, or can it be deferred to an up-stack PR?

Not a blocker to merging, as far as I'm concerned. But personally I would like to see it happen!

Ok, I'll definitely add this in a follow-up PR. Are there any other blockers I should address before we can merge this PR?

@Boshen Boshen added the 0-merge Merge with Graphite Merge Queue label Sep 25, 2024
Copy link

graphite-app bot commented Sep 25, 2024

Merge activity

  • Sep 24, 10:18 PM EDT: The merge label '0-merge' was detected. This PR will be added to the Graphite merge queue once it meets the requirements.
  • Sep 24, 10:18 PM EDT: Boshen added this pull request to the Graphite merge queue.
  • Sep 24, 10:35 PM EDT: Boshen merged this pull request with the Graphite merge queue.

# What This PR Does

Enhance's `oxc_semantic`'s integration tests with a regression test suite that ensures semantic's contract guarantees hold over all test cases in typescript-eslint's scope snapshot tests. Each test case checks a separate assumption and runs independently from other test cases.

This PR sets up the code infrastructure for this test suite and adds two test cases to start us off:
1. Reflexivity tests for `IdentifierReference` and `Reference`
2. Symbol declaration reflexivity tests between declarations in `SymbolTable` and their corresponding node in the AST.

Please refer to the doc comments for each of these tests for an in-depth explanation.

## Aren't our existing tests sufficient?
`oxc_semantic` is currently tested directly via
1. scope snapshot tests, ported from `typescript-eslint`
2. Hand-written tests using `SemanticTester` in `tests/integration`

And indirectly via

3. Conformance test suite over Test262/TypeScript/Babel
4. Linter snapshot tests

Shouldn't this be sufficient? I argue not, for two reasons:

## 1. Clarify Contract Ambiguity

When using `Semantic`, I often find myself asking these questions?
* Does `semantic.symbols().get_declaration(id)` point to a `BindingIdentifer`/`BindingPattern` or the declaration that holds an identifier/pattern?
* Will a `Reference`'s `node_id` point me to an `IdentifierReference` or the expression/statement that is holding an `IdentifierReference`?
* When will `BindingIdentifier`'s `symbol_id` get populated? can we guarantee that after semantic analysis it will never be `None`?
* What actually _is_ the node covered by `semantic.symbols().get_span(id)`? This one really messed me up, and resulted in me creating #4739.
* What scope does `Function::scope_id` point to? The one where the function is declared? The one created by its body? The one created by the type annotations but before the function body? Or something else entirely?

**These test cases are meant to answer such questions and guarnatee those answers as test cases**. No other existing testing solution currently upholds such promises: they only tell us if code expecting one answer or another produces an unexpected result. However, those parts of the codebase could always be adjusted to conform to new `Semantic` behavior, meaning no contract guarantees are actually upheld.

## 2. Existing Tests Do Not Test The Same Behavior

I'll cover each above listed test case one-by-one:

1. For starters, these tests only cover scopes. Additionally, they only tell us **how behavior has changed**, not that **behavior is now incorrect**.
2. These _do_ generally cover the same behaviors, but **are not comprehensive and are difficult to maintain**. These are unit tests that should be used hand-in-hand with this new test suite.
3. The most relevant tests here are for the parser. However, these tests **only tell us if a syntax/parse error was produced**, and tell us nothing about the validity of `Semantic`.
4. Relying on lint rule's output is a a mediiocre proxy of `Semantic`'s behavior at best. They can tell us if changes to `Semantic` break assumptions made by lint rules, but they do not tell us if **those assumptions are the ones we want to uphold to external crates consuming `Semantic`.
@Boshen Boshen force-pushed the don/09-22-test_semantic_add_comprehensive_regression_test_suite branch from aacc835 to 93575cd Compare September 25, 2024 02:25
@graphite-app graphite-app bot merged commit 93575cd into main Sep 25, 2024
26 checks passed
@graphite-app graphite-app bot deleted the don/09-22-test_semantic_add_comprehensive_regression_test_suite branch September 25, 2024 02:35
Boshen added a commit that referenced this pull request Sep 27, 2024
## [0.30.2] - 2024-09-27

### Features

- 60c52ba ast: Allow passing span to `void_0` method (#6065) (Dunqing)
- cca433f codegen: Print `vite` / `webpack` special comments (#6021)
(Dunqing)
- 8d026e1 regular_expression: Implement `GetSpan` for RegExp AST nodes
(#6056) (camchenry)
- 7764793 regular_expression: Implement visitor pattern trait for regex
AST (#6055) (camchenry)
- f866781 semantic: Check for type annotations on left side of `for..in`
and `for..of` iterators (#6043) (DonIsaac)
- 8b2e9aa semantic: Check for JSDoc types in TS type annotations (#6042)
(DonIsaac)
- 28da771 transformer: Do not transform `**` with bigint literals
(#6023) (Boshen)

### Bug Fixes

- a88504c diagnostics: Check for terminal when displaying links (#6018)
(Boshen)
- 418ae25 isolated-declarations: Report uninferrable types in arrays
(#6084) (michaelm)
- e0a8959 minifier: Compute `void number` as `undefined` (#6028)
(Boshen)
- 0658576 paresr: Do not report missing initializer error in ambient
context (#6020) (Boshen)
- b1af73d semantic: Do not create a `global` symbol for `declare global
{}` (#6040) (DonIsaac)
- c8682e9 semantic,codegen,transformer: Handle definite `!` operator in
variable declarator (#6019) (Boshen)

### Performance

- 6b7d3ed isolated-declarations: Should clone transformed AST rather
than original AST (#6078) (Dunqing)
- 85aff19 transformer: Introduce `Stack` (#6093) (overlookmotel)
- ad4ef31 transformer: Introduce `NonEmptyStack` (#6092) (overlookmotel)

### Documentation

- 3099709 allocator: Document `oxc_allocator` crate (#6037) (DonIsaac)
- d60ceb4 oxc: Add README.md and crate-level docs (#6035) (DonIsaac)
- efabfc8 semantic: Improve doc comments on `Reference` methods (#6076)
(overlookmotel)

### Refactor

- 1fc80d1 ast: Move all ts ast related impl methods to `ast_impl`
(#6015) (Dunqing)
- fe696f0 codegen: Simplify printing annotation comments (#6027)
(Dunqing)
- e60ce50 transformer: Add `SparseStack::with_capacity` method (#6094)
(overlookmotel)
- 1399d2c transformer: Move `SparseStack` definition into folder (#6091)
(overlookmotel)
- 6bd29dd transformer: Add more debug assertions (#6090) (overlookmotel)
- c90b9bf transformer: Rename `SparseStack` methods (#6089)
(overlookmotel)
- 2b380c8 transformer: Remove unsued `self.ctx` (#6022) (Boshen)

### Testing

- 93575cd semantic: Add comprehensive regression test suite (#5976)
(DonIsaac)
- a4cec75 transformer: Enable tests (#6032) (overlookmotel)

---------

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
0-merge Merge with Graphite Merge Queue A-semantic Area - Semantic
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants