1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.recommendation; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertEquals; 22 23 import androidx.test.filters.SmallTest; 24 import androidx.test.runner.AndroidJUnit4; 25 26 import com.android.tv.data.ProgramImpl; 27 import com.android.tv.data.api.Program; 28 import com.android.tv.recommendation.RoutineWatchEvaluator.ProgramTime; 29 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 33 import java.util.Calendar; 34 import java.util.List; 35 import java.util.concurrent.TimeUnit; 36 37 /** Tests for {@link RoutineWatchEvaluator}. */ 38 @SmallTest 39 @RunWith(AndroidJUnit4.class) 40 public class RoutineWatchEvaluatorTest extends EvaluatorTestCase<RoutineWatchEvaluator> { 41 private static class ScoredItem implements Comparable<ScoredItem> { 42 private final String mBase; 43 private final String mText; 44 private final double mScore; 45 ScoredItem(String base, String text)46 private ScoredItem(String base, String text) { 47 this.mBase = base; 48 this.mText = text; 49 this.mScore = RoutineWatchEvaluator.calculateTitleMatchScore(base, text); 50 } 51 52 @Override compareTo(ScoredItem scoredItem)53 public int compareTo(ScoredItem scoredItem) { 54 return Double.compare(mScore, scoredItem.mScore); 55 } 56 57 @Override toString()58 public String toString() { 59 return mBase + " scored with " + mText + " is " + mScore; 60 } 61 } 62 score(String t1, String t2)63 private static ScoredItem score(String t1, String t2) { 64 return new ScoredItem(t1, t2); 65 } 66 67 @Override createEvaluator()68 public RoutineWatchEvaluator createEvaluator() { 69 return new RoutineWatchEvaluator(); 70 } 71 72 @Test testSplitTextToWords()73 public void testSplitTextToWords() { 74 assertThat(RoutineWatchEvaluator.splitTextToWords("")).containsExactly().inOrder(); 75 assertThat(RoutineWatchEvaluator.splitTextToWords("Google")) 76 .containsExactly("Google") 77 .inOrder(); 78 assertThat(RoutineWatchEvaluator.splitTextToWords("The Big Bang Theory")) 79 .containsExactly("The", "Big", "Bang", "Theory") 80 .inOrder(); 81 assertThat(RoutineWatchEvaluator.splitTextToWords("Hello, world!")) 82 .containsExactly("Hello", "world") 83 .inOrder(); 84 assertThat(RoutineWatchEvaluator.splitTextToWords("Adam's Rib")) 85 .containsExactly("Adam's", "Rib") 86 .inOrder(); 87 assertThat(RoutineWatchEvaluator.splitTextToWords("G.I. Joe")) 88 .containsExactly("G.I", "Joe") 89 .inOrder(); 90 assertThat(RoutineWatchEvaluator.splitTextToWords("A.I.")).containsExactly("A.I").inOrder(); 91 } 92 93 @Test testCalculateMaximumMatchedWordSequenceLength()94 public void testCalculateMaximumMatchedWordSequenceLength() { 95 assertMaximumMatchedWordSequenceLength(0, "", "Google"); 96 assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Big Bang"); 97 assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Theory Of Big Bang"); 98 assertMaximumMatchedWordSequenceLength(4, "The Big Bang Theory", "The Big Bang Theory"); 99 assertMaximumMatchedWordSequenceLength(1, "Modern Family", "Family Guy"); 100 assertMaximumMatchedWordSequenceLength(1, "The Simpsons", "The Walking Dead"); 101 assertMaximumMatchedWordSequenceLength(3, "Game Of Thrones 1", "Game Of Thrones 6"); 102 assertMaximumMatchedWordSequenceLength(0, "Dexter", "Friends"); 103 } 104 105 @Test testCalculateTitleMatchScore_empty()106 public void testCalculateTitleMatchScore_empty() { 107 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("", "")); 108 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", "")); 109 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("", "foo")); 110 } 111 112 @Test testCalculateTitleMatchScore_spaces()113 public void testCalculateTitleMatchScore_spaces() { 114 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(" ", " ")); 115 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", " ")); 116 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(" ", "foo")); 117 } 118 119 @Test testCalculateTitleMatchScore_null()120 public void testCalculateTitleMatchScore_null() { 121 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(null, null)); 122 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", null)); 123 assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(null, "foo")); 124 } 125 126 @Test testCalculateTitleMatchScore_longerMatchIsBetter()127 public void testCalculateTitleMatchScore_longerMatchIsBetter() { 128 String base = "foo bar baz"; 129 assertThat( 130 new ScoredItem[] { 131 score(base, ""), 132 score(base, "bar"), 133 score(base, "foo bar"), 134 score(base, "foo bar baz") 135 }) 136 .asList() 137 .isInOrder(); 138 } 139 140 @Test testProgramTime_createFromProgram()141 public void testProgramTime_createFromProgram() { 142 Calendar time = Calendar.getInstance(); 143 int todayDayOfWeek = time.get(Calendar.DAY_OF_WEEK); 144 // Value of DayOfWeek is between 1 and 7 (inclusive). 145 int tomorrowDayOfWeek = (todayDayOfWeek % 7) + 1; 146 147 // Today 00:00 - 01:00. 148 ProgramTime programTimeToday0000to0100 = 149 ProgramTime.createFromProgram( 150 createDummyProgram(todayAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1))); 151 assertProgramTime( 152 todayDayOfWeek, 153 hourMinuteToSec(0, 0), 154 hourMinuteToSec(1, 0), 155 programTimeToday0000to0100); 156 157 // Today 23:30 - 24:30. 158 ProgramTime programTimeToday2330to2430 = 159 ProgramTime.createFromProgram( 160 createDummyProgram(todayAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1))); 161 assertProgramTime( 162 todayDayOfWeek, 163 hourMinuteToSec(23, 30), 164 hourMinuteToSec(24, 30), 165 programTimeToday2330to2430); 166 167 // Tomorrow 00:00 - 01:00. 168 ProgramTime programTimeTomorrow0000to0100 = 169 ProgramTime.createFromProgram( 170 createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1))); 171 assertProgramTime( 172 tomorrowDayOfWeek, 173 hourMinuteToSec(0, 0), 174 hourMinuteToSec(1, 0), 175 programTimeTomorrow0000to0100); 176 177 // Tomorrow 23:30 - 24:30. 178 ProgramTime programTimeTomorrow2330to2430 = 179 ProgramTime.createFromProgram( 180 createDummyProgram(tomorrowAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1))); 181 assertProgramTime( 182 tomorrowDayOfWeek, 183 hourMinuteToSec(23, 30), 184 hourMinuteToSec(24, 30), 185 programTimeTomorrow2330to2430); 186 187 // Today 18:00 - Tomorrow 12:00. 188 ProgramTime programTimeToday1800to3600 = 189 ProgramTime.createFromProgram( 190 createDummyProgram(todayAtHourMin(18, 0), TimeUnit.HOURS.toMillis(18))); 191 // Maximum duration of ProgramTime is 12 hours. 192 // So, this program looks like it ends at Tomorrow 06:00 (30:00). 193 assertProgramTime( 194 todayDayOfWeek, 195 hourMinuteToSec(18, 0), 196 hourMinuteToSec(30, 0), 197 programTimeToday1800to3600); 198 } 199 200 @Test testCalculateOverlappedIntervalScore()201 public void testCalculateOverlappedIntervalScore() { 202 // Today 21:00 - 24:00. 203 ProgramTime programTimeToday2100to2400 = 204 ProgramTime.createFromProgram( 205 createDummyProgram(todayAtHourMin(21, 0), TimeUnit.HOURS.toMillis(3))); 206 // Today 22:00 - 01:00. 207 ProgramTime programTimeToday2200to0100 = 208 ProgramTime.createFromProgram( 209 createDummyProgram(todayAtHourMin(22, 0), TimeUnit.HOURS.toMillis(3))); 210 // Tomorrow 00:00 - 03:00. 211 ProgramTime programTimeTomorrow0000to0300 = 212 ProgramTime.createFromProgram( 213 createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(3))); 214 // Tomorrow 20:00 - Tomorrow 23:00. 215 ProgramTime programTimeTomorrow2000to2300 = 216 ProgramTime.createFromProgram( 217 createDummyProgram(tomorrowAtHourMin(20, 0), TimeUnit.HOURS.toMillis(3))); 218 219 // Check intersection time and commutative law in all cases. 220 int oneHourInSec = hourMinuteToSec(1, 0); 221 assertOverlappedIntervalScore( 222 2 * oneHourInSec, true, programTimeToday2100to2400, programTimeToday2200to0100); 223 assertOverlappedIntervalScore( 224 0, false, programTimeToday2100to2400, programTimeTomorrow0000to0300); 225 assertOverlappedIntervalScore( 226 2 * oneHourInSec, false, programTimeToday2100to2400, programTimeTomorrow2000to2300); 227 assertOverlappedIntervalScore( 228 oneHourInSec, true, programTimeToday2200to0100, programTimeTomorrow0000to0300); 229 assertOverlappedIntervalScore( 230 oneHourInSec, false, programTimeToday2200to0100, programTimeTomorrow2000to2300); 231 assertOverlappedIntervalScore( 232 0, false, programTimeTomorrow0000to0300, programTimeTomorrow2000to2300); 233 } 234 235 @Test testGetTimeOfDayInSec()236 public void testGetTimeOfDayInSec() { 237 // Time was set as 00:00:00. So, getTimeOfDay must returns 0 (= 0 * 60 * 60 + 0 * 60 + 0). 238 assertEquals( 239 "TimeOfDayInSec", 240 hourMinuteToSec(0, 0), 241 RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMin(0, 0))); 242 243 // Time was set as 23:59:59. So, getTimeOfDay must returns 23 * 60 + 60 + 59 * 60 + 59. 244 assertEquals( 245 "TimeOfDayInSec", 246 hourMinuteSecondToSec(23, 59, 59), 247 RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMinSec(23, 59, 59))); 248 } 249 assertMaximumMatchedWordSequenceLength( int expectedLength, String text1, String text2)250 private void assertMaximumMatchedWordSequenceLength( 251 int expectedLength, String text1, String text2) { 252 List<String> wordList1 = RoutineWatchEvaluator.splitTextToWords(text1); 253 List<String> wordList2 = RoutineWatchEvaluator.splitTextToWords(text2); 254 assertEquals( 255 "MaximumMatchedWordSequenceLength", 256 expectedLength, 257 RoutineWatchEvaluator.calculateMaximumMatchedWordSequenceLength( 258 wordList1, wordList2)); 259 assertEquals( 260 "MaximumMatchedWordSequenceLength", 261 expectedLength, 262 RoutineWatchEvaluator.calculateMaximumMatchedWordSequenceLength( 263 wordList2, wordList1)); 264 } 265 assertProgramTime( int expectedWeekDay, int expectedStartTimeOfDayInSec, int expectedEndTimeOfDayInSec, ProgramTime actualProgramTime)266 private void assertProgramTime( 267 int expectedWeekDay, 268 int expectedStartTimeOfDayInSec, 269 int expectedEndTimeOfDayInSec, 270 ProgramTime actualProgramTime) { 271 assertEquals("Weekday", expectedWeekDay, actualProgramTime.weekDay); 272 assertEquals( 273 "StartTimeOfDayInSec", 274 expectedStartTimeOfDayInSec, 275 actualProgramTime.startTimeOfDayInSec); 276 assertEquals( 277 "EndTimeOfDayInSec", 278 expectedEndTimeOfDayInSec, 279 actualProgramTime.endTimeOfDayInSec); 280 } 281 assertOverlappedIntervalScore( int expectedSeconds, boolean overlappedOnSameDay, ProgramTime t1, ProgramTime t2)282 private void assertOverlappedIntervalScore( 283 int expectedSeconds, boolean overlappedOnSameDay, ProgramTime t1, ProgramTime t2) { 284 double score = expectedSeconds; 285 if (!overlappedOnSameDay) { 286 score *= RoutineWatchEvaluator.MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK; 287 } 288 // Two tests for testing commutative law. 289 assertEqualScores( 290 "OverlappedIntervalScore", 291 score, 292 RoutineWatchEvaluator.calculateOverlappedIntervalScore(t1, t2)); 293 assertEqualScores( 294 "OverlappedIntervalScore", 295 score, 296 RoutineWatchEvaluator.calculateOverlappedIntervalScore(t2, t1)); 297 } 298 hourMinuteToSec(int hour, int minute)299 private int hourMinuteToSec(int hour, int minute) { 300 return hourMinuteSecondToSec(hour, minute, 0); 301 } 302 hourMinuteSecondToSec(int hour, int minute, int second)303 private int hourMinuteSecondToSec(int hour, int minute, int second) { 304 return hour * 60 * 60 + minute * 60 + second; 305 } 306 todayAtHourMin(int hour, int minute)307 private Calendar todayAtHourMin(int hour, int minute) { 308 return todayAtHourMinSec(hour, minute, 0); 309 } 310 todayAtHourMinSec(int hour, int minute, int second)311 private Calendar todayAtHourMinSec(int hour, int minute, int second) { 312 Calendar time = Calendar.getInstance(); 313 time.set(Calendar.HOUR_OF_DAY, hour); 314 time.set(Calendar.MINUTE, minute); 315 time.set(Calendar.SECOND, second); 316 return time; 317 } 318 tomorrowAtHourMin(int hour, int minute)319 private Calendar tomorrowAtHourMin(int hour, int minute) { 320 Calendar time = todayAtHourMin(hour, minute); 321 time.add(Calendar.DATE, 1); 322 return time; 323 } 324 createDummyProgram(Calendar startTime, long programDurationMs)325 private Program createDummyProgram(Calendar startTime, long programDurationMs) { 326 long startTimeMs = startTime.getTimeInMillis(); 327 328 return new ProgramImpl.Builder() 329 .setStartTimeUtcMillis(startTimeMs) 330 .setEndTimeUtcMillis(startTimeMs + programDurationMs) 331 .build(); 332 } 333 } 334