From c3fed07a8f3dd65358ade8eb13f43ab76849e28b Mon Sep 17 00:00:00 2001 From: Sebastian Steger Date: Wed, 20 Aug 2025 11:50:05 +0000 Subject: [PATCH] airport starting point --- go/06-airport/README.md | 63 +++++++++ go/06-airport/airport/aircraft.go | 39 +++++ go/06-airport/airport/airport.go | 62 ++++++++ .../airport/baggageHandlingSystem.go | 44 ++++++ .../airport/baggageHandlingSystem_test.go | 133 ++++++++++++++++++ go/06-airport/airport/checkin.go | 72 ++++++++++ go/06-airport/airport/checkin_test.go | 116 +++++++++++++++ go/06-airport/airport/flightSchedule.go | 104 ++++++++++++++ go/06-airport/airport/gate.go | 63 +++++++++ go/06-airport/airport/gate_test.go | 112 +++++++++++++++ go/06-airport/airport/passenger.go | 128 +++++++++++++++++ go/06-airport/airport/runway.go | 33 +++++ go/06-airport/airport/securityCheck.go | 32 +++++ go/06-airport/airport/securityCheck_test.go | 81 +++++++++++ go/06-airport/go.mod | 5 + go/06-airport/go.sum | 2 + go/06-airport/main.go | 57 ++++++++ 17 files changed, 1146 insertions(+) create mode 100644 go/06-airport/README.md create mode 100644 go/06-airport/airport/aircraft.go create mode 100644 go/06-airport/airport/airport.go create mode 100644 go/06-airport/airport/baggageHandlingSystem.go create mode 100644 go/06-airport/airport/baggageHandlingSystem_test.go create mode 100644 go/06-airport/airport/checkin.go create mode 100644 go/06-airport/airport/checkin_test.go create mode 100644 go/06-airport/airport/flightSchedule.go create mode 100644 go/06-airport/airport/gate.go create mode 100644 go/06-airport/airport/gate_test.go create mode 100644 go/06-airport/airport/passenger.go create mode 100644 go/06-airport/airport/runway.go create mode 100644 go/06-airport/airport/securityCheck.go create mode 100644 go/06-airport/airport/securityCheck_test.go create mode 100644 go/06-airport/go.mod create mode 100644 go/06-airport/go.sum create mode 100644 go/06-airport/main.go diff --git a/go/06-airport/README.md b/go/06-airport/README.md new file mode 100644 index 0000000..c96f5b9 --- /dev/null +++ b/go/06-airport/README.md @@ -0,0 +1,63 @@ +# Assignment - Airport Simulation +This go-program is a simulation of the departure process of an airport that consists of *aircrafts*, *passengers*, *checkin-counter*, *security-checks*, *gates*, a *baggage handling system*, a *runway*, a *flight schedule* and *gates*. Passengers go through the following steps: +- **Checkin**: The ticket is verified, baggage is checked in and handed over to the baggage handling system, and a boarding pass is issued. +- **Security-Check**: The boarding pass is verified and the passenger is checked for prohibited items. +- **Gate**: The passenger walks to the correct gate and waits there until boarding time. The gate takes then care of boarding the aircraft, while simultaneously loading baggage into the aircraft. + +Each step has a processing time and there are several waiting times in between. + +Aircrafts arrive at the gate on boarding time and leave to the runway on departure time according to the flight schedule. + +## Task + +Your task is to complete the implementation by going through the **TODO** statements in the files `baggageHandlingSystem.go`, `securityCheck.go`, and `gate.go`. All other files already contain an implementation. + +**Hint**: The expected behavior is defined in detail in the corresponding unit tests. + +**Hint**: Be aware of synchronization issues between the different goroutines for all actors. Use `channels` whenever applicable. See `checkin.go` for inspiration. + +## Example Output + +``` +Flight Number Destination Gate Boarding Time Departure Time +-------------------------------------------------------------------------------- +AF2062 ZAG 7 13:59:27 13:59:57 +BA8851 SOF 9 13:59:47 14:00:17 +SK4243 FRA 5 14:01:46 14:02:16 +KL5728 WAW 9 14:03:06 14:03:36 +SK8183 PRG 8 14:03:12 14:03:42 +AF8488 LIS 5 13:59:45 14:00:15 +KL5937 RIX 1 14:01:26 14:01:56 +AY1065 BRU 7 14:00:37 14:01:07 +BA1529 ZRH 5 14:02:03 14:02:33 +IB2405 IST 3 14:01:51 14:02:21 +What/Who arrives at airport? (p for passenger, c for car, t for train, q to quit): p +stoic_dijkstra has arrived at the airport for the flight number IB2405 to IST in 3.11 minutes with 2 bags and carry-on items: [Medicine Knife Sharpener] +stoic_dijkstra completed the checkin procedure and is now heading to security +stoic_dijkstra has successfully passed the security check and is now walking to the gate +elated_hellman has arrived at the airport for the flight number KL5728 to WAW in 4.19 minutes with 2 bags and carry-on items: [Explosives] +vibrant_maxwell has arrived at the airport for the flight number BA8851 to SOF in 0.88 minutes with 2 bags and carry-on items: [Keys Matches] +dazzling_lewin has arrived at the airport for the flight number AF2062 to ZAG in 0.54 minutes with 1 bags and carry-on items: [] +nifty_hermann has arrived at the airport for the flight number IB2405 to IST in 2.94 minutes with 1 bags and carry-on items: [Resistance Bands Gloves] +elated_hellman completed the checkin procedure and is now heading to security +vibrant_maxwell completed the checkin procedure and is now heading to security +elated_hellman: prohibited item detected: [Explosives] +flight AF2062 is ready for boarding +dazzling_lewin completed the checkin procedure and is now heading to security +vibrant_maxwell has successfully passed the security check and is now walking to the gate +nifty_hermann completed the checkin procedure and is now heading to security +dazzling_lewin has successfully passed the security check and is now walking to the gate +nifty_hermann has successfully passed the security check and is now walking to the gate +dazzling_lewin has successfully boarded flight AF2062 to ZAG +flight AF8488 is ready for boarding +flight BA8851 is ready for boarding +loaded baggage {[Goggles Fire Extinguisher Controller]} onto flight BA8851 +loaded baggage {[Book Clipboard]} onto flight BA8851 +vibrant_maxwell has successfully boarded flight BA8851 to SOF +boarding completed for flight number AF2062 +Flight AF2062 is waiting for takeoff +Flight AF2062 to ZAG is taking off +Flight AF2062 to ZAG has taken off with 1 passengers and 0 bags on board +Closing airport +The following baggage was lost today: [] +``` \ No newline at end of file diff --git a/go/06-airport/airport/aircraft.go b/go/06-airport/airport/aircraft.go new file mode 100644 index 0000000..6088c59 --- /dev/null +++ b/go/06-airport/airport/aircraft.go @@ -0,0 +1,39 @@ +package airport + +import ( + "fmt" +) + +type Aircraft struct { + Flight Flight + Passengers []Passenger + Baggage []Baggage +} + +var _ BaggageProcessor = &Aircraft{} + +func NewAircraft(flight Flight) Aircraft { + return Aircraft{flight, []Passenger{}, []Baggage{}} +} + +func (ac *Aircraft) ProcessBaggage(fn FlightNumber, b Baggage) error { + if fn != ac.Flight.Id { + return fmt.Errorf("wrong flight %s", fn) + } + ac.Baggage = append(ac.Baggage, b) + fmt.Printf("loaded baggage %v onto flight %v\n", b, fn) + return nil +} + +func (ac *Aircraft) Process(p Passenger) error { + if p.BoardingPass.FlightNumber != ac.Flight.Id { + return fmt.Errorf("%s is unauthorized to enter aircraft", p.Name) + } + ac.Passengers = append(ac.Passengers, p) + return nil +} + +func (ac *Aircraft) Start(gate *Gate, rwy *Runway) { + gate.ProcessAircraft(ac) + rwy.ProcessAircraft(*ac) +} diff --git a/go/06-airport/airport/airport.go b/go/06-airport/airport/airport.go new file mode 100644 index 0000000..22d1ed4 --- /dev/null +++ b/go/06-airport/airport/airport.go @@ -0,0 +1,62 @@ +package airport + +import ( + "time" +) + +type Airport struct { + Checkin Checkin + Security SecurityCheck + Gates map[GateNumber]*Gate + Runway Runway + Bhs *BaggageHandlingSystem + FlightSchedule FlightSchedule +} + +func DefaultAirport() Airport { + + ap := Airport{ + Security: NewSecurityCheck(time.Duration(1 * time.Second)), + Gates: NewGates(10), + Runway: NewRunway(time.Duration(3 * time.Second)), + } + + fs := GenerateRandomFlightSchedule(ap.Gates, 10) + ap.FlightSchedule = fs + + baggageSinks := make(map[FlightNumber](BaggageProcessor)) + for _, g := range ap.Gates { + + var flightsForGate []Flight + for _, f := range fs.AllFlights() { + if f.GateNumber == g.id { + flightsForGate = append(flightsForGate, f) + } + baggageSinks[f.Id] = g + } + + g.SetFlights(flightsForGate) + ap.Gates[g.id] = g + } + + bhs := NewBaggageHandlingSystem(time.Duration(1*time.Second), baggageSinks) + ap.Bhs = &bhs + ap.Checkin = NewCheckin(time.Duration(1*time.Second), &fs, &bhs) + + return ap +} + +func (a *Airport) Start() { + go a.Checkin.Start() + go a.Security.Start() + go a.Bhs.Start() + go a.Runway.Start() + + for _, g := range a.Gates { + go g.Start() + } +} + +func (a *Airport) NewPassenger() Passenger { + return NewRandomPassenger(a.FlightSchedule.AllFlights()) +} diff --git a/go/06-airport/airport/baggageHandlingSystem.go b/go/06-airport/airport/baggageHandlingSystem.go new file mode 100644 index 0000000..9c81002 --- /dev/null +++ b/go/06-airport/airport/baggageHandlingSystem.go @@ -0,0 +1,44 @@ +package airport + +import ( + "time" +) + +type BaggageProcessor interface { + ProcessBaggage(FlightNumber, Baggage) error +} + +type BaggageHandlingSystem struct { + processingTime time.Duration + sinks map[FlightNumber]BaggageProcessor + //TODO: extend +} + +type LostBaggage struct { + Baggage + FlightNumber FlightNumber + Err error +} + +var _ BaggageProcessor = &BaggageHandlingSystem{} + +func NewBaggageHandlingSystem(processingTime time.Duration, sinks map[FlightNumber]BaggageProcessor) BaggageHandlingSystem { + return BaggageHandlingSystem{ + processingTime: processingTime, + sinks: sinks, + } +} + +func (bhs *BaggageHandlingSystem) ProcessBaggage(fn FlightNumber, b Baggage) error { + //TODO: implement + return nil +} + +func (bhs *BaggageHandlingSystem) CollectLostBaggage() []LostBaggage { + //TODO: implement + return nil +} + +func (bhs *BaggageHandlingSystem) Start() { + //TODO: implement +} diff --git a/go/06-airport/airport/baggageHandlingSystem_test.go b/go/06-airport/airport/baggageHandlingSystem_test.go new file mode 100644 index 0000000..673ef56 --- /dev/null +++ b/go/06-airport/airport/baggageHandlingSystem_test.go @@ -0,0 +1,133 @@ +package airport_test + +import ( + "fmt" + "reflect" + "testing" + "time" + + "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport" +) + +type BaggageSpyData struct { + fn airport.FlightNumber + b airport.Baggage + t time.Time +} + +type BaggageProcessorSpy struct { + Baggage []BaggageSpyData + Err error +} + +func (bps *BaggageProcessorSpy) ProcessBaggage(fn airport.FlightNumber, b airport.Baggage) error { + bps.Baggage = append(bps.Baggage, BaggageSpyData{fn, b, time.Now()}) + return bps.Err +} + +var _ airport.BaggageProcessor = &BaggageProcessorSpy{} + +func compareSink(t *testing.T, expected []airport.Baggage, expectedFlightNumber airport.FlightNumber, got BaggageProcessorSpy, start time.Time) { + for _, b := range got.Baggage { + if elapsed := b.t.Sub(start); elapsed < 1*time.Millisecond { + t.Errorf("processing time too short: got %v, want at least 1ms", elapsed) + } + + if b.fn != expectedFlightNumber { + t.Errorf("expected flight number: %v, got: %v", expectedFlightNumber, b.fn) + } + } + + if len(got.Baggage) != len(expected) { + t.Errorf("expected number of baggage: %d, got: %d", len(expected), len(got.Baggage)) + } + + deliveredBaggage := []airport.Baggage{} + for _, b := range got.Baggage { + deliveredBaggage = append(deliveredBaggage, b.b) + } + if !reflect.DeepEqual(deliveredBaggage, expected) { + t.Errorf("expected delivered baggage for %v: %v, got: %v", expectedFlightNumber, expected, deliveredBaggage) + } + +} + +func TestBaggageHandlingSystem(t *testing.T) { + + sink123 := BaggageProcessorSpy{nil, nil} + sink456 := BaggageProcessorSpy{nil, fmt.Errorf("aircraft already gone")} + sinks := map[airport.FlightNumber]airport.BaggageProcessor{ + airport.FlightNumber("LH123"): &sink123, + airport.FlightNumber("LH456"): &sink456, + } + sut := airport.NewBaggageHandlingSystem(1*time.Millisecond, sinks) + go sut.Start() + + testCases := []struct { + name string + baggage airport.Baggage + fn airport.FlightNumber + expectErr bool + expectDeliveredBaggage123 []airport.Baggage + expectDeliveredBaggage456 []airport.Baggage + expectLostBaggage []airport.LostBaggage + }{ + { + name: "Valid baggage", + baggage: airport.Baggage{[]string{"Socks", "Shirts"}}, + fn: "LH123", + expectErr: false, + expectDeliveredBaggage123: []airport.Baggage{{[]string{"Socks", "Shirts"}}}, + expectDeliveredBaggage456: []airport.Baggage{}, + expectLostBaggage: []airport.LostBaggage{}, + }, + { + name: "Aircraft already gone", + baggage: airport.Baggage{[]string{"Socks", "Shirts"}}, + fn: "LH456", + expectErr: false, + expectDeliveredBaggage123: []airport.Baggage{}, + expectDeliveredBaggage456: []airport.Baggage{{[]string{"Socks", "Shirts"}}}, + expectLostBaggage: []airport.LostBaggage{ + { + airport.Baggage{[]string{"Socks", "Shirts"}}, + "LH456", + fmt.Errorf("aircraft already gone"), + }, + }, + }, + { + name: "Unknown flight", + baggage: airport.Baggage{[]string{"Socks", "Shirts"}}, + fn: "XX999", + expectErr: true, + expectDeliveredBaggage123: []airport.Baggage{}, + expectDeliveredBaggage456: []airport.Baggage{}, + expectLostBaggage: []airport.LostBaggage{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sink123.Baggage = []BaggageSpyData{} + sink456.Baggage = []BaggageSpyData{} + start := time.Now() + + err := sut.ProcessBaggage(tc.fn, tc.baggage) + + time.Sleep(10 * time.Millisecond) //not really threadsafe because the 10 milliseconds have never been guaranteed. TODO: replace by sync mechanism + + if (err != nil) != tc.expectErr { + t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil) + } + + compareSink(t, tc.expectDeliveredBaggage123, "LH123", sink123, start) + compareSink(t, tc.expectDeliveredBaggage456, "LH456", sink456, start) + + lostBaggage := sut.CollectLostBaggage() + if !reflect.DeepEqual(lostBaggage, tc.expectLostBaggage) { + t.Errorf("expected lost baggage: %v, got: %v", tc.expectLostBaggage, lostBaggage) + } + }) + } +} diff --git a/go/06-airport/airport/checkin.go b/go/06-airport/airport/checkin.go new file mode 100644 index 0000000..c3abf3a --- /dev/null +++ b/go/06-airport/airport/checkin.go @@ -0,0 +1,72 @@ +package airport + +import ( + "errors" + "fmt" + "time" +) + +type Checkin struct { + processingTime time.Duration + queue chan passengerCheckinData + flightProvider FlightProvider + baggageProcessor BaggageProcessor +} + +type passengerCheckinData struct { + p *Passenger + err chan error +} + +func NewCheckin(processingTime time.Duration, fp FlightProvider, bp BaggageProcessor) Checkin { + return Checkin{ + processingTime: processingTime, + queue: make(chan passengerCheckinData), + flightProvider: fp, + baggageProcessor: bp, + } +} + +func (c Checkin) Process(p *Passenger) error { + err := make(chan error) + c.queue <- passengerCheckinData{p: p, err: err} + return <-err +} + +func (c Checkin) Start() { + defer fmt.Println("checkin terminated") + for element := range c.queue { + + time.Sleep(c.processingTime) + + //1. check ticket + if element.p.Name != element.p.Ticket.Name { + element.err <- fmt.Errorf("name %s on ticket does not match", element.p.Ticket.Name) + continue + } + + flight, err := c.flightProvider.GetFlight(element.p.Ticket.FlightNumber) + if err != nil { + element.err <- err + continue + } + + //2. issue boarding pass + element.p.BoardingPass = &BoardingPass{element.p.Ticket, flight.GateNumber} + + //3. check in bags + var baggageErr error + for _, bag := range element.p.Bags { + if err := c.baggageProcessor.ProcessBaggage(flight.Id, bag); err != nil { + baggageErr = errors.Join(baggageErr, err) + } + } + if baggageErr != nil { + element.err <- baggageErr + continue + } + element.p.Bags = []Baggage{} + + element.err <- nil + } +} diff --git a/go/06-airport/airport/checkin_test.go b/go/06-airport/airport/checkin_test.go new file mode 100644 index 0000000..2c3488a --- /dev/null +++ b/go/06-airport/airport/checkin_test.go @@ -0,0 +1,116 @@ +package airport_test + +import ( + "fmt" + "reflect" + "testing" + "time" + + "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport" +) + +type FlightProviderStub struct { +} + +func (fps FlightProviderStub) GetFlight(fn airport.FlightNumber) (airport.Flight, error) { + if fn == "LH123" { + return airport.Flight{"LH123", "FRA", airport.GateNumber(1), time.Now(), time.Now()}, nil + } + return airport.Flight{}, fmt.Errorf("unknown flight %v", fn) +} + +var _ airport.FlightProvider = FlightProviderStub{} + +func TestCheckin(t *testing.T) { + + fp := FlightProviderStub{} + bps := BaggageProcessorSpy{} + sut := airport.NewCheckin(1*time.Millisecond, &fp, &bps) + go sut.Start() + + testCases := []struct { + name string + passenger airport.Passenger + expectErr bool + expectBaggage []airport.Baggage + expectBoardingpass *airport.BoardingPass + expectBPSBaggage []airport.Baggage + }{ + { + name: "Valid passenger with valid flight", + passenger: airport.Passenger{ + "Max Mustermann", + airport.Ticket{"Max Mustermann", "FRA", "LH123"}, + nil, + []airport.Baggage{{[]string{"Socks", "Shirts"}}}, + []string{}, + }, + expectErr: false, + expectBaggage: []airport.Baggage{}, + expectBoardingpass: &airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)}, + expectBPSBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}}, + }, + { + name: "Passenger with unknown flight", + passenger: airport.Passenger{ + "John Doe", + airport.Ticket{"John Doe", "MUC", "LH999"}, + nil, + []airport.Baggage{{[]string{"Socks", "Shirts"}}}, + []string{}, + }, + expectErr: true, + expectBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}}, + expectBoardingpass: nil, + expectBPSBaggage: []airport.Baggage{}, + }, + { + name: "Passenger with wrong ticket", + passenger: airport.Passenger{ + "Max Mustermann", + airport.Ticket{"Someone else", "FRA", "LH123"}, + nil, + []airport.Baggage{{[]string{"Socks", "Shirts"}}}, + []string{}, + }, + expectErr: true, + expectBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}}, + expectBoardingpass: nil, + expectBPSBaggage: []airport.Baggage{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bps.Baggage = []BaggageSpyData{} + + start := time.Now() + err := sut.Process(&tc.passenger) + + if elapsed := time.Since(start); elapsed < 1*time.Millisecond { + t.Errorf("processing time too short: got %v, want at least 1ms", elapsed) + } + + if (err != nil) != tc.expectErr { + t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil) + } + + if !reflect.DeepEqual(tc.passenger.BoardingPass, tc.expectBoardingpass) { + t.Errorf("expected boarding pass: %v, got: %v", tc.expectBoardingpass, tc.passenger.BoardingPass) + } + + if !reflect.DeepEqual(tc.passenger.Bags, tc.expectBaggage) { + t.Errorf("expected baggage: %v, got: %v", tc.expectBaggage, tc.passenger.Bags) + } + + gotBPSBaggage := []airport.Baggage{} + for _, b := range bps.Baggage { + gotBPSBaggage = append(gotBPSBaggage, b.b) + } + + if !reflect.DeepEqual(gotBPSBaggage, tc.expectBPSBaggage) { + t.Errorf("expected baggage processor baggage: %v, got: %v", tc.expectBPSBaggage, gotBPSBaggage) + } + }) + } +} diff --git a/go/06-airport/airport/flightSchedule.go b/go/06-airport/airport/flightSchedule.go new file mode 100644 index 0000000..30cb6b1 --- /dev/null +++ b/go/06-airport/airport/flightSchedule.go @@ -0,0 +1,104 @@ +package airport + +import ( + "fmt" + "math/rand" + "strings" + "time" +) + +type FlightNumber string + +type Flight struct { + Id FlightNumber + Destination string + GateNumber GateNumber + BoardingTime time.Time + DepartureTime time.Time +} + +type FlightProvider interface { + GetFlight(FlightNumber) (Flight, error) +} + +type FlightSchedule struct { + flights map[FlightNumber]Flight +} + +var possibleDestinations = []string{ + "AMS", "ATH", "BCN", "BRU", "BUD", "CPH", "DUB", "DUS", "EDI", "FCO", + "FRA", "GVA", "HEL", "IST", "LIS", "LYS", "MAD", "MUC", "OSL", "PRG", + "RIX", "SOF", "STO", "TLL", "VIE", "WAW", "ZAG", "ZRH", "LUX", "MLA", +} + +func GenerateRandomFlightNumbers(count int) []FlightNumber { + + airlines := []string{"LH", "AF", "KL", "BA", "SK", "AZ", "IB", "TK", "AY", "OS"} + flightNumbers := make([]FlightNumber, count) + + for i := 0; i < count; i++ { + airline := airlines[rand.Intn(len(airlines))] + number := rand.Intn(9000) + 1000 // Random 4-digit number + flightNumbers[i] = FlightNumber(fmt.Sprintf("%s%04d", airline, number)) + } + + return flightNumbers +} + +func GenerateRandomFlightSchedule(gates map[GateNumber]*Gate, count int) FlightSchedule { + flightNumbers := GenerateRandomFlightNumbers(count) + flights := make(map[FlightNumber]Flight) + + gateNumbers := make([]GateNumber, 0, len(gates)) + for gn, _ := range gates { + gateNumbers = append(gateNumbers, gn) + } + + for _, flightNumber := range flightNumbers { + destination := possibleDestinations[rand.Intn(len(possibleDestinations))] + + gateNumber := gateNumbers[rand.Intn(len(gateNumbers))] + departureTime := time.Now().Add(time.Duration(rand.Intn(5*60)) * time.Second) + boardingTime := departureTime.Add(-30 * time.Second) + + flights[flightNumber] = Flight{ + Id: flightNumber, + Destination: destination, + GateNumber: gateNumber, + BoardingTime: boardingTime, + DepartureTime: departureTime, + } + } + + return FlightSchedule{flights: flights} +} + +func (fs *FlightSchedule) AllFlights() []Flight { + flights := make([]Flight, 0, len(fs.flights)) + for _, flight := range fs.flights { + flights = append(flights, flight) + } + return flights +} + +func (fs *FlightSchedule) GetFlight(flightNumber FlightNumber) (Flight, error) { + if flight, ok := fs.flights[flightNumber]; ok { + return flight, nil + } else { + return Flight{}, fmt.Errorf("invalid flight number %v", flightNumber) + } +} + +func (fs *FlightSchedule) Print() { + fmt.Printf("%-15s %-15s %-10s %-20s %-20s\n", "Flight Number", "Destination", "Gate", "Boarding Time", "Departure Time") + fmt.Println(strings.Repeat("-", 80)) + for _, flight := range fs.AllFlights() { + fmt.Printf("%-15s %-15s %-10d %-20s %-20s\n", + flight.Id, + flight.Destination, + flight.GateNumber, + flight.BoardingTime.Format("15:04:05"), + flight.DepartureTime.Format("15:04:05"), + ) + } +} diff --git a/go/06-airport/airport/gate.go b/go/06-airport/airport/gate.go new file mode 100644 index 0000000..d746d93 --- /dev/null +++ b/go/06-airport/airport/gate.go @@ -0,0 +1,63 @@ +package airport + +import ( + "math/rand" + "time" +) + +var distantFuture = time.Unix(1<<63-1, 0) // Max value for Unix timestamp + +type GateNumber int + +type Gate struct { + id GateNumber + walkingTimeFromSecurity time.Duration + taxiTimeToRunway time.Duration + //TODO: extend +} + +var _ BaggageProcessor = &Gate{} + +func NewGate(id GateNumber, walkingTimeFromSecurity time.Duration, taxiTimeToRunway time.Duration) Gate { + + return Gate{id, + walkingTimeFromSecurity, + taxiTimeToRunway, + } +} + +func NewGates(count int) map[GateNumber]*Gate { + gates := make(map[GateNumber]*Gate) + for i := range count { + walkingTime := time.Duration(5+rand.Intn(6)) * time.Second + taxiTime := time.Duration(5+rand.Intn(6)) * time.Second + + gate := NewGate(GateNumber(i+1), walkingTime, taxiTime) + gates[GateNumber(i+1)] = &gate + } + return gates +} + +func (g *Gate) SetFlights(flights []Flight) { + //TODO: implement +} + +// Blocks for the walking distance to the gate + the time until the passenger has boarded +func (g *Gate) Process(passenger Passenger) error { + //TODO: implement + return nil +} + +// Blocks until departure (includes taxi time to runway) +func (g *Gate) ProcessAircraft(ac *Aircraft) { + //TODO: implement +} + +func (g *Gate) ProcessBaggage(fn FlightNumber, b Baggage) error { + //TODO: implement + return nil +} + +func (g Gate) Start() { + //TODO: implement +} diff --git a/go/06-airport/airport/gate_test.go b/go/06-airport/airport/gate_test.go new file mode 100644 index 0000000..0e8f626 --- /dev/null +++ b/go/06-airport/airport/gate_test.go @@ -0,0 +1,112 @@ +package airport_test + +import ( + "reflect" + "sync" + "testing" + "time" + + "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport" +) + +func TestGateSingleFlight(t *testing.T) { + + sut := airport.NewGate(airport.GateNumber(25), time.Duration(3*time.Millisecond), time.Duration(1*time.Millisecond)) + flight := airport.Flight{"LH123", "FRA", 25, time.Now().Add(5 * time.Millisecond), time.Now().Add(10 * time.Millisecond)} + sut.SetFlights([]airport.Flight{ + flight, + }) + go sut.Start() + + start := time.Now() + wg := sync.WaitGroup{} + + //the aircraft + wg.Add(1) + ac := airport.NewAircraft(flight) + departureTime := time.Now() + go func() { sut.ProcessAircraft(&ac); departureTime = time.Now(); wg.Done() }() + + //the baggage + wg.Add(1) + baggage := airport.Baggage{[]string{"socks", "shirt"}} + var bag1err error + go func() { bag1err = sut.ProcessBaggage("LH123", baggage); wg.Done() }() + + //a passenger arriving on time + wg.Add(1) + ticket1 := airport.Ticket{"John Doe", "FRA", "LH123"} + pax1 := airport.Passenger{"John Doe", ticket1, &airport.BoardingPass{ticket1, 25}, []airport.Baggage{}, []string{}} + pax1boardingTime := time.Now() + var pax1err error + go func() { pax1err = sut.Process(pax1); pax1boardingTime = time.Now(); wg.Done() }() + + //another passenger arriving too late + wg.Add(1) + ticket2 := airport.Ticket{"Jane Doe", "FRA", "LH123"} + pax2 := airport.Passenger{"Jane Doe", ticket2, &airport.BoardingPass{ticket2, 25}, []airport.Baggage{}, []string{}} + var pax2err error + go func() { time.Sleep(8 * time.Millisecond); pax2err = sut.Process(pax2); wg.Done() }() + + //another passenger arriving sometime during boarding + wg.Add(1) + ticket3 := airport.Ticket{"Alex Smith", "FRA", "LH123"} + pax3 := airport.Passenger{"Alex Smith", ticket3, &airport.BoardingPass{ticket3, 25}, []airport.Baggage{}, []string{}} + pax3boardingTime := time.Now() + var pax3err error + go func() { + time.Sleep(6 * time.Millisecond) + pax3err = sut.Process(pax3) + pax3boardingTime = time.Now() + wg.Done() + }() + + //------------------------FROM HERE: checking ---------------------------------- + wg.Wait() + + //--------------------------check aircraft------------------- + if elapsed := departureTime.Sub(start); elapsed < 11*time.Millisecond { + t.Errorf("departure time too short: got %v, want at least 11ms", elapsed) + } + + expectedBoardedPax := []airport.Passenger{pax1, pax3} + if !reflect.DeepEqual(expectedBoardedPax, ac.Passengers) { + t.Errorf("boarded passengers mismatch: got %v, want %v", ac.Passengers, expectedBoardedPax) + } + + expectedBaggageOnboard := []airport.Baggage{baggage} + if !reflect.DeepEqual(expectedBaggageOnboard, ac.Baggage) { + t.Errorf("baggage on board mismatch: got %v, want %v", ac.Baggage, expectedBaggageOnboard) + } + + //----------------------------check baggage-------------------- + + if bag1err != nil { + t.Errorf("baggage encountered an error during processing: %v", bag1err) + } + + //--------------------------check passengers------------------- + + //boarding can only happen between boarding (5ms from start) and departure (10ms from start) + if elapsed := pax1boardingTime.Sub(start); elapsed < 5*time.Millisecond || elapsed > 10*time.Millisecond { + t.Errorf("passenger 1 boarding time out of range: got %v, want between 5ms and 10ms", elapsed) + } + + if pax1err != nil { + t.Errorf("passenger 1 encountered an error during boarding: %v", pax1err) + } + + if pax2err == nil { + t.Errorf("passenger 2 did not encounter an error during boarding, but was expected to") + } + + //boarding can only happen after boarding (5ms from start) + if elapsed := pax3boardingTime.Sub(start); elapsed < 5*time.Millisecond { + t.Errorf("passenger 3 boarding time out of range: got %v, want more than 5ms", elapsed) + } + + if pax3err != nil { + t.Errorf("passenger 3 encountered an error during boarding: %v", pax3err) + } + +} diff --git a/go/06-airport/airport/passenger.go b/go/06-airport/airport/passenger.go new file mode 100644 index 0000000..c38f78a --- /dev/null +++ b/go/06-airport/airport/passenger.go @@ -0,0 +1,128 @@ +package airport + +import ( + "fmt" + "math/rand" + "time" + + "github.com/docker/docker/pkg/namesgenerator" +) + +type Ticket struct { + Name string + Destination string + FlightNumber FlightNumber +} + +type BoardingPass struct { + Ticket + Gate GateNumber +} + +type Baggage struct { + Items []string +} + +type Passenger struct { + Name string + Ticket Ticket + BoardingPass *BoardingPass + Bags []Baggage + CarryOnItems []string +} + +var PossibleItems = []string{ + "Laptop", "Tablet", "Smartphone", "Headphones", "Charger", "Book", "Magazine", "Notebook", "Pen", "Pencil", "Carabiner", + "Water Bottle", "Snacks", "Sandwich", "Camera", "Tripod", "Gun", "Clothes", "Shoes", "Toothbrush", "Toothpaste", + "Shampoo", "Conditioner", "Soap", "Deodorant", "Perfume", "Makeup Kit", "Hairbrush", "Comb", "Razor", "Shaving Cream", + "First Aid Kit", "Medicine", "Umbrella", "Hat", "Sunglasses", "Scarf", "Gloves", "Wallet", "Passport", "Camping Gear", + "Keys", "Watch", "Jewelry", "Flashlight", "Power Bank", "Knife", "USB Drive", "External Hard Drive", "Gaming Console", "Controller", + "Board Game", "Puzzle", "Toy", "Explosives", "Stuffed Animal", "Blanket", "Pillow", "Sleeping Bag", "Towel", "Swimsuit", + "Goggles", "Flip Flops", "Backpack", "Handbag", "Suitcase", "Briefcase", "Binoculars", "Map", "Guidebook", "Notebook", + "Sketchpad", "Paints", "Brushes", "Musical Instrument", "Sheet Music", "Sports Equipment", "Yoga Mat", "Jump Rope", "Resistance Bands", + "Tent", "Cooking Utensils", "Portable Stove", "Food Containers", "Water Filter", "Fishing Rod", "Knife Sharpener", "Multi-tool", "Rope", + "Compass", "GPS Device", "Safety Vest", "Helmet", "Protective Gear", "Fire Extinguisher", "Matches", "Lighter", "Candles", "Notebook", + "Clipboard", "Calculator", "Documents", "Folders", "Envelopes", +} + +func NewRandomPassenger(flights []Flight) Passenger { + + // Generate random ticket details + name := namesgenerator.GetRandomName(0) + flight := flights[rand.Intn(len(flights))] + + ticket := Ticket{ + Name: name, + Destination: flight.Destination, + FlightNumber: flight.Id, + } + + // Generate random baggage + numBags := rand.Intn(3) // Random number of bags (0 to 2) + bags := make([]Baggage, numBags) + for i := 0; i < numBags; i++ { + numItems := rand.Intn(5) + 1 // Random number of items (1 to 5) + items := make([]string, numItems) + for j := 0; j < numItems; j++ { + items[j] = PossibleItems[rand.Intn(len(PossibleItems))] + } + bags[i] = Baggage{ + Items: items, + } + } + + // Generate random carry-on items + numCarryOnItems := rand.Intn(3) // Random number of carry-on items (0 to 2) + carryOnItems := make([]string, numCarryOnItems) + for i := 0; i < numCarryOnItems; i++ { + carryOnItems[i] = PossibleItems[rand.Intn(len(PossibleItems))] + } + + return Passenger{ + Name: name, + Ticket: ticket, + BoardingPass: nil, + Bags: bags, + CarryOnItems: carryOnItems, + } +} + +func (p *Passenger) Start(ap *Airport) { + + fmt.Printf("%s has arrived at the airport for the flight number %s to %s in %.02f minutes with %d bags and carry-on items: %v\n", + p.Name, + p.Ticket.FlightNumber, + p.Ticket.Destination, + time.Until(ap.FlightSchedule.flights[p.Ticket.FlightNumber].DepartureTime).Minutes(), + len(p.Bags), + p.CarryOnItems) + + err := ap.Checkin.Process(p) + if err != nil { + fmt.Printf("\033[31m%s: %v\033[0m\n", p.Name, err) + return + } + fmt.Printf("%s completed the checkin procedure and is now heading to security\n", p.Name) + + err = ap.Security.Process(*p) + if err != nil { + fmt.Printf("\033[31m%s: %v\033[0m\n", p.Name, err) + return + } + + fmt.Printf("%s has successfully passed the security check and is now walking to the gate\n", p.Name) + + gate, ok := ap.Gates[p.BoardingPass.Gate] + if !ok { + fmt.Printf("\033[31m%s: invalid gate number %v\033[0m\n", p.Name, p.BoardingPass.Gate) + return + } + + err = gate.Process(*p) + if err != nil { + fmt.Printf("\033[31mError: %s: %v\033[0m\n", p.Name, err) + return + } + + fmt.Printf("\033[32m%s has successfully boarded flight %s to %s\033[0m\n", p.Name, p.Ticket.FlightNumber, p.Ticket.Destination) +} diff --git a/go/06-airport/airport/runway.go b/go/06-airport/airport/runway.go new file mode 100644 index 0000000..f96b6de --- /dev/null +++ b/go/06-airport/airport/runway.go @@ -0,0 +1,33 @@ +package airport + +import ( + "fmt" + "time" +) + +type Runway struct { + takeoffDuration time.Duration + queue chan Aircraft +} + +func NewRunway(takeoffDuration time.Duration) Runway { + return Runway{ + takeoffDuration: takeoffDuration, + queue: make(chan Aircraft), + } +} + +func (r *Runway) Start() { + defer fmt.Println("runway terminated") + + for ac := range r.queue { + fmt.Printf("Flight %v to %v is taking off\n", ac.Flight.Id, ac.Flight.Destination) + time.Sleep(r.takeoffDuration) + fmt.Printf("\033[32mFlight %v to %v has taken off with %v passengers and %v bags on board\033[0m\n", ac.Flight.Id, ac.Flight.Destination, int(len(ac.Passengers)), int(len(ac.Baggage))) + } +} + +func (r *Runway) ProcessAircraft(ac Aircraft) { + fmt.Printf("Flight %v is waiting for takeoff\n", ac.Flight.Id) + r.queue <- ac +} diff --git a/go/06-airport/airport/securityCheck.go b/go/06-airport/airport/securityCheck.go new file mode 100644 index 0000000..85ff4ad --- /dev/null +++ b/go/06-airport/airport/securityCheck.go @@ -0,0 +1,32 @@ +package airport + +import ( + "time" +) + +var prohibitedItems = map[string]bool{"Knife": true, "Gun": true, "Explosives": true} + +type SecurityCheck struct { + processingTime time.Duration + //TODO: extend +} + +func NewSecurityCheck(processingTime time.Duration) SecurityCheck { + return SecurityCheck{ + processingTime: processingTime, + } +} + +// processes one passenger at a time. Each passenger must at least spend the processingTime at the security check +func (sc *SecurityCheck) Start() { + //TODO: implement +} + +// returns an error if +// - the passenger has no boarding pass +// - the passenger's boardings pass does not match the name +// - the passenger carries a prohibited item +func (sc SecurityCheck) Process(p Passenger) error { + //TODO: implement + return nil +} diff --git a/go/06-airport/airport/securityCheck_test.go b/go/06-airport/airport/securityCheck_test.go new file mode 100644 index 0000000..31b8029 --- /dev/null +++ b/go/06-airport/airport/securityCheck_test.go @@ -0,0 +1,81 @@ +package airport_test + +import ( + "testing" + "time" + + "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport" +) + +func TestSecurityCheck(t *testing.T) { + + sut := airport.NewSecurityCheck(1 * time.Millisecond) + go sut.Start() + + testCases := []struct { + name string + passenger airport.Passenger + expectErr bool + }{ + { + name: "Valid passenger", + passenger: airport.Passenger{ + "Max Mustermann", + airport.Ticket{"Max Mustermann", "FRA", "LH123"}, + &airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)}, + []airport.Baggage{}, + []string{"Laptop", "Hat"}, + }, + expectErr: false, + }, + { + name: "Passenger without boarding pass", + passenger: airport.Passenger{ + "Max Mustermann", + airport.Ticket{"Max Mustermann", "FRA", "LH123"}, + nil, + []airport.Baggage{}, + []string{"Laptop", "Hat"}, + }, + expectErr: true, + }, + { + name: "Passenger with wrong boarding pass", + passenger: airport.Passenger{ + "Max Mustermann", + airport.Ticket{"Max Mustermann", "FRA", "LH123"}, + &airport.BoardingPass{airport.Ticket{"Someone else", "FRA", "LH123"}, airport.GateNumber(1)}, + []airport.Baggage{}, + []string{"Laptop", "Hat"}, + }, + expectErr: true, + }, + { + name: "Passenger with prohibited item", + passenger: airport.Passenger{ + "Max Mustermann", + airport.Ticket{"Max Mustermann", "FRA", "LH123"}, + &airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)}, + []airport.Baggage{}, + []string{"Laptop", "Hat", "Knife"}, + }, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + start := time.Now() + err := sut.Process(tc.passenger) + + if elapsed := time.Since(start); elapsed < 1*time.Millisecond { + t.Errorf("processing time too short: got %v, want at least 1ms", elapsed) + } + + if (err != nil) != tc.expectErr { + t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil) + } + }) + } +} diff --git a/go/06-airport/go.mod b/go/06-airport/go.mod new file mode 100644 index 0000000..970693f --- /dev/null +++ b/go/06-airport/go.mod @@ -0,0 +1,5 @@ +module gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport + +go 1.25.0 + +require github.com/docker/docker v28.3.3+incompatible diff --git a/go/06-airport/go.sum b/go/06-airport/go.sum new file mode 100644 index 0000000..f8039ef --- /dev/null +++ b/go/06-airport/go.sum @@ -0,0 +1,2 @@ +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= diff --git a/go/06-airport/main.go b/go/06-airport/main.go new file mode 100644 index 0000000..39d0587 --- /dev/null +++ b/go/06-airport/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport" +) + +func main() { + ap := airport.DefaultAirport() + + ap.FlightSchedule.Print() + defer func() { + lostBaggage := ap.Bhs.CollectLostBaggage() + fmt.Println("The following baggage was lost today: ", lostBaggage) + }() + + ap.Start() + + for _, flight := range ap.FlightSchedule.AllFlights() { + ac := airport.NewAircraft(flight) + gate := ap.Gates[flight.GateNumber] + go ac.Start(gate, &ap.Runway) + } + + reader := bufio.NewReader(os.Stdin) + + fmt.Print("What/Who arrives at airport? (p for passenger, c for car, t for train, q to quit): ") + for { + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + + switch input { + case "q": + fmt.Println("Closing airport") + return + case "p": + p := ap.NewPassenger() + go p.Start(&ap) + case "c": + for i := 0; i < 4; i++ { + p := ap.NewPassenger() + go p.Start(&ap) + } + case "t": + for i := 0; i < 50; i++ { + p := ap.NewPassenger() + go p.Start(&ap) + } + default: + fmt.Println("Invalid command.") + } + } +}