mirror of
https://github.com/go-i2p/go-sam-go.git
synced 2025-12-01 09:54:58 -05:00
346 lines
11 KiB
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")
|
|
})
|
|
}
|