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

Automated regression testing for prism work #26

Merged
merged 5 commits into from
Jun 14, 2024

Conversation

egiurleo
Copy link

@egiurleo egiurleo commented Jun 12, 2024

Best read commit-by-commit!

Motivation

As we work on parsing with Prism in Sorbet, we should do some automated testing to ensure that we don't introduce any regressions or break our previous work. However, until we have Prism parsing most kinds of nodes, it doesn't make any sense to run the whole Sorbet test suite, since most tests will just fail.

This PR introduces a new test suite, called prism_test_corpus, that only runs tests in the test/prism_regression folder. We can add tests to this folder as we implement prism parsing for each node, and then eventually turn on other portions of the Sorbet test suite as we get closer to completion.

Test plan

You can see the automatic tests in the check in this PR 🥳

You can run the tests locally with the following command:

./bazel test //test:prism --config=dbg --define RUBY_PATH="/path/to/your/ruby"

@egiurleo egiurleo force-pushed the emily/testing-prism branch 12 times, most recently from 2c7c2e1 to d6f5382 Compare June 13, 2024 18:53
@egiurleo egiurleo changed the title [WIP] automated regression testing for prism work Automated regression testing for prism work Jun 13, 2024
1. Add a new test suite that only tests files in the `test/prism_regression` folder
2. Modify Sorbet's test code to allow the user to specify a parser
3. Add logic to the pipeline test runner to call `runPrismParser` when specified
@@ -272,6 +281,11 @@ test_suite(
tests = ["whitequark_parser_corpus"],
)

