Skip to main content
Now that we’ve built our plugin and verified it works with unit and integration tests, we’re ready to run conformance tests. These tests verify that your plugin correctly integrates with the formae agent, the component that orchestrates resource lifecycle operations in production.

Why conformance tests?

Unit tests verify your plugin logic in isolation. Integration tests verify it works with real infrastructure. Conformance tests go further: they verify your plugin behaves correctly when orchestrated by the formae agent, which is how it will actually be used. Conformance tests exercise the full resource lifecycle through the same code paths used in production:

What you need

Conformance tests require only PKL files, no Go test code needed. The test framework discovers your PKL files and runs the appropriate lifecycle operations.
The PklProject.deps.json file is auto-generated when you run the tests, so you don’t need to create it manually.

The config/ folder

The testdata/config/ folder is ignored by the conformance test framework. Use it for:
  • Shared variables (vars.pkl): common values like test prefixes, stack names
  • Target configurations (target.pkl): reusable target definitions
  • Helper modules: any PKL files that support your tests but aren’t test cases
This keeps your testdata organized without helper files being picked up as failed tests.

Test lifecycle steps

Step 1: Create

The test framework runs formae apply with your base PKL file (e.g., sftp-file.pkl). This creates the resource through your plugin’s Create method.

Step 2: Read and verify

After creation, the framework uses formae inventory to read the resource back and compares it to the expected state from formae eval. This verifies your Read method returns consistent data.

Step 3: Update (optional)

If you provide an update file (e.g., sftp-file-update.pkl), the framework runs formae apply again with the modified properties. This exercises your plugin’s Update method. Skip this step if your resource type has only immutable (createOnly) properties.

Step 4: Replace (optional)

If you provide a replace file (e.g., sftp-file-replace.pkl) that changes a createOnly property, the framework verifies that formae correctly deletes and recreates the resource.

Step 5: Delete

The framework runs formae destroy to remove the resource, exercising your plugin’s Delete method.

Step 6: Discovery

Finally, the framework tests resource discovery:
  1. Creates a resource directly via your plugin (bypassing formae)
  2. Runs discovery to verify formae detects the unmanaged resource
  3. Cleans up the resource
This verifies your List method works correctly for discovery scenarios.

Test data files

Base file (sftp-file.pkl)

The base file defines the initial resource state:
The FORMAE_TEST_RUN_ID environment variable is set by the test framework to ensure unique resource names across test runs.
Include the test run ID in resource names and use a consistent prefix (like conformance-test-). This makes it easy to identify and clean up test resources, especially after interrupted test runs.

Update file (sftp-file-update.pkl)

Changes mutable properties only:

Replace file (sftp-file-replace.pkl)

Changes a createOnly property, triggering delete + create:

PklProject

The testdata/PklProject file configures PKL dependencies:
The version in the URI is automatically updated when you run conformance tests.

Running conformance tests

Conformance tests consist of two types: CRUD tests that verify the resource lifecycle, and discovery tests that verify your plugin can find existing resources. By default, both are run together:
By default, this downloads and tests against the latest stable release of formae.

Filtering tests

To run a specific test by name:
The TEST parameter filters tests by name pattern. This is useful when debugging a specific resource type.

Regex filters

Wrap a filter segment in /…/ delimiters to use a regular expression:
Regex patterns match against the test case name and resource type. An invalid regex causes the test run to fail immediately with a clear error message. Comma separation continues to work: each segment is independently evaluated as either a regex (if wrapped in /…/) or a literal (exact match first, then substring fallback).

Running CRUD or discovery only

CRUD tests exercise the full resource lifecycle: create a resource, read it back, optionally update it, and delete it. These verify your plugin’s Create, Read, Update, and Delete operations work correctly with the formae agent. Discovery tests verify that your plugin can find existing resources that weren’t created through formae. The test creates a resource directly via your plugin, then runs discovery to confirm formae detects it. To run only CRUD lifecycle tests:
To run only discovery tests:
Both targets support the TEST and VERSION parameters:

Targeting a specific version

To test against a specific formae version:
This is useful for:
  • Testing compatibility with older formae versions
  • Reproducing issues reported by users on specific versions
  • Validating your plugin before upgrading formae

What happens

When you run make conformance-test:
  1. Downloads the specified formae version (or latest)
  2. Updates PKL dependencies to match that version
  3. Builds and installs your plugin locally
  4. Runs clean-environment.sh to remove leftover test resources
  5. Executes the conformance test suite
  6. Cleans up test resources

Environment variables

The conformance test framework reads several environment variables to control behavior: FORMAE_TEST_OOB_TIMEOUT vs FORMAE_TEST_OOB_DELETE_TIMEOUT. These bound different waits in different phases and are independent:
  • FORMAE_TEST_OOB_TIMEOUT covers the slow part: your plugin’s Create/Delete RPC for the OOB resource finishing on the cloud side (e.g. EKS or OVH Kube cluster reaching a terminal state).
  • FORMAE_TEST_OOB_DELETE_TIMEOUT covers the much shorter wait after that RPC returns: the agent’s sync detecting the OOB deletion and tombstoning the inventory entry.
Most plugins won’t need to override either. Defaults cover everything we currently exercise on AWS, OVH, and GCP. Example with custom timeouts:
Or set timeouts separately in CI (e.g. for a slow managed-Kubernetes resource):

Environment cleanup

The scripts/ci/clean-environment.sh script removes test resources before and after tests. Customize it for your infrastructure. The script should be idempotent (safe to run multiple times) and delete all resources matching the test prefix. For our SFTP plugin, we list files matching the prefix, then delete them individually (wildcards don’t work reliably in sftp batch mode):

Verify

With the test data files in place, run the conformance tests:
You should see output showing each test phase:

Next: 12 - CI Setup - Set up continuous integration for your plugin