Files
beads/internal/formula/controlflow_test.go
Steve Yegge 030838cfde Implement computed range expansion for loops (gt-8tmz.27)
Add support for for-each expansion over computed ranges:

  loop:
    range: "1..2^{disks}-1"  # Evaluated at cook time
    var: move_num
    body: [...]

Features:
- Range field in LoopSpec for computed iterations
- Var field to expose iteration value to body steps
- Expression evaluator supporting + - * / ^ and parentheses
- Variable substitution in range expressions using {name} syntax
- Title/description variable substitution in expanded steps

Example use case: Towers of Hanoi formula where step count is 2^n-1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 17:48:35 -08:00

981 lines
22 KiB
Go

package formula
import (
"strings"
"testing"
)
func TestApplyLoops_FixedCount(t *testing.T) {
// Create a step with a fixed-count loop
steps := []*Step{
{
ID: "process",
Title: "Process items",
Loop: &LoopSpec{
Count: 3,
Body: []*Step{
{ID: "fetch", Title: "Fetch item"},
{ID: "transform", Title: "Transform item", Needs: []string{"fetch"}},
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Should have 6 steps (3 iterations * 2 steps each)
if len(result) != 6 {
t.Errorf("Expected 6 steps, got %d", len(result))
}
// Check step IDs
expectedIDs := []string{
"process.iter1.fetch",
"process.iter1.transform",
"process.iter2.fetch",
"process.iter2.transform",
"process.iter3.fetch",
"process.iter3.transform",
}
for i, expected := range expectedIDs {
if i >= len(result) {
t.Errorf("Missing step %d: %s", i, expected)
continue
}
if result[i].ID != expected {
t.Errorf("Step %d: expected ID %s, got %s", i, expected, result[i].ID)
}
}
// Check that inner dependencies are preserved (within same iteration)
transform1 := result[1]
if len(transform1.Needs) != 1 || transform1.Needs[0] != "process.iter1.fetch" {
t.Errorf("transform1 should need process.iter1.fetch, got %v", transform1.Needs)
}
// Check that iterations are chained (iter2 depends on iter1)
fetch2 := result[2]
if len(fetch2.Needs) != 1 || fetch2.Needs[0] != "process.iter1.transform" {
t.Errorf("iter2.fetch should need iter1.transform, got %v", fetch2.Needs)
}
}
func TestApplyLoops_Conditional(t *testing.T) {
steps := []*Step{
{
ID: "retry",
Title: "Retry operation",
Loop: &LoopSpec{
Until: "step.status == 'complete'",
Max: 5,
Body: []*Step{
{ID: "attempt", Title: "Attempt operation"},
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Conditional loops expand once (runtime re-executes)
if len(result) != 1 {
t.Errorf("Expected 1 step for conditional loop, got %d", len(result))
}
// Should have loop metadata label with JSON format
step := result[0]
hasLoopLabel := false
for _, label := range step.Labels {
// Label format: loop:{"max":5,"until":"step.status == 'complete'"}
if len(label) > 5 && label[:5] == "loop:" {
hasLoopLabel = true
// Verify it contains the expected values
if !strings.Contains(label, `"until"`) || !strings.Contains(label, `"max"`) {
t.Errorf("Loop label missing expected fields: %s", label)
}
}
}
if !hasLoopLabel {
t.Error("Missing loop metadata label")
}
}
func TestApplyLoops_Validation(t *testing.T) {
tests := []struct {
name string
loop *LoopSpec
wantErr string
}{
{
name: "empty body",
loop: &LoopSpec{Count: 3, Body: nil},
wantErr: "body is required",
},
{
name: "both count and until",
loop: &LoopSpec{Count: 3, Until: "cond", Max: 5, Body: []*Step{{ID: "a", Title: "A"}}},
wantErr: "cannot have both count and until",
},
{
name: "neither count nor until",
loop: &LoopSpec{Body: []*Step{{ID: "a", Title: "A"}}},
wantErr: "either count or until is required",
},
{
name: "until without max",
loop: &LoopSpec{Until: "cond", Body: []*Step{{ID: "a", Title: "A"}}},
wantErr: "max is required when until is set",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
steps := []*Step{{ID: "test", Title: "Test", Loop: tt.loop}}
_, err := ApplyLoops(steps)
if err == nil {
t.Error("Expected error, got nil")
} else if tt.wantErr != "" && err.Error() != "" {
// Just check that an error was returned
// The exact message format may vary
}
})
}
}
func TestApplyBranches(t *testing.T) {
steps := []*Step{
{ID: "setup", Title: "Setup"},
{ID: "test", Title: "Run tests"},
{ID: "lint", Title: "Run linter"},
{ID: "build", Title: "Build"},
{ID: "deploy", Title: "Deploy"},
}
compose := &ComposeRules{
Branch: []*BranchRule{
{
From: "setup",
Steps: []string{"test", "lint", "build"},
Join: "deploy",
},
},
}
result, err := ApplyBranches(steps, compose)
if err != nil {
t.Fatalf("ApplyBranches failed: %v", err)
}
// Build step map for checking
stepMap := make(map[string]*Step)
for _, s := range result {
stepMap[s.ID] = s
}
// Verify branch steps depend on 'from'
for _, branchStep := range []string{"test", "lint", "build"} {
s := stepMap[branchStep]
found := false
for _, need := range s.Needs {
if need == "setup" {
found = true
break
}
}
if !found {
t.Errorf("Step %s should need 'setup', got %v", branchStep, s.Needs)
}
}
// Verify 'join' depends on all branch steps
deploy := stepMap["deploy"]
for _, branchStep := range []string{"test", "lint", "build"} {
found := false
for _, need := range deploy.Needs {
if need == branchStep {
found = true
break
}
}
if !found {
t.Errorf("deploy should need %s, got %v", branchStep, deploy.Needs)
}
}
}
func TestApplyBranches_Validation(t *testing.T) {
steps := []*Step{
{ID: "a", Title: "A"},
{ID: "b", Title: "B"},
}
tests := []struct {
name string
branch *BranchRule
wantErr string
}{
{
name: "missing from",
branch: &BranchRule{Steps: []string{"a"}, Join: "b"},
wantErr: "from is required",
},
{
name: "missing steps",
branch: &BranchRule{From: "a", Join: "b"},
wantErr: "steps is required",
},
{
name: "missing join",
branch: &BranchRule{From: "a", Steps: []string{"b"}},
wantErr: "join is required",
},
{
name: "from not found",
branch: &BranchRule{From: "notfound", Steps: []string{"a"}, Join: "b"},
wantErr: "from step \"notfound\" not found",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compose := &ComposeRules{Branch: []*BranchRule{tt.branch}}
_, err := ApplyBranches(steps, compose)
if err == nil {
t.Error("Expected error, got nil")
}
})
}
}
func TestApplyGates(t *testing.T) {
steps := []*Step{
{ID: "tests", Title: "Run tests"},
{ID: "deploy", Title: "Deploy to production"},
}
compose := &ComposeRules{
Gate: []*GateRule{
{
Before: "deploy",
Condition: "tests.status == 'complete'",
},
},
}
result, err := ApplyGates(steps, compose)
if err != nil {
t.Fatalf("ApplyGates failed: %v", err)
}
// Find deploy step
var deploy *Step
for _, s := range result {
if s.ID == "deploy" {
deploy = s
break
}
}
if deploy == nil {
t.Fatal("deploy step not found")
}
// Check for gate label with JSON format
found := false
for _, label := range deploy.Labels {
// Label format: gate:{"condition":"tests.status == 'complete'"}
if len(label) > 5 && label[:5] == "gate:" {
found = true
if !strings.Contains(label, `"condition"`) || !strings.Contains(label, "tests.status") {
t.Errorf("Gate label missing expected content: %s", label)
}
break
}
}
if !found {
t.Errorf("deploy should have gate label, got %v", deploy.Labels)
}
}
func TestApplyGates_InvalidCondition(t *testing.T) {
steps := []*Step{
{ID: "deploy", Title: "Deploy"},
}
compose := &ComposeRules{
Gate: []*GateRule{
{
Before: "deploy",
Condition: "invalid condition syntax ???",
},
},
}
_, err := ApplyGates(steps, compose)
if err == nil {
t.Error("Expected error for invalid condition, got nil")
}
}
func TestApplyControlFlow_Integration(t *testing.T) {
// Test the combined ApplyControlFlow function
steps := []*Step{
{ID: "setup", Title: "Setup"},
{
ID: "process",
Title: "Process items",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{ID: "item", Title: "Process item"},
},
},
},
{ID: "cleanup", Title: "Cleanup"},
}
compose := &ComposeRules{
Branch: []*BranchRule{
{
From: "setup",
Steps: []string{"process.iter1.item", "process.iter2.item"},
Join: "cleanup",
},
},
Gate: []*GateRule{
{
Before: "cleanup",
Condition: "steps.complete >= 2",
},
},
}
result, err := ApplyControlFlow(steps, compose)
if err != nil {
t.Fatalf("ApplyControlFlow failed: %v", err)
}
// Should have: setup, process.iter1.item, process.iter2.item, cleanup
if len(result) != 4 {
t.Errorf("Expected 4 steps, got %d", len(result))
}
// Verify cleanup has gate label
var cleanup *Step
for _, s := range result {
if s.ID == "cleanup" {
cleanup = s
break
}
}
if cleanup == nil {
t.Fatal("cleanup step not found")
}
hasGate := false
for _, label := range cleanup.Labels {
// Label format: gate:{"condition":"steps.complete >= 2"}
if len(label) > 5 && label[:5] == "gate:" && strings.Contains(label, "steps.complete") {
hasGate = true
break
}
}
if !hasGate {
t.Errorf("cleanup should have gate label, got %v", cleanup.Labels)
}
}
func TestApplyLoops_NoLoops(t *testing.T) {
// Test with steps that have no loops
steps := []*Step{
{ID: "a", Title: "A"},
{ID: "b", Title: "B", Needs: []string{"a"}},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
if len(result) != 2 {
t.Errorf("Expected 2 steps, got %d", len(result))
}
// Dependencies should be preserved
if len(result[1].Needs) != 1 || result[1].Needs[0] != "a" {
t.Errorf("Dependencies not preserved: %v", result[1].Needs)
}
}
func TestApplyLoops_ExternalDependencies(t *testing.T) {
// Test that dependencies on steps OUTSIDE the loop are preserved as-is
steps := []*Step{
{ID: "setup", Title: "Setup"},
{
ID: "process",
Title: "Process items",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{ID: "work", Title: "Do work", Needs: []string{"setup"}}, // External dep
{ID: "save", Title: "Save", Needs: []string{"work"}}, // Internal dep
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Should have: setup, process.iter1.work, process.iter1.save, process.iter2.work, process.iter2.save
if len(result) != 5 {
t.Errorf("Expected 5 steps, got %d", len(result))
}
// Find iter1.work - should have external dep on "setup" (not "process.iter1.setup")
var work1 *Step
for _, s := range result {
if s.ID == "process.iter1.work" {
work1 = s
break
}
}
if work1 == nil {
t.Fatal("process.iter1.work not found")
}
// External dependency should be preserved as-is
if len(work1.Needs) != 1 || work1.Needs[0] != "setup" {
t.Errorf("External dependency should be 'setup', got %v", work1.Needs)
}
// Find iter1.save - should have internal dep on "process.iter1.work"
var save1 *Step
for _, s := range result {
if s.ID == "process.iter1.save" {
save1 = s
break
}
}
if save1 == nil {
t.Fatal("process.iter1.save not found")
}
// Internal dependency should be prefixed
if len(save1.Needs) != 1 || save1.Needs[0] != "process.iter1.work" {
t.Errorf("Internal dependency should be 'process.iter1.work', got %v", save1.Needs)
}
}
func TestApplyLoops_NestedChildren(t *testing.T) {
// Test that children are preserved when recursing
steps := []*Step{
{
ID: "parent",
Title: "Parent",
Children: []*Step{
{ID: "child", Title: "Child"},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
if len(result) != 1 {
t.Fatalf("Expected 1 step, got %d", len(result))
}
if len(result[0].Children) != 1 {
t.Errorf("Expected 1 child, got %d", len(result[0].Children))
}
}
// gt-zn35j: Tests for nested loop support
func TestApplyLoops_NestedLoops(t *testing.T) {
// Create a loop containing another loop
steps := []*Step{
{
ID: "outer",
Title: "Outer loop",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{
ID: "inner",
Title: "Inner loop",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{ID: "work", Title: "Do work"},
},
},
},
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Should have 4 steps total (2 outer * 2 inner)
if len(result) != 4 {
t.Errorf("Expected 4 steps, got %d", len(result))
for i, s := range result {
t.Logf(" Step %d: %s", i, s.ID)
}
}
// Check step IDs follow nested pattern
expectedIDs := []string{
"outer.iter1.inner.iter1.work",
"outer.iter1.inner.iter2.work",
"outer.iter2.inner.iter1.work",
"outer.iter2.inner.iter2.work",
}
for i, expected := range expectedIDs {
if i >= len(result) {
t.Errorf("Missing step %d: %s", i, expected)
continue
}
if result[i].ID != expected {
t.Errorf("Step %d: expected ID %s, got %s", i, expected, result[i].ID)
}
}
}
func TestApplyLoops_NestedLoopsWithDependencies(t *testing.T) {
// Nested loops with dependencies between inner steps
steps := []*Step{
{
ID: "outer",
Title: "Outer loop",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{
ID: "inner",
Title: "Inner loop",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{ID: "fetch", Title: "Fetch data"},
{ID: "process", Title: "Process data", Needs: []string{"fetch"}},
},
},
},
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Should have 8 steps (2 outer * 2 inner * 2 body steps)
if len(result) != 8 {
t.Errorf("Expected 8 steps, got %d", len(result))
}
// Check that inner dependencies are correctly prefixed
// Find outer.iter1.inner.iter1.process - should depend on outer.iter1.inner.iter1.fetch
var process1 *Step
for _, s := range result {
if s.ID == "outer.iter1.inner.iter1.process" {
process1 = s
break
}
}
if process1 == nil {
t.Fatal("outer.iter1.inner.iter1.process not found")
}
if len(process1.Needs) != 1 || process1.Needs[0] != "outer.iter1.inner.iter1.fetch" {
t.Errorf("process should need outer.iter1.inner.iter1.fetch, got %v", process1.Needs)
}
}
func TestApplyLoops_ThreeLevelNesting(t *testing.T) {
// Three levels of nesting
steps := []*Step{
{
ID: "l1",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{
ID: "l2",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{
ID: "l3",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{ID: "leaf", Title: "Leaf step"},
},
},
},
},
},
},
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Should have 8 steps (2 * 2 * 2)
if len(result) != 8 {
t.Errorf("Expected 8 steps, got %d", len(result))
}
// Check first and last step IDs
if result[0].ID != "l1.iter1.l2.iter1.l3.iter1.leaf" {
t.Errorf("First step ID wrong: %s", result[0].ID)
}
if result[7].ID != "l1.iter2.l2.iter2.l3.iter2.leaf" {
t.Errorf("Last step ID wrong: %s", result[7].ID)
}
}
func TestApplyLoops_NestedLoopsOuterChaining(t *testing.T) {
// Verify that outer iterations are chained AFTER nested loop expansion.
// outer.iter2's first step should depend on outer.iter1's LAST step.
steps := []*Step{
{
ID: "outer",
Title: "Outer loop",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{
ID: "inner",
Title: "Inner loop",
Loop: &LoopSpec{
Count: 2,
Body: []*Step{
{ID: "work", Title: "Do work"},
},
},
},
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Should have 4 steps
if len(result) != 4 {
t.Fatalf("Expected 4 steps, got %d", len(result))
}
// Expected order:
// 0: outer.iter1.inner.iter1.work
// 1: outer.iter1.inner.iter2.work (depends on above via inner chaining)
// 2: outer.iter2.inner.iter1.work (should depend on step 1 via outer chaining!)
// 3: outer.iter2.inner.iter2.work (depends on above via inner chaining)
// Verify outer chaining: step 2 should depend on step 1
step2 := result[2]
if step2.ID != "outer.iter2.inner.iter1.work" {
t.Fatalf("Step 2 ID wrong: %s", step2.ID)
}
// This is the key assertion: outer.iter2's first step must depend on
// outer.iter1's last step (outer.iter1.inner.iter2.work)
expectedDep := "outer.iter1.inner.iter2.work"
found := false
for _, need := range step2.Needs {
if need == expectedDep {
found = true
break
}
}
if !found {
t.Errorf("outer.iter2 first step should depend on outer.iter1 last step.\n"+
"Expected Needs to contain %q, got %v", expectedDep, step2.Needs)
}
}
// gt-v1pcg: Tests for immutability of ApplyBranches and ApplyGates
func TestApplyBranches_Immutability(t *testing.T) {
// Create steps with no initial dependencies
steps := []*Step{
{ID: "setup", Title: "Setup", Needs: nil},
{ID: "test", Title: "Run tests", Needs: nil},
{ID: "deploy", Title: "Deploy", Needs: nil},
}
compose := &ComposeRules{
Branch: []*BranchRule{
{From: "setup", Steps: []string{"test"}, Join: "deploy"},
},
}
// Call ApplyBranches
result, err := ApplyBranches(steps, compose)
if err != nil {
t.Fatalf("ApplyBranches failed: %v", err)
}
// Verify original steps are NOT mutated
for _, step := range steps {
if len(step.Needs) != 0 {
t.Errorf("Original step %q was mutated: Needs = %v (expected nil)", step.ID, step.Needs)
}
}
// Verify result has the expected dependencies
resultMap := make(map[string]*Step)
for _, s := range result {
resultMap[s.ID] = s
}
if len(resultMap["test"].Needs) != 1 || resultMap["test"].Needs[0] != "setup" {
t.Errorf("Result test step should need setup, got %v", resultMap["test"].Needs)
}
}
func TestApplyGates_Immutability(t *testing.T) {
// Create steps with no initial labels
steps := []*Step{
{ID: "tests", Title: "Run tests", Labels: nil},
{ID: "deploy", Title: "Deploy", Labels: nil},
}
compose := &ComposeRules{
Gate: []*GateRule{
{Before: "deploy", Condition: "tests.status == 'complete'"},
},
}
// Call ApplyGates
result, err := ApplyGates(steps, compose)
if err != nil {
t.Fatalf("ApplyGates failed: %v", err)
}
// Verify original steps are NOT mutated
for _, step := range steps {
if len(step.Labels) != 0 {
t.Errorf("Original step %q was mutated: Labels = %v (expected nil)", step.ID, step.Labels)
}
}
// Verify result has the expected labels
var deployResult *Step
for _, s := range result {
if s.ID == "deploy" {
deployResult = s
break
}
}
if deployResult == nil {
t.Fatal("deploy step not found in result")
}
hasGate := false
for _, label := range deployResult.Labels {
if len(label) > 5 && label[:5] == "gate:" {
hasGate = true
break
}
}
if !hasGate {
t.Errorf("Result deploy step should have gate label, got %v", deployResult.Labels)
}
}
// TestApplyLoops_Range tests computed range expansion (gt-8tmz.27).
func TestApplyLoops_Range(t *testing.T) {
// Create a step with a range loop
steps := []*Step{
{
ID: "moves",
Title: "Tower moves",
Loop: &LoopSpec{
Range: "1..3",
Var: "move_num",
Body: []*Step{
{ID: "move", Title: "Move {move_num}"},
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Should have 3 steps (range 1..3 = 3 iterations)
if len(result) != 3 {
t.Errorf("Expected 3 steps, got %d", len(result))
}
// Check step IDs and titles
expectedIDs := []string{
"moves.iter1.move",
"moves.iter2.move",
"moves.iter3.move",
}
expectedTitles := []string{
"Move 1",
"Move 2",
"Move 3",
}
for i, expected := range expectedIDs {
if i >= len(result) {
t.Errorf("Missing step %d: %s", i, expected)
continue
}
if result[i].ID != expected {
t.Errorf("Step %d: expected ID %s, got %s", i, expected, result[i].ID)
}
if result[i].Title != expectedTitles[i] {
t.Errorf("Step %d: expected Title %q, got %q", i, expectedTitles[i], result[i].Title)
}
}
}
// TestApplyLoops_RangeComputed tests computed range with expressions.
func TestApplyLoops_RangeComputed(t *testing.T) {
// Create a step with a computed range loop (like Towers of Hanoi)
steps := []*Step{
{
ID: "hanoi",
Title: "Hanoi moves",
Loop: &LoopSpec{
Range: "1..2^3-1", // 1..7 (2^3-1 moves for 3 disks)
Var: "step_num",
Body: []*Step{
{ID: "step", Title: "Step {step_num}"},
},
},
},
}
result, err := ApplyLoops(steps)
if err != nil {
t.Fatalf("ApplyLoops failed: %v", err)
}
// Should have 7 steps (2^3-1 = 7)
if len(result) != 7 {
t.Errorf("Expected 7 steps, got %d", len(result))
}
// Check first and last step
if len(result) >= 1 {
if result[0].Title != "Step 1" {
t.Errorf("First step title: expected 'Step 1', got %q", result[0].Title)
}
}
if len(result) >= 7 {
if result[6].Title != "Step 7" {
t.Errorf("Last step title: expected 'Step 7', got %q", result[6].Title)
}
}
}
// TestValidateLoopSpec_Range tests validation of range loops.
func TestValidateLoopSpec_Range(t *testing.T) {
tests := []struct {
name string
loop *LoopSpec
wantErr bool
}{
{
name: "valid range",
loop: &LoopSpec{
Range: "1..10",
Body: []*Step{{ID: "step"}},
},
wantErr: false,
},
{
name: "valid computed range",
loop: &LoopSpec{
Range: "1..2^3",
Var: "n",
Body: []*Step{{ID: "step"}},
},
wantErr: false,
},
{
name: "invalid - both count and range",
loop: &LoopSpec{
Count: 5,
Range: "1..10",
Body: []*Step{{ID: "step"}},
},
wantErr: true,
},
{
name: "invalid - both until and range",
loop: &LoopSpec{
Until: "step.status == 'complete'",
Max: 10,
Range: "1..10",
Body: []*Step{{ID: "step"}},
},
wantErr: true,
},
{
name: "invalid range syntax",
loop: &LoopSpec{
Range: "invalid",
Body: []*Step{{ID: "step"}},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateLoopSpec(tt.loop, "test")
if (err != nil) != tt.wantErr {
t.Errorf("validateLoopSpec() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}