test: add dynamic test discovery and validity checks.

Modified benchmark_test.go to dynamically discover all .test files in the
tests directory instead of using hardcoded paths.
Added TestSamplesValidity integration test that validates each test file
against its corresponding .expected file.
Added runSampleWithOutput helper function to capture interpreter output.
Added new test cases with expected outputs for validation.
This commit is contained in:
2026-01-12 20:04:00 -05:00
parent 588f4cd521
commit 4a5c424e54
13 changed files with 113 additions and 26 deletions

View File

@@ -2,6 +2,8 @@ package main
import (
"os"
"path/filepath"
"strings"
"testing"
"git.maximhutz.com/max/lambda/internal/config"
@@ -9,6 +11,7 @@ import (
"git.maximhutz.com/max/lambda/pkg/convert"
"git.maximhutz.com/max/lambda/pkg/lambda"
"git.maximhutz.com/max/lambda/pkg/saccharine"
"github.com/stretchr/testify/assert"
)
// Helper function to run a single sample through the lambda interpreter.
@@ -48,17 +51,86 @@ func runSample(samplePath string) error {
return nil
}
// Benchmark all samples using sub-benchmarks.
func BenchmarkSamples(b *testing.B) {
samples := map[string]string{
"Church": "../../tests/church.test",
"Fast": "../../tests/fast.test",
"Saccharine": "../../tests/saccharine.test",
"Simple": "../../tests/simple.test",
"Thunk": "../../tests/thunk.test",
// Helper function to run a sample and return its output.
func runSampleWithOutput(samplePath string) (string, error) {
// Read the sample file.
input, err := os.ReadFile(samplePath)
if err != nil {
return "", err
}
for name, path := range samples {
// Parse code into syntax tree.
ast, err := saccharine.Parse(string(input))
if err != nil {
return "", err
}
// Compile expression to lambda calculus.
compiled := convert.SaccharineToLambda(ast)
// Create minimal config for testing.
cfg := &config.Config{
Source: config.StringSource{Data: ""},
Destination: config.StdoutDestination{},
Profile: "",
Explanation: false,
Statistics: false,
Verbose: false,
}
// Create and run the engine.
process := engine.New(cfg, &compiled)
process.Run()
// Get final result.
output := lambda.Stringify(compiled)
return output + "\n", nil
}
// Test that all samples produce expected output.
func TestSamplesValidity(t *testing.T) {
// Discover all .test files in the tests directory.
testFiles, err := filepath.Glob("../../tests/*.test")
assert.NoError(t, err, "Failed to read tests directory.")
assert.NotEmpty(t, testFiles, "No '*.test' files found in directory.")
for _, testPath := range testFiles {
// Build expected file path.
expectedPath := strings.TrimSuffix(testPath, filepath.Ext(testPath)) + ".expected"
// Check if expected file exists.
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
continue
}
name := strings.TrimSuffix(filepath.Base(testPath), filepath.Ext(testPath))
t.Run(name, func(t *testing.T) {
// Run the sample and capture output.
actual, err := runSampleWithOutput(testPath)
assert.NoError(t, err, "Failed to run sample.")
// Read expected output.
expectedBytes, err := os.ReadFile(expectedPath)
assert.NoError(t, err, "Failed to read expected output.")
expected := string(expectedBytes)
// Compare outputs.
assert.Equal(t, expected, actual, "Output does not match expected.")
})
}
}
// Benchmark all samples using sub-benchmarks.
func BenchmarkSamples(b *testing.B) {
// Discover all .test files in the tests directory.
testFiles, err := filepath.Glob("../../tests/*.test")
assert.NoError(b, err, "Failed to read tests directory.")
assert.NotEmpty(b, testFiles, "No '*.test' files found in directory.")
for _, path := range testFiles {
name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
b.Run(name, func(b *testing.B) {
for b.Loop() {
if err := runSample(path); err != nil {

8
go.mod
View File

@@ -1,3 +1,11 @@
module git.maximhutz.com/max/lambda
go 1.25.5
require github.com/stretchr/testify v1.11.1
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

9
go.sum Normal file
View File

@@ -0,0 +1,9 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,8 @@
0 := \f.\x.x
inc n := \f x.(f (n f x))
exp n m := (m n)
print n := (n F X)
N := (inc (inc (inc (inc (inc 0)))))
(exp N N)
(print (exp N N))

File diff suppressed because one or more lines are too long

8
tests/church_6^6.test Normal file
View File

@@ -0,0 +1,8 @@
0 := \f.\x.x
inc n := \f x.(f (n f x))
exp n m := (m n)
print n := (n F X)
N := (inc (inc (inc (inc (inc (inc 0))))))
(print (exp N N))

View File

@@ -0,0 +1 @@
(0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (1 END)))))))))))))))))))))))))))))))

1
tests/list_2^30.expected Normal file
View File

@@ -0,0 +1 @@
(0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (0 (1 END)))))))))))))))))))))))))))))))

View File

@@ -1,16 +0,0 @@
(\0.
(\inc.
(\add.
(\mult.
(\exp.
(exp (inc (inc (inc (inc 0)))) (inc (inc (inc (inc (inc 0))))))
\n m.(m n)
)
\m n f.(m (n f))
)
\n m.(m inc n)
)
\n f x.(f (n f x))
)
\f x.x
)

1
tests/thunk.expected Normal file
View File

@@ -0,0 +1 @@
VALUE