push Project

main
Obai Albek 2025-09-04 14:58:00 +02:00
commit 7877b7262a
16 changed files with 1285 additions and 0 deletions

25
README.md 100644
View File

@ -0,0 +1,25 @@
# Flughafen-Simulation ✈️
Dieses Projekt ist eine **Flughafensimulation in Go**, die zentrale Prozesse eines Flughafens modelliert.
## Inhalte
- **Check-in** von Passagieren
- **Gepäckabfertigung**
- **Sicherheitskontrolle**
- **Boarding am Gate**
- **Start- und Landebahn-Management**
## Technische Umsetzung
- Implementiert in **Go**
- Nutzung von **Goroutines** und **Channels** zur parallelen Verarbeitung
- **Unit-Tests** zur Überprüfung der Kernfunktionen
## Projektstruktur
- `main.go` → Einstiegspunkt der Simulation
- `checkin/` → Prozesse am Check-in-Schalter
- `baggageHandling/` → Gepäckabfertigung
- `securityCheck/` → Sicherheitskontrolle
- `runway/` → Start- und Landebahn-Logik
- `tests/` → Unit-Tests

2
go.sum 100644
View File

@ -0,0 +1,2 @@
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=

57
main.go 100644
View File

@ -0,0 +1,57 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
"gitty.informatik.hs-mannheim.de/steger/pr3-ss2025/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.")
}
}
}

39
src/aircraft.go 100644
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)
}

62
src/airport.go 100644
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,61 @@
package airport
import (
"fmt"
"time"
)
type BaggageProcessor interface {
ProcessBaggage(FlightNumber, Baggage) error
}
type BaggageHandlingSystem struct {
processingTime time.Duration
sinks map[FlightNumber]BaggageProcessor
lostBaggage chan LostBaggage
}
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,
lostBaggage: make(chan LostBaggage),
}
}
func (bhs *BaggageHandlingSystem) ProcessBaggage(fn FlightNumber, b Baggage) error {
if sink, ok := bhs.sinks[fn]; ok {
go func() {
time.Sleep(bhs.processingTime)
if err := sink.ProcessBaggage(fn, b); err != nil {
bhs.lostBaggage <- LostBaggage{b, fn, err}
}
}()
} else {
return fmt.Errorf("invalid flight %v", fn)
}
return nil
}
func (bhs *BaggageHandlingSystem) CollectLostBaggage() []LostBaggage {
lostBaggage := []LostBaggage{}
for {
select {
case lb := <-bhs.lostBaggage:
lostBaggage = append(lostBaggage, lb)
default:
return lostBaggage
}
}
}
func (bhs *BaggageHandlingSystem) Start() {
}

View File

@ -0,0 +1,133 @@
package airport_test
import (
"fmt"
"reflect"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-ss2025/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)
}
})
}
}

72
src/checkin.go 100644
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
}
}

116
src/checkin_test.go 100644
View File

@ -0,0 +1,116 @@
package airport_test
import (
"fmt"
"reflect"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-ss2025/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"),
)
}
}

195
src/gate.go 100644
View File

