push Project
commit
7877b7262a
|
@ -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
|
||||
|
|
@ -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=
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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() {
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue