Improve testing infrastructure with dynamic discovery and validation #20

Merged
mvhutz merged 4 commits from style/lambda-iterator into main 2026-01-13 01:20:47 +00:00
13 changed files with 113 additions and 26 deletions
Showing only changes of commit 4a5c424e54 - Show all commits

View File

@@ -2,6 +2,8 @@ package main
import ( import (
"os" "os"
"path/filepath"
"strings"
"testing" "testing"
"git.maximhutz.com/max/lambda/internal/config" "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/convert"
"git.maximhutz.com/max/lambda/pkg/lambda" "git.maximhutz.com/max/lambda/pkg/lambda"
"git.maximhutz.com/max/lambda/pkg/saccharine" "git.maximhutz.com/max/lambda/pkg/saccharine"
"github.com/stretchr/testify/assert"
) )
// Helper function to run a single sample through the lambda interpreter. // Helper function to run a single sample through the lambda interpreter.
@@ -48,17 +51,86 @@ func runSample(samplePath string) error {
return nil return nil
} }
// Benchmark all samples using sub-benchmarks. // Helper function to run a sample and return its output.
func BenchmarkSamples(b *testing.B) { func runSampleWithOutput(samplePath string) (string, error) {
samples := map[string]string{ // Read the sample file.
"Church": "../../tests/church.test", input, err := os.ReadFile(samplePath)
"Fast": "../../tests/fast.test", if err != nil {
"Saccharine": "../../tests/saccharine.test", return "", err
"Simple": "../../tests/simple.test",
"Thunk": "../../tests/thunk.test",
} }
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) { b.Run(name, func(b *testing.B) {
for b.Loop() { for b.Loop() {
if err := runSample(path); err != nil { if err := runSample(path); err != nil {

8
go.mod
View File

@@ -1,3 +1,11 @@
module git.maximhutz.com/max/lambda module git.maximhutz.com/max/lambda
go 1.25.5 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 0 := \f.\x.x
inc n := \f x.(f (n f x)) inc n := \f x.(f (n f x))
exp n m := (m n) exp n m := (m n)
print n := (n F X)
N := (inc (inc (inc (inc (inc 0))))) 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