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
17package com.android.tv.recommendation;
18
19import static org.junit.Assert.assertEquals;
20
21import android.support.test.filters.SmallTest;
22import android.test.MoreAsserts;
23
24import com.android.tv.data.Program;
25import com.android.tv.recommendation.RoutineWatchEvaluator.ProgramTime;
26
27import org.junit.Test;
28
29import java.util.Arrays;
30import java.util.Calendar;
31import java.util.List;
32import java.util.TreeSet;
33import java.util.concurrent.TimeUnit;
34
35@SmallTest
36public class RoutineWatchEvaluatorTest extends EvaluatorTestCase<RoutineWatchEvaluator> {
37    private static class ScoredItem implements Comparable<ScoredItem> {
38        private final String mBase;
39        private final String mText;
40        private final double mScore;
41
42        private ScoredItem(String base, String text) {
43            this.mBase = base;
44            this.mText = text;
45            this.mScore = RoutineWatchEvaluator.calculateTitleMatchScore(base, text);
46        }
47
48        @Override
49        public int compareTo(ScoredItem scoredItem) {
50            return Double.compare(mScore, scoredItem.mScore);
51        }
52
53        @Override
54        public String toString() {
55            return mBase + " scored with " + mText + " is " + mScore;
56        }
57    }
58
59    private static ScoredItem score(String t1, String t2) {
60        return new ScoredItem(t1, t2);
61    }
62
63    @Override
64    public RoutineWatchEvaluator createEvaluator() {
65        return new RoutineWatchEvaluator();
66    }
67
68    @Test
69    public void testSplitTextToWords() {
70        assertSplitTextToWords("");
71        assertSplitTextToWords("Google", "Google");
72        assertSplitTextToWords("The Big Bang Theory", "The", "Big", "Bang", "Theory");
73        assertSplitTextToWords("Hello, world!", "Hello", "world");
74        assertSplitTextToWords("Adam's Rib", "Adam's", "Rib");
75        assertSplitTextToWords("G.I. Joe", "G.I", "Joe");
76        assertSplitTextToWords("A.I.", "A.I");
77    }
78
79    @Test
80    public void testCalculateMaximumMatchedWordSequenceLength() {
81        assertMaximumMatchedWordSequenceLength(0, "", "Google");
82        assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Big Bang");
83        assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Theory Of Big Bang");
84        assertMaximumMatchedWordSequenceLength(4, "The Big Bang Theory", "The Big Bang Theory");
85        assertMaximumMatchedWordSequenceLength(1, "Modern Family", "Family Guy");
86        assertMaximumMatchedWordSequenceLength(1, "The Simpsons", "The Walking Dead");
87        assertMaximumMatchedWordSequenceLength(3, "Game Of Thrones 1", "Game Of Thrones 6");
88        assertMaximumMatchedWordSequenceLength(0, "Dexter", "Friends");
89    }
90
91    @Test
92    public void testCalculateTitleMatchScore_empty() {
93        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("", ""));
94        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", ""));
95        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("", "foo"));
96    }
97
98    @Test
99    public void testCalculateTitleMatchScore_spaces() {
100        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(" ", " "));
101        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", " "));
102        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(" ", "foo"));
103    }
104
105
106    @Test
107    public void testCalculateTitleMatchScore_null() {
108        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(null, null));
109        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", null));
110        assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(null, "foo"));
111    }
112
113    @Test
114    public void testCalculateTitleMatchScore_longerMatchIsBetter() {
115        String base = "foo bar baz";
116        assertInOrder(
117                score(base, ""),
118                score(base, "bar"),
119                score(base, "foo bar"),
120                score(base, "foo bar baz"));
121    }
122
123    @Test
124    public void testProgramTime_createFromProgram() {
125        Calendar time = Calendar.getInstance();
126        int todayDayOfWeek = time.get(Calendar.DAY_OF_WEEK);
127        // Value of DayOfWeek is between 1 and 7 (inclusive).
128        int tomorrowDayOfWeek = (todayDayOfWeek % 7) + 1;
129
130        // Today 00:00 - 01:00.
131        ProgramTime programTimeToday0000_0100 = ProgramTime.createFromProgram(
132                createDummyProgram(todayAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1)));
133        assertProgramTime(todayDayOfWeek, hourMinuteToSec(0, 0), hourMinuteToSec(1, 0),
134                programTimeToday0000_0100);
135
136        // Today 23:30 - 24:30.
137        ProgramTime programTimeToday2330_2430 = ProgramTime.createFromProgram(
138                createDummyProgram(todayAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1)));
139        assertProgramTime(todayDayOfWeek, hourMinuteToSec(23, 30), hourMinuteToSec(24, 30),
140                programTimeToday2330_2430);
141
142        // Tomorrow 00:00 - 01:00.
143        ProgramTime programTimeTomorrow0000_0100 = ProgramTime.createFromProgram(
144                createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1)));
145        assertProgramTime(tomorrowDayOfWeek, hourMinuteToSec(0, 0), hourMinuteToSec(1, 0),
146                programTimeTomorrow0000_0100);
147
148        // Tomorrow 23:30 - 24:30.
149        ProgramTime programTimeTomorrow2330_2430 = ProgramTime.createFromProgram(
150                createDummyProgram(tomorrowAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1)));
151        assertProgramTime(tomorrowDayOfWeek, hourMinuteToSec(23, 30), hourMinuteToSec(24, 30),
152                programTimeTomorrow2330_2430);
153
154        // Today 18:00 - Tomorrow 12:00.
155        ProgramTime programTimeToday1800_3600 = ProgramTime.createFromProgram(
156                createDummyProgram(todayAtHourMin(18, 0), TimeUnit.HOURS.toMillis(18)));
157        // Maximum duration of ProgramTime is 12 hours.
158        // So, this program looks like it ends at Tomorrow 06:00 (30:00).
159        assertProgramTime(todayDayOfWeek, hourMinuteToSec(18, 0), hourMinuteToSec(30, 0),
160                programTimeToday1800_3600);
161    }
162
163    @Test
164    public void testCalculateOverlappedIntervalScore() {
165        // Today 21:00 - 24:00.
166        ProgramTime programTimeToday2100_2400 = ProgramTime.createFromProgram(
167                createDummyProgram(todayAtHourMin(21, 0), TimeUnit.HOURS.toMillis(3)));
168        // Today 22:00 - 01:00.
169        ProgramTime programTimeToday2200_0100 = ProgramTime.createFromProgram(
170                createDummyProgram(todayAtHourMin(22, 0), TimeUnit.HOURS.toMillis(3)));
171        // Tomorrow 00:00 - 03:00.
172        ProgramTime programTimeTomorrow0000_0300 = ProgramTime.createFromProgram(
173                createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(3)));
174        // Tomorrow 20:00 - Tomorrow 23:00.
175        ProgramTime programTimeTomorrow2000_2300 = ProgramTime.createFromProgram(
176                createDummyProgram(tomorrowAtHourMin(20, 0), TimeUnit.HOURS.toMillis(3)));
177
178        // Check intersection time and commutative law in all cases.
179        int oneHourInSec = hourMinuteToSec(1, 0);
180        assertOverlappedIntervalScore(2 * oneHourInSec, true, programTimeToday2100_2400,
181                programTimeToday2200_0100);
182        assertOverlappedIntervalScore(0, false, programTimeToday2100_2400,
183                programTimeTomorrow0000_0300);
184        assertOverlappedIntervalScore(2 * oneHourInSec, false, programTimeToday2100_2400,
185                programTimeTomorrow2000_2300);
186        assertOverlappedIntervalScore(oneHourInSec, true, programTimeToday2200_0100,
187                programTimeTomorrow0000_0300);
188        assertOverlappedIntervalScore(oneHourInSec, false, programTimeToday2200_0100,
189                programTimeTomorrow2000_2300);
190        assertOverlappedIntervalScore(0, false, programTimeTomorrow0000_0300,
191                programTimeTomorrow2000_2300);
192    }
193
194    @Test
195    public void testGetTimeOfDayInSec() {
196        // Time was set as 00:00:00. So, getTimeOfDay must returns 0 (= 0 * 60 * 60 + 0 * 60 + 0).
197        assertEquals("TimeOfDayInSec", hourMinuteToSec(0, 0),
198                RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMin(0, 0)));
199
200        // Time was set as 23:59:59. So, getTimeOfDay must returns 23 * 60 + 60 + 59 * 60 + 59.
201        assertEquals("TimeOfDayInSec", hourMinuteSecondToSec(23, 59, 59),
202                RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMinSec(23, 59, 59)));
203    }
204
205    private void assertSplitTextToWords(String text, String... words) {
206        List<String> wordList = RoutineWatchEvaluator.splitTextToWords(text);
207        MoreAsserts.assertContentsInOrder(wordList, words);
208    }
209
210    private void assertMaximumMatchedWordSequenceLength(int expectedLength, String text1,
211            String text2) {
212        List<String> wordList1 = RoutineWatchEvaluator.splitTextToWords(text1);
213        List<String> wordList2 = RoutineWatchEvaluator.splitTextToWords(text2);
214        assertEquals("MaximumMatchedWordSequenceLength", expectedLength,
215                RoutineWatchEvaluator.calculateMaximumMatchedWordSequenceLength(
216                        wordList1, wordList2));
217        assertEquals("MaximumMatchedWordSequenceLength", expectedLength,
218                RoutineWatchEvaluator.calculateMaximumMatchedWordSequenceLength(
219                        wordList2, wordList1));
220    }
221
222    private void assertProgramTime(int expectedWeekDay, int expectedStartTimeOfDayInSec,
223            int expectedEndTimeOfDayInSec, ProgramTime actualProgramTime) {
224        assertEquals("Weekday", expectedWeekDay, actualProgramTime.weekDay);
225        assertEquals("StartTimeOfDayInSec", expectedStartTimeOfDayInSec,
226                actualProgramTime.startTimeOfDayInSec);
227        assertEquals("EndTimeOfDayInSec", expectedEndTimeOfDayInSec,
228                actualProgramTime.endTimeOfDayInSec);
229    }
230
231    private void assertOverlappedIntervalScore(int expectedSeconds, boolean overlappedOnSameDay,
232            ProgramTime t1, ProgramTime t2) {
233        double score = expectedSeconds;
234        if (!overlappedOnSameDay) {
235            score *= RoutineWatchEvaluator.MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK;
236        }
237        // Two tests for testing commutative law.
238        assertEqualScores("OverlappedIntervalScore", score,
239                RoutineWatchEvaluator.calculateOverlappedIntervalScore(t1, t2));
240        assertEqualScores("OverlappedIntervalScore", score,
241                RoutineWatchEvaluator.calculateOverlappedIntervalScore(t2, t1));
242    }
243
244    private int hourMinuteToSec(int hour, int minute) {
245        return hourMinuteSecondToSec(hour, minute, 0);
246    }
247
248    private int hourMinuteSecondToSec(int hour, int minute, int second) {
249        return hour * 60 * 60 + minute * 60 + second;
250    }
251
252    private Calendar todayAtHourMin(int hour, int minute) {
253        return todayAtHourMinSec(hour, minute, 0);
254    }
255
256    private Calendar todayAtHourMinSec(int hour, int minute, int second) {
257        Calendar time = Calendar.getInstance();
258        time.set(Calendar.HOUR_OF_DAY, hour);
259        time.set(Calendar.MINUTE, minute);
260        time.set(Calendar.SECOND, second);
261        return time;
262    }
263
264    private Calendar tomorrowAtHourMin(int hour, int minute) {
265        Calendar time = todayAtHourMin(hour, minute);
266        time.add(Calendar.DATE, 1);
267        return time;
268    }
269
270    private Program createDummyProgram(Calendar startTime, long programDurationMs) {
271        long startTimeMs = startTime.getTimeInMillis();
272
273        return new Program.Builder().setStartTimeUtcMillis(startTimeMs)
274                .setEndTimeUtcMillis(startTimeMs + programDurationMs).build();
275    }
276
277    private static <T> void assertInOrder(T... items) {
278        TreeSet<T> copy = new TreeSet<>(Arrays.asList(items));
279        MoreAsserts.assertContentsInOrder(copy, items);
280    }
281}
282