Project P154

Mutation tester

One small function and four mutants of it, test suites scored by the branches they run and the mutants they catch, the values that tell each mutant apart, and a test double that agrees with all of them.

2 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-18

Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.

About

One small function, four mutants of it, and test suites scored two ways: by the branches they run and by the mutants they catch.

  • main.eml - the menu, the function and its mutants, the suites, one suite check by check, the values that tell each mutant apart, and a suite of your own
  • mutants.eml - the band of a quantity with each mutant, the rate service and its fixed double, the price, the suites, the branches a suite runs, and the first check that catches a mutant

How each part works:

  • band(n) puts a quantity in invalid, low (0 to 9), mid (10 to 99) or high (100 and up), and the price is the band's rate from a service. The mutants: a negative put in low, 10 put in low, 100 put in mid, mid and high swapped.
  • As in the corpus case mutation-survival, coverage counts the branches a suite runs and the mutants count what it notices. Round numbers - -5, 5, 50, 500 - run all four branches and catch 2 of the 4 mutants: full coverage, and a broken function passes. Checking every edge catches all four.
  • As in the corpus case fixture-avoids-the-boundary, which values tell a mutant from the function is swept, not guessed: in -3 to 103 the low edge is told apart by 10 alone and the high edge by 100 alone.
  • As in the corpus case mock-ignores-input, a test double that answers 8 whatever band it is asked about agrees with every mutant: the round numbers checked against it run every branch and catch none.

What is checked: menu choices 1 to 6; a suite from 1 to 4; 1 to 6 whole numbers from -999 to 999, and the double faithful or fixed. An empty answer cancels.

Sessions: sessions/basic.in gives: - the function and the suites; suites 2, 3 and 4 check by check; - the values that tell; a suite of 0, 10 and 100 with the faithful double, and of 50 with the fixed one.

sessions/bad-input.in gives: - menu choices 0 and x; - suites: a cancel, 5 and x; - quantities: a cancel, seven of them, 1000, a lone minus sign and a; - 10 with a cancel and mock for the double; -1 and 0 with the faithful double.

Built on the verified corpus cases mutation-survival (full coverage against the mutants a suite kills), fixture-avoids-the-boundary (the values that detect each off-by-one, swept) and mock-ignores-input (a double that returns success whatever it is called with).

Recorded sessions

What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.

bad-input

interpreter: byte-equal
== Mutation tester ==
One small function, its mutants, and test suites scored by coverage and by the mutants they catch.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 0
Pick a number from 1 to 6.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> x
Pick a number from 1 to 6.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 3
suite (1 to 4)> 
Cancelled.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 3
suite (1 to 4)> 5
Type a suite from 1 to 4.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 3
suite (1 to 4)> x
Type a suite from 1 to 4.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> 
Cancelled.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> 1 2 3 4 5 6 7
Type 1 to 6 whole numbers from -999 to 999.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> 1000
Type 1 to 6 whole numbers from -999 to 999.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> -
Type 1 to 6 whole numbers from -999 to 999.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> a
Type 1 to 6 whole numbers from -999 to 999.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> 10
double (faithful or fixed)> 
Cancelled.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> 10
double (faithful or fixed)> mock
Type faithful or fixed.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> -1 0
double (faithful or fixed)> faithful
Your suite: checks -1, 0 with the faithful double; 2 of 4 branches run.
  sign       caught by -1: price 5 instead of 0
  low edge   survives: no check's price changes
  high edge  survives: no check's price changes
  swap       survives: no check's price changes

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 6
Bye.
What was typed (28 lines)
0
x
3

3
5
3
x
5

5
1 2 3 4 5 6 7
5
1000
5
-
5
a
5
10

5
10
mock
5
-1 0
faithful
6

basic

interpreter: byte-equal
== Mutation tester ==
One small function, its mutants, and test suites scored by coverage and by the mutants they catch.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 1
band(n): invalid below 0, low 0 to 9, mid 10 to 99, high 100 and up; price(n) asks the rate service for the band's rate.
The real service answers 5 for low, 8 for mid, 12 for high, 0 for invalid. The mutants, each one small change:
  sign       a negative quantity is put in low
  low edge   10 is put in low (n < 11)
  high edge  100 is put in mid (n < 101)
  swap       mid and high are swapped

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 2
  suite                checks                 double    branches  mutants caught
  happy path           50                     fixed       1 of 4         0 of 4
  round numbers        -5, 5, 50, 500         faithful    4 of 4         2 of 4
  round, fixed double  -5, 5, 50, 500         fixed       4 of 4         0 of 4
  every edge           -1, 0, 9, 10, 99, 100  faithful    4 of 4         4 of 4
  round numbers runs every branch and catches 2 of 4: full coverage, and a broken function passes.
  round, fixed double runs every branch and catches 0 of 4: full coverage, and a broken function passes.
  A mutant that survives names a behaviour no check is watching.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 3
