An emulator that let you believe in what it does not check would be worse than useless. This page answers a single question, before you install anything: what can I reasonably test against feint? In short, the API contract, the Terraform lifecycle and your client's behaviour are testable; pricing, quotas, real capacity and IAM permissions are not.
What you will get
By the end of this page, you will decide whether a given test makes sense here, state what establishes each verdict rather than trusting a promise, and recognise the limit that can cost you money.
- Read the matrix row by row, in your own vocabulary.
- Tell apart a "yes" from a "yes, with a runtime".
- Identify the most dangerous limit, the one that lets traffic escape.
- Understand why no coverage percentage is published.
The verdict matrix
Each row carries its verdict and what establishes it. A "no" is not a gap waiting to be filled: it is an assumed decision, with a written reason and a date.
| What you want to validate | Verdict | What establishes it, or the limit |
|---|---|---|
| Terraform or OpenTofu syntax, plan and lifecycle | yes | the conformance suite: apply, empty second plan, destroy |
A full workflow with scw, octl or exo | yes | the same run drives all three clients against one emulator |
| Request and response shapes, field by field | yes, with a stated ceiling | every answer is checked against the provider's own API description |
| That your client reads a field the real cloud returns | yes, where a recording exists | recorded shapes arbitrate; the blind spot is counted |
| Dependency order: what refuses to be deleted under what | yes, while the relation is synchronous | each suite's refusal assertions |
| That your code decodes a refusal | yes | each suite demands refusals and fails when none happened |
| Cloud-init, an SSH login, a firewall that filters | with a runtime | keys on the firewall capability the host declares |
| Two VPCs that cannot reach each other | OVN only | keys on the isolation capability, never on a mode name |
| A load balancer that really distributes | OVN, and per pack | the runtime must be able and the pack must hand over |
That a machine claimed running exists on the host | OVN, per provider | a suite reads the API's claims then checks them with incus |
| Reaching a private address from the host | depends on the mode | keys on the private_from_host capability |
| A kernel-level test: sysctl, modules, the boot path | with a VM runtime | a container shares the host's kernel |
| Retry and backoff against a 429, a 5xx or an outage | yes, on the operations you arm | fault injection, deterministic and off by default |
| That a wrong image or instance-type id is rejected | yes, on demand | --strict-catalog refuses anything outside the catalogue you declare |
| Quotas, real capacity, pricing | no | the catalogue is a measured whitelist |
| Authentication and IAM permissions | no | authentication is accepted, never verified |
A resource that stays provisioning for a while | yes, on demand | --consistency eventual walks the states, one transition per read |
Object Storage, and anything addressed as s3.<region> | no | not emulated, and the traffic really leaves |
| Terraform against Exoscale | yes, from provider 0.71.0 | older versions are refused by user agent |
| Multi-zone or multi-region on Exoscale | no | one zone per process, and the reason is the client |
| Traffic actually flowing through a gateway or NAT | no | records move, packets do not |
How to read a verdict
"yes" means a real client drove that operation in the reference run, and that the suite asserted something about the answer. It does not mean every field of every response is right: two defects sat behind a green axis for months, which is exactly why the project publishes seven independent axes and never adds them up.
"with a runtime" means the control plane answers without one, and the claim only holds when machines are real. The right way to know is to ask the emulator rather than guess:
curl -s localhost:4599/_feint/health | jq .capabilities{ "machines": false, "addresses": false, "firewall": false, "isolation": false, "own_kernel": false, "balancing": false, "private_from_host": false}A capability nobody declares reads as absent, deliberately: a check skips itself rather than asserting what no driver promised. What each mode delivers is detailed in Run real machines.
"no" is a decision with a reason, not an oversight. Several of these rows would change if somebody did the corresponding work, and those are open issues, not omissions.
The most important limit: a local run can still reach the real cloud
This is the only limit on this page that can cost you money. feint serves APIs locally; a client can compose the address of a product outside that perimeter itself, and its requests then leave for wherever they were going.
The measured case is Object Storage. On a real configuration, the same
apply created its IP address on the emulator then carried on to
s3.fr-par.scw.cloud, because the Terraform provider hardcodes that address
for this product. Only a 403 on fake credentials made the operation harmless
that day.
The five refusals, and their reasons
These five rows come up most often, and each is an explicit trade-off rather than work left undone.
Identifiers are not checked against anything, unless you ask. By default, a
create naming an image or an instance type that does not exist succeeds:
Scaleway answers 201 for an image UUID that exists nowhere, Outscale accepts
ami-99999999. The real clouds refuse all three. That default is not an
oversight: the emulator has no inventory, so the only identifiers it could
recognise are the ones it invents, and a configuration that hardcodes a
production identifier would then fail on the one thing that has nothing to do
with what it is testing.
The cost was real, and 0.13.0 gives you a way to remove it where it hurts:
--strict-catalog takes a JSON file where you declare, per provider, the
images, templates and machine types your project allows.
{ "scaleway": {"images": ["ubuntu_jammy"], "types": ["DEV1-S"]}, "outscale": {"images": ["ami-fe1a7001"], "types": ["tinav6.c1r1p2"]}, "exoscale": {"templates": ["Linux Ubuntu 22.04 LTS 64-bit"], "types": ["standard.medium"]}}A create that falls outside it is then refused in each cloud's own error shape, not in a house format: your client renders its ordinary not-found message.
{"Errors":[{"Code":"5023","Details":"The ImageId 'ami-99999999' doesn't exist.","Type":"InvalidResource"}]}Without the flag nothing moves: the compatibility mode is byte-identical, and the sentence above stays true word for word. A provider the file does not mention is not checked.
Authentication is accepted, never verified. No signature is checked, on any provider. Credentials merely have to be well formed, because the SDKs validate their shape client-side before sending anything. The consequence is direct: never expose feint on a network you do not control. It is a development tool that grants everything to everyone, by design.
The catalogue is a measured whitelist. Instance types, images and their attributes come from values recorded at the provider, not from a live inventory. Quotas, real capacity and pricing are therefore absent, and no quota-exhaustion test makes sense here.
Lifecycle transitions are immediate. A server goes from stopped to running
within the call. The states clients check are preserved, such as the
refusal to delete a server mid-transition that Terraform depends on. But a refusal
that only exists during a transient state cannot happen: on a real Outscale
account, a snapshot requested before a volume settles is refused, whereas here the
volume is available at once. A script chaining the two succeeds locally and can
fail on the real cloud.
Traffic does not cross the gateways. On Outscale, a gateway or NAT moves records, not packets. The configuration applies, re-plans empty and destroys; what does not exist is the network path behind it.
The version floor that protects you: Terraform and Exoscale
Terraform drives Exoscale from provider version 0.71.0 onwards, and older
versions are refused. That is not a whim: until that version the published
provider built two clients and only one honoured the
EXOSCALE_API_ENDPOINT variable, so an apply or a destroy split between
the emulator and a billed account.
It was not a theoretical worry. A feint down run in the example stack's
directory sent five signed requests to Exoscale's servers. The emulator
therefore refused that client for ten days, from 26 August to 5 September 2026,
while the fix made its way upstream.
Since then the refusal has turned into a floor. A provider older than 0.71.0
is recognised by its user agent and gets a 400 naming the version to pin;
from 0.71.0 onwards the apply goes through. Two practical consequences for
anyone following the old procedure: feint up no longer stops at the
doorstep, and the FEINT_EXOSCALE_ALLOW_TERRAFORM variable is gone, having
nothing left to lift. The exo CLI was never affected and drives this
provider end to end.
Why no percentage is published
This is a methodological choice, and it explains the shape of this whole page. A single number lets a weak claim ride on a strong one. Saying "94 % coverage" would mix operations whose behaviour is demonstrated with others that merely answer something well formed.
For 0.13.0, the project therefore publishes two numbers that do not add up: 395 operations mounted, of which 375 driven by a real client in the reference run. The remaining 20 state at their route why no official client reaches them. The difference between "mounted", "probed" and "driven" is developed in Measuring fidelity.
Key takeaways
- The API contract and the lifecycle are testable; pricing, quotas and permissions are not.
- A "no" is a dated decision, with a written reason, never an oversight.
- A local run can reach the real cloud: Object Storage is the measured case.
- Identifiers are not verified: a typo passes here and breaks in production.
- Authentication is never checked, so the emulator never goes on an open network.
- Transitions are immediate, so a refusal specific to a transient state cannot be reproduced.
- No percentage is published, because a single number would hide the weak claims.