# V2 Testing Requirements ## Non-Negotiable Standards **100% Test Coverage Required** **100% Test Passage Required** **Zero Tolerance for Failures** ## Testing Philosophy Every line of code MUST be tested. Every edge case MUST be covered. Every failure MUST be fixed before merging. ## Coverage Requirements ### Code Coverage Targets ```bash # Minimum coverage requirements (ENFORCED) Overall Project: 100% Per Package: 100% Per File: 100% Branch Coverage: 100% ``` ### Coverage Verification ```bash # Run coverage report go test ./... -coverprofile=coverage.out -covermode=atomic # View coverage by package go tool cover -func=coverage.out # MUST show 100% for every file pkg/parsers/uniswap_v2/parser.go:100.0% pkg/parsers/uniswap_v3/parser.go:100.0% pkg/cache/pool_cache.go:100.0% # ... etc # Generate HTML report go tool cover -html=coverage.out -o coverage.html # CI/CD enforcement if [ $(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') -lt 100 ]; then echo "FAILED: Coverage below 100%" exit 1 fi ``` ## Test Types and Requirements ### 1. Unit Tests (100% Coverage Required) Every function, every method, every code path MUST be tested. ```go // Example: Complete unit test coverage package uniswap_v2 import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestParser_ParseSwapEvent_Success(t *testing.T) { // Test successful parsing } func TestParser_ParseSwapEvent_ZeroAddress(t *testing.T) { // Test zero address rejection } func TestParser_ParseSwapEvent_ZeroAmounts(t *testing.T) { // Test zero amount rejection } func TestParser_ParseSwapEvent_MaxValues(t *testing.T) { // Test maximum value handling } func TestParser_ParseSwapEvent_MinValues(t *testing.T) { // Test minimum value handling } func TestParser_ParseSwapEvent_InvalidLog(t *testing.T) { // Test invalid log handling } func TestParser_ParseSwapEvent_NilTransaction(t *testing.T) { // Test nil transaction handling } func TestParser_ParseSwapEvent_Decimals(t *testing.T) { // Test decimal precision handling tests := []struct{ name string token0Dec uint8 token1Dec uint8 amount0In *big.Int expected *big.Int }{ {"USDC-WETH", 6, 18, big.NewInt(1000000), ...}, {"DAI-USDC", 18, 6, big.NewInt(1000000000000000000), ...}, {"WBTC-WETH", 8, 18, big.NewInt(100000000), ...}, } // Test all combinations } // MUST test ALL error paths func TestParser_ParseSwapEvent_AllErrors(t *testing.T) { tests := []struct{ name string log *types.Log wantErr string }{ {"nil log", nil, "log is nil"}, {"wrong signature", wrongSigLog, "invalid signature"}, {"malformed data", malformedLog, "failed to decode"}, // ... every possible error } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := parser.ParseSwapEvent(tt.log) require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) }) } } ``` ### 2. Integration Tests (Real Data Required) Test with REAL transactions from Arbiscan. ```go // Example: Integration test with real Arbiscan data func TestUniswapV2Parser_RealTransaction(t *testing.T) { // Load real transaction from Arbiscan // txHash: 0x1234... realTx := loadTransaction("testdata/uniswap_v2_swap_0x1234.json") realReceipt := loadReceipt("testdata/uniswap_v2_receipt_0x1234.json") parser := NewParser(logger, cache) events, err := parser.ParseReceipt(realReceipt, realTx) require.NoError(t, err) require.Len(t, events, 1) event := events[0] // Verify against known values from Arbiscan assert.Equal(t, "0x...", event.Token0.Hex()) assert.Equal(t, "0x...", event.Token1.Hex()) assert.Equal(t, big.NewInt(1000000), event.Amount0In) // ... verify all fields match Arbiscan } // MUST test multiple real transactions per protocol func TestUniswapV2Parser_RealTransactions_Comprehensive(t *testing.T) { testCases := []string{ "0x1234", // Basic swap "0x5678", // Swap with ETH "0x9abc", // Multi-hop swap "0xdef0", // Large amount swap "0x2468", // Small amount swap } for _, txHash := range testCases { t.Run(txHash, func(t *testing.T) { // Load and test real transaction }) } } ``` ### 3. Edge Case Tests (Comprehensive) Test EVERY edge case imaginable. ```go func TestParser_EdgeCases(t *testing.T) { tests := []struct{ name string setupFunc func() *types.Log expectError bool errorMsg string }{ // Boundary values {"max uint256", setupMaxUint256, false, ""}, {"min uint256", setupMinUint256, false, ""}, {"zero amount", setupZeroAmount, true, "zero amount"}, // Token edge cases {"same token0 and token1", setupSameTokens, true, "same token"}, {"token0 > token1 (not sorted)", setupUnsorted, false, ""}, // Decimal edge cases {"0 decimals", setupZeroDecimals, true, "invalid decimals"}, {"19 decimals", setup19Decimals, true, "invalid decimals"}, {"different decimals", setupDifferentDecimals, false, ""}, // Amount edge cases {"both in amounts zero", setupBothInZero, true, "zero amount"}, {"both out amounts zero", setupBothOutZero, true, "zero amount"}, {"negative amount (V3)", setupNegativeAmount, false, ""}, // Address edge cases {"zero token0 address", setupZeroToken0, true, "zero address"}, {"zero token1 address", setupZeroToken1, true, "zero address"}, {"zero pool address", setupZeroPool, true, "zero address"}, {"zero sender", setupZeroSender, true, "zero address"}, // Data edge cases {"empty log data", setupEmptyData, true, "empty data"}, {"truncated log data", setupTruncatedData, true, "invalid data"}, {"extra log data", setupExtraData, false, ""}, // Overflow cases {"amount overflow", setupOverflow, false, ""}, {"price overflow", setupPriceOverflow, false, ""}, {"liquidity overflow", setupLiquidityOverflow, false, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { log := tt.setupFunc() event, err := parser.ParseSwapEvent(log) if tt.expectError { require.Error(t, err) assert.Contains(t, err.Error(), tt.errorMsg) assert.Nil(t, event) } else { require.NoError(t, err) assert.NotNil(t, event) } }) } } ``` ### 4. Decimal Precision Tests (Critical) MUST test decimal handling with EXACT precision. ```go func TestDecimalPrecision(t *testing.T) { tests := []struct{ name string token0Decimals uint8 token1Decimals uint8 reserve0 *big.Int reserve1 *big.Int expectedPrice string // Exact decimal string }{ { name: "USDC/WETH (6/18)", token0Decimals: 6, token1Decimals: 18, reserve0: big.NewInt(1000000), // 1 USDC reserve1: big.NewInt(1e18), // 1 WETH expectedPrice: "1.000000000000000000", // Exact }, { name: "WBTC/WETH (8/18)", token0Decimals: 8, token1Decimals: 18, reserve0: big.NewInt(100000000), // 1 WBTC reserve1: big.NewInt(15.5 * 1e18), // 15.5 WETH expectedPrice: "15.500000000000000000", // Exact }, { name: "DAI/USDC (18/6)", token0Decimals: 18, token1Decimals: 6, reserve0: big.NewInt(1e18), // 1 DAI reserve1: big.NewInt(999000), // 0.999 USDC expectedPrice: "0.999000000000000000", // Exact }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { price := CalculatePrice( tt.reserve0, tt.reserve1, tt.token0Decimals, tt.token1Decimals, ) // MUST match exactly assert.Equal(t, tt.expectedPrice, price.Text('f', 18)) }) } } // Test rounding errors func TestDecimalRounding(t *testing.T) { // Ensure no precision loss through multiple operations initial := new(big.Float).SetPrec(256).SetFloat64(1.123456789012345678) // Simulate multiple swaps result := initial for i := 0; i < 1000; i++ { result = simulateSwap(result) } // Should maintain precision diff := new(big.Float).Sub(initial, result) tolerance := new(big.Float).SetFloat64(1e-15) assert.True(t, diff.Cmp(tolerance) < 0, "precision loss detected") } ``` ### 5. Concurrency Tests (Thread Safety) Test concurrent access to shared resources. ```go func TestPoolCache_Concurrency(t *testing.T) { cache := NewPoolCache() // Concurrent writes var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func(id int) { defer wg.Done() pool := createTestPool(id) err := cache.Add(pool) assert.NoError(t, err) }(i) } // Concurrent reads for i := 0; i < 100; i++ { wg.Add(1) go func(id int) { defer wg.Done() pool, err := cache.Get(testAddress(id)) assert.NoError(t, err) if pool != nil { assert.NotNil(t, pool.Token0) } }(i) } wg.Wait() // Verify no data corruption for i := 0; i < 100; i++ { pool, err := cache.Get(testAddress(i)) require.NoError(t, err) if pool != nil { ValidatePoolInfo(pool) // MUST pass validation } } } // Test race conditions func TestPoolCache_RaceConditions(t *testing.T) { // Run with: go test -race cache := NewPoolCache() done := make(chan bool) // Writer goroutine go func() { for i := 0; i < 1000; i++ { cache.Add(createTestPool(i)) } done <- true }() // Reader goroutines for i := 0; i < 10; i++ { go func() { for j := 0; j < 1000; j++ { cache.Get(testAddress(j % 100)) } done <- true }() } // Wait for completion for i := 0; i < 11; i++ { <-done } } ``` ### 6. Performance Tests (Benchmarks Required) MUST benchmark all critical paths. ```go func BenchmarkParser_ParseSwapEvent(b *testing.B) { parser := setupParser() log := createTestLog() b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = parser.ParseSwapEvent(log) } } // MUST meet performance targets func BenchmarkParser_ParseSwapEvent_Target(b *testing.B) { parser := setupParser() log := createTestLog() b.ResetTimer() start := time.Now() for i := 0; i < b.N; i++ { _, _ = parser.ParseSwapEvent(log) } elapsed := time.Since(start) avgTime := elapsed / time.Duration(b.N) // MUST complete in < 1ms if avgTime > time.Millisecond { b.Fatalf("Too slow: %v per operation (target: < 1ms)", avgTime) } } // Memory allocation benchmarks func BenchmarkParser_ParseSwapEvent_Allocs(b *testing.B) { parser := setupParser() log := createTestLog() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = parser.ParseSwapEvent(log) } // Check allocation count // Should minimize allocations } ``` ### 7. Protocol-Specific Tests Each protocol MUST have comprehensive test suite. ```go // Uniswap V2 func TestUniswapV2_AllEventTypes(t *testing.T) { tests := []string{"Swap", "Mint", "Burn", "Sync"} // Test all event types } // Uniswap V3 func TestUniswapV3_AllEventTypes(t *testing.T) { tests := []string{"Swap", "Mint", "Burn", "Flash", "Collect"} // Test all event types + V3 specific logic } // Curve func TestCurve_AllPoolTypes(t *testing.T) { tests := []string{"StableSwap", "CryptoSwap", "Tricrypto"} // Test all Curve variants } // Camelot Algebra versions func TestCamelot_AllAlgebraVersions(t *testing.T) { tests := []string{"AlgebraV1", "AlgebraV1.9", "AlgebraIntegral", "AlgebraDirectional"} // Test all Algebra variants with different fee structures } ``` ### 8. Integration Test Suite Complete end-to-end testing. ```go func TestE2E_FullPipeline(t *testing.T) { // Setup full system monitor := setupMonitor() factory := setupParserFactory() cache := setupCache() validator := setupValidator() arbDetector := setupArbitrageDetector() // Feed real block data block := loadRealBlock("testdata/block_12345.json") // Process through full pipeline txs := monitor.ParseBlock(block) for _, tx := range txs { events, err := factory.ParseTransaction(tx) require.NoError(t, err) for _, event := range events { // Validate err = validator.Validate(event) require.NoError(t, err) // Update cache cache.UpdateFromEvent(event) // Check for arbitrage opps, err := arbDetector.FindOpportunities(event) require.NoError(t, err) for _, opp := range opps { // Verify opportunity is valid ValidateOpportunity(opp) } } } // Verify final state assert.True(t, cache.Size() > 0) assert.True(t, validator.GetStats().TotalValidated > 0) } ``` ## Test Data Requirements ### 1. Real Transaction Data Store real Arbiscan data in `testdata/`: ``` testdata/ ├── uniswap_v2/ │ ├── swap_0x1234.json │ ├── mint_0x5678.json │ └── burn_0x9abc.json ├── uniswap_v3/ │ ├── swap_0xdef0.json │ ├── mint_0x2468.json │ └── flash_0x1357.json ├── curve/ │ └── exchange_0xace0.json └── camelot/ ├── algebra_v1_swap_0xfff0.json ├── algebra_integral_swap_0xeee0.json └── algebra_directional_swap_0xddd0.json ``` ### 2. Test Data Generation ```go // Generate comprehensive test data func GenerateTestData() { protocols := []Protocol{ ProtocolUniswapV2, ProtocolUniswapV3, ProtocolCurve, // ... all protocols } for _, protocol := range protocols { // Generate edge cases generateZeroAddressCases(protocol) generateZeroAmountCases(protocol) generateMaxValueCases(protocol) generateDecimalCases(protocol) generateOverflowCases(protocol) } } ``` ## CI/CD Integration ### Pre-commit Hooks ```bash #!/bin/bash # .git/hooks/pre-commit # Run tests go test ./... -v if [ $? -ne 0 ]; then echo "Tests failed. Commit rejected." exit 1 fi # Check coverage coverage=$(go test ./... -coverprofile=coverage.out -covermode=atomic | \ go tool cover -func=coverage.out | \ grep total | \ awk '{print $3}' | \ sed 's/%//') if (( $(echo "$coverage < 100" | bc -l) )); then echo "Coverage is ${coverage}% (required: 100%). Commit rejected." exit 1 fi echo "All tests passed with 100% coverage. ✓" ``` ### GitHub Actions ```yaml name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v4 with: go-version: '1.24' - name: Run tests run: go test ./... -v -race -coverprofile=coverage.out -covermode=atomic - name: Check coverage run: | coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') if (( $(echo "$coverage < 100" | bc -l) )); then echo "Coverage is ${coverage}% (required: 100%)" exit 1 fi echo "Coverage: ${coverage}% ✓" - name: Upload coverage uses: codecov/codecov-action@v3 with: files: ./coverage.out fail_ci_if_error: true ``` ## Test Execution Commands ```bash # Run all tests with coverage go test ./... -v -race -coverprofile=coverage.out -covermode=atomic # Run specific package tests go test ./pkg/parsers/uniswap_v2/... -v # Run with race detector go test ./... -race # Run benchmarks go test ./... -bench=. -benchmem # Run only unit tests go test ./... -short # Run integration tests go test ./... -run Integration # Generate coverage report go tool cover -html=coverage.out -o coverage.html # Check coverage percentage go tool cover -func=coverage.out | grep total ``` ## Coverage Enforcement Rules 1. **No merging without 100% coverage** - PR CI/CD must show 100% coverage - Manual override NOT allowed 2. **No skipping tests** - `t.Skip()` NOT allowed except for known external dependencies - Must document reason for skip 3. **No ignoring failures** - All test failures MUST be fixed - Cannot merge with failing tests 4. **Coverage for all code paths** - Every `if` statement tested (both branches) - Every `switch` case tested - Every error path tested - Every success path tested ## Test Documentation Each test file MUST include: ```go /* Package uniswap_v2_test provides comprehensive test coverage for UniswapV2Parser. Test Coverage: - ParseSwapEvent: 100% - ParseMintEvent: 100% - ParseBurnEvent: 100% - ValidateEvent: 100% Edge Cases Tested: - Zero addresses (rejected) - Zero amounts (rejected) - Maximum values (accepted) - Decimal precision (verified) - Concurrent access (safe) Real Data Tests: - 5 real Arbiscan transactions - All event types covered - Multiple pool types tested Performance: - Parse time: < 1ms (verified) - Memory: < 1KB per parse (verified) */ ``` --- **ABSOLUTE REQUIREMENT**: 100% coverage, 100% passage, zero tolerance for failures.