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:
@@ -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
8
go.mod
@@ -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
9
go.sum
Normal 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=
|
||||
1
tests/church_5^5.expected
Normal file
1
tests/church_5^5.expected
Normal file
File diff suppressed because one or more lines are too long
@@ -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))
|
||||
1
tests/church_6^6.expected
Normal file
1
tests/church_6^6.expected
Normal file
File diff suppressed because one or more lines are too long
8
tests/church_6^6.test
Normal file
8
tests/church_6^6.test
Normal 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))
|
||||
1
tests/fast_list_2^30.expected
Normal file
1
tests/fast_list_2^30.expected
Normal 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
1
tests/list_2^30.expected
Normal 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,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
1
tests/thunk.expected
Normal file
@@ -0,0 +1 @@
|
||||
VALUE
|
||||
Reference in New Issue
Block a user