suite (1 to 4)> 2
round numbers: checks -5, 5, 50, 500 with the faithful double; 4 of 4 branches run.
  sign       caught by -5: price 5 instead of 0
  low edge   survives: no check's price changes
  high edge  survives: no check's price changes
  swap       caught by 50: price 12 instead of 8

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 3
suite (1 to 4)> 3
round, fixed double: checks -5, 5, 50, 500 with the fixed double; 4 of 4 branches run.
  sign       survives: no check's price changes
  low edge   survives: no check's price changes
  high edge  survives: no check's price changes
  swap       survives: no check's price changes
The fixed double answers 8 whatever band it is asked about, so no band mutant can change a price.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 3
suite (1 to 4)> 4
every edge: checks -1, 0, 9, 10, 99, 100 with the faithful double; 4 of 4 branches run.
  sign       caught by -1: price 5 instead of 0
  low edge   caught by 10: price 5 instead of 8
  high edge  caught by 100: price 8 instead of 12
  swap       caught by 10: price 12 instead of 8

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 4
In -3 to 103, the values whose price tells each mutant from the function (faithful service):
  sign       -3 to -1
  low edge   10
  high edge  100
  swap       10 to 103
An edge mutant is told apart by its edge alone: a fixture of round numbers never meets it.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> 0 10 100
double (faithful or fixed)> faithful
Your suite: checks 0, 10, 100 with the faithful double; 3 of 4 branches run.
  sign       survives: no check's price changes
  low edge   caught by 10: price 5 instead of 8
  high edge  caught by 100: price 8 instead of 12
  swap       caught by 10: price 12 instead of 8

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 5
quantities, up to 6, separated by spaces> 50
double (faithful or fixed)> fixed
Your suite: checks 50 with the fixed double; 1 of 4 branches run.
  sign       survives: no check's price changes
  low edge   survives: no check's price changes
  high edge  survives: no check's price changes
  swap       survives: no check's price changes
The fixed double answers 8 whatever band it is asked about, so no band mutant can change a price.

1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit
choice> 6
Bye.
What was typed (16 lines)
1
2
3
2
3
3
3
4
4
5
0 10 100
faithful
5
50
fixed
6

Modules

The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.

main.eml(entry)

eml
# P154 mutation tester: the function and its mutants, the suites scored by
# coverage and by mutants caught, one suite check by check, the values that
# tell each mutant apart, and a suite of your own.
import mutants

