diff --git a/internal/simulation/engine.go b/internal/simulation/engine.go index 574e5a5..884190f 100644 --- a/internal/simulation/engine.go +++ b/internal/simulation/engine.go @@ -184,8 +184,13 @@ func (e *Engine) Run() { e.Timeline = e.Timeline[:0] for tick := 0; tick < e.Duration; tick++ { + entries := e.findEntryPoints() + if len(entries) == 0 { + fmt.Println("[ERROR] No entry points found! Simulation will not inject requests.") + } + // inject new requests - for _, node := range e.findEntryPoints() { + for _, node := range entries { if shouldInject(tick) { req := &Request{ ID: generateRequestID(tick), @@ -206,23 +211,26 @@ func (e *Engine) Run() { } for id, node := range e.Nodes { + snapshot.NodeHealth[id] = NodeState{ + QueueSize: len(node.GetQueue()), + Alive: node.IsAlive(), + } // tick all nodes node.Tick(tick, currentTimeMs) - emitted := node.Emit() // emit and forward requests to connected nodes - for _, req := range emitted { + for _, req := range node.Emit() { for _, targetID := range node.GetTargets() { - if target, ok := e.Nodes[targetID]; ok && target.IsAlive() { - target.Receive(req) + if target, ok := e.Nodes[targetID]; ok && target.IsAlive() && !hasVisited(req, targetID) { + // Deep copy request and update path + newReq := *req + newReq.Path = append([]string{}, req.Path...) + newReq.Path = append(newReq.Path, targetID) + target.Receive(&newReq) } } } - snapshot.NodeHealth[id] = NodeState{ - QueueSize: len(emitted), - Alive: node.IsAlive(), - } } e.Timeline = append(e.Timeline, snapshot) @@ -271,3 +279,12 @@ func asString(v interface{}) string { return s } + +func hasVisited(req *Request, nodeID string) bool { + for _, id := range req.Path { + if id == nodeID { + return true + } + } + return false +} diff --git a/internal/simulation/engine_test.go b/internal/simulation/engine_test.go index 1f11cb9..d69fa2c 100644 --- a/internal/simulation/engine_test.go +++ b/internal/simulation/engine_test.go @@ -1,6 +1,7 @@ package simulation import ( + "fmt" "os" "path/filepath" "testing" @@ -87,3 +88,43 @@ func TestComplexSimulationRun(t *testing.T) { } } +func TestSimulationRunEndToEnd(t *testing.T) { + data, err := os.ReadFile("testdata/simple_design.json") + fmt.Print(data) + if err != nil { + t.Fatalf("Failed to read test data: %v", err) + } + + var design design.Design + if err := json.Unmarshal(data, &design); err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + engine := NewEngineFromDesign(design, 20, 100) // 20 ticks, 100ms per tick + engine.Run() + + if len(engine.Timeline) != 20 { + t.Errorf("Expected 20 timeline entries, got %d", len(engine.Timeline)) + } + + anyTraffic := false + for _, snapshot := range engine.Timeline { + for _, nodeState := range snapshot.NodeHealth { + if nodeState.QueueSize > 0 { + anyTraffic = true + break + } + } + } + + if !anyTraffic { + t.Errorf("Expected at least one node to have non-zero queue size over time") + } + + // Optional: check a few expected node IDs + for _, id := range []string{"node-1", "node-2"} { + if _, ok := engine.Nodes[id]; !ok { + t.Errorf("Expected node %s to be present in simulation", id) + } + } +}