@ -0,0 +1,195 @@
package airport
import (
"fmt"
"math/rand"
"sort"
"time"
)
var distantFuture = time.Unix(1<<63-1, 0) // Max value for Unix timestamp
type GateNumber int
type aircraftBoardingData struct {
ac *Aircraft
boardingCompleted chan bool
}
type passengerBoardingData struct {
passenger *Passenger
boardingError chan error
}
type baggageLoadingData struct {
Baggage
fn FlightNumber
err chan error
}
type Gate struct {
id GateNumber
walkingTimeFromSecurity time.Duration
taxiTimeToRunway time.Duration
sortedFlights []Flight
aircrafts chan aircraftBoardingData
passengers chan passengerBoardingData
baggage chan baggageLoadingData
}
var _ BaggageProcessor = &Gate{}
func NewGate(id GateNumber, walkingTimeFromSecurity time.Duration, taxiTimeToRunway time.Duration) Gate {
return Gate{id,
walkingTimeFromSecurity,
taxiTimeToRunway,
[]Flight{},
make(chan aircraftBoardingData, 100),
make(chan passengerBoardingData),
make(chan baggageLoadingData),
}
}
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) {
g.sortedFlights = make([]Flight, len(flights))
copy(g.sortedFlights, flights)
sort.Slice(g.sortedFlights, func(i, j int) bool {
return g.sortedFlights[i].BoardingTime.Before(g.sortedFlights[j].BoardingTime)
})
}
// Blocks for the walking distance to the gate + the time until the passenger has boarded
func (g *Gate) Process(passenger Passenger) error {
time.Sleep(g.walkingTimeFromSecurity)
err := make(chan error)
g.passengers <- passengerBoardingData{&passenger, err}
return <-err
}
// Blocks until departure (includes taxi time to runway)
func (g *Gate) ProcessAircraft(ac *Aircraft) {
done := make(chan bool)
g.aircrafts <- aircraftBoardingData{ac, done}
<-done
time.Sleep(g.taxiTimeToRunway)
}
func (g *Gate) ProcessBaggage(fn FlightNumber, b Baggage) error {
err := make(chan error)
g.baggage <- baggageLoadingData{b, fn, err}
return <-err
}
type FlightStatus int
const (
WAITING FlightStatus = iota
BOARDING
COMPLETED
)
func (g Gate) Start() {
defer fmt.Println("gate terminated")
type FlightData struct {
aircraftBoardingData
waitingPassengers []passengerBoardingData
waitingBaggage []baggageLoadingData
status FlightStatus
}
data := make(map[FlightNumber]FlightData)
currentFlight := 0
for {
var deadline time.Time = distantFuture
var flight *Flight = nil
if currentFlight < len(g.sortedFlights) {
flight = &g.sortedFlights[currentFlight]
switch data[flight.Id].status {
case WAITING:
deadline = flight.BoardingTime
case BOARDING:
deadline = flight.DepartureTime
}
}
select {
case boardingData := <-g.aircrafts:
flightNumber := boardingData.ac.Flight.Id
fd := data[flightNumber]
fd.aircraftBoardingData = boardingData
data[flightNumber] = fd
case passengerData := <-g.passengers:
flightData := data[passengerData.passenger.BoardingPass.FlightNumber]
switch flightData.status {
case WAITING:
flightData.waitingPassengers = append(flightData.waitingPassengers, passengerData)
case BOARDING:
passengerData.boardingError <- flightData.ac.Process(*passengerData.passenger)
case COMPLETED:
passengerData.boardingError <- fmt.Errorf("boarding not possible: flight %v already left", flightData.ac.Flight.Id)
}
data[passengerData.passenger.BoardingPass.FlightNumber] = flightData
case baggageData := <-g.baggage:
flightData := data[baggageData.fn]
switch flightData.status {
case WAITING:
flightData.waitingBaggage = append(flightData.waitingBaggage, baggageData)
case BOARDING:
baggageData.err <- flightData.ac.ProcessBaggage(baggageData.fn, baggageData.Baggage)
case COMPLETED:
baggageData.err <- fmt.Errorf("bag cannot be loaded: flight %v already left", baggageData.fn)
}
data[baggageData.fn] = flightData
//only applicable if there is a valid flight
case <-time.After(time.Until(deadline)):
if flight != nil {
flightData := data[flight.Id]
switch flightData.status {
case WAITING:
fmt.Printf("flight %v is ready for boarding\n", flight.Id)
for _, wp := range flightData.waitingPassengers {
wp.boardingError <- flightData.ac.Process(*wp.passenger)
}
flightData.waitingPassengers = nil
for _, wb := range flightData.waitingBaggage {
wb.err <- flightData.ac.ProcessBaggage(wb.fn, wb.Baggage)
}
flightData.waitingBaggage = nil
flightData.status = BOARDING
case BOARDING:
fmt.Printf("boarding completed for flight number %s\n", flight.Id)
flightData.status = COMPLETED
flightData.boardingCompleted <- true
currentFlight++
}
data[flight.Id] = flightData
}
}
}
}

112
src/gate_test.go 100644
View File

@ -0,0 +1,112 @@
package airport_test
import (
"reflect"
"sync"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-ss2025/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)
}
}

128
src/passenger.go 100644
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)
}

33
src/runway.go 100644
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,65 @@
package airport
import (
"fmt"
"time"
)
var prohibitedItems = map[string]bool{"Knife": true, "Gun": true, "Explosives": true}
type SecurityCheck struct {
processingTime time.Duration
queue chan securityCheckPassengerData
}
type securityCheckPassengerData struct {
p Passenger
err chan error
}
func NewSecurityCheck(processingTime time.Duration) SecurityCheck {
return SecurityCheck{
processingTime: processingTime,
queue: make(chan securityCheckPassengerData),
}
}
func (sc *SecurityCheck) Start() {
defer fmt.Println("security check terminated")
for element := range sc.queue {
time.Sleep(sc.processingTime)
//1. check boarding pass
if element.p.BoardingPass == nil {
element.err <- fmt.Errorf("no boarding pass. Go to checkin first")
continue
}
if element.p.BoardingPass.Name != element.p.Name {
element.err <- fmt.Errorf("name %s on ticket does not match", element.p.BoardingPass.Name)
continue
}
//2. check carry on items
detectedProhibitedItems := []string{}
for _, item := range element.p.CarryOnItems {
_, prohibited := prohibitedItems[item]
if prohibited {
detectedProhibitedItems = append(detectedProhibitedItems, item)
}
}
if len(detectedProhibitedItems) > 0 {
element.err <- fmt.Errorf("prohibited item detected: %v", detectedProhibitedItems)
continue
}
element.err <- nil
}
}
func (sc SecurityCheck) Process(p Passenger) error {
err := make(chan error)
sc.queue <- securityCheckPassengerData{p: p, err: err}
return <-err
}

View File

@ -0,0 +1,81 @@
package airport_test
import (
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-ss2025/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)
}
})
}
}