Skip to content

Commit ea68b61

Browse files
authored
[static] enable unit testing in Pulumi projects (#3288)
Signed-off-by: Mateusz Błażejewski <mateusz.blazejewski@digitalasset.com>
1 parent e6eaf43 commit ea68b61

File tree

8 files changed

+7477
-4095
lines changed

8 files changed

+7477
-4095
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ repos:
5050
language: system
5151
entry: "scripts/fix-ts.py"
5252
types_or: [javascript, jsx, ts, tsx]
53-
exclude: "/[.]prettierrc[.]cjs$"
53+
exclude: "(/[.]prettierrc[.]cjs$|jest.config.ts$)"
5454
- id: pulumi_config
5555
name: check pulumi configs
5656
language: system

TESTING.md

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
- [Handling Errors in Integration Tests](#handling-errors-in-integration-tests)
2424
- [Connecting external tools to the shared Canton instances](#connecting-external-tools-to-the-shared-canton-instances)
2525
- [Testing App Upgrades](#testing-app-upgrades)
26-
- [Testing from a custom canton instance]
26+
- [Testing from a custom canton instance](#testing-from-a-custom-canton-instance)
2727
- [Deployment Tests](#deployment-tests)
28+
- [Helm checks](#helm-checks)
29+
- [Pulumi tests](#pulumi-tests)
30+
- [Pulumi state checks](#pulumi-state-checks)
2831
- [CI Without Approval](#ci-without-approval)
2932

3033
# Testing in Splice
@@ -430,9 +433,47 @@ When writing or debugging Helm tests, it is often useful to run `helm unittest`
430433
This produces rendered yaml files under a local `.debug` folder
431434
that can be inspected to understand errors or determine the correct paths for assertions.
432435

433-
### Pulumi checks
436+
### Pulumi tests
434437

435-
Our pulumi checks are based on checked in `expected` files that need to be updated whenever the expected deployment state changes.
438+
We use [jest](https://jestjs.io/) for unit tests in Pulumi projects. With these tests we aim to cover
439+
more complex parts of the deployment implementation and resource definitions. To do the latter we
440+
recommend using techniques described [here](https://www.pulumi.com/docs/iac/guides/testing/unit/).
441+
These tests are meant to be fast as they are run both in CI and in pre-commit hooks.
442+
443+
#### Running tests
444+
To run unit tests from all Pulumi projects run `make -C $SPLICE_ROOT cluster/pulumi/unit-test`.
445+
Any more specific run requires using NPM directly. To do that the following prerequisites must be met:
446+
1. build the Pulumi codebase `make -C $SPLICE_ROOT cluster/pulumi/build`
447+
1. navigate to the root package with `cd $SPLICE_ROOT/cluster/pulumi`
448+
449+
To run all tests from one or more Pulumi projects execute the following command.
450+
```
451+
npm exec -- jest --selectProjects <project> [project]...
452+
```
453+
Specific suites are selected by passing one or more patterns as positional arguments. For example
454+
the following matches `$SPLICE_ROOT/cluster/pulumi/common/src/retries.test.ts` but also any suite
455+
that contains `retries` in its path.
456+
```
457+
npm exec -- jest retries
458+
```
459+
Lastly, individual tests can be filtered using the `--testNamePattern` or `-t` for short as shown
460+
in the following example:
461+
```
462+
npm exec -- jest -t "retry runs the action again if it fails with an exception"
463+
```
464+
All the above ways of filtering tests can be combined to achieve the desired run. More information
465+
on jest CLI can be found [here](https://jestjs.io/docs/cli). One might also opt for an IDE extension.
466+
467+
#### Adding new tests
468+
A new test can be added to an existing `*.test.ts` file or to a new `<module>.test.ts` suite where
469+
`<module>.ts` contains the unit to be tested. These suite files lie next to the module files which
470+
they cover. Please see the [jest "getting started"](https://jestjs.io/docs/getting-started) guide
471+
for tips on how to write tests. The type definitions for jest come from the `@jest/globals`
472+
package usage of which is described [here](https://jestjs.io/docs/getting-started#type-definitions).
473+
474+
### Pulumi state checks
475+
To make sure that the impact of changes in the Pulumi resource definitions is well understood we
476+
rely on checked in `expected` files that need to be updated whenever the expected deployment state changes.
436477

437478
Please run `make cluster/pulumi/update-expected` whenever you intend to change Pulumi deployment scripts in a way that alters deployed state.
438479
Compare the diff of the resulting `expected` files to confirm that the changes are as intended.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { afterAll, expect, jest, test } from '@jest/globals';
4+
5+
import { retry } from './retries';
6+
7+
// suppresses error logs from retry to get cleaner test output
8+
const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {});
9+
afterAll(() => {
10+
consoleErrorMock.mockRestore();
11+
});
12+
13+
test('retry runs the action again if it fails with an exception', async () => {
14+
let executionIndex = 0;
15+
async function unreliableAction(): Promise<void> {
16+
try {
17+
if (executionIndex === 0) {
18+
throw new Error('action failed');
19+
}
20+
} finally {
21+
++executionIndex;
22+
}
23+
}
24+
await expect(retry('', 0, 1, unreliableAction)).resolves.toBeUndefined();
25+
expect(executionIndex).toBe(2);
26+
});

cluster/pulumi/jest.config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import type { Config } from '@jest/types';
5+
import { createDefaultPreset } from 'ts-jest';
6+
import pkg from './package.json' with { type: 'json' };
7+
8+
const config: Config.InitialOptions = {
9+
preset: 'ts-jest',
10+
testEnvironment: 'node',
11+
testMatch: ['<rootDir>/src/**/*.test.ts'],
12+
projects: pkg.workspaces
13+
.filter(workspace => !['observability'].includes(workspace))
14+
.map(projectDir => ({
15+
displayName: projectDir,
16+
rootDir: projectDir,
17+
transform: createDefaultPreset().transform,
18+
})),
19+
};
20+
export default config;

cluster/pulumi/local.mk

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ $(dir)/clean:
1818
$(dir)/format: $(dir)/.build
1919
cd $(@D) && npm run format:fix
2020

21+
.PHONY: $(dir)/unit-test
22+
$(dir)/unit-test: $(dir)/.build
23+
cd $(@D) && npm run test
24+
2125
pulumi_projects ::= operator deployment gcp infra canton-network sv-runbook validator-runbook multi-validator cluster sv-canton validator1 splitwell
2226

2327
.PHONY: $(dir)/test $(dir)/update-expected
24-
$(dir)/test: $(foreach project,$(pulumi_projects),$(dir)/$(project)/test)
28+
$(dir)/test: $(dir)/unit-test $(foreach project,$(pulumi_projects),$(dir)/$(project)/test)
2529

2630
.PHONY: $(dir)/update-expected
2731
$(dir)/update-expected: $(foreach project,$(pulumi_projects),$(dir)/$(project)/update-expected)

0 commit comments

Comments
 (0)