Skip to content

More and better unit tests#15

Merged
cap10morgan merged 21 commits intomainfrom
tests/more-and-better-unit-tests
Nov 3, 2025
Merged

More and better unit tests#15
cap10morgan merged 21 commits intomainfrom
tests/more-and-better-unit-tests

Conversation

@cap10morgan
Copy link
Member

@cap10morgan cap10morgan commented Oct 29, 2025

This started as me just experimenting with how much work it would be to migrate the resources unit tests (😅). Pretty quickly I ran into issues of the tests not being hermetic; both with respect to each other and with respect to the broader system.

So I dove back down that rabbit hole, but this time I hit pay dirt.

These unit tests all pass by themselves and together, and don't care one lick what is or isn't in your home directory (at least the Harper-y stuff; ~/.harperdb, ~/hdb, etc.).

This PR introduces a new unitTests/testUtils.ts module that defines two important functions:

  • createTestSandbox - this creates an environment in which tables can be created and used, but does not need nor use any existing Harper installation; it should be called in a before hook (or a beforeEach if you find it really necessary)
  • cleanupTestSandbox - this cleans up after createTestSandbox and should be called in the after hook (or afterEach if you are calling createTestSandbox in beforeEach). This one is async, so if it isn't the only thing you're passing to the hook (e.g. after(cleanupTestSandbox);), be sure to await it so it finishes its job before other tests are run.

@cap10morgan cap10morgan marked this pull request as ready for review October 29, 2025 21:42
@cap10morgan cap10morgan requested a review from a team October 29, 2025 21:42
@cap10morgan cap10morgan requested a review from a team as a code owner October 29, 2025 21:42
@cap10morgan cap10morgan marked this pull request as draft October 31, 2025 15:35
@cap10morgan
Copy link
Member Author

We found some additional isolation work is needed from config / data in the home directory.

The prior approach broke the ability to specify a subset of tests on the command line
It's pretty flaky, but we're working on it
The before hook is failing pretty often
@cap10morgan cap10morgan marked this pull request as ready for review October 31, 2025 19:10
Copy link
Member

@Ethan-Arrowood Ethan-Arrowood left a comment

Choose a reason for hiding this comment

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

Everything looks great. One code nit pick and a necessary change in the package.json scripts. nice work!

Comment on lines 16 to 21
Copy link
Member

Choose a reason for hiding this comment

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

Not sure how many tables there'd be but you can make this significantly faster by doing:

Suggested change
for (const Table of envs) {
try {
await Table.delete();
// eslint-disable-next-line no-empty
} catch (err) {}
}
await Promise.all(envs.map(table => table.delete())).catch();

Copy link
Member Author

Choose a reason for hiding this comment

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

This is old code from the other repo's test_utils.js so I just copied it over. But sounds like a good change 👍🏻

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in e692c5e

package.json Outdated
Copy link
Member

Choose a reason for hiding this comment

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

Remove this.

The point of splitting test:unit and test:unit:all is so that you can run npm run test:unit <specific-unit-test-path> in order to run a singular test file. The test:unit:all passes in the unitTests directory to test:unit so you can run all the unit tests.

Copy link
Member Author

@cap10morgan cap10morgan Oct 31, 2025

Choose a reason for hiding this comment

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

Hmm... so now with this change running npm run test:unit gives you an error that there are no tests. I wasn't aware that test:unit:all was added so this was surprising and seemed broken. But this is all pretty new so no big deal and now I know. I do wish there were a reasonable way to output a more helpful error message, or even just have this default to "all" when nothing is specified, but I can't think of one.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in 9084d1e

Copy link
Member

Choose a reason for hiding this comment

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

Agreed; I wish there was a better default or something. I couldn't think of a reasonable solution either that didn't involve like executing it via a mini cli script.

