evcc-io/core/loadpoint_test.go
Andreas Linde 410f62316a
Allow chargers to provide vehicle SoC via ISO15118 (#1283)
Some chargers (e.g. Porsche/Audi Mobile Charger Connect) can provide the EVs current SoC via the ISO15118 connection to the car.

This change allows chargers to implement the SoC method and return the SoC or api.ErrNotAvailable if the currently connected car does not provide the data.
2021-08-14 13:51:26 +02:00

813 lines
21 KiB
Go

package core
import (
"testing"
"time"
"github.com/andig/evcc/api"
"github.com/andig/evcc/core/soc"
"github.com/andig/evcc/mock"
"github.com/andig/evcc/push"
"github.com/andig/evcc/util"
evbus "github.com/asaskevich/EventBus"
"github.com/benbjohnson/clock"
"github.com/golang/mock/gomock"
)
const (
minA float64 = 6
maxA float64 = 16
)
type Null struct{}
func (n *Null) CurrentPower() (float64, error) {
return 0, nil
}
func (n *Null) ChargedEnergy() (float64, error) {
return 0, nil
}
func (n *Null) ChargingTime() (time.Duration, error) {
return 0, nil
}
func attachListeners(t *testing.T, lp *LoadPoint) {
Voltage = 230 // V
uiChan := make(chan util.Param)
pushChan := make(chan push.Event)
lpChan := make(chan *LoadPoint)
log := false
go func() {
for {
select {
case v := <-uiChan:
if log {
t.Log(v)
}
case v := <-pushChan:
if log {
t.Log(v)
}
case v := <-lpChan:
if log {
t.Log(v)
}
}
}
}()
if charger, ok := lp.charger.(*mock.MockCharger); ok && charger != nil {
charger.EXPECT().Enabled().Return(true, nil)
charger.EXPECT().MaxCurrent(int64(lp.MinCurrent)).Return(nil)
}
lp.Prepare(uiChan, pushChan, lpChan)
}
func TestNew(t *testing.T) {
lp := NewLoadPoint(util.NewLogger("foo"))
if lp.Phases != 1 {
t.Errorf("Phases %v", lp.Phases)
}
if lp.MinCurrent != minA {
t.Errorf("MinCurrent %v", lp.MinCurrent)
}
if lp.MaxCurrent != maxA {
t.Errorf("MaxCurrent %v", lp.MaxCurrent)
}
if lp.status != api.StatusNone {
t.Errorf("status %v", lp.status)
}
if lp.charging() {
t.Errorf("charging %v", lp.charging())
}
}
func TestUpdatePowerZero(t *testing.T) {
tc := []struct {
status api.ChargeStatus
mode api.ChargeMode
expect func(h *mock.MockCharger)
}{
{api.StatusA, api.ModeOff, func(h *mock.MockCharger) {
h.EXPECT().Enable(false)
}},
{api.StatusA, api.ModeNow, func(h *mock.MockCharger) {
h.EXPECT().Enable(false)
}},
{api.StatusA, api.ModeMinPV, func(h *mock.MockCharger) {
h.EXPECT().Enable(false)
}},
{api.StatusA, api.ModePV, func(h *mock.MockCharger) {
h.EXPECT().Enable(false) // zero since update called with 0
}},
{api.StatusB, api.ModeOff, func(h *mock.MockCharger) {
h.EXPECT().Enable(false)
}},
{api.StatusB, api.ModeNow, func(h *mock.MockCharger) {
h.EXPECT().MaxCurrent(int64(maxA)) // true
}},
{api.StatusB, api.ModeMinPV, func(h *mock.MockCharger) {
// MaxCurrent omitted since identical value
}},
{api.StatusB, api.ModePV, func(h *mock.MockCharger) {
// zero since update called with 0
// force = false due to pv mode climater check
h.EXPECT().Enable(false)
}},
{api.StatusC, api.ModeOff, func(h *mock.MockCharger) {
h.EXPECT().Enable(false)
}},
{api.StatusC, api.ModeNow, func(h *mock.MockCharger) {
h.EXPECT().MaxCurrent(int64(maxA)) // true
}},
{api.StatusC, api.ModeMinPV, func(h *mock.MockCharger) {
// MaxCurrent omitted since identical value
}},
{api.StatusC, api.ModePV, func(h *mock.MockCharger) {
// omitted since PV balanced
}},
}
for _, tc := range tc {
t.Log(tc)
clck := clock.NewMock()
ctrl := gomock.NewController(t)
charger := mock.NewMockCharger(ctrl)
lp := &LoadPoint{
log: util.NewLogger("foo"),
bus: evbus.New(),
clock: clck,
charger: charger,
chargeMeter: &Null{}, // silence nil panics
chargeRater: &Null{}, // silence nil panics
chargeTimer: &Null{}, // silence nil panics
MinCurrent: minA,
MaxCurrent: maxA,
Phases: 1,
status: tc.status, // no status change
}
attachListeners(t, lp)
// initial status
charger.EXPECT().Status().Return(tc.status, nil)
charger.EXPECT().Enabled().Return(true, nil)
if tc.expect != nil {
tc.expect(charger)
}
lp.Mode = tc.mode
lp.Update(0, false) // sitePower 0
ctrl.Finish()
}
}
func TestPVHysteresis(t *testing.T) {
dt := time.Minute
type se struct {
site float64
delay time.Duration // test case delay since start
current float64
}
tc := []struct {
enabled bool
enable, disable float64
series []se
}{
// keep disabled
{false, 0, 0, []se{
{0, 0, 0},
{0, 1, 0},
{0, dt - 1, 0},
{0, dt + 1, 0},
}},
// enable when threshold not configured but min power met
{false, 0, 0, []se{
{-6 * 100 * 10, 0, 0},
{-6 * 100 * 10, 1, 0},
{-6 * 100 * 10, dt - 1, 0},
{-6 * 100 * 10, dt + 1, minA},
}},
// keep disabled when threshold not configured
{false, 0, 0, []se{
{-400, 0, 0},
{-400, 1, 0},
{-400, dt - 1, 0},
{-400, dt + 1, 0},
}},
// keep disabled when threshold (lower minCurrent) not met
{false, -500, 0, []se{
{-400, 0, 0},
{-400, 1, 0},
{-400, dt - 1, 0},
{-400, dt + 1, 0},
}},
// keep disabled when threshold (higher minCurrent) not met
{false, -7 * 100 * 10, 0, []se{
{-6 * 100 * 10, 0, 0},
{-6 * 100 * 10, 1, 0},
{-6 * 100 * 10, dt - 1, 0},
{-6 * 100 * 10, dt + 1, 0},
}},
// enable when threshold met
{false, -500, 0, []se{
{-500, 0, 0},
{-500, 1, 0},
{-500, dt - 1, 0},
{-500, dt + 1, minA},
}},
// keep enabled at max
{true, 500, 0, []se{
{-16 * 100 * 10, 0, maxA},
{-16 * 100 * 10, 1, maxA},
{-16 * 100 * 10, dt - 1, maxA},
{-16 * 100 * 10, dt + 1, maxA},
}},
// keep enabled at min
{true, 500, 0, []se{
{-6 * 100 * 10, 0, minA},
{-6 * 100 * 10, 1, minA},
{-6 * 100 * 10, dt - 1, minA},
{-6 * 100 * 10, dt + 1, minA},
}},
// keep enabled at min (negative threshold)
{true, 0, 500, []se{
{-500, 0, minA},
{-500, 1, minA},
{-500, dt - 1, minA},
{-500, dt + 1, minA},
}},
// disable when threshold met
{true, 0, 500, []se{
{500, 0, minA},
{500, 1, minA},
{500, dt - 1, minA},
{500, dt + 1, 0},
}},
// reset enable timer when threshold not met while timer active
{false, -500, 0, []se{
{-500, 0, 0},
{-500, 1, 0},
{-499, dt - 1, 0}, // should reset timer
{-500, dt + 1, 0}, // new begin of timer
{-500, 2*dt - 2, 0},
{-500, 2*dt - 1, minA},
}},
// reset enable timer when threshold not met while timer active and threshold not configured
{false, 0, 0, []se{
{-6*100*10 - 1, dt + 1, 0},
{-6 * 100 * 10, dt + 1, 0},
{-6 * 100 * 10, dt + 2, 0},
{-6 * 100 * 10, 2 * dt, 0},
{-6 * 100 * 10, 2*dt + 2, minA},
}},
// reset disable timer when threshold not met while timer active
{true, 0, 500, []se{
{500, 0, minA},
{500, 1, minA},
{499, dt - 1, minA}, // reset timer
{500, dt + 1, minA}, // within reset timer duration
{500, 2*dt - 2, minA}, // still within reset timer duration
{500, 2*dt - 1, 0}, // reset timer elapsed
}},
}
for _, status := range []api.ChargeStatus{api.StatusB, api.StatusC} {
for _, tc := range tc {
t.Log(tc)
clck := clock.NewMock()
ctrl := gomock.NewController(t)
charger := mock.NewMockCharger(ctrl)
Voltage = 100
lp := &LoadPoint{
log: util.NewLogger("foo"),
clock: clck,
charger: charger,
MinCurrent: minA,
MaxCurrent: maxA,
Phases: 10,
Enable: ThresholdConfig{
Threshold: tc.enable,
Delay: dt,
},
Disable: ThresholdConfig{
Threshold: tc.disable,
Delay: dt,
},
}
// charging, otherwise PV mode logic is short-circuited
lp.status = status
start := clck.Now()
for step, se := range tc.series {
clck.Set(start.Add(se.delay))
// maxCurrent will read actual current and enabled state in PV mode
// charger.EXPECT().Enabled().Return(tc.enabled, nil)
lp.enabled = tc.enabled
current := lp.pvMaxCurrent(api.ModePV, se.site)
if current != se.current {
t.Errorf("step %d: wanted %.1f, got %.1f", step, se.current, current)
}
}
ctrl.Finish()
}
}
}
func TestPVHysteresisForStatusOtherThanC(t *testing.T) {
clck := clock.NewMock()
ctrl := gomock.NewController(t)
Voltage = 100
lp := &LoadPoint{
log: util.NewLogger("foo"),
clock: clck,
MinCurrent: minA,
MaxCurrent: maxA,
Phases: 10,
}
// not connected, test PV mode logic short-circuited
lp.status = api.StatusA
// maxCurrent will read enabled state in PV mode
sitePower := -float64(lp.Phases)*minA*Voltage + 1 // 1W below min power
current := lp.pvMaxCurrent(api.ModePV, sitePower)
if current != 0 {
t.Errorf("PV mode could not disable charger as expected. Expected 0, got %.f", current)
}
ctrl.Finish()
}
func TestDisableAndEnableAtTargetSoC(t *testing.T) {
clock := clock.NewMock()
ctrl := gomock.NewController(t)
charger := mock.NewMockCharger(ctrl)
vehicle := mock.NewMockVehicle(ctrl)
// wrap vehicle with estimator
vehicle.EXPECT().Capacity().Return(int64(10))
socEstimator := soc.NewEstimator(util.NewLogger("foo"), charger, vehicle, false)
lp := &LoadPoint{
log: util.NewLogger("foo"),
bus: evbus.New(),
clock: clock,
charger: charger,
chargeMeter: &Null{}, // silence nil panics
chargeRater: &Null{}, // silence nil panics
chargeTimer: &Null{}, // silence nil panics
MinCurrent: minA,
MaxCurrent: maxA,
vehicle: vehicle, // needed for targetSoC check
socEstimator: socEstimator, // instead of vehicle: vehicle,
Mode: api.ModeNow,
SoC: SoCConfig{
Target: 90,
Poll: PollConfig{
Mode: pollConnected, // allow polling when connected
Interval: pollInterval,
},
},
}
attachListeners(t, lp)
lp.enabled = true
lp.chargeCurrent = float64(minA)
t.Log("charging below soc target")
vehicle.EXPECT().SoC().Return(85.0, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
lp.Update(500, false)
t.Log("charging above target - soc deactivates charger")
clock.Add(5 * time.Minute)
vehicle.EXPECT().SoC().Return(90.0, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Enable(false).Return(nil)
lp.Update(500, false)
t.Log("deactivated charger changes status to B")
clock.Add(5 * time.Minute)
vehicle.EXPECT().SoC().Return(95.0, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
lp.Update(-5000, false)
t.Log("soc has fallen below target - soc update prevented by timer")
clock.Add(5 * time.Minute)
charger.EXPECT().Status().Return(api.StatusB, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
lp.Update(-5000, false)
t.Log("soc has fallen below target - soc update timer expired")
clock.Add(pollInterval)
vehicle.EXPECT().SoC().Return(85.0, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Enable(true).Return(nil)
lp.Update(-5000, false)
ctrl.Finish()
}
func TestSetModeAndSocAtDisconnect(t *testing.T) {
clock := clock.NewMock()
ctrl := gomock.NewController(t)
charger := mock.NewMockCharger(ctrl)
lp := &LoadPoint{
log: util.NewLogger("foo"),
bus: evbus.New(),
clock: clock,
charger: charger,
chargeMeter: &Null{}, // silence nil panics
chargeRater: &Null{}, // silence nil panics
chargeTimer: &Null{}, // silence nil panics
MinCurrent: minA,
MaxCurrent: maxA,
status: api.StatusC,
OnDisconnect: struct {
Mode api.ChargeMode `mapstructure:"mode"` // Charge mode to apply when car disconnected
TargetSoC int `mapstructure:"targetSoC"` // Target SoC to apply when car disconnected
}{
Mode: api.ModeOff,
TargetSoC: 70,
},
}
attachListeners(t, lp)
lp.enabled = true
lp.chargeCurrent = float64(minA)
lp.Mode = api.ModeNow
t.Log("charging at min")
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
charger.EXPECT().MaxCurrent(int64(maxA)).Return(nil)
lp.Update(500, false)
t.Log("switch off when disconnected")
clock.Add(5 * time.Minute)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusA, nil)
charger.EXPECT().Enable(false).Return(nil)
lp.Update(-3000, false)
if lp.Mode != api.ModeOff {
t.Error("unexpected mode", lp.Mode)
}
ctrl.Finish()
}
// cacheExpecter can be used to verify asynchronously written values from cache
func cacheExpecter(t *testing.T, lp *LoadPoint) (*util.Cache, func(key string, val interface{})) {
// attach cache for verifying values
paramC := make(chan util.Param)
lp.uiChan = paramC
cache := util.NewCache()
go cache.Run(paramC)
expect := func(key string, val interface{}) {
p := cache.Get(key)
t.Logf("%s: %.f", key, p.Val) // REMOVE
if p.Val != val {
t.Errorf("%s wanted: %.0f, got %v", key, val, p.Val)
}
}
return cache, expect
}
func TestChargedEnergyAtDisconnect(t *testing.T) {
clock := clock.NewMock()
ctrl := gomock.NewController(t)
charger := mock.NewMockCharger(ctrl)
rater := mock.NewMockChargeRater(ctrl)
lp := &LoadPoint{
log: util.NewLogger("foo"),
bus: evbus.New(),
clock: clock,
charger: charger,
chargeMeter: &Null{}, // silence nil panics
chargeRater: rater,
chargeTimer: &Null{}, // silence nil panics
MinCurrent: minA,
MaxCurrent: maxA,
status: api.StatusC,
}
attachListeners(t, lp)
lp.enabled = true
lp.chargeCurrent = float64(maxA)
lp.Mode = api.ModeNow
// attach cache for verifying values
_, expectCache := cacheExpecter(t, lp)
t.Log("start charging at 0 kWh")
rater.EXPECT().ChargedEnergy().Return(0.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
lp.Update(-1, false)
t.Log("at 1:00h charging at 5 kWh")
clock.Add(time.Hour)
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
lp.Update(-1, false)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:00h stop charging at 5 kWh")
clock.Add(time.Second)
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
lp.Update(-1, false)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:00h restart charging at 5 kWh")
clock.Add(time.Second)
rater.EXPECT().ChargedEnergy().Return(5.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
lp.Update(-1, false)
expectCache("chargedEnergy", 5000.0)
t.Log("at 1:30h continue charging at 7.5 kWh")
clock.Add(30 * time.Minute)
rater.EXPECT().ChargedEnergy().Return(7.5, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusC, nil)
lp.Update(-1, false)
expectCache("chargedEnergy", 7500.0)
t.Log("at 2:00h stop charging at 10 kWh")
clock.Add(30 * time.Minute)
rater.EXPECT().ChargedEnergy().Return(10.0, nil)
charger.EXPECT().Enabled().Return(lp.enabled, nil)
charger.EXPECT().Status().Return(api.StatusB, nil)
lp.Update(-1, false)
expectCache("chargedEnergy", 10000.0)
ctrl.Finish()
}
func TestTargetSoC(t *testing.T) {
ctrl := gomock.NewController(t)
vhc := mock.NewMockVehicle(ctrl)
tc := []struct {
vehicle api.Vehicle
target int
soc float64
res bool
}{
{nil, 0, 0, false}, // never reached without vehicle
{nil, 0, 10, false}, // never reached without vehicle
{nil, 80, 0, false}, // never reached without vehicle
{nil, 80, 80, false}, // never reached without vehicle
{nil, 80, 100, false}, // never reached without vehicle
{vhc, 0, 0, false}, // target disabled
{vhc, 0, 10, false}, // target disabled
{vhc, 80, 0, false}, // target not reached
{vhc, 80, 80, true}, // target reached
{vhc, 80, 100, true}, // target reached
{vhc, 100, 100, false}, // target reached, let ev control deactivation
}
for _, tc := range tc {
t.Logf("%+v", tc)
lp := &LoadPoint{
vehicle: tc.vehicle,
SoC: SoCConfig{
Target: tc.target,
},
socCharge: tc.soc,
}
if res := lp.targetSocReached(); tc.res != res {
t.Errorf("expected %v, got %v", tc.res, res)
}
}
}
func TestSoCPoll(t *testing.T) {
clock := clock.NewMock()
tRefresh := pollInterval
tNoRefresh := pollInterval / 2
lp := &LoadPoint{
clock: clock,
log: util.NewLogger("foo"),
SoC: SoCConfig{
Poll: PollConfig{
Interval: time.Hour,
},
},
}
tc := []struct {
mode string
status api.ChargeStatus
dt time.Duration
res bool
}{
// pollCharging
{pollCharging, api.StatusA, -1, false},
{pollCharging, api.StatusA, 0, false},
{pollCharging, api.StatusA, tRefresh, false},
{pollCharging, api.StatusB, -1, true}, // poll once when car connected
{pollCharging, api.StatusB, 0, false},
{pollCharging, api.StatusB, tRefresh, false},
{pollCharging, api.StatusC, -1, true},
{pollCharging, api.StatusC, 0, true},
{pollCharging, api.StatusC, tNoRefresh, true}, // cached by vehicle
{pollCharging, api.StatusC, tRefresh, true},
// pollConnected
{pollConnected, api.StatusA, -1, false},
{pollConnected, api.StatusA, 0, false},
{pollConnected, api.StatusA, tRefresh, false},
{pollConnected, api.StatusB, -1, true},
{pollConnected, api.StatusB, 0, false},
{pollConnected, api.StatusB, tNoRefresh, false},
{pollConnected, api.StatusB, tRefresh, true},
{pollConnected, api.StatusC, -1, true},
{pollConnected, api.StatusC, 0, true},
{pollConnected, api.StatusC, tNoRefresh, true}, // cached by vehicle
{pollConnected, api.StatusC, tRefresh, true},
// pollAlways
{pollAlways, api.StatusA, -1, true},
{pollAlways, api.StatusA, 0, false},
{pollAlways, api.StatusA, tNoRefresh, false},
{pollAlways, api.StatusA, tRefresh, true},
{pollAlways, api.StatusB, -1, true},
{pollAlways, api.StatusB, 0, false},
{pollAlways, api.StatusB, tNoRefresh, false},
{pollAlways, api.StatusB, tRefresh, true},
{pollAlways, api.StatusC, -1, true},
{pollAlways, api.StatusC, 0, true},
{pollAlways, api.StatusC, tNoRefresh, true}, // cached by vehicle
{pollAlways, api.StatusC, tRefresh, true},
}
for _, tc := range tc {
t.Logf("%+v", tc)
if tc.dt < 0 {
lp.socUpdated = time.Time{}
} else {
clock.Add(tc.dt)
}
lp.SoC.Poll.Mode = tc.mode
lp.status = tc.status
res := lp.socPollAllowed()
if res {
// mimic update outside of socPollAllowed
lp.socUpdated = clock.Now()
}
if tc.res != res {
t.Errorf("expected %v, got %v", tc.res, res)
}
}
}
func TestMinSoC(t *testing.T) {
ctrl := gomock.NewController(t)
vhc := mock.NewMockVehicle(ctrl)
tc := []struct {
vehicle api.Vehicle
min int
soc float64
res bool
}{
{nil, 0, 0, false}, // never reached without vehicle
{nil, 0, 10, false}, // never reached without vehicle
{nil, 80, 0, false}, // never reached without vehicle
{nil, 80, 80, false}, // never reached without vehicle
{nil, 80, 100, false}, // never reached without vehicle
{vhc, 0, 0, false}, // min disabled
{vhc, 0, 10, false}, // min disabled
{vhc, 80, 0, true}, // min not reached
{vhc, 80, 80, false}, // min reached
{vhc, 80, 100, false}, // min reached
}
for _, tc := range tc {
t.Logf("%+v", tc)
lp := &LoadPoint{
vehicle: tc.vehicle,
SoC: SoCConfig{
Min: tc.min,
},
socCharge: tc.soc,
}
if res := lp.minSocNotReached(); tc.res != res {
t.Errorf("expected %v, got %v", tc.res, res)
}
}
}
func TestVehicleDetectByID(t *testing.T) {
ctrl := gomock.NewController(t)
v1 := mock.NewMockVehicle(ctrl)
v2 := mock.NewMockVehicle(ctrl)
type testcase struct {
string
id, i1, i2 string
res api.Vehicle
prepare func(testcase)
}
tc := []testcase{
{"1/_/_->0", "1", "", "", nil, func(tc testcase) {
v1.EXPECT().Identify().Return(tc.i1, nil)
v2.EXPECT().Identify().Return(tc.i2, nil)
v1.EXPECT().Identify().Return(tc.i1, nil)
v2.EXPECT().Identify().Return(tc.i2, nil)
}},
{"1/1/2->1", "1", "1", "2", v1, func(tc testcase) {
v1.EXPECT().Identify().Return(tc.i1, nil)
}},
{"2/1/2->2", "2", "1", "2", v2, func(tc testcase) {
v1.EXPECT().Identify().Return(tc.i1, nil)
v2.EXPECT().Identify().Return(tc.i2, nil)
}},
{"11/1*/2->1", "11", "1*", "2", v1, func(tc testcase) {
v1.EXPECT().Identify().Return(tc.i1, nil)
v2.EXPECT().Identify().Return(tc.i2, nil)
v1.EXPECT().Identify().Return(tc.i1, nil)
// v2.EXPECT().Identify().Return(tc.i2, nil)
}},
{"22/1*/2*->2", "22", "1*", "2*", v2, func(tc testcase) {
v1.EXPECT().Identify().Return(tc.i1, nil)
v2.EXPECT().Identify().Return(tc.i2, nil)
v1.EXPECT().Identify().Return(tc.i1, nil)
v2.EXPECT().Identify().Return(tc.i2, nil)
}},
{"2/_/*->2", "2", "", "*", v2, func(tc testcase) {
v1.EXPECT().Identify().Return(tc.i1, nil)
v2.EXPECT().Identify().Return(tc.i2, nil)
v1.EXPECT().Identify().Return(tc.i1, nil)
v2.EXPECT().Identify().Return(tc.i2, nil)
}},
}
for _, tc := range tc {
t.Logf("%+v", tc)
lp := &LoadPoint{
log: util.NewLogger("foo"),
vehicles: []api.Vehicle{v1, v2},
}
if tc.prepare != nil {
tc.prepare(tc)
}
if res := lp.selectVehicleByID(tc.id); tc.res != res {
t.Errorf("expected %v, got %v", tc.res, res)
}
}
}