GSoC 2025: Support writing tests with pytest

Hi GRASS devs,

I am Kriti Birda, an undergraduate student of Bachelor of Science at Indian Institute of Technology, Madras. I participated in GSoC with Grass GIS last year. For the past few months, I took a break from open source and contributing to Grass to prioritize my health. I have been feeling better recently and hope to contribute more often in the coming days.

As part of my work on adding JSON output support to several grass modules last year, I wrote many tests using the gunittest framework. Thus, I feel I am well suited to tackle the “Support writing tests with pytest” project. I have opened Handle null cases in assertRasterMinMax by kritibirda26 · Pull Request #5440 · OSGeo/grass · GitHub to fix a small issue in gunittest. This week, I hope to reach out to the relevant mentors to discuss more about the project, learn in depth about gunittest and make a few PRs.

Regards.

1 Like

Hi!

I have opened a pull request to migrate one module to pytest to ensure my understanding of the project requirements is correct, r.info: migrate to pytest by kritibirda26 · Pull Request #5460 · OSGeo/grass · GitHub.

Can someone please take a brief look at it and provide suggestions if any so that I can proceed with preparing a proposal?

Regards.

Hi Kriti,

yes, please create a proposal and we will comment there. We are not necessarily trying to migrate existing unittests now, but the topic would be 1) enabling running pytest with existing testing dataset and 2) providing the similar functions for convenient comparing geospatial data (e.g. assertRasterMinMax) in pytest. This would in long term help with migration to pytest.

For 1), I have mostly this figured out, in pytest: Run gunittest-based tests using a fixture to create a temporary copy of data directory by echoix · Pull Request #380 · echoix/grass · GitHub (I limited myself to vector), but there’s some annoying issues that I can’t figure out how to solve correctly.
First, is that in order to have all the needed grass session env vars set up and the test dataset available, I have to run pytest inside a grass - -exec call, that is inside the test project dataset. That means a bad test, or that overwrites a layer with a name from the sample dataset will impact others. How to solve? Either redo something like gunittest does for launching a new process and copying stuff (we would have the same overhead as before), or reimplement the parts needed to bootstrap launching grass that the C parts needs, a bit like the init script.

Another annoying issue, is that when running gunittest/unittest tests with pytest, the setUp, setUpClass etc are implemented as automatically generated autouse fixtures, but since they can’t be changed to use other fixtures, they are ran before I can define another autouse fixture to « fix » the problems they have when running (and copy the locally defined data folder to a temporary folder). Reading the pytest scopes docs attentively, I could only make it work by having gunittest tests avoid setUpClass calling grass functions, and use a per-test setup only when using pytest, that would be correctly run, since the autouse fixture would catch on correctly there. That means that creating datasets are redone for each test (slower than needed, especially on Windows).
While finding out how to solve that, I observed a known behaviour of setUpClass/tearDownClass (and all the other variants for per-test, per-module, etc.): the teardown only happens on success of the paired setUp function. When I encountered an error somewhere in a setUp, or stopped the test when a setUp function was executing, then the sample dataset was left broken, and next tests couldn’t run. Since Python 3.8, there’s a solution for this, with addCleanup and other scoped variants unittest — Unit testing framework — Python 3.13.2 documentation. I tried it in the PR I linked above, it totally replaces any tearDown functions and I think it’s a better pattern for us. You add functions to call with all arguments needed, and they’ll be executed in reverse order (LIFO), which is exactly what we want. On unsuccessful setUp, the calls already added will be executed. It works quite well.

Another issue, (more pytest related, or when using grass_setup.init() for a session) that I often end up needing to do a monkeypatch env magic, is related to how env vars are handled by python. I fell multiple times in that rabbit hole. Briefly, there’s currently no way to have the env vars changed to be loaded back/acknowledged by Python, if it was not changed by that Python process. Since any first call to a grass C-based module/function will change some env vars, that’s the root cause of [Bug] [help-needed] gs.create_project doesn't seem enough to get a working session on Windows · Issue #4480 · OSGeo/grass · GitHub. I encountered this again, and shuffling the order of tests ran is useful to point this out. Some problems are hidden just because we always run tests in the same order, and the first call to a C-based module isn’t failing. The entire need for so many env vars to make grass (C based) work is kinda hard. Some parts of g.gisenv are also problematic for Python work.

Hi!

I had worked on a proposal but I misunderstood the goals of the project so I will rework it to align to the actually required goals.

Here is the old proposal anyway:

Currently, the tests with pytest create a new project instead of using the existing project. AFAIU gunittest uses the existing project but reading through the source I didn’t find where it copies any data. Can you point me to where gunittest does that?

Hi again!

I have updated the proposal with my understanding of the project so far. I think I am still missing some pieces of the puzzle. I think once I understand the project better I’ll be able to finish up the timeline as well.

In this file, there’s multiple places that files gets copied over, including a local “data” subfolder:

A related question, to my understanding pytest can’t directly run shell script tests using gunittest and such tests would need to rewritten to using python to be able to run under pytest?

IIUC, the code doesn’t copy data from one project to another but from one location to another inside the same project?

Hi Kriti! Nice to see you back around!

The new term project refers to the old term location. This change in terminology was adopted last year in most places facing users, i.e., GUI and manuals, but it has not yet been implemented in the API or tests. Maybe that gives some context?

In short, wherever you see location, it means project. Then, within projects, there are mapsets. That naming remains.

HTH,
Vero

Hi @veroandreo, Nice to see you again too!

Ah makes sense, yes I got a bit confused. To rephrase my question, the code in the linker invoker file can only copy mapsets inside a given location/project but not between two different projects/locations (because possible differences in projection etc.) ?

Hi again,

I have made some more updates to the proposal based on my understanding of the project so far.

@wenzeslaus Thanks for reviewing my proposal. I have taking the comments into consideration.