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

Add testing for R packages #915

Merged
merged 12 commits into from
Oct 4, 2023
Merged

Add testing for R packages #915

merged 12 commits into from
Oct 4, 2023

Conversation

juliasilge
Copy link
Contributor

A draft for now!

const fileContents = vscode.workspace.openTextDocument(uri);
const matches = [];

// TODO: get the tests out of the file contents
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Major TODO right here: how to get the tests out of the file? As currently implemented, we need:

  • a label for the test (like from the first argument to testthat::test_that() plus an identifier for which test it is in that block)
  • a vscode.Position for the start
  • a vscode.Position for the end

Copy link
Contributor Author

@juliasilge juliasilge Jul 25, 2023

Choose a reason for hiding this comment

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

EDITED TO SAY: I think this is probably a bad idea, upon further exploration:

If instead we made matches an array of strings (code for each test), we could use executeCode() to run them. However, that runs code in the console. That might be right for an interactive develop/test type workflow.

We should just make matches an array of label + start position + end position.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is definitely out of scope for this PR because it involves some fairly extensive new work; let's merge with dummy data about where tests are.

Copy link
Member

Choose a reason for hiding this comment

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

I will hopefully make some progress on delivering the coordinates needed to address individual tests in the next week 🤞


return Promise.all(
vscode.workspace.workspaceFolders.map(async workspaceFolder => {
const pattern = new vscode.RelativePattern(workspaceFolder, 'tests/testthat/*.R');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is testthat specific, which I think is fine for alpha. In the longer term, we'll want to support more than just testthat.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Finding the files for Runit or tinytest (or no testing framework?) will not be that hard, but the parsing will be fairly different.

@juliasilge
Copy link
Contributor Author

As of right now, this correctly identifies testthat test files in a real R package and they populate in the testing UI (with the fake matches found via findTests() for now).

@juliasilge
Copy link
Contributor Author

This now will actually run tests! 🎉

(I am still manually doing library(testthat) and devtools::load_all() before running a test though.)

@juliasilge
Copy link
Contributor Author

This is now behind a feature flag:

Screenshot 2023-07-27 at 2 13 50 PM

const matches = [];

// TODO: get the tests out of the file contents
// This is just dummy example data from my cereal package:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For fellow developers on this, here is where you put in some dummy matches for where tests are.

const startIndex = document.offsetAt(range.start);
const endIndex = document.offsetAt(range.end);
const testSource = source.slice(startIndex, endIndex);
positron.runtime.executeCode('r', testSource, true);
Copy link
Contributor Author

@juliasilge juliasilge Jul 27, 2023

Choose a reason for hiding this comment

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

As of right now, this always succeeds (i.e. tests can never fail) because we do not yet return anything from executeCode other than a logical about whether the code was sent off. This also is out of scope for this PR and we will come back to it.

@juliasilge juliasilge marked this pull request as ready for review July 27, 2023 21:43
@juliasilge
Copy link
Contributor Author

Let me outline some of what still needs to be done to make this usable:

  • Discover tests in test files via a parser
  • Examine how we're going to run the tests (probably update executeCode to return something needed here like errors, probably add ability to run test in a "clean room" kind of fresh process)

Longer term, we'll want to add coverage runs. Even longer term we'll want to support tests other than testthat.

@@ -59,5 +60,8 @@ export function activate(context: vscode.ExtensionContext) {
// Register commands.
registerCommands(context);

// Discover R package tests.
discoverTests(context);
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 like how I gave this function a name that says it does something it literally does not do at all right now. 😆 For the long term (i.e. after we do parsing) is this what we want to name this function? Other ideas?

}
};

controller.createRunProfile(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the longer term, we will have multiple run profiles, like for "run in current process", "run in fresh process", and "coverage" at least.

@juliasilge juliasilge requested review from jmcphers and jennybc July 27, 2023 22:00
Copy link
Member

@jennybc jennybc left a comment

Choose a reason for hiding this comment

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

I can only review this from a somewhat weird perspective, but I went ahead and took one pass, in the name of getting this excellent start merged in before @juliasilge is OOO.

extensions/positron-r/src/testing.ts Outdated Show resolved Hide resolved
extensions/positron-r/src/testing.ts Outdated Show resolved Hide resolved
);
context.subscriptions.push(controller);

controller.resolveHandler = async (test) => {
Copy link
Member

Choose a reason for hiding this comment

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

Presumably this will become clear as I engage with this further, but what is test? E.g. is it a file(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.

It's a testItem, which lives in like a little tree structure and can be the whole test suite, some subset of the tests that belong together, or a single test. As I've implemented this here, I believe we have whole test suite, a file of tests, or an individual test. There's a bit of info here:
https://code.visualstudio.com/api/references/vscode-api#TestItem

extensions/positron-r/src/testing.ts Outdated Show resolved Hide resolved

async function runTest(run: vscode.TestRun, test: vscode.TestItem): Promise<vscode.TestRun> {
if (test.children.size > 0) {
test.children.forEach(childTest => runTest(run, childTest));
Copy link
Member

@jennybc jennybc Jul 27, 2023

Choose a reason for hiding this comment

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

This feels like we're delegating operating on the test hierarchy to the VS Code infrastructure. I.e. as if running a test file = running each test in the file, and running all test files = running the tests in each file. Which seems totally logical except ...

We would normally call testthat::test_file() to run one file's-worth of tests and devtools::test() run one one package's-worth of tests. Those approaches come with goodies like doing appropriate setup/teardown (like load_all(), sourcing test helpers), reporting, etc.

Are we going to have think about how to align these conventional approaches with what this testing harness does?

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 think "yes" is the answer here. Right now, with just sending everything to the console for a very interactive workflow, the user would be required to manage this like if they were using cmd + enter. When we add in other run types, we'll need to take on these requirements.

@jmcphers
Copy link
Collaborator

jmcphers commented Oct 3, 2023

@juliasilge What's the status of this PR? Happy to review if you still want to merge it. By my reading it's largely disengaged unless enabled so I am not concerned about stability implications.

@juliasilge
Copy link
Contributor Author

@jmcphers yes, this is the scaffolding we need so I am in favor of merging it. It sounds like there are some questions about whether we should parse the tests using tree sitter in a middleware layer or the lower level Rust, but either way, IMO it's better to have this merged in so folks can concretely see what needs to be generated.

@jmcphers
Copy link
Collaborator

jmcphers commented Oct 4, 2023

Ok, let's get it in!

@jmcphers jmcphers merged commit 1bce527 into main Oct 4, 2023
1 check passed
@juliasilge juliasilge deleted the add-testing-support branch October 4, 2023 17:04
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