AL Runner

What works and what is refused

The goal is the whole AL language. How much runs today is in the status table in the README and in docs/status.md. The scope decisions are ADR-027.

The rule

A feature AL Runner does not support is refused. The test is reported as not run, with the reason, and the exit code is 7 unless you pass --allow-not-run. A guessed or partial behavior is never used as a stand-in. Refusal happens before the run where possible, and mid-run otherwise.

There are two kinds of reason:

  • By design. The surface needs a process, a network or a service tier that AL Runner does not have. It will stay refused.
  • Not measured yet. AL Runner will not guess behavior that no corpus test pins on real Business Central. This group shrinks as tests are added.

Status

Measured on 2026-10-21 against the AL language test corpus (commit f3fe6c99). A test counts as passing only when it passes here and passed on real Business Central.

Business CentralPassingFailingNot run
27.513964 of 17050 (81.9%)03086
28.514002 of 17140 (81.7%)03138
29.014023 of 17234 (81.4%)03211

“Failing” is a case where real Business Central passed and AL Runner gave a different result or an error. About 435 passes on each version run through an unprobed assumption and are counted as flagged. Microsoft’s own Base Application test buckets mostly do not run yet: the full run of 2026-10-16 held 40300 tests, of which 16848 passed, 2508 failed and 20944 were not run ( docs/alrunner-buckets.md).

Runs as Business Central does

  • The AL language: parsing, #if, type checking, overload resolution, namespaces and extensions.
  • Records: create, read, update and delete; filters; keys; FlowFields; CalcSums; SetLoadFields; rename propagation; TransferFields; RecordRef and FieldRef.
  • Triggers and events: table, page and codeunit triggers; integration and business events; subscribers; manual binding; order.
  • Errors: asserterror, TryFunction, GetLastError*, error text and code.
  • Collections and data: List, Dictionary, JSON, XML, streams, BLOBs, TextBuilder, queries (the shapes the corpus pins), XmlPort import and export.
  • Reports, processing only: Run, RunModal, Execute, RunRequestPage, data items, the triggers from OnPreReport to OnPostReport, and the dataset as XML.
  • Pages, headless: the test client opens pages, sets values, validates, runs actions, navigates, and drives modal pages and handlers.
  • Microsoft’s apps, compiled from their own source. Posting routines, validation and System Application modules run as written.
  • Outbound HTTP, answered by an [HttpClientHandler] in the test. A call with no handler raises the error Business Central raises under BlockOutboundRequests.

Replaced by something equivalent

SurfaceWhat AL Runner doesWhere it ends
TablesAn in-memory storeNo row locks, no other session, no SQL errors. LockTable(false) is refused.
TransactionsSnapshots. A passing test commits at its boundary. asserterror rolls back.Commit order across sessions is not modeled.
Test dataCRONUS from the artifact, loaded per table on first touchOnly the demo company.
IdentityUser TESTUSER, tenant default, company My Company, session 1Tests that probe real authentication or other users.
Environment--environment saas (default) or onpremServer topology is fixed.
ClockThe system clock. Sleep(n) does not wait.A test sees a jump, not a delay.
Client UIHandlers and the TestPage clientNo pixels, no layout, no add-ins.
PermissionsEverything is grantedA test that expects “access denied” fails here.
Task schedulerCanCreateTask is false. CreateTask returns an id and never runs the task.A task never executes.
StartSessionStarts nothing during an install; raises Business Central’s error in a test runNo second session exists.
Isolated storageOne store per appUser scopes are refused.
Table connectionsBookkeeping, and the @@test@@ CRM connectionAny other connection string is refused.

Refused by design

AreaInstead
Email, SMTP and ExchangePut the send behind an AL interface and fake it in the test.
Cloud storage, Key Vault, external data connectionsFake the interface.
File upload and download dialogs (a browser round trip)Test with streams.
Report rendering to PDF, Word, Excel or HTML. The XML dataset works.Assert on the dataset.
Web service endpoints (OData, SOAP, API)Call the page or codeunit directly, or use TestPage.
Background jobs and the job queue running tasksCall the code the task would run.
Licensing and entitlement denial pathsUse a real service tier.
Multi-user locking, deadlocks, SQL error behaviorUse a real service tier.
Windows-only .NET libraries (System.Drawing, imaging)Fake the image step.
Apps that ship without AL sourceProvide an .app with source. The message names the app.

DotNet

The core needs no .NET. AL code that uses the DotNet type is served by an optional .NET 8 helper process. A closed set of pure framework members runs inside al-runner and needs no helper: StringBuilder, byte[] access, Encoding, SHA and MD5 hashes, and Base64 conversion.

Tests that reach any other DotNet call need the .NET 8 SDK. The binary carries the helper sources and builds the helper into the cache on first use. Without .NET those tests are not run, with the reason, and the exit code is 8. --require-dotnet turns a missing helper into an error, for CI. Arbitrary .NET that reaches the machine (System.IO.File, System.Net, Process, the registry) is refused by a policy list.

Not available yet

  • Watch mode and the debugger. See Editor and tool integration.
  • The two Contoso lanes of --recipe microsoft.
  • Builds for platforms other than Linux x64, Windows x64 and macOS arm64.

If you hit a “not run” that none of this explains, see Reading results.