@cap10morgan cap10morgan merged commit fb71d95 into main Nov 3, 2025
7 checks passed
@cap10morgan cap10morgan deleted the tests/more-and-better-unit-tests branch November 3, 2025 17:44
maxHeaderSize: env.get(terms.CONFIG_PARAMS.HTTP_MAXHEADERSIZE),
};
if (keepAliveTimeout) {
options['keepAliveTimeout'] = Number(keepAliveTimeout);
Copy link
Member

Choose a reason for hiding this comment

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

eslint lets you get away with using strings in brackets here? Why?

Copy link
Member Author

Choose a reason for hiding this comment

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

Should it not? IIRC I did this instead of actually specifying an interface for the options. But I can certainly go back and do that instead.

Copy link
Member

Choose a reason for hiding this comment

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

This is a workaround for violating TypeScript types? TIL!

Copy link
Member

@kriszyp kriszyp left a comment

Choose a reason for hiding this comment

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

Why are we converting unit tests to TypeScript? If we want our code to be used by TypeScript and JavaScript, isn't JavaScript the broader language that would provide better unit testing coverage?

Copy link
Member

@kriszyp kriszyp left a comment

Choose a reason for hiding this comment

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

I thought we had been using a convention of module-test.js for test names. Where did we decide to change this?

@cap10morgan
Copy link
Member Author

I thought we had been using a convention of module-test.js for test names. Where did we decide to change this?

I wasn't aware we had a convention, and had already run into both the one I used here and the one you describe in the tests that had been brought over from the old repo. I ran across something that said .test.[tj]s was the most common convention in the JS community so picked that one. But if we have a different one we've already decided on, I'm happy to change the filenames to conform to it.

I was planning to write up some conventions for tests in this repo (in a contributing doc or similar), but it sounds like there are some already written up I need to (re-)familiarize myself with. Do you have a link?

*
* - WSM 2025-10-31
*/
describe.skip('Types Validation', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Do we have a follow-up ticket for this? This seemed to be a pretty reliable test before.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll create it along with others for the other skipped tests.

function getHomeDir() {
let homeDir = undefined;
// for hermetic tests
if (process.env.OVERRIDE_HOME_DIR) {
Copy link
Member

Choose a reason for hiding this comment

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

Does this function differently than process.env.ROOTPATH?

Copy link
Member Author

Choose a reason for hiding this comment

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

Does this function differently than process.env.ROOTPATH?

Yes, but that seems to work just as well in its place, so I'll issue a followup PR that switches over. Thanks!

maxHeaderSize: env.get(terms.CONFIG_PARAMS.HTTP_MAXHEADERSIZE),
};
if (keepAliveTimeout) {
options['keepAliveTimeout'] = Number(keepAliveTimeout);
Copy link
Member

Choose a reason for hiding this comment

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

This is a workaround for violating TypeScript types? TIL!

@cap10morgan
Copy link
Member Author

Why are we converting unit tests to TypeScript? If we want our code to be used by TypeScript and JavaScript, isn't JavaScript the broader language that would provide better unit testing coverage?

I think having them be TypeScript gives us (optional) tools to improve the quality of our tests just like it does for the code itself. And I have a hard time imaging test code that passes in TS but would allow a valid JS equivalent to fail (it's entirely possible that this is a failure of my imagination, however!). I'd be curious to hear what others in the community think about this, though.

It also allows us to use the @ path alias in the test imports, but that's probably not sufficient justification to use TS all on its own.

@kriszyp
Copy link
Member

kriszyp commented Nov 3, 2025

Do you have a link?

https://github.com/HarperFast/harperdb/tree/main/unitTests has hundred(s?) of test cases with module-test.js naming convention.

@cap10morgan
Copy link
Member Author

Why are we converting unit tests to TypeScript? If we want our code to be used by TypeScript and JavaScript, isn't JavaScript the broader language that would provide better unit testing coverage?

And I have a hard time imaging test code that passes in TS but would allow a valid JS equivalent to fail (it's entirely possible that this is a failure of my imagination, however!). I'd be curious to hear what others in the community think about this, though.

But perhaps you're thinking of valid JS code that isn't valid TS code that we'd want to test for expected behavior? I guess we'd either have to sprinkle around directives to expect TS errors here and there in some test code, or, like you said, keep the tests written in JS. I think I prefer the former, but it's not a strongly-held opinion. Again I would love to hear what others think here.

@cap10morgan
Copy link
Member Author

Do you have a link?

https://github.com/HarperFast/harperdb/tree/main/unitTests has hundred(s?) of test cases with module-test.js naming convention.

That's true and fair. I had started with bringing over the components unit tests, which were all thingy.test.js and then brought over these so conformed them to those after reading that it was the more common convention in the broader JS community (and the fact that this repo is kind of a "reset" along these lines).

I am happy to use either convention, though!

@cap10morgan cap10morgan mentioned this pull request Nov 3, 2025
@kriszyp
Copy link
Member

kriszyp commented Nov 4, 2025

I don't see how this properly going to track with cherry-picking, since the tests were copied in with different names and there is no rename history. I think this PR needs to be reverted and submitting again with direct filename preservation (like unitTests/resources/blob-test.js), so we properly keep our repositories in sync.

@cap10morgan
Copy link
Member Author

I don't see how this properly going to track with cherry-picking, since the tests were copied in with different names and there is no rename history. I think this PR needs to be reverted and submitting again with direct filename preservation (like unitTests/resources/blob-test.js), so we properly keep our repositories in sync.

Makes sense. Maybe if we decide on a naming convention (because there would already be two different ones in here with just these two categories of unit tests), we could bring the files in w/ their existing names, commit, rename, commit, and then adapt them to this repo?

Thoughts, @Ethan-Arrowood?

@Ethan-Arrowood
Copy link
Member

Yeah so git is actually pretty smart as long as you do the rename through a commit as you described Wes.

Seeing some of Kris' comments now I think redoing this PR would be best.

Since this was merged using a merge commit, adding many individual commits, I think we may just want to reset main branch to before this PR was merged and try again (rather than adding a "revert commit"). This should help us clean up main branch history so its easier for me to sync later.

I'll confirm this procedure on today's standup call

@Ethan-Arrowood
Copy link
Member

For public record:

We confirmed on our eng call today to reset the main branch and redo this PR. I'll be completing this shortly and then posting about the main branch update across relevant channels promptly.

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.

4 participants

Comments