In a busy city intersection, traffic lights are used to manage the flow of vehicles coming from different directions. Each road has its own traffic light, and the lights must coordinate to ensure safety while allowing vehicles to flow efficiently. For example, if the traffic light for the North-South road is green, the traffic light for the East-West road must be red, and vice versa. This ensures that vehicles do not collide at the intersection.
Your task is to simulate this traffic light system using Go's concurrency features. Each traffic light will run independently as a Goroutine, but a central controller will ensure that their states remain synchronized.
Simulate Two Traffic Lights:
State Transition Logic:
Coordination:
Concurrency:
Output Requirements:
[00:00] North-South: Green [00:00] East-West: Red [00:10] East-West: Red [00:10] North-South: Yellow [00:13] North-South: Red [00:13] East-West: Green [00:23] North-South: Red [00:23] East-West: Yellow [00:26] East-West: Red [00:26] North-South: Green [00:36] East-West: Red [00:36] North-South: Yellow [00:39] East-West: Green [00:39] North-South: Red [00:49] East-West: Yellow [00:49] North-South: Red [00:52] East-West: Red [00:52] North-South: Green [01:02] East-West: Red [01:02] North-South: Yellow [01:05] East-West: Green [01:05] North-South: Red [01:15] East-West: Yellow [01:15] North-South: Red
Stop Condition:
The project structure should look like this:
. ├── main.go ├── main_test.go
Your program should have the following structure and implement these functions:
main.go
package main import ( "fmt" "time" ) type LightState string const ( Green LightState = "Green" Yellow LightState = "Yellow" Red LightState = "Red" ) type TrafficLight struct { Name string // Name of traffic light Ch chan LightState // Channel to receive light state changes State LightState // Current light state } // NewTrafficLight creates a new TrafficLight instance func NewTrafficLight(name string) *TrafficLight { return &TrafficLight{ Name: name, Ch: make(chan LightState), State: Red, } } // Run starts the traffic light and listen to the channel for state changes. It also send logs to the logCh channel. func (tl *TrafficLight) Run(logCh chan string, startTime time.Time) { } // controller is a function that controls the traffic light states and changes them in defined intervals. func controller(tl1, tl2 *TrafficLight, cycles int) { } func main() { lightNS := NewTrafficLight("North-South") lightEW := NewTrafficLight("East-West") logCh := make(chan string) // Close channels to stop goroutines defer close(lightNS.Ch) defer close(lightEW.Ch) defer close(logCh) startTime := time.Now() // Start traffic light goroutines go lightNS.Run(logCh, startTime) go lightEW.Run(logCh, startTime) // Start a logger goroutine go func() { for log := range logCh { fmt.Println(log) } }() // Start controller controller(lightNS, lightEW, 3) }
main_test.go
package main import ( "strings" "testing" "time" ) func TestNewTrafficLight(t *testing.T) { light := NewTrafficLight("Test Light") if light.Name != "Test Light" { t.Errorf("Expected name 'Test Light', got '%s'", light.Name) } if light.State != Red { t.Errorf("Expected initial state to be 'Red', got '%s'", light.State) } if light.Ch == nil { t.Errorf("Expected channel to be initialized, got nil") } } func TestTrafficLightRun(t *testing.T) { light := NewTrafficLight("Test Light") logCh := make(chan string, 1) // Buffered channel for testing defer close(logCh) startTime := time.Now() go light.Run(logCh, startTime) // Simulate state change light.Ch <- Green // Validate log output select { case log := <-logCh: if log == "" { t.Errorf("Expected log message, got empty string") } if light.State != Green { t.Errorf("Expected light state 'Green', got '%s'", light.State) } case <-time.After(1 * time.Second): t.Errorf("Timeout waiting for log message") } // Clean up close(light.Ch) } func TestController(t *testing.T) { lightNS := NewTrafficLight("North-South") lightEW := NewTrafficLight("East-West") logCh := make(chan string, 50) // Buffered channel to handle logs defer close(logCh) // Start traffic light goroutines go lightNS.Run(logCh, time.Now()) go lightEW.Run(logCh, time.Now()) // Run the controller in a separate goroutine go func() { controller(lightNS, lightEW, 3) // 3 cycles close(lightNS.Ch) close(lightEW.Ch) }() // Expected sequence for each cycle type lightState struct { NS LightState EW LightState } expectedSequence := []lightState{ {NS: Green, EW: Red}, // Phase 1 {NS: Yellow, EW: Red}, // Phase 2 {NS: Red, EW: Green}, // Phase 3 {NS: Red, EW: Yellow}, // Phase 4 } // Variables to track the current phase and cycle currentPhase := 0 cycleCount := 0 stateMap := map[string]LightState{ "NS": Red, "EW": Red, } timeout := time.After(2 * time.Minute) // Increased timeout for 3 cycles for cycleCount < 3 { select { case log := <-logCh: t.Logf("Received log: %s", log) // Debugging log // Update state map based on the log if strings.Contains(log, "North-South") { if strings.Contains(log, "Green") { stateMap["NS"] = Green } else if strings.Contains(log, "Yellow") { stateMap["NS"] = Yellow } else if strings.Contains(log, "Red") { stateMap["NS"] = Red } } else if strings.Contains(log, "East-West") { if strings.Contains(log, "Green") { stateMap["EW"] = Green } else if strings.Contains(log, "Yellow") { stateMap["EW"] = Yellow } else if strings.Contains(log, "Red") { stateMap["EW"] = Red } } // Check mutual exclusivity and phase completion expected := expectedSequence[currentPhase] if stateMap["NS"] == expected.NS && stateMap["EW"] == expected.EW { // Validate mutual exclusivity if stateMap["NS"] != Red && stateMap["EW"] != Red { t.Errorf("Invalid state: North-South = %s, East-West = %s. Both cannot be non-red at the same time.", stateMap["NS"], stateMap["EW"]) } // Move to the next phase currentPhase++ if currentPhase == len(expectedSequence) { currentPhase = 0 cycleCount++ t.Logf("Completed cycle %d", cycleCount) // Debugging log for cycles } } case <-timeout: t.Errorf("Test timed out before completing all cycles. Current phase: %d, Current cycle: %d", currentPhase, cycleCount) return } } if cycleCount != 3 { t.Errorf("Expected 3 cycles but completed %d cycles.", cycleCount) } } func TestTrafficLightConcurrency(t *testing.T) { light := NewTrafficLight("Test Light") logCh := make(chan string, 10) // Buffered for concurrency testing defer close(logCh) startTime := time.Now() go light.Run(logCh, startTime) // Simulate concurrent state changes go func() { light.Ch <- Green }() go func() { light.Ch <- Yellow }() go func() { light.Ch <- Red }() // Collect and validate logs for i := 0; i < 3; i++ { select { case log := <-logCh: if log == "" { t.Errorf("Expected log message, got empty string") } case <-time.After(1 * time.Second): t.Errorf("Timeout waiting for log message") } } // Clean up close(light.Ch) } func containsState(log, state string) bool { return strings.Contains(log, state) }
To run test cases, execute:
go test -v -timeout=2m
main_test.go
validate both traffic light transitions and overall functionality.