A SimpleX Chat bot that answers arithmetic (closes #1)
check / check (push) Successful in 54s

Remove the template's HTTP service, database and fx wiring. Add exact
arithmetic on go/parser and go/constant, a client that runs simplex-chat
as a child process and drives its WebSocket API, and the bot, which keeps
an auto-accepting address and replies to each message. The image adds the
checksum-pinned simplex-chat v7.0.2 on Ubuntu 22.04.

Model: opus-5-5
This commit is contained in:
clawbot
2026-09-26 22:01:51 +00:00
parent f8ce8cef83
commit 16649e0f2f
66 changed files with 2119 additions and 5059 deletions
+150
View File
@@ -0,0 +1,150 @@
package calc_test
import (
"errors"
"strings"
"testing"
"sneak.berlin/go/simplexcalc/internal/calc"
)
// TestEvaluate covers the two examples the bot was specified with, and
// the arithmetic around them.
func TestEvaluate(t *testing.T) {
t.Parallel()
cases := map[string]string{
// The specification's own examples.
"2 + 2": "4",
"5 * 5/2": "12.5",
"2+2": "4",
" 7 - 10 \n": "-3",
"-3 * 2": "-6",
"+4": "4",
"-(-4)": "4",
"(1 + 2) * 3": "9",
"1 + 2 * 3": "7",
"((2))": "2",
"8 / 2 / 2": "2",
"10 - 2 - 3": "5",
"7 / 2": "3.5",
"25/2": "12.5",
"1 / 3": "0.3333333333333333",
"2 / 3": "0.6666666666666666",
"0.1 + 0.2": "0.3",
"1.5 * 2": "3",
"2.50 * 2": "5",
".5 + .5": "1",
"3. * 2": "6",
"1e3 + 1": "1001",
"2.5e-1": "0.25",
"010 + 1": "11",
"-0": "0",
"0 / 5": "0",
// Exact: a float64 would print 99999999980000000000.
"9999999999 * 9999999999": "99999999980000000001",
"1e21": "1e+21",
"1e20": "100000000000000000000",
"1 / 1e7": "1e-07",
"1 / 1e6": "0.000001",
"1234567.5": "1234567.5",
"-1 / 4": "-0.25",
"1e300 * 1e8": "1e+308",
}
for in, want := range cases {
t.Run(in, func(t *testing.T) {
t.Parallel()
got, err := calc.Evaluate(in)
if err != nil {
t.Fatalf("Evaluate(%q) failed: %v", in, err)
}
if got != want {
t.Errorf("Evaluate(%q) = %q, want %q", in, got, want)
}
})
}
}
// TestEvaluateRefuses covers what must be answered with an error rather
// than a number, and never with a panic.
func TestEvaluateRefuses(t *testing.T) {
t.Parallel()
cases := map[string]error{
"": calc.ErrNotArithmetic,
" ": calc.ErrNotArithmetic,
"hello": calc.ErrNotArithmetic,
"/help": calc.ErrNotArithmetic,
"2 +": calc.ErrNotArithmetic,
"2 2": calc.ErrNotArithmetic,
"2 + 2 =": calc.ErrNotArithmetic,
"x + 1": calc.ErrNotArithmetic,
"len(\"abc\")": calc.ErrNotArithmetic,
"\"a\" + \"b\"": calc.ErrNotArithmetic,
"'a' + 1": calc.ErrNotArithmetic,
"2i * 2i": calc.ErrNotArithmetic,
"0x10 + 1": calc.ErrNotArithmetic,
"1_000 + 1": calc.ErrNotArithmetic,
"7 % 2": calc.ErrNotArithmetic,
"2 ^ 3": calc.ErrNotArithmetic,
"1 << 10": calc.ErrNotArithmetic,
"1 == 1": calc.ErrNotArithmetic,
"!1": calc.ErrNotArithmetic,
"func() int { return 1 }()": calc.ErrNotArithmetic,
"1 / 0": calc.ErrDivisionByZero,
"1 / (2 - 2)": calc.ErrDivisionByZero,
"5 / 0.0": calc.ErrDivisionByZero,
"1e400": calc.ErrTooLarge,
"1e300 * 1e300": calc.ErrTooLarge,
"1e999999999 * 1e999999999": calc.ErrTooLarge,
"1 / 1e-400": calc.ErrTooLarge,
}
for in, want := range cases {
t.Run(in, func(t *testing.T) {
t.Parallel()
got, err := calc.Evaluate(in)
if !errors.Is(err, want) {
t.Errorf("Evaluate(%q) = %q, %v; want error %v", in, got, err, want)
}
})
}
}
// TestEvaluateCapsInput: the length cap is what bounds the work a
// message can cause, so it must hold exactly at the boundary.
func TestEvaluateCapsInput(t *testing.T) {
t.Parallel()
// "1+1+...+1" with the last term padded to land exactly on the cap.
longest := strings.Repeat("1+", calc.MaxInputLength/2-1) + "10"
if len(longest) != calc.MaxInputLength {
t.Fatalf("test setup: expression is %d bytes, want %d",
len(longest), calc.MaxInputLength)
}
got, err := calc.Evaluate(longest)
if err != nil {
t.Fatalf("an expression of exactly MaxInputLength bytes was refused: %v", err)
}
if want := "137"; got != want {
t.Errorf("Evaluate(longest) = %q, want %q", got, want)
}
_, err = calc.Evaluate(longest + "0")
if !errors.Is(err, calc.ErrTooLong) {
t.Errorf("an expression over MaxInputLength gave %v, want ErrTooLong", err)
}
// Surrounding whitespace is not part of the expression.
_, err = calc.Evaluate(" " + longest + "\n")
if err != nil {
t.Errorf("whitespace around a maximal expression counted against the cap: %v", err)
}
}