What is the dev context and how does it relate to testing in EK9?

← Code Quality · Ref: Q767

EK9 enforces a strict compile-time boundary between test code and production code using the 'dev context' concept.

THE DEV CONTEXT

When you compile with -cd, -Cd, or run tests with -t, the compiler includes dev code (programs marked with @Test and all code they reach). When you compile with -c or -C (production builds), @Test programs and their assertions are stripped entirely.

ASSERT VS REQUIRE

- assert: Test-only. Validates expected outcomes. Only allowed in code reachable from @Test programs. Triggers E81012 if found in production code paths.
- require: Production-safe. Enforces preconditions. Works everywhere. Use this in production functions and methods.

THE CALL GRAPH ENFORCES THIS

EK9 performs call graph analysis from all program entry points. If a non-@Test program can reach code containing assert, the compiler raises E81012 (PRODUCTION_ASSERTION). If assert exists in code NOT reachable from any program (test or production), it raises E81011 (ORPHAN_ASSERTION).

WHY THIS MATTERS

1. assert has overhead (expression capture, stack traces) — not wanted in production
2. Mixing test and production validation indicates confused design intent
3. Production code should use require for preconditions, not test assertions
4. Stripping test code from production builds reduces binary size and attack surface

THIS EXAMPLE

The main file has the production program and functions using require. The companion dev file (QA0767_dev_tests.ek9) has the @Test programs using assert. The dev file is only compiled in dev builds (-cd, -t).

See Q155 for writing tests. See Q156 for assertions. See Q157 for running tests. See Q305 for require vs assert vs throw.

Example

defines module qa.codequality.devcontext

  defines function

    <?-
      This function uses 'require' for preconditions.
      It is called ONLY from the PRODUCTION program below.
      If assert were used instead of require, E81012 would trigger
      because the call graph shows it is reachable from non-@Test code.
    -?>
    validateInput()
      -> input as Integer
      <- valid as Boolean: false

      require input?
      require input > 0

      valid: true

    <?-
      Helper for tests only — called exclusively from @Test programs
      in the companion dev file.
    -?>
    doubleValue()
      -> input as Integer
      <- result as Integer: input * 2

  defines program

    <?-
      PRODUCTION PROGRAM — no @Test directive.
      Calls validateInput which uses require (correct).
      If validateInput used assert, E81012 would fire.
    -?>
    ProductionEntry()
      stdout <- Stdout()

      if validateInput(42)
        stdout.println("Input is valid")

Companion: QA0767_dev_tests.ek9

#!ek9
defines module qa.codequality.devcontext

  defines program

    //TEST PROGRAM — marked with @Test.
    //This program only exists in dev builds (-cd, -t).
    //It CAN use assert because it is a test program.
    @Test
    TestDoubleValue()
      stdout <- Stdout()

      result <- doubleValue(5)
      assert result == 10

      result2 <- doubleValue(7)
      assert result2 == 14

      stdout.println("doubleValue tests passed")

//EOF

Common mistakes

E81015 — Using assert in a non-dev source file triggers E81015 TEST_CONSTRUCT_IN_NON_DEV_SOURCE. The assert keyword is only allowed in files within a dev/ directory or matching the QA####_dev_ naming convention. Use require for production preconditions. See ek9 -h E81015 for details.

Incorrect:

      assert input?
      assert input > 0

Correct:

      require input?
      require input > 0
Other ways to ask this
  • Why can I only use assert in @Test programs?
  • What is the difference between dev and production builds?
  • How does EK9 separate test code from production code?
  • What triggers E81012 production assertion?
  • Why does EK9 restrict assert to dev context?

Coming from another language?

Java: assert works everywhere but disabled by default (-ea to enable), JUnit assertions are library methods callable from anywhere — no compile-time enforcement. Python: assert works everywhere, stripped with -O flag — no compile-time boundary. Rust: assert! works everywhere, debug_assert! stripped in release builds — but no call graph analysis to enforce boundaries. Go: no assert keyword, testing.T methods technically callable from non-test code. EK9: compile-time call graph analysis enforces that assert only appears in code reachable from @Test programs.

Keywords: test, assert, E81012, E81011, context, call-graph, require, @Test, compile, boundary, production, dev