Files
go-sam-go/signature_test.go

346 lines
11 KiB
Go

package sam3
import (
"go/ast"
"go/parser"
"go/token"
"reflect"
"strings"
"testing"
)
// TestSAM3SignatureCompatibility verifies that all function signatures exactly match
// the specifications in sigs.md for perfect drop-in replacement compatibility.
func TestSAM3SignatureCompatibility(t *testing.T) {
t.Run("SessionCreationSignatures", func(t *testing.T) {
// Expected signatures from sigs.md
expectedSignatures := map[string]string{
"NewDatagramSession": "func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*DatagramSession, error)",
"NewPrimarySession": "func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error)",
"NewPrimarySessionWithSignature": "func (sam *SAM) NewPrimarySessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*PrimarySession, error)",
"NewRawSession": "func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*RawSession, error)",
"NewStreamSession": "func (sam *SAM) NewStreamSession(id string, keys i2pkeys.I2PKeys, options []string) (*StreamSession, error)",
"NewStreamSessionWithSignature": "func (sam *SAM) NewStreamSessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error)",
"NewStreamSessionWithSignatureAndPorts": "func (sam *SAM) NewStreamSessionWithSignatureAndPorts(id, from, to string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error)",
}
// Get the actual SAM type
samType := reflect.TypeOf(&SAM{})
for methodName := range expectedSignatures {
method, exists := samType.MethodByName(methodName)
if !exists {
t.Errorf("Method %s not found on SAM type", methodName)
continue
}
// Verify method exists and is callable
if method.Type.NumIn() == 0 {
t.Errorf("Method %s has no input parameters", methodName)
continue
}
// Verify first parameter is receiver (*SAM)
if method.Type.In(0) != samType {
t.Errorf("Method %s receiver is not *SAM", methodName)
continue
}
t.Logf("✓ Method %s exists with correct receiver", methodName)
}
})
t.Run("UtilityFunctionSignatures", func(t *testing.T) {
// Test utility functions have expected signatures
utilityTests := []struct {
name string
function interface{}
inputs int
outputs int
}{
{"NewSAM", NewSAM, 1, 2},
{"NewSAMResolver", NewSAMResolver, 1, 2},
{"NewFullSAMResolver", NewFullSAMResolver, 1, 2},
{"RandString", RandString, 0, 1},
{"SAMDefaultAddr", SAMDefaultAddr, 1, 1},
{"ExtractDest", ExtractDest, 1, 1},
{"ExtractPairString", ExtractPairString, 2, 1},
{"ExtractPairInt", ExtractPairInt, 2, 1},
{"GenerateOptionString", GenerateOptionString, 1, 1},
{"PrimarySessionString", PrimarySessionString, 0, 1},
}
for _, test := range utilityTests {
t.Run(test.name, func(t *testing.T) {
funcType := reflect.TypeOf(test.function)
if funcType.Kind() != reflect.Func {
t.Errorf("%s is not a function", test.name)
return
}
if funcType.NumIn() != test.inputs {
t.Errorf("%s expected %d inputs, got %d", test.name, test.inputs, funcType.NumIn())
return
}
if funcType.NumOut() != test.outputs {
t.Errorf("%s expected %d outputs, got %d", test.name, test.outputs, funcType.NumOut())
return
}
t.Logf("✓ %s has correct signature (%d inputs, %d outputs)", test.name, test.inputs, test.outputs)
})
}
})
t.Run("ConfigurationFunctionSignatures", func(t *testing.T) {
// Test configuration functions have functional options pattern
configTests := []struct {
name string
function interface{}
}{
{"SetType", SetType},
{"SetSAMHost", SetSAMHost},
{"SetSAMPort", SetSAMPort},
{"SetName", SetName},
{"SetInLength", SetInLength},
{"SetOutLength", SetOutLength},
{"SetInQuantity", SetInQuantity},
{"SetOutQuantity", SetOutQuantity},
{"SetInBackups", SetInBackups},
{"SetOutBackups", SetOutBackups},
{"SetEncrypt", SetEncrypt},
{"SetCompress", SetCompress},
}
for _, test := range configTests {
t.Run(test.name, func(t *testing.T) {
funcType := reflect.TypeOf(test.function)
if funcType.Kind() != reflect.Func {
t.Errorf("%s is not a function", test.name)
return
}
// Configuration functions should take 1 input and return a function
if funcType.NumIn() != 1 {
t.Errorf("%s expected 1 input parameter", test.name)
return
}
if funcType.NumOut() != 1 {
t.Errorf("%s expected 1 output parameter", test.name)
return
}
// Output should be a function type
outputType := funcType.Out(0)
if outputType.Kind() != reflect.Func {
t.Errorf("%s should return a function", test.name)
return
}
t.Logf("✓ %s follows functional options pattern", test.name)
})
}
})
t.Run("TypeDefinitionCompatibility", func(t *testing.T) {
// Verify type aliases point to correct underlying types
typeTests := []struct {
name string
aliasType interface{}
description string
}{
{"SAM", (*SAM)(nil), "Core SAM connection type"},
{"StreamSession", (*StreamSession)(nil), "TCP-like session type"},
{"DatagramSession", (*DatagramSession)(nil), "UDP-like session type"},
{"RawSession", (*RawSession)(nil), "Anonymous datagram session type"},
{"PrimarySession", (*PrimarySession)(nil), "Multi-session management type"},
{"SAMConn", (*SAMConn)(nil), "Stream connection type"},
{"StreamListener", (*StreamListener)(nil), "Stream listener type"},
{"I2PConfig", (*I2PConfig)(nil), "I2P configuration type"},
{"SAMEmit", (*SAMEmit)(nil), "SAM emission configuration type"},
}
for _, test := range typeTests {
t.Run(test.name, func(t *testing.T) {
typeOf := reflect.TypeOf(test.aliasType)
if typeOf == nil {
t.Errorf("Type %s is nil", test.name)
return
}
// Verify it's a pointer to a struct (expected for these types)
if typeOf.Kind() != reflect.Ptr {
t.Errorf("Type %s should be a pointer type", test.name)
return
}
elem := typeOf.Elem()
if elem.Kind() != reflect.Struct {
t.Errorf("Type %s should point to a struct", test.name)
return
}
t.Logf("✓ Type %s: %s", test.name, test.description)
})
}
})
}
// TestSAM3PackageStructure verifies the package exports match sigs.md expectations.
func TestSAM3PackageStructure(t *testing.T) {
t.Run("PackageExports", func(t *testing.T) {
// Parse the package to get actual exports
fset := token.NewFileSet()
packages, err := parser.ParseDir(fset, ".", nil, parser.ParseComments)
if err != nil {
t.Fatalf("Failed to parse package: %v", err)
}
sam3Pkg, exists := packages["sam3"]
if !exists {
t.Fatal("sam3 package not found")
}
// Collect exported identifiers
exports := make(map[string]bool)
for _, file := range sam3Pkg.Files {
ast.Inspect(file, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.FuncDecl:
if node.Name.IsExported() {
exports[node.Name.Name] = true
}
case *ast.TypeSpec:
if node.Name.IsExported() {
exports[node.Name.Name] = true
}
case *ast.ValueSpec:
for _, name := range node.Names {
if name.IsExported() {
exports[name.Name] = true
}
}
}
return true
})
}
// Expected major exports from sigs.md
expectedExports := []string{
// Types
"SAM", "StreamSession", "DatagramSession", "RawSession", "PrimarySession",
"SAMConn", "StreamListener", "SAMResolver", "I2PConfig", "SAMEmit",
// Constants
"Sig_NONE", "Sig_DSA_SHA1", "Sig_ECDSA_SHA256_P256", "Sig_ECDSA_SHA384_P384",
"Sig_ECDSA_SHA512_P521", "Sig_EdDSA_SHA512_Ed25519",
// Variables
"Options_Humongous", "Options_Large", "Options_Wide", "Options_Medium",
"Options_Default", "Options_Small", "Options_Warning_ZeroHop",
"SAM_HOST", "SAM_PORT",
// Functions
"NewSAM", "NewSAMResolver", "NewFullSAMResolver", "RandString",
"SAMDefaultAddr", "ExtractDest", "ExtractPairString", "ExtractPairInt",
}
for _, expected := range expectedExports {
if !exports[expected] {
t.Errorf("Expected export %s not found", expected)
} else {
t.Logf("✓ Export %s found", expected)
}
}
t.Logf("Package exports %d identifiers total", len(exports))
})
t.Run("ImportCompatibility", func(t *testing.T) {
// Verify the package can be imported as expected
// This test ensures the package structure allows drop-in replacement
// Check that main types are available for type assertions
var sam *SAM
var streamSession *StreamSession
var datagramSession *DatagramSession
var rawSession *RawSession
var primarySession *PrimarySession
// Verify interfaces work as expected
if sam != nil || streamSession != nil || datagramSession != nil ||
rawSession != nil || primarySession != nil {
// This is just for compilation checking
}
// Verify constants are accessible
signatures := []string{
Sig_NONE, Sig_DSA_SHA1, Sig_ECDSA_SHA256_P256,
Sig_ECDSA_SHA384_P384, Sig_ECDSA_SHA512_P521, Sig_EdDSA_SHA512_Ed25519,
}
if len(signatures) != 6 {
t.Error("Not all signature constants are accessible")
}
// Verify option variables are accessible
options := [][]string{
Options_Humongous, Options_Large, Options_Wide,
Options_Medium, Options_Default, Options_Small, Options_Warning_ZeroHop,
}
if len(options) != 7 {
t.Error("Not all option variables are accessible")
}
t.Log("✓ Package structure supports drop-in replacement")
})
}
// TestSAM3DocumentationCompleteness verifies that all public functions have adequate documentation.
func TestSAM3DocumentationCompleteness(t *testing.T) {
t.Run("FunctionDocumentation", func(t *testing.T) {
// Parse the package to check documentation
fset := token.NewFileSet()
packages, err := parser.ParseDir(fset, ".", nil, parser.ParseComments)
if err != nil {
t.Fatalf("Failed to parse package: %v", err)
}
sam3Pkg, exists := packages["sam3"]
if !exists {
t.Fatal("sam3 package not found")
}
undocumentedFunctions := []string{}
// Check function documentation
for _, file := range sam3Pkg.Files {
ast.Inspect(file, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.FuncDecl:
if node.Name.IsExported() {
// Check if function has documentation
if node.Doc == nil || len(node.Doc.List) == 0 {
undocumentedFunctions = append(undocumentedFunctions, node.Name.Name)
} else {
// Check if documentation starts with function name
firstLine := node.Doc.List[0].Text
if !strings.Contains(firstLine, node.Name.Name) {
t.Logf("Warning: %s documentation may not follow Go conventions", node.Name.Name)
}
}
}
}
return true
})
}
if len(undocumentedFunctions) > 0 {
t.Logf("Functions without documentation: %v", undocumentedFunctions)
// We'll log but not fail for documentation, as some may be aliases
}
t.Logf("✓ Documentation completeness check completed")
})
}