From b707fa6cb9599f68a65e4d028bf9c2f404f70f31 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 4 Mar 2020 17:06:09 +0100 Subject: [PATCH] bug5154: added a first trivial test case for the reconciler --- .../analyzing/impl/AbortingFlagFinder.java | 1 - ...TracRaceAndCompetitorStatusReconciler.java | 78 +++++++++++++++++++ ...CompetitorStatusWithRaceLogReconciler.java | 69 +++++++++++++++- .../sailing/gwt/home/server/RaceContext.java | 6 +- .../src/com/sap/sse/common/Util.java | 9 +++ 5 files changed, 157 insertions(+), 6 deletions(-) create mode 100755 java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/AbortingFlagFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/AbortingFlagFinder.java index 4c35629749c..d298d043983 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/AbortingFlagFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/AbortingFlagFinder.java @@ -26,7 +26,6 @@ public class AbortingFlagFinder extends RaceLogAnalyzer { } } } - return null; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java new file mode 100755 index 00000000000..722b329d044 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/TestTracTracRaceAndCompetitorStatusReconciler.java @@ -0,0 +1,78 @@ +package com.sap.sailing.domain.tracking; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; +import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogPassChangeEventImpl; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.tractracadapter.DomainFactory; +import com.sap.sailing.domain.tractracadapter.impl.RaceAndCompetitorStatusWithRaceLogReconciler; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.tractrac.model.lib.api.event.IRace; +import com.tractrac.model.lib.api.event.RaceStatusType; + +/** + * See also bug 5154 and {@link RaceAndCompetitorStatusWithRaceLogReconciler}. + * + * @author Axel Uhl (D043530) + * + */ +public class TestTracTracRaceAndCompetitorStatusReconciler { + private AbstractLogEventAuthor author; + private TrackedRace trackedRace; + private IRace tractracRace; + private RaceLog raceLog; + private RaceAndCompetitorStatusWithRaceLogReconciler reconciler; + private TimePoint startOfPass; + + @Before + public void setUp() { + author = new LogEventAuthorImpl("me", 1); + startOfPass = MillisecondsTimePoint.now(); + tractracRace = mock(IRace.class); + trackedRace = mock(TrackedRace.class); + raceLog = new RaceLogImpl("RaceLogID"); + when(trackedRace.getAttachedRaceLogs()).thenReturn(Collections.singleton(raceLog)); + reconciler = new RaceAndCompetitorStatusWithRaceLogReconciler(DomainFactory.INSTANCE, new RaceLogResolver() { + @Override + public RaceLog resolve(SimpleRaceLogIdentifier identifier) { + return raceLog; + } + }); + raceLog.add(new RaceLogPassChangeEventImpl(startOfPass, author, /* pPassId */ 1)); + } + + @Test + public void testAbandonInFirstPass() { + assertNull(new AbortingFlagFinder(raceLog).analyze()); + when(tractracRace.getStatus()).thenReturn(RaceStatusType.ABANDONED); + final TimePoint abandonTimePoint = startOfPass.plus(Duration.ONE_MINUTE); + when(tractracRace.getStatusTime()).thenReturn(abandonTimePoint.asMillis()); + reconciler.reconcileRaceStatus(tractracRace, trackedRace); + final RaceLogFlagEvent abandonFlagEvent = new AbortingFlagFinder(raceLog).analyze(); + assertNotNull(abandonFlagEvent); + assertSame(Flags.NOVEMBER, abandonFlagEvent.getUpperFlag()); + assertTrue(abandonFlagEvent.isDisplayed()); + assertEquals(abandonTimePoint, abandonFlagEvent.getLogicalTimePoint()); + } +} diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceAndCompetitorStatusWithRaceLogReconciler.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceAndCompetitorStatusWithRaceLogReconciler.java index 8cda993f2db..daf53cd022b 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceAndCompetitorStatusWithRaceLogReconciler.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceAndCompetitorStatusWithRaceLogReconciler.java @@ -6,10 +6,13 @@ import java.util.stream.StreamSupport; import com.sap.sailing.domain.abstractlog.race.CompetitorResult; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sse.common.Util; @@ -38,11 +41,73 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler { this.raceLogResolver = raceLogResolver; } + /** + * The following race status types are currently available on the TracTrac side (see {@link RaceStatusType}): + * + * We are interested in status transitions that need to be reflected by an "N" ("November", abort), "AP" (answering + * pennant, postponement), or 1st substitute (general recall) flag status in the race log. The TracTrac-provided + * status transition has a time stamp on it (see {@link IRace#getStatusTime()}), and so would any aborting flag + * event in a {@link RaceLog} as well as any pass change event. If the TracTrac status time is later than the last + * race log-based aborting flag event from the current pass and the TracTrac status is none of {@code ABANDONED}, + * {@code GENERAL_RECALL} or {@code POSTPONED}, a new pass will be established in the race log. If the TracTrac + * status is any of {@code ABANDONED}, {@code GENERAL_RECALL} or {@code POSTPONED}, and the race log has not the + * matching aborting flag in the current pass, and the TracTrac status update time is later than the last race log + * status, the corresponding race log event that represents {@code ABANDONED}, {@code GENERAL_RECALL} or + * {@code POSTPONED}, respectively, will be appended to the {@link #getDefaultRaceLog(TrackedRace) default race + * log}. + *

+ * + * There is no API currently that allows us to determine the start mode flag. Manual intervention would be required + * if a non-default start mode flag is to be shown. + *

+ * + * If multiple race logs are attached, a "default" race log will be determined, e.g., based on the one that already + * has the most events in it. See {@link #getDefaultRaceLog}. Here go the cases: + *

+ */ public void reconcileRaceStatus(IRace tractracRace, TrackedRace trackedRace) { - final RaceStatusType status = tractracRace.getStatus(); + final RaceStatusType raceStatus = tractracRace.getStatus(); + final long raceStatusUpdateTime = tractracRace.getStatusTime(); + for (final RaceLog raceLog : trackedRace.getAttachedRaceLogs()) { + final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, raceLog); + final RaceLogRaceStatus raceLogRaceStatus = raceState.getStatus(); + final AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); + final RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); + + // TODO bug5154: + } // TODO bug5154 check IRace.getStatus() and somewhere update status once it reached RaceStatusType.OFFICIAL } + /** + * If no race log is attached to the {@code trackedRace}, {@code null} is returned. If exactly one race log + * is attached, it is returned. If multiple race logs are attached, the one with the most entries is returned. + */ + private RaceLog getDefaultRaceLog(TrackedRace trackedRace) { + final RaceLog result; + if (Util.isEmpty(trackedRace.getAttachedRaceLogs())) { + result = null; + } else { + result = Util.stream(trackedRace.getAttachedRaceLogs()).max((rl1, rl2)->Integer.compare(rl1.size(), rl2.size())).get(); + } + return result; + } + /** * If an official finish time exists on the {@link IRaceCompetitor} but not in any of the {@link TrackedRace}'s * {@link RaceLog}-based results, or the corresponding TracTrac information is newer than the race log-based result, @@ -60,7 +125,7 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler { */ public void reconcileCompetitorStatus(IRaceCompetitor raceCompetitor, TrackedRace trackedRace) { final Competitor competitor = domainFactory.resolveCompetitor(raceCompetitor.getCompetitor()); - final RaceCompetitorStatusType status = raceCompetitor.getStatus(); + final RaceCompetitorStatusType competitorStatus = raceCompetitor.getStatus(); final int officialRank = raceCompetitor.getOfficialRank(); final long officialFinishingTime = raceCompetitor.getOfficialFinishTime(); final long timePointForStatusEvent = raceCompetitor.getStatusTime(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java index e5d4083a6d2..e3341f02d87 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java @@ -550,13 +550,13 @@ public class RaceContext { RaceLogFlagEvent abortingFlagEvent = checkForAbortFlagEvent(); if (abortingFlagEvent != null) { Flags upperFlag = abortingFlagEvent.getUpperFlag(); - if(upperFlag.equals(Flags.AP)) { + if (upperFlag.equals(Flags.AP)) { return RaceViewState.POSTPONED; } - if(upperFlag.equals(Flags.NOVEMBER)) { + if (upperFlag.equals(Flags.NOVEMBER)) { return RaceViewState.ABANDONED; } - if(upperFlag.equals(Flags.FIRSTSUBSTITUTE)) { + if (upperFlag.equals(Flags.FIRSTSUBSTITUTE)) { return RaceViewState.ABANDONED; } } diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java index 34799d47302..de5d258d335 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java @@ -16,6 +16,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.sap.sse.common.util.MappingIterable; @@ -928,4 +929,12 @@ public class Util { c++; } } + + /** + * @return a non-parallel stream for the {@link Iterable} passed. Short for + * {@code StreamSupport.stream(iterable.spliterator(), false)}. + */ + public static Stream stream(Iterable iterable) { + return StreamSupport.stream(iterable.spliterator(), /* parallel */ false); + } } \ No newline at end of file