From 352c007a49b916a8626a4bae9d29a903176ed5f0 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 20 Jul 2016 11:07:25 +0200 Subject: [PATCH] a failing test case for bug3794, trying to push scores through the race log into the leaderboard's score corrections Change-Id: I35bc58c25f032e254932b157790557c6f07d3621 --- .../LeaderboardScoringAndRankingTest.java | 192 +-------------- .../LeaderboardScoringAndRankingTestBase.java | 233 ++++++++++++++++++ .../test/ApplyScoresFromRaceLogTest.java | 111 +++++++++ .../server/impl/RaceLogScoringReplicator.java | 4 +- 4 files changed, 349 insertions(+), 191 deletions(-) create mode 100644 java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTestBase.java create mode 100644 java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index ddb1fc8e9ec..75dc747ce44 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -55,7 +55,6 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall; @@ -70,7 +69,6 @@ import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; -import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndZeroRanks; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; @@ -82,10 +80,10 @@ import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.util.impl.ArrayListNavigableSet; -public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { - private ArrayList series; +public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRankingTestBase { + protected ArrayList series; - private Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) { + protected Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) { ThresholdBasedResultDiscardingRuleImpl discardingRules = new ThresholdBasedResultDiscardingRuleImpl(discardingThresholds); return new RegattaLeaderboardImpl(regatta, discardingRules); } @@ -1250,19 +1248,6 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { checkScoresAfterSomeRaces(leaderboard, raceColumnsToConsider, scoresAfter6Races, later, c); } - private void checkScoresAfterSomeRaces(Leaderboard leaderboard, List raceColumnsToConsider, - double[][] scoresAfterNRaces, TimePoint timePoint, Competitor[] competitors) throws NoWindException { - for (int competitorIndex=0; competitorIndex discardedRaceColumns = leaderboard.getResultDiscardingRule() - .getDiscardedRaceColumns(competitors[competitorIndex], leaderboard, raceColumnsToConsider, timePoint); - for (int raceColumnIndex=0; raceColumnIndex columnIter = theSeries.getRaceColumns().iterator(); - for (Competitor[] competitorList : competitorLists) { - RaceColumn raceColumn = columnIter.next(); - final TrackedRace trackedRace; - if (withScores) { - trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitorList)); - } else { - trackedRace = new MockedTrackedRaceWithStartTimeAndZeroRanks(now, Arrays.asList(competitorList)); - } - raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace); - } - return later; - } - - private void createAndAttachTrackedRacesWithStartTimeAndLastMarkPassingTimes( - Series theSeries, String fleetName, Competitor[][] competitorLists, TimePoint[] startTimes, - Map[] lastMarkPassingTimesForCompetitors) { - Iterator columnIter = theSeries.getRaceColumns().iterator(); - int i=0; - for (Competitor[] competitorList : competitorLists) { - RaceColumn raceColumn = columnIter.next(); - final Map lastMarkPassingTimes = lastMarkPassingTimesForCompetitors[i]; - final Waypoint start = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("Left StartBuoy"), new MarkImpl("Right StartBuoy"), "Start")); - final Waypoint finish = new WaypointImpl(new MarkImpl("FinishBuoy")); - TrackedRace trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(startTimes[i], Arrays.asList(competitorList)) { - private static final long serialVersionUID = 1L; - @Override - public NavigableSet getMarkPassings(Competitor competitor) { - ArrayListNavigableSet result = new ArrayListNavigableSet<>(new TimedComparator()); - result.add(new MarkPassingImpl(lastMarkPassingTimes.get(competitor), finish, competitor)); - return result; - } - }; - trackedRace.getRace().getCourse().addWaypoint(0, start); - trackedRace.getRace().getCourse().addWaypoint(1, finish); - raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace); - i++; - } - } - - private List createCompetitors(int numberOfCompetitorsToCreate) { - List result = new ArrayList(); - for (int i=1; i<=numberOfCompetitorsToCreate; i++) { - result.add(createCompetitor("C"+i)); - } - return result; - } - - private Regatta createSimpleRegatta(final int numberOfRaces, final String regattaName, BoatClass boatClass, ScoringScheme scoringScheme) { - series = new ArrayList(); - List fleets = new ArrayList(); - fleets.add(new FleetImpl("Default")); - - List raceColumnNames = new ArrayList(); - for (int i = 1; i <= numberOfRaces; i++) { - raceColumnNames.add("R" + i); - } - Series defaultSeries = new SeriesImpl("Default", /* isMedal */false, /* isFleetsCanRunInParallel */true, - fleets, raceColumnNames, /* trackedRegattaRegistry */null); - series.add(defaultSeries); - - Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass, - /* startDate */null, /* endDate */null, series, /* persistent */false, scoringScheme, "123", null, - OneDesignRankingMetric::new); - return regatta; - } - - private Regatta createRegatta(final int numberOfQualifyingRaces, String[] qualifyingFleetNames, final int numberOfFinalRaces, - String[] finalFleetNames, boolean medalRaceAndSeries, final int numberOfMedalRaces, final String regattaBaseName, BoatClass boatClass, ScoringScheme scoringScheme) { - series = new ArrayList(); - - // -------- qualifying series ------------ - if (qualifyingFleetNames != null && qualifyingFleetNames.length > 0) { - List qualifyingFleets = new ArrayList(); - for (String qualifyingFleetName : qualifyingFleetNames) { - qualifyingFleets.add(new FleetImpl(qualifyingFleetName)); - } - List qualifyingRaceColumnNames = new ArrayList(); - for (int i = 1; i <= numberOfQualifyingRaces; i++) { - qualifyingRaceColumnNames.add("Q" + i); - } - Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets, - qualifyingRaceColumnNames, /* trackedRegattaRegistry */null); - series.add(qualifyingSeries); - } - - // -------- final series ------------ - if (finalFleetNames != null && finalFleetNames.length > 0) { - List finalFleets = new ArrayList(); - int fleetOrdering = 1; - for (String finalFleetName : finalFleetNames) { - finalFleets.add(new FleetImpl(finalFleetName, fleetOrdering++)); - } - List finalRaceColumnNames = new ArrayList(); - for (int i = 1; i <= numberOfFinalRaces; i++) { - finalRaceColumnNames.add("F" + i); - } - Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); - series.add(finalSeries); - } - - if (medalRaceAndSeries) { - // ------------ medal -------------- - List medalFleets = new ArrayList(); - medalFleets.add(new FleetImpl("Medal")); - List medalRaceColumnNames = new ArrayList(); - if(numberOfMedalRaces == 1) { - medalRaceColumnNames.add("M"); - } else if(numberOfMedalRaces > 1) { - for (int i = 1; i <= numberOfMedalRaces; i++) { - medalRaceColumnNames.add("M" + i); - } - } - Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, /* isFleetsCanRunInParallel */ true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null); - series.add(medalSeries); - } - - Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, - /*startDate*/ null, /*endDate*/ null, series, /* persistent */ false, scoringScheme, "123", null, OneDesignRankingMetric::new); - return regatta; - } - - private Regatta createRegattaWithEliminations(final int numberOfEliminations, final int[] numbersOfHeatsPerRound, - final String regattaBaseName, BoatClass boatClass, ScoringScheme scoringScheme) { - series = new ArrayList(); - // example for numbersOfHeatsPerRound: [8, 4, 2, 2] - for (int elimination=1; elimination<=numberOfEliminations; elimination++) { - int heatNumber = 1; - // create one elimination consisting of a number of rounds, each consisting of a number of heats - int roundNumber = 1; - for (int numberOfHeatsPerRound : numbersOfHeatsPerRound) { - final boolean isFinalRound = roundNumber == numbersOfHeatsPerRound.length; - // create one round as a series that has one fleet per heat - List fleetsInRound = new ArrayList(); - for (int heatInRound=1; heatInRound<=numberOfHeatsPerRound; heatInRound++) { - final int ordering = numbersOfHeatsPerRound.length-roundNumber+1+ - // in final round distinguish Final and Losers Final - (isFinalRound ? heatInRound-1 : 1); - fleetsInRound.add(new FleetImpl("Heat "+(heatNumber++), ordering)); - } - List raceColumnNameForRound = new ArrayList(); - raceColumnNameForRound.add("E"+elimination+"R"+roundNumber); - final String roundName; - if (numbersOfHeatsPerRound.length-roundNumber == 2) { - roundName = "Quarter-Final"; - } else if (numbersOfHeatsPerRound.length-roundNumber == 1) { - roundName = "Semi-Final"; - } else if (numbersOfHeatsPerRound.length-roundNumber == 0) { - roundName = "Final"; - } else { - roundName = "Round "+roundNumber; - } - Series seriesForRound = new SeriesImpl("E"+elimination+" "+roundName, /* isMedal */ false, /* isFleetsCanRunInParallel */ true, fleetsInRound, - raceColumnNameForRound, /* trackedRegattaRegistry */null); - if (isFinalRound) { - // last "Final" round; here, the fleets are contiguously scored - seriesForRound.setSplitFleetContiguousScoring(true); - } - series.add(seriesForRound); - roundNumber++; - } - } - Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, - /*startDate*/ null, /*endDate*/ null, series, /* persistent */ false, scoringScheme, - /* ID */ "123", /* course area */ null, OneDesignRankingMetric::new); - return regatta; - } - /** * See bug 1260. There must be a possibility to have ordered fleets that are scored such that the winner of the race of the best fleet * gets the best score in that column; and the winner of the second-best fleet gets the n-th best score in the column with n being the diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTestBase.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTestBase.java new file mode 100644 index 00000000000..5d8c94e37d2 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTestBase.java @@ -0,0 +1,233 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Set; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.MarkImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndZeroRanks; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; +import com.sap.sailing.domain.tracking.impl.TimedComparator; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.util.impl.ArrayListNavigableSet; + +public class LeaderboardScoringAndRankingTestBase extends AbstractLeaderboardTest { + protected ArrayList series; + + protected Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) { + ThresholdBasedResultDiscardingRuleImpl discardingRules = new ThresholdBasedResultDiscardingRuleImpl(discardingThresholds); + return new RegattaLeaderboardImpl(regatta, discardingRules); + } + + protected void checkScoresAfterSomeRaces(Leaderboard leaderboard, List raceColumnsToConsider, + double[][] scoresAfterNRaces, TimePoint timePoint, Competitor[] competitors) throws NoWindException { + for (int competitorIndex=0; competitorIndex discardedRaceColumns = leaderboard.getResultDiscardingRule() + .getDiscardedRaceColumns(competitors[competitorIndex], leaderboard, raceColumnsToConsider, timePoint); + for (int raceColumnIndex=0; raceColumnIndex columnIter = theSeries.getRaceColumns().iterator(); + for (Competitor[] competitorList : competitorLists) { + RaceColumn raceColumn = columnIter.next(); + final TrackedRace trackedRace; + if (withScores) { + trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitorList)); + } else { + trackedRace = new MockedTrackedRaceWithStartTimeAndZeroRanks(now, Arrays.asList(competitorList)); + } + raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace); + } + return later; + } + + protected void createAndAttachTrackedRacesWithStartTimeAndLastMarkPassingTimes( + Series theSeries, String fleetName, Competitor[][] competitorLists, TimePoint[] startTimes, + Map[] lastMarkPassingTimesForCompetitors) { + Iterator columnIter = theSeries.getRaceColumns().iterator(); + int i=0; + for (Competitor[] competitorList : competitorLists) { + RaceColumn raceColumn = columnIter.next(); + final Map lastMarkPassingTimes = lastMarkPassingTimesForCompetitors[i]; + final Waypoint start = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("Left StartBuoy"), new MarkImpl("Right StartBuoy"), "Start")); + final Waypoint finish = new WaypointImpl(new MarkImpl("FinishBuoy")); + TrackedRace trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(startTimes[i], Arrays.asList(competitorList)) { + private static final long serialVersionUID = 1L; + @Override + public NavigableSet getMarkPassings(Competitor competitor) { + ArrayListNavigableSet result = new ArrayListNavigableSet<>(new TimedComparator()); + result.add(new MarkPassingImpl(lastMarkPassingTimes.get(competitor), finish, competitor)); + return result; + } + }; + trackedRace.getRace().getCourse().addWaypoint(0, start); + trackedRace.getRace().getCourse().addWaypoint(1, finish); + raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace); + i++; + } + } + + public static List createCompetitors(int numberOfCompetitorsToCreate) { + List result = new ArrayList(); + for (int i=1; i<=numberOfCompetitorsToCreate; i++) { + result.add(createCompetitor("C"+i)); + } + return result; + } + + protected Regatta createSimpleRegatta(final int numberOfRaces, final String regattaName, BoatClass boatClass, ScoringScheme scoringScheme) { + series = new ArrayList(); + List fleets = new ArrayList(); + fleets.add(new FleetImpl("Default")); + + List raceColumnNames = new ArrayList(); + for (int i = 1; i <= numberOfRaces; i++) { + raceColumnNames.add("R" + i); + } + Series defaultSeries = new SeriesImpl("Default", /* isMedal */false, /* isFleetsCanRunInParallel */true, + fleets, raceColumnNames, /* trackedRegattaRegistry */null); + series.add(defaultSeries); + + Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass, + /* startDate */null, /* endDate */null, series, /* persistent */false, scoringScheme, "123", null, + OneDesignRankingMetric::new); + return regatta; + } + + protected Regatta createRegatta(final int numberOfQualifyingRaces, String[] qualifyingFleetNames, final int numberOfFinalRaces, + String[] finalFleetNames, boolean medalRaceAndSeries, final int numberOfMedalRaces, final String regattaBaseName, BoatClass boatClass, ScoringScheme scoringScheme) { + series = new ArrayList(); + + // -------- qualifying series ------------ + if (qualifyingFleetNames != null && qualifyingFleetNames.length > 0) { + List qualifyingFleets = new ArrayList(); + for (String qualifyingFleetName : qualifyingFleetNames) { + qualifyingFleets.add(new FleetImpl(qualifyingFleetName)); + } + List qualifyingRaceColumnNames = new ArrayList(); + for (int i = 1; i <= numberOfQualifyingRaces; i++) { + qualifyingRaceColumnNames.add("Q" + i); + } + Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets, + qualifyingRaceColumnNames, /* trackedRegattaRegistry */null); + series.add(qualifyingSeries); + } + + // -------- final series ------------ + if (finalFleetNames != null && finalFleetNames.length > 0) { + List finalFleets = new ArrayList(); + int fleetOrdering = 1; + for (String finalFleetName : finalFleetNames) { + finalFleets.add(new FleetImpl(finalFleetName, fleetOrdering++)); + } + List finalRaceColumnNames = new ArrayList(); + for (int i = 1; i <= numberOfFinalRaces; i++) { + finalRaceColumnNames.add("F" + i); + } + Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); + series.add(finalSeries); + } + + if (medalRaceAndSeries) { + // ------------ medal -------------- + List medalFleets = new ArrayList(); + medalFleets.add(new FleetImpl("Medal")); + List medalRaceColumnNames = new ArrayList(); + if(numberOfMedalRaces == 1) { + medalRaceColumnNames.add("M"); + } else if(numberOfMedalRaces > 1) { + for (int i = 1; i <= numberOfMedalRaces; i++) { + medalRaceColumnNames.add("M" + i); + } + } + Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, /* isFleetsCanRunInParallel */ true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null); + series.add(medalSeries); + } + + Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, + /*startDate*/ null, /*endDate*/ null, series, /* persistent */ false, scoringScheme, "123", null, OneDesignRankingMetric::new); + return regatta; + } + + protected Regatta createRegattaWithEliminations(final int numberOfEliminations, final int[] numbersOfHeatsPerRound, + final String regattaBaseName, BoatClass boatClass, ScoringScheme scoringScheme) { + series = new ArrayList(); + // example for numbersOfHeatsPerRound: [8, 4, 2, 2] + for (int elimination=1; elimination<=numberOfEliminations; elimination++) { + int heatNumber = 1; + // create one elimination consisting of a number of rounds, each consisting of a number of heats + int roundNumber = 1; + for (int numberOfHeatsPerRound : numbersOfHeatsPerRound) { + final boolean isFinalRound = roundNumber == numbersOfHeatsPerRound.length; + // create one round as a series that has one fleet per heat + List fleetsInRound = new ArrayList(); + for (int heatInRound=1; heatInRound<=numberOfHeatsPerRound; heatInRound++) { + final int ordering = numbersOfHeatsPerRound.length-roundNumber+1+ + // in final round distinguish Final and Losers Final + (isFinalRound ? heatInRound-1 : 1); + fleetsInRound.add(new FleetImpl("Heat "+(heatNumber++), ordering)); + } + List raceColumnNameForRound = new ArrayList(); + raceColumnNameForRound.add("E"+elimination+"R"+roundNumber); + final String roundName; + if (numbersOfHeatsPerRound.length-roundNumber == 2) { + roundName = "Quarter-Final"; + } else if (numbersOfHeatsPerRound.length-roundNumber == 1) { + roundName = "Semi-Final"; + } else if (numbersOfHeatsPerRound.length-roundNumber == 0) { + roundName = "Final"; + } else { + roundName = "Round "+roundNumber; + } + Series seriesForRound = new SeriesImpl("E"+elimination+" "+roundName, /* isMedal */ false, /* isFleetsCanRunInParallel */ true, fleetsInRound, + raceColumnNameForRound, /* trackedRegattaRegistry */null); + if (isFinalRound) { + // last "Final" round; here, the fleets are contiguously scored + seriesForRound.setSplitFleetContiguousScoring(true); + } + series.add(seriesForRound); + roundNumber++; + } + } + Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, + /*startDate*/ null, /*endDate*/ null, series, /* persistent */ false, scoringScheme, + /* ID */ "123", /* course area */ null, OneDesignRankingMetric::new); + return regatta; + } +} diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java new file mode 100644 index 00000000000..8267c1c0f3d --- /dev/null +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java @@ -0,0 +1,111 @@ +package com.sap.sailing.server.test; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.junit.Test; + +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; +import com.sap.sailing.domain.abstractlog.race.CompetitorResults; +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl; +import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; +import com.sap.sailing.domain.abstractlog.race.state.RaceState; +import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl; +import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.RacingProcedureFactoryImpl; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.BoatImpl; +import com.sap.sailing.domain.base.impl.NationalityImpl; +import com.sap.sailing.domain.base.impl.PersonImpl; +import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.test.LeaderboardScoringAndRankingTestBase; +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sse.common.Color; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTestBase { + /** + * See also bug 3794: make sure that different variants of scores and max points reasons / penalties are applied to the + * leaderboard. + */ + @Test + public void testApplicationOfScoresFromRaceLog() throws NoWindException { + final RacingEventService service = new RacingEventServiceImpl(); + final List competitors = new ArrayList<>(); + for (int i=0; i<20; i++) { + final String competitorName = "C"+i; + competitors.add(service.getBaseDomainFactory().getCompetitorStore().getOrCreateCompetitor(UUID.randomUUID(), + competitorName, /* displayColor */ Color.RED, /* email */ null, /* flagImageURI */ null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + new BoatClassImpl("505", /* typicallyStartsUpwind */ true), /* sailID */ null), + /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null)); + } + final Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */1, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testOneStartedRaceWithDifferentScores", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace f1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors); + RaceColumn f1Column = series.get(1).getRaceColumnByName("F1"); + f1Column.setTrackedRace(f1Column.getFleets().iterator().next(), f1); + final Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + + service.addLeaderboard(leaderboard); // should add a RaceLogScoringReplicator as listener to the leaderboard + service.addRegattaWithoutReplication(regatta); + final Map scores = new HashMap<>(); + final Map mprs = new HashMap<>(); + int oneBasedRank = 1; + final CompetitorResults results = new CompetitorResultsImpl(); + for (final Competitor c : competitors) { + final MaxPointsReason mpr = new MaxPointsReason[] { null, MaxPointsReason.NONE, MaxPointsReason.DNF, MaxPointsReason.OCS }[oneBasedRank%4]; + final Double score = oneBasedRank%5 == 0 ? null : 20*Math.random(); + scores.put(c, score); + mprs.put(c, mpr); + results.add(new CompetitorResultImpl(c.getId(), c.getName(), + oneBasedRank++, mpr, score, /* finishingTime */ null, /* comment */ null)); + } + final RaceLog f1RaceLog = f1Column.getRaceLog(f1Column.getFleets().iterator().next()); + final LogEventAuthorImpl author = new LogEventAuthorImpl("Axel", 0); + final RaceState f1RaceState = new RaceStateImpl(service, f1RaceLog, author, + new RacingProcedureFactoryImpl(author, new EmptyRegattaConfiguration())); + f1RaceState.setFinishPositioningListChanged(now, results); + final List rankedCompetitorsBeforeApplying = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(competitors, rankedCompetitorsBeforeApplying); // no effects of preliminary results list yet + f1RaceState.setFinishPositioningConfirmed(now); + for (final Competitor c : competitors) { + assertEquals(scores.get(c)==null?competitors.indexOf(c)+1:scores.get(c), leaderboard.getTotalPoints(c, f1Column, now), 0.00000001); + } + final List expectedNewOrder = new ArrayList<>(competitors); + expectedNewOrder.sort((c1, c2)-> + new Double(scores.get(c1)==null?competitors.indexOf(c1)+1:scores.get(c1)).compareTo( + new Double(scores.get(c2)==null?competitors.indexOf(c2)+1:scores.get(c2)))); + final List rankedCompetitorsAfterApplying = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(expectedNewOrder, rankedCompetitorsAfterApplying); + } + + +} diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java index 1efadc59320..60f2dda1ee4 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java @@ -148,7 +148,7 @@ public class RaceLogScoringReplicator implements RaceColumnListener { if (positioningList != null) { for (CompetitorResult positionedCompetitor : positioningList) { Competitor competitor = service.getBaseDomainFactory().getExistingCompetitorById(positionedCompetitor.getCompetitorId()); - if (positionedCompetitor.getMaxPointsReason().equals(MaxPointsReason.NONE)) { + if (Util.equalsWithNull(positionedCompetitor.getMaxPointsReason(), MaxPointsReason.NONE)) { try { resetMaxPointsReasonIfNecessary(leaderboard, raceColumn, timePoint, competitor); int rankByRaceCommittee = getRankInPositioningListByRaceCommittee(positionedCompetitor); @@ -172,7 +172,7 @@ public class RaceLogScoringReplicator implements RaceColumnListener { boolean scoreHasBeenCorrected = false; MaxPointsReason trackedMaxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, timePoint); MaxPointsReason maxPointsReasonByRaceCommittee = positionedCompetitor.getMaxPointsReason(); - if (!maxPointsReasonByRaceCommittee.equals(trackedMaxPointsReason)) { + if (!Util.equalsWithNull(maxPointsReasonByRaceCommittee, trackedMaxPointsReason)) { applyMaxPointsReasonOperation(leaderboard, raceColumn, competitor, maxPointsReasonByRaceCommittee, timePoint); scoreHasBeenCorrected = true; }