def test_create_schedules_throws_when_no_schedules_are_possible(self): """ Tests that create_schedules throws an appropriate error message when all sections for the chosen courses overlap, meaning no schedules are possible. """ # Arrange courses = ( CourseFilter('CSCE', '221', include_full=True), CourseFilter('CSCE', '310', section_nums=['501']), ) term = '201931' unavailable_times = [] meetings = [ Meeting(id=10, meeting_days=[True] * 7, start_time=time(0, 0), end_time=time(23, 59), meeting_type='LEC', section=self.sections[0]), Meeting(id=80, meeting_days=[True] * 7, start_time=time(0, 0), end_time=time(23, 59), meeting_type='LEC', section=self.sections[7]), ] Meeting.objects.bulk_create(meetings) expected_error = _NO_SCHEDULES_POSSIBLE # Act + Assert with self.assertRaisesMessage(NoSchedulesError, expected_error): create_schedules(courses, term, unavailable_times)
def test_create_schedules_uses_unavailable_times(self): """ Tests that create_schedule filters out the provided unavailable_times. """ # There are 4 possible schedules to generate, 1 is valid given the # unavailable times # Arrange courses = ( CourseFilter("CSCE", "310"), CourseFilter("CSCE", "121", honors=BasicFilter.NO_PREFERENCE, web=BasicFilter.NO_PREFERENCE) ) term = "201931" include_full = True unavailable_times = [UnavailableTime(time(9, 1), time(9, 2), 4)] meetings = [ # Meetings for CSCE 310-501 Meeting(id=10, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[0]), Meeting(id=11, meeting_days=[True] * 7, start_time=time(9), end_time=time(9, 50), meeting_type='LEC', section=self.sections[0]), # Meetings for CSCE 310-502 Meeting(id=20, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[1]), Meeting(id=21, meeting_days=[True] * 7, start_time=time(8), end_time=time(8, 50), meeting_type='LAB', section=self.sections[1]), # Meetings for CSCE 121-501 Meeting(id=40, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[3]), Meeting(id=41, meeting_days=[True] * 7, start_time=time(9, 10), end_time=time(10), meeting_type='LAB', section=self.sections[3]), # Meetings for CSCE 121-502 Meeting(id=50, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[4]), Meeting(id=51, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[4]), ] Meeting.objects.bulk_create(meetings) expected_schedules = set(((2, 5),)) # Act schedules = set(create_schedules(courses, term, unavailable_times, include_full, num_schedules=10)) # Act print('calculator') print(schedules) print(expected_schedules) self.assertEqual(schedules, expected_schedules)
def test__get_meetings_gets_all_meetings(self): """ Tests that _get_meetings gets all sections/meetings for the specified term and groups them correctly """ # Arrange course = CourseFilter("CSCE", "310") term = "201931" include_full = True unavailable_times = [] meetings = [ # Meetings for CSCE 310-501 Meeting(id=10, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[0]), Meeting(id=11, meeting_days=[True] * 7, start_time=time(9), end_time=time(9, 50), meeting_type='LEC', section=self.sections[0]), # Meetings for CSCE 310-502 Meeting(id=20, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[1]), Meeting(id=21, meeting_days=[True] * 7, start_time=time(8), end_time=time(8, 50), meeting_type='LAB', section=self.sections[1]), # Meetings for CSCE 310-503 Meeting(id=30, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[2]), Meeting(id=31, meeting_days=[True] * 7, start_time=time(8), end_time=time(8, 50), meeting_type='LAB', section=self.sections[2]), ] Meeting.objects.bulk_create(meetings) valid_sections = set((1, 2)) meetings_for_sections = {1: meetings[0:2], 2: meetings[2:4]} # Act meetings = _get_meetings(course, term, include_full, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)
def test__get_meetings_filters_full(self): """ Tests that _get_meetings filters sections with no available seats if the include_full attribute of the CourseFilter is False """ # Arrange course = CourseFilter("CSCE", "121") term = "201931" include_full = False unavailable_times = [] meetings = [ # Meetings for CSCE 121-501 Meeting(id=40, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[3]), Meeting(id=41, meeting_days=[True] * 7, start_time=time(9, 10), end_time=time(10), meeting_type='LAB', section=self.sections[3]), # Meetings for CSCE 121-502 Meeting(id=50, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[4]), Meeting(id=51, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[4]), ] Meeting.objects.bulk_create(meetings) # Section 502 should be filtered because it has no available seats valid_sections = set((4,)) meetings_for_sections = {4: meetings[0:2]} # Act meetings = _get_meetings(course, term, include_full, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)
def test__get_meetings_filters_non_web(self): """ Tests that _get_meetings filters non-web sections if the web attribute of the CourseFilter is 'only' """ # Arrange course = CourseFilter("CSCE", "121", web=BasicFilter.ONLY) term = "201931" include_full = True unavailable_times = [] meetings = [ # Meetings for CSCE 121-501 Meeting(id=40, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[3]), Meeting(id=41, meeting_days=[True] * 7, start_time=time(9, 10), end_time=time(10), meeting_type='LAB', section=self.sections[3]), # Meetings for CSCE 121-502 Meeting(id=50, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[4]), Meeting(id=51, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[4]), ] Meeting.objects.bulk_create(meetings) # Section 501 should be filtered because it isn't a web section valid_sections = set((5,)) meetings_for_sections = {5: meetings[2:]} # Act meetings = _get_meetings(course, term, include_full, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)
def test__get_meetings_filters_honors(self): """ Tests that _get_meetings filters honors sections if the honors attribute of the CourseFilter is 'exclude' """ # Arrange course = CourseFilter("CSCE", "121", honors=BasicFilter.EXCLUDE, web=BasicFilter.NO_PREFERENCE) term = "201931" include_full = True unavailable_times = [] meetings = [ # Meetings for CSCE 121-502 Meeting(id=50, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[4]), Meeting(id=51, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[4]), # Meetings for CSCE 121-201 Meeting(id=60, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[5]), Meeting(id=61, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[5]), ] Meeting.objects.bulk_create(meetings) # Section 201 should be filtered because it is an honors section valid_sections = set((5,)) meetings_for_sections = {5: meetings[0:2]} # Act meetings = _get_meetings(course, term, include_full, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)
def test__get_meetings_filters_section_nums(self): """ Tests that _get_meetings filters sections not in the CourseFilter's section_nums """ # Arrange course = CourseFilter("CSCE", "310", section_nums=[501]) term = "201931" include_full = True unavailable_times = [] meetings = [ # Meetings for CSCE 310-501 Meeting(id=10, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[0]), Meeting(id=11, meeting_days=[True] * 7, start_time=time(9), end_time=time(9, 50), meeting_type='LEC', section=self.sections[0]), # Meetings for CSCE 310-502 Meeting(id=20, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[1]), Meeting(id=21, meeting_days=[True] * 7, start_time=time(8), end_time=time(8, 50), meeting_type='LAB', section=self.sections[1]), ] Meeting.objects.bulk_create(meetings) # Section 502 should be filtered because it isn't in section_nums valid_sections = set((1,)) meetings_for_sections = {1: meetings[0:2]} # Act meetings = _get_meetings(course, term, include_full, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)
def test__get_meetings_handles_unavailability(self): """ Tests that _get_meetings filters sections with meetings conflicting with the given unavailable_times """ # Arrange course = CourseFilter("CSCE", "310") term = "201931" include_full = True unavailable_times = (UnavailableTime(time(8), time(8, 30), 4),) meetings = [ # Meetings for CSCE 310-501 Meeting(id=10, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[0]), Meeting(id=11, meeting_days=[True] * 7, start_time=time(9), end_time=time(9, 50), meeting_type='LEC', section=self.sections[0]), # Meetings for CSCE 310-502 Meeting(id=20, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[1]), Meeting(id=21, meeting_days=[True] * 7, start_time=time(8), end_time=time(8, 50), meeting_type='LAB', section=self.sections[1]), ] Meeting.objects.bulk_create(meetings) # Section 502 should be filtered because of the unavailable time valid_sections = set((1,)) meetings_for_sections = {1: meetings[0:2]} # Act meetings = _get_meetings(course, term, include_full, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)
def test_parse_course_filter_is_correct(self): """ Tests that _parse_counter_filter works on a typical input """ # Arrange course = { "subject": "CSCE", "courseNum": "121", "sections": ["500"], "honors": "exclude", "remote": "exclude", "asynchronous": "exclude", "includeFull": False, } expected = CourseFilter(subject="CSCE", course_num="121", section_nums=["500"], honors=BasicFilter.EXCLUDE, remote=BasicFilter.EXCLUDE, asynchronous=BasicFilter.EXCLUDE, include_full=False) # Act result = _parse_course_filter(course) # Assert self.assertEqual(result, expected)
def test_create_schedules_creates_all_valid_schedules(self): """ Tests that create_schedules makes all valid schedules and doesn't return any invalid ones """ # There are 4 possible schedules to generate, 2 are valid # Arrange courses = ( CourseFilter("CSCE", "310"), CourseFilter("CSCE", "121", honors=BasicFilter.NO_PREFERENCE, web=BasicFilter.NO_PREFERENCE) ) term = "201931" include_full = True unavailable_times = [] meetings = [ # Meetings for CSCE 310-501 Meeting(id=10, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[0]), Meeting(id=11, meeting_days=[True] * 7, start_time=time(9), end_time=time(9, 50), meeting_type='LEC', section=self.sections[0]), # Meetings for CSCE 310-502 Meeting(id=20, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[1]), Meeting(id=21, meeting_days=[True] * 7, start_time=time(8), end_time=time(8, 50), meeting_type='LAB', section=self.sections[1]), # Meetings for CSCE 121-501 Meeting(id=40, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[3]), Meeting(id=41, meeting_days=[True] * 7, start_time=time(9, 10), end_time=time(10), meeting_type='LAB', section=self.sections[3]), # Meetings for CSCE 121-502 Meeting(id=50, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[4]), Meeting(id=51, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[4]), ] Meeting.objects.bulk_create(meetings) expected_schedules = set(((1, 5), (2, 5))) # Act schedules = set(create_schedules(courses, term, unavailable_times, include_full, num_schedules=10)) # Act self.assertEqual(schedules, expected_schedules)
def handle(self, *args, **options): # Setup data to create schedule from course_names = [("COMM", "203"), ("CSCE", "121"), ("CSCE", "411"), ("MATH", "151"), ("CSCE", "181")] courses = [ CourseFilter(subject, course_num) for subject, course_num in course_names ] courses[3] = CourseFilter("ACCT", "229", honors=True) term = "201911" unavailable_times = [ UnavailableTime(datetime.time(8), datetime.time(8, 50), 2), UnavailableTime(datetime.time(8), datetime.time(8, 50), 3) ] start = time() schedules = create_schedules(courses, term, unavailable_times) end = time() print(f"Took {end - start:.4f} seconds to create schedules") print(schedules)
def test__get_meetings_filters_asynchronous(self): """ Tests that _get_meetings filters asynchronous sections if the asynchrnous filter is 'exclude' """ # Arrange course = CourseFilter("CSCE", "121", asynchronous=BasicFilter.EXCLUDE, include_full=True) term = "201931" unavailable_times = [] meetings = [ # Meetings for CSCE 121-501 Meeting(id=40, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[3]), Meeting(id=41, meeting_days=[True] * 7, start_time=time(9, 10), end_time=time(10), meeting_type='LAB', section=self.sections[3]), # Meetings for CSCE 121-M99 Meeting(id=70, meeting_days=[False] * 7, start_time=None, end_time=None, meeting_type='LEC', section=self.sections[6]), Meeting(id=71, meeting_days=[False] * 7, start_time=None, end_time=None, meeting_type='LAB', section=self.sections[6]), ] Meeting.objects.bulk_create(meetings) # Section 501 should be filtered because it isn't a remote section valid_sections = set((4, )) meetings_for_sections = {4: meetings[:2]} # Act result_meetings = _get_meetings(course, term, unavailable_times) # Assert self.assert_meetings_match_expected(result_meetings, valid_sections, meetings_for_sections)
def test__get_meetings_filters_non_honors(self): """ Tests that _get_meetings filters out non-honors sections if the honors attribute of the CourseFilter is 'only' """ # Arrange course = CourseFilter("CSCE", "121", honors=BasicFilter.ONLY, include_full=True) term = "201931" unavailable_times = [] meetings = [ # Meetings for CSCE 121-502 Meeting(id=50, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[4]), Meeting(id=51, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[4]), # Meetings for CSCE 121-201 Meeting(id=60, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[5]), Meeting(id=61, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[5]), ] Meeting.objects.bulk_create(meetings) # Section 502 should be filtered because it isn't an honors section valid_sections = set((6, )) meetings_for_sections = {6: meetings[2:]} # Act meetings = _get_meetings(course, term, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)
def test__get_meetings_handles_no_sections(self): """ Tests that for a course with no sections, _get_meetings returns empty reults """ # Arrange course = CourseFilter("CSCE", "123") term = "201931" include_full = True unavailable_times = [] # Act meetings = _get_meetings(course, term, include_full, unavailable_times) # Assert self.assertFalse(meetings)
def test_create_schedules_throws_when_no_sections_have_seats(self): """ Tests that create_schedules throws an appropriate error message when no sections have available seats, and include_full is set to False. """ # Arrange subject = 'CSCE' course_num = '221' courses = (CourseFilter(subject, course_num, include_full=False), ) term = '201931' unavailable_times = [] expected_error = _NO_SECTIONS_WITH_SEATS.format(subject=subject, course_num=course_num) # Act + Assert with self.assertRaisesMessage(NoSchedulesError, expected_error): create_schedules(courses, term, unavailable_times)
def test_create_shedules_throws_when_no_sections_match_basic_filters(self): """ Tests that create_schedules throws an appropriate error message when no sections match the provided basic filters. """ # Arrange subject = 'CSCE' course_num = '2212' courses = (CourseFilter(subject, course_num, honors=BasicFilter.ONLY, include_full=True), ) term = '201931' unavailable_times = [] expected_error = _BASIC_FILTERS_TOO_RESTRICTIVE.format( subject=subject, course_num=course_num) # Act + Assert with self.assertRaisesMessage(NoSchedulesError, expected_error): create_schedules(courses, term, unavailable_times)
def _parse_course_filter(course) -> CourseFilter: """ Parses the given course to retrieve and convert it to a CourseFilter object to be used in create_schedules """ # Can assume subject & course_num will be given as strings subject = course["subject"] course_num = course["courseNum"] sections = course.get("sections", []) honors = BasicFilter(course.get("honors")) remote = BasicFilter(course.get("remote")) asynchronous = BasicFilter(course.get("asynchronous")) include_full = course.get("includeFull") return CourseFilter(subject=subject, course_num=course_num, section_nums=sections, honors=honors, remote=remote, asynchronous=asynchronous, include_full=include_full)
def _parse_course_filter(course) -> CourseFilter: """ Parses the given course to retrieve and convert it to a CourseFilter object to be used in create_schedules """ # Can assume subject & course_num will be given as strings subject = course["subject"] course_num = course["courseNum"] sections = course.get("sections", []) honors = BasicFilter(course.get("honors")) web = BasicFilter(course.get("web")) return CourseFilter(subject=subject, course_num=course_num, section_nums=sections, honors=honors, web=web)
def test_create_schedules_throws_when_no_sections_match_availability(self): """ Tests that create_schedules throws an appropriate error message when no sections match the selected availabilities. """ # Arrange subject = 'CSCE' course_num = '221' courses = (CourseFilter(subject, course_num, include_full=True), ) term = '201931' unavailable_times = [UnavailableTime(time(0, 0), time(23, 59), 0)] Meeting(id=80, meeting_days=[True, *[False] * 6], start_time=time(0, 0), end_time=time(23, 59), meeting_type='LEC', section=self.sections[7]).save() expected_error = _NO_SECTIONS_MATCH_AVAILABILITIES.format( subject=subject, course_num=course_num) # Act + Assert with self.assertRaisesMessage(NoSchedulesError, expected_error): create_schedules(courses, term, unavailable_times)
def test__get_meetings_manually_selected_sections_override_include_full( self): """ Tests that _get_meetings does not filter full sections selected in section select when include_full is false """ # Arrange # section chosen because it is full section_nums = ["502"] course = CourseFilter("CSCE", "121", section_nums=section_nums, include_full=False) term = "201931" unavailable_times = [] meetings = [ # Meetings for CSCE 121-502 Meeting(id=1, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[4]), Meeting(id=51, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[4]), ] Meeting.objects.bulk_create(meetings) valid_sections = set((5, )) meetings_for_sections = {5: meetings[0:]} # Act meetings = _get_meetings(course, term, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)
def test_parse_course_filter_is_correct(self): """ Tests that _parse_counter_filter works on a typical input """ # Arrange course = { "subject": "CSCE", "courseNum": "121", "sections": ["500"], "honors": "exclude", "web": "exclude", } expected = CourseFilter(subject="CSCE", course_num="121", section_nums=["500"], honors=BasicFilter.EXCLUDE, web=BasicFilter.EXCLUDE) # Act result = _parse_course_filter(course) # Assert self.assertEqual(result, expected)
def test__get_meetings_filters_remote(self): """ Tests that _get_meetings filters remote sections if the honors attribute of the CourseFilter is 'exclude' """ # Arrange course = CourseFilter("CSCE", "121", remote=BasicFilter.EXCLUDE, include_full=True) term = "201931" unavailable_times = [] meetings = [ # Meetings for CSCE 121-501 Meeting(id=40, meeting_days=[True] * 7, start_time=time(11, 30), end_time=time(12, 20), meeting_type='LEC', section=self.sections[3]), Meeting(id=41, meeting_days=[True] * 7, start_time=time(9, 10), end_time=time(10), meeting_type='LAB', section=self.sections[3]), # Meetings for CSCE 121-502 Meeting(id=50, meeting_days=[True] * 7, start_time=time(12, 30), end_time=time(1, 20), meeting_type='LEC', section=self.sections[4]), Meeting(id=51, meeting_days=[True] * 7, start_time=time(10), end_time=time(10, 50), meeting_type='LAB', section=self.sections[4]), # Meetings for CSCE 121-M99 Meeting(id=70, meeting_days=[False] * 7, start_time=None, end_time=None, meeting_type='LEC', section=self.sections[6]), Meeting(id=71, meeting_days=[False] * 7, start_time=None, end_time=None, meeting_type='LAB', section=self.sections[6]), ] Meeting.objects.bulk_create(meetings) # Section M99 should be filtered because it's a remote section valid_sections = set((4, 5)) meetings_for_sections = {4: meetings[0:2], 5: meetings[2:4]} # Act meetings = _get_meetings(course, term, unavailable_times) # Assert self.assert_meetings_match_expected(meetings, valid_sections, meetings_for_sections)