2
1
Fork 0

airport starting point

main
Sebastian Steger 2025-08-20 11:50:05 +00:00
parent 2d1eec66ea
commit c3fed07a8f
17 changed files with 1146 additions and 0 deletions

View File

@ -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: []
```

View File

@ -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)
}

View File

@ -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())
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}
}

View File

@ -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)
}
})
}
}

View File

@ -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"),
)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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

View File

@ -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=

View File

@ -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.")
}
}
}