test_suite(
Copy link
Author

Choose a reason for hiding this comment

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

This adds a new test suite to bazel, allowing us to run ./bazel test //test:prism as a shorthand for all of the prism regression tests, which are defined above.

@@ -262,6 +262,15 @@ pipeline_tests(
extra_files = ["testdata/lsp/rubyfmt-stub/rubyfmt"],
)

pipeline_tests(
Copy link
Author

Choose a reason for hiding this comment

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

This tells bazel which tests to run, which runner to use, and which options to pass into the tests (in this case, parser = "prism", which is a new option that I implemented).

@@ -112,7 +114,7 @@ _TEST_RUNNERS = {
"PackagerTests": ":pipeline_test_runner",
}

def pipeline_tests(suite_name, all_paths, test_name_prefix, extra_files = [], tags = []):
def pipeline_tests(suite_name, all_paths, test_name_prefix, extra_files = [], tags = [], parser = "sorbet"):
Copy link
Author

Choose a reason for hiding this comment

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

The changes in this file us to specify which parser to use for tests and then pass that value along to the test runner.

@@ -855,6 +860,9 @@ int main(int argc, char *argv[]) {
cxxopts::Options options("test_corpus", "Test corpus for Sorbet typechecker");
options.allow_unrecognised_options().add_options()("single_test", "run over single test.",
cxxopts::value<std::string>()->default_value(""), "testpath");
options.allow_unrecognised_options().add_options()("parser", "The parser to use while testing.",
Copy link
Author

Choose a reason for hiding this comment

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

Here is where the test runner allows us to pass in a prism option and then pulls the value from the provided options.

@@ -1,4 +1,5 @@
GENERATED_SRCS = [
"src/diagnostic.c",
Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure how I missed this and how Sorbet has been building locally without it 🤔 But CI was having trouble and I realized I'd forgotten a file!

@@ -350,8 +350,7 @@ const unique_ptr<parser::Node> convertPrismToSorbet(pm_node_t *node, pm_parser_t
}
}

unique_ptr<parser::Node> runPrismParser(core::GlobalState &gs, core::FileRef file, const options::Printers &print,
bool traceLexer, bool traceParser) {
unique_ptr<parser::Node> runPrismParser(core::GlobalState &gs, core::FileRef file) {
Copy link
Author

Choose a reason for hiding this comment

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

Simplified the method signature to remove things I'm not using (yet) for easy use in tests.

@@ -24,6 +25,7 @@ std::vector<core::FileRef> reserveFiles(std::unique_ptr<core::GlobalState> &gs,
std::vector<ast::ParsedFile> index(core::GlobalState &gs, absl::Span<core::FileRef> files, const options::Options &opts,
WorkerPool &workers, const std::unique_ptr<const OwnedKeyValueStore> &kvstore);

std::unique_ptr<parser::Node> runPrismParser(core::GlobalState &gs, core::FileRef file);
Copy link
Author

Choose a reason for hiding this comment

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

Added to the public API for use in tests. I will eventually move this into its own namespace so please don't worry about it for now 😁

Comment on lines +206 to +214
if (parser == realmain::options::Parser::SORBET) {
std::cout << "Parsing with sorbet" << std::endl;
core::UnfreezeNameTable nameTableAccess(*gs); // enters original strings

auto settings = parser::Parser::Settings{};
nodes = parser::Parser::run(*gs, file, settings);
} else if (parser == realmain::options::Parser::PRISM) {
std::cout << "Parsing with prism" << std::endl;
nodes = realmain::pipeline::runPrismParser(*gs, file);
Copy link
Author

Choose a reason for hiding this comment

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

And here's where we actually use the parser option to decide which parser to use. Sorbet's test implementation is weird, in that it breaks down all the components of the pipeline and tests them separately in order to inject special testing logic between them. Hence, why I had to call runPrismParser here. As I've said, I will eventually move that method into its own namespace, which will make it easier to reuse without dipping into pipeline internals.

core::UnfreezeNameTable nameTableAccess(*gs); // enters original strings

auto settings = parser::Parser::Settings{};
nodes = parser::Parser::run(*gs, file, settings);
} else if (parser == realmain::options::Parser::PRISM) {
std::cout << "Parsing with prism" << std::endl;
Copy link
Author

Choose a reason for hiding this comment

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

I'm going to leave these print statements because without some confirmation that we're hitting this code path, there (afaik) no way to tell that we are actually parsing with prism rather than the sorbet parser 😆

with:
ruby-version: 3.3.0
- name: Run tests
run: ./bazel test //test:prism --config=dbg --define RUBY_PATH='/opt/hostedtoolcache/Ruby/3.3.0/x64' --test_output=all
Copy link
Author

Choose a reason for hiding this comment

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

Passes in the path to the Ruby version installed by GitHub actions.

Choose a reason for hiding this comment

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

Looks like building takes 20 minutes. It might be worth exploring if we can use something simple like https://github.com/bazel-contrib/setup-bazel and get caching out of the box.

Copy link
Author

@egiurleo egiurleo Jun 14, 2024

Choose a reason for hiding this comment

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

@egiurleo egiurleo self-assigned this Jun 13, 2024
@egiurleo egiurleo marked this pull request as ready for review June 13, 2024 21:02
@@ -0,0 +1,3 @@
# typed: true
Copy link

Choose a reason for hiding this comment

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

Maybe you can add some *.parse-tree.exp files to ensure the produced AST is correct?

Like what's done with https://github.com/sorbet/sorbet/blob/master/test/testdata/parser/kwnil.parse-tree.exp.

Copy link
Author

Choose a reason for hiding this comment

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

Good idea! I'll look into this now.

.github/workflows/ci.yml Outdated Show resolved Hide resolved
with:
ruby-version: 3.3.0
- name: Run tests
run: ./bazel test //test:prism --config=dbg --define RUBY_PATH='/opt/hostedtoolcache/Ruby/3.3.0/x64' --test_output=all

Choose a reason for hiding this comment

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

Looks like building takes 20 minutes. It might be worth exploring if we can use something simple like https://github.com/bazel-contrib/setup-bazel and get caching out of the box.

@@ -0,0 +1,3 @@
Integer {
Copy link
Author

@egiurleo egiurleo Jun 14, 2024

Choose a reason for hiding this comment

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

@Morriar here are my .parse-tree.exp tests :)

@egiurleo egiurleo merged commit 271ba85 into proj-parsing-w-prism-in-sorbet Jun 14, 2024
1 check passed
@egiurleo egiurleo deleted the emily/testing-prism branch August 6, 2024 20:22
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