def plain(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def pad(s, n):
    s => out
    while len(out) < n:
        out + " " => out
    return out

def lpad(s, n):
    s => out
    while len(out) < n:
        " " + out => out
    return out

def listed(ns):
    "" => out
    for n in ns:
        if out == "":
            str(n) => out
        else:
            out + ", " + str(n) => out
    return out

def caught(ns, double):
    0 => k
    for m in mutants.mutants():
        if mutants.kills(ns, double, m) > 0 - 1000:
            k + 1 => k
    return k

def show_function():
    "band(n): invalid below 0, low 0 to 9, mid 10 to 99, high 100 and up; price(n) asks the rate service for the band's rate." ^0
    "The real service answers 5 for low, 8 for mid, 12 for high, 0 for invalid. The mutants, each one small change:" ^0
    0 => i
    for m in mutants.mutants():
        ("  " + pad(m, 11) + mutants.what()[i]) ^0
        i + 1 => i

def show_suites():
    ("  " + pad("suite", 21) + pad("checks", 23) + pad("double", 10) + "branches  mutants caught") ^0
    for s in mutants.suites():
        ("  " + pad(s[0], 21) + pad(listed(s[1]), 23) + pad(s[2], 10) + lpad(str(mutants.branches(s[1])) + " of 4", 8) + lpad(str(caught(s[1], s[2])) + " of 4", 15)) ^0
    for s in mutants.suites():
        mutants.branches(s[1]) => b
        caught(s[1], s[2]) => k
        if b == 4 and k < 4:
            ("  " + s[0] + " runs every branch and catches " + str(k) + " of 4: full coverage, and a broken function passes.") ^0
    "  A mutant that survives names a behaviour no check is watching." ^0

def one_suite():
    plain(input("suite (1 to 4)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    if t != "1" and t != "2" and t != "3" and t != "4":
        "Type a suite from 1 to 4." ^0
        return
    mutants.suites()[int(t) - 1] => s
    report(s[0], s[1], s[2])

def report(name, ns, double):
    (name + ": checks " + listed(ns) + " with the " + double + " double; " + str(mutants.branches(ns)) + " of 4 branches run.") ^0
    for m in mutants.mutants():
        mutants.kills(ns, double, m) => n
        if n > 0 - 1000:
            ("  " + pad(m, 11) + "caught by " + str(n) + ": price " + str(mutants.price(n, m, double)) + " instead of " + str(mutants.price(n, "none", double))) ^0
        else:
            ("  " + pad(m, 11) + "survives: no check's price changes") ^0
    if double == "fixed":
        "The fixed double answers 8 whatever band it is asked about, so no band mutant can change a price." ^0

def tell():
    "In -3 to 103, the values whose price tells each mutant from the function (faithful service):" ^0
    for m in mutants.mutants():
        "" => out
        0 - 4 => start
        0 - 4 => last
        0 - 3 => n
        while n <= 104:
            n <= 103 and mutants.price(n, m, "faithful") != mutants.price(n, "none", "faithful") => d
            if d and start == 0 - 4:
                n => start
            if d:
                n => last
            if not d and start != 0 - 4:
                if out != "":
                    out + ", " => out
                if start == last:
                    out + str(start) => out
                else:
                    out + str(start) + " to " + str(last) => out
                0 - 4 => start
            n + 1 => n
        ("  " + pad(m, 11) + out) ^0
    "An edge mutant is told apart by its edge alone: a fixture of round numbers never meets it." ^0

def own():
    plain(input("quantities, up to 6, separated by spaces> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    [] => ns
    "" => cur
    for c in t + " ":
        if c == " ":
            if cur != "":
                0 => start
                if cur[0] == "-":
                    1 => start
                if len(cur) == start or len(cur) - start > 3:
                    "Type 1 to 6 whole numbers from -999 to 999." ^0
                    return
                0 => v
                for d in cur[start:len(cur)]:
                    if d < "0" or d > "9":
                        "Type 1 to 6 whole numbers from -999 to 999." ^0
                        return
                    v * 10 + int(d) => v
                if start == 1:
                    0 - v => v
                ns + [v] => ns
                "" => cur
        else:
            cur + c => cur
    if len(ns) > 6:
        "Type 1 to 6 whole numbers from -999 to 999." ^0
        return
    plain(input("double (faithful or fixed)> ")) => d
    if d == "":
        "Cancelled." ^0
        return
    if d != "faithful" and d != "fixed":
        "Type faithful or fixed." ^0
        return
    report("Your suite", ns, d)

"== Mutation tester ==" ^0
"One small function, its mutants, and test suites scored by coverage and by the mutants they catch." ^0
True => running
while running:
    "" ^0
    "1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show_function()
    elif choice == "2":
        show_suites()
    elif choice == "3":
        one_suite()
    elif choice == "4":
        tell()
    elif choice == "5":
        own()
    elif choice == "6":
        False => running
    else:
        "Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import mutants

def plain(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def pad(s, n):
    out = s
    while len(out) < n:
        out = out + " "
    return out

def lpad(s, n):
    out = s
    while len(out) < n:
        out = " " + out
    return out

def listed(ns):
    out = ""
    for n in ns:
        if out == "":
            out = str(n)
        else:
            out = out + ", " + str(n)
    return out

def caught(ns, double):
    k = 0
    for m in mutants.mutants():
        if mutants.kills(ns, double, m) > 0 - 1000:
            k = k + 1
    return k

def show_function():
    print("band(n): invalid below 0, low 0 to 9, mid 10 to 99, high 100 and up; price(n) asks the rate service for the band's rate.")
    print("The real service answers 5 for low, 8 for mid, 12 for high, 0 for invalid. The mutants, each one small change:")
    i = 0
    for m in mutants.mutants():
        print("  " + pad(m, 11) + mutants.what()[i])
        i = i + 1

def show_suites():
    print("  " + pad("suite", 21) + pad("checks", 23) + pad("double", 10) + "branches  mutants caught")
    for s in mutants.suites():
        print("  " + pad(s[0], 21) + pad(listed(s[1]), 23) + pad(s[2], 10) + lpad(str(mutants.branches(s[1])) + " of 4", 8) + lpad(str(caught(s[1], s[2])) + " of 4", 15))
    for s in mutants.suites():
        b = mutants.branches(s[1])
        k = caught(s[1], s[2])
        if b == 4 and k < 4:
            print("  " + s[0] + " runs every branch and catches " + str(k) + " of 4: full coverage, and a broken function passes.")
    print("  A mutant that survives names a behaviour no check is watching.")

def one_suite():
    t = plain(input("suite (1 to 4)> "))
    if t == "":
        print("Cancelled.")
        return
    if t != "1" and t != "2" and t != "3" and t != "4":
        print("Type a suite from 1 to 4.")
        return
    s = mutants.suites()[int(t) - 1]
    report(s[0], s[1], s[2])

def report(name, ns, double):
    print(name + ": checks " + listed(ns) + " with the " + double + " double; " + str(mutants.branches(ns)) + " of 4 branches run.")
    for m in mutants.mutants():
        n = mutants.kills(ns, double, m)
        if n > 0 - 1000:
            print("  " + pad(m, 11) + "caught by " + str(n) + ": price " + str(mutants.price(n, m, double)) + " instead of " + str(mutants.price(n, "none", double)))
        else:
            print("  " + pad(m, 11) + "survives: no check's price changes")
    if double == "fixed":
        print("The fixed double answers 8 whatever band it is asked about, so no band mutant can change a price.")

def tell():
    print("In -3 to 103, the values whose price tells each mutant from the function (faithful service):")
    for m in mutants.mutants():
        out = ""
        start = 0 - 4
        last = 0 - 4
        n = 0 - 3
        while n <= 104:
            d = n <= 103 and mutants.price(n, m, "faithful") != mutants.price(n, "none", "faithful")
            if d and start == 0 - 4:
                start = n
            if d:
                last = n
            if not d and start != 0 - 4:
                if out != "":
                    out = out + ", "
                if start == last:
                    out = out + str(start)
                else:
                    out = out + str(start) + " to " + str(last)
                start = 0 - 4
            n = n + 1
        print("  " + pad(m, 11) + out)
    print("An edge mutant is told apart by its edge alone: a fixture of round numbers never meets it.")

def own():
    t = plain(input("quantities, up to 6, separated by spaces> "))
    if t == "":
        print("Cancelled.")
        return
    ns = []
    cur = ""
    for c in t + " ":
        if c == " ":
            if cur != "":
                start = 0
                if cur[0] == "-":
                    start = 1
                if len(cur) == start or len(cur) - start > 3:
                    print("Type 1 to 6 whole numbers from -999 to 999.")
                    return
                v = 0
                for d in cur[start:len(cur)]:
                    if d < "0" or d > "9":
                        print("Type 1 to 6 whole numbers from -999 to 999.")
                        return
                    v = v * 10 + int(d)
                if start == 1:
                    v = 0 - v
                ns = ns + [v]
                cur = ""
        else:
            cur = cur + c
    if len(ns) > 6:
        print("Type 1 to 6 whole numbers from -999 to 999.")
        return
    d = plain(input("double (faithful or fixed)> "))
    if d == "":
        print("Cancelled.")
        return
    if d != "faithful" and d != "fixed":
        print("Type faithful or fixed.")
        return
    report("Your suite", ns, d)

print("== Mutation tester ==")
print("One small function, its mutants, and test suites scored by coverage and by the mutants they catch.")
running = True
while running:
    print("")
    print("1) the function  2) the suites  3) one suite  4) the values that tell  5) your own suite  6) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show_function()
    elif choice == "2":
        show_suites()
    elif choice == "3":
        one_suite()
    elif choice == "4":
        tell()
    elif choice == "5":
        own()
    elif choice == "6":
        running = False
    else:
        print("Pick a number from 1 to 6.")
print("Bye.")

mutants.eml

eml
# P154 mutation tester - one small function, the mutants of it, and test
# suites scored two ways. As in the corpus case mutation-survival, coverage
# counts the branches a suite runs and a mutant counts what it notices: a
# suite can run every branch and miss a broken one. As in the corpus case
# fixture-avoids-the-boundary, which values can tell a mutant from the
# function is computed by sweeping them, not guessed: an edge mutant is told
# apart by one value only. As in the corpus case mock-ignores-input, a test
# double that answers the same whatever it is asked agrees with every
# mutant. The function: a band for a quantity, and the price the band's
# rate service gives.

def mutants():
    return ["sign", "low edge", "high edge", "swap"]

def what():
    return ["a negative quantity is put in low", "10 is put in low (n < 11)", "100 is put in mid (n < 101)", "mid and high are swapped"]

def band(n, m):
    # invalid below 0, low 0 to 9, mid 10 to 99, high 100 and up;
    # m names a mutant, or "none"
    if n < 0:
        if m == "sign":
            return "low"
        return "invalid"
    10 => top_low
    if m == "low edge":
        11 => top_low
    if n < top_low:
        return "low"
    100 => top_mid
    if m == "high edge":
        101 => top_mid
    if n < top_mid:
        if m == "swap":
            return "high"
        return "mid"
    if m == "swap":
        return "mid"
    return "high"

def rate(b, double):
    # the rate service: faithful answers for the band asked, fixed answers 8
    # whatever it is asked
    if double == "fixed":
        return 8
    if b == "low":
        return 5
    if b == "mid":
        return 8
    if b == "high":
        return 12
    return 0

def price(n, m, double):
    return rate(band(n, m), double)

def suites():
    # [name, the quantities it checks, the double it uses]
    return [["happy path", [50], "fixed"], ["round numbers", [0 - 5, 5, 50, 500], "faithful"], ["round, fixed double", [0 - 5, 5, 50, 500], "fixed"], ["every edge", [0 - 1, 0, 9, 10, 99, 100], "faithful"]]

def branches(ns):
    # the bands, of 4, that the quantities run
    [] => seen
    for n in ns:
        band(n, "none") => b
        False => have
        for s in seen:
            if s == b:
                True => have
        if not have:
            seen + [b] => seen
    return len(seen)

def kills(ns, double, m):
    # the first quantity whose expected price the mutant changes, or -1000
    for n in ns:
        if price(n, m, double) != price(n, "none", double):
            return n
    return 0 - 1000
Python projection (mutants.py)
def mutants():
    return ["sign", "low edge", "high edge", "swap"]

def what():
    return ["a negative quantity is put in low", "10 is put in low (n < 11)", "100 is put in mid (n < 101)", "mid and high are swapped"]

def band(n, m):
    if n < 0:
        if m == "sign":
            return "low"
        return "invalid"
    top_low = 10
    if m == "low edge":
        top_low = 11
    if n < top_low:
        return "low"
    top_mid = 100
    if m == "high edge":
        top_mid = 101
    if n < top_mid:
        if m == "swap":
            return "high"
        return "mid"
    if m == "swap":
        return "mid"
    return "high"

def rate(b, double):
    if double == "fixed":
        return 8
    if b == "low":
        return 5
    if b == "mid":
        return 8
    if b == "high":
        return 12
    return 0

def price(n, m, double):
    return rate(band(n, m), double)

def suites():
    return [["happy path", [50], "fixed"], ["round numbers", [0 - 5, 5, 50, 500], "faithful"], ["round, fixed double", [0 - 5, 5, 50, 500], "fixed"], ["every edge", [0 - 1, 0, 9, 10, 99, 100], "faithful"]]

def branches(ns):
    seen = []
    for n in ns:
        b = band(n, "none")
        have = False
        for s in seen:
            if s == b:
                have = True
        if not have:
            seen = seen + [b]
    return len(seen)

def kills(ns, double, m):
    for n in ns:
        if price(n, m, double) != price(n, "none", double):
            return n
    return 0 - 1000

Built on these corpus cases

ΣEML 2026

Efficient New Language — a high-density semantic overlay for humans and AI agents.

This site’s demos run on the real EML toolchain, in your browser.

© 2026 EveMissLab (一言諾科技有限公司) / Neo.K. All rights reserved.